Ui management reshuffling and start on schematic manager

This commit is contained in:
2025-01-14 01:28:36 +13:00
parent 685a1af396
commit 9ed4a296f6
16 changed files with 291 additions and 183 deletions

View File

@ -6,7 +6,7 @@ import com.pobnellion.aoe.civilisation.Civilisation
import com.pobnellion.aoe.civilisation.CivilisationType
import com.pobnellion.aoe.map.AoeMap
import com.pobnellion.aoe.ui.PlaceHint
import com.pobnellion.aoe.ui.InventoryManager
import com.pobnellion.aoe.ui.UiManager
import org.bukkit.Bukkit
import org.bukkit.GameMode
import org.bukkit.entity.Player
@ -21,7 +21,7 @@ class Aoe : JavaPlugin() {
val civilisations: MutableMap<CivilisationType, Civilisation> = mutableMapOf()
val map: AoeMap = AoeMap()
val players: MutableMap<Player, CivilisationType> = mutableMapOf()
val inventoryManager: InventoryManager = InventoryManager()
val uiManager: UiManager = UiManager()
fun isPlaying(player: Player) = players.containsKey(player)
@ -44,7 +44,7 @@ class Aoe : JavaPlugin() {
}
civilisations.values.forEach { civ -> civ.initSetup() }
inventoryManager.initHotbars()
UiManager.setup()
gameInProgress = true
}
@ -54,7 +54,7 @@ class Aoe : JavaPlugin() {
instance = this
// Events
Bukkit.getPluginManager().registerEvents(InventoryManager(), this)
Bukkit.getPluginManager().registerEvents(UiManager(), this)
Bukkit.getPluginManager().registerEvents(PlaceHint, this)
// Commands

View File

@ -3,4 +3,6 @@ package com.pobnellion.aoe
object Constants {
const val MAX_POP_CAP = 200
const val STARTING_VILLAGER_COUNT = 5
const val ENTITY_SELECTION_TEAM = "entitySelection"
const val PLACE_HINT_TEAM = "placeValid"
}

View File

@ -1,19 +1,15 @@
package com.pobnellion.aoe.building
import com.pobnellion.aoe.Aoe
import com.pobnellion.aoe.building.plains.PlainsTownCenter
import com.pobnellion.aoe.entity.goals.EntityWorkTarget
import com.sk89q.worldedit.WorldEdit
import com.sk89q.worldedit.bukkit.BukkitAdapter
import com.sk89q.worldedit.extent.clipboard.Clipboard
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats
import com.sk89q.worldedit.function.operation.Operation
import com.sk89q.worldedit.function.operation.Operations
import com.sk89q.worldedit.math.BlockVector3
import com.sk89q.worldedit.session.ClipboardHolder
import org.bukkit.Location
import org.bukkit.util.Vector
import java.io.File
import java.io.FileInputStream
abstract class Building(val location: Location, val variant: Int): EntityWorkTarget {
override var currentProgressPercent: Float = 0f
@ -26,16 +22,14 @@ abstract class Building(val location: Location, val variant: Int): EntityWorkTar
var sizeX: Int
var sizeZ: Int
fun center(): Location = location.clone().add(sizeX / 2.0, 0.0, sizeZ / 2.0)
private val clipboard: Clipboard
abstract var populationCapacity: Int
abstract fun getSchematicName(variant: Int): String
init {
val file: File = Aoe.getSchematicFile(this.getSchematicName(variant))
val format = ClipboardFormats.findByFile(file)
format!!.getReader(FileInputStream(file)).use { reader ->
clipboard = reader.read()
}
val clipboard = SchematicManager.get(this.getSchematicName(variant))
if (clipboard == null)
throw NullPointerException("Schematic ${this.getSchematicName(variant)} not found")
sizeX = clipboard.maximumPoint.x() - clipboard.minimumPoint.x()
sizeZ = clipboard.maximumPoint.z() - clipboard.minimumPoint.z()
@ -43,7 +37,8 @@ abstract class Building(val location: Location, val variant: Int): EntityWorkTar
fun placeFull() {
currentProgressPercent = 1f
val offset = clipboard.region.minimumPoint.subtract(clipboard.origin)
val clipboard = SchematicManager.get(this.getSchematicName(variant))
val offset = clipboard!!.region.minimumPoint.subtract(clipboard.origin)
WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(location.world)).use { editSession ->
val operation: Operation = ClipboardHolder(clipboard)
@ -58,7 +53,18 @@ abstract class Building(val location: Location, val variant: Int): EntityWorkTar
interface BuildingInfo {
val buildingType: BuildingType
val schematicNames: List<String>
fun getSize(variant: Int): Vector
fun validate(location: Location): Boolean
fun create(location: Location, variant: Int): Building
fun getSize(variant: Int): Vector {
val clipboard = SchematicManager.get(schematicNames[variant])
if (clipboard == null)
throw NullPointerException("Schematic ${schematicNames[variant]} not found")
val sizeX = clipboard.maximumPoint.x() - clipboard.minimumPoint.x()
val sizeY = clipboard.maximumPoint.y() - clipboard.minimumPoint.y()
val sizeZ = clipboard.maximumPoint.z() - clipboard.minimumPoint.z()
return Vector(sizeX, sizeY, sizeZ)
}
}

View File

@ -0,0 +1,35 @@
package com.pobnellion.aoe.building
import com.pobnellion.aoe.Aoe
import com.sk89q.worldedit.extent.clipboard.Clipboard
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats
import java.io.File
import java.io.FileInputStream
object SchematicManager {
private val schematics: MutableMap<String, Clipboard> = mutableMapOf()
fun add(schematicName: String) {
if (!schematics.containsKey(schematicName))
loadSchematic(schematicName)
}
fun add(schematicNames: List<String>) {
for (schematicName in schematicNames)
if (!schematics.containsKey(schematicName))
loadSchematic(schematicName)
}
fun get(schematicName: String): Clipboard? = schematics[schematicName]
private fun loadSchematic(schematicName: String) {
// TODO: do this in the background async style
val clipboard: Clipboard
val file: File = Aoe.getSchematicFile(schematicName)
val format = ClipboardFormats.findByFile(file)
format!!.getReader(FileInputStream(file)).use { reader ->
clipboard = reader.read()
}
schematics[schematicName] = clipboard
}
}

View File

@ -7,11 +7,10 @@ import com.pobnellion.aoe.building.BuildingInfo
import com.pobnellion.aoe.building.BuildingType
import com.pobnellion.aoe.building.TownCenter
import com.pobnellion.aoe.entity.AoeVillager
import com.pobnellion.aoe.ui.AoeInventory
import com.pobnellion.aoe.ui.BuildingPlaceHint
import net.minecraft.world.phys.AABB
import com.pobnellion.aoe.ui.inventory.BuildingInventory
import org.bukkit.Location
import org.bukkit.craftbukkit.CraftWorld
import org.bukkit.Material
import org.bukkit.entity.Player
import kotlin.math.min
import kotlin.random.Random
@ -20,17 +19,20 @@ abstract class Civilisation(
private val setupLocation: Location
) {
private val buildings: MutableList<Building> = mutableListOf()
private val villagers: MutableList<AoeVillager> = mutableListOf()
public val villagers: MutableList<AoeVillager> = mutableListOf()
val players get() = Aoe.players.filter { player -> player.value == this.type }.keys
val buildingInventory = AoeInventory("Buildings", BuildingType.entries.size)
val buildingInventory = BuildingInventory()
protected abstract fun addSchematicsToManager()
protected abstract fun getBuildingInfo(type: BuildingType): BuildingInfo
protected abstract val name: String
protected abstract val type: CivilisationType
init {
buildingInventory.addItem()
buildingInventory.add(BuildingType.TOWN_CENTER, Material.BELL, "Town center")
buildingInventory.add(BuildingType.HOUSE, Material.OAK_DOOR, "House")
addSchematicsToManager()
}
fun addBuilding(location: Location, buildingInfo: BuildingInfo, variant: Int): Building {
@ -72,12 +74,4 @@ abstract class Civilisation(
}
fun populationCap() = min(Constants.MAX_POP_CAP, buildings.sumOf { building -> building.populationCapacity })
fun selectVillagers(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float) {
(location.world as CraftWorld).handle.getEntitiesOfClass(
AoeVillager::class.java,
AABB(location.x, location.y, location.z, location.x + sizeX, location.y + sizeY, location.z + sizeZ)
)
{ villager -> villager.civilisationType == type }
}
}

View File

@ -2,6 +2,7 @@ package com.pobnellion.aoe.civilisation
import com.pobnellion.aoe.building.BuildingInfo
import com.pobnellion.aoe.building.BuildingType
import com.pobnellion.aoe.building.SchematicManager
import com.pobnellion.aoe.building.plains.PlainsHouse
import com.pobnellion.aoe.building.plains.PlainsTownCenter
import org.bukkit.Location
@ -10,6 +11,11 @@ class Plains(setupLocation: Location) : Civilisation(setupLocation) {
override val name: String = "Plains"
override val type: CivilisationType = CivilisationType.PLAINS
override fun addSchematicsToManager() {
SchematicManager.add(PlainsTownCenter.Info.schematicNames)
SchematicManager.add(PlainsHouse.Info.schematicNames)
}
override fun getBuildingInfo(type: BuildingType): BuildingInfo {
return when (type) {
BuildingType.ARCHERY_RANGE -> TODO()

View File

@ -18,6 +18,8 @@ class AoeVillager(location: Location, var civilisationType: CivilisationType)
targetSelector.removeAllGoals { true }
}
var isActive: Boolean = false;
fun leaveBuilding(building: Building) {
targetSelector.addGoal(0, LeaveBuildingGoal(this, building, 1.0))
}

View File

@ -1,13 +0,0 @@
package com.pobnellion.aoe.ui
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.inventory.Inventory
class AoeInventory(name: String, size: Int) {
val inventory: Inventory = Bukkit.createInventory(null, size + size % 9, Component.text(name))
fun addItem() {
}
}

View File

@ -27,7 +27,6 @@ class AreaPlaceHint(
onTick: (Location, Float, Float, Float) -> Unit,
) {
remove(player)
createOutlineTeam(player)
playerHints[player.uniqueId] = AreaPlaceHint(player, sizeX, sizeY, sizeZ, onConfirm, onTick)
val block = player.getTargetBlockExact(MAX_TARGET_DISTANCE)

View File

@ -28,7 +28,6 @@ class BuildingPlaceHint(
buildingVariant: Int = 0
) {
remove(player)
createOutlineTeam(player)
playerHints[player.uniqueId] = BuildingPlaceHint(player, buildingInfo, onConfirm, buildingVariant)
val block = player.getTargetBlockExact(MAX_TARGET_DISTANCE)

View File

@ -0,0 +1,7 @@
package com.pobnellion.aoe.ui
enum class EntitySelectionMode {
RADIUS,
AREA_HINT,
TWO_POINTS
}

View File

@ -1,106 +0,0 @@
package com.pobnellion.aoe.ui
import com.pobnellion.aoe.Aoe
import com.pobnellion.aoe.building.BuildingType
import com.pobnellion.aoe.getCivilisation
import net.kyori.adventure.text.Component
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.block.Action
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.event.inventory.InventoryDragEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.ItemStack
class InventoryManager : Listener {
private val buildingInventory =
init {
buildingInventory.addItem(createGuiItem(Material.BELL, "Town center", "middle of the town :D"))
buildingInventory.addItem(createGuiItem(Material.OAK_LOG, "House", "Just a house bro"))
buildingInventory.addItem(createGuiItem(Material.EMERALD, "Villager", "willager"))
}
fun initHotbars() {
for (player in Aoe.players.keys) {
player.inventory.clear()
player.inventory.setItem(0, createGuiItem(Material.BRICKS, "Buildings"))
// TODO: double click select selects all of looking at type
player.inventory.setItem(6, createGuiItem(Material.WOODEN_HOE, "Select inactive villagers"))
player.inventory.setItem(7, createGuiItem(Material.IRON_HOE, "Select villagers"))
player.inventory.setItem(8, createGuiItem(Material.IRON_SWORD, "Select army"))
}
}
fun showBuildingInventory(player: Player) {
if (Aoe.isPlaying(player))
player.openInventory(player.getCivilisation()!!.buildingInventory.inventory)
}
// region events
@EventHandler
fun onRightClickItem(event: PlayerInteractEvent) {
if (event.action != Action.RIGHT_CLICK_AIR && event.action != Action.RIGHT_CLICK_BLOCK)
return
if (PlaceHint.contains(event.player))
return
if (event.material != Material.BRICKS)
return
if (Aoe.getTeam(event.player) == null)
return
event.isCancelled = true
showBuildingInventory(event.player)
}
@EventHandler
fun onInventoryClick(event: InventoryClickEvent) {
if (Aoe.players.containsKey(event.whoClicked as Player))
event.isCancelled = true
val player = event.whoClicked as Player
val team = Aoe.getTeam(player) ?: return
if (event.inventory != buildingInventory)
return
var validClick = true
when (event.currentItem?.type) {
Material.OAK_LOG -> team.showBuildingPlaceHint(BuildingType.HOUSE, player)
Material.BELL -> team.showBuildingPlaceHint(BuildingType.TOWN_CENTER, player)
Material.EMERALD -> {}
else -> validClick = false
}
if (validClick)
event.whoClicked.closeInventory(InventoryCloseEvent.Reason.PLUGIN)
}
@EventHandler
fun onInventoryClick(event: InventoryDragEvent) {
if (Aoe.players.containsKey(event.whoClicked as Player))
event.isCancelled = true
}
// endregion
private fun createGuiItem(material: Material, name: String, lore: String? = null): ItemStack {
val item = ItemStack(material, 1)
item.itemMeta.displayName(Component.text(name))
if (lore != null)
item.itemMeta.lore(mutableListOf(Component.text(lore)))
return item
}
}

View File

@ -7,6 +7,7 @@ import com.comphenix.protocol.wrappers.*
import com.comphenix.protocol.wrappers.EnumWrappers.ChatFormatting
import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry
import com.destroystokyo.paper.MaterialSetTag
import com.pobnellion.aoe.Constants
import org.bukkit.Location
import org.bukkit.block.data.BlockData
import org.bukkit.entity.EntityType
@ -44,16 +45,6 @@ abstract class PlaceHint {
return playerHints.containsKey(player.uniqueId)
}
@JvmStatic
protected fun createOutlineTeam(player: Player) {
val packet = PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM)
packet.strings.write(0, "placeValid")
packet.integers.write(0, 0)
packet.optionalTeamParameters.write(0, Optional.of(teamParamsTemplate))
ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet)
}
@EventHandler
fun onPlayerLook(event: PlayerMoveEvent) {
if (!playerHints.containsKey(event.player.uniqueId))
@ -83,16 +74,6 @@ abstract class PlaceHint {
remove(event.player)
}
}
private val teamParamsTemplate: WrappedTeamParameters = WrappedTeamParameters.newBuilder()
.displayName(WrappedChatComponent.fromText(""))
.collisionRule("never")
.nametagVisibility("never")
.options(0)
.color(ChatFormatting.RED)
.prefix(WrappedChatComponent.fromText(""))
.suffix(WrappedChatComponent.fromText(""))
.build()
}
protected abstract val player: Player
@ -120,19 +101,12 @@ abstract class PlaceHint {
addDisplays(initialLocation)
val outlineTeamPacket = PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM)
outlineTeamPacket.strings.write(0, "placeValid")
outlineTeamPacket.integers.write(0, 3)
outlineTeamPacket.getSpecificModifier(Collection::class.java).write(0, displayIds.map { it.second.toString() })
val ids = displayIds.map { it.first }.toMutableList()
val passengerPacket = PacketContainer(PacketType.Play.Server.MOUNT)
passengerPacket.integers.write(0, marker.first)
passengerPacket.integerArrays.write(0, ids.toIntArray())
passengerPacket.integerArrays.write(0, displayIds.map { it.first }.toIntArray())
UiManager.updateTeamEntities(player, Constants.PLACE_HINT_TEAM, displayIds.map { it.second.toString() })
ProtocolLibrary.getProtocolManager().sendServerPacket(player, spawnMarkerPacket)
ProtocolLibrary.getProtocolManager().sendServerPacket(player, outlineTeamPacket)
ProtocolLibrary.getProtocolManager().sendServerPacket(player, passengerPacket)
tick()
@ -227,9 +201,9 @@ abstract class PlaceHint {
protected open fun updateGlowColour(colour: ChatFormatting) {
// outline glow
val outlineTeamPacket = PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM)
outlineTeamPacket.strings.write(0, "placeValid")
outlineTeamPacket.strings.write(0, Constants.PLACE_HINT_TEAM)
outlineTeamPacket.integers.write(0, 2)
val params = WrappedTeamParameters.newBuilder(teamParamsTemplate)
val params = WrappedTeamParameters.newBuilder(UiManager.teamParamsTemplate)
.color(colour)
.build()

View File

@ -0,0 +1,173 @@
package com.pobnellion.aoe.ui
import com.comphenix.protocol.PacketType
import com.comphenix.protocol.ProtocolLibrary
import com.comphenix.protocol.events.PacketContainer
import com.comphenix.protocol.wrappers.EnumWrappers.ChatFormatting
import com.comphenix.protocol.wrappers.WrappedChatComponent
import com.comphenix.protocol.wrappers.WrappedTeamParameters
import com.pobnellion.aoe.Aoe
import com.pobnellion.aoe.Constants
import com.pobnellion.aoe.building.BuildingType
import com.pobnellion.aoe.getCivilisation
import net.kyori.adventure.text.Component
import net.minecraft.world.entity.Entity
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.block.Action
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.event.inventory.InventoryDragEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.ItemStack
import org.bukkit.util.BoundingBox
import java.util.Optional
import kotlin.math.pow
class UiManager : Listener {
companion object {
fun createGuiItem(material: Material, name: String, lore: String? = null): ItemStack {
val item = ItemStack(material, 1)
item.itemMeta.displayName(Component.text(name))
if (lore != null)
item.itemMeta.lore(mutableListOf(Component.text(lore)))
return item
}
fun setup() {
for (player in Aoe.players.keys) {
// Init hotbars
player.inventory.clear()
player.inventory.setItem(0, createGuiItem(Material.BRICKS, "Buildings"))
// TODO: double click select selects all of looking at type
player.inventory.setItem(6, createGuiItem(Material.WOODEN_HOE, "Select inactive villagers"))
player.inventory.setItem(7, createGuiItem(Material.IRON_HOE, "Select villagers"))
player.inventory.setItem(8, createGuiItem(Material.IRON_SWORD, "Select army"))
// TODO: resend these on rejoin
// Send teams
createTeamForPlayer(player, Constants.ENTITY_SELECTION_TEAM)
createTeamForPlayer(player, Constants.PLACE_HINT_TEAM)
}
}
fun createTeamForPlayer(player: Player, teamName: String) {
val packet = PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM)
packet.strings.write(0, teamName)
packet.integers.write(0, 0)
packet.optionalTeamParameters.write(0, Optional.of(teamParamsTemplate))
ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet)
}
fun updateTeamEntities(player: Player, teamName: String, entityIds: List<String>, remove: Boolean = false) {
val teamEntitiesPacket = PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM)
teamEntitiesPacket.strings.write(0, teamName)
teamEntitiesPacket.integers.write(0, if (remove) 4 else 3)
teamEntitiesPacket.getSpecificModifier(Collection::class.java).write(0, entityIds)
ProtocolLibrary.getProtocolManager().sendServerPacket(player, teamEntitiesPacket)
}
val teamParamsTemplate: WrappedTeamParameters = WrappedTeamParameters.newBuilder()
.displayName(WrappedChatComponent.fromText(""))
.collisionRule("never")
.nametagVisibility("never")
.options(0)
.color(ChatFormatting.WHITE)
.prefix(WrappedChatComponent.fromText(""))
.suffix(WrappedChatComponent.fromText(""))
.build()
}
private val playerSelections: MutableMap<Player, List<Entity>> = mutableMapOf()
private val playerSelectionModes: MutableMap<Player, EntitySelectionMode> = mutableMapOf()
private fun selectEntities(player: Player, entities: List<Entity>) {
deselectEntities(player)
playerSelections[player] = entities
updateTeamEntities(player, Constants.ENTITY_SELECTION_TEAM, entities.map { it.uuid.toString() })
}
private fun deselectEntities(player: Player) {
if (!playerSelections.containsKey(player))
return
updateTeamEntities(player, Constants.ENTITY_SELECTION_TEAM, playerSelections[player]!!.map { it.uuid.toString() }, true)
playerSelections.remove(player)
}
// region events
@EventHandler
fun onRightClickItem(event: PlayerInteractEvent) {
if (event.action != Action.RIGHT_CLICK_AIR && event.action != Action.RIGHT_CLICK_BLOCK)
return
if (Aoe.isPlaying(event.player))
return
if (PlaceHint.contains(event.player))
return
val civilisation = event.player.getCivilisation()
if (civilisation == null)
return
when (event.material) {
Material.BRICKS -> event.player.openInventory(civilisation.buildingInventory.inventory)
Material.WOODEN_HOE -> selectEntities(event.player, civilisation.villagers.filter { !it.isActive })
Material.IRON_HOE -> selectEntities(event.player, civilisation.villagers)
Material.IRON_SWORD -> {}
else -> return
}
event.isCancelled = true
}
@EventHandler
fun onInventoryClick(event: InventoryClickEvent) {
if (Aoe.players.containsKey(event.whoClicked as Player))
event.isCancelled = true
val player = event.whoClicked as Player
val team = Aoe.getTeam(player) ?: return
var validClick = true
when (event.currentItem?.type) {
Material.OAK_LOG -> team.showBuildingPlaceHint(BuildingType.HOUSE, player)
Material.BELL -> team.showBuildingPlaceHint(BuildingType.TOWN_CENTER, player)
Material.EMERALD -> {}
else -> validClick = false
}
if (validClick)
event.whoClicked.closeInventory(InventoryCloseEvent.Reason.PLUGIN)
}
@EventHandler
fun onInventoryClick(event: InventoryDragEvent) {
if (Aoe.players.containsKey(event.whoClicked as Player))
event.isCancelled = true
}
// endregion
// region helper functions
fun getEntitiesInArea(entities: List<Entity>, boundingBox: BoundingBox) =
entities.filter { entity -> boundingBox.contains(entity.x, entity.y, entity.z) }
fun getEntitiesAroundPlayer(entities: List<Entity>, player: Player, distance: Float) =
entities.filter { villager -> villager.distanceToSqr(player.x, player.y, player.z) <= distance.pow(2) }
// endregion
}

View File

@ -0,0 +1,15 @@
package com.pobnellion.aoe.ui.inventory
import com.pobnellion.aoe.ui.UiManager
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.inventory.Inventory
abstract class AoeInventory(name: String, size: Int) {
val inventory: Inventory = Bukkit.createInventory(null, size + size % 9, Component.text(name))
fun addItem(material: Material, name: String, lore: String? = null) {
inventory.addItem(UiManager.Companion.createGuiItem(material, name, lore))
}
}

View File

@ -0,0 +1,15 @@
package com.pobnellion.aoe.ui.inventory
import com.pobnellion.aoe.building.BuildingType
import org.bukkit.Material
class BuildingInventory: AoeInventory("Buildings", BuildingType.entries.size) {
private val buildings: MutableMap<Material, BuildingType> = mutableMapOf()
fun add(buildingType: BuildingType, material: Material, name: String, lore: String? = null) {
addItem(material, name, lore)
buildings[material] = buildingType
}
fun getBuilding(material: Material) = buildings[material]
}