From ac7216788c98dcdd35392e4c9aeb8e04b0b82cb1 Mon Sep 17 00:00:00 2001 From: Hare Date: Tue, 9 Dec 2025 10:54:33 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=A4=9A=E4=BE=9D=E5=AD=98=E7=92=B0?= =?UTF-8?q?=E5=A2=83=E5=90=91=E3=81=91=E3=81=AB=E3=83=93=E3=83=AB=E3=83=89?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 - hcu-core | 2 +- settings.gradle.kts | 8 +- .../hcu/faction/command/FactionCommand.kt | 185 ++++++++++++++---- .../hcu/faction/service/FactionService.kt | 33 +++- 5 files changed, 181 insertions(+), 48 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d82b086..e09015f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,7 +31,6 @@ dependencies { compileOnly("org.postgresql:postgresql:42.7.8") compileOnly("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1") - compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") compileOnly("org.jetbrains.exposed:exposed-core:$exposedVersion") compileOnly("org.jetbrains.exposed:exposed-dao:$exposedVersion") compileOnly("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") diff --git a/hcu-core b/hcu-core index 388d0df..520a378 160000 --- a/hcu-core +++ b/hcu-core @@ -1 +1 @@ -Subproject commit 388d0df943f2b4a0232c1f4f55f1cecb2d372fca +Subproject commit 520a378cd5c3da6321305d07cc0b402b73292add diff --git a/settings.gradle.kts b/settings.gradle.kts index 548fb23..22c9f20 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,8 @@ rootProject.name = "faction" -includeBuild("hcu-core") -includeBuild("hcu-core/kommand-lib") -includeBuild("hcu-core/kommand-lib/permits-lib") +if (gradle.parent == null) { + includeBuild("hcu-core") + includeBuild("hcu-core/kommand-lib") + includeBuild("hcu-core/kommand-lib/permits-lib") +} diff --git a/src/main/kotlin/net/hareworks/hcu/faction/command/FactionCommand.kt b/src/main/kotlin/net/hareworks/hcu/faction/command/FactionCommand.kt index 9cb7814..34249a0 100644 --- a/src/main/kotlin/net/hareworks/hcu/faction/command/FactionCommand.kt +++ b/src/main/kotlin/net/hareworks/hcu/faction/command/FactionCommand.kt @@ -14,6 +14,7 @@ import org.bukkit.plugin.java.JavaPlugin import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor import org.bukkit.entity.Player +import net.hareworks.hcu.faction.service.FactionInfo class FactionCommand(private val plugin: JavaPlugin, private val service: FactionService) { @@ -21,6 +22,40 @@ class FactionCommand(private val plugin: JavaPlugin, private val service: Factio kommand(plugin) { command("faction") { description = "Manage your faction" + + executes { + val sender = sender + if (sender !is Player) return@executes + + val factionId = service.getFactionOfPlayer(sender.uniqueId) + if (factionId == null) { + sender.sendMessage(Component.text("You are not in a faction.", NamedTextColor.RED)) + return@executes + } + + val factionName = service.getFactionName(factionId) + if (factionName != null) { + displayFactionInfo(sender, factionName, 1) + } + } + + literal("info") { + string("name") { + executes { + val sender = sender + val name = argument("name") + displayFactionInfo(sender, name, 1) + } + integer("page") { + executes { + val sender = sender + val name = argument("name") + val page = argument("page") + displayFactionInfo(sender, name, page) + } + } + } + } literal("create") { string("name") { @@ -44,12 +79,13 @@ class FactionCommand(private val plugin: JavaPlugin, private val service: Factio } literal("invite") { - player("player") { + string("player") { executes { val sender = sender if (sender !is Player) return@executes - val target = argument("player") + val targetName = argument("player") + val target = plugin.server.getOfflinePlayer(targetName) val factionId = service.getFactionOfPlayer(sender.uniqueId) if (factionId == null) { @@ -97,8 +133,6 @@ class FactionCommand(private val plugin: JavaPlugin, private val service: Factio } } - // Remove join command - literal("accept") { executes { val sender = sender @@ -124,30 +158,20 @@ class FactionCommand(private val plugin: JavaPlugin, private val service: Factio if (sender !is Player) return@executes val targetName = argument("target") - // Check if target is a Faction Name (Accepting Invite) val factionIdByName = service.getFactionIdByName(targetName) - // Check if target is a Player Name (Accepting Join Request - Leader) val factionIdOfSender = service.getFactionOfPlayer(sender.uniqueId) - var processedAsRequest = false if (factionIdOfSender != null) { val role = service.getRole(factionIdOfSender, sender.uniqueId) if (role != FactionRole.MEMBER && role != null) { val targetPlayer = plugin.server.getOfflinePlayer(targetName) - // Try request accept - // Use a simplified check or just try calling the service. - // Service returns error if no request found. - // But if we have ambiguity (Faction name == Player name), what to do? - // We prioritize Request if Sender is Leader? Or Invite? - // Let's try Request first if Leader. - + service.acceptRequest(factionIdOfSender, targetPlayer.uniqueId) .onSuccess { sender.sendMessage(Component.text("Accepted join request from $targetName", NamedTextColor.GREEN)) return@executes } - // If failed (e.g. no request), proceed to check if it's a Faction Invite for ME. } } @@ -157,7 +181,6 @@ class FactionCommand(private val plugin: JavaPlugin, private val service: Factio sender.sendMessage(Component.text("You joined $targetName", NamedTextColor.GREEN)) } .onFailure { err -> - // If we also failed request above, user might be confused. sender.sendMessage(Component.text("Error: $err (or no request found from player)", NamedTextColor.RED)) } return@executes @@ -205,8 +228,9 @@ class FactionCommand(private val plugin: JavaPlugin, private val service: Factio } } } + literal("promote") { - player("player") { + string("player") { executes { val sender = sender if (sender !is Player) return@executes @@ -222,7 +246,8 @@ class FactionCommand(private val plugin: JavaPlugin, private val service: Factio return@executes } - val target = argument("player") + val targetName = argument("player") + val target = plugin.server.getOfflinePlayer(targetName) service.promote(factionId, target.uniqueId) .onSuccess { sender.sendMessage(Component.text("Promoted ${target.name}", NamedTextColor.GREEN)) } @@ -232,7 +257,7 @@ class FactionCommand(private val plugin: JavaPlugin, private val service: Factio } literal("demote") { - player("player") { + string("player") { executes { val sender = sender if (sender !is Player) return@executes @@ -248,7 +273,8 @@ class FactionCommand(private val plugin: JavaPlugin, private val service: Factio return@executes } - val target = argument("player") + val targetName = argument("player") + val target = plugin.server.getOfflinePlayer(targetName) service.demote(factionId, target.uniqueId) .onSuccess { sender.sendMessage(Component.text("Demoted ${target.name}", NamedTextColor.GREEN)) } @@ -326,11 +352,12 @@ class FactionCommand(private val plugin: JavaPlugin, private val service: Factio } literal("kick") { - player("player") { + string("player") { executes { val sender = sender if (sender !is Player) return@executes - val target = argument("player") + val targetName = argument("player") + val target = plugin.server.getOfflinePlayer(targetName) service.kickPlayer(sender.uniqueId, target.uniqueId) .onSuccess { sender.sendMessage(Component.text("Kicked ${target.name}", NamedTextColor.GREEN)) } @@ -353,26 +380,102 @@ class FactionCommand(private val plugin: JavaPlugin, private val service: Factio } } } - - literal("info") { - string("name") { - executes { - val name = argument("name") - val info = service.getFactionInfo(name) - if (info == null) { - sender.sendMessage(Component.text("Faction not found", NamedTextColor.RED)) - } else { - sender.sendMessage(Component.text("Faction: ${info.name}", NamedTextColor.GOLD)) - sender.sendMessage(Component.text("Tag: ${info.tag ?: "None"}", NamedTextColor.WHITE)) - sender.sendMessage(Component.text("Members: ${info.memberCount}", NamedTextColor.WHITE)) - sender.sendMessage(Component.text("Open: ${info.open}", NamedTextColor.WHITE)) - } - } - } - } } } } + + private fun displayFactionInfo(sender: org.bukkit.command.CommandSender, factionName: String, page: Int) { + if (page < 1) { + sender.sendMessage(Component.text("Page must be 1 or higher.", NamedTextColor.RED)) + return + } + val info = service.getFactionInfo(factionName) + if (info == null) { + sender.sendMessage(Component.text("Faction not found.", NamedTextColor.RED)) + return + } + + sender.sendMessage(Component.text("----- ${info.name} -----", NamedTextColor.GOLD)) + sender.sendMessage(Component.text("Tag: ", NamedTextColor.GRAY).append(Component.text(info.tag ?: "None", NamedTextColor.WHITE))) + sender.sendMessage(Component.text("Owner: ", NamedTextColor.GRAY).append(Component.text(info.ownerName ?: "Unknown", NamedTextColor.WHITE))) + sender.sendMessage(Component.text("Members (${info.memberCount}):", NamedTextColor.GRAY)) + + val pageSize = 10 + val members = service.getFactionMembers(info.id, page, pageSize) + + if (members.isEmpty() && page > 1) { + sender.sendMessage(Component.text("No members on this page.", NamedTextColor.YELLOW)) + } + + val viewerId = (sender as? Player)?.uniqueId + val viewerFactionId = if (viewerId != null) service.getFactionOfPlayer(viewerId) else null + val viewerRole = if (viewerId != null && viewerFactionId == info.id) service.getRole(info.id, viewerId) else null + val isSameFaction = viewerFactionId == info.id + + members.forEach { (uuid, name, role) -> + val roleColor = when (role) { + FactionRole.OWNER -> NamedTextColor.RED + FactionRole.EXEC -> NamedTextColor.GOLD + FactionRole.MEMBER -> NamedTextColor.GREEN + else -> NamedTextColor.WHITE + } + + var line = Component.text("- ", NamedTextColor.DARK_GRAY) + .append(Component.text("[$role] ", roleColor)) + .append(Component.text(name, NamedTextColor.WHITE)) + + if (isSameFaction && viewerRole != null && uuid != viewerId) { + // Add management buttons based on role + if (viewerRole == FactionRole.OWNER) { + // Owner can kick anyone (except self, handled by uuid check), promote member/demote exec, transfer + val kickBtn = Component.text(" [Kick]", NamedTextColor.RED) + .clickEvent(net.kyori.adventure.text.event.ClickEvent.runCommand("/faction kick $name")) + .hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Kick $name"))) + line = line.append(kickBtn) + + if (role == FactionRole.MEMBER) { + val promoteBtn = Component.text(" [Promote]", NamedTextColor.AQUA) + .clickEvent(net.kyori.adventure.text.event.ClickEvent.runCommand("/faction promote $name")) + .hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Promote to Exec"))) + line = line.append(promoteBtn) + } + + if (role == FactionRole.EXEC) { + val demoteBtn = Component.text(" [Demote]", NamedTextColor.YELLOW) + .clickEvent(net.kyori.adventure.text.event.ClickEvent.runCommand("/faction demote $name")) + .hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Demote to Member"))) + line = line.append(demoteBtn) + } + } else if (viewerRole == FactionRole.EXEC) { + // Exec can kick member + if (role == FactionRole.MEMBER) { + val kickBtn = Component.text(" [Kick]", NamedTextColor.RED) + .clickEvent(net.kyori.adventure.text.event.ClickEvent.runCommand("/faction kick $name")) + .hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Kick $name"))) + line = line.append(kickBtn) + } + } + } + sender.sendMessage(line) + } + + // Pagination footer + val totalPages = (info.memberCount + pageSize - 1) / pageSize + if (totalPages > 1) { + val prevPage = if (page > 1) page - 1 else 1 + val nextPage = if (page < totalPages) page + 1 else totalPages + + val nav = Component.text("Page $page/$totalPages ", NamedTextColor.GRAY) + if (page > 1) { + nav.append(Component.text("[<]", NamedTextColor.AQUA) + .clickEvent(net.kyori.adventure.text.event.ClickEvent.runCommand("/faction info ${info.name} $prevPage"))) + } + nav.append(Component.text(" ")) + if (page < totalPages) { + nav.append(Component.text("[>]", NamedTextColor.AQUA) + .clickEvent(net.kyori.adventure.text.event.ClickEvent.runCommand("/faction info ${info.name} $nextPage"))) + } + sender.sendMessage(nav) + } + } } - - diff --git a/src/main/kotlin/net/hareworks/hcu/faction/service/FactionService.kt b/src/main/kotlin/net/hareworks/hcu/faction/service/FactionService.kt index 2c613bd..e49594f 100644 --- a/src/main/kotlin/net/hareworks/hcu/faction/service/FactionService.kt +++ b/src/main/kotlin/net/hareworks/hcu/faction/service/FactionService.kt @@ -379,6 +379,24 @@ class FactionService { } } + fun getFactionMembers(factionId: Int, page: Int, pageSize: Int = 10): List> { + return DatabaseSessionManager.transaction { + (FactionMembersTable innerJoin PlayersTable) + .selectAll() + .andWhere { FactionMembersTable.factionId eq factionId } + .sortedBy { it[FactionMembersTable.role] } // Sort in-memory to avoid import issues + .drop((page - 1) * pageSize) + .take(pageSize) + .map { + Triple( + it[PlayersTable.uuid], + it[PlayersTable.username], + it[FactionMembersTable.role] + ) + } + } + } + fun getFactionInfo(name: String): FactionInfo? { return DatabaseSessionManager.transaction { val row = FactionsTable.selectAll().andWhere { FactionsTable.name eq name }.singleOrNull() ?: return@transaction null @@ -389,24 +407,35 @@ class FactionService { .andWhere { FactionMembersTable.role eq FactionRole.OWNER } .singleOrNull()?.get(FactionMembersTable.playerUuid) + // Get owner name + val ownerName = ownerUuid?.let { uuid -> + PlayersTable.selectAll().andWhere { PlayersTable.uuid eq uuid }.singleOrNull()?.get(PlayersTable.username) + } + FactionInfo( + id, row[FactionsTable.name], row[FactionsTable.tag], row[FactionsTable.color], + row[FactionsTable.description], row[FactionsTable.open], count, - ownerUuid + ownerUuid, + ownerName ) } } } data class FactionInfo( + val id: Int, val name: String, val tag: String?, val color: String?, + val description: String?, val open: Boolean, val memberCount: Long, - val owner: UUID? + val owner: UUID?, + val ownerName: String? )