Skip to content

Commit

Permalink
clean up protection listener, update territory access cache when nati…
Browse files Browse the repository at this point in the history
…on relation changes, fix protection for armor stands and item frames
  • Loading branch information
Gutin1 committed Apr 25, 2024
1 parent f9f987c commit 4451944
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package net.horizonsend.ion.server.features.client.whereisit.mod

import io.netty.buffer.Unpooled
import net.horizonsend.ion.common.extensions.information
import net.horizonsend.ion.server.features.nations.region.AccessType
import net.horizonsend.ion.server.listener.misc.ProtectionListener
import net.horizonsend.ion.server.miscellaneous.utils.Tasks
import net.horizonsend.ion.server.miscellaneous.utils.minecraft
Expand Down Expand Up @@ -30,19 +31,18 @@ object Searcher {

val basePos = player.minecraft.blockPosition()
val world = player.world.minecraft
val positions =
searchWorld(
basePos,
world,
itemToFind,
searchContext.tag
).filterNot {
ProtectionListener.denyBlockAccess(
player,
it.key.toLocation(player.world),
null
)
}
val positions = searchWorld(
basePos,
world,
itemToFind,
searchContext.tag
).filterNot {
ProtectionListener.denyBlockAccess(
player,
it.key.toLocation(player.world),
AccessType.INVENTORY_ACCESS
)
}

if (positions.isNotEmpty()) {
val packet = FoundS2C(positions)
Expand Down Expand Up @@ -72,7 +72,7 @@ object Searcher {
ProtectionListener.denyBlockAccess(
player,
it.key.toLocation(player.world),
null
AccessType.INVENTORY_ACCESS
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.horizonsend.ion.server.features.nations.region

enum class AccessType {
BLOCK_EDIT, /* Break/place blocks */
INVENTORY_ACCESS, /* Chest access */
ANIMAL_DAMAGE, /* Hurt passive mobs */
ENTITY_EDIT, /* Item frames, shear sheep, etc */
SWITCH_INTERACT, /* Levers, buttons, etc */
DOORS /* Doors, trapdoors */
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import com.google.common.collect.Multimap
import net.horizonsend.ion.common.database.DbObject
import net.horizonsend.ion.common.database.Oid
import net.horizonsend.ion.common.database.OidDbObjectCompanion
import net.horizonsend.ion.common.database.cache.nations.RelationCache
import net.horizonsend.ion.common.database.cache.nations.SettlementCache
import net.horizonsend.ion.common.database.containsUpdated
import net.horizonsend.ion.common.database.oid
import net.horizonsend.ion.common.database.schema.misc.SLPlayer
import net.horizonsend.ion.common.database.schema.nations.CapturableStation
import net.horizonsend.ion.common.database.schema.nations.Nation
import net.horizonsend.ion.common.database.schema.nations.NationRelation
import net.horizonsend.ion.common.database.schema.nations.Settlement
import net.horizonsend.ion.common.database.schema.nations.SettlementRole
import net.horizonsend.ion.common.database.schema.nations.SettlementZone
Expand Down Expand Up @@ -72,7 +75,7 @@ object Regions : IonServerComponent() {

Tasks.async {
if (player.isOnline) {
cache.forEach(player.world.name) { it.cacheAccess(player) }
cache.forEach(player.world.name) { it.cacheAccessMessage(player) }
}
}
}
Expand All @@ -84,7 +87,7 @@ object Regions : IonServerComponent() {

Tasks.async {
if (player.isOnline) {
cache.forEach(player.world.name) { it.cacheAccess(player) }
cache.forEach(player.world.name) { it.cacheAccessMessage(player) }
}
}
}
Expand Down Expand Up @@ -145,6 +148,14 @@ object Regions : IonServerComponent() {
updateRegionsAsync(change.oid)
}
}

NationRelation.watchUpdates { change ->
if (change.containsUpdated(NationRelation::wish)) {
val relation = RelationCache[change.oid]

for (settlement in Nation.getSettlements(relation.nation)) refreshSettlementTerritoryLocally(settlement)
}
}
}

private fun updateRegionsAsync(id: Oid<Settlement>) {
Expand Down Expand Up @@ -172,7 +183,7 @@ object Regions : IonServerComponent() {
for (player in IonServer.server.onlinePlayers) {
if (PlayerCache[player].settlementOid == settlementId) {
cache.forEach(player.world.name) { region ->
region.cacheAccess(player)
region.cacheAccessMessage(player)
}
}
}
Expand All @@ -184,7 +195,7 @@ object Regions : IonServerComponent() {
val player = Bukkit.getPlayer(uuid) ?: return@locked

cache.forEach(player.world.name) { region ->
region.cacheAccess(player)
region.cacheAccessMessage(player)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ abstract class Region<T : DbObject>(dbObject: DbObject) {

fun isCached(player: Player): Boolean {
synchronized(accessCache) {
return accessCache.contains(player.uniqueId)
return accessCache.containsKey(player.uniqueId)
}
}

fun getInaccessMessage(player: Player): String? {
fun getCachedAccessMessage(player: Player): String? {
synchronized(accessCache) {
check(isCached(player))
return accessCache[player.uniqueId]
}
}

fun cacheAccess(player: Player) {
fun cacheAccessMessage(player: Player) {
synchronized(accessCache) {
accessCache[player.uniqueId] = calculateInaccessMessage(player)
}
Expand All @@ -43,7 +43,7 @@ abstract class Region<T : DbObject>(dbObject: DbObject) {
fun refreshAccessCache() {
synchronized(accessCache) {
accessCache.clear()
bukkitWorld?.players?.forEach(::cacheAccess)
bukkitWorld?.players?.forEach(::cacheAccessMessage)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,18 +172,16 @@ class RegionTerritory(territory: Territory) :
if (playerNation != null && settlementBuildAccess >= Settlement.ForeignRelation.ALLY) {
val settlementNation = SettlementCache[settlement].nation

if (territorySettlement.trustedNations.contains(playerNation)) {
return null
}

// if it's nation access, they can build if they're the same nation
if (settlementBuildAccess == Settlement.ForeignRelation.NATION_MEMBER && settlementNation == playerNation) {
return null
}

// if the min build access is ally, and they're at least an ally, they can build
if (settlementNation != null) {
if (RelationCache[settlementNation, playerNation].ordinal >= NationRelation.Level.ALLY.ordinal) {
val minBuildAccess = SettlementCache[settlement].minBuildAccess

if (minBuildAccess == Settlement.ForeignRelation.ALLY && RelationCache[settlementNation, playerNation] >= NationRelation.Level.ALLY) {
return null
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,25 @@ import io.papermc.paper.event.player.PlayerItemFrameChangeEvent
import net.horizonsend.ion.common.database.schema.starships.PlayerStarshipData
import net.horizonsend.ion.common.utils.lpHasPermission
import net.horizonsend.ion.server.command.nations.SpaceStationCommand
import net.horizonsend.ion.server.features.nations.region.AccessType
import net.horizonsend.ion.server.features.nations.region.Regions
import net.horizonsend.ion.server.features.nations.region.types.RegionTerritory
import net.horizonsend.ion.server.features.starship.DeactivatedPlayerStarships
import net.horizonsend.ion.server.features.starship.active.ActiveStarships
import net.horizonsend.ion.server.listener.SLEventListener
import net.horizonsend.ion.server.miscellaneous.utils.Tasks
import net.horizonsend.ion.server.miscellaneous.utils.Vec3i
import net.horizonsend.ion.server.miscellaneous.utils.action
import net.horizonsend.ion.server.miscellaneous.utils.colorize
import net.horizonsend.ion.server.miscellaneous.utils.component1
import net.horizonsend.ion.server.miscellaneous.utils.component2
import net.horizonsend.ion.server.miscellaneous.utils.component3
import net.horizonsend.ion.server.miscellaneous.utils.component4
import net.horizonsend.ion.server.miscellaneous.utils.isPilot
import net.horizonsend.ion.server.miscellaneous.utils.msg
import org.bukkit.Location
import org.bukkit.Particle
import org.bukkit.block.Block
import org.bukkit.block.data.BlockData
import org.bukkit.block.data.type.Door
import org.bukkit.block.data.type.Switch
import org.bukkit.block.data.type.TrapDoor
import org.bukkit.entity.Animals
import org.bukkit.entity.ArmorStand
import org.bukkit.entity.EntityType
import org.bukkit.entity.Monster
import org.bukkit.entity.Player
Expand All @@ -39,39 +36,62 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.entity.EntityDamageEvent
import org.bukkit.event.entity.EntityExplodeEvent
import org.bukkit.event.entity.EntitySpawnEvent
import org.bukkit.event.player.PlayerArmorStandManipulateEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.InventoryHolder

object ProtectionListener : SLEventListener() {
/** Handle interact events as block edits **/
@EventHandler
fun onClickBlock(event: PlayerInteractEvent) {
val block: Block = event.clickedBlock ?: return
val clickedBlock: Block = event.clickedBlock ?: return
val clickedBlockData: BlockData = clickedBlock.blockData
// Chests, button doors
if (event.action != Action.RIGHT_CLICK_BLOCK) return

if (shouldNotBeChecked(block)) return

onBlockEdit(event, block.location, event.player)
if (clickedBlockData is Switch) return onUseSwitch(event, clickedBlock)
if (clickedBlockData is InventoryHolder) return onEditInventory(event, clickedBlock)
if (clickedBlockData is TrapDoor || clickedBlockData is Door) return onUseDoor(event, clickedBlock)
}

/** Allows exceptions to the onBlockEdit check **/
private fun shouldNotBeChecked(clickedBlock: Block): Boolean {
// It is much easier to decide what should be the exception than to make exceptions
// If something ends up getting checked that shouldn't
// (e.g. clicking the glass in your cockpit), it could break firing weapons.
private fun onUseSwitch(event: PlayerInteractEvent, clickedBlock: Block) {
if (denyBlockAccess(event.player, clickedBlock.location, AccessType.SWITCH_INTERACT)) {
event.isCancelled = true
}
}

// Manually check these
if (clickedBlock.blockData is Switch) return false
if (clickedBlock.blockData is TrapDoor) return false
if (clickedBlock.blockData is Door) return false
private fun onUseDoor(event: PlayerInteractEvent, clickedBlock: Block) {
if (denyBlockAccess(event.player, clickedBlock.location, AccessType.DOORS)) {
event.isCancelled = true
}
}

return clickedBlock.state !is InventoryHolder
private fun onEditInventory(event: PlayerInteractEvent, clickedBlock: Block) {
if (denyBlockAccess(event.player, clickedBlock.location, AccessType.INVENTORY_ACCESS)) {
event.isCancelled = true
}
}

@EventHandler
fun onItemFrameChange(event: PlayerItemFrameChangeEvent) {
if (denyBlockAccess(event.player, event.itemFrame.location, event)) {
if (denyBlockAccess(event.player, event.itemFrame.location, AccessType.ENTITY_EDIT)) {
event.isCancelled = true
}
}

@EventHandler
fun onArmorStandChange(event: PlayerArmorStandManipulateEvent) {
if (denyBlockAccess(event.player, event.rightClicked.location, AccessType.ENTITY_EDIT)) {
event.isCancelled = true
}
}

@EventHandler
fun onArmorStandChange(event: EntityDamageByEntityEvent) {
val player = event.damager as? Player ?: return
val armorStand = event.entity as? ArmorStand ?: return

if (denyBlockAccess(player, armorStand.location, AccessType.ENTITY_EDIT)) {
event.isCancelled = true
}
}
Expand All @@ -81,7 +101,7 @@ object ProtectionListener : SLEventListener() {
val damager = event.damager as? Player ?: return
if (event.entity !is Animals) return

if (denyBlockAccess(damager, event.entity.location, event)) {
if (denyBlockAccess(damager, event.entity.location, AccessType.ANIMAL_DAMAGE)) {
event.isCancelled = true
}
}
Expand All @@ -93,33 +113,20 @@ object ProtectionListener : SLEventListener() {
fun onBlockBreak(event: BlockBreakEvent) = onBlockEdit(event, event.block.location, event.player)

private fun onBlockEdit(event: Cancellable, location: Location, player: Player) {
if (denyBlockAccess(player, location, event)) {
if (denyBlockAccess(player, location, AccessType.BLOCK_EDIT)) {
event.isCancelled = true
}
}

/** Called on block break etc. GriefPrevention check should be done first.
* Loops through protected regions at location, checks each one for access message
* @return true if the event should be cancelled, false if it should stay the same. */
fun denyBlockAccess(player: Player, location: Location, event: Cancellable?): Boolean {
var denied = false

if (isRegionDenied(player, location)) denied = true

val (world, x, y, z) = location
val shipContaining = DeactivatedPlayerStarships.getContaining(world, x.toInt(), y.toInt(), z.toInt())

// Need to also check for null
if (shipContaining !is PlayerStarshipData?) return true

if (shipContaining?.isPilot(player) == true && event !is BlockPlaceEvent) denied = false

val (x1, y1, z1) = Vec3i(location)
if (ActiveStarships.findByPilot(player)?.contains(x1, y1, z1) == true) denied = false
fun denyBlockAccess(player: Player, location: Location, accessType: AccessType): Boolean {
// If it is part of their ship, allow
if (checkShipBypass(player, location, accessType)) return true

if (isLockedShipDenied(player, location)) return true

return denied
return isRegionDenied(player, location)
}

fun isRegionDenied(player: Player, location: Location): Boolean {
Expand All @@ -132,17 +139,17 @@ object ProtectionListener : SLEventListener() {
return@async
}

region.cacheAccess(player)
region.cacheAccessMessage(player)
}

player msg "&eCaching... &o(you probably shouldn't see this, it's probably a bug, but it's harmless)"
player.sendRichMessage("<yellow>Caching... <italic>(you probably shouldn't see this, it's probably a bug, but it's harmless)")
denied = true
continue
}

// break because if there are overlapping regions, that means that this one had the highest priority
// and they had access
val message = region.getInaccessMessage(player) ?: break
val message = region.getCachedAccessMessage(player) ?: break

// Only cancel if they're not in dutymode, otherwise tell them they are bypassing
if (player.hasPermission("dutymode")) {
Expand All @@ -162,6 +169,27 @@ object ProtectionListener : SLEventListener() {
return denied
}

/**
* Returns true if the player is allowed to access this point because it is part of their ship
**/
private fun checkShipBypass(player: Player, location: Location, accessType: AccessType): Boolean {
if (accessType == AccessType.BLOCK_EDIT) return false
if (accessType == AccessType.ENTITY_EDIT) return false
if (accessType == AccessType.ANIMAL_DAMAGE) return false

val world = location.world
val x = location.blockX
val y = location.blockY
val z = location.blockZ

val shipContaining = DeactivatedPlayerStarships.getContaining(world, x, y, z)
?: ActiveStarships.findByPilot(player)?.takeIf { it.contains(x, y, z) }?.data
?: return false

if (shipContaining !is PlayerStarshipData) return false
return shipContaining.isPilot(player)
}

private fun isLockedShipDenied(player: Player, location: Location): Boolean {
if (SpaceStationCommand.disallowedWorlds.contains(location.world.name.lowercase())) return false
if (player.uniqueId.lpHasPermission("ion.bypass-locks")) return false
Expand Down

0 comments on commit 4451944

Please sign in to comment.