feat(mining): implement complete custom mining system with SQLite persistence
This commit is contained in:
+37
-39
@@ -1,54 +1,52 @@
|
|||||||
@startuml mining_model
|
@startuml mining_model
|
||||||
!theme plain
|
skinparam roundcorner 8
|
||||||
skinparam roundcorner 10
|
|
||||||
skinparam shadowing false
|
skinparam shadowing false
|
||||||
skinparam classAttributeIconSize 0
|
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>> {
|
package "Database Schema (SQLite / MySQL)" <<Rectangle>> {
|
||||||
entity "ores" as OresTable {
|
class "ores" as OresTable << (T,#5DADE2) >> {
|
||||||
* id : VARCHAR(64) <<PK>>
|
+ id : VARCHAR(64) [PK]
|
||||||
--
|
--
|
||||||
* block_material : VARCHAR(64)
|
+ block_material : VARCHAR(64)
|
||||||
* hardness : DOUBLE
|
+ hardness : DOUBLE
|
||||||
* rarity : VARCHAR(32)
|
+ rarity : VARCHAR(32)
|
||||||
* stock : INTEGER
|
+ stock : INTEGER
|
||||||
* respawnable : BOOLEAN
|
+ respawnable : BOOLEAN
|
||||||
* respawn_time : INTEGER
|
+ respawn_time : INTEGER
|
||||||
* temp_block : VARCHAR(64)
|
+ temp_block : VARCHAR(64)
|
||||||
created_at : TIMESTAMP
|
+ created_at : TIMESTAMP
|
||||||
}
|
}
|
||||||
|
|
||||||
entity "ore_drops" as OreDropsTable {
|
class "ore_drops" as OreDropsTable << (T,#5DADE2) >> {
|
||||||
* id : INTEGER <<PK, AUTOINCREMENT>>
|
+ id : INTEGER [PK AUTO]
|
||||||
--
|
--
|
||||||
* ore_id : VARCHAR(64) <<FK>>
|
+ ore_id : VARCHAR(64) [FK]
|
||||||
* item_material : VARCHAR(64)
|
+ item_material : VARCHAR(64)
|
||||||
* default_amount : INTEGER
|
+ default_amount : INTEGER
|
||||||
* default_chance : DOUBLE
|
+ default_chance : DOUBLE
|
||||||
* fortunable : BOOLEAN
|
+ fortunable : BOOLEAN
|
||||||
* fortune_multiplier : DOUBLE
|
+ fortune_multiplier : DOUBLE
|
||||||
* smeltable : BOOLEAN
|
+ smeltable : BOOLEAN
|
||||||
smelted_material : VARCHAR(64)
|
+ smelted_material : VARCHAR(64)
|
||||||
}
|
}
|
||||||
|
|
||||||
entity "placed_ores" as PlacedOresTable {
|
class "placed_ores" as PlacedOresTable << (T,#5DADE2) >> {
|
||||||
* id : INTEGER <<PK, AUTOINCREMENT>>
|
+ id : INTEGER [PK AUTO]
|
||||||
--
|
--
|
||||||
* ore_id : VARCHAR(64) <<FK>>
|
+ ore_id : VARCHAR(64) [FK]
|
||||||
* world : VARCHAR(64)
|
+ world : VARCHAR(64)
|
||||||
* x : INTEGER
|
+ x : INTEGER
|
||||||
* y : INTEGER
|
+ y : INTEGER
|
||||||
* z : INTEGER
|
+ z : INTEGER
|
||||||
* current_stock : INTEGER
|
+ current_stock : INTEGER
|
||||||
* is_depleted : BOOLEAN
|
+ is_depleted : BOOLEAN
|
||||||
* next_respawn_at : BIGINT
|
+ next_respawn_at : BIGINT
|
||||||
}
|
}
|
||||||
|
|
||||||
OresTable ||..o{ OreDropsTable : "1:N (Drops configurés)"
|
OresTable "1" *-- "0..*" OreDropsTable : "drops configurés"
|
||||||
OresTable ||..o{ PlacedOresTable : "1:N (Blocs actifs dans le monde)"
|
OresTable "1" *-- "0..*" PlacedOresTable : "blocs actifs"
|
||||||
}
|
}
|
||||||
|
|
||||||
package "fr.luc.gamingcore.mining.model" {
|
package "fr.luc.gamingcore.mining.model" {
|
||||||
@@ -137,8 +135,8 @@ package "fr.luc.gamingcore.mining.listener" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomOre "1" *-- "many" OreDrop
|
CustomOre "1" *-- "0..*" OreDrop
|
||||||
CustomOre "1" -- "many" PlacedOreBlock
|
CustomOre "1" -- "0..*" PlacedOreBlock
|
||||||
CustomOre --> OreRarity
|
CustomOre --> OreRarity
|
||||||
|
|
||||||
OreManager o-- CustomOre
|
OreManager o-- CustomOre
|
||||||
|
|||||||
@@ -1,25 +1,73 @@
|
|||||||
package fr.luc.gamingcore;
|
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;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
public final class GamingCore extends JavaPlugin {
|
public final class GamingCore extends JavaPlugin {
|
||||||
|
|
||||||
private static GamingCore instance;
|
private static GamingCore instance;
|
||||||
|
|
||||||
|
private DatabaseManager databaseManager;
|
||||||
|
private OreManager oreManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
instance = this;
|
instance = this;
|
||||||
saveDefaultConfig();
|
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
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
|
if (oreManager != null) {
|
||||||
|
oreManager.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
getLogger().info("GamingCore a ete desactive !");
|
getLogger().info("GamingCore a ete desactive !");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GamingCore getInstance() {
|
public static GamingCore getInstance() {
|
||||||
return instance;
|
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
|
main: fr.luc.gamingcore.GamingCore
|
||||||
api-version: '1.16'
|
api-version: '1.16'
|
||||||
author: Luc
|
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