feat: ファクション所有とドラフト・アクティベート

This commit is contained in:
Keisuke Hirata 2025-12-20 02:02:28 +09:00
parent 2621d28e67
commit 767f974228
15 changed files with 733 additions and 121 deletions

2
Lands

@ -1 +1 @@
Subproject commit d27057dc89e228ce120c5b5b4534b3c130e8480a
Subproject commit 0f432b0e89614bc07bd710423284da5e0228e25a

View File

@ -46,7 +46,14 @@ class LandSectorPlugin : JavaPlugin() {
return
}
val service = SectorService(db)
val landsPlugin = server.pluginManager.getPlugin("lands") as? net.hareworks.hcu.lands.App
val landService = landsPlugin?.landService
if (landService == null) {
logger.severe("Lands plugin or service not ready.")
return
}
val service = SectorService(db, landService)
try {
service.init()
} catch (e: Exception) {
@ -63,7 +70,8 @@ class LandSectorPlugin : JavaPlugin() {
val selService = selectionService
if (selService != null) {
server.pluginManager.registerEvents(SectorListener(this, service, pIdService, selService), this)
val factionService = net.hareworks.hcu.faction.service.FactionService()
server.pluginManager.registerEvents(SectorListener(this, service, pIdService, selService, factionService), this)
server.pluginManager.registerEvents(net.hareworks.hcu.landsector.listener.SelectionListener(this, selService, service), this)
// Schedule visualization task

View File

@ -36,8 +36,13 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
integer("sectorId") {
executes {
val id = argument<Int>("sectorId")
sender.sendMessage(Component.text("Activation for Sector #$id pending implementation.", NamedTextColor.YELLOW))
// TODO: Validate selection and lock in
val service = landSectorPlugin.sectorService ?: return@executes
if (service.activateSector(id)) {
sender.sendMessage(Component.text("Sector #$id activated and land secured!", NamedTextColor.GREEN))
} else {
sender.sendMessage(Component.text("Failed to activate sector #$id (Already active or empty parts?).", NamedTextColor.RED))
}
}
}
}
@ -118,6 +123,45 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
}
}
literal("transfer") {
integer("sectorId") {
integer("targetActorId") {
executes {
val sectorId = argument<Int>("sectorId")
val targetActorId = argument<Int>("targetActorId")
val player = sender as? Player ?: return@executes
val service = landSectorPlugin.sectorService ?: return@executes
if (service.transferSector(sectorId, targetActorId)) {
sender.sendMessage(Component.text("Transfer successful!", NamedTextColor.GREEN))
} else {
sender.sendMessage(Component.text("Transfer failed.", NamedTextColor.RED))
}
}
}
}
}
literal("range") {
literal("delete") {
integer("sectorId") {
integer("rangeIndex") {
executes {
val sectorId = argument<Int>("sectorId")
val rangeIndex = argument<Int>("rangeIndex")
val service = landSectorPlugin.sectorService ?: return@executes
if (service.deleteRange(sectorId, rangeIndex)) {
sender.sendMessage(Component.text("Range #$rangeIndex in Sector #$sectorId deleted.", NamedTextColor.GREEN))
} else {
sender.sendMessage(Component.text("Failed to delete range. (Invalid index or sector already confirmed?)", NamedTextColor.RED))
}
}
}
}
}
}
literal("delete") {
integer("id") {
executes {

View File

@ -0,0 +1,16 @@
package net.hareworks.hcu.landsector.database
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import net.hareworks.hcu.lands.model.LandData
import org.jetbrains.exposed.v1.core.Table
object SectorDraftsTable : Table("sector_drafts") {
val sectorId = integer("sector_id").references(SectorsTable.id)
val data = text("data").transform(
{ json -> Json.decodeFromString(LandData.serializer(), json) },
{ value -> Json.encodeToString(LandData.serializer(), value) }
)
override val primaryKey = PrimaryKey(sectorId)
}

View File

@ -11,20 +11,7 @@ object SectorsTable : Table("land_sectors") {
val z = integer("z")
val hp = integer("hp").default(1000)
override val primaryKey = PrimaryKey(id)
}
object SectorRangesTable : Table("land_sector_ranges") {
val id = integer("id").autoIncrement()
val sectorId = integer("sector_id").references(SectorsTable.id)
val type = varchar("type", 16)
val x1 = integer("x1")
val y1 = integer("y1")
val z1 = integer("z1")
val x2 = integer("x2")
val y2 = integer("y2")
val z2 = integer("z2")
val isSneaking = bool("is_sneaking")
val landId = integer("land_id").nullable()
override val primaryKey = PrimaryKey(id)
}

View File

@ -31,11 +31,15 @@ import net.hareworks.hcu.landsector.service.SelectionService
import net.kyori.adventure.text.event.ClickEvent
import org.bukkit.event.player.PlayerInteractEntityEvent
import net.hareworks.hcu.faction.service.FactionService
import net.hareworks.hcu.faction.database.schema.FactionRole
class SectorListener(
private val plugin: LandSectorPlugin,
private val sectorService: SectorService,
private val playerIdService: PlayerIdService,
private val selectionService: SelectionService
private val selectionService: SelectionService,
private val factionService: FactionService
) : Listener {
@EventHandler
@ -216,7 +220,33 @@ class SectorListener(
if (pEntry == null) return
val sector = sectorService.getSector(sectorId) ?: return
if (sector.ownerActorId != pEntry.actorId) {
// Resolve Owner Name
var ownerName = "Unknown"
// Try getting faction name
val factionName = factionService.getFactionName(sector.ownerActorId)
if (factionName != null) {
ownerName = "Faction: $factionName"
} else {
// Assume player. Ideally resolve UUID from ActorID, but for now ID or fallback
// We don't have direct Player Actor ID -> Name cache here easily without querying DB or UUID service
// Just show ID for now to be safe, or "Player #ID"
ownerName = "Player #${sector.ownerActorId}"
}
val canManage = if (sector.ownerActorId == pEntry.actorId) {
true
} else {
// Or if owner is my faction and I am EXEC/OWNER
val myFaction = factionService.getFactionOfPlayer(player.uniqueId)
if (myFaction == sector.ownerActorId) {
val myRole = factionService.getRole(myFaction, player.uniqueId)
myRole == FactionRole.OWNER || myRole == FactionRole.EXEC
} else {
false
}
}
if (!canManage) {
player.sendMessage(Component.text("You are not the owner of this sector.", NamedTextColor.RED))
return
}
@ -230,7 +260,21 @@ class SectorListener(
// Build Page Content
val content = Component.text()
.append(Component.text("Sector Core @ ${sector.x},${sector.y},${sector.z}\n\n", NamedTextColor.BLACK))
.append(Component.text("Sector Core @ ${sector.x},${sector.y},${sector.z}\n", NamedTextColor.BLACK))
.append(Component.text("Owner: $ownerName\n\n", NamedTextColor.DARK_GRAY))
// Transfer Check
val myFaction = factionService.getFactionOfPlayer(player.uniqueId)
if (myFaction != null && sector.ownerActorId != myFaction) {
val myRole = factionService.getRole(myFaction, player.uniqueId)
if (myRole == FactionRole.OWNER || myRole == FactionRole.EXEC) {
content.append(
Component.text("[Transfer to Faction]\n\n", NamedTextColor.GOLD)
.clickEvent(ClickEvent.runCommand("/landsector transfer $sectorId $myFaction"))
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to transfer ownership to your faction")))
)
}
}
// Actions Row 1
content.append(
@ -260,6 +304,12 @@ class SectorListener(
content.append(Component.text("Parts:", NamedTextColor.BLACK))
ranges.forEach { range ->
content.append(Component.text("\n- [${range.id}] ${range.type}", NamedTextColor.DARK_GRAY))
content.append(Component.text(" "))
content.append(
Component.text("[x]", NamedTextColor.RED)
.clickEvent(ClickEvent.runCommand("/landsector range delete $sectorId ${range.id}"))
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to delete range")))
)
}
}

View File

@ -7,5 +7,6 @@ data class Sector(
val x: Int,
val y: Int,
val z: Int,
val hp: Int
val hp: Int,
val landId: Int? = null
)

View File

@ -1,7 +1,8 @@
package net.hareworks.hcu.landsector.service
import net.hareworks.hcu.landsector.database.SectorDraftsTable
import net.hareworks.hcu.landsector.database.SectorsTable
import net.hareworks.hcu.landsector.database.SectorRangesTable
// SectorRangesTable removed
import net.hareworks.hcu.landsector.model.Sector
import net.hareworks.hcu.landsector.model.SectorRange
import net.hareworks.hcu.landsector.model.SelectionMode
@ -17,15 +18,21 @@ import org.jetbrains.exposed.v1.jdbc.deleteWhere
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import java.util.concurrent.ConcurrentHashMap
class SectorService(private val database: Database) {
// ... class definition ...
class SectorService(private val database: Database, private val landService: net.hareworks.hcu.lands.service.LandService) {
private val hpCache = ConcurrentHashMap<Int, Int>()
private val dirtySet = ConcurrentHashMap.newKeySet<Int>()
private val sectorsCache = ConcurrentHashMap<Int, Sector>()
// Draft storage: SectorId -> Land (Draft)
private val draftLands = ConcurrentHashMap<Int, net.hareworks.hcu.lands.model.Land>()
fun init() {
transaction(database) {
SchemaUtils.createMissingTablesAndColumns(SectorsTable, SectorRangesTable)
SchemaUtils.createMissingTablesAndColumns(SectorsTable, SectorDraftsTable)
// Load Sectors
SectorsTable.selectAll().forEach {
val id = it[SectorsTable.id]
val sector = Sector(
@ -35,10 +42,28 @@ class SectorService(private val database: Database) {
it[SectorsTable.x],
it[SectorsTable.y],
it[SectorsTable.z],
it[SectorsTable.hp]
it[SectorsTable.hp],
it[SectorsTable.landId]
)
sectorsCache[id] = sector
}
// Load Drafts
SectorDraftsTable.selectAll().forEach {
val sId = it[SectorDraftsTable.sectorId]
val data = it[SectorDraftsTable.data]
val sector = sectorsCache[sId]
if (sector != null && sector.landId == null) {
draftLands[sId] = net.hareworks.hcu.lands.model.Land(
id = -1,
name = "Draft Sector $sId",
actorId = sector.ownerActorId,
world = sector.world,
data = data
)
}
}
}
}
@ -51,11 +76,29 @@ class SectorService(private val database: Database) {
it[SectorsTable.y] = y
it[SectorsTable.z] = z
it[SectorsTable.hp] = 1000
it[SectorsTable.landId] = null
}[SectorsTable.id]
hpCache[id] = 1000
val sector = Sector(id, ownerActorId, world, x, y, z, 1000)
val sector = Sector(id, ownerActorId, world, x, y, z, 1000, null)
sectorsCache[id] = sector
// Initialize draft land
val draftLand = net.hareworks.hcu.lands.model.Land(
id = -1, // Dummy ID
name = "Draft Sector $id",
actorId = ownerActorId,
world = world,
data = net.hareworks.hcu.lands.model.LandData()
)
draftLands[id] = draftLand
// Persist draft
SectorDraftsTable.insert {
it[SectorDraftsTable.sectorId] = id
it[SectorDraftsTable.data] = draftLand.data
}
sector
}
}
@ -77,7 +120,8 @@ class SectorService(private val database: Database) {
it[SectorsTable.x],
it[SectorsTable.y],
it[SectorsTable.z],
it[SectorsTable.hp]
it[SectorsTable.hp],
it[SectorsTable.landId]
)
}
}
@ -138,7 +182,7 @@ class SectorService(private val database: Database) {
val sector = transaction(database) {
val record = SectorsTable.selectAll().andWhere { SectorsTable.id eq id }.singleOrNull() ?: return@transaction null
SectorRangesTable.deleteWhere { SectorRangesTable.sectorId eq id }
SectorDraftsTable.deleteWhere { SectorDraftsTable.sectorId eq id }
SectorsTable.deleteWhere { SectorsTable.id eq id }
Sector(
@ -148,7 +192,8 @@ class SectorService(private val database: Database) {
record[SectorsTable.x],
record[SectorsTable.y],
record[SectorsTable.z],
record[SectorsTable.hp]
record[SectorsTable.hp],
record[SectorsTable.landId]
)
}
@ -156,6 +201,11 @@ class SectorService(private val database: Database) {
hpCache.remove(id)
dirtySet.remove(id)
sectorsCache.remove(id)
draftLands.remove(id)
if (sector.landId != null) {
landService.deleteLand(sector.landId)
}
}
return sector
@ -203,7 +253,8 @@ class SectorService(private val database: Database) {
it[SectorsTable.x],
it[SectorsTable.y],
it[SectorsTable.z],
hp
hp,
it[SectorsTable.landId]
)
}
}
@ -226,46 +277,195 @@ class SectorService(private val database: Database) {
it[SectorsTable.x],
it[SectorsTable.y],
it[SectorsTable.z],
hp
hp,
it[SectorsTable.landId]
)
}
}
}
fun addRange(sectorId: Int, mode: SelectionMode, x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int, isSneaking: Boolean): SectorRange {
return transaction(database) {
val id = SectorRangesTable.insert {
it[SectorRangesTable.sectorId] = sectorId
it[SectorRangesTable.type] = mode.name
it[SectorRangesTable.x1] = x1
it[SectorRangesTable.y1] = y1
it[SectorRangesTable.z1] = z1
it[SectorRangesTable.x2] = x2
it[SectorRangesTable.y2] = y2
it[SectorRangesTable.z2] = z2
it[SectorRangesTable.isSneaking] = isSneaking
}[SectorRangesTable.id]
SectorRange(id, sectorId, mode, x1, y1, z1, x2, y2, z2, isSneaking)
fun addRange(sectorId: Int, mode: SelectionMode, x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int, isSneaking: Boolean): Any? {
val sector = getSector(sectorId) ?: return null
// Cannot edit if already confirmed
if (sector.landId != null) return null
val draft = draftLands.computeIfAbsent(sectorId) {
net.hareworks.hcu.lands.model.Land(
id = -1,
name = "Draft Sector $sectorId",
actorId = sector.ownerActorId,
world = sector.world,
data = net.hareworks.hcu.lands.model.LandData()
)
}
val shape = when (mode) {
SelectionMode.CUBOID -> {
net.hareworks.hcu.lands.model.Shape.Cuboid(x1, y1, z1, x2, y2, z2)
}
SelectionMode.CYLINDER -> {
// Convert bounds to cylinder params
// Approximating from bounding box
val minX = minOf(x1, x2)
val maxX = maxOf(x1, x2)
val minZ = minOf(z1, z2)
val maxZ = maxOf(z1, z2)
val minY = minOf(y1, y2)
val maxY = maxOf(y1, y2)
val cx = (minX + maxX) / 2
val cz = (minZ + maxZ) / 2
val cy = (minY + maxY) / 2
// Radius is max dist from center to edge of bounding box in X or Z
val radiusX = (maxX - minX) / 2.0
val radiusZ = (maxZ - minZ) / 2.0
val radius = maxOf(radiusX, radiusZ)
val height = maxY - minY + 1
val bottom = (cy - minY)
val top = (maxY - cy)
net.hareworks.hcu.lands.model.Shape.Cylinder(cx, cy, cz, radius, bottom, top)
}
else -> return null
}
// Use LandService modify pattern or direct replacement since stored in local map
val newParts = draft.data.parts.toMutableList()
newParts.add(shape)
val newDraft = draft.copy(data = draft.data.copy(parts = newParts))
draftLands[sectorId] = newDraft
// Persist change
transaction(database) {
// Check if exists first? Or upsert if supported easily.
// We know it exists from creation, but safety check.
val count = SectorDraftsTable.update({ SectorDraftsTable.sectorId eq sectorId }) {
it[SectorDraftsTable.data] = newDraft.data
}
if (count == 0) {
SectorDraftsTable.insert {
it[SectorDraftsTable.sectorId] = sectorId
it[SectorDraftsTable.data] = newDraft.data
}
}
}
return shape
}
fun getRanges(sectorId: Int): List<SectorRange> {
val sector = getSector(sectorId) ?: return emptyList()
val land = if (sector.landId != null) {
landService.getLand(sector.landId)
} else {
draftLands[sectorId]
} ?: return emptyList()
// Convert Shapes back to SectorRanges for UI compatibility
return land.data.parts.mapIndexed { index, shape ->
when (shape) {
is net.hareworks.hcu.lands.model.Shape.Cuboid -> {
SectorRange(
index, // Use index as ID for UI
sectorId,
SelectionMode.CUBOID,
shape.x1, shape.y1, shape.z1,
shape.x2, shape.y2, shape.z2,
false
)
}
is net.hareworks.hcu.lands.model.Shape.Cylinder -> {
SectorRange(
index,
sectorId,
SelectionMode.CYLINDER,
shape.x, shape.y, shape.z,
shape.x, shape.y, shape.z,
false
)
}
// Default fallback for unknown shapes if any (shouldn't happen with current limited types)
else -> SectorRange(index, sectorId, SelectionMode.CUBOID, 0,0,0,0,0,0, false)
}
}
}
fun deleteRange(sectorId: Int, rangeIndex: Int): Boolean {
val sector = getSector(sectorId) ?: return false
if (sector.landId != null) return false // Cannot edit confirmed
val draft = draftLands[sectorId] ?: return false
if (rangeIndex < 0 || rangeIndex >= draft.data.parts.size) return false
val newParts = draft.data.parts.toMutableList()
newParts.removeAt(rangeIndex)
val newDraft = draft.copy(data = draft.data.copy(parts = newParts))
draftLands[sectorId] = newDraft
// Persist change
transaction(database) {
SectorDraftsTable.update({ SectorDraftsTable.sectorId eq sectorId }) {
it[SectorDraftsTable.data] = newDraft.data
}
}
return true
}
fun activateSector(sectorId: Int): Boolean {
val sector = getSector(sectorId) ?: return false
if (sector.landId != null) return false // Already active
val draft = draftLands[sectorId] ?: return false
if (draft.data.parts.isEmpty()) return false
val landName = "Sector_${sectorId}_${System.currentTimeMillis()}"
if (landService.createLand(landName, draft.actorId, draft.world)) {
// Find the created land to get ID
val lands = landService.findLandsByName(landName)
val createdLand = lands.firstOrNull() ?: return false
// Update parts
landService.modifyLand(createdLand.id) { data ->
data.copy(parts = draft.data.parts)
}
// Update Sector DB & Delete Draft
transaction(database) {
SectorsTable.update({ SectorsTable.id eq sectorId }) {
it[SectorsTable.landId] = createdLand.id
}
SectorDraftsTable.deleteWhere { SectorDraftsTable.sectorId eq sectorId }
}
// Update cache & Clear draft
sectorsCache[sectorId] = sector.copy(landId = createdLand.id)
draftLands.remove(sectorId)
return true
}
return false
}
fun transferSector(id: Int, newOwnerActorId: Int): Boolean {
return transaction(database) {
SectorRangesTable.selectAll().andWhere { SectorRangesTable.sectorId eq sectorId }.map {
SectorRange(
it[SectorRangesTable.id],
it[SectorRangesTable.sectorId],
SelectionMode.valueOf(it[SectorRangesTable.type]),
it[SectorRangesTable.x1],
it[SectorRangesTable.y1],
it[SectorRangesTable.z1],
it[SectorRangesTable.x2],
it[SectorRangesTable.y2],
it[SectorRangesTable.z2],
it[SectorRangesTable.isSneaking]
)
val updated = SectorsTable.update({ SectorsTable.id eq id }) {
it[ownerActorId] = newOwnerActorId
}
if (updated > 0) {
// Update cache
val current = sectorsCache[id]
if (current != null) {
sectorsCache[id] = current.copy(ownerActorId = newOwnerActorId)
}
true
} else {
false
}
}
}

View File

@ -46,7 +46,14 @@ class LandSectorPlugin : JavaPlugin() {
return
}
val service = SectorService(db)
val landsPlugin = server.pluginManager.getPlugin("lands") as? net.hareworks.hcu.lands.App
val landService = landsPlugin?.landService
if (landService == null) {
logger.severe("Lands plugin or service not ready.")
return
}
val service = SectorService(db, landService)
try {
service.init()
} catch (e: Exception) {
@ -63,7 +70,8 @@ class LandSectorPlugin : JavaPlugin() {
val selService = selectionService
if (selService != null) {
server.pluginManager.registerEvents(SectorListener(this, service, pIdService, selService), this)
val factionService = net.hareworks.hcu.faction.service.FactionService()
server.pluginManager.registerEvents(SectorListener(this, service, pIdService, selService, factionService), this)
server.pluginManager.registerEvents(net.hareworks.hcu.landsector.listener.SelectionListener(this, selService, service), this)
// Schedule visualization task

View File

@ -36,8 +36,13 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
integer("sectorId") {
executes {
val id = argument<Int>("sectorId")
sender.sendMessage(Component.text("Activation for Sector #$id pending implementation.", NamedTextColor.YELLOW))
// TODO: Validate selection and lock in
val service = landSectorPlugin.sectorService ?: return@executes
if (service.activateSector(id)) {
sender.sendMessage(Component.text("Sector #$id activated and land secured!", NamedTextColor.GREEN))
} else {
sender.sendMessage(Component.text("Failed to activate sector #$id (Already active or empty parts?).", NamedTextColor.RED))
}
}
}
}
@ -118,6 +123,45 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
}
}
literal("transfer") {
integer("sectorId") {
integer("targetActorId") {
executes {
val sectorId = argument<Int>("sectorId")
val targetActorId = argument<Int>("targetActorId")
val player = sender as? Player ?: return@executes
val service = landSectorPlugin.sectorService ?: return@executes
if (service.transferSector(sectorId, targetActorId)) {
sender.sendMessage(Component.text("Transfer successful!", NamedTextColor.GREEN))
} else {
sender.sendMessage(Component.text("Transfer failed.", NamedTextColor.RED))
}
}
}
}
}
literal("range") {
literal("delete") {
integer("sectorId") {
integer("rangeIndex") {
executes {
val sectorId = argument<Int>("sectorId")
val rangeIndex = argument<Int>("rangeIndex")
val service = landSectorPlugin.sectorService ?: return@executes
if (service.deleteRange(sectorId, rangeIndex)) {
sender.sendMessage(Component.text("Range #$rangeIndex in Sector #$sectorId deleted.", NamedTextColor.GREEN))
} else {
sender.sendMessage(Component.text("Failed to delete range. (Invalid index or sector already confirmed?)", NamedTextColor.RED))
}
}
}
}
}
}
literal("delete") {
integer("id") {
executes {

View File

@ -0,0 +1,16 @@
package net.hareworks.hcu.landsector.database
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import net.hareworks.hcu.lands.model.LandData
import org.jetbrains.exposed.v1.core.Table
object SectorDraftsTable : Table("sector_drafts") {
val sectorId = integer("sector_id").references(SectorsTable.id)
val data = text("data").transform(
{ json -> Json.decodeFromString(LandData.serializer(), json) },
{ value -> Json.encodeToString(LandData.serializer(), value) }
)
override val primaryKey = PrimaryKey(sectorId)
}

View File

@ -11,20 +11,7 @@ object SectorsTable : Table("land_sectors") {
val z = integer("z")
val hp = integer("hp").default(1000)
override val primaryKey = PrimaryKey(id)
}
object SectorRangesTable : Table("land_sector_ranges") {
val id = integer("id").autoIncrement()
val sectorId = integer("sector_id").references(SectorsTable.id)
val type = varchar("type", 16)
val x1 = integer("x1")
val y1 = integer("y1")
val z1 = integer("z1")
val x2 = integer("x2")
val y2 = integer("y2")
val z2 = integer("z2")
val isSneaking = bool("is_sneaking")
val landId = integer("land_id").nullable()
override val primaryKey = PrimaryKey(id)
}

View File

@ -31,11 +31,15 @@ import net.hareworks.hcu.landsector.service.SelectionService
import net.kyori.adventure.text.event.ClickEvent
import org.bukkit.event.player.PlayerInteractEntityEvent
import net.hareworks.hcu.faction.service.FactionService
import net.hareworks.hcu.faction.database.schema.FactionRole
class SectorListener(
private val plugin: LandSectorPlugin,
private val sectorService: SectorService,
private val playerIdService: PlayerIdService,
private val selectionService: SelectionService
private val selectionService: SelectionService,
private val factionService: FactionService
) : Listener {
@EventHandler
@ -216,7 +220,33 @@ class SectorListener(
if (pEntry == null) return
val sector = sectorService.getSector(sectorId) ?: return
if (sector.ownerActorId != pEntry.actorId) {
// Resolve Owner Name
var ownerName = "Unknown"
// Try getting faction name
val factionName = factionService.getFactionName(sector.ownerActorId)
if (factionName != null) {
ownerName = "Faction: $factionName"
} else {
// Assume player. Ideally resolve UUID from ActorID, but for now ID or fallback
// We don't have direct Player Actor ID -> Name cache here easily without querying DB or UUID service
// Just show ID for now to be safe, or "Player #ID"
ownerName = "Player #${sector.ownerActorId}"
}
val canManage = if (sector.ownerActorId == pEntry.actorId) {
true
} else {
// Or if owner is my faction and I am EXEC/OWNER
val myFaction = factionService.getFactionOfPlayer(player.uniqueId)
if (myFaction == sector.ownerActorId) {
val myRole = factionService.getRole(myFaction, player.uniqueId)
myRole == FactionRole.OWNER || myRole == FactionRole.EXEC
} else {
false
}
}
if (!canManage) {
player.sendMessage(Component.text("You are not the owner of this sector.", NamedTextColor.RED))
return
}
@ -230,7 +260,21 @@ class SectorListener(
// Build Page Content
val content = Component.text()
.append(Component.text("Sector Core @ ${sector.x},${sector.y},${sector.z}\n\n", NamedTextColor.BLACK))
.append(Component.text("Sector Core @ ${sector.x},${sector.y},${sector.z}\n", NamedTextColor.BLACK))
.append(Component.text("Owner: $ownerName\n\n", NamedTextColor.DARK_GRAY))
// Transfer Check
val myFaction = factionService.getFactionOfPlayer(player.uniqueId)
if (myFaction != null && sector.ownerActorId != myFaction) {
val myRole = factionService.getRole(myFaction, player.uniqueId)
if (myRole == FactionRole.OWNER || myRole == FactionRole.EXEC) {
content.append(
Component.text("[Transfer to Faction]\n\n", NamedTextColor.GOLD)
.clickEvent(ClickEvent.runCommand("/landsector transfer $sectorId $myFaction"))
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to transfer ownership to your faction")))
)
}
}
// Actions Row 1
content.append(
@ -260,6 +304,12 @@ class SectorListener(
content.append(Component.text("Parts:", NamedTextColor.BLACK))
ranges.forEach { range ->
content.append(Component.text("\n- [${range.id}] ${range.type}", NamedTextColor.DARK_GRAY))
content.append(Component.text(" "))
content.append(
Component.text("[x]", NamedTextColor.RED)
.clickEvent(ClickEvent.runCommand("/landsector range delete $sectorId ${range.id}"))
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to delete range")))
)
}
}

View File

@ -7,5 +7,6 @@ data class Sector(
val x: Int,
val y: Int,
val z: Int,
val hp: Int
val hp: Int,
val landId: Int? = null
)

View File

@ -1,7 +1,8 @@
package net.hareworks.hcu.landsector.service
import net.hareworks.hcu.landsector.database.SectorDraftsTable
import net.hareworks.hcu.landsector.database.SectorsTable
import net.hareworks.hcu.landsector.database.SectorRangesTable
// SectorRangesTable removed
import net.hareworks.hcu.landsector.model.Sector
import net.hareworks.hcu.landsector.model.SectorRange
import net.hareworks.hcu.landsector.model.SelectionMode
@ -17,15 +18,21 @@ import org.jetbrains.exposed.v1.jdbc.deleteWhere
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import java.util.concurrent.ConcurrentHashMap
class SectorService(private val database: Database) {
// ... class definition ...
class SectorService(private val database: Database, private val landService: net.hareworks.hcu.lands.service.LandService) {
private val hpCache = ConcurrentHashMap<Int, Int>()
private val dirtySet = ConcurrentHashMap.newKeySet<Int>()
private val sectorsCache = ConcurrentHashMap<Int, Sector>()
// Draft storage: SectorId -> Land (Draft)
private val draftLands = ConcurrentHashMap<Int, net.hareworks.hcu.lands.model.Land>()
fun init() {
transaction(database) {
SchemaUtils.createMissingTablesAndColumns(SectorsTable, SectorRangesTable)
SchemaUtils.createMissingTablesAndColumns(SectorsTable, SectorDraftsTable)
// Load Sectors
SectorsTable.selectAll().forEach {
val id = it[SectorsTable.id]
val sector = Sector(
@ -35,10 +42,28 @@ class SectorService(private val database: Database) {
it[SectorsTable.x],
it[SectorsTable.y],
it[SectorsTable.z],
it[SectorsTable.hp]
it[SectorsTable.hp],
it[SectorsTable.landId]
)
sectorsCache[id] = sector
}
// Load Drafts
SectorDraftsTable.selectAll().forEach {
val sId = it[SectorDraftsTable.sectorId]
val data = it[SectorDraftsTable.data]
val sector = sectorsCache[sId]
if (sector != null && sector.landId == null) {
draftLands[sId] = net.hareworks.hcu.lands.model.Land(
id = -1,
name = "Draft Sector $sId",
actorId = sector.ownerActorId,
world = sector.world,
data = data
)
}
}
}
}
@ -51,11 +76,29 @@ class SectorService(private val database: Database) {
it[SectorsTable.y] = y
it[SectorsTable.z] = z
it[SectorsTable.hp] = 1000
it[SectorsTable.landId] = null
}[SectorsTable.id]
hpCache[id] = 1000
val sector = Sector(id, ownerActorId, world, x, y, z, 1000)
val sector = Sector(id, ownerActorId, world, x, y, z, 1000, null)
sectorsCache[id] = sector
// Initialize draft land
val draftLand = net.hareworks.hcu.lands.model.Land(
id = -1, // Dummy ID
name = "Draft Sector $id",
actorId = ownerActorId,
world = world,
data = net.hareworks.hcu.lands.model.LandData()
)
draftLands[id] = draftLand
// Persist draft
SectorDraftsTable.insert {
it[SectorDraftsTable.sectorId] = id
it[SectorDraftsTable.data] = draftLand.data
}
sector
}
}
@ -77,7 +120,8 @@ class SectorService(private val database: Database) {
it[SectorsTable.x],
it[SectorsTable.y],
it[SectorsTable.z],
it[SectorsTable.hp]
it[SectorsTable.hp],
it[SectorsTable.landId]
)
}
}
@ -138,7 +182,7 @@ class SectorService(private val database: Database) {
val sector = transaction(database) {
val record = SectorsTable.selectAll().andWhere { SectorsTable.id eq id }.singleOrNull() ?: return@transaction null
SectorRangesTable.deleteWhere { SectorRangesTable.sectorId eq id }
SectorDraftsTable.deleteWhere { SectorDraftsTable.sectorId eq id }
SectorsTable.deleteWhere { SectorsTable.id eq id }
Sector(
@ -148,7 +192,8 @@ class SectorService(private val database: Database) {
record[SectorsTable.x],
record[SectorsTable.y],
record[SectorsTable.z],
record[SectorsTable.hp]
record[SectorsTable.hp],
record[SectorsTable.landId]
)
}
@ -156,6 +201,11 @@ class SectorService(private val database: Database) {
hpCache.remove(id)
dirtySet.remove(id)
sectorsCache.remove(id)
draftLands.remove(id)
if (sector.landId != null) {
landService.deleteLand(sector.landId)
}
}
return sector
@ -203,7 +253,8 @@ class SectorService(private val database: Database) {
it[SectorsTable.x],
it[SectorsTable.y],
it[SectorsTable.z],
hp
hp,
it[SectorsTable.landId]
)
}
}
@ -226,46 +277,195 @@ class SectorService(private val database: Database) {
it[SectorsTable.x],
it[SectorsTable.y],
it[SectorsTable.z],
hp
hp,
it[SectorsTable.landId]
)
}
}
}
fun addRange(sectorId: Int, mode: SelectionMode, x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int, isSneaking: Boolean): SectorRange {
return transaction(database) {
val id = SectorRangesTable.insert {
it[SectorRangesTable.sectorId] = sectorId
it[SectorRangesTable.type] = mode.name
it[SectorRangesTable.x1] = x1
it[SectorRangesTable.y1] = y1
it[SectorRangesTable.z1] = z1
it[SectorRangesTable.x2] = x2
it[SectorRangesTable.y2] = y2
it[SectorRangesTable.z2] = z2
it[SectorRangesTable.isSneaking] = isSneaking
}[SectorRangesTable.id]
SectorRange(id, sectorId, mode, x1, y1, z1, x2, y2, z2, isSneaking)
fun addRange(sectorId: Int, mode: SelectionMode, x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int, isSneaking: Boolean): Any? {
val sector = getSector(sectorId) ?: return null
// Cannot edit if already confirmed
if (sector.landId != null) return null
val draft = draftLands.computeIfAbsent(sectorId) {
net.hareworks.hcu.lands.model.Land(
id = -1,
name = "Draft Sector $sectorId",
actorId = sector.ownerActorId,
world = sector.world,
data = net.hareworks.hcu.lands.model.LandData()
)
}
val shape = when (mode) {
SelectionMode.CUBOID -> {
net.hareworks.hcu.lands.model.Shape.Cuboid(x1, y1, z1, x2, y2, z2)
}
SelectionMode.CYLINDER -> {
// Convert bounds to cylinder params
// Approximating from bounding box
val minX = minOf(x1, x2)
val maxX = maxOf(x1, x2)
val minZ = minOf(z1, z2)
val maxZ = maxOf(z1, z2)
val minY = minOf(y1, y2)
val maxY = maxOf(y1, y2)
val cx = (minX + maxX) / 2
val cz = (minZ + maxZ) / 2
val cy = (minY + maxY) / 2
// Radius is max dist from center to edge of bounding box in X or Z
val radiusX = (maxX - minX) / 2.0
val radiusZ = (maxZ - minZ) / 2.0
val radius = maxOf(radiusX, radiusZ)
val height = maxY - minY + 1
val bottom = (cy - minY)
val top = (maxY - cy)
net.hareworks.hcu.lands.model.Shape.Cylinder(cx, cy, cz, radius, bottom, top)
}
else -> return null
}
// Use LandService modify pattern or direct replacement since stored in local map
val newParts = draft.data.parts.toMutableList()
newParts.add(shape)
val newDraft = draft.copy(data = draft.data.copy(parts = newParts))
draftLands[sectorId] = newDraft
// Persist change
transaction(database) {
// Check if exists first? Or upsert if supported easily.
// We know it exists from creation, but safety check.
val count = SectorDraftsTable.update({ SectorDraftsTable.sectorId eq sectorId }) {
it[SectorDraftsTable.data] = newDraft.data
}
if (count == 0) {
SectorDraftsTable.insert {
it[SectorDraftsTable.sectorId] = sectorId
it[SectorDraftsTable.data] = newDraft.data
}
}
}
return shape
}
fun getRanges(sectorId: Int): List<SectorRange> {
val sector = getSector(sectorId) ?: return emptyList()
val land = if (sector.landId != null) {
landService.getLand(sector.landId)
} else {
draftLands[sectorId]
} ?: return emptyList()
// Convert Shapes back to SectorRanges for UI compatibility
return land.data.parts.mapIndexed { index, shape ->
when (shape) {
is net.hareworks.hcu.lands.model.Shape.Cuboid -> {
SectorRange(
index, // Use index as ID for UI
sectorId,
SelectionMode.CUBOID,
shape.x1, shape.y1, shape.z1,
shape.x2, shape.y2, shape.z2,
false
)
}
is net.hareworks.hcu.lands.model.Shape.Cylinder -> {
SectorRange(
index,
sectorId,
SelectionMode.CYLINDER,
shape.x, shape.y, shape.z,
shape.x, shape.y, shape.z,
false
)
}
// Default fallback for unknown shapes if any (shouldn't happen with current limited types)
else -> SectorRange(index, sectorId, SelectionMode.CUBOID, 0,0,0,0,0,0, false)
}
}
}
fun deleteRange(sectorId: Int, rangeIndex: Int): Boolean {
val sector = getSector(sectorId) ?: return false
if (sector.landId != null) return false // Cannot edit confirmed
val draft = draftLands[sectorId] ?: return false
if (rangeIndex < 0 || rangeIndex >= draft.data.parts.size) return false
val newParts = draft.data.parts.toMutableList()
newParts.removeAt(rangeIndex)
val newDraft = draft.copy(data = draft.data.copy(parts = newParts))
draftLands[sectorId] = newDraft
// Persist change
transaction(database) {
SectorDraftsTable.update({ SectorDraftsTable.sectorId eq sectorId }) {
it[SectorDraftsTable.data] = newDraft.data
}
}
return true
}
fun activateSector(sectorId: Int): Boolean {
val sector = getSector(sectorId) ?: return false
if (sector.landId != null) return false // Already active
val draft = draftLands[sectorId] ?: return false
if (draft.data.parts.isEmpty()) return false
val landName = "Sector_${sectorId}_${System.currentTimeMillis()}"
if (landService.createLand(landName, draft.actorId, draft.world)) {
// Find the created land to get ID
val lands = landService.findLandsByName(landName)
val createdLand = lands.firstOrNull() ?: return false
// Update parts
landService.modifyLand(createdLand.id) { data ->
data.copy(parts = draft.data.parts)
}
// Update Sector DB & Delete Draft
transaction(database) {
SectorsTable.update({ SectorsTable.id eq sectorId }) {
it[SectorsTable.landId] = createdLand.id
}
SectorDraftsTable.deleteWhere { SectorDraftsTable.sectorId eq sectorId }
}
// Update cache & Clear draft
sectorsCache[sectorId] = sector.copy(landId = createdLand.id)
draftLands.remove(sectorId)
return true
}
return false
}
fun transferSector(id: Int, newOwnerActorId: Int): Boolean {
return transaction(database) {
SectorRangesTable.selectAll().andWhere { SectorRangesTable.sectorId eq sectorId }.map {
SectorRange(
it[SectorRangesTable.id],
it[SectorRangesTable.sectorId],
SelectionMode.valueOf(it[SectorRangesTable.type]),
it[SectorRangesTable.x1],
it[SectorRangesTable.y1],
it[SectorRangesTable.z1],
it[SectorRangesTable.x2],
it[SectorRangesTable.y2],
it[SectorRangesTable.z2],
it[SectorRangesTable.isSneaking]
)
val updated = SectorsTable.update({ SectorsTable.id eq id }) {
it[ownerActorId] = newOwnerActorId
}
if (updated > 0) {
// Update cache
val current = sectorsCache[id]
if (current != null) {
sectorsCache[id] = current.copy(ownerActorId = newOwnerActorId)
}
true
} else {
false
}
}
}