Merge pull request 'island-experimenty' (#11) from island-experimenty into master
Reviewed-on: #11master
commit
8126729ea7
36 changed files with 3425 additions and 130 deletions
@ -0,0 +1,34 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.database.objects; |
||||||
|
|
||||||
|
|
||||||
|
import com.j256.ormlite.field.DatabaseField; |
||||||
|
import com.j256.ormlite.table.DatabaseTable; |
||||||
|
import xyz.soukup.ecoCraftCore.database.DaoRegistry; |
||||||
|
|
||||||
|
import java.sql.SQLException; |
||||||
|
|
||||||
|
@DatabaseTable(tableName = "active_servers") |
||||||
|
public class ActiveServer { |
||||||
|
@DatabaseField(unique = true, id = true) |
||||||
|
private String name; |
||||||
|
|
||||||
|
public ActiveServer(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public ActiveServer(String name){ |
||||||
|
this.name = name; |
||||||
|
} |
||||||
|
|
||||||
|
public void save() throws SQLException { |
||||||
|
DaoRegistry.getActiveServerDao().createIfNotExists(this); |
||||||
|
} |
||||||
|
|
||||||
|
public void delete() throws SQLException { |
||||||
|
DaoRegistry.getActiveServerDao().delete(this); |
||||||
|
} |
||||||
|
|
||||||
|
public String getName() { |
||||||
|
return name; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,213 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.database.objects; |
||||||
|
|
||||||
|
import com.j256.ormlite.dao.ForeignCollection; |
||||||
|
import com.j256.ormlite.field.DatabaseField; |
||||||
|
import com.j256.ormlite.field.ForeignCollectionField; |
||||||
|
import com.j256.ormlite.table.DatabaseTable; |
||||||
|
import xyz.soukup.ecoCraftCore.database.DaoRegistry; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
@DatabaseTable(tableName = "regions") |
||||||
|
public class Region { |
||||||
|
|
||||||
|
public static final HashMap<String, List<Region>> cache = new HashMap<>(); |
||||||
|
|
||||||
|
@DatabaseField(generatedId = true) |
||||||
|
private int id; |
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false) |
||||||
|
private String island; |
||||||
|
|
||||||
|
@DatabaseField(columnName = "region_type", canBeNull = false) |
||||||
|
private int regionType; |
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false) |
||||||
|
private int x1; |
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false) |
||||||
|
private int y1; |
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false) |
||||||
|
private int z1; |
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false) |
||||||
|
private int x2; |
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false) |
||||||
|
private int y2; |
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false) |
||||||
|
private int z2; |
||||||
|
|
||||||
|
@ForeignCollectionField(eager = true) |
||||||
|
private ForeignCollection<RegionMember> regionMembers; |
||||||
|
|
||||||
|
@DatabaseField() |
||||||
|
private Integer value; |
||||||
|
|
||||||
|
public Region() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public Region(String island, int regionType, int x1, int y1, int z1, int x2, int y2, int z2) { |
||||||
|
this.island = island; |
||||||
|
this.regionType = regionType; |
||||||
|
this.x1 = Math.min(x1, x2); |
||||||
|
this.y1 = Math.min(y1, y2); |
||||||
|
this.x2 = Math.max(x1, x2); |
||||||
|
this.y2 = Math.max(y1, y2); |
||||||
|
this.z1 = Math.min(z1, z2); |
||||||
|
this.z2 = Math.max(z1, z2); |
||||||
|
|
||||||
|
} |
||||||
|
public int getId() { |
||||||
|
return id; |
||||||
|
} |
||||||
|
|
||||||
|
public int getRegionType() { |
||||||
|
return regionType; |
||||||
|
} |
||||||
|
|
||||||
|
public int getX1() { |
||||||
|
return x1; |
||||||
|
} |
||||||
|
|
||||||
|
public int getZ1() { |
||||||
|
return z1; |
||||||
|
} |
||||||
|
|
||||||
|
public int getZ2() { |
||||||
|
return z2; |
||||||
|
} |
||||||
|
|
||||||
|
public int getY1() { |
||||||
|
return y1; |
||||||
|
} |
||||||
|
|
||||||
|
public int getX2() { |
||||||
|
return x2; |
||||||
|
} |
||||||
|
|
||||||
|
public int getY2() { |
||||||
|
return y2; |
||||||
|
} |
||||||
|
|
||||||
|
public ForeignCollection<RegionMember> getRegionMembers() { |
||||||
|
return regionMembers; |
||||||
|
} |
||||||
|
|
||||||
|
public Integer getValue() { |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
public String getIsland() { |
||||||
|
return island; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void setRegionType(int regionType) { |
||||||
|
this.regionType = regionType; |
||||||
|
} |
||||||
|
|
||||||
|
public void setValue(Integer value) { |
||||||
|
this.value = value; |
||||||
|
} |
||||||
|
|
||||||
|
public void save(){ |
||||||
|
try { |
||||||
|
|
||||||
|
DaoRegistry.getRegionDao().createOrUpdate(this); |
||||||
|
List<Region> regions = cache.get(island); |
||||||
|
|
||||||
|
if (regions == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
regions.removeIf(region -> region.getId() == this.id); |
||||||
|
regions.add(this); |
||||||
|
} catch (Exception e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void addRegionMember(String memberType, String member, String membershipType){ |
||||||
|
RegionMember regionMember = new RegionMember(this, memberType, member, membershipType); |
||||||
|
regionMember.save(); |
||||||
|
|
||||||
|
Region updatedRegion = Region.findById(this.id); |
||||||
|
List<Region> regions = cache.get(island); |
||||||
|
|
||||||
|
if (regions == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
regions.removeIf(region -> region.getId() == this.id); |
||||||
|
regions.add(updatedRegion); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public boolean isInside(int x, int y, int z) { |
||||||
|
return x >= this.x1 && x <= this.x2 && y >= this.y1 && y <= this.y2 && z >= this.z1 && z <= this.z2; |
||||||
|
} |
||||||
|
|
||||||
|
public static Region findById(int id) { |
||||||
|
try { |
||||||
|
return DaoRegistry.getRegionDao().queryForId(id); |
||||||
|
} catch (Exception e) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static void cacheRegions(String island) { |
||||||
|
try { |
||||||
|
List<Region> regions = DaoRegistry.getRegionDao().queryBuilder() |
||||||
|
.where() |
||||||
|
.eq("island", island) |
||||||
|
.query(); |
||||||
|
cache.put(island, regions); |
||||||
|
} catch (Exception e) { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static Region findRegion(int x, int y, int z, String island) { |
||||||
|
|
||||||
|
Region region = null; |
||||||
|
int highestType = -1; |
||||||
|
|
||||||
|
if (!cache.containsKey(island)) { |
||||||
|
cacheRegions(island); |
||||||
|
} |
||||||
|
|
||||||
|
for (Region cachedRegion : cache.get(island)) { |
||||||
|
if (!cachedRegion.isInside(x, y, z)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (highestType >= cachedRegion.getRegionType()){ |
||||||
|
continue; |
||||||
|
} |
||||||
|
region = cachedRegion; |
||||||
|
} |
||||||
|
|
||||||
|
return region; |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void changeBoundaries(int x1, int y1, int z1, int x2, int y2, int z2){ |
||||||
|
this.x1 = Math.min(x1, x2); |
||||||
|
this.y1 = Math.min(y1, y2); |
||||||
|
this.x2 = Math.max(x1, x2); |
||||||
|
this.y2 = Math.max(y1, y2); |
||||||
|
this.z1 = Math.min(z1, z2); |
||||||
|
this.z2 = Math.max(z1, z2); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,65 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.database.objects; |
||||||
|
|
||||||
|
import com.j256.ormlite.field.DatabaseField; |
||||||
|
import com.j256.ormlite.table.DatabaseTable; |
||||||
|
import xyz.soukup.ecoCraftCore.database.DaoRegistry; |
||||||
|
|
||||||
|
import java.sql.SQLException; |
||||||
|
|
||||||
|
@DatabaseTable(tableName = "region_members") |
||||||
|
public class RegionMember { |
||||||
|
|
||||||
|
@DatabaseField(generatedId = true) |
||||||
|
private int id; |
||||||
|
|
||||||
|
@DatabaseField(foreign = true, foreignAutoRefresh = true) |
||||||
|
private Region region; |
||||||
|
|
||||||
|
@DatabaseField(columnName = "member_type", canBeNull = false) |
||||||
|
private String membertype; |
||||||
|
|
||||||
|
@DatabaseField(columnName = "member_name", canBeNull = false) |
||||||
|
private String memberName; |
||||||
|
|
||||||
|
@DatabaseField(columnName = "membership_type", canBeNull = false) |
||||||
|
private String membershipType; |
||||||
|
|
||||||
|
public RegionMember(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public RegionMember(Region region, String membertype, String memberName, String membershipType){ |
||||||
|
this.region = region; |
||||||
|
this.membertype = membertype; |
||||||
|
this.memberName = memberName; |
||||||
|
this.membershipType = membershipType; |
||||||
|
} |
||||||
|
|
||||||
|
public Region getRegion() { |
||||||
|
return region; |
||||||
|
} |
||||||
|
|
||||||
|
public String getMembertype() { |
||||||
|
return membertype; |
||||||
|
} |
||||||
|
|
||||||
|
public String getName() { |
||||||
|
return memberName; |
||||||
|
} |
||||||
|
|
||||||
|
public String getMembershipType() { |
||||||
|
return membershipType; |
||||||
|
} |
||||||
|
|
||||||
|
public void setMembershipType(String membershipType) { |
||||||
|
this.membershipType = membershipType; |
||||||
|
} |
||||||
|
|
||||||
|
public void save(){ |
||||||
|
try { |
||||||
|
DaoRegistry.getRegionMemberDao().createOrUpdate(this); |
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,96 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.database.objects; |
||||||
|
|
||||||
|
import com.j256.ormlite.field.DatabaseField; |
||||||
|
import com.j256.ormlite.table.DatabaseTable; |
||||||
|
import org.bukkit.Location; |
||||||
|
import xyz.soukup.ecoCraftCore.database.DaoRegistry; |
||||||
|
|
||||||
|
import java.sql.SQLException; |
||||||
|
|
||||||
|
@DatabaseTable(tableName = "teleport_requests") |
||||||
|
public class TeleportRequest { |
||||||
|
|
||||||
|
@DatabaseField(unique = true, id = true) |
||||||
|
private String player; |
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false) |
||||||
|
private String server; |
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false) |
||||||
|
private String world; |
||||||
|
|
||||||
|
@DatabaseField |
||||||
|
private Integer x; |
||||||
|
|
||||||
|
@DatabaseField |
||||||
|
private Integer y; |
||||||
|
|
||||||
|
@DatabaseField |
||||||
|
private Integer z; |
||||||
|
|
||||||
|
@DatabaseField |
||||||
|
private Float yaw; |
||||||
|
|
||||||
|
@DatabaseField |
||||||
|
private Float pitch; |
||||||
|
|
||||||
|
public TeleportRequest() { |
||||||
|
} |
||||||
|
|
||||||
|
public TeleportRequest(String player, String server, String world) { |
||||||
|
this.player = player; |
||||||
|
this.server = server; |
||||||
|
this.world = world; |
||||||
|
} |
||||||
|
|
||||||
|
public TeleportRequest(String player, String server, String world, Integer x, Integer y, Integer z, Float yaw, Float pitch) { |
||||||
|
this.player = player; |
||||||
|
this.server = server; |
||||||
|
this.world = world; |
||||||
|
this.x = x; |
||||||
|
this.y = y; |
||||||
|
this.z = z; |
||||||
|
this.yaw = yaw; |
||||||
|
this.pitch = pitch; |
||||||
|
} |
||||||
|
|
||||||
|
public String getServer() { |
||||||
|
return server; |
||||||
|
} |
||||||
|
|
||||||
|
public String getWorld() { |
||||||
|
return world; |
||||||
|
} |
||||||
|
|
||||||
|
public String getPlayer() { |
||||||
|
return player; |
||||||
|
} |
||||||
|
|
||||||
|
public Integer getX() { |
||||||
|
return x; |
||||||
|
} |
||||||
|
|
||||||
|
public Integer getY() { |
||||||
|
return y; |
||||||
|
} |
||||||
|
|
||||||
|
public Integer getZ() { |
||||||
|
return z; |
||||||
|
} |
||||||
|
|
||||||
|
public Float getYaw() { |
||||||
|
return yaw; |
||||||
|
} |
||||||
|
|
||||||
|
public Float getPitch() { |
||||||
|
return pitch; |
||||||
|
} |
||||||
|
|
||||||
|
public void save() throws SQLException { |
||||||
|
DaoRegistry.getTeleportRequestsDao().createOrUpdate(this); |
||||||
|
} |
||||||
|
|
||||||
|
public void delete() throws SQLException { |
||||||
|
DaoRegistry.getTeleportRequestsDao().delete(this); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,257 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.islands; |
||||||
|
|
||||||
|
import com.github.retrooper.packetevents.event.PacketListener; |
||||||
|
import com.github.retrooper.packetevents.event.PacketSendEvent; |
||||||
|
import com.github.retrooper.packetevents.protocol.packettype.PacketType; |
||||||
|
import com.github.retrooper.packetevents.protocol.world.chunk.BaseChunk; |
||||||
|
import com.github.retrooper.packetevents.protocol.world.chunk.Column; |
||||||
|
import com.github.retrooper.packetevents.protocol.world.chunk.LightData; |
||||||
|
import com.github.retrooper.packetevents.protocol.world.chunk.TileEntity; |
||||||
|
import com.github.retrooper.packetevents.protocol.world.chunk.impl.v_1_18.Chunk_v1_18; |
||||||
|
import com.github.retrooper.packetevents.protocol.world.chunk.palette.DataPalette; |
||||||
|
import com.github.retrooper.packetevents.protocol.world.chunk.palette.PaletteType; |
||||||
|
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChunkData; |
||||||
|
import org.bukkit.Bukkit; |
||||||
|
import org.bukkit.NamespacedKey; |
||||||
|
import org.bukkit.World; |
||||||
|
import org.bukkit.entity.Player; |
||||||
|
import org.bukkit.persistence.PersistentDataContainer; |
||||||
|
import org.bukkit.persistence.PersistentDataType; |
||||||
|
import org.bukkit.plugin.java.JavaPlugin; |
||||||
|
|
||||||
|
import java.lang.reflect.Field; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.BitSet; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.HashMap; |
||||||
|
|
||||||
|
public class ChunkModifier implements PacketListener { |
||||||
|
|
||||||
|
private final JavaPlugin plugin; |
||||||
|
private final NamespacedKey keyX1, keyX2, keyZ1, keyZ2, keyType; |
||||||
|
|
||||||
|
HashMap<String, Integer> blockStates = new HashMap<>(); |
||||||
|
|
||||||
|
public ChunkModifier(JavaPlugin plugin) { |
||||||
|
this.plugin = plugin; |
||||||
|
this.keyX1 = new NamespacedKey(plugin, "borderx1"); |
||||||
|
this.keyX2 = new NamespacedKey(plugin, "borderx2"); |
||||||
|
this.keyZ1 = new NamespacedKey(plugin, "bordery1"); // Based on your spec
|
||||||
|
this.keyZ2 = new NamespacedKey(plugin, "borderx2"); // Based on your spec
|
||||||
|
this.keyType = new NamespacedKey(plugin, "island_type"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onPacketSend(PacketSendEvent event) { |
||||||
|
if (event.getPacketType() != PacketType.Play.Server.CHUNK_DATA) return; |
||||||
|
|
||||||
|
Player bukkitPlayer = Bukkit.getPlayer(event.getUser().getUUID()); |
||||||
|
if (bukkitPlayer == null) return; |
||||||
|
|
||||||
|
World world = bukkitPlayer.getWorld(); |
||||||
|
PersistentDataContainer pdc = world.getPersistentDataContainer(); |
||||||
|
|
||||||
|
String type = pdc.get(keyType, PersistentDataType.STRING); |
||||||
|
if (type == null) return; |
||||||
|
|
||||||
|
String block; |
||||||
|
|
||||||
|
switch (type){ |
||||||
|
case "flat_grass": |
||||||
|
case "flat_sand": |
||||||
|
block = "WATER"; |
||||||
|
break; |
||||||
|
case "flat_hell": |
||||||
|
block = "LAVA"; |
||||||
|
break; |
||||||
|
default: |
||||||
|
return; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
Integer x1 = pdc.get(keyX1, PersistentDataType.INTEGER); |
||||||
|
Integer x2 = pdc.get(keyX2, PersistentDataType.INTEGER); |
||||||
|
Integer z1 = pdc.get(keyZ1, PersistentDataType.INTEGER); |
||||||
|
Integer z2 = pdc.get(keyZ2, PersistentDataType.INTEGER); |
||||||
|
if (x1 == null || x2 == null || z1 == null || z2 == null) return; |
||||||
|
|
||||||
|
int minCX = Math.min(x1 >> 4, x2 >> 4) - 1; |
||||||
|
int maxCX = Math.max(x1 >> 4, x2 >> 4) + 1; |
||||||
|
int minCZ = Math.min(z1 >> 4, z2 >> 4) - 1; |
||||||
|
int maxCZ = Math.max(z1 >> 4, z2 >> 4) + 1; |
||||||
|
|
||||||
|
WrapperPlayServerChunkData wrapper = new WrapperPlayServerChunkData(event); |
||||||
|
|
||||||
|
Column original = wrapper.getColumn(); |
||||||
|
if (original == null) return; |
||||||
|
|
||||||
|
int chunkX = original.getX(); |
||||||
|
int chunkZ = original.getZ(); |
||||||
|
if (chunkX >= minCX && chunkX <= maxCX && chunkZ >= minCZ && chunkZ <= maxCZ) return; |
||||||
|
|
||||||
|
Column ghostColumn = createGhostColumn(world, original, resolveBlockStateID(block)); |
||||||
|
if (ghostColumn == null) return; |
||||||
|
|
||||||
|
wrapper.setColumn(ghostColumn); |
||||||
|
|
||||||
|
wrapper.setLightData(buildFullBrightLightData(world)); |
||||||
|
} |
||||||
|
|
||||||
|
private static LightData buildFullBrightLightData(World world) { |
||||||
|
int sections = (world.getMaxHeight() - world.getMinHeight()) >> 4; |
||||||
|
if (sections <= 0) sections = 24; |
||||||
|
|
||||||
|
int lightCount = sections + 2; |
||||||
|
|
||||||
|
byte[] fullSky = new byte[2048]; |
||||||
|
Arrays.fill(fullSky, (byte) 0xFF); |
||||||
|
|
||||||
|
byte[] noBlock = new byte[2048]; |
||||||
|
|
||||||
|
|
||||||
|
boolean hasSkyLight = world.getEnvironment() != World.Environment.NETHER |
||||||
|
&& world.getEnvironment() != World.Environment.THE_END; // adjust if you have custom dims
|
||||||
|
|
||||||
|
BitSet skyMask = new BitSet(lightCount); |
||||||
|
BitSet blockMask = new BitSet(lightCount); |
||||||
|
BitSet emptySkyMask = new BitSet(lightCount); |
||||||
|
BitSet emptyBlockMask = new BitSet(lightCount); |
||||||
|
|
||||||
|
byte[][] skyArray; |
||||||
|
int skyCount; |
||||||
|
if (hasSkyLight) { |
||||||
|
skyMask.set(0, lightCount); |
||||||
|
skyCount = lightCount; |
||||||
|
skyArray = new byte[skyCount][]; |
||||||
|
for (int i = 0; i < skyCount; i++) { |
||||||
|
skyArray[i] = fullSky; |
||||||
|
} |
||||||
|
} else { |
||||||
|
// no skylight dimension
|
||||||
|
emptySkyMask.set(0, lightCount); |
||||||
|
skyCount = 0; |
||||||
|
skyArray = new byte[0][]; |
||||||
|
} |
||||||
|
|
||||||
|
blockMask.set(0, lightCount); |
||||||
|
int blockCount = lightCount; |
||||||
|
byte[][] blockArray = new byte[blockCount][]; |
||||||
|
for (int i = 0; i < blockCount; i++) { |
||||||
|
blockArray[i] = noBlock; |
||||||
|
} |
||||||
|
|
||||||
|
LightData ld = new LightData(); |
||||||
|
ld.setTrustEdges(true); |
||||||
|
ld.setSkyLightMask(skyMask); |
||||||
|
ld.setBlockLightMask(blockMask); |
||||||
|
ld.setEmptySkyLightMask(emptySkyMask); |
||||||
|
ld.setEmptyBlockLightMask(emptyBlockMask); |
||||||
|
ld.setSkyLightCount(skyCount); |
||||||
|
ld.setBlockLightCount(blockCount); |
||||||
|
ld.setSkyLightArray(skyArray); |
||||||
|
ld.setBlockLightArray(blockArray); |
||||||
|
return ld; |
||||||
|
} |
||||||
|
|
||||||
|
private Column createGhostColumn(World world, Column original, int blockID) { |
||||||
|
int sections = (world.getMaxHeight() - world.getMinHeight()) >> 4; |
||||||
|
if (sections <= 0) sections = 24; |
||||||
|
|
||||||
|
BaseChunk[] originalChunks = original.getChunks(); |
||||||
|
|
||||||
|
// Find any existing biome palette from the original packet; we will reuse it.
|
||||||
|
DataPalette fallbackBiomePalette = null; |
||||||
|
if (originalChunks != null) { |
||||||
|
for (BaseChunk bc : originalChunks) { |
||||||
|
if (bc instanceof Chunk_v1_18 c) { |
||||||
|
fallbackBiomePalette = c.getBiomeData(); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
BaseChunk[] chunks = new BaseChunk[sections]; |
||||||
|
|
||||||
|
for (int sectionIndex = 0; sectionIndex < sections; sectionIndex++) { |
||||||
|
DataPalette biomePalette = fallbackBiomePalette; |
||||||
|
|
||||||
|
if (originalChunks != null && sectionIndex < originalChunks.length && originalChunks[sectionIndex] instanceof Chunk_v1_18 c) { |
||||||
|
// Prefer the matching section's biome palette if present
|
||||||
|
biomePalette = c.getBiomeData(); |
||||||
|
} |
||||||
|
|
||||||
|
DataPalette blockPalette = PaletteType.CHUNK.create(); |
||||||
|
|
||||||
|
Chunk_v1_18 section = new Chunk_v1_18(0, blockPalette, biomePalette); |
||||||
|
section.set(0, 0, 0, 0); |
||||||
|
|
||||||
|
chunks[sectionIndex] = section; |
||||||
|
} |
||||||
|
|
||||||
|
if (chunks[0] instanceof Chunk_v1_18 section0) { |
||||||
|
for (int localY = 0; localY <= 1; localY++) { |
||||||
|
for (int x = 0; x < 16; x++) { |
||||||
|
for (int z = 0; z < 16; z++) { |
||||||
|
section0.set(x, localY, z, blockID); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return new Column( |
||||||
|
original.getX(), |
||||||
|
original.getZ(), |
||||||
|
true, |
||||||
|
chunks, |
||||||
|
new TileEntity[0], |
||||||
|
original.getHeightMaps() |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
private int resolveBlockStateID(String name) { |
||||||
|
Integer stateID = blockStates.get(name); |
||||||
|
if (stateID != null) return stateID; |
||||||
|
|
||||||
|
int resolved = 0; |
||||||
|
|
||||||
|
try { |
||||||
|
Object blockState = resolveNmsBlockState(name); |
||||||
|
if (blockState != null) { |
||||||
|
Integer id = tryGetBlockStateIdViaBlockGetId(blockState); |
||||||
|
if (id != null && id > 0) { |
||||||
|
resolved = id; |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (ReflectiveOperationException ignored) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
blockStates.put(name, resolved); |
||||||
|
return resolved; |
||||||
|
} |
||||||
|
|
||||||
|
private static Object resolveNmsBlockState(String name) throws ReflectiveOperationException { |
||||||
|
Class<?> blocksClass = Class.forName("net.minecraft.world.level.block.Blocks"); |
||||||
|
Field waterField = blocksClass.getField(name); |
||||||
|
Object waterBlock = waterField.get(null); |
||||||
|
|
||||||
|
Method defaultBlockState = waterBlock.getClass().getMethod("defaultBlockState"); |
||||||
|
return defaultBlockState.invoke(waterBlock); |
||||||
|
} |
||||||
|
|
||||||
|
private static Integer tryGetBlockStateIdViaBlockGetId(Object blockState) throws ReflectiveOperationException { |
||||||
|
// Block.getId(BlockState) exists on many modern versions
|
||||||
|
Class<?> blockClass = Class.forName("net.minecraft.world.level.block.Block"); |
||||||
|
Class<?> blockStateClass = Class.forName("net.minecraft.world.level.block.state.BlockState"); |
||||||
|
|
||||||
|
try { |
||||||
|
Method getId = blockClass.getMethod("getId", blockStateClass); |
||||||
|
Object idObj = getId.invoke(null, blockState); |
||||||
|
return (idObj instanceof Integer i) ? i : null; |
||||||
|
} catch (NoSuchMethodException ignored) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,72 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.islands; |
||||||
|
|
||||||
|
import com.infernalsuite.asp.api.exceptions.UnknownWorldException; |
||||||
|
import com.infernalsuite.asp.api.loaders.SlimeLoader; |
||||||
|
import xyz.soukup.ecoCraftCore.EcoCraftCore; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.nio.file.Files; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
public class FileIslandLoader implements SlimeLoader { |
||||||
|
|
||||||
|
private final Path storagePath; |
||||||
|
private static final String EXTENSION = ".slime"; |
||||||
|
|
||||||
|
public FileIslandLoader() { |
||||||
|
// Creates a directory named 'islands' inside your plugin folder
|
||||||
|
this.storagePath = EcoCraftCore.plugin.getDataFolder().toPath().resolve("island_templates"); |
||||||
|
|
||||||
|
if (!Files.exists(storagePath)) { |
||||||
|
storagePath.toFile().mkdirs(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Path getWorldPath(String worldName) { |
||||||
|
return storagePath.resolve(worldName + EXTENSION); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public byte[] readWorld(String worldName) throws UnknownWorldException, IOException { |
||||||
|
Path path = getWorldPath(worldName); |
||||||
|
if (!Files.exists(path)) { |
||||||
|
throw new UnknownWorldException(worldName); |
||||||
|
} |
||||||
|
return Files.readAllBytes(path); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean worldExists(String worldName) throws IOException { |
||||||
|
return Files.exists(getWorldPath(worldName)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void saveWorld(String worldName, byte[] serializedWorld) throws IOException { |
||||||
|
// Files.write will create or overwrite the file automatically
|
||||||
|
Files.write(getWorldPath(worldName), serializedWorld); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void deleteWorld(String worldName) throws IOException { |
||||||
|
Files.deleteIfExists(getWorldPath(worldName)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<String> listWorlds() throws IOException { |
||||||
|
File folder = storagePath.toFile(); |
||||||
|
File[] files = folder.listFiles((dir, name) -> name.endsWith(EXTENSION)); |
||||||
|
|
||||||
|
if (files == null) { |
||||||
|
return Collections.emptyList(); |
||||||
|
} |
||||||
|
|
||||||
|
return Arrays.stream(files) |
||||||
|
.map(file -> file.getName().replace(EXTENSION, "")) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,324 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.islands; |
||||||
|
|
||||||
|
import com.infernalsuite.asp.api.AdvancedSlimePaperAPI; |
||||||
|
import com.infernalsuite.asp.api.world.SlimeWorld; |
||||||
|
import com.j256.ormlite.stmt.QueryBuilder; |
||||||
|
import com.mojang.brigadier.arguments.IntegerArgumentType; |
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType; |
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder; |
||||||
|
import com.mojang.brigadier.context.CommandContext; |
||||||
|
import io.papermc.paper.command.brigadier.CommandSourceStack; |
||||||
|
import io.papermc.paper.command.brigadier.Commands; |
||||||
|
import org.bukkit.Location; |
||||||
|
import org.bukkit.NamespacedKey; |
||||||
|
import org.bukkit.entity.Player; |
||||||
|
import org.bukkit.persistence.PersistentDataContainer; |
||||||
|
import org.bukkit.persistence.PersistentDataType; |
||||||
|
import xyz.soukup.ecoCraftCore.database.DaoRegistry; |
||||||
|
import xyz.soukup.ecoCraftCore.database.objects.Island; |
||||||
|
import xyz.soukup.ecoCraftCore.messages.Messages; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.sql.SQLException; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import static xyz.soukup.ecoCraftCore.EcoCraftCore.config; |
||||||
|
import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin; |
||||||
|
|
||||||
|
public class IslandAdminCommand { |
||||||
|
private final AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance(); |
||||||
|
|
||||||
|
|
||||||
|
public static LiteralArgumentBuilder<CommandSourceStack> getCommand() { |
||||||
|
|
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> tp = Commands.literal("tp") |
||||||
|
.then(Commands.argument("uuid", StringArgumentType.word()) |
||||||
|
.executes(IslandAdminCommand::teleport) |
||||||
|
.suggests(((context, builder) -> { |
||||||
|
try { |
||||||
|
QueryBuilder<Island, Integer> queryBuilder = DaoRegistry.getIslandDao().queryBuilder(); |
||||||
|
queryBuilder.selectColumns("uuid"); |
||||||
|
List<Island> islands = queryBuilder.query(); |
||||||
|
for (Island island : islands) { |
||||||
|
builder.suggest(island.getUuid()); |
||||||
|
} |
||||||
|
return builder.buildFuture(); |
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
}))); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> create = Commands.literal("create") |
||||||
|
.then(Commands.argument("type", StringArgumentType.word()) |
||||||
|
.suggests(((context, builder) -> { |
||||||
|
try { |
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
FileIslandLoader fileLoader = islandManager.fileLoader; |
||||||
|
for (String world: fileLoader.listWorlds()){ |
||||||
|
builder.suggest(world); |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
return builder.buildFuture(); |
||||||
|
})) |
||||||
|
.then(Commands.argument("display_name", StringArgumentType.string()) |
||||||
|
.then(Commands.argument("description", StringArgumentType.greedyString()) |
||||||
|
.executes(IslandAdminCommand::createWorld)))); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> load = Commands.literal("load") |
||||||
|
.then(Commands.argument("uuid", StringArgumentType.word()) |
||||||
|
.suggests(((context, builder) -> { |
||||||
|
try { |
||||||
|
QueryBuilder<Island, Integer> queryBuilder = DaoRegistry.getIslandDao().queryBuilder(); |
||||||
|
queryBuilder.selectColumns("uuid"); |
||||||
|
List<Island> islands = queryBuilder.query(); |
||||||
|
for (Island island : islands) { |
||||||
|
builder.suggest(island.getUuid()); |
||||||
|
} |
||||||
|
return builder.buildFuture(); |
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
})) |
||||||
|
.executes(IslandAdminCommand::loadWorld)); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> loadTemplate = Commands.literal("loadTemplate") |
||||||
|
.then(Commands.argument("name", StringArgumentType.word()) |
||||||
|
.suggests(((context, builder) -> { |
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
FileIslandLoader fileLoader = islandManager.fileLoader; |
||||||
|
|
||||||
|
try { |
||||||
|
for (String world: fileLoader.listWorlds()){ |
||||||
|
builder.suggest(world); |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
return builder.buildFuture(); |
||||||
|
})) |
||||||
|
.executes(IslandAdminCommand::loadTemplate)); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> template = Commands.literal("template") |
||||||
|
.then(Commands.argument("uuid", StringArgumentType.word()) |
||||||
|
.suggests((context, builder) -> { |
||||||
|
try { |
||||||
|
QueryBuilder<Island, Integer> queryBuilder = DaoRegistry.getIslandDao().queryBuilder(); |
||||||
|
queryBuilder.selectColumns("uuid"); |
||||||
|
List<Island> islands = queryBuilder.query(); |
||||||
|
for (Island island : islands) { |
||||||
|
builder.suggest(island.getUuid()); |
||||||
|
} |
||||||
|
return builder.buildFuture(); |
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
}) |
||||||
|
.then(Commands.argument("templateName", StringArgumentType.word()) |
||||||
|
.executes(IslandAdminCommand::createTemplate))); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> metadata = Commands.literal("metadata") |
||||||
|
.then(Commands.argument("key", StringArgumentType.word()) |
||||||
|
.then(Commands.argument("value", IntegerArgumentType.integer()) |
||||||
|
.executes(IslandAdminCommand::setMetadata))); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> metadataString = Commands.literal("metadataString") |
||||||
|
.then(Commands.argument("key", StringArgumentType.word()) |
||||||
|
.then(Commands.argument("value", StringArgumentType.string()) |
||||||
|
.executes(IslandAdminCommand::setMetadataString))); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> expand = Commands.literal("expand") |
||||||
|
.then(Commands.argument("lenght", IntegerArgumentType.integer()) |
||||||
|
.executes(IslandAdminCommand::expandIsland)); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> listMetadata = Commands.literal("listMetadata") |
||||||
|
.executes(IslandAdminCommand::readAllMetadata); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> spawn = Commands.literal("spawn") |
||||||
|
.executes(IslandAdminCommand::setSpawn); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> enviroment = Commands.literal("environment") |
||||||
|
.then(Commands.argument("environment", StringArgumentType.word()) |
||||||
|
.executes(IslandAdminCommand::setEnvironment) |
||||||
|
.suggests(((context, builder) -> { |
||||||
|
builder.suggest("normal"); |
||||||
|
builder.suggest("nether"); |
||||||
|
builder.suggest("the_end"); |
||||||
|
return builder.buildFuture(); |
||||||
|
}))); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> setDefaultIsland = Commands.literal("setDefaultIsland") |
||||||
|
.executes(IslandAdminCommand::setDefualtIsland); |
||||||
|
|
||||||
|
return Commands.literal("island-admin") |
||||||
|
.requires(commandSourceStack -> commandSourceStack.getSender().isOp()) |
||||||
|
.then(tp) |
||||||
|
.then(create) |
||||||
|
.then(load) |
||||||
|
.then(template) |
||||||
|
.then(metadata) |
||||||
|
.then(metadataString) |
||||||
|
.then(listMetadata) |
||||||
|
.then(loadTemplate) |
||||||
|
.then(spawn) |
||||||
|
.then(enviroment) |
||||||
|
.then(expand) |
||||||
|
.then(setDefaultIsland); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private static int setDefualtIsland(CommandContext<CommandSourceStack> context){ |
||||||
|
if (!(context.getSource().getSender() instanceof Player player)) return 0; |
||||||
|
String uuid = player.getWorld().getName(); |
||||||
|
config.set("islands.spawn", uuid); |
||||||
|
plugin.saveConfig(); |
||||||
|
player.sendMessage("done."); |
||||||
|
return 0; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private static int setSpawn(CommandContext<CommandSourceStack> context) { |
||||||
|
if (!(context.getSource().getSender() instanceof Player player)) return 0; |
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
Location spawn = player.getLocation(); |
||||||
|
String uuid = player.getWorld().getName(); |
||||||
|
islandManager.changeSpawn(spawn, uuid); |
||||||
|
Messages.send(player, "island.setSpawn.success"); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
private static int setEnvironment(CommandContext<CommandSourceStack> context) { |
||||||
|
if (!(context.getSource().getSender() instanceof Player player)) return 0; |
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
String uuid = player.getWorld().getName(); |
||||||
|
islandManager.changeEnviroment(context.getArgument("environment", String.class), uuid); |
||||||
|
Messages.send(player, "island.setEnvironment.success"); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private static int teleport(CommandContext<CommandSourceStack> context) { |
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
try { |
||||||
|
Integer status = islandManager.teleport((Player) context.getSource().getSender(), context.getArgument("uuid", String.class)); |
||||||
|
context.getSource().getSender().sendMessage(String.valueOf(status)); |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
private static int createWorld(CommandContext<CommandSourceStack> context) { |
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
String type = context.getArgument("type", String.class); |
||||||
|
String displayName = context.getArgument("display_name", String.class); |
||||||
|
String description = context.getArgument("description", String.class); |
||||||
|
String owner = context.getSource().getSender().getName(); |
||||||
|
String uuid = islandManager.createIsland(type, displayName, description, owner, "player"); |
||||||
|
context.getSource().getSender().sendMessage("Created island: " + uuid); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
private static int loadWorld(CommandContext<CommandSourceStack> context) { |
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
islandManager.loadIsland(context.getArgument("uuid", String.class)); |
||||||
|
context.getSource().getSender().sendMessage("done."); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
private static int loadTemplate(CommandContext<CommandSourceStack> context) { |
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
islandManager.loadIslandTemplate(context.getArgument("name", String.class)); |
||||||
|
context.getSource().getSender().sendMessage("done."); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
private static int createTemplate(CommandContext<CommandSourceStack> context) { |
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
String uuid = StringArgumentType.getString(context, "uuid"); |
||||||
|
String templateName = StringArgumentType.getString(context, "templateName"); |
||||||
|
context.getSource().getSender().sendMessage("§aCreating template '" + templateName + "' from world '" + uuid + "'..."); |
||||||
|
islandManager.createIslandTemplate(uuid, templateName); |
||||||
|
context.getSource().getSender().sendMessage("§aTemplate created successfully!"); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
private static int setMetadata(CommandContext<CommandSourceStack> context) { |
||||||
|
if (!(context.getSource().getSender() instanceof Player player)) return 0; |
||||||
|
|
||||||
|
String keyName = StringArgumentType.getString(context, "key"); |
||||||
|
Integer value = IntegerArgumentType.getInteger(context, "value"); |
||||||
|
|
||||||
|
player.getWorld().getPersistentDataContainer().set(new NamespacedKey(plugin, keyName), PersistentDataType.INTEGER, value); |
||||||
|
context.getSource().getSender().sendMessage(keyName + " set to " + value); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
private static int setMetadataString(CommandContext<CommandSourceStack> context) { |
||||||
|
if (!(context.getSource().getSender() instanceof Player player)) return 0; |
||||||
|
|
||||||
|
String keyName = StringArgumentType.getString(context, "key"); |
||||||
|
String value = StringArgumentType.getString(context, "value"); |
||||||
|
|
||||||
|
player.getWorld().getPersistentDataContainer().set(new NamespacedKey(plugin, keyName), PersistentDataType.STRING, value); |
||||||
|
context.getSource().getSender().sendMessage(keyName + " set to " + value); |
||||||
|
|
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
private static int readAllMetadata(CommandContext<CommandSourceStack> context) { |
||||||
|
if (!(context.getSource().getSender() instanceof Player player)) { |
||||||
|
context.getSource().getSender().sendMessage("§cOnly players can use this."); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
PersistentDataContainer pdc = player.getWorld().getPersistentDataContainer(); |
||||||
|
if (pdc.getKeys().isEmpty()) { |
||||||
|
player.sendMessage("§eNo metadata found."); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
player.sendMessage("§6--- World Metadata ---"); |
||||||
|
for (NamespacedKey key : pdc.getKeys()) { |
||||||
|
String val = "unknown"; |
||||||
|
// Logic to determine type for display
|
||||||
|
if (pdc.has(key, PersistentDataType.STRING)) val = pdc.get(key, PersistentDataType.STRING); |
||||||
|
else if (pdc.has(key, PersistentDataType.INTEGER)) val = String.valueOf(pdc.get(key, PersistentDataType.INTEGER)); |
||||||
|
|
||||||
|
player.sendMessage("§b" + key.getKey() + "§7: §f" + val); |
||||||
|
} |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
private static int expandIsland(CommandContext<CommandSourceStack> context){ |
||||||
|
|
||||||
|
Player player = (Player) context.getSource().getSender(); |
||||||
|
float yaw = player.getLocation().getYaw(); |
||||||
|
int lenght = IntegerArgumentType.getInteger(context, "lenght"); |
||||||
|
String uuid = player.getWorld().getName(); |
||||||
|
|
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
islandManager.expandIsland(yaw, lenght, uuid); |
||||||
|
|
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
private static void saveSlimeWorld(SlimeWorld world, Player player, String key, String val) { |
||||||
|
try { |
||||||
|
AdvancedSlimePaperAPI.instance().saveWorld(world); |
||||||
|
player.sendMessage("§aMetadata set: §f" + key + " §7= §f" + val); |
||||||
|
} catch (IOException e) { |
||||||
|
player.sendMessage("§cFailed to save world metadata!"); |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -1,32 +0,0 @@ |
|||||||
package xyz.soukup.ecoCraftCore.islands; |
|
||||||
|
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType; |
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder; |
|
||||||
import com.mojang.brigadier.context.CommandContext; |
|
||||||
import io.papermc.paper.command.brigadier.CommandSourceStack; |
|
||||||
import io.papermc.paper.command.brigadier.Commands; |
|
||||||
|
|
||||||
|
|
||||||
public class IslandCommand { |
|
||||||
|
|
||||||
LiteralArgumentBuilder<CommandSourceStack> tp = Commands.literal("tp") |
|
||||||
.then(Commands.argument("uuid", StringArgumentType.word()) |
|
||||||
.executes(IslandCommand::teleport)); |
|
||||||
|
|
||||||
LiteralArgumentBuilder<CommandSourceStack> create = Commands.literal("create") |
|
||||||
.then(Commands.argument("name", StringArgumentType.word()) |
|
||||||
.then(Commands.argument("display_name", StringArgumentType.string()))); |
|
||||||
|
|
||||||
private static int teleport(CommandContext<CommandSourceStack> context) { |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
private static int createWorld(CommandContext<CommandSourceStack> context) { |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
private static int loadWorld(CommandContext<CommandSourceStack> context) { |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,188 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.islands; |
||||||
|
|
||||||
|
import com.github.stefvanschie.inventoryframework.gui.GuiItem; |
||||||
|
import com.github.stefvanschie.inventoryframework.gui.type.ChestGui; |
||||||
|
import com.github.stefvanschie.inventoryframework.gui.type.HopperGui; |
||||||
|
import com.github.stefvanschie.inventoryframework.pane.OutlinePane; |
||||||
|
import com.github.stefvanschie.inventoryframework.pane.PaginatedPane; |
||||||
|
import com.github.stefvanschie.inventoryframework.pane.component.PagingButtons; |
||||||
|
import com.github.stefvanschie.inventoryframework.pane.util.Slot; |
||||||
|
import com.j256.ormlite.stmt.QueryBuilder; |
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder; |
||||||
|
import com.mojang.brigadier.context.CommandContext; |
||||||
|
import io.papermc.paper.command.brigadier.CommandSourceStack; |
||||||
|
import io.papermc.paper.command.brigadier.Commands; |
||||||
|
import net.kyori.adventure.text.Component; |
||||||
|
import net.kyori.adventure.text.format.TextColor; |
||||||
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; |
||||||
|
import org.bukkit.Material; |
||||||
|
import org.bukkit.entity.Player; |
||||||
|
import xyz.soukup.ecoCraftCore.database.DaoRegistry; |
||||||
|
import xyz.soukup.ecoCraftCore.database.objects.Island; |
||||||
|
import xyz.soukup.ecoCraftCore.database.objects.Region; |
||||||
|
import xyz.soukup.ecoCraftCore.database.objects.RegionMember; |
||||||
|
import xyz.soukup.ecoCraftCore.gui.GuiItemBuilder; |
||||||
|
import xyz.soukup.ecoCraftCore.messages.Messages; |
||||||
|
|
||||||
|
import java.sql.SQLException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
public class IslandSelectorCommand { |
||||||
|
|
||||||
|
public static LiteralArgumentBuilder<CommandSourceStack> getCommand(){ |
||||||
|
return Commands.literal("is") |
||||||
|
.executes(IslandSelectorCommand::displayIslandListSelectorGui); |
||||||
|
} |
||||||
|
|
||||||
|
private static int displayIslandListSelectorGui(CommandContext<CommandSourceStack> context) { |
||||||
|
String title = LegacyComponentSerializer.legacySection().serialize(Messages.get("menu.island-selector.title")); |
||||||
|
HopperGui hopperGui = new HopperGui(title); |
||||||
|
hopperGui.setOnGlobalClick(event -> event.setCancelled(true)); |
||||||
|
|
||||||
|
OutlinePane outlinePane = new OutlinePane(0, 0, 5, 1); |
||||||
|
Player player = (Player) context.getSource().getSender(); |
||||||
|
|
||||||
|
outlinePane.addItem(selectorItem("menu.island-selector.my-islands", Material.GRASS_BLOCK, getMyIslands(player))); |
||||||
|
outlinePane.addItem(selectorItem("menu.island-selector.shared-islands", Material.MOSS_BLOCK, getSharedIslands(player))); |
||||||
|
outlinePane.addItem(selectorItem("menu.island-selector.public-islands", Material.SAND, getPublicIslands())); |
||||||
|
|
||||||
|
hopperGui.getSlotsComponent().addPane(outlinePane); |
||||||
|
|
||||||
|
if (player.isOp()){ |
||||||
|
outlinePane.addItem(selectorItem("menu.island-selector.all-islands", Material.CRIMSON_NYLIUM, getAllIslands())); |
||||||
|
} |
||||||
|
|
||||||
|
hopperGui.show(player); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
private static List<Island> getAllIslands(){ |
||||||
|
QueryBuilder<Island, Integer> queryBuilder = DaoRegistry.getIslandDao().queryBuilder(); |
||||||
|
queryBuilder.selectColumns("display_name", "uuid", "descritpion", "type"); |
||||||
|
try { |
||||||
|
return queryBuilder.query(); |
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static List<Island> getPublicIslands(){ |
||||||
|
QueryBuilder<Island, Integer> queryBuilder = DaoRegistry.getIslandDao().queryBuilder(); |
||||||
|
queryBuilder.selectColumns("display_name", "uuid", "descritpion", "type"); |
||||||
|
try { |
||||||
|
queryBuilder.where().eq("is_public", true); |
||||||
|
return queryBuilder.query(); |
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static List<Island> getMyIslands(Player player){ |
||||||
|
QueryBuilder<Island, Integer> queryBuilder = DaoRegistry.getIslandDao().queryBuilder(); |
||||||
|
queryBuilder.selectColumns("display_name", "uuid", "descritpion", "type"); |
||||||
|
try { |
||||||
|
queryBuilder.where().eq("owner", player.getName()).and().eq("owner_type", "player"); |
||||||
|
return queryBuilder.query(); |
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static List<Island> getSharedIslands(Player player){ |
||||||
|
QueryBuilder<RegionMember, Integer> memberQb = DaoRegistry.getRegionMemberDao().queryBuilder(); |
||||||
|
memberQb.selectColumns("region_id"); |
||||||
|
try { |
||||||
|
memberQb.where().eq("member_name", player.getName()).and().eq("member_type", "player"); |
||||||
|
|
||||||
|
QueryBuilder<Region, Integer> regionQb = DaoRegistry.getRegionDao().queryBuilder(); |
||||||
|
regionQb.selectColumns("island"); |
||||||
|
regionQb.where().in("id", memberQb); |
||||||
|
|
||||||
|
return DaoRegistry.getIslandDao().queryBuilder() |
||||||
|
.where() |
||||||
|
.in("uuid", regionQb) |
||||||
|
.query(); |
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public static GuiItem selectorItem(String key, Material material, List<Island> islands){ |
||||||
|
GuiItemBuilder guiItemBuilder = new GuiItemBuilder(material); |
||||||
|
guiItemBuilder.setName(Messages.get(key)); |
||||||
|
GuiItem guiItem = guiItemBuilder.build(); |
||||||
|
guiItem.setAction(event -> openIslandListGui((Player) event.getWhoClicked(), islands)); |
||||||
|
return guiItem; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private static void openIslandListGui(Player player, List<Island> islands){ |
||||||
|
String title = LegacyComponentSerializer.legacySection().serialize(Messages.get("menu.island-selector.title")); |
||||||
|
ChestGui chestGui = new ChestGui(4, title); |
||||||
|
chestGui.setOnGlobalClick(event -> event.setCancelled(true)); |
||||||
|
|
||||||
|
PaginatedPane paginatedPane = new PaginatedPane(0, 0, 9, 3); |
||||||
|
|
||||||
|
paginatedPane.populateWithGuiItems(itemsFromIslands(player, islands)); |
||||||
|
PagingButtons pagingButtons = new PagingButtons(Slot.fromXY(0, 3), 9, paginatedPane); |
||||||
|
|
||||||
|
chestGui.addPane(paginatedPane); |
||||||
|
chestGui.addPane(pagingButtons); |
||||||
|
|
||||||
|
chestGui.show(player); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private static List<GuiItem> itemsFromIslands(Player player, List<Island> islands){ |
||||||
|
List<GuiItem> guiItems = new ArrayList<>(); |
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
for (Island island : islands){ |
||||||
|
Material material; |
||||||
|
TextColor color; |
||||||
|
|
||||||
|
switch (island.getType()){ |
||||||
|
case "flat_grass": |
||||||
|
material = Material.GRASS_BLOCK; |
||||||
|
color = TextColor.color(0x02bd02); |
||||||
|
break; |
||||||
|
case "flat_sand": |
||||||
|
material = Material.SAND; |
||||||
|
color = TextColor.color(0xfccf03); |
||||||
|
break; |
||||||
|
case "flat_hell": |
||||||
|
material = Material.SOUL_SAND; |
||||||
|
color = TextColor.color(0xf00707); |
||||||
|
break; |
||||||
|
case "void": |
||||||
|
material = Material.GLASS; |
||||||
|
color = TextColor.color(0xffffff); |
||||||
|
break; |
||||||
|
default: |
||||||
|
material = Material.WHITE_WOOL; |
||||||
|
color = TextColor.color(0xffffff); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
GuiItemBuilder guiItemBuilder = new GuiItemBuilder(material); |
||||||
|
guiItemBuilder.setName(Component.text(island.getDisplayName(), color)); |
||||||
|
guiItemBuilder.setRawLore(island.getDescritpion()); |
||||||
|
|
||||||
|
GuiItem guiItem = guiItemBuilder.build(); |
||||||
|
guiItem.setAction(event -> { |
||||||
|
try { |
||||||
|
islandManager.teleport(player, island.getUuid()); |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
guiItems.add(guiItem); |
||||||
|
|
||||||
|
} |
||||||
|
return guiItems; |
||||||
|
} |
||||||
|
} |
||||||
@ -1,4 +1,4 @@ |
|||||||
package xyz.soukup.ecoCraftCore.islands; |
package xyz.soukup.ecoCraftCore.islands; |
||||||
|
|
||||||
public class UnloadWorld { |
public class UnloadIsland { |
||||||
} |
} |
||||||
@ -0,0 +1,65 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.mines; |
||||||
|
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder; |
||||||
|
import com.mojang.brigadier.context.CommandContext; |
||||||
|
import io.papermc.paper.command.brigadier.CommandSourceStack; |
||||||
|
import io.papermc.paper.command.brigadier.Commands; |
||||||
|
import org.bukkit.Location; |
||||||
|
import org.bukkit.World; |
||||||
|
import org.bukkit.entity.Player; |
||||||
|
import xyz.soukup.ecoCraftCore.messages.Messages; |
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage") |
||||||
|
public class MineCommand { |
||||||
|
|
||||||
|
public static LiteralArgumentBuilder<CommandSourceStack> createCommand() { |
||||||
|
LiteralArgumentBuilder<CommandSourceStack> regenerate = Commands.literal("regenerate") |
||||||
|
.executes(MineCommand::regenerateMines); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> tp = Commands.literal("tp") |
||||||
|
.requires(source -> source.getSender() instanceof Player) |
||||||
|
.executes(MineCommand::teleportToMines); |
||||||
|
|
||||||
|
return Commands.literal("mine") |
||||||
|
.then(regenerate) |
||||||
|
.then(tp); |
||||||
|
} |
||||||
|
|
||||||
|
private static int regenerateMines(CommandContext<CommandSourceStack> context) { |
||||||
|
World world = MineWorldManager.getWorld(); |
||||||
|
|
||||||
|
if (world == null) { |
||||||
|
Messages.send(context.getSource().getSender(), "mine.error.no-world"); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
Messages.send(context.getSource().getSender(), "mine.regenerating"); |
||||||
|
|
||||||
|
// Teleport all players out of the mine world first
|
||||||
|
for (Player p : world.getPlayers()) { |
||||||
|
p.teleport(p.getServer().getWorlds().getFirst().getSpawnLocation()); |
||||||
|
Messages.send(p, "mine.teleported-out"); |
||||||
|
} |
||||||
|
|
||||||
|
// Delete old world, create fresh one, then generate
|
||||||
|
MineWorldManager.recreateWorld(newWorld -> MineManager.regenerate(newWorld)); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
private static int teleportToMines(CommandContext<CommandSourceStack> context) { |
||||||
|
Player player = (Player) context.getSource().getSender(); |
||||||
|
|
||||||
|
World world = MineWorldManager.getWorld(); |
||||||
|
|
||||||
|
if (world == null) { |
||||||
|
Messages.send(player, "mine.error.no-world"); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
Location spawn = MineWorldManager.getSpawnLocation(); |
||||||
|
player.teleport(spawn); |
||||||
|
Messages.send(player, "mine.teleporting"); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,696 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.mines; |
||||||
|
|
||||||
|
import org.bukkit.Bukkit; |
||||||
|
import org.bukkit.Material; |
||||||
|
import org.bukkit.World; |
||||||
|
import org.bukkit.block.Block; |
||||||
|
import org.bukkit.block.BlockFace; |
||||||
|
|
||||||
|
import java.util.*; |
||||||
|
|
||||||
|
import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin; |
||||||
|
|
||||||
|
public class MineManager { |
||||||
|
|
||||||
|
private static final int TUNNEL_RADIUS = 5; // 11x11 = radius 5 from center
|
||||||
|
private static final int MIN_BRANCH_SIZE = 30; |
||||||
|
private static final int FIRST_BRANCH_MIN_SIZE = 20; |
||||||
|
private static final int FIRST_BRANCH_SPLIT_DELAY = 12; |
||||||
|
private static final int MAX_BRANCH_SIZE = 80; |
||||||
|
private static final double BASE_SPLIT_INCREMENT = 0.004; |
||||||
|
private static final double SPLIT_DECAY_PER_BRANCH = 0.0004; |
||||||
|
private static final int BLOCKS_PER_TICK = 800; |
||||||
|
|
||||||
|
// Transition zone: how many blocks before/after a border we blend
|
||||||
|
private static final int TRANSITION_RANGE = 8; |
||||||
|
|
||||||
|
private static final Random random = new Random(); |
||||||
|
|
||||||
|
private static final BlockFace[] DIRECTIONS = { |
||||||
|
BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.DOWN, BlockFace.UP |
||||||
|
}; |
||||||
|
|
||||||
|
// --- Biome definitions ---
|
||||||
|
|
||||||
|
public enum MineBiome { |
||||||
|
NORMAL, DEEP, SUPER_DEEP |
||||||
|
} |
||||||
|
|
||||||
|
// Fill progression per biome
|
||||||
|
private static final Material[] NORMAL_FILLS = {Material.STONE, Material.ANDESITE, Material.COBBLESTONE, Material.TUFF}; |
||||||
|
private static final int[] NORMAL_THRESHOLDS = {0, 30, 60, 90}; |
||||||
|
|
||||||
|
private static final Material[] DEEP_FILLS = {Material.DEEPSLATE, Material.COBBLED_DEEPSLATE, Material.CRACKED_DEEPSLATE_TILES}; |
||||||
|
private static final int[] DEEP_THRESHOLDS = {0, 40, 80}; |
||||||
|
|
||||||
|
private static final Material[] SUPER_DEEP_FILLS = {Material.SMOOTH_BASALT, Material.BLACKSTONE, Material.CRACKED_POLISHED_BLACKSTONE_BRICKS, Material.GILDED_BLACKSTONE}; |
||||||
|
private static final int[] SUPER_DEEP_THRESHOLDS = {0, 40, 80, 120}; |
||||||
|
|
||||||
|
// Special inline blocks (placed during generation, not ores)
|
||||||
|
private static final double DIRT_CHANCE = 0.04; |
||||||
|
private static final double GRAVEL_CHANCE = 0.05; |
||||||
|
private static final double INFESTED_CHANCE = 0.008; |
||||||
|
private static final double MUD_CHANCE_SUPER_DEEP = 0.03; |
||||||
|
private static final double LAVA_CHANCE_SUPER_DEEP = 0.05; |
||||||
|
|
||||||
|
// Deep biome lava: starts low and increases through the fills
|
||||||
|
private static final double LAVA_CHANCE_DEEP_MIN = 0.005; |
||||||
|
private static final double LAVA_CHANCE_DEEP_MAX = 0.04; |
||||||
|
|
||||||
|
// --- Ore tables per fill type (rolled on break) ---
|
||||||
|
|
||||||
|
public record OreEntry(Material material, double weight) {} |
||||||
|
|
||||||
|
private static final Map<Material, OreEntry[]> ORE_TABLES = new LinkedHashMap<>(); |
||||||
|
|
||||||
|
static { |
||||||
|
ORE_TABLES.put(Material.STONE, new OreEntry[]{ |
||||||
|
new OreEntry(Material.COPPER_ORE, 1.0), |
||||||
|
}); |
||||||
|
ORE_TABLES.put(Material.ANDESITE, new OreEntry[]{ |
||||||
|
new OreEntry(Material.COAL_ORE, 1.0), |
||||||
|
}); |
||||||
|
ORE_TABLES.put(Material.COBBLESTONE, new OreEntry[]{ |
||||||
|
new OreEntry(Material.IRON_ORE, 1.0), |
||||||
|
}); |
||||||
|
ORE_TABLES.put(Material.TUFF, new OreEntry[]{ |
||||||
|
new OreEntry(Material.GOLD_ORE, 1.0), |
||||||
|
}); |
||||||
|
ORE_TABLES.put(Material.DEEPSLATE, new OreEntry[]{ |
||||||
|
new OreEntry(Material.DEEPSLATE_EMERALD_ORE, 0.4), |
||||||
|
new OreEntry(Material.DEEPSLATE_LAPIS_ORE, 0.6), |
||||||
|
}); |
||||||
|
ORE_TABLES.put(Material.COBBLED_DEEPSLATE, new OreEntry[]{ |
||||||
|
new OreEntry(Material.DEEPSLATE_REDSTONE_ORE, 0.6), |
||||||
|
new OreEntry(Material.RAW_COPPER_BLOCK, 0.4), |
||||||
|
}); |
||||||
|
ORE_TABLES.put(Material.CRACKED_DEEPSLATE_TILES, new OreEntry[]{ |
||||||
|
new OreEntry(Material.DEEPSLATE_DIAMOND_ORE, 0.6), |
||||||
|
new OreEntry(Material.RAW_IRON_BLOCK, 0.4), |
||||||
|
}); |
||||||
|
ORE_TABLES.put(Material.SMOOTH_BASALT, new OreEntry[]{ |
||||||
|
new OreEntry(Material.COAL_BLOCK, 1.0), |
||||||
|
}); |
||||||
|
ORE_TABLES.put(Material.BLACKSTONE, new OreEntry[]{ |
||||||
|
new OreEntry(Material.RAW_GOLD_BLOCK, 1.0), |
||||||
|
}); |
||||||
|
ORE_TABLES.put(Material.CRACKED_POLISHED_BLACKSTONE_BRICKS, new OreEntry[]{ |
||||||
|
new OreEntry(Material.ANCIENT_DEBRIS, 1.0), |
||||||
|
}); |
||||||
|
ORE_TABLES.put(Material.GILDED_BLACKSTONE, new OreEntry[]{ |
||||||
|
new OreEntry(Material.GOLD_BLOCK, 1.0), |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// Ore chance depending on how far this fill is from the detected zone
|
||||||
|
// Index 0 = current fill, 1 = previous fill (one step lighter), 2 = three fills back, 3+ = any other
|
||||||
|
private static final double ORE_CHANCE_CURRENT = 0.05; |
||||||
|
private static final double ORE_CHANCE_PREVIOUS = 0.10; |
||||||
|
private static final double ORE_CHANCE_THREE_BACK = 0.02; |
||||||
|
private static final double ORE_CHANCE_OTHER = 0.01; |
||||||
|
|
||||||
|
// Ordered list of ALL fills from shallowest to deepest (used for distance calculations)
|
||||||
|
private static final List<Material> ALL_FILLS_ORDERED = new ArrayList<>(); |
||||||
|
|
||||||
|
static { |
||||||
|
Collections.addAll(ALL_FILLS_ORDERED, NORMAL_FILLS); |
||||||
|
Collections.addAll(ALL_FILLS_ORDERED, DEEP_FILLS); |
||||||
|
Collections.addAll(ALL_FILLS_ORDERED, SUPER_DEEP_FILLS); |
||||||
|
} |
||||||
|
|
||||||
|
// --- All known fill materials (for checking in MineWorldManager) ---
|
||||||
|
|
||||||
|
private static final Set<Material> ALL_FILL_MATERIALS = new HashSet<>(ALL_FILLS_ORDERED); |
||||||
|
|
||||||
|
public static boolean isFillMaterial(Material material) { |
||||||
|
return ALL_FILL_MATERIALS.contains(material); |
||||||
|
} |
||||||
|
|
||||||
|
// --- Biome Y tracking ---
|
||||||
|
|
||||||
|
private static int normalStartY = 0; |
||||||
|
private static int deepStartY = 0; |
||||||
|
private static int superDeepStartY = 0; |
||||||
|
|
||||||
|
// --- Branch data class ---
|
||||||
|
|
||||||
|
private static class Branch { |
||||||
|
final BlockFace direction; |
||||||
|
final int startX, startY, startZ; |
||||||
|
final int distanceFromStart; |
||||||
|
final int maxSize; |
||||||
|
final boolean isFirst; |
||||||
|
final MineBiome biome; |
||||||
|
double chanceToSplit; |
||||||
|
|
||||||
|
int endX, endY, endZ; |
||||||
|
|
||||||
|
Branch(BlockFace direction, int startX, int startY, int startZ, |
||||||
|
int distanceFromStart, int maxSize, boolean isFirst, MineBiome biome) { |
||||||
|
this.direction = direction; |
||||||
|
this.startX = startX; |
||||||
|
this.startY = startY; |
||||||
|
this.startZ = startZ; |
||||||
|
this.distanceFromStart = distanceFromStart; |
||||||
|
this.maxSize = maxSize; |
||||||
|
this.isFirst = isFirst; |
||||||
|
this.biome = biome; |
||||||
|
this.chanceToSplit = 0; |
||||||
|
this.endX = startX; |
||||||
|
this.endY = startY; |
||||||
|
this.endZ = startZ; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class BlockPlacement { |
||||||
|
final int x, y, z; |
||||||
|
final Material material; |
||||||
|
|
||||||
|
BlockPlacement(int x, int y, int z, Material material) { |
||||||
|
this.x = x; |
||||||
|
this.y = y; |
||||||
|
this.z = z; |
||||||
|
this.material = material; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ===================== MAIN ENTRY POINT =====================
|
||||||
|
|
||||||
|
public static void regenerate(World world) { |
||||||
|
int startY = world.getMaxHeight() - 11; |
||||||
|
int minY = world.getMinHeight() + 5; |
||||||
|
int maxY = world.getMaxHeight(); |
||||||
|
int worldMinY = world.getMinHeight(); |
||||||
|
|
||||||
|
normalStartY = startY; |
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { |
||||||
|
Set<Long> fillPositions = new HashSet<>(); |
||||||
|
List<BlockPlacement> fillPlacements = new ArrayList<>(); |
||||||
|
|
||||||
|
// ===== Biome 1: NORMAL =====
|
||||||
|
plugin.getLogger().info("[MineGen] === Starting NORMAL biome generation ==="); |
||||||
|
List<Branch> normalBranches = new ArrayList<>(); |
||||||
|
int[] branchCount = {0}; |
||||||
|
generateBiomeBranches(MineBiome.NORMAL, 0, startY, 0, startY, minY, |
||||||
|
fillPlacements, fillPositions, normalBranches, branchCount); |
||||||
|
plugin.getLogger().info("[MineGen] NORMAL done: " + fillPlacements.size() + " fill blocks, branches=" + branchCount[0]); |
||||||
|
|
||||||
|
// Find lowest Y from NORMAL
|
||||||
|
Branch lowestNormal = findLowestBranch(normalBranches); |
||||||
|
int deepX = lowestNormal != null ? lowestNormal.endX : 0; |
||||||
|
int deepY = lowestNormal != null ? lowestNormal.endY : startY - 100; |
||||||
|
int deepZ = lowestNormal != null ? lowestNormal.endZ : 0; |
||||||
|
deepStartY = deepY; |
||||||
|
|
||||||
|
// ===== Biome 2: DEEP =====
|
||||||
|
plugin.getLogger().info("[MineGen] === Starting DEEP biome generation at Y=" + deepY + " ==="); |
||||||
|
List<Branch> deepBranches = new ArrayList<>(); |
||||||
|
generateBiomeBranches(MineBiome.DEEP, deepX, deepY, deepZ, deepY, minY, |
||||||
|
fillPlacements, fillPositions, deepBranches, branchCount); |
||||||
|
plugin.getLogger().info("[MineGen] DEEP done: " + fillPlacements.size() + " total fill blocks, branches=" + branchCount[0]); |
||||||
|
|
||||||
|
// Find lowest Y from DEEP
|
||||||
|
Branch lowestDeep = findLowestBranch(deepBranches); |
||||||
|
int sdX = lowestDeep != null ? lowestDeep.endX : deepX; |
||||||
|
int sdY = lowestDeep != null ? lowestDeep.endY : deepY - 100; |
||||||
|
int sdZ = lowestDeep != null ? lowestDeep.endZ : deepZ; |
||||||
|
superDeepStartY = sdY; |
||||||
|
|
||||||
|
// ===== Biome 3: SUPER_DEEP =====
|
||||||
|
plugin.getLogger().info("[MineGen] === Starting SUPER_DEEP biome generation at Y=" + sdY + " ==="); |
||||||
|
List<Branch> superDeepBranches = new ArrayList<>(); |
||||||
|
generateBiomeBranches(MineBiome.SUPER_DEEP, sdX, sdY, sdZ, sdY, minY, |
||||||
|
fillPlacements, fillPositions, superDeepBranches, branchCount); |
||||||
|
plugin.getLogger().info("[MineGen] SUPER_DEEP done: " + fillPlacements.size() + " total fill blocks, branches=" + branchCount[0]); |
||||||
|
|
||||||
|
// ===== Walls =====
|
||||||
|
List<BlockPlacement> wallPlacements = new ArrayList<>(); |
||||||
|
generateWalls(fillPositions, wallPlacements, startY); |
||||||
|
plugin.getLogger().info("[MineGen] Walls done: " + wallPlacements.size() + " bedrock blocks."); |
||||||
|
|
||||||
|
// ===== Schedule placement on main thread =====
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> |
||||||
|
scheduleBlockPlacements(world, fillPlacements, wallPlacements, startY, worldMinY, maxY)); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// ===================== BIOME BRANCH GENERATION =====================
|
||||||
|
|
||||||
|
private static void generateBiomeBranches(MineBiome biome, int startX, int startY, int startZ, |
||||||
|
int ceilingY, int minY, |
||||||
|
List<BlockPlacement> placements, Set<Long> fillPositions, |
||||||
|
List<Branch> completedBranches, int[] branchCount) { |
||||||
|
Deque<Branch> queue = new ArrayDeque<>(); |
||||||
|
|
||||||
|
// Reset branch count for this biome so split chance starts fresh
|
||||||
|
int[] biomeBranchCount = {0}; |
||||||
|
|
||||||
|
int firstSize = Math.max(FIRST_BRANCH_MIN_SIZE, MIN_BRANCH_SIZE + random.nextInt(MAX_BRANCH_SIZE - MIN_BRANCH_SIZE)); |
||||||
|
Branch initial = new Branch(BlockFace.DOWN, startX, startY, startZ, 0, firstSize, true, biome); |
||||||
|
queue.add(initial); |
||||||
|
biomeBranchCount[0]++; |
||||||
|
branchCount[0]++; |
||||||
|
|
||||||
|
plugin.getLogger().info("[MineGen] [" + biome + "] Starting at (" + startX + "," + startY + "," + startZ + "), minY=" + minY); |
||||||
|
|
||||||
|
while (!queue.isEmpty()) { |
||||||
|
Branch branch = queue.pollFirst(); |
||||||
|
double currentIncrement = Math.max(0, BASE_SPLIT_INCREMENT - (biomeBranchCount[0] * SPLIT_DECAY_PER_BRANCH)); |
||||||
|
plugin.getLogger().info("[MineGen] [" + biome + "] Processing branch dir=" + branch.direction |
||||||
|
+ " start=(" + branch.startX + "," + branch.startY + "," + branch.startZ + ")" |
||||||
|
+ " dist=" + branch.distanceFromStart + " maxSize=" + branch.maxSize |
||||||
|
+ " first=" + branch.isFirst |
||||||
|
+ " queued=" + queue.size() + " fillBlocks=" + fillPositions.size() |
||||||
|
+ " splitIncrement=" + String.format("%.5f", currentIncrement)); |
||||||
|
|
||||||
|
generateSingleBranch(branch, queue, placements, fillPositions, ceilingY, minY, biomeBranchCount); |
||||||
|
completedBranches.add(branch); |
||||||
|
} |
||||||
|
|
||||||
|
plugin.getLogger().info("[MineGen] [" + biome + "] Complete. Branches=" + completedBranches.size()); |
||||||
|
} |
||||||
|
|
||||||
|
private static void generateSingleBranch(Branch branch, Deque<Branch> queue, List<BlockPlacement> placements, |
||||||
|
Set<Long> fillPositions, int ceilingY, int minY, int[] branchCount) { |
||||||
|
int cx = branch.startX; |
||||||
|
int cy = branch.startY; |
||||||
|
int cz = branch.startZ; |
||||||
|
|
||||||
|
for (int step = 0; step < branch.maxSize; step++) { |
||||||
|
int distance = branch.distanceFromStart + step; |
||||||
|
|
||||||
|
generateCrossSection(cx, cy, cz, branch.direction, distance, branch.biome, placements, fillPositions); |
||||||
|
|
||||||
|
branch.endX = cx; |
||||||
|
branch.endY = cy; |
||||||
|
branch.endZ = cz; |
||||||
|
|
||||||
|
// First branch: skip split logic for the first 12 blocks
|
||||||
|
if (branch.isFirst && step < FIRST_BRANCH_SPLIT_DELAY) { |
||||||
|
cx += branch.direction.getModX(); |
||||||
|
cy += branch.direction.getModY(); |
||||||
|
cz += branch.direction.getModZ(); |
||||||
|
if (cy < minY || cy > ceilingY + 5) break; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
double splitIncrement = Math.max(0, BASE_SPLIT_INCREMENT - (branchCount[0] * SPLIT_DECAY_PER_BRANCH)); |
||||||
|
|
||||||
|
if (splitIncrement <= 0) { |
||||||
|
cx += branch.direction.getModX(); |
||||||
|
cy += branch.direction.getModY(); |
||||||
|
cz += branch.direction.getModZ(); |
||||||
|
if (cy < minY || cy > ceilingY + 5) break; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
branch.chanceToSplit += splitIncrement; |
||||||
|
|
||||||
|
if (random.nextDouble() < branch.chanceToSplit) { |
||||||
|
branch.chanceToSplit = 0; |
||||||
|
|
||||||
|
int splitCount = 1 + random.nextInt(4); |
||||||
|
List<BlockFace> available = getAvailableDirections(branch.direction); |
||||||
|
Collections.shuffle(available, random); |
||||||
|
|
||||||
|
int created = 0; |
||||||
|
for (BlockFace dir : available) { |
||||||
|
if (created >= splitCount) break; |
||||||
|
|
||||||
|
int branchMaxSize = MIN_BRANCH_SIZE + random.nextInt(MAX_BRANCH_SIZE - MIN_BRANCH_SIZE); |
||||||
|
|
||||||
|
if (dir == BlockFace.UP && cy + branchMaxSize > ceilingY) continue; |
||||||
|
if (dir == BlockFace.DOWN && cy - branchMaxSize < minY) continue; |
||||||
|
|
||||||
|
queue.add(new Branch(dir, cx, cy, cz, distance, branchMaxSize, false, branch.biome)); |
||||||
|
branchCount[0]++; |
||||||
|
created++; |
||||||
|
} |
||||||
|
plugin.getLogger().info("[MineGen] [" + branch.biome + "] Split at step=" + step |
||||||
|
+ " pos=(" + cx + "," + cy + "," + cz + ") created=" + created |
||||||
|
+ " total=" + branchCount[0]); |
||||||
|
} |
||||||
|
|
||||||
|
cx += branch.direction.getModX(); |
||||||
|
cy += branch.direction.getModY(); |
||||||
|
cz += branch.direction.getModZ(); |
||||||
|
|
||||||
|
if (cy < minY || cy > ceilingY + 5) break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static Branch findLowestBranch(List<Branch> branches) { |
||||||
|
Branch lowest = null; |
||||||
|
for (Branch b : branches) { |
||||||
|
if (lowest == null || b.endY < lowest.endY) { |
||||||
|
lowest = b; |
||||||
|
} |
||||||
|
} |
||||||
|
return lowest; |
||||||
|
} |
||||||
|
|
||||||
|
private static List<BlockFace> getAvailableDirections(BlockFace current) { |
||||||
|
List<BlockFace> dirs = new ArrayList<>(); |
||||||
|
BlockFace opposite = current.getOppositeFace(); |
||||||
|
for (BlockFace dir : DIRECTIONS) { |
||||||
|
if (dir != opposite && dir != current) { |
||||||
|
dirs.add(dir); |
||||||
|
} |
||||||
|
} |
||||||
|
return dirs; |
||||||
|
} |
||||||
|
|
||||||
|
// ===================== CROSS SECTION GENERATION =====================
|
||||||
|
|
||||||
|
private static void generateCrossSection(int cx, int cy, int cz, BlockFace direction, int distance, |
||||||
|
MineBiome biome, List<BlockPlacement> placements, Set<Long> fillPositions) { |
||||||
|
int[][] offsets = getPerpOffsets(direction); |
||||||
|
|
||||||
|
for (int a = -TUNNEL_RADIUS; a <= TUNNEL_RADIUS; a++) { |
||||||
|
for (int b = -TUNNEL_RADIUS; b <= TUNNEL_RADIUS; b++) { |
||||||
|
int bx = cx + offsets[0][0] * a + offsets[1][0] * b; |
||||||
|
int by = cy + offsets[0][1] * a + offsets[1][1] * b; |
||||||
|
int bz = cz + offsets[0][2] * a + offsets[1][2] * b; |
||||||
|
|
||||||
|
long key = posKey(bx, by, bz); |
||||||
|
if (!fillPositions.add(key)) continue; |
||||||
|
|
||||||
|
Material mat = pickFillForDistance(distance, biome); |
||||||
|
placements.add(new BlockPlacement(bx, by, bz, mat)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ===================== FILL SELECTION (no ores during generation) =====================
|
||||||
|
|
||||||
|
private static Material pickFillForDistance(int distance, MineBiome biome) { |
||||||
|
Material[] fills = getFillsForBiome(biome); |
||||||
|
int[] thresholds = getThresholdsForBiome(biome); |
||||||
|
|
||||||
|
// Determine primary fill
|
||||||
|
Material primaryFill = fills[0]; |
||||||
|
for (int i = thresholds.length - 1; i >= 0; i--) { |
||||||
|
if (distance >= thresholds[i]) { |
||||||
|
primaryFill = fills[i]; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Check transition zone
|
||||||
|
Material transitionFill = getTransitionFill(distance, fills, thresholds); |
||||||
|
if (transitionFill != null && transitionFill != primaryFill) { |
||||||
|
int borderDist = getDistanceToBorder(distance, thresholds); |
||||||
|
double blendRatio = 0.5 * (1.0 - (double) Math.abs(borderDist) / TRANSITION_RANGE); |
||||||
|
if (random.nextDouble() < blendRatio) { |
||||||
|
primaryFill = transitionFill; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Roll for special inline blocks (dirt, gravel, lava, mud, infested)
|
||||||
|
Material special = rollSpecialBlock(biome, primaryFill, distance); |
||||||
|
if (special != null) return special; |
||||||
|
|
||||||
|
return primaryFill; |
||||||
|
} |
||||||
|
|
||||||
|
private static Material rollSpecialBlock(MineBiome biome, Material fill, int distance) { |
||||||
|
double roll = random.nextDouble(); |
||||||
|
switch (biome) { |
||||||
|
case NORMAL: |
||||||
|
if (roll < DIRT_CHANCE) return Material.DIRT; |
||||||
|
roll -= DIRT_CHANCE; |
||||||
|
if (roll < GRAVEL_CHANCE) return Material.GRAVEL; |
||||||
|
roll -= GRAVEL_CHANCE; |
||||||
|
// Rare infested variants
|
||||||
|
if (roll < INFESTED_CHANCE) { |
||||||
|
return switch (fill) { |
||||||
|
case STONE -> Material.INFESTED_STONE; |
||||||
|
case COBBLESTONE -> Material.INFESTED_COBBLESTONE; |
||||||
|
default -> null; // andesite, tuff don't have infested variants
|
||||||
|
}; |
||||||
|
} |
||||||
|
break; |
||||||
|
case DEEP: |
||||||
|
// Lava in ALL deep fills, chance increases with distance through the biome
|
||||||
|
// Deep thresholds max out at 80+ (cracked_deepslate_tiles)
|
||||||
|
// Total deep distance range is roughly 0-120
|
||||||
|
double deepProgress = Math.min(1.0, distance / 120.0); |
||||||
|
double lavaChance = LAVA_CHANCE_DEEP_MIN + (LAVA_CHANCE_DEEP_MAX - LAVA_CHANCE_DEEP_MIN) * deepProgress; |
||||||
|
if (roll < lavaChance) return Material.LAVA; |
||||||
|
break; |
||||||
|
case SUPER_DEEP: |
||||||
|
if (roll < MUD_CHANCE_SUPER_DEEP) return Material.MUD; |
||||||
|
roll -= MUD_CHANCE_SUPER_DEEP; |
||||||
|
if (roll < LAVA_CHANCE_SUPER_DEEP) return Material.LAVA; |
||||||
|
break; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private static Material getTransitionFill(int distance, Material[] fills, int[] thresholds) { |
||||||
|
for (int i = 1; i < thresholds.length; i++) { |
||||||
|
int border = thresholds[i]; |
||||||
|
int diff = distance - border; |
||||||
|
if (Math.abs(diff) <= TRANSITION_RANGE) { |
||||||
|
return (diff < 0) ? fills[i] : fills[i - 1]; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private static int getDistanceToBorder(int distance, int[] thresholds) { |
||||||
|
int closest = Integer.MAX_VALUE; |
||||||
|
for (int i = 1; i < thresholds.length; i++) { |
||||||
|
int diff = distance - thresholds[i]; |
||||||
|
if (Math.abs(diff) < Math.abs(closest)) { |
||||||
|
closest = diff; |
||||||
|
} |
||||||
|
} |
||||||
|
return closest; |
||||||
|
} |
||||||
|
|
||||||
|
private static Material[] getFillsForBiome(MineBiome biome) { |
||||||
|
return switch (biome) { |
||||||
|
case NORMAL -> NORMAL_FILLS; |
||||||
|
case DEEP -> DEEP_FILLS; |
||||||
|
case SUPER_DEEP -> SUPER_DEEP_FILLS; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
private static int[] getThresholdsForBiome(MineBiome biome) { |
||||||
|
return switch (biome) { |
||||||
|
case NORMAL -> NORMAL_THRESHOLDS; |
||||||
|
case DEEP -> DEEP_THRESHOLDS; |
||||||
|
case SUPER_DEEP -> SUPER_DEEP_THRESHOLDS; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// ===================== ORE-ON-BREAK SYSTEM (called from MineWorldManager) =====================
|
||||||
|
|
||||||
|
/** |
||||||
|
* Detects the deepest fill material among the 6 surrounding blocks to determine |
||||||
|
* which fill zone the player is in. Then rolls for an ore based on the broken block's |
||||||
|
* position in the fill sequence relative to the detected zone. |
||||||
|
* |
||||||
|
* Ore chances: |
||||||
|
* 5% for ores from the current (detected) fill |
||||||
|
* 10% for ores from the previous fill (one step lighter) |
||||||
|
* 2% for ores from three fills back |
||||||
|
* 1% for any other fill's ores |
||||||
|
* |
||||||
|
* Returns null if no ore was rolled. |
||||||
|
*/ |
||||||
|
public static Material rollOreOnBreak(Block brokenBlock, Material brokenType) { |
||||||
|
// Detect the deepest fill in the 6 surrounding blocks
|
||||||
|
Material detectedFill = detectDeepestSurroundingFill(brokenBlock); |
||||||
|
if (detectedFill == null) { |
||||||
|
// Fallback: use the broken block itself if it's a fill
|
||||||
|
detectedFill = isFillMaterial(brokenType) ? brokenType : Material.STONE; |
||||||
|
} |
||||||
|
|
||||||
|
int detectedIndex = ALL_FILLS_ORDERED.indexOf(detectedFill); |
||||||
|
int brokenIndex = ALL_FILLS_ORDERED.indexOf(brokenType); |
||||||
|
|
||||||
|
// If the broken block isn't a fill material, no ore roll
|
||||||
|
if (brokenIndex < 0) return null; |
||||||
|
|
||||||
|
// How many steps back from the detected fill is this broken block?
|
||||||
|
// detectedIndex is the deepest (highest index), brokenIndex is where we are in the sequence
|
||||||
|
int stepsBack = detectedIndex - brokenIndex; |
||||||
|
|
||||||
|
// Determine ore chance based on distance from detected fill
|
||||||
|
double chance; |
||||||
|
if (stepsBack == 0) { |
||||||
|
chance = ORE_CHANCE_CURRENT; // 5% - breaking the detected fill itself
|
||||||
|
} else if (stepsBack == 1) { |
||||||
|
chance = ORE_CHANCE_PREVIOUS; // 10% - one step lighter
|
||||||
|
} else if (stepsBack == 2) { |
||||||
|
chance = ORE_CHANCE_THREE_BACK; // 2% - two steps lighter
|
||||||
|
} else { |
||||||
|
chance = ORE_CHANCE_OTHER; // 1% - anything else
|
||||||
|
} |
||||||
|
|
||||||
|
// Roll for ore
|
||||||
|
if (random.nextDouble() >= chance) return null; |
||||||
|
|
||||||
|
// Pick a random ore from the broken block's ore table
|
||||||
|
OreEntry[] oreTable = ORE_TABLES.get(brokenType); |
||||||
|
if (oreTable == null || oreTable.length == 0) return null; |
||||||
|
|
||||||
|
return pickWeightedOre(oreTable); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Looks at the 6 blocks surrounding the broken block and finds the deepest fill material. |
||||||
|
*/ |
||||||
|
private static Material detectDeepestSurroundingFill(Block block) { |
||||||
|
Material deepest = null; |
||||||
|
int deepestIndex = -1; |
||||||
|
|
||||||
|
for (BlockFace face : DIRECTIONS) { |
||||||
|
Block neighbor = block.getRelative(face); |
||||||
|
Material type = neighbor.getType(); |
||||||
|
|
||||||
|
int index = ALL_FILLS_ORDERED.indexOf(type); |
||||||
|
if (index > deepestIndex) { |
||||||
|
deepestIndex = index; |
||||||
|
deepest = type; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return deepest; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Picks a random ore from the table using weights. |
||||||
|
*/ |
||||||
|
private static Material pickWeightedOre(OreEntry[] table) { |
||||||
|
double totalWeight = 0; |
||||||
|
for (OreEntry e : table) totalWeight += e.weight; |
||||||
|
|
||||||
|
double roll = random.nextDouble() * totalWeight; |
||||||
|
double cumulative = 0; |
||||||
|
for (OreEntry e : table) { |
||||||
|
cumulative += e.weight; |
||||||
|
if (roll < cumulative) return e.material; |
||||||
|
} |
||||||
|
return table[table.length - 1].material; |
||||||
|
} |
||||||
|
|
||||||
|
// ===================== PERPENDICULAR AXES =====================
|
||||||
|
|
||||||
|
private static int[][] getPerpOffsets(BlockFace dir) { |
||||||
|
return switch (dir) { |
||||||
|
case UP, DOWN -> new int[][]{{1, 0, 0}, {0, 0, 1}}; |
||||||
|
case NORTH, SOUTH -> new int[][]{{1, 0, 0}, {0, 1, 0}}; |
||||||
|
case EAST, WEST -> new int[][]{{0, 0, 1}, {0, 1, 0}}; |
||||||
|
default -> new int[][]{{1, 0, 0}, {0, 0, 1}}; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// ===================== WALL GENERATION =====================
|
||||||
|
|
||||||
|
private static void generateWalls(Set<Long> fillPositions, List<BlockPlacement> wallPlacements, int startY) { |
||||||
|
Set<Long> wallPositions = new HashSet<>(); |
||||||
|
plugin.getLogger().info("[MineGen] Generating bedrock walls for " + fillPositions.size() + " fill positions..."); |
||||||
|
|
||||||
|
for (long key : fillPositions) { |
||||||
|
int x = decodeX(key); |
||||||
|
int y = decodeY(key); |
||||||
|
int z = decodeZ(key); |
||||||
|
|
||||||
|
for (BlockFace face : DIRECTIONS) { |
||||||
|
int nx = x + face.getModX(); |
||||||
|
int ny = y + face.getModY(); |
||||||
|
int nz = z + face.getModZ(); |
||||||
|
|
||||||
|
long neighborKey = posKey(nx, ny, nz); |
||||||
|
|
||||||
|
if (!fillPositions.contains(neighborKey)) { |
||||||
|
if (ny > startY) continue; |
||||||
|
|
||||||
|
if (wallPositions.add(neighborKey)) { |
||||||
|
wallPlacements.add(new BlockPlacement(nx, ny, nz, Material.BEDROCK)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ===================== BLOCK PLACEMENT SCHEDULING =====================
|
||||||
|
|
||||||
|
private static void scheduleBlockPlacements(World world, List<BlockPlacement> fillPlacements, |
||||||
|
List<BlockPlacement> wallPlacements, |
||||||
|
int startY, int minY, int maxY) { |
||||||
|
// Three-phase placement: 1) Dirt placeholder 2) Bedrock walls 3) Actual fill
|
||||||
|
List<BlockPlacement> allPlacements = new ArrayList<>(); |
||||||
|
|
||||||
|
// Phase A: Dirt placeholders at all fill positions
|
||||||
|
List<BlockPlacement> dirtPlaceholders = new ArrayList<>(fillPlacements.size()); |
||||||
|
for (BlockPlacement bp : fillPlacements) { |
||||||
|
dirtPlaceholders.add(new BlockPlacement(bp.x, bp.y, bp.z, Material.DIRT)); |
||||||
|
} |
||||||
|
allPlacements.addAll(dirtPlaceholders); |
||||||
|
|
||||||
|
// Phase B: Bedrock walls
|
||||||
|
allPlacements.addAll(wallPlacements); |
||||||
|
|
||||||
|
// Phase C: Actual fill (overwrites dirt)
|
||||||
|
allPlacements.addAll(fillPlacements); |
||||||
|
|
||||||
|
// Air blocks above the entrance opening
|
||||||
|
for (int a = -TUNNEL_RADIUS; a <= TUNNEL_RADIUS; a++) { |
||||||
|
for (int b = -TUNNEL_RADIUS; b <= TUNNEL_RADIUS; b++) { |
||||||
|
allPlacements.add(new BlockPlacement(a, startY + 1, b, Material.AIR)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int totalBlocks = allPlacements.size(); |
||||||
|
int tickDelay = 0; |
||||||
|
|
||||||
|
plugin.getLogger().info("[MineGen] Scheduling " + totalBlocks + " block placements (" |
||||||
|
+ dirtPlaceholders.size() + " placeholder + " + wallPlacements.size() + " walls + " |
||||||
|
+ fillPlacements.size() + " fill) in batches of " + BLOCKS_PER_TICK); |
||||||
|
|
||||||
|
for (int i = 0; i < totalBlocks; i += BLOCKS_PER_TICK) { |
||||||
|
int from = i; |
||||||
|
int to = Math.min(i + BLOCKS_PER_TICK, totalBlocks); |
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskLater(plugin, () -> { |
||||||
|
for (int j = from; j < to; j++) { |
||||||
|
BlockPlacement bp = allPlacements.get(j); |
||||||
|
if (bp.y < minY || bp.y >= maxY) continue; |
||||||
|
world.getBlockAt(bp.x, bp.y, bp.z).setType(bp.material, false); |
||||||
|
} |
||||||
|
}, tickDelay); |
||||||
|
|
||||||
|
tickDelay++; |
||||||
|
} |
||||||
|
|
||||||
|
int finalDelay = tickDelay; |
||||||
|
Bukkit.getScheduler().runTaskLater(plugin, |
||||||
|
() -> plugin.getLogger().info("Mine regeneration complete. " + totalBlocks + " blocks placed."), |
||||||
|
finalDelay + 2); |
||||||
|
} |
||||||
|
|
||||||
|
// ===================== POSITION ENCODING/DECODING =====================
|
||||||
|
|
||||||
|
private static long posKey(int x, int y, int z) { |
||||||
|
return ((long) (x + 30000000) & 0x3FFFFFF) |
||||||
|
| (((long) (y + 2048) & 0xFFF) << 26) |
||||||
|
| (((long) (z + 30000000) & 0x3FFFFFF) << 38); |
||||||
|
} |
||||||
|
|
||||||
|
private static int decodeX(long key) { |
||||||
|
return (int) (key & 0x3FFFFFF) - 30000000; |
||||||
|
} |
||||||
|
|
||||||
|
private static int decodeY(long key) { |
||||||
|
return (int) ((key >> 26) & 0xFFF) - 2048; |
||||||
|
} |
||||||
|
|
||||||
|
private static int decodeZ(long key) { |
||||||
|
return (int) ((key >> 38) & 0x3FFFFFF) - 30000000; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,304 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.mines; |
||||||
|
|
||||||
|
import com.infernalsuite.asp.api.AdvancedSlimePaperAPI; |
||||||
|
import com.infernalsuite.asp.api.world.SlimeWorld; |
||||||
|
import com.infernalsuite.asp.api.world.properties.SlimeProperties; |
||||||
|
import com.infernalsuite.asp.api.world.properties.SlimePropertyMap; |
||||||
|
import org.bukkit.*; |
||||||
|
import org.bukkit.entity.Player; |
||||||
|
import org.bukkit.event.EventHandler; |
||||||
|
import org.bukkit.event.Listener; |
||||||
|
import org.bukkit.event.block.BlockBreakEvent; |
||||||
|
import org.bukkit.event.block.BlockExplodeEvent; |
||||||
|
import org.bukkit.event.entity.EntityExplodeEvent; |
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent; |
||||||
|
import org.bukkit.event.player.PlayerChangedWorldEvent; |
||||||
|
import org.bukkit.event.player.PlayerItemDamageEvent; |
||||||
|
import org.bukkit.event.player.PlayerJoinEvent; |
||||||
|
import org.bukkit.inventory.ItemStack; |
||||||
|
import org.bukkit.inventory.meta.Damageable; |
||||||
|
import org.bukkit.potion.PotionEffect; |
||||||
|
import org.bukkit.potion.PotionEffectType; |
||||||
|
import xyz.soukup.ecoCraftCore.database.objects.Island; |
||||||
|
import xyz.soukup.ecoCraftCore.islands.DatabaseIslandLoader; |
||||||
|
|
||||||
|
import java.util.function.Consumer; |
||||||
|
|
||||||
|
import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin; |
||||||
|
|
||||||
|
public class MineWorldManager implements Listener { |
||||||
|
|
||||||
|
public static final String MINE_WORLD_NAME = "mine_world"; |
||||||
|
private static World mineWorld; |
||||||
|
|
||||||
|
public static void init() { |
||||||
|
AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance(); |
||||||
|
DatabaseIslandLoader loader = new DatabaseIslandLoader(); |
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { |
||||||
|
try { |
||||||
|
SlimeWorld slimeWorld = null; |
||||||
|
|
||||||
|
// Try to load existing world
|
||||||
|
if (loader.worldExists(MINE_WORLD_NAME)) { |
||||||
|
try { |
||||||
|
slimeWorld = asp.readWorld(loader, MINE_WORLD_NAME, false, new SlimePropertyMap()); |
||||||
|
} catch (Exception e) { |
||||||
|
plugin.getLogger().warning("Mine world data is corrupted, recreating..."); |
||||||
|
loader.deleteWorld(MINE_WORLD_NAME); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Create fresh world if loading failed or didn't exist
|
||||||
|
if (slimeWorld == null) { |
||||||
|
SlimePropertyMap props = new SlimePropertyMap(); |
||||||
|
props.setValue(SlimeProperties.ENVIRONMENT, "NORMAL"); |
||||||
|
|
||||||
|
Island island = new Island("mine", MINE_WORLD_NAME, "Mine World", "Shared mine world", "server", "system", null); |
||||||
|
island.save(); |
||||||
|
|
||||||
|
slimeWorld = asp.createEmptyWorld(MINE_WORLD_NAME, false, props, loader); |
||||||
|
} |
||||||
|
|
||||||
|
SlimeWorld finalWorld = slimeWorld; |
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> { |
||||||
|
asp.loadWorld(finalWorld, true); |
||||||
|
mineWorld = Bukkit.getWorld(MINE_WORLD_NAME); |
||||||
|
|
||||||
|
if (mineWorld != null) { |
||||||
|
mineWorld.setGameRule(GameRule.DO_MOB_SPAWNING, false); |
||||||
|
mineWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); |
||||||
|
mineWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false); |
||||||
|
mineWorld.setTime(6000); |
||||||
|
plugin.getLogger().info("Mine world loaded successfully."); |
||||||
|
} |
||||||
|
}); |
||||||
|
} catch (Exception e) { |
||||||
|
e.printStackTrace(); |
||||||
|
plugin.getLogger().severe("Failed to load mine world."); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public static World getWorld() { |
||||||
|
return mineWorld; |
||||||
|
} |
||||||
|
|
||||||
|
public static Location getSpawnLocation() { |
||||||
|
if (mineWorld == null) return null; |
||||||
|
int startY = mineWorld.getMaxHeight() - 10; |
||||||
|
return new Location(mineWorld, 5, startY + 1, 5); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Deletes the current mine world and creates a fresh empty one. |
||||||
|
* Calls the callback with the new World once it's ready. |
||||||
|
* Must be called from the main thread with all players already teleported out. |
||||||
|
*/ |
||||||
|
public static void recreateWorld(Consumer<World> callback) { |
||||||
|
AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance(); |
||||||
|
DatabaseIslandLoader loader = new DatabaseIslandLoader(); |
||||||
|
|
||||||
|
plugin.getLogger().info("[MineWorld] Unloading old mine world..."); |
||||||
|
|
||||||
|
if (mineWorld != null) { |
||||||
|
Bukkit.unloadWorld(mineWorld, false); |
||||||
|
mineWorld = null; |
||||||
|
} |
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { |
||||||
|
try { |
||||||
|
if (loader.worldExists(MINE_WORLD_NAME)) { |
||||||
|
loader.deleteWorld(MINE_WORLD_NAME); |
||||||
|
plugin.getLogger().info("[MineWorld] Old mine world deleted."); |
||||||
|
} |
||||||
|
|
||||||
|
SlimePropertyMap props = new SlimePropertyMap(); |
||||||
|
props.setValue(SlimeProperties.ENVIRONMENT, "NORMAL"); |
||||||
|
|
||||||
|
Island island = new Island("mine", MINE_WORLD_NAME, "Mine World", "Shared mine world", "server", "system", null); |
||||||
|
island.save(); |
||||||
|
|
||||||
|
SlimeWorld slimeWorld = asp.createEmptyWorld(MINE_WORLD_NAME, false, props, loader); |
||||||
|
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> { |
||||||
|
asp.loadWorld(slimeWorld, true); |
||||||
|
mineWorld = Bukkit.getWorld(MINE_WORLD_NAME); |
||||||
|
|
||||||
|
if (mineWorld != null) { |
||||||
|
mineWorld.setGameRule(GameRule.DO_MOB_SPAWNING, false); |
||||||
|
mineWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); |
||||||
|
mineWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false); |
||||||
|
mineWorld.setTime(6000); |
||||||
|
plugin.getLogger().info("[MineWorld] Fresh mine world created and loaded."); |
||||||
|
callback.accept(mineWorld); |
||||||
|
} else { |
||||||
|
plugin.getLogger().severe("[MineWorld] Failed to load fresh mine world."); |
||||||
|
} |
||||||
|
}); |
||||||
|
} catch (Exception e) { |
||||||
|
e.printStackTrace(); |
||||||
|
plugin.getLogger().severe("[MineWorld] Failed to recreate mine world."); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isInMineWorld(Player player) { |
||||||
|
return player.getWorld().equals(mineWorld); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isInMineWorld(World world) { |
||||||
|
return world.equals(mineWorld); |
||||||
|
} |
||||||
|
|
||||||
|
// Apply invisible mining fatigue when entering mine world
|
||||||
|
@EventHandler |
||||||
|
public void onWorldChange(PlayerChangedWorldEvent event) { |
||||||
|
Player player = event.getPlayer(); |
||||||
|
|
||||||
|
// Entering mine world
|
||||||
|
if (isInMineWorld(player)) { |
||||||
|
applyMiningFatigue(player); |
||||||
|
} |
||||||
|
|
||||||
|
// Leaving mine world
|
||||||
|
if (isInMineWorld(event.getFrom())) { |
||||||
|
player.removePotionEffect(PotionEffectType.MINING_FATIGUE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Re-apply fatigue on join if player is in mine world
|
||||||
|
@EventHandler |
||||||
|
public void onJoin(PlayerJoinEvent event) { |
||||||
|
Player player = event.getPlayer(); |
||||||
|
if (isInMineWorld(player)) { |
||||||
|
applyMiningFatigue(player); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Re-apply fatigue on respawn in mine world
|
||||||
|
@EventHandler |
||||||
|
public void onDeath(PlayerDeathEvent event) { |
||||||
|
Bukkit.getScheduler().runTaskLater(plugin, () -> { |
||||||
|
Player player = event.getPlayer(); |
||||||
|
if (isInMineWorld(player)) { |
||||||
|
applyMiningFatigue(player); |
||||||
|
} |
||||||
|
}, 1L); |
||||||
|
} |
||||||
|
|
||||||
|
private void applyMiningFatigue(Player player) { |
||||||
|
player.addPotionEffect(new PotionEffect( |
||||||
|
PotionEffectType.MINING_FATIGUE, |
||||||
|
Integer.MAX_VALUE, |
||||||
|
0, // level I (amplifier 0)
|
||||||
|
true, // ambient
|
||||||
|
false, // no particles
|
||||||
|
false // no icon
|
||||||
|
)); |
||||||
|
} |
||||||
|
|
||||||
|
// Double tool durability damage in mine world
|
||||||
|
@EventHandler |
||||||
|
public void onItemDamage(PlayerItemDamageEvent event) { |
||||||
|
if (!isInMineWorld(event.getPlayer())) return; |
||||||
|
if (event.getItem().getType().getMaxDurability() <= 0) return; |
||||||
|
event.setDamage(event.getDamage() * 2); |
||||||
|
} |
||||||
|
|
||||||
|
// Prevent entity explosions from destroying bedrock walls
|
||||||
|
@EventHandler |
||||||
|
public void onEntityExplode(EntityExplodeEvent event) { |
||||||
|
if (!isInMineWorld(event.getEntity().getWorld())) return; |
||||||
|
event.blockList().removeIf(block -> block.getType() == Material.BEDROCK); |
||||||
|
} |
||||||
|
|
||||||
|
// Prevent block explosions from destroying bedrock walls
|
||||||
|
@EventHandler |
||||||
|
public void onBlockExplode(BlockExplodeEvent event) { |
||||||
|
if (!isInMineWorld(event.getBlock().getWorld())) return; |
||||||
|
event.blockList().removeIf(block -> block.getType() == Material.BEDROCK); |
||||||
|
} |
||||||
|
|
||||||
|
// Block break logic: bedrock unbreakable, ore-on-break for ALL fills, mining sequence
|
||||||
|
@EventHandler |
||||||
|
public void onBlockBreak(BlockBreakEvent event) { |
||||||
|
if (!isInMineWorld(event.getPlayer())) return; |
||||||
|
|
||||||
|
Material type = event.getBlock().getType(); |
||||||
|
|
||||||
|
// Bedrock is completely unbreakable (mine walls)
|
||||||
|
if (type == Material.BEDROCK) { |
||||||
|
event.setCancelled(true); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// For any fill material (at any stage of the mining sequence), roll for ore first.
|
||||||
|
// The ore replaces the block as another mining step — player then breaks the ore normally.
|
||||||
|
if (MineManager.isFillMaterial(type)) { |
||||||
|
Material ore = MineManager.rollOreOnBreak(event.getBlock(), type); |
||||||
|
if (ore != null) { |
||||||
|
event.setCancelled(true); |
||||||
|
event.getBlock().setType(ore); |
||||||
|
event.getBlock().getState().update(true); |
||||||
|
applyToolDamage(event); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// No ore rolled — apply normal mining sequence (deeper fills degrade into lighter fills)
|
||||||
|
Material nextTier = getNextTier(type); |
||||||
|
if (nextTier != null) { |
||||||
|
event.setCancelled(true); |
||||||
|
event.getBlock().setType(nextTier); |
||||||
|
event.getBlock().getState().update(true); |
||||||
|
applyToolDamage(event); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Terminal fill (stone) or ores — break normally
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Applies doubled durability damage to the player's tool when we cancel a break event. |
||||||
|
* Only applies if the held item actually has durability. |
||||||
|
*/ |
||||||
|
private void applyToolDamage(BlockBreakEvent event) { |
||||||
|
ItemStack tool = event.getPlayer().getInventory().getItemInMainHand(); |
||||||
|
if (tool.getType().isAir() || tool.getType().getMaxDurability() <= 0) return; |
||||||
|
if (!(tool.getItemMeta() instanceof Damageable damageable)) return; |
||||||
|
|
||||||
|
damageable.setDamage(damageable.getDamage() + 2); // doubled durability
|
||||||
|
tool.setItemMeta(damageable); |
||||||
|
|
||||||
|
if (damageable.getDamage() >= tool.getType().getMaxDurability()) { |
||||||
|
event.getPlayer().getInventory().setItemInMainHand(null); |
||||||
|
event.getPlayer().playSound(event.getPlayer().getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0f, 1.0f); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Mining sequence: deeper fills degrade into lighter fills. |
||||||
|
* Returns null when the block should break normally (terminal fill = stone). |
||||||
|
* |
||||||
|
* Chain: gilded_blackstone -> cracked_polished_blackstone_bricks -> blackstone -> smooth_basalt |
||||||
|
* -> cracked_deepslate_tiles -> cobbled_deepslate -> deepslate -> tuff -> cobblestone |
||||||
|
* -> andesite -> stone -> null (breaks normally) |
||||||
|
*/ |
||||||
|
private Material getNextTier(Material material) { |
||||||
|
return switch (material) { |
||||||
|
case GILDED_BLACKSTONE -> Material.CRACKED_POLISHED_BLACKSTONE_BRICKS; |
||||||
|
case CRACKED_POLISHED_BLACKSTONE_BRICKS -> Material.BLACKSTONE; |
||||||
|
case BLACKSTONE -> Material.SMOOTH_BASALT; |
||||||
|
case SMOOTH_BASALT -> Material.CRACKED_DEEPSLATE_TILES; |
||||||
|
case CRACKED_DEEPSLATE_TILES -> Material.COBBLED_DEEPSLATE; |
||||||
|
case COBBLED_DEEPSLATE -> Material.DEEPSLATE; |
||||||
|
case DEEPSLATE -> Material.TUFF; |
||||||
|
case TUFF -> Material.COBBLESTONE; |
||||||
|
case COBBLESTONE -> Material.ANDESITE; |
||||||
|
case ANDESITE -> Material.STONE; |
||||||
|
default -> null; // Stone and non-fill blocks break normally
|
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,27 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.player; |
||||||
|
|
||||||
|
import com.destroystokyo.paper.event.player.PlayerPostRespawnEvent; |
||||||
|
import org.bukkit.entity.Player; |
||||||
|
import org.bukkit.event.EventHandler; |
||||||
|
import org.bukkit.event.Listener; |
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent; |
||||||
|
import org.bukkit.event.player.PlayerRespawnEvent; |
||||||
|
import xyz.soukup.ecoCraftCore.islands.IslandManager; |
||||||
|
|
||||||
|
import static xyz.soukup.ecoCraftCore.EcoCraftCore.config; |
||||||
|
|
||||||
|
public class OnKill implements Listener { |
||||||
|
@EventHandler |
||||||
|
public void spawnTeleportOnKill(PlayerPostRespawnEvent event){ |
||||||
|
Player player = event.getPlayer(); |
||||||
|
|
||||||
|
String uuid = config.getString("islands.spawn"); |
||||||
|
|
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
try { |
||||||
|
islandManager.teleport(player, uuid); |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,50 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.player; |
||||||
|
|
||||||
|
import com.google.common.eventbus.DeadEvent; |
||||||
|
import com.j256.ormlite.stmt.QueryBuilder; |
||||||
|
import org.bukkit.entity.Player; |
||||||
|
import org.bukkit.event.EventHandler; |
||||||
|
import org.bukkit.event.Listener; |
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent; |
||||||
|
import org.bukkit.event.player.PlayerJoinEvent; |
||||||
|
import xyz.soukup.ecoCraftCore.database.DaoRegistry; |
||||||
|
import xyz.soukup.ecoCraftCore.database.objects.TeleportRequest; |
||||||
|
import xyz.soukup.ecoCraftCore.islands.IslandManager; |
||||||
|
|
||||||
|
import java.sql.SQLException; |
||||||
|
|
||||||
|
import static xyz.soukup.ecoCraftCore.EcoCraftCore.config; |
||||||
|
|
||||||
|
public class TeleportRequestsHandler implements Listener { |
||||||
|
|
||||||
|
@EventHandler |
||||||
|
public void teleportRequestHandler(PlayerJoinEvent event){ |
||||||
|
try { |
||||||
|
Player player = event.getPlayer(); |
||||||
|
|
||||||
|
QueryBuilder<TeleportRequest, Integer> queryBuilder = DaoRegistry.getTeleportRequestsDao().queryBuilder(); |
||||||
|
queryBuilder.where().eq("player", player.getName()); |
||||||
|
|
||||||
|
TeleportRequest teleportRequest = queryBuilder.queryForFirst(); |
||||||
|
|
||||||
|
IslandManager islandManager = new IslandManager(); |
||||||
|
|
||||||
|
|
||||||
|
if (teleportRequest == null){ |
||||||
|
islandManager.teleport(player, config.getString("islands.spawn")); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
islandManager.teleportLocally(player, teleportRequest.getWorld(), teleportRequest.getX(), teleportRequest.getY(), teleportRequest.getY(), teleportRequest.getYaw(), teleportRequest.getPitch()); |
||||||
|
|
||||||
|
teleportRequest.delete(); |
||||||
|
|
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,97 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.regions; |
||||||
|
|
||||||
|
import com.j256.ormlite.stmt.QueryBuilder; |
||||||
|
import com.mojang.brigadier.arguments.IntegerArgumentType; |
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType; |
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder; |
||||||
|
import com.mojang.brigadier.context.CommandContext; |
||||||
|
import io.papermc.paper.command.brigadier.CommandSourceStack; |
||||||
|
import io.papermc.paper.command.brigadier.Commands; |
||||||
|
import org.bukkit.command.CommandSender; |
||||||
|
import org.bukkit.entity.Player; |
||||||
|
import xyz.soukup.ecoCraftCore.database.DaoRegistry; |
||||||
|
import xyz.soukup.ecoCraftCore.database.objects.Region; |
||||||
|
import xyz.soukup.ecoCraftCore.messages.Messages; |
||||||
|
|
||||||
|
import java.sql.SQLException; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import static xyz.soukup.ecoCraftCore.EcoCraftCore.plugin; |
||||||
|
|
||||||
|
public class RegionAdminCommand { |
||||||
|
public static LiteralArgumentBuilder<CommandSourceStack> getCommand() { |
||||||
|
LiteralArgumentBuilder<CommandSourceStack> create = Commands.literal("create") |
||||||
|
.then(Commands.argument("type", IntegerArgumentType.integer(0, 1)) |
||||||
|
.executes(RegionAdminCommand::createRegion)); |
||||||
|
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> addMember = Commands.literal("addMember") |
||||||
|
.then(Commands.argument("id", IntegerArgumentType.integer()) |
||||||
|
.suggests((context, builder) -> { |
||||||
|
try { |
||||||
|
QueryBuilder<Region, Integer> queryBuilder = DaoRegistry.getRegionDao().queryBuilder(); |
||||||
|
queryBuilder.selectColumns("id"); |
||||||
|
List<Region> regions = queryBuilder.query(); |
||||||
|
for (Region region : regions) { |
||||||
|
builder.suggest(region.getId()); |
||||||
|
} |
||||||
|
return builder.buildFuture(); |
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
}) |
||||||
|
.then(Commands.argument("player", StringArgumentType.word()) |
||||||
|
.suggests((context, builder) -> { |
||||||
|
for (Player onlinePlayer : plugin.getServer().getOnlinePlayers()) { |
||||||
|
builder.suggest(onlinePlayer.getName()); |
||||||
|
} |
||||||
|
return builder.buildFuture(); |
||||||
|
}) |
||||||
|
.then(Commands.argument("membership", StringArgumentType.word()) |
||||||
|
.suggests(((context, builder) -> { |
||||||
|
builder.suggest("owner"); |
||||||
|
builder.suggest("editor"); |
||||||
|
builder.suggest("member"); |
||||||
|
return builder.buildFuture(); |
||||||
|
})) |
||||||
|
.executes(RegionAdminCommand::addMember)))); |
||||||
|
|
||||||
|
|
||||||
|
return Commands.literal("region") |
||||||
|
.then(create) |
||||||
|
.then(addMember); |
||||||
|
} |
||||||
|
|
||||||
|
private static int createRegion(CommandContext<CommandSourceStack> context) { |
||||||
|
Integer type = context.getArgument("type", Integer.class); |
||||||
|
if(!(context.getSource().getSender() instanceof Player player)){ |
||||||
|
Messages.send(context.getSource().getSender(), "generic.error.not-player"); |
||||||
|
return 0; |
||||||
|
}; |
||||||
|
|
||||||
|
int status = RegionManager.createRegion(player, type, player.getName(), "player"); |
||||||
|
|
||||||
|
switch (status){ |
||||||
|
case 0 -> Messages.send(player, "generic.success.created"); |
||||||
|
case 1 -> Messages.send(player, "region.error.not-marked"); |
||||||
|
case 2 -> Messages.send(player, "region.error.not-same-world"); |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
private static int addMember(CommandContext<CommandSourceStack> context) { |
||||||
|
Integer id = context.getArgument("id", Integer.class); |
||||||
|
String player = context.getArgument("player", String.class); |
||||||
|
String membership = context.getArgument("membership", String.class); |
||||||
|
CommandSender commandSender = context.getSource().getSender(); |
||||||
|
|
||||||
|
int status = RegionManager.addMember(id, "player", player, membership); |
||||||
|
switch (status){ |
||||||
|
case 0 -> Messages.send(commandSender, "generic.success.created"); |
||||||
|
case 1 -> Messages.send(commandSender, "region.error.not-exist"); |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,133 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.regions; |
||||||
|
|
||||||
|
import org.bukkit.block.Block; |
||||||
|
import org.bukkit.entity.Player; |
||||||
|
import org.bukkit.event.EventHandler; |
||||||
|
import org.bukkit.event.Listener; |
||||||
|
import org.bukkit.event.block.BlockBreakEvent; |
||||||
|
import org.bukkit.event.block.BlockPlaceEvent; |
||||||
|
import org.bukkit.event.player.*; |
||||||
|
import org.bukkit.inventory.ItemStack; |
||||||
|
import xyz.soukup.ecoCraftCore.database.objects.Region; |
||||||
|
import xyz.soukup.ecoCraftCore.database.objects.RegionMember; |
||||||
|
|
||||||
|
public class RegionEvents implements Listener { |
||||||
|
|
||||||
|
@EventHandler |
||||||
|
public void onPlayerInteract(PlayerInteractEvent event){ |
||||||
|
switch (event.getAction()) { |
||||||
|
case RIGHT_CLICK_BLOCK: |
||||||
|
case LEFT_CLICK_BLOCK: |
||||||
|
break; |
||||||
|
default: |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
Block block = event.getClickedBlock(); |
||||||
|
ItemStack item = event.getItem(); |
||||||
|
if (block == null) return; |
||||||
|
|
||||||
|
if (item != null && item.getType().isBlock() && !block.getType().isInteractable()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
boolean allowed = isAllowedToInteract( |
||||||
|
block.getWorld().getName(), |
||||||
|
event.getPlayer(), |
||||||
|
block.getX(), |
||||||
|
block.getY(), |
||||||
|
block.getZ() |
||||||
|
); |
||||||
|
|
||||||
|
event.setCancelled(!allowed); |
||||||
|
} |
||||||
|
|
||||||
|
@EventHandler |
||||||
|
public void onBlockPlace(BlockPlaceEvent event) { |
||||||
|
Block block = event.getBlockPlaced(); |
||||||
|
|
||||||
|
boolean allowed = isAllowedToInteract( |
||||||
|
block.getWorld().getName(), |
||||||
|
event.getPlayer(), |
||||||
|
block.getX(), |
||||||
|
block.getY(), |
||||||
|
block.getZ() |
||||||
|
); |
||||||
|
|
||||||
|
event.setCancelled(!allowed); |
||||||
|
} |
||||||
|
|
||||||
|
@EventHandler |
||||||
|
public void onBlockBreak(BlockBreakEvent event) { |
||||||
|
|
||||||
|
Block block = event.getBlock(); |
||||||
|
|
||||||
|
boolean allowed = isAllowedToInteract( |
||||||
|
block.getWorld().getName(), |
||||||
|
event.getPlayer(), |
||||||
|
block.getX(), |
||||||
|
block.getY(), |
||||||
|
block.getZ() |
||||||
|
); |
||||||
|
|
||||||
|
event.setCancelled(!allowed); |
||||||
|
} |
||||||
|
|
||||||
|
@EventHandler |
||||||
|
public void onBucketUse(PlayerBucketEmptyEvent event) { |
||||||
|
Block clickedBlock = event.getBlockClicked(); |
||||||
|
|
||||||
|
boolean allowed = isAllowedToInteract( |
||||||
|
clickedBlock.getWorld().getName(), |
||||||
|
event.getPlayer(), |
||||||
|
clickedBlock.getX(), |
||||||
|
clickedBlock.getY(), |
||||||
|
clickedBlock.getZ() |
||||||
|
); |
||||||
|
|
||||||
|
event.setCancelled(!allowed); |
||||||
|
} |
||||||
|
|
||||||
|
@EventHandler |
||||||
|
public void onBucketUse(PlayerBucketFillEvent event) { |
||||||
|
Block clickedBlock = event.getBlockClicked(); |
||||||
|
|
||||||
|
boolean allowed = isAllowedToInteract( |
||||||
|
clickedBlock.getWorld().getName(), |
||||||
|
event.getPlayer(), |
||||||
|
clickedBlock.getX(), |
||||||
|
clickedBlock.getY(), |
||||||
|
clickedBlock.getZ() |
||||||
|
); |
||||||
|
|
||||||
|
event.setCancelled(!allowed); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isAllowedToInteract(String island, Player player, int x, int y, int z){ |
||||||
|
if (player.isOp()){ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
Region region = Region.findRegion(x, y, z, island); |
||||||
|
|
||||||
|
if (region == null){ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
String name = player.getName(); |
||||||
|
|
||||||
|
for (RegionMember regionMember : region.getRegionMembers()){ |
||||||
|
|
||||||
|
if (regionMember.getMembertype().equals("player") && regionMember.getName().equals(name)){ |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
package xyz.soukup.ecoCraftCore.regions; |
||||||
|
|
||||||
|
import org.bukkit.Location; |
||||||
|
import org.bukkit.entity.Player; |
||||||
|
import xyz.soukup.ecoCraftCore.database.objects.Region; |
||||||
|
import xyz.soukup.ecoCraftCore.positionMarker.MarkerEvent; |
||||||
|
|
||||||
|
public class RegionManager { |
||||||
|
public static int createRegion(Player player, Integer type, String owner, String ownerType){ |
||||||
|
Location primaryLocation = MarkerEvent.primaryLocations.get(player); |
||||||
|
Location secondaryLocation = MarkerEvent.secondaryLocations.get(player); |
||||||
|
|
||||||
|
if (primaryLocation == null || secondaryLocation == null){ |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
if (primaryLocation.getWorld() != secondaryLocation.getWorld()){ |
||||||
|
return 2; |
||||||
|
} |
||||||
|
|
||||||
|
String worldName = primaryLocation.getWorld().getName(); |
||||||
|
int x1 = primaryLocation.getBlockX(); |
||||||
|
int y1 = primaryLocation.getBlockY(); |
||||||
|
int z1 = primaryLocation.getBlockZ(); |
||||||
|
int x2 = secondaryLocation.getBlockX(); |
||||||
|
int y2 = secondaryLocation.getBlockY(); |
||||||
|
int z2 = secondaryLocation.getBlockZ(); |
||||||
|
|
||||||
|
Region region = new Region(worldName, type, x1, y1, z1, x2, y2, z2); |
||||||
|
region.save(); |
||||||
|
|
||||||
|
region.addRegionMember(ownerType, owner, "owner"); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
public static int addMember(int id, String memberType, String member, String membershipType){ |
||||||
|
Region region = Region.findById(id); |
||||||
|
|
||||||
|
if (region == null){ |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
region.addRegionMember(memberType, member, membershipType); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
} |
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue