From 47bfcfc1e4e3335767b1fd9fb51b5b68d0e23c2b Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 20 Dec 2025 01:26:09 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A1=A8=E7=A4=BA=E5=AE=89=E5=AE=9A?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/hareworks/hcu/shop/ShopVisuals.kt | 70 ++++- .../hcu/shop/command/ShopCommands.kt | 117 ++++++-- .../hcu/shop/listeners/ShopListener.kt | 257 ++++++++++++++---- 3 files changed, 372 insertions(+), 72 deletions(-) diff --git a/src/main/kotlin/net/hareworks/hcu/shop/ShopVisuals.kt b/src/main/kotlin/net/hareworks/hcu/shop/ShopVisuals.kt index e153ba0..a2eca69 100644 --- a/src/main/kotlin/net/hareworks/hcu/shop/ShopVisuals.kt +++ b/src/main/kotlin/net/hareworks/hcu/shop/ShopVisuals.kt @@ -51,13 +51,67 @@ object ShopVisuals { signSide.line(2, Component.empty()) signSide.line(3, Component.text(ownerName, NamedTextColor.GRAY)) - // 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 chestBlock = block.getRelative(org.bukkit.block.BlockFace.DOWN) + if (chestBlock.type != org.bukkit.Material.CHEST) return 0 + + val chest = chestBlock.state as? org.bukkit.block.Chest ?: return 0 + val inventory = chest.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().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 + } } } diff --git a/src/main/kotlin/net/hareworks/hcu/shop/command/ShopCommands.kt b/src/main/kotlin/net/hareworks/hcu/shop/command/ShopCommands.kt index bc710a0..e0d75ce 100644 --- a/src/main/kotlin/net/hareworks/hcu/shop/command/ShopCommands.kt +++ b/src/main/kotlin/net/hareworks/hcu/shop/command/ShopCommands.kt @@ -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)) } } diff --git a/src/main/kotlin/net/hareworks/hcu/shop/listeners/ShopListener.kt b/src/main/kotlin/net/hareworks/hcu/shop/listeners/ShopListener.kt index 457098d..a318aad 100644 --- a/src/main/kotlin/net/hareworks/hcu/shop/listeners/ShopListener.kt +++ b/src/main/kotlin/net/hareworks/hcu/shop/listeners/ShopListener.kt @@ -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 @@ -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 onChestInteract(event: PlayerInteractEvent) { + if (event.action != Action.RIGHT_CLICK_BLOCK) return + val block = event.clickedBlock ?: return + if (block.type != Material.CHEST) 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 Chest Break Protection + if (block.type == Material.CHEST) { + 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())