Compare commits

..

18 Commits

  1. 44
      pom.xml
  2. 65
      src/main/java/xyz/soukup/ecoCraftCore/EcoCraftCore.java
  3. 2
      src/main/java/xyz/soukup/ecoCraftCore/database/DaoRegistry.java
  4. 15
      src/main/java/xyz/soukup/ecoCraftCore/database/objects/Island.java
  5. 60
      src/main/java/xyz/soukup/ecoCraftCore/database/objects/Region.java
  6. 33
      src/main/java/xyz/soukup/ecoCraftCore/database/objects/Shop.java
  7. 47
      src/main/java/xyz/soukup/ecoCraftCore/database/objects/VirtualChest.java
  8. 25
      src/main/java/xyz/soukup/ecoCraftCore/islands/IslandAdminCommand.java
  9. 54
      src/main/java/xyz/soukup/ecoCraftCore/islands/IslandManager.java
  10. 18
      src/main/java/xyz/soukup/ecoCraftCore/messages/JoinLeaveMessageSupress.java
  11. 3
      src/main/java/xyz/soukup/ecoCraftCore/messages/Messages.java
  12. 75
      src/main/java/xyz/soukup/ecoCraftCore/mines/MineCommand.java
  13. 62
      src/main/java/xyz/soukup/ecoCraftCore/mines/MineManager.java
  14. 230
      src/main/java/xyz/soukup/ecoCraftCore/mines/MineWorldManager.java
  15. 50
      src/main/java/xyz/soukup/ecoCraftCore/mines/generation/MineBranchState.java
  16. 68
      src/main/java/xyz/soukup/ecoCraftCore/mines/generation/MineDirection.java
  17. 191
      src/main/java/xyz/soukup/ecoCraftCore/mines/generation/MineGenerationConfig.java
  18. 9
      src/main/java/xyz/soukup/ecoCraftCore/mines/generation/MineGenerationPoint.java
  19. 376
      src/main/java/xyz/soukup/ecoCraftCore/mines/generation/MineGenerator.java
  20. 12
      src/main/java/xyz/soukup/ecoCraftCore/mines/generation/MineGridPosition.java
  21. 181
      src/main/java/xyz/soukup/ecoCraftCore/mines/generation/MineRoomLibrary.java
  22. 28
      src/main/java/xyz/soukup/ecoCraftCore/mines/generation/MineRoomPrefab.java
  23. 67
      src/main/java/xyz/soukup/ecoCraftCore/regions/RegionAdminCommand.java
  24. 311
      src/main/java/xyz/soukup/ecoCraftCore/regions/RegionEvents.java
  25. 49
      src/main/java/xyz/soukup/ecoCraftCore/regions/RegionManager.java
  26. 107
      src/main/java/xyz/soukup/ecoCraftCore/shop/ShopAdminCommand.java
  27. 2
      src/main/java/xyz/soukup/ecoCraftCore/shop/ShopCommand.java
  28. 89
      src/main/java/xyz/soukup/ecoCraftCore/shop/ShopLogic.java
  29. 187
      src/main/java/xyz/soukup/ecoCraftCore/sign/SignEditCommand.java
  30. 142
      src/main/java/xyz/soukup/ecoCraftCore/sit/LetMeSit.java
  31. 83
      src/main/java/xyz/soukup/ecoCraftCore/virtualChest/VirtualChestLogic.java
  32. 37
      src/main/resources/config.yml
  33. 33
      src/main/resources/messages.yml

@ -18,7 +18,6 @@
<build>
<defaultGoal>clean package</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -29,6 +28,16 @@
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>slime</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
<plugin>
<groupId>io.ebean</groupId>
@ -47,7 +56,6 @@
<packages>xyz.soukup.ecoCraftCore.database.objects.**</packages>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
@ -83,37 +91,39 @@
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>enginehub</id>
<url>https://maven.enginehub.org/repo/</url>
</repository>
<repository>
<id>codemc-releases</id>
<url>https://repo.codemc.io/repository/maven-releases/</url>
</repository>
<repository>
<id>is-releases</id>
<url>https://repo.infernalsuite.com/repository/maven-releases/</url>
</repository>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
@ -121,57 +131,55 @@
<groupId>com.infernalsuite.asp</groupId>
<artifactId>api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.j256.ormlite</groupId>
<artifactId>ormlite-core</artifactId>
<version>6.1</version>
</dependency>
<dependency>
<groupId>com.j256.ormlite</groupId>
<artifactId>ormlite-jdbc</artifactId>
<version>6.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.3.0</version>
</dependency>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.10-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sk89q.worldedit</groupId>
<artifactId>worldedit-bukkit</artifactId>
<version>7.3.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.stefvanschie.inventoryframework</groupId>
<artifactId>IF</artifactId>
<version>0.11.6</version>
</dependency>
<dependency>
<groupId>com.github.MatrixCreations</groupId>
<artifactId>MatrixColorAPI</artifactId>
<version>v1.0.7</version>
</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>
</project>
</project>

@ -5,10 +5,12 @@ import com.github.retrooper.packetevents.event.PacketListenerPriority;
import com.infernalsuite.asp.api.AdvancedSlimePaperAPI;
import com.infernalsuite.asp.api.world.SlimeWorldInstance;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.jdbc.DataSourceConnectionSource;
import com.j256.ormlite.stmt.UpdateBuilder;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager;
import org.bukkit.Bukkit;
@ -22,19 +24,29 @@ 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.messages.JoinLeaveMessageSupress;
import xyz.soukup.ecoCraftCore.mines.MineCommand;
import xyz.soukup.ecoCraftCore.mines.MineWorldManager;
import xyz.soukup.ecoCraftCore.money.MoneyCommand;
import xyz.soukup.ecoCraftCore.player.OnKill;
import xyz.soukup.ecoCraftCore.player.PreparePlayer;
import xyz.soukup.ecoCraftCore.player.TeleportRequestsHandler;
import xyz.soukup.ecoCraftCore.positionMarker.MarkerCommand;
import xyz.soukup.ecoCraftCore.shop.ShopAdminCommand;
import xyz.soukup.ecoCraftCore.regions.RegionAdminCommand;
import xyz.soukup.ecoCraftCore.regions.RegionEvents;
import xyz.soukup.ecoCraftCore.shop.ShopCommand;
import xyz.soukup.ecoCraftCore.database.objects.Account;
import xyz.soukup.ecoCraftCore.database.objects.Island;
import xyz.soukup.ecoCraftCore.database.objects.Shop;
import xyz.soukup.ecoCraftCore.database.objects.Transaction;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
import xyz.soukup.ecoCraftCore.inventory.VirtualChest;
import xyz.soukup.ecoCraftCore.database.objects.VirtualChest;
import xyz.soukup.ecoCraftCore.positionMarker.MarkerEvent;
import xyz.soukup.ecoCraftCore.database.DaoRegistry;
import xyz.soukup.ecoCraftCore.messages.Messages;
import xyz.soukup.ecoCraftCore.shop.ShopLogic;
import xyz.soukup.ecoCraftCore.sign.SignEditCommand;
import xyz.soukup.ecoCraftCore.sit.LetMeSit;
import xyz.soukup.ecoCraftCore.virtualChest.VirtualChestLogic;
import java.io.IOException;
@ -76,11 +88,15 @@ public final class EcoCraftCore extends JavaPlugin {
registerCommands();
registerEvents();
prepareSlimeWorldsSaver();
prepareWorldInactivityUnloader();
}catch (IOException e) {
throw new RuntimeException(e);
e.printStackTrace();
getLogger().severe("Failed to save island templates.");
}
MineWorldManager.init();
this.getServer().getScheduler().runTaskTimer(this, /* Lambda: */ task -> {
VirtualChest.saveCache();
} , 6000, 6000);
@ -104,6 +120,7 @@ public final class EcoCraftCore extends JavaPlugin {
AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance();
for (SlimeWorldInstance slimeWorldInstance : asp.getLoadedWorlds()){
getLogger().info("Saved database world " + slimeWorldInstance.getName());
try {
asp.saveWorld(slimeWorldInstance);
} catch (IOException e) {
@ -116,7 +133,7 @@ public final class EcoCraftCore extends JavaPlugin {
private void prepareWorldInactivityUnloader(){
Bukkit.getScheduler().runTaskTimer(plugin, () -> {
long timeoutMillis = 30 * 60 * 1000;
long timeoutMillis = 6 * 60 * 1000;
long now = System.currentTimeMillis();
AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance();
@ -139,6 +156,16 @@ public final class EcoCraftCore extends JavaPlugin {
if ((now - emptySince) >= timeoutMillis) {
Bukkit.unloadWorld(world, true);
getLogger().info("Unloading world " + world.getName() + " due to inactivity");
try {
UpdateBuilder<Island, Integer> updateBuilder = DaoRegistry.getIslandDao().updateBuilder();
updateBuilder.where().eq("uuid", world.getName());
updateBuilder.updateColumnValue("active_on", "");
updateBuilder.update();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@ -188,7 +215,18 @@ public final class EcoCraftCore extends JavaPlugin {
String databasePassword = config.getString("database.password");
String databaseUrl = "jdbc:mysql://" + databaseHost + ":" + databasePort + "/" + databaseName;
connectionSource = new JdbcConnectionSource(databaseUrl, databaseUsername, databasePassword);
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);
@ -241,12 +279,17 @@ public final class EcoCraftCore extends JavaPlugin {
private void registerCommands() {
@NotNull LifecycleEventManager<@NotNull Plugin> lm = this.getLifecycleManager();
lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(ShopAdminCommand.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(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()));
lm.registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(SignEditCommand.getCommand().build()));
}
private void registerEvents(){
@ -258,10 +301,16 @@ public final class EcoCraftCore extends JavaPlugin {
pm.registerEvents(new VirtualChestLogic(), this);
pm.registerEvents(new ShopLogic(), this);
pm.registerEvents(new PreparePlayer(), this);
pm.registerEvents(new RegionEvents(), this);
pm.registerEvents(new MineWorldManager(), this);
pm.registerEvents(new JoinLeaveMessageSupress(), this);
pm.registerEvents(new LetMeSit(), this);
EventManager events = PacketEvents.getAPI().getEventManager();
events.registerListener(new ChunkModifier(this), PacketListenerPriority.NORMAL);
}
}

@ -2,7 +2,7 @@ package xyz.soukup.ecoCraftCore.database;
import com.j256.ormlite.dao.Dao;
import xyz.soukup.ecoCraftCore.database.objects.*;
import xyz.soukup.ecoCraftCore.inventory.VirtualChest;
import xyz.soukup.ecoCraftCore.database.objects.VirtualChest;
public class DaoRegistry {
private static Dao<Shop, Integer> shopDao;

@ -109,10 +109,25 @@ public class Island {
}
}
public static Island findByUuid(String uuid) {
try {
return DaoRegistry.getIslandDao().queryBuilder().where().eq("uuid", uuid).queryForFirst();
} catch (SQLException e) {
return null;
}
}
public void save(){
try {
// createOrUpdate works by primary id; resolve id from uuid first to avoid duplicate inserts.
if (this.id <= 0 && this.uuid != null) {
Island existing = findByUuid(this.uuid);
if (existing != null) {
this.id = existing.id;
}
}
DaoRegistry.getIslandDao().createOrUpdate(this);
} catch (SQLException e) {
throw new RuntimeException(e);

@ -29,12 +29,18 @@ public class Region {
@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;
@ -45,15 +51,20 @@ public class Region {
}
public Region(String island, int regionType, int x1, int y1, int x2, int y2) {
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.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;
@ -63,6 +74,14 @@ public class Region {
return x1;
}
public int getZ1() {
return z1;
}
public int getZ2() {
return z2;
}
public int getY1() {
return y1;
}
@ -101,7 +120,16 @@ public class Region {
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();
}
@ -110,10 +138,21 @@ public class Region {
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) {
return x >= this.x1 && x <= this.x2 && y >= this.y1 && y <= this.y2;
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) {
@ -136,7 +175,7 @@ public class Region {
}
}
public static Region findRegion(int x, int y, String island) {
public static Region findRegion(int x, int y, int z, String island) {
Region region = null;
int highestType = -1;
@ -146,7 +185,7 @@ public class Region {
}
for (Region cachedRegion : cache.get(island)) {
if (!cachedRegion.isInside(x, y)) {
if (!cachedRegion.isInside(x, y, z)) {
continue;
}
if (highestType >= cachedRegion.getRegionType()){
@ -160,6 +199,15 @@ public class 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);
}
}

@ -7,6 +7,7 @@ import org.bukkit.block.sign.Side;
import org.bukkit.block.sign.SignSide;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import xyz.soukup.ecoCraftCore.database.DaoRegistry;
import xyz.soukup.ecoCraftCore.utilities.Converter;
@ -28,6 +29,7 @@ public class Shop {
@DatabaseField(canBeNull = false)
private String ownerType;
@DatabaseField(canBeNull = false)
private String itemName;
@ -77,14 +79,6 @@ public class Shop {
this.virtualChestID = virtualChestID;
}
public void setOwnerType(String ownerType) {
this.ownerType = ownerType;
}
public void setOwner(String username) {
this.owner = username;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
@ -109,6 +103,7 @@ public class Shop {
this.stock = stock;
}
public static Shop findById(int id) {
Shop shop = cache.get(id);
@ -123,6 +118,10 @@ public class Shop {
}
}
public static HashMap<Integer, Shop> getCache(){
return cache;
}
public void save(){
try {
DaoRegistry.getShopDao().createOrUpdate(this);
@ -133,13 +132,29 @@ public class Shop {
cache.put(this.id, this);
}
public void delete(){
try {
cache.remove(this.id);
DaoRegistry.getShopDao().delete(this);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public ItemStack getItemStack() {
return Converter.itemstackFromString(this.itemStackString);
}
public void writeIntoSign(Sign sign){
PDC.set(sign, "shop", this.id);
if (PDC.getUniversal(sign, "line1", PersistentDataType.STRING) != null){
sign.update();
return;
}
Component prices;
Component shopType;
@ -166,7 +181,7 @@ public class Shop {
}
public long getId() {
public int getId() {
return id;
}

@ -1,18 +1,22 @@
package xyz.soukup.ecoCraftCore.inventory;
package xyz.soukup.ecoCraftCore.database.objects;
import com.j256.ormlite.field.DataType;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.stmt.DeleteBuilder;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.table.DatabaseTable;
import org.bukkit.block.Chest;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import xyz.soukup.ecoCraftCore.database.DaoRegistry;
import xyz.soukup.ecoCraftCore.inventory.InventoryUtils;
import xyz.soukup.ecoCraftCore.utilities.Converter;
import xyz.soukup.ecoCraftCore.utilities.PDC;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@DatabaseTable(tableName = "virtual_chests")
public class VirtualChest {
@ -29,7 +33,7 @@ public class VirtualChest {
private Inventory inventory;
private static final HashMap<Integer, VirtualChest> cache = new HashMap<>();
private static final HashSet<Integer> cacheChanges = new HashSet<>();
private static final HashSet<Integer> changedCached = new HashSet<>();
public VirtualChest(){
@ -77,7 +81,7 @@ public class VirtualChest {
public static void saveCache(){
cache.forEach((key, value) -> {
if (!cacheChanges.contains(key)) return;
if (!changedCached.contains(key)) return;
value.databaseSave();
});
@ -87,7 +91,7 @@ public class VirtualChest {
public void save() {
cache.put(this.id, this);
cacheChanges.add(this.id);
changedCached.add(this.id);
}
public void databaseSave(){
@ -101,7 +105,40 @@ public class VirtualChest {
}
cache.put(this.id, this);
cacheChanges.remove(this.id);
changedCached.remove(this.id);
}
public void delete(){
try {
DaoRegistry.getVirtualChestDao().delete(this);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void deleteSelfAndShops(){
QueryBuilder<Shop, Integer> queryBuilder = DaoRegistry.getShopDao().queryBuilder();
DeleteBuilder<Shop, Integer> deleteBuilder = DaoRegistry.getShopDao().deleteBuilder();
try {
queryBuilder
.selectColumns("id")
.where()
.eq("virtualChestID", this.id);
List<Shop> shops = queryBuilder.query();
HashMap<Integer, Shop> cache = Shop.getCache();
for(Shop shop : shops){
cache.remove(shop.getId());
}
deleteBuilder.where().eq("virtualChestID", this.id);
deleteBuilder.delete();
DaoRegistry.getVirtualChestDao().delete(this);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void setInventory(Inventory inventory) {

@ -9,8 +9,11 @@ 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.Bukkit;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.block.Biome;
import org.bukkit.entity.Player;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
@ -101,6 +104,15 @@ public class IslandAdminCommand {
}))
.executes(IslandAdminCommand::loadTemplate));
LiteralArgumentBuilder<CommandSourceStack> setBiome = Commands.literal("biome")
.then(Commands.argument("biome", StringArgumentType.string())
.suggests(((context, builder) -> {
Registry<Biome> biomeRegistry = Registry.BIOME;
biomeRegistry.stream().forEach(biome -> builder.suggest(biome.getKey().asString()));
return builder.buildFuture();
}))
.executes(IslandAdminCommand::setBiome));
LiteralArgumentBuilder<CommandSourceStack> template = Commands.literal("template")
.then(Commands.argument("uuid", StringArgumentType.word())
.suggests((context, builder) -> {
@ -165,7 +177,8 @@ public class IslandAdminCommand {
.then(spawn)
.then(enviroment)
.then(expand)
.then(setDefaultIsland);
.then(setDefaultIsland)
.then(setBiome);
@ -202,6 +215,16 @@ public class IslandAdminCommand {
return 0;
}
private static int setBiome(CommandContext<CommandSourceStack> context) {
if (!(context.getSource().getSender() instanceof Player player)) return 0;
IslandManager islandManager = new IslandManager();
String uuid = player.getWorld().getName();
islandManager.changeBiome(context.getArgument("biome", String.class), uuid);
Messages.send(player, "island.set-biome.success");
return 0;
}
private static int teleport(CommandContext<CommandSourceStack> context) {
IslandManager islandManager = new IslandManager();

@ -19,6 +19,7 @@ import org.bukkit.entity.Player;
import org.bukkit.persistence.PersistentDataType;
import xyz.soukup.ecoCraftCore.database.objects.Island;
import xyz.soukup.ecoCraftCore.database.DaoRegistry;
import xyz.soukup.ecoCraftCore.database.objects.Region;
import xyz.soukup.ecoCraftCore.database.objects.TeleportRequest;
import xyz.soukup.ecoCraftCore.utilities.PDC;
@ -82,6 +83,8 @@ public class IslandManager {
});
} catch (Exception e) { e.printStackTrace(); }
createWorldRegion(uuid, owner, ownerType, type);
return uuid;
}
@ -238,6 +241,20 @@ public class IslandManager {
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);
@ -253,6 +270,18 @@ public class IslandManager {
}
}
public void changeBiome(String biome, String uuid){
SlimeWorld slimeWorld = asp.getLoadedWorld(uuid);
SlimePropertyMap slimePropertyMap = slimeWorld.getPropertyMap();
slimePropertyMap.setValue(SlimeProperties.DEFAULT_BIOME, biome);
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();
@ -369,10 +398,7 @@ public class IslandManager {
}
}
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);
changeBoundaries(world, x1, x2, z1, z2);
int chunkMaxX = ((Math.max(x1, x2) + 15) & ~15) + 16;
int chunkMinX = (Math.min(x1, x2) & ~15) - 16;
@ -397,6 +423,26 @@ public class IslandManager {
;
}
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);
}
}

@ -0,0 +1,18 @@
package xyz.soukup.ecoCraftCore.messages;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class JoinLeaveMessageSupress implements Listener {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
event.joinMessage(null);
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
event.quitMessage(null);
}
}

@ -4,6 +4,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
@ -39,7 +40,7 @@ public class Messages {
}
public static String getAsString(String key){
return MiniMessage.miniMessage().serialize(Messages.get(key));
return LegacyComponentSerializer.legacySection().serialize(Messages.get(key));
}
public static Component get(String key, HashMap<String, String> placeholders){

@ -0,0 +1,75 @@
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.command.CommandSender;
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) {
CommandSender sender = context.getSource().getSender();
World world = MineWorldManager.getWorld();
if (world == null) {
Messages.send(sender, "mine.error.no-world");
return 0;
}
Messages.send(sender, "mine.regenerating");
for (Player player : world.getPlayers()) {
player.teleport(player.getServer().getWorlds().getFirst().getSpawnLocation());
Messages.send(player, "mine.teleported-out");
}
MineWorldManager.recreateWorld(newWorld -> {
boolean success = MineManager.regenerate(newWorld);
if (success) {
Messages.send(sender, "mine.regenerate-complete");
} else {
Messages.send(sender, "mine.error.regenerate-failed");
}
});
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();
if (spawn == null) {
Messages.send(player, "mine.error.no-world");
return 0;
}
player.teleport(spawn);
Messages.send(player, "mine.teleporting");
return 1;
}
}

@ -0,0 +1,62 @@
package xyz.soukup.ecoCraftCore.mines;
import org.bukkit.Location;
import org.bukkit.World;
import xyz.soukup.ecoCraftCore.mines.generation.MineGenerationConfig;
import xyz.soukup.ecoCraftCore.mines.generation.MineGenerator;
import xyz.soukup.ecoCraftCore.mines.generation.MineRoomLibrary;
import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin;
public final class MineManager {
private MineManager() {
}
public static boolean regenerate(World world) {
MineGenerationConfig generationConfig = MineGenerationConfig.fromConfiguration(plugin.getConfig(), world);
MineRoomLibrary roomLibrary = MineRoomLibrary.load(
plugin.getDataFolder(),
generationConfig.roomsDirectory(),
plugin.getLogger()
);
if (roomLibrary.isEmpty()) {
plugin.getLogger().severe("[MineGen] No valid room prefabs were loaded from: "
+ roomLibrary.roomsDirectory().getAbsolutePath());
return false;
}
MineGenerator.Result generationResult = new MineGenerator(
world,
generationConfig,
roomLibrary,
plugin.getLogger()
).generate();
if (!generationResult.success()) {
return false;
}
Location spawn = getConfiguredSpawnLocation(world);
world.setSpawnLocation(spawn);
plugin.getLogger().info("[MineGen] Mine generated with " + generationResult.roomsPlaced() + " rooms.");
return true;
}
public static Location getConfiguredSpawnLocation(World world) {
Location fallback = new Location(world, 5.5, world.getMaxHeight() - 9, 5.5, 0f, 0f);
if (!plugin.getConfig().isConfigurationSection("mine.spawn")) {
return fallback;
}
double x = plugin.getConfig().getDouble("mine.spawn.x", fallback.getX());
double y = plugin.getConfig().getDouble("mine.spawn.y", fallback.getY());
double z = plugin.getConfig().getDouble("mine.spawn.z", fallback.getZ());
float yaw = (float) plugin.getConfig().getDouble("mine.spawn.yaw", 0.0);
float pitch = (float) plugin.getConfig().getDouble("mine.spawn.pitch", 0.0);
return new Location(world, x, y, z, yaw, pitch);
}
}

@ -0,0 +1,230 @@
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.Bukkit;
import org.bukkit.GameRule;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
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.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;
if (loader.worldExists(MINE_WORLD_NAME)) {
try {
slimeWorld = asp.readWorld(loader, MINE_WORLD_NAME, false, new SlimePropertyMap());
} catch (Exception exception) {
plugin.getLogger().warning("Mine world data is corrupted, recreating...");
loader.deleteWorld(MINE_WORLD_NAME);
}
}
if (slimeWorld == null) {
SlimePropertyMap properties = new SlimePropertyMap();
properties.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, properties, 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);
if (plugin.getConfig().getBoolean("mine.paste-on-startup", false)) {
MineManager.regenerate(mineWorld);
}
plugin.getLogger().info("Mine world loaded successfully.");
}
});
} catch (Exception exception) {
exception.printStackTrace();
plugin.getLogger().severe("Failed to load mine world.");
}
});
}
public static World getWorld() {
return mineWorld;
}
public static Location getSpawnLocation() {
if (mineWorld == null) {
return null;
}
return MineManager.getConfiguredSpawnLocation(mineWorld);
}
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 {
loader.deleteWorld(MINE_WORLD_NAME);
plugin.getLogger().info("[MineWorld] Old mine world metadata deleted.");
SlimePropertyMap properties = new SlimePropertyMap();
properties.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, properties, 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 exception) {
exception.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);
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent event) {
Player player = event.getPlayer();
if (isInMineWorld(player)) {
applyMiningFatigue(player);
}
if (isInMineWorld(event.getFrom())) {
player.removePotionEffect(PotionEffectType.MINING_FATIGUE);
}
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
if (isInMineWorld(player)) {
applyMiningFatigue(player);
}
}
@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,
true,
false,
false
));
}
@EventHandler
public void onItemDamage(PlayerItemDamageEvent event) {
if (!isInMineWorld(event.getPlayer())) {
return;
}
if (event.getItem().getType().getMaxDurability() <= 0) {
return;
}
event.setDamage(event.getDamage() * 2);
}
@EventHandler
public void onEntityExplode(EntityExplodeEvent event) {
if (!isInMineWorld(event.getEntity().getWorld())) {
return;
}
event.blockList().removeIf(block -> block.getType() == Material.BEDROCK);
}
@EventHandler
public void onBlockExplode(BlockExplodeEvent event) {
if (!isInMineWorld(event.getBlock().getWorld())) {
return;
}
event.blockList().removeIf(block -> block.getType() == Material.BEDROCK);
}
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
if (!isInMineWorld(event.getPlayer())) {
return;
}
if (event.getBlock().getType() == Material.BEDROCK) {
event.setCancelled(true);
}
}
}

@ -0,0 +1,50 @@
package xyz.soukup.ecoCraftCore.mines.generation;
import java.util.Locale;
public record MineBranchState(
int branchId,
boolean mainPath,
int depth,
String activeNaturalBiome,
int biomeOriginDepth,
int naturalBiomeLength
) {
public boolean hasActiveNaturalBiome() {
return activeNaturalBiome != null && !activeNaturalBiome.isBlank();
}
public MineBranchState withDepth(int newDepth) {
return new MineBranchState(
branchId,
mainPath,
newDepth,
activeNaturalBiome,
biomeOriginDepth,
naturalBiomeLength
);
}
public MineBranchState clearNaturalBiome() {
return new MineBranchState(
branchId,
mainPath,
depth,
null,
-1,
0
);
}
public MineBranchState activateNaturalBiome(String biome, int originDepth, int length) {
return new MineBranchState(
branchId,
mainPath,
depth,
biome == null ? null : biome.toUpperCase(Locale.ROOT),
originDepth,
Math.max(1, length)
);
}
}

@ -0,0 +1,68 @@
package xyz.soukup.ecoCraftCore.mines.generation;
import java.util.ArrayList;
import java.util.List;
public enum MineDirection {
FRONT(1 << 0, 0, 0, 1),
BACK(1 << 1, 0, 0, -1),
LEFT(1 << 2, -1, 0, 0),
RIGHT(1 << 3, 1, 0, 0),
TOP(1 << 4, 0, 1, 0),
BOTTOM(1 << 5, 0, -1, 0);
private static final List<MineDirection> HORIZONTAL_DIRECTIONS = List.of(FRONT, BACK, LEFT, RIGHT);
private final int bitMask;
private final int deltaX;
private final int deltaY;
private final int deltaZ;
MineDirection(int bitMask, int deltaX, int deltaY, int deltaZ) {
this.bitMask = bitMask;
this.deltaX = deltaX;
this.deltaY = deltaY;
this.deltaZ = deltaZ;
}
public int bitMask() {
return bitMask;
}
public int deltaX() {
return deltaX;
}
public int deltaY() {
return deltaY;
}
public int deltaZ() {
return deltaZ;
}
public MineDirection opposite() {
return switch (this) {
case FRONT -> BACK;
case BACK -> FRONT;
case LEFT -> RIGHT;
case RIGHT -> LEFT;
case TOP -> BOTTOM;
case BOTTOM -> TOP;
};
}
public static List<MineDirection> horizontalDirections() {
return HORIZONTAL_DIRECTIONS;
}
public static List<MineDirection> directionsFromMask(int exitsMask) {
List<MineDirection> directions = new ArrayList<>();
for (MineDirection direction : values()) {
if ((exitsMask & direction.bitMask) != 0) {
directions.add(direction);
}
}
return directions;
}
}

@ -0,0 +1,191 @@
package xyz.soukup.ecoCraftCore.mines.generation;
import com.sk89q.worldedit.math.BlockVector3;
import org.bukkit.World;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
public record MineGenerationConfig(
String roomsDirectory,
String starterRoomId,
String defaultBiome,
int roomSize,
int maxDepth,
int maxRooms,
boolean ignoreAir,
BlockVector3 origin,
List<DepthBiomeRule> depthBiomes,
List<NaturalBiomeRule> naturalBiomes
) {
private static final String BASE_PATH = "mine.generator";
private static final Pattern ROOM_IDENTIFIER_PATTERN = Pattern.compile("^[A-Z]{2}\\d+$");
public static MineGenerationConfig fromConfiguration(FileConfiguration configuration, World world) {
String roomsDirectory = configuration.getString(BASE_PATH + ".rooms-directory", "rooms");
String starterRoomId = normalizeRoomIdentifier(configuration.getString(BASE_PATH + ".starter-room", "ST15"));
String defaultBiome = normalizeBiome(configuration.getString(BASE_PATH + ".default-biome", "ST"), "ST");
int roomSize = Math.max(1, configuration.getInt(BASE_PATH + ".room-size", 16));
int maxDepth = Math.max(1, configuration.getInt(BASE_PATH + ".max-depth", 64));
int maxRooms = Math.max(1, configuration.getInt(BASE_PATH + ".max-rooms", 320));
boolean ignoreAir = configuration.getBoolean(BASE_PATH + ".ignore-air", false);
int defaultY = world.getMaxHeight() - 10;
BlockVector3 origin = BlockVector3.at(
configuration.getInt(BASE_PATH + ".origin.x", 0),
configuration.getInt(BASE_PATH + ".origin.y", defaultY),
configuration.getInt(BASE_PATH + ".origin.z", 0)
);
List<DepthBiomeRule> depthBiomes = parseDepthBiomes(
configuration.getMapList(BASE_PATH + ".depth-biomes"),
defaultBiome
);
List<NaturalBiomeRule> naturalBiomes = parseNaturalBiomes(
configuration.getMapList(BASE_PATH + ".natural-biomes")
);
return new MineGenerationConfig(
roomsDirectory,
starterRoomId,
defaultBiome,
roomSize,
maxDepth,
maxRooms,
ignoreAir,
origin,
depthBiomes,
naturalBiomes
);
}
public String resolveDepthBiome(int depth) {
String biome = defaultBiome;
for (DepthBiomeRule rule : depthBiomes) {
if (depth >= rule.minDepth()) {
biome = rule.biome();
} else {
break;
}
}
return biome;
}
private static List<DepthBiomeRule> parseDepthBiomes(List<Map<?, ?>> entries, String defaultBiome) {
List<DepthBiomeRule> rules = new ArrayList<>();
for (Map<?, ?> entry : entries) {
String biome = normalizeBiome(entry.get("biome"), null);
if (biome == null) {
continue;
}
int minDepth = Math.max(0, readInt(entry, "min-depth", 0));
rules.add(new DepthBiomeRule(biome, minDepth));
}
if (rules.isEmpty()) {
rules.add(new DepthBiomeRule(defaultBiome, 0));
}
rules.sort(Comparator.comparingInt(DepthBiomeRule::minDepth));
return List.copyOf(rules);
}
private static List<NaturalBiomeRule> parseNaturalBiomes(List<Map<?, ?>> entries) {
List<NaturalBiomeRule> rules = new ArrayList<>();
for (Map<?, ?> entry : entries) {
String biome = normalizeBiome(entry.get("biome"), null);
if (biome == null) {
continue;
}
double chance = clamp(readDouble(entry, "chance", 0.0), 0.0, 1.0);
if (chance <= 0.0) {
continue;
}
int length = Math.max(1, readInt(entry, "length", 1));
rules.add(new NaturalBiomeRule(biome, chance, length));
}
return List.copyOf(rules);
}
private static int readInt(Map<?, ?> entry, String key, int fallback) {
Object value = entry.get(key);
if (value instanceof Number number) {
return number.intValue();
}
if (value instanceof String stringValue) {
try {
return Integer.parseInt(stringValue);
} catch (NumberFormatException ignored) {
return fallback;
}
}
return fallback;
}
private static double readDouble(Map<?, ?> entry, String key, double fallback) {
Object value = entry.get(key);
if (value instanceof Number number) {
return number.doubleValue();
}
if (value instanceof String stringValue) {
try {
return Double.parseDouble(stringValue);
} catch (NumberFormatException ignored) {
return fallback;
}
}
return fallback;
}
private static double clamp(double value, double min, double max) {
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
private static String normalizeRoomIdentifier(String rawValue) {
if (rawValue == null || rawValue.isBlank()) {
return "ST15";
}
String normalized = rawValue.trim().toUpperCase(Locale.ROOT);
int extensionIndex = normalized.indexOf('.');
if (extensionIndex > 0) {
normalized = normalized.substring(0, extensionIndex);
}
if (!ROOM_IDENTIFIER_PATTERN.matcher(normalized).matches()) {
return "ST15";
}
return normalized;
}
private static String normalizeBiome(Object rawValue, String fallback) {
if (!(rawValue instanceof String biome) || biome.isBlank()) {
return fallback;
}
return biome.trim().toUpperCase(Locale.ROOT);
}
public record DepthBiomeRule(String biome, int minDepth) {
}
public record NaturalBiomeRule(String biome, double chance, int length) {
}
}

@ -0,0 +1,9 @@
package xyz.soukup.ecoCraftCore.mines.generation;
public record MineGenerationPoint(
MineGridPosition gridPosition,
MineDirection incomingDirection,
MineDirection travelDirection,
MineBranchState branchState
) {
}

@ -0,0 +1,376 @@
package xyz.soukup.ecoCraftCore.mines.generation;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.session.ClipboardHolder;
import org.bukkit.World;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class MineGenerator {
private static final MineGridPosition STARTER_GRID_POSITION = new MineGridPosition(0, 0, 0);
private final World world;
private final MineGenerationConfig config;
private final MineRoomLibrary roomLibrary;
private final Logger logger;
private final Random random;
private int nextBranchId = 1;
public MineGenerator(World world, MineGenerationConfig config, MineRoomLibrary roomLibrary, Logger logger) {
this.world = world;
this.config = config;
this.roomLibrary = roomLibrary;
this.logger = logger;
this.random = ThreadLocalRandom.current();
}
public Result generate() {
List<MineRoomPrefab> starterCandidates = roomLibrary.roomsForIdentifier(config.starterRoomId());
if (starterCandidates.isEmpty()) {
logger.severe("[MineGen] Starter room '" + config.starterRoomId() + "' was not found in room prefabs.");
return Result.failure();
}
MineRoomPrefab starterRoom = chooseWeighted(starterCandidates);
if (starterRoom == null) {
logger.severe("[MineGen] Starter room selection failed.");
return Result.failure();
}
if (!hasAllHorizontalExits(starterRoom)) {
logger.severe("[MineGen] Starter room '" + starterRoom.sourceFile().getName()
+ "' must contain FRONT, BACK, LEFT and RIGHT exits.");
return Result.failure();
}
Map<MineGridPosition, MineRoomPrefab> occupiedPositions = new HashMap<>();
Deque<MineGenerationPoint> generationQueue = new ArrayDeque<>();
int roomsPlaced = 0;
com.sk89q.worldedit.world.World worldEditWorld = BukkitAdapter.adapt(world);
try (EditSession editSession = WorldEdit.getInstance().newEditSession(worldEditWorld)) {
pasteRoom(editSession, starterRoom, STARTER_GRID_POSITION);
occupiedPositions.put(STARTER_GRID_POSITION, starterRoom);
roomsPlaced++;
for (MineDirection direction : MineDirection.horizontalDirections()) {
MineBranchState branchState = new MineBranchState(nextBranchId++, true, 1, null, -1, 0);
enqueueNextPoint(generationQueue, STARTER_GRID_POSITION, direction, branchState);
}
while (!generationQueue.isEmpty() && roomsPlaced < config.maxRooms()) {
MineGenerationPoint point = generationQueue.poll();
MineBranchState branchState = expireNaturalBiomeIfNeeded(point.branchState());
if (branchState.depth() > config.maxDepth()) {
continue;
}
if (occupiedPositions.containsKey(point.gridPosition())) {
continue;
}
MineBranchState placementState = maybeActivateNaturalBiome(branchState);
String resolvedBiome = resolveBiome(placementState);
MineRoomPrefab selectedRoom = selectRoom(
point,
placementState,
resolvedBiome,
occupiedPositions
);
if (selectedRoom == null) {
continue;
}
pasteRoom(editSession, selectedRoom, point.gridPosition());
occupiedPositions.put(point.gridPosition(), selectedRoom);
roomsPlaced++;
enqueueFollowingPoints(
generationQueue,
point,
placementState,
selectedRoom
);
}
} catch (WorldEditException exception) {
logger.log(Level.SEVERE, "[MineGen] Failed to generate mine layout.", exception);
return Result.failure();
}
return new Result(true, roomsPlaced);
}
private String resolveBiome(MineBranchState branchState) {
if (branchState.hasActiveNaturalBiome()) {
return branchState.activeNaturalBiome();
}
return config.resolveDepthBiome(branchState.depth());
}
private MineRoomPrefab selectRoom(
MineGenerationPoint point,
MineBranchState branchState,
String biome,
Map<MineGridPosition, MineRoomPrefab> occupiedPositions
) {
List<MineRoomPrefab> candidates = collectCandidateRooms(
point,
branchState,
biome,
occupiedPositions,
true
);
if (candidates.isEmpty()) {
candidates = collectCandidateRooms(
point,
branchState,
biome,
occupiedPositions,
false
);
}
if (candidates.isEmpty()) {
return null;
}
return chooseWeighted(candidates);
}
private List<MineRoomPrefab> collectCandidateRooms(
MineGenerationPoint point,
MineBranchState branchState,
String biome,
Map<MineGridPosition, MineRoomPrefab> occupiedPositions,
boolean avoidOccupiedExitTargets
) {
int blockedExitMask = buildBlockedExitMask(point.gridPosition(), point.incomingDirection(), occupiedPositions);
List<MineRoomPrefab> candidates = new ArrayList<>();
for (MineRoomPrefab room : roomLibrary.allRooms()) {
if (!room.biome().equalsIgnoreCase(biome)) {
continue;
}
if (!room.hasExit(point.incomingDirection())) {
continue;
}
if (!matchesBranchConstraints(room, branchState.mainPath())) {
continue;
}
if (avoidOccupiedExitTargets && (room.exitsMask() & blockedExitMask) != 0) {
continue;
}
candidates.add(room);
}
return candidates;
}
private boolean matchesBranchConstraints(MineRoomPrefab room, boolean mainPath) {
int exitCount = room.exitCount();
if (mainPath) {
return exitCount >= 2;
}
return exitCount == 2;
}
private int buildBlockedExitMask(
MineGridPosition targetPosition,
MineDirection incomingDirection,
Map<MineGridPosition, MineRoomPrefab> occupiedPositions
) {
int blockedMask = 0;
for (MineDirection direction : MineDirection.values()) {
if (direction == incomingDirection) {
continue;
}
if (occupiedPositions.containsKey(targetPosition.offset(direction))) {
blockedMask |= direction.bitMask();
}
}
return blockedMask;
}
private void enqueueFollowingPoints(
Deque<MineGenerationPoint> generationQueue,
MineGenerationPoint point,
MineBranchState placementState,
MineRoomPrefab room
) {
List<MineDirection> outgoingDirections = room.exits().stream()
.filter(direction -> direction != point.incomingDirection())
.toList();
if (outgoingDirections.isEmpty()) {
return;
}
MineDirection continuationDirection = chooseContinuationDirection(outgoingDirections, point.travelDirection());
MineBranchState continuationState = placementState.withDepth(placementState.depth() + 1);
enqueueNextPoint(generationQueue, point.gridPosition(), continuationDirection, continuationState);
if (!placementState.mainPath()) {
return;
}
for (MineDirection direction : outgoingDirections) {
if (direction == continuationDirection) {
continue;
}
MineBranchState childBranchState = new MineBranchState(
nextBranchId++,
false,
placementState.depth() + 1,
placementState.activeNaturalBiome(),
placementState.biomeOriginDepth(),
placementState.naturalBiomeLength()
);
enqueueNextPoint(generationQueue, point.gridPosition(), direction, childBranchState);
}
}
private MineDirection chooseContinuationDirection(List<MineDirection> outgoingDirections, MineDirection preferredDirection) {
if (outgoingDirections.contains(preferredDirection)) {
return preferredDirection;
}
return outgoingDirections.get(random.nextInt(outgoingDirections.size()));
}
private MineBranchState expireNaturalBiomeIfNeeded(MineBranchState branchState) {
if (!branchState.hasActiveNaturalBiome()) {
return branchState;
}
int roomsSpentInBiome = branchState.depth() - branchState.biomeOriginDepth();
if (roomsSpentInBiome >= branchState.naturalBiomeLength()) {
return branchState.clearNaturalBiome();
}
return branchState;
}
private MineBranchState maybeActivateNaturalBiome(MineBranchState branchState) {
if (branchState.hasActiveNaturalBiome()) {
return branchState;
}
if (config.naturalBiomes().isEmpty()) {
return branchState;
}
double totalChance = 0.0;
for (MineGenerationConfig.NaturalBiomeRule rule : config.naturalBiomes()) {
totalChance += rule.chance();
}
if (totalChance <= 0.0) {
return branchState;
}
if (random.nextDouble() > Math.min(1.0, totalChance)) {
return branchState;
}
double selectionRoll = random.nextDouble(totalChance);
double cumulativeChance = 0.0;
for (MineGenerationConfig.NaturalBiomeRule rule : config.naturalBiomes()) {
cumulativeChance += rule.chance();
if (selectionRoll <= cumulativeChance) {
return branchState.activateNaturalBiome(rule.biome(), branchState.depth(), rule.length());
}
}
return branchState;
}
private boolean hasAllHorizontalExits(MineRoomPrefab room) {
for (MineDirection direction : MineDirection.horizontalDirections()) {
if (!room.hasExit(direction)) {
return false;
}
}
return true;
}
private static void enqueueNextPoint(
Deque<MineGenerationPoint> generationQueue,
MineGridPosition currentPosition,
MineDirection outgoingDirection,
MineBranchState branchState
) {
generationQueue.add(new MineGenerationPoint(
currentPosition.offset(outgoingDirection),
outgoingDirection.opposite(),
outgoingDirection,
branchState
));
}
private MineRoomPrefab chooseWeighted(List<MineRoomPrefab> rooms) {
if (rooms.isEmpty()) {
return null;
}
if (rooms.size() == 1) {
return rooms.getFirst();
}
int totalWeight = 0;
for (MineRoomPrefab room : rooms) {
totalWeight += Math.max(1, room.weight());
}
int roll = random.nextInt(totalWeight);
int runningWeight = 0;
for (MineRoomPrefab room : rooms) {
runningWeight += Math.max(1, room.weight());
if (roll < runningWeight) {
return room;
}
}
return rooms.getLast();
}
private void pasteRoom(EditSession editSession, MineRoomPrefab room, MineGridPosition gridPosition) throws WorldEditException {
BlockVector3 target = toWorldPosition(gridPosition);
Operation operation = new ClipboardHolder(room.clipboard())
.createPaste(editSession)
.to(target)
.ignoreAirBlocks(config.ignoreAir())
.build();
Operations.complete(operation);
}
private BlockVector3 toWorldPosition(MineGridPosition gridPosition) {
return config.origin().add(
gridPosition.x() * config.roomSize(),
gridPosition.y() * config.roomSize(),
gridPosition.z() * config.roomSize()
);
}
public record Result(boolean success, int roomsPlaced) {
public static Result failure() {
return new Result(false, 0);
}
}
}

@ -0,0 +1,12 @@
package xyz.soukup.ecoCraftCore.mines.generation;
public record MineGridPosition(int x, int y, int z) {
public MineGridPosition offset(MineDirection direction) {
return new MineGridPosition(
x + direction.deltaX(),
y + direction.deltaY(),
z + direction.deltaZ()
);
}
}

@ -0,0 +1,181 @@
package xyz.soukup.ecoCraftCore.mines.generation;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public final class MineRoomLibrary {
private static final Pattern ROOM_FILE_PATTERN = Pattern.compile("^([A-Za-z]{2})(\\d+)(?:_(\\d+))?$");
private final File roomsDirectory;
private final List<MineRoomPrefab> prefabs;
private final Map<String, List<MineRoomPrefab>> prefabsByIdentifier;
private MineRoomLibrary(
File roomsDirectory,
List<MineRoomPrefab> prefabs,
Map<String, List<MineRoomPrefab>> prefabsByIdentifier
) {
this.roomsDirectory = roomsDirectory;
this.prefabs = prefabs;
this.prefabsByIdentifier = prefabsByIdentifier;
}
public static MineRoomLibrary load(File dataFolder, String roomsDirectoryName, Logger logger) {
String safeDirectoryName = roomsDirectoryName == null || roomsDirectoryName.isBlank()
? "rooms"
: roomsDirectoryName;
File roomsDirectory = new File(dataFolder, safeDirectoryName);
if (!roomsDirectory.exists() && !roomsDirectory.mkdirs()) {
logger.warning("[MineGen] Could not create room prefab directory: " + roomsDirectory.getAbsolutePath());
}
File[] roomFiles = roomsDirectory.listFiles(MineRoomLibrary::isSupportedRoomFile);
if (roomFiles == null || roomFiles.length == 0) {
return new MineRoomLibrary(roomsDirectory, List.of(), Map.of());
}
List<MineRoomPrefab> prefabs = new ArrayList<>();
Map<String, List<MineRoomPrefab>> prefabsByIdentifier = new HashMap<>();
for (File roomFile : roomFiles) {
RoomFileMetadata metadata = parseMetadata(roomFile.getName(), logger);
if (metadata == null) {
continue;
}
ClipboardFormat format = ClipboardFormats.findByFile(roomFile);
if (format == null) {
logger.warning("[MineGen] Unsupported room format: " + roomFile.getName());
continue;
}
try (
FileInputStream inputStream = new FileInputStream(roomFile);
ClipboardReader reader = format.getReader(inputStream)
) {
Clipboard clipboard = reader.read();
MineRoomPrefab prefab = new MineRoomPrefab(
metadata.identifier(),
metadata.biome(),
metadata.exitsMask(),
metadata.weight(),
roomFile,
clipboard
);
prefabs.add(prefab);
prefabsByIdentifier.computeIfAbsent(metadata.identifier(), ignored -> new ArrayList<>()).add(prefab);
} catch (IOException exception) {
logger.warning("[MineGen] Failed to load room prefab " + roomFile.getName()
+ ": " + exception.getMessage());
}
}
return new MineRoomLibrary(
roomsDirectory,
List.copyOf(prefabs),
prefabsByIdentifier.entrySet().stream()
.collect(Collectors.toUnmodifiableMap(
Map.Entry::getKey,
entry -> List.copyOf(entry.getValue())
))
);
}
public File roomsDirectory() {
return roomsDirectory;
}
public boolean isEmpty() {
return prefabs.isEmpty();
}
public List<MineRoomPrefab> allRooms() {
return prefabs;
}
public List<MineRoomPrefab> roomsForIdentifier(String identifier) {
if (identifier == null) {
return List.of();
}
return prefabsByIdentifier.getOrDefault(identifier.toUpperCase(Locale.ROOT), List.of());
}
private static boolean isSupportedRoomFile(File file) {
if (!file.isFile()) {
return false;
}
String lowerName = file.getName().toLowerCase(Locale.ROOT);
return lowerName.endsWith(".schematic") || lowerName.endsWith(".schem");
}
private static RoomFileMetadata parseMetadata(String fileName, Logger logger) {
int extensionIndex = fileName.lastIndexOf('.');
if (extensionIndex <= 0) {
logger.warning("[MineGen] Ignoring room prefab '" + fileName + "': missing file extension.");
return null;
}
String baseName = fileName.substring(0, extensionIndex);
Matcher matcher = ROOM_FILE_PATTERN.matcher(baseName);
if (!matcher.matches()) {
logger.warning("[MineGen] Ignoring room prefab '" + fileName
+ "': expected format <room_biome><room_exits>_<room_weight>.");
return null;
}
String biome = matcher.group(1).toUpperCase(Locale.ROOT);
int exitsMask;
try {
exitsMask = Integer.parseInt(matcher.group(2));
} catch (NumberFormatException exception) {
logger.warning("[MineGen] Ignoring room prefab '" + fileName + "': invalid exits value.");
return null;
}
if (exitsMask <= 0 || exitsMask > 63) {
logger.warning("[MineGen] Ignoring room prefab '" + fileName
+ "': exits bitmask must be in range 1..63.");
return null;
}
int weight = 1;
String weightGroup = matcher.group(3);
if (weightGroup != null) {
try {
weight = Integer.parseInt(weightGroup);
if (weight <= 0) {
logger.warning("[MineGen] Room prefab '" + fileName + "' has non-positive weight, using 1.");
weight = 1;
}
} catch (NumberFormatException exception) {
logger.warning("[MineGen] Room prefab '" + fileName + "' has invalid weight, using 1.");
weight = 1;
}
}
String identifier = biome + exitsMask;
return new RoomFileMetadata(identifier, biome, exitsMask, weight);
}
private record RoomFileMetadata(String identifier, String biome, int exitsMask, int weight) {
}
}

@ -0,0 +1,28 @@
package xyz.soukup.ecoCraftCore.mines.generation;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import java.io.File;
import java.util.List;
public record MineRoomPrefab(
String identifier,
String biome,
int exitsMask,
int weight,
File sourceFile,
Clipboard clipboard
) {
public boolean hasExit(MineDirection direction) {
return (exitsMask & direction.bitMask()) != 0;
}
public int exitCount() {
return Integer.bitCount(exitsMask);
}
public List<MineDirection> exits() {
return MineDirection.directionsFromMask(exitsMask);
}
}

@ -1,21 +1,64 @@
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> createCommand() {
public static LiteralArgumentBuilder<CommandSourceStack> getCommand() {
LiteralArgumentBuilder<CommandSourceStack> create = Commands.literal("create")
.then(Commands.argument("type", IntegerArgumentType.integer(0, 1)))
.executes(RegionAdminCommand::createRegion);
.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(create)
.then(addMember);
}
private static int createRegion(CommandContext<CommandSourceStack> context) {
@ -35,4 +78,20 @@ public class RegionAdminCommand {
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;
}
}

@ -1,36 +1,319 @@
package xyz.soukup.ecoCraftCore.regions;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
import org.bukkit.entity.Minecart;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.inventory.InventoryPickupItemEvent;
import org.bukkit.event.player.*;
import org.bukkit.event.vehicle.VehicleDestroyEvent;
import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import xyz.soukup.ecoCraftCore.database.objects.Region;
import xyz.soukup.ecoCraftCore.database.objects.RegionMember;
import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin;
public class RegionEvents implements Listener {
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event){
event.setCancelled(!isRegionMember(event.getClickedBlock().getWorld().getName(), event.getPlayer(), event.getClickedBlock().getX(), event.getClickedBlock().getY()));
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()
);
if (allowed){
return;
}
event.setCancelled(true);
}
@EventHandler
public void onPlayerInteractPhysical(PlayerInteractEvent event){
if (!event.getAction().equals(Action.PHYSICAL)){
return;
}
private boolean isRegionMember(String island, Player player, int x, int y){
Region region = Region.findRegion(x, y, island);
Player player = event.getPlayer();
Location location = player.getLocation();
if (region == null){
return false;
}
boolean allowed = isAllowedToInteract(
location.getWorld().getName(),
player,
location.getBlockX(),
location.getBlockY(),
location.getBlockZ()
);
String name = player.getName();
if (allowed){
return;
}
for (RegionMember regionMember : region.getRegionMembers()){
if (regionMember.getMembertype().equals("player") && regionMember.getName().equals(name)){
return true;
}
}
event.setCancelled(true);
}
@EventHandler
public void onBlockPlace(BlockPlaceEvent event) {
Block block = event.getBlockPlaced();
boolean allowed = isAllowedToInteract(
block.getWorld().getName(),
event.getPlayer(),
block.getX(),
block.getY(),
block.getZ()
);
if (allowed){
return;
}
event.setCancelled(true);
}
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
Block block = event.getBlock();
boolean allowed = isAllowedToInteract(
block.getWorld().getName(),
event.getPlayer(),
block.getX(),
block.getY(),
block.getZ()
);
if (allowed){
return;
}
event.setCancelled(true);
}
@EventHandler
public void onBucketUse(PlayerBucketEmptyEvent event) {
Block clickedBlock = event.getBlockClicked();
boolean allowed = isAllowedToInteract(
clickedBlock.getWorld().getName(),
event.getPlayer(),
clickedBlock.getX(),
clickedBlock.getY(),
clickedBlock.getZ()
);
if (allowed){
return;
}
event.setCancelled(true);
}
@EventHandler
public void onBucketUse(PlayerBucketFillEvent event) {
Block clickedBlock = event.getBlockClicked();
boolean allowed = isAllowedToInteract(
clickedBlock.getWorld().getName(),
event.getPlayer(),
clickedBlock.getX(),
clickedBlock.getY(),
clickedBlock.getZ()
);
if (allowed){
return;
}
event.setCancelled(true);
}
@EventHandler
public void onEntityInteract(PlayerInteractEntityEvent event){
Entity entity = event.getRightClicked();
Location location = entity.getLocation();
boolean allowed = isAllowedToInteract(
location.getWorld().getName(),
event.getPlayer(),
location.getBlockX(),
location.getBlockY(),
location.getBlockZ());
if (allowed){
return;
}
event.setCancelled(true);
}
@EventHandler
public void onEntityHit(EntityDamageByEntityEvent event){
if (!(event.getDamager() instanceof Player player)){
return;
}
Entity entity = event.getEntity();
Location location = entity.getLocation();
boolean allowed = isAllowedToInteract(
location.getWorld().getName(),
player,
location.getBlockX(),
location.getBlockY(),
location.getBlockZ());
if (allowed){
return;
}
event.setCancelled(true);
}
@EventHandler
public void onMinecartPush(VehicleEntityCollisionEvent event) {
if (!(event.getEntity() instanceof Player player)){
return;
}
Location location = event.getVehicle().getLocation();
boolean allowed = isAllowedToInteract(
location.getWorld().getName(),
player,
location.getBlockX(),
location.getBlockY(),
location.getBlockZ());
if (allowed){
return;
}
event.setCancelled(true);
}
//Preventing hopper interaction :P
@EventHandler
public void onDrop(PlayerDropItemEvent event) {
Player player = event.getPlayer();
Location location = player.getLocation();
boolean allowed = isAllowedToInteract(
location.getWorld().getName(),
player,
location.getBlockX(),
location.getBlockY(),
location.getBlockZ());
if (allowed){
return;
}
Item item = event.getItemDrop();
item.getPersistentDataContainer().set(new NamespacedKey(plugin, "hopper_stopper"), PersistentDataType.BYTE, (byte) 1);
}
@EventHandler
public void onHopperPickup(InventoryPickupItemEvent event) {
Item item = event.getItem();
if (!item.getPersistentDataContainer().has(new NamespacedKey(plugin, "hopper_stopper"), PersistentDataType.BYTE)) {
return;
}
event.setCancelled(true);
}
@EventHandler
public void onVehicleDestroy(VehicleDestroyEvent event) {
if (!(event.getAttacker() instanceof Player player)){
return;
}
Location location = event.getVehicle().getLocation();
boolean allowed = isAllowedToInteract(
location.getWorld().getName(),
player,
location.getBlockX(),
location.getBlockY(),
location.getBlockZ());
if (allowed){
return;
}
event.setCancelled(true);
}
private boolean isAllowedToInteract(String island, Player player, Location location){
return isAllowedToInteract(island, player, location.getBlockX(), location.getBlockY(), location.getBlockZ());
}
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;
}
}

@ -3,6 +3,7 @@ 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.database.objects.RegionMember;
import xyz.soukup.ecoCraftCore.positionMarker.MarkerEvent;
public class RegionManager {
@ -20,15 +21,57 @@ public class RegionManager {
String worldName = primaryLocation.getWorld().getName();
int x1 = primaryLocation.getBlockX();
int y1 = primaryLocation.getBlockZ();
int y1 = primaryLocation.getBlockY();
int z1 = primaryLocation.getBlockZ();
int x2 = secondaryLocation.getBlockX();
int y2 = secondaryLocation.getBlockZ();
int y2 = secondaryLocation.getBlockY();
int z2 = secondaryLocation.getBlockZ();
Region region = new Region(worldName, type, x1, y1, x2, y2);
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;
}
public static boolean isAllowedToInteract(Player player, Location location){
if (player.isOp()){
return true;
}
int x = location.getBlockX();
int y = location.getBlockY();
int z = location.getBlockZ();
String island = location.getWorld().getName();
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;
}
}

@ -1,107 +0,0 @@
package xyz.soukup.ecoCraftCore.shop;
import com.mojang.brigadier.arguments.*;
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.block.Chest;
import org.bukkit.block.Sign;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import xyz.soukup.ecoCraftCore.positionMarker.MarkerEvent;
import xyz.soukup.ecoCraftCore.database.objects.Shop;
import xyz.soukup.ecoCraftCore.inventory.VirtualChest;
import xyz.soukup.ecoCraftCore.inventory.InventoryUtils;
import xyz.soukup.ecoCraftCore.messages.Messages;
import xyz.soukup.ecoCraftCore.utilities.PDC;
public class ShopAdminCommand {
public static LiteralArgumentBuilder<CommandSourceStack> getCommand() {
// Define the argument types
var amountArg = Commands.argument("amount", IntegerArgumentType.integer(1));
var buyPriceArg = Commands.argument("buy_price", FloatArgumentType.floatArg(0.0F));
var sellPriceArg = Commands.argument("sell_price", FloatArgumentType.floatArg(0.0F));
// Path: /shop <amount> ...
return Commands.literal("shop-admin")
.then(amountArg
// Branch 1: ... buy <price> [sell <price>]
.then(Commands.literal("buy")
.then(buyPriceArg
.executes(ShopAdminCommand::createShop)
.then(Commands.literal("sell")
.then(sellPriceArg.executes(ShopAdminCommand::createShop)))))
// Branch 2: ... sell <price> [buy <price>]
.then(Commands.literal("sell")
.then(sellPriceArg
.executes(ShopAdminCommand::createShop)
.then(Commands.literal("buy")
.then(buyPriceArg.executes(ShopAdminCommand::createShop))))));
}
private static int createShop(CommandContext<CommandSourceStack> ctx) {
CommandSourceStack source = ctx.getSource();
if (!(source.getSender() instanceof Player player)) {
Messages.send(source.getSender(), "generic.error.not-player");
return 0;
}
// 1. Check if blocks are marked
Chest chest = MarkerEvent.chests.get(player);
Sign sign = MarkerEvent.signs.get(player);
if (sign == null || chest == null) {
Messages.send(player, "shop.error.not-marked");
return 0;
}
// 2. Validate Item in Hand
ItemStack item = player.getInventory().getItemInMainHand();
if (item.isEmpty()) {
Messages.send(player, "shop.error.empty-hand");
return 0;
}
// 3. Check if already a shop
if (PDC.getInteger(sign, "shop") != null) {
Messages.send(player, "shop.error.already-shop");
return 0;
}
// 4. Parse Arguments Safely
int amount = ctx.getArgument("amount", Integer.class);
float buyPrice = 0.0F;
try {
buyPrice = ctx.getArgument("buy_price", Float.class);
} catch (IllegalArgumentException ignored) {}
float sellPrice = 0.0F;
try {
sellPrice = ctx.getArgument("sell_price", Float.class);
} catch (IllegalArgumentException ignored) {}
// 5. Database and PDC Logic
VirtualChest virtualChest = VirtualChest.getOrCreate(chest);
virtualChest.databaseSave();
PDC.set(chest, "virtual", virtualChest.getId());
// 6. Inventory Calculations
int freeSpace = InventoryUtils.getSpaceLeft(chest.getInventory(), item);
int stock = InventoryUtils.getCount(chest.getInventory(), item);
// 7. Final Creation
Shop shop = new Shop(player, item, stock, freeSpace, amount, buyPrice, sellPrice, virtualChest.getId());
shop.setOwnerType("admin");
shop.setOwner("admin");
shop.save();
shop.writeIntoSign(sign);
Messages.send(player, "shop.created");
return 1;
}
}

@ -11,7 +11,7 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import xyz.soukup.ecoCraftCore.positionMarker.MarkerEvent;
import xyz.soukup.ecoCraftCore.database.objects.Shop;
import xyz.soukup.ecoCraftCore.inventory.VirtualChest;
import xyz.soukup.ecoCraftCore.database.objects.VirtualChest;
import xyz.soukup.ecoCraftCore.inventory.InventoryUtils;
import xyz.soukup.ecoCraftCore.messages.Messages;
import xyz.soukup.ecoCraftCore.utilities.PDC;

@ -1,6 +1,8 @@
package xyz.soukup.ecoCraftCore.shop;
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.StaticPane;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.dialog.DialogResponseView;
@ -11,13 +13,16 @@ import io.papermc.paper.registry.data.dialog.input.DialogInput;
import io.papermc.paper.registry.data.dialog.type.DialogType;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.event.ClickCallback;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
@ -29,7 +34,7 @@ import xyz.soukup.ecoCraftCore.messages.Messages;
import xyz.soukup.ecoCraftCore.database.objects.Account;
import xyz.soukup.ecoCraftCore.database.objects.Shop;
import xyz.soukup.ecoCraftCore.database.objects.Transaction;
import xyz.soukup.ecoCraftCore.inventory.VirtualChest;
import xyz.soukup.ecoCraftCore.database.objects.VirtualChest;
import xyz.soukup.ecoCraftCore.utilities.*;
import java.util.HashMap;
@ -87,6 +92,39 @@ public class ShopLogic implements Listener {
gui.show(player);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onDestroy(BlockBreakEvent event){
if (event.isCancelled()){
return;
}
Block block = event.getBlock();
if (!(block.getState() instanceof Sign sign)){
return;
}
Integer id = PDC.getInteger(sign, "shop");
if (id == null){
return;
}
Shop shop = Shop.findById(id);
if (shop == null){
return;
}
event.setCancelled(true);
Player player = event.getPlayer();
HopperGui gui = confirmBreakGui(block, shop);
gui.show(player);
}
public static ChestGui buildShopGui(Player player, Shop shop){
@ -219,7 +257,6 @@ public class ShopLogic implements Listener {
return;
}
boolean isAdminType = shop.getOwnerType().equals("admin");
int amount = shop.getAmount() * multiplier;
float price = shop.getPriceBuy() * multiplier;
ItemStack itemStack = shop.getItemStack();
@ -227,7 +264,7 @@ public class ShopLogic implements Listener {
Account account = Account.getOrCreate(player);
if (!isAdminType && price > account.getBalance()){
if (price > account.getBalance()){
Messages.send(player, "generic.error.no-money.self");
return;
}
@ -261,7 +298,7 @@ public class ShopLogic implements Listener {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("count", Integer.toString(amount));
hashMap.put("price", String.format("%.2f", price));
hashMap.put("item", itemStack.displayName().toString());
hashMap.put("item", PlainTextComponentSerializer.plainText().serialize(itemStack.displayName()));
Messages.send(player, "shop.buy", hashMap);
@ -309,7 +346,6 @@ public class ShopLogic implements Listener {
return;
}
boolean isAdminType = shop.getOwnerType().equals("admin");
int amount = shop.getAmount() * multiplier;
float price = shop.getPriceSell() * multiplier;
ItemStack itemStack = shop.getItemStack();
@ -317,7 +353,7 @@ public class ShopLogic implements Listener {
Account account = Account.getOrCreate(shop.getOwnerType(), shop.getOwner());
if (!isAdminType && price > account.getBalance()){
if (price > account.getBalance()){
Messages.send(player, "generic.error.no-money.self");
return;
}
@ -350,9 +386,48 @@ public class ShopLogic implements Listener {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("count", Integer.toString(amount));
hashMap.put("price", String.format("%.2f", price));
hashMap.put("item", itemStack.displayName().toString());
hashMap.put("item", PlainTextComponentSerializer.plainText().serialize(itemStack.displayName()));
Messages.send(player, "shop.sell", hashMap);
}
public static HopperGui confirmBreakGui(Block block, Shop shop){
HopperGui gui = new HopperGui(Messages.getAsString("menu.destroy-confirmation.title"));
OutlinePane pane = new OutlinePane(0,0, 5, 1);
gui.setOnGlobalClick(inventoryClickEvent -> {
inventoryClickEvent.setCancelled(true);
});
GuiItem confirmButton = new GuiItemBuilder(Material.LIME_WOOL)
.setName(Messages.get("menu.destroy-confirmation.confirm"))
.build();
GuiItem cancelButton = new GuiItemBuilder(Material.RED_WOOL)
.setName(Messages.get("menu.destroy-confirmation.cancel"))
.build();
cancelButton.setAction(event -> {
event.getClickedInventory().close();
});
confirmButton.setAction(event -> {
destroyShopAndBlock(block, shop);
event.getClickedInventory().close();
});
pane.addItem(confirmButton);
pane.addItem(cancelButton);
gui.getSlotsComponent().addPane(pane);
return gui;
}
private static void destroyShopAndBlock(Block block, Shop shop){
block.breakNaturally();
shop.delete();
}
}

@ -0,0 +1,187 @@
package xyz.soukup.ecoCraftCore.sign;
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 io.papermc.paper.dialog.Dialog;
import io.papermc.paper.dialog.DialogResponseView;
import io.papermc.paper.registry.data.dialog.ActionButton;
import io.papermc.paper.registry.data.dialog.DialogBase;
import io.papermc.paper.registry.data.dialog.action.DialogAction;
import io.papermc.paper.registry.data.dialog.input.DialogInput;
import io.papermc.paper.registry.data.dialog.type.DialogType;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickCallback;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.block.sign.Side;
import org.bukkit.block.sign.SignSide;
import org.bukkit.entity.Player;
import org.bukkit.persistence.PersistentDataType;
import xyz.soukup.ecoCraftCore.database.objects.Shop;
import xyz.soukup.ecoCraftCore.messages.Messages;
import xyz.soukup.ecoCraftCore.regions.RegionManager;
import xyz.soukup.ecoCraftCore.utilities.PDC;
import javax.sound.sampled.Line;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class SignEditCommand {
private static final HashMap<Player, List<String>> clipBoard = new HashMap<>();
public static LiteralArgumentBuilder<CommandSourceStack> getCommand(){
return Commands.literal("edit-sign")
.requires(source -> source.getSender() instanceof Player)
.executes(SignEditCommand::openEditMenu);
}
private static int openEditMenu(CommandContext<CommandSourceStack> context){
Player player = (Player) context.getSource().getSender();
Block block = player.getTargetBlock(null, 10);
BlockState blockState = block.getState();
if (!(blockState instanceof Sign sign)){
Messages.send(player, "sign-edit.error.not-sign");
return 1;
}
Location location = sign.getLocation();
if (!RegionManager.isAllowedToInteract(player, location)){
Messages.send(player, "region.error.not-allowed-to-interact");
return 1;
}
Dialog dialog = buildEditDialog(sign, player, null);
player.showDialog(dialog);
return 0;
}
public static Dialog buildEditDialog(Sign sign, Player player, List<String> lines){
if (lines == null){
lines = getLines(sign);
}
List<ActionButton> actionButtons = new ArrayList<>();
actionButtons.add(ActionButton.builder(Messages.get("gui.sign-edit.buttons.copy"))
.action(DialogAction.customClick((view, audience) -> copySignText(view, player, sign), ClickCallback.Options.builder().build()))
.build());
actionButtons.add(ActionButton.builder(Messages.get("gui.sign-edit.buttons.paste"))
.action(DialogAction.customClick((view, audience) -> pasteSignText(sign, player), ClickCallback.Options.builder().build()))
.build());
actionButtons.add(ActionButton.builder(Messages.get("gui.sign-edit.buttons.confirm"))
.action(DialogAction.customClick((view, audience) -> editSign(view, sign, player), ClickCallback.Options.builder().build()))
.build());
actionButtons.add(ActionButton.builder(Messages.get("gui.sign-edit.buttons.cancel"))
.action(DialogAction.customClick((view, audience) -> audience.closeDialog(), ClickCallback.Options.builder().build()))
.build());
List<String> finalLines = lines;
return Dialog.create(builder -> builder.empty()
.base(DialogBase.builder(Messages.get("gui.sign-edit.title"))
.inputs(List.of(
DialogInput.text("line1", Messages.get("gui.sign-edit.inputs.line1"))
.initial(finalLines.getFirst())
.maxLength(256)
.build(),
DialogInput.text("line2", Messages.get("gui.sign-edit.inputs.line2"))
.initial(finalLines.get(1))
.maxLength(256)
.build(),
DialogInput.text("line3", Messages.get("gui.sign-edit.inputs.line3"))
.initial(finalLines.get(2))
.maxLength(256)
.build(),
DialogInput.text("line4", Messages.get("gui.sign-edit.inputs.line4"))
.initial(finalLines.get(3))
.maxLength(256)
.build()
))
.build())
.type(DialogType.multiAction(actionButtons).build())
);
}
private static List<String> getLines(Sign sign){
List<String> lines = new ArrayList<>();
if (PDC.getUniversal(sign, "line1", PersistentDataType.STRING) != null){
for (int i = 1; i<5; i++){
lines.add((String) PDC.getUniversal(sign, "line" + i,PersistentDataType.STRING));
}
return lines;
}
SignSide signSide = sign.getSide(Side.FRONT);
List<Component> componentLines = signSide.lines();
for (Component componentLine : componentLines){
lines.add(MiniMessage.miniMessage().serialize(componentLine));
}
return lines;
}
private static void copySignText(DialogResponseView view, Player player, Sign sign){
List<String> lines = new ArrayList<>();
lines.add(view.getText("line1"));
lines.add(view.getText("line2"));
lines.add(view.getText("line3"));
lines.add(view.getText("line4"));
clipBoard.put(player, lines);
Messages.send(player, "gui.sign-edit.success.copied");
pasteSignText(sign, player);
}
private static void pasteSignText(Sign sign, Player player){
Dialog dialog = buildEditDialog(sign, player, clipBoard.get(player));
player.showDialog(dialog);
}
private static void editSign(DialogResponseView view, Sign sign, Audience audience){
MiniMessage mm = MiniMessage.miniMessage();
SignSide signSide = sign.getSide(Side.FRONT);
List<Component> lines = new ArrayList<>();
for (int i = 1; i < 5; i++) {
String key = "line" + i;
String line = view.getText(key);
if (line == null){
line = "";
}
PDC.setUniversal(sign, key, line, PersistentDataType.STRING);
signSide.line(i-1, mm.deserialize(line));
}
sign.update();
}
}

@ -0,0 +1,142 @@
package xyz.soukup.ecoCraftCore.sit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.type.Stairs;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.vehicle.VehicleExitEvent;
import org.bukkit.persistence.PersistentDataType;
import xyz.soukup.ecoCraftCore.utilities.PDC;
public class LetMeSit implements Listener {
@EventHandler
public void onStairsClick(PlayerInteractEvent event){
Player player = event.getPlayer();
if (player.isSneaking()){
return;
}
if (event.getAction() != Action.RIGHT_CLICK_BLOCK){
return;
}
Block block = event.getClickedBlock();
if (block == null){
return;
}
if (!(block.getBlockData() instanceof Stairs stairs)){
return;
}
if (stairs.getHalf() == Bisected.Half.TOP){
return;
}
switch (stairs.getShape()) {
case INNER_LEFT:
case INNER_RIGHT:
case OUTER_LEFT:
case OUTER_RIGHT:
return;
}
float yaw = switch (stairs.getFacing()) {
case NORTH -> 0f;
case WEST -> -90f;
case EAST -> 90f;
default -> 180f;
};
Location location = block.getLocation();
location.add(0.5, 0.5, 0.5);
location.setYaw(yaw);
World world = location.getWorld();
for (Entity entity : world.getNearbyEntities(location, 0.5, 1,0.5)){
if (!(entity instanceof ArmorStand armorStand)){
continue;
}
if (PDC.getUniversal(armorStand, "chair", PersistentDataType.BOOLEAN) == null){
continue;
}
if (!armorStand.getPassengers().isEmpty()){
return;
}
player.setRotation(armorStand.getYaw(), 0f);
armorStand.addPassenger(player);
event.setCancelled(true);
return;
}
ArmorStand armorStand = world.spawn(location, ArmorStand.class, stand -> {
stand.setInvisible(true);
stand.setInvulnerable(true);
stand.setGravity(false);
stand.setSilent(true);
stand.setMarker(true);
});
PDC.setUniversal(armorStand, "chair", true, PersistentDataType.BOOLEAN);
player.setRotation(yaw, 0f);
armorStand.addPassenger(player);
event.setCancelled(true);
}
@EventHandler
public void unmount(VehicleExitEvent event){
Location location = event.getVehicle().getLocation();
cleanArmorStand(location);
}
@EventHandler
public void playerLeave(PlayerQuitEvent event){
Location location = event.getPlayer().getLocation();
cleanArmorStand(location);
}
@EventHandler
public void teleport(PlayerTeleportEvent event){
Location location = event.getFrom();
cleanArmorStand(location);
}
private void cleanArmorStand(Location location){
World world = location.getWorld();
for (Entity entity : world.getNearbyEntities(location, 0.5, 1,0.5)){
if (!(entity instanceof ArmorStand armorStand)){
continue;
}
if (PDC.getUniversal(armorStand, "chair", PersistentDataType.BOOLEAN) == null){
continue;
}
if (!armorStand.getPassengers().isEmpty()){
continue;
}
armorStand.remove();
}
}
}

@ -1,17 +1,24 @@
package xyz.soukup.ecoCraftCore.virtualChest;
import org.bukkit.block.Chest;
import org.bukkit.block.DoubleChest;
import org.bukkit.block.TileState;
import com.github.stefvanschie.inventoryframework.gui.GuiItem;
import com.github.stefvanschie.inventoryframework.gui.type.HopperGui;
import com.github.stefvanschie.inventoryframework.pane.OutlinePane;
import org.bukkit.Material;
import org.bukkit.block.*;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryMoveItemEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import xyz.soukup.ecoCraftCore.inventory.VirtualChest;
import xyz.soukup.ecoCraftCore.database.objects.Shop;
import xyz.soukup.ecoCraftCore.gui.GuiItemBuilder;
import xyz.soukup.ecoCraftCore.database.objects.VirtualChest;
import xyz.soukup.ecoCraftCore.messages.Messages;
import xyz.soukup.ecoCraftCore.utilities.PDC;
import java.util.HashMap;
@ -20,6 +27,39 @@ public class VirtualChestLogic implements Listener {
public static HashMap<Integer, Integer> openedChests = new HashMap<>();
@EventHandler(priority = EventPriority.HIGHEST)
public void blockBreak(BlockBreakEvent event){
Block block = event.getBlock();
BlockState blockState = block.getState();
Player player = event.getPlayer();
Integer id;
if (blockState instanceof Chest chest){
id = PDC.getInteger(chest, "virtual");
} else if (blockState instanceof DoubleChest doubleChest) {
id = PDC.getInteger((TileState) doubleChest.getRightSide(), "virtual");
}else {
return;
}
if (id == null){
return;
}
VirtualChest virtualChest = VirtualChest.findById(id);
if (virtualChest == null){
return;
}
event.setCancelled(true);
HopperGui gui = confirmBreakGui(block, virtualChest);
gui.show(player);
}
@EventHandler
public void chestOpen(InventoryOpenEvent event){
InventoryHolder inventoryHolder = event.getInventory().getHolder();
@ -143,8 +183,43 @@ public class VirtualChestLogic implements Listener {
virtualChest.save();
}
public static HopperGui confirmBreakGui(Block block, VirtualChest virtualChest){
HopperGui gui = new HopperGui(Messages.getAsString("menu.destroy-confirmation.title"));
OutlinePane pane = new OutlinePane(0,0, 5, 1);
gui.setOnGlobalClick(inventoryClickEvent -> {
inventoryClickEvent.setCancelled(true);
});
GuiItem confirmButton = new GuiItemBuilder(Material.LIME_WOOL)
.setName(Messages.get("menu.destroy-confirmation.confirm"))
.build();
GuiItem cancelButton = new GuiItemBuilder(Material.RED_WOOL)
.setName(Messages.get("menu.destroy-confirmation.cancel"))
.build();
cancelButton.setAction(event -> {
event.getClickedInventory().close();
});
confirmButton.setAction(event -> {
destroyShopAndBlock(block, virtualChest);
event.getClickedInventory().close();
});
pane.addItem(confirmButton);
pane.addItem(cancelButton);
gui.getSlotsComponent().addPane(pane);
return gui;
}
private static void destroyShopAndBlock(Block block, VirtualChest virtualChest){
block.breakNaturally();
virtualChest.deleteSelfAndShops();
}
}

@ -11,4 +11,39 @@ database:
cache:
save-interval: 6000
islands:
spawn: null
spawn: null
mine:
paste-on-startup: false
spawn:
x: 5.5
y: 311.0
z: 5.5
yaw: 0.0
pitch: 0.0
generator:
rooms-directory: "rooms"
starter-room: "ST15"
default-biome: "ST"
room-size: 16
max-depth: 64
max-rooms: 320
ignore-air: false
origin:
x: 0
y: 310
z: 0
depth-biomes:
- biome: "ST"
min-depth: 0
- biome: "DS"
min-depth: 20
- biome: "MS"
min-depth: 40
natural-biomes:
- biome: "DS"
chance: 0.08
length: 6
- biome: "MS"
chance: 0.05
length: 5

@ -15,6 +15,11 @@ generic:
region:
error:
not-marked: "<red>Musíš nejprve označit pozice"
not-exist: "<red>Region neexistuje"
not-allowed-to-interact: "<red>Nemáš povolení dělat změny na tomto území"
sign-edit:
error:
not-sign: "<red>Musíš se dívat na cedulku."
shop:
error:
already-shop: "<red>Tato cedule již je obchod"
@ -49,6 +54,10 @@ marker:
primary: "<green>První pozice označena (<x>,<y>,<z>)"
secondary: "<green>Druhá pozice označena (<x>,<y>,<z>)"
menu:
destroy-confirmation:
title: "<gold>Zníčit obchod?"
confirm: "<green><bold>Ano."
cancel: "<red><bold>Ne."
island-selector:
title: "<green><bold>Výběr ostrovu"
my-islands: "<yellow><bold>Moje ostrovy"
@ -62,6 +71,22 @@ gui:
error:
invalid-input: "<red>Vámi zadané hodnoty nejsou platné."
all: "<green>Seznam guis: <yellow><1>, <2>"
sign-edit:
title: "<green><bold>Editace Cedulky"
inputs:
line1: "<yellow>1. Linka"
line2: "<yellow>2. Linka"
line3: "<yellow>3. Linka"
line4: "<yellow>4. Linka"
glow: "<yellow>Zvíraznit text"
buttons:
confirm: "<green>Potvrdit"
cancel: "<red>Zrušit"
copy: "<yellow>Kopírovat"
paste: "<yellow>Vložit"
success:
copied: "<green>Obsah cedulky zkopírován!"
pasted: "<green>Obsah cedulky vložen!"
shop-edit:
title: "<green><bold>Editace Obchodu"
inputs:
@ -76,4 +101,12 @@ gui:
title: "<dark_green>Obchod"
buy: "<green>Koupit <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ý."
regenerate-failed: "<red>Regenerace dolů selhala. Zkontroluj room prefab soubory a server log."

Loading…
Cancel
Save