From b095840dc8b7a7b35c51f98c20be039e41dc5e0a Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 28 Aug 2019 21:57:09 -0400 Subject: [PATCH] Votekick --- core/assets/bundles/bundle.properties | 1 + .../io/anuke/mindustry/core/NetServer.java | 107 +++++++++++++++++- core/src/io/anuke/mindustry/net/Packets.java | 2 +- 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 1b1dd00afb..e657765766 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -69,6 +69,7 @@ players.single = {0} player online server.closing = [accent]Closing server... server.kicked.kick = You have been kicked from the server! server.kicked.serverClose = Server closed. +server.kicked.vote = You have been vote-kicked. Goodbye. server.kicked.clientOutdated = Outdated client! Update your game! server.kicked.serverOutdated = Outdated server! Ask the host to update! server.kicked.banned = You are banned on this server. diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index e91e2066bc..39874dcea9 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -4,8 +4,7 @@ import io.anuke.annotations.Annotations.Loc; import io.anuke.annotations.Annotations.Remote; import io.anuke.arc.ApplicationListener; import io.anuke.arc.Events; -import io.anuke.arc.collection.IntMap; -import io.anuke.arc.collection.ObjectSet; +import io.anuke.arc.collection.*; import io.anuke.arc.graphics.Color; import io.anuke.arc.graphics.Colors; import io.anuke.arc.math.Mathf; @@ -218,10 +217,108 @@ public class NetServer implements ApplicationListener{ for(int i = commandsPerPage * page; i < Math.min(commandsPerPage * (page + 1), clientCommands.getCommandList().size); i++){ Command command = clientCommands.getCommandList().get(i); - result.append("[orange] ").append(command.text).append("[lightgray] ").append(command.paramText).append("[lightgray] - ").append(command.description).append("\n"); + result.append("[orange] ").append(command.text).append("[white] ").append(command.paramText).append("[lightgray] - ").append(command.description).append("\n"); } player.sendMessage(result.toString()); }); + + //duration of a a kick in seconds + int kickDuration = 10 * 60; + + class VoteSession{ + Player target; + ObjectSet voted = new ObjectSet<>(); + ObjectMap map; + Timer.Task task; + int votes; + + public VoteSession(ObjectMap map, Player target){ + this.target = target; + this.map = map; + this.task = Timer.schedule(() -> { + if(!checkPass()){ + Call.sendMessage(Strings.format("[lightgray]Vote failed. Not enough votes to kick[orange] {0}[lightgray].", target.name)); + } + map.remove(target); + task.cancel(); + }, 60 * 1.5f); + } + + boolean checkPass(){ + if(votes >= votesRequired() && target.isAdded() && target.con.isConnected()){ + Call.sendMessage(Strings.format("[orange]Vote passed.[scarlet] {0}[orange] will be kicked from the server.", target.name)); + admins.getInfo(target.uuid).lastKicked = Time.millis() + kickDuration*1000; + kick(target.con.id, KickReason.vote); + return true; + } + return false; + } + } + + //cooldown between votes + int voteTime = 60 * 10; + Timekeeper vtime = new Timekeeper(voteTime); + //current kick sessions + ObjectMap currentlyKicking = new ObjectMap<>(); + + clientCommands.register("votekick", "[player]", "Vote to kick a player, with a cooldown.", (args, player) -> { + if(playerGroup.size() < 3){ + player.sendMessage("[scarlet]At least 3 players are needed to start a votekick."); + return; + } + + if(currentlyKicking.values().toArray().contains(v -> v.voted.contains(player.uuid) || v.voted.contains(admins.getInfo(player.uuid).lastIP))){ + player.sendMessage("[scarlet]You've already voted. Sit down."); + return; + } + + if(args.length == 0){ + StringBuilder builder = new StringBuilder(); + builder.append("[orange]Players to kick: \n"); + for(Player p : playerGroup.all()){ + if(p.isAdmin || p.con == null || p == player) continue; + + builder.append("[lightgray] ").append(p.name).append("[accent] (#").append(p.con.id).append(")\n"); + } + player.sendMessage(builder.toString()); + }else{ + Player found; + if(args[0].length() > 1 && args[0].startsWith("#") && Strings.canParseInt(args[0].substring(1))){ + int id = Strings.parseInt(args[0].substring(1)); + found = playerGroup.find(p -> p.con != null && p.con.id == id); + }else{ + found = playerGroup.find(p -> p.name.equalsIgnoreCase(args[0])); + } + + if(found != null){ + if(player == found){ + player.sendMessage("[scarlet]If you're interested in kicking yourself, just leave."); + }else if(found.isAdmin){ + player.sendMessage("[scarlet]Did you really expect to be able to kick an admin?"); + }else{ + if(!currentlyKicking.containsKey(found) && !vtime.get()){ + player.sendMessage("[scarlet]You must wait " + voteTime/60 + " minutes between votekicks."); + return; + } + + VoteSession session = currentlyKicking.getOr(found, () -> new VoteSession(currentlyKicking, found)); + session.votes ++; + session.voted.addAll(player.uuid, admins.getInfo(player.uuid).lastIP); + + Call.sendMessage(Strings.format("[orange]{0}[lightgray] has voted to kick[orange] {1}[].[accent] ({2}/{3})\n[lightgray]Type[orange] /votekick #{4}[] to agree.", + player.name, found.name, session.votes, votesRequired(), found.con.id)); + session.checkPass(); + vtime.reset(); + } + }else{ + player.sendMessage("[scarlet]No player[orange]'" + args[0] + "'[scarlet] found."); + } + } + }); + } + + public int votesRequired(){ + return playerGroup.size() * 2 / 3; } public Team assignTeam(Player current, Iterable players){ @@ -453,10 +550,10 @@ public class NetServer implements ApplicationListener{ Player player = connections.get(con.id); - if(player != null && (reason == KickReason.kick || reason == KickReason.banned) && player.uuid != null){ + if(player != null && (reason == KickReason.kick || reason == KickReason.banned || reason == KickReason.vote) && player.uuid != null){ PlayerInfo info = admins.getInfo(player.uuid); info.timesKicked++; - info.lastKicked = Time.millis(); + info.lastKicked = Math.max(Time.millis(), info.lastKicked); } Call.onKick(connection, reason); diff --git a/core/src/io/anuke/mindustry/net/Packets.java b/core/src/io/anuke/mindustry/net/Packets.java index 75d218cc11..1891866494 100644 --- a/core/src/io/anuke/mindustry/net/Packets.java +++ b/core/src/io/anuke/mindustry/net/Packets.java @@ -14,7 +14,7 @@ public class Packets{ public enum KickReason{ kick, clientOutdated, serverOutdated, banned, gameover(true), recentKick, - nameInUse, idInUse, nameEmpty, customClient, serverClose; + nameInUse, idInUse, nameEmpty, customClient, serverClose, vote; public final boolean quiet;