Skip to content
Merged
97 changes: 91 additions & 6 deletions src/main/java/cn/rukkit/Rukkit.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
import cn.rukkit.game.NetworkPlayer;
import cn.rukkit.game.SaveData;
import cn.rukkit.network.*;
import cn.rukkit.network.core.CoreRoomGameServer;
import cn.rukkit.network.core.handler.ServerPacketHandlerManager;
import cn.rukkit.network.room.ServerGlobalConnectionManager;
import cn.rukkit.network.room.ServerRoomConnection;
import cn.rukkit.network.room.ServerRoomManager;
import java.io.*;

import cn.rukkit.network.packet.handler.PacketHandlerManager;
Expand Down Expand Up @@ -57,22 +62,44 @@ public class Rukkit {
private static RoomManager roomManager;
private static SaveData defaultSave;
private static PacketHandlerManager packetHandlerManager;
private static CoreRoomGameServer coreServer;
private static ServerGlobalConnectionManager coreConnectionManager;
private static ServerRoomManager coreRoomManager;
private static ServerPacketHandlerManager corePacketHandlerManager;

public static void shutdown(String message) {
// TODO: Implement this method
log.info("Server will shutdown...");
log.info("Disconnect current players...");
getGlobalConnectionManager().broadcastGlobalServerMessage("Server is stopped!");
if (isCoreNetworkEnabled() && coreConnectionManager != null) {
coreConnectionManager.broadcastGlobalServerMessage("Server is stopped!");
} else if (connectionManager != null) {
connectionManager.broadcastGlobalServerMessage("Server is stopped!");
}
log.info("Disabling all plugins...");
pluginManager.disableAllPlugins();
if (pluginManager != null) {
pluginManager.disableAllPlugins();
}
log.info("Saving player data...");
for (RoomConnection connection: getGlobalConnectionManager().getConnections()) {
connection.player.savePlayerData();
if (isCoreNetworkEnabled() && coreConnectionManager != null) {
for (ServerRoomConnection connection : coreConnectionManager.getConnections()) {
connection.player.savePlayerData();
}
} else if (connectionManager != null) {
for (RoomConnection connection: connectionManager.getConnections()) {
connection.player.savePlayerData();
}
}
log.info("Stop ThreadManager...");
getThreadManager().shutdown();
if (threadManager != null) {
threadManager.shutdown();
}
log.info("Shutdown server...");
getGameServer().stopServer();
if (isCoreNetworkEnabled() && coreServer != null) {
coreServer.stopServer();
} else if (server != null) {
server.stopServer();
}
log.info("Stop terminal...");
RukkitLauncher.isTerminalRunning = false;
// RukkitLauncher.terminalThread.interrupt();
Expand Down Expand Up @@ -109,6 +136,11 @@ public static boolean isStarted() {
return isStarted;
}

/** Returns whether the migrated network runtime has been selected. */
public static boolean isCoreNetworkEnabled() {
return config != null && config.isCoreNetworkEnabled();
}

/**
* Get a rukkit config.
* {@link RukkitConfig}
Expand Down Expand Up @@ -145,6 +177,10 @@ public static RoomGameServer getGameServer() {
return server;
}

public static CoreRoomGameServer getCoreGameServer() {
return coreServer;
}

public static PluginManager getPluginManager() {
return pluginManager;
}
Expand Down Expand Up @@ -226,11 +262,23 @@ public static final ModManager getModManager() {
public static RoomManager getRoomManager() {
return roomManager;
}

public static ServerRoomManager getCoreRoomManager() {
return coreRoomManager;
}

public static ServerGlobalConnectionManager getCoreGlobalConnectionManager() {
return coreConnectionManager;
}

public static PacketHandlerManager getPacketHandlerManager() {
return packetHandlerManager;
}

public static ServerPacketHandlerManager getCorePacketHandlerManager() {
return corePacketHandlerManager;
}

public static void loadDefaultSave() throws IOException {
InputStream in = Rukkit.class.getClassLoader().getResourceAsStream("defaultSave");
byte[] data = new byte[in.available()];
Expand Down Expand Up @@ -299,6 +347,43 @@ public static final void startServer() throws IOException, InterruptedException
modManager.loadAllModsInDir();
log.info("init::CommandManager");
commandManager = new CommandManager();
if (isCoreNetworkEnabled()) {
log.info("init::CoreRoomManager");
coreRoomManager = new ServerRoomManager(round, config.maxRoom);
log.info("init::CoreConnectionManager");
coreConnectionManager = new ServerGlobalConnectionManager(coreRoomManager);
log.info("init::CorePacketHandlerManager");
corePacketHandlerManager = new ServerPacketHandlerManager();
corePacketHandlerManager.registerInternalHandler(
coreRoomManager,
coreConnectionManager,
(connection, command) -> commandManager.executeChatCommand(
connection, coreConnectionManager, command));
log.info("init::CoreRoomGameServer");
coreServer = new CoreRoomGameServer(
config.serverPort, corePacketHandlerManager, coreConnectionManager);

/* Core startup uses adapters that only depend on the migrated room model.
* Legacy plugins remain on the legacy startup path below. */
log.info("init::PluginManager (core-compatible plugins only)");
pluginManager = new PluginManager();
pluginManager.loadPlugin(new BasePlugin());
pluginManager.loadPlugin(new CoreCommandPlugin());
pluginManager.loadPlugin(new CoreTestCommandPlugin());
pluginManager.loadPlugin(new ServerCommandPlugin());
pluginManager.enableAllPlugins();

log.info("start::core game server on port:" + config.serverPort);
threadManager.submit(() -> {
try {
coreServer.action(time);
} catch (InterruptedException e) {
log.error("A error occurred:", e);
Thread.currentThread().interrupt();
}
});
return;
}
log.info("init::PacketHandlerManager");
packetHandlerManager = new PacketHandlerManager();
packetHandlerManager.registerInternalHandler();
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/cn/rukkit/command/ChatCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class ChatCommand
public boolean adminRequired = false;
private boolean isEnabled = false;
private ChatCommandListener chatListener;
private ChatCommandContextListener contextListener;
private RukkitPlugin fromPlugin;


Expand All @@ -38,6 +39,28 @@ public ChatCommand(String msg, String helpMessage,int args, ChatCommandListener
this.adminRequired = adminRequired;
}

/**
* Creates a command owned by the migrated network stack.
*
* <p>This is a factory instead of an overloaded constructor so existing
* lambda calls targeting {@link ChatCommandListener} remain source
* compatible.</p>
*/
public static ChatCommand contextCommand(String msg, String helpMessage, int args,
ChatCommandContextListener contextListener, RukkitPlugin fromPlugin) {
return contextCommand(msg, helpMessage, args, contextListener, fromPlugin, false);
}

/** Creates a migrated command with an explicit administrator requirement. */
public static ChatCommand contextCommand(String msg, String helpMessage, int args,
ChatCommandContextListener contextListener, RukkitPlugin fromPlugin,
boolean adminRequired) {
ChatCommand command = new ChatCommand(msg, helpMessage, args,
(ChatCommandListener) null, fromPlugin, adminRequired);
command.setContextListener(contextListener);
return command;
}

public RukkitPlugin getFromPlugin() {
return fromPlugin;
}
Expand All @@ -57,4 +80,12 @@ public void setListener(ChatCommandListener listener) {
public ChatCommandListener getListener() {
return chatListener;
}

public void setContextListener(ChatCommandContextListener listener) {
this.contextListener = listener;
}

public ChatCommandContextListener getContextListener() {
return contextListener;
}
}
33 changes: 33 additions & 0 deletions src/main/java/cn/rukkit/command/ChatCommandContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2020-2022 RukkitDev Team and contributors.
*
* This project uses GNU Affero General Public License v3.0.You can find this license in the following link.
* 本项目使用 GNU Affero General Public License v3.0 许可证,你可以在下方链接查看:
*
* https://github.com/RukkitDev/Rukkit/blob/master/LICENSE
*/

package cn.rukkit.command;

import cn.rukkit.game.NetworkPlayer;

/**
* Application-level capabilities exposed to a chat command.
*
* <p>The context deliberately hides the concrete network connection and
* packet implementation so the same command semantics can serve both
* network stacks during migration.</p>
*/
public interface ChatCommandContext {
NetworkPlayer player();

int connectionCount();

int activeThreadCount();

int threadPoolCount();

void sendServerMessage(String message);

void broadcastCommandEcho(String command);
}
16 changes: 16 additions & 0 deletions src/main/java/cn/rukkit/command/ChatCommandContextListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright 2020-2022 RukkitDev Team and contributors.
*
* This project uses GNU Affero General Public License v3.0.You can find this license in the following link.
* 本项目使用 GNU Affero General Public License v3.0 许可证,你可以在下方链接查看:
*
* https://github.com/RukkitDev/Rukkit/blob/master/LICENSE
*/

package cn.rukkit.command;

/** Listener contract for commands running on the connection-neutral context. */
@FunctionalInterface
public interface ChatCommandContextListener {
boolean onSend(ChatCommandContext context, String[] args);
}
63 changes: 63 additions & 0 deletions src/main/java/cn/rukkit/command/CommandManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import org.jline.reader.impl.completer.StringsCompleter;
import org.slf4j.*;
import cn.rukkit.network.*;
import cn.rukkit.network.room.ServerGlobalConnectionManager;
import cn.rukkit.network.room.ServerRoomConnection;
import cn.rukkit.*;
import cn.rukkit.network.packet.*;
import java.io.*;
Expand All @@ -33,6 +35,10 @@ public class CommandManager

public void registerCommand(ChatCommand cmd) {
log.debug(String.format("Registering Command '%s' from plugin '%s'...",cmd.cmd,cmd.getFromPlugin().config.name));
if (cmd.getContextListener() == null
&& cmd.getListener() instanceof ChatCommandContextListener contextListener) {
cmd.setContextListener(contextListener);
}
if (fetchCommand(cmd.cmd) != null) {
log.warn(String.format("Command '%s' had already registered.",cmd.cmd));
} else {
Expand All @@ -49,6 +55,20 @@ public void registerServerCommand(ServerCommand cmd) {
serverCmdString.add(cmd.cmd);
}
}

/**
* Normalizes the optional prefix accepted by the {@code qc} command.
*
* <p>The nested command may be written as {@code command},
* {@code -command}, or {@code .command}. Only one prefix is removed.</p>
*/
public static String normalizeNestedCommand(String command) {
if (command == null || command.isEmpty()) {
return command;
}
char prefix = command.charAt(0);
return prefix == '-' || prefix == '.' ? command.substring(1) : command;
}

public void executeChatCommand(RoomConnection connection, String cmd) {
String[] cmds = cmd.split("\\s+", 2);
Expand Down Expand Up @@ -80,6 +100,49 @@ public void executeChatCommand(RoomConnection connection, String cmd) {
}
}

/**
* Execute a command for the migrated connection stack.
*
* <p>The legacy {@link ChatCommandListener} is intentionally not invoked
* here because it requires a legacy {@link RoomConnection}. Commands are
* migrated by assigning a {@link ChatCommandContextListener} to the
* command; until then the command is reported as not migrated.</p>
*/
public void executeChatCommand(ServerRoomConnection connection, String cmd) {
executeChatCommand(connection, null, cmd);
}

/** Execute a command with migrated global runtime services. */
public void executeChatCommand(ServerRoomConnection connection,
ServerGlobalConnectionManager globalConnectionManager,
String cmd) {
ChatCommandContext context = new ServerChatCommandContext(connection, globalConnectionManager);
String[] cmds = cmd.split("\\s+", 2);
ChatCommand cmdObj = fetchCommand(cmds[0]);
if (cmdObj == null) {
context.sendServerMessage(LangUtil.getString("chat.invalidCommand"));
return;
}
if (cmdObj.adminRequired && !context.player().isAdmin) {
context.sendServerMessage(LangUtil.getString("chat.privDenied"));
return;
}

ChatCommandContextListener listener = cmdObj.getContextListener();
if (listener == null) {
log.warn("Command '{}' has no migrated context listener", cmds[0]);
return;
}

log.trace("cmd is:{}", cmds[0]);
String[] args = cmds.length > 1 && cmdObj.args > 0
? cmds[1].split(" ", cmdObj.args)
: new String[0];
if (listener.onSend(context, args)) {
context.broadcastCommandEcho(cmd);
}
}

public void executeServerCommand(String cmd) {
String[] cmds = cmd.split("\\s+", 2);
ServerCommand cmdObj = fetchServerCommand(cmds[0]);
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/cn/rukkit/command/RoomCommandContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2020-2022 RukkitDev Team and contributors.
*
* This project uses GNU Affero General Public License v3.0.You can find this license in the following link.
* 本项目使用 GNU Affero General Public License v3.0 许可证,你可以在下方链接查看:
*
* https://github.com/RukkitDev/Rukkit/blob/master/LICENSE
*/

package cn.rukkit.command;

import cn.rukkit.network.room.ServerRoom;
import cn.rukkit.network.room.ServerRoomConnection;

/**
* Optional room capability for commands that need the current core room.
*
* <p>Keeping this capability separate from {@link ChatCommandContext} prevents
* room-specific state from becoming part of every command's base contract.</p>
*/
public interface RoomCommandContext extends ChatCommandContext {
ServerRoom room();

ServerRoomConnection connection();
}
Loading
Loading