1 Commits

Author SHA1 Message Date
ThePindabaas
70d97334cd add player listener
All checks were successful
Build / Explore-Gitea-Actions (push) Successful in 44s
2026-07-26 20:36:45 +02:00
7 changed files with 281 additions and 19 deletions

View File

@@ -1,5 +1,9 @@
package net.ardakaz.griefalert;
import net.ardakaz.griefalert.database.DatabaseService;
import net.ardakaz.griefalert.database.Migrations;
import net.ardakaz.griefalert.database.PlayerRepository;
import net.ardakaz.griefalert.listener.PlayerListener;
import net.coreprotect.CoreProtect;
import net.coreprotect.CoreProtectAPI;
import net.coreprotect.CoreProtectAPI.ParseResult;
@@ -28,6 +32,7 @@ import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import java.sql.*;
@@ -35,6 +40,10 @@ import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -48,6 +57,8 @@ public class GriefAlert extends JavaPlugin implements Listener {
private String MAP_LINK;
private Boolean ALLOW_STEALING;
private Connection connection;
private DatabaseService database;
private Logger logger;
/*
UTILITY FUNCTIONS
@@ -155,6 +166,7 @@ public class GriefAlert extends JavaPlugin implements Listener {
// Init GriefAlert
@Override
public void onEnable() {
logger = getLogger();
coreProtectAPI = getCoreProtect();
if (coreProtectAPI == null) {
@@ -163,7 +175,6 @@ public class GriefAlert extends JavaPlugin implements Listener {
return;
}
getServer().getPluginManager().registerEvents(this, this);
// Config
saveDefaultConfig();
@@ -176,9 +187,18 @@ public class GriefAlert extends JavaPlugin implements Listener {
setupDatabase();
PlayerRepository playerRepository = new PlayerRepository(database, logger);
PlayerListener playerListener = new PlayerListener(playerRepository, logger);
PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(this, this);
pluginManager.registerEvents(playerListener, this);
getCommand("griefalert").setTabCompleter(this);
getLogger().info("GriefAlert has been enabled.");
}
@Override
@@ -193,19 +213,19 @@ public class GriefAlert extends JavaPlugin implements Listener {
}
private void setupDatabase() {
ExecutorService executor = Executors.newSingleThreadExecutor();
String jdbcUrl = getConfig().getString("jdbc-url");
try {
connection = DriverManager.getConnection(jdbcUrl);
Statement stmt = connection.createStatement();
stmt.executeUpdate(Migrations.CREATE_TABLE_IGNORED_LOCATIONS);
stmt.executeUpdate(Migrations.CREATE_TABLE_PLAYERS);
stmt.executeUpdate(Migrations.CREATE_TABLE_ACTIONS);
stmt.executeUpdate(Migrations.CREATE_TABLE_ALERTS);
stmt.executeUpdate(Migrations.CREATE_TABLE_REPORTS);
stmt.close();
getLogger().info("Connected to "+jdbcUrl);
database = new DatabaseService(executor, connection);
logger.info("Connected to " + jdbcUrl);
CompletableFuture<Void> migrationExecution = new Migrations(database, logger).execute();
migrationExecution.join();
logger.info("Database migration finished");
} catch (SQLException e) {
getLogger().severe("Could not set up SQLite database: " + e.getMessage());
logger.severe("Could not set up SQLite database: " + e.getMessage());
}
}

View File

@@ -0,0 +1,9 @@
package net.ardakaz.griefalert.database;
import java.sql.Connection;
import java.sql.SQLException;
@FunctionalInterface
public interface DatabaseOperation<T> {
T execute(Connection connection) throws SQLException;
}

View File

@@ -0,0 +1,27 @@
package net.ardakaz.griefalert.database;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
public class DatabaseService {
private final ExecutorService executor;
private final Connection connection;
public DatabaseService(ExecutorService executor, Connection connection) {
this.executor = executor;
this.connection = connection;
}
public <T> CompletableFuture<T> submit(DatabaseOperation<T> operation) {
return CompletableFuture.supplyAsync(() -> {
try {
return operation.execute(connection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}, executor);
}
}

View File

@@ -1,8 +1,21 @@
package net.ardakaz.griefalert;
package net.ardakaz.griefalert.database;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;
public class Migrations {
public static final String CREATE_TABLE_IGNORED_LOCATIONS =
private DatabaseService database;
private Logger logger;
public Migrations(DatabaseService database, Logger logger) {
this.database = database;
this.logger = logger;
}
private static final String CREATE_TABLE_IGNORED_LOCATIONS =
"CREATE TABLE IF NOT EXISTS ignored_locations (" +
"x INTEGER, " +
"y INTEGER, " +
@@ -11,16 +24,16 @@ public class Migrations {
"PRIMARY KEY (x, y, z, world)" +
")";
public static final String CREATE_TABLE_PLAYERS =
private static final String CREATE_TABLE_PLAYERS =
"CREATE TABLE IF NOT EXISTS players (" +
"id TEXT PRIMARY KEY, " +
"names TEXT NOT NULL, " +
"playtime INTEGER NOT NULL DEFAULT 0, " +
"firstseen INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
"lastseen INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP" +
"firstseen INTEGER NOT NULL, " +
"lastseen INTEGER NOT NULL, " +
"playtime INTEGER NOT NULL DEFAULT 0 " +
")";
public static final String CREATE_TABLE_ACTIONS =
private static final String CREATE_TABLE_ACTIONS =
"CREATE TABLE IF NOT EXISTS actions (" +
"id INTEGER PRIMARY KEY, " +
"player TEXT NOT NULL, " +
@@ -32,7 +45,7 @@ public class Migrations {
"FOREIGN KEY(player) REFERENCES players(id)" +
")";
public static final String CREATE_TABLE_ALERTS =
private static final String CREATE_TABLE_ALERTS =
"CREATE TABLE IF NOT EXISTS alerts (" +
"id INTEGER PRIMARY KEY, " +
"player TEXT NOT NULL, " +
@@ -52,7 +65,7 @@ public class Migrations {
"FOREIGN KEY(player) REFERENCES players(id)" +
")";
public static final String CREATE_TABLE_REPORTS =
private static final String CREATE_TABLE_REPORTS =
"CREATE TABLE IF NOT EXISTS reports (" +
"id INTEGER PRIMARY KEY, " +
"player TEXT NOT NULL, " +
@@ -65,4 +78,19 @@ public class Migrations {
"timestamp INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
"state TEXT NOT NULL DEFAULT 'UNRESOLVED'" +
")";
public CompletableFuture<Void> execute() {
return database.submit(connection -> {
try(Statement stmt = connection.createStatement()){
stmt.executeUpdate(Migrations.CREATE_TABLE_IGNORED_LOCATIONS);
stmt.executeUpdate(Migrations.CREATE_TABLE_PLAYERS);
stmt.executeUpdate(Migrations.CREATE_TABLE_ACTIONS);
stmt.executeUpdate(Migrations.CREATE_TABLE_ALERTS);
stmt.executeUpdate(Migrations.CREATE_TABLE_REPORTS);
} catch (SQLException e) {
logger.severe("Failed to execute database migrations: " + e.getMessage());
}
return null;
});
}
}

View File

@@ -0,0 +1,6 @@
package net.ardakaz.griefalert.database;
import java.util.List;
public record PlayerEntity(String id, List<String> names, long playtime, long firstSeen, long lastSeen) {
}

View File

@@ -0,0 +1,95 @@
package net.ardakaz.griefalert.database;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;
public class PlayerRepository {
private Logger logger;
private final DatabaseService database;
public PlayerRepository(DatabaseService database, Logger logger) {
this.database = database;
this.logger = logger;
}
public CompletableFuture<PlayerEntity> get(String uuid) {
return database.submit(connection -> {
try (PreparedStatement ps = connection.prepareStatement("SELECT * FROM players WHERE id=?")) {
ps.setString(1, uuid);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return new PlayerEntity(
rs.getString("id"),
Arrays.stream(rs.getString("names").split(",")).toList(),
rs.getLong("playtime"),
rs.getLong("firstseen"),
rs.getLong("lastseen")
);
}
}
} catch (SQLException e) {
logger.severe("DB error: " + e.getMessage());
}
return null;
});
}
public CompletableFuture<Void> upsert(PlayerEntity entity) {
return database.submit(connection -> {
String upsert = """
INSERT INTO players(id, names, firstseen, lastseen) VALUES(?,?,?,?)
ON CONFLICT(id)
DO UPDATE SET names=excluded.names, playtime=?, lastseen=excluded.lastseen;
""";
try (PreparedStatement ps = connection.prepareStatement(upsert)) {
ps.setString(1, entity.id());
String names = String.join(",", entity.names());
ps.setString(2, names);
ps.setLong(3, entity.firstSeen());
ps.setLong(4, entity.lastSeen());
ps.setLong(5, entity.playtime());
ps.executeUpdate();
} catch (SQLException e) {
logger.severe("Failed to save player: " + entity);
logger.severe(e.getMessage());
}
return null;
});
}
public CompletableFuture<Void> updateNames(String id, List<String> names) {
return database.submit(connection -> {
String update = """
UPDATE players
SET names = ?
WHERE id = ?
""";
try (PreparedStatement ps = connection.prepareStatement(update)) {
ps.setString(1, String.join(",", names));
ps.setString(2, id);
ps.executeUpdate();
} catch (SQLException e) {
logger.severe("Failed to update names for playerID: " + id);
logger.severe(e.getMessage());
}
return null;
});
}
}

View File

@@ -0,0 +1,77 @@
package net.ardakaz.griefalert.listener;
import net.ardakaz.griefalert.database.PlayerEntity;
import net.ardakaz.griefalert.database.PlayerRepository;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
public class PlayerListener implements Listener {
private Logger logger;
private PlayerRepository repository;
public PlayerListener(PlayerRepository repository, Logger logger) {
this.logger = logger;
this.repository = repository;
}
@EventHandler
public void onPlayerLogin(PlayerJoinEvent event) {
Player eventPlayer = event.getPlayer();
repository.get(
eventPlayer.getUniqueId().toString()
).thenAccept(dbPlayer -> {
if (dbPlayer == null) {
long now = System.currentTimeMillis();
repository.upsert(new PlayerEntity(
eventPlayer.getUniqueId().toString(),
List.of(eventPlayer.getName()),
0,
now,
now
)
);
} else {
List<String> newNames = updateNames(eventPlayer.getName(), dbPlayer.names());
repository.updateNames(dbPlayer.id(), newNames);
}
});
}
@EventHandler
public void onPlayerLogout(PlayerQuitEvent event) {
Player eventPlayer = event.getPlayer();
long playtimeSeconds = eventPlayer.getStatistic(Statistic.PLAY_ONE_MINUTE) / 20L;
repository.get(
eventPlayer.getUniqueId().toString()
).thenAccept(dbPlayer -> {
if (dbPlayer != null) {
PlayerEntity updatedPlayer = new PlayerEntity(
dbPlayer.id(),
dbPlayer.names(),
playtimeSeconds,
dbPlayer.firstSeen(),
System.currentTimeMillis()
);
repository.upsert(updatedPlayer);
}
});
}
private List<String> updateNames(String currentName, List<String> historicalNames) {
List<String> newNames = new ArrayList<>(historicalNames);
while (newNames.contains(currentName)) {
newNames.remove(currentName);
}
newNames.add(currentName);
return newNames;
}
}