Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
cf43558
added * for itemflag hiding in settings item
MaksyKun Aug 17, 2025
b7cef4d
added * for itemflag hiding in settings item
MaksyKun Aug 17, 2025
427e017
Merge branch 'refs/heads/dev' into vanilla-behaviour-changes
MaksyKun Aug 17, 2025
edcfa80
added mechanic to disable bukkit recipes
MaksyKun Aug 17, 2025
26adbf5
added condition line next to requirement line to define what will be …
MaksyKun Sep 5, 2025
8b693f9
added auto-joining
MaksyKun Sep 5, 2025
95c0b8a
fixed some event services for joining/leaving professions
MaksyKun Sep 5, 2025
7c156d4
removed debug
MaksyKun Sep 7, 2025
cca604c
fixed ingredient hash on craft method
MaksyKun Sep 7, 2025
855c6a1
Merge branch 'dev' into vanilla-behaviour-changes
MaksyKun Sep 7, 2025
e23f7ad
Merge branch 'dev' into vanilla-behaviour-changes
Travja Sep 9, 2025
4bb2545
Formatting
Travja Sep 9, 2025
b418ce3
fixed categories not being openable when registered in the gui
MaksyKun Sep 14, 2025
d18c796
remove latest gui on leave
MaksyKun Sep 16, 2025
02e07a6
caching money to reduce lags through db-connections
MaksyKun Sep 16, 2025
b9c341c
added debug for issue handling with queue data loss on rejoining
MaksyKun Sep 17, 2025
9aadc3e
fixed queue data loss with properly configured primary keys
MaksyKun Sep 17, 2025
362ff2a
removed debug
MaksyKun Sep 17, 2025
2f6666a
fixed skull meta on queued items
MaksyKun Sep 18, 2025
a9a4900
added * for itemflag hiding in settings item
MaksyKun Aug 17, 2025
411fec3
fixed recipe gui reacting on player inventory instead of top inv
MaksyKun Sep 24, 2025
b51de93
improved queue updating for performance
MaksyKun Sep 27, 2025
7e23813
sync fixes and inv checks
MaksyKun Oct 7, 2025
6d1344d
Merge branch 'dev' into bug-fixing
MaksyKun Oct 7, 2025
dbdd992
implemented lock mechanism to prevent the plugin to load crafting que…
MaksyKun Oct 8, 2025
86f481a
changing some mistakes
MaksyKun Oct 8, 2025
0f35496
fixed some issues related to limits
MaksyKun Oct 12, 2025
da2b2ae
preparations for future enhancements
MaksyKun Oct 12, 2025
3b7f68b
fixed hashes not updating on changes of itemstack amount
MaksyKun Oct 13, 2025
6ba8401
further sql fixes
MaksyKun Oct 19, 2025
3c0954b
re-implemented a `includeOriginalLore` setting
MaksyKun Nov 20, 2025
fec17e3
Merge branch 'dev' into bug-fixing
MaksyKun Nov 20, 2025
fe00e98
copilot-instruction
MaksyKun Jan 25, 2026
d159cba
Merge branch 'dev' into bug-fixing
MaksyKun Jan 25, 2026
d9d33fe
fixed craft show on empty set
MaksyKun Jan 25, 2026
3ab8872
changed typo mistake in sql swapping
MaksyKun Feb 15, 2026
33a84bd
initializing auto increment per DatabaseType
MaksyKun Feb 15, 2026
f2b4712
adressed npe potential from locking
MaksyKun Feb 15, 2026
4377c02
adressed async cache clearing improvement
MaksyKun Feb 15, 2026
041c16e
fix: Enhance saving lock mechanism and prevent data loss
Travja Feb 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .github/copilot-instructions.md
Empty file.
19 changes: 12 additions & 7 deletions src/main/java/studio/magemonkey/fusion/Fusion.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,18 @@ private void runQueueTask() {

@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
PlayerLoader.loadPlayer(event.getPlayer());
if(!Cfg.autoJoinProfessions.isEmpty()) {
Cfg.autoJoinProfessions(event.getPlayer());
}
if (Cfg.craftingQueue) {
notifyForQueue(event.getPlayer());
}
Player player = event.getPlayer();
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
PlayerLoader.getPlayerBlocking(player, 5000); // Wait up to 5s for any pending saves to finish
Bukkit.getScheduler().runTask(this, () -> {
if(!Cfg.autoJoinProfessions.isEmpty()) {
Cfg.autoJoinProfessions(player);
}
if (Cfg.craftingQueue) {
notifyForQueue(player);
}
});
});
}

@EventHandler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,14 @@ public FusionPlayer getPlayer(UUID uuid) {

/**
* Save the player data of a player.
* This will save the player data to the database and reload the player.
* This will save the player data to the database.
*
* @param player The Player object of the player.
*/
public void savePlayer(Player player) {
FusionPlayer fusionPlayer = getPlayer(player);
if (fusionPlayer != null) {
fusionPlayer.save();
PlayerLoader.unloadPlayer(player);
PlayerLoader.loadPlayer(player);
} else {
FusionAPI.getInstance()
.getLogger()
Expand Down
84 changes: 69 additions & 15 deletions src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class SQLManager {
private static String user;
private static String password;

// Track the currently selected database type so we can produce dialect-specific SQL when needed
private static DatabaseType currentType;

public static void init() {
FileConfiguration cfg = Cfg.getConfig();
DatabaseType type = DatabaseType.valueOf(cfg.getString("storage.type", "LOCAL").toUpperCase());
Expand All @@ -38,6 +41,9 @@ public static void init() {
user = cfg.getString("storage.user", "root");
password = cfg.getString("storage.password", "password");

// store the selected type
currentType = type;

Fusion.getInstance().getLogger().info("Initializing SQLManager with type: " + type);

switch (type) {
Expand All @@ -56,6 +62,7 @@ public static void init() {
Fusion.getInstance().getLogger().severe("Failed to initialize the Connection.");
} else {
fusionPlayersSQL = new FusionPlayersSQL();
fusionPlayersSQL.clearAllLocks();
fusionProfessionsSQL = new FusionProfessionsSQL();
fusionQueuesSQL = new FusionQueuesSQL();
fusionRecipeLimitsSQL = new FusionRecipeLimitsSQL();
Expand Down Expand Up @@ -154,11 +161,10 @@ public static void swapToLocal() {
statement.execute("DROP TABLE IF EXISTS fusion_players");
statement.execute("DROP TABLE IF EXISTS fusion_professions");
statement.execute("DROP TABLE IF EXISTS fusion_queues");
statement.execute("CREATE TABLE IF NOT EXISTS fusion_players(UUID varchar(36), AutoCrafting boolean)");
statement.execute(
"CREATE TABLE IF NOT EXISTS fusion_professions(Id long, UUID varchar(36), Profession varchar(100), Experience numeric, Mastered boolean, Joined boolean)");
statement.execute(
"CREATE TABLE IF NOT EXISTS fusion_queues(Id long, UUID varchar(36), RecipePath varchar(100), Timestamp BIGINT, CraftingTime numeric, SavedSeconds numeric)");
statement.execute("CREATE TABLE IF NOT EXISTS fusion_players(UUID varchar(36) PRIMARY KEY, AutoCrafting boolean DEFAULT false, Locked boolean DEFAULT false)");
// Use SQLite-compatible id column definition
statement.execute("CREATE TABLE IF NOT EXISTS fusion_professions(" + getIdColumn(DatabaseType.LOCAL) + " UUID varchar(36), Profession varchar(100), Experience numeric, Mastered boolean, Joined boolean)");
statement.execute("CREATE TABLE IF NOT EXISTS fusion_queues(" + getIdColumn(DatabaseType.LOCAL) + " UUID varchar(36), RecipePath varchar(100), Timestamp BIGINT, CraftingTime numeric, SavedSeconds numeric)");

} catch (SQLException e) {
Fusion.getInstance().getLogger().severe("Error while dropping tables: " + e.getMessage());
Expand All @@ -173,10 +179,16 @@ public static void swapToLocal() {

try (Connection sqliteConnection = getSQLiteConnection();
PreparedStatement insertStatement = sqliteConnection.prepareStatement(
"INSERT INTO fusion_players (UUID, AutoCrafting) VALUES (?, ?)")) {
"INSERT INTO fusion_players (UUID, AutoCrafting, Locked) VALUES (?, ?, ?)") ) {
while (resultPlayers.next()) {
insertStatement.setString(1, resultPlayers.getString("UUID"));
insertStatement.setBoolean(2, resultPlayers.getBoolean("AutoCrafting"));
// If source DB doesn't have Locked column, getBoolean will return false; that's acceptable
try {
insertStatement.setBoolean(3, resultPlayers.getBoolean("Locked"));
} catch (SQLException ignored) {
insertStatement.setBoolean(3, false);
}
insertStatement.executeUpdate();
}
} catch (SQLException e) {
Expand All @@ -192,7 +204,7 @@ public static void swapToLocal() {

try (Connection sqliteConnection = getSQLiteConnection();
PreparedStatement insertStatement = sqliteConnection.prepareStatement(
"INSERT INTO fusion_professions (Id, UUID, Profession, Experience, Mastered, Joined) VALUES (?, ?, ?, ?, ?, ?)")) {
"INSERT INTO fusion_professions (Id, UUID, Profession, Experience, Mastered, Joined) VALUES (?, ?, ?, ?, ?, ?)") ) {
insertProfession(resultProfessions, insertStatement);
} catch (SQLException e) {
Fusion.getInstance()
Expand All @@ -207,7 +219,7 @@ public static void swapToLocal() {

try (Connection sqliteConnection = getSQLiteConnection();
PreparedStatement insertStatement = sqliteConnection.prepareStatement(
"INSERT INTO fusion_queues (Id, UUID, RecipePath, Timestamp, CraftingTime, SavedSeconds) VALUES (?, ?, ?, ?, ?, ?)")) {
"INSERT INTO fusion_queues (Id, UUID, RecipePath, Timestamp, CraftingTime, SavedSeconds) VALUES (?, ?, ?, ?, ?, ?)") ) {
insertQueue(resultQueues, insertStatement);
} catch (SQLException e) {
Fusion.getInstance()
Expand Down Expand Up @@ -235,19 +247,31 @@ public static void swapToSql() {

// Delete all content of the current database and recreate tables
sqlStatement.execute("DROP TABLE IF EXISTS fusion_players, fusion_professions, fusion_queues");
sqlStatement.execute("CREATE TABLE IF NOT EXISTS fusion_players(UUID varchar(36), AutoCrafting boolean)");
sqlStatement.execute(
"CREATE TABLE IF NOT EXISTS fusion_professions(Id long, UUID varchar(36), Profession varchar(100), Experience numeric, Mastered boolean, Joined boolean)");
sqlStatement.execute(
"CREATE TABLE IF NOT EXISTS fusion_queues(Id long, UUID varchar(36), RecipePath varchar(100), Timestamp BIGINT, CraftingTime numeric, SavedSeconds numeric)");
sqlStatement.execute("CREATE TABLE IF NOT EXISTS fusion_players(UUID varchar(36) PRIMARY KEY, AutoCrafting boolean DEFAULT false, Locked boolean DEFAULT false)");
// Use MySQL-compatible id column definition
sqlStatement.execute("CREATE TABLE IF NOT EXISTS fusion_professions(" + getIdColumn(DatabaseType.MYSQL) + " UUID varchar(36), Profession varchar(100), Experience numeric, Mastered boolean, Joined boolean)");
sqlStatement.execute("CREATE TABLE IF NOT EXISTS fusion_queues(" + getIdColumn(DatabaseType.MYSQL) + " UUID varchar(36), RecipePath varchar(100), Timestamp BIGINT, CraftingTime numeric, SavedSeconds numeric)");

// Get all data from the local database
try (Connection sqliteConnection = getSQLiteConnection();
Statement localStatement = sqliteConnection.createStatement()) {

// Retrieve data from local database
ResultSet resultPlayers = localStatement.executeQuery("SELECT * FROM fusion_players");
insertPlayers(sqlConnection, resultPlayers);
// Ensure we transfer Locked value if present
try (PreparedStatement insert = sqlConnection.prepareStatement(
"INSERT INTO fusion_players (UUID, AutoCrafting, Locked) VALUES (?, ?, ?)") ) {
while (resultPlayers.next()) {
insert.setString(1, resultPlayers.getString("UUID"));
insert.setBoolean(2, resultPlayers.getBoolean("AutoCrafting"));
try {
insert.setBoolean(3, resultPlayers.getBoolean("Locked"));
} catch (SQLException ignored) {
insert.setBoolean(3, false);
}
insert.executeUpdate();
}
}

ResultSet resultProfessions = localStatement.executeQuery("SELECT * FROM fusion_professions");
insertProfessions(sqlConnection, resultProfessions);
Expand Down Expand Up @@ -277,11 +301,16 @@ public static void swapToSql() {
}

private static void insertPlayers(Connection connection, ResultSet resultSet) throws SQLException {
String insertQuery = "INSERT INTO fusion_players (UUID, AutoCrafting) VALUES (?, ?)";
String insertQuery = "INSERT INTO fusion_players (UUID, AutoCrafting, Locked) VALUES (?, ?, ?)";
try (PreparedStatement preparedStatement = connection.prepareStatement(insertQuery)) {
while (resultSet.next()) {
preparedStatement.setString(1, resultSet.getString("UUID"));
preparedStatement.setBoolean(2, resultSet.getBoolean("AutoCrafting"));
try {
preparedStatement.setBoolean(3, resultSet.getBoolean("Locked"));
} catch (SQLException ignored) {
preparedStatement.setBoolean(3, false);
}
preparedStatement.executeUpdate();
}
}
Expand Down Expand Up @@ -327,4 +356,29 @@ private static void insertProfession(ResultSet resultProfessions, PreparedStatem
insertStatement.executeUpdate();
}
}

/**
* Returns a dialect-specific id column definition including the trailing comma.
* For SQLITE (LOCAL) this returns: "Id INTEGER PRIMARY KEY AUTOINCREMENT,"
* For MYSQL/MARIADB this returns: "Id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,"
*/
public static String getIdColumn(DatabaseType type) {
if (type == DatabaseType.LOCAL) {
return "Id INTEGER PRIMARY KEY AUTOINCREMENT,";
}
// default to MySQL/MariaDB style
return "Id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,";
}

/**
* Returns the id column definition for the currently configured database type.
*/
public static String getIdColumn() {
return getIdColumn(currentType == null ? DatabaseType.MYSQL : currentType);
}

public static DatabaseType getDatabaseType() {
return currentType;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import studio.magemonkey.fusion.Fusion;
import studio.magemonkey.fusion.cfg.sql.SQLManager;
import studio.magemonkey.fusion.cfg.sql.DatabaseType;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
Expand All @@ -16,9 +17,18 @@ public class FusionPlayersSQL {
public FusionPlayersSQL() {
try (PreparedStatement create = SQLManager.connection()
.prepareStatement("CREATE TABLE IF NOT EXISTS " + Table + "("
+ "UUID varchar(36), "
+ "AutoCrafting boolean)")) {
+ "UUID varchar(36) PRIMARY KEY, "
+ "AutoCrafting boolean DEFAULT false, "
+ "Locked boolean DEFAULT false)")) {
create.execute();

boolean lockedColumnAdded = alterIfLockedNotExistent();
if (lockedColumnAdded) {
Fusion.getInstance()
.getLogger()
.info("[SQL:FusionPlayersSQL:FusionPlayersSQL] Added 'Locked' column to 'fusion_players' table.");
}

} catch (SQLException e) {
Fusion.getInstance()
.getLogger()
Expand All @@ -42,19 +52,75 @@ public void setAutoCrafting(UUID uuid, boolean autoCrafting) {
}
}

public void setLocked(UUID uuid, boolean locked) {
addPlayer(uuid);
try (PreparedStatement update = SQLManager.connection()
.prepareStatement("UPDATE " + Table + " SET Locked=? WHERE UUID=?")) {
update.setBoolean(1, locked);
update.setString(2, uuid.toString());
update.execute();
} catch (SQLException e) {
Fusion.getInstance()
.getLogger()
.warning("[SQL:FusionPlayersSQL:setLocked] Something went wrong with the sql-connection: "
+ e.getMessage());
}
}

public void clearAllLocks() {
try (PreparedStatement update = SQLManager.connection()
.prepareStatement("UPDATE " + Table + " SET Locked=?")) {
update.setBoolean(1, false);
update.execute();
} catch (SQLException e) {
Fusion.getInstance()
.getLogger()
.warning("[SQL:FusionPlayersSQL:clearAllLocks] Something went wrong with the sql-connection: "
+ e.getMessage());
}
}

public boolean isLocked(UUID uuid) {
try (PreparedStatement select = SQLManager.connection()
.prepareStatement("SELECT Locked FROM " + Table + " WHERE UUID=?")) {
select.setString(1, uuid.toString());
ResultSet result = select.executeQuery();
if (result.next())
return result.getBoolean("Locked");
} catch (SQLException e) {
Fusion.getInstance()
.getLogger()
.warning("[SQL:FusionPlayersSQL:isLocked] Something went wrong with the sql-connection: "
+ e.getMessage());
}
return false;
}

public void addPlayer(UUID uuid) {
if (hasPlayer(uuid))
return;
try (PreparedStatement insert = SQLManager.connection()
.prepareStatement("INSERT INTO " + Table + "(UUID, AutoCrafting) VALUES(?,?)")) {
// Use a dialect-aware insert that ignores duplicates to avoid race conditions across nodes
DatabaseType dbType = SQLManager.getDatabaseType();
String sql;
if (dbType == DatabaseType.LOCAL) {
// SQLite: INSERT OR IGNORE
sql = "INSERT OR IGNORE INTO " + Table + "(UUID, AutoCrafting, Locked) VALUES(?,?,?)";
} else {
// MySQL/MariaDB: use ON DUPLICATE KEY UPDATE as a no-op
sql = "INSERT INTO " + Table + "(UUID, AutoCrafting, Locked) VALUES(?,?,?) ON DUPLICATE KEY UPDATE UUID=UUID";
}

try (PreparedStatement insert = SQLManager.connection().prepareStatement(sql)) {
insert.setString(1, uuid.toString());
insert.setBoolean(2, false);
insert.setBoolean(3, false);
insert.execute();
} catch (SQLException e) {
// If we still hit a duplicate key exception, ignore it safely
if (e.getSQLState() != null && (e.getSQLState().startsWith("23") || e.getMessage().toLowerCase().contains("duplicate"))) {
return;
}
Fusion.getInstance()
.getLogger()
.warning("[SQL:FusionPlayersSQL:addPlayer] Something went wrong with the sql-connection: "
+ e.getMessage());
.warning("[SQL:FusionPlayersSQL:addPlayer] Something went wrong with the sql-connection: " + e.getMessage());
}
}

Expand Down Expand Up @@ -89,4 +155,26 @@ public boolean isAutoCrafting(UUID uuid) {
}
return false;
}

public boolean alterIfLockedNotExistent() {
try (PreparedStatement select = SQLManager.connection()
.prepareStatement("SELECT Locked FROM " + Table + " LIMIT 1")) {
ResultSet result = select.executeQuery();
if (result.next())
return false;
} catch (SQLException e) {
// Column does not exist, we need to add it
try (PreparedStatement alter = SQLManager.connection()
.prepareStatement("ALTER TABLE " + Table + " ADD COLUMN Locked boolean DEFAULT false")) {
alter.execute();
return true;
} catch (SQLException ex) {
Fusion.getInstance()
.getLogger()
.warning("[SQL:FusionPlayersSQL:alterIfLockedNotExistent] Something went wrong with the sql-connection: "
+ ex.getMessage());
}
}
return false;
}
}
Loading