moving things around and inventory stuff

This commit is contained in:
ruby
2025-01-12 21:32:12 +13:00
parent 57626e4f95
commit 685a1af396
13 changed files with 244 additions and 81 deletions

View File

@ -6,8 +6,9 @@ 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.PlaceItem
import com.pobnellion.aoe.ui.InventoryManager
import org.bukkit.Bukkit
import org.bukkit.GameMode
import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin
import java.io.File
@ -19,6 +20,10 @@ class Aoe : JavaPlugin() {
private var instance: Aoe? = null
val civilisations: MutableMap<CivilisationType, Civilisation> = mutableMapOf()
val map: AoeMap = AoeMap()
val players: MutableMap<Player, CivilisationType> = mutableMapOf()
val inventoryManager: InventoryManager = InventoryManager()
fun isPlaying(player: Player) = players.containsKey(player)
fun getTeam(player: Player): Civilisation? {
return civilisations.values.singleOrNull { team -> team.players.contains(player) }
@ -28,8 +33,18 @@ class Aoe : JavaPlugin() {
Paths.get(instance!!.dataFolder.path, "schematics", "$schematicName.schem").toFile()
fun startGame() {
if (!gameInProgress)
if (gameInProgress)
return
for (player in Bukkit.getOnlinePlayers()) {
if (isPlaying(player))
player.gameMode = GameMode.CREATIVE
else
player.gameMode = GameMode.SPECTATOR
}
civilisations.values.forEach { civ -> civ.initSetup() }
inventoryManager.initHotbars()
gameInProgress = true
}
@ -39,7 +54,7 @@ class Aoe : JavaPlugin() {
instance = this
// Events
Bukkit.getPluginManager().registerEvents(PlaceItem(), this)
Bukkit.getPluginManager().registerEvents(InventoryManager(), this)
Bukkit.getPluginManager().registerEvents(PlaceHint, this)
// Commands

View File

@ -0,0 +1,8 @@
package com.pobnellion.aoe
import com.pobnellion.aoe.civilisation.Civilisation
import org.bukkit.entity.Player
fun Player.getCivilisation(): Civilisation? {
return Aoe.civilisations[Aoe.players[this]]
}

View File

@ -11,6 +11,7 @@ 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
@ -22,19 +23,26 @@ abstract class Building(val location: Location, val variant: Int): EntityWorkTar
override fun removeProgress(amount: Float) {}
override fun onComplete() {}
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
fun placeFull() {
currentProgressPercent = 1f
var clipboard: Clipboard
val file: File = Aoe.getSchematicFile(getSchematicName(variant))
init {
val file: File = Aoe.getSchematicFile(this.getSchematicName(variant))
val format = ClipboardFormats.findByFile(file)
format!!.getReader(FileInputStream(file)).use { reader ->
clipboard = reader.read()
}
sizeX = clipboard.maximumPoint.x() - clipboard.minimumPoint.x()
sizeZ = clipboard.maximumPoint.z() - clipboard.minimumPoint.z()
}
fun placeFull() {
currentProgressPercent = 1f
val offset = clipboard.region.minimumPoint.subtract(clipboard.origin)
WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(location.world)).use { editSession ->
@ -50,6 +58,7 @@ 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
}

View File

@ -1,13 +1,17 @@
package com.pobnellion.aoe.civilisation
import com.pobnellion.aoe.Aoe
import com.pobnellion.aoe.Constants
import com.pobnellion.aoe.building.Building
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 org.bukkit.Location
import org.bukkit.craftbukkit.CraftWorld
import org.bukkit.entity.Player
import kotlin.math.min
import kotlin.random.Random
@ -17,15 +21,17 @@ abstract class Civilisation(
) {
private val buildings: MutableList<Building> = mutableListOf()
private val villagers: MutableList<AoeVillager> = mutableListOf()
val players: MutableList<Player> = mutableListOf()
fun addPlayer(player: Player) {
players.add(player)
player.sendMessage("Joined team ${name()}")
}
val players get() = Aoe.players.filter { player -> player.value == this.type }.keys
val buildingInventory = AoeInventory("Buildings", BuildingType.entries.size)
protected abstract fun getBuildingInfo(type: BuildingType): BuildingInfo
protected abstract fun name(): String
protected abstract val name: String
protected abstract val type: CivilisationType
init {
buildingInventory.addItem()
}
fun addBuilding(location: Location, buildingInfo: BuildingInfo, variant: Int): Building {
val building = buildingInfo.create(location, variant)
@ -47,9 +53,31 @@ abstract class Civilisation(
townCenter.placeFull()
// Spawn initial villagers
for (i in 0..Constants.STARTING_VILLAGER_COUNT)
villagers.add(AoeVillager(townCenter.villagerSpawnLocation))
for (i in 0..< Constants.STARTING_VILLAGER_COUNT) {
val villager = AoeVillager(townCenter.villagerSpawnLocation, type)
villager.leaveBuilding(townCenter)
villagers.add(villager)
}
// tp players above town center
for (player in players) {
player.teleport(townCenter.location
.clone()
.add(townCenterInfo.getSize(variant).x / 2, 0.0, townCenterInfo.getSize(variant).z / 2)
.toHighestLocation()
.add(0.0, 10.0, 0.0))
player.isFlying = true
}
}
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

@ -7,7 +7,8 @@ import com.pobnellion.aoe.building.plains.PlainsTownCenter
import org.bukkit.Location
class Plains(setupLocation: Location) : Civilisation(setupLocation) {
override fun name(): String = "Plains"
override val name: String = "Plains"
override val type: CivilisationType = CivilisationType.PLAINS
override fun getBuildingInfo(type: BuildingType): BuildingInfo {
return when (type) {

View File

@ -48,7 +48,7 @@ class TestCommand : CommandExecutor {
CivilisationType.PLAINS -> Aoe.civilisations[CivilisationType.PLAINS] = Plains(Aoe.map.getStartingLocations()[0])
}
Aoe.civilisations[civilisationType]!!.addPlayer(player)
Aoe.players[player] = civilisationType
}
}

View File

@ -1,13 +1,16 @@
package com.pobnellion.aoe.entity
import com.pobnellion.aoe.building.Building
import com.pobnellion.aoe.civilisation.CivilisationType
import com.pobnellion.aoe.entity.goals.GoToBuildingGoal
import com.pobnellion.aoe.entity.goals.LeaveBuildingGoal
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.npc.Villager
import org.bukkit.Location
import org.bukkit.craftbukkit.CraftWorld
class AoeVillager(location: Location) : Villager(EntityType.VILLAGER, (location.world as CraftWorld).handle) {
class AoeVillager(location: Location, var civilisationType: CivilisationType)
: Villager(EntityType.VILLAGER, (location.world as CraftWorld).handle) {
init {
setPos(location.x, location.y, location.z)
level().addFreshEntity(this)
@ -15,6 +18,10 @@ class AoeVillager(location: Location) : Villager(EntityType.VILLAGER, (location.
targetSelector.removeAllGoals { true }
}
fun leaveBuilding(building: Building) {
targetSelector.addGoal(0, LeaveBuildingGoal(this, building, 1.0))
}
fun goToBuilding(building: Building) {
targetSelector.addGoal(0, GoToBuildingGoal(this, building, 1.0))
}

View File

@ -0,0 +1,52 @@
package com.pobnellion.aoe.entity.goals
import com.pobnellion.aoe.building.Building
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.goal.Goal
import net.minecraft.world.entity.ai.util.DefaultRandomPos
import net.minecraft.world.level.pathfinder.Path
import net.minecraft.world.phys.Vec3
import java.util.*
import kotlin.math.sqrt
import kotlin.math.pow
class LeaveBuildingGoal(
private val mob: PathfinderMob,
private val building: Building,
private var speedModifier: Double
): Goal() {
private var path: Path? = null
init {
this.setFlags(EnumSet.of(Flag.MOVE))
}
override fun canUse(): Boolean {
val buildingCornerDistance = sqrt((building.sizeX.toFloat().pow(2) + building.sizeZ.toFloat().pow(2)))
val buildingCenter = building.center()
val fromPosition = Vec3(buildingCenter.x, buildingCenter.y, buildingCenter.z)
val posAway = DefaultRandomPos.getPosAway(mob, (buildingCornerDistance / 2).toInt(), 2, fromPosition)
if (posAway == null)
return false
if (fromPosition.distanceToSqr(posAway.x, posAway.y, posAway.z) < fromPosition.distanceToSqr(mob.position()))
return false
path = mob.navigation.createPath(posAway.x, posAway.y, posAway.z, 0)
return path != null
}
override fun start() {
mob.navigation.moveTo(path, speedModifier)
}
override fun tick() {
if (!canContinueToUse())
mob.targetSelector.removeGoal(this)
}
override fun canContinueToUse(): Boolean {
return !mob.navigation.isDone
}
}

View File

@ -0,0 +1,13 @@
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

@ -1,13 +1,6 @@
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.BukkitConverters
import com.comphenix.protocol.wrappers.Pair
import com.comphenix.protocol.wrappers.WrappedBlockData
import com.comphenix.protocol.wrappers.WrappedDataValue
import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.entity.Player
@ -22,7 +15,7 @@ class AreaPlaceHint(
private val sizeY: Float,
private val sizeZ: Float,
private val onConfirm: (Location, Float, Float, Float) -> Unit,
private val validate: (Location, Float, Float, Float) -> Boolean
private val onTick: (Location, Float, Float, Float) -> Unit,
) : PlaceHint() {
companion object {
fun add(
@ -31,12 +24,12 @@ class AreaPlaceHint(
sizeY: Float,
sizeZ: Float,
onConfirm: (Location, Float, Float, Float) -> Unit,
validate: (Location, Float, Float, Float) -> Boolean,
onTick: (Location, Float, Float, Float) -> Unit,
) {
remove(player)
createOutlineTeam(player)
playerHints[player.uniqueId] = AreaPlaceHint(player, sizeX, sizeY, sizeZ, onConfirm, validate)
playerHints[player.uniqueId] = AreaPlaceHint(player, sizeX, sizeY, sizeZ, onConfirm, onTick)
val block = player.getTargetBlockExact(MAX_TARGET_DISTANCE)
playerHints[player.uniqueId]!!.moveTo(block?.location)
}
@ -48,9 +41,14 @@ class AreaPlaceHint(
this.offset = Vector(sizeX / 2.0, 0.0, sizeZ / 2.0)
}
override fun validate(location: Location) = validate(location, sizeX, sizeY, sizeZ)
override fun tick() {
currentLocation?.let { onTick(it, sizeX, sizeY, sizeZ) }
}
override fun confirm() = onConfirm(currentLocation!!, sizeX, sizeY, sizeZ)
override fun confirm(): Boolean {
currentLocation?.let { onConfirm(it, sizeX, sizeY, sizeZ) }
return true
}
override fun addDisplays(initialLocation: Location) {
base = spawnBlockDisplay(initialLocation, Material.GRAY_STAINED_GLASS.createBlockData(),
@ -58,23 +56,23 @@ class AreaPlaceHint(
}
override fun updateColour() {
super.updateColour()
// base
val dataPacket = PacketContainer(PacketType.Play.Server.ENTITY_METADATA)
dataPacket.integers.write(0, base.first)
val material = if (isValid) Material.LIME_STAINED_GLASS else Material.RED_STAINED_GLASS
val blockData = BukkitConverters
.getWrappedBlockDataConverter()
.getGeneric(WrappedBlockData.createData(material))
val metadata = listOf(
WrappedDataValue(23, Registry.getBlockDataSerializer(false), blockData)
)
dataPacket.dataValueCollectionModifier.write(0, metadata)
ProtocolLibrary.getProtocolManager().sendServerPacket(player, dataPacket)
}
// override fun updateGlowColour(colour: ChatFormatting) {
// super.updateGlowColour(colour)
//
// // base
// val dataPacket = PacketContainer(PacketType.Play.Server.ENTITY_METADATA)
// dataPacket.integers.write(0, base.first)
//
// val material = if (isValid) Material.LIME_STAINED_GLASS else Material.RED_STAINED_GLASS
// val blockData = BukkitConverters
// .getWrappedBlockDataConverter()
// .getGeneric(WrappedBlockData.createData(material))
//
// val metadata = listOf(
// WrappedDataValue(23, Registry.getBlockDataSerializer(false), blockData)
// )
//
// dataPacket.dataValueCollectionModifier.write(0, metadata)
// ProtocolLibrary.getProtocolManager().sendServerPacket(player, dataPacket)
// }
}

View File

@ -1,5 +1,6 @@
package com.pobnellion.aoe.ui
import com.comphenix.protocol.wrappers.EnumWrappers
import com.pobnellion.aoe.Aoe
import com.pobnellion.aoe.building.BuildingInfo
import com.sk89q.worldedit.bukkit.BukkitAdapter
@ -36,6 +37,7 @@ class BuildingPlaceHint(
}
private val schematic: Clipboard
private var isValid = false
init {
val schematicName = buildingInfo.schematicNames[buildingVariant]
@ -53,9 +55,23 @@ class BuildingPlaceHint(
-floor(clipboard.dimensions.z() / 2.0))
}
override fun validate(location: Location) = buildingInfo.validate(location)
override fun tick() {
// Update material
val valid = currentLocation?.let { buildingInfo.validate(it) } ?: false
override fun confirm() = onConfirm(currentLocation!!, buildingInfo, buildingVariant)
if (valid != isValid) {
isValid = valid
updateGlowColour(if (isValid) EnumWrappers.ChatFormatting.GREEN else EnumWrappers.ChatFormatting.RED)
}
}
override fun confirm(): Boolean {
if (!isValid || currentLocation == null)
return false
onConfirm(currentLocation!!, buildingInfo, buildingVariant)
return true
}
override fun addDisplays(initialLocation: Location) {
schematic.iterator().forEach { blockPosition ->

View File

@ -2,8 +2,8 @@ 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.Bukkit
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
@ -16,8 +16,8 @@ import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.ItemStack
class PlaceItem : Listener {
private val buildingInventory = Bukkit.createInventory(null, 9, Component.text("Buildings"))
class InventoryManager : Listener {
private val buildingInventory =
init {
buildingInventory.addItem(createGuiItem(Material.BELL, "Town center", "middle of the town :D"))
@ -25,6 +25,25 @@ class PlaceItem : Listener {
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)
@ -40,18 +59,20 @@ class PlaceItem : Listener {
return
event.isCancelled = true
event.player.openInventory(buildingInventory)
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
event.isCancelled = true
var validClick = true
when (event.currentItem?.type) {
@ -67,14 +88,19 @@ class PlaceItem : Listener {
@EventHandler
fun onInventoryClick(event: InventoryDragEvent) {
if (event.inventory == buildingInventory)
if (Aoe.players.containsKey(event.whoClicked as Player))
event.isCancelled = true
}
private fun createGuiItem(material: Material, name: String, lore: String): ItemStack {
// 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

@ -4,6 +4,7 @@ import com.comphenix.protocol.PacketType
import com.comphenix.protocol.ProtocolLibrary
import com.comphenix.protocol.events.PacketContainer
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 org.bukkit.Location
@ -73,10 +74,7 @@ abstract class PlaceHint {
// left click - confirm
if (event.action == Action.LEFT_CLICK_BLOCK || event.action == Action.LEFT_CLICK_AIR) {
if (!playerHints[event.player.uniqueId]!!.isValid)
return
playerHints[event.player.uniqueId]!!.confirm()
if (playerHints[event.player.uniqueId]!!.confirm())
remove(event.player)
}
@ -91,7 +89,7 @@ abstract class PlaceHint {
.collisionRule("never")
.nametagVisibility("never")
.options(0)
.color(EnumWrappers.ChatFormatting.RED)
.color(ChatFormatting.RED)
.prefix(WrappedChatComponent.fromText(""))
.suffix(WrappedChatComponent.fromText(""))
.build()
@ -102,11 +100,10 @@ abstract class PlaceHint {
private var marker: Pair<Int, UUID> = Pair((Math.random() * Int.MAX_VALUE).toInt(), UUID.randomUUID())
protected val displayIds: MutableList<Pair<Int, UUID>> = mutableListOf()
var currentLocation: Location? = null
var isValid = false
protected abstract fun addDisplays(initialLocation: Location)
protected abstract fun confirm()
protected abstract fun validate(location: Location): Boolean
protected abstract fun confirm(): Boolean
protected abstract fun tick()
private fun show(initialLocation: Location) {
initialLocation.add(this.offset)
@ -138,8 +135,7 @@ abstract class PlaceHint {
ProtocolLibrary.getProtocolManager().sendServerPacket(player, outlineTeamPacket)
ProtocolLibrary.getProtocolManager().sendServerPacket(player, passengerPacket)
isValid = validate(initialLocation)
updateColour()
tick()
}
private fun hide() {
@ -181,13 +177,7 @@ abstract class PlaceHint {
currentLocation = newLocation
// Update material
val valid = validate(newLocation)
if (valid != isValid) {
isValid = valid
updateColour()
}
this.tick()
}
// region helper functions
@ -234,13 +224,13 @@ abstract class PlaceHint {
return Pair<Int, UUID>(id, uuid)
}
protected open fun updateColour() {
protected open fun updateGlowColour(colour: ChatFormatting) {
// outline glow
val outlineTeamPacket = PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM)
outlineTeamPacket.strings.write(0, "placeValid")
outlineTeamPacket.integers.write(0, 2)
val params = WrappedTeamParameters.newBuilder(teamParamsTemplate)
.color(if (isValid) EnumWrappers.ChatFormatting.GREEN else EnumWrappers.ChatFormatting.RED)
.color(colour)
.build()
outlineTeamPacket.optionalTeamParameters.write(0, Optional.of(params))