964 lines
37 KiB
Java
964 lines
37 KiB
Java
package mindustry.server;
|
|
|
|
import arc.*;
|
|
import arc.files.*;
|
|
import arc.struct.*;
|
|
import arc.struct.Array.*;
|
|
import arc.util.ArcAnnotate.*;
|
|
import arc.util.*;
|
|
import arc.util.Timer;
|
|
import arc.util.CommandHandler.*;
|
|
import arc.util.Timer.*;
|
|
import arc.util.serialization.*;
|
|
import arc.util.serialization.JsonValue.*;
|
|
import mindustry.*;
|
|
import mindustry.core.GameState.*;
|
|
import mindustry.core.*;
|
|
import mindustry.entities.*;
|
|
import mindustry.entities.type.*;
|
|
import mindustry.game.*;
|
|
import mindustry.game.EventType.*;
|
|
import mindustry.gen.*;
|
|
import mindustry.io.*;
|
|
import mindustry.maps.Map;
|
|
import mindustry.maps.*;
|
|
import mindustry.maps.Maps.*;
|
|
import mindustry.mod.Mods.*;
|
|
import mindustry.net.Administration.*;
|
|
import mindustry.net.Packets.*;
|
|
import mindustry.type.*;
|
|
|
|
import java.io.*;
|
|
import java.net.*;
|
|
import java.time.*;
|
|
import java.time.format.*;
|
|
import java.util.*;
|
|
|
|
import static arc.util.Log.*;
|
|
import static mindustry.Vars.*;
|
|
|
|
public class ServerControl implements ApplicationListener{
|
|
private static final int roundExtraTime = 12;
|
|
private static final int maxLogLength = 1024 * 512;
|
|
|
|
protected static String[] tags = {"&lc&fb[D]", "&lg&fb[I]", "&ly&fb[W]", "&lr&fb[E]", ""};
|
|
protected static DateTimeFormatter dateTime = DateTimeFormatter.ofPattern("MM-dd-yyyy | HH:mm:ss");
|
|
|
|
private final CommandHandler handler = new CommandHandler("");
|
|
private final Fi logFolder = Core.settings.getDataDirectory().child("logs/");
|
|
|
|
private Fi currentLogFile;
|
|
private boolean inExtraRound;
|
|
private Task lastTask;
|
|
private Gamemode lastMode = Gamemode.survival;
|
|
private @Nullable Map nextMapOverride;
|
|
|
|
private Thread socketThread;
|
|
private ServerSocket serverSocket;
|
|
private PrintWriter socketOutput;
|
|
|
|
public ServerControl(String[] args){
|
|
Core.settings.defaults(
|
|
"shufflemode", "normal",
|
|
"bans", "",
|
|
"admins", "",
|
|
"shufflemode", "custom",
|
|
"globalrules", "{reactorExplosions: false}"
|
|
);
|
|
|
|
Log.setLogger((level, text, args1) -> {
|
|
String result = "[" + dateTime.format(LocalDateTime.now()) + "] " + format(tags[level.ordinal()] + " " + text + "&fr", args1);
|
|
System.out.println(result);
|
|
|
|
if(Config.logging.bool()){
|
|
logToFile("[" + dateTime.format(LocalDateTime.now()) + "] " + format(tags[level.ordinal()] + " " + text + "&fr", false, args1));
|
|
}
|
|
|
|
if(socketOutput != null){
|
|
try{
|
|
socketOutput.println(format(text + "&fr", false, args1));
|
|
}catch(Throwable e){
|
|
err("Error occurred logging to socket: {0}", e.getClass().getSimpleName());
|
|
}
|
|
}
|
|
});
|
|
|
|
Time.setDeltaProvider(() -> Core.graphics.getDeltaTime() * 60f);
|
|
Effects.setScreenShakeProvider((a, b) -> {});
|
|
Effects.setEffectProvider((a, b, c, d, e, f) -> {});
|
|
|
|
registerCommands();
|
|
|
|
Core.app.post(() -> {
|
|
Array<String> commands = new Array<>();
|
|
|
|
if(args.length > 0){
|
|
commands.addAll(Strings.join(" ", args).split(","));
|
|
info("&lmFound {0} command-line arguments to parse.", commands.size);
|
|
}
|
|
|
|
if(!Config.startCommands.string().isEmpty()){
|
|
String[] startup = Strings.join(" ", Config.startCommands.string()).split(",");
|
|
info("&lmFound {0} startup commands.", startup.length);
|
|
commands.addAll(startup);
|
|
}
|
|
|
|
for(String s : commands){
|
|
CommandResponse response = handler.handleMessage(s);
|
|
if(response.type != ResponseType.valid){
|
|
err("Invalid command argument sent: '{0}': {1}", s, response.type.name());
|
|
err("Argument usage: &lc<command-1> <command1-args...>,<command-2> <command-2-args2...>");
|
|
}
|
|
}
|
|
});
|
|
|
|
customMapDirectory.mkdirs();
|
|
|
|
Thread thread = new Thread(this::readCommands, "Server Controls");
|
|
thread.setDaemon(true);
|
|
thread.start();
|
|
|
|
if(Version.build == -1){
|
|
warn("&lyYour server is running a custom build, which means that client checking is disabled.");
|
|
warn("&lyIt is highly advised to specify which version you're using by building with gradle args &lc-Pbuildversion=&lm<build>&ly.");
|
|
}
|
|
|
|
//set up default shuffle mode
|
|
try{
|
|
maps.setShuffleMode(ShuffleMode.valueOf(Core.settings.getString("shufflemode")));
|
|
}catch(Exception e){
|
|
maps.setShuffleMode(ShuffleMode.all);
|
|
}
|
|
|
|
Events.on(GameOverEvent.class, event -> {
|
|
if(inExtraRound) return;
|
|
if(state.rules.waves){
|
|
info("&lcGame over! Reached wave &ly{0}&lc with &ly{1}&lc players online on map &ly{2}&lc.", state.wave, playerGroup.size(), Strings.capitalize(world.getMap().name()));
|
|
}else{
|
|
info("&lcGame over! Team &ly{0}&lc is victorious with &ly{1}&lc players online on map &ly{2}&lc.", event.winner.name, playerGroup.size(), Strings.capitalize(world.getMap().name()));
|
|
}
|
|
|
|
//set next map to be played
|
|
Map map = nextMapOverride != null ? nextMapOverride : maps.getNextMap(world.getMap());
|
|
nextMapOverride = null;
|
|
if(map != null){
|
|
Call.onInfoMessage((state.rules.pvp
|
|
? "[YELLOW]The " + event.winner.name + " team is victorious![]" : "[SCARLET]Game over![]")
|
|
+ "\nNext selected map:[accent] " + map.name() + "[]"
|
|
+ (map.tags.containsKey("author") && !map.tags.get("author").trim().isEmpty() ? " by[accent] " + map.author() + "[]" : "") + "." +
|
|
"\nNew game begins in " + roundExtraTime + "[] seconds.");
|
|
|
|
info("Selected next map to be {0}.", map.name());
|
|
|
|
play(true, () -> world.loadMap(map, map.applyRules(lastMode)));
|
|
}else{
|
|
netServer.kickAll(KickReason.gameover);
|
|
state.set(State.menu);
|
|
net.closeServer();
|
|
}
|
|
});
|
|
|
|
Events.on(Trigger.socketConfigChanged, () -> {
|
|
toggleSocket(false);
|
|
toggleSocket(Config.socketInput.bool());
|
|
});
|
|
|
|
Events.on(PlayEvent.class, e -> {
|
|
try{
|
|
JsonValue value = JsonIO.json().fromJson(null, Core.settings.getString("globalrules"));
|
|
JsonIO.json().readFields(state.rules, value);
|
|
}catch(Throwable t){
|
|
Log.err("Error applying custom rules, proceeding without them.", t);
|
|
}
|
|
});
|
|
|
|
if(!mods.list().isEmpty()){
|
|
info("&lc{0} mods loaded.", mods.list().size);
|
|
}
|
|
|
|
toggleSocket(Config.socketInput.bool());
|
|
|
|
info("&lcServer loaded. Type &ly'help'&lc for help.");
|
|
}
|
|
|
|
private void registerCommands(){
|
|
handler.register("help", "Displays this command list.", arg -> {
|
|
info("Commands:");
|
|
for(Command command : handler.getCommandList()){
|
|
info(" &y" + command.text + (command.paramText.isEmpty() ? "" : " ") + command.paramText + " - &lm" + command.description);
|
|
}
|
|
});
|
|
|
|
handler.register("version", "Displays server version info.", arg -> {
|
|
info("&lmVersion: &lyMindustry {0}-{1} {2} / build {3}", Version.number, Version.modifier, Version.type, Version.build + (Version.revision == 0 ? "" : "." + Version.revision));
|
|
info("&lmJava Version: &ly{0}", System.getProperty("java.version"));
|
|
});
|
|
|
|
handler.register("exit", "Exit the server application.", arg -> {
|
|
info("Shutting down server.");
|
|
net.dispose();
|
|
Core.app.exit();
|
|
});
|
|
|
|
handler.register("stop", "Stop hosting the server.", arg -> {
|
|
net.closeServer();
|
|
if(lastTask != null) lastTask.cancel();
|
|
state.set(State.menu);
|
|
info("Stopped server.");
|
|
});
|
|
|
|
handler.register("host", "[mapname] [mode]", "Open the server. Will default to survival and a random map if not specified.", arg -> {
|
|
if(state.is(State.playing)){
|
|
err("Already hosting. Type 'stop' to stop hosting first.");
|
|
return;
|
|
}
|
|
|
|
if(lastTask != null) lastTask.cancel();
|
|
|
|
Map result;
|
|
if(arg.length > 0){
|
|
result = maps.all().find(map -> map.name().equalsIgnoreCase(arg[0].replace('_', ' ')) || map.name().equalsIgnoreCase(arg[0]));
|
|
|
|
if(result == null){
|
|
err("No map with name &y'{0}'&lr found.", arg[0]);
|
|
return;
|
|
}
|
|
}else{
|
|
Array<Map> maps = Vars.maps.customMaps().size == 0 ? Vars.maps.defaultMaps() : Vars.maps.customMaps();
|
|
result = maps.random();
|
|
info("Randomized next map to be {0}.", result.name());
|
|
}
|
|
|
|
Gamemode preset = Gamemode.survival;
|
|
|
|
if(arg.length > 1){
|
|
try{
|
|
preset = Gamemode.valueOf(arg[1]);
|
|
}catch(IllegalArgumentException e){
|
|
err("No gamemode '{0}' found.", arg[1]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
info("Loading map...");
|
|
|
|
logic.reset();
|
|
lastMode = preset;
|
|
try{
|
|
world.loadMap(result, result.applyRules(lastMode));
|
|
state.rules = result.applyRules(preset);
|
|
logic.play();
|
|
|
|
info("Map loaded.");
|
|
|
|
netServer.openServer();
|
|
}catch(MapException e){
|
|
Log.err(e.map.name() + ": " + e.getMessage());
|
|
}
|
|
});
|
|
|
|
handler.register("maps", "Display all available maps.", arg -> {
|
|
if(!maps.all().isEmpty()){
|
|
info("Maps:");
|
|
for(Map map : maps.all()){
|
|
info(" &ly{0}: &lb&fi{1} / {2}x{3}", map.name(), map.custom ? "Custom" : "Default", map.width, map.height);
|
|
}
|
|
}else{
|
|
info("No maps found.");
|
|
}
|
|
info("&lyMap directory: &lb&fi{0}", customMapDirectory.file().getAbsoluteFile().toString());
|
|
});
|
|
|
|
handler.register("reloadmaps", "Reload all maps from disk.", arg -> {
|
|
int beforeMaps = maps.all().size;
|
|
maps.reload();
|
|
if(maps.all().size > beforeMaps){
|
|
info("&lc{0}&ly new map(s) found and reloaded.", maps.all().size - beforeMaps);
|
|
}else{
|
|
info("&lyMaps reloaded.");
|
|
}
|
|
});
|
|
|
|
handler.register("status", "Display server status.", arg -> {
|
|
if(state.is(State.menu)){
|
|
info("Status: &rserver closed");
|
|
}else{
|
|
info("Status:");
|
|
info(" &lyPlaying on map &fi{0}&fb &lb/&ly Wave {1}", Strings.capitalize(world.getMap().name()), state.wave);
|
|
|
|
if(state.rules.waves){
|
|
info("&ly {0} enemies.", state.enemies);
|
|
}else{
|
|
info("&ly {0} seconds until next wave.", (int)(state.wavetime / 60));
|
|
}
|
|
|
|
info(" &ly{0} FPS, {1} MB used.", Core.graphics.getFramesPerSecond(), Core.app.getJavaHeap() / 1024 / 1024);
|
|
|
|
if(playerGroup.size() > 0){
|
|
info(" &lyPlayers: {0}", playerGroup.size());
|
|
for(Player p : playerGroup.all()){
|
|
info(" &y{0} / {1}", p.name, p.uuid);
|
|
}
|
|
}else{
|
|
info(" &lyNo players connected.");
|
|
}
|
|
}
|
|
});
|
|
|
|
handler.register("mods", "Display all loaded mods.", arg -> {
|
|
if(!mods.list().isEmpty()){
|
|
info("Mods:");
|
|
for(LoadedMod mod : mods.list()){
|
|
info(" &ly{0} &lcv{1}", mod.meta.displayName(), mod.meta.version);
|
|
}
|
|
}else{
|
|
info("No mods found.");
|
|
}
|
|
info("&lyMod directory: &lb&fi{0}", modDirectory.file().getAbsoluteFile().toString());
|
|
});
|
|
|
|
handler.register("mod", "<name...>", "Display information about a loaded plugin.", arg -> {
|
|
LoadedMod mod = mods.list().find(p -> p.meta.name.equalsIgnoreCase(arg[0]));
|
|
if(mod != null){
|
|
info("Name: &ly{0}", mod.meta.displayName());
|
|
info("Internal Name: &ly{0}", mod.name);
|
|
info("Version: &ly{0}", mod.meta.version);
|
|
info("Author: &ly{0}", mod.meta.author);
|
|
info("Path: &ly{0}", mod.file.path());
|
|
info("Description: &ly{0}", mod.meta.description);
|
|
}else{
|
|
info("No mod with name &ly'{0}'&lg found.");
|
|
}
|
|
});
|
|
|
|
handler.register("js", "<script...>", "Run arbitrary Javascript.", arg -> {
|
|
info("&lc" + mods.getScripts().runConsole(arg[0]));
|
|
});
|
|
|
|
handler.register("say", "<message...>", "Send a message to all players.", arg -> {
|
|
if(!state.is(State.playing)){
|
|
err("Not hosting. Host a game first.");
|
|
return;
|
|
}
|
|
|
|
Call.sendMessage("[scarlet][[Server]:[] " + arg[0]);
|
|
|
|
info("&lyServer: &lb{0}", arg[0]);
|
|
});
|
|
|
|
handler.register("difficulty", "<difficulty>", "Set game difficulty.", arg -> {
|
|
try{
|
|
state.rules.waveSpacing = Difficulty.valueOf(arg[0]).waveTime * 60 * 60 * 2;
|
|
info("Difficulty set to '{0}'.", arg[0]);
|
|
}catch(IllegalArgumentException e){
|
|
err("No difficulty with name '{0}' found.", arg[0]);
|
|
}
|
|
});
|
|
|
|
handler.register("rules", "[remove/add] [name] [value...]", "List, remove or add global rules. These will apply regardless of map.", arg -> {
|
|
String rules = Core.settings.getString("globalrules");
|
|
JsonValue base = JsonIO.json().fromJson(null, rules);
|
|
|
|
if(arg.length == 0){
|
|
Log.info("&lyRules:\n{0}", JsonIO.print(rules));
|
|
}else if(arg.length == 1){
|
|
Log.err("Invalid usage. Specify which rule to remove or add.");
|
|
}else{
|
|
if(!(arg[0].equals("remove") || arg[0].equals("add"))){
|
|
Log.err("Invalid usage. Either add or remove rules.");
|
|
return;
|
|
}
|
|
|
|
boolean remove = arg[0].equals("remove");
|
|
if(remove){
|
|
if(base.has(arg[1])){
|
|
Log.info("Rule &lc'{0}'&lg removed.", arg[1]);
|
|
base.remove(arg[1]);
|
|
}else{
|
|
Log.err("Rule not defined, so not removed.");
|
|
return;
|
|
}
|
|
}else{
|
|
if(arg.length < 3){
|
|
Log.err("Missing last argument. Specify which value to set the rule to.");
|
|
return;
|
|
}
|
|
|
|
try{
|
|
JsonValue value = new JsonReader().parse(arg[2]);
|
|
value.name = arg[1];
|
|
|
|
JsonValue parent = new JsonValue(ValueType.object);
|
|
parent.addChild(value);
|
|
|
|
JsonIO.json().readField(state.rules, value.name, parent);
|
|
if(base.has(value.name)){
|
|
base.remove(value.name);
|
|
}
|
|
base.addChild(arg[1], value);
|
|
Log.info("Changed rule: &ly{0}", value.toString().replace("\n", " "));
|
|
}catch(Throwable e){
|
|
Log.err("Error parsing rule JSON: {0}", e.getMessage());
|
|
}
|
|
}
|
|
|
|
Core.settings.putSave("globalrules", base.toString());
|
|
Call.onSetRules(state.rules);
|
|
}
|
|
});
|
|
|
|
handler.register("fillitems", "[team]", "Fill the core with items.", arg -> {
|
|
if(!state.is(State.playing)){
|
|
err("Not playing. Host first.");
|
|
return;
|
|
}
|
|
|
|
Team team = arg.length == 0 ? Team.sharded : Structs.find(Team.all(), t -> t.name.equals(arg[0]));
|
|
|
|
if(team == null){
|
|
err("No team with that name found.");
|
|
return;
|
|
}
|
|
|
|
if(state.teams.cores(team).isEmpty()){
|
|
err("That team has no cores.");
|
|
return;
|
|
}
|
|
|
|
for(Item item : content.items()){
|
|
if(item.type == ItemType.material){
|
|
state.teams.cores(team).first().items.set(item, state.teams.cores(team).first().block.itemCapacity);
|
|
}
|
|
}
|
|
|
|
info("Core filled.");
|
|
|
|
});
|
|
|
|
handler.register("playerlimit", "[off/somenumber]", "Set the server player limit.", arg -> {
|
|
if(arg.length == 0){
|
|
info("Player limit is currently &lc{0}.", netServer.admins.getPlayerLimit() == 0 ? "off" : netServer.admins.getPlayerLimit());
|
|
return;
|
|
}
|
|
if(arg[0].equals("off")){
|
|
netServer.admins.setPlayerLimit(0);
|
|
info("Player limit disabled.");
|
|
return;
|
|
}
|
|
|
|
if(Strings.canParsePostiveInt(arg[0]) && Strings.parseInt(arg[0]) > 0){
|
|
int lim = Strings.parseInt(arg[0]);
|
|
netServer.admins.setPlayerLimit(lim);
|
|
info("Player limit is now &lc{0}.", lim);
|
|
}else{
|
|
err("Limit must be a number above 0.");
|
|
}
|
|
});
|
|
|
|
handler.register("config", "[name] [value...]", "Configure server settings.", arg -> {
|
|
if(arg.length == 0){
|
|
info("&lyAll config values:");
|
|
for(Config c : Config.all){
|
|
Log.info("&ly| &lc{0}:&lm {1}", c.name(), c.get());
|
|
Log.info("&ly| | {0}", c.description);
|
|
Log.info("&ly|");
|
|
}
|
|
return;
|
|
}
|
|
|
|
try{
|
|
Config c = Config.valueOf(arg[0]);
|
|
if(arg.length == 1){
|
|
Log.info("&lc'{0}'&lg is currently &lc{1}.", c.name(), c.get());
|
|
}else{
|
|
if(c.isBool()){
|
|
c.set(arg[1].equals("on") || arg[1].equals("true"));
|
|
}else if(c.isNum()){
|
|
try{
|
|
c.set(Integer.parseInt(arg[1]));
|
|
}catch(NumberFormatException e){
|
|
Log.err("Not a valid number: {0}", arg[1]);
|
|
return;
|
|
}
|
|
}else if(c.isString()){
|
|
c.set(arg[1]);
|
|
}
|
|
|
|
Log.info("&lc{0}&lg set to &lc{1}.", c.name(), c.get());
|
|
}
|
|
}catch(IllegalArgumentException e){
|
|
err("Unknown config: '{0}'. Run the command with no arguments to get a list of valid configs.", arg[0]);
|
|
}
|
|
});
|
|
|
|
handler.register("subnet-ban", "[add/remove] [address]", "Ban a subnet. This simply rejects all connections with IPs starting with some string.", arg -> {
|
|
if(arg.length == 0){
|
|
Log.info("Subnets banned: &lc{0}", netServer.admins.getSubnetBans().isEmpty() ? "<none>" : "");
|
|
for(String subnet : netServer.admins.getSubnetBans()){
|
|
Log.info("&ly " + subnet + "");
|
|
}
|
|
}else if(arg.length == 1){
|
|
err("You must provide a subnet to add or remove.");
|
|
}else{
|
|
if(arg[0].equals("add")){
|
|
if(netServer.admins.getSubnetBans().contains(arg[1])){
|
|
err("That subnet is already banned.");
|
|
return;
|
|
}
|
|
|
|
netServer.admins.addSubnetBan(arg[1]);
|
|
Log.info("Banned &ly{0}&lc**", arg[1]);
|
|
}else if(arg[0].equals("remove")){
|
|
if(!netServer.admins.getSubnetBans().contains(arg[1])){
|
|
err("That subnet isn't banned.");
|
|
return;
|
|
}
|
|
|
|
netServer.admins.removeSubnetBan(arg[1]);
|
|
Log.info("Unbanned &ly{0}&lc**", arg[1]);
|
|
}else{
|
|
err("Incorrect usage. You must provide add/remove as the second argument.");
|
|
}
|
|
}
|
|
});
|
|
|
|
handler.register("whitelisted", "List the entire whitelist.", arg -> {
|
|
if(netServer.admins.getWhitelisted().isEmpty()){
|
|
info("&lyNo whitelisted players found.");
|
|
return;
|
|
}
|
|
|
|
info("&lyWhitelist:");
|
|
netServer.admins.getWhitelisted().each(p -> Log.info("- &ly{0}", p.lastName));
|
|
});
|
|
|
|
handler.register("whitelist-add", "<ID>", "Add a player to the whitelist by ID.", arg -> {
|
|
PlayerInfo info = netServer.admins.getInfoOptional(arg[0]);
|
|
if(info == null){
|
|
err("Player ID not found. You must use the ID displayed when a player joins a server.");
|
|
return;
|
|
}
|
|
|
|
netServer.admins.whitelist(arg[0]);
|
|
info("Player &ly'{0}'&lg has been whitelisted.", info.lastName);
|
|
});
|
|
|
|
handler.register("whitelist-remove", "<ID>", "Remove a player to the whitelist by ID.", arg -> {
|
|
PlayerInfo info = netServer.admins.getInfoOptional(arg[0]);
|
|
if(info == null){
|
|
err("Player ID not found. You must use the ID displayed when a player joins a server.");
|
|
return;
|
|
}
|
|
|
|
netServer.admins.unwhitelist(arg[0]);
|
|
info("Player &ly'{0}'&lg has been un-whitelisted.", info.lastName);
|
|
});
|
|
|
|
handler.register("shuffle", "[none/all/custom/builtin]", "Set map shuffling mode.", arg -> {
|
|
if(arg.length == 0){
|
|
info("Shuffle mode current set to &ly'{0}'&lg.", maps.getShuffleMode());
|
|
}else{
|
|
try{
|
|
ShuffleMode mode = ShuffleMode.valueOf(arg[0]);
|
|
Core.settings.putSave("shufflemode", mode.name());
|
|
maps.setShuffleMode(mode);
|
|
info("Shuffle mode set to &ly'{0}'&lg.", arg[0]);
|
|
}catch(Exception e){
|
|
err("Invalid shuffle mode.");
|
|
}
|
|
}
|
|
});
|
|
|
|
handler.register("nextmap", "<mapname...>", "Set the next map to be played after a game-over. Overrides shuffling.", arg -> {
|
|
Map res = maps.all().find(map -> map.name().equalsIgnoreCase(arg[0].replace('_', ' ')) || map.name().equalsIgnoreCase(arg[0]));
|
|
if(res != null){
|
|
nextMapOverride = res;
|
|
Log.info("Next map set to &ly'{0}'.", res.name());
|
|
}else{
|
|
Log.err("No map '{0}' found.", arg[0]);
|
|
}
|
|
});
|
|
|
|
handler.register("kick", "<username...>", "Kick a person by name.", arg -> {
|
|
if(!state.is(State.playing)){
|
|
err("Not hosting a game yet. Calm down.");
|
|
return;
|
|
}
|
|
|
|
Player target = playerGroup.find(p -> p.name.equals(arg[0]));
|
|
|
|
if(target != null){
|
|
Call.sendMessage("[scarlet] " + target.name + "[scarlet] has been kicked by the server.");
|
|
target.con.kick(KickReason.kick);
|
|
info("It is done.");
|
|
}else{
|
|
info("Nobody with that name could be found...");
|
|
}
|
|
});
|
|
|
|
handler.register("ban", "<type-id/name/ip> <username/IP/ID...>", "Ban a person.", arg -> {
|
|
if(arg[0].equals("id")){
|
|
netServer.admins.banPlayerID(arg[1]);
|
|
info("Banned.");
|
|
}else if(arg[0].equals("name")){
|
|
Player target = playerGroup.find(p -> p.name.equalsIgnoreCase(arg[1]));
|
|
if(target != null){
|
|
netServer.admins.banPlayer(target.uuid);
|
|
info("Banned.");
|
|
}else{
|
|
err("No matches found.");
|
|
}
|
|
}else if(arg[0].equals("ip")){
|
|
netServer.admins.banPlayerIP(arg[1]);
|
|
info("Banned.");
|
|
}else{
|
|
err("Invalid type.");
|
|
}
|
|
|
|
for(Player player : playerGroup.all()){
|
|
if(netServer.admins.isIDBanned(player.uuid)){
|
|
Call.sendMessage("[scarlet] " + player.name + " has been banned.");
|
|
player.con.kick(KickReason.banned);
|
|
}
|
|
}
|
|
});
|
|
|
|
handler.register("bans", "List all banned IPs and IDs.", arg -> {
|
|
Array<PlayerInfo> bans = netServer.admins.getBanned();
|
|
|
|
if(bans.size == 0){
|
|
info("No ID-banned players have been found.");
|
|
}else{
|
|
info("&lyBanned players [ID]:");
|
|
for(PlayerInfo info : bans){
|
|
info(" &ly {0} / Last known name: '{1}'", info.id, info.lastName);
|
|
}
|
|
}
|
|
|
|
Array<String> ipbans = netServer.admins.getBannedIPs();
|
|
|
|
if(ipbans.size == 0){
|
|
info("No IP-banned players have been found.");
|
|
}else{
|
|
info("&lmBanned players [IP]:");
|
|
for(String string : ipbans){
|
|
PlayerInfo info = netServer.admins.findByIP(string);
|
|
if(info != null){
|
|
info(" &lm '{0}' / Last known name: '{1}' / ID: '{2}'", string, info.lastName, info.id);
|
|
}else{
|
|
info(" &lm '{0}' (No known name or info)", string);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
handler.register("unban", "<ip/ID>", "Completely unban a person by IP or ID.", arg -> {
|
|
if(arg[0].contains(".")){
|
|
if(netServer.admins.unbanPlayerIP(arg[0])){
|
|
info("Unbanned player by IP: {0}.", arg[0]);
|
|
}else{
|
|
err("That IP is not banned!");
|
|
}
|
|
}else{
|
|
if(netServer.admins.unbanPlayerID(arg[0])){
|
|
info("Unbanned player by ID: {0}.", arg[0]);
|
|
}else{
|
|
err("That ID is not banned!");
|
|
}
|
|
}
|
|
});
|
|
|
|
handler.register("admin", "<username...>", "Make an online user admin", arg -> {
|
|
if(!state.is(State.playing)){
|
|
err("Open the server first.");
|
|
return;
|
|
}
|
|
|
|
Player target = playerGroup.find(p -> p.name.equals(arg[0]));
|
|
|
|
if(target != null){
|
|
netServer.admins.adminPlayer(target.uuid, target.usid);
|
|
target.isAdmin = true;
|
|
info("Admin-ed player: {0}", arg[0]);
|
|
}else{
|
|
info("Nobody with that name could be found.");
|
|
}
|
|
});
|
|
|
|
handler.register("unadmin", "<username...>", "Removes admin status from an online player", arg -> {
|
|
if(!state.is(State.playing)){
|
|
err("Open the server first.");
|
|
return;
|
|
}
|
|
|
|
Player target = playerGroup.find(p -> p.name.equals(arg[0]));
|
|
|
|
if(target != null){
|
|
netServer.admins.unAdminPlayer(target.uuid);
|
|
target.isAdmin = false;
|
|
info("Un-admin-ed player: {0}", arg[0]);
|
|
}else{
|
|
info("Nobody with that name could be found.");
|
|
}
|
|
});
|
|
|
|
handler.register("admins", "List all admins.", arg -> {
|
|
Array<PlayerInfo> admins = netServer.admins.getAdmins();
|
|
|
|
if(admins.size == 0){
|
|
info("No admins have been found.");
|
|
}else{
|
|
info("&lyAdmins:");
|
|
for(PlayerInfo info : admins){
|
|
info(" &lm {0} / ID: '{1}' / IP: '{2}'", info.lastName, info.id, info.lastIP);
|
|
}
|
|
}
|
|
});
|
|
|
|
handler.register("runwave", "Trigger the next wave.", arg -> {
|
|
if(!state.is(State.playing)){
|
|
err("Not hosting. Host a game first.");
|
|
}else{
|
|
logic.runWave();
|
|
info("Wave spawned.");
|
|
}
|
|
});
|
|
|
|
handler.register("load", "<slot>", "Load a save from a slot.", arg -> {
|
|
if(state.is(State.playing)){
|
|
err("Already hosting. Type 'stop' to stop hosting first.");
|
|
return;
|
|
}
|
|
|
|
Fi file = saveDirectory.child(arg[0] + "." + saveExtension);
|
|
|
|
if(!SaveIO.isSaveValid(file)){
|
|
err("No (valid) save data found for slot.");
|
|
return;
|
|
}
|
|
|
|
Core.app.post(() -> {
|
|
try{
|
|
SaveIO.load(file);
|
|
state.rules.zone = null;
|
|
info("Save loaded.");
|
|
state.set(State.playing);
|
|
netServer.openServer();
|
|
}catch(Throwable t){
|
|
err("Failed to load save. Outdated or corrupt file.");
|
|
}
|
|
});
|
|
});
|
|
|
|
handler.register("save", "<slot>", "Save game state to a slot.", arg -> {
|
|
if(!state.is(State.playing)){
|
|
err("Not hosting. Host a game first.");
|
|
return;
|
|
}
|
|
|
|
Fi file = saveDirectory.child(arg[0] + "." + saveExtension);
|
|
|
|
Core.app.post(() -> {
|
|
SaveIO.save(file);
|
|
info("Saved to {0}.", file);
|
|
});
|
|
});
|
|
|
|
handler.register("saves", "List all saves in the save directory.", arg -> {
|
|
info("Save files: ");
|
|
for(Fi file : saveDirectory.list()){
|
|
if(file.extension().equals(saveExtension)){
|
|
info("| &ly{0}", file.nameWithoutExtension());
|
|
}
|
|
}
|
|
});
|
|
|
|
handler.register("gameover", "Force a game over.", arg -> {
|
|
if(state.is(State.menu)){
|
|
err("Not playing a map.");
|
|
return;
|
|
}
|
|
|
|
info("&lyCore destroyed.");
|
|
inExtraRound = false;
|
|
Events.fire(new GameOverEvent(Team.crux));
|
|
});
|
|
|
|
handler.register("info", "<IP/UUID/name...>", "Find player info(s). Can optionally check for all names or IPs a player has had.", arg -> {
|
|
|
|
ObjectSet<PlayerInfo> infos = netServer.admins.findByName(arg[0]);
|
|
|
|
if(infos.size > 0){
|
|
info("&lgPlayers found: {0}", infos.size);
|
|
|
|
int i = 0;
|
|
for(PlayerInfo info : infos){
|
|
info("&lc[{0}] Trace info for player '{1}' / UUID {2}", i++, info.lastName, info.id);
|
|
info(" &lyall names used: {0}", info.names);
|
|
info(" &lyIP: {0}", info.lastIP);
|
|
info(" &lyall IPs used: {0}", info.ips);
|
|
info(" &lytimes joined: {0}", info.timesJoined);
|
|
info(" &lytimes kicked: {0}", info.timesKicked);
|
|
}
|
|
}else{
|
|
info("Nobody with that name could be found.");
|
|
}
|
|
});
|
|
|
|
handler.register("gc", "Trigger a grabage struct. Testing only.", arg -> {
|
|
int pre = (int)(Core.app.getJavaHeap() / 1024 / 1024);
|
|
System.gc();
|
|
int post = (int)(Core.app.getJavaHeap() / 1024 / 1024);
|
|
info("&ly{0}&lg MB collected. Memory usage now at &ly{1}&lg MB.", pre - post, post);
|
|
});
|
|
|
|
mods.eachClass(p -> p.registerServerCommands(handler));
|
|
mods.eachClass(p -> p.registerClientCommands(netServer.clientCommands));
|
|
}
|
|
|
|
private void readCommands(){
|
|
|
|
Scanner scan = new Scanner(System.in);
|
|
while(scan.hasNext()){
|
|
String line = scan.nextLine();
|
|
Core.app.post(() -> handleCommandString(line));
|
|
}
|
|
}
|
|
|
|
private void handleCommandString(String line){
|
|
CommandResponse response = handler.handleMessage(line);
|
|
|
|
if(response.type == ResponseType.unknownCommand){
|
|
|
|
int minDst = 0;
|
|
Command closest = null;
|
|
|
|
for(Command command : handler.getCommandList()){
|
|
int dst = Strings.levenshtein(command.text, response.runCommand);
|
|
if(dst < 3 && (closest == null || dst < minDst)){
|
|
minDst = dst;
|
|
closest = command;
|
|
}
|
|
}
|
|
|
|
if(closest != null){
|
|
err("Command not found. Did you mean \"" + closest.text + "\"?");
|
|
}else{
|
|
err("Invalid command. Type 'help' for help.");
|
|
}
|
|
}else if(response.type == ResponseType.fewArguments){
|
|
err("Too few command arguments. Usage: " + response.command.text + " " + response.command.paramText);
|
|
}else if(response.type == ResponseType.manyArguments){
|
|
err("Too many command arguments. Usage: " + response.command.text + " " + response.command.paramText);
|
|
}
|
|
}
|
|
|
|
private void play(boolean wait, Runnable run){
|
|
inExtraRound = true;
|
|
Runnable r = () -> {
|
|
Array<Player> players = new Array<>();
|
|
for(Player p : playerGroup.all()){
|
|
players.add(p);
|
|
p.setDead(true);
|
|
}
|
|
|
|
logic.reset();
|
|
|
|
Call.onWorldDataBegin();
|
|
run.run();
|
|
state.rules = world.getMap().applyRules(lastMode);
|
|
logic.play();
|
|
|
|
for(Player p : players){
|
|
if(p.con == null) continue;
|
|
|
|
p.reset();
|
|
if(state.rules.pvp){
|
|
p.setTeam(netServer.assignTeam(p, new ArrayIterable<>(players)));
|
|
}
|
|
netServer.sendWorldData(p);
|
|
}
|
|
inExtraRound = false;
|
|
};
|
|
|
|
if(wait){
|
|
lastTask = new Task(){
|
|
@Override
|
|
public void run(){
|
|
try{
|
|
r.run();
|
|
}catch(MapException e){
|
|
Log.err(e.map.name() + ": " + e.getMessage());
|
|
net.closeServer();
|
|
}
|
|
}
|
|
};
|
|
|
|
Timer.schedule(lastTask, roundExtraTime);
|
|
}else{
|
|
r.run();
|
|
}
|
|
}
|
|
|
|
private void logToFile(String text){
|
|
if(currentLogFile != null && currentLogFile.length() > maxLogLength){
|
|
String date = DateTimeFormatter.ofPattern("MM-dd-yyyy | HH:mm:ss").format(LocalDateTime.now());
|
|
currentLogFile.writeString("[End of log file. Date: " + date + "]\n", true);
|
|
currentLogFile = null;
|
|
}
|
|
|
|
if(currentLogFile == null){
|
|
int i = 0;
|
|
while(logFolder.child("log-" + i + ".txt").length() >= maxLogLength){
|
|
i++;
|
|
}
|
|
|
|
currentLogFile = logFolder.child("log-" + i + ".txt");
|
|
}
|
|
|
|
currentLogFile.writeString(text + "\n", true);
|
|
}
|
|
|
|
private void toggleSocket(boolean on){
|
|
if(on && socketThread == null){
|
|
socketThread = new Thread(() -> {
|
|
try{
|
|
serverSocket = new ServerSocket();
|
|
serverSocket.bind(new InetSocketAddress(Config.socketInputAddress.string(), Config.socketInputPort.num()));
|
|
while(true){
|
|
Socket client = serverSocket.accept();
|
|
info("&lmRecieved command socket connection: &lb{0}", serverSocket.getLocalSocketAddress());
|
|
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
|
|
socketOutput = new PrintWriter(client.getOutputStream(), true);
|
|
String line;
|
|
while(client.isConnected() && (line = in.readLine()) != null){
|
|
String result = line;
|
|
Core.app.post(() -> handleCommandString(result));
|
|
}
|
|
info("&lmLost command socket connection: &lb{0}", serverSocket.getLocalSocketAddress());
|
|
socketOutput = null;
|
|
}
|
|
}catch(BindException b){
|
|
err("Command input socket already in use. Is another instance of the server running?");
|
|
}catch(IOException e){
|
|
if(!e.getMessage().equals("Socket closed")){
|
|
err("Terminating socket server.");
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
});
|
|
socketThread.setDaemon(true);
|
|
socketThread.start();
|
|
}else if(socketThread != null){
|
|
socketThread.interrupt();
|
|
try{
|
|
serverSocket.close();
|
|
}catch(IOException e){
|
|
e.printStackTrace();
|
|
}
|
|
socketThread = null;
|
|
socketOutput = null;
|
|
}
|
|
}
|
|
}
|