diff --git a/pom.xml b/pom.xml
index 59f0843..38fc361 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,11 +82,16 @@
src/main/resources
true
+
+
+ codemc-releases
+ https://repo.codemc.io/repository/maven-releases/
+
is-releases
https://repo.infernalsuite.com/repository/maven-releases/
@@ -103,6 +108,8 @@
jitpack.io
https://jitpack.io
+
+
@@ -143,5 +150,16 @@
MatrixColorAPI
v1.0.7
+
+ com.github.retrooper
+ packetevents-spigot
+ 2.11.2
+ provided
+
+
+ com.zaxxer
+ HikariCP
+ 6.2.1
+
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/EcoCraftCore.java b/src/main/java/xyz/soukup/ecoCraftCore/EcoCraftCore.java
index ce8362f..aa96cc4 100644
--- a/src/main/java/xyz/soukup/ecoCraftCore/EcoCraftCore.java
+++ b/src/main/java/xyz/soukup/ecoCraftCore/EcoCraftCore.java
@@ -1,24 +1,47 @@
package xyz.soukup.ecoCraftCore;
+import com.github.retrooper.packetevents.event.EventManager;
+import com.github.retrooper.packetevents.event.PacketListenerPriority;
+import com.infernalsuite.asp.api.AdvancedSlimePaperAPI;
+import com.infernalsuite.asp.api.world.SlimeWorldInstance;
import com.j256.ormlite.dao.DaoManager;
+import com.j256.ormlite.jdbc.DataSourceConnectionSource;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
+import com.j256.ormlite.stmt.UpdateBuilder;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
+import xyz.soukup.ecoCraftCore.database.objects.*;
+import xyz.soukup.ecoCraftCore.islands.IslandAdminCommand;
+import xyz.soukup.ecoCraftCore.islands.IslandSelectorCommand;
+import xyz.soukup.ecoCraftCore.mines.MineCommand;
+import xyz.soukup.ecoCraftCore.mines.MineWorldManager;
import xyz.soukup.ecoCraftCore.money.MoneyCommand;
+import xyz.soukup.ecoCraftCore.player.OnKill;
import xyz.soukup.ecoCraftCore.player.PreparePlayer;
-import xyz.soukup.ecoCraftCore.positionMarker.RulerCommand;
+import xyz.soukup.ecoCraftCore.player.TeleportRequestsHandler;
+import xyz.soukup.ecoCraftCore.positionMarker.MarkerCommand;
+import xyz.soukup.ecoCraftCore.regions.RegionAdminCommand;
+import xyz.soukup.ecoCraftCore.regions.RegionEvents;
import xyz.soukup.ecoCraftCore.shop.ShopCommand;
import xyz.soukup.ecoCraftCore.database.objects.Account;
+import xyz.soukup.ecoCraftCore.database.objects.Island;
import xyz.soukup.ecoCraftCore.database.objects.Shop;
import xyz.soukup.ecoCraftCore.database.objects.Transaction;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
import xyz.soukup.ecoCraftCore.inventory.VirtualChest;
-import xyz.soukup.ecoCraftCore.positionMarker.RulerMarking;
+import xyz.soukup.ecoCraftCore.positionMarker.MarkerEvent;
import xyz.soukup.ecoCraftCore.database.DaoRegistry;
import xyz.soukup.ecoCraftCore.messages.Messages;
import xyz.soukup.ecoCraftCore.shop.ShopLogic;
@@ -29,28 +52,43 @@ import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
+import com.github.retrooper.packetevents.PacketEvents;
+import xyz.soukup.ecoCraftCore.islands.ChunkModifier;
public final class EcoCraftCore extends JavaPlugin {
public static EcoCraftCore plugin;
public static ConnectionSource connectionSource;
+ public static FileConfiguration config;
@Override
public void onEnable() {
+
this.getLogger().info("plugin starting out");
+
plugin = this;
+ config = getConfig();
+
+
+ this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
+
+ PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
+ PacketEvents.getAPI().load();
+ PacketEvents.getAPI().init();
+
try {
+ plugin.saveConfig();
+ saveIslandTemplates();
Messages.init();
prepareDatabase();
registerCommands();
registerEvents();
- } catch (SQLException e) {
- e.printStackTrace();
- getLogger().severe("Failed to initialize database.");
- } catch (IOException e) {
- throw new RuntimeException(e);
+ prepareSlimeWorldsSaver();
+
+ }catch (IOException e) {
+ MineWorldManager.init();
}
this.getServer().getScheduler().runTaskTimer(this, /* Lambda: */ task -> {
@@ -62,53 +100,196 @@ public final class EcoCraftCore extends JavaPlugin {
@Override
public void onDisable() {
+ plugin.saveConfig();
VirtualChest.saveCache();
+ saveSlimeWorlds();
+ adiosDatabase();
- try {
- if (connectionSource != null) {
- connectionSource.close();
+ PacketEvents.getAPI().terminate();
+
+ }
+
+ private void prepareSlimeWorldsSaver(){
+ Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> {
+ AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance();
+
+ for (SlimeWorldInstance slimeWorldInstance : asp.getLoadedWorlds()){
+ try {
+ asp.saveWorld(slimeWorldInstance);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
- } catch (Exception e) {
- e.printStackTrace();
+ }, 12000L, 12000L);
+ }
+
+ private void prepareWorldInactivityUnloader(){
+ Bukkit.getScheduler().runTaskTimer(plugin, () -> {
+
+ long timeoutMillis = 30 * 60 * 1000;
+ long now = System.currentTimeMillis();
+
+ AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance();
+ for (SlimeWorldInstance slimeWorldInstance : asp.getLoadedWorlds()) {
+
+ World world = slimeWorldInstance.getBukkitWorld();
+
+ if (!world.getPlayers().isEmpty()){
+ world.removeMetadata("last_empty_time", plugin);
+ continue;
+ }
+
+ if (!world.hasMetadata("last_empty_time")) {
+ world.setMetadata("last_empty_time", new FixedMetadataValue(plugin, now));
+ continue;
+ }
+
+ long emptySince = world.getMetadata("last_empty_time").get(0).asLong();
+
+
+ if ((now - emptySince) >= timeoutMillis) {
+ Bukkit.unloadWorld(world, true);
+ }
+
+
+
+ }
+
+ }, 1200L, 1200L);
+
+ }
+
+ private void saveSlimeWorlds(){
+ AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance();
+
+ for (SlimeWorldInstance slimeWorldInstance : asp.getLoadedWorlds()){
+ try {
+ asp.saveWorld(slimeWorldInstance);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ try {
+ UpdateBuilder updateBuilder = DaoRegistry.getIslandDao().updateBuilder();
+ updateBuilder.where().eq("active_on", config.getString("server.name"));
+ updateBuilder.updateColumnValue("active_on", "");
+ updateBuilder.update();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
}
}
- private void prepareDatabase() throws SQLException {
- String databaseUrl = "jdbc:mysql://localhost:3306/ecc";
- connectionSource = new JdbcConnectionSource(databaseUrl, "ecc", "ecc");
- Logger.getLogger("com.j256.ormlite.table.TableUtils").setLevel(Level.OFF);
+ private void saveIslandTemplates(){
+ plugin.saveResource("island_templates/flat_grass.slime", false);
+ plugin.saveResource("island_templates/flat_sand.slime", false);
+ plugin.saveResource("island_templates/flat_hell.slime", false);
- TableUtils.createTableIfNotExists(connectionSource, Transaction.class);
- TableUtils.createTableIfNotExists(connectionSource, Shop.class);
- TableUtils.createTableIfNotExists(connectionSource, VirtualChest.class);
- TableUtils.createTableIfNotExists(connectionSource, Account.class);
+ }
- DaoRegistry.setTransactionDao(DaoManager.createDao(connectionSource, Transaction.class));
- DaoRegistry.setShopDao(DaoManager.createDao(connectionSource, Shop.class));
- DaoRegistry.setVirtualChestDao(DaoManager.createDao(connectionSource, VirtualChest.class));
- DaoRegistry.setAccountDao(DaoManager.createDao(connectionSource, Account.class));
+ private void prepareDatabase() {
+
+ try {
+ String databaseHost = config.getString("database.host");
+ String databasePort = config.getString("database.port");
+ String databaseName = config.getString("database.database");
+ String databaseUsername = config.getString("database.user");
+ String databasePassword = config.getString("database.password");
+
+ String databaseUrl = "jdbc:mysql://" + databaseHost + ":" + databasePort + "/" + databaseName;
+
+ HikariConfig hikariConfig = new HikariConfig();
+ hikariConfig.setJdbcUrl(databaseUrl);
+ hikariConfig.setUsername(databaseUsername);
+ hikariConfig.setPassword(databasePassword);
+ hikariConfig.setMaximumPoolSize(5);
+ hikariConfig.setKeepaliveTime(60000);
+ hikariConfig.setMaxLifetime(1800000);
+
+ HikariDataSource dataSource = new HikariDataSource(hikariConfig);
+
+ connectionSource = new DataSourceConnectionSource(dataSource, databaseUrl);
+ Logger.getLogger("com.j256.ormlite.table.TableUtils").setLevel(Level.OFF);
+
+ TableUtils.createTableIfNotExists(connectionSource, ActiveServer.class);
+ TableUtils.createTableIfNotExists(connectionSource, Transaction.class);
+ TableUtils.createTableIfNotExists(connectionSource, Shop.class);
+ TableUtils.createTableIfNotExists(connectionSource, VirtualChest.class);
+ TableUtils.createTableIfNotExists(connectionSource, Account.class);
+ TableUtils.createTableIfNotExists(connectionSource, Island.class);
+ TableUtils.createTableIfNotExists(connectionSource, TeleportRequest.class);
+ TableUtils.createTableIfNotExists(connectionSource, Region.class);
+ TableUtils.createTableIfNotExists(connectionSource, RegionMember.class);
+
+ DaoRegistry.setActiveServerDao(DaoManager.createDao(connectionSource, ActiveServer.class));
+ DaoRegistry.setTransactionDao(DaoManager.createDao(connectionSource, Transaction.class));
+ DaoRegistry.setIslandDaoo(DaoManager.createDao(connectionSource, Island.class));
+ DaoRegistry.setShopDao(DaoManager.createDao(connectionSource, Shop.class));
+ DaoRegistry.setVirtualChestDao(DaoManager.createDao(connectionSource, VirtualChest.class));
+ DaoRegistry.setAccountDao(DaoManager.createDao(connectionSource, Account.class));
+ DaoRegistry.setTeleportRequestsDao(DaoManager.createDao(connectionSource, TeleportRequest.class));
+ DaoRegistry.setRegionDao(DaoManager.createDao(connectionSource, Region.class));
+ DaoRegistry.setRegionMemberDao(DaoManager.createDao(connectionSource, RegionMember.class));
+
+
+ ActiveServer activeServer = new ActiveServer(config.getString("server.name"));
+ activeServer.save();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+
+
+ }
+
+ private void adiosDatabase(){
+
+
+ try {
+ ActiveServer activeServer = new ActiveServer(config.getString("server.name"));
+ activeServer.delete();
+
+ if (connectionSource != null) {
+ connectionSource.close();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
}
private void registerCommands() {
@NotNull LifecycleEventManager<@NotNull Plugin> lm = this.getLifecycleManager();
- lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(ShopCommand.createCommand().build()));
- lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(RulerCommand.createCommand().build()));
- lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(MoneyCommand.createCommand().build()));
+ lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(ShopCommand.getCommand().build()));
+ lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(MarkerCommand.getCommand().build()));
+ lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(MoneyCommand.getCommand().build()));
+ lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(IslandAdminCommand.getCommand().build()));
+ lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(IslandSelectorCommand.getCommand().build()));
+ lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(RegionAdminCommand.getCommand().build()));
+ lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(ShopCommand.getCommand().build()));
+ lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(MarkerCommand.getCommand().build()));
+ lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(MoneyCommand.getCommand().build()));
+ lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(MineCommand.createCommand().build()));
}
private void registerEvents(){
PluginManager pm = this.getServer().getPluginManager();
- pm.registerEvents(new RulerMarking(), this);
+ pm.registerEvents(new TeleportRequestsHandler(), this);
+ pm.registerEvents(new OnKill(), this);
+ pm.registerEvents(new MarkerEvent(), this);
pm.registerEvents(new VirtualChestLogic(), this);
pm.registerEvents(new ShopLogic(), this);
pm.registerEvents(new PreparePlayer(), this);
+ pm.registerEvents(new RegionEvents(), this);
+ pm.registerEvents(new MineWorldManager(), this);
- }
+ EventManager events = PacketEvents.getAPI().getEventManager();
+ events.registerListener(new ChunkModifier(this), PacketListenerPriority.NORMAL);
+ }
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/database/DaoRegistry.java b/src/main/java/xyz/soukup/ecoCraftCore/database/DaoRegistry.java
index 46c9389..9b62b01 100644
--- a/src/main/java/xyz/soukup/ecoCraftCore/database/DaoRegistry.java
+++ b/src/main/java/xyz/soukup/ecoCraftCore/database/DaoRegistry.java
@@ -10,7 +10,42 @@ public class DaoRegistry {
private static Dao virtualChestDao;
private static Dao accountDao;
private static Dao islandDao;
+ private static Dao teleportRequestsDao;
+ private static Dao activeServerDao;
+ private static Dao regionDao;
+ private static Dao regionMemberDao;
+ public static Dao getRegionMemberDao() {
+ return regionMemberDao;
+ }
+
+ public static void setRegionMemberDao(Dao regionMemberDao) {
+ DaoRegistry.regionMemberDao = regionMemberDao;
+ }
+
+ public static Dao getRegionDao() {
+ return regionDao;
+ }
+
+ public static void setRegionDao(Dao regionDao) {
+ DaoRegistry.regionDao = regionDao;
+ }
+
+ public static Dao getTeleportRequestsDao() {
+ return teleportRequestsDao;
+ }
+
+ public static void setTeleportRequestsDao(Dao teleportRequestsDao) {
+ DaoRegistry.teleportRequestsDao = teleportRequestsDao;
+ }
+
+ public static Dao getActiveServerDao() {
+ return activeServerDao;
+ }
+
+ public static void setActiveServerDao(Dao activeServerDao) {
+ DaoRegistry.activeServerDao = activeServerDao;
+ }
public static Dao getShopDao() {
return shopDao;
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/database/objects/ActiveServer.java b/src/main/java/xyz/soukup/ecoCraftCore/database/objects/ActiveServer.java
new file mode 100644
index 0000000..f02ce5b
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/database/objects/ActiveServer.java
@@ -0,0 +1,34 @@
+package xyz.soukup.ecoCraftCore.database.objects;
+
+
+import com.j256.ormlite.field.DatabaseField;
+import com.j256.ormlite.table.DatabaseTable;
+import xyz.soukup.ecoCraftCore.database.DaoRegistry;
+
+import java.sql.SQLException;
+
+@DatabaseTable(tableName = "active_servers")
+public class ActiveServer {
+ @DatabaseField(unique = true, id = true)
+ private String name;
+
+ public ActiveServer(){
+
+ }
+
+ public ActiveServer(String name){
+ this.name = name;
+ }
+
+ public void save() throws SQLException {
+ DaoRegistry.getActiveServerDao().createIfNotExists(this);
+ }
+
+ public void delete() throws SQLException {
+ DaoRegistry.getActiveServerDao().delete(this);
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/database/objects/Island.java b/src/main/java/xyz/soukup/ecoCraftCore/database/objects/Island.java
index edd47c5..0d10702 100644
--- a/src/main/java/xyz/soukup/ecoCraftCore/database/objects/Island.java
+++ b/src/main/java/xyz/soukup/ecoCraftCore/database/objects/Island.java
@@ -15,9 +15,8 @@ public class Island {
@DatabaseField(canBeNull = false, unique = true)
private String uuid;
- @DatabaseField(canBeNull = false, unique = true)
- private String name;
-
+ @DatabaseField(canBeNull = false)
+ private String type;
@DatabaseField(columnName = "display_name")
private String displayName;
@@ -28,21 +27,26 @@ public class Island {
@DatabaseField(canBeNull = false)
private String owner;
- @DatabaseField(canBeNull = false)
+ @DatabaseField(columnName = "owner_type", canBeNull = false)
private String ownerType;
@DatabaseField(canBeNull = false, dataType = DataType.BYTE_ARRAY, columnDefinition = "LONGBLOB")
private byte[] data;
+ @DatabaseField(columnName = "is_public", canBeNull = false, defaultValue = "false")
+ private Boolean isPublic;
+
@DatabaseField(defaultValue = "", columnName = "active_on")
private String activeOn;
+
+
public Island(){
}
- public Island(String name, String uuid, String displayName, String descritpion, String owner, String ownerType, byte[] data) {
- this.name = name;
+ public Island(String type, String uuid, String displayName, String descritpion, String owner, String ownerType, byte[] data) {
+ this.type = type;
this.uuid = uuid;
this.displayName = displayName;
this.descritpion = descritpion;
@@ -52,6 +56,13 @@ public class Island {
}
+ public Boolean getPublic() {
+ return isPublic;
+ }
+ public void setPublic(Boolean isPublic) {
+ this.isPublic = isPublic;
+ }
+
public void setData(byte[] data) {
this.data = data;
}
@@ -60,8 +71,8 @@ public class Island {
return id;
}
- public String getName() {
- return name;
+ public String getType() {
+ return type;
}
public String getDisplayName() {
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/database/objects/Region.java b/src/main/java/xyz/soukup/ecoCraftCore/database/objects/Region.java
new file mode 100644
index 0000000..ea06c7c
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/database/objects/Region.java
@@ -0,0 +1,213 @@
+package xyz.soukup.ecoCraftCore.database.objects;
+
+import com.j256.ormlite.dao.ForeignCollection;
+import com.j256.ormlite.field.DatabaseField;
+import com.j256.ormlite.field.ForeignCollectionField;
+import com.j256.ormlite.table.DatabaseTable;
+import xyz.soukup.ecoCraftCore.database.DaoRegistry;
+
+import java.util.HashMap;
+import java.util.List;
+
+@DatabaseTable(tableName = "regions")
+public class Region {
+
+ public static final HashMap> cache = new HashMap<>();
+
+ @DatabaseField(generatedId = true)
+ private int id;
+
+ @DatabaseField(canBeNull = false)
+ private String island;
+
+ @DatabaseField(columnName = "region_type", canBeNull = false)
+ private int regionType;
+
+ @DatabaseField(canBeNull = false)
+ private int x1;
+
+ @DatabaseField(canBeNull = false)
+ private int y1;
+
+ @DatabaseField(canBeNull = false)
+ private int z1;
+
+ @DatabaseField(canBeNull = false)
+ private int x2;
+
+ @DatabaseField(canBeNull = false)
+ private int y2;
+
+ @DatabaseField(canBeNull = false)
+ private int z2;
+
+ @ForeignCollectionField(eager = true)
+ private ForeignCollection regionMembers;
+
+ @DatabaseField()
+ private Integer value;
+
+ public Region() {
+
+ }
+
+ public Region(String island, int regionType, int x1, int y1, int z1, int x2, int y2, int z2) {
+ this.island = island;
+ this.regionType = regionType;
+ this.x1 = Math.min(x1, x2);
+ this.y1 = Math.min(y1, y2);
+ this.x2 = Math.max(x1, x2);
+ this.y2 = Math.max(y1, y2);
+ this.z1 = Math.min(z1, z2);
+ this.z2 = Math.max(z1, z2);
+
+ }
+ public int getId() {
+ return id;
+ }
+
+ public int getRegionType() {
+ return regionType;
+ }
+
+ public int getX1() {
+ return x1;
+ }
+
+ public int getZ1() {
+ return z1;
+ }
+
+ public int getZ2() {
+ return z2;
+ }
+
+ public int getY1() {
+ return y1;
+ }
+
+ public int getX2() {
+ return x2;
+ }
+
+ public int getY2() {
+ return y2;
+ }
+
+ public ForeignCollection getRegionMembers() {
+ return regionMembers;
+ }
+
+ public Integer getValue() {
+ return value;
+ }
+
+ public String getIsland() {
+ return island;
+ }
+
+
+
+
+
+ public void setRegionType(int regionType) {
+ this.regionType = regionType;
+ }
+
+ public void setValue(Integer value) {
+ this.value = value;
+ }
+
+ public void save(){
+ try {
+
+ DaoRegistry.getRegionDao().createOrUpdate(this);
+ List regions = cache.get(island);
+
+ if (regions == null) {
+ return;
+ }
+
+ regions.removeIf(region -> region.getId() == this.id);
+ regions.add(this);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void addRegionMember(String memberType, String member, String membershipType){
+ RegionMember regionMember = new RegionMember(this, memberType, member, membershipType);
+ regionMember.save();
+
+ Region updatedRegion = Region.findById(this.id);
+ List regions = cache.get(island);
+
+ if (regions == null) {
+ return;
+ }
+
+ regions.removeIf(region -> region.getId() == this.id);
+ regions.add(updatedRegion);
+
+ }
+
+ public boolean isInside(int x, int y, int z) {
+ return x >= this.x1 && x <= this.x2 && y >= this.y1 && y <= this.y2 && z >= this.z1 && z <= this.z2;
+ }
+
+ public static Region findById(int id) {
+ try {
+ return DaoRegistry.getRegionDao().queryForId(id);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public static void cacheRegions(String island) {
+ try {
+ List regions = DaoRegistry.getRegionDao().queryBuilder()
+ .where()
+ .eq("island", island)
+ .query();
+ cache.put(island, regions);
+ } catch (Exception e) {
+
+ }
+ }
+
+ public static Region findRegion(int x, int y, int z, String island) {
+
+ Region region = null;
+ int highestType = -1;
+
+ if (!cache.containsKey(island)) {
+ cacheRegions(island);
+ }
+
+ for (Region cachedRegion : cache.get(island)) {
+ if (!cachedRegion.isInside(x, y, z)) {
+ continue;
+ }
+ if (highestType >= cachedRegion.getRegionType()){
+ continue;
+ }
+ region = cachedRegion;
+ }
+
+ return region;
+
+
+ }
+
+ public void changeBoundaries(int x1, int y1, int z1, int x2, int y2, int z2){
+ this.x1 = Math.min(x1, x2);
+ this.y1 = Math.min(y1, y2);
+ this.x2 = Math.max(x1, x2);
+ this.y2 = Math.max(y1, y2);
+ this.z1 = Math.min(z1, z2);
+ this.z2 = Math.max(z1, z2);
+ }
+
+
+}
+
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/database/objects/RegionMember.java b/src/main/java/xyz/soukup/ecoCraftCore/database/objects/RegionMember.java
new file mode 100644
index 0000000..610d47a
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/database/objects/RegionMember.java
@@ -0,0 +1,65 @@
+package xyz.soukup.ecoCraftCore.database.objects;
+
+import com.j256.ormlite.field.DatabaseField;
+import com.j256.ormlite.table.DatabaseTable;
+import xyz.soukup.ecoCraftCore.database.DaoRegistry;
+
+import java.sql.SQLException;
+
+@DatabaseTable(tableName = "region_members")
+public class RegionMember {
+
+ @DatabaseField(generatedId = true)
+ private int id;
+
+ @DatabaseField(foreign = true, foreignAutoRefresh = true)
+ private Region region;
+
+ @DatabaseField(columnName = "member_type", canBeNull = false)
+ private String membertype;
+
+ @DatabaseField(columnName = "member_name", canBeNull = false)
+ private String memberName;
+
+ @DatabaseField(columnName = "membership_type", canBeNull = false)
+ private String membershipType;
+
+ public RegionMember(){
+
+ }
+
+ public RegionMember(Region region, String membertype, String memberName, String membershipType){
+ this.region = region;
+ this.membertype = membertype;
+ this.memberName = memberName;
+ this.membershipType = membershipType;
+ }
+
+ public Region getRegion() {
+ return region;
+ }
+
+ public String getMembertype() {
+ return membertype;
+ }
+
+ public String getName() {
+ return memberName;
+ }
+
+ public String getMembershipType() {
+ return membershipType;
+ }
+
+ public void setMembershipType(String membershipType) {
+ this.membershipType = membershipType;
+ }
+
+ public void save(){
+ try {
+ DaoRegistry.getRegionMemberDao().createOrUpdate(this);
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/database/objects/TeleportRequest.java b/src/main/java/xyz/soukup/ecoCraftCore/database/objects/TeleportRequest.java
new file mode 100644
index 0000000..0dc48bf
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/database/objects/TeleportRequest.java
@@ -0,0 +1,96 @@
+package xyz.soukup.ecoCraftCore.database.objects;
+
+import com.j256.ormlite.field.DatabaseField;
+import com.j256.ormlite.table.DatabaseTable;
+import org.bukkit.Location;
+import xyz.soukup.ecoCraftCore.database.DaoRegistry;
+
+import java.sql.SQLException;
+
+@DatabaseTable(tableName = "teleport_requests")
+public class TeleportRequest {
+
+ @DatabaseField(unique = true, id = true)
+ private String player;
+
+ @DatabaseField(canBeNull = false)
+ private String server;
+
+ @DatabaseField(canBeNull = false)
+ private String world;
+
+ @DatabaseField
+ private Integer x;
+
+ @DatabaseField
+ private Integer y;
+
+ @DatabaseField
+ private Integer z;
+
+ @DatabaseField
+ private Float yaw;
+
+ @DatabaseField
+ private Float pitch;
+
+ public TeleportRequest() {
+ }
+
+ public TeleportRequest(String player, String server, String world) {
+ this.player = player;
+ this.server = server;
+ this.world = world;
+ }
+
+ public TeleportRequest(String player, String server, String world, Integer x, Integer y, Integer z, Float yaw, Float pitch) {
+ this.player = player;
+ this.server = server;
+ this.world = world;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.yaw = yaw;
+ this.pitch = pitch;
+ }
+
+ public String getServer() {
+ return server;
+ }
+
+ public String getWorld() {
+ return world;
+ }
+
+ public String getPlayer() {
+ return player;
+ }
+
+ public Integer getX() {
+ return x;
+ }
+
+ public Integer getY() {
+ return y;
+ }
+
+ public Integer getZ() {
+ return z;
+ }
+
+ public Float getYaw() {
+ return yaw;
+ }
+
+ public Float getPitch() {
+ return pitch;
+ }
+
+ public void save() throws SQLException {
+ DaoRegistry.getTeleportRequestsDao().createOrUpdate(this);
+ }
+
+ public void delete() throws SQLException {
+ DaoRegistry.getTeleportRequestsDao().delete(this);
+ }
+}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/gui/GuiItemBuilder.java b/src/main/java/xyz/soukup/ecoCraftCore/gui/GuiItemBuilder.java
index 66f95c2..3c38f46 100644
--- a/src/main/java/xyz/soukup/ecoCraftCore/gui/GuiItemBuilder.java
+++ b/src/main/java/xyz/soukup/ecoCraftCore/gui/GuiItemBuilder.java
@@ -2,16 +2,19 @@ package xyz.soukup.ecoCraftCore.gui;
import com.github.stefvanschie.inventoryframework.gui.GuiItem;
import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.stream.Collectors;
public class GuiItemBuilder {
- private ItemStack itemStack;
- private ItemMeta itemMeta;
+ private final ItemStack itemStack;
+ private final ItemMeta itemMeta;
public GuiItemBuilder(Material material){
this.itemStack = new ItemStack(material);
@@ -30,6 +33,14 @@ public class GuiItemBuilder {
return this;
}
+ public GuiItemBuilder setRawLore(String rawLore){
+ List lore = Arrays.stream(rawLore.split("\\R"))
+ .map(line -> MiniMessage.miniMessage().deserialize(line))
+ .collect(Collectors.toList());
+ itemMeta.lore(lore);
+ return this;
+ }
+
public GuiItem build(){
this.itemStack.setItemMeta(this.itemMeta);
return new GuiItem(this.itemStack);
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/islands/ChunkModifier.java b/src/main/java/xyz/soukup/ecoCraftCore/islands/ChunkModifier.java
new file mode 100644
index 0000000..2ae90a1
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/islands/ChunkModifier.java
@@ -0,0 +1,257 @@
+package xyz.soukup.ecoCraftCore.islands;
+
+import com.github.retrooper.packetevents.event.PacketListener;
+import com.github.retrooper.packetevents.event.PacketSendEvent;
+import com.github.retrooper.packetevents.protocol.packettype.PacketType;
+import com.github.retrooper.packetevents.protocol.world.chunk.BaseChunk;
+import com.github.retrooper.packetevents.protocol.world.chunk.Column;
+import com.github.retrooper.packetevents.protocol.world.chunk.LightData;
+import com.github.retrooper.packetevents.protocol.world.chunk.TileEntity;
+import com.github.retrooper.packetevents.protocol.world.chunk.impl.v_1_18.Chunk_v1_18;
+import com.github.retrooper.packetevents.protocol.world.chunk.palette.DataPalette;
+import com.github.retrooper.packetevents.protocol.world.chunk.palette.PaletteType;
+import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChunkData;
+import org.bukkit.Bukkit;
+import org.bukkit.NamespacedKey;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.bukkit.persistence.PersistentDataType;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.BitSet;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class ChunkModifier implements PacketListener {
+
+ private final JavaPlugin plugin;
+ private final NamespacedKey keyX1, keyX2, keyZ1, keyZ2, keyType;
+
+ HashMap blockStates = new HashMap<>();
+
+ public ChunkModifier(JavaPlugin plugin) {
+ this.plugin = plugin;
+ this.keyX1 = new NamespacedKey(plugin, "borderx1");
+ this.keyX2 = new NamespacedKey(plugin, "borderx2");
+ this.keyZ1 = new NamespacedKey(plugin, "bordery1"); // Based on your spec
+ this.keyZ2 = new NamespacedKey(plugin, "borderx2"); // Based on your spec
+ this.keyType = new NamespacedKey(plugin, "island_type");
+ }
+
+ @Override
+ public void onPacketSend(PacketSendEvent event) {
+ if (event.getPacketType() != PacketType.Play.Server.CHUNK_DATA) return;
+
+ Player bukkitPlayer = Bukkit.getPlayer(event.getUser().getUUID());
+ if (bukkitPlayer == null) return;
+
+ World world = bukkitPlayer.getWorld();
+ PersistentDataContainer pdc = world.getPersistentDataContainer();
+
+ String type = pdc.get(keyType, PersistentDataType.STRING);
+ if (type == null) return;
+
+ String block;
+
+ switch (type){
+ case "flat_grass":
+ case "flat_sand":
+ block = "WATER";
+ break;
+ case "flat_hell":
+ block = "LAVA";
+ break;
+ default:
+ return;
+
+ }
+
+ Integer x1 = pdc.get(keyX1, PersistentDataType.INTEGER);
+ Integer x2 = pdc.get(keyX2, PersistentDataType.INTEGER);
+ Integer z1 = pdc.get(keyZ1, PersistentDataType.INTEGER);
+ Integer z2 = pdc.get(keyZ2, PersistentDataType.INTEGER);
+ if (x1 == null || x2 == null || z1 == null || z2 == null) return;
+
+ int minCX = Math.min(x1 >> 4, x2 >> 4) - 1;
+ int maxCX = Math.max(x1 >> 4, x2 >> 4) + 1;
+ int minCZ = Math.min(z1 >> 4, z2 >> 4) - 1;
+ int maxCZ = Math.max(z1 >> 4, z2 >> 4) + 1;
+
+ WrapperPlayServerChunkData wrapper = new WrapperPlayServerChunkData(event);
+
+ Column original = wrapper.getColumn();
+ if (original == null) return;
+
+ int chunkX = original.getX();
+ int chunkZ = original.getZ();
+ if (chunkX >= minCX && chunkX <= maxCX && chunkZ >= minCZ && chunkZ <= maxCZ) return;
+
+ Column ghostColumn = createGhostColumn(world, original, resolveBlockStateID(block));
+ if (ghostColumn == null) return;
+
+ wrapper.setColumn(ghostColumn);
+
+ wrapper.setLightData(buildFullBrightLightData(world));
+ }
+
+ private static LightData buildFullBrightLightData(World world) {
+ int sections = (world.getMaxHeight() - world.getMinHeight()) >> 4;
+ if (sections <= 0) sections = 24;
+
+ int lightCount = sections + 2;
+
+ byte[] fullSky = new byte[2048];
+ Arrays.fill(fullSky, (byte) 0xFF);
+
+ byte[] noBlock = new byte[2048];
+
+
+ boolean hasSkyLight = world.getEnvironment() != World.Environment.NETHER
+ && world.getEnvironment() != World.Environment.THE_END; // adjust if you have custom dims
+
+ BitSet skyMask = new BitSet(lightCount);
+ BitSet blockMask = new BitSet(lightCount);
+ BitSet emptySkyMask = new BitSet(lightCount);
+ BitSet emptyBlockMask = new BitSet(lightCount);
+
+ byte[][] skyArray;
+ int skyCount;
+ if (hasSkyLight) {
+ skyMask.set(0, lightCount);
+ skyCount = lightCount;
+ skyArray = new byte[skyCount][];
+ for (int i = 0; i < skyCount; i++) {
+ skyArray[i] = fullSky;
+ }
+ } else {
+ // no skylight dimension
+ emptySkyMask.set(0, lightCount);
+ skyCount = 0;
+ skyArray = new byte[0][];
+ }
+
+ blockMask.set(0, lightCount);
+ int blockCount = lightCount;
+ byte[][] blockArray = new byte[blockCount][];
+ for (int i = 0; i < blockCount; i++) {
+ blockArray[i] = noBlock;
+ }
+
+ LightData ld = new LightData();
+ ld.setTrustEdges(true);
+ ld.setSkyLightMask(skyMask);
+ ld.setBlockLightMask(blockMask);
+ ld.setEmptySkyLightMask(emptySkyMask);
+ ld.setEmptyBlockLightMask(emptyBlockMask);
+ ld.setSkyLightCount(skyCount);
+ ld.setBlockLightCount(blockCount);
+ ld.setSkyLightArray(skyArray);
+ ld.setBlockLightArray(blockArray);
+ return ld;
+ }
+
+ private Column createGhostColumn(World world, Column original, int blockID) {
+ int sections = (world.getMaxHeight() - world.getMinHeight()) >> 4;
+ if (sections <= 0) sections = 24;
+
+ BaseChunk[] originalChunks = original.getChunks();
+
+ // Find any existing biome palette from the original packet; we will reuse it.
+ DataPalette fallbackBiomePalette = null;
+ if (originalChunks != null) {
+ for (BaseChunk bc : originalChunks) {
+ if (bc instanceof Chunk_v1_18 c) {
+ fallbackBiomePalette = c.getBiomeData();
+ break;
+ }
+ }
+ }
+
+
+ BaseChunk[] chunks = new BaseChunk[sections];
+
+ for (int sectionIndex = 0; sectionIndex < sections; sectionIndex++) {
+ DataPalette biomePalette = fallbackBiomePalette;
+
+ if (originalChunks != null && sectionIndex < originalChunks.length && originalChunks[sectionIndex] instanceof Chunk_v1_18 c) {
+ // Prefer the matching section's biome palette if present
+ biomePalette = c.getBiomeData();
+ }
+
+ DataPalette blockPalette = PaletteType.CHUNK.create();
+
+ Chunk_v1_18 section = new Chunk_v1_18(0, blockPalette, biomePalette);
+ section.set(0, 0, 0, 0);
+
+ chunks[sectionIndex] = section;
+ }
+
+ if (chunks[0] instanceof Chunk_v1_18 section0) {
+ for (int localY = 0; localY <= 1; localY++) {
+ for (int x = 0; x < 16; x++) {
+ for (int z = 0; z < 16; z++) {
+ section0.set(x, localY, z, blockID);
+ }
+ }
+ }
+ }
+
+ return new Column(
+ original.getX(),
+ original.getZ(),
+ true,
+ chunks,
+ new TileEntity[0],
+ original.getHeightMaps()
+ );
+ }
+
+ private int resolveBlockStateID(String name) {
+ Integer stateID = blockStates.get(name);
+ if (stateID != null) return stateID;
+
+ int resolved = 0;
+
+ try {
+ Object blockState = resolveNmsBlockState(name);
+ if (blockState != null) {
+ Integer id = tryGetBlockStateIdViaBlockGetId(blockState);
+ if (id != null && id > 0) {
+ resolved = id;
+ }
+ }
+ } catch (ReflectiveOperationException ignored) {
+
+ }
+
+ blockStates.put(name, resolved);
+ return resolved;
+ }
+
+ private static Object resolveNmsBlockState(String name) throws ReflectiveOperationException {
+ Class> blocksClass = Class.forName("net.minecraft.world.level.block.Blocks");
+ Field waterField = blocksClass.getField(name);
+ Object waterBlock = waterField.get(null);
+
+ Method defaultBlockState = waterBlock.getClass().getMethod("defaultBlockState");
+ return defaultBlockState.invoke(waterBlock);
+ }
+
+ private static Integer tryGetBlockStateIdViaBlockGetId(Object blockState) throws ReflectiveOperationException {
+ // Block.getId(BlockState) exists on many modern versions
+ Class> blockClass = Class.forName("net.minecraft.world.level.block.Block");
+ Class> blockStateClass = Class.forName("net.minecraft.world.level.block.state.BlockState");
+
+ try {
+ Method getId = blockClass.getMethod("getId", blockStateClass);
+ Object idObj = getId.invoke(null, blockState);
+ return (idObj instanceof Integer i) ? i : null;
+ } catch (NoSuchMethodException ignored) {
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandLoader.java b/src/main/java/xyz/soukup/ecoCraftCore/islands/DatabaseIslandLoader.java
similarity index 95%
rename from src/main/java/xyz/soukup/ecoCraftCore/islands/IslandLoader.java
rename to src/main/java/xyz/soukup/ecoCraftCore/islands/DatabaseIslandLoader.java
index 1431f76..93c8b6e 100644
--- a/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandLoader.java
+++ b/src/main/java/xyz/soukup/ecoCraftCore/islands/DatabaseIslandLoader.java
@@ -10,7 +10,7 @@ import java.sql.SQLException;
import java.util.List;
import java.util.stream.Collectors;
-public class IslandLoader implements SlimeLoader {
+public class DatabaseIslandLoader implements SlimeLoader {
@Override
@@ -38,6 +38,8 @@ public class IslandLoader implements SlimeLoader {
.setCountOf(true)
.where()
.eq("uuid", worldName)
+ .and()
+ .isNotNull("data")
.countOf();
return count > 0;
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/islands/FileIslandLoader.java b/src/main/java/xyz/soukup/ecoCraftCore/islands/FileIslandLoader.java
new file mode 100644
index 0000000..e4ebff8
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/islands/FileIslandLoader.java
@@ -0,0 +1,72 @@
+package xyz.soukup.ecoCraftCore.islands;
+
+import com.infernalsuite.asp.api.exceptions.UnknownWorldException;
+import com.infernalsuite.asp.api.loaders.SlimeLoader;
+import xyz.soukup.ecoCraftCore.EcoCraftCore;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class FileIslandLoader implements SlimeLoader {
+
+ private final Path storagePath;
+ private static final String EXTENSION = ".slime";
+
+ public FileIslandLoader() {
+ // Creates a directory named 'islands' inside your plugin folder
+ this.storagePath = EcoCraftCore.plugin.getDataFolder().toPath().resolve("island_templates");
+
+ if (!Files.exists(storagePath)) {
+ storagePath.toFile().mkdirs();
+ }
+ }
+
+ private Path getWorldPath(String worldName) {
+ return storagePath.resolve(worldName + EXTENSION);
+ }
+
+ @Override
+ public byte[] readWorld(String worldName) throws UnknownWorldException, IOException {
+ Path path = getWorldPath(worldName);
+ if (!Files.exists(path)) {
+ throw new UnknownWorldException(worldName);
+ }
+ return Files.readAllBytes(path);
+ }
+
+ @Override
+ public boolean worldExists(String worldName) throws IOException {
+ return Files.exists(getWorldPath(worldName));
+ }
+
+ @Override
+ public void saveWorld(String worldName, byte[] serializedWorld) throws IOException {
+ // Files.write will create or overwrite the file automatically
+ Files.write(getWorldPath(worldName), serializedWorld);
+ }
+
+ @Override
+ public void deleteWorld(String worldName) throws IOException {
+ Files.deleteIfExists(getWorldPath(worldName));
+ }
+
+ @Override
+ public List listWorlds() throws IOException {
+ File folder = storagePath.toFile();
+ File[] files = folder.listFiles((dir, name) -> name.endsWith(EXTENSION));
+
+ if (files == null) {
+ return Collections.emptyList();
+ }
+
+ return Arrays.stream(files)
+ .map(file -> file.getName().replace(EXTENSION, ""))
+ .collect(Collectors.toList());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandAdminCommand.java b/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandAdminCommand.java
new file mode 100644
index 0000000..5d7cd81
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandAdminCommand.java
@@ -0,0 +1,324 @@
+package xyz.soukup.ecoCraftCore.islands;
+
+import com.infernalsuite.asp.api.AdvancedSlimePaperAPI;
+import com.infernalsuite.asp.api.world.SlimeWorld;
+import com.j256.ormlite.stmt.QueryBuilder;
+import com.mojang.brigadier.arguments.IntegerArgumentType;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.context.CommandContext;
+import io.papermc.paper.command.brigadier.CommandSourceStack;
+import io.papermc.paper.command.brigadier.Commands;
+import org.bukkit.Location;
+import org.bukkit.NamespacedKey;
+import org.bukkit.entity.Player;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.bukkit.persistence.PersistentDataType;
+import xyz.soukup.ecoCraftCore.database.DaoRegistry;
+import xyz.soukup.ecoCraftCore.database.objects.Island;
+import xyz.soukup.ecoCraftCore.messages.Messages;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.List;
+
+import static xyz.soukup.ecoCraftCore.EcoCraftCore.config;
+import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin;
+
+public class IslandAdminCommand {
+ private final AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance();
+
+
+ public static LiteralArgumentBuilder getCommand() {
+
+
+ LiteralArgumentBuilder tp = Commands.literal("tp")
+ .then(Commands.argument("uuid", StringArgumentType.word())
+ .executes(IslandAdminCommand::teleport)
+ .suggests(((context, builder) -> {
+ try {
+ QueryBuilder queryBuilder = DaoRegistry.getIslandDao().queryBuilder();
+ queryBuilder.selectColumns("uuid");
+ List islands = queryBuilder.query();
+ for (Island island : islands) {
+ builder.suggest(island.getUuid());
+ }
+ return builder.buildFuture();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ })));
+
+ LiteralArgumentBuilder create = Commands.literal("create")
+ .then(Commands.argument("type", StringArgumentType.word())
+ .suggests(((context, builder) -> {
+ try {
+ IslandManager islandManager = new IslandManager();
+ FileIslandLoader fileLoader = islandManager.fileLoader;
+ for (String world: fileLoader.listWorlds()){
+ builder.suggest(world);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return builder.buildFuture();
+ }))
+ .then(Commands.argument("display_name", StringArgumentType.string())
+ .then(Commands.argument("description", StringArgumentType.greedyString())
+ .executes(IslandAdminCommand::createWorld))));
+
+ LiteralArgumentBuilder load = Commands.literal("load")
+ .then(Commands.argument("uuid", StringArgumentType.word())
+ .suggests(((context, builder) -> {
+ try {
+ QueryBuilder queryBuilder = DaoRegistry.getIslandDao().queryBuilder();
+ queryBuilder.selectColumns("uuid");
+ List islands = queryBuilder.query();
+ for (Island island : islands) {
+ builder.suggest(island.getUuid());
+ }
+ return builder.buildFuture();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }))
+ .executes(IslandAdminCommand::loadWorld));
+
+ LiteralArgumentBuilder loadTemplate = Commands.literal("loadTemplate")
+ .then(Commands.argument("name", StringArgumentType.word())
+ .suggests(((context, builder) -> {
+ IslandManager islandManager = new IslandManager();
+ FileIslandLoader fileLoader = islandManager.fileLoader;
+
+ try {
+ for (String world: fileLoader.listWorlds()){
+ builder.suggest(world);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return builder.buildFuture();
+ }))
+ .executes(IslandAdminCommand::loadTemplate));
+
+ LiteralArgumentBuilder template = Commands.literal("template")
+ .then(Commands.argument("uuid", StringArgumentType.word())
+ .suggests((context, builder) -> {
+ try {
+ QueryBuilder queryBuilder = DaoRegistry.getIslandDao().queryBuilder();
+ queryBuilder.selectColumns("uuid");
+ List islands = queryBuilder.query();
+ for (Island island : islands) {
+ builder.suggest(island.getUuid());
+ }
+ return builder.buildFuture();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ })
+ .then(Commands.argument("templateName", StringArgumentType.word())
+ .executes(IslandAdminCommand::createTemplate)));
+
+ LiteralArgumentBuilder metadata = Commands.literal("metadata")
+ .then(Commands.argument("key", StringArgumentType.word())
+ .then(Commands.argument("value", IntegerArgumentType.integer())
+ .executes(IslandAdminCommand::setMetadata)));
+
+ LiteralArgumentBuilder metadataString = Commands.literal("metadataString")
+ .then(Commands.argument("key", StringArgumentType.word())
+ .then(Commands.argument("value", StringArgumentType.string())
+ .executes(IslandAdminCommand::setMetadataString)));
+
+ LiteralArgumentBuilder expand = Commands.literal("expand")
+ .then(Commands.argument("lenght", IntegerArgumentType.integer())
+ .executes(IslandAdminCommand::expandIsland));
+
+ LiteralArgumentBuilder listMetadata = Commands.literal("listMetadata")
+ .executes(IslandAdminCommand::readAllMetadata);
+
+ LiteralArgumentBuilder spawn = Commands.literal("spawn")
+ .executes(IslandAdminCommand::setSpawn);
+
+ LiteralArgumentBuilder enviroment = Commands.literal("environment")
+ .then(Commands.argument("environment", StringArgumentType.word())
+ .executes(IslandAdminCommand::setEnvironment)
+ .suggests(((context, builder) -> {
+ builder.suggest("normal");
+ builder.suggest("nether");
+ builder.suggest("the_end");
+ return builder.buildFuture();
+ })));
+
+ LiteralArgumentBuilder setDefaultIsland = Commands.literal("setDefaultIsland")
+ .executes(IslandAdminCommand::setDefualtIsland);
+
+ return Commands.literal("island-admin")
+ .requires(commandSourceStack -> commandSourceStack.getSender().isOp())
+ .then(tp)
+ .then(create)
+ .then(load)
+ .then(template)
+ .then(metadata)
+ .then(metadataString)
+ .then(listMetadata)
+ .then(loadTemplate)
+ .then(spawn)
+ .then(enviroment)
+ .then(expand)
+ .then(setDefaultIsland);
+
+
+
+ }
+
+ private static int setDefualtIsland(CommandContext context){
+ if (!(context.getSource().getSender() instanceof Player player)) return 0;
+ String uuid = player.getWorld().getName();
+ config.set("islands.spawn", uuid);
+ plugin.saveConfig();
+ player.sendMessage("done.");
+ return 0;
+
+ }
+
+ private static int setSpawn(CommandContext context) {
+ if (!(context.getSource().getSender() instanceof Player player)) return 0;
+ IslandManager islandManager = new IslandManager();
+ Location spawn = player.getLocation();
+ String uuid = player.getWorld().getName();
+ islandManager.changeSpawn(spawn, uuid);
+ Messages.send(player, "island.setSpawn.success");
+
+ return 0;
+ }
+
+ private static int setEnvironment(CommandContext context) {
+ if (!(context.getSource().getSender() instanceof Player player)) return 0;
+ IslandManager islandManager = new IslandManager();
+ String uuid = player.getWorld().getName();
+ islandManager.changeEnviroment(context.getArgument("environment", String.class), uuid);
+ Messages.send(player, "island.setEnvironment.success");
+
+ return 0;
+ }
+
+
+ private static int teleport(CommandContext context) {
+ IslandManager islandManager = new IslandManager();
+ try {
+ Integer status = islandManager.teleport((Player) context.getSource().getSender(), context.getArgument("uuid", String.class));
+ context.getSource().getSender().sendMessage(String.valueOf(status));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return 0;
+ }
+
+ private static int createWorld(CommandContext context) {
+ IslandManager islandManager = new IslandManager();
+ String type = context.getArgument("type", String.class);
+ String displayName = context.getArgument("display_name", String.class);
+ String description = context.getArgument("description", String.class);
+ String owner = context.getSource().getSender().getName();
+ String uuid = islandManager.createIsland(type, displayName, description, owner, "player");
+ context.getSource().getSender().sendMessage("Created island: " + uuid);
+
+ return 0;
+ }
+
+ private static int loadWorld(CommandContext context) {
+ IslandManager islandManager = new IslandManager();
+ islandManager.loadIsland(context.getArgument("uuid", String.class));
+ context.getSource().getSender().sendMessage("done.");
+ return 0;
+ }
+
+ private static int loadTemplate(CommandContext context) {
+ IslandManager islandManager = new IslandManager();
+ islandManager.loadIslandTemplate(context.getArgument("name", String.class));
+ context.getSource().getSender().sendMessage("done.");
+ return 0;
+ }
+
+ private static int createTemplate(CommandContext context) {
+ IslandManager islandManager = new IslandManager();
+ String uuid = StringArgumentType.getString(context, "uuid");
+ String templateName = StringArgumentType.getString(context, "templateName");
+ context.getSource().getSender().sendMessage("§aCreating template '" + templateName + "' from world '" + uuid + "'...");
+ islandManager.createIslandTemplate(uuid, templateName);
+ context.getSource().getSender().sendMessage("§aTemplate created successfully!");
+ return 1;
+ }
+
+ private static int setMetadata(CommandContext context) {
+ if (!(context.getSource().getSender() instanceof Player player)) return 0;
+
+ String keyName = StringArgumentType.getString(context, "key");
+ Integer value = IntegerArgumentType.getInteger(context, "value");
+
+ player.getWorld().getPersistentDataContainer().set(new NamespacedKey(plugin, keyName), PersistentDataType.INTEGER, value);
+ context.getSource().getSender().sendMessage(keyName + " set to " + value);
+ return 1;
+ }
+
+ private static int setMetadataString(CommandContext context) {
+ if (!(context.getSource().getSender() instanceof Player player)) return 0;
+
+ String keyName = StringArgumentType.getString(context, "key");
+ String value = StringArgumentType.getString(context, "value");
+
+ player.getWorld().getPersistentDataContainer().set(new NamespacedKey(plugin, keyName), PersistentDataType.STRING, value);
+ context.getSource().getSender().sendMessage(keyName + " set to " + value);
+
+ return 1;
+ }
+
+ private static int readAllMetadata(CommandContext context) {
+ if (!(context.getSource().getSender() instanceof Player player)) {
+ context.getSource().getSender().sendMessage("§cOnly players can use this.");
+ return 0;
+ }
+
+
+ PersistentDataContainer pdc = player.getWorld().getPersistentDataContainer();
+ if (pdc.getKeys().isEmpty()) {
+ player.sendMessage("§eNo metadata found.");
+ return 1;
+ }
+
+ player.sendMessage("§6--- World Metadata ---");
+ for (NamespacedKey key : pdc.getKeys()) {
+ String val = "unknown";
+ // Logic to determine type for display
+ if (pdc.has(key, PersistentDataType.STRING)) val = pdc.get(key, PersistentDataType.STRING);
+ else if (pdc.has(key, PersistentDataType.INTEGER)) val = String.valueOf(pdc.get(key, PersistentDataType.INTEGER));
+
+ player.sendMessage("§b" + key.getKey() + "§7: §f" + val);
+ }
+ return 1;
+ }
+
+ private static int expandIsland(CommandContext context){
+
+ Player player = (Player) context.getSource().getSender();
+ float yaw = player.getLocation().getYaw();
+ int lenght = IntegerArgumentType.getInteger(context, "lenght");
+ String uuid = player.getWorld().getName();
+
+ IslandManager islandManager = new IslandManager();
+ islandManager.expandIsland(yaw, lenght, uuid);
+
+
+ return 0;
+ }
+
+ private static void saveSlimeWorld(SlimeWorld world, Player player, String key, String val) {
+ try {
+ AdvancedSlimePaperAPI.instance().saveWorld(world);
+ player.sendMessage("§aMetadata set: §f" + key + " §7= §f" + val);
+ } catch (IOException e) {
+ player.sendMessage("§cFailed to save world metadata!");
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandCommand.java b/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandCommand.java
deleted file mode 100644
index 283b225..0000000
--- a/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandCommand.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package xyz.soukup.ecoCraftCore.islands;
-
-import com.mojang.brigadier.arguments.StringArgumentType;
-import com.mojang.brigadier.builder.LiteralArgumentBuilder;
-import com.mojang.brigadier.context.CommandContext;
-import io.papermc.paper.command.brigadier.CommandSourceStack;
-import io.papermc.paper.command.brigadier.Commands;
-
-
-public class IslandCommand {
-
- LiteralArgumentBuilder tp = Commands.literal("tp")
- .then(Commands.argument("uuid", StringArgumentType.word())
- .executes(IslandCommand::teleport));
-
- LiteralArgumentBuilder create = Commands.literal("create")
- .then(Commands.argument("name", StringArgumentType.word())
- .then(Commands.argument("display_name", StringArgumentType.string())));
-
- private static int teleport(CommandContext context) {
- return 0;
- }
-
- private static int createWorld(CommandContext context) {
- return 0;
- }
-
- private static int loadWorld(CommandContext context) {
- return 0;
- }
-
-}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandManager.java b/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandManager.java
index 939b960..301594f 100644
--- a/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandManager.java
+++ b/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandManager.java
@@ -1,85 +1,459 @@
package xyz.soukup.ecoCraftCore.islands;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
import com.infernalsuite.asp.api.AdvancedSlimePaperAPI;
+import com.infernalsuite.asp.api.exceptions.CorruptedWorldException;
+import com.infernalsuite.asp.api.exceptions.NewerFormatException;
+import com.infernalsuite.asp.api.exceptions.UnknownWorldException;
+import com.infernalsuite.asp.api.exceptions.WorldAlreadyExistsException;
import com.infernalsuite.asp.api.world.SlimeWorld;
import com.infernalsuite.asp.api.world.properties.SlimeProperties;
import com.infernalsuite.asp.api.world.properties.SlimePropertyMap;
-import org.bukkit.Bukkit;
+import com.j256.ormlite.dao.Dao;
+import com.j256.ormlite.dao.GenericRawResults;
+import com.j256.ormlite.stmt.QueryBuilder;
+import com.j256.ormlite.stmt.UpdateBuilder;
+import org.bukkit.*;
+import org.bukkit.entity.Player;
+import org.bukkit.persistence.PersistentDataType;
import xyz.soukup.ecoCraftCore.database.objects.Island;
import xyz.soukup.ecoCraftCore.database.DaoRegistry;
+import xyz.soukup.ecoCraftCore.database.objects.Region;
+import xyz.soukup.ecoCraftCore.database.objects.TeleportRequest;
+import xyz.soukup.ecoCraftCore.utilities.PDC;
+import java.io.IOException;
import java.sql.SQLException;
+import java.util.Objects;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import static xyz.soukup.ecoCraftCore.EcoCraftCore.config;
import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin;
public class IslandManager {
private final AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance();
- private final IslandLoader loader = new IslandLoader();
+ private final DatabaseIslandLoader databaseLoader = new DatabaseIslandLoader();
+ public final FileIslandLoader fileLoader = new FileIslandLoader();
+ private final Dao dao = DaoRegistry.getIslandDao();
+ public void createIslandTemplate(String uuid, String templateName){
+ try {
+ SlimeWorld slimeWorld = asp.getLoadedWorld(uuid).clone(templateName, fileLoader);
+ asp.saveWorld(slimeWorld);
+ } catch (WorldAlreadyExistsException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
- public void createIsland(String name, String displayName, String descritpion, String owner, String ownerType) {
+ public String createIsland(String type, String displayName, String descritpion, String owner, String ownerType) {
String uuid = UUID.randomUUID().toString();
-
- SlimePropertyMap props = new SlimePropertyMap();
- props.setValue(SlimeProperties.ENVIRONMENT, "overworld");
- props.setValue(SlimeProperties.WORLD_TYPE, "flat");
-
- // Create empty world in ASWM
try {
- // Note: createEmptyWorld is fast, so we can run some parts sync if needed,
- // but it's best to run the whole chain async.
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
try {
- // Create the database entry first so the loader has a row to update
-
- Island island = new Island(name, uuid, displayName, descritpion, owner, ownerType, null);
+ SlimeWorld slimeWorld;
+ Island island = new Island(type, uuid, displayName, descritpion, owner, ownerType, null);
island.save();
- SlimeWorld slimeWorld = asp.createEmptyWorld(uuid, false, props, loader);
+ if (fileLoader.worldExists(type)){
+ slimeWorld = asp.readWorld(fileLoader, type, false, new SlimePropertyMap()).clone(uuid, databaseLoader);
+ asp.saveWorld(slimeWorld);
+
+ }else {
+ SlimePropertyMap props = new SlimePropertyMap();
+ props.setValue(SlimeProperties.ENVIRONMENT, "normal");
+ props.setValue(SlimeProperties.WORLD_TYPE, "flat");
+ props.setValue(SlimeProperties.ALLOW_ANIMALS, false);
+ props.setValue(SlimeProperties.ALLOW_MONSTERS, false);
+ props.setValue(SlimeProperties.SPAWN_X, 0);
+ props.setValue(SlimeProperties.SPAWN_Y, 2);
+ props.setValue(SlimeProperties.SPAWN_Z, 0);
+
+ slimeWorld = asp.createEmptyWorld(uuid, false, props, databaseLoader);
+ asp.saveWorld(slimeWorld);
+
+ }
- Bukkit.getScheduler().runTask(plugin, () -> {
- asp.loadWorld(slimeWorld, true);
- });
} catch (Exception e) { e.printStackTrace(); }
});
} catch (Exception e) { e.printStackTrace(); }
+
+ createWorldRegion(uuid, owner, ownerType, type);
+
+ return uuid;
}
- // 2. Retrieve and Load existing island
- public int loadIsland(String uuid) {
- if (Bukkit.getWorld(uuid) != null) {
- return 0;
+
+ public int teleport(Player player, String uuid) throws Exception {
+ return teleport(player, uuid, null, null, null, null, null);
+
+ }
+
+ public int teleport(Player player, String uuid, Integer x, Integer y, Integer z, Float yaw, Float pitch) throws Exception {
+
+ if(uuid == null) return 4;
+
+ if (Bukkit.getWorld(uuid) != null){
+ teleportLocally(player, uuid, x, y, z, yaw, pitch);
+ return 2;
}
- try {
- Island island = DaoRegistry.getIslandDao().queryBuilder()
- .selectColumns("active_on")
- .where()
- .eq("uuid", uuid)
- .queryForFirst();
-
- if (!island.getActiveOn().isEmpty()){
- return 1;
+ String whereIsActive = whereIsActive(uuid);
+ if (whereIsActive != null && !whereIsActive.isEmpty()){
+ plugin.getLogger().info("dd: "+ whereIsActive);
+ sendPlayerAway(player, whereIsActive, uuid, x, y, z, yaw, pitch);
+ return 3;
+ }
+
+ QueryBuilder queryBuilder = dao.queryBuilder().setCountOf(true);
+ queryBuilder.where().eq("uuid", uuid);
+
+ if (dao.countOf(queryBuilder.prepare()) < 1){
+ return 1;
+ }
+
+ if (player.getVirtualHost() != null){
+ String emptiestServer = getEmptiestServer();
+ if (!Objects.equals(emptiestServer, config.getString("server.name"))){
+ sendPlayerAway(player, emptiestServer, uuid, x, y, z, yaw, pitch);
}
- } catch (SQLException e) {
- return 2;
+
}
+ teleportLocally(player, uuid, x, y, z, yaw, pitch);
+ return 0;
+
+ }
+
+ public void teleportLocally(Player player, String uuid,
+ Integer x, Integer y, Integer z,
+ Float yaw, Float pitch) {
+
+ loadIsland(uuid).thenAccept(world -> {
+
+ Location location = world.getSpawnLocation();
+
+ if (x != null) {
+ location = new Location(world, x, y, z, yaw, pitch);
+ }
+
+ player.teleport(location);
+
+ }).exceptionally(ex -> {
+ ex.printStackTrace();
+ player.sendMessage("§cFailed to load island.");
+ return null;
+ });
+ }
+
+ private String whereIsActive(String uuid) throws SQLException {
+ Island island = dao.queryBuilder()
+ .selectColumns("active_on")
+ .where()
+ .eq("uuid", uuid)
+ .queryForFirst();
+
+ if (island == null){
+ return null;
+ }
+ if (island.getActiveOn() == null){
+ return null;
+ }
+
+ return island.getActiveOn();
+ }
+
+ private void sendPlayerAway(Player player, String server, String uuid, Integer x, Integer y, Integer z, Float yaw, Float pitch) throws SQLException {
+ TeleportRequest teleportRequest = new TeleportRequest(player.getName(), server, uuid, x, y, z, yaw, pitch);
+ teleportRequest.save();
+
+ ByteArrayDataOutput out = ByteStreams.newDataOutput();
+ out.writeUTF("Connect");
+ out.writeUTF(server);
+ player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray());
+ }
+
+ public void loadIslandTemplate(String name){
+ SlimeWorld slimeWorld = null;
+ try {
+ slimeWorld = asp.readWorld(fileLoader, name, false, new SlimePropertyMap());
+ asp.loadWorld(slimeWorld, true);
+
+ } catch (UnknownWorldException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } catch (CorruptedWorldException e) {
+ throw new RuntimeException(e);
+ } catch (NewerFormatException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public CompletableFuture loadIsland(String uuid) {
+
+ CompletableFuture future = new CompletableFuture<>();
+
+ if (Bukkit.getWorld(uuid) != null) {
+ future.complete(Bukkit.getWorld(uuid));
+ return future;
+ }
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
try {
- SlimeWorld slimeWorld = asp.readWorld(loader, uuid, false, new SlimePropertyMap());
+ SlimeWorld slimeWorld = asp.readWorld(databaseLoader, uuid, false, new SlimePropertyMap());
+
Bukkit.getScheduler().runTask(plugin, () -> {
- asp.loadWorld(slimeWorld, true);
+ try {
+
+ UpdateBuilder updateBuilder = dao.updateBuilder();
+ updateBuilder.where().eq("uuid", uuid);
+ updateBuilder.updateColumnValue("active_on", config.getString("server.name"));
+ updateBuilder.update();
+
+ asp.loadWorld(slimeWorld, true);
+
+ World world = Bukkit.getWorld(uuid);
+ if (world != null) {
+ future.complete(world);
+ } else {
+ future.completeExceptionally(
+ new IllegalStateException("World loaded but Bukkit returned null"));
+ }
+
+ } catch (Exception e) {
+ future.completeExceptionally(e);
+ }
});
- } catch (Exception e) { e.printStackTrace(); }
+
+ } catch (Exception e) {
+ future.completeExceptionally(e);
+ }
});
- return 0;
+
+ return future;
+ }
+
+ private void createWorldRegion(String uuid, String owner, String ownerType, String islandType) {
+ int x1 = -32;
+ int x2 = 31;
+ int y1 = -64;
+ int y2 = 320;
+ int z1 = -32;
+ int z2 = 31;
+
+
+ Region region = new Region(uuid, -1, x1, y1, z1, x2, y2, z2);
+ region.save();
+ region.addRegionMember(ownerType, owner, "owner");
+ }
+
+ public void changeSpawn(Location location, String uuid) {
+ SlimeWorld slimeWorld = asp.getLoadedWorld(uuid);
+ SlimePropertyMap slimePropertyMap = slimeWorld.getPropertyMap();
+ slimePropertyMap.setValue(SlimeProperties.SPAWN_X, location.getBlockX());
+ slimePropertyMap.setValue(SlimeProperties.SPAWN_Y, location.getBlockY());
+ slimePropertyMap.setValue(SlimeProperties.SPAWN_Z, location.getBlockZ());
+
+ try {
+ asp.saveWorld(slimeWorld);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void changeEnviroment( String environment, String uuid) {
+ SlimeWorld slimeWorld = asp.getLoadedWorld(uuid);
+ SlimePropertyMap slimePropertyMap = slimeWorld.getPropertyMap();
+ slimePropertyMap.setValue(SlimeProperties.ENVIRONMENT, environment);
+
+ try {
+ asp.saveWorld(slimeWorld);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void expandIsland(float yaw, int lenght, String uuid){
+ World world = Bukkit.getWorld(uuid);
+
+ if (world == null){
+ return;
+ }
+
+ String islandType = (String) PDC.getUniversal(world, "island_type", PersistentDataType.STRING);
+ switch (islandType){
+ case "flat_grass" -> flatExpander(world, yaw, lenght, Material.GRASS_BLOCK, Material.STONE, Material.WATER);
+ case "flat_sand" -> flatExpander(world, yaw, lenght, Material.SAND, Material.STONE, Material.WATER);
+ case "flat_hell" -> flatExpander(world, yaw, lenght, Material.SOUL_SAND, Material.NETHERRACK, Material.LAVA);
+
+
+ }
+
+
+ }
+
+ public void flatExpander(World world, float yaw, int lenght, Material surface, Material base, Material surrounding){
+
+ int x1 = (int) PDC.getUniversal(world, "borderx1", PersistentDataType.INTEGER);
+ int x2 = (int) PDC.getUniversal(world, "borderx2", PersistentDataType.INTEGER);
+ int z1 = (int) PDC.getUniversal(world, "bordery1", PersistentDataType.INTEGER);
+ int z2 = (int) PDC.getUniversal(world, "bordery2", PersistentDataType.INTEGER);
+ int y = world.getMinHeight();
+
+
+ float yawNormalized = yaw % 360f;
+ if (yawNormalized < 0) yawNormalized += 360f;
+
+ int quadrant = Math.round(yawNormalized / 90.0f) & 3;
+
+
+ int xMin;
+ int xMax;
+ int zMin;
+ int zMax;
+
+ switch (quadrant) {
+ case 0:
+ //+Z
+ xMin = Math.min(x1, x2);
+ xMax = Math.max(x1, x2);
+ zMin = Math.max(z1, z2);
+ zMax = zMin + lenght;
+ z2 = Math.min(z1, z2);
+ z1 = zMax;
+
+ break;
+ case 1:
+ //-X
+ zMin = Math.min(z1, z2);
+ zMax = Math.max(z1, z2);
+ xMax = Math.min(x1, x2);
+ xMin = xMax - lenght;
+ x2 = Math.max(x1, x2);
+ x1 = xMin;
+
+ break;
+ case 2:
+ //-Z
+ xMin = Math.min(x1, x2);
+ xMax = Math.max(x1, x2);
+ zMax = Math.min(z1, z2);
+ zMin = zMax - lenght;
+ z2 = Math.max(z1, z2);
+ z1 = zMin;
+
+ break;
+ default:
+ //+X
+ zMin = Math.min(z1, z2);
+ zMax = Math.max(z1, z2);
+ xMin = Math.max(x1, x2);
+ xMax = xMin + lenght;
+ x2 = Math.min(x1, x2);
+ x1 = xMax;
+
+ break;
+
+ }
+
+ for (int x = xMin; x < (xMax + 1); x++) {
+ for (int z = zMin; z < (zMax + 1); z++) {
+ world.setType(x, y, z, base);
+ world.setType(x, y+1, z, base);
+
+ if (((quadrant == 0 || quadrant == 2) && (x == xMin || x == xMax))
+ || ((quadrant == 1 || quadrant == 3) && (z == zMin || z == zMax))) {
+ continue;
+ }
+
+ if ((quadrant == 0 && z == zMax)
+ || (quadrant == 1 && x == xMin)
+ || (quadrant == 2 && z == zMin)
+ || (quadrant == 3 && x == xMax)){
+ continue;
+ }
+
+ world.setType(x, y+2, z, surface);
+ }
+ }
+
+ changeBoundaries(world, x1, x2, z1, z2);
+
+ int chunkMaxX = ((Math.max(x1, x2) + 15) & ~15) + 16;
+ int chunkMinX = (Math.min(x1, x2) & ~15) - 16;
+ int chunkMaxZ = ((Math.max(z1, z2) + 15) & ~15) + 16;
+ int chunkMinZ = (Math.min(z1, z2) & ~15) - 16;
+
+ xMin = Math.min(x1, x2);
+ xMax = Math.max(x1, x2);
+ zMin = Math.min(z1, z2);
+ zMax = Math.max(z1, z2);
+
+ for (int x = chunkMinX; x < chunkMaxX + 1; x ++) {
+ for (int z = chunkMinZ; z < chunkMaxZ + 1; z ++) {
+ if ((x >= xMin && x <= xMax) && (z >= zMin && z <= zMax)) {
+ continue;
+ }
+ world.getBlockAt(x,y,z).setType(surrounding, false);
+ world.getBlockAt(x,y+1,z).setType(surrounding, false);
+
+ }
+ }
+ ;
+ }
+
+ private void changeBoundaries(World world, int x1, int x2, int z1, int z2){
+ PDC.setUniversal(world, "borderx1", x1, PersistentDataType.INTEGER);
+ PDC.setUniversal(world, "borderx2", x2, PersistentDataType.INTEGER);
+ PDC.setUniversal(world, "bordery1", z1, PersistentDataType.INTEGER);
+ PDC.setUniversal(world, "bordery2", z2, PersistentDataType.INTEGER);
+
+ QueryBuilder queryBuilder = DaoRegistry.getRegionDao().queryBuilder();
+ try {
+ queryBuilder.where()
+ .eq("island", world.getName())
+ .and()
+ .eq("region_type", 0);
+ Region region = queryBuilder.queryForFirst();
+ region.changeBoundaries(x1, 320, z1, x2,-64, z2);
+ region.save();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+
+
+ private String getEmptiestServer() throws Exception {
+ String query = "SELECT active_servers.name " +
+ "FROM active_servers " +
+ "LEFT JOIN islands ON active_servers.name = islands.active_on " +
+ "GROUP BY active_servers.name " +
+ "ORDER BY COUNT(islands.uuid) ASC " +
+ "LIMIT 1";
+
+ GenericRawResults rawResults =
+ DaoRegistry.getActiveServerDao().queryRaw(query);
+
+ String[] firstResult = rawResults.getFirstResult();
+ rawResults.close();
+
+ if (firstResult != null && firstResult.length > 0) {
+ String serverName = firstResult[0];
+ return firstResult[0];
+ }
+
+ return null;
}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandSelectorCommand.java b/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandSelectorCommand.java
new file mode 100644
index 0000000..546e875
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/islands/IslandSelectorCommand.java
@@ -0,0 +1,188 @@
+package xyz.soukup.ecoCraftCore.islands;
+
+import com.github.stefvanschie.inventoryframework.gui.GuiItem;
+import com.github.stefvanschie.inventoryframework.gui.type.ChestGui;
+import com.github.stefvanschie.inventoryframework.gui.type.HopperGui;
+import com.github.stefvanschie.inventoryframework.pane.OutlinePane;
+import com.github.stefvanschie.inventoryframework.pane.PaginatedPane;
+import com.github.stefvanschie.inventoryframework.pane.component.PagingButtons;
+import com.github.stefvanschie.inventoryframework.pane.util.Slot;
+import com.j256.ormlite.stmt.QueryBuilder;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.context.CommandContext;
+import io.papermc.paper.command.brigadier.CommandSourceStack;
+import io.papermc.paper.command.brigadier.Commands;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.TextColor;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import xyz.soukup.ecoCraftCore.database.DaoRegistry;
+import xyz.soukup.ecoCraftCore.database.objects.Island;
+import xyz.soukup.ecoCraftCore.database.objects.Region;
+import xyz.soukup.ecoCraftCore.database.objects.RegionMember;
+import xyz.soukup.ecoCraftCore.gui.GuiItemBuilder;
+import xyz.soukup.ecoCraftCore.messages.Messages;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class IslandSelectorCommand {
+
+ public static LiteralArgumentBuilder getCommand(){
+ return Commands.literal("is")
+ .executes(IslandSelectorCommand::displayIslandListSelectorGui);
+ }
+
+ private static int displayIslandListSelectorGui(CommandContext context) {
+ String title = LegacyComponentSerializer.legacySection().serialize(Messages.get("menu.island-selector.title"));
+ HopperGui hopperGui = new HopperGui(title);
+ hopperGui.setOnGlobalClick(event -> event.setCancelled(true));
+
+ OutlinePane outlinePane = new OutlinePane(0, 0, 5, 1);
+ Player player = (Player) context.getSource().getSender();
+
+ outlinePane.addItem(selectorItem("menu.island-selector.my-islands", Material.GRASS_BLOCK, getMyIslands(player)));
+ outlinePane.addItem(selectorItem("menu.island-selector.shared-islands", Material.MOSS_BLOCK, getSharedIslands(player)));
+ outlinePane.addItem(selectorItem("menu.island-selector.public-islands", Material.SAND, getPublicIslands()));
+
+ hopperGui.getSlotsComponent().addPane(outlinePane);
+
+ if (player.isOp()){
+ outlinePane.addItem(selectorItem("menu.island-selector.all-islands", Material.CRIMSON_NYLIUM, getAllIslands()));
+ }
+
+ hopperGui.show(player);
+ return 0;
+ }
+
+ private static List getAllIslands(){
+ QueryBuilder queryBuilder = DaoRegistry.getIslandDao().queryBuilder();
+ queryBuilder.selectColumns("display_name", "uuid", "descritpion", "type");
+ try {
+ return queryBuilder.query();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static List getPublicIslands(){
+ QueryBuilder queryBuilder = DaoRegistry.getIslandDao().queryBuilder();
+ queryBuilder.selectColumns("display_name", "uuid", "descritpion", "type");
+ try {
+ queryBuilder.where().eq("is_public", true);
+ return queryBuilder.query();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static List getMyIslands(Player player){
+ QueryBuilder queryBuilder = DaoRegistry.getIslandDao().queryBuilder();
+ queryBuilder.selectColumns("display_name", "uuid", "descritpion", "type");
+ try {
+ queryBuilder.where().eq("owner", player.getName()).and().eq("owner_type", "player");
+ return queryBuilder.query();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static List getSharedIslands(Player player){
+ QueryBuilder memberQb = DaoRegistry.getRegionMemberDao().queryBuilder();
+ memberQb.selectColumns("region_id");
+ try {
+ memberQb.where().eq("member_name", player.getName()).and().eq("member_type", "player");
+
+ QueryBuilder regionQb = DaoRegistry.getRegionDao().queryBuilder();
+ regionQb.selectColumns("island");
+ regionQb.where().in("id", memberQb);
+
+ return DaoRegistry.getIslandDao().queryBuilder()
+ .where()
+ .in("uuid", regionQb)
+ .query();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public static GuiItem selectorItem(String key, Material material, List islands){
+ GuiItemBuilder guiItemBuilder = new GuiItemBuilder(material);
+ guiItemBuilder.setName(Messages.get(key));
+ GuiItem guiItem = guiItemBuilder.build();
+ guiItem.setAction(event -> openIslandListGui((Player) event.getWhoClicked(), islands));
+ return guiItem;
+ }
+
+
+ private static void openIslandListGui(Player player, List islands){
+ String title = LegacyComponentSerializer.legacySection().serialize(Messages.get("menu.island-selector.title"));
+ ChestGui chestGui = new ChestGui(4, title);
+ chestGui.setOnGlobalClick(event -> event.setCancelled(true));
+
+ PaginatedPane paginatedPane = new PaginatedPane(0, 0, 9, 3);
+
+ paginatedPane.populateWithGuiItems(itemsFromIslands(player, islands));
+ PagingButtons pagingButtons = new PagingButtons(Slot.fromXY(0, 3), 9, paginatedPane);
+
+ chestGui.addPane(paginatedPane);
+ chestGui.addPane(pagingButtons);
+
+ chestGui.show(player);
+ }
+
+
+ private static List itemsFromIslands(Player player, List islands){
+ List guiItems = new ArrayList<>();
+ IslandManager islandManager = new IslandManager();
+ for (Island island : islands){
+ Material material;
+ TextColor color;
+
+ switch (island.getType()){
+ case "flat_grass":
+ material = Material.GRASS_BLOCK;
+ color = TextColor.color(0x02bd02);
+ break;
+ case "flat_sand":
+ material = Material.SAND;
+ color = TextColor.color(0xfccf03);
+ break;
+ case "flat_hell":
+ material = Material.SOUL_SAND;
+ color = TextColor.color(0xf00707);
+ break;
+ case "void":
+ material = Material.GLASS;
+ color = TextColor.color(0xffffff);
+ break;
+ default:
+ material = Material.WHITE_WOOL;
+ color = TextColor.color(0xffffff);
+ break;
+ }
+
+
+ GuiItemBuilder guiItemBuilder = new GuiItemBuilder(material);
+ guiItemBuilder.setName(Component.text(island.getDisplayName(), color));
+ guiItemBuilder.setRawLore(island.getDescritpion());
+
+ GuiItem guiItem = guiItemBuilder.build();
+ guiItem.setAction(event -> {
+ try {
+ islandManager.teleport(player, island.getUuid());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ guiItems.add(guiItem);
+
+ }
+ return guiItems;
+ }
+}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/islands/UnloadWorld.java b/src/main/java/xyz/soukup/ecoCraftCore/islands/UnloadIsland.java
similarity index 61%
rename from src/main/java/xyz/soukup/ecoCraftCore/islands/UnloadWorld.java
rename to src/main/java/xyz/soukup/ecoCraftCore/islands/UnloadIsland.java
index 63977a8..9a14b2f 100644
--- a/src/main/java/xyz/soukup/ecoCraftCore/islands/UnloadWorld.java
+++ b/src/main/java/xyz/soukup/ecoCraftCore/islands/UnloadIsland.java
@@ -1,4 +1,4 @@
package xyz.soukup.ecoCraftCore.islands;
-public class UnloadWorld {
+public class UnloadIsland {
}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/mines/MineCommand.java b/src/main/java/xyz/soukup/ecoCraftCore/mines/MineCommand.java
new file mode 100644
index 0000000..25cd2f4
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/mines/MineCommand.java
@@ -0,0 +1,65 @@
+package xyz.soukup.ecoCraftCore.mines;
+
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.context.CommandContext;
+import io.papermc.paper.command.brigadier.CommandSourceStack;
+import io.papermc.paper.command.brigadier.Commands;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import xyz.soukup.ecoCraftCore.messages.Messages;
+
+@SuppressWarnings("UnstableApiUsage")
+public class MineCommand {
+
+ public static LiteralArgumentBuilder createCommand() {
+ LiteralArgumentBuilder regenerate = Commands.literal("regenerate")
+ .executes(MineCommand::regenerateMines);
+
+ LiteralArgumentBuilder tp = Commands.literal("tp")
+ .requires(source -> source.getSender() instanceof Player)
+ .executes(MineCommand::teleportToMines);
+
+ return Commands.literal("mine")
+ .then(regenerate)
+ .then(tp);
+ }
+
+ private static int regenerateMines(CommandContext context) {
+ World world = MineWorldManager.getWorld();
+
+ if (world == null) {
+ Messages.send(context.getSource().getSender(), "mine.error.no-world");
+ return 0;
+ }
+
+ Messages.send(context.getSource().getSender(), "mine.regenerating");
+
+ // Teleport all players out of the mine world first
+ for (Player p : world.getPlayers()) {
+ p.teleport(p.getServer().getWorlds().getFirst().getSpawnLocation());
+ Messages.send(p, "mine.teleported-out");
+ }
+
+ // Delete old world, create fresh one, then generate
+ MineWorldManager.recreateWorld(newWorld -> MineManager.regenerate(newWorld));
+ return 1;
+ }
+
+ private static int teleportToMines(CommandContext context) {
+ Player player = (Player) context.getSource().getSender();
+
+ World world = MineWorldManager.getWorld();
+
+ if (world == null) {
+ Messages.send(player, "mine.error.no-world");
+ return 0;
+ }
+
+ Location spawn = MineWorldManager.getSpawnLocation();
+ player.teleport(spawn);
+ Messages.send(player, "mine.teleporting");
+ return 1;
+ }
+}
+
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/mines/MineManager.java b/src/main/java/xyz/soukup/ecoCraftCore/mines/MineManager.java
new file mode 100644
index 0000000..614575d
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/mines/MineManager.java
@@ -0,0 +1,696 @@
+package xyz.soukup.ecoCraftCore.mines;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+
+import java.util.*;
+
+import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin;
+
+public class MineManager {
+
+ private static final int TUNNEL_RADIUS = 5; // 11x11 = radius 5 from center
+ private static final int MIN_BRANCH_SIZE = 30;
+ private static final int FIRST_BRANCH_MIN_SIZE = 20;
+ private static final int FIRST_BRANCH_SPLIT_DELAY = 12;
+ private static final int MAX_BRANCH_SIZE = 80;
+ private static final double BASE_SPLIT_INCREMENT = 0.004;
+ private static final double SPLIT_DECAY_PER_BRANCH = 0.0004;
+ private static final int BLOCKS_PER_TICK = 800;
+
+ // Transition zone: how many blocks before/after a border we blend
+ private static final int TRANSITION_RANGE = 8;
+
+ private static final Random random = new Random();
+
+ private static final BlockFace[] DIRECTIONS = {
+ BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.DOWN, BlockFace.UP
+ };
+
+ // --- Biome definitions ---
+
+ public enum MineBiome {
+ NORMAL, DEEP, SUPER_DEEP
+ }
+
+ // Fill progression per biome
+ private static final Material[] NORMAL_FILLS = {Material.STONE, Material.ANDESITE, Material.COBBLESTONE, Material.TUFF};
+ private static final int[] NORMAL_THRESHOLDS = {0, 30, 60, 90};
+
+ private static final Material[] DEEP_FILLS = {Material.DEEPSLATE, Material.COBBLED_DEEPSLATE, Material.CRACKED_DEEPSLATE_TILES};
+ private static final int[] DEEP_THRESHOLDS = {0, 40, 80};
+
+ private static final Material[] SUPER_DEEP_FILLS = {Material.SMOOTH_BASALT, Material.BLACKSTONE, Material.CRACKED_POLISHED_BLACKSTONE_BRICKS, Material.GILDED_BLACKSTONE};
+ private static final int[] SUPER_DEEP_THRESHOLDS = {0, 40, 80, 120};
+
+ // Special inline blocks (placed during generation, not ores)
+ private static final double DIRT_CHANCE = 0.04;
+ private static final double GRAVEL_CHANCE = 0.05;
+ private static final double INFESTED_CHANCE = 0.008;
+ private static final double MUD_CHANCE_SUPER_DEEP = 0.03;
+ private static final double LAVA_CHANCE_SUPER_DEEP = 0.05;
+
+ // Deep biome lava: starts low and increases through the fills
+ private static final double LAVA_CHANCE_DEEP_MIN = 0.005;
+ private static final double LAVA_CHANCE_DEEP_MAX = 0.04;
+
+ // --- Ore tables per fill type (rolled on break) ---
+
+ public record OreEntry(Material material, double weight) {}
+
+ private static final Map ORE_TABLES = new LinkedHashMap<>();
+
+ static {
+ ORE_TABLES.put(Material.STONE, new OreEntry[]{
+ new OreEntry(Material.COPPER_ORE, 1.0),
+ });
+ ORE_TABLES.put(Material.ANDESITE, new OreEntry[]{
+ new OreEntry(Material.COAL_ORE, 1.0),
+ });
+ ORE_TABLES.put(Material.COBBLESTONE, new OreEntry[]{
+ new OreEntry(Material.IRON_ORE, 1.0),
+ });
+ ORE_TABLES.put(Material.TUFF, new OreEntry[]{
+ new OreEntry(Material.GOLD_ORE, 1.0),
+ });
+ ORE_TABLES.put(Material.DEEPSLATE, new OreEntry[]{
+ new OreEntry(Material.DEEPSLATE_EMERALD_ORE, 0.4),
+ new OreEntry(Material.DEEPSLATE_LAPIS_ORE, 0.6),
+ });
+ ORE_TABLES.put(Material.COBBLED_DEEPSLATE, new OreEntry[]{
+ new OreEntry(Material.DEEPSLATE_REDSTONE_ORE, 0.6),
+ new OreEntry(Material.RAW_COPPER_BLOCK, 0.4),
+ });
+ ORE_TABLES.put(Material.CRACKED_DEEPSLATE_TILES, new OreEntry[]{
+ new OreEntry(Material.DEEPSLATE_DIAMOND_ORE, 0.6),
+ new OreEntry(Material.RAW_IRON_BLOCK, 0.4),
+ });
+ ORE_TABLES.put(Material.SMOOTH_BASALT, new OreEntry[]{
+ new OreEntry(Material.COAL_BLOCK, 1.0),
+ });
+ ORE_TABLES.put(Material.BLACKSTONE, new OreEntry[]{
+ new OreEntry(Material.RAW_GOLD_BLOCK, 1.0),
+ });
+ ORE_TABLES.put(Material.CRACKED_POLISHED_BLACKSTONE_BRICKS, new OreEntry[]{
+ new OreEntry(Material.ANCIENT_DEBRIS, 1.0),
+ });
+ ORE_TABLES.put(Material.GILDED_BLACKSTONE, new OreEntry[]{
+ new OreEntry(Material.GOLD_BLOCK, 1.0),
+ });
+ }
+
+ // Ore chance depending on how far this fill is from the detected zone
+ // Index 0 = current fill, 1 = previous fill (one step lighter), 2 = three fills back, 3+ = any other
+ private static final double ORE_CHANCE_CURRENT = 0.05;
+ private static final double ORE_CHANCE_PREVIOUS = 0.10;
+ private static final double ORE_CHANCE_THREE_BACK = 0.02;
+ private static final double ORE_CHANCE_OTHER = 0.01;
+
+ // Ordered list of ALL fills from shallowest to deepest (used for distance calculations)
+ private static final List ALL_FILLS_ORDERED = new ArrayList<>();
+
+ static {
+ Collections.addAll(ALL_FILLS_ORDERED, NORMAL_FILLS);
+ Collections.addAll(ALL_FILLS_ORDERED, DEEP_FILLS);
+ Collections.addAll(ALL_FILLS_ORDERED, SUPER_DEEP_FILLS);
+ }
+
+ // --- All known fill materials (for checking in MineWorldManager) ---
+
+ private static final Set ALL_FILL_MATERIALS = new HashSet<>(ALL_FILLS_ORDERED);
+
+ public static boolean isFillMaterial(Material material) {
+ return ALL_FILL_MATERIALS.contains(material);
+ }
+
+ // --- Biome Y tracking ---
+
+ private static int normalStartY = 0;
+ private static int deepStartY = 0;
+ private static int superDeepStartY = 0;
+
+ // --- Branch data class ---
+
+ private static class Branch {
+ final BlockFace direction;
+ final int startX, startY, startZ;
+ final int distanceFromStart;
+ final int maxSize;
+ final boolean isFirst;
+ final MineBiome biome;
+ double chanceToSplit;
+
+ int endX, endY, endZ;
+
+ Branch(BlockFace direction, int startX, int startY, int startZ,
+ int distanceFromStart, int maxSize, boolean isFirst, MineBiome biome) {
+ this.direction = direction;
+ this.startX = startX;
+ this.startY = startY;
+ this.startZ = startZ;
+ this.distanceFromStart = distanceFromStart;
+ this.maxSize = maxSize;
+ this.isFirst = isFirst;
+ this.biome = biome;
+ this.chanceToSplit = 0;
+ this.endX = startX;
+ this.endY = startY;
+ this.endZ = startZ;
+ }
+ }
+
+ private static class BlockPlacement {
+ final int x, y, z;
+ final Material material;
+
+ BlockPlacement(int x, int y, int z, Material material) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.material = material;
+ }
+ }
+
+ // ===================== MAIN ENTRY POINT =====================
+
+ public static void regenerate(World world) {
+ int startY = world.getMaxHeight() - 11;
+ int minY = world.getMinHeight() + 5;
+ int maxY = world.getMaxHeight();
+ int worldMinY = world.getMinHeight();
+
+ normalStartY = startY;
+
+ Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
+ Set fillPositions = new HashSet<>();
+ List fillPlacements = new ArrayList<>();
+
+ // ===== Biome 1: NORMAL =====
+ plugin.getLogger().info("[MineGen] === Starting NORMAL biome generation ===");
+ List normalBranches = new ArrayList<>();
+ int[] branchCount = {0};
+ generateBiomeBranches(MineBiome.NORMAL, 0, startY, 0, startY, minY,
+ fillPlacements, fillPositions, normalBranches, branchCount);
+ plugin.getLogger().info("[MineGen] NORMAL done: " + fillPlacements.size() + " fill blocks, branches=" + branchCount[0]);
+
+ // Find lowest Y from NORMAL
+ Branch lowestNormal = findLowestBranch(normalBranches);
+ int deepX = lowestNormal != null ? lowestNormal.endX : 0;
+ int deepY = lowestNormal != null ? lowestNormal.endY : startY - 100;
+ int deepZ = lowestNormal != null ? lowestNormal.endZ : 0;
+ deepStartY = deepY;
+
+ // ===== Biome 2: DEEP =====
+ plugin.getLogger().info("[MineGen] === Starting DEEP biome generation at Y=" + deepY + " ===");
+ List deepBranches = new ArrayList<>();
+ generateBiomeBranches(MineBiome.DEEP, deepX, deepY, deepZ, deepY, minY,
+ fillPlacements, fillPositions, deepBranches, branchCount);
+ plugin.getLogger().info("[MineGen] DEEP done: " + fillPlacements.size() + " total fill blocks, branches=" + branchCount[0]);
+
+ // Find lowest Y from DEEP
+ Branch lowestDeep = findLowestBranch(deepBranches);
+ int sdX = lowestDeep != null ? lowestDeep.endX : deepX;
+ int sdY = lowestDeep != null ? lowestDeep.endY : deepY - 100;
+ int sdZ = lowestDeep != null ? lowestDeep.endZ : deepZ;
+ superDeepStartY = sdY;
+
+ // ===== Biome 3: SUPER_DEEP =====
+ plugin.getLogger().info("[MineGen] === Starting SUPER_DEEP biome generation at Y=" + sdY + " ===");
+ List superDeepBranches = new ArrayList<>();
+ generateBiomeBranches(MineBiome.SUPER_DEEP, sdX, sdY, sdZ, sdY, minY,
+ fillPlacements, fillPositions, superDeepBranches, branchCount);
+ plugin.getLogger().info("[MineGen] SUPER_DEEP done: " + fillPlacements.size() + " total fill blocks, branches=" + branchCount[0]);
+
+ // ===== Walls =====
+ List wallPlacements = new ArrayList<>();
+ generateWalls(fillPositions, wallPlacements, startY);
+ plugin.getLogger().info("[MineGen] Walls done: " + wallPlacements.size() + " bedrock blocks.");
+
+ // ===== Schedule placement on main thread =====
+ Bukkit.getScheduler().runTask(plugin, () ->
+ scheduleBlockPlacements(world, fillPlacements, wallPlacements, startY, worldMinY, maxY));
+ });
+ }
+
+ // ===================== BIOME BRANCH GENERATION =====================
+
+ private static void generateBiomeBranches(MineBiome biome, int startX, int startY, int startZ,
+ int ceilingY, int minY,
+ List placements, Set fillPositions,
+ List completedBranches, int[] branchCount) {
+ Deque queue = new ArrayDeque<>();
+
+ // Reset branch count for this biome so split chance starts fresh
+ int[] biomeBranchCount = {0};
+
+ int firstSize = Math.max(FIRST_BRANCH_MIN_SIZE, MIN_BRANCH_SIZE + random.nextInt(MAX_BRANCH_SIZE - MIN_BRANCH_SIZE));
+ Branch initial = new Branch(BlockFace.DOWN, startX, startY, startZ, 0, firstSize, true, biome);
+ queue.add(initial);
+ biomeBranchCount[0]++;
+ branchCount[0]++;
+
+ plugin.getLogger().info("[MineGen] [" + biome + "] Starting at (" + startX + "," + startY + "," + startZ + "), minY=" + minY);
+
+ while (!queue.isEmpty()) {
+ Branch branch = queue.pollFirst();
+ double currentIncrement = Math.max(0, BASE_SPLIT_INCREMENT - (biomeBranchCount[0] * SPLIT_DECAY_PER_BRANCH));
+ plugin.getLogger().info("[MineGen] [" + biome + "] Processing branch dir=" + branch.direction
+ + " start=(" + branch.startX + "," + branch.startY + "," + branch.startZ + ")"
+ + " dist=" + branch.distanceFromStart + " maxSize=" + branch.maxSize
+ + " first=" + branch.isFirst
+ + " queued=" + queue.size() + " fillBlocks=" + fillPositions.size()
+ + " splitIncrement=" + String.format("%.5f", currentIncrement));
+
+ generateSingleBranch(branch, queue, placements, fillPositions, ceilingY, minY, biomeBranchCount);
+ completedBranches.add(branch);
+ }
+
+ plugin.getLogger().info("[MineGen] [" + biome + "] Complete. Branches=" + completedBranches.size());
+ }
+
+ private static void generateSingleBranch(Branch branch, Deque queue, List placements,
+ Set fillPositions, int ceilingY, int minY, int[] branchCount) {
+ int cx = branch.startX;
+ int cy = branch.startY;
+ int cz = branch.startZ;
+
+ for (int step = 0; step < branch.maxSize; step++) {
+ int distance = branch.distanceFromStart + step;
+
+ generateCrossSection(cx, cy, cz, branch.direction, distance, branch.biome, placements, fillPositions);
+
+ branch.endX = cx;
+ branch.endY = cy;
+ branch.endZ = cz;
+
+ // First branch: skip split logic for the first 12 blocks
+ if (branch.isFirst && step < FIRST_BRANCH_SPLIT_DELAY) {
+ cx += branch.direction.getModX();
+ cy += branch.direction.getModY();
+ cz += branch.direction.getModZ();
+ if (cy < minY || cy > ceilingY + 5) break;
+ continue;
+ }
+
+ double splitIncrement = Math.max(0, BASE_SPLIT_INCREMENT - (branchCount[0] * SPLIT_DECAY_PER_BRANCH));
+
+ if (splitIncrement <= 0) {
+ cx += branch.direction.getModX();
+ cy += branch.direction.getModY();
+ cz += branch.direction.getModZ();
+ if (cy < minY || cy > ceilingY + 5) break;
+ continue;
+ }
+
+ branch.chanceToSplit += splitIncrement;
+
+ if (random.nextDouble() < branch.chanceToSplit) {
+ branch.chanceToSplit = 0;
+
+ int splitCount = 1 + random.nextInt(4);
+ List available = getAvailableDirections(branch.direction);
+ Collections.shuffle(available, random);
+
+ int created = 0;
+ for (BlockFace dir : available) {
+ if (created >= splitCount) break;
+
+ int branchMaxSize = MIN_BRANCH_SIZE + random.nextInt(MAX_BRANCH_SIZE - MIN_BRANCH_SIZE);
+
+ if (dir == BlockFace.UP && cy + branchMaxSize > ceilingY) continue;
+ if (dir == BlockFace.DOWN && cy - branchMaxSize < minY) continue;
+
+ queue.add(new Branch(dir, cx, cy, cz, distance, branchMaxSize, false, branch.biome));
+ branchCount[0]++;
+ created++;
+ }
+ plugin.getLogger().info("[MineGen] [" + branch.biome + "] Split at step=" + step
+ + " pos=(" + cx + "," + cy + "," + cz + ") created=" + created
+ + " total=" + branchCount[0]);
+ }
+
+ cx += branch.direction.getModX();
+ cy += branch.direction.getModY();
+ cz += branch.direction.getModZ();
+
+ if (cy < minY || cy > ceilingY + 5) break;
+ }
+ }
+
+ private static Branch findLowestBranch(List branches) {
+ Branch lowest = null;
+ for (Branch b : branches) {
+ if (lowest == null || b.endY < lowest.endY) {
+ lowest = b;
+ }
+ }
+ return lowest;
+ }
+
+ private static List getAvailableDirections(BlockFace current) {
+ List dirs = new ArrayList<>();
+ BlockFace opposite = current.getOppositeFace();
+ for (BlockFace dir : DIRECTIONS) {
+ if (dir != opposite && dir != current) {
+ dirs.add(dir);
+ }
+ }
+ return dirs;
+ }
+
+ // ===================== CROSS SECTION GENERATION =====================
+
+ private static void generateCrossSection(int cx, int cy, int cz, BlockFace direction, int distance,
+ MineBiome biome, List placements, Set fillPositions) {
+ int[][] offsets = getPerpOffsets(direction);
+
+ for (int a = -TUNNEL_RADIUS; a <= TUNNEL_RADIUS; a++) {
+ for (int b = -TUNNEL_RADIUS; b <= TUNNEL_RADIUS; b++) {
+ int bx = cx + offsets[0][0] * a + offsets[1][0] * b;
+ int by = cy + offsets[0][1] * a + offsets[1][1] * b;
+ int bz = cz + offsets[0][2] * a + offsets[1][2] * b;
+
+ long key = posKey(bx, by, bz);
+ if (!fillPositions.add(key)) continue;
+
+ Material mat = pickFillForDistance(distance, biome);
+ placements.add(new BlockPlacement(bx, by, bz, mat));
+ }
+ }
+ }
+
+ // ===================== FILL SELECTION (no ores during generation) =====================
+
+ private static Material pickFillForDistance(int distance, MineBiome biome) {
+ Material[] fills = getFillsForBiome(biome);
+ int[] thresholds = getThresholdsForBiome(biome);
+
+ // Determine primary fill
+ Material primaryFill = fills[0];
+ for (int i = thresholds.length - 1; i >= 0; i--) {
+ if (distance >= thresholds[i]) {
+ primaryFill = fills[i];
+ break;
+ }
+ }
+
+ // Check transition zone
+ Material transitionFill = getTransitionFill(distance, fills, thresholds);
+ if (transitionFill != null && transitionFill != primaryFill) {
+ int borderDist = getDistanceToBorder(distance, thresholds);
+ double blendRatio = 0.5 * (1.0 - (double) Math.abs(borderDist) / TRANSITION_RANGE);
+ if (random.nextDouble() < blendRatio) {
+ primaryFill = transitionFill;
+ }
+ }
+
+ // Roll for special inline blocks (dirt, gravel, lava, mud, infested)
+ Material special = rollSpecialBlock(biome, primaryFill, distance);
+ if (special != null) return special;
+
+ return primaryFill;
+ }
+
+ private static Material rollSpecialBlock(MineBiome biome, Material fill, int distance) {
+ double roll = random.nextDouble();
+ switch (biome) {
+ case NORMAL:
+ if (roll < DIRT_CHANCE) return Material.DIRT;
+ roll -= DIRT_CHANCE;
+ if (roll < GRAVEL_CHANCE) return Material.GRAVEL;
+ roll -= GRAVEL_CHANCE;
+ // Rare infested variants
+ if (roll < INFESTED_CHANCE) {
+ return switch (fill) {
+ case STONE -> Material.INFESTED_STONE;
+ case COBBLESTONE -> Material.INFESTED_COBBLESTONE;
+ default -> null; // andesite, tuff don't have infested variants
+ };
+ }
+ break;
+ case DEEP:
+ // Lava in ALL deep fills, chance increases with distance through the biome
+ // Deep thresholds max out at 80+ (cracked_deepslate_tiles)
+ // Total deep distance range is roughly 0-120
+ double deepProgress = Math.min(1.0, distance / 120.0);
+ double lavaChance = LAVA_CHANCE_DEEP_MIN + (LAVA_CHANCE_DEEP_MAX - LAVA_CHANCE_DEEP_MIN) * deepProgress;
+ if (roll < lavaChance) return Material.LAVA;
+ break;
+ case SUPER_DEEP:
+ if (roll < MUD_CHANCE_SUPER_DEEP) return Material.MUD;
+ roll -= MUD_CHANCE_SUPER_DEEP;
+ if (roll < LAVA_CHANCE_SUPER_DEEP) return Material.LAVA;
+ break;
+ }
+ return null;
+ }
+
+ private static Material getTransitionFill(int distance, Material[] fills, int[] thresholds) {
+ for (int i = 1; i < thresholds.length; i++) {
+ int border = thresholds[i];
+ int diff = distance - border;
+ if (Math.abs(diff) <= TRANSITION_RANGE) {
+ return (diff < 0) ? fills[i] : fills[i - 1];
+ }
+ }
+ return null;
+ }
+
+ private static int getDistanceToBorder(int distance, int[] thresholds) {
+ int closest = Integer.MAX_VALUE;
+ for (int i = 1; i < thresholds.length; i++) {
+ int diff = distance - thresholds[i];
+ if (Math.abs(diff) < Math.abs(closest)) {
+ closest = diff;
+ }
+ }
+ return closest;
+ }
+
+ private static Material[] getFillsForBiome(MineBiome biome) {
+ return switch (biome) {
+ case NORMAL -> NORMAL_FILLS;
+ case DEEP -> DEEP_FILLS;
+ case SUPER_DEEP -> SUPER_DEEP_FILLS;
+ };
+ }
+
+ private static int[] getThresholdsForBiome(MineBiome biome) {
+ return switch (biome) {
+ case NORMAL -> NORMAL_THRESHOLDS;
+ case DEEP -> DEEP_THRESHOLDS;
+ case SUPER_DEEP -> SUPER_DEEP_THRESHOLDS;
+ };
+ }
+
+ // ===================== ORE-ON-BREAK SYSTEM (called from MineWorldManager) =====================
+
+ /**
+ * Detects the deepest fill material among the 6 surrounding blocks to determine
+ * which fill zone the player is in. Then rolls for an ore based on the broken block's
+ * position in the fill sequence relative to the detected zone.
+ *
+ * Ore chances:
+ * 5% for ores from the current (detected) fill
+ * 10% for ores from the previous fill (one step lighter)
+ * 2% for ores from three fills back
+ * 1% for any other fill's ores
+ *
+ * Returns null if no ore was rolled.
+ */
+ public static Material rollOreOnBreak(Block brokenBlock, Material brokenType) {
+ // Detect the deepest fill in the 6 surrounding blocks
+ Material detectedFill = detectDeepestSurroundingFill(brokenBlock);
+ if (detectedFill == null) {
+ // Fallback: use the broken block itself if it's a fill
+ detectedFill = isFillMaterial(brokenType) ? brokenType : Material.STONE;
+ }
+
+ int detectedIndex = ALL_FILLS_ORDERED.indexOf(detectedFill);
+ int brokenIndex = ALL_FILLS_ORDERED.indexOf(brokenType);
+
+ // If the broken block isn't a fill material, no ore roll
+ if (brokenIndex < 0) return null;
+
+ // How many steps back from the detected fill is this broken block?
+ // detectedIndex is the deepest (highest index), brokenIndex is where we are in the sequence
+ int stepsBack = detectedIndex - brokenIndex;
+
+ // Determine ore chance based on distance from detected fill
+ double chance;
+ if (stepsBack == 0) {
+ chance = ORE_CHANCE_CURRENT; // 5% - breaking the detected fill itself
+ } else if (stepsBack == 1) {
+ chance = ORE_CHANCE_PREVIOUS; // 10% - one step lighter
+ } else if (stepsBack == 2) {
+ chance = ORE_CHANCE_THREE_BACK; // 2% - two steps lighter
+ } else {
+ chance = ORE_CHANCE_OTHER; // 1% - anything else
+ }
+
+ // Roll for ore
+ if (random.nextDouble() >= chance) return null;
+
+ // Pick a random ore from the broken block's ore table
+ OreEntry[] oreTable = ORE_TABLES.get(brokenType);
+ if (oreTable == null || oreTable.length == 0) return null;
+
+ return pickWeightedOre(oreTable);
+ }
+
+ /**
+ * Looks at the 6 blocks surrounding the broken block and finds the deepest fill material.
+ */
+ private static Material detectDeepestSurroundingFill(Block block) {
+ Material deepest = null;
+ int deepestIndex = -1;
+
+ for (BlockFace face : DIRECTIONS) {
+ Block neighbor = block.getRelative(face);
+ Material type = neighbor.getType();
+
+ int index = ALL_FILLS_ORDERED.indexOf(type);
+ if (index > deepestIndex) {
+ deepestIndex = index;
+ deepest = type;
+ }
+ }
+
+ return deepest;
+ }
+
+ /**
+ * Picks a random ore from the table using weights.
+ */
+ private static Material pickWeightedOre(OreEntry[] table) {
+ double totalWeight = 0;
+ for (OreEntry e : table) totalWeight += e.weight;
+
+ double roll = random.nextDouble() * totalWeight;
+ double cumulative = 0;
+ for (OreEntry e : table) {
+ cumulative += e.weight;
+ if (roll < cumulative) return e.material;
+ }
+ return table[table.length - 1].material;
+ }
+
+ // ===================== PERPENDICULAR AXES =====================
+
+ private static int[][] getPerpOffsets(BlockFace dir) {
+ return switch (dir) {
+ case UP, DOWN -> new int[][]{{1, 0, 0}, {0, 0, 1}};
+ case NORTH, SOUTH -> new int[][]{{1, 0, 0}, {0, 1, 0}};
+ case EAST, WEST -> new int[][]{{0, 0, 1}, {0, 1, 0}};
+ default -> new int[][]{{1, 0, 0}, {0, 0, 1}};
+ };
+ }
+
+ // ===================== WALL GENERATION =====================
+
+ private static void generateWalls(Set fillPositions, List wallPlacements, int startY) {
+ Set wallPositions = new HashSet<>();
+ plugin.getLogger().info("[MineGen] Generating bedrock walls for " + fillPositions.size() + " fill positions...");
+
+ for (long key : fillPositions) {
+ int x = decodeX(key);
+ int y = decodeY(key);
+ int z = decodeZ(key);
+
+ for (BlockFace face : DIRECTIONS) {
+ int nx = x + face.getModX();
+ int ny = y + face.getModY();
+ int nz = z + face.getModZ();
+
+ long neighborKey = posKey(nx, ny, nz);
+
+ if (!fillPositions.contains(neighborKey)) {
+ if (ny > startY) continue;
+
+ if (wallPositions.add(neighborKey)) {
+ wallPlacements.add(new BlockPlacement(nx, ny, nz, Material.BEDROCK));
+ }
+ }
+ }
+ }
+ }
+
+ // ===================== BLOCK PLACEMENT SCHEDULING =====================
+
+ private static void scheduleBlockPlacements(World world, List fillPlacements,
+ List wallPlacements,
+ int startY, int minY, int maxY) {
+ // Three-phase placement: 1) Dirt placeholder 2) Bedrock walls 3) Actual fill
+ List allPlacements = new ArrayList<>();
+
+ // Phase A: Dirt placeholders at all fill positions
+ List dirtPlaceholders = new ArrayList<>(fillPlacements.size());
+ for (BlockPlacement bp : fillPlacements) {
+ dirtPlaceholders.add(new BlockPlacement(bp.x, bp.y, bp.z, Material.DIRT));
+ }
+ allPlacements.addAll(dirtPlaceholders);
+
+ // Phase B: Bedrock walls
+ allPlacements.addAll(wallPlacements);
+
+ // Phase C: Actual fill (overwrites dirt)
+ allPlacements.addAll(fillPlacements);
+
+ // Air blocks above the entrance opening
+ for (int a = -TUNNEL_RADIUS; a <= TUNNEL_RADIUS; a++) {
+ for (int b = -TUNNEL_RADIUS; b <= TUNNEL_RADIUS; b++) {
+ allPlacements.add(new BlockPlacement(a, startY + 1, b, Material.AIR));
+ }
+ }
+
+ int totalBlocks = allPlacements.size();
+ int tickDelay = 0;
+
+ plugin.getLogger().info("[MineGen] Scheduling " + totalBlocks + " block placements ("
+ + dirtPlaceholders.size() + " placeholder + " + wallPlacements.size() + " walls + "
+ + fillPlacements.size() + " fill) in batches of " + BLOCKS_PER_TICK);
+
+ for (int i = 0; i < totalBlocks; i += BLOCKS_PER_TICK) {
+ int from = i;
+ int to = Math.min(i + BLOCKS_PER_TICK, totalBlocks);
+
+ Bukkit.getScheduler().runTaskLater(plugin, () -> {
+ for (int j = from; j < to; j++) {
+ BlockPlacement bp = allPlacements.get(j);
+ if (bp.y < minY || bp.y >= maxY) continue;
+ world.getBlockAt(bp.x, bp.y, bp.z).setType(bp.material, false);
+ }
+ }, tickDelay);
+
+ tickDelay++;
+ }
+
+ int finalDelay = tickDelay;
+ Bukkit.getScheduler().runTaskLater(plugin,
+ () -> plugin.getLogger().info("Mine regeneration complete. " + totalBlocks + " blocks placed."),
+ finalDelay + 2);
+ }
+
+ // ===================== POSITION ENCODING/DECODING =====================
+
+ private static long posKey(int x, int y, int z) {
+ return ((long) (x + 30000000) & 0x3FFFFFF)
+ | (((long) (y + 2048) & 0xFFF) << 26)
+ | (((long) (z + 30000000) & 0x3FFFFFF) << 38);
+ }
+
+ private static int decodeX(long key) {
+ return (int) (key & 0x3FFFFFF) - 30000000;
+ }
+
+ private static int decodeY(long key) {
+ return (int) ((key >> 26) & 0xFFF) - 2048;
+ }
+
+ private static int decodeZ(long key) {
+ return (int) ((key >> 38) & 0x3FFFFFF) - 30000000;
+ }
+}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/mines/MineWorldManager.java b/src/main/java/xyz/soukup/ecoCraftCore/mines/MineWorldManager.java
new file mode 100644
index 0000000..1a2d8d8
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/mines/MineWorldManager.java
@@ -0,0 +1,304 @@
+package xyz.soukup.ecoCraftCore.mines;
+
+import com.infernalsuite.asp.api.AdvancedSlimePaperAPI;
+import com.infernalsuite.asp.api.world.SlimeWorld;
+import com.infernalsuite.asp.api.world.properties.SlimeProperties;
+import com.infernalsuite.asp.api.world.properties.SlimePropertyMap;
+import org.bukkit.*;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.block.BlockExplodeEvent;
+import org.bukkit.event.entity.EntityExplodeEvent;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.event.player.PlayerChangedWorldEvent;
+import org.bukkit.event.player.PlayerItemDamageEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.Damageable;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.potion.PotionEffectType;
+import xyz.soukup.ecoCraftCore.database.objects.Island;
+import xyz.soukup.ecoCraftCore.islands.DatabaseIslandLoader;
+
+import java.util.function.Consumer;
+
+import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin;
+
+public class MineWorldManager implements Listener {
+
+ public static final String MINE_WORLD_NAME = "mine_world";
+ private static World mineWorld;
+
+ public static void init() {
+ AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance();
+ DatabaseIslandLoader loader = new DatabaseIslandLoader();
+
+ Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
+ try {
+ SlimeWorld slimeWorld = null;
+
+ // Try to load existing world
+ if (loader.worldExists(MINE_WORLD_NAME)) {
+ try {
+ slimeWorld = asp.readWorld(loader, MINE_WORLD_NAME, false, new SlimePropertyMap());
+ } catch (Exception e) {
+ plugin.getLogger().warning("Mine world data is corrupted, recreating...");
+ loader.deleteWorld(MINE_WORLD_NAME);
+ }
+ }
+
+ // Create fresh world if loading failed or didn't exist
+ if (slimeWorld == null) {
+ SlimePropertyMap props = new SlimePropertyMap();
+ props.setValue(SlimeProperties.ENVIRONMENT, "NORMAL");
+
+ Island island = new Island("mine", MINE_WORLD_NAME, "Mine World", "Shared mine world", "server", "system", null);
+ island.save();
+
+ slimeWorld = asp.createEmptyWorld(MINE_WORLD_NAME, false, props, loader);
+ }
+
+ SlimeWorld finalWorld = slimeWorld;
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ asp.loadWorld(finalWorld, true);
+ mineWorld = Bukkit.getWorld(MINE_WORLD_NAME);
+
+ if (mineWorld != null) {
+ mineWorld.setGameRule(GameRule.DO_MOB_SPAWNING, false);
+ mineWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false);
+ mineWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false);
+ mineWorld.setTime(6000);
+ plugin.getLogger().info("Mine world loaded successfully.");
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ plugin.getLogger().severe("Failed to load mine world.");
+ }
+ });
+ }
+
+ public static World getWorld() {
+ return mineWorld;
+ }
+
+ public static Location getSpawnLocation() {
+ if (mineWorld == null) return null;
+ int startY = mineWorld.getMaxHeight() - 10;
+ return new Location(mineWorld, 5, startY + 1, 5);
+ }
+
+ /**
+ * Deletes the current mine world and creates a fresh empty one.
+ * Calls the callback with the new World once it's ready.
+ * Must be called from the main thread with all players already teleported out.
+ */
+ public static void recreateWorld(Consumer callback) {
+ AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance();
+ DatabaseIslandLoader loader = new DatabaseIslandLoader();
+
+ plugin.getLogger().info("[MineWorld] Unloading old mine world...");
+
+ if (mineWorld != null) {
+ Bukkit.unloadWorld(mineWorld, false);
+ mineWorld = null;
+ }
+
+ Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
+ try {
+ if (loader.worldExists(MINE_WORLD_NAME)) {
+ loader.deleteWorld(MINE_WORLD_NAME);
+ plugin.getLogger().info("[MineWorld] Old mine world deleted.");
+ }
+
+ SlimePropertyMap props = new SlimePropertyMap();
+ props.setValue(SlimeProperties.ENVIRONMENT, "NORMAL");
+
+ Island island = new Island("mine", MINE_WORLD_NAME, "Mine World", "Shared mine world", "server", "system", null);
+ island.save();
+
+ SlimeWorld slimeWorld = asp.createEmptyWorld(MINE_WORLD_NAME, false, props, loader);
+
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ asp.loadWorld(slimeWorld, true);
+ mineWorld = Bukkit.getWorld(MINE_WORLD_NAME);
+
+ if (mineWorld != null) {
+ mineWorld.setGameRule(GameRule.DO_MOB_SPAWNING, false);
+ mineWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false);
+ mineWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false);
+ mineWorld.setTime(6000);
+ plugin.getLogger().info("[MineWorld] Fresh mine world created and loaded.");
+ callback.accept(mineWorld);
+ } else {
+ plugin.getLogger().severe("[MineWorld] Failed to load fresh mine world.");
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ plugin.getLogger().severe("[MineWorld] Failed to recreate mine world.");
+ }
+ });
+ }
+
+ private boolean isInMineWorld(Player player) {
+ return player.getWorld().equals(mineWorld);
+ }
+
+ private boolean isInMineWorld(World world) {
+ return world.equals(mineWorld);
+ }
+
+ // Apply invisible mining fatigue when entering mine world
+ @EventHandler
+ public void onWorldChange(PlayerChangedWorldEvent event) {
+ Player player = event.getPlayer();
+
+ // Entering mine world
+ if (isInMineWorld(player)) {
+ applyMiningFatigue(player);
+ }
+
+ // Leaving mine world
+ if (isInMineWorld(event.getFrom())) {
+ player.removePotionEffect(PotionEffectType.MINING_FATIGUE);
+ }
+ }
+
+ // Re-apply fatigue on join if player is in mine world
+ @EventHandler
+ public void onJoin(PlayerJoinEvent event) {
+ Player player = event.getPlayer();
+ if (isInMineWorld(player)) {
+ applyMiningFatigue(player);
+ }
+ }
+
+ // Re-apply fatigue on respawn in mine world
+ @EventHandler
+ public void onDeath(PlayerDeathEvent event) {
+ Bukkit.getScheduler().runTaskLater(plugin, () -> {
+ Player player = event.getPlayer();
+ if (isInMineWorld(player)) {
+ applyMiningFatigue(player);
+ }
+ }, 1L);
+ }
+
+ private void applyMiningFatigue(Player player) {
+ player.addPotionEffect(new PotionEffect(
+ PotionEffectType.MINING_FATIGUE,
+ Integer.MAX_VALUE,
+ 0, // level I (amplifier 0)
+ true, // ambient
+ false, // no particles
+ false // no icon
+ ));
+ }
+
+ // Double tool durability damage in mine world
+ @EventHandler
+ public void onItemDamage(PlayerItemDamageEvent event) {
+ if (!isInMineWorld(event.getPlayer())) return;
+ if (event.getItem().getType().getMaxDurability() <= 0) return;
+ event.setDamage(event.getDamage() * 2);
+ }
+
+ // Prevent entity explosions from destroying bedrock walls
+ @EventHandler
+ public void onEntityExplode(EntityExplodeEvent event) {
+ if (!isInMineWorld(event.getEntity().getWorld())) return;
+ event.blockList().removeIf(block -> block.getType() == Material.BEDROCK);
+ }
+
+ // Prevent block explosions from destroying bedrock walls
+ @EventHandler
+ public void onBlockExplode(BlockExplodeEvent event) {
+ if (!isInMineWorld(event.getBlock().getWorld())) return;
+ event.blockList().removeIf(block -> block.getType() == Material.BEDROCK);
+ }
+
+ // Block break logic: bedrock unbreakable, ore-on-break for ALL fills, mining sequence
+ @EventHandler
+ public void onBlockBreak(BlockBreakEvent event) {
+ if (!isInMineWorld(event.getPlayer())) return;
+
+ Material type = event.getBlock().getType();
+
+ // Bedrock is completely unbreakable (mine walls)
+ if (type == Material.BEDROCK) {
+ event.setCancelled(true);
+ return;
+ }
+
+ // For any fill material (at any stage of the mining sequence), roll for ore first.
+ // The ore replaces the block as another mining step — player then breaks the ore normally.
+ if (MineManager.isFillMaterial(type)) {
+ Material ore = MineManager.rollOreOnBreak(event.getBlock(), type);
+ if (ore != null) {
+ event.setCancelled(true);
+ event.getBlock().setType(ore);
+ event.getBlock().getState().update(true);
+ applyToolDamage(event);
+ return;
+ }
+ }
+
+ // No ore rolled — apply normal mining sequence (deeper fills degrade into lighter fills)
+ Material nextTier = getNextTier(type);
+ if (nextTier != null) {
+ event.setCancelled(true);
+ event.getBlock().setType(nextTier);
+ event.getBlock().getState().update(true);
+ applyToolDamage(event);
+ return;
+ }
+
+ // Terminal fill (stone) or ores — break normally
+ }
+
+ /**
+ * Applies doubled durability damage to the player's tool when we cancel a break event.
+ * Only applies if the held item actually has durability.
+ */
+ private void applyToolDamage(BlockBreakEvent event) {
+ ItemStack tool = event.getPlayer().getInventory().getItemInMainHand();
+ if (tool.getType().isAir() || tool.getType().getMaxDurability() <= 0) return;
+ if (!(tool.getItemMeta() instanceof Damageable damageable)) return;
+
+ damageable.setDamage(damageable.getDamage() + 2); // doubled durability
+ tool.setItemMeta(damageable);
+
+ if (damageable.getDamage() >= tool.getType().getMaxDurability()) {
+ event.getPlayer().getInventory().setItemInMainHand(null);
+ event.getPlayer().playSound(event.getPlayer().getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0f, 1.0f);
+ }
+ }
+
+ /**
+ * Mining sequence: deeper fills degrade into lighter fills.
+ * Returns null when the block should break normally (terminal fill = stone).
+ *
+ * Chain: gilded_blackstone -> cracked_polished_blackstone_bricks -> blackstone -> smooth_basalt
+ * -> cracked_deepslate_tiles -> cobbled_deepslate -> deepslate -> tuff -> cobblestone
+ * -> andesite -> stone -> null (breaks normally)
+ */
+ private Material getNextTier(Material material) {
+ return switch (material) {
+ case GILDED_BLACKSTONE -> Material.CRACKED_POLISHED_BLACKSTONE_BRICKS;
+ case CRACKED_POLISHED_BLACKSTONE_BRICKS -> Material.BLACKSTONE;
+ case BLACKSTONE -> Material.SMOOTH_BASALT;
+ case SMOOTH_BASALT -> Material.CRACKED_DEEPSLATE_TILES;
+ case CRACKED_DEEPSLATE_TILES -> Material.COBBLED_DEEPSLATE;
+ case COBBLED_DEEPSLATE -> Material.DEEPSLATE;
+ case DEEPSLATE -> Material.TUFF;
+ case TUFF -> Material.COBBLESTONE;
+ case COBBLESTONE -> Material.ANDESITE;
+ case ANDESITE -> Material.STONE;
+ default -> null; // Stone and non-fill blocks break normally
+ };
+ }
+}
+
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/money/MoneyCommand.java b/src/main/java/xyz/soukup/ecoCraftCore/money/MoneyCommand.java
index 2590e1c..8129bc1 100644
--- a/src/main/java/xyz/soukup/ecoCraftCore/money/MoneyCommand.java
+++ b/src/main/java/xyz/soukup/ecoCraftCore/money/MoneyCommand.java
@@ -19,7 +19,7 @@ import xyz.soukup.ecoCraftCore.messages.PHHM;
public class MoneyCommand {
- public static LiteralArgumentBuilder createCommand() {
+ public static LiteralArgumentBuilder getCommand() {
// 1. Send Branch
LiteralArgumentBuilder send = Commands.literal("send")
.requires(source -> source.getSender() instanceof Player)
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/player/OnKill.java b/src/main/java/xyz/soukup/ecoCraftCore/player/OnKill.java
new file mode 100644
index 0000000..51bfd46
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/player/OnKill.java
@@ -0,0 +1,27 @@
+package xyz.soukup.ecoCraftCore.player;
+
+import com.destroystokyo.paper.event.player.PlayerPostRespawnEvent;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.event.player.PlayerRespawnEvent;
+import xyz.soukup.ecoCraftCore.islands.IslandManager;
+
+import static xyz.soukup.ecoCraftCore.EcoCraftCore.config;
+
+public class OnKill implements Listener {
+ @EventHandler
+ public void spawnTeleportOnKill(PlayerPostRespawnEvent event){
+ Player player = event.getPlayer();
+
+ String uuid = config.getString("islands.spawn");
+
+ IslandManager islandManager = new IslandManager();
+ try {
+ islandManager.teleport(player, uuid);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/player/TeleportRequestsHandler.java b/src/main/java/xyz/soukup/ecoCraftCore/player/TeleportRequestsHandler.java
new file mode 100644
index 0000000..56a7c25
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/player/TeleportRequestsHandler.java
@@ -0,0 +1,50 @@
+package xyz.soukup.ecoCraftCore.player;
+
+import com.google.common.eventbus.DeadEvent;
+import com.j256.ormlite.stmt.QueryBuilder;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import xyz.soukup.ecoCraftCore.database.DaoRegistry;
+import xyz.soukup.ecoCraftCore.database.objects.TeleportRequest;
+import xyz.soukup.ecoCraftCore.islands.IslandManager;
+
+import java.sql.SQLException;
+
+import static xyz.soukup.ecoCraftCore.EcoCraftCore.config;
+
+public class TeleportRequestsHandler implements Listener {
+
+ @EventHandler
+ public void teleportRequestHandler(PlayerJoinEvent event){
+ try {
+ Player player = event.getPlayer();
+
+ QueryBuilder queryBuilder = DaoRegistry.getTeleportRequestsDao().queryBuilder();
+ queryBuilder.where().eq("player", player.getName());
+
+ TeleportRequest teleportRequest = queryBuilder.queryForFirst();
+
+ IslandManager islandManager = new IslandManager();
+
+
+ if (teleportRequest == null){
+ islandManager.teleport(player, config.getString("islands.spawn"));
+ return;
+ }
+
+
+ islandManager.teleportLocally(player, teleportRequest.getWorld(), teleportRequest.getX(), teleportRequest.getY(), teleportRequest.getY(), teleportRequest.getYaw(), teleportRequest.getPitch());
+
+ teleportRequest.delete();
+
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/positionMarker/RulerCommand.java b/src/main/java/xyz/soukup/ecoCraftCore/positionMarker/MarkerCommand.java
similarity index 79%
rename from src/main/java/xyz/soukup/ecoCraftCore/positionMarker/RulerCommand.java
rename to src/main/java/xyz/soukup/ecoCraftCore/positionMarker/MarkerCommand.java
index dd09626..d70f180 100644
--- a/src/main/java/xyz/soukup/ecoCraftCore/positionMarker/RulerCommand.java
+++ b/src/main/java/xyz/soukup/ecoCraftCore/positionMarker/MarkerCommand.java
@@ -11,22 +11,21 @@ import org.bukkit.inventory.ItemStack;
import xyz.soukup.ecoCraftCore.messages.Messages;
import xyz.soukup.ecoCraftCore.utilities.PDC;
-public class RulerCommand {
- public static LiteralArgumentBuilder createCommand() {
+public class MarkerCommand {
+ public static LiteralArgumentBuilder getCommand() {
return Commands.literal("ruler")
- .executes(RulerCommand::obtainRuler);
+ .executes(MarkerCommand::obtainRuler);
}
private static int obtainRuler(CommandContext context){
CommandSender commandSender = context.getSource().getSender();
- if (!(commandSender instanceof Player)){
+ if (!(commandSender instanceof Player player)){
Messages.send(commandSender, "generic.error.not-player");
return 0;
}
- Player player = (Player) commandSender;
ItemStack itemStack = new ItemStack(Material.BLAZE_ROD);
PDC.set(itemStack, "ruler", true);
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/positionMarker/RulerMarking.java b/src/main/java/xyz/soukup/ecoCraftCore/positionMarker/MarkerEvent.java
similarity index 98%
rename from src/main/java/xyz/soukup/ecoCraftCore/positionMarker/RulerMarking.java
rename to src/main/java/xyz/soukup/ecoCraftCore/positionMarker/MarkerEvent.java
index 2f47d46..caba5ab 100644
--- a/src/main/java/xyz/soukup/ecoCraftCore/positionMarker/RulerMarking.java
+++ b/src/main/java/xyz/soukup/ecoCraftCore/positionMarker/MarkerEvent.java
@@ -16,7 +16,7 @@ import xyz.soukup.ecoCraftCore.messages.PHHM;
import java.util.HashMap;
-public class RulerMarking implements Listener {
+public class MarkerEvent implements Listener {
public static HashMap primaryLocations = new HashMap<>();
public static HashMap secondaryLocations = new HashMap<>();
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/regions/RegionAdminCommand.java b/src/main/java/xyz/soukup/ecoCraftCore/regions/RegionAdminCommand.java
new file mode 100644
index 0000000..a9c9b6d
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/regions/RegionAdminCommand.java
@@ -0,0 +1,97 @@
+package xyz.soukup.ecoCraftCore.regions;
+
+import com.j256.ormlite.stmt.QueryBuilder;
+import com.mojang.brigadier.arguments.IntegerArgumentType;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.context.CommandContext;
+import io.papermc.paper.command.brigadier.CommandSourceStack;
+import io.papermc.paper.command.brigadier.Commands;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import xyz.soukup.ecoCraftCore.database.DaoRegistry;
+import xyz.soukup.ecoCraftCore.database.objects.Region;
+import xyz.soukup.ecoCraftCore.messages.Messages;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin;
+
+public class RegionAdminCommand {
+ public static LiteralArgumentBuilder getCommand() {
+ LiteralArgumentBuilder create = Commands.literal("create")
+ .then(Commands.argument("type", IntegerArgumentType.integer(0, 1))
+ .executes(RegionAdminCommand::createRegion));
+
+ LiteralArgumentBuilder addMember = Commands.literal("addMember")
+ .then(Commands.argument("id", IntegerArgumentType.integer())
+ .suggests((context, builder) -> {
+ try {
+ QueryBuilder queryBuilder = DaoRegistry.getRegionDao().queryBuilder();
+ queryBuilder.selectColumns("id");
+ List regions = queryBuilder.query();
+ for (Region region : regions) {
+ builder.suggest(region.getId());
+ }
+ return builder.buildFuture();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ })
+ .then(Commands.argument("player", StringArgumentType.word())
+ .suggests((context, builder) -> {
+ for (Player onlinePlayer : plugin.getServer().getOnlinePlayers()) {
+ builder.suggest(onlinePlayer.getName());
+ }
+ return builder.buildFuture();
+ })
+ .then(Commands.argument("membership", StringArgumentType.word())
+ .suggests(((context, builder) -> {
+ builder.suggest("owner");
+ builder.suggest("editor");
+ builder.suggest("member");
+ return builder.buildFuture();
+ }))
+ .executes(RegionAdminCommand::addMember))));
+
+
+ return Commands.literal("region")
+ .then(create)
+ .then(addMember);
+ }
+
+ private static int createRegion(CommandContext context) {
+ Integer type = context.getArgument("type", Integer.class);
+ if(!(context.getSource().getSender() instanceof Player player)){
+ Messages.send(context.getSource().getSender(), "generic.error.not-player");
+ return 0;
+ };
+
+ int status = RegionManager.createRegion(player, type, player.getName(), "player");
+
+ switch (status){
+ case 0 -> Messages.send(player, "generic.success.created");
+ case 1 -> Messages.send(player, "region.error.not-marked");
+ case 2 -> Messages.send(player, "region.error.not-same-world");
+ }
+
+ return 0;
+ }
+
+ private static int addMember(CommandContext context) {
+ Integer id = context.getArgument("id", Integer.class);
+ String player = context.getArgument("player", String.class);
+ String membership = context.getArgument("membership", String.class);
+ CommandSender commandSender = context.getSource().getSender();
+
+ int status = RegionManager.addMember(id, "player", player, membership);
+ switch (status){
+ case 0 -> Messages.send(commandSender, "generic.success.created");
+ case 1 -> Messages.send(commandSender, "region.error.not-exist");
+ }
+ return 0;
+ }
+
+
+}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/regions/RegionEvents.java b/src/main/java/xyz/soukup/ecoCraftCore/regions/RegionEvents.java
new file mode 100644
index 0000000..151a597
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/regions/RegionEvents.java
@@ -0,0 +1,133 @@
+package xyz.soukup.ecoCraftCore.regions;
+
+import org.bukkit.block.Block;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.block.BlockPlaceEvent;
+import org.bukkit.event.player.*;
+import org.bukkit.inventory.ItemStack;
+import xyz.soukup.ecoCraftCore.database.objects.Region;
+import xyz.soukup.ecoCraftCore.database.objects.RegionMember;
+
+public class RegionEvents implements Listener {
+
+ @EventHandler
+ public void onPlayerInteract(PlayerInteractEvent event){
+ switch (event.getAction()) {
+ case RIGHT_CLICK_BLOCK:
+ case LEFT_CLICK_BLOCK:
+ break;
+ default:
+ return;
+ }
+
+
+ Block block = event.getClickedBlock();
+ ItemStack item = event.getItem();
+ if (block == null) return;
+
+ if (item != null && item.getType().isBlock() && !block.getType().isInteractable()) {
+ return;
+ }
+
+ boolean allowed = isAllowedToInteract(
+ block.getWorld().getName(),
+ event.getPlayer(),
+ block.getX(),
+ block.getY(),
+ block.getZ()
+ );
+
+ event.setCancelled(!allowed);
+ }
+
+ @EventHandler
+ public void onBlockPlace(BlockPlaceEvent event) {
+ Block block = event.getBlockPlaced();
+
+ boolean allowed = isAllowedToInteract(
+ block.getWorld().getName(),
+ event.getPlayer(),
+ block.getX(),
+ block.getY(),
+ block.getZ()
+ );
+
+ event.setCancelled(!allowed);
+ }
+
+ @EventHandler
+ public void onBlockBreak(BlockBreakEvent event) {
+
+ Block block = event.getBlock();
+
+ boolean allowed = isAllowedToInteract(
+ block.getWorld().getName(),
+ event.getPlayer(),
+ block.getX(),
+ block.getY(),
+ block.getZ()
+ );
+
+ event.setCancelled(!allowed);
+ }
+
+ @EventHandler
+ public void onBucketUse(PlayerBucketEmptyEvent event) {
+ Block clickedBlock = event.getBlockClicked();
+
+ boolean allowed = isAllowedToInteract(
+ clickedBlock.getWorld().getName(),
+ event.getPlayer(),
+ clickedBlock.getX(),
+ clickedBlock.getY(),
+ clickedBlock.getZ()
+ );
+
+ event.setCancelled(!allowed);
+ }
+
+ @EventHandler
+ public void onBucketUse(PlayerBucketFillEvent event) {
+ Block clickedBlock = event.getBlockClicked();
+
+ boolean allowed = isAllowedToInteract(
+ clickedBlock.getWorld().getName(),
+ event.getPlayer(),
+ clickedBlock.getX(),
+ clickedBlock.getY(),
+ clickedBlock.getZ()
+ );
+
+ event.setCancelled(!allowed);
+ }
+
+
+
+ private boolean isAllowedToInteract(String island, Player player, int x, int y, int z){
+ if (player.isOp()){
+ return true;
+ }
+
+ Region region = Region.findRegion(x, y, z, island);
+
+ if (region == null){
+ return false;
+ }
+
+ String name = player.getName();
+
+ for (RegionMember regionMember : region.getRegionMembers()){
+
+ if (regionMember.getMembertype().equals("player") && regionMember.getName().equals(name)){
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/regions/RegionManager.java b/src/main/java/xyz/soukup/ecoCraftCore/regions/RegionManager.java
new file mode 100644
index 0000000..14c480d
--- /dev/null
+++ b/src/main/java/xyz/soukup/ecoCraftCore/regions/RegionManager.java
@@ -0,0 +1,48 @@
+package xyz.soukup.ecoCraftCore.regions;
+
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import xyz.soukup.ecoCraftCore.database.objects.Region;
+import xyz.soukup.ecoCraftCore.positionMarker.MarkerEvent;
+
+public class RegionManager {
+ public static int createRegion(Player player, Integer type, String owner, String ownerType){
+ Location primaryLocation = MarkerEvent.primaryLocations.get(player);
+ Location secondaryLocation = MarkerEvent.secondaryLocations.get(player);
+
+ if (primaryLocation == null || secondaryLocation == null){
+ return 1;
+ }
+
+ if (primaryLocation.getWorld() != secondaryLocation.getWorld()){
+ return 2;
+ }
+
+ String worldName = primaryLocation.getWorld().getName();
+ int x1 = primaryLocation.getBlockX();
+ int y1 = primaryLocation.getBlockY();
+ int z1 = primaryLocation.getBlockZ();
+ int x2 = secondaryLocation.getBlockX();
+ int y2 = secondaryLocation.getBlockY();
+ int z2 = secondaryLocation.getBlockZ();
+
+ Region region = new Region(worldName, type, x1, y1, z1, x2, y2, z2);
+ region.save();
+
+ region.addRegionMember(ownerType, owner, "owner");
+
+ return 0;
+ }
+
+ public static int addMember(int id, String memberType, String member, String membershipType){
+ Region region = Region.findById(id);
+
+ if (region == null){
+ return 1;
+ }
+
+ region.addRegionMember(memberType, member, membershipType);
+
+ return 0;
+ }
+}
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/shop/ShopCommand.java b/src/main/java/xyz/soukup/ecoCraftCore/shop/ShopCommand.java
index 591003e..c53fbe0 100644
--- a/src/main/java/xyz/soukup/ecoCraftCore/shop/ShopCommand.java
+++ b/src/main/java/xyz/soukup/ecoCraftCore/shop/ShopCommand.java
@@ -9,17 +9,16 @@ import org.bukkit.block.Chest;
import org.bukkit.block.Sign;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
-import xyz.soukup.ecoCraftCore.positionMarker.RulerMarking;
+import xyz.soukup.ecoCraftCore.positionMarker.MarkerEvent;
import xyz.soukup.ecoCraftCore.database.objects.Shop;
import xyz.soukup.ecoCraftCore.inventory.VirtualChest;
import xyz.soukup.ecoCraftCore.inventory.InventoryUtils;
import xyz.soukup.ecoCraftCore.messages.Messages;
import xyz.soukup.ecoCraftCore.utilities.PDC;
-@SuppressWarnings("UnstableApiUsage")
public class ShopCommand {
- public static LiteralArgumentBuilder createCommand() {
+ public static LiteralArgumentBuilder getCommand() {
// Define the argument types
var amountArg = Commands.argument("amount", IntegerArgumentType.integer(1));
var buyPriceArg = Commands.argument("buy_price", FloatArgumentType.floatArg(0.0F));
@@ -52,8 +51,8 @@ public class ShopCommand {
}
// 1. Check if blocks are marked
- Chest chest = RulerMarking.chests.get(player);
- Sign sign = RulerMarking.signs.get(player);
+ Chest chest = MarkerEvent.chests.get(player);
+ Sign sign = MarkerEvent.signs.get(player);
if (sign == null || chest == null) {
Messages.send(player, "shop.error.not-marked");
diff --git a/src/main/java/xyz/soukup/ecoCraftCore/utilities/PDC.java b/src/main/java/xyz/soukup/ecoCraftCore/utilities/PDC.java
index 0d14c28..a79c19e 100644
--- a/src/main/java/xyz/soukup/ecoCraftCore/utilities/PDC.java
+++ b/src/main/java/xyz/soukup/ecoCraftCore/utilities/PDC.java
@@ -9,11 +9,15 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataHolder;
import org.bukkit.persistence.PersistentDataType;
+import xyz.soukup.ecoCraftCore.EcoCraftCore;
+
+import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin;
@SuppressWarnings({"unchecked", "rawtypes"})
public class PDC {
-
-
+
+
+
//TileState
public static void set(TileState tileState, String key, Integer value){
@@ -65,7 +69,7 @@ public class PDC {
return (String) get(itemStack, key, PersistentDataType.STRING);
}
- private static Object get(ItemStack itemStack, String key, PersistentDataType persistentDataType){
+ public static Object get(ItemStack itemStack, String key, PersistentDataType persistentDataType){
ItemMeta itemMeta = itemStack.getItemMeta();
return getUniversal(itemMeta, key, persistentDataType);
}
@@ -80,15 +84,15 @@ public class PDC {
//Univerzální set & get
- private static Object getUniversal(PersistentDataHolder persistentDataHolder, String key, PersistentDataType persistentDataType){
+ public static Object getUniversal(PersistentDataHolder persistentDataHolder, String key, PersistentDataType persistentDataType){
PersistentDataContainer persistentDataContainer = persistentDataHolder.getPersistentDataContainer();
- NamespacedKey namespacedKey = new NamespacedKey("ecc", key);
+ NamespacedKey namespacedKey = new NamespacedKey(plugin, key);
return persistentDataContainer.get(namespacedKey, persistentDataType);
}
- private static void setUniversal(PersistentDataHolder persistentDataHolder, String key, Object value, PersistentDataType persistentDataType){
+ public static void setUniversal(PersistentDataHolder persistentDataHolder, String key, Object value, PersistentDataType persistentDataType){
PersistentDataContainer persistentDataContainer = persistentDataHolder.getPersistentDataContainer();
- NamespacedKey namespacedKey = new NamespacedKey("ecc", key);
+ NamespacedKey namespacedKey = new NamespacedKey(plugin, key);
persistentDataContainer.set(namespacedKey, persistentDataType, value);
}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index af80369..3d9ced4 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -9,4 +9,6 @@ database:
password: "ecc"
database: "ecc"
cache:
- save-interval: 6000
\ No newline at end of file
+ save-interval: 6000
+islands:
+ spawn: null
\ No newline at end of file
diff --git a/src/main/resources/island_templates/flat_grass.slime b/src/main/resources/island_templates/flat_grass.slime
new file mode 100644
index 0000000..7345ce4
Binary files /dev/null and b/src/main/resources/island_templates/flat_grass.slime differ
diff --git a/src/main/resources/island_templates/flat_hell.slime b/src/main/resources/island_templates/flat_hell.slime
new file mode 100644
index 0000000..b047926
Binary files /dev/null and b/src/main/resources/island_templates/flat_hell.slime differ
diff --git a/src/main/resources/island_templates/flat_sand.slime b/src/main/resources/island_templates/flat_sand.slime
new file mode 100644
index 0000000..7874e7e
Binary files /dev/null and b/src/main/resources/island_templates/flat_sand.slime differ
diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml
index 36219f0..27f75fc 100644
--- a/src/main/resources/messages.yml
+++ b/src/main/resources/messages.yml
@@ -1,4 +1,6 @@
generic:
+ success:
+ creat ed: Vytvořeno.
error:
not-player: "Na tuto akci musíš být hráč"
no-funds:
@@ -10,6 +12,10 @@ generic:
no-item:
self: "Nemáš dostatek itemů"
shop: "Obchod nemá dostatek itemů"
+region:
+ error:
+ not-marked: "Musíš nejprve označit pozice"
+ not-exist: "Region neexistuje"
shop:
error:
already-shop: "Tato cedule již je obchod"
@@ -44,6 +50,12 @@ marker:
primary: "První pozice označena (,,)"
secondary: "Druhá pozice označena (,,)"
menu:
+ island-selector:
+ title: "Výběr ostrovu"
+ my-islands: "Moje ostrovy"
+ shared-islands: "Ostrovy sdílené se mnou"
+ public-islands: "Veřejné Ostrovy"
+ all-islands: "Všechny ostrovy (Pouze OP)"
shop:
buy: "Koupit ks za $"
sell: "Prodat ks za $"
@@ -65,4 +77,11 @@ gui:
title: "Obchod"
buy: "Koupit ks za $"
sell: "Prodat ks za $"
+mine:
+ regenerating: "Regenerace dolů začala..."
+ regenerate-complete: "Regenerace dolů dokončena."
+ teleporting: "Teleportuješ se do dolů."
+ teleported-out: "Byl jsi teleportován z dolů kvůli regeneraci."
+ error:
+ no-world: "Důlní svět není načtený."
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 5868d64..2c14e67 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -2,3 +2,6 @@ name: EcoCraftCore
version: '1.0-SNAPSHOT'
main: xyz.soukup.ecoCraftCore.EcoCraftCore
api-version: '1.21'
+depend:
+ - packetevents
+