package net.rustnotmc.client; import java.util.LinkedHashMap; import java.util.Map; import net.rustnotmc.api.client.ClientCommand; import net.rustnotmc.api.client.CommandArgs; import net.rustnotmc.api.client.CommandScope; import net.rustnotmc.api.client.Commands; import net.rustnotmc.loader.Log; import net.rustnotmc.loader.ModLoader; /** * The mod command table. * * Only a CLIENT scoped command is carried out here. SERVER and BOTH are not * consumed by the client at all: the line is left for the server to take, which * is the whole point of the scope. The client never decides an effect the server * owns. * * The table is shared by every mod, so a name can only be claimed once. A second * mod asking for a taken name is refused loudly rather than silently shadowing * the first, which would be miserable to debug. * * Lives in game-fork/src-overlay; src is regenerated on every build. */ final class RsxCommands implements Commands { private static final class Entry { final String owner; final ClientCommand handler; final CommandScope scope; Entry(String owner, ClientCommand handler, CommandScope scope) { this.owner = owner; this.handler = handler; this.scope = scope; } } private final Map entries = new LinkedHashMap(); @Override public boolean register(String name, ClientCommand handler, CommandScope scope) { if (name == null || handler == null || scope == null) { return false; } String key = normalise(name); if (key.length() == 0) { return false; } String owner = registering(); Entry clash = this.entries.get(key); if (clash != null) { if (clash.owner.equals(owner) && clash.handler == handler) { return true; } Log.warn("command /" + key + " is already registered by " + clash.owner + "; the second registration was refused"); return false; } this.entries.put(key, new Entry(owner, handler, scope)); Log.info("registered command /" + key + " scope=" + scope.name().toLowerCase()); return true; } @Override public boolean register(String name, ClientCommand handler) { return register(name, handler, CommandScope.CLIENT); } @Override public boolean isRegistered(String name) { return name != null && this.entries.containsKey(normalise(name)); } @Override public CommandScope scopeOf(String name) { Entry e = name == null ? null : this.entries.get(normalise(name)); return e == null ? null : e.scope; } @Override public int count() { return this.entries.size(); } /** * Offers a typed line to the mod commands. * * @return true when the client carried it out and the line must not be sent * on. False means the line is not a client command, which is the * answer for SERVER and BOTH: the server has to see those. */ boolean dispatch(String line, String sender) { if (line == null || !line.startsWith("/")) { return false; } String body = line.substring(1).trim(); int space = body.indexOf(' '); String name = space < 0 ? body : body.substring(0, space); Entry entry = this.entries.get(normalise(name)); if (entry == null || entry.scope != CommandScope.CLIENT) { return false; } try { entry.handler.run(CommandArgs.parse(body, sender)); } catch (Throwable t) { Log.error("command /" + name + " failed", t); } return true; } private static String normalise(String name) { String v = name.trim(); if (v.startsWith("/")) { v = v.substring(1); } return v.toLowerCase(); } /** * Who is registering right now. * * The loader knows this because it is the one calling onConstruct, and the * game can read it without the loader ever depending on the game. */ private static String registering() { String who = ModLoader.REGISTERING.get(); return who == null ? "unknown" : who; } }