Messaging yourself gets a random answer from your other self after ten
seconds, formatted like a normal private message, instead of "nobody
receives your message".
- Clicking a player name or @mention suggests /whois <player>; the
hover explains the click
- /chat <channel> <message> writes once into a channel and replaces
/chat <channel> msg <message>
- Clicking the Global or Local/Fight icon suggests /chat, for foreign
arenas /chat <arena>
Hidden block entities were removed from chunk data, so the client did not
render blocks like shulker boxes at all. They are now sent with empty data,
hiding their contents but not the block itself.
The steps that can lead into or past a changed block are computed once per
hull instead of searching all directions and parts on every block update,
which makes an update about three times faster.
Doors and trapdoors do not occlude for the client, so they had no full face
in the hull and rays passed through closed trapdoors, revealing blocks behind
them along diagonals. Their face masks now come from their shape, so the face
their panel covers is full.
Instead of a block occluding as a whole, the hull stores which of its six
faces are full, using the occlusion shapes the client culls faces with. A ray
inside a block can only leave it through an open face and enter the next
block through its open opposite face; if only that face is full, the ray
reaches its surface, so the block is visible, but stops there. Slabs, stairs
and stacked slabs now hide what is behind them; double slabs need no special
case anymore. Faces only open up on block updates, after which the rays that
could not get into, out of or past the block are continued.
Hidden blocks occlude by the same decision as the techhider, so waterlogged
blocks count as well when water is hidden. The hull therefore takes the
ObfuscationConfig, which the fight shares with its TechHiderWrapper.
Players using /hullhider also get the techhider of the region's gamemode in
the build area, like in a fight: its hidden blocks are replaced and occlude
in the hull.
A ray only moves diagonally if it could also reach the block by moving along
the single axes of the direction one after another without passing an
occluding block, so it no longer squeezes between blocks touching at an edge
or corner. When a block stops occluding, the diagonal rays it blocked are
continued, so incremental updates match a full initialisation.
Double slabs now occlude as well, as slab materials do not; block updates
pass the block data instead of the material for that.
To keep the initialisation fast, a ray only checks a diagonal step after the
cheaper checks, and the branch directions are computed once per hull.
The repeating timers that linked classes and the plugin main classes started
once for the whole runtime now use @LinkedTimer, on Spigot (BauSystem,
SpigotCore, LobbySystem, MissileWars, TowerRun, Teamserver) and Velocity.
The FightInfoPacketSenders of MissileWars and TowerRun are linked themselves
instead of being started by the main class.
A method of a linked class annotated with @LinkedTimer(delay, period) is run
repeatedly by the linker, on Spigot and Velocity. The method must not have
parameters, its return value is ignored. It is called through a MethodHandle
bound once when linking instead of reflection; LambdaMetafactory cannot be used
as the linker and the linked classes are in different class loaders.
The unit defaults to ticks and can be set to milliseconds up to hours; the
Unit enum converts it to ticks for Spigot and to a TimeUnit for Velocity.
HullHiderCommand uses it for its tick task.
Every loader started a timer and registered itself as listener, but neither
was stopped, so each loader kept running every tick until the server
restarted. Both are now stopped when the loader is stopped or replaced by a
new one.
HullHider gets a hideEntities parameter: the fight still hides entities inside
hidden hulls, /hullhider only hides blocks. As the TechHider also dropped
spawn packets at hidden positions, ForPlayer#canAccessEntitySpawn decides
about them now, which the BauTechHider always allows.
The value of a @Linked field of a linked class is linked like a linked class
itself, e.g. registered as listener, and unlinked again on unlink. The field
has to be initialised when linking. HullHiderCommand and TPSSystem use it
instead of registering their listeners by hand.
/hullhider hides the inside of the build area hull of a region from the
players who toggle it, like the hull hider of a fight. Each region gets its
own hull while somebody uses it; it is rebuilt every time somebody enables it,
as WorldEdit and Axiom changes do not update it. /techhider, /xray and
/hullhider exclude each other per player and region.
Which players may see the hull contents is now decided by Viewers, a sealed
class with HiddenByDefault (fight) and VisibleByDefault (bau). Added players
are stored in a set; isViewer alone defines what being added means.
HullHider#removeHull stops hiding a hull: it shows the entities and REntities
the hull hid and resends its chunks to everybody who could not view it. A hull
is enabled again by adding and initialising it.
Hull#resendChunks only resends chunks the player has loaded. HullHider now
resends the chunks to non-viewers after initialising a hull itself, so
FightHullHider no longer does it.
The new TechHider registers a permanent global filter, so BauSystem now uses
a single BauTechHider that combines the hiding rules of the spectator
techhider, /techhider and /xray per player. To support the different
obfuscation blocks of these rules, ForPlayer now provides the obfuscation
block per position.
The ChunkHider now also checks single valued chunk sections, which were sent
unfiltered before, and recalculates their block count if they change.
Unhandled packet types are logged once; statistics, debug samples and ping
responses are passed through.
The legacy TechHider and its access widener entries are removed.
ObfuscationConfig resolves the hidden blocks, block states (including all
waterlogged states when water is hidden) and block entity types plus the
obfuscation block, either from a gamemode's techhider config or from plain
materials. TechHiderWrapper now only contains the fight specific access rules.
Hull no longer depends on FightSystem: plugin, world, region, ground
visibility and hidden blocks are passed in. HullRegion is the common block
box hulls work on. The generic HullHider<K> manages one hull per key and does
the block, entity and REntity tracking; FightHullHider keeps the fight
specific parts (team hulls, viewer rules, state dependent registration).
Entities are now checked against the hull with block coordinates, so entities
in the bottom layer and at the max z edge are handled correctly.
HiderCore holds the TechHider, ChunkHider and AccessPrivilegeProvider so that
FightSystem and BauSystem can both shade it. BlockIds and ProtocolUtils stay in
SpigotCore (used by AntiNocom and RFallingBlockEntity) and move to
de.steamwar.core to avoid a split package. The access widener entries the
TechHider needs are duplicated into hidercore.accesswidener; the legacy
TechHider still needs its own copies in SpigotCore.
Chunks encoded while the hull was filled as hidden contain obfuscated
blocks that become visible after initialize, and no block update is
generated for them. Reload the team's extend region for all players
that cannot see the hull.
Stripping ids of hidden entities left ghost entities on clients once
the entity became visible and was later removed. Removing an unknown
entity id is harmless, so the packet is passed through.
Spectators hid HiddenBlocks in the whole arena for block updates, but
full chunk packets outside both extend regions were sent unfiltered, so
the same blocks were hidden or visible depending on the packet type.
Hidden regions are now a list: spectators hide both team extend
regions, team members the opposite one. Block checks and the per-chunk
bypass use the same regions, so everything outside the extend regions
is visible and unfiltered.
AccessPrivilegeProvider#forPlayer returns a PlayerAccess that resolves
the player's hidden region once. TechHider creates it once per outer
packet (shared across bundle sub-packets) and passes it to every
processor, so all decisions within a packet are consistent and the
region/referee lookup no longer runs per block on the netty thread.
getHiddenRegion looked the player up in FightTeam.players twice (team,
then FightPlayer) on netty threads. A concurrent reset or removePlayer
made the second lookup return null, the NPE was swallowed by the global
filter and the packet was dropped. Resolve the FightPlayer once and
derive team and canEntern from it.
The processor for ClientboundEntityEventPacket used getEventId(), the
status byte (2 = hurt, 3 = death, ...), as the entity id. Events of
hidden entities therefore passed the filter, and events of visible
entities could be dropped when an unrelated hidden entity had the same
id. Use the entityId field instead.
Hull kept a set of affected players that was only filled by a join
listener active in the Schem states, so spectators joining earlier were
never hidden from the hull. The set was also a plain HashSet read on
netty threads, where a racing lookup meant "not hidden".
Invert it into a concurrent set of viewers (referees, own team, boarded
players); everyone else is hidden by default.
- Hull: viewer UUID set with addViewer/removeViewer/forgetViewer, a
null player counts as hidden
- Hull: entities are hidden/shown when they enter/leave the hull for all
non-viewer online players; uncovered blocks go to non-viewers
- HullHider: join/quit handled in a listener active in every state; quit
clears the viewer entry
- HullHider: TeamDeathEvent re-hides the enemy hull and resends its chunks
- FightPlayer: stop the enter countdown before firing TeamDeathEvent so
listeners see canEntern() == false
Commands ending in a String... parameter are treated as help commands.
When such a command failed with an @ErrorMessage (e.g. /msg or /kick
with an offline player), the generic help matched next, printed the
usage and the collected error was never shown.
Stop before generic help (noTabComplete) if an earlier help command
already collected errors, and print those instead.
- Mark the receiver's default channel with a green icon
- Show the TeamChat icon in yellow, all other channel icons in gray
- Use the rank prefix and rank color for sender names in TeamChat and
ServerTeamChat, like in the global chat