Compare commits

...

2 Commits

Author SHA1 Message Date
84c4252592 feat: コンテナ追加 2025-12-20 01:56:22 +09:00
47bfcfc1e4 feat: 表示安定化 2025-12-20 01:26:09 +09:00
3 changed files with 391 additions and 80 deletions

View File

@ -9,6 +9,14 @@ import org.bukkit.block.sign.Side
import org.bukkit.plugin.java.JavaPlugin
object ShopVisuals {
val VALID_CONTAINERS = setOfNotNull(
org.bukkit.Material.CHEST,
org.bukkit.Material.TRAPPED_CHEST,
org.bukkit.Material.BARREL,
org.bukkit.Material.DECORATED_POT,
org.bukkit.Material.COPPER_CHEST
)
fun updateSign(plugin: JavaPlugin, block: org.bukkit.block.Block, shopData: ShopData, side: Side) {
val state = block.state as? Sign ?: return
val signSide = state.getSide(side)
@ -46,18 +54,75 @@ object ShopVisuals {
// L3: (Empty)
// L4: <Owner Name> (Gray)
signSide.line(0, Component.text(itemName, NamedTextColor.AQUA))
signSide.line(1, Component.text("x$amount - $priceStr", NamedTextColor.BLACK))
signSide.line(2, Component.empty())
signSide.line(0, Component.empty())
signSide.line(1, Component.text(itemName, NamedTextColor.AQUA))
signSide.line(2, Component.text("x$amount - $priceStr", NamedTextColor.BLACK))
signSide.line(3, Component.text(ownerName, NamedTextColor.GRAY))
signSide.isGlowingText = true
// Update the sign one tick later to ensure persistence compatibility
// Or if called from command, update immediately?
// Safer to always schedule if in doubt, or check constraints.
// For command, immediate update is fine, but for event, delay is needed.
// Let's us runTask always for safety.
plugin.server.scheduler.runTask(plugin, Runnable {
state.update()
})
// Update the sign immediately
state.isWaxed = true // Force lock visually
state.update()
}
fun calculateStock(block: org.bukkit.block.Block, item: org.bukkit.inventory.ItemStack?): Int {
if (item == null) return 0
val downBlock = block.getRelative(org.bukkit.block.BlockFace.DOWN)
if (downBlock.type !in VALID_CONTAINERS) return 0
// Use InventoryHolder to support Chests, Barrels, etc.
val container = downBlock.state as? org.bukkit.inventory.InventoryHolder ?: return 0
val inventory = container.inventory
var count = 0
for (i in 0 until inventory.size) {
val slotItem = inventory.getItem(i)
if (slotItem != null && slotItem.isSimilar(item)) {
count += slotItem.amount
}
}
return count
}
fun removeDisplay(location: org.bukkit.Location, face: String) {
val world = location.world ?: return
val block = location.block
val data = block.blockData as? org.bukkit.block.data.Rotatable ?: return
val rotation = data.rotation
val yaw = yawFromBlockFace(rotation) + 90f
val offset = if (face == "FRONT") org.joml.Vector3f(0.25f, 0.25f, 0.0f) else org.joml.Vector3f(-0.25f, 0.25f, 0.0f)
val rad = Math.toRadians(yaw.toDouble())
offset.rotateY(-rad.toFloat())
val center = location.clone().add(0.5, 0.0, 0.5)
val targetPos = center.clone().add(offset.x.toDouble(), offset.y.toDouble(), offset.z.toDouble())
// Search for entity
val nearby = world.getNearbyEntities(targetPos, 0.2, 0.2, 0.2)
nearby.filterIsInstance<org.bukkit.entity.ItemDisplay>().forEach { it.remove() }
}
fun yawFromBlockFace(face: org.bukkit.block.BlockFace): Float {
return when (face) {
org.bukkit.block.BlockFace.SOUTH -> 0f
org.bukkit.block.BlockFace.SOUTH_SOUTH_WEST -> 22.5f
org.bukkit.block.BlockFace.SOUTH_WEST -> 45f
org.bukkit.block.BlockFace.WEST_SOUTH_WEST -> 67.5f
org.bukkit.block.BlockFace.WEST -> 90f
org.bukkit.block.BlockFace.WEST_NORTH_WEST -> 112.5f
org.bukkit.block.BlockFace.NORTH_WEST -> 135f
org.bukkit.block.BlockFace.NORTH_NORTH_WEST -> 157.5f
org.bukkit.block.BlockFace.NORTH -> 180f
org.bukkit.block.BlockFace.NORTH_NORTH_EAST -> 202.5f
org.bukkit.block.BlockFace.NORTH_EAST -> 225f
org.bukkit.block.BlockFace.EAST_NORTH_EAST -> 247.5f
org.bukkit.block.BlockFace.EAST -> 270f
org.bukkit.block.BlockFace.EAST_SOUTH_EAST -> 292.5f
org.bukkit.block.BlockFace.SOUTH_EAST -> 315f
org.bukkit.block.BlockFace.SOUTH_SOUTH_EAST -> 337.5f
else -> 0f
}
}
}

View File

@ -51,14 +51,30 @@ object ShopCommands {
}
}
literal("update-signs") {
literal("list") {
permission {
description = "Force update all shop signs"
defaultValue = org.bukkit.permissions.PermissionDefault.OP
description = "List all shops sorted by distance"
defaultValue = org.bukkit.permissions.PermissionDefault.TRUE
}
executes {
val count = updateAllSigns(plugin)
sender.sendMessage(Component.text("Updated $count shop signs.", NamedTextColor.GREEN))
if (sender !is Player) {
sender.sendMessage(Component.text("Only players can use this command.", NamedTextColor.RED))
return@executes
}
displayShopList(plugin, sender as Player)
}
}
literal("delete") {
permission {
description = "Delete a shop by ID"
defaultValue = org.bukkit.permissions.PermissionDefault.OP
}
integer("id") {
executes {
val id: Int = argument("id")
deleteShop(plugin, sender, id)
}
}
}
}
@ -83,20 +99,87 @@ object ShopCommands {
sender.sendMessage(Component.text("Given Shop Sign Tier $tier", NamedTextColor.GREEN))
}
private fun updateAllSigns(plugin: ShopPlugin): Int {
private fun displayShopList(plugin: ShopPlugin, player: Player) {
val shops = net.hareworks.hcu.shop.database.ShopRepository.findAll()
var count = 0
for (shop in shops) {
val world = org.bukkit.Bukkit.getWorld(shop.worldUid) ?: continue
val block = world.getBlockAt(shop.x, shop.y, shop.z)
// Ideally we check if loaded, but getBlockAt loads chunk if not async?
// In main thread command, it's fine for now, but beware of lag with thousands of shops.
if (block.type == Material.OAK_SIGN) {
val side = if (shop.face == "FRONT") org.bukkit.block.sign.Side.FRONT else org.bukkit.block.sign.Side.BACK
net.hareworks.hcu.shop.ShopVisuals.updateSign(plugin, block, shop, side)
count++
val playerLoc = player.location
val playerWorldUid = player.world.uid
// Filter by world and sort by distance
val sortedShops = shops.filter { it.worldUid == playerWorldUid }
.map { shop ->
val loc = org.bukkit.Location(player.world, shop.x.toDouble(), shop.y.toDouble(), shop.z.toDouble())
shop to playerLoc.distance(loc)
}
.sortedBy { it.second }
player.sendMessage(Component.text("--- Shop List (Nearest) ---", NamedTextColor.GOLD))
if (sortedShops.isEmpty()) {
player.sendMessage(Component.text("No shops found in this world.", NamedTextColor.GRAY))
} else {
sortedShops.forEach { (shop, distance) ->
val distStr = String.format("%.1fm", distance)
val itemStack = shop.item
val itemName = if (itemStack != null) {
itemStack.type.name.lowercase().split("_")
.joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
} else {
"Not Set"
}
val price = net.hareworks.hcu.economy.api.EconomyService.format(shop.price.toInt())
player.sendMessage(
Component.text("#${shop.id}", NamedTextColor.YELLOW)
.append(Component.text(" - ", NamedTextColor.GRAY))
.append(Component.text(itemName, NamedTextColor.AQUA))
.append(Component.text(" ($price)", NamedTextColor.GREEN))
.append(Component.text(" - ", NamedTextColor.GRAY))
.append(Component.text(distStr, NamedTextColor.GRAY))
)
}
}
return count
}
private fun deleteShop(plugin: ShopPlugin, sender: org.bukkit.command.CommandSender, id: Int) {
val shop = net.hareworks.hcu.shop.database.ShopRepository.findById(id)
if (shop == null) {
sender.sendMessage(Component.text("Shop with ID $id not found.", NamedTextColor.RED))
return
}
// Remove Display
val world = org.bukkit.Bukkit.getWorld(shop.worldUid)
if (world != null) {
val location = org.bukkit.Location(world, shop.x.toDouble(), shop.y.toDouble(), shop.z.toDouble())
net.hareworks.hcu.shop.ShopVisuals.removeDisplay(location, shop.face)
val block = location.block
if (block.type == Material.OAK_SIGN) {
// Check for other shops at this location
val otherFace = if (shop.face == "FRONT") "BACK" else "FRONT"
val otherShop = net.hareworks.hcu.shop.database.ShopRepository.findByLocationAndFace(location, otherFace)
if (otherShop == null) {
// No other shops, remove the block
block.type = Material.AIR
// Create breakage particles/sound? Optional.
} else {
// Clear text for that side
val sign = block.state as? org.bukkit.block.Sign
if (sign != null) {
val side = if (shop.face == "FRONT") org.bukkit.block.sign.Side.FRONT else org.bukkit.block.sign.Side.BACK
val signSide = sign.getSide(side)
signSide.line(0, Component.empty())
signSide.line(1, Component.empty())
signSide.line(2, Component.empty())
signSide.line(3, Component.empty())
sign.update()
sender.sendMessage(Component.text("Sign block kept because another shop exists on the other side.", NamedTextColor.YELLOW))
}
}
}
}
net.hareworks.hcu.shop.database.ShopRepository.delete(id)
sender.sendMessage(Component.text("Shop #$id deleted.", NamedTextColor.GREEN))
}
}

View File

@ -16,6 +16,7 @@ import org.bukkit.entity.ItemDisplay
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.block.BlockPlaceEvent
import org.bukkit.event.block.BlockBreakEvent
import org.bukkit.event.block.SignChangeEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.event.block.Action
@ -33,7 +34,7 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
private val tierKey = NamespacedKey(plugin, "shop_tier")
@EventHandler
@EventHandler(ignoreCancelled = true)
fun onShopSignPlace(event: BlockPlaceEvent) {
val item = event.itemInHand
val meta = item.itemMeta ?: return
@ -47,9 +48,9 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
return
}
// Check block against (Chest)
if (event.blockAgainst.type != Material.CHEST) {
// Only valid on top of chests
// Check block against (Chest/Container)
if (event.blockAgainst.type !in ShopVisuals.VALID_CONTAINERS) {
// Only valid on top of supported containers
event.isCancelled = true
return
}
@ -64,12 +65,12 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
// Just ensure color is black initially if needed, but Visuals will overwrite.
val frontSide = signState.getSide(Side.FRONT)
frontSide.color = DyeColor.BLACK
frontSide.isGlowingText = false
frontSide.isGlowingText = true
// Back Text
val backSide = signState.getSide(Side.BACK)
backSide.color = DyeColor.BLACK
backSide.isGlowingText = false
backSide.isGlowingText = true
// Save Tier to Block and Wax (Lock)
signState.persistentDataContainer.set(tierKey, PersistentDataType.INTEGER, tier)
@ -88,15 +89,15 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
} else {
val actorId = playerEntry.actorId
plugin.logger.info("Creating shops for player ${event.player.name} at ${event.blockPlaced.location}")
// Create FRONT shop
val frontShop = ShopRepository.create(
actorId = actorId,
location = event.blockPlaced.location,
face = "FRONT"
)
if (frontShop != null) {
ShopVisuals.updateSign(plugin, event.blockPlaced, frontShop, Side.FRONT)
}
plugin.logger.info("Front shop created: ${frontShop != null} (ID: ${frontShop?.id})")
// Create BACK shop
val backShop = ShopRepository.create(
@ -104,13 +105,35 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
location = event.blockPlaced.location,
face = "BACK"
)
if (backShop != null) {
ShopVisuals.updateSign(plugin, event.blockPlaced, backShop, Side.BACK)
}
}
plugin.logger.info("Back shop created: ${backShop != null} (ID: ${backShop?.id})")
// Spawn Displays
spawnDisplays(event.blockPlaced)
// Close UI and Update Visuals (Delayed to override client-side editor opening)
// Close UI immediately (Next tick)
plugin.server.scheduler.runTask(plugin, Runnable {
event.player.closeInventory()
})
// Update Visuals Delayed to ensure client state is settled
plugin.server.scheduler.runTaskLater(plugin, Runnable {
plugin.logger.info("Executing delayed visual update task.")
if (frontShop != null) {
plugin.logger.info("Updating FRONT sign.")
ShopVisuals.updateSign(plugin, event.blockPlaced, frontShop, Side.FRONT)
} else {
plugin.logger.warning("Front shop was null in delayed task.")
}
if (backShop != null) {
plugin.logger.info("Updating BACK sign.")
ShopVisuals.updateSign(plugin, event.blockPlaced, backShop, Side.BACK)
} else {
plugin.logger.warning("Back shop was null in delayed task.")
}
spawnDisplays(event.blockPlaced)
}, 1L)
}
}
private fun spawnDisplays(block: org.bukkit.block.Block) {
@ -118,7 +141,7 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
val data = block.blockData as? Rotatable ?: return
val rotation = data.rotation // BlockFace
val yaw = yawFromBlockFace(rotation) + 90f
val yaw = ShopVisuals.yawFromBlockFace(rotation) + 90f
// Calculate offsets relative to center (0.5, 0, 0.5)
// Original request assumes 0,0,0 is block origin.
@ -129,14 +152,6 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
val backOffset = Vector3f(-0.25f, 0.25f, 0.0f)
// Rotate offsets
// yaw is degrees clockwise from South (Z+).
// radians need to be negated for standard CCW rotation logic if we use that,
// or just use rotateY with appropriate sign.
// rotateY in JOML: "Rotates this vector by the given angle in radians around the Y axis"
// Standard geometric Y rotation is CCW.
// Minecraft Yaw 90 (West) should rotate (0,0,1) to (-1,0,0).
// Std Rot 90 CCW: (0,0,1) -> (1,0,0).
// So they are opposite. We should rotate by -yaw.
val rad = Math.toRadians(yaw.toDouble())
frontOffset.rotateY(-rad.toFloat())
@ -151,7 +166,6 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
// Spawn Front
world.spawn(frontPos, ItemDisplay::class.java) { display ->
// display.setItemStack(ItemStack(Material.AIR)) // Default empty
display.setRotation(yaw, 0f)
// Transformation
@ -182,28 +196,6 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
}
}
private fun yawFromBlockFace(face: BlockFace): Float {
return when (face) {
BlockFace.SOUTH -> 0f
BlockFace.SOUTH_SOUTH_WEST -> 22.5f
BlockFace.SOUTH_WEST -> 45f
BlockFace.WEST_SOUTH_WEST -> 67.5f
BlockFace.WEST -> 90f
BlockFace.WEST_NORTH_WEST -> 112.5f
BlockFace.NORTH_WEST -> 135f
BlockFace.NORTH_NORTH_WEST -> 157.5f
BlockFace.NORTH -> 180f
BlockFace.NORTH_NORTH_EAST -> 202.5f
BlockFace.NORTH_EAST -> 225f
BlockFace.EAST_NORTH_EAST -> 247.5f
BlockFace.EAST -> 270f
BlockFace.EAST_SOUTH_EAST -> 292.5f
BlockFace.SOUTH_EAST -> 315f
BlockFace.SOUTH_SOUTH_EAST -> 337.5f
else -> 0f
}
}
@EventHandler
fun onSignChange(event: SignChangeEvent) {
val player = event.player
@ -230,15 +222,29 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
player.sendMessage(Component.text("Shop price set to $$price", NamedTextColor.GREEN))
// Update Actual Sign Visuals
ShopVisuals.updateSign(plugin, event.block, shopData.copy(price = price), event.side)
// Run 1 tick later to override server-side sign handling if needed
plugin.server.scheduler.runTask(plugin, Runnable {
ShopVisuals.updateSign(plugin, event.block, shopData.copy(price = price), event.side)
})
} else {
player.sendMessage(Component.text("Invalid price format. Please enter a valid number.", NamedTextColor.RED))
// Revert lock if invalid? Or keep it unlocked?
// Better to relock to be safe, regardless of valid input or not.
}
// Clean up Map
ShopInputMap.pendingInputs.remove(player.uniqueId)
event.isCancelled = true
event.isCancelled = true // Cancel the actual text update since we handled it virtually
// RELOCK THE SIGN
val signState = event.block.state as? Sign
if (signState != null) {
plugin.server.scheduler.runTask(plugin, Runnable {
signState.isWaxed = true
signState.update()
})
}
} else {
// Normal sign editing protection
@ -246,6 +252,14 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
if (state.persistentDataContainer.has(tierKey, PersistentDataType.INTEGER)) {
// If strictly preventing editing of shop signs unless in setup mode
event.isCancelled = true
val sideStr = if (event.side == Side.FRONT) "FRONT" else "BACK"
val shopData = ShopRepository.findByLocationAndFace(event.block.location, sideStr)
if (shopData != null) {
plugin.server.scheduler.runTask(plugin, Runnable {
ShopVisuals.updateSign(plugin, event.block, shopData, event.side)
})
}
}
}
}
@ -283,15 +297,99 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
if (shopData.actorId == playerEntry.actorId) {
// Is Owner -> Open Edit GUI
openEditGui(event.player, shopData)
// Also show info to owner
val item = shopData.item
val itemName = if (item != null) {
item.type.name.lowercase().split("_")
.joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
} else {
"Not Set"
}
val stock = ShopVisuals.calculateStock(block, item)
val priceStr = net.hareworks.hcu.economy.api.EconomyService.format(shopData.price.toInt())
val amount = item?.amount ?: 0
val ownerName = event.player.name // Use current player name since they are owner
event.player.sendMessage(Component.empty())
event.player.sendMessage(Component.text("Shop Owner: ", NamedTextColor.GRAY).append(Component.text(ownerName, NamedTextColor.WHITE)))
event.player.sendMessage(Component.text("Item: ", NamedTextColor.GRAY).append(Component.text(itemName, NamedTextColor.AQUA)))
event.player.sendMessage(Component.text("Price: ", NamedTextColor.GRAY).append(Component.text("$priceStr for x$amount", NamedTextColor.GREEN)))
event.player.sendMessage(Component.text("Stock: ", NamedTextColor.GRAY).append(Component.text(stock, NamedTextColor.YELLOW)))
event.player.sendMessage(Component.empty())
} else {
// Is Customer -> Buy Logic (TODO)
event.player.sendMessage(Component.text("Customer interactions not implemented yet.", NamedTextColor.GRAY))
// Display Shop Info (Shared Logic)
// We want to show this to everyone, including owner? Or just customers?
// User request implies showing it on click.
// Let's refactor display logic or just copy-paste for safety for now.
val item = shopData.item
val itemName = if (item != null) {
item.type.name.lowercase().split("_")
.joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
} else {
"Not Set"
}
// Calculate Stock
val stock = ShopVisuals.calculateStock(block, item)
val priceStr = net.hareworks.hcu.economy.api.EconomyService.format(shopData.price.toInt())
val amount = item?.amount ?: 0
val ownerName = try {
org.bukkit.Bukkit.getOfflinePlayer(playerEntry.uuid).name ?: playerEntry.uuid.toString()
} catch (e: Exception) {
playerEntry.uuid.toString()
}
event.player.sendMessage(Component.empty())
event.player.sendMessage(Component.text("Shop Owner: ", NamedTextColor.GRAY).append(Component.text(ownerName, NamedTextColor.WHITE)))
event.player.sendMessage(Component.text("Item: ", NamedTextColor.GRAY).append(Component.text(itemName, NamedTextColor.AQUA)))
event.player.sendMessage(Component.text("Price: ", NamedTextColor.GRAY).append(Component.text("$priceStr for x$amount", NamedTextColor.GREEN)))
event.player.sendMessage(Component.text("Stock: ", NamedTextColor.GRAY).append(Component.text(stock, NamedTextColor.YELLOW)))
event.player.sendMessage(Component.empty())
}
} else {
plugin.logger.warning("Shop data missing for location ${block.location} face $face")
}
}
@EventHandler
fun onContainerInteract(event: PlayerInteractEvent) {
if (event.action != Action.RIGHT_CLICK_BLOCK) return
val block = event.clickedBlock ?: return
if (block.type !in ShopVisuals.VALID_CONTAINERS) return
// Check for Shop Sign above
val signBlock = block.getRelative(BlockFace.UP)
if (signBlock.type != Material.OAK_SIGN) return
val state = signBlock.state as? Sign ?: return
if (!state.persistentDataContainer.has(tierKey, PersistentDataType.INTEGER)) return
// It is a shop chest. Check ownership.
val playerIdService = PlayerIdServiceImpl(plugin.logger)
val playerEntry = playerIdService.find(event.player.uniqueId)
// We need to find at least one shop associated with this sign to determine owner
// Since both front and back shops have same owner, just checking FRONT is enough if it exists, or BACK.
var shopData = ShopRepository.findByLocationAndFace(signBlock.location, "FRONT")
if (shopData == null) {
shopData = ShopRepository.findByLocationAndFace(signBlock.location, "BACK")
}
if (shopData != null && playerEntry != null) {
if (shopData.actorId != playerEntry.actorId) {
// Not owner -> Cancel open
event.isCancelled = true
event.player.sendMessage(Component.text("This chest is locked by the shop.", NamedTextColor.RED))
} else {
// Owner -> Allow open (Default behavior)
}
}
}
private fun openEditGui(player: org.bukkit.entity.Player, shopData: net.hareworks.hcu.shop.database.ShopData) {
// 1x5 UI using Hopper
val inventory = org.bukkit.Bukkit.createInventory(player, InventoryType.HOPPER, Component.text("Shop Setup: ${shopData.face}"))
@ -482,9 +580,74 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
ShopInputMap.pendingInputs[player.uniqueId] = shopId
// Open Editor
// UNLOCK SIGN SO PLAYER CAN EDIT
sign.isWaxed = false
sign.update()
player.openSign(sign, side)
}
@EventHandler(ignoreCancelled = true)
fun onBlockBreak(event: org.bukkit.event.block.BlockBreakEvent) {
val block = event.block
// Handle Container Break Protection
if (block.type in ShopVisuals.VALID_CONTAINERS) {
val signBlock = block.getRelative(BlockFace.UP)
if (signBlock.type == Material.OAK_SIGN) {
val state = signBlock.state as? Sign
if (state != null && state.persistentDataContainer.has(tierKey, PersistentDataType.INTEGER)) {
event.isCancelled = true
event.player.sendMessage(Component.text("Please remove the shop sign first.", NamedTextColor.RED))
}
}
return
}
// Handle Shop Sign Break
if (block.type != Material.OAK_SIGN) return
val state = block.state as? Sign ?: return
if (!state.persistentDataContainer.has(tierKey, PersistentDataType.INTEGER)) return
// It is a shop sign.
val playerIdService = PlayerIdServiceImpl(plugin.logger)
val playerEntry = playerIdService.find(event.player.uniqueId)
// Find Shop Data
val location = block.location
val frontShop = ShopRepository.findByLocationAndFace(location, "FRONT")
val backShop = ShopRepository.findByLocationAndFace(location, "BACK")
// Check ownership (Optional: assume if they can break block they can remove shop)
if (playerEntry != null) {
val ownerId = frontShop?.actorId ?: backShop?.actorId
if (ownerId != null && ownerId != playerEntry.actorId) {
if (!event.player.hasPermission("hcu.shop.admin")) {
event.isCancelled = true
event.player.sendMessage(Component.text("You are not the owner of this shop.", NamedTextColor.RED))
return
}
}
}
// Delete Shops
if (frontShop != null) {
ShopRepository.delete(frontShop.id)
ShopVisuals.removeDisplay(location, "FRONT")
}
if (backShop != null) {
ShopRepository.delete(backShop.id)
ShopVisuals.removeDisplay(location, "BACK")
}
event.player.sendMessage(Component.text("Shop removed.", NamedTextColor.GREEN))
}
private fun updateShopDisplay(shopData: net.hareworks.hcu.shop.database.ShopData) {
val world = org.bukkit.Bukkit.getWorld(shopData.worldUid) ?: return
val block = world.getBlockAt(shopData.x, shopData.y, shopData.z)
@ -493,7 +656,7 @@ class ShopListener(private val plugin: ShopPlugin) : Listener {
val data = block.blockData as? Rotatable ?: return
val rotation = data.rotation
val yaw = yawFromBlockFace(rotation) + 90f
val yaw = ShopVisuals.yawFromBlockFace(rotation) + 90f
val offset = if (shopData.face == "FRONT") Vector3f(0.25f, 0.25f, 0.0f) else Vector3f(-0.25f, 0.25f, 0.0f)
val rad = Math.toRadians(yaw.toDouble())