feat: migrate shops when an AreaShop-OG plot is transferred
Transferring a plot moved the AreaShop region and its WorldGuard membership to the new owner, but the QuickShop shops inside kept the previous owner. The new holder could not manage them and the old owner still collected every sale on a plot they no longer held. Listen for AreaShop-OG's new TransferredRegionEvent and hand the shops in the region to QuickShop-OG's ShopManager#transferShopsOwnership, leaving stock in place. When the running QuickShop has no such method the count of orphaned shops is reported instead of failing silently. Replace the vendored libs/AreaShop-2.9.1.jar with a git submodule at libs/AreaShop-OG, following the Template-OG layout. The jar was upstream AreaShop, not AreaShop-OG, so newly added AreaShop-OG API was invisible to the compile classpath. AreaShop-OG is a multi-project build, so it joins as a composite build via includeBuild. Also prefer QuickShop's chunk coordinate shop lookup over the Chunk overload, so scanning a region no longer forces its chunks to load, and walk the region's chunk range inclusively instead of overshooting by one chunk column. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f3eb494984
commit
b1d58052e4
8 files changed
+111
-30
No files matched your search
@@ -0,0 +1,4 @@
|
||||
[submodule "libs/AreaShop-OG"]
|
||||
path = libs/AreaShop-OG
|
||||
url = https://github.com/true-og/AreaShop-OG
|
||||
branch = dev/bleeding
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
# Fetch all submodule content.
|
||||
git submodule update --force --recursive --init
|
||||
+1
-1
@@ -58,7 +58,7 @@ repositories {
|
||||
dependencies {
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare Purpur API version to be packaged.
|
||||
compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.8") // Import WorldGuard API.
|
||||
compileOnly(files("libs/AreaShop-2.9.1.jar")) // Import AreaShop-OG compatible upstream API.
|
||||
compileOnly("me.wiefferink:areashop:1.0") // Import AreaShop-OG API (from the libs/ submodule).
|
||||
}
|
||||
|
||||
/* ---------------------- Reproducible jars ---------------------------- */
|
||||
|
||||
Binary file not shown.
Submodule
+1
Submodule libs/AreaShop-OG added at 8984508336.
@@ -1,3 +1,9 @@
|
||||
plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" }
|
||||
|
||||
rootProject.name = "ShopBridge-OG"
|
||||
|
||||
ProcessBuilder("sh", "bootstrap.sh").directory(rootDir).inheritIO().start().let {
|
||||
if (it.waitFor() != 0) throw GradleException("bootstrap.sh failed")
|
||||
}
|
||||
|
||||
includeBuild("libs/AreaShop-OG") // Import AreaShop-OG multi-project build (from source).
|
||||
+62
-29
@@ -1,10 +1,11 @@
|
||||
import java.util.UUID
|
||||
import java.util.logging.Level
|
||||
import me.wiefferink.areashop.events.notify.DeletedRegionEvent
|
||||
import me.wiefferink.areashop.events.notify.ResoldRegionEvent
|
||||
import me.wiefferink.areashop.events.notify.SoldRegionEvent
|
||||
import me.wiefferink.areashop.events.notify.TransferredRegionEvent
|
||||
import me.wiefferink.areashop.events.notify.UnrentedRegionEvent
|
||||
import me.wiefferink.areashop.regions.GeneralRegion
|
||||
import org.bukkit.Chunk
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.EventPriority
|
||||
@@ -57,35 +58,39 @@ class Main : JavaPlugin(), Listener {
|
||||
removeShopsIn(event.region, wipeStock = false)
|
||||
}
|
||||
|
||||
// A player handed the plot to another player, so the shops follow the plot.
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
fun onRegionTransferred(event: TransferredRegionEvent) {
|
||||
transferShopsIn(event.region, event.toPlayer ?: return)
|
||||
}
|
||||
|
||||
private fun transferShopsIn(region: GeneralRegion, newOwner: UUID) {
|
||||
val shops = collectShopsIn(region)
|
||||
if (shops.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val handled =
|
||||
try {
|
||||
quickShop.transferShopsOwnership(shops.values, newOwner)
|
||||
} catch (error: Exception) {
|
||||
logger.warning("Could not transfer shops in region ${region.name}: ${error.message}")
|
||||
return
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
logger.warning(
|
||||
"${QuickShopAdapter.PLUGIN_NAME} has no ownership transfer API, " +
|
||||
"${shops.size} shop(s) in region ${region.name} still belong to the previous owner."
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
logger.info("Transferred ${shops.size} shop(s) in region ${region.name} to $newOwner.")
|
||||
}
|
||||
|
||||
private fun removeShopsIn(region: GeneralRegion, wipeStock: Boolean) {
|
||||
val protectedRegion = region.region ?: return
|
||||
val world = region.world ?: return
|
||||
|
||||
val minPoint = region.minimumPoint
|
||||
val maxPoint = region.maximumPoint
|
||||
val chuckLocations = hashSetOf<Chunk>()
|
||||
|
||||
var x = minPoint.blockX
|
||||
while (x <= maxPoint.blockX + 16) {
|
||||
var z = minPoint.blockZ
|
||||
while (z <= maxPoint.blockZ + 16) {
|
||||
chuckLocations.add(world.getChunkAt(x shr 4, z shr 4))
|
||||
z += 16
|
||||
}
|
||||
x += 16
|
||||
}
|
||||
|
||||
val shopMap = hashMapOf<Location, Any>()
|
||||
|
||||
for (chunk in chuckLocations) {
|
||||
shopMap.putAll(quickShop.getShops(chunk))
|
||||
}
|
||||
|
||||
for ((shopLocation, shop) in shopMap) {
|
||||
if (!protectedRegion.contains(shopLocation.blockX, shopLocation.blockY, shopLocation.blockZ)) {
|
||||
continue
|
||||
}
|
||||
|
||||
for ((shopLocation, shop) in collectShopsIn(region)) {
|
||||
// Empty the container first, because the shop's inventory link is no longer resolvable after deletion.
|
||||
if (wipeStock) {
|
||||
try {
|
||||
@@ -100,4 +105,32 @@ class Main : JavaPlugin(), Listener {
|
||||
quickShop.deleteShop(shop)
|
||||
}
|
||||
}
|
||||
|
||||
// Every shop inside the region's WorldGuard boundary, keyed by its location.
|
||||
private fun collectShopsIn(region: GeneralRegion): Map<Location, Any> {
|
||||
val protectedRegion = region.region ?: return emptyMap()
|
||||
val world = region.world ?: return emptyMap()
|
||||
|
||||
val minPoint = region.minimumPoint
|
||||
val maxPoint = region.maximumPoint
|
||||
val minChunkX = minPoint.blockX shr 4
|
||||
val maxChunkX = maxPoint.blockX shr 4
|
||||
val minChunkZ = minPoint.blockZ shr 4
|
||||
val maxChunkZ = maxPoint.blockZ shr 4
|
||||
|
||||
val shopMap = hashMapOf<Location, Any>()
|
||||
for (chunkX in minChunkX..maxChunkX) {
|
||||
for (chunkZ in minChunkZ..maxChunkZ) {
|
||||
// Prefer the coordinate lookup, the Chunk overload would load the chunk.
|
||||
val shops =
|
||||
quickShop.getShops(world.name, chunkX, chunkZ)
|
||||
?: quickShop.getShops(world.getChunkAt(chunkX, chunkZ))
|
||||
shopMap.putAll(shops)
|
||||
}
|
||||
}
|
||||
|
||||
return shopMap.filterKeys { location ->
|
||||
protectedRegion.contains(location.blockX, location.blockY, location.blockZ)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.lang.reflect.Method
|
||||
import java.util.UUID
|
||||
import org.bukkit.Chunk
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.plugin.Plugin
|
||||
@@ -9,6 +10,18 @@ internal class QuickShopAdapter private constructor(private val shopManager: Any
|
||||
shopManager.javaClass.methods.findMethod("getShops", Chunk::class.java)
|
||||
?: throw IllegalStateException("QuickShop-OG does not expose ShopManager#getShops(Chunk).")
|
||||
|
||||
// Chunk coordinate lookup, preferred because it never forces a chunk to load.
|
||||
private val getShopsByCoordinateMethod =
|
||||
shopManager.javaClass.methods.findMethod(
|
||||
"getShops",
|
||||
String::class.java,
|
||||
Int::class.javaPrimitiveType!!,
|
||||
Int::class.javaPrimitiveType!!,
|
||||
)
|
||||
|
||||
private val transferShopsOwnershipMethod =
|
||||
shopManager.javaClass.methods.findMethod("transferShopsOwnership", Collection::class.java, UUID::class.java)
|
||||
|
||||
private val managerDeleteShopMethod =
|
||||
shopManager.javaClass.methods.firstOrNull { method ->
|
||||
method.name == "deleteShop" && method.parameterCount == 1
|
||||
@@ -21,6 +34,26 @@ internal class QuickShopAdapter private constructor(private val shopManager: Any
|
||||
fun getShops(chunk: Chunk): Map<Location, Any> {
|
||||
val shops = getShopsMethod.call(shopManager, chunk) as? Map<*, *> ?: return emptyMap()
|
||||
|
||||
return toShopMap(shops)
|
||||
}
|
||||
|
||||
// Returns null when this QuickShop build has no coordinate based lookup.
|
||||
fun getShops(world: String, chunkX: Int, chunkZ: Int): Map<Location, Any>? {
|
||||
val method = getShopsByCoordinateMethod ?: return null
|
||||
val shops = method.call(shopManager, world, chunkX, chunkZ) as? Map<*, *> ?: return emptyMap()
|
||||
|
||||
return toShopMap(shops)
|
||||
}
|
||||
|
||||
// True when the shops were handed to QuickShop, false when unsupported.
|
||||
fun transferShopsOwnership(shops: Collection<Any>, newOwner: UUID): Boolean {
|
||||
val method = transferShopsOwnershipMethod ?: return false
|
||||
method.call(shopManager, shops, newOwner)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun toShopMap(shops: Map<*, *>): Map<Location, Any> {
|
||||
return shops.entries.associate { entry ->
|
||||
val location =
|
||||
entry.key as? Location
|
||||
|
||||
Reference in new issue
Block a user