From 2577a71ca7b267ce6bcf1044e1084ddf93d19d78 Mon Sep 17 00:00:00 2001 From: "Ansgar [Hiajen]" Date: Sun, 20 Jun 2021 16:32:54 +0200 Subject: [PATCH] implement tag tools and check tools --- build.gradle | 8 +- src/main/java/net/saltymc/eaa/EaaMod.java | 1 + .../eaa/commands/CheckPlayerCommand.java | 69 ++++++++++++++ .../net/saltymc/eaa/commands/TagCommand.java | 41 ++++++--- .../eaa/custom/ping/CustomPlayerListHud.java | 10 +- .../saltymc/eaa/util/database/DB_Player.java | 23 +++-- .../net/saltymc/eaa/util/database/DB_Tag.java | 61 +++++++++++- .../saltymc/eaa/util/database/Postgre.java | 87 ++++++++++++------ .../eaa/util/mojangApi/PlayerInfo.java | 2 + .../resources/datenbank/datenbank_shema.mwb | Bin 8180 -> 8211 bytes 10 files changed, 252 insertions(+), 50 deletions(-) create mode 100644 src/main/java/net/saltymc/eaa/commands/CheckPlayerCommand.java diff --git a/build.gradle b/build.gradle index eeee300..adb1436 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,13 @@ dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - include group: 'org.postgresql', name: 'postgresql', version: '42.2.9' + //include group: 'org.postgresql', name: 'postgresql', version: '42.2.9' + // https://mvnrepository.com/artifact/mysql/mysql-connector-java + //implementation(include('mysql:mysql-connector-java:8.0.25')) + include group: 'mysql', name: 'mysql-connector-java', version: '8.0.25' + implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.25' + //modImplementation(group: 'mysql', name: 'mysql-connector-java', version: '8.0.25') + // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" diff --git a/src/main/java/net/saltymc/eaa/EaaMod.java b/src/main/java/net/saltymc/eaa/EaaMod.java index 9e2e266..2062d86 100644 --- a/src/main/java/net/saltymc/eaa/EaaMod.java +++ b/src/main/java/net/saltymc/eaa/EaaMod.java @@ -24,6 +24,7 @@ public final class EaaMod implements ClientModInitializer { //Register Commands new EchoCommand(); new TagCommand(); + new CheckPlayerCommand(); } } diff --git a/src/main/java/net/saltymc/eaa/commands/CheckPlayerCommand.java b/src/main/java/net/saltymc/eaa/commands/CheckPlayerCommand.java new file mode 100644 index 0000000..7fdc9c0 --- /dev/null +++ b/src/main/java/net/saltymc/eaa/commands/CheckPlayerCommand.java @@ -0,0 +1,69 @@ +package net.saltymc.eaa.commands; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; +import net.minecraft.client.MinecraftClient; +import net.minecraft.command.argument.EntityArgumentType; +import net.minecraft.text.Text; +import net.saltymc.eaa.util.database.DB_Player; +import net.saltymc.eaa.util.database.DB_Tag; +import net.saltymc.eaa.util.mojangApi.PlayerInfo; + +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import static net.fabricmc.fabric.api.client.command.v1.ClientCommandManager.argument; +import static net.fabricmc.fabric.api.client.command.v1.ClientCommandManager.literal; + +public class CheckPlayerCommand extends EaaModCommand{ + + private static final Map player_tags = new HashMap<>(); + public static final SimpleDateFormat dateformat = new SimpleDateFormat("dd.MM.yyyy"); + + @Override + public int run(CommandContext context) { + FabricClientCommandSource source = context.getSource(); + + String player = StringArgumentType.getString(context,"player"); + + try { + String playerUUID = MinecraftClient.getInstance().getNetworkHandler().getPlayerListEntry(player).getProfile().getId().toString(); + + List names = PlayerInfo.playerUUIDToNames(playerUUID); + for (int i = 1; i <= names.size(); i++) + source.sendFeedback(Text.of(i + ". " + names.get(i-1))); + + List tags = DB_Tag.getTagsFromPlayer(DB_Player.getPlayer(playerUUID)); + if (tags.size() > 0) { + ListIterator it = tags.listIterator(tags.size()); + while (it.hasPrevious()) { + DB_Tag curr = it.previous(); + source.sendFeedback(Text.of( + dateformat.format(curr.getTagDate()) + " | " + curr.getType().getTag() + " (" + curr.getGrade() + ")" + )); + } + } else { + source.sendFeedback(Text.of("Nothing to appeal")); + } + + } catch (Exception e) { + source.sendFeedback(Text.of(e.toString())); + } + + return 1; + } + + @Override + public LiteralArgumentBuilder getCommandSpecification() { + return literal("/check") + .then( + argument("player", StringArgumentType.word()) + .suggests((ctx, builder) -> EntityArgumentType.player().listSuggestions(ctx, builder)) + .executes(this)); + } +} diff --git a/src/main/java/net/saltymc/eaa/commands/TagCommand.java b/src/main/java/net/saltymc/eaa/commands/TagCommand.java index dccca2c..16a9297 100644 --- a/src/main/java/net/saltymc/eaa/commands/TagCommand.java +++ b/src/main/java/net/saltymc/eaa/commands/TagCommand.java @@ -1,24 +1,29 @@ package net.saltymc.eaa.commands; -import com.mojang.brigadier.Message; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.toast.SystemToast; import net.minecraft.command.argument.EntityArgumentType; import net.minecraft.text.Text; -import net.saltymc.eaa.custom.ping.PingColors; import net.saltymc.eaa.util.database.DB_Player; import net.saltymc.eaa.util.database.DB_Tag; -import net.saltymc.eaa.util.mojangApi.PlayerInfo; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static net.fabricmc.fabric.api.client.command.v1.ClientCommandManager.argument; import static net.fabricmc.fabric.api.client.command.v1.ClientCommandManager.literal; public class TagCommand extends EaaModCommand{ + + private static final Map player_tags = new HashMap<>(); + @Override public int run(CommandContext context) { FabricClientCommandSource source = context.getSource(); @@ -28,17 +33,18 @@ public class TagCommand extends EaaModCommand{ int grade = IntegerArgumentType.getInteger(context, "grade"); try { - String playerUUID = PlayerInfo.playerNameToUUID(player); + String playerUUID = MinecraftClient.getInstance().getNetworkHandler().getPlayerListEntry(player).getProfile().getId().toString(); - DB_Player db_player = DB_Player.getPlayer(playerUUID); + DB_Player db_player = DB_Player.getPlayer(playerUUID, true); new DB_Tag(db_player, tag, grade).put(); + loadPlayer(playerUUID, true); + + SystemToast.add(source.getClient().getToastManager(), SystemToast.Type.TUTORIAL_HINT, Text.of("Player Tagged"), + Text.of(player + " | " + tag.name() + " | " + grade)); } catch (Exception e) { source.sendFeedback(Text.of(e.toString())); } - - SystemToast.add(source.getClient().getToastManager(), SystemToast.Type.TUTORIAL_HINT, Text.of("Player Tagged"), - Text.of(player + " | " + tag.name() + " | " + grade)); return 1; } @@ -61,17 +67,30 @@ public class TagCommand extends EaaModCommand{ ))); } - public static CustomText getScoreboardTag(String uuid){ + public static void loadPlayer(String uuid, boolean reload){ + if (!player_tags.containsKey(uuid) || reload){ + DB_Player db_player = DB_Player.getPlayer(uuid); + if (db_player == null) + return; - return new CustomText(Text.of("[NaN]"), PingColors.COLOR_GREY); + List tag = DB_Tag.getTagsFromPlayer(db_player); + if (tag.size() > 0) + player_tags.put(uuid, tag.get(0)); + } + } + public static DB_Tag.Type getScoreboardTag(String uuid){ + loadPlayer(uuid, false); + if (player_tags.containsKey(uuid)) + return player_tags.get(uuid).getType(); + else + return DB_Tag.Type.NOTTAGGED; } public static class CustomText { private final Text text; private final int color; - public CustomText(Text text, int color) { this.text = text; this.color = color; diff --git a/src/main/java/net/saltymc/eaa/custom/ping/CustomPlayerListHud.java b/src/main/java/net/saltymc/eaa/custom/ping/CustomPlayerListHud.java index 713964d..bd0f0c0 100644 --- a/src/main/java/net/saltymc/eaa/custom/ping/CustomPlayerListHud.java +++ b/src/main/java/net/saltymc/eaa/custom/ping/CustomPlayerListHud.java @@ -27,6 +27,7 @@ import net.minecraft.text.OrderedText; import net.minecraft.text.Text; import net.minecraft.world.GameMode; import net.saltymc.eaa.commands.TagCommand; +import net.saltymc.eaa.util.database.DB_Tag; /** * By: https://github.com/vladmarica/better-ping-display-fabric/ @@ -160,10 +161,13 @@ public final class CustomPlayerListHud { aa += 9; } + /* + * PLAYER TAG + */ int offset_tag_playername = aa; - TagCommand.CustomText playerTAG = TagCommand.getScoreboardTag(player.getProfile().getId().toString()); - mc.textRenderer.drawWithShadow(stack, playerTAG.getText(), (float)aa, (float)ab, playerTAG.getColor()); - offset_tag_playername += mc.textRenderer.getWidth(playerTAG.getText().asOrderedText()) + 2; + DB_Tag.Type playerTAG = TagCommand.getScoreboardTag(player.getProfile().getId().toString()); + mc.textRenderer.drawWithShadow(stack, playerTAG.getTag(), (float)aa, (float)ab, playerTAG.getColor()); + offset_tag_playername += mc.textRenderer.getWidth(playerTAG.getTag()) + 2; Text playerName = hud.getPlayerName(player); diff --git a/src/main/java/net/saltymc/eaa/util/database/DB_Player.java b/src/main/java/net/saltymc/eaa/util/database/DB_Player.java index b47b337..8855a0b 100644 --- a/src/main/java/net/saltymc/eaa/util/database/DB_Player.java +++ b/src/main/java/net/saltymc/eaa/util/database/DB_Player.java @@ -33,7 +33,7 @@ public class DB_Player { try { PreparedStatement statement; if (player.id != -1) { - statement = Postgre.getInstance().connection.prepareStatement("UPDATE " + tableName + " SET id = ?, UUID = ? WHERE id = ?;"); + statement = Postgre.getStatement("UPDATE " + tableName + " SET id = ?, UUID = ? WHERE id = ?;"); statement.setInt(1, player.id); statement.setString(2, player.uuid); @@ -41,13 +41,13 @@ public class DB_Player { statement.setInt(3, player.id); } else { - statement = Postgre.getInstance().connection.prepareStatement("INSERT INTO " + tableName + " (UUID) " + + statement = Postgre.getStatement("INSERT INTO " + tableName + " (UUID) " + "VALUES(?);"); statement.setString(1, player.uuid); } - statement.executeQuery(); + statement.execute(); } catch (SQLException e){ throw new RuntimeException("Could not safe Player", e); } @@ -56,7 +56,7 @@ public class DB_Player { public static DB_Player getPlayer(int id){ try { - PreparedStatement statement = Postgre.getInstance().connection.prepareStatement("SELECT * FROM " + tableName + " WHERE id = ?"); + PreparedStatement statement = Postgre.getStatement("SELECT * FROM " + tableName + " WHERE id = ?"); statement.setInt(1, id); ResultSet rs = statement.executeQuery(); rs.next(); @@ -67,19 +67,25 @@ public class DB_Player { } public static DB_Player getPlayer(String uuid){ + return getPlayer(uuid, false); + } + + public static DB_Player getPlayer(String uuid, boolean createIfNull){ try { - PreparedStatement statement = Postgre.getInstance().connection.prepareStatement("SELECT * FROM Player WHERE UUID = ?"); + PreparedStatement statement = Postgre.getStatement("SELECT * FROM Player WHERE UUID = ?"); statement.setString(1, uuid); ResultSet rs = statement.executeQuery(); if (rs.next()){ return new DB_Player(rs.getInt("id"), rs.getString("UUID")); - } else { + } else if (createIfNull){ //unknown Player - create new DB_Player db_player = new DB_Player(uuid); db_player.put(); return getPlayer(uuid); + } else { + return null; } } catch (SQLException e){ throw new RuntimeException("Could not get Player from Database!", e); @@ -101,4 +107,9 @@ public class DB_Player { public void setUuid(String uuid) { this.uuid = uuid; } + + public String toString(){ + return "{ \"id\" : " + id + "," + + "\"uuid\" :" + uuid + "}"; + } } diff --git a/src/main/java/net/saltymc/eaa/util/database/DB_Tag.java b/src/main/java/net/saltymc/eaa/util/database/DB_Tag.java index a8b3dd0..78ca175 100644 --- a/src/main/java/net/saltymc/eaa/util/database/DB_Tag.java +++ b/src/main/java/net/saltymc/eaa/util/database/DB_Tag.java @@ -1,5 +1,7 @@ package net.saltymc.eaa.util.database; +import net.saltymc.eaa.custom.ping.PingColors; + import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -34,7 +36,7 @@ public class DB_Tag { public static void putTag(DB_Tag tag){ try { - PreparedStatement statement = Postgre.getInstance().connect().prepareStatement( + PreparedStatement statement = Postgre.getStatement( "INSERT INTO " + tableName + " (Player_ID, Grade, TagType, TagDate) " + "VALUES (?, ?, ?, ?);"); @@ -42,14 +44,19 @@ public class DB_Tag { statement.setInt(2,tag.grade); statement.setString(3, tag.type.name()); statement.setDate(4 , new java.sql.Date(new Date().getTime())); + + statement.execute(); } catch (SQLException e){ throw new RuntimeException("Could not save Tag!", e); } } public static List getTagsFromPlayer(DB_Player player){ + if (player == null) + return new ArrayList<>(); + try { - PreparedStatement statement = Postgre.getInstance().connect().prepareStatement("SELECT * FROM " + tableName + " WHERE Player_ID = ? ORDER BY TagDate ASC;"); + PreparedStatement statement = Postgre.getStatement("SELECT * FROM " + tableName + " WHERE Player_ID = ? ORDER BY TagDate ASC;"); statement.setInt(1, player.getId()); ResultSet rs = statement.executeQuery(); @@ -71,7 +78,55 @@ public class DB_Tag { } } + public DB_Player getPlayer() { + return player; + } + + public int getGrade() { + return grade; + } + + public Type getType() { + return type; + } + + public Date getTagDate() { + return tagDate; + } + public enum Type { - HACKER, FRIENDLY, GOODPLAYER, IDIOT + HACKER( + "HAX", "PLayer who hacks", PingColors.COLOR_END + ), FRIENDLY( + "FND", "Player you like", PingColors.COLOR_START + ), GOODPLAYER( + "PRO", "Fair player with good skill", PingColors.COLOR_START + ), IDIOT( + "BAD", "Person who is annoying and bad", PingColors.COLOR_MID + ), NOTTAGGED( + "NaN", "Player is not tagged", PingColors.COLOR_GREY + ); + + String tag, description; + int color; + + Type(String tag, String description, int color){ + this.tag = tag; + this.description = description; + this.color = color; + } + + + public String getTag() { + return tag; + } + + public String getDescription() { + return description; + } + + public int getColor() { + return color; + } } } diff --git a/src/main/java/net/saltymc/eaa/util/database/Postgre.java b/src/main/java/net/saltymc/eaa/util/database/Postgre.java index 0d80917..cdea712 100644 --- a/src/main/java/net/saltymc/eaa/util/database/Postgre.java +++ b/src/main/java/net/saltymc/eaa/util/database/Postgre.java @@ -1,8 +1,9 @@ package net.saltymc.eaa.util.database; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; +import net.saltymc.eaa.EaaMod; + +import java.sql.*; +import com.mysql.cj.jdbc.Driver; public class Postgre { @@ -10,12 +11,25 @@ public class Postgre { private final String user = "postgres"; private final String password = "root"; - private static Postgre postgre; - public Connection connection; + private static Postgre postgre; + private Connection connection; + + public Postgre() { + + try { + //load driver / config driver + EaaMod.LOGGER.debug("Lade DB Treiber"); + + new Driver(); + + connection = DriverManager.getConnection("jdbc:" + url + db_name , user, password); + + } catch (SQLException e){ + EaaMod.LOGGER.error(e.getMessage()); + e.printStackTrace(); + } + } - public Postgre(){ - this.connection = connect(); - } static Postgre getInstance(){ if (postgre == null) @@ -24,31 +38,52 @@ public class Postgre { return postgre; } + static PreparedStatement getStatement(String statement) throws SQLException{ + return getInstance().connection.prepareStatement(statement); + } /** - * Connect to the PostgreSQL database - * - * @return a Connection object + * closes current connection and reload it */ - public Connection connect() { - Connection conn = null; + private void reload(){ + cleanUp(); + try { - conn = DriverManager.getConnection(url, user, password); - Class.forName("org.postgresql.Driver"); + //load driver / config driver + EaaMod.LOGGER.debug("Lade DB Treiber"); + Class driver_class = Class.forName(driverName); + Driver dbdriver = (Driver) driver_class.newInstance(); + DriverManager.registerDriver(dbdriver); - if (conn != null) { - System.out.println("Connected to the PostgreSQL server successfully."); - } else { - System.out.println("Failed to make connection!"); + try { + EaaMod.LOGGER.debug("Create connection to Database"); + //Create connection + connection = DriverManager.getConnection("jdbc:" + url + db_name , user, password); + } catch (SQLException e) { + EaaMod.LOGGER.error(e.getMessage()); } + } catch (Exception e){ + EaaMod.LOGGER.error(e.getMessage()); + } + } - } catch (SQLException e) { - System.out.println(e.getMessage()); - } catch (ClassNotFoundException e) { - System.out.println("PostgreSQL JDBC driver not found."); - e.printStackTrace(); - } + /** + * Inspired by https://www.vogella.com/tutorials/MySQLJava/article.html + * + * CleanUp of SQL connection + */ + public void cleanUp(){ + EaaMod.LOGGER.debug("Close database connection"); + try { - return conn; + connection.setAutoCommit(true); + + if (connection != null) + connection.close(); + + connection = null; + } catch(SQLException e){ + e.printStackTrace(); + } } } diff --git a/src/main/java/net/saltymc/eaa/util/mojangApi/PlayerInfo.java b/src/main/java/net/saltymc/eaa/util/mojangApi/PlayerInfo.java index d8b8eb0..d4354a2 100644 --- a/src/main/java/net/saltymc/eaa/util/mojangApi/PlayerInfo.java +++ b/src/main/java/net/saltymc/eaa/util/mojangApi/PlayerInfo.java @@ -3,6 +3,7 @@ package net.saltymc.eaa.util.mojangApi; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import net.minecraft.client.network.PlayerListEntry; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; @@ -14,6 +15,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.ArrayList; +import java.util.Collection; import java.util.List; public class PlayerInfo { diff --git a/src/main/resources/datenbank/datenbank_shema.mwb b/src/main/resources/datenbank/datenbank_shema.mwb index 284fcf03ecbb6e2c90135934767e7d7d57225a95..5e725245a1ea29a1ec9cfaf3e3075aa80251854e 100644 GIT binary patch delta 6699 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 B1xWw^ delta 6707 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 uF>R)w1>Cm(24qJ