fix(velocity): 新增 /felis lobby,/felis go lobby 也能回大厅
This commit is contained in:
4 files changed
+95
No files matched your search
@@ -173,6 +173,7 @@ What it does when routing is active:
|
|||||||
| Server-list ping (`ProxyPingEvent`) | Answers from the cached lifecycle view with a phase-aware MOTD (online / starting / sleeping) — **read-only, never wakes** anything. Mirroring each backend's own MOTD by background-pinging ready servers is a later slice. |
|
| Server-list ping (`ProxyPingEvent`) | Answers from the cached lifecycle view with a phase-aware MOTD (online / starting / sleeping) — **read-only, never wakes** anything. Mirroring each backend's own MOTD by background-pinging ready servers is a later slice. |
|
||||||
| Join report (`ServerConnectedEvent`) | Reports real joins to a felis backend via `POST …/join-event`, so the reaper sees activity and the player is auto-added to the server allowlist. |
|
| Join report (`ServerConnectedEvent`) | Reports real joins to a felis backend via `POST …/join-event`, so the reaper sees activity and the player is auto-added to the server allowlist. |
|
||||||
| `/felis`, `/felis list` | Operator status: online-mode, root-domain, lobby, and the known server set with phase/ready. |
|
| `/felis`, `/felis list` | Operator status: online-mode, root-domain, lobby, and the known server set with phase/ready. |
|
||||||
|
| `/felis lobby`, `/felis go <lobby>` | Moves the player back to the lobby from any backend. Nothing is woken, and a wait already queued still moves them when its server is ready. Kept under `/felis` so a user server's own `/lobby` or `/hub` is not shadowed by the proxy. |
|
||||||
|
|
||||||
Velocity-only config keys (read from the same `felis-link.properties` / env as
|
Velocity-only config keys (read from the same `felis-link.properties` / env as
|
||||||
`/link`; env wins):
|
`/link`; env wins):
|
||||||
|
|||||||
@@ -439,6 +439,11 @@ public final class FelisVelocityPlugin {
|
|||||||
sendServerList(ctx.getSource());
|
sendServerList(ctx.getSource());
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}))
|
}))
|
||||||
|
.then(BrigadierCommand.literalArgumentBuilder("lobby")
|
||||||
|
.executes(ctx -> {
|
||||||
|
doLobby(ctx.getSource());
|
||||||
|
return Command.SINGLE_SUCCESS;
|
||||||
|
}))
|
||||||
.then(BrigadierCommand.literalArgumentBuilder("go")
|
.then(BrigadierCommand.literalArgumentBuilder("go")
|
||||||
.then(BrigadierCommand.requiredArgumentBuilder("server", StringArgumentType.word())
|
.then(BrigadierCommand.requiredArgumentBuilder("server", StringArgumentType.word())
|
||||||
.executes(ctx -> {
|
.executes(ctx -> {
|
||||||
@@ -569,6 +574,8 @@ public final class FelisVelocityPlugin {
|
|||||||
zh ? "此代理已知的 felis 服务器" : "the felis servers this proxy knows");
|
zh ? "此代理已知的 felis 服务器" : "the felis servers this proxy knows");
|
||||||
helpLine(source, "/felis go <server>",
|
helpLine(source, "/felis go <server>",
|
||||||
zh ? "启动服务器并在就绪后把你传送过去" : "start a server and move you in when it's ready");
|
zh ? "启动服务器并在就绪后把你传送过去" : "start a server and move you in when it's ready");
|
||||||
|
helpLine(source, "/felis lobby",
|
||||||
|
zh ? "回到大厅" : "back to the lobby");
|
||||||
helpLine(source, "/felis claim",
|
helpLine(source, "/felis claim",
|
||||||
zh ? "认领你所在的服务器" : "take ownership of the server you're on");
|
zh ? "认领你所在的服务器" : "take ownership of the server you're on");
|
||||||
helpLine(source, "/felis migrate",
|
helpLine(source, "/felis migrate",
|
||||||
@@ -626,6 +633,11 @@ public final class FelisVelocityPlugin {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String target = serverArg.trim();
|
String target = serverArg.trim();
|
||||||
|
if (target.equalsIgnoreCase(config.lobbyServer())) {
|
||||||
|
// The lobby is a system server, so the loop below would call it unknown.
|
||||||
|
router.toLobby(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
ServerView match = null;
|
ServerView match = null;
|
||||||
for (ServerView v : registry.all()) {
|
for (ServerView v : registry.all()) {
|
||||||
if (!isSystemServer(v.name()) && v.name().equalsIgnoreCase(target)) {
|
if (!isSystemServer(v.name()) && v.name().equalsIgnoreCase(target)) {
|
||||||
@@ -659,6 +671,18 @@ public final class FelisVelocityPlugin {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void doLobby(CommandSource source) {
|
||||||
|
Player player = requirePlayer(source);
|
||||||
|
if (player == null || !ensureOutOfLimbo(player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!routingActive) {
|
||||||
|
player.sendMessage(routingDisabled(zh(player)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
router.toLobby(player);
|
||||||
|
}
|
||||||
|
|
||||||
private void doClaim(CommandSource source) {
|
private void doClaim(CommandSource source) {
|
||||||
Player player = requirePlayer(source);
|
Player player = requirePlayer(source);
|
||||||
if (player == null || !ensureOutOfLimbo(player)) {
|
if (player == null || !ensureOutOfLimbo(player)) {
|
||||||
|
|||||||
@@ -225,6 +225,33 @@ public final class WaitingRouter {
|
|||||||
authorizeAndWait(player, serverName, false);
|
authorizeAndWait(player, serverName, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toLobby moves a player who is already past the login gate back to the lobby, for
|
||||||
|
* {@code /felis lobby} and {@code /felis go <lobby>}. The lobby is always up, so there
|
||||||
|
* is nothing to wake and no queue to join; a failed connect is reported like any other
|
||||||
|
* transfer. A wait the player joined earlier stays: the lobby is where the queue parks
|
||||||
|
* players anyway, and it still moves them when their server is ready.
|
||||||
|
*/
|
||||||
|
void toLobby(Player player) {
|
||||||
|
boolean zh = FelisVelocityPlugin.zh(player);
|
||||||
|
if (player.getCurrentServer()
|
||||||
|
.map(sc -> serverNamed(sc.getServer(), lobbyServer))
|
||||||
|
.orElse(false)) {
|
||||||
|
player.sendMessage(Component.text(
|
||||||
|
zh ? "你已经在大厅了。" : "You're already in the lobby.", NamedTextColor.YELLOW));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Optional<RegisteredServer> lobby = proxy.getServer(lobbyServer);
|
||||||
|
if (lobby.isEmpty()) {
|
||||||
|
log.warn("Felis: {} asked for the lobby but '{}' is not registered", player.getUniqueId(), lobbyServer);
|
||||||
|
player.sendMessage(Component.text(
|
||||||
|
zh ? "大厅暂时不可用,请稍后再试。" : "The lobby is unavailable right now. Try again shortly.",
|
||||||
|
NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transfer(player, lobbyServer, lobby.get());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* enqueueFromInvite is {@link #enqueueFromCommand} for an accepted invite. An invite can
|
* enqueueFromInvite is {@link #enqueueFromCommand} for an accepted invite. An invite can
|
||||||
* only name the server its sender is standing on, so the target is running by
|
* only name the server its sender is standing on, so the target is running by
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public final class WaitingRouterTest {
|
|||||||
longStart();
|
longStart();
|
||||||
menuAndCommands();
|
menuAndCommands();
|
||||||
joins();
|
joins();
|
||||||
|
backToLobby();
|
||||||
disconnectAndRelease();
|
disconnectAndRelease();
|
||||||
} finally {
|
} finally {
|
||||||
api.close();
|
api.close();
|
||||||
@@ -605,6 +606,43 @@ public final class WaitingRouterTest {
|
|||||||
assertEq("refused transfer: no retry advice", false, full.said("try again"));
|
assertEq("refused transfer: no retry advice", false, full.said("try again"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /felis lobby and /felis go lobby: the lobby is a system server, so it never
|
||||||
|
// appeared among the servers /felis go knows.
|
||||||
|
private static void backToLobby() {
|
||||||
|
Fakes.FakePlayer back = player(null, true);
|
||||||
|
back.current = beta;
|
||||||
|
router.toLobby(back.player);
|
||||||
|
assertEq("from a server: moved to the lobby", List.of("lobby"), List.copyOf(back.connects));
|
||||||
|
assertEq("from a server: nothing asked of felis-api", 0, api.count(LINK + back.id));
|
||||||
|
|
||||||
|
Fakes.FakePlayer there = player(null, true);
|
||||||
|
there.current = lobby;
|
||||||
|
router.toLobby(there.player);
|
||||||
|
assertEq("in the lobby: told", true, there.said("You're already in the lobby."));
|
||||||
|
assertEq("in the lobby: no move", 0, there.connects.size());
|
||||||
|
|
||||||
|
Fakes.FakePlayer between = player(null, true);
|
||||||
|
router.toLobby(between.player);
|
||||||
|
assertEq("on no server yet: moved to the lobby", List.of("lobby"), List.copyOf(between.connects));
|
||||||
|
|
||||||
|
// A wait survives the trip: the queue parks players in the lobby anyway.
|
||||||
|
Fakes.FakePlayer waiter = player(null, true);
|
||||||
|
waiter.current = beta;
|
||||||
|
int before = router.waitingCount();
|
||||||
|
router.enqueueFromCommand(waiter.player, "zeta");
|
||||||
|
Fakes.await("waiter queued", () -> router.waitingCount() == before + 1);
|
||||||
|
router.toLobby(waiter.player);
|
||||||
|
assertEq("waiting: moved to the lobby", List.of("lobby"), List.copyOf(waiter.connects));
|
||||||
|
assertEq("waiting: still queued", before + 1, router.waitingCount());
|
||||||
|
router.onDisconnect(new DisconnectEvent(waiter.player, DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN));
|
||||||
|
|
||||||
|
Fakes.FakePlayer full = player(null, true);
|
||||||
|
full.current = beta;
|
||||||
|
full.refusals.put("lobby", "Server is full");
|
||||||
|
router.toLobby(full.player);
|
||||||
|
Fakes.await("lobby refused: told", () -> full.said("Couldn't connect you to « lobby ». Reason: Server is full"));
|
||||||
|
}
|
||||||
|
|
||||||
private static void disconnectAndRelease() {
|
private static void disconnectAndRelease() {
|
||||||
// Leaving the proxy drops the queue entry.
|
// Leaving the proxy drops the queue entry.
|
||||||
Fakes.FakePlayer leaver = player(null, true);
|
Fakes.FakePlayer leaver = player(null, true);
|
||||||
@@ -648,6 +686,11 @@ public final class WaitingRouterTest {
|
|||||||
router.releaseFromLogin(early.player);
|
router.releaseFromLogin(early.player);
|
||||||
assertEq("no lobby yet: nothing sent", 0, early.connects.size());
|
assertEq("no lobby yet: nothing sent", 0, early.connects.size());
|
||||||
assertEq("no lobby yet: logged", 1, log.count("WARN", "login release for " + early.id + " but the lobby"));
|
assertEq("no lobby yet: logged", 1, log.count("WARN", "login release for " + early.id + " but the lobby"));
|
||||||
|
Fakes.FakePlayer asker = player(null, true);
|
||||||
|
asker.current = beta;
|
||||||
|
router.toLobby(asker.player);
|
||||||
|
assertEq("no lobby: told", true, asker.said("The lobby is unavailable right now."));
|
||||||
|
assertEq("no lobby: no move", 0, asker.connects.size());
|
||||||
|
|
||||||
// With no login gate registered a host-routed player is turned away.
|
// With no login gate registered a host-routed player is turned away.
|
||||||
net.remove("login");
|
net.remove("login");
|
||||||
|
|||||||
Reference in new issue
Block a user