Loading plugins/README.md +7 −2 Changes for plugins/README.md: 7 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -326,8 +326,13 @@ minutes; subsequent builds are fast. Jars land in each module's `build/libs`. CI both gates: `bash plugins/test.sh` (JDK 25 — the install-time plugins, the codec/invite/server-list tests, and the proxy routing tests that drive ServerRegistry, WaitingRouter and ControlChannel against the real velocity-api; run those alone with `plugins/velocity/gradlew -p plugins/velocity routingTest`) and `bash plugins/test-mods.sh` (JDK 17 — the three loader mods, via the wrappers above). `plugins/velocity/gradlew -p plugins/velocity routingTest`. It also runs the lobby menu test that drives LobbyMenu against the real paper-api (`plugins/paper/gradlew -p plugins/paper lobbyTest`), the login gate test that drives LoginFlow against a stub felis-api on virtual ticks (`plugins/limbo/gradlew -p plugins/limbo -PlimboVersion=<lock's LIMBO_VERSION> loginTest`), and ModLinkTest for the `/link` the loader mods share) and `bash plugins/test-mods.sh` (JDK 17 — the three loader mods, via the wrappers above). ### Dependency verification Loading plugins/fabric/src/main/java/best/lolicon/felis/fabric/FelisFabricMod.java +45 −44 Changes for plugins/fabric/src/main/java/best/lolicon/felis/fabric/FelisFabricMod.java: 45 added lines, 44 removed lines. Original line number Diff line number Diff line package best.lolicon.felis.fabric; import best.lolicon.felis.link.LinkClient; import best.lolicon.felis.link.LinkCode; import best.lolicon.felis.link.LinkConfig; import best.lolicon.felis.link.LinkConfigLoader; import best.lolicon.felis.link.LinkException; import best.lolicon.felis.link.ModLinkPolicy; import best.lolicon.felis.link.ModLink; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.exceptions.CommandSyntaxException; Loading @@ -16,40 +14,39 @@ import net.fabricmc.loader.api.FabricLoader; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import org.slf4j.Logger; import java.io.IOException; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * FelisFabricMod is the Fabric (dedicated-server) leg of the §10 account-link * flow. A server-side {@code /link} command takes the player's already-verified * UUID, asks felis-api for a one-time code, and shows it in chat; the player then * redeems it on the web console. The HTTP call is pushed onto a daemon I/O thread * and the reply is hopped back onto the server thread, so a slow felis-api never * stalls the tick loop. Failures collapse to a generic chat line with details * confined to the server log. * redeems it on the web console. The command itself is the shared {@link ModLink} * (the HTTP call on a daemon I/O thread, the reply back on the server thread, one * generic line for any failure); this class registers it and adapts the command * source. */ public final class FelisFabricMod implements DedicatedServerModInitializer { private static final Logger LOGGER = LogUtils.getLogger(); private final ExecutorService io = Executors.newSingleThreadExecutor(r -> { private final Executor io = Executors.newSingleThreadExecutor(r -> { Thread t = new Thread(r, "felis-link-io"); t.setDaemon(true); return t; }); private LinkClient linkClient; private ModLink link; @Override public void onInitializeServer() { try { LinkConfig config = LinkConfigLoader.load( FabricLoader.getInstance().getConfigDir().resolve("felis-link.properties")); this.linkClient = new LinkClient(config); this.link = new ModLink(new LinkClient(config), io, LOGGER::warn); } catch (IOException e) { LOGGER.error("Felis link disabled: {}", e.getMessage()); return; Loading @@ -60,44 +57,48 @@ public final class FelisFabricMod implements DedicatedServerModInitializer { } private void register(CommandDispatcher<CommandSourceStack> dispatcher) { dispatcher.register(Commands.literal("link").executes(ctx -> { CommandSourceStack source = ctx.getSource(); ServerPlayer player; dispatcher.register(Commands.literal("link").executes(ctx -> link.run(new LinkSource(ctx.getSource())))); } /** LinkSource is a command source as the shared /link sees it. */ private static final class LinkSource implements ModLink.Source { private final CommandSourceStack source; private final ServerPlayer player; // null for the console or a command block LinkSource(CommandSourceStack source) { this.source = source; ServerPlayer p; try { player = source.getPlayerOrException(); p = source.getPlayerOrException(); } catch (CommandSyntaxException e) { source.sendFailure(Component.literal("/link 只能由玩家执行 / /link can only be run by a player.")); return 0; p = null; } if (!source.getServer().usesAuthentication()) { LOGGER.warn(ModLinkPolicy.OFFLINE_LOG); source.sendFailure(Component.literal(ModLinkPolicy.OFFLINE_REPLY)); return 0; this.player = p; } requestAndReply(source.getServer(), player); return 1; })); @Override public UUID player() { return player != null ? player.getUUID() : null; } private void requestAndReply(MinecraftServer server, ServerPlayer player) { UUID uuid = player.getUUID(); player.sendSystemMessage(Component.literal("正在获取绑定码… / Requesting a link code…")); io.submit(() -> { try { LinkCode code = linkClient.requestCode(uuid); server.execute(() -> { player.sendSystemMessage(Component.literal( "绑定码 / Link code: " + code.code() + "(几分钟内有效 / valid a few minutes)")); player.sendSystemMessage(Component.literal(code.panelUrl() != null ? "在此完成绑定 / Finish linking at: " + code.panelUrl() : "在网页控制台 → 账户 中输入 / Enter it on the web console → Account.")); }); } catch (LinkException e) { LOGGER.warn("link code request failed for {} (status={}, code={}): {}", uuid, e.statusCode(), e.errorCode(), e.getMessage()); server.execute(() -> player.sendSystemMessage(Component.literal( "现在无法获取绑定码,请稍后再试 / Couldn't get a link code right now. Please try again in a moment."))); @Override public boolean onlineMode() { return source.getServer().usesAuthentication(); } @Override public void fail(String line) { source.sendFailure(Component.literal(line)); } @Override public void tell(String line) { player.sendSystemMessage(Component.literal(line)); } @Override public void onServerThread(Runnable task) { source.getServer().execute(task); } }); } } plugins/forge/src/main/java/best/lolicon/felis/forge/FelisForgeMod.java +45 −44 Changes for plugins/forge/src/main/java/best/lolicon/felis/forge/FelisForgeMod.java: 45 added lines, 44 removed lines. Original line number Diff line number Diff line package best.lolicon.felis.forge; import best.lolicon.felis.link.LinkClient; import best.lolicon.felis.link.LinkCode; import best.lolicon.felis.link.LinkConfig; import best.lolicon.felis.link.LinkConfigLoader; import best.lolicon.felis.link.LinkException; import best.lolicon.felis.link.ModLinkPolicy; import best.lolicon.felis.link.ModLink; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.exceptions.CommandSyntaxException; Loading @@ -13,7 +11,6 @@ import com.mojang.logging.LogUtils; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.RegisterCommandsEvent; Loading @@ -24,34 +21,34 @@ import org.slf4j.Logger; import java.io.IOException; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * FelisForgeMod is the Forge server-side leg of the §10 account-link flow. On * {@link RegisterCommandsEvent} it installs a {@code /link} command that mints a * one-time code from felis-api for the player's already-verified UUID. As on the * other loaders, the HTTP call runs on a daemon I/O thread and the reply is hopped * back onto the server thread; failures collapse to a generic chat line with * details kept to the server log. If config is missing the mod stays loaded but * one-time code from felis-api for the player's already-verified UUID. The command * is the shared {@link ModLink}, as on the other loaders (the HTTP call on a daemon * I/O thread, the reply back on the server thread, one generic line for any * failure). If config is missing the mod stays loaded but * never registers the command, so the proxy/server runs un-crippled. */ @Mod("felis_link") public final class FelisForgeMod { private static final Logger LOGGER = LogUtils.getLogger(); private final ExecutorService io = Executors.newSingleThreadExecutor(r -> { private final Executor io = Executors.newSingleThreadExecutor(r -> { Thread t = new Thread(r, "felis-link-io"); t.setDaemon(true); return t; }); private LinkClient linkClient; private ModLink link; public FelisForgeMod() { try { LinkConfig config = LinkConfigLoader.load( FMLPaths.CONFIGDIR.get().resolve("felis-link.properties")); this.linkClient = new LinkClient(config); this.link = new ModLink(new LinkClient(config), io, LOGGER::warn); MinecraftForge.EVENT_BUS.register(this); LOGGER.info("Felis link ready; /link will be registered."); } catch (IOException e) { Loading @@ -65,44 +62,48 @@ public final class FelisForgeMod { } private void register(CommandDispatcher<CommandSourceStack> dispatcher) { dispatcher.register(Commands.literal("link").executes(ctx -> { CommandSourceStack source = ctx.getSource(); ServerPlayer player; dispatcher.register(Commands.literal("link").executes(ctx -> link.run(new LinkSource(ctx.getSource())))); } /** LinkSource is a command source as the shared /link sees it. */ private static final class LinkSource implements ModLink.Source { private final CommandSourceStack source; private final ServerPlayer player; // null for the console or a command block LinkSource(CommandSourceStack source) { this.source = source; ServerPlayer p; try { player = source.getPlayerOrException(); p = source.getPlayerOrException(); } catch (CommandSyntaxException e) { source.sendFailure(Component.literal("/link 只能由玩家执行 / /link can only be run by a player.")); return 0; p = null; } if (!source.getServer().usesAuthentication()) { LOGGER.warn(ModLinkPolicy.OFFLINE_LOG); source.sendFailure(Component.literal(ModLinkPolicy.OFFLINE_REPLY)); return 0; this.player = p; } requestAndReply(source.getServer(), player); return 1; })); @Override public UUID player() { return player != null ? player.getUUID() : null; } private void requestAndReply(MinecraftServer server, ServerPlayer player) { UUID uuid = player.getUUID(); player.sendSystemMessage(Component.literal("正在获取绑定码… / Requesting a link code…")); io.submit(() -> { try { LinkCode code = linkClient.requestCode(uuid); server.execute(() -> { player.sendSystemMessage(Component.literal( "绑定码 / Link code: " + code.code() + "(几分钟内有效 / valid a few minutes)")); player.sendSystemMessage(Component.literal(code.panelUrl() != null ? "在此完成绑定 / Finish linking at: " + code.panelUrl() : "在网页控制台 → 账户 中输入 / Enter it on the web console → Account.")); }); } catch (LinkException e) { LOGGER.warn("link code request failed for {} (status={}, code={}): {}", uuid, e.statusCode(), e.errorCode(), e.getMessage()); server.execute(() -> player.sendSystemMessage(Component.literal( "现在无法获取绑定码,请稍后再试 / Couldn't get a link code right now. Please try again in a moment."))); @Override public boolean onlineMode() { return source.getServer().usesAuthentication(); } @Override public void fail(String line) { source.sendFailure(Component.literal(line)); } @Override public void tell(String line) { player.sendSystemMessage(Component.literal(line)); } @Override public void onServerThread(Runnable task) { source.getServer().execute(task); } }); } } plugins/limbo/build.gradle +23 −0 Changes for plugins/limbo/build.gradle: 23 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -58,3 +58,26 @@ tasks.withType(JavaCompile).configureEach { // bytecode so the plugin loads on any Limbo running Java 17+. options.release = 17 } // The login gate's self-test: LoginFlowTest runs the whole login flow on a virtual // clock (a fake scheduler and players around the real LoginFlow, the real felis-api // clients against a stub) plus the readiness endpoint, the env parsing and the book. // A framework-free main like the other plugins' tests; plugins/test.sh runs // `./gradlew loginTest`. The Limbo jar is on the classpath for the adventure types the // book is built from. sourceSets { loginTest { java { srcDir 'test' } compileClasspath += sourceSets.main.output + configurations.compileClasspath runtimeClasspath += output + compileClasspath } } tasks.register('loginTest', JavaExec) { group = 'verification' description = 'Runs the LoginFlowTest self-test main.' classpath = sourceSets.loginTest.runtimeClasspath mainClass = 'best.lolicon.felis.limbo.LoginFlowTest' } plugins/limbo/src/main/java/best/lolicon/felis/limbo/FelisLimboPlugin.java +91 −366 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
plugins/README.md +7 −2 Changes for plugins/README.md: 7 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -326,8 +326,13 @@ minutes; subsequent builds are fast. Jars land in each module's `build/libs`. CI both gates: `bash plugins/test.sh` (JDK 25 — the install-time plugins, the codec/invite/server-list tests, and the proxy routing tests that drive ServerRegistry, WaitingRouter and ControlChannel against the real velocity-api; run those alone with `plugins/velocity/gradlew -p plugins/velocity routingTest`) and `bash plugins/test-mods.sh` (JDK 17 — the three loader mods, via the wrappers above). `plugins/velocity/gradlew -p plugins/velocity routingTest`. It also runs the lobby menu test that drives LobbyMenu against the real paper-api (`plugins/paper/gradlew -p plugins/paper lobbyTest`), the login gate test that drives LoginFlow against a stub felis-api on virtual ticks (`plugins/limbo/gradlew -p plugins/limbo -PlimboVersion=<lock's LIMBO_VERSION> loginTest`), and ModLinkTest for the `/link` the loader mods share) and `bash plugins/test-mods.sh` (JDK 17 — the three loader mods, via the wrappers above). ### Dependency verification Loading
plugins/fabric/src/main/java/best/lolicon/felis/fabric/FelisFabricMod.java +45 −44 Changes for plugins/fabric/src/main/java/best/lolicon/felis/fabric/FelisFabricMod.java: 45 added lines, 44 removed lines. Original line number Diff line number Diff line package best.lolicon.felis.fabric; import best.lolicon.felis.link.LinkClient; import best.lolicon.felis.link.LinkCode; import best.lolicon.felis.link.LinkConfig; import best.lolicon.felis.link.LinkConfigLoader; import best.lolicon.felis.link.LinkException; import best.lolicon.felis.link.ModLinkPolicy; import best.lolicon.felis.link.ModLink; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.exceptions.CommandSyntaxException; Loading @@ -16,40 +14,39 @@ import net.fabricmc.loader.api.FabricLoader; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import org.slf4j.Logger; import java.io.IOException; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * FelisFabricMod is the Fabric (dedicated-server) leg of the §10 account-link * flow. A server-side {@code /link} command takes the player's already-verified * UUID, asks felis-api for a one-time code, and shows it in chat; the player then * redeems it on the web console. The HTTP call is pushed onto a daemon I/O thread * and the reply is hopped back onto the server thread, so a slow felis-api never * stalls the tick loop. Failures collapse to a generic chat line with details * confined to the server log. * redeems it on the web console. The command itself is the shared {@link ModLink} * (the HTTP call on a daemon I/O thread, the reply back on the server thread, one * generic line for any failure); this class registers it and adapts the command * source. */ public final class FelisFabricMod implements DedicatedServerModInitializer { private static final Logger LOGGER = LogUtils.getLogger(); private final ExecutorService io = Executors.newSingleThreadExecutor(r -> { private final Executor io = Executors.newSingleThreadExecutor(r -> { Thread t = new Thread(r, "felis-link-io"); t.setDaemon(true); return t; }); private LinkClient linkClient; private ModLink link; @Override public void onInitializeServer() { try { LinkConfig config = LinkConfigLoader.load( FabricLoader.getInstance().getConfigDir().resolve("felis-link.properties")); this.linkClient = new LinkClient(config); this.link = new ModLink(new LinkClient(config), io, LOGGER::warn); } catch (IOException e) { LOGGER.error("Felis link disabled: {}", e.getMessage()); return; Loading @@ -60,44 +57,48 @@ public final class FelisFabricMod implements DedicatedServerModInitializer { } private void register(CommandDispatcher<CommandSourceStack> dispatcher) { dispatcher.register(Commands.literal("link").executes(ctx -> { CommandSourceStack source = ctx.getSource(); ServerPlayer player; dispatcher.register(Commands.literal("link").executes(ctx -> link.run(new LinkSource(ctx.getSource())))); } /** LinkSource is a command source as the shared /link sees it. */ private static final class LinkSource implements ModLink.Source { private final CommandSourceStack source; private final ServerPlayer player; // null for the console or a command block LinkSource(CommandSourceStack source) { this.source = source; ServerPlayer p; try { player = source.getPlayerOrException(); p = source.getPlayerOrException(); } catch (CommandSyntaxException e) { source.sendFailure(Component.literal("/link 只能由玩家执行 / /link can only be run by a player.")); return 0; p = null; } if (!source.getServer().usesAuthentication()) { LOGGER.warn(ModLinkPolicy.OFFLINE_LOG); source.sendFailure(Component.literal(ModLinkPolicy.OFFLINE_REPLY)); return 0; this.player = p; } requestAndReply(source.getServer(), player); return 1; })); @Override public UUID player() { return player != null ? player.getUUID() : null; } private void requestAndReply(MinecraftServer server, ServerPlayer player) { UUID uuid = player.getUUID(); player.sendSystemMessage(Component.literal("正在获取绑定码… / Requesting a link code…")); io.submit(() -> { try { LinkCode code = linkClient.requestCode(uuid); server.execute(() -> { player.sendSystemMessage(Component.literal( "绑定码 / Link code: " + code.code() + "(几分钟内有效 / valid a few minutes)")); player.sendSystemMessage(Component.literal(code.panelUrl() != null ? "在此完成绑定 / Finish linking at: " + code.panelUrl() : "在网页控制台 → 账户 中输入 / Enter it on the web console → Account.")); }); } catch (LinkException e) { LOGGER.warn("link code request failed for {} (status={}, code={}): {}", uuid, e.statusCode(), e.errorCode(), e.getMessage()); server.execute(() -> player.sendSystemMessage(Component.literal( "现在无法获取绑定码,请稍后再试 / Couldn't get a link code right now. Please try again in a moment."))); @Override public boolean onlineMode() { return source.getServer().usesAuthentication(); } @Override public void fail(String line) { source.sendFailure(Component.literal(line)); } @Override public void tell(String line) { player.sendSystemMessage(Component.literal(line)); } @Override public void onServerThread(Runnable task) { source.getServer().execute(task); } }); } }
plugins/forge/src/main/java/best/lolicon/felis/forge/FelisForgeMod.java +45 −44 Changes for plugins/forge/src/main/java/best/lolicon/felis/forge/FelisForgeMod.java: 45 added lines, 44 removed lines. Original line number Diff line number Diff line package best.lolicon.felis.forge; import best.lolicon.felis.link.LinkClient; import best.lolicon.felis.link.LinkCode; import best.lolicon.felis.link.LinkConfig; import best.lolicon.felis.link.LinkConfigLoader; import best.lolicon.felis.link.LinkException; import best.lolicon.felis.link.ModLinkPolicy; import best.lolicon.felis.link.ModLink; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.exceptions.CommandSyntaxException; Loading @@ -13,7 +11,6 @@ import com.mojang.logging.LogUtils; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.RegisterCommandsEvent; Loading @@ -24,34 +21,34 @@ import org.slf4j.Logger; import java.io.IOException; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * FelisForgeMod is the Forge server-side leg of the §10 account-link flow. On * {@link RegisterCommandsEvent} it installs a {@code /link} command that mints a * one-time code from felis-api for the player's already-verified UUID. As on the * other loaders, the HTTP call runs on a daemon I/O thread and the reply is hopped * back onto the server thread; failures collapse to a generic chat line with * details kept to the server log. If config is missing the mod stays loaded but * one-time code from felis-api for the player's already-verified UUID. The command * is the shared {@link ModLink}, as on the other loaders (the HTTP call on a daemon * I/O thread, the reply back on the server thread, one generic line for any * failure). If config is missing the mod stays loaded but * never registers the command, so the proxy/server runs un-crippled. */ @Mod("felis_link") public final class FelisForgeMod { private static final Logger LOGGER = LogUtils.getLogger(); private final ExecutorService io = Executors.newSingleThreadExecutor(r -> { private final Executor io = Executors.newSingleThreadExecutor(r -> { Thread t = new Thread(r, "felis-link-io"); t.setDaemon(true); return t; }); private LinkClient linkClient; private ModLink link; public FelisForgeMod() { try { LinkConfig config = LinkConfigLoader.load( FMLPaths.CONFIGDIR.get().resolve("felis-link.properties")); this.linkClient = new LinkClient(config); this.link = new ModLink(new LinkClient(config), io, LOGGER::warn); MinecraftForge.EVENT_BUS.register(this); LOGGER.info("Felis link ready; /link will be registered."); } catch (IOException e) { Loading @@ -65,44 +62,48 @@ public final class FelisForgeMod { } private void register(CommandDispatcher<CommandSourceStack> dispatcher) { dispatcher.register(Commands.literal("link").executes(ctx -> { CommandSourceStack source = ctx.getSource(); ServerPlayer player; dispatcher.register(Commands.literal("link").executes(ctx -> link.run(new LinkSource(ctx.getSource())))); } /** LinkSource is a command source as the shared /link sees it. */ private static final class LinkSource implements ModLink.Source { private final CommandSourceStack source; private final ServerPlayer player; // null for the console or a command block LinkSource(CommandSourceStack source) { this.source = source; ServerPlayer p; try { player = source.getPlayerOrException(); p = source.getPlayerOrException(); } catch (CommandSyntaxException e) { source.sendFailure(Component.literal("/link 只能由玩家执行 / /link can only be run by a player.")); return 0; p = null; } if (!source.getServer().usesAuthentication()) { LOGGER.warn(ModLinkPolicy.OFFLINE_LOG); source.sendFailure(Component.literal(ModLinkPolicy.OFFLINE_REPLY)); return 0; this.player = p; } requestAndReply(source.getServer(), player); return 1; })); @Override public UUID player() { return player != null ? player.getUUID() : null; } private void requestAndReply(MinecraftServer server, ServerPlayer player) { UUID uuid = player.getUUID(); player.sendSystemMessage(Component.literal("正在获取绑定码… / Requesting a link code…")); io.submit(() -> { try { LinkCode code = linkClient.requestCode(uuid); server.execute(() -> { player.sendSystemMessage(Component.literal( "绑定码 / Link code: " + code.code() + "(几分钟内有效 / valid a few minutes)")); player.sendSystemMessage(Component.literal(code.panelUrl() != null ? "在此完成绑定 / Finish linking at: " + code.panelUrl() : "在网页控制台 → 账户 中输入 / Enter it on the web console → Account.")); }); } catch (LinkException e) { LOGGER.warn("link code request failed for {} (status={}, code={}): {}", uuid, e.statusCode(), e.errorCode(), e.getMessage()); server.execute(() -> player.sendSystemMessage(Component.literal( "现在无法获取绑定码,请稍后再试 / Couldn't get a link code right now. Please try again in a moment."))); @Override public boolean onlineMode() { return source.getServer().usesAuthentication(); } @Override public void fail(String line) { source.sendFailure(Component.literal(line)); } @Override public void tell(String line) { player.sendSystemMessage(Component.literal(line)); } @Override public void onServerThread(Runnable task) { source.getServer().execute(task); } }); } }
plugins/limbo/build.gradle +23 −0 Changes for plugins/limbo/build.gradle: 23 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -58,3 +58,26 @@ tasks.withType(JavaCompile).configureEach { // bytecode so the plugin loads on any Limbo running Java 17+. options.release = 17 } // The login gate's self-test: LoginFlowTest runs the whole login flow on a virtual // clock (a fake scheduler and players around the real LoginFlow, the real felis-api // clients against a stub) plus the readiness endpoint, the env parsing and the book. // A framework-free main like the other plugins' tests; plugins/test.sh runs // `./gradlew loginTest`. The Limbo jar is on the classpath for the adventure types the // book is built from. sourceSets { loginTest { java { srcDir 'test' } compileClasspath += sourceSets.main.output + configurations.compileClasspath runtimeClasspath += output + compileClasspath } } tasks.register('loginTest', JavaExec) { group = 'verification' description = 'Runs the LoginFlowTest self-test main.' classpath = sourceSets.loginTest.runtimeClasspath mainClass = 'best.lolicon.felis.limbo.LoginFlowTest' }
plugins/limbo/src/main/java/best/lolicon/felis/limbo/FelisLimboPlugin.java +91 −366 File changed.Preview size limit exceeded, changes collapsed. Show changes