feat(mining): implement complete custom mining system with SQLite persistence
This commit is contained in:
+37
-39
@@ -1,54 +1,52 @@
|
||||
@startuml mining_model
|
||||
!theme plain
|
||||
skinparam roundcorner 10
|
||||
skinparam roundcorner 8
|
||||
skinparam shadowing false
|
||||
skinparam classAttributeIconSize 0
|
||||
skinparam linetype ortho
|
||||
|
||||
title Système de Minage Personnalisé (GamingCore) - Modèle de Données & Architecture
|
||||
title Système de Minage Personnalisé (GamingCore) - Modèle & Architecture
|
||||
|
||||
package "Database Schema (SQLite / MySQL)" <<Database>> {
|
||||
entity "ores" as OresTable {
|
||||
* id : VARCHAR(64) <<PK>>
|
||||
package "Database Schema (SQLite / MySQL)" <<Rectangle>> {
|
||||
class "ores" as OresTable << (T,#5DADE2) >> {
|
||||
+ id : VARCHAR(64) [PK]
|
||||
--
|
||||
* block_material : VARCHAR(64)
|
||||
* hardness : DOUBLE
|
||||
* rarity : VARCHAR(32)
|
||||
* stock : INTEGER
|
||||
* respawnable : BOOLEAN
|
||||
* respawn_time : INTEGER
|
||||
* temp_block : VARCHAR(64)
|
||||
created_at : TIMESTAMP
|
||||
+ block_material : VARCHAR(64)
|
||||
+ hardness : DOUBLE
|
||||
+ rarity : VARCHAR(32)
|
||||
+ stock : INTEGER
|
||||
+ respawnable : BOOLEAN
|
||||
+ respawn_time : INTEGER
|
||||
+ temp_block : VARCHAR(64)
|
||||
+ created_at : TIMESTAMP
|
||||
}
|
||||
|
||||
entity "ore_drops" as OreDropsTable {
|
||||
* id : INTEGER <<PK, AUTOINCREMENT>>
|
||||
class "ore_drops" as OreDropsTable << (T,#5DADE2) >> {
|
||||
+ id : INTEGER [PK AUTO]
|
||||
--
|
||||
* ore_id : VARCHAR(64) <<FK>>
|
||||
* item_material : VARCHAR(64)
|
||||
* default_amount : INTEGER
|
||||
* default_chance : DOUBLE
|
||||
* fortunable : BOOLEAN
|
||||
* fortune_multiplier : DOUBLE
|
||||
* smeltable : BOOLEAN
|
||||
smelted_material : VARCHAR(64)
|
||||
+ ore_id : VARCHAR(64) [FK]
|
||||
+ item_material : VARCHAR(64)
|
||||
+ default_amount : INTEGER
|
||||
+ default_chance : DOUBLE
|
||||
+ fortunable : BOOLEAN
|
||||
+ fortune_multiplier : DOUBLE
|
||||
+ smeltable : BOOLEAN
|
||||
+ smelted_material : VARCHAR(64)
|
||||
}
|
||||
|
||||
entity "placed_ores" as PlacedOresTable {
|
||||
* id : INTEGER <<PK, AUTOINCREMENT>>
|
||||
class "placed_ores" as PlacedOresTable << (T,#5DADE2) >> {
|
||||
+ id : INTEGER [PK AUTO]
|
||||
--
|
||||
* ore_id : VARCHAR(64) <<FK>>
|
||||
* world : VARCHAR(64)
|
||||
* x : INTEGER
|
||||
* y : INTEGER
|
||||
* z : INTEGER
|
||||
* current_stock : INTEGER
|
||||
* is_depleted : BOOLEAN
|
||||
* next_respawn_at : BIGINT
|
||||
+ ore_id : VARCHAR(64) [FK]
|
||||
+ world : VARCHAR(64)
|
||||
+ x : INTEGER
|
||||
+ y : INTEGER
|
||||
+ z : INTEGER
|
||||
+ current_stock : INTEGER
|
||||
+ is_depleted : BOOLEAN
|
||||
+ next_respawn_at : BIGINT
|
||||
}
|
||||
|
||||
OresTable ||..o{ OreDropsTable : "1:N (Drops configurés)"
|
||||
OresTable ||..o{ PlacedOresTable : "1:N (Blocs actifs dans le monde)"
|
||||
OresTable "1" *-- "0..*" OreDropsTable : "drops configurés"
|
||||
OresTable "1" *-- "0..*" PlacedOresTable : "blocs actifs"
|
||||
}
|
||||
|
||||
package "fr.luc.gamingcore.mining.model" {
|
||||
@@ -137,8 +135,8 @@ package "fr.luc.gamingcore.mining.listener" {
|
||||
}
|
||||
}
|
||||
|
||||
CustomOre "1" *-- "many" OreDrop
|
||||
CustomOre "1" -- "many" PlacedOreBlock
|
||||
CustomOre "1" *-- "0..*" OreDrop
|
||||
CustomOre "1" -- "0..*" PlacedOreBlock
|
||||
CustomOre --> OreRarity
|
||||
|
||||
OreManager o-- CustomOre
|
||||
|
||||
@@ -1,25 +1,73 @@
|
||||
package fr.luc.gamingcore;
|
||||
|
||||
import fr.luc.gamingcore.mining.command.MiningCommand;
|
||||
import fr.luc.gamingcore.mining.database.DatabaseManager;
|
||||
import fr.luc.gamingcore.mining.listener.BlockBreakListener;
|
||||
import fr.luc.gamingcore.mining.listener.BlockProtectionListener;
|
||||
import fr.luc.gamingcore.mining.manager.OreManager;
|
||||
import fr.luc.gamingcore.util.TextUtil;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public final class GamingCore extends JavaPlugin {
|
||||
|
||||
private static GamingCore instance;
|
||||
|
||||
private DatabaseManager databaseManager;
|
||||
private OreManager oreManager;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
saveDefaultConfig();
|
||||
|
||||
getLogger().info("GamingCore a ete active avec succes !");
|
||||
// Initialize Text Util prefix from config
|
||||
TextUtil.setPrefix(getConfig().getString("prefix"));
|
||||
|
||||
// Initialize SQLite Database
|
||||
this.databaseManager = new DatabaseManager(this);
|
||||
this.databaseManager.initialize();
|
||||
|
||||
// Initialize Ore Manager & Load Data
|
||||
this.oreManager = new OreManager(this, databaseManager);
|
||||
this.oreManager.loadData();
|
||||
|
||||
// Register Listeners
|
||||
PluginManager pm = getServer().getPluginManager();
|
||||
pm.registerEvents(new BlockBreakListener(this, oreManager), this);
|
||||
pm.registerEvents(new BlockProtectionListener(oreManager), this);
|
||||
|
||||
// Register Commands
|
||||
MiningCommand miningCommand = new MiningCommand(this, oreManager);
|
||||
PluginCommand gcCmd = getCommand("gamingcore");
|
||||
if (gcCmd != null) {
|
||||
gcCmd.setExecutor(miningCommand);
|
||||
gcCmd.setTabCompleter(miningCommand);
|
||||
}
|
||||
|
||||
getLogger().info("GamingCore (Custom Mining Module) a ete active avec succes !");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (oreManager != null) {
|
||||
oreManager.shutdown();
|
||||
}
|
||||
|
||||
getLogger().info("GamingCore a ete desactive !");
|
||||
}
|
||||
|
||||
public static GamingCore getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public DatabaseManager getDatabaseManager() {
|
||||
return databaseManager;
|
||||
}
|
||||
|
||||
public OreManager getOreManager() {
|
||||
return oreManager;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
package fr.luc.gamingcore.mining.command;
|
||||
|
||||
import fr.luc.gamingcore.GamingCore;
|
||||
import fr.luc.gamingcore.mining.manager.OreManager;
|
||||
import fr.luc.gamingcore.mining.model.CustomOre;
|
||||
import fr.luc.gamingcore.mining.model.OreDrop;
|
||||
import fr.luc.gamingcore.mining.model.PlacedOreBlock;
|
||||
import fr.luc.gamingcore.util.TextUtil;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MiningCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
private final GamingCore plugin;
|
||||
private final OreManager oreManager;
|
||||
|
||||
public MiningCommand(GamingCore plugin, OreManager oreManager) {
|
||||
this.plugin = plugin;
|
||||
this.oreManager = oreManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length == 0 || args[0].equalsIgnoreCase("help")) {
|
||||
sendHelp(sender, label);
|
||||
return true;
|
||||
}
|
||||
|
||||
String sub = args[0].toLowerCase();
|
||||
|
||||
if (sub.equals("reload")) {
|
||||
if (!sender.hasPermission("gamingcore.admin")) {
|
||||
TextUtil.sendMessage(sender, "<red>Tu n'as pas la permission d'exécuter cette commande.</red>");
|
||||
return true;
|
||||
}
|
||||
plugin.reloadConfig();
|
||||
TextUtil.setPrefix(plugin.getConfig().getString("prefix"));
|
||||
oreManager.loadData();
|
||||
TextUtil.sendMessage(sender, "<green>Configuration et minerais rechargés avec succès !</green>");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sub.equals("ore")) {
|
||||
if (args.length < 2) {
|
||||
sendHelp(sender, label);
|
||||
return true;
|
||||
}
|
||||
|
||||
String action = args[1].toLowerCase();
|
||||
|
||||
switch (action) {
|
||||
case "list":
|
||||
handleList(sender);
|
||||
return true;
|
||||
|
||||
case "set":
|
||||
case "place":
|
||||
if (!sender.hasPermission("gamingcore.admin")) {
|
||||
TextUtil.sendMessage(sender, "<red>Tu n'as pas la permission d'exécuter cette commande.</red>");
|
||||
return true;
|
||||
}
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("Cette commande doit être exécutée par un joueur.");
|
||||
return true;
|
||||
}
|
||||
if (args.length < 3) {
|
||||
TextUtil.sendMessage(sender, "<red>Utilisation: /" + label + " ore set <ore_id></red>");
|
||||
return true;
|
||||
}
|
||||
handleSetOre((Player) sender, args[2]);
|
||||
return true;
|
||||
|
||||
case "remove":
|
||||
case "delete":
|
||||
if (!sender.hasPermission("gamingcore.admin")) {
|
||||
TextUtil.sendMessage(sender, "<red>Tu n'as pas la permission d'exécuter cette commande.</red>");
|
||||
return true;
|
||||
}
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("Cette commande doit être exécutée par un joueur.");
|
||||
return true;
|
||||
}
|
||||
handleRemoveOre((Player) sender);
|
||||
return true;
|
||||
|
||||
case "info":
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("Cette commande doit être exécutée par un joueur.");
|
||||
return true;
|
||||
}
|
||||
handleInfoOre((Player) sender);
|
||||
return true;
|
||||
|
||||
default:
|
||||
sendHelp(sender, label);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
sendHelp(sender, label);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleList(CommandSender sender) {
|
||||
Collection<CustomOre> ores = oreManager.getAllOres();
|
||||
if (ores.isEmpty()) {
|
||||
TextUtil.sendMessage(sender, "<yellow>Aucun minerai personnalisé n'est actuellement configuré.</yellow>");
|
||||
return;
|
||||
}
|
||||
|
||||
TextUtil.sendRawMessage(sender, "<gradient:#00C9FF:#92FE9D>═══════════ [ Minerais Personnalisés ] ═══════════</gradient>");
|
||||
for (CustomOre ore : ores) {
|
||||
TextUtil.sendRawMessage(sender, " <dark_gray>•</dark_gray> <white><bold>" + ore.getId() + "</bold></white> - " + ore.getDisplayName() +
|
||||
" " + ore.getRarity().getTag() + " <gray>(Stock: <yellow>" + ore.getStock() + "</yellow>, Respawn: <yellow>" + ore.getRespawnTimeSeconds() + "s</yellow>)</gray>");
|
||||
for (OreDrop drop : ore.getDrops()) {
|
||||
TextUtil.sendRawMessage(sender, " <dark_gray>└─</dark_gray> <gray>Drop: <white>" + drop.getItemMaterial().name() + "</white> x" + drop.getDefaultAmount() +
|
||||
" <dark_gray>(" + (int)(drop.getDefaultChance() * 100) + "%)</dark_gray></gray>");
|
||||
}
|
||||
}
|
||||
TextUtil.sendRawMessage(sender, "<gradient:#00C9FF:#92FE9D>══════════════════════════════════════════════════</gradient>");
|
||||
}
|
||||
|
||||
private void handleSetOre(Player player, String oreId) {
|
||||
CustomOre ore = oreManager.getOre(oreId);
|
||||
if (ore == null) {
|
||||
TextUtil.sendMessage(player, "<red>Minerai introuvable : <yellow>" + oreId + "</yellow>. Tape /gc ore list pour voir la liste.</red>");
|
||||
return;
|
||||
}
|
||||
|
||||
Block targetBlock = player.getTargetBlockExact(5);
|
||||
if (targetBlock == null || targetBlock.isEmpty()) {
|
||||
TextUtil.sendMessage(player, "<red>Tu dois regarder un bloc valide à moins de 5 blocs de distance.</red>");
|
||||
return;
|
||||
}
|
||||
|
||||
Location loc = targetBlock.getLocation();
|
||||
PlacedOreBlock placed = oreManager.placeOreBlock(ore.getId(), loc);
|
||||
if (placed != null) {
|
||||
TextUtil.sendMessage(player, "<green>Bloc défini avec succès en tant que minerai <white>" + ore.getId() + "</white> (" + ore.getDisplayName() + ") !</green>");
|
||||
} else {
|
||||
TextUtil.sendMessage(player, "<red>Une erreur est survenue lors de la pose du minerai.</red>");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRemoveOre(Player player) {
|
||||
Block targetBlock = player.getTargetBlockExact(5);
|
||||
if (targetBlock == null || targetBlock.isEmpty()) {
|
||||
TextUtil.sendMessage(player, "<red>Tu dois regarder un bloc valide à moins de 5 blocs de distance.</red>");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean removed = oreManager.removeOreBlock(targetBlock.getLocation());
|
||||
if (removed) {
|
||||
TextUtil.sendMessage(player, "<green>Minerai personnalisé retiré avec succès de ce bloc.</green>");
|
||||
} else {
|
||||
TextUtil.sendMessage(player, "<red>Ce bloc n'est pas un minerai personnalisé actif.</red>");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInfoOre(Player player) {
|
||||
Block targetBlock = player.getTargetBlockExact(5);
|
||||
if (targetBlock == null || targetBlock.isEmpty()) {
|
||||
TextUtil.sendMessage(player, "<red>Tu dois regarder un bloc valide à moins de 5 blocs de distance.</red>");
|
||||
return;
|
||||
}
|
||||
|
||||
PlacedOreBlock placed = oreManager.getPlacedBlock(targetBlock.getLocation());
|
||||
if (placed == null) {
|
||||
TextUtil.sendMessage(player, "<red>Ce bloc n'est pas un minerai personnalisé.</red>");
|
||||
return;
|
||||
}
|
||||
|
||||
CustomOre ore = placed.getOreType();
|
||||
TextUtil.sendRawMessage(player, "<gradient:#00C9FF:#92FE9D>═══════════ [ Info Minerai ] ═══════════</gradient>");
|
||||
TextUtil.sendRawMessage(player, " <gray>ID : <white>" + placed.getOreId() + "</white></gray>");
|
||||
TextUtil.sendRawMessage(player, " <gray>Nom : " + ore.getDisplayName() + "</gray>");
|
||||
TextUtil.sendRawMessage(player, " <gray>Rareté : " + ore.getRarity().getTag() + "</gray>");
|
||||
TextUtil.sendRawMessage(player, " <gray>Stock : <yellow>" + placed.getCurrentStock() + "</yellow>/<white>" + ore.getStock() + "</white></gray>");
|
||||
TextUtil.sendRawMessage(player, " <gray>Épuisé : <white>" + (placed.isDepleted() ? "<red>Oui</red>" : "<green>Non</green>") + "</white></gray>");
|
||||
if (placed.isDepleted()) {
|
||||
long remaining = Math.max(0, (placed.getNextRespawnTimestamp() - System.currentTimeMillis()) / 1000);
|
||||
TextUtil.sendRawMessage(player, " <gray>Respawn dans : <yellow>" + remaining + "s</yellow></gray>");
|
||||
}
|
||||
TextUtil.sendRawMessage(player, "<gradient:#00C9FF:#92FE9D>════════════════════════════════════════</gradient>");
|
||||
}
|
||||
|
||||
private void sendHelp(CommandSender sender, String label) {
|
||||
TextUtil.sendRawMessage(sender, "<gradient:#00C9FF:#92FE9D>═══════════ [ GamingCore Mining ] ═══════════</gradient>");
|
||||
TextUtil.sendRawMessage(sender, " <yellow>/" + label + " ore list</yellow> <dark_gray>-</dark_gray> <gray>Liste des minerais disponibles</gray>");
|
||||
TextUtil.sendRawMessage(sender, " <yellow>/" + label + " ore info</yellow> <dark_gray>-</dark_gray> <gray>Voir les infos du bloc ciblé</gray>");
|
||||
if (sender.hasPermission("gamingcore.admin")) {
|
||||
TextUtil.sendRawMessage(sender, " <yellow>/" + label + " ore set <ore_id></yellow> <dark_gray>-</dark_gray> <gray>Transformer le bloc ciblé en minerai</gray>");
|
||||
TextUtil.sendRawMessage(sender, " <yellow>/" + label + " ore remove</yellow> <dark_gray>-</dark_gray> <gray>Retirer le minerai ciblé</gray>");
|
||||
TextUtil.sendRawMessage(sender, " <yellow>/" + label + " reload</yellow> <dark_gray>-</dark_gray> <gray>Recharger la configuration et la BDD</gray>");
|
||||
}
|
||||
TextUtil.sendRawMessage(sender, "<gradient:#00C9FF:#92FE9D>════════════════════════════════════════════</gradient>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
List<String> completions = new ArrayList<>();
|
||||
|
||||
if (args.length == 1) {
|
||||
List<String> subcommands = new ArrayList<>(Arrays.asList("help", "ore"));
|
||||
if (sender.hasPermission("gamingcore.admin")) {
|
||||
subcommands.add("reload");
|
||||
}
|
||||
return subcommands.stream()
|
||||
.filter(s -> s.toLowerCase().startsWith(args[0].toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
if (args.length == 2 && args[0].equalsIgnoreCase("ore")) {
|
||||
List<String> oreSubcommands = new ArrayList<>(Arrays.asList("list", "info"));
|
||||
if (sender.hasPermission("gamingcore.admin")) {
|
||||
oreSubcommands.addAll(Arrays.asList("set", "remove"));
|
||||
}
|
||||
return oreSubcommands.stream()
|
||||
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
if (args.length == 3 && args[0].equalsIgnoreCase("ore") && args[1].equalsIgnoreCase("set")) {
|
||||
if (sender.hasPermission("gamingcore.admin")) {
|
||||
return oreManager.getAllOres().stream()
|
||||
.map(CustomOre::getId)
|
||||
.filter(id -> id.toLowerCase().startsWith(args[2].toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
return completions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
package fr.luc.gamingcore.mining.database;
|
||||
|
||||
import com.cryptomorin.xseries.XMaterial;
|
||||
import fr.luc.gamingcore.GamingCore;
|
||||
import fr.luc.gamingcore.mining.model.*;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class DatabaseManager {
|
||||
|
||||
private final GamingCore plugin;
|
||||
private final File dbFile;
|
||||
private Connection connection;
|
||||
|
||||
public DatabaseManager(GamingCore plugin) {
|
||||
this.plugin = plugin;
|
||||
this.dbFile = new File(plugin.getDataFolder(), "database.db");
|
||||
}
|
||||
|
||||
public synchronized void initialize() {
|
||||
if (!plugin.getDataFolder().exists()) {
|
||||
plugin.getDataFolder().mkdirs();
|
||||
}
|
||||
|
||||
try {
|
||||
connect();
|
||||
createTables();
|
||||
seedDefaultOresIfEmpty();
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Impossible d'initialiser la base de donnees SQLite !", e);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void connect() throws SQLException {
|
||||
if (connection != null && !connection.isClosed()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Paper runtime includes SQLite JDBC by default
|
||||
}
|
||||
String url = "jdbc:sqlite:" + dbFile.getAbsolutePath();
|
||||
connection = DriverManager.getConnection(url);
|
||||
}
|
||||
|
||||
private synchronized Connection getConnection() throws SQLException {
|
||||
if (connection == null || connection.isClosed()) {
|
||||
connect();
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
private void createTables() throws SQLException {
|
||||
try (Statement stmt = getConnection().createStatement()) {
|
||||
// Ores Table
|
||||
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS ores (" +
|
||||
"id VARCHAR(64) PRIMARY KEY, " +
|
||||
"display_name VARCHAR(64) NOT NULL, " +
|
||||
"block_material VARCHAR(64) NOT NULL, " +
|
||||
"hardness DOUBLE NOT NULL DEFAULT 1.0, " +
|
||||
"rarity VARCHAR(32) NOT NULL DEFAULT 'COMMON', " +
|
||||
"stock INTEGER NOT NULL DEFAULT 1, " +
|
||||
"respawnable INTEGER NOT NULL DEFAULT 1, " +
|
||||
"respawn_time INTEGER NOT NULL DEFAULT 10, " +
|
||||
"temp_block VARCHAR(64) NOT NULL DEFAULT 'BEDROCK', " +
|
||||
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP" +
|
||||
");");
|
||||
|
||||
// Ore Drops Table
|
||||
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS ore_drops (" +
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
"ore_id VARCHAR(64) NOT NULL, " +
|
||||
"item_material VARCHAR(64) NOT NULL, " +
|
||||
"default_amount INTEGER NOT NULL DEFAULT 1, " +
|
||||
"default_chance DOUBLE NOT NULL DEFAULT 1.0, " +
|
||||
"fortunable INTEGER NOT NULL DEFAULT 1, " +
|
||||
"fortune_multiplier DOUBLE NOT NULL DEFAULT 1.0, " +
|
||||
"smeltable INTEGER NOT NULL DEFAULT 0, " +
|
||||
"smelted_material VARCHAR(64), " +
|
||||
"FOREIGN KEY (ore_id) REFERENCES ores(id) ON DELETE CASCADE" +
|
||||
");");
|
||||
|
||||
// Placed Ores Table
|
||||
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS placed_ores (" +
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
"ore_id VARCHAR(64) NOT NULL, " +
|
||||
"world VARCHAR(64) NOT NULL, " +
|
||||
"x INTEGER NOT NULL, " +
|
||||
"y INTEGER NOT NULL, " +
|
||||
"z INTEGER NOT NULL, " +
|
||||
"current_stock INTEGER NOT NULL, " +
|
||||
"is_depleted INTEGER NOT NULL DEFAULT 0, " +
|
||||
"next_respawn_at BIGINT NOT NULL DEFAULT 0, " +
|
||||
"FOREIGN KEY (ore_id) REFERENCES ores(id) ON DELETE CASCADE" +
|
||||
");");
|
||||
}
|
||||
}
|
||||
|
||||
private void seedDefaultOresIfEmpty() throws SQLException {
|
||||
try (Statement stmt = getConnection().createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM ores")) {
|
||||
if (rs.next() && rs.getInt(1) == 0) {
|
||||
plugin.getLogger().info("Insertion des minerais par defaut dans la base SQLite...");
|
||||
|
||||
// 1. Custom Iron
|
||||
CustomOre iron = new CustomOre("custom_iron", "<white>Fer Renforcé</white>", XMaterial.IRON_ORE, 3.0,
|
||||
OreRarity.COMMON, 3, true, 15, XMaterial.BEDROCK, new ArrayList<>());
|
||||
iron.addDrop(new OreDrop(0, "custom_iron", XMaterial.RAW_IRON, 1, 1.0, true, 1.0, true, XMaterial.IRON_INGOT));
|
||||
saveOre(iron);
|
||||
|
||||
// 2. Custom Gold
|
||||
CustomOre gold = new CustomOre("custom_gold", "<gold>Or Pur</gold>", XMaterial.GOLD_ORE, 3.5,
|
||||
OreRarity.UNCOMMON, 4, true, 20, XMaterial.BEDROCK, new ArrayList<>());
|
||||
gold.addDrop(new OreDrop(0, "custom_gold", XMaterial.RAW_GOLD, 1, 1.0, true, 1.0, true, XMaterial.GOLD_INGOT));
|
||||
saveOre(gold);
|
||||
|
||||
// 3. Custom Diamond
|
||||
CustomOre diamond = new CustomOre("custom_diamond", "<aqua>Diamant Cristallin</aqua>", XMaterial.DIAMOND_ORE, 5.0,
|
||||
OreRarity.RARE, 5, true, 45, XMaterial.BEDROCK, new ArrayList<>());
|
||||
diamond.addDrop(new OreDrop(0, "custom_diamond", XMaterial.DIAMOND, 1, 1.0, true, 1.2, false, null));
|
||||
saveOre(diamond);
|
||||
|
||||
// 4. Custom Ancient Debris / Netherite
|
||||
CustomOre netherite = new CustomOre("custom_netherite", "<dark_purple>Débris Ancien Sacré</dark_purple>", XMaterial.ANCIENT_DEBRIS, 8.0,
|
||||
OreRarity.LEGENDARY, 8, true, 120, XMaterial.BEDROCK, new ArrayList<>());
|
||||
netherite.addDrop(new OreDrop(0, "custom_netherite", XMaterial.NETHERITE_SCRAP, 1, 0.9, true, 1.3, false, null));
|
||||
saveOre(netherite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void saveOre(CustomOre ore) throws SQLException {
|
||||
String sql = "INSERT OR REPLACE INTO ores (id, display_name, block_material, hardness, rarity, stock, respawnable, respawn_time, temp_block) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
|
||||
ps.setString(1, ore.getId());
|
||||
ps.setString(2, ore.getDisplayName());
|
||||
ps.setString(3, ore.getBlockMaterial().name());
|
||||
ps.setDouble(4, ore.getHardness());
|
||||
ps.setString(5, ore.getRarity().name());
|
||||
ps.setInt(6, ore.getStock());
|
||||
ps.setInt(7, ore.isRespawnable() ? 1 : 0);
|
||||
ps.setInt(8, ore.getRespawnTimeSeconds());
|
||||
ps.setString(9, ore.getTempBlockMaterial().name());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
|
||||
// Drops
|
||||
for (OreDrop drop : ore.getDrops()) {
|
||||
saveOreDrop(drop);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void saveOreDrop(OreDrop drop) throws SQLException {
|
||||
String sql = "INSERT INTO ore_drops (ore_id, item_material, default_amount, default_chance, fortunable, fortune_multiplier, smeltable, smelted_material) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
|
||||
ps.setString(1, drop.getOreId());
|
||||
ps.setString(2, drop.getItemMaterial().name());
|
||||
ps.setInt(3, drop.getDefaultAmount());
|
||||
ps.setDouble(4, drop.getDefaultChance());
|
||||
ps.setInt(5, drop.isFortunable() ? 1 : 0);
|
||||
ps.setDouble(6, drop.getFortuneMultiplier());
|
||||
ps.setInt(7, drop.isSmeltable() ? 1 : 0);
|
||||
ps.setString(8, drop.getSmeltedMaterial() != null ? drop.getSmeltedMaterial().name() : null);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Map<String, CustomOre> loadAllOres() {
|
||||
Map<String, CustomOre> ores = new HashMap<>();
|
||||
String sql = "SELECT * FROM ores";
|
||||
|
||||
try (Statement stmt = getConnection().createStatement();
|
||||
ResultSet rs = stmt.executeQuery(sql)) {
|
||||
|
||||
while (rs.next()) {
|
||||
String id = rs.getString("id");
|
||||
String displayName = rs.getString("display_name");
|
||||
XMaterial blockMat = XMaterial.matchXMaterial(rs.getString("block_material")).orElse(XMaterial.STONE);
|
||||
double hardness = rs.getDouble("hardness");
|
||||
OreRarity rarity = OreRarity.fromString(rs.getString("rarity"));
|
||||
int stock = rs.getInt("stock");
|
||||
boolean respawnable = rs.getInt("respawnable") == 1;
|
||||
int respawnTime = rs.getInt("respawn_time");
|
||||
XMaterial tempBlock = XMaterial.matchXMaterial(rs.getString("temp_block")).orElse(XMaterial.BEDROCK);
|
||||
|
||||
CustomOre ore = new CustomOre(id, displayName, blockMat, hardness, rarity, stock, respawnable, respawnTime, tempBlock, new ArrayList<>());
|
||||
ores.put(id, ore);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Erreur lors du chargement des minerais !", e);
|
||||
}
|
||||
|
||||
// Load drops for all ores
|
||||
String dropsSql = "SELECT * FROM ore_drops";
|
||||
try (Statement stmt = getConnection().createStatement();
|
||||
ResultSet rs = stmt.executeQuery(dropsSql)) {
|
||||
|
||||
while (rs.next()) {
|
||||
int dropId = rs.getInt("id");
|
||||
String oreId = rs.getString("ore_id");
|
||||
XMaterial itemMat = XMaterial.matchXMaterial(rs.getString("item_material")).orElse(XMaterial.COBBLESTONE);
|
||||
int amount = rs.getInt("default_amount");
|
||||
double chance = rs.getDouble("default_chance");
|
||||
boolean fortunable = rs.getInt("fortunable") == 1;
|
||||
double fortuneMult = rs.getDouble("fortune_multiplier");
|
||||
boolean smeltable = rs.getInt("smeltable") == 1;
|
||||
String smeltedMatStr = rs.getString("smelted_material");
|
||||
XMaterial smeltedMat = smeltedMatStr != null ? XMaterial.matchXMaterial(smeltedMatStr).orElse(null) : null;
|
||||
|
||||
CustomOre ore = ores.get(oreId);
|
||||
if (ore != null) {
|
||||
OreDrop drop = new OreDrop(dropId, oreId, itemMat, amount, chance, fortunable, fortuneMult, smeltable, smeltedMat);
|
||||
ore.addDrop(drop);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Erreur lors du chargement des drops de minerais !", e);
|
||||
}
|
||||
|
||||
return ores;
|
||||
}
|
||||
|
||||
public synchronized Map<BlockLocation, PlacedOreBlock> loadPlacedBlocks(Map<String, CustomOre> ores) {
|
||||
Map<BlockLocation, PlacedOreBlock> map = new HashMap<>();
|
||||
String sql = "SELECT * FROM placed_ores";
|
||||
|
||||
try (Statement stmt = getConnection().createStatement();
|
||||
ResultSet rs = stmt.executeQuery(sql)) {
|
||||
|
||||
while (rs.next()) {
|
||||
int id = rs.getInt("id");
|
||||
String oreId = rs.getString("ore_id");
|
||||
String world = rs.getString("world");
|
||||
int x = rs.getInt("x");
|
||||
int y = rs.getInt("y");
|
||||
int z = rs.getInt("z");
|
||||
int stock = rs.getInt("current_stock");
|
||||
boolean depleted = rs.getInt("is_depleted") == 1;
|
||||
long nextRespawnAt = rs.getLong("next_respawn_at");
|
||||
|
||||
CustomOre ore = ores.get(oreId);
|
||||
if (ore != null) {
|
||||
BlockLocation loc = new BlockLocation(world, x, y, z);
|
||||
PlacedOreBlock placed = new PlacedOreBlock(id, oreId, ore, loc, stock, depleted, nextRespawnAt);
|
||||
map.put(loc, placed);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Erreur lors du chargement des blocs places !", e);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public synchronized int insertPlacedBlock(PlacedOreBlock block) {
|
||||
String sql = "INSERT INTO placed_ores (ore_id, world, x, y, z, current_stock, is_depleted, next_respawn_at) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
try (PreparedStatement ps = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
|
||||
ps.setString(1, block.getOreId());
|
||||
ps.setString(2, block.getLocation().getWorldName());
|
||||
ps.setInt(3, block.getLocation().getX());
|
||||
ps.setInt(4, block.getLocation().getY());
|
||||
ps.setInt(5, block.getLocation().getZ());
|
||||
ps.setInt(6, block.getCurrentStock());
|
||||
ps.setInt(7, block.isDepleted() ? 1 : 0);
|
||||
ps.setLong(8, block.getNextRespawnTimestamp());
|
||||
ps.executeUpdate();
|
||||
|
||||
try (ResultSet rs = ps.getGeneratedKeys()) {
|
||||
if (rs.next()) {
|
||||
int generatedId = rs.getInt(1);
|
||||
block.setId(generatedId);
|
||||
return generatedId;
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Erreur lors de l'enregistrement d'un minerai pose !", e);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void updatePlacedBlockAsync(PlacedOreBlock block) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
String sql = "UPDATE placed_ores SET current_stock = ?, is_depleted = ?, next_respawn_at = ? WHERE id = ?";
|
||||
try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
|
||||
ps.setInt(1, block.getCurrentStock());
|
||||
ps.setInt(2, block.isDepleted() ? 1 : 0);
|
||||
ps.setLong(3, block.getNextRespawnTimestamp());
|
||||
ps.setInt(4, block.getId());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Erreur lors de la mise a jour de l'etat du minerai pose !", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void deletePlacedBlockAsync(BlockLocation loc) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
String sql = "DELETE FROM placed_ores WHERE world = ? AND x = ? AND y = ? AND z = ?";
|
||||
try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
|
||||
ps.setString(1, loc.getWorldName());
|
||||
ps.setInt(2, loc.getX());
|
||||
ps.setInt(3, loc.getY());
|
||||
ps.setInt(4, loc.getZ());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Erreur lors de la suppression d'un minerai pose !", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public synchronized void close() {
|
||||
try {
|
||||
if (connection != null && !connection.isClosed()) {
|
||||
connection.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.WARNING, "Erreur lors de la fermeture de la connexion SQLite", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package fr.luc.gamingcore.mining.listener;
|
||||
|
||||
import fr.luc.gamingcore.GamingCore;
|
||||
import fr.luc.gamingcore.mining.manager.OreManager;
|
||||
import fr.luc.gamingcore.mining.model.PlacedOreBlock;
|
||||
import fr.luc.gamingcore.util.TextUtil;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
|
||||
public class BlockBreakListener implements Listener {
|
||||
|
||||
private final GamingCore plugin;
|
||||
private final OreManager oreManager;
|
||||
|
||||
public BlockBreakListener(GamingCore plugin, OreManager oreManager) {
|
||||
this.plugin = plugin;
|
||||
this.oreManager = oreManager;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onBlockBreak(BlockBreakEvent event) {
|
||||
Block block = event.getBlock();
|
||||
PlacedOreBlock placed = oreManager.getPlacedBlock(block.getLocation());
|
||||
if (placed == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
|
||||
// If admin in creative mode sneaking breaks the block, remove it permanently
|
||||
if (player.getGameMode() == GameMode.CREATIVE && player.isSneaking() && player.hasPermission("gamingcore.admin")) {
|
||||
oreManager.removeOreBlock(block.getLocation());
|
||||
TextUtil.sendMessage(player, "<green>Minerai personnalisé retiré avec succès !</green>");
|
||||
event.setCancelled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Delegate to OreManager for custom mining mechanics
|
||||
oreManager.handleMine(player, event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package fr.luc.gamingcore.mining.listener;
|
||||
|
||||
import fr.luc.gamingcore.mining.manager.OreManager;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockExplodeEvent;
|
||||
import org.bukkit.event.block.BlockPistonExtendEvent;
|
||||
import org.bukkit.event.block.BlockPistonRetractEvent;
|
||||
import org.bukkit.event.entity.EntityExplodeEvent;
|
||||
|
||||
public class BlockProtectionListener implements Listener {
|
||||
|
||||
private final OreManager oreManager;
|
||||
|
||||
public BlockProtectionListener(OreManager oreManager) {
|
||||
this.oreManager = oreManager;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||
public void onEntityExplode(EntityExplodeEvent event) {
|
||||
event.blockList().removeIf(block -> oreManager.isPlacedOre(block.getLocation()));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||
public void onBlockExplode(BlockExplodeEvent event) {
|
||||
event.blockList().removeIf(block -> oreManager.isPlacedOre(block.getLocation()));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||
public void onPistonExtend(BlockPistonExtendEvent event) {
|
||||
for (Block block : event.getBlocks()) {
|
||||
if (oreManager.isPlacedOre(block.getLocation())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||
public void onPistonRetract(BlockPistonRetractEvent event) {
|
||||
for (Block block : event.getBlocks()) {
|
||||
if (oreManager.isPlacedOre(block.getLocation())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package fr.luc.gamingcore.mining.manager;
|
||||
|
||||
import com.cryptomorin.xseries.XMaterial;
|
||||
import com.cryptomorin.xseries.XSound;
|
||||
import fr.luc.gamingcore.GamingCore;
|
||||
import fr.luc.gamingcore.mining.database.DatabaseManager;
|
||||
import fr.luc.gamingcore.mining.model.*;
|
||||
import fr.luc.gamingcore.util.TextUtil;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class OreManager {
|
||||
|
||||
private final GamingCore plugin;
|
||||
private final DatabaseManager databaseManager;
|
||||
private final RespawnScheduler respawnScheduler;
|
||||
|
||||
private final Map<String, CustomOre> registeredOres = new ConcurrentHashMap<>();
|
||||
private final Map<BlockLocation, PlacedOreBlock> placedBlocks = new ConcurrentHashMap<>();
|
||||
|
||||
public OreManager(GamingCore plugin, DatabaseManager databaseManager) {
|
||||
this.plugin = plugin;
|
||||
this.databaseManager = databaseManager;
|
||||
this.respawnScheduler = new RespawnScheduler(plugin, this);
|
||||
}
|
||||
|
||||
public void loadData() {
|
||||
registeredOres.clear();
|
||||
placedBlocks.clear();
|
||||
|
||||
// 1. Load Custom Ores from DB
|
||||
Map<String, CustomOre> ores = databaseManager.loadAllOres();
|
||||
registeredOres.putAll(ores);
|
||||
plugin.getLogger().info("Charges " + registeredOres.size() + " minerais personnalises.");
|
||||
|
||||
// 2. Load Placed Blocks from DB
|
||||
Map<BlockLocation, PlacedOreBlock> placed = databaseManager.loadPlacedBlocks(registeredOres);
|
||||
placedBlocks.putAll(placed);
|
||||
plugin.getLogger().info("Charges " + placedBlocks.size() + " blocs de minerais actifs.");
|
||||
|
||||
// 3. Start Respawn Scheduler
|
||||
respawnScheduler.start();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
respawnScheduler.stop();
|
||||
databaseManager.close();
|
||||
}
|
||||
|
||||
public DatabaseManager getDatabaseManager() {
|
||||
return databaseManager;
|
||||
}
|
||||
|
||||
public RespawnScheduler getRespawnScheduler() {
|
||||
return respawnScheduler;
|
||||
}
|
||||
|
||||
public CustomOre getOre(String id) {
|
||||
if (id == null) return null;
|
||||
return registeredOres.get(id.toLowerCase());
|
||||
}
|
||||
|
||||
public Collection<CustomOre> getAllOres() {
|
||||
return Collections.unmodifiableCollection(registeredOres.values());
|
||||
}
|
||||
|
||||
public void registerOre(CustomOre ore) {
|
||||
if (ore != null) {
|
||||
registeredOres.put(ore.getId().toLowerCase(), ore);
|
||||
try {
|
||||
databaseManager.saveOre(ore);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("Erreur lors de la sauvegarde du minerai : " + ore.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PlacedOreBlock getPlacedBlock(Location loc) {
|
||||
if (loc == null || loc.getWorld() == null) return null;
|
||||
return placedBlocks.get(BlockLocation.fromLocation(loc));
|
||||
}
|
||||
|
||||
public PlacedOreBlock getPlacedBlock(BlockLocation loc) {
|
||||
if (loc == null) return null;
|
||||
return placedBlocks.get(loc);
|
||||
}
|
||||
|
||||
public Collection<PlacedOreBlock> getAllPlacedBlocks() {
|
||||
return Collections.unmodifiableCollection(placedBlocks.values());
|
||||
}
|
||||
|
||||
public boolean isPlacedOre(Location loc) {
|
||||
return getPlacedBlock(loc) != null;
|
||||
}
|
||||
|
||||
public PlacedOreBlock placeOreBlock(String oreId, Location loc) {
|
||||
CustomOre ore = getOre(oreId);
|
||||
if (ore == null || loc == null || loc.getWorld() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BlockLocation blockLoc = BlockLocation.fromLocation(loc);
|
||||
PlacedOreBlock placed = new PlacedOreBlock(-1, ore.getId(), ore, blockLoc, ore.getStock(), false, 0);
|
||||
|
||||
// Update block in world
|
||||
Block block = loc.getBlock();
|
||||
block.setType(ore.parseBlockMaterial());
|
||||
|
||||
// Save in DB and cache
|
||||
int generatedId = databaseManager.insertPlacedBlock(placed);
|
||||
placed.setId(generatedId);
|
||||
placedBlocks.put(blockLoc, placed);
|
||||
|
||||
return placed;
|
||||
}
|
||||
|
||||
public boolean removeOreBlock(Location loc) {
|
||||
if (loc == null || loc.getWorld() == null) return false;
|
||||
BlockLocation blockLoc = BlockLocation.fromLocation(loc);
|
||||
PlacedOreBlock removed = placedBlocks.remove(blockLoc);
|
||||
if (removed != null) {
|
||||
databaseManager.deletePlacedBlockAsync(blockLoc);
|
||||
loc.getBlock().setType(Material.AIR);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles custom mining interaction when a player breaks a placed ore block.
|
||||
*/
|
||||
public void handleMine(Player player, BlockBreakEvent event) {
|
||||
Block block = event.getBlock();
|
||||
PlacedOreBlock placed = getPlacedBlock(block.getLocation());
|
||||
if (placed == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel the standard vanilla break event
|
||||
event.setCancelled(true);
|
||||
|
||||
// If block is already depleted and waiting to respawn, do nothing or notify player
|
||||
if (placed.isDepleted()) {
|
||||
long remainingSeconds = Math.max(1, (placed.getNextRespawnTimestamp() - System.currentTimeMillis()) / 1000);
|
||||
TextUtil.sendActionBar(player, "<red>Ce minerai est épuisé ! Réapparition dans <yellow>" + remainingSeconds + "s</yellow>.</red>");
|
||||
XSound.BLOCK_STONE_HIT.play(player, 0.7f, 0.5f);
|
||||
return;
|
||||
}
|
||||
|
||||
CustomOre ore = placed.getOreType();
|
||||
if (ore == null) return;
|
||||
|
||||
// Tool & Enchantment checks
|
||||
ItemStack handItem = player.getInventory().getItemInMainHand();
|
||||
int fortuneLevel = 0;
|
||||
if (handItem.hasItemMeta() && handItem.getItemMeta().hasEnchant(Enchantment.LOOT_BONUS_BLOCKS)) {
|
||||
fortuneLevel = handItem.getEnchantmentLevel(Enchantment.LOOT_BONUS_BLOCKS);
|
||||
}
|
||||
|
||||
// Calculate and drop items
|
||||
Location dropLoc = block.getLocation().clone().add(0.5, 0.5, 0.5);
|
||||
for (OreDrop drop : ore.getDrops()) {
|
||||
ItemStack dropItem = drop.calculateDrop(fortuneLevel, false);
|
||||
if (dropItem != null && dropItem.getType() != Material.AIR) {
|
||||
block.getWorld().dropItemNaturally(dropLoc, dropItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Visual & audio feedback for hit
|
||||
try {
|
||||
block.getWorld().spawnParticle(Particle.CRIT, dropLoc, 8, 0.2, 0.2, 0.2, 0.05);
|
||||
} catch (Throwable ignored) {}
|
||||
XSound.BLOCK_STONE_BREAK.play(player.getLocation(), 0.8f, 1.2f);
|
||||
XSound.ENTITY_EXPERIENCE_ORB_PICKUP.play(player.getLocation(), 0.5f, 1.5f);
|
||||
|
||||
// Decrement stock
|
||||
boolean justDepleted = placed.decrementStock();
|
||||
|
||||
if (justDepleted) {
|
||||
// Transform block to tempBlock (e.g. Bedrock)
|
||||
block.setType(ore.parseTempBlockMaterial());
|
||||
|
||||
// Visual effects for depletion
|
||||
try {
|
||||
block.getWorld().spawnParticle(Particle.SMOKE_LARGE, dropLoc, 10, 0.2, 0.2, 0.2, 0.02);
|
||||
} catch (Throwable ignored) {}
|
||||
XSound.BLOCK_ANVIL_LAND.play(player.getLocation(), 0.6f, 0.8f);
|
||||
|
||||
TextUtil.sendActionBar(player, "<red>⛏ " + ore.getDisplayName() + " <dark_gray>| <red>Épuisé ! (<yellow>" + ore.getRespawnTimeSeconds() + "s</yellow>)</red>");
|
||||
} else {
|
||||
// Stock remaining feedback
|
||||
int current = placed.getCurrentStock();
|
||||
int total = ore.getStock();
|
||||
String stockColor = current > (total / 2) ? "<green>" : "<gold>";
|
||||
TextUtil.sendActionBar(player, "<gray>⛏ " + ore.getDisplayName() + " <dark_gray>| <gray>Stock: " + stockColor + current + "</color><dark_gray>/</dark_gray><white>" + total + "</white>");
|
||||
}
|
||||
|
||||
// Update database state asynchronously
|
||||
databaseManager.updatePlacedBlockAsync(placed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package fr.luc.gamingcore.mining.manager;
|
||||
|
||||
import com.cryptomorin.xseries.XSound;
|
||||
import fr.luc.gamingcore.GamingCore;
|
||||
import fr.luc.gamingcore.mining.model.BlockLocation;
|
||||
import fr.luc.gamingcore.mining.model.PlacedOreBlock;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
public class RespawnScheduler {
|
||||
|
||||
private final GamingCore plugin;
|
||||
private final OreManager oreManager;
|
||||
private BukkitTask task;
|
||||
|
||||
public RespawnScheduler(GamingCore plugin, OreManager oreManager) {
|
||||
this.plugin = plugin;
|
||||
this.oreManager = oreManager;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
stop();
|
||||
// Check every second (20 ticks)
|
||||
this.task = new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
checkRespawnQueue();
|
||||
}
|
||||
}.runTaskTimer(plugin, 20L, 20L);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (task != null && !task.isCancelled()) {
|
||||
task.cancel();
|
||||
task = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void checkRespawnQueue() {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
for (PlacedOreBlock placed : oreManager.getAllPlacedBlocks()) {
|
||||
if (!placed.isDepleted() || placed.getNextRespawnTimestamp() <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (now >= placed.getNextRespawnTimestamp()) {
|
||||
respawnBlock(placed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void respawnBlock(PlacedOreBlock placed) {
|
||||
BlockLocation blockLoc = placed.getLocation();
|
||||
Location loc = blockLoc.toLocation();
|
||||
if (loc == null || loc.getWorld() == null || !loc.getWorld().isChunkLoaded(loc.getBlockX() >> 4, loc.getBlockZ() >> 4)) {
|
||||
// World not loaded or chunk not loaded: reset state anyway so it appears correct when chunk loads
|
||||
placed.respawn();
|
||||
oreManager.getDatabaseManager().updatePlacedBlockAsync(placed);
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = loc.getBlock();
|
||||
Material oreMat = placed.getOreType().parseBlockMaterial();
|
||||
block.setType(oreMat);
|
||||
|
||||
// Respawn state in memory & database
|
||||
placed.respawn();
|
||||
oreManager.getDatabaseManager().updatePlacedBlockAsync(placed);
|
||||
|
||||
// Visual & audio effects
|
||||
try {
|
||||
loc.getWorld().spawnParticle(Particle.VILLAGER_HAPPY, loc.clone().add(0.5, 0.5, 0.5), 15, 0.3, 0.3, 0.3, 0.1);
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
XSound.BLOCK_STONE_PLACE.play(loc, 1.0f, 1.2f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package fr.luc.gamingcore.mining.model;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class BlockLocation {
|
||||
|
||||
private final String worldName;
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final int z;
|
||||
|
||||
public BlockLocation(String worldName, int x, int y, int z) {
|
||||
this.worldName = worldName != null ? worldName : "world";
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public static BlockLocation fromLocation(Location loc) {
|
||||
if (loc == null || loc.getWorld() == null) {
|
||||
throw new IllegalArgumentException("Location and World cannot be null");
|
||||
}
|
||||
return new BlockLocation(loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
||||
}
|
||||
|
||||
public static BlockLocation fromBlock(Block block) {
|
||||
if (block == null) {
|
||||
throw new IllegalArgumentException("Block cannot be null");
|
||||
}
|
||||
return fromLocation(block.getLocation());
|
||||
}
|
||||
|
||||
public Location toLocation() {
|
||||
World world = Bukkit.getWorld(worldName);
|
||||
if (world == null) return null;
|
||||
return new Location(world, x, y, z);
|
||||
}
|
||||
|
||||
public Block getBlock() {
|
||||
Location loc = toLocation();
|
||||
return loc != null ? loc.getBlock() : null;
|
||||
}
|
||||
|
||||
public String getWorldName() {
|
||||
return worldName;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public int getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
BlockLocation that = (BlockLocation) o;
|
||||
return x == that.x && y == that.y && z == that.z && Objects.equals(worldName, that.worldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(worldName, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return worldName + ":" + x + "," + y + "," + z;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package fr.luc.gamingcore.mining.model;
|
||||
|
||||
import com.cryptomorin.xseries.XMaterial;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class CustomOre {
|
||||
|
||||
private final String id;
|
||||
private final String displayName;
|
||||
private final XMaterial blockMaterial;
|
||||
private final double hardness;
|
||||
private final OreRarity rarity;
|
||||
private final int stock;
|
||||
private final boolean respawnable;
|
||||
private final int respawnTimeSeconds;
|
||||
private final XMaterial tempBlockMaterial;
|
||||
private final List<OreDrop> drops;
|
||||
|
||||
public CustomOre(String id, String displayName, XMaterial blockMaterial, double hardness,
|
||||
OreRarity rarity, int stock, boolean respawnable, int respawnTimeSeconds,
|
||||
XMaterial tempBlockMaterial, List<OreDrop> drops) {
|
||||
this.id = id;
|
||||
this.displayName = displayName != null ? displayName : id;
|
||||
this.blockMaterial = blockMaterial != null ? blockMaterial : XMaterial.STONE;
|
||||
this.hardness = Math.max(0.1, hardness);
|
||||
this.rarity = rarity != null ? rarity : OreRarity.COMMON;
|
||||
this.stock = Math.max(1, stock);
|
||||
this.respawnable = respawnable;
|
||||
this.respawnTimeSeconds = Math.max(1, respawnTimeSeconds);
|
||||
this.tempBlockMaterial = tempBlockMaterial != null ? tempBlockMaterial : XMaterial.BEDROCK;
|
||||
this.drops = drops != null ? new ArrayList<>(drops) : new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public XMaterial getBlockMaterial() {
|
||||
return blockMaterial;
|
||||
}
|
||||
|
||||
public Material parseBlockMaterial() {
|
||||
Material mat = blockMaterial.parseMaterial();
|
||||
return mat != null ? mat : Material.STONE;
|
||||
}
|
||||
|
||||
public double getHardness() {
|
||||
return hardness;
|
||||
}
|
||||
|
||||
public OreRarity getRarity() {
|
||||
return rarity;
|
||||
}
|
||||
|
||||
public int getStock() {
|
||||
return stock;
|
||||
}
|
||||
|
||||
public boolean isRespawnable() {
|
||||
return respawnable;
|
||||
}
|
||||
|
||||
public int getRespawnTimeSeconds() {
|
||||
return respawnTimeSeconds;
|
||||
}
|
||||
|
||||
public XMaterial getTempBlockMaterial() {
|
||||
return tempBlockMaterial;
|
||||
}
|
||||
|
||||
public Material parseTempBlockMaterial() {
|
||||
Material mat = tempBlockMaterial.parseMaterial();
|
||||
return mat != null ? mat : Material.BEDROCK;
|
||||
}
|
||||
|
||||
public List<OreDrop> getDrops() {
|
||||
return Collections.unmodifiableList(drops);
|
||||
}
|
||||
|
||||
public void addDrop(OreDrop drop) {
|
||||
if (drop != null) {
|
||||
this.drops.add(drop);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearDrops() {
|
||||
this.drops.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package fr.luc.gamingcore.mining.model;
|
||||
|
||||
import com.cryptomorin.xseries.XMaterial;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class OreDrop {
|
||||
|
||||
private final int id;
|
||||
private final String oreId;
|
||||
private final XMaterial itemMaterial;
|
||||
private final int defaultAmount;
|
||||
private final double defaultChance;
|
||||
private final boolean fortunable;
|
||||
private final double fortuneMultiplier;
|
||||
private final boolean smeltable;
|
||||
private final XMaterial smeltedMaterial;
|
||||
|
||||
public OreDrop(int id, String oreId, XMaterial itemMaterial, int defaultAmount, double defaultChance,
|
||||
boolean fortunable, double fortuneMultiplier, boolean smeltable, XMaterial smeltedMaterial) {
|
||||
this.id = id;
|
||||
this.oreId = oreId;
|
||||
this.itemMaterial = itemMaterial != null ? itemMaterial : XMaterial.COBBLESTONE;
|
||||
this.defaultAmount = Math.max(1, defaultAmount);
|
||||
this.defaultChance = Math.min(1.0, Math.max(0.0, defaultChance));
|
||||
this.fortunable = fortunable;
|
||||
this.fortuneMultiplier = fortuneMultiplier;
|
||||
this.smeltable = smeltable;
|
||||
this.smeltedMaterial = smeltedMaterial;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getOreId() {
|
||||
return oreId;
|
||||
}
|
||||
|
||||
public XMaterial getItemMaterial() {
|
||||
return itemMaterial;
|
||||
}
|
||||
|
||||
public int getDefaultAmount() {
|
||||
return defaultAmount;
|
||||
}
|
||||
|
||||
public double getDefaultChance() {
|
||||
return defaultChance;
|
||||
}
|
||||
|
||||
public boolean isFortunable() {
|
||||
return fortunable;
|
||||
}
|
||||
|
||||
public double getFortuneMultiplier() {
|
||||
return fortuneMultiplier;
|
||||
}
|
||||
|
||||
public boolean isSmeltable() {
|
||||
return smeltable;
|
||||
}
|
||||
|
||||
public XMaterial getSmeltedMaterial() {
|
||||
return smeltedMaterial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the drop based on chance, fortune level, and auto-smelt status.
|
||||
*
|
||||
* @param fortuneLevel Level of Fortune enchantment on tool (0 if none)
|
||||
* @param autoSmelt Whether auto-smelt is active
|
||||
* @return ItemStack if drop was successful, or null if chance roll failed
|
||||
*/
|
||||
public ItemStack calculateDrop(int fortuneLevel, boolean autoSmelt) {
|
||||
double roll = ThreadLocalRandom.current().nextDouble();
|
||||
if (roll > defaultChance) {
|
||||
return null; // Failed drop chance roll
|
||||
}
|
||||
|
||||
int amount = defaultAmount;
|
||||
if (fortunable && fortuneLevel > 0) {
|
||||
// Vanilla-like fortune bonus calculation with customizable multiplier
|
||||
int bonus = ThreadLocalRandom.current().nextInt(fortuneLevel + 2) - 1;
|
||||
if (bonus > 0) {
|
||||
amount += (int) Math.round(bonus * Math.max(1.0, fortuneMultiplier));
|
||||
}
|
||||
}
|
||||
|
||||
XMaterial targetMaterial = itemMaterial;
|
||||
if (smeltable && autoSmelt && smeltedMaterial != null && smeltedMaterial.isSupported()) {
|
||||
targetMaterial = smeltedMaterial;
|
||||
}
|
||||
|
||||
ItemStack item = targetMaterial.parseItem();
|
||||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
item.setAmount(Math.max(1, amount));
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package fr.luc.gamingcore.mining.model;
|
||||
|
||||
public enum OreRarity {
|
||||
COMMON("Commun", "<gray>Commun</gray>", "<gray>[Commun]</gray>"),
|
||||
UNCOMMON("Peu Commun", "<green>Peu Commun</green>", "<green>[Peu Commun]</green>"),
|
||||
RARE("Rare", "<blue>Rare</blue>", "<blue>[Rare]</blue>"),
|
||||
EPIC("Épique", "<light_purple>Épique</light_purple>", "<light_purple>[Épique]</light_purple>"),
|
||||
LEGENDARY("Légendaire", "<gold>Légendaire</gold>", "<gold>[Légendaire]</gold>"),
|
||||
MYTHIC("Mythique", "<gradient:#FF0055:#7A00FF>Mythique</gradient>", "<gradient:#FF0055:#7A00FF>[Mythique]</gradient>");
|
||||
|
||||
private final String displayName;
|
||||
private final String formattedName;
|
||||
private final String tag;
|
||||
|
||||
OreRarity(String displayName, String formattedName, String tag) {
|
||||
this.displayName = displayName;
|
||||
this.formattedName = formattedName;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getFormattedName() {
|
||||
return formattedName;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static OreRarity fromString(String name) {
|
||||
if (name == null) return COMMON;
|
||||
try {
|
||||
return OreRarity.valueOf(name.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return COMMON;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package fr.luc.gamingcore.mining.model;
|
||||
|
||||
public class PlacedOreBlock {
|
||||
|
||||
private int id;
|
||||
private final String oreId;
|
||||
private CustomOre oreType;
|
||||
private final BlockLocation location;
|
||||
private int currentStock;
|
||||
private boolean depleted;
|
||||
private long nextRespawnTimestamp;
|
||||
|
||||
public PlacedOreBlock(int id, String oreId, CustomOre oreType, BlockLocation location,
|
||||
int currentStock, boolean depleted, long nextRespawnTimestamp) {
|
||||
this.id = id;
|
||||
this.oreId = oreId;
|
||||
this.oreType = oreType;
|
||||
this.location = location;
|
||||
this.currentStock = currentStock;
|
||||
this.depleted = depleted;
|
||||
this.nextRespawnTimestamp = nextRespawnTimestamp;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getOreId() {
|
||||
return oreId;
|
||||
}
|
||||
|
||||
public CustomOre getOreType() {
|
||||
return oreType;
|
||||
}
|
||||
|
||||
public void setOreType(CustomOre oreType) {
|
||||
this.oreType = oreType;
|
||||
}
|
||||
|
||||
public BlockLocation getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public int getCurrentStock() {
|
||||
return currentStock;
|
||||
}
|
||||
|
||||
public void setCurrentStock(int currentStock) {
|
||||
this.currentStock = currentStock;
|
||||
}
|
||||
|
||||
public boolean isDepleted() {
|
||||
return depleted;
|
||||
}
|
||||
|
||||
public void setDepleted(boolean depleted) {
|
||||
this.depleted = depleted;
|
||||
}
|
||||
|
||||
public long getNextRespawnTimestamp() {
|
||||
return nextRespawnTimestamp;
|
||||
}
|
||||
|
||||
public void setNextRespawnTimestamp(long nextRespawnTimestamp) {
|
||||
this.nextRespawnTimestamp = nextRespawnTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the stock by 1 and checks if it reached 0.
|
||||
*
|
||||
* @return true if the ore has just been depleted
|
||||
*/
|
||||
public boolean decrementStock() {
|
||||
this.currentStock--;
|
||||
if (this.currentStock <= 0) {
|
||||
this.currentStock = 0;
|
||||
this.depleted = true;
|
||||
if (this.oreType != null && this.oreType.isRespawnable()) {
|
||||
this.nextRespawnTimestamp = System.currentTimeMillis() + (this.oreType.getRespawnTimeSeconds() * 1000L);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the stock and removes depleted flag on respawn.
|
||||
*/
|
||||
public void respawn() {
|
||||
this.depleted = false;
|
||||
this.nextRespawnTimestamp = 0L;
|
||||
if (this.oreType != null) {
|
||||
this.currentStock = this.oreType.getStock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package fr.luc.gamingcore.util;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public final class TextUtil {
|
||||
|
||||
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
|
||||
private static final LegacyComponentSerializer AMPERSAND_SERIALIZER = LegacyComponentSerializer.legacyAmpersand();
|
||||
|
||||
private static String prefix = "<gradient:#00C9FF:#92FE9D>[GamingCore]</gradient> <dark_gray>»</dark_gray> <gray>";
|
||||
|
||||
private TextUtil() {}
|
||||
|
||||
public static void setPrefix(String newPrefix) {
|
||||
if (newPrefix != null) {
|
||||
prefix = newPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string formatted with MiniMessage or legacy & color codes into a Component.
|
||||
*/
|
||||
public static Component parse(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return Component.empty();
|
||||
}
|
||||
String formatted = input.replace('§', '&');
|
||||
if (formatted.contains("&")) {
|
||||
return AMPERSAND_SERIALIZER.deserialize(formatted);
|
||||
}
|
||||
return MINI_MESSAGE.deserialize(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a parsed message with prefix to a CommandSender (or Player).
|
||||
*/
|
||||
public static void sendMessage(CommandSender sender, String message) {
|
||||
if (sender == null || message == null) return;
|
||||
sender.sendMessage(parse(prefix + message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a parsed raw message without prefix to a CommandSender.
|
||||
*/
|
||||
public static void sendRawMessage(CommandSender sender, String message) {
|
||||
if (sender == null || message == null) return;
|
||||
sender.sendMessage(parse(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an action bar message to a player.
|
||||
*/
|
||||
public static void sendActionBar(Player player, String message) {
|
||||
if (player == null || message == null) return;
|
||||
player.sendActionBar(parse(message));
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,20 @@ version: 1.0.0
|
||||
main: fr.luc.gamingcore.GamingCore
|
||||
api-version: '1.16'
|
||||
author: Luc
|
||||
description: GamingCore Plugin
|
||||
description: GamingCore Plugin - Système de Minage Personnalisé
|
||||
|
||||
commands:
|
||||
gamingcore:
|
||||
description: Commande principale de GamingCore
|
||||
aliases: [gc, mining]
|
||||
permission: gamingcore.use
|
||||
usage: /<command> [help|ore|reload]
|
||||
|
||||
permissions:
|
||||
gamingcore.use:
|
||||
description: Permet d'utiliser les commandes de base de GamingCore
|
||||
default: true
|
||||
gamingcore.admin:
|
||||
description: Permet d'administrer les minerais et recharger le plugin
|
||||
default: op
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package fr.luc.gamingcore.mining;
|
||||
|
||||
import com.cryptomorin.xseries.XMaterial;
|
||||
import fr.luc.gamingcore.mining.model.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class OreModelTest {
|
||||
|
||||
@Test
|
||||
public void testOreStockAndDepletion() {
|
||||
CustomOre ore = new CustomOre("test_iron", "Test Iron", XMaterial.IRON_ORE, 3.0,
|
||||
OreRarity.COMMON, 3, true, 10, XMaterial.BEDROCK, new ArrayList<>());
|
||||
|
||||
BlockLocation loc = new BlockLocation("world", 100, 64, -200);
|
||||
PlacedOreBlock placed = new PlacedOreBlock(1, ore.getId(), ore, loc, ore.getStock(), false, 0);
|
||||
|
||||
assertEquals(3, placed.getCurrentStock());
|
||||
assertFalse(placed.isDepleted());
|
||||
|
||||
// First hit
|
||||
boolean depleted1 = placed.decrementStock();
|
||||
assertFalse(depleted1);
|
||||
assertEquals(2, placed.getCurrentStock());
|
||||
|
||||
// Second hit
|
||||
boolean depleted2 = placed.decrementStock();
|
||||
assertFalse(depleted2);
|
||||
assertEquals(1, placed.getCurrentStock());
|
||||
|
||||
// Third hit -> Depleted
|
||||
boolean depleted3 = placed.decrementStock();
|
||||
assertTrue(depleted3);
|
||||
assertEquals(0, placed.getCurrentStock());
|
||||
assertTrue(placed.isDepleted());
|
||||
assertTrue(placed.getNextRespawnTimestamp() > System.currentTimeMillis());
|
||||
|
||||
// Respawn
|
||||
placed.respawn();
|
||||
assertFalse(placed.isDepleted());
|
||||
assertEquals(3, placed.getCurrentStock());
|
||||
assertEquals(0L, placed.getNextRespawnTimestamp());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockLocationEquality() {
|
||||
BlockLocation loc1 = new BlockLocation("world", 10, 20, 30);
|
||||
BlockLocation loc2 = new BlockLocation("world", 10, 20, 30);
|
||||
BlockLocation loc3 = new BlockLocation("nether", 10, 20, 30);
|
||||
|
||||
assertEquals(loc1, loc2);
|
||||
assertEquals(loc1.hashCode(), loc2.hashCode());
|
||||
assertNotEquals(loc1, loc3);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user