Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ jobs:
with:
use_release_asset: "true" # publish the jar attached to the release; do not rebuild
# ChunkBlock's own CurseForge project. Do not reuse 1512493 — that is AOneBlock's.
# Hangar has not approved ChunkBlock yet; a blank slug makes the reusable workflow
# skip the Hangar job entirely, so the release still succeeds. Fill in "ChunkBlock"
# once the project exists.
hangar_slug: "" # blank = skip Hangar
# https://hangar.papermc.io/BentoboxWorld/ChunkBlock
hangar_slug: "ChunkBlock"
curseforge_id: "1646388"
game_versions: "26.2,26.1.2,26.1.1,26.1,1.21.11,1.21.10,1.21.9,1.21.8,1.21.7,1.21.6,1.21.5"
version: ${{ inputs.version }} # empty on release events -> falls back to the release tag
Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ commands, and permissions.
The player command is `/ch` (alias `/chunkblock`), the admin command `/chadmin`
(aliases `/chunkblockadmin`, `/cba`). Beyond the standard BentoBox island commands:

| Command | Description |
|---|---|
| `/ch chunks` | Your chunk count, spendable credit, and a chat map of your territory |
| `/ch count` | Magic block count and phase |
| `/chadmin chunks <player> [reset]` | Inspect a player's chunks and credit, or re-lock them back to the start |
| `/chadmin bypass` | Toggle chunk-lock enforcement for yourself (needs `chunkblock.mod.bypasschunks`) |
| Command | Description |
|------------------------------------|----------------------------------------------------------------------------------|
| `/ch chunks` | Your chunk count, spendable credit, and a chat map of your territory |
| `/ch count` | Magic block count and phase |
| `/chadmin chunks <player> [reset]` | Inspect a player's chunks and credit, or re-lock them back to the start |
| `/chadmin bypass` | Toggle chunk-lock enforcement for yourself (needs `chunkblock.mod.bypasschunks`) |

## How claiming works

Expand Down Expand Up @@ -79,13 +79,13 @@ The player command is `/ch` (alias `/chunkblock`), the admin command `/chadmin`
In addition to the phase placeholders inherited from the magic-block engine
(`chunkblock_my_island_phase`, `chunkblock_my_island_count`, ...):

| Placeholder | Value |
|---|---|
| `chunkblock_island_chunks` | Unlocked chunk count |
| `chunkblock_island_max_chunks` | Maximum claimable chunks |
| `chunkblock_island_chunk_credit` | Level credit available to spend |
| Placeholder | Value |
|--------------------------------------|----------------------------------------------------|
| `chunkblock_island_chunks` | Unlocked chunk count |
| `chunkblock_island_max_chunks` | Maximum claimable chunks |
| `chunkblock_island_chunk_credit` | Level credit available to spend |
| `chunkblock_island_next_chunk_level` | Total island level needed to afford the next chunk |
| `chunkblock_island_ring` | Ring number of the outermost claimed chunk |
| `chunkblock_island_ring` | Ring number of the outermost claimed chunk |

## For developers

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.3.0</build.version>
<build.version>1.4.0</build.version>
<!-- SonarCloud -->
<sonar.projectKey>BentoBoxWorld_ChunkBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down
5 changes: 2 additions & 3 deletions src/main/java/world/bentobox/chunkblock/ChunkBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
import world.bentobox.chunkblock.requests.UnlockedChunksHandler;
import world.bentobox.chunkblock.requests.LocationStatsHandler;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.addons.Addon.State;
import world.bentobox.bentobox.api.configuration.Config;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.api.flags.Flag;
Expand Down Expand Up @@ -522,9 +521,9 @@ public void saveDefaultConfig() {
this.saveResource("trophies.yml", false);
}

/*
/**
* (non-Javadoc)
*
*
* @see world.bentobox.bentobox.api.addons.Addon#allLoaded()
*/
@Override
Expand Down
31 changes: 5 additions & 26 deletions src/main/java/world/bentobox/chunkblock/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,6 @@ public class Settings implements WorldSettings {
@ConfigEntry(path = "chunkblock.levels-per-chunk")
private int levelsPerChunk = 1;

@ConfigComment("Maximum number of chunks an island can claim, including the center chunk.")
@ConfigComment("441 chunks is a full 21 x 21 chunk square. Use -1 for no limit beyond what the")
@ConfigComment("island protection range can hold. The effective maximum is always capped so")
@ConfigComment("claimed chunks fit inside the protection range.")
@ConfigEntry(path = "chunkblock.max-chunks")
private int maxChunks = 441;

@ConfigComment("Require confirmation before level credit is spent on a chunk. When true, the")
@ConfigComment("first hit on the border previews the target chunk and its cost; the player")
@ConfigComment("must then sneak and hit the border again to actually claim it. This stops a")
Expand Down Expand Up @@ -313,8 +306,8 @@ public class Settings implements WorldSettings {
@ConfigComment("Default protection range radius in blocks. Cannot be larger than distance.")
@ConfigComment("Admins can change protection sizes for players individually using /chadmin range set <player> <new range>")
@ConfigComment("or set this permission: chunkblock.island.range.<number>")
@ConfigComment("ChunkBlock: this must cover the largest unlockable ring of chunks (see chunkblock.max-chunks).")
@ConfigComment("With max-chunks 441 (21x21, ring 10) the minimum needed is 168.")
@ConfigComment("ChunkBlock: the protection range determines how many chunks can be claimed.")
@ConfigComment("With range 168 the largest ring is 10, giving a 21x21 = 441 chunk square.")
@ConfigEntry(path = "world.protection-range")
private int islandProtectionRange = 168;

Expand Down Expand Up @@ -1244,9 +1237,9 @@ public void setDifficulty(Difficulty difficulty) {
public void setIslandDistance(int islandDistance) {
int snapped = Math.max(8, Math.round(islandDistance / 8F) * 8);
if (snapped != islandDistance) {
Logger.getLogger("ChunkBlock").warning(
"distance-between-islands must be a multiple of 8 for ChunkBlock; adjusted from "
+ islandDistance + " to " + snapped);
Logger.getLogger("ChunkBlock").warning(() ->
String.format("distance-between-islands must be a multiple of 8 for ChunkBlock; adjusted from %d to %d",
islandDistance, snapped));
}
this.islandDistance = snapped;
}
Expand Down Expand Up @@ -2624,20 +2617,6 @@ public void setLevelsPerChunk(int levelsPerChunk) {
this.levelsPerChunk = levelsPerChunk;
}

/**
* @return the configured maximum number of unlockable chunks including the center; -1 means unlimited
*/
public int getMaxChunks() {
return maxChunks;
}

/**
* @param maxChunks the maxChunks to set
*/
public void setMaxChunks(int maxChunks) {
this.maxChunks = maxChunks;
}

/**
* @return true if ring milestones are announced to the whole server
*/
Expand Down
80 changes: 42 additions & 38 deletions src/main/java/world/bentobox/chunkblock/chunks/BorderDisplay.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,16 @@ public void showBorder(Player player) {
hideBorder(player);
return;
}
Optional<Island> optionalIsland = addon.getIslands().getIslandAt(player.getLocation());
Location loc = player.getLocation();
if (loc == null) {
return;
}
Optional<Island> optionalIsland = addon.getIslands().getIslandAt(loc);
if (optionalIsland.isEmpty()) {
return;
}
Island island = optionalIsland.get();
ChunkManager cm = addon.getChunkManager();
Location loc = player.getLocation();
int pcx = loc.getBlockX() >> 4;
int pcz = loc.getBlockZ() >> 4;
// Check the 3x3 chunk neighborhood; locked chunks with an unlocked neighbor
Expand All @@ -131,30 +134,32 @@ public void showBorder(Player player) {
for (int dcz = -1; dcz <= 1; dcz++) {
int cx = pcx + dcx;
int cz = pcz + dcz;
if (cm.isUnlocked(island, cx, cz)) {
continue;
}
// West face (x = cx*16) shows if the west neighbor is unlocked;
// the locked-side block of that plane is at x = wallX
if (cm.isUnlocked(island, cx - 1, cz)) {
drawWallX(player, cx << 4, cz << 4, 0);
}
// East face (x = cx*16+16); locked side is at x = wallX - 1
if (cm.isUnlocked(island, cx + 1, cz)) {
drawWallX(player, (cx << 4) + 16, cz << 4, -1);
}
// North face (z = cz*16)
if (cm.isUnlocked(island, cx, cz - 1)) {
drawWallZ(player, cx << 4, cz << 4, 0);
}
// South face (z = cz*16+16)
if (cm.isUnlocked(island, cx, cz + 1)) {
drawWallZ(player, cx << 4, (cz << 4) + 16, -1);
}
drawLockedChunkFaces(player, island, cm, cx, cz);
}
}
}

/**
* Draws all four faces of a locked chunk that border unlocked territory.
*/
private void drawLockedChunkFaces(Player player, Island island, ChunkManager cm, int cx, int cz) {
if (cm.isUnlocked(island, cx, cz)) {
return;
}
if (cm.isUnlocked(island, cx - 1, cz)) {
drawWallX(player, cx << 4, cz << 4, 0);
}
if (cm.isUnlocked(island, cx + 1, cz)) {
drawWallX(player, (cx << 4) + 16, cz << 4, -1);
}
if (cm.isUnlocked(island, cx, cz - 1)) {
drawWallZ(player, cx << 4, cz << 4, 0);
}
if (cm.isUnlocked(island, cx, cz + 1)) {
drawWallZ(player, cx << 4, (cz << 4) + 16, -1);
}
}

/**
* Draws a wall on the plane x = wallX spanning the chunk starting at block minZ.
*/
Expand Down Expand Up @@ -194,23 +199,22 @@ private void drawColumn(Player player, int x, int z, boolean alongZ, int barrier
World world = player.getWorld();
int yFrom = Math.max(world.getMinHeight() - OUT_OF_WORLD_DEPTH, player.getLocation().getBlockY() - BARRIER_RADIUS);
int yTo = Math.min(world.getMaxHeight() + 2 * OUT_OF_WORLD_DEPTH, player.getLocation().getBlockY() + BARRIER_RADIUS);
boolean particles = addon.getSettings().isBorderShowParticles();
boolean barriers = addon.getSettings().isBorderBarrierBlocks();
for (int y = yFrom; y <= yTo; y++) {
if (particles) {
boolean outOfWorld = y < world.getMinHeight() || y > world.getMaxHeight();
Color color = outOfWorld ? OUT_OF_WORLD_COLOR : addon.getSettings().getBorderParticleColor();
// The wall plane runs along block boundaries; offset the dust onto the plane
double px = alongZ ? x : x + 0.5D;
double pz = alongZ ? z + 0.5D : z;
player.spawnParticle(Particle.DUST, px, y + 0.5D, pz, 1, 0, 0, 0, 0,
new Particle.DustOptions(color, 1.0F));
}
if (barriers && y >= world.getMinHeight() && y < world.getMaxHeight()) {
// Barrier blocks go just inside the locked chunk so they never obstruct
// the player's own territory
sendBarrier(player, new Location(world, barrierX, y, barrierZ));
}
drawColumnSegment(player, x, y, z, alongZ, barrierX, barrierZ, world);
}
}

private void drawColumnSegment(Player player, int x, int y, int z, boolean alongZ, int barrierX, int barrierZ, World world) {
if (addon.getSettings().isBorderShowParticles()) {
boolean outOfWorld = y < world.getMinHeight() || y > world.getMaxHeight();
Color color = outOfWorld ? OUT_OF_WORLD_COLOR : addon.getSettings().getBorderParticleColor();
double px = alongZ ? x : x + 0.5D;
double pz = alongZ ? z + 0.5D : z;
player.spawnParticle(Particle.DUST, px, y + 0.5D, pz, 1, 0, 0, 0, 0,
new Particle.DustOptions(color, 1.0F));
}
if (addon.getSettings().isBorderBarrierBlocks() && y >= world.getMinHeight() && y < world.getMaxHeight()) {
sendBarrier(player, new Location(world, barrierX, y, barrierZ));
}
}

Expand Down
15 changes: 5 additions & 10 deletions src/main/java/world/bentobox/chunkblock/chunks/ChunkManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public enum ClaimResult {
ALREADY_UNLOCKED,
/** The chunk does not touch the island's unlocked territory */
NOT_ADJACENT,
/** The chunk is outside the island's protection range or over max-chunks */
/** The chunk is outside the island's protection range */
BEYOND_LIMIT,
/** Not enough level credit */
NO_CREDIT
Expand Down Expand Up @@ -301,9 +301,7 @@ public ClaimResult checkGeometry(Island island, int chunkX, int chunkZ) {
if (data.isChunkUnlocked(dx, dz)) {
return ClaimResult.ALREADY_UNLOCKED;
}
if (Math.max(Math.abs(dx), Math.abs(dz)) > maxRingRadius(island)
|| (addon.getSettings().getMaxChunks() >= 0
&& data.getUnlockedChunkCount() >= addon.getSettings().getMaxChunks())) {
if (Math.max(Math.abs(dx), Math.abs(dz)) > maxRingRadius(island)) {
return ClaimResult.BEYOND_LIMIT;
}
// Must share a face with territory the island already owns
Expand Down Expand Up @@ -379,18 +377,15 @@ public int maxRingRadius(Island island) {
}

/**
* Returns the effective maximum number of chunks this island can unlock: the
* configured max-chunks, additionally capped by what fits inside the island's
* protection range.
* Returns the maximum number of chunks this island can unlock, determined by
* what fits inside the island's protection range.
*
* @param island the island
* @return the maximum unlockable chunk count, always &gt;= 1
*/
public int getMaxChunks(Island island) {
int rangeRadius = maxRingRadius(island);
int rangeCap = (2 * rangeRadius + 1) * (2 * rangeRadius + 1);
int configured = addon.getSettings().getMaxChunks();
return Math.max(1, configured < 0 ? rangeCap : Math.min(configured, rangeCap));
return Math.max(1, (2 * rangeRadius + 1) * (2 * rangeRadius + 1));
}

/**
Expand Down
27 changes: 22 additions & 5 deletions src/main/java/world/bentobox/chunkblock/chunks/ChunkMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,43 @@ private ChunkMap() {

/**
* Maps the territory around an island, row by row from north to south and west to east
* within a row — the order both maps draw in.
* within a row — the order both maps draw in. The viewport is centered on the island
* center chunk.
*
* @param addon the addon
* @param island the island whose territory is mapped
* @param viewer where the player is standing, or null if they are nowhere on the map
* @param radius how many chunks out from the center the map reaches
* @param radius how many chunks out from the viewport center the map reaches
* @return the cells of a square map (2 * radius + 1) chunks across
*/
public static List<Cell> cells(@NonNull ChunkBlock addon, @NonNull Island island, @Nullable Location viewer,
int radius) {
return cells(addon, island, viewer, radius, 0, 0);
}

/**
* Maps the territory around an island with a shifted viewport center. The viewport is
* centered on the chunk at {@code (viewDx, viewDz)} relative to the island center.
*
* @param addon the addon
* @param island the island whose territory is mapped
* @param viewer where the player is standing, or null if they are nowhere on the map
* @param radius how many chunks out from the viewport center the map reaches
* @param viewDx viewport center chunk offset east of the island center
* @param viewDz viewport center chunk offset south of the island center
* @return the cells of a square map (2 * radius + 1) chunks across
*/
public static List<Cell> cells(@NonNull ChunkBlock addon, @NonNull Island island, @Nullable Location viewer,
int radius, int viewDx, int viewDz) {
ChunkManager cm = addon.getChunkManager();
int centerChunkX = island.getCenter().getBlockX() >> 4;
int centerChunkZ = island.getCenter().getBlockZ() >> 4;
// A player who is not in this world stands on no chunk of the map
boolean sameWorld = viewer != null && Util.sameWorld(island.getWorld(), viewer.getWorld());
int playerDx = sameWorld ? (viewer.getBlockX() >> 4) - centerChunkX : Integer.MIN_VALUE;
int playerDz = sameWorld ? (viewer.getBlockZ() >> 4) - centerChunkZ : Integer.MIN_VALUE;
List<Cell> cells = new ArrayList<>();
for (int dz = -radius; dz <= radius; dz++) {
for (int dx = -radius; dx <= radius; dx++) {
for (int dz = viewDz - radius; dz <= viewDz + radius; dz++) {
for (int dx = viewDx - radius; dx <= viewDx + radius; dx++) {
Kind kind;
if (dx == 0 && dz == 0) {
kind = Kind.CENTER;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ public boolean canExecute(User user, String label, List<String> args) {

@Override
public boolean execute(User user, String label, List<String> args) {
UUID targetUUID = Util.getUUID(args.get(0));
UUID targetUUID = Util.getUUID(args.getFirst());
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.getFirst());
return false;
}
Island island = getIslands().getIsland(getWorld(), targetUUID);
Expand All @@ -58,7 +58,7 @@ public boolean execute(User user, String label, List<String> args) {
}
ChunkManager cm = addon.getChunkManager();
if (args.size() == 1) {
user.sendMessage("chunkblock.commands.admin.chunks.info", TextVariables.NAME, args.get(0),
user.sendMessage("chunkblock.commands.admin.chunks.info", TextVariables.NAME, args.getFirst(),
TextVariables.NUMBER, String.valueOf(cm.getUnlockedChunkCount(island)),
"[max]", String.valueOf(cm.getMaxChunks(island)),
"[spent]", String.valueOf(cm.getSpentLevels(island)),
Expand All @@ -68,7 +68,7 @@ public boolean execute(User user, String label, List<String> args) {
if ("reset".equals(args.get(1).toLowerCase(Locale.ENGLISH))) {
addon.getOneBlocksIsland(island).resetUnlockedChunks();
addon.getBlockListener().saveIsland(island);
user.sendMessage("chunkblock.commands.admin.chunks.reset", TextVariables.NAME, args.get(0));
user.sendMessage("chunkblock.commands.admin.chunks.reset", TextVariables.NAME, args.getFirst());
return true;
}
showHelp(this, user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ public AdminCommand(GameModeAddon addon) {
super(addon);
}

/* (non-Javadoc)
/**
* (non-Javadoc)
*
* @see world.bentobox.bentobox.api.commands.admin.DefaultAdminCommand#setup()
*/
@Override
Expand Down
Loading
Loading