diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/MineCity.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/MineCity.kt index 1b17c39..70a04df 100644 --- a/api/src/main/kotlin/br/com/gamemods/minecity/api/MineCity.kt +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/MineCity.kt @@ -2,6 +2,7 @@ package br.com.gamemods.minecity.api import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi import br.com.gamemods.minecity.api.service.MineCityInternal +import br.com.gamemods.minecity.api.service.claim.ClaimService import br.com.gamemods.minecity.api.service.namedplayer.NamedPlayerService import br.com.gamemods.minecity.api.service.permission.PermissionService @@ -25,6 +26,11 @@ public interface MineCity { */ public val permission: PermissionService + /** + * Allows to get the claims with easy + */ + public val claims: ClaimService + /** * This companion object allows MineCity interface to be used directly in kotlin delegating all API calls to * the instance that is set at [MineCityInternal.implementation]. diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/Claim.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/Claim.kt index dc003d0..7797a47 100644 --- a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/Claim.kt +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/Claim.kt @@ -1,17 +1,40 @@ package br.com.gamemods.minecity.api.claim +import br.com.gamemods.minecity.api.MineCity +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi import br.com.gamemods.minecity.api.id.ClaimId +import br.com.gamemods.minecity.api.id.ClaimPermissionId import br.com.gamemods.minecity.api.serializer.UniqueId +import br.com.gamemods.minecity.api.service.MineCityInternal import kotlinx.serialization.Serializable @Serializable public data class Claim( - val id: ClaimId = ClaimId(), + val id: ClaimId, val shape: Set, + val name: String, val parentId: ClaimId? = null, val owner: UniqueId? = null, val rent: ClaimRentContract? = null, val city: City? = null, val isAdmin: Boolean = false, val settings: ClaimSettings = ClaimSettings(), -) +) { + @OptIn(InternalMineCityApi::class) + public fun hasPermission(playerId: UniqueId, permissionId: ClaimPermissionId, silent: Boolean = false): Boolean { + if (MineCity.players.isAdminMode(playerId)) { + return true + } + if (rent != null && rent.rentedTo == playerId) { + return true + } else if (playerId == owner) { + return true + } + + val allow = settings.hasPermission(playerId, permissionId) + if (!allow && !silent) { + MineCity.players.sendMessage(playerId, MineCityInternal.permissionDeniedMessage(this, permissionId)) + } + return allow + } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimSettings.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimSettings.kt index 5a7ddff..c7b5102 100644 --- a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimSettings.kt +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimSettings.kt @@ -1,7 +1,8 @@ package br.com.gamemods.minecity.api.claim +import br.com.gamemods.minecity.api.id.ClaimPermissionId import br.com.gamemods.minecity.api.id.ClamFlagId -import br.com.gamemods.minecity.api.id.ClamPermissionId +import br.com.gamemods.minecity.api.serializer.UniqueId import kotlinx.serialization.Serializable /** @@ -14,6 +15,22 @@ import kotlinx.serialization.Serializable @Serializable public data class ClaimSettings( val defaultFlags: Map = emptyMap(), - val defaultPermissions: Map = emptyMap(), + val defaultPermissions: Map = emptyMap(), val trustLevels: List = emptyList(), -) +) { + /** + * Calculates the effective flags of this claim. + * + * @param playerId The player that is checking the flags + * @return The effective flags of this claim + */ + public fun hasPermission(playerId: UniqueId, permissionId: ClaimPermissionId): Boolean { + return defaultPermissions[permissionId] ?: trustLevels.fold(false) { currentResult, level -> + if (playerId !in level.players) { + currentResult + } else { + level.permissions[permissionId] ?: currentResult + } + } + } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/TrustLevel.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/TrustLevel.kt index 18bca81..52c4f2d 100644 --- a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/TrustLevel.kt +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/TrustLevel.kt @@ -1,7 +1,7 @@ package br.com.gamemods.minecity.api.claim +import br.com.gamemods.minecity.api.id.ClaimPermissionId import br.com.gamemods.minecity.api.id.ClamFlagId -import br.com.gamemods.minecity.api.id.ClamPermissionId import br.com.gamemods.minecity.api.id.TrustLevelId import br.com.gamemods.minecity.api.serializer.MiniComponent import br.com.gamemods.minecity.api.serializer.UniqueId @@ -21,5 +21,5 @@ public data class TrustLevel( val displayName: MiniComponent, val players: Set = emptySet(), val flags: Map = emptyMap(), - val permissions: Map = emptyMap() + val permissions: Map = emptyMap() ) diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/id/ClamPermissionId.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/ClamPermissionId.kt deleted file mode 100644 index 3c01e2a..0000000 --- a/api/src/main/kotlin/br/com/gamemods/minecity/api/id/ClamPermissionId.kt +++ /dev/null @@ -1,12 +0,0 @@ -package br.com.gamemods.minecity.api.id - -import kotlinx.serialization.Serializable - -/** - * String identifier that identifies the ID of a permission that can be configured on MineCity claims. - */ -@Serializable -@JvmInline -public value class ClamPermissionId(private val id: String) { - override fun toString(): String = id -} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/service/MineCityInternal.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/service/MineCityInternal.kt index 2c3d9f4..d570ccd 100644 --- a/api/src/main/kotlin/br/com/gamemods/minecity/api/service/MineCityInternal.kt +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/service/MineCityInternal.kt @@ -2,6 +2,10 @@ package br.com.gamemods.minecity.api.service import br.com.gamemods.minecity.api.MineCity import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.claim.Claim +import br.com.gamemods.minecity.api.id.ClaimPermissionId +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor /** * Internal MineCity states and services, should not be used by API users. @@ -12,4 +16,19 @@ public object MineCityInternal { * Access to the MineCity API implementation, must be modified only by MineCity itself, can be accessed freely. */ public lateinit var implementation: MineCity + + /** + * Creates a message to be sent to the player when a permission is denied. + */ + public fun permissionDeniedMessage(claim: Claim, permissionId: ClaimPermissionId): Component { + return Component.text() + .content("MineCity> ").color(NamedTextColor.DARK_RED) + .append( + Component.text().color(NamedTextColor.RED) + .append(Component.text("You don't have ")) + .append(MineCity.permission[permissionId].name.color(NamedTextColor.YELLOW)) + .append(Component.text(" permission in ")) + .append(Component.text(claim.name, NamedTextColor.YELLOW)) + ).build() + } } diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/service/namedplayer/NamedPlayerService.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/service/namedplayer/NamedPlayerService.kt index 8b3b2ef..f6c9b57 100644 --- a/api/src/main/kotlin/br/com/gamemods/minecity/api/service/namedplayer/NamedPlayerService.kt +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/service/namedplayer/NamedPlayerService.kt @@ -1,18 +1,20 @@ package br.com.gamemods.minecity.api.service.namedplayer +import br.com.gamemods.minecity.api.annotation.threading.ASyncFriendly import br.com.gamemods.minecity.api.annotation.threading.SyncFriendly import br.com.gamemods.minecity.api.id.NamedPlayer +import br.com.gamemods.minecity.api.serializer.UniqueId import kotlinx.coroutines.Deferred -import java.util.* +import net.kyori.adventure.text.Component /** * Manages the [NamedPlayer] objects. */ public interface NamedPlayerService { /** - * Gets [NamedPlayer] for the given player [uuid], attempts to use a cached value before starting a query. + * Gets [NamedPlayer] for the given player [playerId], attempts to use a cached value before starting a query. */ - public operator fun get(uuid: UUID): Deferred + public operator fun get(playerId: UniqueId): Deferred /** * Gets [NamedPlayer] for the given player [name], attempts to use a cached value before starting a query. @@ -20,14 +22,26 @@ public interface NamedPlayerService { public operator fun get(name: String): Deferred /** - * Checks if the given player [uuid] has ready-to-use cached value. + * Checks if the given player [playerId] has ready-to-use cached value. */ @SyncFriendly - public operator fun contains(uuid: UUID): Boolean + public operator fun contains(playerId: UniqueId): Boolean /** * Checks if the given player [name] has ready-to-use cached value. */ @SyncFriendly public operator fun contains(name: String): Boolean + + /** + * Sends a message to the given player [playerId] if the player is online. + */ + @SyncFriendly + @ASyncFriendly + public fun sendMessage(playerId: UniqueId, message: Component) + + /** + * Checks if the given player [playerId] is in admin mode. + */ + public fun isAdminMode(playerId: UniqueId): Boolean } diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/MineCityCore.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/MineCityCore.kt index 544fed9..4abb039 100644 --- a/core/src/main/kotlin/br/com/gamemods/minecity/core/MineCityCore.kt +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/MineCityCore.kt @@ -4,6 +4,7 @@ import br.com.gamemods.minecity.api.MineCity import br.com.gamemods.minecity.api.MineCityPlatform import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi import br.com.gamemods.minecity.api.annotation.side.ServerSideOnly +import br.com.gamemods.minecity.api.service.claim.ClaimService import br.com.gamemods.minecity.api.service.namedplayer.NamedPlayerService import br.com.gamemods.minecity.api.service.permission.PermissionService import br.com.gamemods.minecity.core.service.world.WorldService @@ -23,6 +24,7 @@ class MineCityCore( val worlds: WorldService, override val players: NamedPlayerService, override val permission: PermissionService, + override val claims: ClaimService, ): MineCity { /** diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/service/claim/CoreClaimService.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/service/claim/CoreClaimService.kt new file mode 100644 index 0000000..b7c35e3 --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/service/claim/CoreClaimService.kt @@ -0,0 +1,35 @@ +package br.com.gamemods.minecity.core.service.claim + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.claim.Claim +import br.com.gamemods.minecity.api.id.ClaimId +import br.com.gamemods.minecity.api.id.WorldId +import br.com.gamemods.minecity.api.math.pos.BlockGridPositioned +import br.com.gamemods.minecity.api.service.claim.ClaimService +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred + +@InternalMineCityApi +class CoreClaimService: ClaimService { + @Suppress("CanBeVal", "UnnecessaryVariable") + override fun get(worldId: WorldId, pos: BlockGridPositioned): Claim? { + //TODO Implement + var result: Claim? = Claim( + id = ClaimId(), + shape = emptySet(), + name = "TestClaim", + ) + //result = null + return result + } + + override fun load(worldId: WorldId, pos: BlockGridPositioned): Deferred { + //TODO Implement + return CompletableDeferred(null) + } + + override fun create(claim: Claim): Deferred { + //TODO Implement + return CompletableDeferred(claim) + } +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/MineCityFabric.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/MineCityFabric.kt index d6a783d..43e94f6 100644 --- a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/MineCityFabric.kt +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/MineCityFabric.kt @@ -9,6 +9,7 @@ import br.com.gamemods.minecity.api.service.MineCityInternal import br.com.gamemods.minecity.core.MineCityCore import br.com.gamemods.minecity.core.dispatchers.Async import br.com.gamemods.minecity.core.dispatchers.Sync +import br.com.gamemods.minecity.core.service.claim.CoreClaimService import br.com.gamemods.minecity.core.service.permission.CorePermissionService import br.com.gamemods.minecity.fabric.math.pos.FabricBlockLocation import br.com.gamemods.minecity.fabric.math.pos.FabricChunkLocation @@ -63,6 +64,7 @@ object MineCityFabric : ModInitializer, MineCityPlatform { worlds = FabricWorldService(this), players = FabricNamedPlayerService(this), permission = CorePermissionService(), + claims = CoreClaimService(), ) MineCityInternal.implementation = core core.onInitialize() diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/FabricNamedPlayerService.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/FabricNamedPlayerService.kt index 03700a0..a8a1a77 100644 --- a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/FabricNamedPlayerService.kt +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/FabricNamedPlayerService.kt @@ -2,6 +2,7 @@ package br.com.gamemods.minecity.fabric.service import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi import br.com.gamemods.minecity.api.id.NamedPlayer +import br.com.gamemods.minecity.api.serializer.UniqueId import br.com.gamemods.minecity.api.service.namedplayer.NamedPlayerService import br.com.gamemods.minecity.fabric.MineCityFabric import com.google.common.cache.CacheBuilder @@ -9,6 +10,7 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.datetime.Clock +import net.kyori.adventure.text.Component import java.util.* import java.util.concurrent.TimeUnit @@ -17,12 +19,27 @@ class FabricNamedPlayerService(private val platform: MineCityFabric): NamedPlaye private val uuid2named = CacheBuilder.newBuilder().expireAfterAccess(30L, TimeUnit.MINUTES).build>() private val name2named = CacheBuilder.newBuilder().expireAfterAccess(30L, TimeUnit.MINUTES).build>() - override fun get(uuid: UUID): Deferred { + override fun isAdminMode(playerId: UniqueId): Boolean { + //TODO Implement + return false + } + + @Suppress("DeferredResultUnused") + override fun sendMessage(playerId: UniqueId, message: Component) { + platform.runOnServer { server -> + server.syncOnly { + server.mcServer.native.playerManager.getPlayer(playerId)?.sendMessage(message) + } + } + } + + @Suppress("CheckedExceptionsKotlin") + override fun get(playerId: UUID): Deferred { @Suppress("DuplicatedCode") - return uuid2named.get(uuid) { + return uuid2named.get(playerId) { platform.runOnServer { server -> server.syncOnly { - server.mcServer.native.playerManager.getPlayer(uuid)?.let { player -> + server.mcServer.native.playerManager.getPlayer(playerId)?.let { player -> NamedPlayer(player.uuid, player.entityName, Clock.System.now()).also { named -> name2named.put(named.name, CompletableDeferred(named)) } @@ -32,6 +49,7 @@ class FabricNamedPlayerService(private val platform: MineCityFabric): NamedPlaye } } + @Suppress("CheckedExceptionsKotlin") override fun get(name: String): Deferred { @Suppress("DuplicatedCode") return name2named.get(name) { @@ -48,8 +66,8 @@ class FabricNamedPlayerService(private val platform: MineCityFabric): NamedPlaye } @OptIn(ExperimentalCoroutinesApi::class) - override fun contains(uuid: UUID): Boolean { - return uuid2named.getIfPresent(uuid)?.let { deferred -> + override fun contains(playerId: UUID): Boolean { + return uuid2named.getIfPresent(playerId)?.let { deferred -> if (deferred.isCompleted) { try { deferred.getCompleted() != null diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/claim/FabricClaimService.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/claim/FabricClaimService.kt new file mode 100644 index 0000000..6826a0a --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/claim/FabricClaimService.kt @@ -0,0 +1,22 @@ +package br.com.gamemods.minecity.fabric.service.claim + +import br.com.gamemods.minecity.api.annotation.PlatformDependent +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.id.WorldId +import br.com.gamemods.minecity.api.service.claim.ClaimService +import br.com.gamemods.minecity.fabric.wrapper.FabricBlockPosWrapper.Companion.wrapper +import net.minecraft.registry.RegistryKey +import net.minecraft.util.math.BlockPos +import net.minecraft.world.World + +@InternalMineCityApi +class FabricClaimService { + companion object { + operator fun ClaimService.get(world: World, pos: BlockPos) = this[world.mineCityWorldId, pos.wrapper] + + @OptIn(PlatformDependent::class) + val RegistryKey.mineCityWorldId get() = WorldId(value.toString()) + + val World.mineCityWorldId get() = registryKey.mineCityWorldId + } +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/permission/FabricDoorClaimPermission.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/permission/FabricDoorClaimPermission.kt index 17176fa..316ea19 100644 --- a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/permission/FabricDoorClaimPermission.kt +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/permission/FabricDoorClaimPermission.kt @@ -1,13 +1,55 @@ package br.com.gamemods.minecity.fabric.service.permission +import br.com.gamemods.minecity.api.MineCity import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi import br.com.gamemods.minecity.api.id.ClaimPermissionId import br.com.gamemods.minecity.api.service.permission.ClaimPermission +import br.com.gamemods.minecity.fabric.service.claim.FabricClaimService.Companion.get +import net.fabricmc.fabric.api.event.player.UseBlockCallback import net.kyori.adventure.text.Component +import net.minecraft.block.DoorBlock +import net.minecraft.block.TrapdoorBlock +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.util.ActionResult +import net.minecraft.util.Hand +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.hit.HitResult +import net.minecraft.world.World @InternalMineCityApi class FabricDoorClaimPermission: ClaimPermission( id = ClaimPermissionId.DOORS, name = Component.text("Doors"), description = Component.text("Allows the player to open and close doors in the claim.") -) +) { + override fun onRegister() { + UseBlockCallback.EVENT.register(OnUseBlock()) + } + + private inner class OnUseBlock: UseBlockCallback { + override fun interact(player: PlayerEntity, world: World, hand: Hand, hitResult: BlockHitResult): ActionResult { + if (world.isClient) { + return ActionResult.PASS + } + + if (hand != Hand.MAIN_HAND || hitResult.type != HitResult.Type.BLOCK) { + return ActionResult.PASS + } + + val clickPos = hitResult.blockPos + val blockState = world.getBlockState(clickPos) + val block = blockState.block + + if (block !is DoorBlock && block !is TrapdoorBlock) { + return ActionResult.SUCCESS + } + + val claim = MineCity.claims[world, clickPos] ?: return ActionResult.PASS + return if (claim.hasPermission(player.identity().uuid(), ClaimPermissionId.DOORS)) { + ActionResult.PASS + } else { + ActionResult.FAIL + } + } + } +}