Improve command system

This commit is contained in:
Melledy
2025-11-25 23:09:20 -08:00
parent fd8e8925ca
commit 2acd506245
19 changed files with 175 additions and 172 deletions

View File

@@ -233,7 +233,8 @@ public class Nebula {
String input;
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
while ((input = br.readLine()) != null) {
Nebula.getCommandManager().invoke(null, input);
var result = Nebula.getCommandManager().invoke(null, input);
Nebula.getLogger().info(result.getMessage());
}
} catch (Exception e) {
Nebula.getLogger().error("Console error:", e);

View File

@@ -113,18 +113,6 @@ public class CommandArgs {
return this.list.get(index);
}
/**
* Sends a message to the command sender
* @param message
*/
public void sendMessage(String message) {
if (sender != null) {
sender.sendMessage(message);
} else {
Nebula.getLogger().info(message);
}
}
public boolean hasFlag(String flag) {
if (this.flags == null) return false;
return this.flags.contains(flag);

View File

@@ -10,6 +10,6 @@ public interface CommandHandler {
return getData().label();
}
public void execute(CommandArgs args);
public String execute(CommandArgs args);
}

View File

@@ -108,7 +108,7 @@ public class CommandManager {
return sender.getAccount().hasPermission("target." + command.permission());
}
public void invoke(Player sender, String message) {
public CommandResult invoke(Player sender, String message) {
// Parse message into arguments
List<String> args = Arrays.stream(message.split(" ")).collect(Collectors.toCollection(ArrayList::new));
@@ -122,6 +122,9 @@ public class CommandManager {
// Get command handler
CommandHandler handler = this.commands.get(label);
// Create result object
var result = CommandResult.builder();
// Execute command
if (handler != null) {
@@ -131,8 +134,8 @@ public class CommandManager {
// Check if sender has permission to run the command.
if (sender != null && !this.checkPermission(sender, command)) {
// We have a double null check here just in case
sender.sendMessage("You do not have permission to use this command.");
return;
result.message("Error - You do not have permission to use this command");
return result.build();
}
// Build command arguments
@@ -140,14 +143,14 @@ public class CommandManager {
// Check targeted permission
if (sender != cmdArgs.getTarget() && !this.checkTargetPermission(sender, command)) {
cmdArgs.sendMessage("You do not have permission to use this command on another player.");
return;
result.message("Error - You do not have permission to use this command on another player");
return result.build();
}
// Make sure our command has a target
if (command.requireTarget() && cmdArgs.getTarget() == null) {
cmdArgs.sendMessage("Error: Targeted player not found or offline");
return;
result.message("Error - Targeted player not found or offline");
return result.build();
}
// Log
@@ -156,13 +159,20 @@ public class CommandManager {
}
// Run command
handler.execute(cmdArgs);
} else {
if (sender != null) {
sender.sendMessage("Invalid Command!");
} else {
Nebula.getLogger().info("Invalid Command!");
String commandMessage = handler.execute(cmdArgs);
// Parse out last newline
if (commandMessage.endsWith("\n")) {
commandMessage = commandMessage.substring(0, commandMessage.length() - 1);
}
// Set result data
result.command(handler);
result.message(commandMessage);
} else {
result.message("Invalid Command!");
}
return result.build();
}
}

View File

@@ -0,0 +1,19 @@
package emu.nebula.command;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class CommandResult {
private CommandHandler command;
private String message;
public boolean isCommandTypeOf(Class<? extends CommandHandler> handler) {
if (command == null) {
return false;
}
return command.getClass().equals(handler);
}
}

View File

@@ -10,10 +10,9 @@ import emu.nebula.util.Utils;
public class AccountCommand implements CommandHandler {
@Override
public void execute(CommandArgs args) {
public String execute(CommandArgs args) {
if (args.size() < 2) {
args.sendMessage("Invalid amount of args");
return;
return "Invalid amount of args";
}
String command = args.get(0).toLowerCase();
@@ -29,19 +28,22 @@ public class AccountCommand implements CommandHandler {
}
if (AccountHelper.createAccount(username, null, reservedUid) != null) {
args.sendMessage("Account created");
return "Account created";
} else {
args.sendMessage("Account already exists");
return "Account already exists";
}
}
case "delete" -> {
if (AccountHelper.deleteAccount(username)) {
args.sendMessage("Account deleted");
return "Account deleted";
} else {
args.sendMessage("Account doesnt exist");
return "Account doesnt exist";
}
}
}
// Fallback
return "Account sub command not found";
}
}

View File

@@ -22,7 +22,7 @@ import emu.nebula.command.CommandHandler;
public class CharacterCommand implements CommandHandler {
@Override
public void execute(CommandArgs args) {
public String execute(CommandArgs args) {
// Init
var player = args.getTarget();
var characters = new HashSet<GameCharacter>();
@@ -51,8 +51,7 @@ public class CharacterCommand implements CommandHandler {
// Sanity check
if (characters.isEmpty()) {
args.sendMessage("Error: No characters selected");
return;
return "Error: No characters selected";
}
// List of modified characters that we send to the client for updates
@@ -73,8 +72,7 @@ public class CharacterCommand implements CommandHandler {
}
if (modified.isEmpty()) {
args.sendMessage("No changes applied");
return;
return "No changes applied";
}
// Encode and send
@@ -85,6 +83,6 @@ public class CharacterCommand implements CommandHandler {
}
player.addNextPackage(NetMsgId.chars_final_notify, proto);
args.sendMessage("Updated " + modified.size() + " character(s)");
return "Updated " + modified.size() + " character(s)";
}
}

View File

@@ -25,7 +25,7 @@ import java.util.HashSet;
public class CleanCommand implements CommandHandler {
@Override
public void execute(CommandArgs args) {
public String execute(CommandArgs args) {
var player = args.getTarget();
var inv = player.getInventory();
@@ -77,42 +77,39 @@ public class CleanCommand implements CommandHandler {
} else {
for (int id : ids) {
ItemDef data = GameData.getItemDataTable().get(id);
if (data == null) continue;
ItemType type = data.getItemType();
if (data != null) {
ItemType type = data.getItemType();
switch (type) {
case Res -> {
if (doResources) {
int count = inv.getResourceCount(id);
if (count > 0) removeMap.add(id, count);
}
}
case Item -> {
if (doItems) {
int count = inv.getItemCount(id);
if (count > 0) removeMap.add(id, count);
}
}
case Disc, Char, CharacterSkin, Title, Honor -> {
}
default -> {
if (doItems) {
int count = inv.getItemCount(id);
if (count > 0) {
removeMap.add(id, count);
break;
}
}
if (doResources) {
int count = inv.getResourceCount(id);
if (count > 0) removeMap.add(id, count);
}
switch (type) {
case Res -> {
if (doResources) {
int count = inv.getResourceCount(id);
if (count > 0) removeMap.add(id, count);
}
}
case Item -> {
if (doItems) {
int count = inv.getItemCount(id);
if (count > 0) removeMap.add(id, count);
}
}
case Disc, Char, CharacterSkin, Title, Honor -> {
}
default -> {
if (doItems) {
int count = inv.getItemCount(id);
if (count > 0) {
removeMap.add(id, count);
break;
}
}
if (doResources) {
int count = inv.getResourceCount(id);
if (count > 0) removeMap.add(id, count);
}
}
} else {
args.sendMessage("Error: Invalid item id " + id);
}
}
}
@@ -122,11 +119,11 @@ public class CleanCommand implements CommandHandler {
}
if (change.isEmpty()) {
args.sendMessage("No items/resources removed");
return;
return "No items/resources removed";
}
player.addNextPackage(NetMsgId.items_change_notify, change.toProto());
args.sendMessage("Inventory cleaned");
return "Inventory cleaned";
}
}

View File

@@ -21,7 +21,7 @@ import emu.nebula.command.CommandHandler;
public class DiscCommand implements CommandHandler {
@Override
public void execute(CommandArgs args) {
public String execute(CommandArgs args) {
// Init
var player = args.getTarget();
var discs = new HashSet<GameDisc>();
@@ -50,7 +50,7 @@ public class DiscCommand implements CommandHandler {
// Sanity check
if (discs.isEmpty()) {
return;
return "No discs selected";
}
// List of modified characters that we send to the client for updates
@@ -71,12 +71,15 @@ public class DiscCommand implements CommandHandler {
}
if (modified.isEmpty()) {
return;
return "No discs changed";
}
// Encode and send
for (var disc : modified) {
player.addNextPackage(NetMsgId.disc_reset_notify, disc.toProto());
}
// Return message
return "Changed " + modified.size() + " discs";
}
}

View File

@@ -31,15 +31,14 @@ public class GiveAllCommand implements CommandHandler {
);
@Override
public void execute(CommandArgs args) {
public String execute(CommandArgs args) {
Player target = args.getTarget();
String type = args.get(0).toLowerCase();
var change = new PlayerChangeInfo();
var message = new StringBuilder();
switch (type) {
default -> args.sendMessage("Error: Invalid type");
case "m", "materials", "mats" -> {
// Create items map
var items = new ItemParamMap();
@@ -66,7 +65,7 @@ public class GiveAllCommand implements CommandHandler {
target.getInventory().addItems(items, change);
// Send message
args.sendMessage("Giving " + target.getName() + " " + items.size() + " items");
message.append("Giving " + target.getName() + " " + items.size() + " items.\n");
}
case "d", "discs" -> {
// Get all discs
@@ -92,7 +91,7 @@ public class GiveAllCommand implements CommandHandler {
}
// Send message
args.sendMessage("Giving " + target.getName() + " all discs");
message.append("Giving " + target.getName() + " all discs.\n");
}
case "c", "characters", "trekkers", "t" -> {
// Get all characters
@@ -118,16 +117,22 @@ public class GiveAllCommand implements CommandHandler {
}
// Send message
args.sendMessage("Giving " + target.getName() + " all characters");
message.append("Giving " + target.getName() + " all characters.\n");
}
default -> {
// Ignored
}
}
if (change.isEmpty()) {
return;
return "No items given to the player";
}
// Encode and send
target.addNextPackage(NetMsgId.items_change_notify, change.toProto());
// Complete
return message.toString();
}
}

View File

@@ -17,7 +17,7 @@ import emu.nebula.command.CommandHandler;
public class GiveCommand implements CommandHandler {
@Override
public void execute(CommandArgs args) {
public String execute(CommandArgs args) {
// Setup mail
var mail = new GameMail("System", "Give Command Result", "");
@@ -31,18 +31,17 @@ public class GiveCommand implements CommandHandler {
var itemData = GameData.getItemDataTable().get(itemId);
if (itemData == null) {
args.sendMessage("Item \"" + arg + "\" does not exist!");
continue;
}
// Add
mail.addAttachment(itemId, amount);
// Log
args.sendMessage("Giving " + args.getTarget().getName() + " " + amount + " of " + itemId);
}
// Add mail
args.getTarget().getMailbox().sendMail(mail);
//
return "Give command success, check your mail";
}
}

View File

@@ -9,8 +9,10 @@ import emu.nebula.command.CommandHandler;
public class HelpCommand implements CommandHandler {
@Override
public void execute(CommandArgs args) {
args.sendMessage("Displaying list of commands:");
public String execute(CommandArgs args) {
var message = new StringBuilder();
message.append("Displaying list of commands:\n");
// Sort command names
var labels = Nebula.getCommandManager().getLabels().keySet().stream().sorted().toList();
@@ -21,9 +23,11 @@ public class HelpCommand implements CommandHandler {
// Only send command description if the sender has permission to use the command
if (Nebula.getCommandManager().checkPermission(args.getSender(), command)) {
args.sendMessage(command.desc());
message.append(command.desc());
message.append("\n");
}
}
return message.toString();
}
}

View File

@@ -17,41 +17,36 @@ import emu.nebula.util.Utils;
desc = "/mail \"subject\" \"body\" [itemId xQty | itemId:qty ...]. Sends the targeted player a system mail."
)
public class MailCommand implements CommandHandler {
private static final String USAGE_TEXT = "Usage: /mail \"subject\" \"body\" [itemId xQty | itemId:qty ...]";
private static final Pattern QUOTED_TEXT = Pattern.compile("\"([^\"]*)\"");
@Override
public void execute(CommandArgs args) {
public String execute(CommandArgs args) {
var target = args.getTarget();
if (target == null) {
args.sendMessage("Error: Targeted player not found or offline");
return;
return "Error - Targeted player not found or offline";
}
String rawInput = args.getRaw() == null ? "" : args.getRaw().trim();
if (rawInput.isEmpty()) {
sendUsage(args);
return;
return USAGE_TEXT;
}
Matcher matcher = QUOTED_TEXT.matcher(rawInput);
if (!matcher.find()) {
sendUsage(args);
return;
return USAGE_TEXT;
}
String subject = matcher.group(1).trim();
if (!matcher.find()) {
args.sendMessage("Mail body must be wrapped in quotes after the subject.");
return;
return "Mail body must be wrapped in quotes after the subject.";
}
String body = matcher.group(1).trim();
int attachmentStartIndex = matcher.end();
if (subject.isEmpty()) {
args.sendMessage("Mail subject cannot be empty.");
return;
return "Mail subject cannot be empty.";
}
if (body.isEmpty()) {
@@ -68,7 +63,7 @@ public class MailCommand implements CommandHandler {
parseAttachments(attachmentSection, mail, args);
target.getMailbox().sendMail(mail);
args.sendMessage("Mail sent to " + target.getName() + " with subject \"" + subject + "\".");
return "Mail sent to " + target.getName() + " with subject \"" + subject + "\".";
}
private void parseAttachments(String attachmentSection, GameMail mail, CommandArgs args) {
@@ -93,7 +88,7 @@ public class MailCommand implements CommandHandler {
if (token.startsWith("x") && token.length() > 1) {
if (pendingItemId == null) {
args.sendMessage("Quantity token '" + token + "' must follow an item id.");
//args.sendMessage("Quantity token '" + token + "' must follow an item id.");
continue;
}
@@ -110,7 +105,7 @@ public class MailCommand implements CommandHandler {
int itemId = Utils.parseSafeInt(token);
if (itemId <= 0) {
args.sendMessage("Invalid item id '" + token + "'.");
//args.sendMessage("Invalid item id '" + token + "'.");
pendingItemId = null;
continue;
}
@@ -119,7 +114,7 @@ public class MailCommand implements CommandHandler {
continue;
}
args.sendMessage("Ignoring attachment token '" + token + "'.");
//args.sendMessage("Ignoring attachment token '" + token + "'.");
}
if (pendingItemId != null) {
@@ -129,7 +124,7 @@ public class MailCommand implements CommandHandler {
private void addAttachment(GameMail mail, CommandArgs args, int itemId, int quantity) {
if (itemId <= 0) {
args.sendMessage("Item id must be positive.");
//args.sendMessage("Item id must be positive.");
return;
}
@@ -145,8 +140,4 @@ public class MailCommand implements CommandHandler {
}
return !token.isEmpty();
}
private void sendUsage(CommandArgs args) {
args.sendMessage("Usage: /mail \"subject\" \"body\" [itemId xQty | itemId:qty ...]");
}
}

View File

@@ -9,14 +9,17 @@ import emu.nebula.command.CommandHandler;
public class ReloadCommand implements CommandHandler {
@Override
public void execute(CommandArgs args) {
public String execute(CommandArgs args) {
// Reload config first
Nebula.loadConfig();
// Reload patch list if the server is running
if (Nebula.getHttpServer() != null) {
Nebula.getHttpServer().loadPatchList();
}
args.sendMessage("Reloaded the server config");
// Result message
return "Reloaded the server config";
}
}

View File

@@ -9,17 +9,11 @@ import java.util.Random;
@Command(label = "remote", permission = "player.remote", requireTarget = true, desc = "/remote. Send remote to web remote")
public class RemoteKeyCommand implements CommandHandler {
private static String lastMessage;
public static String getLastMessage() {
return lastMessage;
}
private final String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@Override
public void execute(CommandArgs args) {
public String execute(CommandArgs args) {
if (Nebula.getConfig().getRemoteCommand().useRemoteServices) {
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
StringBuilder sb = new StringBuilder();
Random random = new Random();
@@ -27,18 +21,12 @@ public class RemoteKeyCommand implements CommandHandler {
int index = random.nextInt(characters.length());
sb.append(characters.charAt(index));
}
args.getTarget().setPlayerRemoteToken(sb.toString());
args.getTarget().save();
String textsend = "Key Generated: " + sb.toString();
lastMessage = textsend;
args.sendMessage(textsend);
return;
args.getTarget().setRemoteToken(sb.toString());
return "Key Generated: " + sb.toString();
} else {
args.getTarget().setRemoteToken(null);
return "Remote Command Disabled on Server";
}
String textsend = "RemoteCommand Disabled on Server";
args.getTarget().setPlayerRemoteToken(null);
args.getTarget().save();
lastMessage = textsend;
args.sendMessage(textsend);
}
}

View File

@@ -2,6 +2,7 @@ package emu.nebula.game.player;
import java.util.Stack;
import dev.morphia.annotations.AlsoLoad;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
@@ -46,7 +47,6 @@ import emu.nebula.proto.Public.WorldClassRewardState;
import emu.nebula.proto.Public.Title;
import lombok.Getter;
import lombok.Setter;
import us.hebi.quickbuf.ProtoMessage;
import us.hebi.quickbuf.RepeatedInt;
@@ -59,6 +59,10 @@ public class Player implements GameDatabaseObject {
private transient Account account;
private transient GameSession session;
@Indexed
@AlsoLoad("playerRemoteToken")
private String remoteToken;
// Details
private String name;
private String signature;
@@ -90,7 +94,6 @@ public class Player implements GameDatabaseObject {
private final transient InfinityTowerManager infinityTowerManager;
private final transient VampireSurvivorManager vampireSurvivorManager;
private final transient ScoreBossManager scoreBossManager;
@Indexed @Setter @Getter private String playerRemoteToken;
// Referenced data
private transient Inventory inventory;
@@ -147,7 +150,6 @@ public class Player implements GameDatabaseObject {
this.honor = new int[3];
this.showChars = new int[3];
this.boards = new int[] {410301};
this.playerRemoteToken = null;
this.level = 1;
this.energy = 240;
@@ -200,6 +202,25 @@ public class Player implements GameDatabaseObject {
return this.session != null;
}
public void setRemoteToken(String token) {
// Skip if tokens are the same
if (this.remoteToken == null) {
if (token == null) {
return;
}
} else if (this.remoteToken != null) {
if (this.remoteToken.equals(token)) {
return;
}
}
// Set remote token
this.remoteToken = token;
// Update in database
Nebula.getGameDatabase().update(this, this.getUid(), "remoteToken", this.remoteToken);
}
public boolean getGender() {
return this.gender;
}
@@ -553,12 +574,6 @@ public class Player implements GameDatabaseObject {
return change;
}
//
public void sendMessage(String string) {
// Empty
}
// Dailies
public void checkResetDailies() {

View File

@@ -60,10 +60,6 @@ public class GameSession {
var player = this.player;
this.player = null;
// Clear remote token
player.setPlayerRemoteToken(null);
player.save();
// Remove session from player
player.removeSession();

View File

@@ -23,27 +23,11 @@ public class HandlerPlayerSignatureEdit extends NetHandler {
// Check if we need to handle a command
if (signature.charAt(0) == '!' || signature.charAt(0) == '/') {
String commandLabel = signature.toLowerCase().trim();
if (commandLabel.startsWith("!") || commandLabel.startsWith("/")) {
commandLabel = commandLabel.substring(1).split(" ")[0];
}
Nebula.getCommandManager().invoke(session.getPlayer(), signature);
// If this is the remote command, return the message
if ("remote".equals(commandLabel)) {
String remoteMessage = emu.nebula.command.commands.RemoteKeyCommand.getLastMessage();
if (remoteMessage != null) {
return session.encodeMsg(
NetMsgId.player_signature_edit_failed_ack,
Error.newInstance().setCode(119902).addArguments("\n" + remoteMessage)
);
}
}
var result = Nebula.getCommandManager().invoke(session.getPlayer(), signature);
return session.encodeMsg(
NetMsgId.player_signature_edit_failed_ack,
Error.newInstance().setCode(119902).addArguments("\nCommand Success")
Error.newInstance().setCode(119902).addArguments("\nCommand Result: " + result.getMessage())
);
}

View File

@@ -57,7 +57,7 @@ public class RemoteHandler implements Handler {
if (cachedUid != null) {
player = Nebula.getGameContext().getPlayerModule().getPlayer(cachedUid);
// Verify token matches (in case player changed token or cache is stale)
if (player != null && !token.equals(player.getPlayerRemoteToken())) {
if (player != null && !token.equals(player.getRemoteToken())) {
player = null;
tokenCache.remove(token);
}
@@ -65,7 +65,7 @@ public class RemoteHandler implements Handler {
// 2. Fallback to DB if not in cache or cache invalid
if (player == null) {
player = Nebula.getGameDatabase().getObjectByField(Player.class, "playerRemoteToken", token);
player = Nebula.getGameDatabase().getObjectByField(Player.class, "remoteToken", token);
if (player != null) {
tokenCache.put(token, player.getUid());
}