a68f328

Branches > main > a68f328 > files

Update event, fact, and effect handling code

Grahp, 2026-07-03

Commit: a68f328d1c8d28147eea9f75f5eb102c71c26333
Parent: bedd093

14 changed files:

src/main/kotlin/net/bowmap/Main.kt modified (view file)

@@ -15,12 +15,6 @@       server.sendMessage(Component.text("Loading Bowmap..."))        setupStormbow(this)-      Chat.register()-      ZoneGuard.register()-      Death.register()-      Welcome.register()-      Fishing.register()-      Durability.register()     } catch (t: Exception) {       logger.severe("Startup Failed!")       server.shutdown()

src/main/kotlin/net/bowmap/handlers/Chat.kt modified (view file)

@@ -1,24 +1,18 @@ package net.bowmap.handlers -import net.bowmap.stormbow.handler-import net.bowmap.stormbow.fact.*-import net.bowmap.stormbow.effect.*+import net.bowmap.stormbow.* import net.kyori.adventure.text.Component  import net.bowmap.server.permission.prefix import net.bowmap.server.adventure.headComponent import kotlinx.collections.immutable.* -object Chat {-  fun register() {-    handler<PlayerSentChatMessage> {-      val msg = headComponent(player).append(-        Component.text(" "),-        prefix(player),-        player.displayName(),-        Component.text(": "),-        message)-      effects(SendMessage(audience = audience, message = msg))-    }-  }+fun sendUpdatedChatMessage(f: PlayerSentChatMessage): Effects {+  val msg = headComponent(f.player).append(+    Component.text(" "),+    prefix(f.player),+    f.player.displayName(),+    Component.text(": "),+    f.message)+  return Effects(SendMessage(audience = f.audience, message = msg)) }

src/main/kotlin/net/bowmap/handlers/Death.kt modified (view file)

@@ -1,8 +1,6 @@ package net.bowmap.handlers -import net.bowmap.stormbow.handler-import net.bowmap.stormbow.fact.*-import net.bowmap.stormbow.effect.*+import net.bowmap.stormbow.*  import net.bowmap.server.adventure.headComponent import net.bowmap.server.item.*@@ -30,25 +28,18 @@          .append(Component.text(" Died.", RED)) } -object Death {-  fun register() {-    handler<PlayerDied> {-      // TODO tp to 0, 0-      effects(-        Cancel(),-        Broadcast(message = deathMessage(player, cause)),-        Teleport(entity = player, location = Location(player.world, 0.5, 57.0, 0.5))-      )-    }+fun handlePlayerDeath(f: PlayerDied): Effects =+  Effects(+    Cancel(),+    Broadcast(message = deathMessage(f.player, f.cause)),+    Teleport(entity = f.player, location = Location(f.player.world, 0.5, 57.0, 0.5))+  ) -    handler<PlayerKilledEntity> {-      // TODO loot tables-      val lootTable = mobLootTable(entity)-      if (lootTable is Loot) {-        effects(Give(player, lootTable.getLoot()))-      } else {-        effects(Give(player, Item(CustomItemType.BUG)), Debug("lootTable", lootTable), Debug("class", entity::class))-      }-    }+fun handlePlayerKill(f: PlayerKilledEntity): Effects {+  val lootTable = mobLootTable(f.entity)+  if (lootTable is Loot) {+    return Effects(Give(f.player, lootTable.getLoot()))+  } else {+    return Effects(Give(f.player, Item(CustomItemType.BUG)), Debug("lootTable", lootTable), Debug("class", f.entity::class))   } }

src/main/kotlin/net/bowmap/handlers/Durability.kt modified (view file)

@@ -1,14 +1,6 @@ package net.bowmap.handlers -import net.bowmap.stormbow.handler-import net.bowmap.stormbow.fact.*-import net.bowmap.stormbow.effect.*+import net.bowmap.stormbow.* -object Durability {-  fun register() {-    // Bowmap items do not have durability!-    handler<ItemLostDurability> {-      effects(Cancel())-    }-  }-}+fun handleItemLosingDurability(f: ItemLostDurability): Effects =+  Effects(Cancel())

src/main/kotlin/net/bowmap/handlers/Fishing.kt modified (view file)

@@ -1,8 +1,6 @@ package net.bowmap.handlers -import net.bowmap.stormbow.handler-import net.bowmap.stormbow.fact.*-import net.bowmap.stormbow.effect.*+import net.bowmap.stormbow.*  import net.bowmap.server.loot.* import org.bukkit.Material.*@@ -15,6 +13,7 @@ import net.bowmap.server.loot.tables.fishingLootTable import net.bowmap.server.item.toItemStacks +// TODO f, not e private fun calculateFishVelocity(e: PlayerCaughtFish): Vector {   val eye = e.player.eyeLocation   val distance = eye.distance(e.hook.location)@@ -25,20 +24,17 @@             .multiply(0.2 + distance * 0.1) } -object Fishing {-  fun register() {-    handler<PlayerStartedFishing> {-      effects(SetFishTime(ticks = 20))-    }+fun handlePlayerFishing(f: PlayerStartedFishing): Effects =+  Effects(SetFishTime(ticks = 20)) -    handler<PlayerCaughtFish> {-      val loot = fishingLootTable.getLoot()+fun handlePlayerCatchingFish(f: PlayerCaughtFish): Effects {+  val loot = fishingLootTable.getLoot() -      for (stack in toItemStacks(loot)) {-        val item = hook.world.dropItem(hook.location, stack)-        item.setVelocity(calculateFishVelocity(this))-      }-      effects()-    }+  // TODO use an effect!+  for (stack in toItemStacks(loot)) {+    val item = f.hook.world.dropItem(f.hook.location, stack)+    item.setVelocity(calculateFishVelocity(f))   }++  return Effects() }

src/main/kotlin/net/bowmap/handlers/Welcome.kt modified (view file)

@@ -1,25 +1,17 @@ package net.bowmap.handlers -import net.bowmap.stormbow.handler-import net.bowmap.stormbow.fact.*-import net.bowmap.stormbow.effect.*+import net.bowmap.stormbow.*  import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor import net.bowmap.server.adventure.headComponent -object Welcome {-  fun register() {-    handler<PlayerJoined> {-        effects(Broadcast(Component.text("Welcome to Bowmap, ", NamedTextColor.GREEN)-        .append(headComponent(player).color(NamedTextColor.WHITE),-                Component.text(" "),-                player.displayName().colorIfAbsent(NamedTextColor.WHITE),-                Component.text("!", NamedTextColor.GREEN))))-    }+fun handlePlayerJoin(f: PlayerJoined): Effects =+  Effects(Broadcast(Component.text("Welcome to Bowmap, ", NamedTextColor.GREEN)+                             .append(headComponent(f.player).color(NamedTextColor.WHITE),+                                     Component.text(" "),+                                     f.player.displayName().colorIfAbsent(NamedTextColor.WHITE),+                                     Component.text("!", NamedTextColor.GREEN)))) -    handler<PlayerQuit> {-      effects(Broadcast(headComponent(player).append(Component.text(" "), player.displayName(), Component.text(" left the server", NamedTextColor.RED))))-    }-  }-}+fun handlePlayerQuit(f: PlayerQuit) : Effects =+  Effects(Broadcast(headComponent(f.player).append(Component.text(" "), f.player.displayName(), Component.text(" left the server", NamedTextColor.RED))))

src/main/kotlin/net/bowmap/handlers/ZoneGuard.kt modified (view file)

@@ -1,8 +1,6 @@ package net.bowmap.handlers -import net.bowmap.stormbow.handler-import net.bowmap.stormbow.fact.*-import net.bowmap.stormbow.effect.*+import net.bowmap.stormbow.*  import org.bukkit.event.Listener import org.bukkit.event.block.BlockBreakEvent@@ -45,14 +43,9 @@   return isInsideRegion(spawn, b.location) } -object ZoneGuard {-  fun register() {-    handler<PlayerBrokeBlock> {-      if (isBlockInSpawn(block)) {-        effects(Cancel())-      } else {-        effects()-      }-    }+fun handleBlockBreak(f: PlayerBrokeBlock): Effects =+  if (isBlockInSpawn(f.block)) {+    Effects(Cancel())+  } else {+    Effects()   }-}

src/main/kotlin/net/bowmap/server/Update.kt added (view file)

@@ -0,0 +1,17 @@+package net.bowmap.server++import net.bowmap.stormbow.*+import kotlinx.collections.immutable.*+import net.bowmap.handlers.*++fun update(fact: Fact): Effects = when(fact) {+  is PlayerSentChatMessage -> sendUpdatedChatMessage(fact)+  is PlayerBrokeBlock -> handleBlockBreak(fact)+  is PlayerDied -> handlePlayerDeath(fact)+  is PlayerKilledEntity -> handlePlayerKill(fact)+  is PlayerJoined -> handlePlayerJoin(fact)+  is PlayerQuit -> handlePlayerQuit(fact)+  is PlayerStartedFishing -> handlePlayerFishing(fact)+  is PlayerCaughtFish -> handlePlayerCatchingFish(fact)+  is ItemLostDurability -> handleItemLosingDurability(fact)+}

src/main/kotlin/net/bowmap/stormbow/Effect.kt renamed from src/main/kotlin/net/bowmap/stormbow/effect/Effects.kt (view file)

@@ -1,15 +1,49 @@-package net.bowmap.stormbow.effect+package net.bowmap.stormbow  import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor.* import net.kyori.adventure.audience.Audience-import net.bowmap.stormbow.effect.* import org.bukkit.inventory.ItemStack import org.bukkit.Bukkit import org.bukkit.Location import org.bukkit.entity.Player import org.bukkit.entity.Entity import net.bowmap.server.item.*+import kotlinx.collections.immutable.*++/// An effect is a piece of data representing a side effect that can be executed,+/// such as with a Bukkit API call+sealed interface Effect++/// An effect that knows how to execute itself.+/// Some effects are to be run by event listeners themselves+sealed interface DefaultEffect : Effect {+  fun execute()+}++/// An effect that requires an event handler to respond to it+sealed interface EventEffect : Effect++data class Effects(val list: PersistentList<Effect>) {+  constructor(vararg effects: Effect) : this(effects.toList().toPersistentList())+}++fun execute(effects: Effects) {+  effects.list.forEach { effect ->+    if (effect is DefaultEffect) {+      effect.execute()+    }+  }+}++/// Returns a the first effect of type T in the list effects+inline fun <reified T : Effect> find(effects: Effects): T? =+  effects.list.filterIsInstance<T>().firstOrNull()++/// Returns whether an effect of type T exists in the list of effects+inline fun <reified T : Effect> contains(effects: Effects): Boolean =+  effects.list.any {it is T}+  // TODO you can send a message to more than one audience // I'd like audiences to just be able to be collections :/@@ -43,10 +77,12 @@  /// Cancels the current action. /// Events will generally call .setCancelled(true), but this is a logical action, and they could do something else.-class Cancel() : Effect-data class SetFishTime(val ticks: Int) : Effect+class Cancel() : EventEffect+data class SetFishTime(val ticks: Int) : EventEffect+ data class Teleport(val entity: Entity, val location: Location) : DefaultEffect {   override fun execute() {     entity.teleport(location)   } }+

src/main/kotlin/net/bowmap/stormbow/Fact.kt renamed from src/main/kotlin/net/bowmap/stormbow/fact/Facts.kt (view file)

@@ -1,4 +1,4 @@-package net.bowmap.stormbow.fact+package net.bowmap.stormbow  import org.bukkit.entity.* import org.bukkit.damage.DamageSource@@ -7,6 +7,8 @@ import net.kyori.adventure.audience.Audience import net.kyori.adventure.text.Component +sealed interface Fact+ // TODO a player is a game entity... // Game entities should probably be referenced by their UUID data class PlayerSentChatMessage(val player: Player, val audience: Collection<Audience>, val message: Component) : Fact

src/main/kotlin/net/bowmap/stormbow/FactRegistry.kt deleted (view file)

@@ -1,19 +0,0 @@-package net.bowmap.stormbow--import kotlinx.collections.immutable.*-import net.bowmap.stormbow.fact.Fact-import net.bowmap.stormbow.effect.Effect-import kotlin.reflect.KClass--// TODO fact handlers can also yield more facts, which themselves need to be handled recursively!-typealias Handler = (Fact) -> PersistentList<Effect>--var registry: PersistentMap<KClass<out Fact>, PersistentList<Handler>> = persistentMapOf()--inline fun <reified T : Fact> handler(noinline handler: T.() -> PersistentList<Effect>) {-  val adapted: (Fact) -> PersistentList<Effect> = { fact ->-    handler(fact as T)-  }-  val list = registry[T::class] ?: persistentListOf()-  registry = registry.putting(T::class, list.adding(adapted))-}

src/main/kotlin/net/bowmap/stormbow/Listeners.kt modified (view file)

@@ -11,10 +11,10 @@  import org.bukkit.event.player.PlayerFishEvent.State -import net.bowmap.stormbow.fact.*-import net.bowmap.stormbow.effect.*+import net.bowmap.server.update  import org.bukkit.event.player.PlayerItemDamageEvent+import kotlinx.collections.immutable.*  /// Listens to many Bukkit events and handles their respective facts class Listeners : Listener {@@ -23,12 +23,12 @@     val f = PlayerSentChatMessage(player = e.player, audience = e.viewers(), message = e.message())     e.setCancelled(true)     // Here, SendMessage effects could replace the chat message, but there's no reason to-    execute(handle(f))+    execute(update(f))   }    @EventHandler   fun onItemDamage(e: PlayerItemDamageEvent) {-    val effects = handle(ItemLostDurability(player = e.player, itemStack = e.item))+    val effects = update(ItemLostDurability(player = e.player, itemStack = e.item))     if (contains<Cancel>(effects)) {       e.setCancelled(true)     }@@ -37,7 +37,7 @@   @EventHandler   fun onBlockBreak(e: BlockBreakEvent) {     val f = PlayerBrokeBlock(player = e.player, block = e.block)-    val effects = handle(f)+    val effects = update(f)     if (contains<Cancel>(effects)) {       e.setCancelled(true)     }@@ -50,7 +50,7 @@     val attacker = e.damageSource.causingEntity      if (victim is Player) {-      val effects = handle(PlayerDied(player = victim, cause = e.damageSource))+      val effects = update(PlayerDied(player = victim, cause = e.damageSource))       if (contains<Cancel>(effects)) {         e.setCancelled(true)       }@@ -58,8 +58,8 @@     }      if (attacker is Player) {-      val effects = handle(PlayerKilledEntity(player = attacker, entity = victim, cause = e.damageSource))-      if (effects.any {it is Cancel}) {+      val effects = update(PlayerKilledEntity(player = attacker, entity = victim, cause = e.damageSource))+      if (contains<Cancel>(effects)) {         e.setCancelled(true)       }       execute(effects)@@ -72,20 +72,20 @@   @EventHandler   fun onPlayerJoin(e: PlayerJoinEvent) {     e.joinMessage(null)-    execute(handle(PlayerJoined(player = e.player)))+    execute(update(PlayerJoined(player = e.player)))   }    @EventHandler   fun onPlayerQuit(e: PlayerQuitEvent) {     e.quitMessage(null)-    execute(handle(PlayerQuit(player = e.player)))+    execute(update(PlayerQuit(player = e.player)))   }    @EventHandler   fun onFishEvent(e: PlayerFishEvent) {     when (e.state) {       State.FISHING -> {-        val effects = handle(PlayerStartedFishing(player = e.player, hook = e.hook))+        val effects = update(PlayerStartedFishing(player = e.player, hook = e.hook))         if (contains<Cancel>(effects)) {           e.setCancelled(true)         }@@ -97,7 +97,7 @@       }        State.CAUGHT_FISH -> {-        execute(handle(PlayerCaughtFish(player = e.player, hook = e.hook)))+        execute(update(PlayerCaughtFish(player = e.player, hook = e.hook)))          e.caught?.remove()         e.expToDrop = 0

src/main/kotlin/net/bowmap/stormbow/effect/Effect.kt deleted (view file)

@@ -1,33 +0,0 @@-package net.bowmap.stormbow.effect--import kotlinx.collections.immutable.*--/// An effect is a piece of data representing a side effect that can be executed,-/// such as with a Bukkit API call-interface Effect--/// An effect that knows how to execute itself.-/// Some effects are to be run by event listeners themselves-interface DefaultEffect : Effect {-  fun execute()-}--fun execute(effects: PersistentList<Effect>) {-  effects.forEach { effect ->-    if (effect is DefaultEffect) {-      effect.execute()-    }-  }-}--fun effects(vararg e: Effect) = persistentListOf(*e)--/// Returns a the first effect of type T in the list effects-inline fun <reified T : Effect> find(effects: PersistentList<Effect>): T? {-  return effects.filterIsInstance<T>().firstOrNull()-}--/// Returns whether an effect of type T exists in the list of effects-inline fun <reified T : Effect> contains(effects: PersistentList<Effect>): Boolean {-  return effects.any {it is T}-}

src/main/kotlin/net/bowmap/stormbow/fact/Fact.kt deleted (view file)

@@ -1,16 +0,0 @@-package net.bowmap.stormbow.fact--import kotlinx.collections.immutable.*-import net.bowmap.stormbow.effect.Effect-import net.bowmap.stormbow.registry--/// A fact is something that happened in the game-interface Fact--fun handle(fact: Fact): PersistentList<Effect> {-  var effects: PersistentList<Effect> = persistentListOf()-  registry[fact::class]?.forEach { handler ->-    effects = effects.addingAll(handler(fact))-  }-  return effects-}