This commit is contained in:
Volas171 2020-12-06 22:12:45 +00:00
parent 1316f08095
commit f377d760f7
5 changed files with 1044 additions and 0 deletions

73
BotThread.java Normal file
View File

@ -0,0 +1,73 @@
package mindustry.plugin;
import arc.math.Mathf;
import arc.util.CommandHandler;
import mindustry.gen.Groups;
import mindustry.gen.Player;
import mindustry.plugin.commands.ModeratorCommands;
import mindustry.plugin.commands.PublicCommands;
import mindustry.plugin.commands.ReviewerCommands;
import mindustry.plugin.datas.PlayerData;
import mindustry.plugin.discord.ReactionAdd;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Activity;
import org.json.JSONObject;
import mindustry.plugin.discord.DiscordCommands;
import static mindustry.Vars.netServer;
import static mindustry.plugin.ioMain.*;
import static mindustry.plugin.utils.Funcs.*;
import static mindustry.plugin.discord.Loader.*;
public class BotThread extends Thread {
public JDA api;
private Thread mt;
private JSONObject data;
public DiscordCommands commandHandler = new DiscordCommands();
public ReactionAdd reactionHandler = new ReactionAdd();
public CommandHandler publicHandler = new CommandHandler(prefix);
public CommandHandler reviewerHandler = new CommandHandler(prefix);
public CommandHandler moderatorHandler = new CommandHandler(prefix);
public BotThread(JDA api, Thread mt, JSONObject data) {
this.api = api; //new DiscordApiBuilder().setToken(data.get(0)).login().join();
this.mt = mt;
this.data = data;
// register commands
api.addEventListener(commandHandler);
api.addEventListener(reactionHandler);
new PublicCommands().registerCommands(publicHandler);
new ReviewerCommands().registerCommands(reviewerHandler);
new ModeratorCommands().registerCommands(moderatorHandler);
}
public void run(){
while (this.mt.isAlive()){
try {
Thread.sleep(30 * 1000);
for (Player p : Groups.player) {
PlayerData pd = playerDataHashMap.get(p.uuid());
if (pd != null)
setJedisData(p.uuid(), pd);
}
if(Mathf.chance(0.01f)){
api.getPresence().setActivity(Activity.playing("( ͡° ͜ʖ ͡°)"));
} else {
api.getPresence().setActivity(Activity.playing("with " + Groups.player.size() + (netServer.admins.getPlayerLimit() == 0 ? "" : "/" + netServer.admins.getPlayerLimit()) + " players"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
api.shutdown();
}
}

416
ModeratorCommands.java Normal file
View File

@ -0,0 +1,416 @@
package mindustry.plugin.commands;
import arc.Core;
import arc.math.Mathf;
import arc.util.CommandHandler;
import mindustry.content.Bullets;
import mindustry.content.UnitTypes;
import mindustry.entities.bullet.BulletType;
import mindustry.game.Team;
import mindustry.gen.Call;
import mindustry.gen.Groups;
import mindustry.gen.Player;
import mindustry.gen.Unit;
import mindustry.net.Administration;
import mindustry.plugin.datas.PlayerData;
import mindustry.plugin.discord.Context;
import mindustry.plugin.ioMain;
import mindustry.type.UnitType;
import net.dv8tion.jda.api.EmbedBuilder;
import java.lang.reflect.Field;
import java.time.Instant;
import java.util.HashMap;
import java.util.stream.IntStream;
import static mindustry.Vars.*;
import static mindustry.net.Administration.*;
import static mindustry.net.Packets.*;
import static mindustry.plugin.discord.Loader.serverName;
import static mindustry.plugin.ioMain.*;
import static mindustry.plugin.utils.Funcs.*;
public class ModeratorCommands {
public ModeratorCommands(){
}
public void registerCommands(CommandHandler handler){
handler.<Context>register("announce", "<text...>", "Display a message on top of the screen for all players for 10 seconds", (args, ctx) -> {
Call.infoToast(args[0], 10);
ctx.sendEmbed(true, ":round_pushpin: announcement sent successfully!", args[0]);
});
handler.<Context>register("event", "<ip> <port> [force?]", "Set the new event's ip & port. If force is set to true, all players will be forced to join immedietly.", (args, ctx) -> {
eventIp = args[0];
eventPort = Integer.parseInt(args[1]);
boolean f = false;
if(args.length >= 3 && Boolean.parseBoolean(args[2])) {
f = true;
Groups.player.forEach(player -> {
Call.connect(player.con, eventIp, eventPort);
});
}
ctx.sendEmbed(true, ":crossed_swords: event ip set successfully!", args[0] + ":" + eventPort + (f ? "\nalso forced everyone to join" : ""));
});
handler.<Context>register("alert", "<player> <text...>", " " + serverName, (args, ctx) -> {
Player player = findPlayer(args[0]);
if(args[0].toLowerCase().equals("all")){
Call.infoMessage(args[1]);
ctx.sendEmbed(true, ":round_pushpin: alert to everyone sent successfully!", args[1]);
}else{
if(player != null){
Call.infoMessage(player.con, args[1]);
ctx.sendEmbed(true, ":round_pushpin: alert to " + escapeCharacters(player.name) + " sent successfully!", args[1]);
}else{
ctx.sendEmbed(false, ":round_pushpin: can't find player " + args[1]);
}
}
});
handler.<Context>register("ban", "<player> <minutes> [reason...]", "Ban a player by the provided name, id or uuid (do offline bans using uuid)", (args, ctx) -> {
Player player = findPlayer(args[0]);
if(player != null){
PlayerData pd = playerDataHashMap.get(player.uuid());
if(pd != null){
long until = Instant.now().getEpochSecond() + Integer.parseInt(args[1]) * 60;
pd.bannedUntil = until;
pd.banReason = (args.length >= 3 ? args[2] : "not specified") + "\n" + "[accent]Until: " + epochToString(until) + "\n[accent]Ban ID:[] " + player.uuid().substring(0, 4);
playerDataHashMap.put(player.uuid(), pd);
// setJedisData(player.uuid, pd);
HashMap<String, String> fields = new HashMap<>();
fields.put("UUID", player.uuid());
ctx.sendEmbed(true, ":hammer: the ban hammer has been swung at " + escapeCharacters(player.name), "reason: *" + escapeColorCodes(pd.banReason) + "*", fields, false);
player.con.kick(KickReason.banned);
}else{
ctx.sendEmbed(false, ":interrobang: internal server error, please ping fuzz");
}
}else{
PlayerData pd = getJedisData(args[0]);
if(pd != null){
long until = Instant.now().getEpochSecond() + Integer.parseInt(args[1]) * 60;
pd.bannedUntil = until;
pd.banReason = (args.length >= 3 ? args[2] : "not specified") + "\n" + "[accent]Until: " + epochToString(until) + "\n[accent]Ban ID:[] " + args[0].substring(0, 4);
setJedisData(args[0], pd);
HashMap<String, String> fields = new HashMap<>();
fields.put("UUID", args[0]);
ctx.sendEmbed(true, ":hammer: the ban hammer has been swung at " + escapeCharacters(netServer.admins.getInfo(args[0]).lastName),"reason: *" + escapeColorCodes(pd.banReason) + "*", fields, false);
}else{
ctx.sendEmbed(false, ":hammer: that player or uuid cannot be found");
}
}
});
handler.<Context>register("kick", "<player>", "Kick a player from " + serverName, (args, ctx) -> {
Player player = findPlayer(args[0]);
if(player != null){
player.con.kick(KickReason.kick);
ctx.sendEmbed(true, ":football: kicked " + escapeCharacters(player.name) + " successfully!", player.uuid());
}else{
ctx.sendEmbed(false, ":round_pushpin: can't find player " + escapeCharacters(args[0]));
}
});
handler.<Context>register("unban", "<uuid>", "Unban the specified player by uuid (works for votekicks as well)", (args, ctx) -> {
PlayerData pd = getJedisData(args[0]);
if(pd!= null){
PlayerInfo info = netServer.admins.getInfo(args[0]);
info.lastKicked = 0;
pd.bannedUntil = 0;
setJedisData(args[0], pd);
ctx.sendEmbed(true, ":wrench: unbanned " + escapeCharacters(info.lastName) + " successfully!");
}else{
ctx.sendEmbed(false, ":wrench: that uuid doesn't exist in the database..");
}
});
handler.<Context>register("playersinfo", "Check the information about all players on the server.", (args, ctx) -> {
EmbedBuilder eb = new EmbedBuilder();
eb.setColor(Pals.progress);
eb.setTitle(":satellite: **players online: **" + Groups.player.size());
StringBuilder pi = new StringBuilder();
int pn = 1;
for(Player p : Groups.player){
if (!p.admin) {
pi
.append("**")
.append(pn + "")
.append("** `")
.append(escapeCharacters(p.name))
.append("` : ")
.append(p.con.address)
.append(" : ")
.append(p.uuid())
.append("\n");
} else {
pi
.append("**")
.append(pn + "")
.append("** `")
.append(escapeCharacters(p.name))
.append("`")
.append("\n");
}
pn++;
}
eb.setDescription(pi);
ctx.sendEmbed(eb);
});
handler.<Context>register("lookup", "<uuid|name>", "Lookup the specified player by uuid or name (name search only works when player is online)", (args, ctx) -> {
EmbedBuilder eb = new EmbedBuilder();
Administration.PlayerInfo info;
Player player = findPlayer(args[0]);
if (player != null) {
info = netServer.admins.getInfo(player.uuid());
} else{
if(args[0].length() == 24) { // uuid length
info = netServer.admins.getInfo(args[0]);
}else{
ctx.sendEmbed(false, ":mag: can't find that uuid in the database..");
return;
}
}
eb.setColor(Pals.progress);
eb.setTitle(":mag: " + escapeCharacters(info.lastName) + "'s lookup");
eb.addField("UUID", info.id, false);
eb.addField("Last used ip", info.lastIP, true);
eb.addField("Times kicked", String.valueOf(info.timesKicked), true);
StringBuilder s = new StringBuilder();
s.append("**All used names: **\n");
for (String name : info.names) {
s.append(escapeCharacters(name)).append(" / ");
}
s.append("\n\n**All used IPs: **\n");
for (String ip : info.ips) {
s.append(escapeCharacters(ip)).append(" / ");
}
eb.setDescription(s.toString());
ctx.channel.sendMessage(eb.build()).queue();
});
handler.<Context>register("setrank", "<uuid> <rank>", "Set the specified uuid's rank to the one provided.", (args, ctx) -> {
int rank;
try{
rank = Integer.parseInt(args[1]);
}catch (NumberFormatException e) {
ctx.sendEmbed(false, ":wrench: error parsing rank number");
return;
}
if(rank < rankNames.size()) {
PlayerData pd = playerDataHashMap.containsKey(args[0]) ? playerDataHashMap.get(args[0]) : getJedisData(args[0]);
if (pd != null) {
pd.rank = rank;
if(playerDataHashMap.containsKey(args[0])){
playerDataHashMap.put(args[0], pd);
}
setJedisData(args[0], pd);
PlayerInfo info = netServer.admins.getInfo(args[0]);
ctx.sendEmbed(true, ":wrench: set " + escapeCharacters(info.lastName) + "'s rank to " + escapeColorCodes(rankNames.get(rank).name));
} else {
ctx.sendEmbed(false, ":wrench: that uuid doesn't exist in the database..");
}
}else{
ctx.sendEmbed(false, ":wrench: error parsing rank number");
}
});
handler.<Context>register("convert", "<player> <unit>", "Change a players unit into the specified one", (args, ctx) -> {
UnitType desiredUnit;
try {
Field field = UnitTypes.class.getDeclaredField(args[1]);
desiredUnit = (UnitType)field.get(null);
} catch (NoSuchFieldException | IllegalAccessException ignored) {
ctx.sendEmbed(false, ":robot: that unit doesn't exist");
return;
}
Player player = findPlayer(args[0]);
if(player != null){
Unit nu = desiredUnit.create(player.team());
nu.set(player.getX(), player.getY());
nu.add();
player.unit().kill();
player.unit(nu);
player.afterSync();
ctx.sendEmbed(true, ":robot: changed " + escapeCharacters(player.name) + "'s unit to " + desiredUnit.name);
}else if(args[0].toLowerCase().equals("all")){
for(Player p : Groups.player){
Unit nu = desiredUnit.create(p.team());
nu.set(p.getX(), p.getY());
nu.add();
p.unit().kill();
p.unit(nu);
p.afterSync();
}
ctx.sendEmbed(true, ":robot: changed everyone's unit to " + desiredUnit.name);
}else{
ctx.sendEmbed(false, ":robot: can't find " + escapeCharacters(args[0]));
}
});
handler.<Context>register("team", "<player> <teamid>", "Change a players team into the specified team id", (args, ctx) -> {
int teamid;
try{
teamid = Integer.parseInt(args[1]);
}catch (Exception e){ ctx.sendEmbed(false, ":triangular_flag_on_post: error parsing team id number"); return;}
Player player = findPlayer(args[0]);
if(player != null){
player.team(Team.get(teamid));
ctx.sendEmbed(true, ":triangular_flag_on_post: changed " + escapeCharacters(player.name) + "'s team to " + Team.get(teamid).name);
}else if(args[0].toLowerCase().equals("all")){
for(Player p : Groups.player){ p.team(Team.get(teamid)); }
ctx.sendEmbed(true, ":triangular_flag_on_post: changed everyone's team to " + Team.get(teamid).name);
}else{
ctx.sendEmbed(false, ":triangular_flag_on_post: can't find " + escapeCharacters(args[0]));
}
});
handler.<Context>register("motd", "<message...>", "Change the welcome message popup when a new player joins Set to 'none' if you want to disable motd", (args, ctx) -> {
if(args[0].toLowerCase().equals("none")){
welcomeMessage = "";
ctx.sendEmbed(true, ":newspaper: disabled welcome message successfully!");
}else{
welcomeMessage = args[0];
ctx.sendEmbed(true, ":newspaper: changed welcome message successfully!", args[0]);
}
Core.settings.put("welcomeMessage", welcomeMessage);
Core.settings.forceSave();
});
handler.<Context>register("statmessage", "<message...>", "Change the stat message popup when a player uses the /info command", (args, ctx) -> {
if(args[0].toLowerCase().equals("none")){
statMessage = "";
ctx.sendEmbed(true, ":newspaper: disabled stat message successfully!");
}else{
statMessage = args[0];
ctx.sendEmbed(true, ":newspaper: changed stat message successfully!", args[0]);
}
Core.settings.put("statMessage", statMessage);
Core.settings.forceSave();
});
handler.<Context>register("spawn", "<player> <unit> <amount>", "Spawn a specified amount of units near the player's position.", (args, ctx) -> {
int amt;
try{
amt = Integer.parseInt(args[2]);
}catch (Exception e){ ctx.sendEmbed(false, ":robot: error parsing amount number"); return;}
UnitType desiredUnitType = UnitTypes.dagger;
try {
Field field = UnitTypes.class.getDeclaredField(args[1]);
desiredUnitType = (UnitType) field.get(null);
} catch (NoSuchFieldException | IllegalAccessException ignored) {
ctx.sendEmbed(false, ":robot: that unit doesn't exist");
return;
}
Player player = findPlayer(args[0]);
if(player != null){
UnitType finalDesiredUnitType = desiredUnitType;
IntStream.range(0, amt).forEach(i -> {
Unit unit = finalDesiredUnitType.create(player.team());
unit.set(player.getX(), player.getY());
unit.add();
});
ctx.sendEmbed(true, ":robot: spawned " + amt + " " + finalDesiredUnitType + "s at " + escapeColorCodes(player.name) + "'s position");
}else{
ctx.sendEmbed(false, ":robot: can't find " + escapeCharacters(args[0]));
}
});
handler.<Context>register("kill", "<player|unit>", "Kill the specified player or all specified units on the map.", (args, ctx) -> {
UnitType desiredUnitType;
try {
Field field = UnitTypes.class.getDeclaredField(args[0]);
desiredUnitType = (UnitType) field.get(null);
int amt = 0;
for(Unit unit : Groups.unit){
if(unit.type == desiredUnitType){ unit.kill(); amt++; }
}
ctx.sendEmbed(true, ":knife: killed " + amt + " " + args[0] + "s");
} catch (NoSuchFieldException | IllegalAccessException ignored) {
Player player = findPlayer(args[0]);
if(player != null){
player.unit().kill();
ctx.sendEmbed(true, ":knife: killed " + escapeCharacters(player.name));
}else if(args[0].toLowerCase().equals("all")){
Groups.player.forEach(p -> p.unit().kill());
ctx.sendEmbed(true, ":knife: killed everyone, muhahaha");
}else{
ctx.sendEmbed(false, ":knife: can't find " + escapeCharacters(args[0]));
}
}
});
handler.<Context>register("weapon", "<player> <bullet> [damage] [lifetime] [velocity]", "Modify the specified players weapon with the provided parameters", (args, ctx) -> {
BulletType desiredBulletType;
float dmg = 1f;
float life = 1f;
float vel = 1f;
if(args.length > 2){
try{
dmg = Float.parseFloat(args[2]);
}catch (Exception e){ ctx.sendEmbed(false, ":gun: error parsing damage number"); return;}
}
if(args.length > 3){
try{
life = Float.parseFloat(args[3]);
}catch (Exception e){ ctx.sendEmbed(false, ":gun: error parsing lifetime number"); return;}
}
if(args.length > 4){
try{
vel = Float.parseFloat(args[4]);
}catch (Exception e){ ctx.sendEmbed(false, ":gun: error parsing velocity number"); return;}
}
try {
Field field = Bullets.class.getDeclaredField(args[1]);
desiredBulletType = (BulletType) field.get(null);
} catch (NoSuchFieldException | IllegalAccessException ignored) {
ctx.sendEmbed(false, ":gun: invalid bullet type");
desiredBulletType = null;
}
HashMap<String, String> fields = new HashMap<>();
Player player = findPlayer(args[0]);
if(player != null){
PlayerData pd = playerDataHashMap.get(player.uuid());
pd.bt = desiredBulletType;
pd.sclDamage = dmg;
pd.sclLifetime = life;
pd.sclVelocity = vel;
playerDataHashMap.put(player.uuid(), pd);
fields.put("Bullet", args[1]);
fields.put("Bullet lifetime", args[2]);
fields.put("Bullet velocity", args[3]);
ctx.sendEmbed(true, ":gun: modded " + escapeCharacters(player.name) + "'s gun", fields, true);
}else if(args[0].toLowerCase().equals("all")){
for(Player p : Groups.player) {
PlayerData pd = playerDataHashMap.get(p.uuid());
pd.bt = desiredBulletType;
pd.sclDamage = dmg;
pd.sclLifetime = life;
pd.sclVelocity = vel;
playerDataHashMap.put(p.uuid(), pd);
}
fields.put("Bullet", args[1]);
fields.put("Bullet lifetime", args[2]);
fields.put("Bullet velocity", args[3]);
ctx.sendEmbed(true, ":gun: modded everyone's gun", fields, true);
}else{
ctx.sendEmbed(false, ":gun: can't find " + escapeCharacters(args[0]));
}
});
}
}

205
PublicCommands.java Normal file
View File

@ -0,0 +1,205 @@
package mindustry.plugin.commands;
import arc.files.Fi;
import arc.struct.Seq;
import arc.util.CommandHandler;
import mindustry.gen.Groups;
import mindustry.gen.Player;
import mindustry.io.SaveIO;
import mindustry.maps.Map;
import mindustry.gen.Call;
import mindustry.plugin.datas.ContentHandler;
import mindustry.plugin.discord.Context;
import net.dv8tion.jda.api.EmbedBuilder;
import javax.imageio.ImageIO;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.zip.InflaterInputStream;
import static arc.util.CommandHandler.*;
import static mindustry.Vars.*;
import static mindustry.plugin.utils.Funcs.*;
import static mindustry.plugin.discord.Loader.*;
import static net.dv8tion.jda.api.entities.Message.*;
public class PublicCommands {
public void registerCommands(CommandHandler handler) {
handler.<Context>register("chat", "<message...>", "Send a message to in-game chat in " + serverName, (args, ctx) -> {
if(args[0].length() < chatMessageMaxSize){
Call.sendMessage("[sky]" + ctx.author.getAsTag() + " @discord >[] " + args[0]);
ctx.sendEmbed(true, ":mailbox_with_mail: **message sent!**", "``" + escapeCharacters(args[0]) + "``");
} else{
ctx.sendEmbed(false, ":exclamation: **message too big!**", "maximum size: **" + chatMessageMaxSize + " characters**");
}
});
handler.<Context>register("maps", "Displays all available maps in the playlist. Use " + prefix + "map <name> to download a specific map.", (args, ctx) -> {
Seq<Map> mapList = maps.customMaps();
StringBuilder smallMaps = new StringBuilder();
StringBuilder mediumMaps = new StringBuilder();
StringBuilder bigMaps = new StringBuilder();
for(Map map : mapList){
int size = map.height * map.width;
if(size <= 62500) { smallMaps.append("**").append(escapeCharacters(map.name())).append("** ").append(map.width).append("x").append(map.height).append("\n"); }
if(size > 62500 && size < 160000) { mediumMaps.append("**").append(escapeCharacters(map.name())).append("** ").append(map.width).append("x").append(map.height).append("\n"); }
if(size >= 160000) { bigMaps.append("**").append(escapeCharacters(map.name())).append("** ").append(map.width).append("x").append(map.height).append("\n"); }
}
HashMap<String, String> fields = new HashMap<>();
if(smallMaps.length() > 0){fields.put("small maps", smallMaps.toString()); }
if(mediumMaps.length() > 0){fields.put("medium maps", mediumMaps.toString()); }
if(bigMaps.length() > 0){fields.put("big maps", bigMaps.toString()); }
ctx.sendEmbed(true,":map: **" + mapList.size + " maps** in " + serverName + "'s playlist", fields, true);
});
handler.<Context>register("map","<map...>", "Previews and provides a download for the specified map. (check maps with " + prefix + "maps)", (args, ctx) -> {
Map map = getMapBySelector(args[0].trim());
if (map != null){
try {
Fi mapFile = map.file;
ContentHandler.Map visualMap = contentHandler.readMap(map.file.read());
File imageFile = new File(assets + "image_" + mapFile.name().replaceAll(".msav", ".png"));
ImageIO.write(visualMap.image, "png", imageFile);
EmbedBuilder eb = new EmbedBuilder().setColor(Pals.success).setTitle(":map: " + escapeCharacters(map.name())).setFooter(map.width + "x" + map.height).setDescription(escapeCharacters(map.description())).setAuthor(escapeCharacters(map.author()));
eb.setImage("attachment://" + imageFile.getName());
ctx.channel.sendFile(mapFile.file()).addFile(imageFile).embed(eb.build()).queue();
//ctx.channel.sendFile(mapFile.file()).embed(eb.build()).queue();
} catch (Exception e) {
ctx.sendEmbed(false, ":eyes: **internal server error**");
e.printStackTrace();
}
}else{
ctx.sendEmbed(false, ":mag: map **" + escapeCharacters(args[0]) + "** not found");
}
});
handler.<Context>register("submitmap", "Submit a map to be added to the server playlist (will be reviewed by a moderator automatically). Must attach a valid .msav file.", (args, ctx) -> {
Attachment attachment = (ctx.event.getMessage().getAttachments().size() == 1 ? ctx.event.getMessage().getAttachments().get(0) : null);
if (attachment == null) {
ctx.sendEmbed(false, ":link: **you need to attach a valid .msav file!**");
return;
}
File mapFile = new File(assets + attachment.getFileName());
attachment.downloadToFile(mapFile).thenAccept(file -> {
Fi fi = new Fi(mapFile);
byte[] bytes = fi.readBytes();
DataInputStream dis = new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(bytes)));
if (attachment.getFileName().endsWith(".msav") && SaveIO.isSaveValid(dis)) {
try {
OutputStream os = new FileOutputStream(mapFile);
os.write(bytes);
os.close();
ContentHandler.Map map = contentHandler.readMap(fi.read());
File imageFile = new File(assets + "image_" + attachment.getFileName().replaceAll(".msav", ".png"));
ImageIO.write(map.image, "png", imageFile);
EmbedBuilder eb = new EmbedBuilder();
eb.setColor(Pals.progress);
eb.setTitle(escapeCharacters(map.name));
eb.setDescription(map.description);
eb.setAuthor(ctx.author.getAsTag(), null, ctx.author.getAvatarUrl());
eb.setFooter("react to this message accordingly to approve/disapprove this map.");
eb.setImage("attachment://" + imageFile.getName());
mapSubmissions.sendFile(mapFile).addFile(imageFile).embed(eb.build()).queue(message -> {
message.addReaction("YES:735555385934741554").queue();
message.addReaction("NO:735554784534462475").queue();
});
ctx.sendEmbed(true, ":map: **" + escapeCharacters(map.name) + "** submitted successfully!", "a moderator will soon approve or disapprove your map.");
} catch (Exception e) {
e.printStackTrace();
ctx.sendEmbed(false, ":interrobang: **attachment invalid or corrupted!**");
}
} else {
ctx.sendEmbed(false, ":interrobang: **attachment invalid or corrupted!**");
}
});
});
handler.<Context>register("players","Get all online in-game players.", (args, ctx) -> {
EmbedBuilder eb = new EmbedBuilder();
eb.setColor(Pals.progress);
eb.setTitle(":satellite: **players online: **" + Groups.player.size());
StringBuilder s = new StringBuilder();
int pn = 1;
for(Player p : Groups.player){
s
.append("**")
.append(pn + "")
.append("** `")
.append(escapeCharacters(p.name))
.append("`")
.append(" : ")
.append(p.id)
.append("\n");
pn++;
}
eb.setDescription(s);
ctx.sendEmbed(eb);
});
handler.<Context>register("status", "View the status of this server.", (args, ctx) -> {
HashMap<String, String> fields = new HashMap<>();
fields.put("players", String.valueOf(Groups.player.size()));
fields.put("map", escapeCharacters(state.map.name()));
fields.put("wave", String.valueOf(state.wave));
ctx.sendEmbed(true, ":desktop: **" + serverName + "**", fields, false);
});
handler.<Context>register("help", "[command]", "Display help for a specified command, or all commands.", (args, ctx) -> {
EmbedBuilder eb = new EmbedBuilder();
eb.setTitle(":newspaper: all available commands");
eb.setDescription("use help [command] to view more information about a command.");
eb.setColor(Pals.progress);
if(args.length <= 0) {
StringBuilder admin = new StringBuilder();
StringBuilder mod = new StringBuilder();
StringBuilder reviewer = new StringBuilder();
StringBuilder publics = new StringBuilder();
for (Command cmd : bt.moderatorHandler.getCommandList()) {
mod.append("**").append(cmd.text).append("**").append(" ").append(cmd.paramText).append("\n");
}
for (Command cmd : bt.reviewerHandler.getCommandList()) {
reviewer.append("**").append(cmd.text).append("**").append(" ").append(cmd.paramText).append("\n");
}
for (Command cmd : bt.publicHandler.getCommandList()) {
publics.append("**").append(cmd.text).append("**").append(" ").append(cmd.paramText).append("\n");
}
eb.addField("Moderation", mod.toString(), true);
eb.addField("Maps", reviewer.toString(), true);
eb.addField("Public", publics.toString(), true);
ctx.channel.sendMessage(eb.build()).queue();
}else{
Command cmd = null;
for(Command c : bt.moderatorHandler.getCommandList()){
if(c.text.equals(args[0].toLowerCase())) cmd = c;
}
for(Command c : bt.reviewerHandler.getCommandList()){
if(c.text.equals(args[0].toLowerCase())) cmd = c;
}
for(Command c : bt.publicHandler.getCommandList()){
if(c.text.equals(args[0].toLowerCase())) cmd = c;
}
if(cmd != null){
ctx.sendEmbed(true, ":gear: " + cmd.text + (cmd.paramText.length() > 0 ? " *" + cmd.paramText + "*" : ""), cmd.description);
}else{
ctx.sendEmbed(false, ":interrobang: that command doesn't exist!");
}
}
});
}
}

343
ioMain.java Normal file
View File

@ -0,0 +1,343 @@
package mindustry.plugin;
import java.time.Instant;
import java.util.HashMap;
import java.util.concurrent.CompletableFuture;
import arc.Core;
import arc.math.Mathf;
import arc.util.*;
import arc.util.Timer;
import mindustry.content.*;
import mindustry.gen.Groups;
import mindustry.gen.Player;
import mindustry.graphics.Pal;
import mindustry.mod.Plugin;
import mindustry.net.Administration;
import mindustry.plugin.datas.ContentHandler;
import mindustry.plugin.datas.PlayerData;
import mindustry.plugin.datas.TileInfo;
import mindustry.plugin.discord.Loader;
import mindustry.plugin.utils.Funcs;
import mindustry.plugin.utils.MapRules;
import mindustry.plugin.utils.VoteSession;
import mindustry.world.Tile;
import arc.Events;
import mindustry.Vars;
import mindustry.game.EventType;
import mindustry.gen.Call;
import static mindustry.Vars.*;
import static mindustry.plugin.utils.Funcs.*;
import static mindustry.plugin.discord.Loader.*;
public class ioMain extends Plugin {
public static HashMap<String, PlayerData> playerDataHashMap = new HashMap<>();
public static int minutesPassed = 0;
public static HashMap<Tile, TileInfo> tileInfoHashMap = new HashMap<>();
//register event handlers and create variables in the constructor
public ioMain() {
//we can load this before anything else, it doesnt matter
Loader.load();
// display on screen messages
float duration = 10f;
int start = 450;
int increment = 30;
Timer.schedule(() -> {
int currentInc = 0;
for(String msg : onScreenMessages){
Call.infoPopup(msg, duration, 20, 50, 20, start + currentInc, 0);
currentInc = currentInc + increment;
}
}, 0, 10);
Events.on(EventType.ServerLoadEvent.class, event -> {
contentHandler = new ContentHandler();
Log.info("Everything's loaded !");
});
Events.on(EventType.TapEvent.class, tapEvent -> {
if(tapEvent.tile != null) {
Player player = tapEvent.player;
PlayerData pd = playerDataHashMap.get(player.uuid());
Tile t = tapEvent.tile;
pd.tapTile = t;
if (pd.inspector) {
Call.effect(player.con, Fx.placeBlock, t.worldx(), t.worldy(), 0.75f, Pal.accent);
player.sendMessage("[orange]--[] [accent]tile [](" + t.x + ", " + t.y + ")[accent] block:[] " + ((t.block() == null || t.block() == Blocks.air) ? "[#545454]none" : t.block().name) + " [orange]--[]");
TileInfo info = tileInfoHashMap.getOrDefault(t, new TileInfo());
if (info.placedBy != null) {
String pBy = (player.admin() ? info.placedByUUID : info.placedBy);
player.sendMessage("[accent]last placed by:[] " + escapeColorCodes(pBy));
}
if (info.destroyedBy != null) {
String dBy = (player.admin() ? info.destroyedByUUID : info.destroyedBy);
player.sendMessage("[accent]last [scarlet]deconstructed[] by:[] " + escapeColorCodes(dBy));
}
if (t.block() == Blocks.air && info.wasHere != null){
player.sendMessage("[accent]block that was here:[] " + info.wasHere);
}
if (info.configuredBy != null) {
String cBy = (player.admin() ? info.configuredByUUID : info.configuredBy);
player.sendMessage("[accent]last configured by:[] " + escapeColorCodes(cBy));
}
}
}
});
// player disconnected
Events.on(EventType.PlayerLeave.class, event -> {
String uuid = event.player.uuid();
if(playerDataHashMap.containsKey(uuid))
setJedisData(uuid, playerDataHashMap.get(uuid));
//free ram
playerDataHashMap.remove(uuid);
});
// player joined
Events.on(EventType.PlayerJoin.class, event -> {
CompletableFuture.runAsync(() -> {
Player player = event.player;
PlayerData pd = getJedisData(player.uuid());
if(pd == null && playerDataHashMap.containsKey(player.uuid())){
pd = playerDataHashMap.get(player.uuid());
}
if (pd != null) {
if (pd.bannedUntil > Instant.now().getEpochSecond()) {
player.con.kick("[scarlet]You are banned.[accent] Reason:\n" + pd.banReason);
return;
}
if(pd.rank > 0){
pd.tag = rankNames.get(pd.rank).tag;
// todo: figure out a non-intrusive way to do this
// player.name(pd.tag + " " + player.name);
}
} else { // not in database
pd = new PlayerData(0);
setJedisData(player.uuid(), new PlayerData(0));
}
playerDataHashMap.put(player.uuid(), pd);
if (welcomeMessage.length() > 0 && !welcomeMessage.equals("none")) {
Call.infoMessage(player.con, formatMessage(player, welcomeMessage));
}
if (pd != null){
if (pd.bannedUntil > Instant.now().getEpochSecond()){
for (int i=0; i < 30; i++)
Call.infoMessage(player.con, formatMessage(player, welcomeMessage));
player.con.kick("[scarlet]You are banned.[accent] Reason:\n" + pd.banReason, 0);
}
}
if(bannedNames.contains(player.name.trim().toLowerCase()))
player.con.kick("Influx Capacitor failed. Quantom leap needs to be restarted.");
});
});
Events.on(EventType.BuildSelectEvent.class, event -> {
if(event.builder instanceof Player){
if(event.tile != null){
Player player = (Player) event.builder;
PlayerData pd = playerDataHashMap.get(player.uuid());
TileInfo info = tileInfoHashMap.getOrDefault(event.tile, new TileInfo());
if(!event.breaking){
info.placedBy = player.name;
info.placedByUUID = player.uuid();
info.wasHere = (event.tile.block() != Blocks.air ? event.tile.block().name : "[#545454]none");
pd.buildingsBuilt++;
playerDataHashMap.put(player.uuid(), pd);
} else{
info.destroyedBy = player.name;
info.destroyedByUUID = player.uuid();
}
tileInfoHashMap.put(event.tile, info);
}
}
});
Events.on(EventType.TapEvent.class, event -> {
if(event.tile != null & event.player != null){
TileInfo info = tileInfoHashMap.getOrDefault(event.tile, new TileInfo());
Player player = event.player;
info.configuredBy = player.name;
info.configuredByUUID = player.uuid();
tileInfoHashMap.put(event.tile, info);
}
});
Events.on(EventType.WorldLoadEvent.class, event -> {
Timer.schedule(MapRules::run, 1); // idk
});
Events.on(EventType.ServerLoadEvent.class, event -> {
// action filter
Vars.netServer.admins.addActionFilter(action -> {
Player player = action.player;
PlayerData pd = playerDataHashMap.get(player.uuid());
if (player == null) return true;
if (player.admin()) return true;
if (!pd.canInteract) return false;
return action.type != Administration.ActionType.rotate;
});
});
Events.on(EventType.Trigger.update.getClass(), event -> {
for(Player p : Groups.player){
PlayerData pd = playerDataHashMap.get(p.uuid());
if (pd != null && pd.bt != null && p.shooting()) {
Call.createBullet(pd.bt, p.team(), p.getX(), p.getY(), p.unit().rotation, pd.sclDamage, pd.sclVelocity, pd.sclLifetime);
}
}
});
Events.on(EventType.GameOverEvent.class, event -> {
for(Player p : Groups.player){
PlayerData pd = playerDataHashMap.get(p.uuid());
pd.gamesPlayed++;
playerDataHashMap.put(p.uuid(), pd);
}
});
}
//register commands that run on the server
@Override
public void registerServerCommands(CommandHandler handler){
handler.removeCommand("exit");
handler.register("exit", "exits the server", ioMain::exit);
}
//cooldown between map votes
int voteCooldown = 120 * 1;
//register commands that player can invoke in-game
@Override
public void registerClientCommands(CommandHandler handler){
if (api != null) {
handler.<Player>register("inspector", "Toggle on tile inspector. (Grief detection)", (args, player) -> {
PlayerData pd = playerDataHashMap.get(player.uuid());
pd.inspector = !pd.inspector;
player.sendMessage("[accent]Tile inspector " + (pd.inspector ? "enabled" : "disabled") + ".");
});
handler.<Player>register("stats", "[player...]", "Display stats of the specified player (or yourself, if no player provided)", (args, player) -> {
if(!statMessage.equals("none")) {
if (args.length <= 0) {
Call.infoMessage(player.con, formatMessage(player, statMessage));
} else {
Player p = findPlayer(args[0]);
if (p != null) {
Call.infoMessage(player.con, formatMessage(p, statMessage));
} else {
player.sendMessage("[lightgray]Can't find that player!");
}
}
}
});
handler.<Player>register("event", "Join an ongoing event (if there is one)", (args, player) -> {
if(eventIp.length() > 0){
Call.connect(player.con, eventIp, eventPort);
} else{
player.sendMessage("[accent]There is no ongoing event at this time.");
}
});
handler.<Player>register("maps","[page]", "Display all maps in the playlist.", (args, player) -> { // self info
if(args.length > 0 && !Strings.canParseInt(args[0])){
player.sendMessage("[scarlet]'page' must be a number.");
return;
}
int perPage = 6;
int page = args.length > 0 ? Strings.parseInt(args[0]) : 1;
int pages = Mathf.ceil((float)Vars.maps.customMaps().size / perPage);
page --;
if(page >= pages || page < 0){
player.sendMessage("[scarlet]'page' must be a number between[orange] 1[] and[orange] " + pages + "[scarlet].");
return;
}
StringBuilder result = new StringBuilder();
result.append(Strings.format("[orange]-- Maps Page[lightgray] {0}[gray]/[lightgray]{1}[orange] --\n\n", (page+1), pages));
for(int i = perPage * page; i < Math.min(perPage * (page + 1), Vars.maps.customMaps().size); i++){
mindustry.maps.Map map = Vars.maps.customMaps().get(i);
result.append("[white] - [accent]").append(escapeColorCodes(map.name())).append("\n");
}
player.sendMessage(result.toString());
});
Timekeeper vtime = new Timekeeper(voteCooldown);
VoteSession[] currentlyKicking = {null};
handler.<Player>register("nominate","<map...>", "[regular+] Vote to change to a specific map.", (args, player) -> {
if(!state.rules.pvp || player.admin()) {
mindustry.maps.Map found = getMapBySelector(args[0]);
if(found != null){
if(!vtime.get()){
player.sendMessage("[scarlet]You must wait " + voteCooldown/60 + " minutes between nominations.");
return;
}
VoteSession session = new VoteSession(currentlyKicking, found);
session.vote(player, 1);
vtime.reset();
currentlyKicking[0] = session;
}else{
player.sendMessage("[scarlet]No map[orange]'" + args[0] + "'[scarlet] found.");
}
} else {
player.sendMessage("[scarlet]This command is disabled on pvp.");
}
});
handler.<Player>register("rtv", "Vote to change the map.", (args, player) -> { // self info
if(currentlyKicking[0] == null){
player.sendMessage("[scarlet]No map is being voted on.");
}else{
//hosts can vote all they want
if(player.uuid() != null && (currentlyKicking[0].voted.contains(player.uuid()) || currentlyKicking[0].voted.contains(netServer.admins.getInfo(player.uuid()).lastIP))){
player.sendMessage("[scarlet]You've already voted.");
return;
}
currentlyKicking[0].vote(player, 1);
}
});
}
}
public static void exit(String[] uselessness){
exit();
}
public static void exit(){
if(playerDataHashMap != null){
playerDataHashMap.forEach(Funcs::setJedisData);
}
if(api != null){
api.shutdownNow();
}
Vars.net.dispose();
Core.app.exit();
System.exit(0);
}
}

7
mod.json Normal file
View File

@ -0,0 +1,7 @@
{
"name": "ioPlugin",
"author": "Various, see https://github.com/fuzzbuck/mindustry.io-plugin/blob/master/CONTRIBUTORS.MD",
"main": "mindustry.plugin.ioMain",
"description": "Allows interaction with discord & vice versa, makes moderation easier.",
"version": 1.61
}