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
4 changes: 4 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ check(api.apiVersion == 2)
val plot = api.getPlotAt(player.world.name, player.location.blockX, player.location.blockZ)
val owned = api.getOwnedPlots(player.uniqueId)
val accessible = api.getAccessiblePlots(player.uniqueId)

// Exact containment for a building/arena footprint. Unlike corner checks this also rejects holes.
val fits = plot?.containsArea(player.world.name, minX, minZ, maxX, maxZ) == true
val roundArenaFits = plot?.containsCircle(player.world.name, centerX, centerZ, diameter) == true
```

Java:
Expand Down
55 changes: 55 additions & 0 deletions src/main/kotlin/pl/syntaxdevteam/plotsx/api/PlotsXApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,61 @@ data class PlotSnapshot @JvmOverloads constructor(val id: Int, val owner: UUID,
(if (radius == null) chunks.any { it.x == Math.floorDiv(x, 16) && it.z == Math.floorDiv(z, 16) }
else (kotlin.math.abs(x.toLong() - this.x) <= radius.toLong() &&
kotlin.math.abs(z.toLong() - this.z) <= radius.toLong()) || extensions.any { it.contains(x, z) })

/**
* Returns true only when every block column in the inclusive rectangle belongs to this plot.
* This is intentionally stricter than checking the four corners: chunk plots and joined classic
* segments may be non-rectangular and can contain holes between otherwise valid corners.
*/
fun containsArea(world: String, minX: Int, minZ: Int, maxX: Int, maxZ: Int): Boolean {
if (!this.world.equals(world, true) || minX > maxX || minZ > maxZ) return false
if (radius == null) {
val claimed = chunks.toHashSet()
for (chunkX in Math.floorDiv(minX, 16)..Math.floorDiv(maxX, 16)) {
for (chunkZ in Math.floorDiv(minZ, 16)..Math.floorDiv(maxZ, 16)) {
if (ChunkSnapshot(chunkX, chunkZ) !in claimed) return false
}
}
return true
}

// A rectangle covered by a union of square regions has no gaps iff each horizontal span is
// fully covered on every row. Avoid iterating every block in large declared areas.
for (blockZ in minZ..maxZ) {
val intervals = buildList {
fun addRegion(centerX: Int, centerZ: Int, regionRadius: Int) {
if (blockZ.toLong() !in centerZ.toLong() - regionRadius..centerZ.toLong() + regionRadius) return
add(centerX.toLong() - regionRadius to centerX.toLong() + regionRadius)
}
addRegion(x, z, requireNotNull(radius))
extensions.forEach { addRegion(it.x, it.z, it.radius) }
}.sortedBy { it.first }
var coveredUntil = minX.toLong() - 1
for ((start, end) in intervals) {
if (start > coveredUntil + 1) break
if (end > coveredUntil) coveredUntil = end
if (coveredUntil >= maxX) break
}
if (coveredUntil < maxX) return false
}
return true
}

/** Exact filled-circle containment. Diameter is expressed in block columns around the centre. */
fun containsCircle(world: String, centerX: Int, centerZ: Int, diameter: Int): Boolean {
if (diameter <= 0) return false
val radius = diameter / 2.0
val minZ = kotlin.math.ceil(centerZ - radius + 0.5).toInt()
val maxZ = kotlin.math.floor(centerZ + radius - 0.5).toInt()
for (blockZ in minZ..maxZ) {
val dz = blockZ + 0.5 - (centerZ + 0.5)
val halfSpan = kotlin.math.sqrt(radius * radius - dz * dz)
val minX = kotlin.math.ceil(centerX + 0.5 - halfSpan).toInt()
val maxX = kotlin.math.floor(centerX + 0.5 + halfSpan).toInt()
if (minX <= maxX && !containsArea(world, minX, blockZ, maxX, blockZ)) return false
}
return true
}
}

/** API v2: complete 16 x 16 block columns; radius is null for chunk plots. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,28 @@ package pl.syntaxdevteam.plotsx.databases

import org.junit.Assert.*
import org.junit.Test
import pl.syntaxdevteam.plotsx.api.ChunkSnapshot
import pl.syntaxdevteam.plotsx.api.PlotRegionSnapshot
import pl.syntaxdevteam.plotsx.api.PlotSnapshot
import java.util.UUID

class PlotGeometryTest {

@Test
fun `api snapshot requires the complete requested area to be claimed`() {
val owner = UUID.randomUUID()
val chunkPlot = PlotSnapshot(1, owner, "world", 0, 64, 0, null, "chunks", 0,
chunks = listOf(ChunkSnapshot(0, 0), ChunkSnapshot(1, 0)))
assertTrue(chunkPlot.containsArea("world", 2, 2, 29, 13))
assertFalse(chunkPlot.containsArea("world", 2, 2, 29, 18))

val joined = PlotSnapshot(2, owner, "world", 0, 64, 0, 2, "joined", 0,
extensions = listOf(PlotRegionSnapshot(5, 0, 2)))
assertTrue(joined.containsArea("world", -2, -2, 7, 2))
assertFalse(joined.containsArea("world", -2, -3, 7, 2))
assertTrue(joined.containsCircle("world", 0, 0, 5))
assertFalse(joined.containsCircle("world", 0, 0, 7))
}
private val base = PlotData(1, UUID.randomUUID(), 0, 0, 64, 16, "world", "Home", 0)

@Test fun `north and east form L without claiming missing corner`() {
Expand Down
Loading