Skip to content

Regions

Pétrus Pradella edited this page Nov 2, 2026 · 14 revisions

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


The model

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).


RegionManager: load, create, remove

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 its getCause()). Every failed try posts a RegionLoadFailedEvent and 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.
  • create is locked until the regions are loaded. create(name, world, shape[, options]) before that throws IllegalStateException: the index lacks the stored regions, so the name check and the overlap policy would run against half the truth. Create from regions.whenLoaded().thenRun(...), or check isLoaded(). The define verb 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 an OverlapSource: 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 through OverlapProviders.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) or remove(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 default onECPluginShutdownPre closes the manager (writing what is pending), unregisters the owner's flags and event sources and stops its usage watches - override it only calling super.

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().


Flags

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.

Writing a flag

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 (its decide is final) and narrow applies; InteractDenyFlag adds the only/except lists, ActorDenyFlag is a refinement by actor (new ActorDenyFlag(key, parent, description, "animal", "monster")). The inherited onApplied sends the region's deny-message to a denied player.
  • Session: FlagCategory.SESSION, EventSource.MOVE in the needs, and onEnter / 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 extends PlayerStateFlag<T>; one that acts on an interval implements TickingFlag<T> and needs EventSource.TICK.
  • Data: FlagCategory.DATA with EventSource.NONE, like teleport-point.
  • Scope and joining: a flag that may follow the player (a party's buff) overrides getScope() to ACTOR; a flag whose layers should add up, multiply or concatenate instead of the first one winning overrides getResolver() and getAcceptedResolvers() with FlagResolvers (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 in getChannels(), and one of the two declares FlagInteraction.with(otherKey, "what happens") in getInteractions() - 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 flag catalog

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.

Protection

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

Session, data and time

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):

  • invincible with damage-over-time: invincible = TRUE: no damage over time.
  • deny-friendly-fire with deny-pvp: deny-pvp = TRUE already denies every hit; deny-pvp = FALSE + deny-friendly-fire = TRUE: PvP only between different groups.
  • invincible with damage-multiplier: invincible = TRUE: nothing to multiply.
  • allow-fly with force-fly: force-fly = TRUE flies the player even where allow-fly = FALSE grounds them.
  • heal with damage-over-time: the two add up to one net change per shared interval.
  • feed with hunger-over-time: the two add up to one net change per shared interval.
  • effects-while-inside with game-mode, allow-fly, force-fly, time-lock, weather-lock and potion-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.


Settings

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's

editFlagSettings 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.


Groups and resolution

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 break

Members 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 priority

since 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):

  1. Bypass - evernifecore.region.bypass.*, evernifecore.region.bypass.<owner>.<name>, or the flag's bypassPermission in any region there: the flag reads its default (only for a bypassable flag).
  2. 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.
  3. Order them: lock first, then the written priority (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 newest since among what is left equal.
  4. Cut after the first layer in mode: set: the layers after it read CUT_BY_SET.
  5. 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.
  6. 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().


Event sources and the on-demand listeners

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 of ActionContext; a flag that needs the source.
  • Bukkit (:minecraft): ActionListeners.follow(owner, source, listener, afterRegistered) registers the ECListener in the plugin's name when the source becomes needed and takes it off after Regions.listener-idle-time (3 min) idle; the overload with a collection of sources returns one UsageWatch per source and keeps the listener while ANY of them is needed. Inside the listener: McActionContexts.isDisabled(world), then Actions.denied(ctx) (the regions decide, then the source's .Pre if 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 a TickingFlag, 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 .Pre exists; a flag that needs another one is logged at boot as having no effect there. Hytale raises MOVE, BREAK, PLACE, INTERACT, DAMAGE, SPAWN, ITEM_PICKUP, ITEM_DROP, KILL, CRAFT, RESPAWN, EXPLORE, COMMAND (its FinalCMD commands) and TICK; Bukkit raises every source (BREED from 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.

Sessions: entering, staying, leaving

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 by MOVE, TELEPORT or VEHICLE can be cancelled, the others already happened. Once a crossing took effect, one RegionCrossedEvent carries 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 the revert of the settings (RevertStrategy.lastValid(), trail(steps), allowAfter(denials), fixed(point), spawn(), worldSpawn()); a player denied stuckAfter times inside stuckWindow is moved further back, and finally to stuckFallback. spawn() sends to the server spawn a plugin registered (ISpawnProvider on the base provider), else the world's.
  • Session flags run onEnter when a value starts to apply, onLeave when it stops and onRevalidate when 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 of potion-effects) go through the player's HeldPlayerState (one per player, HeldPlayerState.of(uuid)), held by any StateHolder - a flag is one, by its key, and so is a scope: held effect 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's rewrite: when the latest holder lets go while others still hold, the holder now latest writes its own value again (a scope: held gamemode released inside a game-mode region 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 setting restore: PERSISTENT (default), on the next join after a server stop, through the core's PDSection region_state (PlayerStateBackup). restore: MEMORY keeps it only in memory. The player is changed only through EverNifeCore.getPlatform().getPlayerState() (IPlayerStateAccess, see Platform Abstraction).

Quest objectives and conditions

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 } ] } ]

Commands: the RegionCommandNode

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).


Placeholders

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.


Events

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).


Terrain

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 (.schematic on WE 6) plus .entities and needs WorldEdit; on 1.7.10 the WorldEdit Forge mod in mods/ 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 with IllegalStateException.
  • A restore runs its first tick at once and every next one through IPlatform.runOnWorldThreadNextTick, so the per-tick budget is real; RegionTerrainEvent reports each tick (getBlocks(), getNanos()).

Selection

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.*.


Threads

  • 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.ECClock and delayed work goes through ECScheduler.scheduleAsync; a test moves both at once (ECClock.setClock, ECScheduler.set), which is what RegionWorld does.
  • decide runs on the event's thread. SelectionTool.start, its onConfirm, 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 the getUniqueId(), never the handle.

Hytale

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.


Public but internal

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.


See also

Clone this wiki locally