a68f328
Branches > main > a68f328 > files
Update event, fact, and effect handling code
Commit: a68f328d1c8d28147eea9f75f5eb102c71c26333
Parent: bedd093
14 changed files:
src/main/kotlin/net/bowmap/Main.kt 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 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 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 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 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 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 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 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 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 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) : Factsrc/main/kotlin/net/bowmap/stormbow/FactRegistry.kt 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 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 = 0src/main/kotlin/net/bowmap/stormbow/effect/Effect.kt 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 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-}