Files
Stargate/README.md
T
Michael Burgess 508a3d9b51 Never place a chevron on the flat bottom threshold
pickChevrons() spaced chevrons evenly starting from whichever ring
block the flood-fill happened to discover first - an arbitrary point
depending on where the ring was punched, with no relationship to the
ring's actual top/bottom. That let spacing land a chevron directly on
the bottom row you walk through, which a real Stargate never has.

For a vertical ring, chevron spacing is now anchored at the true
topmost block and the bottom row is excluded from the candidate list
entirely before spacing runs. Horizontal rings are unaffected - they
have no "bottom" to avoid, so they still use the full ring.
2026-08-09 14:29:42 -04:00

152 lines
8.5 KiB
Markdown

# Stargate
A network-aware Stargate portal plugin for PaperMC, with sign-based dialing and
cross-server travel over Velocity or BungeeCord.
## Modules
| Module | What it is |
|---|---|
| `stargate-common` | Shared models + JDBC storage (SQLite / MySQL) used only by the Paper plugin |
| `stargate-paper` | The actual gate plugin — install this on every backend server |
| `stargate-velocity` | Proxy companion for Velocity — install on the proxy only if using cross-server gates |
| `stargate-bungee` | Same, for BungeeCord/Waterfall |
Build everything with `./gradlew build`. Jars land in each module's `build/libs/`.
## Building a gate
There's no fixed size or shape - build any single connected ring out of
`gate.frame-materials` (default: just obsidian) with a fully sealed hollow interior.
No gaps; the interior must be enclosed. "Connected" includes the usual diagonal
stepping a round/octagonal ring needs at its corners (blocks touching only
edge-to-edge or corner-to-corner still count), so a circular build doesn't need every
block to share a full face with the next.
Add more materials to `gate.frame-materials` if your ring mixes blocks, but keep the
list to materials used *only* for the ring - anything also used decoratively nearby
(a backdrop wall, a matching accent elsewhere) risks getting swept into the frame if
it touches the ring anywhere in the same plane, which is also where stray chevrons
would show up.
Build it free-standing, one block thick, in a single flat plane (a vertical ring
facing a direction, like all the examples above) - the plugin figures out which axis
is the ring's "thickness" and only looks for the interior within that plane, so it
doesn't try to flood-fill out through the open air in front of and behind the gate.
Chevrons aren't a separate material you place. When the gate is linked, the plugin
flood-fills the ring, walks it in geometric order, and picks `gate.chevron-count`
(default 7) blocks evenly spaced around the ring's actual perimeter - anchored at the
topmost point for a vertical ring, and never the flat bottom row you walk through,
matching a real Stargate. Those blocks swap to `gate.chevron-lit-material` (default
glowstone) while dialing/open, and revert to whatever they looked like at rest when
the gate closes - so a chevron can rest as plain obsidian and blend invisibly into
the frame until it lights up.
1. Build the ring.
2. Optionally place a button directly below where you'll put the sign - this becomes
the iris shield toggle (see below). Not required; a gate without one just has no
shield control.
3. Place a sign anywhere - it doesn't need to touch the frame, so it can act like a DHD
console standing apart from the gate - with:
- Line 1: `[Stargate]`
- Line 2: network name (blank = default network)
- Line 3: gate name (blank = auto-generated)
- Line 4: `hidden` to keep it out of the cycle list, `fixed:GateName` to lock this
gate to always dial `GateName` (no right-click cycling), or blank
4. The sign will show "Punch the gate (30s)". Within `gate.link-timeout-seconds`,
left-click (punch) any frame block, within `gate.max-link-distance` of the sign.
The plugin scans out from that block and links the sign to it.
If punching a block doesn't work, the plugin tells you exactly why in chat - wrong
material, unsealed interior, too far from the sign, etc.
## Using a gate
- **Right-click** the sign: cycles the destination shown on line 3 among the other
gates on the same network.
- **Left-click** the sign: dials the shown destination - chevrons light in sequence,
then the interior becomes the event horizon (`gate.iris-open-material`, default
water) and travel opens up. It auto-closes after `dialing.open-seconds`. That water
is contained to exactly the iris blocks - the plugin actively cancels any attempt
by Minecraft's own fluid physics to spread it further, so it can't leak out through
a gap or an odd ring shape and flood the ground around the gate. A gate that's
already dialing or open won't dial again - the animation itself, not just the fully
open state, blocks re-entry - so two people clicking the same sign can't start
overlapping dials or connect it to two destinations at once.
- **Shift + left-click** the sign: destroys the gate on the spot (same permission
check as `/sg destroy` - owner or `stargate.admin`), breaking the sign as if
you'd mined it. Quicker than looking at the sign and typing the command.
- Travel is **one-way**: only the gate you dialed *from* can send anything anywhere.
Walking (or wandering, or drifting) into its event horizon teleports whatever
entered to the destination, arriving just clear of its iris (never inside it) and
still facing whatever direction it was already moving, so entering forward always
means exiting forward. The destination's horizon is visible but not walkable -
stepping into it does nothing, same as a real Stargate only running one direction
at a time.
- Not just players - mobs, dropped items, minecarts, boats, arrows, anything that
moves through the event horizon gets sent through too. Non-player entities can
only travel to a same-server destination, though; there's no way to carry an
arbitrary entity's state across the Velocity/Bungee plugin-messaging hop, so a
cross-server gate just won't move anything but players.
- The **iris shield** is a separate, physical thing from the wormhole connection.
**Right-click a button placed directly below the sign** to toggle it. Closed means
closed - `gate.iris-shield-material` (default bedrock), solid, blocking everything -
whether or not the gate is even connected right now, and travel is refused even if
something manages to occupy that space anyway (flight, an elytra, a vehicle - not
just relying on block collision). It blocks in both directions: a closed shield on
the gate you're dialing *from* stops you from ever stepping in, and a closed shield
on the *destination* refuses the trip too, matching a real iris stopping anything
that hits it from either side. With the shield open, the interior just reflects the
connection state: `gate.iris-idle-material` (default air) if idle, or the event
horizon if actively dialed. Closing needs nothing extra; if the gate has a pin set
(`/sg pin <code>`), opening a closed shield prompts you to type the code in chat
within `gate.pin-timeout-seconds` before it'll open.
## Multi-world
Gates are addressed by network name, not world — a gate on any loaded world can dial
any other gate on the same network regardless of world, exactly like same-server
cross-world travel in the original Stargate mod.
## Cross-server (Velocity / Bungee)
1. Set `storage.type: mysql` in every backend server's `config.yml` and point them at
the **same** database — this is how servers see each other's gates. Add any
connection parameters your host requires (TLS, timezone, etc.) under
`storage.mysql.properties` - each key becomes `?key=value` on the JDBC URL. It
defaults to `useSSL: false` / `autoReconnect: true`; list a key here to override
that default (e.g. `useSSL: true` plus `sslMode: REQUIRED`).
2. Give each backend a unique `server-id` in `config.yml` that matches its name in
the proxy config (`velocity.toml` / `config.yml` servers list).
3. Set `cross-server.enabled: true` on every backend.
4. Drop `stargate-velocity` (or `stargate-bungee`) into the proxy's plugin folder.
When a player dials a gate hosted on another backend, the Paper plugin asks the
proxy (over the `stargate:teleport` plugin channel) to connect the player to that
server; once they land, the proxy forwards a delivery message so the destination
server's Stargate instance teleports them to the gate's exit point.
Each backend only loads gates from the shared database at startup (and on
`/sg reload`), then polls for what other servers have added, changed, or removed
every `cross-server.refresh-seconds` (default 30) while `cross-server.enabled: true`.
So a gate built on one server shows up as a destination on another within that
window automatically - no restart or manual reload needed, though `/sg reload`
still works if you want it immediately.
## Permissions
- `stargate.use` (default: true) — dial/cycle gates
- `stargate.create` (default: op) — build new gates
- `stargate.destroy` (default: op) — break your own gates
- `stargate.admin` (default: op) — reload, break/manage any gate
## Commands
- `/sg list [network]`
- `/sg networks`
- `/sg destroy` (look at a gate's sign)
- `/sg pin <code|clear>` (look at a gate's sign) — requires that code in chat before a
closed iris shield will open on that gate
- `/sg reload`