From c41af62e37b1aa6559b63330c3f86a7aa3ecc3a5 Mon Sep 17 00:00:00 2001 From: Callum Johnson Date: Thu, 8 Oct 2026 21:27:14 +0100 Subject: [PATCH 1/9] feat: Folia Scheduling --- .../com/willfp/libreforge/HolderStates.kt | 6 +- .../kotlin/com/willfp/libreforge/Regions.kt | 130 ++++++++++++++++++ .../libreforge/commands/CommandReload.kt | 11 +- .../com/willfp/libreforge/dates/DateClock.kt | 2 +- .../com/willfp/libreforge/dates/DateUtils.kt | 19 ++- .../effects/impl/EffectAddHolder.kt | 2 +- .../effects/impl/EffectAddHolderInRadius.kt | 2 +- .../effects/impl/EffectAddHolderToVictim.kt | 2 +- .../effects/impl/EffectBrewTimeMultiplier.kt | 2 +- .../impl/EffectClearInvulnerability.kt | 2 +- .../effects/impl/EffectCreateExplosion.kt | 2 +- .../effects/impl/EffectCreateHologram.kt | 2 +- .../impl/EffectDamageNearbyEntities.kt | 2 +- .../effects/impl/EffectDamageTwice.kt | 2 +- .../impl/EffectDontConsumeLapisChance.kt | 2 +- .../effects/impl/EffectDontConsumeXpChance.kt | 2 +- .../effects/impl/EffectGlowNearbyBlocks.kt | 2 +- .../effects/impl/EffectHarvestCrop.kt | 2 +- .../effects/impl/EffectInfiniteBucket.kt | 9 +- .../libreforge/effects/impl/EffectKeepItem.kt | 4 +- .../effects/impl/EffectParticleAnimation.kt | 5 +- .../impl/EffectPermanentPotionEffect.kt | 5 +- .../effects/impl/EffectPlaceBlock.kt | 2 +- .../impl/EffectPotionDurationMultiplier.kt | 2 +- .../effects/impl/EffectReelSpeedMultiplier.kt | 6 +- .../effects/impl/EffectRemovePotionEffect.kt | 2 +- .../effects/impl/EffectReplaceNear.kt | 2 +- .../effects/impl/EffectReplantCrops.kt | 2 +- .../effects/impl/EffectRunCommand.kt | 13 +- .../effects/impl/EffectRunPlayerCommand.kt | 21 +-- .../effects/impl/EffectSetGlowing.kt | 2 +- .../libreforge/effects/impl/EffectSetTime.kt | 6 +- .../effects/impl/EffectSetVelocity.kt | 2 +- .../effects/impl/EffectSetVictimVelocity.kt | 2 +- .../effects/impl/EffectSetWeather.kt | 34 ++--- .../libreforge/effects/impl/EffectSilence.kt | 2 +- .../effects/impl/EffectSpawnMobs.kt | 2 +- .../effects/impl/EffectSpawnParticle.kt | 5 +- .../effects/impl/EffectStrikeLightning.kt | 4 +- .../libreforge/effects/impl/EffectStripAI.kt | 2 +- .../libreforge/effects/impl/EffectTimeBomb.kt | 2 +- .../effects/impl/EffectTraceback.kt | 26 +++- .../impl/EffectVictimSpeedMultiplier.kt | 2 +- .../effects/templates/AttributeEffect.kt | 2 +- .../impl/TriggerItemsAdderBlockItemDrop.kt | 2 +- .../nexo/impl/TriggerNexoBlockItemDrop.kt | 2 +- .../lands/impl/TriggerEnterClaim.kt | 4 +- .../lands/impl/TriggerExitClaim.kt | 4 +- .../integrations/lands/impl/TriggerUnclaim.kt | 8 +- .../impl/RoseStackerBreedListener.kt | 2 +- .../impl/RoseStackerPlaceholderListener.kt | 2 +- .../impl/RoseStackerStackedDeathListener.kt | 5 +- .../skinsrestorer/SkinsRestorerIntegration.kt | 2 +- .../skinsrestorer/impl/EffectSrClearSkin.kt | 2 +- .../skinsrestorer/impl/EffectSrCopySkin.kt | 4 +- .../impl/EffectSrGenerateSkin.kt | 4 +- .../skinsrestorer/impl/EffectSrRefreshSkin.kt | 2 +- .../skinsrestorer/impl/EffectSrSetSkin.kt | 4 +- .../impl/EffectSrSetSkinFromTexture.kt | 2 +- .../skinsrestorer/impl/EffectSrStealSkin.kt | 4 +- .../impl/EffectSrUpdateSkinData.kt | 2 +- .../vault/impl/VaultBalancePoller.kt | 21 +-- .../triggers/DispatchedTriggerFactory.kt | 2 +- .../triggers/impl/TriggerAltClick.kt | 2 +- .../libreforge/triggers/impl/TriggerBrew.kt | 2 +- .../triggers/impl/TriggerBrewIngredient.kt | 2 +- .../libreforge/triggers/impl/TriggerGainXp.kt | 2 +- .../triggers/impl/TriggerGroupGlobalStatic.kt | 2 +- .../triggers/impl/TriggerGroupStatic.kt | 18 ++- .../triggers/impl/TriggerMeleeAttack.kt | 4 +- .../triggers/impl/TriggerRunCommand.kt | 2 +- .../triggers/impl/TriggerSendMessage.kt | 2 +- .../libreforge/triggers/impl/TriggerSmelt.kt | 2 +- .../triggers/impl/TriggerTeleport.kt | 2 +- 74 files changed, 330 insertions(+), 148 deletions(-) create mode 100644 core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/HolderStates.kt b/core/common/src/main/kotlin/com/willfp/libreforge/HolderStates.kt index 8614cf6fa..e3140312e 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/HolderStates.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/HolderStates.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.integrations.afk.AFKManager import com.willfp.eco.core.placeholder.InjectablePlaceholder +import com.willfp.eco.core.scheduling.EcoTask import com.willfp.libreforge.conditions.Condition import com.willfp.libreforge.conditions.ConditionBlock import org.bukkit.Bukkit @@ -10,7 +11,6 @@ import org.bukkit.Location import org.bukkit.entity.Entity import org.bukkit.entity.LivingEntity import org.bukkit.entity.Player -import org.bukkit.scheduler.BukkitTask import java.util.Collections import java.util.IdentityHashMap import java.util.UUID @@ -69,7 +69,7 @@ internal object HolderStates { private var resetRequested = false - private var task: BukkitTask? = null + private var task: EcoTask? = null private var shutdownSweepDone = false @@ -132,7 +132,7 @@ internal object HolderStates { } task?.cancel() - task = Bukkit.getScheduler().runTaskTimer(plugin, Runnable { flush() }, 1, 1) + task = plugin.scheduler.global().runTimer(1, 1) { flush() } if (!states.containsKey(GlobalDispatcher.uuid)) { create(GlobalDispatcher, StateKind.GLOBAL) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt b/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt new file mode 100644 index 000000000..0d993e47e --- /dev/null +++ b/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt @@ -0,0 +1,130 @@ +package com.willfp.libreforge + +import com.willfp.eco.core.Eco +import com.willfp.eco.core.FoliaSupport +import com.willfp.eco.core.Prerequisite +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.entity.Entity + +/** + * The one place libreforge decides which thread may touch what. + * + * Call sites are the same on every platform; only the answers differ. Off Folia there is one + * main thread that owns everything, so [runOwned] is always inline and nothing changes. On Folia + * each region owns its own entities and blocks, and the global region owns server-wide state. + * + * Paper-only `Bukkit` methods are only reached behind [isFolia], so Spigot never resolves them. + */ +internal object Regions { + /** + * If the server is Folia. + */ + val isFolia = Prerequisite.HAS_FOLIA.isMet + + /** + * If the current thread may touch [entity]. + * + * Off Folia this is the main thread, not eco's `isOwnedByCurrentRegion`, which is `true` + * on async threads too. Use this to decide between a live read and a cached one. + */ + fun owns(entity: Entity): Boolean = + if (isFolia) Bukkit.isOwnedByCurrentRegion(entity) else Bukkit.isPrimaryThread() + + /** + * If the current thread may touch the blocks at [location]. + */ + fun owns(location: Location): Boolean = + if (isFolia) Bukkit.isOwnedByCurrentRegion(location) else Bukkit.isPrimaryThread() + + /** + * If the current thread may touch server-wide state. + */ + fun ownsGlobal(): Boolean = + if (isFolia) Bukkit.isGlobalTickThread() else Bukkit.isPrimaryThread() + + /** + * If the current thread may touch [dispatcher]: its entity, else its location, else + * server-wide state. + */ + fun owns(dispatcher: Dispatcher<*>): Boolean { + val target = dispatcher.dispatcher + if (target is Entity) { + return owns(target) + } + + val location = dispatcher.location ?: return ownsGlobal() + return owns(location) + } + + /** + * If [location] can be touched from here, for skipping blocks and entities in other regions + * during area scans. Always `true` off Folia, so scans are unchanged there. + */ + fun canReach(location: Location): Boolean = + Eco.get().isOwnedByCurrentRegion(location) + + /** + * If [entity] can be touched from here. Always `true` off Folia. + */ + fun canReach(entity: Entity): Boolean = + Eco.get().isOwnedByCurrentRegion(entity) + + /** + * Run [block] on the region owning [entity]: now if this thread owns it, otherwise on the + * entity's next tick. Always inline off Folia, as before. + */ + inline fun runOwned(entity: Entity, crossinline block: () -> Unit) { + if (Eco.get().isOwnedByCurrentRegion(entity)) { + block() + } else { + plugin.scheduler.on(entity).run { block() } + } + } + + /** + * Run [block] on the region owning [location]. Always inline off Folia. + */ + inline fun runOwned(location: Location, crossinline block: () -> Unit) { + if (Eco.get().isOwnedByCurrentRegion(location)) { + block() + } else { + plugin.scheduler.at(location).run { block() } + } + } + + /** + * Run [block] on the global region. Always inline off Folia. + */ + inline fun runGlobal(crossinline block: () -> Unit) { + if (!isFolia || Bukkit.isGlobalTickThread()) { + block() + } else { + plugin.scheduler.global().run { block() } + } + } + + /** + * Run [block] on the region owning [dispatcher]. Always inline off Folia. + */ + inline fun runOwned(dispatcher: Dispatcher<*>, crossinline block: () -> Unit) { + val target = dispatcher.dispatcher + if (target is Entity) { + runOwned(target, block) + return + } + + val location = dispatcher.location + if (location != null) { + runOwned(location, block) + } else { + runGlobal(block) + } + } + + /** + * If [feature] is unavailable here, logging it once. Always `false` off Folia. + */ + fun isUnsupported(feature: String): Boolean = + FoliaSupport.isUnsupported(feature) +} diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandReload.kt b/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandReload.kt index 80705946c..4f59730e2 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandReload.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandReload.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.commands import com.willfp.eco.core.command.impl.Subcommand import com.willfp.eco.util.StringUtils import com.willfp.eco.util.toNiceString +import com.willfp.libreforge.Regions import com.willfp.libreforge.plugin import org.bukkit.command.CommandSender @@ -13,9 +14,11 @@ internal object CommandReload : Subcommand( false ) { override fun onExecute(sender: CommandSender, args: List) { - sender.sendMessage( - plugin.langYml.getMessage("reloaded", StringUtils.FormatOption.WITHOUT_PLACEHOLDERS) - .replace("%time%", plugin.reloadWithTime(false).toNiceString()) - ) + Regions.runGlobal { + sender.sendMessage( + plugin.langYml.getMessage("reloaded", StringUtils.FormatOption.WITHOUT_PLACEHOLDERS) + .replace("%time%", plugin.reloadWithTime(false).toNiceString()) + ) + } } } \ No newline at end of file diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/dates/DateClock.kt b/core/common/src/main/kotlin/com/willfp/libreforge/dates/DateClock.kt index 0aa58361a..1bf108f77 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/dates/DateClock.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/dates/DateClock.kt @@ -38,7 +38,7 @@ object DateClock { isStarted = true - plugin.scheduler.runTimer(20, 20) { + plugin.scheduler.global().runTimer(20, 20) { for (boundary in tracker.advance(LocalDateTime.now(Dates.zone))) { for (listener in listeners) { if (listener.period == boundary.period && listener.edge == boundary.edge) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/dates/DateUtils.kt b/core/common/src/main/kotlin/com/willfp/libreforge/dates/DateUtils.kt index a08614729..960a5a999 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/dates/DateUtils.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/dates/DateUtils.kt @@ -1,6 +1,7 @@ package com.willfp.libreforge.dates import com.willfp.eco.core.config.interfaces.Config +import com.willfp.libreforge.Regions import com.willfp.libreforge.toDispatcher import com.willfp.libreforge.triggers.Trigger import com.willfp.libreforge.triggers.TriggerData @@ -11,13 +12,17 @@ import org.bukkit.Bukkit */ internal fun Trigger.dispatchForOnlinePlayers() { for (player in Bukkit.getOnlinePlayers()) { - dispatch( - player.toDispatcher(), - TriggerData( - player = player, - location = player.location - ) - ) + Regions.runOwned(player) { + if (player.isOnline) { + dispatch( + player.toDispatcher(), + TriggerData( + player = player, + location = player.location + ) + ) + } + } } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolder.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolder.kt index 9c541da36..a5e12b293 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolder.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolder.kt @@ -79,7 +79,7 @@ object EffectAddHolder : Effect("add_holder") { holders[dispatcher.uuid].add(holder) dispatcher.invalidate(provider) - plugin.scheduler.runLater(duration.toLong()) { + plugin.scheduler.global().runLater(duration.toLong()) { holders[dispatcher.uuid].remove(holder) if (holders[dispatcher.uuid].isEmpty()) { holders.remove(dispatcher.uuid) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderInRadius.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderInRadius.kt index 6d1b956a8..864b91fa4 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderInRadius.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderInRadius.kt @@ -107,7 +107,7 @@ object EffectAddHolderInRadius : Effect("add_holder_in_radius") holders += holder provider.invalidateNear(holder.holder, holder.uuid, holder.location, holder.radius) - plugin.scheduler.runLater(duration.toLong()) { + plugin.scheduler.global().runLater(duration.toLong()) { holders -= holder provider.invalidateNear(holder.holder, holder.uuid, holder.location, holder.radius) } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderToVictim.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderToVictim.kt index 9d62ab270..5643c4485 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderToVictim.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderToVictim.kt @@ -84,7 +84,7 @@ object EffectAddHolderToVictim : Effect("add_holder_to_victim") holders[player.uniqueId].add(holder) player.toDispatcher().invalidate(provider) - plugin.scheduler.runLater(duration.toLong()) { + plugin.scheduler.global().runLater(duration.toLong()) { holders[player.uniqueId].remove(holder) if (holders[player.uniqueId].isEmpty()) { holders.remove(player.uniqueId) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBrewTimeMultiplier.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBrewTimeMultiplier.kt index c825d31c4..db8fa8110 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBrewTimeMultiplier.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBrewTimeMultiplier.kt @@ -25,7 +25,7 @@ object EffectBrewTimeMultiplier : MultiplierEffect("brew_time_multiplier") { // 2 seconds later to allow for the brewing stand to update, I guess. // This is from old EcoSkills code - plugin.scheduler.runLater(2) { + plugin.scheduler.on(player).runLater(2) { val stand = player.openInventory.topInventory.holder if (stand is BrewingStand) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectClearInvulnerability.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectClearInvulnerability.kt index e974137d2..7c3fdf869 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectClearInvulnerability.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectClearInvulnerability.kt @@ -18,7 +18,7 @@ object EffectClearInvulnerability : Effect("clear_invulnerability override fun onTrigger(config: Config, data: TriggerData, compileData: NoCompileData): Boolean { val victim = data.victim ?: return false - plugin.scheduler.run { + plugin.scheduler.on(victim).run { victim.noDamageTicks = 0 } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectCreateExplosion.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectCreateExplosion.kt index 314a772b9..8c10cf5f8 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectCreateExplosion.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectCreateExplosion.kt @@ -66,7 +66,7 @@ object EffectCreateExplosion : Effect("create_explosion"), Listen val breakBlocks = config.getBoolOrNull("break_blocks") ?: true for (i in 1..amount) { - plugin.scheduler.runLater(i.toLong()) { + plugin.scheduler.at(location).runLater(i.toLong()) { world.createExplosion(location, power.toFloat(), fire, breakBlocks, source) } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectCreateHologram.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectCreateHologram.kt index 4414075ba..d26dd6354 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectCreateHologram.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectCreateHologram.kt @@ -45,7 +45,7 @@ object EffectCreateHologram : Effect("create_hologram") { val hologram = HologramManager.createHologram(location, text) - plugin.scheduler.runLater(duration.toLong()) { + plugin.scheduler.at(location).runLater(duration.toLong()) { hologram.remove() } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageNearbyEntities.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageNearbyEntities.kt index 6cf4093fb..56dec119f 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageNearbyEntities.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageNearbyEntities.kt @@ -103,7 +103,7 @@ object EffectDamageNearbyEntities : Effect>("damage_n } entity.setMetadata("ignore-nearby-damage", plugin.metadataValueFactory.create(true)) - plugin.scheduler.runLater(5) { entity.removeMetadata("ignore-nearby-damage", plugin) } + plugin.scheduler.on(entity).runLater(5) { entity.removeMetadata("ignore-nearby-damage", plugin) } if (!damageSelf && (entity == player)) { continue diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageTwice.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageTwice.kt index 29c102b6a..0319868e4 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageTwice.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageTwice.kt @@ -31,7 +31,7 @@ object EffectDamageTwice : Effect("damage_twice") { return false } - plugin.scheduler.run { + plugin.scheduler.on(victim).run { victim.setMetadata(META_KEY, plugin.createMetadataValue(true)) victim.noDamageTicks = 0 victim.damage(event.damage, event.damager) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDontConsumeLapisChance.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDontConsumeLapisChance.kt index 8c9197ba5..53cb9f848 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDontConsumeLapisChance.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDontConsumeLapisChance.kt @@ -22,7 +22,7 @@ object EffectDontConsumeLapisChance : ChanceMultiplierEffect("dont_consume_lapis } // 2 Ticks because that's what I did in EcoSkills! - plugin.scheduler.runLater(2) { + plugin.scheduler.on(player).runLater(2) { event.inventory.addItem(ItemStack(Material.LAPIS_LAZULI, cost)) } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDontConsumeXpChance.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDontConsumeXpChance.kt index 0eb560d5e..5f1c474b8 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDontConsumeXpChance.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDontConsumeXpChance.kt @@ -20,7 +20,7 @@ object EffectDontConsumeXpChance : ChanceMultiplierEffect("dont_consume_xp_chanc } // 2 Ticks because that's what I did in EcoSkills! - plugin.scheduler.runLater(2) { + plugin.scheduler.on(player).runLater(2) { player.giveExpLevels(cost) } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGlowNearbyBlocks.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGlowNearbyBlocks.kt index 9a855e33f..cd67a7b92 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGlowNearbyBlocks.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGlowNearbyBlocks.kt @@ -110,7 +110,7 @@ object EffectGlowNearbyBlocks : Effect("glow_nearby_blocks") { team.addEntry(shulker.uniqueId.toString()) block.setMetadata("gnb-uuid", plugin.metadataValueFactory.create(shulker.uniqueId)) - plugin.scheduler.runLater(duration.toLong()) { + plugin.scheduler.at(block.location).runLater(duration.toLong()) { team.removeEntry(shulker.uniqueId.toString()) shulker.remove() block.removeMetadata("gnb-uuid", plugin) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectHarvestCrop.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectHarvestCrop.kt index b0fc943b0..fd98203ab 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectHarvestCrop.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectHarvestCrop.kt @@ -35,7 +35,7 @@ object EffectHarvestCrop : Effect("harvest_crop") { block.drops.forEach { drop -> block.world.dropItemNaturally(block.location, drop) } - plugin.scheduler.run { + plugin.scheduler.at(block.location).run { blockData.age = 0 block.type = block.type block.blockData = blockData diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectInfiniteBucket.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectInfiniteBucket.kt index d8868f11b..c980bd182 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectInfiniteBucket.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectInfiniteBucket.kt @@ -1,9 +1,11 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config +import com.willfp.eco.core.scheduling.TaskContext import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.ProvidedHolder +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect @@ -42,7 +44,7 @@ object EffectInfiniteBucket : Effect>("infinite_bucket") { val slot = player.inventory.heldItemSlot - plugin.scheduler.runAsync { + refillContext(player).run { val item = player.inventory.getItem(slot) if (item != null && item.type == Material.BUCKET) { player.inventory.setItem(slot, ItemStack(event.bucket)) @@ -59,7 +61,7 @@ object EffectInfiniteBucket : Effect>("infinite_bucket") { val slot = player.inventory.heldItemSlot - plugin.scheduler.runAsync { + refillContext(player).run { val item = player.inventory.getItem(slot) if (item != null && item.type == Material.BUCKET) { player.inventory.setItem(slot, ItemStack(Material.MILK_BUCKET)) @@ -67,6 +69,9 @@ object EffectInfiniteBucket : Effect>("infinite_bucket") { } } + private fun refillContext(player: Player): TaskContext = + if (Regions.isFolia) plugin.scheduler.on(player) else plugin.scheduler.async() + override fun makeCompileData(config: Config, context: ViolationContext): Set { val types = config.getStrings("types", "type") return if (types.size == 1 && types[0].equals("any", ignoreCase = true)) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepItem.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepItem.kt index 2ba09a632..43deaa6fa 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepItem.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepItem.kt @@ -110,12 +110,12 @@ object EffectKeepItem : Effect("keep_item") { @EventHandler fun onRespawn(event: PlayerRespawnEvent) { - plugin.scheduler.run { restoreItems(event.player) } + plugin.scheduler.on(event.player).run { restoreItems(event.player) } } @EventHandler fun onJoin(event: PlayerJoinEvent) { - plugin.scheduler.run { restoreItems(event.player) } + plugin.scheduler.on(event.player).run { restoreItems(event.player) } } private fun restoreItems(player: Player) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectParticleAnimation.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectParticleAnimation.kt index 2ed3a059b..8d854a735 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectParticleAnimation.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectParticleAnimation.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.particle.Particles import com.willfp.libreforge.ArgType +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect @@ -101,7 +102,9 @@ object EffectParticleAnimation : Effect?>("particle_an val args = config.getSubsection("particle_args") - plugin.scheduler.async().runTimer({ task -> + val context = if (Regions.isFolia) plugin.scheduler.on(entity) else plugin.scheduler.async() + + context.runTimer({ task -> val entityVector = if (config.getBool("use-eye-location") && entity is LivingEntity) { entity.eyeLocation.toFloat3() } else { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectPermanentPotionEffect.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectPermanentPotionEffect.kt index 10c974a66..2aa20fe27 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectPermanentPotionEffect.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectPermanentPotionEffect.kt @@ -99,15 +99,14 @@ object EffectPermanentPotionEffect : Effect("permanent_potion_eff fun onRespawn(event: PlayerRespawnEvent) { val player = event.player - plugin.server.scheduler.runTask(plugin, Runnable { + plugin.scheduler.on(player).run { val types = getHolderData(player) .values .map { it.effectType } .toSet() types.forEach { refreshEffectsOfType(player, it) } - } - ) + } } override fun onEnable( diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectPlaceBlock.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectPlaceBlock.kt index b259cac39..243fb3e5b 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectPlaceBlock.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectPlaceBlock.kt @@ -46,7 +46,7 @@ object EffectPlaceBlock : Effect("place_block") { val oldBlock = Blocks.getBlock(block) toPlace.place(location) val placedType = location.block.type - plugin.scheduler.runLater(duration.toLong()) { + plugin.scheduler.at(location).runLater(duration.toLong()) { if (location.block.type == placedType) { oldBlock.place(location) } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectPotionDurationMultiplier.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectPotionDurationMultiplier.kt index cdb13faf1..7e1f51bfe 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectPotionDurationMultiplier.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectPotionDurationMultiplier.kt @@ -22,7 +22,7 @@ object EffectPotionDurationMultiplier : MultiplierEffect("potion_duration_multip val multiplier = getMultiplier(player.toDispatcher()) - plugin.scheduler.run { + plugin.scheduler.at(event.block.location).run { for (i in 0..2) { val item = event.contents.getItem(i) ?: continue val meta = item.itemMeta as? PotionMeta ?: continue diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReelSpeedMultiplier.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReelSpeedMultiplier.kt index 7e1bd516e..bf98d2b68 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReelSpeedMultiplier.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReelSpeedMultiplier.kt @@ -33,8 +33,10 @@ object EffectReelSpeedMultiplier : MultiplierEffect("reel_speed_multiplier") { .normalize() .multiply(multiplier) - plugin.scheduler.run { - event.caught?.velocity = vector + val caught = event.caught ?: return + + plugin.scheduler.on(caught).run { + caught.velocity = vector } } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRemovePotionEffect.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRemovePotionEffect.kt index 6263d5109..cd51b2c44 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRemovePotionEffect.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRemovePotionEffect.kt @@ -49,7 +49,7 @@ object EffectRemovePotionEffect : Effect("remove_potion_effect") data.victim ?: return false } - plugin.scheduler.run { + plugin.scheduler.on(toApply).run { toApply.removePotionEffect( @Suppress("DEPRECATION") PotionEffectType.getByName(config.getFormattedString("effect", data).uppercase()) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplaceNear.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplaceNear.kt index 51d1f950b..6558293cd 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplaceNear.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplaceNear.kt @@ -155,7 +155,7 @@ object EffectReplaceNear : Effect("replace_near") { val oldBlockData = toReplace.blockData toReplace.setMetadata("rn-block", plugin.createMetadataValue(true)) - plugin.scheduler.runLater(duration.toLong()) { + plugin.scheduler.at(toReplace.location).runLater(duration.toLong()) { if (toReplace.hasMetadata("rn-block")) { toReplace.type = oldBlock toReplace.blockData = oldBlockData diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplantCrops.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplantCrops.kt index 943ecb9a0..17a91f2ab 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplantCrops.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplantCrops.kt @@ -134,7 +134,7 @@ object EffectReplantCrops : Effect("replant_crops") { data.age = 0 - plugin.scheduler.run { + plugin.scheduler.at(block.location).run { block.type = type block.blockData = data diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRunCommand.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRunCommand.kt index 1bb4ee41b..83657a116 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRunCommand.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRunCommand.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.placeholder.translatePlaceholders import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getStrings @@ -40,11 +41,13 @@ object EffectRunCommand : Effect("run_command") { .map { it.translatePlaceholders(config.toPlaceholderContext(data)) } .dropLastWhile { it.isEmpty() } - commands.forEach { - Bukkit.getServer().dispatchCommand( - Bukkit.getConsoleSender(), - it - ) + Regions.runGlobal { + commands.forEach { + Bukkit.getServer().dispatchCommand( + Bukkit.getConsoleSender(), + it + ) + } } return true diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRunPlayerCommand.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRunPlayerCommand.kt index e4ac8a23a..ea535a3b6 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRunPlayerCommand.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRunPlayerCommand.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.placeholder.translatePlaceholders import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getStrings @@ -46,16 +47,20 @@ object EffectRunPlayerCommand : Effect("run_player_command") { .map { it.translatePlaceholders(config.toPlaceholderContext(data)) } .dropLastWhile { it.isEmpty() } - val isOp = player.isOp + val asOp = config.getBool("as_op") - commands.forEach { - try { - if (!isOp) { - player.isOp = config.getBool("as_op") + Regions.runOwned(player) { + val isOp = player.isOp + + commands.forEach { + try { + if (!isOp) { + player.isOp = asOp + } + player.performCommand(it) + } finally { + player.isOp = isOp } - player.performCommand(it) - } finally { - player.isOp = isOp } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetGlowing.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetGlowing.kt index 4b0ba953f..838ad0cfb 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetGlowing.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetGlowing.kt @@ -41,7 +41,7 @@ object EffectSetGlowing : Effect("set_glowing") { victim.isGlowing = glowing if (duration != null && duration > 0) { - plugin.scheduler.runLater(duration.toLong()) { + plugin.scheduler.on(victim).runLater(duration.toLong()) { victim.isGlowing = !glowing } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetTime.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetTime.kt index 4a8be60d2..4d9ca38b1 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetTime.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetTime.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getFormattedString @@ -51,8 +52,11 @@ object EffectSetTime : Effect("set_time") { } ?: return false val time = config.getIntFromExpression("time", data).toLong() + val relative = config.getBool("relative") - world.time = if (config.getBool("relative")) world.time + time else time + Regions.runGlobal { + world.time = if (relative) world.time + time else time + } return true } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetVelocity.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetVelocity.kt index a4802784c..62f3bbf3c 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetVelocity.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetVelocity.kt @@ -46,7 +46,7 @@ object EffectSetVelocity : Effect("set_velocity") { override fun onTrigger(config: Config, data: TriggerData, compileData: NoCompileData): Boolean { val player = data.player ?: return false - plugin.scheduler.runLater(1) { + plugin.scheduler.on(player).runLater(1) { player.velocity = Vector( config.getDoubleFromExpression("x", data), config.getDoubleFromExpression("y", data), diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetVictimVelocity.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetVictimVelocity.kt index aa0f9c504..afe77dd9e 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetVictimVelocity.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetVictimVelocity.kt @@ -46,7 +46,7 @@ object EffectSetVictimVelocity : Effect("set_victim_velocity") { override fun onTrigger(config: Config, data: TriggerData, compileData: NoCompileData): Boolean { val victim = data.victim ?: return false - plugin.scheduler.runLater(1) { + plugin.scheduler.on(victim).runLater(1) { victim.velocity = Vector( config.getDoubleFromExpression("x", data), config.getDoubleFromExpression("y", data), diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetWeather.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetWeather.kt index a5ddb2ac6..071cc740e 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetWeather.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetWeather.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getFormattedString @@ -50,30 +51,23 @@ object EffectSetWeather : Effect("set_weather") { Bukkit.getWorld(worldName) } ?: return false - when (config.getFormattedString("weather", data).lowercase()) { - "clear", "sun" -> { - world.setStorm(false) - world.isThundering = false - } - - "rain", "downfall", "storm" -> { - world.setStorm(true) - world.isThundering = false - } - - "thunder" -> { - world.setStorm(true) - world.isThundering = true - } - + val (storm, thundering) = when (config.getFormattedString("weather", data).lowercase()) { + "clear", "sun" -> false to false + "rain", "downfall", "storm" -> true to false + "thunder" -> true to true else -> return false } - if (config.has("duration")) { - val duration = config.getIntFromExpression("duration", data) + val duration = if (config.has("duration")) config.getIntFromExpression("duration", data) else null - world.weatherDuration = duration - world.thunderDuration = duration + Regions.runGlobal { + world.setStorm(storm) + world.isThundering = thundering + + if (duration != null) { + world.weatherDuration = duration + world.thunderDuration = duration + } } return true diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSilence.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSilence.kt index 8f0b29315..1e3849c81 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSilence.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSilence.kt @@ -33,7 +33,7 @@ object EffectSilence : Effect("silence") { val ticks = config.getIntFromExpression("ticks", data) victim.isSilent = true - plugin.scheduler.runLater(ticks.toLong()) { + plugin.scheduler.on(victim).runLater(ticks.toLong()) { victim.isSilent = false } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSpawnMobs.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSpawnMobs.kt index 536bfe5a9..fcda8c245 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSpawnMobs.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSpawnMobs.kt @@ -114,7 +114,7 @@ object EffectSpawnMobs : Effect("spawn_mobs") { mob.owner = player } - plugin.scheduler.runLater(ticksToLive.toLong()) { mob.remove() } + plugin.scheduler.on(mob).runLater(ticksToLive.toLong()) { mob.remove() } } return true diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSpawnParticle.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSpawnParticle.kt index 065689c20..2870655c6 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSpawnParticle.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSpawnParticle.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.particle.Particles import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getFormattedString @@ -44,7 +45,9 @@ object EffectSpawnParticle : Effect( val particle = Particles.lookup(config.getFormattedString("particle", data)) val amount = config.getOrElse("amount", 1) { getIntFromExpression(it, data) } - plugin.scheduler.runAsync { + val context = if (Regions.isFolia) plugin.scheduler.at(location) else plugin.scheduler.async() + + context.run { particle.spawn(location, amount) } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectStrikeLightning.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectStrikeLightning.kt index ff5f70d02..111e5ebb9 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectStrikeLightning.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectStrikeLightning.kt @@ -36,9 +36,9 @@ object EffectStrikeLightning : Effect("strike_lightning") { val amount = config.getOrElse("amount", 1) { getIntFromExpression(it, data) } for (i in 1..amount) { - plugin.scheduler.runLater({ + plugin.scheduler.at(location).runLater(1) { world.strikeLightning(location) - }, 1) + } } return true diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectStripAI.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectStripAI.kt index 857ac9ffc..9fba38163 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectStripAI.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectStripAI.kt @@ -42,7 +42,7 @@ object EffectStripAI : Effect("strip_ai") { victim.setAI(false) - plugin.scheduler.runLater(duration.toLong()) { + plugin.scheduler.on(victim).runLater(duration.toLong()) { victim.setAI(true) } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTimeBomb.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTimeBomb.kt index 4ffcede03..b2e7faff7 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTimeBomb.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTimeBomb.kt @@ -56,7 +56,7 @@ object EffectTimeBomb : Effect("time_bomb") { if (glow) victim.isGlowing = true - plugin.scheduler.runLater(fuse.toLong()) { + plugin.scheduler.on(victim).runLater(fuse.toLong()) { if (glow) victim.isGlowing = false if (!victim.isDead) { val loc = victim.location diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTraceback.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTraceback.kt index 8625b7e26..be2cd5b33 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTraceback.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTraceback.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getDoubleFromExpression @@ -11,6 +12,7 @@ import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerParameter import org.bukkit.Bukkit import org.bukkit.Location +import org.bukkit.entity.Player object EffectTraceback : Effect("traceback") { override val description = "Teleports the player back to where they were a specified number of seconds ago." @@ -51,15 +53,25 @@ object EffectTraceback : Effect("traceback") { } override fun postRegister() { - plugin.scheduler.runTimer(20, 20) { + plugin.scheduler.global().runTimer(20, 20) { for (player in Bukkit.getOnlinePlayers()) { - @Suppress("UNCHECKED_CAST") - val times = player.getMetadata(key).getOrNull(0)?.value() as? List ?: emptyList() - val newTimes = (if (times.size < 30) times else times.drop(1)) + player.location - - player.removeMetadata(key, plugin) - player.setMetadata(key, plugin.metadataValueFactory.create(newTimes)) + Regions.runOwned(player) { + recordLocation(player) + } } } } + + private fun recordLocation(player: Player) { + if (!player.isOnline) { + return + } + + @Suppress("UNCHECKED_CAST") + val times = player.getMetadata(key).getOrNull(0)?.value() as? List ?: emptyList() + val newTimes = (if (times.size < 30) times else times.drop(1)) + player.location + + player.removeMetadata(key, plugin) + player.setMetadata(key, plugin.metadataValueFactory.create(newTimes)) + } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectVictimSpeedMultiplier.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectVictimSpeedMultiplier.kt index ae76173c7..273c5118c 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectVictimSpeedMultiplier.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectVictimSpeedMultiplier.kt @@ -57,7 +57,7 @@ object EffectVictimSpeedMultiplier : Effect("victim_speed_multipl victim.setMetadata(META_KEY, plugin.createMetadataValue(true)) - plugin.scheduler.runLater(duration.toLong()) { + plugin.scheduler.on(victim).runLater(duration.toLong()) { attribute.baseValue = attributeValue victim.removeMetadata(META_KEY, plugin) } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/AttributeEffect.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/AttributeEffect.kt index c7d4c59ba..ce8358d78 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/AttributeEffect.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/AttributeEffect.kt @@ -112,7 +112,7 @@ abstract class AttributeEffect private constructor( // Run on next tick to prevent constraining to the lower value during reloads. // Skipped while the server is stopping: the scheduler may refuse it, and the tick never comes. if (!HolderLifecycle.isStopping()) { - plugin.scheduler.run { + plugin.scheduler.on(entity).run { constrainAttribute(entity, instance.value) } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/custom_blocks/itemsadder/impl/TriggerItemsAdderBlockItemDrop.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/custom_blocks/itemsadder/impl/TriggerItemsAdderBlockItemDrop.kt index 033aa4a9f..7855e5993 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/custom_blocks/itemsadder/impl/TriggerItemsAdderBlockItemDrop.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/custom_blocks/itemsadder/impl/TriggerItemsAdderBlockItemDrop.kt @@ -37,7 +37,7 @@ object TriggerItemsAdderBlockItemDrop : Listener { val entry = PendingBreak(player.uniqueId, mutableListOf()) pending[loc] = entry - plugin.scheduler.runLater(1) { + plugin.scheduler.at(loc).runLater(1) { val break_ = pending.remove(loc) ?: return@runLater processDrops(loc, break_.playerUuid, break_.items) } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/custom_blocks/nexo/impl/TriggerNexoBlockItemDrop.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/custom_blocks/nexo/impl/TriggerNexoBlockItemDrop.kt index 2cfc2dc4a..3673441b1 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/custom_blocks/nexo/impl/TriggerNexoBlockItemDrop.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/custom_blocks/nexo/impl/TriggerNexoBlockItemDrop.kt @@ -36,7 +36,7 @@ object TriggerNexoBlockItemDrop : Listener { val loc = event.block.location pending[loc] = PendingBreak(player.uniqueId, mutableListOf()) - plugin.scheduler.runLater(1) { + plugin.scheduler.at(loc).runLater(1) { val pendingBreak = pending.remove(loc) ?: return@runLater processDrops(loc, pendingBreak.playerUuid, pendingBreak.items) } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerEnterClaim.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerEnterClaim.kt index b38773751..5edbdebd0 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerEnterClaim.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerEnterClaim.kt @@ -34,7 +34,7 @@ object TriggerEnterClaim : Trigger("enter_claim") { val player = Bukkit.getPlayer(landPlayer.uid) ?: return val location = player.location - Bukkit.getScheduler().runTask(plugin, Runnable { + plugin.scheduler.on(player).run { // TriggerDispatchEvent may only be triggered synchronously. this.dispatch( player.toDispatcher(), @@ -45,6 +45,6 @@ object TriggerEnterClaim : Trigger("enter_claim") { text = event.area.name ) ) - }) + } } } \ No newline at end of file diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerExitClaim.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerExitClaim.kt index 2515d27e1..8e08bf01a 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerExitClaim.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerExitClaim.kt @@ -34,7 +34,7 @@ object TriggerExitClaim : Trigger("exit_claim") { val player = Bukkit.getPlayer(landPlayer.uid) ?: return val location = player.location - Bukkit.getScheduler().runTask(plugin, Runnable { + plugin.scheduler.on(player).run { // TriggerDispatchEvent may only be triggered synchronously. this.dispatch( player.toDispatcher(), @@ -45,6 +45,6 @@ object TriggerExitClaim : Trigger("exit_claim") { text = event.area.name ) ) - }) + } } } \ No newline at end of file diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerUnclaim.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerUnclaim.kt index d0ac714ae..1c590dc3d 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerUnclaim.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerUnclaim.kt @@ -42,7 +42,7 @@ object TriggerUnclaim : Trigger("unclaim") { multiChunkUnclaimingPlayers[player.uniqueId] = event.affectedChunks.size } - Bukkit.getScheduler().runTask(plugin, Runnable { + plugin.scheduler.on(player).run { // TriggerDispatchEvent may only be triggered synchronously. this.dispatch( player.toDispatcher(), @@ -53,7 +53,7 @@ object TriggerUnclaim : Trigger("unclaim") { value = event.affectedChunks.size.toDouble() ) ) - }) + } } @EventHandler(ignoreCancelled = true) @@ -92,7 +92,7 @@ object TriggerUnclaim : Trigger("unclaim") { return } - Bukkit.getScheduler().runTask(plugin, Runnable { + plugin.scheduler.on(player).run { // TriggerDispatchEvent may only be triggered synchronously. this.dispatch( player.toDispatcher(), @@ -102,6 +102,6 @@ object TriggerUnclaim : Trigger("unclaim") { location = player.location ) ) - }) + } } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerBreedListener.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerBreedListener.kt index 7eb7d1148..c15962694 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerBreedListener.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerBreedListener.kt @@ -79,7 +79,7 @@ object RoseStackerBreedListener : Listener { } pending += breed - plugin.scheduler.runLater(EXPIRY_TICKS) { pending -= breed } + plugin.scheduler.global().runLater(EXPIRY_TICKS) { pending -= breed } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerPlaceholderListener.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerPlaceholderListener.kt index b7ed6fe9f..11218c209 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerPlaceholderListener.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerPlaceholderListener.kt @@ -24,7 +24,7 @@ object RoseStackerPlaceholderListener : Listener { val uuid = event.entity.uniqueId stackSizes[uuid] = stacked.stackSize - plugin.scheduler.runLater(CACHE_TICKS) { stackSizes -= uuid } + plugin.scheduler.global().runLater(CACHE_TICKS) { stackSizes -= uuid } } @EventHandler diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerStackedDeathListener.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerStackedDeathListener.kt index 77a71d8d8..71ace9293 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerStackedDeathListener.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerStackedDeathListener.kt @@ -1,5 +1,6 @@ package com.willfp.libreforge.integrations.rosestacker.impl +import com.willfp.libreforge.Regions import com.willfp.libreforge.plugin import com.willfp.libreforge.triggers.impl.TriggerEntityDeath import com.willfp.libreforge.triggers.impl.TriggerKill @@ -29,12 +30,12 @@ object RoseStackerStackedDeathListener : Listener { val killer = event.killer // RoseStacker calls this event asynchronously when death-event-trigger-async is enabled. - plugin.scheduler.run { + plugin.scheduler.at(victim.location).run { repeat(extraDeaths) { // The entity at the top of the stack has already been dispatched for by the // EntityDeathEvent, so these would otherwise be filtered out as duplicates. TriggerEntityDeath.force(victim, allowDuplicates = true) - killer?.let { TriggerKill.force(it, victim, allowDuplicates = true) } + killer?.let { Regions.runOwned(it) { TriggerKill.force(it, victim, allowDuplicates = true) } } } } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/SkinsRestorerIntegration.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/SkinsRestorerIntegration.kt index a6c592033..d3aeb2542 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/SkinsRestorerIntegration.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/SkinsRestorerIntegration.kt @@ -101,7 +101,7 @@ object SkinsRestorerIntegration : LoadableIntegration { invalidateSkinCache(player.uniqueId) - plugin.scheduler.run { + plugin.scheduler.global().run { invalidateSkinCache(player.uniqueId) player.toDispatcher().updateEffects() } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrClearSkin.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrClearSkin.kt index 43df07616..62fc14905 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrClearSkin.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrClearSkin.kt @@ -25,7 +25,7 @@ object EffectSrClearSkin : Effect("sr_clear_skin") { val player = data.player ?: return false val api = skinsRestorer ?: return false - plugin.scheduler.runAsync { + plugin.scheduler.async().run { runCatching { api.playerStorage.removeSkinIdOfPlayer(player.uniqueId) } invalidateSkinCache(player.uniqueId) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrCopySkin.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrCopySkin.kt index 73d8d3980..b901e73fd 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrCopySkin.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrCopySkin.kt @@ -50,10 +50,10 @@ object EffectSrCopySkin : Effect("sr_copy_skin") { val persist = config.getBoolOrNull("persist") ?: true - plugin.scheduler.runAsync { + plugin.scheduler.async().run { val property = victim.lookupSkin().property ?: runCatching { api.playerStorage.getSkinOfPlayer(victim.uniqueId).orElse(null) }.getOrNull() - ?: return@runAsync + ?: return@run if (persist) { runCatching { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrGenerateSkin.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrGenerateSkin.kt index 8c7d26b32..0aff522c9 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrGenerateSkin.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrGenerateSkin.kt @@ -67,8 +67,8 @@ object EffectSrGenerateSkin : Effect("sr_generate_skin") { val variant = parseSkinVariant(config.getStringOrNull("variant")) val persist = config.getBoolOrNull("persist") ?: true - plugin.scheduler.runAsync { - val response = runCatching { api.mineSkinAPI.genSkin(url, variant) }.getOrNull() ?: return@runAsync + plugin.scheduler.async().run { + val response = runCatching { api.mineSkinAPI.genSkin(url, variant) }.getOrNull() ?: return@run runCatching { api.skinStorage.setURLSkinByResponse(url, response) } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrRefreshSkin.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrRefreshSkin.kt index 876608974..a66d1d504 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrRefreshSkin.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrRefreshSkin.kt @@ -26,7 +26,7 @@ object EffectSrRefreshSkin : Effect("sr_refresh_skin") { override fun onTrigger(config: Config, data: TriggerData, compileData: NoCompileData): Boolean { val player = data.player ?: return false - plugin.scheduler.runAsync { + plugin.scheduler.async().run { invalidateSkinCache(player.uniqueId) runCatching { skinApplier?.applySkin(player) } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrSetSkin.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrSetSkin.kt index a99d5f43a..9dc189240 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrSetSkin.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrSetSkin.kt @@ -67,10 +67,10 @@ object EffectSrSetSkin : Effect("sr_set_skin") { val variant = parseSkinVariant(config.getStringOrNull("variant")) val persist = config.getBoolOrNull("persist") ?: true - plugin.scheduler.runAsync { + plugin.scheduler.async().run { val result = runCatching { api.skinStorage.findOrCreateSkinData(skin, variant) } .getOrNull() - ?.orElse(null) ?: return@runAsync + ?.orElse(null) ?: return@run if (persist) { runCatching { api.playerStorage.setSkinIdOfPlayer(player.uniqueId, result.identifier) } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrSetSkinFromTexture.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrSetSkinFromTexture.kt index 9b6648566..5371ea1a4 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrSetSkinFromTexture.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrSetSkinFromTexture.kt @@ -57,7 +57,7 @@ object EffectSrSetSkinFromTexture : Effect("sr_set_skin_from_text val property = SkinProperty.of(value, signature) - plugin.scheduler.runAsync { + plugin.scheduler.async().run { runCatching { skinApplier?.applySkin(player, property) } invalidateSkinCache(player.uniqueId) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrStealSkin.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrStealSkin.kt index 385a25d4f..451ec3fa0 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrStealSkin.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrStealSkin.kt @@ -50,10 +50,10 @@ object EffectSrStealSkin : Effect("sr_steal_skin") { val persist = config.getBoolOrNull("persist") ?: true - plugin.scheduler.runAsync { + plugin.scheduler.async().run { val property = player.lookupSkin().property ?: runCatching { api.playerStorage.getSkinOfPlayer(player.uniqueId).orElse(null) }.getOrNull() - ?: return@runAsync + ?: return@run if (persist) { runCatching { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrUpdateSkinData.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrUpdateSkinData.kt index e472ce2b2..a62f80083 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrUpdateSkinData.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/skinsrestorer/impl/EffectSrUpdateSkinData.kt @@ -28,7 +28,7 @@ object EffectSrUpdateSkinData : Effect("sr_update_skin_data") { val player = data.player ?: return false val api = skinsRestorer ?: return false - plugin.scheduler.runAsync { + plugin.scheduler.async().run { val property = runCatching { api.skinStorage.updatePlayerSkinData(player.uniqueId) } .getOrNull() ?.orElse(null) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/vault/impl/VaultBalancePoller.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/vault/impl/VaultBalancePoller.kt index bf1750902..95670e52f 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/vault/impl/VaultBalancePoller.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/vault/impl/VaultBalancePoller.kt @@ -1,6 +1,7 @@ package com.willfp.libreforge.integrations.vault.impl import com.willfp.eco.core.scheduling.EcoTask +import com.willfp.libreforge.Regions import com.willfp.libreforge.plugin import com.willfp.libreforge.toDispatcher import com.willfp.libreforge.triggers.TriggerData @@ -25,7 +26,7 @@ object VaultBalancePoller : Listener { return } - task = plugin.scheduler.runTimer(intervalTicks, intervalTicks) { + task = plugin.scheduler.global().runTimer(intervalTicks, intervalTicks) { poll() } } @@ -52,15 +53,17 @@ object VaultBalancePoller : Listener { lastSeenBalances[player.uniqueId] = newBalance if (previousBalance != null && newBalance > previousBalance) { - TriggerGainCurrency.dispatch( - player.toDispatcher(), - TriggerData( - player = player, - value = (newBalance - previousBalance).toDouble(), - altValue = newBalance.toDouble(), - text = "vault" + Regions.runOwned(player) { + TriggerGainCurrency.dispatch( + player.toDispatcher(), + TriggerData( + player = player, + value = (newBalance - previousBalance).toDouble(), + altValue = newBalance.toDouble(), + text = "vault" + ) ) - ) + } } } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/DispatchedTriggerFactory.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/DispatchedTriggerFactory.kt index 215bcde6a..669e8a973 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/DispatchedTriggerFactory.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/DispatchedTriggerFactory.kt @@ -41,7 +41,7 @@ class DispatchedTriggerFactory( } internal fun startTicking() { - plugin.scheduler.runTimer(1, 1) { + plugin.scheduler.global().runTimer(1, 1) { dispatcherTriggers.clear() } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerAltClick.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerAltClick.kt index d4c112462..7960d4efd 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerAltClick.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerAltClick.kt @@ -140,7 +140,7 @@ object TriggerAltClick : Trigger("alt_click") { preventDoubleTriggers += player.uniqueId - plugin.scheduler.run { + plugin.scheduler.global().run { preventDoubleTriggers -= player.uniqueId } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerBrew.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerBrew.kt index a13e8eb67..5ee93d361 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerBrew.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerBrew.kt @@ -46,7 +46,7 @@ object TriggerBrew : Trigger("brew") { val location = inventory.location ?: return val oldContents = inventory.contents - plugin.scheduler.runLater(2) { + plugin.scheduler.at(location).runLater(2) { val newContents = inventory.contents if (!oldContents.contentEquals(newContents)) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerBrewIngredient.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerBrewIngredient.kt index 3cf4c202e..65ff6d335 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerBrewIngredient.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerBrewIngredient.kt @@ -46,7 +46,7 @@ object TriggerBrewIngredient : Trigger("brew_ingredient") { val location = inventory.location ?: return val oldContents = inventory.contents - plugin.scheduler.runLater(2) { + plugin.scheduler.at(location).runLater(2) { val newContents = inventory.contents if (!oldContents.contentEquals(newContents)) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGainXp.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGainXp.kt index 9e4dc3dc1..f58c9351e 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGainXp.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGainXp.kt @@ -53,7 +53,7 @@ object TriggerGainXp : Trigger("gain_xp") { val player = event.player telekinesisGranted += player.uniqueId - plugin.scheduler.run { + plugin.scheduler.global().run { telekinesisGranted -= player.uniqueId } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGroupGlobalStatic.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGroupGlobalStatic.kt index f1bee3ea8..ec1d4cf81 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGroupGlobalStatic.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGroupGlobalStatic.kt @@ -30,7 +30,7 @@ object TriggerGroupGlobalStatic : TriggerGroup("global_static") { } override fun postRegister() { - plugin.scheduler.runTimer(1, 1) { + plugin.scheduler.global().runTimer(1, 1) { tick++ for ((interval, trigger) in registry) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGroupStatic.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGroupStatic.kt index ffa790e97..48a715ce1 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGroupStatic.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGroupStatic.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.triggers.impl import com.willfp.eco.core.Prerequisite import com.willfp.eco.core.placeholder.context.placeholderContext import com.willfp.eco.util.evaluateExpressionOrNull +import com.willfp.libreforge.Regions import com.willfp.libreforge.plugin import com.willfp.libreforge.toDispatcher import com.willfp.libreforge.triggers.Trigger @@ -34,20 +35,29 @@ object TriggerGroupStatic : TriggerGroup("static") { } override fun postRegister() { - plugin.scheduler.runTimer(1, 1) { + plugin.scheduler.global().runTimer(1, 1) { tick++ + val currentTick = tick for ((interval, trigger) in registry) { - if (tick % interval == 0) { + if (currentTick % interval == 0) { for (player in Bukkit.getOnlinePlayers()) { - trigger.dispatchFor(player) + Regions.runOwned(player) { + if (player.isOnline) { + trigger.dispatchFor(player) + } + } } } } for ((_, trigger) in dynamicRegistry) { for (player in Bukkit.getOnlinePlayers()) { - trigger.dispatchIfMet(player, tick) + Regions.runOwned(player) { + if (player.isOnline) { + trigger.dispatchIfMet(player, currentTick) + } + } } } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerMeleeAttack.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerMeleeAttack.kt index ddc37b145..23df562eb 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerMeleeAttack.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerMeleeAttack.kt @@ -58,9 +58,9 @@ object TriggerMeleeAttack : Trigger("melee_attack") { fun handle(event: PrePlayerAttackEntityEvent) { if (!(event.willAttack())) return dataMap[event.player.uniqueId] = event.player.attackCooldown - plugin.scheduler.runLater({ + plugin.scheduler.global().runLater(1) { dataMap.remove(event.player.uniqueId) - }, 1L) + } } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerRunCommand.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerRunCommand.kt index 1f086e86f..ce4e095d2 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerRunCommand.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerRunCommand.kt @@ -28,7 +28,7 @@ object TriggerRunCommand : Trigger("run_command") { fun handle(event: PlayerCommandPreprocessEvent) { val player = event.player - plugin.scheduler.run { + plugin.scheduler.on(player).run { this.dispatch( player.toDispatcher(), TriggerData( diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerSendMessage.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerSendMessage.kt index 4b1420d95..d56aeebb0 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerSendMessage.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerSendMessage.kt @@ -31,7 +31,7 @@ object TriggerSendMessage : Trigger("send_message") { fun handle(event: AsyncPlayerChatEvent) { val player = event.player - plugin.scheduler.run { + plugin.scheduler.on(player).run { this.dispatch( player.toDispatcher(), TriggerData( diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerSmelt.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerSmelt.kt index b31d690cd..37abc4ef2 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerSmelt.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerSmelt.kt @@ -46,7 +46,7 @@ object TriggerSmelt : Trigger("smelt") { val location = inventory.location ?: return val oldContents = inventory.contents - plugin.scheduler.runLater(2) { + plugin.scheduler.at(location).runLater(2) { val newContents = inventory.contents if (!oldContents.contentEquals(newContents)) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerTeleport.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerTeleport.kt index 7b23c2068..e52eb2a06 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerTeleport.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerTeleport.kt @@ -26,7 +26,7 @@ object TriggerTeleport : Trigger("teleport") { @EventHandler(ignoreCancelled = true) fun handle(event: PlayerTeleportEvent) { - plugin.scheduler.run { + plugin.scheduler.on(event.player).run { this.dispatch( event.player.toDispatcher(), TriggerData( From 9ffcdad1ebfcf925ddae45678a4e5f3d42584870 Mon Sep 17 00:00:00 2001 From: Callum Johnson Date: Thu, 8 Oct 2026 21:27:33 +0100 Subject: [PATCH 2/9] feat: Folia compatible teleportation --- .../main/kotlin/com/willfp/libreforge/Regions.kt | 13 +++++++++++++ .../libreforge/effects/impl/EffectSwapPositions.kt | 3 ++- .../libreforge/effects/impl/EffectTeleport.kt | 3 ++- .../libreforge/effects/impl/EffectTeleportTo.kt | 3 ++- .../effects/impl/EffectTeleportToGround.kt | 3 ++- .../effects/impl/EffectTeleportToHighestBlock.kt | 4 +++- .../effects/impl/EffectTeleportToLookingAt.kt | 3 ++- .../effects/impl/EffectTeleportToLowestBlock.kt | 4 +++- .../libreforge/effects/impl/EffectTraceback.kt | 2 +- .../libreforge/effects/impl/EffectTransmission.kt | 3 ++- .../impl/animations/impl/AnimationSpinItem.kt | 3 ++- .../cmi/impl/EffectCmiTeleportToWarp.kt | 3 ++- 12 files changed, 36 insertions(+), 11 deletions(-) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt b/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt index 0d993e47e..26d0799d3 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt @@ -122,6 +122,19 @@ internal object Regions { } } + /** + * Teleport [entity]. Folia has no synchronous teleport, so it moves on a later tick there + * and this returns `true` once scheduled. + */ + fun teleport(entity: Entity, location: Location): Boolean { + if (isFolia) { + entity.teleportAsync(location) + return true + } + + return entity.teleport(location) + } + /** * If [feature] is unavailable here, logging it once. Always `false` off Folia. */ diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSwapPositions.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSwapPositions.kt index ae2d311a6..e53bc520f 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSwapPositions.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSwapPositions.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.Prerequisite import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerParameter @@ -28,7 +29,7 @@ object EffectSwapPositions : Effect("swap_positions") { } else { player.teleport(victimLoc) } - victim.teleport(playerLoc) + Regions.teleport(victim, playerLoc) return true } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleport.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleport.kt index 0f3e88c36..3ded6f1b5 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleport.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleport.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerParameter @@ -20,7 +21,7 @@ object EffectTeleport : Effect("teleport") { val location = data.location ?: return false location.pitch = player.location.pitch location.yaw = player.location.yaw - player.teleport(location) + Regions.teleport(player, location) return true } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportTo.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportTo.kt index 184f601f6..3b64a3e06 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportTo.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportTo.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getDoubleFromExpression @@ -62,7 +63,7 @@ object EffectTeleportTo : Effect("teleport_to") { config.getDoubleFromExpression("z", data) ) - player.teleport(loc) + Regions.teleport(player, loc) return true } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToGround.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToGround.kt index c2310d73e..6be87d4f8 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToGround.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToGround.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerParameter @@ -32,7 +33,7 @@ object EffectTeleportToGround : Effect("teleport_to_ground") { current = current.subtract(0.0, 1.0, 0.0) } - player.teleport(current) + Regions.teleport(player, current) return true } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToHighestBlock.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToHighestBlock.kt index 35a7fd2e0..d739e484b 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToHighestBlock.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToHighestBlock.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerParameter @@ -22,7 +23,8 @@ object EffectTeleportToHighestBlock : Effect("teleport_to_highest val y = world.getHighestBlockYAt(location) - player.teleport( + Regions.teleport( + player, Location( world, location.blockX + 0.5, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToLookingAt.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToLookingAt.kt index 51d5aacc2..e57ef9f21 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToLookingAt.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToLookingAt.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getDoubleFromExpression @@ -39,7 +40,7 @@ object EffectTeleportToLookingAt : Effect("teleport_to_looking_at target.yaw = location.yaw target.pitch = location.pitch - player.teleport(target) + Regions.teleport(player, target) return true } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToLowestBlock.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToLowestBlock.kt index fe38715bc..7bea579c8 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToLowestBlock.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTeleportToLowestBlock.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerParameter @@ -29,7 +30,8 @@ object EffectTeleportToLowestBlock : Effect("teleport_to_lowest_b continue } - player.teleport( + Regions.teleport( + player, Location( world, location.blockX + 0.5, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTraceback.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTraceback.kt index be2cd5b33..bf1d342a4 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTraceback.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTraceback.kt @@ -47,7 +47,7 @@ object EffectTraceback : Effect("traceback") { val location = times.getOrElse(index) { times.lastOrNull() } ?: return false - player.teleport(location) + Regions.teleport(player, location) return true } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTransmission.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTransmission.kt index 1713aad90..394c5a6a3 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTransmission.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTransmission.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getDoubleFromExpression @@ -59,6 +60,6 @@ object EffectTransmission : Effect("transmission") { location.pitch = player.location.pitch location.yaw = player.location.yaw - return player.teleport(location) + return Regions.teleport(player, location) } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/animations/impl/AnimationSpinItem.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/animations/impl/AnimationSpinItem.kt index 550bbabd1..8841b968e 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/animations/impl/AnimationSpinItem.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/animations/impl/AnimationSpinItem.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl.animations.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.items.Items import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.impl.animations.Animation import com.willfp.libreforge.getDoubleFromExpression @@ -76,7 +77,7 @@ object AnimationSpinItem : Animation>("spin_item val z = sin(armorStandAngle) * radius val armorStandLocation = sourceLocation.clone().add(x, 0.0, z) - armorStand.teleport(armorStandLocation.add(0.0,0.5, 0.0)) + Regions.teleport(armorStand, armorStandLocation.add(0.0,0.5, 0.0)) armorStand.rightArmPose = EulerAngle(0.0, armorStandAngle + Math.PI, 0.0) // Add PI to make the item point outwards } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/cmi/impl/EffectCmiTeleportToWarp.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/cmi/impl/EffectCmiTeleportToWarp.kt index 2f641d2f9..978392352 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/cmi/impl/EffectCmiTeleportToWarp.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/cmi/impl/EffectCmiTeleportToWarp.kt @@ -4,6 +4,7 @@ import com.Zrips.CMI.CMI import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.triggers.TriggerData @@ -38,7 +39,7 @@ object EffectCmiTeleportToWarp : Effect("cmi_teleport_to_warp") { ?.bukkitLoc ?: return false - player.teleport(location) + Regions.teleport(player, location) return true } From 067739cb5ac4785a5666ef63b34b0a08ec041ccd Mon Sep 17 00:00:00 2001 From: Callum Johnson Date: Thu, 8 Oct 2026 21:44:47 +0100 Subject: [PATCH 3/9] feat: Folia thread safety --- .../com/willfp/libreforge/AliasedRegistry.kt | 3 +- .../com/willfp/libreforge/HolderProvider.kt | 41 +++++++++++-------- .../com/willfp/libreforge/HolderSignals.kt | 20 +++++---- .../com/willfp/libreforge/HolderStates.kt | 15 +++++-- .../libreforge/LibreforgeSpigotPlugin.kt | 1 + .../kotlin/com/willfp/libreforge/Points.kt | 13 +++--- .../libreforge/SeparatorAmbivalentConfig.kt | 3 +- .../willfp/libreforge/conditions/Condition.kt | 1 + .../libreforge/display/ItemFlagDisplay.kt | 11 +++-- .../arguments/impl/ArgumentCooldown.kt | 6 +-- .../effects/arguments/impl/ArgumentEvery.kt | 4 +- .../arguments/impl/ArgumentPointCost.kt | 2 +- .../effects/executors/ChainExecutors.kt | 3 +- .../effects/impl/EffectAddGlobalPoints.kt | 7 ++-- .../effects/impl/EffectAddHolder.kt | 4 +- .../effects/impl/EffectAddHolderInRadius.kt | 3 +- .../effects/impl/EffectAddHolderToVictim.kt | 4 +- .../impl/EffectAddPermanentHolderInRadius.kt | 7 ++-- .../effects/impl/EffectAddPoints.kt | 8 ++-- .../impl/EffectAntigravityProjectile.kt | 4 +- .../effects/impl/EffectBlockCommands.kt | 14 +++++-- .../impl/EffectDamageNearbyEntities.kt | 5 ++- .../effects/impl/EffectFeatherStep.kt | 4 +- .../libreforge/effects/impl/EffectFlight.kt | 4 +- .../effects/impl/EffectGiveGlobalPoints.kt | 2 +- .../effects/impl/EffectGivePoints.kt | 2 +- .../effects/impl/EffectInfiniteBucket.kt | 3 +- .../effects/impl/EffectItemMagnet.kt | 3 +- .../effects/impl/EffectKeepInventory.kt | 4 +- .../libreforge/effects/impl/EffectKeepItem.kt | 4 +- .../effects/impl/EffectKeepLevel.kt | 4 +- .../effects/impl/EffectRapidBows.kt | 4 +- .../effects/impl/EffectReplantCrops.kt | 4 +- .../effects/impl/EffectSetUnbreakable.kt | 3 +- .../effects/impl/EffectTelekinesis.kt | 5 ++- .../effects/impl/bossbar/BossBars.kt | 4 +- .../templates/ChanceMultiplierEffect.kt | 4 +- .../templates/MultiMultiplierEffect.kt | 8 ++-- .../effects/templates/MultiplierEffect.kt | 4 +- .../integrations/lands/impl/TriggerUnclaim.kt | 3 +- .../luckperms/impl/EffectLpGivePermission.kt | 4 +- .../impl/RoseStackerBreedListener.kt | 9 +++- .../impl/RoseStackerPlaceholderListener.kt | 3 +- .../vault/impl/EffectGivePermission.kt | 4 +- .../vault/impl/VaultBalancePoller.kt | 1 + .../com/willfp/libreforge/slot/SlotTypes.kt | 7 +++- .../com/willfp/libreforge/triggers/Trigger.kt | 2 + .../triggers/impl/TriggerAltClick.kt | 3 +- .../triggers/impl/TriggerChangeBiome.kt | 3 +- .../triggers/impl/TriggerCollideWithEntity.kt | 3 +- .../libreforge/triggers/impl/TriggerGainXp.kt | 3 +- .../triggers/impl/TriggerGroupCustom.kt | 5 ++- .../triggers/impl/TriggerMeleeAttack.kt | 5 ++- 53 files changed, 185 insertions(+), 115 deletions(-) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/AliasedRegistry.kt b/core/common/src/main/kotlin/com/willfp/libreforge/AliasedRegistry.kt index 7037a5a23..f6ffeddc6 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/AliasedRegistry.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/AliasedRegistry.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge import com.willfp.eco.core.registry.KRegistrable import com.willfp.eco.core.registry.Registry +import java.util.concurrent.ConcurrentHashMap interface Aliased : KRegistrable { /** @@ -16,7 +17,7 @@ interface Aliased : KRegistrable { * When an ID and an alias clash, the most recently registered element wins. */ abstract class AliasedRegistry : Registry() { - private val aliasRegistry = mutableMapOf() + private val aliasRegistry = ConcurrentHashMap() override fun get(id: String): T? { return aliasRegistry[id] ?: super.get(id) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/HolderProvider.kt b/core/common/src/main/kotlin/com/willfp/libreforge/HolderProvider.kt index a9d561599..eba483158 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/HolderProvider.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/HolderProvider.kt @@ -6,8 +6,10 @@ import com.willfp.libreforge.slot.ItemHolderFinder import org.bukkit.Location import org.bukkit.event.Event import org.bukkit.event.HandlerList +import java.util.Collections import java.util.IdentityHashMap import java.util.UUID +import java.util.concurrent.CopyOnWriteArrayList /** @@ -178,12 +180,14 @@ data class ProvidedEffectBlock( } } -private val providers = mutableListOf() +private val providers = CopyOnWriteArrayList() -private val providerIds = IdentityHashMap() +private val providerIds: MutableMap = Collections.synchronizedMap(IdentityHashMap()) private val providerIdCounts = mutableMapOf() +private val providerRegistrationLock = Any() + /** * The registered providers, in registration order. */ @@ -200,16 +204,19 @@ internal fun registeredProviderId(provider: HolderProvider): String = * Register a new holder provider. */ fun registerHolderProvider(provider: HolderProvider): Boolean { - if (providerIds.containsKey(provider)) { - return false - } + synchronized(providerRegistrationLock) { + if (providerIds.containsKey(provider)) { + return false + } - val baseId = provider.id - val count = providerIdCounts.getOrDefault(baseId, 0) - providerIdCounts[baseId] = count + 1 - providerIds[provider] = if (count == 0) baseId else "$baseId#$count" + val baseId = provider.id + val count = providerIdCounts.getOrDefault(baseId, 0) + providerIdCounts[baseId] = count + 1 + providerIds[provider] = if (count == 0) baseId else "$baseId#$count" + + providers.add(provider) + } - providers.add(provider) HolderSignals.registerProvider(provider) HolderStates.markProviderEverywhere(provider) return true @@ -219,10 +226,12 @@ fun registerHolderProvider(provider: HolderProvider): Boolean { * Remove holder providers, used when the plugin that registered them is disabled. */ internal fun unregisterHolderProviders(filter: (HolderProvider) -> Boolean): List { - val removed = providers.filter(filter) - providers.removeAll(removed) - removed.forEach { providerIds.remove(it) } - return removed + synchronized(providerRegistrationLock) { + val removed = providers.filter(filter) + providers.removeAll(removed) + removed.forEach { providerIds.remove(it) } + return removed + } } /** @@ -312,7 +321,7 @@ inline fun registerSpecificHolderProvider( fun registerSlotHolderFinderAsProvider(finder: ItemHolderFinder<*>) = registerHolderProvider(finder.toHolderProvider()) -private val refreshFunctions = mutableListOf<(Dispatcher<*>) -> Unit>() +private val refreshFunctions = CopyOnWriteArrayList<(Dispatcher<*>) -> Unit>() /** * Register a function to be called before a dispatcher's providers are re-asked. @@ -376,7 +385,7 @@ fun Dispatcher<*>.refreshHolders() = fun Dispatcher<*>.forceRefreshHolders() = HolderStates.forceMarkAllProviders(this) -private val holderPlaceholderProviders = mutableListOf<(ProvidedHolder, Dispatcher<*>) -> Collection>() +private val holderPlaceholderProviders = CopyOnWriteArrayList<(ProvidedHolder, Dispatcher<*>) -> Collection>() /** * Register a function to generate placeholders for a holder. diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/HolderSignals.kt b/core/common/src/main/kotlin/com/willfp/libreforge/HolderSignals.kt index 1cb52d539..617d9755c 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/HolderSignals.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/HolderSignals.kt @@ -8,23 +8,26 @@ import org.bukkit.event.Listener import org.bukkit.plugin.EventExecutor import java.util.Collections import java.util.IdentityHashMap +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.CopyOnWriteArraySet /** * Routes change signals to the providers and conditions that declared them. */ internal object HolderSignals { - private val providersBySignal = HashMap>() + private val providersBySignal = ConcurrentHashMap>() - private val conditionsBySignal = HashMap>>() + private val conditionsBySignal = ConcurrentHashMap>>() private class CustomHandler( val owner: Any, val action: (Event) -> Unit ) - private val customHandlers = HashMap, MutableList>() + private val customHandlers = ConcurrentHashMap, CopyOnWriteArrayList>() - private val listening = mutableSetOf>() + private val listening = ConcurrentHashMap.newKeySet>() private val registeredConditions: MutableSet> = Collections.newSetFromMap(IdentityHashMap()) @@ -36,16 +39,18 @@ internal object HolderSignals { fun conditionsFor(change: HolderChange): Set> = conditionsBySignal[change] ?: emptySet() + @Synchronized fun registerProvider(provider: HolderProvider) { for (change in provider.invalidatedBy) { if (change is HolderChange.Custom<*>) { addCustom(change, provider) { dispatcher, scope -> HolderStates.markProvider(dispatcher, provider, scope) } } else { - providersBySignal.getOrPut(change) { mutableListOf() } += provider + providersBySignal.computeIfAbsent(change) { CopyOnWriteArrayList() } += provider } } } + @Synchronized fun registerCondition(condition: Condition<*>) { // Registration re-runs on every reload. if (!registeredConditions.add(condition)) { @@ -58,7 +63,7 @@ internal object HolderSignals { if (change is HolderChange.Custom<*>) { addCustom(change, condition) { dispatcher, scope -> HolderStates.markCondition(dispatcher, condition, scope) } } else { - conditionsBySignal.getOrPut(change) { mutableSetOf() } += condition + conditionsBySignal.computeIfAbsent(change) { CopyOnWriteArraySet() } += condition } } } @@ -66,6 +71,7 @@ internal object HolderSignals { /** * Stop routing signals to [providers] and to the conditions loaded by [classLoader]. */ + @Synchronized fun unregisterOwnedBy(classLoader: ClassLoader, providers: Collection) { val removed: MutableSet = Collections.newSetFromMap(IdentityHashMap()) removed.addAll(providers) @@ -92,7 +98,7 @@ internal object HolderSignals { @Suppress("UNCHECKED_CAST") val scopeOf = change.scopeOf as ((Event) -> SignalScope?)? - customHandlers.getOrPut(change.event) { mutableListOf() } += CustomHandler(owner) { event -> + customHandlers.computeIfAbsent(change.event) { CopyOnWriteArrayList() } += CustomHandler(owner) { event -> dispatcherOf(event)?.let { mark(it, scopeOf?.invoke(event)) } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/HolderStates.kt b/core/common/src/main/kotlin/com/willfp/libreforge/HolderStates.kt index e3140312e..68ff9ea2c 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/HolderStates.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/HolderStates.kt @@ -41,7 +41,8 @@ internal object HolderStates { private const val MAX_ADMISSION_ATTEMPTS = 200 // Classloaders of libreforge-based plugins disabled at runtime; their code must not run again. - private val unloadedClassLoaders: MutableSet = Collections.newSetFromMap(IdentityHashMap()) + @Volatile + private var unloadedClassLoaders: Set = emptySet() // In creation order, which a reset follows. private val states = LinkedHashMap() @@ -81,6 +82,7 @@ internal object HolderStates { private val defaultSettings = Settings(0, 500, 20, 600, 50, entitiesEnabled = true, skipAFKPlayers = true) + @Volatile var settings = defaultSettings private set @@ -261,8 +263,10 @@ internal object HolderStates { /** * If [code] belongs to a libreforge-based plugin that was disabled at runtime. */ - fun isUnloaded(code: Any): Boolean = - unloadedClassLoaders.isNotEmpty() && code.javaClass.classLoader in unloadedClassLoaders + fun isUnloaded(code: Any): Boolean { + val loaders = unloadedClassLoaders + return loaders.isNotEmpty() && code.javaClass.classLoader in loaders + } fun markConditionsAll(dispatcher: Dispatcher<*>) = withState(dispatcher) { it.conditionDirtyAll = true } @@ -810,7 +814,10 @@ internal object HolderStates { } val classLoader = disabledPlugin.javaClass.classLoader - unloadedClassLoaders += classLoader + unloadedClassLoaders = Collections.newSetFromMap(IdentityHashMap()).apply { + addAll(unloadedClassLoaders) + add(classLoader) + } val removed = unregisterHolderProviders { it.ownerClass.classLoader === classLoader } HolderSignals.unregisterOwnedBy(classLoader, removed) unregisterHolderFunctions(classLoader) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/LibreforgeSpigotPlugin.kt b/core/common/src/main/kotlin/com/willfp/libreforge/LibreforgeSpigotPlugin.kt index 958c6309c..bcc372e8f 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/LibreforgeSpigotPlugin.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/LibreforgeSpigotPlugin.kt @@ -105,6 +105,7 @@ class LibreforgeSpigotPlugin : EcoPlugin() { val dispatchedTriggerFactory = DispatchedTriggerFactory(this) + @Volatile private var hasLoaded = false private val configCategories = listOf( diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/Points.kt b/core/common/src/main/kotlin/com/willfp/libreforge/Points.kt index 2f934cd01..ce435377c 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/Points.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/Points.kt @@ -43,11 +43,11 @@ class PointPriceFactory(private val type: String) : PriceFactory { } override fun pay(player: Player, multiplier: Double) { - player.points[type] -= getValue(player, multiplier) + player.points.add(type, -getValue(player, multiplier)) } override fun giveTo(player: Player, multiplier: Double) { - player.points[type] += getValue(player, multiplier) + player.points.add(type, getValue(player, multiplier)) } override fun getValue(player: Player, multiplier: Double): Double { @@ -68,7 +68,7 @@ class PointPriceFactory(private val type: String) : PriceFactory { } } -private val initializedPoints = mutableSetOf() +private val initializedPoints = ConcurrentHashMap.newKeySet() class PointsMap( private val profile: Profile @@ -88,11 +88,10 @@ class PointsMap( } private fun initializeIfNeeded(type: String) { - if (type in initializedPoints) { + if (!initializedPoints.add(type)) { return } - initializedPoints += type Prices.registerPriceFactory(PointPriceFactory(type)) } @@ -103,7 +102,9 @@ class PointsMap( } fun add(key: String, amount: Double) { - set(key, get(key) + amount) + initializeIfNeeded(key) + + profile.add(getKey(key), amount) } operator fun set(key: String, value: Double) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/SeparatorAmbivalentConfig.kt b/core/common/src/main/kotlin/com/willfp/libreforge/SeparatorAmbivalentConfig.kt index 0b786369a..41537cc9e 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/SeparatorAmbivalentConfig.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/SeparatorAmbivalentConfig.kt @@ -10,11 +10,12 @@ import com.willfp.eco.util.NumberUtils import com.willfp.eco.util.StringUtils import com.willfp.libreforge.triggers.TriggerData import org.bukkit.entity.Player +import java.util.concurrent.ConcurrentHashMap private class SeparatorAmbivalentConfig( private val config: Config ) : Config { - private val resolvedPaths = HashMap() + private val resolvedPaths = ConcurrentHashMap() private inline fun preprocess(path: String, getter: (String) -> T): T? { return preprocess(path, getter, { it == null }, null) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/conditions/Condition.kt b/core/common/src/main/kotlin/com/willfp/libreforge/conditions/Condition.kt index 01b71b84a..69fb1dba3 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/conditions/Condition.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/conditions/Condition.kt @@ -16,6 +16,7 @@ abstract class Condition( /** * Whether this condition is enabled. */ + @Volatile open var isEnabled: Boolean = false protected set diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/display/ItemFlagDisplay.kt b/core/common/src/main/kotlin/com/willfp/libreforge/display/ItemFlagDisplay.kt index 52bbaa2a6..150d0b1c5 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/display/ItemFlagDisplay.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/display/ItemFlagDisplay.kt @@ -12,8 +12,10 @@ import org.bukkit.persistence.PersistentDataType class ItemFlagDisplay( private val plugin: LibreforgeSpigotPlugin ) : DisplayModule(plugin, DisplayPriority.HIGHEST) { - private val flags = mutableSetOf() + @Volatile + private var flags = emptySet() + @Volatile private var enabled = false private val pdcKey = plugin.createNamespacedKey("display_flags") @@ -25,16 +27,18 @@ class ItemFlagDisplay( internal fun reload() { enabled = plugin.configYml.getBool("display.enabled") - flags.clear() + val newFlags = mutableSetOf() for (flagName in plugin.configYml.getStrings("display.item-flags")) { try { - flags += ItemFlag.valueOf(flagName.uppercase()) + newFlags += ItemFlag.valueOf(flagName.uppercase()) } catch (e: IllegalArgumentException) { plugin.logger.warning("Invalid item flag for display.item-flags: $flagName") plugin.logger.warning("Valid options are: ${ItemFlag.entries.joinToString(", ") { it.name.lowercase() }}") } } + + flags = newFlags } override fun display(context: DisplayContext) { @@ -42,6 +46,7 @@ class ItemFlagDisplay( return } + val flags = flags val fis = context.itemStack.fast() fis.persistentDataContainer.set(pdcKey, PersistentDataType.STRING, flags.joinToString(",")) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/arguments/impl/ArgumentCooldown.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/arguments/impl/ArgumentCooldown.kt index 45ca7c679..374b4eb41 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/arguments/impl/ArgumentCooldown.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/arguments/impl/ArgumentCooldown.kt @@ -17,11 +17,12 @@ import com.willfp.libreforge.plugin import com.willfp.libreforge.triggers.DispatchedTrigger import org.bukkit.entity.Player import java.util.UUID +import java.util.concurrent.ConcurrentHashMap import kotlin.math.ceil object ArgumentCooldown : EffectArgument("cooldown") { // Maps cooldown groups to Player UUIDs mapped to expiry time - private val cooldownTracker = mutableMapOf>() + private val cooldownTracker = ConcurrentHashMap>() override fun isMet(element: ConfigurableElement, trigger: DispatchedTrigger, compileData: Chain?): Boolean { return getCooldown(element, trigger) <= 0 @@ -41,10 +42,9 @@ object ArgumentCooldown : EffectArgument("cooldown") { override fun ifMet(element: ConfigurableElement, trigger: DispatchedTrigger, compileData: Chain?) { val group = element.config.getStringOrNull("cooldown_group") ?: element.uuid.toString() - val effectEndTimes = cooldownTracker[group] ?: mutableMapOf() + val effectEndTimes = cooldownTracker.computeIfAbsent(group) { ConcurrentHashMap() } effectEndTimes[trigger.dispatcher.uuid] = System.currentTimeMillis() + (element.config.getDoubleFromExpression("cooldown", trigger.data) * 1000L).toLong() - cooldownTracker[group] = effectEndTimes } override fun ifNotMet(element: ConfigurableElement, trigger: DispatchedTrigger, compileData: Chain?) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/arguments/impl/ArgumentEvery.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/arguments/impl/ArgumentEvery.kt index 758a2b4d8..38ba2908a 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/arguments/impl/ArgumentEvery.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/arguments/impl/ArgumentEvery.kt @@ -1,6 +1,6 @@ package com.willfp.libreforge.effects.arguments.impl -import com.willfp.eco.core.map.nestedMap +import com.willfp.eco.core.map.concurrentNestedMap import com.willfp.libreforge.ConfigurableElement import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.effects.arguments.EffectArgument @@ -9,7 +9,7 @@ import com.willfp.libreforge.triggers.DispatchedTrigger import java.util.UUID object ArgumentEvery: EffectArgument("every") { - private val everyHandler = nestedMap() + private val everyHandler = concurrentNestedMap() override fun isMet(element: ConfigurableElement, trigger: DispatchedTrigger, compileData: NoCompileData): Boolean { val current = everyHandler[element.uuid][trigger.dispatcher.uuid] ?: 1 diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/arguments/impl/ArgumentPointCost.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/arguments/impl/ArgumentPointCost.kt index 98fe6e191..adb07008d 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/arguments/impl/ArgumentPointCost.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/arguments/impl/ArgumentPointCost.kt @@ -54,6 +54,6 @@ object ArgumentPointCost : EffectArgument("point_cost") { val cost = element.config.getDoubleFromExpression("point_cost.cost", trigger.data) val type = element.config.getString("point_cost.type") - player.points[type] -= cost + player.points.add(type, -cost) } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/executors/ChainExecutors.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/executors/ChainExecutors.kt index 4a9c8844e..04b058760 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/executors/ChainExecutors.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/executors/ChainExecutors.kt @@ -3,9 +3,10 @@ package com.willfp.libreforge.effects.executors import com.willfp.libreforge.effects.executors.impl.CycleExecutorFactory import com.willfp.libreforge.effects.executors.impl.NormalExecutorFactory import com.willfp.libreforge.effects.executors.impl.RandomExecutorFactory +import java.util.concurrent.ConcurrentHashMap object ChainExecutors { - private val registry = mutableMapOf() + private val registry = ConcurrentHashMap() /** * Get executor by ID, or null if invalid ID is provided. diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddGlobalPoints.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddGlobalPoints.kt index 1b96f0593..5d5beb5a1 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddGlobalPoints.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddGlobalPoints.kt @@ -11,6 +11,7 @@ import com.willfp.libreforge.effects.Identifiers import com.willfp.libreforge.get import com.willfp.libreforge.globalPoints import java.util.UUID +import java.util.concurrent.ConcurrentHashMap object EffectAddGlobalPoints : Effect("add_global_points") { override val description = "Permanently increases a global point counter while the holder is active." @@ -32,7 +33,7 @@ object EffectAddGlobalPoints : Effect("add_global_points") { ) } - private val tracker = mutableMapOf() + private val tracker = ConcurrentHashMap() override fun onEnable( dispatcher: Dispatcher<*>, @@ -48,13 +49,13 @@ object EffectAddGlobalPoints : Effect("add_global_points") { point, amount ) - globalPoints[point] += amount + globalPoints.add(point, amount) } override fun onDisable(dispatcher: Dispatcher<*>, identifiers: Identifiers, holder: ProvidedHolder) { val addedPoint = tracker.remove(identifiers.uuid) ?: return - globalPoints[addedPoint.point] -= addedPoint.amount + globalPoints.add(addedPoint.point, -addedPoint.amount) } private data class AddedPoint( diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolder.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolder.kt index a5e12b293..9fb4f8b8f 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolder.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolder.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.Holder @@ -53,7 +53,7 @@ object EffectAddHolder : Effect("add_holder") { ) } - private val holders = listMap() + private val holders = concurrentListMap() // Libreforge knows exactly when these change, so the provider is never polled. private val provider = object : HolderProvider { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderInRadius.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderInRadius.kt index 864b91fa4..c5e455d5d 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderInRadius.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderInRadius.kt @@ -25,6 +25,7 @@ import com.willfp.libreforge.triggers.TriggerData import org.bukkit.Location import java.util.Objects import java.util.UUID +import java.util.concurrent.ConcurrentHashMap object EffectAddHolderInRadius : Effect("add_holder_in_radius") { override val description = "Temporarily applies a set of effects and conditions to all nearby entities within a radius." @@ -66,7 +67,7 @@ object EffectAddHolderInRadius : Effect("add_holder_in_radius") ) } - private val holders = mutableSetOf() + private val holders = ConcurrentHashMap.newKeySet() // Invalidated near a holder when it is added or expires; polling covers movement. private val provider = object : HolderProvider { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderToVictim.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderToVictim.kt index 5643c4485..f15e9ac56 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderToVictim.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddHolderToVictim.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.Holder @@ -57,7 +57,7 @@ object EffectAddHolderToVictim : Effect("add_holder_to_victim") ) } - private val holders = listMap() + private val holders = concurrentListMap() // Libreforge knows exactly when these change, so the provider is never polled. private val provider = object : HolderProvider { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddPermanentHolderInRadius.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddPermanentHolderInRadius.kt index 7f68b3aa6..0ccd5ab6d 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddPermanentHolderInRadius.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddPermanentHolderInRadius.kt @@ -24,6 +24,7 @@ import org.bukkit.Bukkit import org.bukkit.NamespacedKey import java.util.Objects import java.util.UUID +import java.util.concurrent.ConcurrentHashMap object EffectAddPermanentHolderInRadius : Effect("add_permanent_holder_in_radius") { override val description = "Permanently applies a set of effects and conditions to all nearby entities within a radius while the holder is active." @@ -56,7 +57,7 @@ object EffectAddPermanentHolderInRadius : Effect("add_permanent_ ) } - private val holders = mutableSetOf() + private val holders = ConcurrentHashMap.newKeySet() // Invalidated near a holder on a real enable or disable; polling covers movement. private val provider = object : HolderProvider { @@ -146,9 +147,9 @@ object EffectAddPermanentHolderInRadius : Effect("add_permanent_ private class PermanentNearbyHolder( val holder: Holder, - var radius: Double, + @Volatile var radius: Double, val owner: UUID, - var applyToSelf: Boolean + @Volatile var applyToSelf: Boolean ) { fun canApplyTo(dispatcher: Dispatcher<*>): Boolean { val dispatcherLocation = dispatcher.location ?: return false diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddPoints.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddPoints.kt index 2abfc5f18..21e75f1b6 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddPoints.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAddPoints.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.nestedMap +import com.willfp.eco.core.map.concurrentNestedMap import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData @@ -34,7 +34,7 @@ object EffectAddPoints : Effect("add_points") { ) } - private val tracker = nestedMap() + private val tracker = concurrentNestedMap() override fun onEnable( dispatcher: Dispatcher<*>, @@ -53,7 +53,7 @@ object EffectAddPoints : Effect("add_points") { amount ) - player.points[point] += amount + player.points.add(point, amount) } override fun onDisable(dispatcher: Dispatcher<*>, identifiers: Identifiers, holder: ProvidedHolder) { @@ -62,7 +62,7 @@ object EffectAddPoints : Effect("add_points") { val addedPoint = tracker[player.uniqueId][identifiers.uuid] ?: return tracker[player.uniqueId].remove(identifiers.uuid) - player.points[addedPoint.point] -= addedPoint.amount + player.points.add(addedPoint.point, -addedPoint.amount) } private data class AddedPoint( diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAntigravityProjectile.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAntigravityProjectile.kt index a3dcfa381..923acb55b 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAntigravityProjectile.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAntigravityProjectile.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.ProvidedHolder @@ -16,7 +16,7 @@ import java.util.UUID object EffectAntigravityProjectile : Effect("antigravity_projectile") { override val description = "Makes all projectiles the player fires travel in a straight line, unaffected by gravity." override val categories = setOf("combat") - private val players = listMap() + private val players = concurrentListMap() override fun onEnable( dispatcher: Dispatcher<*>, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBlockCommands.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBlockCommands.kt index 8dca438e6..a38d4da24 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBlockCommands.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBlockCommands.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.nestedListMap +import com.willfp.eco.core.map.concurrentNestedListMap import com.willfp.eco.util.formatEco import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher @@ -13,6 +13,7 @@ import com.willfp.libreforge.effects.Identifiers import org.bukkit.event.EventHandler import org.bukkit.event.player.PlayerCommandPreprocessEvent import java.util.UUID +import java.util.concurrent.ConcurrentHashMap object EffectBlockCommands : Effect("block_commands") { override val description = "Prevents the player from using specific commands while the holder is active." @@ -33,8 +34,8 @@ object EffectBlockCommands : Effect("block_commands") { ) } - private val players = nestedListMap() - private val messages = mutableMapOf?>() + private val players = concurrentNestedListMap() + private val messages = ConcurrentHashMap>() override fun onEnable( dispatcher: Dispatcher<*>, @@ -45,7 +46,12 @@ object EffectBlockCommands : Effect("block_commands") { ) { val commands = players[dispatcher.uuid] commands[identifiers.uuid] = config.getStrings("commands") - messages[identifiers.uuid] = config.getStringsOrNull("messages") + val configMessages = config.getStringsOrNull("messages") + if (configMessages != null) { + messages[identifiers.uuid] = configMessages + } else { + messages.remove(identifiers.uuid) + } players[dispatcher.uuid] = commands } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageNearbyEntities.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageNearbyEntities.kt index 56dec119f..efa56f02e 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageNearbyEntities.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageNearbyEntities.kt @@ -20,7 +20,10 @@ object EffectDamageNearbyEntities : Effect>("damage_n override val description = "Deals damage to all nearby entities within a radius." override val categories = setOf("combat") - private val damagedEntities = mutableSetOf() + private val damagedEntitiesByThread = ThreadLocal.withInitial { mutableSetOf() } + + private val damagedEntities: MutableSet + get() = damagedEntitiesByThread.get() override val parameters = setOf( TriggerParameter.LOCATION, TriggerParameter.PLAYER diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectFeatherStep.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectFeatherStep.kt index 91a95334e..2f2ff1a82 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectFeatherStep.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectFeatherStep.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.ProvidedHolder @@ -17,7 +17,7 @@ object EffectFeatherStep : Effect("feather_step") { override val description = "Prevents the player trampling crops." override val categories = setOf("movement", "player") - private val players = listMap() + private val players = concurrentListMap() override fun onEnable( dispatcher: Dispatcher<*>, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectFlight.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectFlight.kt index 89a3cfbe7..d80632ab8 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectFlight.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectFlight.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.ProvidedHolder @@ -17,7 +17,7 @@ object EffectFlight : Effect("flight") { override val shouldReload = false - private val players = listMap() + private val players = concurrentListMap() override fun onEnable( dispatcher: Dispatcher<*>, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGiveGlobalPoints.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGiveGlobalPoints.kt index a576bff5d..c3e4a1937 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGiveGlobalPoints.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGiveGlobalPoints.kt @@ -33,7 +33,7 @@ object EffectGiveGlobalPoints : Effect("give_global_points") { } override fun onTrigger(config: Config, data: TriggerData, compileData: NoCompileData): Boolean { - globalPoints[config.getFormattedString("type", data)] += config.getDoubleFromExpression("amount", data) + globalPoints.add(config.getFormattedString("type", data), config.getDoubleFromExpression("amount", data)) return true } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGivePoints.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGivePoints.kt index f0639c94a..c1eaf43e7 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGivePoints.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGivePoints.kt @@ -37,7 +37,7 @@ object EffectGivePoints : Effect("give_points") { override fun onTrigger(config: Config, data: TriggerData, compileData: NoCompileData): Boolean { val player = data.player ?: return false - player.points[config.getFormattedString("type", data)] += config.getDoubleFromExpression("amount", data) + player.points.add(config.getFormattedString("type", data), config.getDoubleFromExpression("amount", data)) return true } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectInfiniteBucket.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectInfiniteBucket.kt index c980bd182..98ebd80c4 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectInfiniteBucket.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectInfiniteBucket.kt @@ -20,6 +20,7 @@ import org.bukkit.event.player.PlayerBucketEmptyEvent import org.bukkit.event.player.PlayerItemConsumeEvent import org.bukkit.inventory.ItemStack import java.util.UUID +import java.util.concurrent.ConcurrentHashMap object EffectInfiniteBucket : Effect>("infinite_bucket") { override val description = "Prevents the specified bucket types from being emptied, refilling them automatically." @@ -34,7 +35,7 @@ object EffectInfiniteBucket : Effect>("infinite_bucket") { ) } - private val activePlayers = mutableMapOf>() + private val activePlayers = ConcurrentHashMap>() @EventHandler fun onBucketEmpty(event: PlayerBucketEmptyEvent) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectItemMagnet.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectItemMagnet.kt index 2fd09be6b..f3a3709c4 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectItemMagnet.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectItemMagnet.kt @@ -17,6 +17,7 @@ import com.willfp.libreforge.getOrNull import com.willfp.libreforge.plugin import org.bukkit.entity.Entity import org.bukkit.entity.Item +import java.util.concurrent.ConcurrentHashMap object EffectItemMagnet : Effect("item_magnet") { override val description = "Pulls nearby dropped items toward the holder while active, optionally restricted to specific item types." @@ -51,7 +52,7 @@ object EffectItemMagnet : Effect("item_magnet ) } - private val tasks = mutableMapOf() + private val tasks = ConcurrentHashMap() override fun onEnable( dispatcher: Dispatcher<*>, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepInventory.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepInventory.kt index 0bd5dc615..d793c0cf7 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepInventory.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepInventory.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.ProvidedHolder @@ -15,7 +15,7 @@ object EffectKeepInventory : Effect("keep_inventory") { override val description = "Prevents the player from dropping their inventory on death." override val categories = setOf("player", "inventory") - private val players = listMap() + private val players = concurrentListMap() override fun onEnable( dispatcher: Dispatcher<*>, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepItem.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepItem.kt index 43deaa6fa..a9b6c433c 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepItem.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepItem.kt @@ -6,7 +6,7 @@ import com.willfp.eco.core.data.keys.PersistentDataKey import com.willfp.eco.core.data.keys.PersistentDataKeyType import com.willfp.eco.core.data.profile import com.willfp.eco.core.items.Items -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData @@ -41,7 +41,7 @@ object EffectKeepItem : Effect("keep_item") { ) } - private val players = listMap>() + private val players = concurrentListMap>() private val savedItemsKey by lazy { PersistentDataKey( diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepLevel.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepLevel.kt index b32ae6fe6..f8e45d6c0 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepLevel.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectKeepLevel.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.ProvidedHolder @@ -15,7 +15,7 @@ object EffectKeepLevel : Effect("keep_level") { override val description = "Prevents the player from losing their XP levels on death." override val categories = setOf("economy", "player") - private val players = listMap() + private val players = concurrentListMap() override fun onEnable( dispatcher: Dispatcher<*>, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRapidBows.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRapidBows.kt index 3224b9b02..f112a8469 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRapidBows.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRapidBows.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData @@ -31,7 +31,7 @@ object EffectRapidBows : Effect("rapid_bows") { ) } - private val modifiers = listMap() + private val modifiers = concurrentListMap() private const val MAX_FORCE = 3.0 diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplantCrops.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplantCrops.kt index 17a91f2ab..c9ce76ab4 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplantCrops.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplantCrops.kt @@ -2,7 +2,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.integrations.antigrief.AntigriefManager -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData @@ -41,7 +41,7 @@ object EffectReplantCrops : Effect("replant_crops") { ) } - private val players = listMap() + private val players = concurrentListMap() override fun onEnable( dispatcher: Dispatcher<*>, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetUnbreakable.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetUnbreakable.kt index f2d537a86..bb6489db5 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetUnbreakable.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetUnbreakable.kt @@ -18,6 +18,7 @@ import org.bukkit.Material import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import java.util.UUID +import java.util.concurrent.ConcurrentHashMap object EffectSetUnbreakable : Effect("set_unbreakable") { override val description = "Makes an item in a specific slot unbreakable while the effect is active." @@ -50,7 +51,7 @@ object EffectSetUnbreakable : Effect("set_unbreakable") { private val VALID_SLOTS = setOf("holder", "mainhand", "offhand", "helmet", "chestplate", "leggings", "boots") private data class EnabledState(val slot: String, val persistOnDisable: Boolean) - private val enabledStates = HashMap() + private val enabledStates = ConcurrentHashMap() override fun makeCompileData(config: Config, context: ViolationContext): NoCompileData { val slot = config.getStringOrNull("slot") diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTelekinesis.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTelekinesis.kt index 3f2831592..494e6e336 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTelekinesis.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectTelekinesis.kt @@ -4,7 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.drops.DropQueue import com.willfp.eco.core.events.EntityDeathByEntityEvent import com.willfp.eco.core.integrations.antigrief.AntigriefManager -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.eco.util.TelekinesisUtils import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher @@ -43,8 +43,9 @@ object EffectTelekinesis : Effect("telekinesis") { ) } - private val players = listMap() + private val players = concurrentListMap() + @Volatile internal var allowTamedMobKills: Boolean = false private set diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/bossbar/BossBars.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/bossbar/BossBars.kt index 1291e40a0..3c43335a5 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/bossbar/BossBars.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/bossbar/BossBars.kt @@ -1,12 +1,12 @@ package com.willfp.libreforge.effects.impl.bossbar -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.eco.util.asAudience import net.kyori.adventure.bossbar.BossBar import org.bukkit.Bukkit object BossBars { - private val registry = listMap() + private val registry = concurrentListMap() fun register(bossBar: RegistrableBossBar) { if (registry[bossBar.id].any { it.uuid == bossBar.uuid }) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/ChanceMultiplierEffect.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/ChanceMultiplierEffect.kt index e80f1ee59..39197589b 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/ChanceMultiplierEffect.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/ChanceMultiplierEffect.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.effects.templates import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.eco.util.randDouble import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher @@ -26,7 +26,7 @@ abstract class ChanceMultiplierEffect(id: String) : Effect(id) { ) } - private val modifiers = listMap() + private val modifiers = concurrentListMap() override fun onEnable( dispatcher: Dispatcher<*>, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/MultiMultiplierEffect.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/MultiMultiplierEffect.kt index 932021cbe..010ae90b0 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/MultiMultiplierEffect.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/MultiMultiplierEffect.kt @@ -1,8 +1,8 @@ package com.willfp.libreforge.effects.templates import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap -import com.willfp.eco.core.map.nestedListMap +import com.willfp.eco.core.map.concurrentListMap +import com.willfp.eco.core.map.concurrentNestedListMap import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData @@ -26,8 +26,8 @@ abstract class MultiMultiplierEffect(id: String) : Effect() - private val modifiers = nestedListMap() + private val globalModifiers = concurrentListMap() + private val modifiers = concurrentNestedListMap() /** * The key to look for in arguments, e.g. "stat" or "skill". diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/MultiplierEffect.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/MultiplierEffect.kt index 8722b0f51..05c2cc439 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/MultiplierEffect.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/MultiplierEffect.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.effects.templates import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData @@ -24,7 +24,7 @@ abstract class MultiplierEffect(id: String) : Effect(id) { ) } - private val modifiers = listMap() + private val modifiers = concurrentListMap() override fun onEnable( dispatcher: Dispatcher<*>, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerUnclaim.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerUnclaim.kt index 1c590dc3d..5f2564093 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerUnclaim.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/lands/impl/TriggerUnclaim.kt @@ -11,6 +11,7 @@ import me.angeschossen.lands.api.events.land.claiming.selection.LandUnclaimSelec import org.bukkit.Bukkit import org.bukkit.event.EventHandler import java.util.UUID +import java.util.concurrent.ConcurrentHashMap object TriggerUnclaim : Trigger("unclaim") { override val description = "Fires when the player unclaims Lands chunks." @@ -31,7 +32,7 @@ object TriggerUnclaim : Trigger("unclaim") { TriggerParameter.VALUE ) - private val multiChunkUnclaimingPlayers = mutableMapOf() + private val multiChunkUnclaimingPlayers = ConcurrentHashMap() @EventHandler(ignoreCancelled = true) fun handle(event: LandUnclaimSelectionEvent) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/luckperms/impl/EffectLpGivePermission.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/luckperms/impl/EffectLpGivePermission.kt index 0ed8920e0..cfa0bdb77 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/luckperms/impl/EffectLpGivePermission.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/luckperms/impl/EffectLpGivePermission.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.integrations.luckperms.impl import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData @@ -56,7 +56,7 @@ object EffectLpGivePermission : Effect("lp_give_permission") { ) } - private val permissions = listMap() + private val permissions = concurrentListMap() override fun onEnable( dispatcher: Dispatcher<*>, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerBreedListener.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerBreedListener.kt index c15962694..1cda4efc5 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerBreedListener.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerBreedListener.kt @@ -13,6 +13,7 @@ import org.bukkit.event.Listener import org.bukkit.event.entity.CreatureSpawnEvent import org.bukkit.event.player.PlayerInteractEntityEvent import org.bukkit.inventory.ItemStack +import java.util.concurrent.CopyOnWriteArrayList /** * RoseStacker breeds an entire stack at once (cumulative breeding), which it does by cancelling @@ -32,11 +33,15 @@ object RoseStackerBreedListener : Listener { val bredWith: ItemStack ) - private val pending = mutableListOf() + private val pending = CopyOnWriteArrayList() // Captured before RoseStacker consumes the breeding item at EventPriority.HIGH, then read // back at MONITOR once it's known whether RoseStacker has taken the breeding over. - private var captured: PendingBreed? = null + private val capturedByThread = ThreadLocal() + + private var captured: PendingBreed? + get() = capturedByThread.get() + set(value) = capturedByThread.set(value) @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) fun capture(event: PlayerInteractEntityEvent) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerPlaceholderListener.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerPlaceholderListener.kt index 11218c209..3294d9629 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerPlaceholderListener.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/rosestacker/impl/RoseStackerPlaceholderListener.kt @@ -9,6 +9,7 @@ import org.bukkit.event.EventPriority import org.bukkit.event.Listener import org.bukkit.event.entity.EntityDeathEvent import java.util.UUID +import java.util.concurrent.ConcurrentHashMap object RoseStackerPlaceholderListener : Listener { // Long enough to outlive RoseStacker's asynchronous stack loot calculation. @@ -16,7 +17,7 @@ object RoseStackerPlaceholderListener : Listener { // RoseStacker unstacks the entity while handling its death, so the stack size has to be // remembered beforehand for triggers like kill and entity_death to be able to read it. - private val stackSizes = mutableMapOf() + private val stackSizes = ConcurrentHashMap() @EventHandler(priority = EventPriority.LOWEST) fun cacheStackSize(event: EntityDeathEvent) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/vault/impl/EffectGivePermission.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/vault/impl/EffectGivePermission.kt index 9ac680a41..daf475ae4 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/vault/impl/EffectGivePermission.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/vault/impl/EffectGivePermission.kt @@ -1,7 +1,7 @@ package com.willfp.libreforge.integrations.vault.impl import com.willfp.eco.core.config.interfaces.Config -import com.willfp.eco.core.map.listMap +import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData @@ -29,7 +29,7 @@ class EffectGivePermission( ) } - private val permissions = listMap() + private val permissions = concurrentListMap() override fun onEnable( dispatcher: Dispatcher<*>, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/vault/impl/VaultBalancePoller.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/vault/impl/VaultBalancePoller.kt index 95670e52f..b0e835ccd 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/vault/impl/VaultBalancePoller.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/vault/impl/VaultBalancePoller.kt @@ -17,6 +17,7 @@ import java.util.concurrent.ConcurrentHashMap object VaultBalancePoller : Listener { private val lastSeenBalances = ConcurrentHashMap() + @Volatile private var task: EcoTask? = null fun start() { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/slot/SlotTypes.kt b/core/common/src/main/kotlin/com/willfp/libreforge/slot/SlotTypes.kt index d3e1cdd01..440c0c77b 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/slot/SlotTypes.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/slot/SlotTypes.kt @@ -14,6 +14,7 @@ import com.willfp.libreforge.slot.impl.SlotTypeMainhand import com.willfp.libreforge.slot.impl.SlotTypeOffhand object SlotTypes : Registry() { + @Volatile lateinit var baseTypes: List private set @@ -38,8 +39,10 @@ object SlotTypes : Registry() { } // Create new slot type if it doesn't exist - return createNew(id) - ?.apply { register(this) } + return synchronized(this) { + super.get(id) ?: createNew(id) + ?.apply { register(this) } + } } private fun createNew(id: String): SlotType? { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/Trigger.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/Trigger.kt index a1d1ad83a..7e80dcf92 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/Trigger.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/Trigger.kt @@ -53,12 +53,14 @@ abstract class Trigger( /** * Whether this trigger is enabled. */ + @Volatile open var isEnabled: Boolean = false protected set /** * If the listener is registered. */ + @Volatile private var isListenerRegistered = false /** diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerAltClick.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerAltClick.kt index 7960d4efd..445ae493a 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerAltClick.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerAltClick.kt @@ -15,6 +15,7 @@ import org.bukkit.event.EventHandler import org.bukkit.event.block.Action import org.bukkit.event.player.PlayerInteractEvent import java.util.UUID +import java.util.concurrent.ConcurrentHashMap object TriggerAltClick : Trigger("alt_click") { override val description = "Fires when the player right-clicks with an item in hand, or left-clicks with a bow, rod, or trident." @@ -73,7 +74,7 @@ object TriggerAltClick : Trigger("alt_click") { Material.DROPPER ) - private val preventDoubleTriggers = mutableSetOf() + private val preventDoubleTriggers = ConcurrentHashMap.newKeySet() init { BLOCK_BLACKLIST.addAll(Tag.BUTTONS.values) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerChangeBiome.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerChangeBiome.kt index b0959d066..c7bb115f2 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerChangeBiome.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerChangeBiome.kt @@ -10,6 +10,7 @@ import org.bukkit.event.EventHandler import org.bukkit.event.player.PlayerMoveEvent import org.bukkit.event.player.PlayerQuitEvent import java.util.UUID +import java.util.concurrent.ConcurrentHashMap object TriggerChangeBiome : Trigger("change_biome") { override val description = "Fires when the player moves into a different biome." @@ -27,7 +28,7 @@ object TriggerChangeBiome : Trigger("change_biome") { TriggerParameter.TEXT ) - private val lastBiome = mutableMapOf() + private val lastBiome = ConcurrentHashMap() @EventHandler(ignoreCancelled = true) fun handle(event: PlayerMoveEvent) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerCollideWithEntity.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerCollideWithEntity.kt index 8b3a69b7c..96a330a90 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerCollideWithEntity.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerCollideWithEntity.kt @@ -8,6 +8,7 @@ import org.bukkit.entity.LivingEntity import org.bukkit.event.EventHandler import org.bukkit.event.player.PlayerMoveEvent import java.util.UUID +import java.util.concurrent.ConcurrentHashMap object TriggerCollideWithEntity : Trigger("collide_with_entity") { override val description = "Fires when the player physically collides with a nearby entity." @@ -29,7 +30,7 @@ object TriggerCollideWithEntity : Trigger("collide_with_entity") { TriggerParameter.LOCATION ) - private val cooldown = mutableMapOf, Long>() + private val cooldown = ConcurrentHashMap, Long>() private const val COLLISION_COOLDOWN_MS = 500L @EventHandler(ignoreCancelled = true) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGainXp.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGainXp.kt index f58c9351e..82d77f20f 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGainXp.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGainXp.kt @@ -9,6 +9,7 @@ import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerParameter import org.bukkit.event.EventHandler import java.util.UUID +import java.util.concurrent.ConcurrentHashMap object TriggerGainXp : Trigger("gain_xp") { override val description = "Fires when the player gains experience points." @@ -25,7 +26,7 @@ object TriggerGainXp : Trigger("gain_xp") { TriggerParameter.VALUE ) - private val telekinesisGranted = mutableSetOf() + private val telekinesisGranted = ConcurrentHashMap.newKeySet() @EventHandler(ignoreCancelled = true) fun handle(event: NaturalExpGainEvent) { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGroupCustom.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGroupCustom.kt index 7f1a38b81..0eb80abc1 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGroupCustom.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerGroupCustom.kt @@ -5,15 +5,16 @@ import com.willfp.libreforge.triggers.Trigger import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerGroup import com.willfp.libreforge.triggers.TriggerParameter +import java.util.concurrent.ConcurrentHashMap object TriggerGroupCustom : TriggerGroup("custom") { - private val registry = mutableMapOf() + private val registry = ConcurrentHashMap() val knownTriggers: Set get() = registry.keys override fun create(value: String): TriggerCustom { - return registry.getOrPut(value) { TriggerCustom(value) } + return registry.computeIfAbsent(value) { TriggerCustom(value) } } class TriggerCustom(id: String) : Trigger("custom_$id") { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerMeleeAttack.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerMeleeAttack.kt index 23df562eb..f15084bed 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerMeleeAttack.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerMeleeAttack.kt @@ -43,7 +43,10 @@ object TriggerMeleeAttack : Trigger("melee_attack") { TriggerParameter.ALT_VALUE ) - private val processedEvents = mutableSetOf() + private val processedEventsByThread = ThreadLocal.withInitial { mutableSetOf() } + + private val processedEvents: MutableSet + get() = processedEventsByThread.get() fun registerPaperExclusiveListeners() { val mcVersion = Bukkit.getServer().bukkitVersion.split("-").getOrNull(0)?.split(".")?.getOrNull(0)?.toInt() ?: 0 From 523fb843ce54f74dcab54eeecd7cff0d9a26ad95 Mon Sep 17 00:00:00 2001 From: Callum Johnson Date: Thu, 8 Oct 2026 21:51:38 +0100 Subject: [PATCH 4/9] feat: Folia region ownership checks --- .../com/willfp/libreforge/BlockBreaker.kt | 2 + .../kotlin/com/willfp/libreforge/Regions.kt | 33 +++++++++ .../libreforge/commands/CommandGetItemData.kt | 67 ++++++++++--------- .../commands/CommandItemLevelGiveLevel.kt | 27 ++++---- .../commands/CommandItemLevelGiveXp.kt | 27 ++++---- .../libreforge/commands/CommandTrigger.kt | 21 +++--- .../commands/custom/CustomCommand.kt | 15 +++-- .../libreforge/conditions/ConditionBlock.kt | 12 ++-- .../conditions/impl/ConditionIsStorm.kt | 10 ++- .../conditions/impl/ConditionNearBlock.kt | 6 ++ .../conditions/impl/ConditionNearEntity.kt | 5 ++ .../libreforge/effects/impl/EffectAOE.kt | 3 +- .../effects/impl/EffectAOEBlocks.kt | 3 +- .../effects/impl/EffectAllPlayers.kt | 11 +-- .../impl/EffectAntigravityProjectile.kt | 5 +- .../effects/impl/EffectArrowRing.kt | 5 ++ .../effects/impl/EffectArrowStorm.kt | 5 ++ .../libreforge/effects/impl/EffectBleed.kt | 14 ++-- .../libreforge/effects/impl/EffectBlink.kt | 3 +- .../effects/impl/EffectChainLightning.kt | 5 ++ .../impl/EffectDamageNearbyEntities.kt | 5 ++ .../libreforge/effects/impl/EffectDrill.kt | 5 ++ .../effects/impl/EffectFrostNova.kt | 5 ++ .../libreforge/effects/impl/EffectHoming.kt | 5 ++ .../effects/impl/EffectItemMagnet.kt | 5 ++ .../effects/impl/EffectMineRadius.kt | 5 ++ .../effects/impl/EffectMineRadiusOneDeep.kt | 5 ++ .../effects/impl/EffectMineShape.kt | 5 ++ .../effects/impl/EffectRandomPlayer.kt | 11 +-- .../effects/impl/EffectRandomTeleport.kt | 15 +++++ .../impl/EffectRemoveNearbyEntities.kt | 5 ++ .../effects/impl/EffectReplaceNear.kt | 5 ++ .../effects/impl/EffectSetNearbyBlocks.kt | 6 ++ .../effects/impl/EffectShockwave.kt | 5 ++ .../libreforge/effects/impl/EffectSoulRip.kt | 5 ++ .../effects/impl/EffectSpawnMobs.kt | 6 ++ .../libreforge/effects/impl/EffectSwarm.kt | 5 ++ .../libreforge/effects/impl/EffectVortex.kt | 5 ++ .../effects/impl/aoe/impl/AOEShapeBeam.kt | 9 +++ .../effects/impl/aoe/impl/AOEShapeCircle.kt | 15 ++++- .../effects/impl/aoe/impl/AOEShapeCone.kt | 17 ++++- .../impl/aoe/impl/AOEShapeOffsetCircle.kt | 21 ++++-- .../impl/aoe/impl/AOEShapeScanInFront.kt | 17 ++++- .../effects/templates/MineBlockEffect.kt | 11 +-- .../integrations/smartpets/SmartPetsUtils.kt | 3 +- .../mutators/impl/MutatorLocationToCursor.kt | 3 +- .../impl/MutatorVictimToNearestEntity.kt | 5 ++ .../triggers/impl/TriggerAltClick.kt | 5 +- 48 files changed, 378 insertions(+), 115 deletions(-) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/BlockBreaker.kt b/core/common/src/main/kotlin/com/willfp/libreforge/BlockBreaker.kt index 705b599e0..caee81eac 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/BlockBreaker.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/BlockBreaker.kt @@ -18,6 +18,8 @@ interface BlockBreaker { /** * Break [blocks] on behalf of this dispatcher. * + * Called on the thread that owns the blocks: on Folia, blocks in other regions are left out. + * * @param data The trigger the breaking effect is running for, which carries * the drop event the drops belong in, where one exists. */ diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt b/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt index 26d0799d3..d281e08fd 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt @@ -6,6 +6,7 @@ import com.willfp.eco.core.Prerequisite import org.bukkit.Bukkit import org.bukkit.Location import org.bukkit.entity.Entity +import kotlin.math.ceil /** * The one place libreforge decides which thread may touch what. @@ -64,6 +65,38 @@ internal object Regions { fun canReach(location: Location): Boolean = Eco.get().isOwnedByCurrentRegion(location) + /** + * If every chunk within [radius] blocks of [center] can be touched from here, for area scans + * such as `getNearbyEntities`, which Folia refuses when they reach into another region. + * Always `true` off Folia. + */ + fun canReach(center: Location, radius: Double): Boolean { + if (!isFolia) { + return true + } + + if (center.world == null) { + return false + } + + val radiusChunks = ceil(radius / 16).toInt() + 1 + return Bukkit.isOwnedByCurrentRegion(center, radiusChunks) + } + + /** + * The longest distance up to [distance] that a ray or scan from [origin] can cover without + * leaving this region. Always [distance] off Folia. + */ + fun reachableDistance(origin: Location, distance: Double): Double { + var reachable = distance + + while (reachable >= 1 && !canReach(origin, reachable)) { + reachable /= 2 + } + + return if (reachable >= 1) reachable else 0.0 + } + /** * If [entity] can be touched from here. Always `true` off Folia. */ diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandGetItemData.kt b/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandGetItemData.kt index 43838e20f..74547aebd 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandGetItemData.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandGetItemData.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.commands import com.willfp.eco.core.command.impl.Subcommand import com.willfp.eco.core.items.Items import com.willfp.eco.util.asAudience +import com.willfp.libreforge.Regions import com.willfp.libreforge.isEcoEmpty import com.willfp.libreforge.plugin import com.willfp.libreforge.slot.SlotTypes @@ -53,43 +54,45 @@ internal object CommandGetItemData : Subcommand( return } - val items = slotType.getItemSlots(player) - .distinct() - .sorted() - .map { slot -> slot to player.inventory.getItem(slot) } - .filterNot { (_, item) -> item.isEcoEmpty } + Regions.runOwned(player) { + val items = slotType.getItemSlots(player) + .distinct() + .sorted() + .map { slot -> slot to player.inventory.getItem(slot) } + .filterNot { (_, item) -> item.isEcoEmpty } + + if (items.isEmpty()) { + sender.sendMessage( + plugin.langYml.getMessage("empty-slot") + .replace("%player%", player.name) + .replace("%slot%", slotString) + ) + return@runOwned + } - if (items.isEmpty()) { sender.sendMessage( - plugin.langYml.getMessage("empty-slot") + plugin.langYml.getMessage("item-data-header") .replace("%player%", player.name) - .replace("%slot%", slotString) + .replace("%slot%", slotType.id) ) - return - } - sender.sendMessage( - plugin.langYml.getMessage("item-data-header") - .replace("%player%", player.name) - .replace("%slot%", slotType.id) - ) - - val audience = sender.asAudience() - - for ((slot, item) in items) { - val compactSnbt = Items.toSNBT(item!!) - .removeRootField("DataVersion") - .compactOutsideStrings() - val escapedSnbt = compactSnbt.escapeAsJsonStringLiteral() - - audience.sendMessage( - Component.text("[$slot] ", NamedTextColor.GRAY) - .append( - Component.text(escapedSnbt, NamedTextColor.YELLOW) - .clickEvent(ClickEvent.copyToClipboard(escapedSnbt)) - .hoverEvent(HoverEvent.showText(Component.text("Click to copy", NamedTextColor.GRAY))) - ) - ) + val audience = sender.asAudience() + + for ((slot, item) in items) { + val compactSnbt = Items.toSNBT(item!!) + .removeRootField("DataVersion") + .compactOutsideStrings() + val escapedSnbt = compactSnbt.escapeAsJsonStringLiteral() + + audience.sendMessage( + Component.text("[$slot] ", NamedTextColor.GRAY) + .append( + Component.text(escapedSnbt, NamedTextColor.YELLOW) + .clickEvent(ClickEvent.copyToClipboard(escapedSnbt)) + .hoverEvent(HoverEvent.showText(Component.text("Click to copy", NamedTextColor.GRAY))) + ) + ) + } } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandItemLevelGiveLevel.kt b/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandItemLevelGiveLevel.kt index 63620da12..36bf5efdd 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandItemLevelGiveLevel.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandItemLevelGiveLevel.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.commands import com.willfp.eco.core.command.impl.Subcommand import com.willfp.eco.core.placeholder.context.PlaceholderContext +import com.willfp.libreforge.Regions import com.willfp.libreforge.levels.LevelTypes import com.willfp.libreforge.levels.levels import com.willfp.libreforge.plugin @@ -58,21 +59,23 @@ internal object CommandItemLevelGiveLevel : Subcommand( return } - val item = player.inventory.itemInMainHand + Regions.runOwned(player) { + val item = player.inventory.itemInMainHand - if (item.type.isAir) { - sender.sendMessage(plugin.langYml.getMessage("must-have-item")) - return - } + if (item.type.isAir) { + sender.sendMessage(plugin.langYml.getMessage("must-have-item")) + return@runOwned + } - item.levels.gainLevels(levelType, amount, PlaceholderContext(player)) + item.levels.gainLevels(levelType, amount, PlaceholderContext(player)) - sender.sendMessage( - plugin.langYml.getMessage("item-level-given") - .replace("%playername%", player.name) - .replace("%level%", levelIdString) - .replace("%amount%", amount.toString()) - ) + sender.sendMessage( + plugin.langYml.getMessage("item-level-given") + .replace("%playername%", player.name) + .replace("%level%", levelIdString) + .replace("%amount%", amount.toString()) + ) + } } override fun tabComplete(sender: CommandSender, args: List): List { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandItemLevelGiveXp.kt b/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandItemLevelGiveXp.kt index 7bceddfe5..0fff4d315 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandItemLevelGiveXp.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandItemLevelGiveXp.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.commands import com.willfp.eco.core.command.impl.Subcommand import com.willfp.eco.core.placeholder.context.PlaceholderContext import com.willfp.eco.util.toNiceString +import com.willfp.libreforge.Regions import com.willfp.libreforge.levels.LevelTypes import com.willfp.libreforge.levels.levels import com.willfp.libreforge.plugin @@ -59,21 +60,23 @@ internal object CommandItemLevelGiveXp : Subcommand( return } - val item = player.inventory.itemInMainHand + Regions.runOwned(player) { + val item = player.inventory.itemInMainHand - if (item.type.isAir) { - sender.sendMessage(plugin.langYml.getMessage("must-have-item")) - return - } + if (item.type.isAir) { + sender.sendMessage(plugin.langYml.getMessage("must-have-item")) + return@runOwned + } - item.levels.gainXP(levelType, amount, PlaceholderContext(player)) + item.levels.gainXP(levelType, amount, PlaceholderContext(player)) - sender.sendMessage( - plugin.langYml.getMessage("item-level-xp-given") - .replace("%playername%", player.name) - .replace("%level%", levelIdString) - .replace("%amount%", amount.toNiceString()) - ) + sender.sendMessage( + plugin.langYml.getMessage("item-level-xp-given") + .replace("%playername%", player.name) + .replace("%level%", levelIdString) + .replace("%amount%", amount.toNiceString()) + ) + } } override fun tabComplete(sender: CommandSender, args: List): List { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandTrigger.kt b/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandTrigger.kt index ed3bbde57..0bf85b076 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandTrigger.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/commands/CommandTrigger.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.command.impl.Subcommand import com.willfp.eco.util.formatEco import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.GlobalDispatcher +import com.willfp.libreforge.Regions import com.willfp.libreforge.get import com.willfp.libreforge.plugin import com.willfp.libreforge.toDispatcher @@ -63,16 +64,20 @@ internal object CommandTrigger : Subcommand( val value = args.getOrNull(2)?.formatEco(Bukkit.getPlayer(dispatcherName), true) ?.toDoubleOrNull() + val customTrigger = TriggerGroupCustom.create(trigger) + for (dispatcher in dispatchers) { - TriggerGroupCustom.create(trigger).dispatch( - dispatcher, - TriggerData( - player = dispatcher.get(), - victim = dispatcher.get(), - location = dispatcher.get()?.location, - value = value ?: 1.0 + Regions.runOwned(dispatcher) { + customTrigger.dispatch( + dispatcher, + TriggerData( + player = dispatcher.get(), + victim = dispatcher.get(), + location = dispatcher.get()?.location, + value = value ?: 1.0 + ) ) - ) + } } sender.sendMessage(plugin.langYml.getMessage("triggered").replace("%id%", trigger)) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/commands/custom/CustomCommand.kt b/core/common/src/main/kotlin/com/willfp/libreforge/commands/custom/CustomCommand.kt index 0eddbbc74..47b785f82 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/commands/custom/CustomCommand.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/commands/custom/CustomCommand.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.command.impl.PluginCommand import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.registry.KRegistrable +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.effects.Effects import com.willfp.libreforge.effects.executors.impl.NormalExecutorFactory @@ -64,13 +65,15 @@ class CustomCommand( val player = commandPlayer(sender, args) ?: return val value = commandValue(sender, args.getOrNull(valueArgIndex)) ?: return - commandEffects?.trigger( - player.toDispatcher(), - TriggerData( - player = player, - value = value + Regions.runOwned(player) { + commandEffects?.trigger( + player.toDispatcher(), + TriggerData( + player = player, + value = value + ) ) - ) + } } override fun tabComplete(sender: CommandSender, args: MutableList): List { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/conditions/ConditionBlock.kt b/core/common/src/main/kotlin/com/willfp/libreforge/conditions/ConditionBlock.kt index 69b5e0136..375062212 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/conditions/ConditionBlock.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/conditions/ConditionBlock.kt @@ -6,10 +6,10 @@ import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.DynamicConfigs import com.willfp.libreforge.HolderStates import com.willfp.libreforge.ProvidedHolder +import com.willfp.libreforge.Regions import com.willfp.libreforge.applyHolder import com.willfp.libreforge.effects.Chain import com.willfp.libreforge.plugin -import org.bukkit.Bukkit /** * A single condition config block. @@ -37,14 +37,14 @@ class ConditionBlock internal constructor( fun isMet(dispatcher: Dispatcher<*>, holder: ProvidedHolder): Boolean { /* - Conditions are not thread-safe, so we must run them on the main thread. - However, conditions being met or not needs to work on packet processing threads, - so the synchronous result for tracked dispatchers is kept and returned if we are not - on the main thread. + Conditions are not thread-safe, so we must run them on the thread that owns the + dispatcher (the main thread, off Folia). However, conditions being met or not needs to + work on packet processing threads, so the synchronous result for tracked dispatchers is + kept and returned if we are not on that thread. */ - if (!Bukkit.isPrimaryThread()) { + if (!Regions.owns(dispatcher)) { return HolderStates.conditionResult(dispatcher, this) ?: plugin.configYml.getBool("conditions.default-state-off-main-thread") } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/conditions/impl/ConditionIsStorm.kt b/core/common/src/main/kotlin/com/willfp/libreforge/conditions/impl/ConditionIsStorm.kt index 3b37bfd04..65b35e92a 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/conditions/impl/ConditionIsStorm.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/conditions/impl/ConditionIsStorm.kt @@ -4,9 +4,11 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.ProvidedHolder +import com.willfp.libreforge.Regions import com.willfp.libreforge.conditions.Condition import com.willfp.libreforge.toDispatcher import com.willfp.libreforge.updateEffects +import org.bukkit.Bukkit import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.weather.WeatherChangeEvent @@ -26,7 +28,13 @@ object ConditionIsStorm: Condition("is_storm") { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) fun handle(event: WeatherChangeEvent) { - for (entity in event.world.entities) { + val entities = if (Regions.isFolia) { + Bukkit.getOnlinePlayers().filter { it.world == event.world } + } else { + event.world.entities + } + + for (entity in entities) { entity.toDispatcher().updateEffects() } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/conditions/impl/ConditionNearBlock.kt b/core/common/src/main/kotlin/com/willfp/libreforge/conditions/impl/ConditionNearBlock.kt index 33b7effda..e8624d7dd 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/conditions/impl/ConditionNearBlock.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/conditions/impl/ConditionNearBlock.kt @@ -5,6 +5,7 @@ import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.ProvidedHolder +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.conditions.Condition import com.willfp.libreforge.get @@ -49,6 +50,11 @@ object ConditionNearBlock : Condition("near_block") { location.blockY + y, location.blockZ + z ) + + if (!Regions.canReach(block.location)) { + continue + } + if (block.type == targetMaterial) return true } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/conditions/impl/ConditionNearEntity.kt b/core/common/src/main/kotlin/com/willfp/libreforge/conditions/impl/ConditionNearEntity.kt index e767eda63..9a5c03556 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/conditions/impl/ConditionNearEntity.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/conditions/impl/ConditionNearEntity.kt @@ -7,6 +7,7 @@ import com.willfp.eco.core.entities.impl.EmptyTestableEntity import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.ProvidedHolder +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.conditions.Condition @@ -47,6 +48,10 @@ object ConditionNearEntity : Condition>("near_entity" val location = dispatcher.location ?: return false val radius = config.getDoubleFromExpression("radius", dispatcher.get()) + if (!Regions.canReach(location, radius)) { + return false + } + // Default to require there to be at least 1 entity if no minimum is defined by the user. val nearbyEntityMinimumRequirement = config.getIntFromExpression("amount", dispatcher.get()).coerceAtLeast(1) val nearbyEntitiesWhichMatchCriteria = location.world.getNearbyEntities(location, radius, radius, radius).count { diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAOE.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAOE.kt index 0114e1210..ddf424bdc 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAOE.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAOE.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect @@ -62,7 +63,7 @@ object EffectAOE : Effect("aoe") { location.direction.toFloat3(), location.world, data - ).filterNot { it.uniqueId == data.dispatcher.uuid }) { + ).filter { Regions.canReach(it) }.filterNot { it.uniqueId == data.dispatcher.uuid }) { compileData.chain ?.trigger( data.copy( diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAOEBlocks.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAOEBlocks.kt index 8b5f1b4cf..7991ee0ee 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAOEBlocks.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAOEBlocks.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect @@ -62,7 +63,7 @@ object EffectAOEBlocks : Effect("aoe_blocks") { location.direction.toFloat3(), location.world, data - ).filterNot { it.isEmpty }) { + ).filter { Regions.canReach(it.location) }.filterNot { it.isEmpty }) { compileData.chain ?.trigger( data.copy( diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAllPlayers.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAllPlayers.kt index 84d8e3e89..02c1db7fe 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAllPlayers.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAllPlayers.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Chain @@ -35,10 +36,12 @@ object EffectAllPlayers : Effect("all_players") { override fun onTrigger(config: Config, data: TriggerData, compileData: Chain?): Boolean { for (player in Bukkit.getOnlinePlayers()) { - compileData?.trigger( - data.copy(player = player) - .dispatch(player.toDispatcher()), - ) + Regions.runOwned(player) { + compileData?.trigger( + data.copy(player = player) + .dispatch(player.toDispatcher()), + ) + } } return true diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAntigravityProjectile.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAntigravityProjectile.kt index 923acb55b..02d6bdc71 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAntigravityProjectile.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectAntigravityProjectile.kt @@ -5,6 +5,7 @@ import com.willfp.eco.core.map.concurrentListMap import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.ProvidedHolder +import com.willfp.libreforge.Regions import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.effects.Identifiers import com.willfp.libreforge.plugin @@ -45,8 +46,8 @@ object EffectAntigravityProjectile : Effect("antigravity_projecti return@runTimer } val velocity = projectile.velocity - val nextChunk = projectile.location.add(velocity).chunk - if (!nextChunk.isLoaded) { + val next = projectile.location.add(velocity) + if (!Regions.canReach(next) || !next.chunk.isLoaded) { projectile.setGravity(true) task.cancel() return@runTimer diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectArrowRing.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectArrowRing.kt index d7db7baef..994a13b1a 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectArrowRing.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectArrowRing.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.integrations.antigrief.AntigriefManager import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getDoubleFromExpression @@ -70,6 +71,10 @@ object EffectArrowRing : Effect("arrow_ring") { val damage = config.getDoubleFromExpression("arrow_damage", data) val flameTicks = config.getIntFromExpression("fire_ticks", data) + if (!Regions.canReach(location, radius + 0.5)) { + return false + } + if (data.player != null) { if (!location.getNearbyPlayers(radius + 0.5f).all { AntigriefManager.canInjure(data.player, it) }) { return false diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectArrowStorm.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectArrowStorm.kt index 10c21de15..efa2a5eb6 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectArrowStorm.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectArrowStorm.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.util.NumberUtils import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getDoubleFromExpression @@ -68,6 +69,10 @@ object EffectArrowStorm : Effect("arrow_storm") { val respectFlame = config.getBoolOrNull("respect_flame") ?: true val flame = respectFlame && (data.item?.containsEnchantment(org.bukkit.enchantments.Enchantment.FLAME) ?: false) + if (!Regions.canReach(location, spread)) { + return false + } + repeat(amount) { val spawnLoc = location.clone().add( NumberUtils.randFloat(-spread, spread), diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBleed.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBleed.kt index 99e80f3df..f632527e2 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBleed.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBleed.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.Prerequisite import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.dealDamage import com.willfp.libreforge.effects.Effect @@ -79,11 +80,14 @@ object EffectBleed : Effect("bleed") { victim.killer = data.player } - if (data.player != null) { - TriggerKill.force( - data.player, - victim - ) + val player = data.player + if (player != null) { + Regions.runOwned(player) { + TriggerKill.force( + player, + victim + ) + } } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBlink.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBlink.kt index 0b6fd1102..581c25bb0 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBlink.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectBlink.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.Prerequisite import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getIntFromExpression @@ -37,7 +38,7 @@ object EffectBlink : Effect("blink") { for (i in 1..distance) { val next = player.location.clone().add(direction.clone().multiply(i)) - if (!next.block.isPassable) break + if (!Regions.canReach(next) || !next.block.isPassable) break destination = next } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectChainLightning.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectChainLightning.kt index 67d623929..a4c2e86e9 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectChainLightning.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectChainLightning.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.dealDamage import com.willfp.libreforge.effects.Effect @@ -74,6 +75,10 @@ object EffectChainLightning : Effect("chain_lightning") { current.world.strikeLightningEffect(current.location) current.dealDamage(damage, source, trueDamage) + if (!Regions.canReach(current.location, range)) { + return true + } + val next = current.getNearbyEntities(range, range, range) .filterIsInstance() .filter { it !in hit && it != player } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageNearbyEntities.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageNearbyEntities.kt index efa56f02e..e4911f640 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageNearbyEntities.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDamageNearbyEntities.kt @@ -5,6 +5,7 @@ import com.willfp.eco.core.entities.Entities import com.willfp.eco.core.entities.TestableEntity import com.willfp.eco.core.integrations.antigrief.AntigriefManager import com.willfp.libreforge.ArgType +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.dealDamage @@ -86,6 +87,10 @@ object EffectDamageNearbyEntities : Effect>("damage_n val damage = config.getDoubleFromExpression("damage", data) val damageSelf = config.getBoolOrNull("damage_self") ?: true + if (!Regions.canReach(location, radius)) { + return false + } + for (entity in world.getNearbyEntities(location, radius, radius, radius)) { if (entity.hasMetadata("ignore-nearby-damage") || damagedEntities.contains(entity.uniqueId)) { continue diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDrill.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDrill.kt index 147e8be74..4bda35e3f 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDrill.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectDrill.kt @@ -6,6 +6,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.integrations.antigrief.AntigriefManager import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.templates.MineBlockEffect import com.willfp.libreforge.getFormattedStrings @@ -96,6 +97,10 @@ object EffectDrill : MineBlockEffect("drill") { val offset = forwardAxis.clone().multiply(i) val toBreak = block.world.getBlockAt(block.location.clone().add(offset)) + if (!Regions.canReach(toBreak.location)) { + continue + } + if (blacklist.matches(toBreak)) { continue } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectFrostNova.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectFrostNova.kt index 2d0a0dc64..4d1834fb8 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectFrostNova.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectFrostNova.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getDoubleFromExpression @@ -61,6 +62,10 @@ object EffectFrostNova : Effect("frost_nova") { val slowDuration = config.getOrNull("slow_duration") { getIntFromExpression(it, data) } ?: 0 val slowAmplifier = config.getOrNull("slow_amplifier") { getIntFromExpression(it, data) } ?: 0 + if (!Regions.canReach(location, radius)) { + return false + } + location.world?.getNearbyEntities(location, radius, radius, radius) ?.filterIsInstance() ?.filter { it != player } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectHoming.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectHoming.kt index 803ebf5e1..1faf2a5a2 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectHoming.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectHoming.kt @@ -7,6 +7,7 @@ import com.willfp.eco.core.integrations.antigrief.AntigriefManager import com.willfp.libreforge.ArgType import com.willfp.libreforge.ConfigViolation import com.willfp.libreforge.ConfigWarning +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect @@ -300,6 +301,10 @@ object EffectHoming : Effect("homing") { val headingDirection = heading.normalize() val rangeSquared = (range * range).toFloat() + if (!Regions.canReach(arrow.location, range)) { + return null + } + return arrow.getNearbyEntities(range, range, range) .asSequence() .filterIsInstance() diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectItemMagnet.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectItemMagnet.kt index f3a3709c4..e5e7d52b5 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectItemMagnet.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectItemMagnet.kt @@ -7,6 +7,7 @@ import com.willfp.eco.core.scheduling.EcoTask import com.willfp.libreforge.ArgType import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.ProvidedHolder +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect @@ -72,6 +73,10 @@ object EffectItemMagnet : Effect("item_magnet val location = dispatcher.location ?: return@runTimer val world = location.world ?: return@runTimer + if (!Regions.canReach(location, radius)) { + return@runTimer + } + for (entity in world.getNearbyEntities(location, radius, radius, radius)) { val item = entity as? Item ?: continue val stack = item.itemStack diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectMineRadius.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectMineRadius.kt index 148a34a6b..a4d79e1a0 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectMineRadius.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectMineRadius.kt @@ -6,6 +6,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.integrations.antigrief.AntigriefManager import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.templates.MineBlockEffect import com.willfp.libreforge.getFormattedStrings @@ -119,6 +120,10 @@ object EffectMineRadius : MineBlockEffect("mine_radius") { val blocks = mutableSetOf() for (toBreak in candidates) { + if (!Regions.canReach(toBreak.location)) { + continue + } + if (toBreak.location.blockY !in block.world.minHeight..block.world.maxHeight) { continue } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectMineRadiusOneDeep.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectMineRadiusOneDeep.kt index c1253a0c1..0c16a7f04 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectMineRadiusOneDeep.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectMineRadiusOneDeep.kt @@ -6,6 +6,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.integrations.antigrief.AntigriefManager import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.templates.MineBlockEffect import com.willfp.libreforge.getFormattedStrings @@ -137,6 +138,10 @@ object EffectMineRadiusOneDeep : MineBlockEffect("mine_radius_one block.location.clone().add(x.toDouble(), y.toDouble(), z.toDouble()) ) + if (!Regions.canReach(toBreak.location)) { + continue + } + if (toBreak.location.blockY !in block.world.minHeight..block.world.maxHeight) { continue } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectMineShape.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectMineShape.kt index 6fabd121e..721fb760f 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectMineShape.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectMineShape.kt @@ -6,6 +6,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.integrations.antigrief.AntigriefManager import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.templates.MineBlockEffect import com.willfp.libreforge.getFormattedStrings @@ -147,6 +148,10 @@ object EffectMineShape : MineBlockEffect("mine_shape") { ) ) + if (!Regions.canReach(targetBlock.location)) { + continue + } + if (targetBlock.location.blockY !in triggerBlock.world.minHeight..triggerBlock.world.maxHeight) { continue } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRandomPlayer.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRandomPlayer.kt index 1654bfd47..c434a6215 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRandomPlayer.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRandomPlayer.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Chain @@ -29,10 +30,12 @@ object EffectRandomPlayer : Effect("random_player") { override fun onTrigger(config: Config, data: TriggerData, compileData: Chain?): Boolean { Bukkit.getOnlinePlayers().randomOrNull()?.let { player -> - compileData?.trigger( - data.copy(player = player) - .dispatch(player.toDispatcher()), - ) + Regions.runOwned(player) { + compileData?.trigger( + data.copy(player = player) + .dispatch(player.toDispatcher()), + ) + } } return true diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRandomTeleport.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRandomTeleport.kt index 7eaefc4d4..87c8673d0 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRandomTeleport.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRandomTeleport.kt @@ -5,12 +5,17 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.util.NumberUtils import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.getIntFromExpression import com.willfp.libreforge.getOrNull import com.willfp.libreforge.effects.Effect +import com.willfp.libreforge.plugin import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerParameter +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.entity.Player object EffectRandomTeleport : Effect("random_teleport") { override val description = "Teleports the player to a random location within a radius, landing on the highest safe block." @@ -54,6 +59,16 @@ object EffectRandomTeleport : Effect("random_teleport") { loc.x += offsetX loc.z += offsetZ + + if (!Regions.canReach(loc)) { + plugin.scheduler.at(loc).run { teleportToSurface(player, world, loc) } + return true + } + + return teleportToSurface(player, world, loc) + } + + private fun teleportToSurface(player: Player, world: World, loc: Location): Boolean { loc.y = world.getHighestBlockYAt(loc).toDouble() + 1 if (loc.y < world.minHeight || loc.y > world.maxHeight) return false diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRemoveNearbyEntities.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRemoveNearbyEntities.kt index 967756139..a4b00db52 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRemoveNearbyEntities.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectRemoveNearbyEntities.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.entities.Entities import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getDoubleFromExpression @@ -52,6 +53,10 @@ object EffectRemoveNearbyEntities : Effect("remove_nearby_entitie var removed = false + if (!Regions.canReach(location, radius)) { + return false + } + for (entity in world.getNearbyEntities(location, radius, radius, radius)) { if (entity is Player) { continue diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplaceNear.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplaceNear.kt index 6558293cd..2c669a38e 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplaceNear.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectReplaceNear.kt @@ -7,6 +7,7 @@ import com.willfp.eco.core.integrations.antigrief.AntigriefManager import com.willfp.eco.core.items.Items import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getFormattedString @@ -119,6 +120,10 @@ object EffectReplaceNear : Effect("replace_near") { block.location.clone().add(x.toDouble(), y.toDouble(), z.toDouble()) ) + if (!Regions.canReach(toReplace.location)) { + continue + } + if (blacklist.matches(toReplace)) { continue } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetNearbyBlocks.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetNearbyBlocks.kt index d87757b4b..6217b3dac 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetNearbyBlocks.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSetNearbyBlocks.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.blocks.Blocks import com.willfp.eco.core.blocks.TestableBlock import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect @@ -43,6 +44,11 @@ object EffectSetNearbyBlocks : Effect("set_nearby_blocks") { for (y in -radius..radius) { for (z in -radius..radius) { val blockLocation = location.clone().add(x.toDouble(), y.toDouble(), z.toDouble()) + + if (!Regions.canReach(blockLocation)) { + continue + } + compileData.place(blockLocation) } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectShockwave.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectShockwave.kt index 7bac28136..d462b16bd 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectShockwave.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectShockwave.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.dealDamage import com.willfp.libreforge.effects.Effect @@ -73,6 +74,10 @@ object EffectShockwave : Effect("shockwave") { val trueDamage = config.getBool("true_damage") val source = if (config.getBool("use_source")) player else null + if (!Regions.canReach(origin, radius)) { + return false + } + val hit = mutableSetOf() var pulse = 0 diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSoulRip.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSoulRip.kt index 2bf1f5641..e4d694ad5 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSoulRip.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSoulRip.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.dealDamage import com.willfp.libreforge.effects.Effect @@ -66,6 +67,10 @@ object EffectSoulRip : Effect("soul_rip") { val trueDamage = config.getBool("true_damage") val source = if (config.getBool("use_source")) player else null + if (!Regions.canReach(location, radius)) { + return false + } + val targets = location.world?.getNearbyEntities(location, radius, radius, radius) ?.filterIsInstance() ?.filter { it != player } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSpawnMobs.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSpawnMobs.kt index fcda8c245..372838cd5 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSpawnMobs.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSpawnMobs.kt @@ -5,6 +5,7 @@ import com.willfp.eco.core.entities.Entities import com.willfp.eco.util.NumberUtils import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.getDoubleFromExpression @@ -95,6 +96,11 @@ object EffectSpawnMobs : Effect("spawn_mobs") { NumberUtils.randFloat(0.0, range), NumberUtils.randFloat(-range, range) ) + + if (!Regions.canReach(locationToSpawn)) { + continue + } + val mob = entityType.spawn(locationToSpawn) as Mob val healthAttr = mob.getAttribute(Attribute.MAX_HEALTH) ?: continue healthAttr.baseValue = health diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSwarm.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSwarm.kt index 2ff65a10d..3bddc5f72 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSwarm.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectSwarm.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.entities.Entities import com.willfp.eco.core.entities.TestableEntity import com.willfp.libreforge.ArgType +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect @@ -35,6 +36,10 @@ object EffectSwarm : Effect?>("swarm") { val radius = config.getDoubleFromExpression("radius", data) + if (!Regions.canReach(victim.location, radius)) { + return false + } + victim.getNearbyEntities(radius, radius, radius) .filterIsInstance() .filter { compileData?.any { t -> t.matches(it) } ?: true } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectVortex.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectVortex.kt index d249c4c3c..d3118df77 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectVortex.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectVortex.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.entities.Entities import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.dealDamage import com.willfp.libreforge.effects.Effect @@ -92,6 +93,10 @@ object EffectVortex : Effect("vortex") { val whitelist = config.getStringsOrNull("whitelist")?.map { Entities.lookup(it) } val blacklist = config.getFormattedStrings("blacklist", data).map { Entities.lookup(it) } + if (!Regions.canReach(origin, radius)) { + return false + } + val affected = mutableSetOf() var tick = 0 diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeBeam.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeBeam.kt index 858870b1c..a262c2b02 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeBeam.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeBeam.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl.aoe.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.impl.aoe.AOEShape import com.willfp.libreforge.getDoubleFromExpression @@ -45,6 +46,10 @@ object AOEShapeBeam : AOEShape("beam") { for (i in 1..distance) { val center = (location + offset * i.toFloat()).toLocation(world) + if (!Regions.canReach(center, radius)) { + return entities + } + val atBeamPoint = center.getNearbyEntities(radius, radius, radius) .filterIsInstance() .filterNot { it.uniqueId == data.player?.uniqueId } @@ -86,6 +91,10 @@ object AOEShapeBeam : AOEShape("beam") { for (i in 0..distance) { val center = (location + offset * i.toFloat()).toLocation(world) + if (!Regions.canReach(center, radius)) { + return blocks + } + val atBeamPoint = center.getNearbyBlocksInSphere(radius) blocks += atBeamPoint diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeCircle.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeCircle.kt index 26543eb79..765b60a9f 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeCircle.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeCircle.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl.aoe.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.impl.aoe.AOEShape import com.willfp.libreforge.getDoubleFromExpression @@ -27,8 +28,13 @@ object AOEShapeCircle : AOEShape("circle") { compileData: NoCompileData ): Collection { val radius = config.getDoubleFromExpression("radius", data) + val center = location.toLocation(world) - return location.toLocation(world).getNearbyEntities(radius, radius, radius) + if (!Regions.canReach(center, radius)) { + return emptyList() + } + + return center.getNearbyEntities(radius, radius, radius) .filterIsInstance() } @@ -41,7 +47,12 @@ object AOEShapeCircle : AOEShape("circle") { compileData: NoCompileData ): Collection { val radius = config.getDoubleFromExpression("radius", data) + val center = location.toLocation(world) + + if (!Regions.canReach(center, radius)) { + return emptyList() + } - return location.toLocation(world).getNearbyBlocksInSphere(radius) + return center.getNearbyBlocksInSphere(radius) } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeCone.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeCone.kt index 82b2b91e5..03c2d06ef 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeCone.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeCone.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl.aoe.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.angle import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.impl.aoe.AOEShape @@ -38,7 +39,13 @@ object AOEShapeCone: AOEShape("cone") { val direction2 = direction.xz - return location.toLocation(world).getNearbyEntities(radius, radius, radius) + val center = location.toLocation(world) + + if (!Regions.canReach(center, radius)) { + return emptyList() + } + + return center.getNearbyEntities(radius, radius, radius) .filterIsInstance() .filter { val entityPosition = it.location.toFloat3().xz @@ -66,7 +73,13 @@ object AOEShapeCone: AOEShape("cone") { val direction2 = direction.xz - return location.toLocation(world).getNearbyBlocksInSphere(radius) + val center = location.toLocation(world) + + if (!Regions.canReach(center, radius)) { + return emptyList() + } + + return center.getNearbyBlocksInSphere(radius) .filter { val blockPosition = it.location.toFloat3().xz diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeOffsetCircle.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeOffsetCircle.kt index 3afa56d82..7d1c6176f 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeOffsetCircle.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeOffsetCircle.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl.aoe.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.impl.aoe.AOEShape import com.willfp.libreforge.getDoubleFromExpression @@ -31,9 +32,13 @@ object AOEShapeOffsetCircle : AOEShape("offset_circle") { val radius = config.getDoubleFromExpression("radius", data) val offset = config.getDoubleFromExpression("offset", data) - return (location + direction.normalize() * offset.toFloat()) - .toLocation(world) - .getNearbyEntities(radius, radius, radius).filterIsInstance() + val center = (location + direction.normalize() * offset.toFloat()).toLocation(world) + + if (!Regions.canReach(center, radius)) { + return emptyList() + } + + return center.getNearbyEntities(radius, radius, radius).filterIsInstance() } override fun getBlocks( @@ -47,8 +52,12 @@ object AOEShapeOffsetCircle : AOEShape("offset_circle") { val radius = config.getDoubleFromExpression("radius", data) val offset = config.getDoubleFromExpression("offset", data) - return (location + direction.normalize() * offset.toFloat()) - .toLocation(world) - .getNearbyBlocksInSphere(radius) + val center = (location + direction.normalize() * offset.toFloat()).toLocation(world) + + if (!Regions.canReach(center, radius)) { + return emptyList() + } + + return center.getNearbyBlocksInSphere(radius) } } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeScanInFront.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeScanInFront.kt index 5278efd86..626a35a99 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeScanInFront.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/aoe/impl/AOEShapeScanInFront.kt @@ -2,6 +2,7 @@ package com.willfp.libreforge.effects.impl.aoe.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.impl.aoe.AOEShape import com.willfp.libreforge.getDoubleFromExpression @@ -36,7 +37,13 @@ object AOEShapeScanInFront : AOEShape("scan_in_front") { val offset = direction.normalize() for (i in 1..maxDistance) { - val entities = (location + offset * i.toFloat()).toLocation(world) + val center = (location + offset * i.toFloat()).toLocation(world) + + if (!Regions.canReach(center, radius)) { + return emptyList() + } + + val entities = center .getNearbyEntities(radius, radius, radius) .filterIsInstance() .filterNot { it.uniqueId == data.player?.uniqueId } @@ -65,7 +72,13 @@ object AOEShapeScanInFront : AOEShape("scan_in_front") { val offset = direction.normalize() for (i in 0..maxDistance) { - val blocks = (location + offset * i.toFloat()).toLocation(world) + val center = (location + offset * i.toFloat()).toLocation(world) + + if (!Regions.canReach(center, radius)) { + return emptyList() + } + + val blocks = center .getNearbyBlocksInSphere(radius) .filterNot { it.isEmpty } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/MineBlockEffect.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/MineBlockEffect.kt index a921e9da1..b296da871 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/MineBlockEffect.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/templates/MineBlockEffect.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.util.runExempted import com.willfp.eco.util.simplify import com.willfp.libreforge.BlockBreaker +import com.willfp.libreforge.Regions import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.plugin import com.willfp.libreforge.triggers.TriggerData @@ -119,7 +120,7 @@ abstract class MineBlockEffect(id: String) : Effect(id) { val breaker = this.dispatcher as? BlockBreaker if (breaker != null) { - breaker.breakBlocks(blocks, this) + breaker.breakBlocks(blocks.filter { Regions.canReach(it.location) }, this) return } @@ -127,18 +128,20 @@ abstract class MineBlockEffect(id: String) : Effect(id) { } protected fun Player.breakBlocksSafely(blocks: Collection, mode: PreventTriggerMode) { + val reachable = blocks.filter { Regions.canReach(it.location) } + if (plugin.configYml.getBool("effects.use-setblock-break")) { - blocks.forEach { it.type = Material.AIR } + reachable.forEach { it.type = Material.AIR } return } if (mode == PreventTriggerMode.ALL) { - blocks.forEach { it.breakNaturally() } + reachable.forEach { it.breakNaturally() } return } this.runExempted { - for (block in blocks) { + for (block in reachable) { if (block.world != this.world) { continue } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/smartpets/SmartPetsUtils.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/smartpets/SmartPetsUtils.kt index 5a4aac623..adec58ab2 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/smartpets/SmartPetsUtils.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/smartpets/SmartPetsUtils.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.integrations.smartpets import com.smartpets.api.SmartPetsAPI import com.smartpets.events.SmartPetEvent import com.smartpets.model.Pet +import com.willfp.libreforge.Regions import com.willfp.libreforge.plugin import com.willfp.libreforge.toDispatcher import com.willfp.libreforge.triggers.Trigger @@ -24,7 +25,7 @@ internal val SmartPetEvent.owner: Player? * SmartPets fires its events off the main thread when the change happens async. */ internal fun Player.onMainThread(action: () -> Unit) { - if (Bukkit.isPrimaryThread()) { + if (Regions.owns(this)) { action() } else { plugin.scheduler.on(this).run { action() } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/mutators/impl/MutatorLocationToCursor.kt b/core/common/src/main/kotlin/com/willfp/libreforge/mutators/impl/MutatorLocationToCursor.kt index 8c63567e0..a91d7c4e8 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/mutators/impl/MutatorLocationToCursor.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/mutators/impl/MutatorLocationToCursor.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.mutators.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.mutators.Mutator import com.willfp.libreforge.mutators.parameterTransformers @@ -57,7 +58,7 @@ object MutatorLocationToCursor : Mutator("location_to_cursor") { val result = start?.world?.rayTrace( start.location, start.eyeLocation.direction, - plugin.configYml.getDouble("raytrace-distance"), + Regions.reachableDistance(start.location, plugin.configYml.getDouble("raytrace-distance")), FluidCollisionMode.NEVER, true, 0.0, diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/mutators/impl/MutatorVictimToNearestEntity.kt b/core/common/src/main/kotlin/com/willfp/libreforge/mutators/impl/MutatorVictimToNearestEntity.kt index 3956d1843..492a65bb6 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/mutators/impl/MutatorVictimToNearestEntity.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/mutators/impl/MutatorVictimToNearestEntity.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.mutators.impl import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.getDoubleFromExpression import com.willfp.libreforge.mutators.Mutator @@ -46,6 +47,10 @@ object MutatorVictimToNearestEntity : Mutator("victim_to_nearest_ val radius = config.getDoubleFromExpression("radius", data) val includePlayer = config.has("include_player") && config.getBool("include_player") + if (!Regions.canReach(location, radius)) { + return data + } + val nearest = world.getNearbyEntities(location, radius, radius, radius) .filterIsInstance() .filter { includePlayer || it != data.player } diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerAltClick.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerAltClick.kt index 445ae493a..a627420be 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerAltClick.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/impl/TriggerAltClick.kt @@ -1,5 +1,6 @@ package com.willfp.libreforge.triggers.impl +import com.willfp.libreforge.Regions import com.willfp.libreforge.plugin import com.willfp.libreforge.toDispatcher import com.willfp.libreforge.triggers.Trigger @@ -119,13 +120,13 @@ object TriggerAltClick : Trigger("alt_click") { val location: Location? val world = player.location.world ?: return val result = player.rayTraceBlocks( - plugin.configYml.getDouble("raytrace-distance"), + Regions.reachableDistance(player.eyeLocation, plugin.configYml.getDouble("raytrace-distance")), FluidCollisionMode.NEVER ) val entityResult = world.rayTraceEntities( player.eyeLocation, - player.eyeLocation.direction, 50.0, 3.0 + player.eyeLocation.direction, Regions.reachableDistance(player.eyeLocation, 50.0), 3.0 ) { entity: Entity? -> entity is LivingEntity } location = result?.hitPosition?.toLocation(world) From f60067d9f5263b1401fd3bc2fc6807ab7ffccace Mon Sep 17 00:00:00 2001 From: Callum Johnson Date: Thu, 8 Oct 2026 22:10:42 +0100 Subject: [PATCH 5/9] feat: per-call placeholder injection --- core/common/build.gradle.kts | 3 + .../com/willfp/libreforge/InjectionScope.kt | 105 +++++++++++++ .../willfp/libreforge/ProvidedHolderConfig.kt | 86 +++++++++-- .../libreforge/SeparatorAmbivalentConfig.kt | 49 ++++-- .../libreforge/counters/bind/BoundCounter.kt | 5 + .../willfp/libreforge/effects/ChainElement.kt | 11 +- .../com/willfp/libreforge/effects/Effect.kt | 5 +- .../willfp/libreforge/effects/ElementLike.kt | 8 +- .../placeholders/InjectionScopeTest.kt | 146 ++++++++++++++++++ 9 files changed, 386 insertions(+), 32 deletions(-) create mode 100644 core/common/src/main/kotlin/com/willfp/libreforge/InjectionScope.kt create mode 100644 core/common/src/test/kotlin/com/willfp/libreforge/placeholders/InjectionScopeTest.kt diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index 03f24fcc6..f6faebcb7 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -74,6 +74,9 @@ dependencies { testImplementation("com.willfp:eco:${findProperty("eco-version")}") testImplementation(kotlin("stdlib", version = "2.3.0")) + // eco's placeholder types reference Bukkit classes, which the server provides at runtime. + testImplementation("org.purpurmc.purpur:purpur-api:1.21.4-R0.1-SNAPSHOT") + // Reads the bundled holidays.yml in tests; the server provides SnakeYAML at runtime. testImplementation("org.yaml:snakeyaml:2.2") diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/InjectionScope.kt b/core/common/src/main/kotlin/com/willfp/libreforge/InjectionScope.kt new file mode 100644 index 000000000..55de7a535 --- /dev/null +++ b/core/common/src/main/kotlin/com/willfp/libreforge/InjectionScope.kt @@ -0,0 +1,105 @@ +package com.willfp.libreforge + +import com.willfp.eco.core.config.interfaces.Config +import com.willfp.eco.core.placeholder.InjectablePlaceholder +import java.util.Collections +import java.util.IdentityHashMap +import java.util.concurrent.ConcurrentHashMap + +/** + * The placeholders injected during one call, such as one effect being triggered. + * + * Compiled configs are shared by every dispatcher with the same holder, and on Folia they're read + * on many regions at once, so placeholders injected during a call are kept here rather than on the + * configs themselves, and are only seen by that call. Injections made outside any call still go to + * the config. + */ +internal class InjectionScope private constructor( + private val parent: InjectionScope? +) { + private val injections: MutableMap> = + Collections.synchronizedMap(IdentityHashMap()) + + /** + * Inject [placeholders] into [config] for this call. + */ + fun inject(config: Config, placeholders: Iterable) { + val forConfig = injections.getOrPut(config) { ConcurrentHashMap() } + + for (placeholder in placeholders) { + forConfig[placeholder.patternString] = placeholder + } + } + + /** + * Give [child] the placeholders injected into [parent] so far, as reading a subsection does. + */ + fun inherit(parent: Config, child: Config) { + val inherited = injectionsFor(parent) + + if (inherited.isNotEmpty()) { + inject(child, inherited.values) + } + } + + /** + * The placeholders injected into [config] by this call and the calls it's nested in, by + * pattern, with the innermost call winning. + */ + fun injectionsFor(config: Config): Map { + val own = injections[config] + val outer = parent?.injectionsFor(config) + + return when { + own == null -> outer ?: emptyMap() + outer.isNullOrEmpty() -> own + else -> outer + own + } + } + + /** + * [base] with the placeholders injected into [config] by this call on top. + */ + fun merge(config: Config, base: List): List { + val injected = injectionsFor(config) + + if (injected.isEmpty()) { + return base + } + + return base.filter { it.patternString !in injected } + injected.values + } + + companion object { + private val current = ThreadLocal() + + /** + * The call running on this thread, if any. + */ + fun current(): InjectionScope? = current.get() + + /** + * Run [block] as a new call, nested in the current one if there is one. + */ + fun open(block: () -> T): T = + enter(InjectionScope(current.get()), block) + + /** + * Run [block] in an existing call, such as a delayed repeat of it. + */ + fun enter(scope: InjectionScope, block: () -> T): T { + val previous = current.get() + current.set(scope) + + try { + return block() + } finally { + if (previous == null) { + current.remove() + } else { + current.set(previous) + } + } + } + } +} diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/ProvidedHolderConfig.kt b/core/common/src/main/kotlin/com/willfp/libreforge/ProvidedHolderConfig.kt index f23c645b8..22bddffc2 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/ProvidedHolderConfig.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/ProvidedHolderConfig.kt @@ -1,61 +1,115 @@ package com.willfp.libreforge +import com.willfp.eco.core.config.ConfigType import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.placeholder.InjectablePlaceholder import com.willfp.eco.core.placeholder.StaticPlaceholder import com.willfp.eco.core.placeholder.context.PlaceholderContext import com.willfp.eco.core.placeholder.context.copy import com.willfp.eco.util.NumberUtils +import com.willfp.eco.util.StringUtils import com.willfp.eco.util.formatEco +import java.util.concurrent.ConcurrentHashMap /** * A [config] that uses a provided [holder] as a source of placeholders. * - * This allows for item placeholders to be used in config values. + * This allows for item placeholders to be used in config values. The holder's placeholders are + * kept here rather than on the shared [config], so effects that keep this config and read it later + * always see their own holder's values. */ private class ProvidedHolderConfig( private val config: Config, private val holder: ProvidedHolder -) : Config by config { +) : Config { + private val injections = ConcurrentHashMap() + + private fun wrap(child: Config): Config = + ProvidedHolderConfig(child, holder).also { it.addInjectablePlaceholder(injections.values.toMutableList()) } + override fun getDoubleFromExpression(path: String, context: PlaceholderContext): Double { return NumberUtils.evaluateExpression( this.getString(path), - context.withInjectableContext(config).copy(item = holder.getProvider()) + context.withInjectableContext(this).copy(item = holder.getProvider()) ) } override fun getFormattedStringOrNull(path: String, context: PlaceholderContext): String? { val string = this.getStringOrNull(path) ?: return null - return string.formatEco(context.withInjectableContext(config).copy(item = holder.getProvider())) + return string.formatEco(context.withInjectableContext(this).copy(item = holder.getProvider())) } override fun getFormattedStringsOrNull(path: String, context: PlaceholderContext): List? { val strings = this.getStringsOrNull(path) ?: return null - return strings.formatEco(context.withInjectableContext(config).copy(item = holder.getProvider())) + return strings.formatEco(context.withInjectableContext(this).copy(item = holder.getProvider())) } - // Kotlin Delegation doesn't seem to like these methods? - // I'm sure there's a better way of doing this but debugging this was a pain in the dick + override fun clone(): Config = wrap(config.clone()) + + override fun toPlaintext(): String = config.toPlaintext() + + override fun has(path: String): Boolean = config.has(path) + + override fun getKeys(deep: Boolean): List = config.getKeys(deep) + + override fun get(path: String): Any? = config.get(path) + + override fun set(path: String, obj: Any?) = config.set(path, obj) + + override fun getSubsectionOrNull(path: String): Config? = config.getSubsectionOrNull(path)?.let { wrap(it) } + + override fun getIntOrNull(path: String): Int? = config.getIntOrNull(path) + + override fun getIntsOrNull(path: String): List? = config.getIntsOrNull(path) + + override fun getBoolOrNull(path: String): Boolean? = config.getBoolOrNull(path) + + override fun getBoolsOrNull(path: String): List? = config.getBoolsOrNull(path) + + override fun getStringOrNull(path: String, format: Boolean, option: StringUtils.FormatOption): String? = + config.getStringOrNull(path, format, option) + + override fun getStringsOrNull( + path: String, format: Boolean, option: StringUtils.FormatOption + ): List? = config.getStringsOrNull(path, format, option) + + override fun getDoubleOrNull(path: String): Double? = config.getDoubleOrNull(path) + + override fun getDoublesOrNull(path: String): List? = config.getDoublesOrNull(path) + + override fun getSubsectionsOrNull(path: String): List? = + config.getSubsectionsOrNull(path)?.map { wrap(it) } + + override fun getType(): ConfigType = config.type + override fun injectPlaceholders(vararg placeholders: InjectablePlaceholder) = - config.injectPlaceholders(*placeholders) + addInjectablePlaceholder(placeholders.toMutableList()) override fun injectPlaceholders(vararg placeholders: StaticPlaceholder) = - config.injectPlaceholders(*placeholders) + addInjectablePlaceholder(placeholders.toMutableList()) + + override fun addInjectablePlaceholder(placeholders: MutableIterable) { + for (placeholder in placeholders) { + injections[placeholder.patternString] = placeholder + } + } - override fun addInjectablePlaceholder(placeholders: MutableIterable) = - config.addInjectablePlaceholder(placeholders) + override fun getPlaceholderInjections(): List { + val base = config.placeholderInjections - override fun getPlaceholderInjections(): List = - config.placeholderInjections + if (injections.isEmpty()) { + return base + } + + return base.filter { !injections.containsKey(it.patternString) } + injections.values + } override fun clearInjectedPlaceholders() = - config.clearInjectedPlaceholders() + injections.clear() } fun Config.applyHolder(providedHolder: ProvidedHolder, dispatcher: Dispatcher<*>): Config = ProvidedHolderConfig(this, providedHolder).apply { - // Placeholders are cached per holder while the dispatcher's state holds it; injection still - // happens on every call, as holders sharing a compiled config would otherwise see each other's. addInjectablePlaceholder( HolderStates.cachedPlaceholders(dispatcher, providedHolder.holder) ?: providedHolder.generatePlaceholders(dispatcher).mapToPlaceholders() diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/SeparatorAmbivalentConfig.kt b/core/common/src/main/kotlin/com/willfp/libreforge/SeparatorAmbivalentConfig.kt index 41537cc9e..fa1de48d8 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/SeparatorAmbivalentConfig.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/SeparatorAmbivalentConfig.kt @@ -13,10 +13,19 @@ import org.bukkit.entity.Player import java.util.concurrent.ConcurrentHashMap private class SeparatorAmbivalentConfig( - private val config: Config + val config: Config, + private val boundScope: InjectionScope? = null ) : Config { private val resolvedPaths = ConcurrentHashMap() + private val scope: InjectionScope? + get() = boundScope ?: InjectionScope.current() + + private fun wrap(child: Config): Config { + scope?.inherit(config, child) + return SeparatorAmbivalentConfig(child, boundScope) + } + private inline fun preprocess(path: String, getter: (String) -> T): T? { return preprocess(path, getter, { it == null }, null) } @@ -62,7 +71,7 @@ private class SeparatorAmbivalentConfig( override fun set(path: String, obj: Any?) = config.set(path, obj) override fun getSubsectionOrNull(path: String): Config? = preprocess(path) { - config.getSubsectionOrNull(it)?.separatorAmbivalent() + config.getSubsectionOrNull(it)?.let { child -> wrap(child) } } override fun getIntOrNull(path: String): Int? = preprocess(path) { config.getIntOrNull(it) } @@ -85,22 +94,31 @@ private class SeparatorAmbivalentConfig( override fun getDoublesOrNull(path: String): List? = preprocess(path) { config.getDoublesOrNull(it) } override fun getSubsectionsOrNull(path: String): List? = - preprocess(path) { config.getSubsectionsOrNull(it)?.map { cfg -> cfg.separatorAmbivalent() } } + preprocess(path) { config.getSubsectionsOrNull(it)?.map { cfg -> wrap(cfg) } } override fun getType(): ConfigType = config.type // Adding them all, because I'm lazy override fun injectPlaceholders(vararg placeholders: InjectablePlaceholder) = - config.injectPlaceholders(*placeholders) + addInjectablePlaceholder(placeholders.toMutableList()) override fun injectPlaceholders(vararg placeholders: StaticPlaceholder) = - config.injectPlaceholders(*placeholders) + addInjectablePlaceholder(placeholders.toMutableList()) + + override fun addInjectablePlaceholder(placeholders: MutableIterable) { + val scope = scope - override fun addInjectablePlaceholder(placeholders: MutableIterable) = - config.addInjectablePlaceholder(placeholders) + if (scope != null) { + scope.inject(config, placeholders) + } else { + config.addInjectablePlaceholder(placeholders) + } + } - override fun getPlaceholderInjections(): List = - config.placeholderInjections + override fun getPlaceholderInjections(): List { + val base = config.placeholderInjections + return scope?.merge(config, base) ?: base + } override fun clearInjectedPlaceholders() = config.clearInjectedPlaceholders() @@ -115,6 +133,19 @@ private class SeparatorAmbivalentConfig( fun Config.separatorAmbivalent(): Config = this as? SeparatorAmbivalentConfig ?: SeparatorAmbivalentConfig(this) +/** + * This config as seen by [scope], whichever thread reads it, for configs that outlive the call + * that injected into them (such as one captured by a delayed task). + */ +internal fun Config.boundTo(scope: InjectionScope): Config = + SeparatorAmbivalentConfig((this as? SeparatorAmbivalentConfig)?.config ?: this, scope) + +/** + * This config bound to the call running on this thread, or itself outside of one. + */ +internal fun Config.inCurrentScope(): Config = + InjectionScope.current()?.let { boundTo(it) } ?: this + fun Config.toPlaceholderContext(data: TriggerData? = null): PlaceholderContext { val additionalPlayers = if (data?.victim is Player) { listOf(AdditionalPlayer(data.victim, "victim")) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/counters/bind/BoundCounter.kt b/core/common/src/main/kotlin/com/willfp/libreforge/counters/bind/BoundCounter.kt index 92617ecb4..7660d1af8 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/counters/bind/BoundCounter.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/counters/bind/BoundCounter.kt @@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.placeholder.context.placeholderContext import com.willfp.eco.util.evaluateExpression import com.willfp.libreforge.DynamicConfigs +import com.willfp.libreforge.InjectionScope import com.willfp.libreforge.counters.Accumulator import com.willfp.libreforge.counters.Counter import com.willfp.libreforge.get @@ -41,6 +42,10 @@ internal data class BoundCounter( * Called only for dispatches of the counter's own trigger (bindings are indexed by trigger). */ fun accept(trigger: DispatchedTrigger) { + InjectionScope.open { acceptInScope(trigger) } + } + + private fun acceptInScope(trigger: DispatchedTrigger) { val data = trigger.data val player = trigger.dispatcher.get() ?: return diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/ChainElement.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/ChainElement.kt index 1989248aa..ece91cd1a 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/ChainElement.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/ChainElement.kt @@ -3,6 +3,7 @@ package com.willfp.libreforge.effects import com.willfp.eco.core.config.interfaces.Config import com.willfp.libreforge.Compiled import com.willfp.libreforge.Dispatcher +import com.willfp.libreforge.InjectionScope import com.willfp.libreforge.ProvidedHolder import com.willfp.libreforge.Weighted import com.willfp.libreforge.getDoubleFromExpression @@ -41,11 +42,13 @@ class ChainElement internal constructor( return weight } - elementConfig.addInjectablePlaceholder(trigger.placeholders) + return InjectionScope.open { + elementConfig.addInjectablePlaceholder(trigger.placeholders) - return runCatching { - elementConfig.getDoubleFromExpression("weight", trigger.data) - }.getOrDefault(weight) + runCatching { + elementConfig.getDoubleFromExpression("weight", trigger.data) + }.getOrDefault(weight) + } } @Deprecated( diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/Effect.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/Effect.kt index 10e4e04d1..2eaa4b72a 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/Effect.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/Effect.kt @@ -5,6 +5,7 @@ import com.willfp.libreforge.Compilable import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.ProvidedHolder import com.willfp.libreforge.applyHolder +import com.willfp.libreforge.inCurrentScope import com.willfp.libreforge.mutators.MutatorList import com.willfp.libreforge.mutators.emptyMutatorList import com.willfp.libreforge.plugin @@ -269,7 +270,7 @@ abstract class Effect( trigger: DispatchedTrigger, config: ChainElement ): Boolean = onTrigger( - config.config, + config.config.inCurrentScope(), trigger.data.copy().apply { this.inheritedTriggerPlaceholders = trigger.rawPlaceholders }, config.compileData ) @@ -296,7 +297,7 @@ abstract class Effect( fun shouldTrigger( trigger: DispatchedTrigger, config: ChainElement - ): Boolean = shouldTrigger(config.config, trigger.data, config.compileData) + ): Boolean = shouldTrigger(config.config.inCurrentScope(), trigger.data, config.compileData) /** * If the effect should trigger, ran before effect arguments in order diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/ElementLike.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/ElementLike.kt index 43f9f11c3..21db2f493 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/ElementLike.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/ElementLike.kt @@ -6,6 +6,7 @@ import com.willfp.eco.core.placeholder.InjectablePlaceholder import com.willfp.libreforge.ConfigurableElement import com.willfp.libreforge.DynamicConfigs import com.willfp.libreforge.DynamicNumericValue +import com.willfp.libreforge.InjectionScope import com.willfp.libreforge.NamedValue import com.willfp.libreforge.conditions.ConditionList import com.willfp.libreforge.effects.arguments.EffectArgumentList @@ -67,6 +68,10 @@ abstract class ElementLike : ConfigurableElement { return doTrigger(trigger) } + return InjectionScope.open { triggerInScope(trigger) } + } + + private fun triggerInScope(trigger: DispatchedTrigger): Boolean { // It would be nice to abstract repeat/delay away here, but that would be // really, really, overengineering it - even for me. var repeatTimes = 1 @@ -174,6 +179,7 @@ abstract class ElementLike : ConfigurableElement { } } else { // Delay between each repeat. + val scope = InjectionScope.current()!! var repeats = 0 val context = data.player?.let { plugin.scheduler.on(it) } ?: data.location?.let { plugin.scheduler.at(it) } @@ -181,7 +187,7 @@ abstract class ElementLike : ConfigurableElement { context.runTimer({ task -> repeats++ - trigger() + InjectionScope.enter(scope) { trigger() } if (repeats >= repeatTimes) { task.cancel() diff --git a/core/common/src/test/kotlin/com/willfp/libreforge/placeholders/InjectionScopeTest.kt b/core/common/src/test/kotlin/com/willfp/libreforge/placeholders/InjectionScopeTest.kt new file mode 100644 index 000000000..c8cb04d36 --- /dev/null +++ b/core/common/src/test/kotlin/com/willfp/libreforge/placeholders/InjectionScopeTest.kt @@ -0,0 +1,146 @@ +package com.willfp.libreforge.placeholders + +import com.willfp.eco.core.config.interfaces.Config +import com.willfp.eco.core.placeholder.InjectablePlaceholder +import com.willfp.eco.core.placeholder.context.PlaceholderContext +import com.willfp.libreforge.InjectionScope +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.lang.reflect.Proxy +import java.util.regex.Pattern + +class InjectionScopeTest { + private class Value(private val name: String, private val value: String) : InjectablePlaceholder { + override fun getPattern(): Pattern = Pattern.compile(name) + + override fun getValue(args: String, context: PlaceholderContext): String = value + } + + private fun config(): Config = + Proxy.newProxyInstance(Config::class.java.classLoader, arrayOf(Config::class.java)) { proxy, method, args -> + when (method.name) { + "hashCode" -> System.identityHashCode(proxy) + "equals" -> proxy === args?.get(0) + else -> throw UnsupportedOperationException(method.name) + } + } as Config + + private fun InjectionScope.valueOf(config: Config, name: String): String? = + injectionsFor(config)[name]?.getValue(name, PlaceholderContext.EMPTY) + + @Test + fun noScopeOutsideACall() { + assertNull(InjectionScope.current()) + } + + @Test + fun injectionsStayInTheirCall() { + val shared = config() + + val first = InjectionScope.open { + InjectionScope.current()!!.also { it.inject(shared, listOf(Value("level", "1"))) } + } + + val second = InjectionScope.open { + InjectionScope.current()!!.also { it.inject(shared, listOf(Value("level", "2"))) } + } + + assertEquals("1", first.valueOf(shared, "level")) + assertEquals("2", second.valueOf(shared, "level")) + assertNull(InjectionScope.current()) + } + + @Test + fun nestedCallsSeeTheirParentAndWinOverIt() { + val shared = config() + + InjectionScope.open { + val outer = InjectionScope.current()!! + outer.inject(shared, listOf(Value("a", "outer"), Value("b", "outer"))) + + InjectionScope.open { + val inner = InjectionScope.current()!! + inner.inject(shared, listOf(Value("b", "inner"))) + + assertEquals("outer", inner.valueOf(shared, "a")) + assertEquals("inner", inner.valueOf(shared, "b")) + } + + assertEquals("outer", outer.valueOf(shared, "b")) + assertTrue(InjectionScope.current() === outer) + } + } + + @Test + fun subsectionsInheritWhatTheirParentHad() { + val parent = config() + val child = config() + + InjectionScope.open { + val scope = InjectionScope.current()!! + scope.inject(parent, listOf(Value("level", "3"))) + scope.inherit(parent, child) + + assertEquals("3", scope.valueOf(child, "level")) + } + } + + @Test + fun mergeOverridesTheConfigsOwnInjections() { + val shared = config() + val base = listOf(Value("level", "base"), Value("other", "base")) + + InjectionScope.open { + val scope = InjectionScope.current()!! + scope.inject(shared, listOf(Value("level", "call"))) + + val merged = scope.merge(shared, base).associate { it.patternString to it.getValue("", PlaceholderContext.EMPTY) } + + assertEquals(mapOf("other" to "base", "level" to "call"), merged) + } + } + + @Test + fun aDelayedRunReentersItsCallOnAnotherThread() { + val shared = config() + + val scope = InjectionScope.open { + InjectionScope.current()!!.also { it.inject(shared, listOf(Value("level", "4"))) } + } + + var seen: String? = null + val thread = Thread { + InjectionScope.enter(scope) { + seen = InjectionScope.current()!!.valueOf(shared, "level") + } + } + thread.start() + thread.join() + + assertEquals("4", seen) + } + + @Test + fun callsOnDifferentThreadsDontSeeEachOther() { + val shared = config() + val seen = arrayOfNulls(2) + + val threads = (0..1).map { index -> + Thread { + InjectionScope.open { + val scope = InjectionScope.current()!! + scope.inject(shared, listOf(Value("level", index.toString()))) + Thread.sleep(20) + seen[index] = scope.valueOf(shared, "level") + } + } + } + + threads.forEach { it.start() } + threads.forEach { it.join() } + + assertEquals(listOf("0", "1"), seen.toList()) + } +} From bc575481c20135891fbc690ffcd59e65fb49ba55 Mon Sep 17 00:00:00 2001 From: Callum Johnson Date: Thu, 8 Oct 2026 22:30:17 +0100 Subject: [PATCH 6/9] feat: region-aware holder engine --- .../com/willfp/libreforge/HolderState.kt | 5 +- .../com/willfp/libreforge/HolderStates.kt | 239 +++++++++++++----- 2 files changed, 178 insertions(+), 66 deletions(-) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/HolderState.kt b/core/common/src/main/kotlin/com/willfp/libreforge/HolderState.kt index ea6479930..240c435ca 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/HolderState.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/HolderState.kt @@ -200,7 +200,8 @@ internal enum class StateKind { } /** - * Everything libreforge tracks for one dispatcher. Accessed only on the main thread. + * Everything libreforge tracks for one dispatcher. Accessed only on the thread that owns the + * dispatcher (the main thread off Folia); [admitted] and [isRemoved] are also read by the engine. */ internal class HolderState( val dispatcher: Dispatcher<*>, @@ -261,6 +262,7 @@ internal class HolderState( private var clickScopes: ArrayList? = ArrayList() // Set when the state is dropped, possibly by a handler in the middle of its own update. + @Volatile var isRemoved = false var admissionAttempts = 0 @@ -268,6 +270,7 @@ internal class HolderState( var bypassCooldown = false // Non-player states wait for the new-state budget before their first flush. + @Volatile var admitted = false var needsCleanup = false diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/HolderStates.kt b/core/common/src/main/kotlin/com/willfp/libreforge/HolderStates.kt index 68ff9ea2c..280fd393a 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/HolderStates.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/HolderStates.kt @@ -20,8 +20,11 @@ import java.util.concurrent.ConcurrentLinkedQueue /** * The registry of [HolderState]s, and the flush that applies everything marked on them. * - * States are only touched on the main thread; marks from other threads are queued. Everything - * marked during a tick is applied at the start of the next one, after the event that caused it. + * The registry is only touched on the engine thread (the main thread, or the global region on + * Folia); marks from other threads are queued. A state itself is only touched on the thread that + * owns its dispatcher, which off Folia is the main thread too, so the flush hands each state's work + * to its owner. Everything marked during a tick is applied at the start of the next one, after the + * event that caused it. */ internal object HolderStates { internal class Settings( @@ -44,8 +47,8 @@ internal object HolderStates { @Volatile private var unloadedClassLoaders: Set = emptySet() - // In creation order, which a reset follows. - private val states = LinkedHashMap() + // In creation order, which a reset follows. Written only on the engine thread, read from owners. + private val states: MutableMap = Collections.synchronizedMap(LinkedHashMap()) private val dirty = LinkedHashSet() @@ -63,10 +66,8 @@ internal object HolderStates { private var flushing = false - private var flushThread: Thread? = null - private val isFlushThread: Boolean - get() = flushThread?.let { it === Thread.currentThread() } ?: Bukkit.isPrimaryThread() + get() = Regions.ownsGlobal() private var resetRequested = false @@ -77,6 +78,7 @@ internal object HolderStates { /** * The flush counter, used as the tick clock for ages. */ + @Volatile var tick = 0 private set @@ -144,6 +146,10 @@ internal object HolderStates { trackPlayer(player) } + if (Regions.isFolia) { + return + } + for (world in Bukkit.getWorlds()) { for (entity in world.livingEntities) { trackEntity(entity) @@ -170,12 +176,20 @@ internal object HolderStates { } } + /** + * Run [action] on the thread that owns [state]'s dispatcher: now off Folia. + */ + private inline fun onOwner(state: HolderState, crossinline action: () -> Unit) = + Regions.runOwned(state.dispatcher) { action() } + private inline fun withState(dispatcher: Dispatcher<*>, crossinline action: (HolderState) -> Unit) { mark { val state = stateFor(dispatcher) if (state != null) { - action(state) - markDirty(state) + onOwner(state) { + action(state) + markDirty(state) + } } } } @@ -202,7 +216,7 @@ internal object HolderStates { return create(dispatcher, StateKind.CUSTOM) } - fun markDirty(state: HolderState) { + fun markDirty(state: HolderState) = onFlushThread { if (state.admitted && states[state.uuid] === state) { dirty += state } @@ -218,8 +232,10 @@ internal object HolderStates { fun markProviderEverywhere(provider: HolderProvider) = mark { if (provider in registeredHolderProviders) { for (state in states.values) { - state.markProvider(provider) - markDirty(state) + onOwner(state) { + state.markProvider(provider) + markDirty(state) + } } } } @@ -234,19 +250,32 @@ internal object HolderStates { return@mark } - val nearby = hashSetOf(owner) val world = location?.world - if (world != null) { - world.getNearbyEntities(location, radius, radius, radius).mapTo(nearby) { it.uniqueId } + if (location == null || world == null) { + markNear(provider, holder, hashSetOf(owner)) + return@mark } - for (state in states.values) { + Regions.runOwned(location) { + val nearby = hashSetOf(owner) + if (Regions.canReach(location, radius)) { + world.getNearbyEntities(location, radius, radius, radius).mapTo(nearby) { it.uniqueId } + } + + onFlushThread { markNear(provider, holder, nearby) } + } + } + + private fun markNear(provider: HolderProvider, holder: Holder, nearby: Set) { + for (state in states.values) { + onOwner(state) { if (state.uuid in nearby || state.provides(provider, holder)) { state.markProvider(provider) markDirty(state) } } } + } fun markAllProviders(dispatcher: Dispatcher<*>) = withState(dispatcher) { it.markProviders(registeredHolderProviders) } @@ -341,23 +370,49 @@ internal object HolderStates { /** * Stop tracking [entity], disabling its active effects. */ - fun untrackEntity(entity: LivingEntity, isNPC: Boolean = false): Unit = onFlushThread { + fun untrackEntity(entity: LivingEntity, isNPC: Boolean = false) { if (!isNPC && entity.isRealPlayer) { - return@onFlushThread + return } - val state = states[entity.uniqueId] ?: return@onFlushThread - if (state.dispatcher.dispatcher === entity) { - remove(state) + disableNowIfOwned(entity) + + onFlushThread { + val state = states[entity.uniqueId] ?: return@onFlushThread + if (state.dispatcher.dispatcher === entity) { + remove(state) + } } } /** * Stop tracking a player, disabling its active effects. */ - fun untrackPlayer(player: Player): Unit = onFlushThread { - val state = states[player.uniqueId] ?: return@onFlushThread - remove(state) + fun untrackPlayer(player: Player) { + disableNowIfOwned(player) + + onFlushThread { + val state = states[player.uniqueId] ?: return@onFlushThread + remove(state) + } + } + + /** + * On Folia, removal events fire on the entity's region, before the queued untrack reaches the + * engine and while the entity can still be touched, so its effects are disabled there and then. + */ + private fun disableNowIfOwned(entity: Entity) { + if (isFlushThread || !Regions.owns(entity)) { + return + } + + val state = states[entity.uniqueId] ?: return + if (state.dispatcher.dispatcher !== entity || state.isRemoved) { + return + } + + state.isRemoved = true + guarded("disable effects for ${state.uuid}") { state.disableAll() } } /** @@ -411,7 +466,7 @@ internal object HolderStates { playerBuckets[bucketOf(state.uuid, PLAYER_BUCKETS)].remove(state) entityBuckets[bucketOf(state.uuid, entityBuckets.size)].remove(state) - state.disableAll() + disableOnOwner(state) if (!states.containsKey(state.uuid)) { publishedHolders.remove(state.uuid) @@ -420,6 +475,21 @@ internal object HolderStates { } } + /** + * Disable [state]'s effects on its owner, or, if a Folia entity is gone before that runs, where it + * was removed, so per-dispatcher state held by effects is still released. + */ + private fun disableOnOwner(state: HolderState) { + val entity = state.dispatcher.dispatcher as? Entity + if (entity == null || Regions.canReach(entity)) { + onOwner(state) { state.disableAll() } + return + } + + val disable = Runnable { guarded("disable effects for ${state.uuid}") { state.disableAll() } } + plugin.scheduler.on(entity).onRetired(disable).run(disable) + } + private fun bucketOf(uuid: UUID, buckets: Int): Int = (uuid.leastSignificantBits.toInt() and Int.MAX_VALUE) % buckets @@ -429,7 +499,6 @@ internal object HolderStates { } tick++ - flushThread = Thread.currentThread() while (true) { val action = offThreadMarks.poll() ?: break @@ -480,7 +549,7 @@ internal object HolderStates { } for (state in states.values.toList()) { - state.resetPending = true + onOwner(state) { state.resetPending = true } when (state.kind) { // Spread large mob counts over ticks, in the same budget as new states. @@ -531,14 +600,15 @@ internal object HolderStates { private fun visitBuckets() { for (state in snapshotOf(playerBuckets[tick % PLAYER_BUCKETS])) { - if (state.kind == StateKind.PLAYER && settings.skipAFKPlayers && !state.resetPending) { - val player = state.dispatcher.dispatcher as Player - if (AFKManager.isAfk(player)) { - continue + onOwner(state) { + if (state.kind == StateKind.PLAYER && settings.skipAFKPlayers && !state.resetPending + && AFKManager.isAfk(state.dispatcher.dispatcher as Player) + ) { + return@onOwner } - } - visit(state) + visit(state) + } } for (state in snapshotOf(entityBuckets[tick % entityBuckets.size])) { @@ -546,13 +616,15 @@ internal object HolderStates { continue } - val entity = state.dispatcher.dispatcher as LivingEntity - if (!entity.isValid) { - remove(state) - continue - } + onOwner(state) { + val entity = state.dispatcher.dispatcher as LivingEntity + if (!entity.isValid) { + onFlushThread { remove(state) } + return@onOwner + } - visit(state) + visit(state) + } } } @@ -563,6 +635,10 @@ internal object HolderStates { } private fun visit(state: HolderState) { + if (state.isRemoved) { + return + } + guarded("visit holders for ${state.uuid}") { state.visit(tick, settings) } if (state.hasWork) { markDirty(state) @@ -580,31 +656,47 @@ internal object HolderStates { continue } - val entity = state.dispatcher.dispatcher as LivingEntity - if (entity.isDead) { - remove(state) - continue + onOwner(state) { + if (admit(state)) { + admitted++ + } } + } + } - // Not in the world yet; removals are caught by events. Given up on if it never arrives. - if (!entity.isValid) { - if (++state.admissionAttempts < MAX_ADMISSION_ATTEMPTS) { + /** + * Admit [state] if its entity is in the world. Returns if it was admitted. + */ + private fun admit(state: HolderState): Boolean { + val entity = state.dispatcher.dispatcher as LivingEntity + if (entity.isDead) { + onFlushThread { remove(state) } + return false + } + + // Not in the world yet; removals are caught by events. Given up on if it never arrives. + if (!entity.isValid) { + val attempts = ++state.admissionAttempts + onFlushThread { + if (attempts < MAX_ADMISSION_ATTEMPTS) { newStates.addLast(state) } else { remove(state) } - continue } + return false + } - if (state.needsCleanup) { - state.needsCleanup = false - entity.removeEcoAttributeModifiers() - } + if (state.needsCleanup) { + state.needsCleanup = false + entity.removeEcoAttributeModifiers() + } + onFlushThread { state.admitted = true markDirty(state) - admitted++ } + return true } private fun processDirty() { @@ -618,12 +710,12 @@ internal object HolderStates { val now = System.currentTimeMillis() for (state in batch) { - update(state, now) + onOwner(state) { update(state, now) } } } private fun update(state: HolderState, now: Long) { - if (states[state.uuid] !== state || !state.admitted) { + if (state.isRemoved || states[state.uuid] !== state || !state.admitted) { return } @@ -633,9 +725,13 @@ internal object HolderStates { } if (heldBack) { - dirty += state - } else if (state.kind == StateKind.CUSTOM && state.isIdle && states[state.uuid] === state) { - remove(state) + onFlushThread { dirty += state } + } else if (state.kind == StateKind.CUSTOM && state.isIdle) { + onFlushThread { + if (states[state.uuid] === state) { + remove(state) + } + } } } @@ -644,7 +740,7 @@ internal object HolderStates { * main thread or during a holder update, where the changes are applied in the next tick. */ fun flushNow(dispatcher: Dispatcher<*>) { - if (!isFlushThread || flushing) { + if (!isFlushThread || flushing || !Regions.owns(dispatcher)) { return } @@ -666,7 +762,7 @@ internal object HolderStates { * and asked on demand, storing nothing, for untracked dispatchers. */ fun holders(dispatcher: Dispatcher<*>): List { - if (!isFlushThread) { + if (!Regions.owns(dispatcher)) { return publishedHolders[dispatcher.uuid]?.all ?: askOnDemand(dispatcher) } @@ -688,7 +784,7 @@ internal object HolderStates { * The stored answer of [provider] for a tracked [dispatcher], or null if it must be scanned. */ fun storedAnswer(dispatcher: Dispatcher<*>, provider: HolderProvider): Collection? { - if (!isFlushThread) { + if (!Regions.owns(dispatcher)) { return publishedHolders[dispatcher.uuid]?.byProvider?.get(provider) } @@ -701,7 +797,7 @@ internal object HolderStates { } fun providedActiveEffects(dispatcher: Dispatcher<*>): List { - if (!isFlushThread) { + if (!Regions.owns(dispatcher)) { return publishedActiveEffects[dispatcher.uuid] ?: emptyList() } @@ -712,7 +808,7 @@ internal object HolderStates { * The cached placeholders of [holder] on a tracked [dispatcher], or null to generate them. */ fun cachedPlaceholders(dispatcher: Dispatcher<*>, holder: Holder): List? { - if (!isFlushThread) { + if (!Regions.owns(dispatcher)) { return null } @@ -777,7 +873,11 @@ internal object HolderStates { } task = null - for (state in states.values.toList()) { + for (state in synchronized(states) { states.values.toList() }) { + if (!canTouchNow(state)) { + continue + } + try { state.disableAll() } catch (e: Exception) { @@ -799,6 +899,15 @@ internal object HolderStates { } } + private fun canTouchNow(state: HolderState): Boolean { + val target = state.dispatcher.dispatcher + if (target is Entity) { + return Regions.canReach(target) + } + + return state.dispatcher.location?.let { Regions.canReach(it) } ?: true + } + /** * Handle a libreforge-based [disabledPlugin] being disabled: the shutdown sweep while the server is * stopping, otherwise only that plugin's effects and providers are removed. @@ -822,8 +931,8 @@ internal object HolderStates { HolderSignals.unregisterOwnedBy(classLoader, removed) unregisterHolderFunctions(classLoader) - for (state in states.values.toList()) { - state.disableOwnedBy(classLoader, removed) + for (state in synchronized(states) { states.values.toList() }) { + onOwner(state) { state.disableOwnedBy(classLoader, removed) } } plugin.logger.warning( From 27fe9d497d94b8c4c87a6c8dca5c606ac12acc9c Mon Sep 17 00:00:00 2001 From: Callum Johnson Date: Thu, 8 Oct 2026 22:44:48 +0100 Subject: [PATCH 7/9] feat: cross-region trigger dispatch --- .../kotlin/com/willfp/libreforge/Regions.kt | 18 +++++++++++ .../willfp/libreforge/effects/ElementLike.kt | 32 ++++++++++++++++++- .../com/willfp/libreforge/triggers/Trigger.kt | 21 ++++++++---- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt b/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt index d281e08fd..84fdbfaa5 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/Regions.kt @@ -83,6 +83,24 @@ internal object Regions { return Bukkit.isOwnedByCurrentRegion(center, radiusChunks) } + /** + * If [dispatcher] can be touched from here: its entity, else its location, else server-wide + * state. Always `true` off Folia. + */ + fun canReach(dispatcher: Dispatcher<*>): Boolean { + if (!isFolia) { + return true + } + + val target = dispatcher.dispatcher + if (target is Entity) { + return canReach(target) + } + + val location = dispatcher.location ?: return Bukkit.isGlobalTickThread() + return canReach(location) + } + /** * The longest distance up to [distance] that a ray or scan from [origin] can cover without * leaving this region. Always [distance] off Folia. diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/ElementLike.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/ElementLike.kt index 21db2f493..e2af72f46 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/ElementLike.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/ElementLike.kt @@ -8,6 +8,7 @@ import com.willfp.libreforge.DynamicConfigs import com.willfp.libreforge.DynamicNumericValue import com.willfp.libreforge.InjectionScope import com.willfp.libreforge.NamedValue +import com.willfp.libreforge.Regions import com.willfp.libreforge.conditions.ConditionList import com.willfp.libreforge.effects.arguments.EffectArgumentList import com.willfp.libreforge.filters.FilterList @@ -68,9 +69,37 @@ abstract class ElementLike : ConfigurableElement { return doTrigger(trigger) } + if (!Regions.canReach(trigger.dispatcher)) { + Regions.runOwned(trigger.dispatcher) { trigger(trigger) } + return true + } + return InjectionScope.open { triggerInScope(trigger) } } + /** + * Trigger [mutated], on the region of its dispatcher if a mutator moved it from [original] to one + * this thread doesn't own, carrying this call's placeholders there. + */ + private fun doTriggerOwned(original: DispatchedTrigger, mutated: DispatchedTrigger): Boolean { + val target = mutated.data.dispatcher + + if (target.dispatcher === original.data.dispatcher.dispatcher || Regions.canReach(target)) { + return doTrigger(mutated) + } + + val scope = InjectionScope.current() + Regions.runOwned(target) { + if (scope != null) { + InjectionScope.enter(scope) { doTrigger(mutated) } + } else { + doTrigger(mutated) + } + } + + return true + } + private fun triggerInScope(trigger: DispatchedTrigger): Boolean { // It would be nice to abstract repeat/delay away here, but that would be // really, really, overengineering it - even for me. @@ -155,7 +184,8 @@ abstract class ElementLike : ConfigurableElement { fun trigger() { // Set to true if triggered. - didTrigger = didTrigger or doTrigger( + didTrigger = didTrigger or doTriggerOwned( + trigger, trigger.copy( // Mutate again here for each repeat, as mutator args can // depend on %repeat_count% (e.g. spin_velocity in shoot_extra_arrows). diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/Trigger.kt b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/Trigger.kt index 7e80dcf92..e542941d1 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/triggers/Trigger.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/triggers/Trigger.kt @@ -4,6 +4,7 @@ import com.willfp.libreforge.Aliased import com.willfp.libreforge.Dispatcher import com.willfp.libreforge.ProvidedEffectBlock import com.willfp.libreforge.ProvidedHolder +import com.willfp.libreforge.Regions import com.willfp.libreforge.counters.bind.BoundCounters import com.willfp.libreforge.generatePlaceholders import com.willfp.libreforge.getProvidedActiveEffects @@ -95,12 +96,20 @@ abstract class Trigger( data: TriggerData, forceHolders: Collection? = null, allowDuplicates: Boolean = false - ) = dispatchOnEffects( - dispatcher, - data, - forceHolders?.getProvidedActiveEffects(dispatcher) ?: dispatcher.providedActiveEffects, - allowDuplicates - ) + ) { + if (!Regions.canReach(dispatcher) && data.event != null) { + Regions.isUnsupported("Triggers editing an event for a dispatcher in another region") + } + + Regions.runOwned(dispatcher) { + dispatchOnEffects( + dispatcher, + data, + forceHolders?.getProvidedActiveEffects(dispatcher) ?: dispatcher.providedActiveEffects, + allowDuplicates + ) + } + } /** * Dispatch the trigger on a collection of [ProvidedEffectBlock]s. From 4617e662a1bc4443dbc454e352b4a9637768f0d2 Mon Sep 17 00:00:00 2001 From: Callum Johnson Date: Fri, 9 Oct 2026 09:26:16 +0100 Subject: [PATCH 8/9] feat: Folia Support --- .../willfp/libreforge/effects/impl/EffectGlowNearbyBlocks.kt | 5 +++++ .../integrations/paper/impl/EffectDropPickupItem.kt | 2 ++ core/common/src/main/resources/plugin.yml | 1 + 3 files changed, 8 insertions(+) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGlowNearbyBlocks.kt b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGlowNearbyBlocks.kt index cd67a7b92..65ae5b42f 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGlowNearbyBlocks.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/effects/impl/EffectGlowNearbyBlocks.kt @@ -6,6 +6,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.util.TeamUtils import com.willfp.libreforge.ArgType import com.willfp.libreforge.NoCompileData +import com.willfp.libreforge.Regions import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Effect import com.willfp.libreforge.enumValueOfOrNull @@ -62,6 +63,10 @@ object EffectGlowNearbyBlocks : Effect("glow_nearby_blocks") { } override fun onTrigger(config: Config, data: TriggerData, compileData: NoCompileData): Boolean { + if (Regions.isUnsupported("glow_nearby_blocks")) { + return false + } + val location = data.location ?: return false val radius = config.getIntFromExpression("radius", data) diff --git a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/paper/impl/EffectDropPickupItem.kt b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/paper/impl/EffectDropPickupItem.kt index 9ddf84492..8713549dd 100644 --- a/core/common/src/main/kotlin/com/willfp/libreforge/integrations/paper/impl/EffectDropPickupItem.kt +++ b/core/common/src/main/kotlin/com/willfp/libreforge/integrations/paper/impl/EffectDropPickupItem.kt @@ -6,6 +6,7 @@ import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.items.Items import com.willfp.eco.util.TeamUtils import com.willfp.libreforge.ArgType +import com.willfp.libreforge.Regions import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.arguments import com.willfp.libreforge.effects.Chain @@ -66,6 +67,7 @@ object EffectDropPickupItem : Effect("drop_pickup_item") { val glowColor = config.getStringOrNull("glow-color") ?.let { runCatching { ChatColor.valueOf(it.uppercase()) }.getOrNull() } + ?.takeUnless { Regions.isUnsupported("drop_pickup_item glow-color") } val itemStack = Items.lookup(config.getString("item")).item diff --git a/core/common/src/main/resources/plugin.yml b/core/common/src/main/resources/plugin.yml index 42a08557a..3c956504a 100644 --- a/core/common/src/main/resources/plugin.yml +++ b/core/common/src/main/resources/plugin.yml @@ -2,6 +2,7 @@ name: libreforge version: ${projectVersion} main: com.willfp.libreforge.LibreforgeSpigotPlugin api-version: 1.21.8 +folia-supported: true authors: [ Auxilor, Exanthiax, Callum ] website: willfp.com From 61c9f9810f51046b6e1498979759b55776fff98b Mon Sep 17 00:00:00 2001 From: Exanthiax <107284021+Exanthiax@users.noreply.github.com> Date: Sun, 11 Oct 2026 12:29:03 +0100 Subject: [PATCH 9/9] Update gradle.properties --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 859af237b..43beaddf2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ -version = 2026.40 +version = 2026.41 kotlin.code.style = official org.gradle.parallel=true -eco-version=2026.40 +eco-version=2026.41 kotlin.daemon.jvmargs=-Xmx2g