latest

Branches > main > latest > files

Wrap mutable bukkit classes in stormbow types

Grahp, 2026-07-03

Commit: bfb81082b7efa03d53e62da84f32a75821ae1807
Parent: 039d1df

22 changed files:

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

@@ -1,6 +1,7 @@ package net.bowmap.handlers  import net.bowmap.stormbow.*+import net.bowmap.server.fact.* import net.kyori.adventure.text.Component import net.bowmap.server.permission.prefix import net.bowmap.server.adventure.headComponent@@ -9,7 +10,7 @@   val msg = headComponent(f.player).append(     Component.text(" "),     prefix(f.player),-    f.player.displayName(),+    f.player.name,     Component.text(": "),     f.message)   return Effects(SendMessage(audience = f.audience, message = msg))

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

@@ -1,30 +1,23 @@ package net.bowmap.handlers  import net.bowmap.stormbow.*+import net.bowmap.server.fact.*+import net.bowmap.stormbow.game.*  import net.bowmap.server.adventure.headComponent-import net.bowmap.server.item.* import net.bowmap.server.loot.tables.mobLootTable import net.bowmap.server.loot.Loot import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor.* import org.bukkit.Material-import org.bukkit.entity.Entity-import org.bukkit.entity.Player-import org.bukkit.entity.Zombie-import org.bukkit.event.EventHandler-import org.bukkit.event.Listener-import org.bukkit.event.entity.EntityDeathEvent-import org.bukkit.inventory.ItemStack import org.bukkit.damage.DamageSource-import org.bukkit.Location  // Doesn't use attacker but how the player died does contribute to the death event // This should probably be the damage source? private fun deathMessage(player: Player, attacker: DamageSource): Component {   return headComponent(player)          .append(Component.text(" "))-         .append(player.displayName())+         .append(player.name)          .append(Component.text(" Died.", RED)) } @@ -32,7 +25,7 @@   Effects(     Cancel(),     Broadcast(message = deathMessage(f.player, f.cause)),-    Teleport(entity = f.player, location = Location(f.player.world, 0.5, 57.0, 0.5))+    // Teleport(entity = f.player, location = Location(f.player.world, 0.5, 57.0, 0.5))   )  fun handlePlayerKill(f: PlayerKilledEntity): Effects {

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

@@ -1,6 +1,7 @@ package net.bowmap.handlers  import net.bowmap.stormbow.*+import net.bowmap.server.fact.*  fun handleItemLosingDurability(f: ItemLostDurability): Effects =   Effects(Cancel())

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

@@ -1,40 +1,36 @@ package net.bowmap.handlers  import net.bowmap.stormbow.*+import net.bowmap.stormbow.game.*+import net.bowmap.server.fact.*  import net.bowmap.server.loot.* import org.bukkit.Material.*-import org.bukkit.event.EventHandler-import org.bukkit.event.Listener-import org.bukkit.event.player.PlayerFishEvent-import org.bukkit.event.player.PlayerFishEvent.State-import org.bukkit.util.Vector  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)--  return eye.subtract(e.hook.location)-            .toVector()-            .normalize()-            .multiply(0.2 + distance * 0.1)-}+// private fun calculateFishVelocity(e: PlayerCaughtFish): Vector {+//   val eye = e.player.eyeLocation+//   val distance = eye.distance(e.hook.location)+//+//   return eye.subtract(e.hook.location)+//             .toVector()+//             .normalize()+//             .multiply(0.2 + distance * 0.1)+// }  fun handlePlayerFishing(f: PlayerStartedFishing): Effects =   Effects(SetFishTime(ticks = 20)) +// TODO effects here! fun handlePlayerCatchingFish(f: PlayerCaughtFish): Effects {-  val loot = fishingLootTable.getLoot()--  // TODO use an effect!-  for (stack in toItemStacks(loot)) {-    val item = f.hook.world.dropItem(f.hook.location, stack)-    item.setVelocity(calculateFishVelocity(f))-  }+  // val loot = fishingLootTable.getLoot()+  //+  // 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,6 +1,7 @@ package net.bowmap.handlers  import net.bowmap.stormbow.*+import net.bowmap.server.fact.*  import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor@@ -10,8 +11,8 @@   Effects(Broadcast(Component.text("Welcome to Bowmap, ", NamedTextColor.GREEN)                              .append(headComponent(f.player).color(NamedTextColor.WHITE),                                      Component.text(" "),-                                     f.player.displayName().colorIfAbsent(NamedTextColor.WHITE),+                                     f.player.name.colorIfAbsent(NamedTextColor.WHITE),                                      Component.text("!", NamedTextColor.GREEN))))  fun handlePlayerQuit(f: PlayerQuit) : Effects =-  Effects(Broadcast(headComponent(f.player).append(Component.text(" "), f.player.displayName(), Component.text(" left the server", NamedTextColor.RED))))+  Effects(Broadcast(headComponent(f.player).append(Component.text(" "), f.player.name, Component.text(" left the server", NamedTextColor.RED))))

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

@@ -1,47 +1,41 @@ package net.bowmap.handlers  import net.bowmap.stormbow.*+import net.bowmap.stormbow.game.*+import net.bowmap.server.fact.* -import org.bukkit.event.Listener-import org.bukkit.event.block.BlockBreakEvent-import org.bukkit.event.EventHandler-import org.bukkit.block.Block-import org.bukkit.Bukkit-import org.bukkit.Location+// data class Region(val from: Location, val to: Location) {+//   init {+//     require(from.world == to.world) {+//       "Regions can only exist in a single world"+//     }+//   }+// }+//+// // TODO hmm...+// // Would prefer if I didn't have to just rely on this+// val spawn = Region(Location(5, spawnWorld.minHeight.toDouble(), -5.0), Location(spawnWorld, -5.0, spawnWorld.maxHeight - 1.0, 5.0))+//+// fun isInsideRegion(region: Region, location: Location): Boolean {+//     if (location.world != region.from.world) return false+//+//     val minX = minOf(region.from.blockX, region.to.blockX)+//     val maxX = maxOf(region.from.blockX, region.to.blockX)+//+//     val minY = minOf(region.from.blockY, region.to.blockY)+//     val maxY = maxOf(region.from.blockY, region.to.blockY)+//+//     val minZ = minOf(region.from.blockZ, region.to.blockZ)+//     val maxZ = maxOf(region.from.blockZ, region.to.blockZ)+//+//     return location.blockX in minX..maxX &&+//            location.blockY in minY..maxY &&+//            location.blockZ in minZ..maxZ+// } -data class Region(val from: Location, val to: Location) {-  init {-    require(from.world == to.world) {-      "Regions can only exist in a single world"-    }-  }-}--// TODO hmm...-// Would prefer if I didn't have to just rely on this-val spawnWorld = Bukkit.getWorld("world")!!-val spawn = Region(Location(spawnWorld, 5.0, spawnWorld.minHeight.toDouble(), -5.0), Location(spawnWorld, -5.0, spawnWorld.maxHeight - 1.0, 5.0))--fun isInsideRegion(region: Region, location: Location): Boolean {-    if (location.world != region.from.world) return false--    val minX = minOf(region.from.blockX, region.to.blockX)-    val maxX = maxOf(region.from.blockX, region.to.blockX)--    val minY = minOf(region.from.blockY, region.to.blockY)-    val maxY = maxOf(region.from.blockY, region.to.blockY)--    val minZ = minOf(region.from.blockZ, region.to.blockZ)-    val maxZ = maxOf(region.from.blockZ, region.to.blockZ)--    return location.blockX in minX..maxX &&-           location.blockY in minY..maxY &&-           location.blockZ in minZ..maxZ-}--fun isBlockInSpawn(b: Block): Boolean {-  return isInsideRegion(spawn, b.location)-}+fun isBlockInSpawn(b: Block): Boolean =+  // isInsideRegion(spawn, b.location)+  false  fun handleBlockBreak(f: PlayerBrokeBlock): Effects =   if (isBlockInSpawn(f.block)) {

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

@@ -1,6 +1,7 @@ package net.bowmap.server  import net.bowmap.stormbow.*+import net.bowmap.server.fact.* import net.bowmap.handlers.*  // TODO call setup() from an onEnable fact

src/main/kotlin/net/bowmap/server/adventure/Utils.kt modified (view file)

@@ -1,12 +1,11 @@ package net.bowmap.server.adventure -import org.bukkit.entity.Player-+import net.bowmap.stormbow.game.* import net.kyori.adventure.text.Component; import net.kyori.adventure.text.`object`.ObjectContents;  val SPACE = Component.text()  fun headComponent(p: Player): Component {-  return Component.`object`(ObjectContents.playerHead(p.uniqueId))+  return Component.`object`(ObjectContents.playerHead(p.uuid)) }

src/main/kotlin/net/bowmap/server/fact/Fact.kt modified (view file)

@@ -1,24 +1,20 @@ package net.bowmap.server.fact -// import org.bukkit.entity.*-// import org.bukkit.damage.DamageSource-// import org.bukkit.block.Block-// import org.bukkit.inventory.ItemStack+import org.bukkit.damage.DamageSource import net.kyori.adventure.audience.Audience import net.kyori.adventure.text.Component-import net.bowmap.server.entity.Player+import net.bowmap.stormbow.game.*  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 data class PlayerBrokeBlock(val player: Player, val block: Block) : Fact data class PlayerKilledEntity(val player: Player, val entity: Entity, val cause: DamageSource) : Fact data class PlayerDied(val player: Player, val cause: DamageSource) : Fact data class PlayerJoined(val player: Player) : Fact data class PlayerQuit(val player: Player) : Fact-data class PlayerStartedFishing(val player: Player, val hook: FishHook) : Fact-data class PlayerCaughtFish(val player: Player, val hook: FishHook) : Fact+// Will eventually need fishing related things :p+data class PlayerStartedFishing(val player: Player) : Fact+data class PlayerCaughtFish(val player: Player) : Fact // Bowmap items do not have durability, so they can't be used here data class ItemLostDurability(val player: Player) : Fact

src/main/kotlin/net/bowmap/server/item/ItemStacks.kt deleted (view file)

@@ -1,36 +0,0 @@-/// Itemstack interop functions-package net.bowmap.server.item--import org.bukkit.inventory.ItemStack--fun toItemStack(item: Item): ItemStack {-  // TODO custommodeldata-  val itemStack = ItemStack(material(item.type), item.amount)-  val meta = itemStack.getItemMeta()--  // itemName is not editable via an anvil, which is the desired behavior-  // TODO make sure its not italics!-  meta.itemName(item.name)-  meta.lore(computeLore(item))--  itemStack.setItemMeta(meta)-  return itemStack-}--fun toItemStacks(items: List<Item>): List<ItemStack> {-  return items.map(::toItemStack)-}--/// Turns a vanilla ItemStack into a Bowmap item.-/// This function is the inverse of toItemStack, so that toItem(toItemStack(x)) == x-fun toItem(itemStack: ItemStack): Item {-  // TODO custommodeldata-  // See the above itemName comment-  // val meta = itemStack.getItemMeta()-  return Item(-    // TODO right now this won't work with any custom materials-    // It'll have to be stored somwhere on the item :)-    type = MaterialItemType(itemStack.type),-    amount = itemStack.amount,-  )-}

src/main/kotlin/net/bowmap/server/loot/LootTables.kt modified (view file)

@@ -2,7 +2,7 @@  import org.bukkit.Material import java.util.concurrent.ThreadLocalRandom-import net.bowmap.server.item.*+import net.bowmap.stormbow.game.*  interface Loot {   fun getLoot(): List<Item>

src/main/kotlin/net/bowmap/server/loot/tables/Mobs.kt modified (view file)

@@ -1,9 +1,7 @@ package net.bowmap.server.loot.tables -import org.bukkit.entity.Zombie-import org.bukkit.entity.Entity-import net.bowmap.server.item.* import kotlin.reflect.KClass+import net.bowmap.stormbow.game.* import net.bowmap.server.loot.* import org.bukkit.Material.* @@ -21,7 +19,7 @@   ) ) -fun mobLootTable(entity: Entity): Loot? = when (entity) {-  is Zombie -> zombieLootTable+fun mobLootTable(entity: Entity): Loot? = when (entity.entityType) {+  EntityType.ZOMBIE -> zombieLootTable   else -> null }

src/main/kotlin/net/bowmap/server/permission/Rank.kt modified (view file)

@@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextDecoration-import org.bukkit.entity.Player+import net.bowmap.stormbow.game.*  enum class Rank {   DEFAULT, OWNER@@ -15,9 +15,10 @@ val ranks = mapOf(grahp to Rank.OWNER)  fun rank(p: Player): Rank {-  return ranks.getOrDefault(p.uniqueId, Rank.DEFAULT)+  return ranks.getOrDefault(p.uuid, Rank.DEFAULT) } +// TODO use when val rankPrefix = mapOf(   Rank.OWNER to Component.text("Owner")                          .color(NamedTextColor.RED)

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

@@ -6,9 +6,7 @@ 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 net.bowmap.stormbow.game.* import kotlinx.collections.immutable.*  /// An effect is a piece of data representing a side effect that can be executed,@@ -71,7 +69,7 @@ data class Give(val player: Player, val items: List<Item>) : DefaultEffect {   constructor(player: Player, item: Item) : this(player, listOf(item))   override fun execute() {-    player.give(items.map(::toItemStack))+    Bukkit.getPlayer(player.uuid)?.give(items.map(::toItemStack))   } } @@ -82,7 +80,7 @@  data class Teleport(val entity: Entity, val location: Location) : DefaultEffect {   override fun execute() {-    entity.teleport(location)+    Bukkit.getEntity(entity.uuid)?.teleport(location)   } } 

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

@@ -5,7 +5,6 @@ import org.bukkit.event.entity.* import org.bukkit.event.player.* import org.bukkit.event.block.*-import org.bukkit.entity.* import net.kyori.adventure.text.Component import net.kyori.adventure.audience.Audience @@ -17,11 +16,13 @@ import org.bukkit.event.player.PlayerItemDamageEvent import kotlinx.collections.immutable.* +import net.bowmap.stormbow.game.*+ /// Listens to many Bukkit events and handles their respective facts class Listeners : Listener {   @EventHandler   fun onPlayerChat(e: AsyncChatEvent) {-    val f = PlayerSentChatMessage(player = e.player, audience = e.viewers(), message = e.message())+    val f = PlayerSentChatMessage(player = fromBukkitPlayer(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(update(f))@@ -29,7 +30,7 @@    @EventHandler   fun onItemDamage(e: PlayerItemDamageEvent) {-    val effects = update(ItemLostDurability(player = e.player, itemStack = e.item))+    val effects = update(ItemLostDurability(player = fromBukkitPlayer(e.player)))     if (contains<Cancel>(effects)) {       e.setCancelled(true)     }@@ -37,7 +38,7 @@    @EventHandler   fun onBlockBreak(e: BlockBreakEvent) {-    val f = PlayerBrokeBlock(player = e.player, block = e.block)+    val f = PlayerBrokeBlock(player = fromBukkitPlayer(e.player), block = toGameBlock(e.block))     val effects = update(f)     if (contains<Cancel>(effects)) {       e.setCancelled(true)@@ -50,16 +51,16 @@     val victim = e.entity     val attacker = e.damageSource.causingEntity -    if (victim is Player) {-      val effects = update(PlayerDied(player = victim, cause = e.damageSource))+    if (victim is org.bukkit.entity.Player) {+      val effects = update(PlayerDied(player = fromBukkitPlayer(victim), cause = e.damageSource))       if (contains<Cancel>(effects)) {         e.setCancelled(true)       }       execute(effects)     } -    if (attacker is Player) {-      val effects = update(PlayerKilledEntity(player = attacker, entity = victim, cause = e.damageSource))+    if (attacker is org.bukkit.entity.Player) {+      val effects = update(PlayerKilledEntity(player = fromBukkitPlayer(attacker), entity = toGameEntity(victim), cause = e.damageSource))       if (contains<Cancel>(effects)) {         e.setCancelled(true)       }@@ -73,20 +74,20 @@   @EventHandler   fun onPlayerJoin(e: PlayerJoinEvent) {     e.joinMessage(null)-    execute(update(PlayerJoined(player = e.player)))+    execute(update(PlayerJoined(player = fromBukkitPlayer(e.player))))   }    @EventHandler   fun onPlayerQuit(e: PlayerQuitEvent) {     e.quitMessage(null)-    execute(update(PlayerQuit(player = e.player)))+    execute(update(PlayerQuit(player = fromBukkitPlayer(e.player))))   }    @EventHandler   fun onFishEvent(e: PlayerFishEvent) {     when (e.state) {       State.FISHING -> {-        val effects = update(PlayerStartedFishing(player = e.player, hook = e.hook))+        val effects = update(PlayerStartedFishing(player = fromBukkitPlayer(e.player)))         if (contains<Cancel>(effects)) {           e.setCancelled(true)         }@@ -98,7 +99,7 @@       }        State.CAUGHT_FISH -> {-        execute(update(PlayerCaughtFish(player = e.player, hook = e.hook)))+        execute(update(PlayerCaughtFish(player = fromBukkitPlayer(e.player))))          e.caught?.remove()         e.expToDrop = 0

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

@@ -4,6 +4,7 @@ import org.bukkit.plugin.java.JavaPlugin import net.kyori.adventure.text.Component +// TODO use a queue and have bowmap stuff depend on stormbow stuff but not the other way around import net.bowmap.server.setup  class Plugin : JavaPlugin() {

src/main/kotlin/net/bowmap/stormbow/README.md added (view file)

@@ -0,0 +1 @@+Bukkit wrapping immutable API.

src/main/kotlin/net/bowmap/stormbow/game/Block.kt added (view file)

@@ -0,0 +1,7 @@+package net.bowmap.stormbow.game++// TODO data as well+public class Block(val itemType: ItemType)++fun toGameBlock(block: org.bukkit.block.Block) =+  Block(MaterialItemType(block.type))

src/main/kotlin/net/bowmap/stormbow/game/Entity.kt added (view file)

@@ -0,0 +1,36 @@+package net.bowmap.stormbow.game++import java.util.UUID+import net.kyori.adventure.text.Component++// An immutable reference to a mutable entity+// Can only call pure functions on this entity to get things from it+// And execute effects on it to do things to it++// Players also go under this category++/// An immutable game entity+interface Entity {+  val uuid: UUID+  val entityType: EntityType+}++enum class EntityType {+  ZOMBIE, PLAYER, UNKNOWN+}++data class UnknownEntity(override val uuid: UUID) : Entity {+  override val entityType = EntityType.UNKNOWN+}++/// An immutable player+data class Player(override val uuid: UUID, val name: Component) : Entity {+  override val entityType = EntityType.PLAYER+}++// TODO rename+fun fromBukkitPlayer(p: org.bukkit.entity.Player): Player =+  Player(uuid = p.uniqueId, name = p.displayName())++fun toGameEntity(e: org.bukkit.entity.Entity): Entity =+  UnknownEntity(uuid = e.uniqueId)

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

@@ -1,21 +0,0 @@-package net.bowmap.stormbow.game--import java.util.UUID-import org.bukkit.Player--// An immutable reference to a mutable entity-// Can only call impure functions on this entity to get things from it-// And execute effects on it to do things to it--// Players also go under this category--/// An immutable game entity-interface Entity {-  val uuid: UUID-}--/// An immutable player-data class Player(override val uuid: UUID) : Entity--fun fromBukkitPlayer(p: org.bukkit.Player): Player =-  Player(uuid = p.uniqueId)

src/main/kotlin/net/bowmap/stormbow/game/Item.kt renamed from src/main/kotlin/net/bowmap/server/item/Item.kt (view file)

@@ -1,4 +1,4 @@-package net.bowmap.server.item+package net.bowmap.stormbow.game  import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor@@ -84,3 +84,37 @@ ) : ItemType {   BUG(Material.STONE_BUTTON, Component.text("This is a bug yeag"), "You found a bug!", rarity = Rarity.SPECIAL) }++/// itemStack interop functions++fun toItemStack(item: Item): ItemStack {+  // TODO custommodeldata+  val itemStack = ItemStack(material(item.type), item.amount)+  val meta = itemStack.getItemMeta()++  // itemName is not editable via an anvil, which is the desired behavior+  // TODO make sure its not italics!+  meta.itemName(item.name)+  meta.lore(computeLore(item))++  itemStack.setItemMeta(meta)+  return itemStack+}++fun toItemStacks(items: List<Item>): List<ItemStack> {+  return items.map(::toItemStack)+}++/// Turns a vanilla ItemStack into a Bowmap item.+/// This function is the inverse of toItemStack, so that toItem(toItemStack(x)) == x+fun toItem(itemStack: ItemStack): Item {+  // TODO custommodeldata+  // See the above itemName comment+  // val meta = itemStack.getItemMeta()+  return Item(+    // TODO right now this won't work with any custom materials+    // It'll have to be stored somwhere on the item :)+    type = MaterialItemType(itemStack.type),+    amount = itemStack.amount,+  )+}

src/main/kotlin/net/bowmap/stormbow/game/Location.kt added (view file)

@@ -0,0 +1,4 @@+package net.bowmap.stormbow.game++data class Location(val x: Int, val y: Int, val z: Int)+data class Position(val x: Double, val y: Double, val z: Double, val yaw: Float, val pitch: Float)