Commit Graph
16 Commits
Author SHA1 Message Date
Michael Burgess 1b03009fa3 Periodically sync remote gates instead of only loading once at startup
Gates were only ever loaded from the shared database at plugin enable
or /sg reload, so a server that's already running never found out
about a gate another backend created afterward - the row existed in
MySQL, but nothing told this JVM to look again. Reported as "no other
gates on network 'main'" despite both gates being visibly present in
the database.

Added GateManager.refreshRemoteGates(), scheduled every
cross-server.refresh-seconds (default 30) whenever cross-server is
enabled: it re-queries storage off the main thread, then merges only
OTHER servers' gates into gatesById on the main thread - adding new
ones, removing deleted ones, and never touching this server's own
gates or any live open/connected/outgoing runtime state.
2026-08-09 13:11:38 -04:00
Michael Burgess 2d47962ea1 Allow arbitrary MySQL JDBC connection properties in config.yml
storage.mysql.properties is now a free-form map merged into the JDBC
URL as ?key=value&key=value..., letting hosts that require TLS set
things like sslMode: REQUIRED, or override the useSSL/autoReconnect
defaults, without needing a code change for every possible parameter.
2026-08-09 12:44:33 -04:00
Michael Burgess 576c078abf Actually enforce the iris shield instead of only rendering it
The shield's closed/open state only ever controlled the iris block's
appearance (bedrock vs water) - the teleport listener never checked
it, only whether the gate was open+outgoing. A solid bedrock block
stops normal walking, but nothing stopped a player who ended up
standing there anyway (flight, elytra, a vehicle) from still getting
teleported, which is what was reported: iris visibly closed, travel
still worked.

findOutgoingGateAt() now skips any gate whose iris is closed, and
both teleportPlayer()/teleportEntity() also refuse the trip if the
DESTINATION's shield is closed, matching a real iris blocking
whatever hits it from either side. Same check added to the
cross-server delivery path in CrossServerBridge for the race where
the shield closes between the proxy request and the player landing.
2026-08-09 12:25:08 -04:00
Michael Burgess c98f89c225 Add shift-left-click on the sign as a shortcut to destroy the gate
Same permission check as /sg destroy (owner with stargate.destroy, or
stargate.admin). Also breaks the physical sign block via
breakNaturally() so it drops like a normal mined sign instead of just
vanishing, matching what happens when StructureProtectListener reacts
to an actual BlockBreakEvent on the sign.
2026-08-09 12:16:24 -04:00
Michael Burgess 980f73e7d2 Narrow default frame-materials to just obsidian
GOLD_BLOCK/GILDED_BLACKSTONE/BIRCH_PLANKS/OAK_PLANKS were also present
on a nearby backdrop wall in the user's build, so even with the
plane-locked diagonal fix (which only stops leaks through the ring's
thickness), the flood-fill could still sweep in decorative use of the
same materials sitting in-plane with the ring, putting chevrons on
unrelated blocks. Default is now just OBSIDIAN; anyone mixing
materials should keep the list to ring-only blocks per the updated
config.yml/README guidance.
2026-08-09 12:03:56 -04:00
Michael Burgess f58fad26d7 Transport non-player entities through gates too
GateTeleportListener previously only listened to PlayerMoveEvent, so
mobs, dropped items, minecarts, boats, and everything else that isn't
a player just walked/rolled/drifted straight through the event
horizon block with no effect. Added a Paper EntityMoveEvent handler
sharing the same one-way/outgoing-only matching, safe-landing, and
cooldown logic as the player path.

Non-player entities are restricted to same-server destinations - the
cross-server plugin-messaging protocol only carries a player's UUID
and look direction across the proxy hop, not arbitrary entity state,
so there's no way to hand off a mob or item to another backend.
2026-08-09 11:54:40 -04:00
Michael Burgess 8932dfed29 Fix chevron over-count, plane-lock diagonal frame connectivity, true one-way travel, iris shield independence
Chevron placement: the "protruding elbow" heuristic marked every
convex corner of a smooth ring as a chevron, which for a true
octagon/circle is *every* corner - producing 8 chevrons instead of a
deliberate subset. Replaced with a fixed, configurable count
(gate.chevron-count, default 7) evenly spaced along the ring's actual
geometric walk order.

Frame flood-fill leaking into unrelated structures: the diagonal
("stepped ring") connectivity fix from two commits ago used a full 3D
26-neighbor search, which let the flood leak sideways through the
ring's thickness into any other nearby structure sharing a frame
material (e.g. gilded blackstone used decoratively on a backdrop
wall), putting chevrons on it. The scan now bootstraps with a small
orthogonal-only flood to find the ring's plane first, then only
allows diagonal connectivity within that plane - never through depth.

One-way travel, properly this time: RuntimeGate gained an `outgoing`
flag, set true only on the gate that was actually dialed. The
teleport listener now only matches outgoing gates, so the destination
shows the event horizon but isn't walkable - stepping into it does
nothing, and only the dialing side can send a player anywhere.

Iris shield: it's a physical barrier independent of the wormhole, not
tied to "connected" state. Closed always means iris-shield-material
(default changed from IRON_BLOCK to BEDROCK per feedback), whether or
not the gate is even dialed right now. Only when the shield is open
does the idle/connected distinction (air vs event horizon) apply.
applyIrisMaterial() centralizes this so link, restart re-scan,
open/close, and the shield toggle all agree.
2026-08-09 11:52:04 -04:00
Michael Burgess b822389387 Fix one-way travel, rework iris to SG-1 mechanics, glowstone chevrons, pin-locked shield
One-way travel bug: the exit point landed players directly inside the
destination gate's own iris footprint. Since dialing opens both ends,
arriving there immediately re-triggered the teleport listener and
bounced them straight back - reading exactly like one-way travel.
GateManager.computeSafeLanding now nudges the landing spot along the
player's travel direction until it clears the destination's iris
blocks, and GateTeleportListener adds a short per-player cooldown as a
second guard against re-trigger. Same-server teleports now also
preserve the player's exact yaw/pitch and velocity instead of forcing
a fixed stored orientation, so entering forward always means exiting
forward; the cross-server plugin-messaging protocol was extended to
carry yaw/pitch through the proxy hop for the same reason.

Iris rework to match SG-1 rather than a binary open/closed gate:
- Idle (not connected): iris-idle-material, default AIR - you see
  straight through an inactive ring, not a wall.
- Connected: iris-open-material (WATER, the event horizon) unless the
  gate's separate iris shield is closed, in which case
  iris-shield-material (IRON_BLOCK) blocks travel even though the
  wormhole is active.
- A button placed directly below the control sign toggles the shield
  (GateIrisButtonListener). Closing needs nothing; opening a gate with
  a pin code set (/sg pin <code>) prompts the player to type it in
  chat within gate.pin-timeout-seconds first.

Also: gate.chevron-lit-material default changed from gilded blackstone
to glowstone per feedback.
2026-08-09 11:36:17 -04:00
Michael Burgess 190f875d23 Fix interior flood leaking out the front/back of free-standing rings
A one-block-thick ring standing free (nothing built behind it, like
every reference screenshot) has open air directly in front of and
behind the interior. The iris/interior flood-fill searched all 6
directions, so it always escaped through that open air and flooded
the surrounding world instead of staying inside the ring - reliably
producing "interior isn't fully enclosed" regardless of how sound the
ring actually was.

Fixed by detecting which world axis the ring is thinnest along (same
idea already used for chevron placement) and restricting both the
interior candidate search and the flood itself to the other two axes,
so it can only explore within the ring's own plane.

Also fixed iris material semantics per feedback: the interior is no
longer just air when idle. gate.iris-material is now split into
iris-closed-material (default BEDROCK - solid, blocks all passage)
and iris-open-material (default WATER, the event horizon), applied
at link time, on open/close, and after a restart re-scan.
2026-08-09 11:19:55 -04:00
Michael Burgess 282c8cfdbb Fix frame flood-fill to follow diagonal (stepped) ring connections
Round/octagonal rings built the normal Minecraft way step diagonally
at the corners - two frame blocks touching only edge-to-edge, not
face-to-face. The scanner only checked the 6 face-adjacent neighbours,
so those blocks were treated as disconnected fragments and the whole
ring failed to register (or only a fraction of it did).

Frame discovery now flood-fills using all 26 surrounding offsets
instead of just the 6 faces. The geometric chevron walk needed the
same fix - its in-plane adjacency now checks 8 directions (orthogonal
+ diagonal) instead of 4, so stepped rings still produce a clean
single-loop walk instead of falling back to evenly-spaced chevrons.

Interior/iris flood-fill is intentionally left orthogonal-only: since
it only ever moves face-to-face, a diagonal-only frame corner already
blocks it from slipping past, so enclosure detection didn't need to
change.
2026-08-09 11:04:20 -04:00
Michael Burgess fe3b2b0f14 Revert to flexible flood-fill scanning with geometric chevron placement
The exact-template approach from the last commit over-fit to the
sample ASCII diagram, which was meant to teach the target shape, not
be matched literally cell-for-cell. Back to flood-filling any
connected, fully-enclosed ring the builder makes (any size/shape), but
chevrons are no longer evenly spaced by scan order - the scanner walks
the ring in geometric order and marks local maxima of distance from
the interior's centroid, i.e. the blocks that stick out further than
their ring-neighbours. That's where a real Stargate's chevrons sit
(the 11-wide reference ring naturally produces 7 this way: the top
apex, four shoulder elbows, two side bumps), without hardcoding a
specific size.

Falls back to the old evenly-spaced picker only if the ring isn't a
simple single-thickness loop (each frame block should have exactly
two in-plane neighbours) so odd/thick builds still work.

gate.max-frame-blocks/max-iris-blocks/min-frame-blocks are back;
gate.template is gone.
2026-08-09 10:57:00 -04:00
Michael Burgess ed8415a497 Replace flood-fill scanner with an exact gate template
Instead of guessing a gate's shape by flood-filling connected frame
material, the plugin now matches a fixed ASCII template against the
world (gate.template in config.yml), the same idea as the classic
Stargate plugin's .gate files. Default is an 11x11 ring with 7
chevrons at their real positions rather than auto-spaced.

- New GateTemplate parses the '*'/'1'/'2'/'0' grid.
- New GateTemplateScanner tries every plausible alignment of a
  punched block against a frame/chevron cell, then verifies the rest
  of the template matches; no more flood-fill leak/enclosure
  diagnostics needed since the shape is exact.
- The player's look direction at punch time (snapped to N/S/E/W)
  decides whether template columns run along world X or Z; this
  facing is now stored in Gate.facing (repurposing the old
  sign-attachment field) so restarts can re-scan deterministically.
- Exit-teleport yaw now comes from that stored facing instead of a
  geometric guess.
- Removed gate.chevron-count/max-frame-blocks/max-iris-blocks/
  min-frame-blocks - the template itself defines size and chevrons.
2026-08-09 10:48:47 -04:00
Michael Burgess 317602c00e Decouple the control sign from the gate frame: place then punch to link
Sign creation no longer scans the block it's mounted on. Instead,
placing a "[Stargate]" sign starts a pending link (shown as "Punch the
gate" on the sign) and the next block the owner left-clicks within
gate.link-timeout-seconds and gate.max-link-distance becomes the scan
seed. This lets the sign act like a DHD console standing apart from
the gate instead of being physically attached to the frame.

- Gate now stores the punched link block's coordinates (link_x/y/z)
  so restarts re-scan from there instead of deriving a seed from the
  sign's attachment, which no longer applies.
- New GateLinkListener handles the punch and reports scan failures
  via the existing diagnostic messages; also clears pending state on
  disconnect.
- Exit-location facing is now derived from the punched frame block's
  position relative to the iris, since a WallSign facing is no longer
  guaranteed to exist or be relevant.
2026-08-09 10:32:46 -04:00
Michael Burgess c99f4c9f85 Auto-select chevron slots instead of requiring a distinct unlit material
Chevrons no longer need to be built out of a special block - the plugin
picks gate.chevron-count frame blocks evenly spaced around the scanned
ring and remembers their resting material, so a chevron can rest as
plain obsidian (matching the rest of the frame) and only reveal itself
by swapping to gate.chevron-lit-material while dialing/open. Restoring
on close now puts back the original block instead of a fixed material,
so alternating decorative frames (e.g. obsidian/gilded blackstone) are
preserved exactly.
2026-08-09 10:20:14 -04:00
Michael Burgess d651f6586f Add fixed-destination gates and /sg networks command
Sign line 4 now accepts fixed:GateName to lock a gate to one destination
without cycling, and /sg networks lists every known network.
2026-08-09 09:56:12 -04:00
Michael Burgess 83a0392ee9 Initial Stargate plugin: Paper gate plugin + Velocity/Bungee cross-server bridges 2026-08-09 09:53:55 -04:00