Skip to content

Gateway (PackGateway proxy plugin + PackGatewayBackend)

Checks every player's modpack version at the proxy and sends them to the right first server: modded players to the modded hub, unmodded players to the info hub, and outdated packs to an update screen. Its main half can send brand-new players to a tutorial spot with a Pokégear.

What it does

One jar holds two plugins. The same build is installed in two places:

Part Runs on Live file name Plugin name
Proxy plugin Velocity plugins/GatewayVelocity-<version>.jar PackGateway (id packgateway)
Backend plugin main plugins/PackGatewayBackend-<version>.jar PackGatewayBackend

Proxy part (PackGateway):

  • When a player logs in, asks their client for its PackGateway version over the login channel packgateway:version_check. It also reads the NeoForge mod list as a backup.
  • If the pack is too old, disconnects them with an "update your modpack" screen that has a clickable CurseForge download link (and an optional Discord link).
  • Picks the first server for every connection, so players never fall through to Velocity's try list:
  • Modded client with a good pack: modded-hub-server (hub).
  • Unmodded client: vanilla-info-server (info).
  • Brand-new modded player, when first-join routing is on: new-player-main-server (main), with a one-time tutorial marker.
  • If a player is kicked from the modded hub, sends them to the info hub with "You need the modpack to play on this server!"
  • Only speaks when it ends a connection. It does not message players who are connected.

Backend part (PackGatewayBackend, on main):

  • When a player arrives with the tutorial marker, waits half a second, then teleports them to the saved tutorial spot and gives them a Pokégear item.
  • A natural first join also needs Bukkit to agree the player has never played on main. A staff test (see below) skips that check.
  • Right-clicking the Pokégear runs /pokegear (the Poke Gear menu). Only the tagged item does this. Plain Camping Gear is not affected.

How players use it

Players do not run any commands.

  • With the right modpack, they land on the modded hub as normal.
  • Without the modpack, they land on the info hub.
  • With an old pack, they see "Modpack Update Required", their version, the required version, and an [Open Modpack Download] button.
  • Brand-new players (if first-join routing is on) start on main at the tutorial spot, holding a Pokégear. Right-click it to open the menu. It is a normal item: it can be dropped or lost on death. Staff can give a new one.

Staff guide

Proxy commands (run in-game on any server, or in the Velocity console). Alias /pgateway.

Task Command Permission
Reload the proxy config /packgateway reload packgateway.reload
Queue a one-time first-join test for a player /packgateway firstjoin reset <player-or-uuid> packgateway.firstjoin.reset
Cancel a queued test /packgateway firstjoin cancel <player-or-uuid> packgateway.firstjoin.reset

A queued test sends that player through the new-player path once, on their next login, then clears itself. It never removes them from the real join history. The target must be online or already in the join history.

These are proxy permissions, so grant them with lpv (LuckPerms on Velocity), not lp. Staff ranks do not have them by default. The proxy console always works.

Backend commands (on main). All need packgateway.firstjoin.set (op by default).

Task Command
Save the tutorial spot where you stand, facing the way you face /firstjoin set
Show the saved tutorial spot /firstjoin show
Teleport yourself to the tutorial spot /firstjoin test
Give yourself a Pokégear /firstjoin gear
Give a player a Pokégear /firstjoin gear Steve

/firstjoin set takes effect at once. Command replies show in the action bar. Test the Pokégear as a non-op, because ops skip CommandWhitelist.

Configuration

Proxy: plugins/packgateway/config.yml on Velocity. /packgateway reload re-reads it. If the file fails to parse, the proxy refuses new logins with "PackGateway is not configured correctly" until it is fixed and reloaded.

Key What it is for
current-version Lowest accepted pack version. "1.1" also accepts 1.1.0, 1.1.5, and so on
allow-newer-versions Keep true. current-version is bumped only for breaking pack changes, so it is usually behind the shipped pack. false would kick everyone on a newer pack
modded-hub-server, vanilla-info-server Velocity server names for modded and unmodded players
version-mod-ids Mod id that carries the pack version. Must be packgateway
routing-enabled Kill switch. false hands all routing back to velocity.toml. Use only to triage a broken gate
ignored-hosts Hostnames the gate leaves alone, so Velocity's [forced-hosts] picks the server. Exact match only, no wildcards. A wrong entry silently turns off the gate for real players. Must be a top-level key
outdated-message-* Text for the update screen. {required_version} and {client_version} work. -link-line and -discord-line are URLs, not text; https:// is added if missing
first-join-routing-enabled Turns on the new-player route to main. Default false
first-join-history-file NetworkJoinMessages' joined.txt (needs FirstJoinStorageType: TEXT)
protected-existing-users-file Frozen list of known players, always treated as returning
first-join-history-minimum-records If the history file has fewer rows than this, first-join routing turns itself off
new-player-main-server Where new players go (main)

The download link must be the CurseForge project page, not the website, and it must match the client mod's own error screen link in scripts/update_staging_mods/modpack-overrides/config/packgateway-client.properties (ops repo). Old keys scanned-hosts and host-filter-enabled are ignored and log a warning.

Backend: plugins/PackGatewayBackend/tutorial-location.properties on main holds the tutorial spot (world UUID, world name, x, y, z, yaw, pitch). Change it with /firstjoin set, not by hand. Hand edits need a restart.

A new jar on either side needs a restart of that server. When rolling out a first-join change, restart main first, check its boot, then restart Velocity.

Data and storage

File Where What it holds
config.yml Velocity plugins/packgateway/ Proxy settings
first-join-test-overrides.txt Velocity plugins/packgateway/ Queued one-time tests (UUID and name). Safe to edit while no test is wanted
protected-existing-users.txt Velocity plugins/packgateway/ Frozen list of returning players. Do not delete: a missing or broken file turns first-join routing off
joined.txt Velocity plugins/networkjoinmessages/ Owned by NetworkJoinMessages. Read only
tutorial-location.properties main plugins/PackGatewayBackend/ Tutorial spot

The tutorial marker travels in the forwarded login profile as property packgateway_first_join. The proxy removes it after the first server connection, so it never carries over to a later server switch. The Pokégear is tagged with the item data key packgatewaybackend:pokegear.

Troubleshooting

Symptom Cause Fix
Modded players land on info, or get kicked and crash The gate is not seeing the client's version Check the Velocity log for Sent version query and responded to version query. Check routing-enabled and ignored-hosts
Everyone with the current pack is kicked as outdated allow-newer-versions is false, or current-version is too high Set allow-newer-versions: true and reload
"PackGateway is not configured correctly" Proxy config.yml did not parse Read Could not read config.yml in the Velocity log. A common cause is a nested ignored-hosts key
"Modded server is unavailable" modded-hub-server or new-player-main-server is not in velocity.toml Fix the name
New players go to hub instead of main First-join routing off, or history unavailable Look for First-join registries are unavailable. Check the history and protected files exist and pass the minimum row count
New player reached main but was not teleported Tutorial spot not set, or they had played on main before /firstjoin show. Look for Ignored natural first-join marker
No Pokégear given pixelmon:camping_gear did not resolve Look for did not resolve in the main boot log
Right-click does nothing Item is not the tagged one, or /pokegear is blocked Give a fresh one with /firstjoin gear; test as a non-op

Good boot lines:

Velocity: PackGateway plugin loaded.   (then a config summary, including "Routing enabled: true")
main:     Pokégear item: pixelmon:camping_gear -> PIXELMON_CAMPING_GEAR
main:     First-join tutorial location: world <x> <y> <z> yaw=... pitch=...
main:     Reading PackGateway first-join markers from the Velocity-forwarded profile.

Maintainer notes

  • The repo is named GatewayVelocity, but the Velocity plugin id is packgateway. The proxy part must not use Bukkit APIs, and the backend part must not use Velocity APIs. Shared code is in com.snowbird.gateway.common.
  • velocity-plugin.json is generated from the @Plugin annotation. Keep the annotation version and build.gradle version in sync. plugin.yml takes its version from Gradle.
  • Host matching strips the FML null-byte marker and anything after a TCPShield ///, then compares exactly. Earlier versions used an allow-list (scanned-hosts) that missed the live domain and turned the gate off. 1.1.0 switched to the deny-list ignored-hosts.
  • The login query is the main signal because TCPShield can drop the FML host marker. The mod-list check is a backup.
  • If a natural first-join handoff lands on the wrong server, the proxy queues a one-time test for that player so the tutorial still happens on their next login.
  • Older ops notes say the backend runs /warp tutorial. Since 1.2.2 it uses its own saved spot instead, seeded from that warp.
  • Deploy history and bundles: manual_uploads/*packgateway* and manual_uploads/2026-09-14-first-join-* in the ops repo. Server notes: documentation/servers/velocity.md.

Building

./gradlew clean test build

Java 21. Output: build/libs/GatewayVelocity-<version>.jar. Install that one file on Velocity as GatewayVelocity-<version>.jar and on main as PackGatewayBackend-<version>.jar. Twelve unit tests cover the signal codec, join history, test overrides, and the location store.

Full command, permission and config tables: Reference.