Merge pull request 'island-experimenty' (#11) from island-experimenty into master

Reviewed-on: #11
master
jakub 1 month ago
commit 8126729ea7
  1. 18
      pom.xml
  2. 239
      src/main/java/xyz/soukup/ecoCraftCore/EcoCraftCore.java
  3. 35
      src/main/java/xyz/soukup/ecoCraftCore/database/DaoRegistry.java
  4. 34
      src/main/java/xyz/soukup/ecoCraftCore/database/objects/ActiveServer.java
  5. 27
      src/main/java/xyz/soukup/ecoCraftCore/database/objects/Island.java
  6. 213
      src/main/java/xyz/soukup/ecoCraftCore/database/objects/Region.java
  7. 65
      src/main/java/xyz/soukup/ecoCraftCore/database/objects/RegionMember.java
  8. 96
      src/main/java/xyz/soukup/ecoCraftCore/database/objects/TeleportRequest.java
  9. 15
      src/main/java/xyz/soukup/ecoCraftCore/gui/GuiItemBuilder.java
  10. 257
      src/main/java/xyz/soukup/ecoCraftCore/islands/ChunkModifier.java
  11. 4
      src/main/java/xyz/soukup/ecoCraftCore/islands/DatabaseIslandLoader.java
  12. 72
      src/main/java/xyz/soukup/ecoCraftCore/islands/FileIslandLoader.java
  13. 324
      src/main/java/xyz/soukup/ecoCraftCore/islands/IslandAdminCommand.java
  14. 32
      src/main/java/xyz/soukup/ecoCraftCore/islands/IslandCommand.java
  15. 448
      src/main/java/xyz/soukup/ecoCraftCore/islands/IslandManager.java
  16. 188
      src/main/java/xyz/soukup/ecoCraftCore/islands/IslandSelectorCommand.java
  17. 2
      src/main/java/xyz/soukup/ecoCraftCore/islands/UnloadIsland.java
  18. 65
      src/main/java/xyz/soukup/ecoCraftCore/mines/MineCommand.java
  19. 696
      src/main/java/xyz/soukup/ecoCraftCore/mines/MineManager.java
  20. 304
      src/main/java/xyz/soukup/ecoCraftCore/mines/MineWorldManager.java
  21. 2
      src/main/java/xyz/soukup/ecoCraftCore/money/MoneyCommand.java
  22. 27
      src/main/java/xyz/soukup/ecoCraftCore/player/OnKill.java
  23. 50
      src/main/java/xyz/soukup/ecoCraftCore/player/TeleportRequestsHandler.java
  24. 9
      src/main/java/xyz/soukup/ecoCraftCore/positionMarker/MarkerCommand.java
  25. 2
      src/main/java/xyz/soukup/ecoCraftCore/positionMarker/MarkerEvent.java
  26. 97
      src/main/java/xyz/soukup/ecoCraftCore/regions/RegionAdminCommand.java
  27. 133
      src/main/java/xyz/soukup/ecoCraftCore/regions/RegionEvents.java
  28. 48
      src/main/java/xyz/soukup/ecoCraftCore/regions/RegionManager.java
  29. 9
      src/main/java/xyz/soukup/ecoCraftCore/shop/ShopCommand.java
  30. 18
      src/main/java/xyz/soukup/ecoCraftCore/utilities/PDC.java
  31. 4
      src/main/resources/config.yml
  32. BIN
      src/main/resources/island_templates/flat_grass.slime
  33. BIN
      src/main/resources/island_templates/flat_hell.slime
  34. BIN
      src/main/resources/island_templates/flat_sand.slime
  35. 19
      src/main/resources/messages.yml
  36. 3
      src/main/resources/plugin.yml

@ -82,11 +82,16 @@
<resource> <resource>
<directory>src/main/resources</directory> <directory>src/main/resources</directory>
<filtering>true</filtering> <filtering>true</filtering>
</resource> </resource>
</resources> </resources>
</build> </build>
<repositories> <repositories>
<repository>
<id>codemc-releases</id>
<url>https://repo.codemc.io/repository/maven-releases/</url>
</repository>
<repository> <repository>
<id>is-releases</id> <id>is-releases</id>
<url>https://repo.infernalsuite.com/repository/maven-releases/</url> <url>https://repo.infernalsuite.com/repository/maven-releases/</url>
@ -103,6 +108,8 @@
<id>jitpack.io</id> <id>jitpack.io</id>
<url>https://jitpack.io</url> <url>https://jitpack.io</url>
</repository> </repository>
</repositories> </repositories>
<dependencies> <dependencies>
@ -143,5 +150,16 @@
<artifactId>MatrixColorAPI</artifactId> <artifactId>MatrixColorAPI</artifactId>
<version>v1.0.7</version> <version>v1.0.7</version>
</dependency> </dependency>
<dependency>
<groupId>com.github.retrooper</groupId>
<artifactId>packetevents-spigot</artifactId>
<version>2.11.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>6.2.1</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

@ -1,24 +1,47 @@
package xyz.soukup.ecoCraftCore; 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.dao.DaoManager;
import com.j256.ormlite.jdbc.DataSourceConnectionSource;
import com.j256.ormlite.jdbc.JdbcConnectionSource; import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.stmt.UpdateBuilder;
import com.j256.ormlite.support.ConnectionSource; import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils; 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 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.Plugin;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull; 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.money.MoneyCommand;
import xyz.soukup.ecoCraftCore.player.OnKill;
import xyz.soukup.ecoCraftCore.player.PreparePlayer; 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.shop.ShopCommand;
import xyz.soukup.ecoCraftCore.database.objects.Account; 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.Shop;
import xyz.soukup.ecoCraftCore.database.objects.Transaction; import xyz.soukup.ecoCraftCore.database.objects.Transaction;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
import xyz.soukup.ecoCraftCore.inventory.VirtualChest; 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.database.DaoRegistry;
import xyz.soukup.ecoCraftCore.messages.Messages; import xyz.soukup.ecoCraftCore.messages.Messages;
import xyz.soukup.ecoCraftCore.shop.ShopLogic; import xyz.soukup.ecoCraftCore.shop.ShopLogic;
@ -29,28 +52,43 @@ import java.sql.SQLException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import com.github.retrooper.packetevents.PacketEvents;
import xyz.soukup.ecoCraftCore.islands.ChunkModifier;
public final class EcoCraftCore extends JavaPlugin { public final class EcoCraftCore extends JavaPlugin {
public static EcoCraftCore plugin; public static EcoCraftCore plugin;
public static ConnectionSource connectionSource; public static ConnectionSource connectionSource;
public static FileConfiguration config;
@Override @Override
public void onEnable() { public void onEnable() {
this.getLogger().info("plugin starting out"); this.getLogger().info("plugin starting out");
plugin = this; plugin = this;
config = getConfig();
this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
PacketEvents.getAPI().load();
PacketEvents.getAPI().init();
try { try {
plugin.saveConfig();
saveIslandTemplates();
Messages.init(); Messages.init();
prepareDatabase(); prepareDatabase();
registerCommands(); registerCommands();
registerEvents(); registerEvents();
} catch (SQLException e) { prepareSlimeWorldsSaver();
e.printStackTrace();
getLogger().severe("Failed to initialize database."); }catch (IOException e) {
} catch (IOException e) { MineWorldManager.init();
throw new RuntimeException(e);
} }
this.getServer().getScheduler().runTaskTimer(this, /* Lambda: */ task -> { this.getServer().getScheduler().runTaskTimer(this, /* Lambda: */ task -> {
@ -62,53 +100,196 @@ public final class EcoCraftCore extends JavaPlugin {
@Override @Override
public void onDisable() { public void onDisable() {
plugin.saveConfig();
VirtualChest.saveCache(); VirtualChest.saveCache();
saveSlimeWorlds();
adiosDatabase();
try { PacketEvents.getAPI().terminate();
if (connectionSource != null) {
connectionSource.close(); }
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) { }, 12000L, 12000L);
e.printStackTrace(); }
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<Island, Integer> 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 { private void saveIslandTemplates(){
String databaseUrl = "jdbc:mysql://localhost:3306/ecc"; plugin.saveResource("island_templates/flat_grass.slime", false);
connectionSource = new JdbcConnectionSource(databaseUrl, "ecc", "ecc"); plugin.saveResource("island_templates/flat_sand.slime", false);
Logger.getLogger("com.j256.ormlite.table.TableUtils").setLevel(Level.OFF); 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)); private void prepareDatabase() {
DaoRegistry.setShopDao(DaoManager.createDao(connectionSource, Shop.class));
DaoRegistry.setVirtualChestDao(DaoManager.createDao(connectionSource, VirtualChest.class)); try {
DaoRegistry.setAccountDao(DaoManager.createDao(connectionSource, Account.class)); 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() { private void registerCommands() {
@NotNull LifecycleEventManager<@NotNull Plugin> lm = this.getLifecycleManager(); @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(ShopCommand.getCommand().build()));
lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(RulerCommand.createCommand().build())); lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(MarkerCommand.getCommand().build()));
lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(MoneyCommand.createCommand().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(){ private void registerEvents(){
PluginManager pm = this.getServer().getPluginManager(); 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 VirtualChestLogic(), this);
pm.registerEvents(new ShopLogic(), this); pm.registerEvents(new ShopLogic(), this);
pm.registerEvents(new PreparePlayer(), 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);
}

@ -10,7 +10,42 @@ public class DaoRegistry {
private static Dao<VirtualChest, Integer> virtualChestDao; private static Dao<VirtualChest, Integer> virtualChestDao;
private static Dao<Account, Integer> accountDao; private static Dao<Account, Integer> accountDao;
private static Dao<Island, Integer> islandDao; private static Dao<Island, Integer> islandDao;
private static Dao<TeleportRequest, Integer> teleportRequestsDao;
private static Dao<ActiveServer, Integer> activeServerDao;
private static Dao<Region, Integer> regionDao;
private static Dao<RegionMember, Integer> regionMemberDao;
public static Dao<RegionMember, Integer> getRegionMemberDao() {
return regionMemberDao;
}
public static void setRegionMemberDao(Dao<RegionMember, Integer> regionMemberDao) {
DaoRegistry.regionMemberDao = regionMemberDao;
}
public static Dao<Region, Integer> getRegionDao() {
return regionDao;
}
public static void setRegionDao(Dao<Region, Integer> regionDao) {
DaoRegistry.regionDao = regionDao;
}
public static Dao<TeleportRequest, Integer> getTeleportRequestsDao() {
return teleportRequestsDao;
}
public static void setTeleportRequestsDao(Dao<TeleportRequest, Integer> teleportRequestsDao) {
DaoRegistry.teleportRequestsDao = teleportRequestsDao;
}
public static Dao<ActiveServer, Integer> getActiveServerDao() {
return activeServerDao;
}
public static void setActiveServerDao(Dao<ActiveServer, Integer> activeServerDao) {
DaoRegistry.activeServerDao = activeServerDao;
}
public static Dao<Shop, Integer> getShopDao() { public static Dao<Shop, Integer> getShopDao() {
return shopDao; return shopDao;

@ -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;
}
}

@ -15,9 +15,8 @@ public class Island {
@DatabaseField(canBeNull = false, unique = true) @DatabaseField(canBeNull = false, unique = true)
private String uuid; private String uuid;
@DatabaseField(canBeNull = false, unique = true) @DatabaseField(canBeNull = false)
private String name; private String type;
@DatabaseField(columnName = "display_name") @DatabaseField(columnName = "display_name")
private String displayName; private String displayName;
@ -28,21 +27,26 @@ public class Island {
@DatabaseField(canBeNull = false) @DatabaseField(canBeNull = false)
private String owner; private String owner;
@DatabaseField(canBeNull = false) @DatabaseField(columnName = "owner_type", canBeNull = false)
private String ownerType; private String ownerType;
@DatabaseField(canBeNull = false, dataType = DataType.BYTE_ARRAY, columnDefinition = "LONGBLOB") @DatabaseField(canBeNull = false, dataType = DataType.BYTE_ARRAY, columnDefinition = "LONGBLOB")
private byte[] data; private byte[] data;
@DatabaseField(columnName = "is_public", canBeNull = false, defaultValue = "false")
private Boolean isPublic;
@DatabaseField(defaultValue = "", columnName = "active_on") @DatabaseField(defaultValue = "", columnName = "active_on")
private String activeOn; private String activeOn;
public Island(){ public Island(){
} }
public Island(String name, String uuid, String displayName, String descritpion, String owner, String ownerType, byte[] data) { public Island(String type, String uuid, String displayName, String descritpion, String owner, String ownerType, byte[] data) {
this.name = name; this.type = type;
this.uuid = uuid; this.uuid = uuid;
this.displayName = displayName; this.displayName = displayName;
this.descritpion = descritpion; 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) { public void setData(byte[] data) {
this.data = data; this.data = data;
} }
@ -60,8 +71,8 @@ public class Island {
return id; return id;
} }
public String getName() { public String getType() {
return name; return type;
} }
public String getDisplayName() { public String getDisplayName() {

@ -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<String, List<Region>> 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<RegionMember> 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<RegionMember> 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<Region> 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<Region> 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<Region> 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);
}
}

@ -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);
}
}
}

@ -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);
}
}

@ -2,16 +2,19 @@ package xyz.soukup.ecoCraftCore.gui;
import com.github.stefvanschie.inventoryframework.gui.GuiItem; import com.github.stefvanschie.inventoryframework.gui.GuiItem;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
public class GuiItemBuilder { public class GuiItemBuilder {
private ItemStack itemStack; private final ItemStack itemStack;
private ItemMeta itemMeta; private final ItemMeta itemMeta;
public GuiItemBuilder(Material material){ public GuiItemBuilder(Material material){
this.itemStack = new ItemStack(material); this.itemStack = new ItemStack(material);
@ -30,6 +33,14 @@ public class GuiItemBuilder {
return this; return this;
} }
public GuiItemBuilder setRawLore(String rawLore){
List<Component> lore = Arrays.stream(rawLore.split("\\R"))
.map(line -> MiniMessage.miniMessage().deserialize(line))
.collect(Collectors.toList());
itemMeta.lore(lore);
return this;
}
public GuiItem build(){ public GuiItem build(){
this.itemStack.setItemMeta(this.itemMeta); this.itemStack.setItemMeta(this.itemMeta);
return new GuiItem(this.itemStack); return new GuiItem(this.itemStack);

@ -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<String, Integer> 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;
}
}
}

@ -10,7 +10,7 @@ import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class IslandLoader implements SlimeLoader { public class DatabaseIslandLoader implements SlimeLoader {
@Override @Override
@ -38,6 +38,8 @@ public class IslandLoader implements SlimeLoader {
.setCountOf(true) .setCountOf(true)
.where() .where()
.eq("uuid", worldName) .eq("uuid", worldName)
.and()
.isNotNull("data")
.countOf(); .countOf();
return count > 0; return count > 0;

@ -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<String> 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());
}
}

@ -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<CommandSourceStack> getCommand() {
LiteralArgumentBuilder<CommandSourceStack> tp = Commands.literal("tp")
.then(Commands.argument("uuid", StringArgumentType.word())
.executes(IslandAdminCommand::teleport)
.suggests(((context, builder) -> {
try {
QueryBuilder<Island, Integer> queryBuilder = DaoRegistry.getIslandDao().queryBuilder();
queryBuilder.selectColumns("uuid");
List<Island> islands = queryBuilder.query();
for (Island island : islands) {
builder.suggest(island.getUuid());
}
return builder.buildFuture();
} catch (SQLException e) {
throw new RuntimeException(e);
}
})));
LiteralArgumentBuilder<CommandSourceStack> 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<CommandSourceStack> load = Commands.literal("load")
.then(Commands.argument("uuid", StringArgumentType.word())
.suggests(((context, builder) -> {
try {
QueryBuilder<Island, Integer> queryBuilder = DaoRegistry.getIslandDao().queryBuilder();
queryBuilder.selectColumns("uuid");
List<Island> 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<CommandSourceStack> 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<CommandSourceStack> template = Commands.literal("template")
.then(Commands.argument("uuid", StringArgumentType.word())
.suggests((context, builder) -> {
try {
QueryBuilder<Island, Integer> queryBuilder = DaoRegistry.getIslandDao().queryBuilder();
queryBuilder.selectColumns("uuid");
List<Island> 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<CommandSourceStack> metadata = Commands.literal("metadata")
.then(Commands.argument("key", StringArgumentType.word())
.then(Commands.argument("value", IntegerArgumentType.integer())
.executes(IslandAdminCommand::setMetadata)));
LiteralArgumentBuilder<CommandSourceStack> metadataString = Commands.literal("metadataString")
.then(Commands.argument("key", StringArgumentType.word())
.then(Commands.argument("value", StringArgumentType.string())
.executes(IslandAdminCommand::setMetadataString)));
LiteralArgumentBuilder<CommandSourceStack> expand = Commands.literal("expand")
.then(Commands.argument("lenght", IntegerArgumentType.integer())
.executes(IslandAdminCommand::expandIsland));
LiteralArgumentBuilder<CommandSourceStack> listMetadata = Commands.literal("listMetadata")
.executes(IslandAdminCommand::readAllMetadata);
LiteralArgumentBuilder<CommandSourceStack> spawn = Commands.literal("spawn")
.executes(IslandAdminCommand::setSpawn);
LiteralArgumentBuilder<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> context) {
IslandManager islandManager = new IslandManager();
islandManager.loadIsland(context.getArgument("uuid", String.class));
context.getSource().getSender().sendMessage("done.");
return 0;
}
private static int loadTemplate(CommandContext<CommandSourceStack> context) {
IslandManager islandManager = new IslandManager();
islandManager.loadIslandTemplate(context.getArgument("name", String.class));
context.getSource().getSender().sendMessage("done.");
return 0;
}
private static int createTemplate(CommandContext<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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();
}
}
}

@ -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<CommandSourceStack> tp = Commands.literal("tp")
.then(Commands.argument("uuid", StringArgumentType.word())
.executes(IslandCommand::teleport));
LiteralArgumentBuilder<CommandSourceStack> create = Commands.literal("create")
.then(Commands.argument("name", StringArgumentType.word())
.then(Commands.argument("display_name", StringArgumentType.string())));
private static int teleport(CommandContext<CommandSourceStack> context) {
return 0;
}
private static int createWorld(CommandContext<CommandSourceStack> context) {
return 0;
}
private static int loadWorld(CommandContext<CommandSourceStack> context) {
return 0;
}
}

@ -1,85 +1,459 @@
package xyz.soukup.ecoCraftCore.islands; 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.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.SlimeWorld;
import com.infernalsuite.asp.api.world.properties.SlimeProperties; import com.infernalsuite.asp.api.world.properties.SlimeProperties;
import com.infernalsuite.asp.api.world.properties.SlimePropertyMap; 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.objects.Island;
import xyz.soukup.ecoCraftCore.database.DaoRegistry; 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.sql.SQLException;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import static xyz.soukup.ecoCraftCore.EcoCraftCore.config;
import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin; import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin;
public class IslandManager { public class IslandManager {
private final AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance(); 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<Island, Integer> 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(); 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 { 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, () -> { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
try { try {
// Create the database entry first so the loader has a row to update
SlimeWorld slimeWorld;
Island island = new Island(name, uuid, displayName, descritpion, owner, ownerType, null); Island island = new Island(type, uuid, displayName, descritpion, owner, ownerType, null);
island.save(); 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(); }
}); });
} 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 { String whereIsActive = whereIsActive(uuid);
Island island = DaoRegistry.getIslandDao().queryBuilder() if (whereIsActive != null && !whereIsActive.isEmpty()){
.selectColumns("active_on") plugin.getLogger().info("dd: "+ whereIsActive);
.where() sendPlayerAway(player, whereIsActive, uuid, x, y, z, yaw, pitch);
.eq("uuid", uuid) return 3;
.queryForFirst(); }
if (!island.getActiveOn().isEmpty()){ QueryBuilder<Island, Integer> queryBuilder = dao.queryBuilder().setCountOf(true);
return 1; 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<World> loadIsland(String uuid) {
CompletableFuture<World> future = new CompletableFuture<>();
if (Bukkit.getWorld(uuid) != null) {
future.complete(Bukkit.getWorld(uuid));
return future;
}
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
try { try {
SlimeWorld slimeWorld = asp.readWorld(loader, uuid, false, new SlimePropertyMap()); SlimeWorld slimeWorld = asp.readWorld(databaseLoader, uuid, false, new SlimePropertyMap());
Bukkit.getScheduler().runTask(plugin, () -> { 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<Region, Integer> 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<String[]> 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;
} }

@ -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<CommandSourceStack> getCommand(){
return Commands.literal("is")
.executes(IslandSelectorCommand::displayIslandListSelectorGui);
}
private static int displayIslandListSelectorGui(CommandContext<CommandSourceStack> 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<Island> getAllIslands(){
QueryBuilder<Island, Integer> 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<Island> getPublicIslands(){
QueryBuilder<Island, Integer> 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<Island> getMyIslands(Player player){
QueryBuilder<Island, Integer> 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<Island> getSharedIslands(Player player){
QueryBuilder<RegionMember, Integer> memberQb = DaoRegistry.getRegionMemberDao().queryBuilder();
memberQb.selectColumns("region_id");
try {
memberQb.where().eq("member_name", player.getName()).and().eq("member_type", "player");
QueryBuilder<Region, Integer> 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<Island> 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<Island> 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<GuiItem> itemsFromIslands(Player player, List<Island> islands){
List<GuiItem> 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;
}
}

@ -1,4 +1,4 @@
package xyz.soukup.ecoCraftCore.islands; package xyz.soukup.ecoCraftCore.islands;
public class UnloadWorld { public class UnloadIsland {
} }

@ -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<CommandSourceStack> createCommand() {
LiteralArgumentBuilder<CommandSourceStack> regenerate = Commands.literal("regenerate")
.executes(MineCommand::regenerateMines);
LiteralArgumentBuilder<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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;
}
}

@ -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<Material, OreEntry[]> 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<Material> 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<Material> 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<Long> fillPositions = new HashSet<>();
List<BlockPlacement> fillPlacements = new ArrayList<>();
// ===== Biome 1: NORMAL =====
plugin.getLogger().info("[MineGen] === Starting NORMAL biome generation ===");
List<Branch> 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<Branch> 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<Branch> 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<BlockPlacement> 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<BlockPlacement> placements, Set<Long> fillPositions,
List<Branch> completedBranches, int[] branchCount) {
Deque<Branch> 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<Branch> queue, List<BlockPlacement> placements,
Set<Long> 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<BlockFace> 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<Branch> branches) {
Branch lowest = null;
for (Branch b : branches) {
if (lowest == null || b.endY < lowest.endY) {
lowest = b;
}
}
return lowest;
}
private static List<BlockFace> getAvailableDirections(BlockFace current) {
List<BlockFace> 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<BlockPlacement> placements, Set<Long> 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<Long> fillPositions, List<BlockPlacement> wallPlacements, int startY) {
Set<Long> 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<BlockPlacement> fillPlacements,
List<BlockPlacement> wallPlacements,
int startY, int minY, int maxY) {
// Three-phase placement: 1) Dirt placeholder 2) Bedrock walls 3) Actual fill
List<BlockPlacement> allPlacements = new ArrayList<>();
// Phase A: Dirt placeholders at all fill positions
List<BlockPlacement> 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;
}
}

@ -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<World> 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
};
}
}

@ -19,7 +19,7 @@ import xyz.soukup.ecoCraftCore.messages.PHHM;
public class MoneyCommand { public class MoneyCommand {
public static LiteralArgumentBuilder<CommandSourceStack> createCommand() { public static LiteralArgumentBuilder<CommandSourceStack> getCommand() {
// 1. Send Branch // 1. Send Branch
LiteralArgumentBuilder<CommandSourceStack> send = Commands.literal("send") LiteralArgumentBuilder<CommandSourceStack> send = Commands.literal("send")
.requires(source -> source.getSender() instanceof Player) .requires(source -> source.getSender() instanceof Player)

@ -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);
}
}
}

@ -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<TeleportRequest, Integer> 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);
}
}
}

@ -11,22 +11,21 @@ import org.bukkit.inventory.ItemStack;
import xyz.soukup.ecoCraftCore.messages.Messages; import xyz.soukup.ecoCraftCore.messages.Messages;
import xyz.soukup.ecoCraftCore.utilities.PDC; import xyz.soukup.ecoCraftCore.utilities.PDC;
public class RulerCommand { public class MarkerCommand {
public static LiteralArgumentBuilder<CommandSourceStack> createCommand() { public static LiteralArgumentBuilder<CommandSourceStack> getCommand() {
return Commands.literal("ruler") return Commands.literal("ruler")
.executes(RulerCommand::obtainRuler); .executes(MarkerCommand::obtainRuler);
} }
private static int obtainRuler(CommandContext<CommandSourceStack> context){ private static int obtainRuler(CommandContext<CommandSourceStack> context){
CommandSender commandSender = context.getSource().getSender(); CommandSender commandSender = context.getSource().getSender();
if (!(commandSender instanceof Player)){ if (!(commandSender instanceof Player player)){
Messages.send(commandSender, "generic.error.not-player"); Messages.send(commandSender, "generic.error.not-player");
return 0; return 0;
} }
Player player = (Player) commandSender;
ItemStack itemStack = new ItemStack(Material.BLAZE_ROD); ItemStack itemStack = new ItemStack(Material.BLAZE_ROD);
PDC.set(itemStack, "ruler", true); PDC.set(itemStack, "ruler", true);

@ -16,7 +16,7 @@ import xyz.soukup.ecoCraftCore.messages.PHHM;
import java.util.HashMap; import java.util.HashMap;
public class RulerMarking implements Listener { public class MarkerEvent implements Listener {
public static HashMap<Player, Location> primaryLocations = new HashMap<>(); public static HashMap<Player, Location> primaryLocations = new HashMap<>();
public static HashMap<Player, Location> secondaryLocations = new HashMap<>(); public static HashMap<Player, Location> secondaryLocations = new HashMap<>();

@ -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<CommandSourceStack> getCommand() {
LiteralArgumentBuilder<CommandSourceStack> create = Commands.literal("create")
.then(Commands.argument("type", IntegerArgumentType.integer(0, 1))
.executes(RegionAdminCommand::createRegion));
LiteralArgumentBuilder<CommandSourceStack> addMember = Commands.literal("addMember")
.then(Commands.argument("id", IntegerArgumentType.integer())
.suggests((context, builder) -> {
try {
QueryBuilder<Region, Integer> queryBuilder = DaoRegistry.getRegionDao().queryBuilder();
queryBuilder.selectColumns("id");
List<Region> 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<CommandSourceStack> 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<CommandSourceStack> 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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -9,17 +9,16 @@ import org.bukkit.block.Chest;
import org.bukkit.block.Sign; import org.bukkit.block.Sign;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; 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.database.objects.Shop;
import xyz.soukup.ecoCraftCore.inventory.VirtualChest; import xyz.soukup.ecoCraftCore.inventory.VirtualChest;
import xyz.soukup.ecoCraftCore.inventory.InventoryUtils; import xyz.soukup.ecoCraftCore.inventory.InventoryUtils;
import xyz.soukup.ecoCraftCore.messages.Messages; import xyz.soukup.ecoCraftCore.messages.Messages;
import xyz.soukup.ecoCraftCore.utilities.PDC; import xyz.soukup.ecoCraftCore.utilities.PDC;
@SuppressWarnings("UnstableApiUsage")
public class ShopCommand { public class ShopCommand {
public static LiteralArgumentBuilder<CommandSourceStack> createCommand() { public static LiteralArgumentBuilder<CommandSourceStack> getCommand() {
// Define the argument types // Define the argument types
var amountArg = Commands.argument("amount", IntegerArgumentType.integer(1)); var amountArg = Commands.argument("amount", IntegerArgumentType.integer(1));
var buyPriceArg = Commands.argument("buy_price", FloatArgumentType.floatArg(0.0F)); var buyPriceArg = Commands.argument("buy_price", FloatArgumentType.floatArg(0.0F));
@ -52,8 +51,8 @@ public class ShopCommand {
} }
// 1. Check if blocks are marked // 1. Check if blocks are marked
Chest chest = RulerMarking.chests.get(player); Chest chest = MarkerEvent.chests.get(player);
Sign sign = RulerMarking.signs.get(player); Sign sign = MarkerEvent.signs.get(player);
if (sign == null || chest == null) { if (sign == null || chest == null) {
Messages.send(player, "shop.error.not-marked"); Messages.send(player, "shop.error.not-marked");

@ -9,11 +9,15 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataHolder; import org.bukkit.persistence.PersistentDataHolder;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import xyz.soukup.ecoCraftCore.EcoCraftCore;
import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin;
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
public class PDC { public class PDC {
//TileState //TileState
public static void set(TileState tileState, String key, Integer value){ public static void set(TileState tileState, String key, Integer value){
@ -65,7 +69,7 @@ public class PDC {
return (String) get(itemStack, key, PersistentDataType.STRING); 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(); ItemMeta itemMeta = itemStack.getItemMeta();
return getUniversal(itemMeta, key, persistentDataType); return getUniversal(itemMeta, key, persistentDataType);
} }
@ -80,15 +84,15 @@ public class PDC {
//Univerzální set & get //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(); PersistentDataContainer persistentDataContainer = persistentDataHolder.getPersistentDataContainer();
NamespacedKey namespacedKey = new NamespacedKey("ecc", key); NamespacedKey namespacedKey = new NamespacedKey(plugin, key);
return persistentDataContainer.get(namespacedKey, persistentDataType); 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(); PersistentDataContainer persistentDataContainer = persistentDataHolder.getPersistentDataContainer();
NamespacedKey namespacedKey = new NamespacedKey("ecc", key); NamespacedKey namespacedKey = new NamespacedKey(plugin, key);
persistentDataContainer.set(namespacedKey, persistentDataType, value); persistentDataContainer.set(namespacedKey, persistentDataType, value);
} }

@ -9,4 +9,6 @@ database:
password: "ecc" password: "ecc"
database: "ecc" database: "ecc"
cache: cache:
save-interval: 6000 save-interval: 6000
islands:
spawn: null

@ -1,4 +1,6 @@
generic: generic:
success:
creat ed: <green>Vytvořeno.
error: error:
not-player: "<red>Na tuto akci musíš být hráč" not-player: "<red>Na tuto akci musíš být hráč"
no-funds: no-funds:
@ -10,6 +12,10 @@ generic:
no-item: no-item:
self: "<red>Nemáš dostatek itemů" self: "<red>Nemáš dostatek itemů"
shop: "<red>Obchod nemá dostatek itemů" shop: "<red>Obchod nemá dostatek itemů"
region:
error:
not-marked: "<red>Musíš nejprve označit pozice"
not-exist: "<red>Region neexistuje"
shop: shop:
error: error:
already-shop: "<red>Tato cedule již je obchod" already-shop: "<red>Tato cedule již je obchod"
@ -44,6 +50,12 @@ marker:
primary: "<green>První pozice označena (<x>,<y>,<z>)" primary: "<green>První pozice označena (<x>,<y>,<z>)"
secondary: "<green>Druhá pozice označena (<x>,<y>,<z>)" secondary: "<green>Druhá pozice označena (<x>,<y>,<z>)"
menu: menu:
island-selector:
title: "<green><bold>Výběr ostrovu"
my-islands: "<yellow><bold>Moje ostrovy"
shared-islands: "<blue><bold>Ostrovy sdílené se mnou"
public-islands: "<green><bold>Veřejné Ostrovy"
all-islands: "<red><bold>Všechny ostrovy (Pouze OP)"
shop: shop:
buy: "<green>Koupit <amount>ks za <price>$" buy: "<green>Koupit <amount>ks za <price>$"
sell: "<yellow>Prodat <amount>ks za <price>$" sell: "<yellow>Prodat <amount>ks za <price>$"
@ -65,4 +77,11 @@ gui:
title: "<dark_green>Obchod" title: "<dark_green>Obchod"
buy: "<green>Koupit <amount>ks za <price>$" buy: "<green>Koupit <amount>ks za <price>$"
sell: "<yellow>Prodat <amount>ks za <price>$" sell: "<yellow>Prodat <amount>ks za <price>$"
mine:
regenerating: "<green>Regenerace dolů začala..."
regenerate-complete: "<green>Regenerace dolů dokončena."
teleporting: "<green>Teleportuješ se do dolů."
teleported-out: "<yellow>Byl jsi teleportován z dolů kvůli regeneraci."
error:
no-world: "<red>Důlní svět není načtený."

@ -2,3 +2,6 @@ name: EcoCraftCore
version: '1.0-SNAPSHOT' version: '1.0-SNAPSHOT'
main: xyz.soukup.ecoCraftCore.EcoCraftCore main: xyz.soukup.ecoCraftCore.EcoCraftCore
api-version: '1.21' api-version: '1.21'
depend:
- packetevents

Loading…
Cancel
Save