diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index e67b1630d4..acbe9149c1 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -219,6 +219,7 @@ public class NetClient extends Module { new Random().nextBytes(bytes); String result = new String(Base64Coder.encode(bytes)); Settings.putString("usid-" + ip, result); + Settings.save(); return result; } } diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index da30e5efd2..7eeb697bc5 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -485,14 +485,18 @@ public class NetServer extends Module{ return; } - if(other == null || other.isAdmin){ + if(other == null || (other.isAdmin && other != player)){ //fun fact: this means you can ban yourself Log.err("{0} attempted to perform admin action on nonexistant or admin player.", player.name); return; } String ip = player.con.address; - if(action == AdminAction.ban){ + if(action == AdminAction.wave) { + //no verification is done, so admins can hypothetically spam waves + //not a real issue, because server owners may want to do just that + state.wavetime = 0f; + }else if(action == AdminAction.ban){ netServer.admins.banPlayerIP(ip); netServer.kick(other.con.id, KickReason.banned); Log.info("&lc{0} has banned {1}.", player.name, other.name); diff --git a/core/src/io/anuke/mindustry/net/Packets.java b/core/src/io/anuke/mindustry/net/Packets.java index 26041a4f55..c3986416c6 100644 --- a/core/src/io/anuke/mindustry/net/Packets.java +++ b/core/src/io/anuke/mindustry/net/Packets.java @@ -179,7 +179,7 @@ public class Packets { } public enum AdminAction{ - kick, ban, trace + kick, ban, trace, wave } /**Marks the beginning of a stream.*/ diff --git a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java index f61acbe745..8fb5334a82 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java @@ -8,7 +8,9 @@ import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Scaling; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.net.Net; +import io.anuke.mindustry.net.Packets.AdminAction; import io.anuke.mindustry.type.Recipe; import io.anuke.mindustry.ui.IntFormat; import io.anuke.mindustry.ui.Minimap; @@ -331,13 +333,17 @@ public class HudFragment extends Fragment{ private void playButton(float uheight){ new imagebutton("icon-play", 30f, () -> { - state.wavetime = 0f; + if(Net.client() && players[0].isAdmin){ + Call.onAdminRequest(players[0], AdminAction.wave); + }else { + state.wavetime = 0f; + } }).height(uheight).fillX().right().padTop(-8f).padBottom(-12f).padLeft(-15).padRight(-10).width(40f).update(l->{ - boolean vis = state.mode.disableWaveTimer && (Net.server() || !Net.active()); + boolean vis = state.mode.disableWaveTimer && ((Net.server() || players[0].isAdmin) || !Net.active()); boolean paused = state.is(State.paused) || !vis; l.getStyle().imageUp = Core.skin.getDrawable(vis ? "icon-play" : "clear"); l.setTouchable(!paused ? Touchable.enabled : Touchable.disabled); - }).visible(() -> state.mode.disableWaveTimer && (Net.server() || !Net.active()) && unitGroups[Team.red.ordinal()].size() == 0); + }).visible(() -> state.mode.disableWaveTimer && ((Net.server() || players[0].isAdmin) || !Net.active()) && unitGroups[Team.red.ordinal()].size() == 0); } } diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index b9f87cd798..99c93b1b6d 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -8,6 +8,7 @@ import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.game.Difficulty; import io.anuke.mindustry.game.EventType.GameOverEvent; import io.anuke.mindustry.game.GameMode; +import io.anuke.mindustry.game.Team; import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.io.Map; import io.anuke.mindustry.io.SaveIO; @@ -138,7 +139,7 @@ public class ServerControl extends Module { Log.info("Stopped server."); }); - handler.register("host", "[mapname] [mode]", "Open the server with a specific map.", arg -> { + handler.register("host", "[mode] [mapname]", "Open the server with a specific map.", arg -> { if(state.is(State.playing)){ err("Already hosting. Type 'stop' to stop hosting first."); return; @@ -147,34 +148,39 @@ public class ServerControl extends Module { Map result = null; if(arg.length > 0) { - String search = arg[0]; - for (Map map : world.maps().all()) { - if (map.name.equalsIgnoreCase(search)) - result = map; - } - - if(result == null){ - err("No map with name &y'{0}'&lr found.", search); - return; - } - GameMode mode; try{ - mode = arg.length < 2 ? GameMode.waves : GameMode.valueOf(arg[1]); + mode = GameMode.valueOf(arg[0]); }catch (IllegalArgumentException e){ - err("No gamemode '{0}' found.", arg[1]); + err("No gamemode '{0}' found.", arg[0]); return; } info("Loading map..."); state.mode = mode; - logic.reset(); - world.loadMap(result); + if(arg.length > 1) { + String search = arg[1]; + for (Map map : world.maps().all()) { + if (map.name.equalsIgnoreCase(search)) + result = map; + } + + if (result == null) { + err("No map with name &y'{0}'&lr found.", search); + return; + } + + logic.reset(); + world.loadMap(result); + }else{ + Log.info("&ly&fiNo map specified, so a procedural one was generated."); + world.loadProceduralMap(); + } }else{ - world.loadProceduralMap(); Log.info("&ly&fiNo map specified, so a procedural one was generated."); + world.loadProceduralMap(); } logic.play(); @@ -194,10 +200,10 @@ public class ServerControl extends Module { if(state.is(State.menu)){ info("&lyStatus: &rserver closed"); }else{ - info("&lyStatus: &lcPlaying on map &fi{0}&fb &lb/&lc Wave {1} &lb/&lc {2}", - Strings.capitalize(world.getMap().name), state.wave, Strings.capitalize(state.difficulty.name())); - if(state.enemies > 0){ - info("&ly{0} enemies remaining.", state.enemies); + info("&lyStatus: &lcPlaying on map &fi{0}&fb &lb/&lc Wave {1} &lb/&lc {2} &lb/&lc {3}", + Strings.capitalize(world.getMap().name), state.wave, Strings.capitalize(state.difficulty.name()), Strings.capitalize(state.mode.name())); + if(state.mode.disableWaveTimer){ + info("&ly{0} enemies.", unitGroups[Team.red.ordinal()].size()); }else{ info("&ly{0} seconds until next wave.", (int)(state.wavetime / 60)); } @@ -464,6 +470,7 @@ public class ServerControl extends Module { if(target != null){ netServer.admins.adminPlayer(target.uuid, target.usid); + target.isAdmin = true; info("Admin-ed player by ID: {0} / {1}", target.uuid, arg[0]); }else{ info("Nobody with that name could be found."); @@ -487,6 +494,7 @@ public class ServerControl extends Module { if(target != null){ netServer.admins.unAdminPlayer(target.uuid); + target.isAdmin = false; info("Un-admin-ed player by ID: {0} / {1}", target.uuid, arg[0]); }else{ info("Nobody with that name could be found."); @@ -509,8 +517,6 @@ public class ServerControl extends Module { handler.register("runwave", "Trigger the next wave.", arg -> { if(!state.is(State.playing)) { err("Not hosting. Host a game first."); - }else if(state.enemies > 0){ - err("There are still {0} enemies remaining.", state.enemies); }else{ logic.runWave(); info("Wave spawned.");