Repository navigation
Regions
What this page covers: the region system of the core - named areas with rules (flags), who they apply to (groups, bypass), what asks them (event sources and the on-demand listeners), what happens while a player is inside (sessions), the command verbs a plugin mounts, terrain, selection, threads and Hytale. It is written for whoever builds a protection plugin, an arena, a plot system or a spawn on top of the core.
The core is the engine: index, flag resolution, movement sessions, listeners, persistence and the
command verbs. It creates no region by itself and ships no command that creates one - /ecore region here|info|list only reads. A plugin creates regions through the API or by mounting the RegionCommandNode
in its own command; FinalProtection is the reference consumer. Nothing here touches WorldGuard: the old
bridge (WGPlatform, ProtectionAll) is a separate subject, see Integrations.
Everything lives in br.com.finalcraft.evernifecore.region (:common) unless a platform module is named.
On this page: Model - RegionManager - Flags - Flag catalog - Settings - Groups and resolution - Event sources - Sessions - Commands - Placeholders - Events - Terrain - Selection - Threads - Hytale - Internal pieces
A Region belongs to one plugin (its owner) and one world, and has a shape.
| Property | API | Notes |
|---|---|---|
| Id |
getId() -> RegionId (owner:name) |
both parts lowercased; owner [a-z0-9_.-]{1,64}, name [a-z0-9_-]{1,64} (flags and groups share the name rule) |
| Shape | getShape() |
CuboidShape.of(a, b) / ofUnboundedY(a, b), PolygonShape.of(pointsXZ, minY, maxY) / ofUnboundedY(points), WorldShape.INSTANCE (the whole world) |
| Priority | get/setPriority(int) |
higher is asked first; regions of the same priority form a band |
| Parent | get/setParent(Region) |
same owner and same world; the children carry its values (below) |
| Tags |
getTags(), addTag, removeTag
|
free labels; RegionManager.getByTag, RegionLookup.byTag
|
| Metadata | get/setMetadata(JsonNode) |
the plugin's own data, stored with the region |
| Display name, description |
get/setDisplayName, get/setDescription
|
free text with colors and spaces; the info verb shows both |
| Expiry | get/setExpiresAt(Instant) |
the sweeper removes the region when it passes, with RegionRemovedEvent.Cause.EXPIRED
|
| Persistence |
getPersistence() (storage.Persistence, shared with groups and quests) |
PERSISTENT (stored) or TRANSIENT (indexed and evented, never stored; gone when the owner disables or the world unloads) |
| Players inside |
getPlayersInside(), contains(...)
|
A RegionOptions (immutable: defaults() plus withPriority, withParent, withPersistence,
withExpiresAt, withTags, withMetadata, withPolicy) sets them at creation. A cuboid keeps the real Y of
its corners; ofUnboundedY covers the whole column, read against IPlatform.getWorldHeight(world).
The regions of ONE plugin, stored in that plugin's own ECStorage (collection regions, one entity per
region). RegionManager.of(pluginData, storage) is memoized per owner (ofIfPresent(pluginData) reads it);
one storage per owner - a second owner on the same storage, or another storage for the same owner without
close(), is an IllegalStateException.
RegionManager.of(pluginData) opens a manager without storage, for TRANSIENT regions only (a quest's "reach X",
a minigame arena): it is loaded at once, getStorage() is null, and creating a PERSISTENT region there is a
refusal in the CreationResult that names both ways out. When the owner already has a manager on a storage,
of(pluginData) returns that one; of(pluginData, storage) over a storage-less manager throws until it is
close()d.
RegionManager regions = RegionManager.of(pd, storage);
regions.setForeignAccess(true); //never stored: say it on every enable
RetryPolicy policy = RetryPolicy.attempts(5).every(Duration.ofSeconds(2)).giveUpAfter(Duration.ofSeconds(30));
regions.load(policy)
.exceptionally(neverLoaded -> {
EverNifeCore.getPlatform().shutdown("MyPlugin could not load its regions");
return null;
})
.join();-
load(RetryPolicy)reads every region of the owner, of every world, off the calling thread, and completes on the first try that succeeds or fails once the policy gives up (the message names the tries, the time and the last cause, also itsgetCause()). Every failed try posts aRegionLoadFailedEventand leaves the owner with no regions.load()without a policy retries every 30 s forever, and its future answers only the first try.RetryPolicy(util.retry):attempts(n),.every(d)or.backoff(first, multiplier, max),.giveUpAfter(total),forever(d). -
whenLoaded()completes when the first load succeeds, after however many failed tries;isLoaded()is the lock-free question. A manager that stores nothing starts loaded. -
createis locked until the regions are loaded.create(name, world, shape[, options])before that throwsIllegalStateException: the index lacks the stored regions, so the name check and the overlap policy would run against half the truth. Create fromregions.whenLoaded().thenRun(...), or checkisLoaded(). Thedefineverb answers "still loading" instead. -
A refusal is a result, never an exception: a name in use, an invalid name, a parent of another owner or
world, a policy that refuses an overlap.
CreationResult.isCreated(),getRegion(),getOverlaps(),getRefusal()(text that already says what to do). -
Overlaps:
dryRun(world, shape)lists what the shape would share blocks with (Overlap, with anOverlapSource:OURS_SAME_OWNER,OURS_OTHER_OWNER,TRIGGER_VOLUMES).RegionPolicy.allowAll()is the default;RegionPolicy.refuseIfTouches(sources...)refuses. Between polygons and the world shape the test is by bounding box. Another system reports its own volumes throughOverlapProviders.register(owner, provider); a provider that throws is left out of that check with a warning. -
Change and remove:
redefine(region, shape),remove(region)(throws when it has children) orremove(region, RemoveStrategy.CASCADE | ORPHAN_CHILDREN). Reads:get(name),getAll(),getByWorld(world),getByTag(tag). -
Persistence: every change is written off the caller's thread, a tick later;
flush()writes the pending ones now. The defaultonECPluginShutdownPrecloses the manager (writing what is pending), unregisters the owner's flags and event sources and stops its usage watches - override it only callingsuper.
RegionLookup.get() is the global, lock-free read side of every owner's regions: at(...) (by point, block,
FLocation or player), in(world, shape), byId, byOwner, byTag, byWorld, all().
A flag is a subclass of Flag<T> (br.com.finalcraft.evernifecore.flag; the 61 core flags and CoreFlags in
flag.core) registered in FlagRegistry.get() - never a listener of its own. What a flag decides on - the action
contexts, Actor, EventSource, EventSourceUsage - lives in br.com.finalcraft.evernifecore.action, and what the
session flags change on a player (HeldPlayerState, IPlayerStateAccess) in player.state. Its key
(FlagKey, owner:name) names the plugin that registers it; registering under another owner is an
IllegalArgumentException. A region (or one of its groups) holds a value per flag:
arena.setFlag(CoreFlags.DENY_BLOCK_BREAK, Tristate.TRUE);
arena.setFlag(CoreFlags.GREETING, "§aWelcome to the arena");
arena.setFlag(CoreFlags.DENY_PVP, Tristate.FALSE, Duration.ofMinutes(5)); //temporary: the permanent value comes back after
arena.removeFlag(CoreFlags.GREETING);getFlag(flag) is the region's LOCAL value only - no group, no parent, no neighbour. setFlag with a flag
that is not registered throws; a stored value of a flag that is absent stays raw in getUnknownFlags() and
comes back live when the flag registers again.
What each flag declares: its FlagType (FlagTypes.TRISTATE, INTEGER, DOUBLE, TEXT, LOCATION
world|x|y|z, WEATHER, AMOUNT_EVERY 2 every 5s, EFFECT speed:2, TEXT_LIST (;-separated),
EFFECT_LIST (,-separated), AMOUNT_EVERY_LIST, BOOSTERS, effects(flag, holdableOnly) and gate(flag) (a logic
definition kept with the node it was written as, a FlagDefinition), listOf(type, separator), enumOf(Enum.class),
jackson(id, Pojo.class) typed as k=v k2="two words"); its default (getDefault(), null = the flag does nothing
where nobody sets it); its scope (getScope(): PLACE, a value about a place, or ACTOR, a value that may also follow
the player); how its layers join (getResolver(), among getAcceptedResolvers() - see
Groups and resolution); the channels it touches and how it meets the flags sharing one
(getChannels(), getInteractions()); the event sources it needs (getNeeds()); its
FlagCategory - PROTECTION (asked about an action), SESSION (an effect while the player is inside) or
DATA (only carries a value); whether a bypass lifts it (isBypassable()).
Tristate. Tristate.TRUE, FALSE, UNDEFINED. A flag's name says what TRUE does: deny-pvp = TRUE
denies PvP. Every DenyFlag defaults to FALSE and joins equally specific layers with prefer-among-equals TRUE:
two regions of one band that disagree deny.
A value of type TEXT is the rest of the line, stored literally - quotes included. /fp flag arena deny-message Protected area stores Protected area; typed with quotes, the player sees the quotes. Write
text values without quotes. This differs on purpose from a command flag (--x 'Two Words') and from
settings (--fs key="a b"), where quotes group words and disappear.
Refinements and the flag hierarchy. A flag may refine another of the same type (getParent(), fixed at
construction, registered first): deny-block-break-mobs refines deny-block-break. For an action both
applies to, the refinement is the flag asked - the most specific one that speaks about the action answers
for it, and its parent is not asked. Where no region defines the refinement it reads the value of its nearest
defined ancestor, and the decision stays the refinement's own. RegionLookup.resolve and explain climb the
same way: explain(CoreFlags.DENY_BLOCK_BREAK_MOBS, region, null) in a region that only sets
deny-block-break = TRUE answers TRUE, getFlag() names deny-block-break, and the layers are those of
deny-block-break. Defined, a refinement never climbs: deny-block-break-mobs = FALSE frees mobs where deny-block-break = TRUE still holds everyone else.
Region hierarchy. A region that does not define a flag reads its parent's (and its groups'), then the
grandparent's - a Resolution.Reason.INHERITED layer. A parent never competes beside one of its children that is a
candidate: the child carries its values.
public final class DenyBucketFlag extends DenyFlag {
public DenyBucketFlag() {
super(FlagKey.of(MyFlags.OWNER, "deny-bucket"), EventSource.INTERACT);
}
@Override
public boolean applies(ActionContext context) {
if (!(context instanceof BlockContext)) {
return false;
}
TypeKey held = ((BlockContext) context).getItemInHandKey();
return held != null && held.matches("minecraft:*bucket");
}
@Override
public String getDescription() { //header of regions/flags/deny-bucket.yml
return "TRUE: buckets cannot be used in the region.";
}
}
FlagRegistry.get().register(pd, new DenyBucketFlag()); //before load(): stored values link on register-
Protection: extend
DenyFlag(itsdecideis final) and narrowapplies;InteractDenyFlagadds theonly/exceptlists,ActorDenyFlagis a refinement by actor (new ActorDenyFlag(key, parent, description, "animal", "monster")). The inheritedonAppliedsends the region'sdeny-messageto a denied player. -
Session:
FlagCategory.SESSION,EventSource.MOVEin the needs, andonEnter/onLeave/onRevalidate;getRevalidation()says when the value is checked again for a player who did not cross a border (Revalidation.onBoundary()default,interval(d),everyLookup(),custom(...)). A flag that changes the player's state and gives it back on leaving extendsPlayerStateFlag<T>; one that acts on an interval implementsTickingFlag<T>and needsEventSource.TICK. -
Data:
FlagCategory.DATAwithEventSource.NONE, liketeleport-point. -
Scope and joining: a flag that may follow the player (a party's buff) overrides
getScope()toACTOR; a flag whose layers should add up, multiply or concatenate instead of the first one winning overridesgetResolver()andgetAcceptedResolvers()withFlagResolvers(firstDefined(),preferAmongEquals(winner),sumByInterval(),unionMaxLevel(),concat(),productCapped(...),max(),min()). A flag that changes the same thing as another (a slot of the player's state, a damage channel) names it ingetChannels(), and one of the two declaresFlagInteraction.with(otherKey, "what happens")ingetInteractions()- two undeclared flags on one channel fail to register, naming both:
@Override
public Set<String> getChannels() {
return Collections.singleton("slot:flight");
}
@Override
public Set<FlagInteraction> getInteractions() {
return Collections.singleton(FlagInteraction.with(CoreFlags.ALLOW_FLY.getKey(), "TRUE wins over allow-fly = FALSE"));
}Unregistering is automatic in the default onECPluginShutdownPre (FlagRegistry.get().unregisterAll(owner));
stored values go back to raw and nothing is deleted.
The core registers 61 flags (CoreFlags.all(), owner evernifecore, each a public static field of
CoreFlags). "deny" = a DenyFlag: Tristate, default FALSE, equal layers prefer TRUE, TRUE denies what it
applies to. Scope says whether a holder that follows the player may carry the flag (ACTOR) or only a place
(PLACE). Joins as is the strategy that joins the layers, then the others the settings may pick with resolve:.
Collides with names the flags it shares a channel with; the rule is under the tables. A value "with no default" does nothing where no region sets it. Every flag is lifted by the
bypass permissions except deny-pvp and deny-friendly-fire. The Hytale column says where Hytale differs; "boot warning" means the
flag has no effect there and the core logs one WARN line for it at boot.
| Key | Value | Scope | Joins as | Collides with | Source | What it does | Hytale |
|---|---|---|---|---|---|---|---|
deny-pvp |
deny | ACTOR |
prefer-among-equals (or first-defined) |
DAMAGE | cancels damage to a player or fake player when a player is behind the attacker; not bypassable | same, a projectile carries its shooter | |
deny-friendly-fire |
deny | ACTOR |
prefer-among-equals (or first-defined) |
deny-pvp |
DAMAGE | cancels a hit between two players who share a group of any provider (GroupLookup.shareGroup, a region's own groups aside); on a place it holds for everyone there, on a group wherever its members are; not bypassable
|
same |
invincible |
deny | ACTOR |
prefer-among-equals (or first-defined) |
damage-multiplier, damage-over-time
|
DAMAGE | cancels damage dealt to a player by an entity or a block explosion | cancels every damage to a player, environment causes included |
deny-damage-animals |
deny | PLACE |
prefer-among-equals (or first-defined) |
DAMAGE | animals cannot be hurt | an animal is an NPC whose attitude is not hostile | |
deny-damage-from-mobs |
deny | ACTOR |
prefer-among-equals (or first-defined) |
DAMAGE | a monster or animal with no player behind it cannot hurt a player | same | |
deny-block-break |
deny, trigger 1 s | PLACE |
prefer-among-equals (or first-defined) |
BREAK | breaking, a mob changing a block, burning, pistons, the blocks of an explosion | every hit of a break is asked; a block the world breaks by itself (EnvironmentBreakBlockEvent: fire, a harvest nobody did) is not cancellable and cannot be denied - it is only journaled and posted |
|
deny-block-break-mobs |
refines deny-block-break
|
PLACE |
prefer-among-equals (or first-defined) |
BREAK | the same, for animals and monsters as the actor (an enderman, a creeper's blast) | an explosion is the environment's, never a mob's | |
deny-block-break-environment |
refines deny-block-break
|
PLACE |
prefer-among-equals (or first-defined) |
BREAK | fire burning, a block or entity-less explosion | every explosion | |
deny-block-break-fake-players |
refines deny-block-break
|
PLACE |
prefer-among-equals (or first-defined) |
BREAK | fake players (FCBukkitUtil.isFakePlayer) |
never matches; the boot log says it "never matches on this platform: it refines evernifecore:deny-block-break for [fake-player], and this server has none" | |
deny-block-place |
deny | PLACE |
prefer-among-equals (or first-defined) |
PLACE | placing, a mob placing, lighting fire (not its spread), pistons | the block is the item in hand | |
deny-block-place-environment |
refines deny-block-place
|
PLACE |
prefer-among-equals (or first-defined) |
PLACE | fire lit by lava or lightning | not verified | |
deny-block-place-fake-players |
refines deny-block-place
|
PLACE |
prefer-among-equals (or first-defined) |
PLACE | fake players | never matches; the boot log says it "never matches on this platform: it refines evernifecore:deny-block-place for [fake-player], and this server has none" | |
deny-fire-spread |
deny | PLACE |
prefer-among-equals (or first-defined) |
FIRE_SPREAD | fire spreading (BlockIgniteEvent SPREAD, BlockSpreadEvent of fire) |
boot warning | |
deny-fluid-flow |
deny | PLACE |
prefer-among-equals (or first-defined) |
FLUID | a fluid flowing in from outside the region | boot warning | |
deny-explosion |
deny | PLACE |
prefer-among-equals (or first-defined) |
EXPLOSION | cancels the whole explosion | boot warning: the blocks are still kept one by one by deny-block-break
|
|
deny-explosion-mobs |
refines deny-explosion
|
PLACE |
prefer-among-equals (or first-defined) |
EXPLOSION | explosions of animals and monsters (a creeper) | boot warning | |
deny-interact |
deny, TypeFilterSettings
|
PLACE |
prefer-among-equals (or first-defined) |
INTERACT | using a block or an entity | use events and the Secondary/Ability1-4 interaction chains | |
deny-interact-blocks |
refines deny-interact
|
PLACE |
prefer-among-equals (or first-defined) |
INTERACT | using a block | in an interaction chain the block key is hytale:Unknown
|
|
deny-interact-entities |
refines deny-interact
|
PLACE |
prefer-among-equals (or first-defined) |
INTERACT | using an entity | same | |
deny-spawn |
deny, TypeFilterSettings
|
PLACE |
prefer-among-equals (or first-defined) |
SPAWN | creatures spawning (spawn eggs pass) | the NPC added with AddReason.SPAWN is removed before it ticks |
|
deny-spawn-monster |
refines deny-spawn
|
PLACE |
prefer-among-equals (or first-defined) |
SPAWN | monsters | a hostile NPC | |
deny-spawn-animal |
refines deny-spawn
|
PLACE |
prefer-among-equals (or first-defined) |
SPAWN | animals | a non-hostile NPC | |
deny-item-pickup |
deny, TypeFilterSettings
|
ACTOR |
prefer-among-equals (or first-defined) |
ITEM_PICKUP | picking items up (any living entity from 1.12, only players before) | only the interactive pickup has an event | |
deny-item-drop |
deny, TypeFilterSettings
|
ACTOR |
prefer-among-equals (or first-defined) |
ITEM_DROP | a player dropping items | same | |
deny-vehicle |
deny | PLACE |
prefer-among-equals (or first-defined) |
VEHICLE | placing, breaking or entering a vehicle | boot warning | |
deny-vehicle-place |
refines deny-vehicle
|
PLACE |
prefer-among-equals (or first-defined) |
VEHICLE | placing one (before 1.13: the use of the minecart or boat item) | boot warning | |
deny-vehicle-break |
refines deny-vehicle
|
PLACE |
prefer-among-equals (or first-defined) |
VEHICLE | breaking one | boot warning | |
deny-vehicle-enter |
refines deny-vehicle
|
PLACE |
prefer-among-equals (or first-defined) |
VEHICLE | entering one | boot warning | |
deny-entry |
deny, EntryDenySettings, trigger 250 ms |
PLACE |
prefer-among-equals (or first-defined) |
MOVE | entering the region: the step is reverted, a teleport cancelled, a vehicle sent back | a tick per player reverts with a Teleport
|
|
deny-exit |
deny, MoveDenySettings, trigger 250 ms |
PLACE |
prefer-among-equals (or first-defined) |
MOVE | leaving it | same | |
deny-teleport-in |
deny, MoveDenySettings
|
PLACE |
prefer-among-equals (or first-defined) |
MOVE | teleporting in | the teleport happens and the player is sent back | |
deny-teleport-out |
deny, MoveDenySettings
|
PLACE |
prefer-among-equals (or first-defined) |
MOVE | teleporting out | same | |
deny-entry-with-message |
denied=<tristate> message=<text>, no default, EntryDenySettings
|
PLACE |
prefer-among-equals (or first-defined) |
MOVE | denies the entry and sends that message; of equal layers the one that denies wins | same as deny-entry
|
|
entry-gate |
a Gate {require: [...], cost: [...]}, no default, EntryGateSettings
|
PLACE | first-defined |
MOVE | entering checks the conditions every time and charges the costs through the gate's audited pay as charge says; a player who fails is sent back and told why |
same as deny-entry
|
|
damage-multiplier |
a number, no default, DamageMultiplierSettings
|
ACTOR |
product-capped (or max, first-defined) |
invincible |
DAMAGE | scales the damage a player takes (layers multiply within floor/cap); an invincible player is denied first. Bukkit asks it for a hit by an entity or a block explosion |
every damage to a player, environment causes included, scaled in the filter group |
| Key | Value | Scope | Joins as | Collides with | Source | What it does | Hytale |
|---|---|---|---|---|---|---|---|
greeting |
text, no default | PLACE | first-defined |
MOVE | a message on entering | ||
farewell |
text, no default | PLACE | first-defined |
MOVE | a message on leaving | ||
deny-message |
text, no default (data) | PLACE | first-defined |
none | what every deny flag tells the denied player, and blocked-commands too |
||
teleport-point |
location, no default (data) | PLACE | first-defined |
none | where the tp verb sends |
||
spawn-point |
location, no default (data) | PLACE | first-defined |
RESPAWN | who dies inside respawns there, ahead of the bed | sends the player to its point once no teleport is left on him (the server's own respawn teleport gone, up to two seconds); a RespawnEvent with no death behind it, at once |
|
track-changes |
Tristate (data), TrackChangesSettings
|
PLACE |
prefer-among-equals (or first-defined) |
BREAK, PLACE, EXPLOSION | keeps the terrain journal of the region (see Terrain) | the boot log says it "acts in part on this platform: it needs [evernifecore:explosion], which this server never raises; the rest applies"; journals breaks, hits, places, explosion blocks and the blocks the world breaks by itself; no entities | |
time-lock |
time=DAY|NOON|NIGHT|MIDNIGHT relative=<bool>, no default, TimeLockSettings
|
ACTOR | first-defined |
effects-while-inside |
MOVE | the player's clock, ramped (smoothTransition, transition) |
a client time override |
weather-lock |
clear, rain or a weather id of the server |
ACTOR | first-defined |
effects-while-inside |
MOVE | the player's weather | boot warning (no capability) |
game-mode |
SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR
|
ACTOR | first-defined |
effects-while-inside |
MOVE | the game mode while inside (no SPECTATOR before 1.8) | only ADVENTURE and CREATIVE act |
allow-fly |
Tristate, default UNDEFINED, equal layers prefer FALSE | ACTOR |
prefer-among-equals (or first-defined) |
effects-while-inside, force-fly
|
MOVE | TRUE lets the player fly; FALSE grounds everyone but a spectator | same |
force-fly |
Tristate, default UNDEFINED, equal layers prefer TRUE | ACTOR |
prefer-among-equals (or first-defined) |
allow-fly, effects-while-inside
|
MOVE, FLIGHT_TOGGLE | puts the player flying and cancels the landing | the boot log says it "acts in part on this platform: it needs [evernifecore:flight-toggle], which this server never raises; the rest applies": entering still puts the player flying, landing is not cancelled |
potion-effects |
effect:level list (speed:2,jump), no default |
ACTOR |
union-max-level (or concat, first-defined) |
effects-while-inside |
MOVE, TICK | keeps the effects on, renewed by the region tick; on leaving only what it gave is taken | the level is ignored, the effect id carries it |
command-on-enter |
;-separated lines, player: / console: each, no default |
PLACE |
concat (or first-defined) |
MOVE | runs the commands on entering, placeholders resolved | ||
command-on-exit |
same | PLACE |
concat (or first-defined) |
MOVE | on leaving | ||
heal |
N every T list (2 every 1s, 5 every 10s), no default |
ACTOR |
sum-by-interval (or first-defined) |
damage-over-time |
MOVE, TICK | heals up to full | |
feed |
N every T list (2 every 1s, 5 every 10s), no default |
ACTOR |
sum-by-interval (or first-defined) |
hunger-over-time |
MOVE, TICK | feeds up to full | boot warning (no capability) |
damage-over-time |
N every T list, no default, DamageOverTimeSettings
|
ACTOR |
sum-by-interval (or first-defined) |
heal, invincible
|
MOVE, TICK | damage through armor; ignoreArmor: true lowers the health straight |
same (DamageSystems.executeDamage) |
hunger-over-time |
N every T list (2 every 1s, 5 every 10s), no default |
ACTOR |
sum-by-interval (or first-defined) |
feed |
MOVE, TICK | takes food | boot warning (no capability) |
effects-on-enter |
list of logic effects ([{type: message, text: hi}]), no default |
PLACE |
concat (or first-defined) |
MOVE | applies the effects on entering; an effect whose plugin is absent stays as written, skipped and shown (pending: <type>) in info
|
the effects Hytale supports | |
effects-on-exit |
same | PLACE |
concat (or first-defined) |
MOVE | on leaving | same | |
effects-while-inside |
holdable effects only (gamemode, fly, potion-effect, player-time, player-weather) |
ACTOR |
concat (or first-defined) |
allow-fly, force-fly, game-mode, potion-effects, time-lock, weather-lock
|
MOVE | holds them while the value applies and gives them back on leaving or quitting; any other effect is refused when read, naming effects-on-enter
|
the slots Hytale supports |
boosters |
list of on=<gain> factor=<n> or bonus=<n> (one of the two), no default (data) |
ACTOR |
concat (or first-defined) |
none | carries boosters for the booster engine to read | ||
max-members |
a whole number, default 0 = no cap (data, groups only) | ACTOR | first-defined |
none | a group takes nobody past it: add answers false, join/accept say "is full (n/n, max-members)"; a GroupKind default caps a group that sets nothing |
same | |
share-xp |
a fraction, default 0 (data, groups of a manager only), ShareSettings
|
ACTOR | first-defined |
XP_GAIN | every other online member in range (radius 50, sameWorld) gets a copy of amount * fraction of the experience a member picks up, as evernifecore:exp-points under the caller evernifecore:group/share-xp
|
logs once: no XP source | |
share-money |
a fraction, default 0 (data, groups of a manager only), ShareSettings
|
ACTOR | first-defined |
none (BalanceChangeEvent) |
the same for a positive change of money; the copies it gives (caller evernifecore:group/share-money) are never shared again |
same | |
glow-members |
Tristate, default UNDEFINED, equal layers prefer TRUE (data, groups of a manager only) | ACTOR |
prefer-among-equals (or first-defined) |
none | TRUE makes the online members glow for everyone, coloured by one scoreboard team per group (IGroupGlowProvider, Bukkit 1.9+); without a provider it logs once and /ecore group says unsupported on this platform
|
no effect (no provider) |
Declared interactions (the flag on the left declares it):
-
invinciblewithdamage-over-time: invincible = TRUE: no damage over time. -
deny-friendly-firewithdeny-pvp: deny-pvp = TRUE already denies every hit; deny-pvp = FALSE + deny-friendly-fire = TRUE: PvP only between different groups. -
invinciblewithdamage-multiplier: invincible = TRUE: nothing to multiply. -
allow-flywithforce-fly: force-fly = TRUE flies the player even where allow-fly = FALSE grounds them. -
healwithdamage-over-time: the two add up to one net change per shared interval. -
feedwithhunger-over-time: the two add up to one net change per shared interval. -
effects-while-insidewithgame-mode,allow-fly,force-fly,time-lock,weather-lockandpotion-effects: the first holder of a slot saves what the player had, the last gives it back.
The six flags over the logic module are classes of flag.core: EffectsFlag (onEnter(), onExit()),
EffectsWhileInsideFlag, EntryGateFlag, BoostersFlag (values BoosterEntry: on, factor, bonus) and
DamageMultiplierFlag. Their effect and gate values are FlagDefinitions - the parsed definition with the node it
was written as, which is what is stored, shown and compared; Effects.getPendingType(effect) names the type an
element waits for. damage-multiplier writes its factor on the hit (DamageContext.multiplyDamage(flag, factor),
read by getDamageMultiplier()) and the platform's damage listener applies it; entry-gate remembers what a
player paid in RegionPlayerSession.getPaidGates(); effects-while-inside gives its slots back through
HeldPlayerState.releaseAll(holder, player).
A plugin adds its own flags beside these: FinalProtection's blocked-commands (a ;-separated list, its own
event source finalprotection:command, Bukkit only - Hytale has no event before a command) is the example.
A flag with adjustable behaviour declares getSettingsType(), a subclass of FlagSettings (a Jackson POJO
with a no-arg constructor, @Comment on the fields). Every flag has the three base fields:
| Field | Default | Meaning |
|---|---|---|
triggerInterval |
0s (every effect runs) |
the least time between two effects of the flag (a deny message, a sound) on the same player; a flag raises the floor with getDefaultTriggerInterval() (deny-block-break 1 s, the movement flags 250 ms) |
revalidationInterval |
none | how often a session flag re-checks a player standing inside, overriding an interval revalidation |
bypassPermission |
none | a node of the server owner's choice that also lifts the flag, read from the settings in effect in each region where the flag is read |
resolve |
none (the flag's default strategy) | the id of the strategy that joins the layers, among those the flag accepts (the "Joins as" column); one outside the list fails the server's settings at register and a region's override when set, naming the accepted ones |
The core's settings types:
| Type | Used by | Fields |
|---|---|---|
DenySettings |
every DenyFlag
|
actors (empty = all) and exceptActors: a word (player, fake-player, animal, monster, npc, other, entity, block, environment) or a key/glob (minecraft:creeper, pixelmon:*) |
TypeFilterSettings |
deny-interact and its refinements, deny-spawn and its refinements, deny-item-pickup, deny-item-drop
|
only / except: block, item or creature keys and globs |
MoveDenySettings |
deny-exit, deny-teleport-in, deny-teleport-out
|
revert (last-valid default, trail:<n>, allow-after:<n>, fixed:x,y,z[,world], spawn, worldspawn), stuckAfter (12), stuckWindow (1 s), stuckFallback
|
EntryDenySettings |
deny-entry, deny-entry-with-message
|
the above plus expelFoundInside (false): a player found inside without crossing - logged in there, or the region appeared or changed around them - is sent out, respecting members and bypass |
PlayerStateSettings |
the player-state flags and potion-effects
|
restore: PERSISTENT (default) or MEMORY (see Sessions) |
TimeLockSettings |
time-lock |
restore plus smoothTransition (true) and transition (2 s) |
DamageOverTimeSettings |
damage-over-time |
ignoreArmor (false) |
EntryGateSettings |
entry-gate |
those of EntryDenySettings plus charge: ONCE_PER_SESSION (default; once per login while the server runs), EVERY_ENTRY, PER_WINDOW with window (daily at 00:00, weekly on monday at 00:00); the conditions are checked on every entry |
DamageMultiplierSettings |
damage-multiplier |
floor (0) and cap (10): the bounds of the product, read by product-capped
|
TrackChangesSettings |
track-changes |
persistence (MEMORY / DISK), blocks, nbt, entities (all true) |
Two levels. The server level is flag.getSettings(): the code defaults, or
<owner's data folder>/regions/flags/<flag>.yml (root Settings:) while settings files are in use -
Regions.generate-global-settings-configuration-files in EverNifeCore's config.yml, false by default, or the
flag's own getSettingsFilePolicy() (FOLLOW_SERVER, ALWAYS, NEVER). The files of the core's flags therefore
live in the core's folder (see Gotchas and Pitfalls). They load again when the owner
reloads, and every flag's when EverNifeCore reloads. The region level is an override per region:
EntryDenySettings mine = region.getEffectiveFlagSettings(CoreFlags.DENY_ENTRY); //the region's, else the server's
region.editFlagSettings(CoreFlags.DENY_ENTRY, (EntryDenySettings s) -> {
s.setExpelFoundInside(true);
s.setRevert(RevertStrategy.fixed(lobby));
});
region.removeFlagSettings(CoreFlags.DENY_ENTRY); //back to the server'seditFlagSettings starts from a COPY of the effective settings and stores the result; changing the object of
getSettings() changes every region without an override, and an object read from getFlagSettings is written
only by calling setFlagSettings again. Each change posts RegionChangedEvent with Type.FLAG_SETTINGS.
From a command: flag <region> <flag> <value> --fs k=v (or --fs {json}; --fs actors=monster,environment is the
same as a YAML list) sets settings with the value, and settings <region> <flag> field=value alone; - drops the
override. For a refinement, the settings come from the flag of the chain whose value decided: an inherited
only=[...] narrows the refinement too when the types fit.
A region's group is a Group of the group engine (Groups) of kind GroupKind.REGION_LOCAL: it belongs to that
region (getRegion(), id owner:<region>/<group>), is stored inside the region's record, and holds flag values of
its own that apply to its members there:
Group builders = arena.getGroup("builders"); //created when absent, REGION_LOCAL
builders.add(player.getUniqueId());
builders.add(guest.getUniqueId(), Duration.ofHours(1));
builders.setLeader(player.getUniqueId()); //optional; roles are free tags in a region's group
builders.setPermission("myplugin.arena.build");
builders.setPredicate(p -> teams.isInTeam(p.getUniqueId(), "red")); //never stored: set it on every enable
builders.setMembershipCheck(MembershipCheck.interval(Duration.ofSeconds(30))); //cache the permission/predicate answer
builders.setFlag(CoreFlags.DENY_BLOCK_BREAK, Tristate.FALSE); //members may breakMembers by UUID (a GroupMember, with an optional deadline), by permission or by predicate (contains(player));
getGroupIfPresent, removeGroup (dissolves it, DissolveCause.REGION_REMOVED, as removing the region does),
getGroups(). Every change posts a Group*Event whose getGroup().getRegion() names the region. Something a
predicate reads changed? RegionLookup.get().invalidate(uuid). A region's group is a list of the place, not a team:
GroupLookup.shareGroup and the team flags (share-*, glow-members) ignore it.
The groups of a GroupManager (a party, a clan) are ACTOR holders that follow their members anywhere: the core
registers them in ActorHolders, so a party's invincible holds in the open field too. A protection flag about the
victim - invincible, deny-damage-from-mobs, damage-multiplier - reads the layers of the player hit, not of the
attacker (Flag.getSubject(context)): a party's invincible shields its member from a zombie, and no one its members hit.
Holders and the envelope. A region and a group of a region are FlagHolders of scope PLACE. A plugin may
register holders that follow the player wherever they stand (a party, a guild) in ActorHolders.register(owner, provider); they are ACTOR holders and carry only ACTOR flags (FlagHolder.accepts; a value the flag refuses
is ignored and warned once). The plugin's teardown (IECPluginBootstrap.onECPluginShutdownPre,
ECPluginManager.removePluginData) drops the providers it registered, so no holder of a gone plugin joins a resolution. Every value has an envelope, a FlagValue:
arena.setFlag(CoreFlags.HEAL, FlagValue.permanent(Collections.singletonList(AmountEvery.parse("50 every 1s")))
.withMode(FlagMode.SET) //replaces every layer after it (ADD, the default, joins them)
.withPriority(10) //compared before the contextual order
.withLock(true)); //a marker: a locked layer comes before any prioritysince is stamped from ECClock.now(). Storage writes the envelope beside the value - mode only when set,
priority only when not 0, lock only when true, since always:
evernifecore:heal: { value: "0 every 1s", mode: set, lock: true, since: "2026-10-24T18:41:00Z" }A value stored before the envelope existed reads as add, priority 0, no lock and the oldest possible since.
How a value is resolved (RegionLookup.resolve(flag, world, x, y, z, player), resolve(flag, player),
tristate, isTrue):
-
Bypass -
evernifecore.region.bypass.*,evernifecore.region.bypass.<owner>.<name>, or the flag'sbypassPermissionin any region there: the flag reads its default (only for a bypassable flag). -
Collect the layers. For each region at the point (a parent of another candidate is skipped): every value of
the nearest of it and its ancestors that defines anything for the player - each group of that region that
contains the player and the region's own value. Then the holders of the player in
ActorHolders. -
Order them:
lockfirst, then the writtenpriority(highest first), then the contextual order - by the region's priority, a group before its region, the regions before the holders that follow the player - and the newestsinceamong what is left equal. -
Cut after the first layer in
mode: set: the layers after it readCUT_BY_SET. -
Join what is left with the flag's resolver (or the one
resolve:names in the settings in effect):first-defined(the first layer),prefer-among-equals(the first, unless an equally ranked layer holds the preferred value),sum-by-interval(2 every 1s+10 every 1s=12 every 1s; other intervals keep their own entry),union-max-level(speed:1+speed:2=speed:2),concat,product-capped(within the settings' bounds),max,min. - Nothing anywhere: the flag's default. For a refinement, the climb to its parent flag (above) happens here.
Two locked layers that disagree where regions overlap are a conflict: the most specific wins, the other reads
LOCK_OVERRULED, and it is reported when the owner loads, on setFlag and in region info.
resolve(flag, region, player) is what one region alone gives, ignoring its neighbours. explain(...) is the
same question with the work shown: a Resolution with getValue(), getSource() (the FlagHolder of the first
counted layer, null for a default or a bypass), getRegion() (its region, when there is one), getReason()
(GROUP_VALUE, LOCAL_VALUE, INHERITED, ACTOR_VALUE, BYPASS, DEFAULT), getFlag(), getResolver(),
getLayers() (every layer in order, each with its FlagValue, rank and Use: COUNTED, CUT_BY_SET,
NOT_COUNTED, LOCK_OVERRULED) and getCountedLayers(). The here verb prints every layer and what the resolver
did with it; info shows each value's envelope, the strategy, lock conflicts and declared interactions; the flag
and group flag verbs take --mode add|set, --priority <n> (-p) and --lock.
Deciding an action. RegionLookup.get().decide(context) takes an ActionContext (BlockContext,
DamageContext, EntityContext, SpawnContext, ExplosionContext, FluidContext, ItemContext,
VehicleContext, MoveContext) and answers Decision.DENY or PASS: the first protection flag that needs the
context's source, applies, and denies wins. A point outside every region passes without asking. Each flag that
took part runs its onApplied at the pace of its triggerInterval, then the region's FlagHandler. An event
asked in stages (an explosion as a whole, then each block) goes through one
try (RegionDispatch d = RegionLookup.get().dispatch()): the actors' native handles live until it closes.
Who acts is an Actor (ENTITY, BLOCK, ENVIRONMENT; getKey() a TypeKey like minecraft:creeper,
getEntityType() an ECEntityType - PLAYER, FAKE_PLAYER, ANIMAL, MONSTER, NPC, OTHER -,
getRootPlayer() the player behind it, the TNT lighter for instance; getNpc() the NpcRef of an NPC a provider
recognised, null otherwise - see NPCs). A member passes because the root of the
actor is them; a mob, fire or a piston has no root - only actors / exceptActors or a refinement frees them.
Handlers per region. region.setFlagHandler(pd, flag, ctx -> ...) hears DENIED, ALLOWED, APPLIED and
REMOVED for one flag in one region; on a denied movement it may choose the way back (revertTo, revertWith,
allow). FlagOutcomeContext.getPlayer() is the flag's subject (Flag.getSubject): the victim for invincible,
deny-damage-from-mobs and damage-multiplier (also under a mob's hit), else the player behind the action, null when
none. Never stored: set it on every enable, after load().
A flag says which events it needs (EventSource, 28 of them): MOVE, BREAK, PLACE, INTERACT, DAMAGE,
SPAWN, EXPLOSION, FLUID, RESPAWN, ITEM_PICKUP, ITEM_DROP, VEHICLE, FIRE_SPREAD, FLIGHT_TOGGLE, KILL,
CRAFT, SMELT, FISH, CONSUME, XP_GAIN, ENCHANT, TAME, BREED, SHEAR, COMMAND, EXPLORE, TICK and
NONE. Every one but MOVE, TICK and NONE is also a public <Source>ActionEvent on the bus
(Events): a subscriber of it needs the source exactly as a flag does. A platform listener for a source exists only while some region holds a flag that needs it: no region
with deny-fluid-flow, no BlockFromToEvent listener - the hottest event of the server. That is why the cheap
and the expensive halves are separate sources (ITEM_PICKUP / ITEM_DROP, FIRE_SPREAD / PLACE).
-
action.EventSourceUsage.get()keeps the count:isNeeded(source),acquire(source)->UsageHold(a need that is not a region's, a selection in progress),watch(owner, source, onNeeded, onIdle)->UsageWatch.EventSourceUsage.stopAll(owner)stops an owner's watches; the default shutdown does it. -
A plugin's own source:
EventSource.register(pd, "duel-hit")(myplugin:duel-hit),EventSource.unregisterAll(pd); a context subclass ofActionContext; a flag that needs the source. -
Bukkit (
:minecraft):ActionListeners.follow(owner, source, listener, afterRegistered)registers theECListenerin the plugin's name when the source becomes needed and takes it off afterRegions.listener-idle-time(3 min) idle; the overload with a collection of sources returns oneUsageWatchper source and keeps the listener while ANY of them is needed. Inside the listener:McActionContexts.isDisabled(world), thenActions.denied(ctx)(the regions decide, then the source's.Preif it has one).
EventSource duelHit = EventSource.register(pd, "duel-hit");
FlagRegistry.get().register(pd, denyDuelHit); //a DenyFlag that needs duelHit
UsageWatch watch = ActionListeners.follow(pd, duelHit, new DuelHitListener(duelHit), null);
// shutdown
watch.stop();-
TICK: one task a second (
RegionTicker), scheduled only while some region holds aTickingFlag, that ticks only the players standing in such a region, on their world's thread. -
What a platform raises:
ActionSupport.declare(platform, post, pre, nativeCommandSource)declares the core sources the platform fires and where a.Preexists; a flag that needs another one is logged at boot as having no effect there. Hytale raisesMOVE,BREAK,PLACE,INTERACT,DAMAGE,SPAWN,ITEM_PICKUP,ITEM_DROP,KILL,CRAFT,RESPAWN,EXPLORE,COMMAND(itsFinalCMDcommands) andTICK; Bukkit raises every source (BREEDfrom 1.12).setMissingCapabilities(...)does the same for what the platform cannot change on a player (Flag.getNeededCapabilities()). A plugin's own source is never judged by it. -
Regions.disabled-worlds(core config): nothing is asked of the regions in those worlds.
Each player has a RegionPlayerSession (RegionLookup.get().getSession(player), getSessionIfPresent(uuid)):
the regions they are in (getCurrent()), when they entered each (getEnteredAt(region)), the last position
and the trail of accepted blocks a denied step goes back along. The movement tracker updates it on every block
or world change, teleport, vehicle move, respawn, join and quit (MoveCause), and when a region is created,
removed or changed around the player.
-
Border events first: leaves are posted before enters (
RegionLeaveEvent,RegionEnterEvent); a crossing caused byMOVE,TELEPORTorVEHICLEcan be cancelled, the others already happened. Once a crossing took effect, oneRegionCrossedEventcarries every region left and entered (never cancellable; a cancelled crossing posts none). -
Movement flags (
deny-entry,deny-exit, the teleport flags) decide the crossing with that region's own values only. A denied step goes back by therevertof the settings (RevertStrategy.lastValid(),trail(steps),allowAfter(denials),fixed(point),spawn(),worldSpawn()); a player deniedstuckAftertimes insidestuckWindowis moved further back, and finally tostuckFallback.spawn()sends to the server spawn a plugin registered (ISpawnProvideron the base provider), else the world's. -
Session flags run
onEnterwhen a value starts to apply,onLeavewhen it stops andonRevalidatewhen it changes or its check comes due (Revalidation). -
The player's state comes back. The player-state flags (
time-lock,weather-lock,allow-fly,force-fly,game-mode, each effect ofpotion-effects) go through the player'sHeldPlayerState(one per player,HeldPlayerState.of(uuid)), held by anyStateHolder- a flag is one, by its key, and so is ascope: heldeffect of the logic engine: the first holder of a slot saves what the player had, the last one to let it go writes it back.hold(slot, holder, value, rewrite, persistent, online)takes the holder'srewrite: when the latest holder lets go while others still hold, the holder now latest writes its own value again (ascope: heldgamemode released inside agame-moderegion gives back the region's mode), and a holder that takes the slot again becomes the latest. What the player had comes back on leaving, on quitting and, with the settingrestore: PERSISTENT(default), on the next join after a server stop, through the core's PDSectionregion_state(PlayerStateBackup).restore: MEMORYkeeps it only in memory. The player is changed only throughEverNifeCore.getPlatform().getPlayerState()(IPlayerStateAccess, see Platform Abstraction).
The quest engine counts on these crossings: objectives enter-region, leave-region, stay-in-region
(measure: continuous starts over on leaving, accumulated adds the stays up; a group quest reads presence: all | any) and reach-location, whose target is a
TRANSIENT region tagged evernifecore:quest-target made when the quest registers and removed when it unregisters
(RegionLookup.get().byTag("evernifecore:quest-target")). In logic.core (any Condition user): in-region
({region, for-at-least}), region-flag, region-member, and the variable region-time (how long the player has stood inside without leaving).
- { id: hold, type: stay-in-region, region: arena, duration: 40s, measure: accumulated }
fail-when: [ { type: none, conditions: [ { type: in-region, region: arena } ] } ]The ready verbs enter the plugin's command as a node; with subcmd = "" they answer at its root:
@FinalCMD(aliases = {"fprotect", "fp"}, permission = "finalprotection.command")
public class FProtectCommand {
@FinalCMD.Node(subcmd = "", permission = "finalprotection.region")
private final RegionCommandNode regions;
public FProtectCommand(RegionManager manager, ItemSpec wand) {
//the third argument adds <base>.<verb> per verb, checked before any argument is read
this.regions = new RegionCommandNode(manager, SelectionSpec.cuboid().withTool(wand), "finalprotection.region")
.allowForeign(ForeignAccess.none());
}
}| Verb | What it does |
|---|---|
select |
starts a selection with the node's SelectionSpec
|
define <name> [-f] |
creates a region from the selection; refuses an overlap without -f, answers "still loading" before the load |
redefine <region> |
gives the region the current selection |
remove <region> [--cascade | --orphan] |
removes it; with children one of the two is required |
info <region> / list
|
reads |
flag <region> <flag> [value...] [-t <ttl>] [--fs k=v] |
sets, replaces or clears (-) a value; -t 1h gives it a time to live; deny-build is a shortcut for both block flags |
settings <region> <flag> [field=value...] |
the region's settings override; - drops it |
priority <region> <n> / parent <region> [parent]
|
|
displayname <region> [name...] / description <region> [text...]
|
free text with colors and spaces; no text clears |
tp <region> / view <region>
|
teleports to the teleport-point, else the region's center / draws the outline |
tag <region> ... |
the region's tags |
here |
the regions at the player and, for a flag, the explain of its value |
group <region> <group> add|remove|flag|list|priority|permission |
the access groups |
terrain <region> snapshot|snapshots|restore [n] [--confirm]|undo |
the owner's terrain (below); restore without --confirm only describes the snapshot |
A bare region name means the node's owner; another owner's region is reached as owner:name and only read,
unless allowForeign(ForeignAccess.all() | owners(...)) lets the node edit it AND that owner accepts
(setForeignAccess(true)). Region and Flag are builtin @Arg types in any command of the plugin
(@Arg("<region>") Region region); under a RegionScope a bare name is the scope's owner's. restore and undo
walk getTerrainBudget() positions per tick (setTerrainBudget(int), default 1000).
RegionPlaceholders.register(replacer, RegionPlaceholderScope.ownedBy(pd)) adds the region placeholders
(in_region, region_count, region_<op> for the player's region, region_[name]_<op> for one by name -
name, display name, owner, bounds, players, time inside, flag values, groups) to any replacer in one line; the
core serves %evernifecore_region_...% over everyRegion(). flag_[flag] is the region's effective value,
refinement climb included. The keys, the two selectors, the scopes and the [...] block grammar are on
Placeholders.
RegionCreatedEvent, RegionRemovedEvent, RegionRedefinedEvent, RegionChangedEvent (with its Type),
RegionFlagChangedEvent, RegionEnterEvent / RegionLeaveEvent, RegionCrossedEvent,
RegionTerrainEvent and RegionLoadFailedEvent, each posted only when something listens, on the thread of
whoever made the change, for every plugin's regions - filter by getOwner(). The table is on Events; a change of a
region's group is a Group*Event (Groups).
One RegionTerrain per owner (regions.getTerrain()); it never protects, only puts the ground back.
RegionTerrain terrain = regions.getTerrain();
terrain.track(arena); //track-changes TRUE: the journal keeps the ORIGINAL of each changed position
terrain.restoreJournal(arena, 500).thenAccept(n -> ...); //500 blocks a tick, newest change first
terrain.snapshot(arena).thenAccept(file -> ...); //the bounding box, keeping the newest getSnapshotRetention() (3)
terrain.restoreSnapshot(arena, 0, 500); //0 = the newest; 500 positions of the box a tick; only what the shape contains-
Bukkit: the journal listens at
MONITOR- breaks, places, explosion blocks, broken armor stands and hanging entities (not fire, fluids or pistons). A snapshot is a WorldEdit.schem(.schematicon WE 6) plus.entitiesand needs WorldEdit; on 1.7.10 the WorldEdit Forge mod inmods/too, or chests come back empty (the core warns at boot). -
Hytale: the journal keeps blocks and, with
nbt, their block entity; a snapshot is a.prefab.json. - Every snapshot carries a
.version; restoring one of another game version throws. One terrain operation per region at a time: another one meanwhile completes withIllegalStateException. - A restore runs its first tick at once and every next one through
IPlatform.runOnWorldThreadNextTick, so the per-tick budget is real;RegionTerrainEventreports each tick (getBlocks(),getNanos()).
SelectionSpec.cuboid() (two points) or polygon() (three or more), withTool(ItemSpec),
withLivePreview(boolean), withTimeout(Duration). ItemSpec.of("minecraft:golden_hoe", name, lore),
ItemSpec.DEFAULT_TOOL, withNativeItem(...). Left click marks a point, right click takes the last one off,
sneak + right click confirms. A flow of your own skips the select verb:
String world = fplayer.getLocation().getWorldName();
SelectionTool.start(fplayer, SelectionSpec.polygon(), shape -> regions.create("plot-" + fplayer.getName(), world, shape));The points keep their real Y; for a full column convert in the onConfirm (CuboidShape.ofUnboundedY). On
Bukkit, McWorldEditSelectionBridge.selectionOf(player) turns a WorldEdit selection into a shape (null without
WorldEdit or with an incomplete selection). Config: Regions.selection.live-preview, Regions.selection.idle-time,
Regions.visualizer.*.
- The manager and region API run from any thread: writers are serialized, reads take no lock.
- An event goes out on the thread of whoever made the change.
- Storage I/O never runs on the caller's thread; a change is written a tick later.
- Time comes from
time.ECClockand delayed work goes throughECScheduler.scheduleAsync; a test moves both at once (ECClock.setClock,ECScheduler.set), which is whatRegionWorlddoes. -
decideruns on the event's thread.SelectionTool.start, itsonConfirm, the visualizer, the session flags and the region tick run on the player's world thread (IPlatform.isWorldThread/runOnWorldThread). - A native handle (
actor.getNativeEntity()/getNativeBlock()) lives only while the event is decided; keep thegetUniqueId(), never the handle.
The same RegionManager, flags and RegionCommandNode work on Hytale; a plugin registers nothing for the core's
flags to apply there. The core puts its region ECS Systems on the server ONCE, from setup() (the only time the
server takes a System), and each one starts with EventSourceUsage.get().isNeeded(source). Hytale has no
explosion event: an explosion is a DamageBlockEvent per block with no actor, decided as a BREAK by
environment:explosion, so deny-explosion has no effect. Movement is a tick per player that asks only on a block
or world change and reverts with a Teleport. Flags without effect on Hytale say so at boot - the catalog's
Hytale column. A plugin's own source on Hytale is a System of the plugin, put on the server in its own
setup(). Details and the gaps of the public API: Hytale Platform and
Gotchas and Pitfalls.
Some types and methods are public only because another package of the core calls them; their javadoc starts
with "Internal:" - ProtectionFlagGate, RegionIndex, FlagStore,
RegionLookup.resolution(...), RegionManager.closeOwnedBy(...), the constructors of Group and
RegionPlayerSession, and more. They are not API: a plugin does not call them, and they change without a
changelog entry for plugins.
-
Placeholders - the region placeholders and the
[...]block. - Events - the region events.
- Groups - the group engine a region's groups belong to, and the group flags.
-
Platform Abstraction - the region seams of
IPlatform,IPlayerStateAccess. - Hytale Platform - regions on Hytale.
-
Testing Toolkit -
RegionWorld, players placed withat(...). - Gotchas and Pitfalls - what the platforms let through.
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