diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index b8f998ab54..01167331eb 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -263,7 +263,17 @@ trace.mobile = Mobile Client: [accent]{0} trace.modclient = Custom Client: [accent]{0} trace.times.joined = Times Joined: [accent]{0} trace.times.kicked = Times Kicked: [accent]{0} +trace.ips = IPs: +trace.names = Names: invalidid = Invalid client ID! Submit a bug report. + +player.ban = Ban +player.kick = Kick +player.trace = Trace +player.admin = Toggle Admin +player.team = Change Team +player.changeteam = Team + server.bans = Bans server.bans.none = No banned players found! server.admins = Admins diff --git a/core/src/mindustry/ai/ControlPathfinder.java b/core/src/mindustry/ai/ControlPathfinder.java index c369f0d986..4c72159df0 100644 --- a/core/src/mindustry/ai/ControlPathfinder.java +++ b/core/src/mindustry/ai/ControlPathfinder.java @@ -50,8 +50,7 @@ public class ControlPathfinder{ costLegs = (team, tile) -> PathTile.legSolid(tile) ? impassable : 1 + - (PathTile.deep(tile) ? 6000 : 0) + - (PathTile.nearSolid(tile) || PathTile.solid(tile) ? 3 : 0), + (PathTile.deep(tile) ? 6000 : 0), costNaval = (team, tile) -> //impassable same-team neutral block, or non-liquid diff --git a/core/src/mindustry/core/NetServer.java b/core/src/mindustry/core/NetServer.java index 479ca7aa04..2fb5de2f71 100644 --- a/core/src/mindustry/core/NetServer.java +++ b/core/src/mindustry/core/NetServer.java @@ -784,7 +784,7 @@ public class NetServer implements ApplicationListener{ } @Remote(targets = Loc.client, called = Loc.server) - public static void adminRequest(Player player, Player other, AdminAction action){ + public static void adminRequest(Player player, Player other, AdminAction action, Object params){ if(!player.admin && !player.isLocal()){ warn("ACCESS DENIED: Player @ / @ attempted to perform admin action '@' on '@' without proper security access.", player.plainName(), player.con == null ? "null" : player.con.address, action.name(), other == null ? null : other.plainName()); @@ -798,28 +798,38 @@ public class NetServer implements ApplicationListener{ Events.fire(new EventType.AdminRequestEvent(player, other, action)); - 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 - logic.skipWave(); - info("&lc@ &fi&lk[&lb@&fi&lk]&fb has skipped the wave.", player.plainName(), player.uuid()); - }else if(action == AdminAction.ban){ - netServer.admins.banPlayerID(other.con.uuid); - netServer.admins.banPlayerIP(other.con.address); - other.kick(KickReason.banned); - info("&lc@ &fi&lk[&lb@&fi&lk]&fb has banned @ &fi&lk[&lb@&fi&lk]&fb.", player.plainName(), player.uuid(), other.plainName(), other.uuid()); - }else if(action == AdminAction.kick){ - other.kick(KickReason.kick); - info("&lc@ &fi&lk[&lb@&fi&lk]&fb has kicked @ &fi&lk[&lb@&fi&lk]&fb.", player.plainName(), player.uuid(), other.plainName(), other.uuid()); - }else if(action == AdminAction.trace){ - PlayerInfo stats = netServer.admins.getInfo(other.uuid()); - TraceInfo info = new TraceInfo(other.con.address, other.uuid(), other.con.modclient, other.con.mobile, stats.timesJoined, stats.timesKicked); - if(player.con != null){ - Call.traceInfo(player.con, other, info); - }else{ - NetClient.traceInfo(other, info); + switch(action){ + case wave -> { + //no verification is done, so admins can hypothetically spam waves + //not a real issue, because server owners may want to do just that + logic.skipWave(); + info("&lc@ &fi&lk[&lb@&fi&lk]&fb has skipped the wave.", player.plainName(), player.uuid()); + } + case ban -> { + netServer.admins.banPlayerID(other.con.uuid); + netServer.admins.banPlayerIP(other.con.address); + other.kick(KickReason.banned); + info("&lc@ &fi&lk[&lb@&fi&lk]&fb has banned @ &fi&lk[&lb@&fi&lk]&fb.", player.plainName(), player.uuid(), other.plainName(), other.uuid()); + } + case kick -> { + other.kick(KickReason.kick); + info("&lc@ &fi&lk[&lb@&fi&lk]&fb has kicked @ &fi&lk[&lb@&fi&lk]&fb.", player.plainName(), player.uuid(), other.plainName(), other.uuid()); + } + case trace -> { + PlayerInfo stats = netServer.admins.getInfo(other.uuid()); + TraceInfo info = new TraceInfo(other.con.address, other.uuid(), other.con.modclient, other.con.mobile, stats.timesJoined, stats.timesKicked, stats.ips.toArray(String.class), stats.names.toArray(String.class)); + if(player.con != null){ + Call.traceInfo(player.con, other, info); + }else{ + NetClient.traceInfo(other, info); + } + info("&lc@ &fi&lk[&lb@&fi&lk]&fb has requested trace info of @ &fi&lk[&lb@&fi&lk]&fb.", player.plainName(), player.uuid(), other.plainName(), other.uuid()); + } + case switchTeam -> { + if(params instanceof Team team){ + other.team(team); + } } - info("&lc@ &fi&lk[&lb@&fi&lk]&fb has requested trace info of @ &fi&lk[&lb@&fi&lk]&fb.", player.plainName(), player.uuid(), other.plainName(), other.uuid()); } } diff --git a/core/src/mindustry/io/TypeIO.java b/core/src/mindustry/io/TypeIO.java index 8be6b0cf90..c0ee5f6d9f 100644 --- a/core/src/mindustry/io/TypeIO.java +++ b/core/src/mindustry/io/TypeIO.java @@ -835,13 +835,40 @@ public class TypeIO{ write.b(trace.mobile ? (byte)1 : 0); write.i(trace.timesJoined); write.i(trace.timesKicked); + //there is a cap to prevent TCP packet size overrun + writeStrings(write, trace.ips, 12); + writeStrings(write, trace.names, 12); } public static TraceInfo readTraceInfo(Reads read){ - return new TraceInfo(readString(read), readString(read), read.b() == 1, read.b() == 1, read.i(), read.i()); + return new TraceInfo(readString(read), readString(read), read.b() == 1, read.b() == 1, read.i(), read.i(), readStrings(read), readStrings(read)); } - public static void writeStrings(Writes write, String[][] strings){ + public static void writeStrings(Writes write, String[] strings, int maxLen){ + write.b(Math.min(strings.length, maxLen)); + for(int i = 0; i < Math.min(strings.length, maxLen); i++){ + writeString(write, strings[i]); + } + } + + public static void writeStrings(Writes write, String[] strings){ + write.b(strings.length); + for(String s : strings){ + writeString(write, s); + } + } + + public static String[] readStrings(Reads read){ + int length = read.ub(); + var result = new String[length]; + for(int j = 0; j < length; j++){ + result[j] = readString(read); + } + return result; + } + + + public static void writeStringArray(Writes write, String[][] strings){ write.b(strings.length); for(String[] string : strings){ write.b(string.length); @@ -851,7 +878,7 @@ public class TypeIO{ } } - public static String[][] readStrings(Reads read){ + public static String[][] readStringArray(Reads read){ int rows = read.ub(); String[][] strings = new String[rows][]; diff --git a/core/src/mindustry/net/Administration.java b/core/src/mindustry/net/Administration.java index a8b4a01307..4d68060fe4 100644 --- a/core/src/mindustry/net/Administration.java +++ b/core/src/mindustry/net/Administration.java @@ -624,14 +624,17 @@ public class Administration{ public String ip, uuid; public boolean modded, mobile; public int timesJoined, timesKicked; + public String[] ips, names; - public TraceInfo(String ip, String uuid, boolean modded, boolean mobile, int timesJoined, int timesKicked){ + public TraceInfo(String ip, String uuid, boolean modded, boolean mobile, int timesJoined, int timesKicked, String[] ips, String[] names){ this.ip = ip; this.uuid = uuid; this.modded = modded; this.mobile = mobile; this.timesJoined = timesJoined; this.timesKicked = timesKicked; + this.names = names; + this.ips = ips; } } diff --git a/core/src/mindustry/net/Packets.java b/core/src/mindustry/net/Packets.java index cf24d9029a..041ddcdb0c 100644 --- a/core/src/mindustry/net/Packets.java +++ b/core/src/mindustry/net/Packets.java @@ -38,7 +38,7 @@ public class Packets{ } public enum AdminAction{ - kick, ban, trace, wave + kick, ban, trace, wave, switchTeam } /** Generic client connection event. */ diff --git a/core/src/mindustry/ui/dialogs/BaseDialog.java b/core/src/mindustry/ui/dialogs/BaseDialog.java index 5ff305fd84..d6852be3b8 100644 --- a/core/src/mindustry/ui/dialogs/BaseDialog.java +++ b/core/src/mindustry/ui/dialogs/BaseDialog.java @@ -20,8 +20,7 @@ public class BaseDialog extends Dialog{ setFillParent(true); this.title.setAlignment(Align.center); titleTable.row(); - titleTable.image(Tex.whiteui, Pal.accent) - .growX().height(3f).pad(4f); + titleTable.image(Tex.whiteui, Pal.accent).growX().height(3f).pad(4f); hidden(() -> { if(shouldPause && state.isGame() && !net.active() && !wasPaused){ diff --git a/core/src/mindustry/ui/dialogs/TraceDialog.java b/core/src/mindustry/ui/dialogs/TraceDialog.java index 5d835b8a2c..ebf0de8cbd 100644 --- a/core/src/mindustry/ui/dialogs/TraceDialog.java +++ b/core/src/mindustry/ui/dialogs/TraceDialog.java @@ -39,14 +39,22 @@ public class TraceDialog extends BaseDialog{ c.add(Core.bundle.format("trace.id", info.uuid)).row(); }).row(); - table.add(Core.bundle.format("trace.modclient", info.modded)); - table.row(); - table.add(Core.bundle.format("trace.mobile", info.mobile)); - table.row(); - table.add(Core.bundle.format("trace.times.joined", info.timesJoined)); - table.row(); - table.add(Core.bundle.format("trace.times.kicked", info.timesKicked)); - table.row(); + table.add(Core.bundle.format("trace.modclient", info.modded)).row(); + table.add(Core.bundle.format("trace.mobile", info.mobile)).row(); + table.add(Core.bundle.format("trace.times.joined", info.timesJoined)).row(); + table.add(Core.bundle.format("trace.times.kicked", info.timesKicked)).row(); + + for(int i = 0; i < 2; i++){ + table.add(i == 0 ? "@trace.ips" : "@trace.names").row(); + String[] list = i == 0 ? info.ips : info.names; + + table.pane(t -> { + t.left(); + for(String val : list){ + t.add("[lightgray]" + val).left().row(); + } + }).padLeft(20f).fill().left().row(); + } table.add().pad(5); table.row(); diff --git a/core/src/mindustry/ui/fragments/HudFragment.java b/core/src/mindustry/ui/fragments/HudFragment.java index 7251d269bd..b4568679db 100644 --- a/core/src/mindustry/ui/fragments/HudFragment.java +++ b/core/src/mindustry/ui/fragments/HudFragment.java @@ -228,7 +228,7 @@ public class HudFragment{ //table with button to skip wave s.button(Icon.play, rightStyle, 30f, () -> { if(net.client() && player.admin){ - Call.adminRequest(player, AdminAction.wave); + Call.adminRequest(player, AdminAction.wave, null); }else{ logic.skipWave(); } diff --git a/core/src/mindustry/ui/fragments/PlayerListFragment.java b/core/src/mindustry/ui/fragments/PlayerListFragment.java index 30becd5d34..f263bc788c 100644 --- a/core/src/mindustry/ui/fragments/PlayerListFragment.java +++ b/core/src/mindustry/ui/fragments/PlayerListFragment.java @@ -10,12 +10,14 @@ import arc.scene.ui.ImageButton.*; import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; +import mindustry.game.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.input.*; import mindustry.net.*; import mindustry.net.Packets.*; import mindustry.ui.*; +import mindustry.ui.dialogs.*; import static mindustry.Vars.*; @@ -156,45 +158,99 @@ public class PlayerListFragment{ imageOverColor = Color.lightGray; }}; - if((net.server() || player.admin) && !user.isLocal() && (!user.admin || net.server())){ + //TODO for testing only + if((net.server() || player.admin || true) && (!user.admin || net.server() || user == player)){ button.add().growY(); - float bs = (h) / 2f; + button.button(Icon.menu, ustyle, () -> { + var dialog = new BaseDialog(user.coloredName()); - button.table(t -> { - t.defaults().size(bs); + dialog.title.setColor(Color.white); + dialog.titleTable.remove(); - t.button(Icon.hammerSmall, ustyle, - () -> ui.showConfirm("@confirm", Core.bundle.format("confirmban", user.name()), () -> Call.adminRequest(user, AdminAction.ban))); - t.button(Icon.cancelSmall, ustyle, - () -> ui.showConfirm("@confirm", Core.bundle.format("confirmkick", user.name()), () -> Call.adminRequest(user, AdminAction.kick))); + dialog.closeOnBack(); - t.row(); + var bstyle = Styles.defaultt; - t.button(Icon.adminSmall, style, () -> { - if(net.client()) return; + dialog.cont.add(user.coloredName()).row(); + dialog.cont.image(Tex.whiteui, Pal.accent).fillX().height(3f).pad(4f).row(); - String id = user.uuid(); + dialog.cont.pane(t -> { + t.defaults().size(220f, 55f).pad(3f); - if(user.admin){ - ui.showConfirm("@confirm", Core.bundle.format("confirmunadmin", user.name()), () -> { - netServer.admins.unAdminPlayer(id); - user.admin = false; - }); - }else{ - ui.showConfirm("@confirm", Core.bundle.format("confirmadmin", user.name()), () -> { - netServer.admins.adminPlayer(id, user.usid()); - user.admin = true; - }); + if(user != player){ + t.button("@player.ban", Icon.hammer, bstyle, () -> { + ui.showConfirm("@confirm", Core.bundle.format("confirmban", user.name()), () -> Call.adminRequest(user, AdminAction.ban, null)); + dialog.hide(); + }).row(); + + t.button("@player.kick", Icon.cancel, bstyle, () -> { + ui.showConfirm("@confirm", Core.bundle.format("confirmkick", user.name()), () -> Call.adminRequest(user, AdminAction.kick, null)); + dialog.hide(); + }).row(); } - }).update(b -> b.setChecked(user.admin)) - .disabled(b -> net.client()) - .touchable(() -> net.client() ? Touchable.disabled : Touchable.enabled) - .checked(user.admin); - t.button(Icon.zoomSmall, ustyle, () -> Call.adminRequest(user, AdminAction.trace)); + if(!user.isLocal()){ + t.button("@player.trace", Icon.zoom, bstyle, () -> { + Call.adminRequest(user, AdminAction.trace, null); + dialog.hide(); + }).row(); + } - }).padRight(12).size(bs + 10f, bs); + t.button("@player.team", Icon.redo, bstyle, () -> { + var teamSelect = new BaseDialog(Core.bundle.get("player.team") + ": " + user.name); + teamSelect.setFillParent(false); + + var group = new ButtonGroup<>(); + + int i = 0; + + for(Team team : Team.baseTeams){ + var b = new ImageButton(Tex.whiteui, Styles.clearNoneTogglei); + b.margin(4f); + b.getImageCell().grow(); + b.getStyle().imageUpColor = team.color; + b.clicked(() -> { + Call.adminRequest(user, AdminAction.switchTeam, team); + teamSelect.hide(); + }); + teamSelect.cont.add(b).size(50f).checked(a -> user.team() == team).group(group); + + if(i++ % 3 == 2) teamSelect.cont.row(); + } + + teamSelect.addCloseButton(); + teamSelect.show(); + + dialog.hide(); + }).row(); + + if(!net.client() && !user.isLocal()){ + t.button("@player.admin", Icon.admin, Styles.togglet, () -> { + dialog.hide(); + String id = user.uuid(); + + if(user.admin){ + ui.showConfirm("@confirm", Core.bundle.format("confirmunadmin", user.name()), () -> { + netServer.admins.unAdminPlayer(id); + user.admin = false; + }); + }else{ + ui.showConfirm("@confirm", Core.bundle.format("confirmadmin", user.name()), () -> { + netServer.admins.adminPlayer(id, user.usid()); + user.admin = true; + }); + } + }).checked(b -> user.admin).row(); + } + }).row(); + + dialog.cont.button("@back", Icon.left, dialog::hide).padTop(-1f).size(220f, 55f); + + dialog.show(); + + + }).size(h); }else if(!user.isLocal() && !user.admin && net.client() && Groups.player.size() >= 3 && player.team() == user.team()){ //votekick button.add().growY();