Compare commits

...

10 Commits

34 changed files with 1069 additions and 307 deletions

4
.gitignore vendored
View File

@ -115,5 +115,9 @@ gradle-app.setting
run/ run/
runs/ runs/
# Ignore gradle.properties cos its got sensitive info
gradle.properties
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar !gradle-wrapper.jar

View File

@ -1,7 +1,10 @@
import de.nilsdruyen.gradle.ftp.UploadExtension
plugins { plugins {
kotlin("jvm") version "2.1.20-Beta1" kotlin("jvm") version "2.1.20-Beta1"
id("com.github.johnrengelman.shadow") version "8.1.1" id("com.gradleup.shadow") version "8.3.5"
// id("de.nilsdruyen.gradle-ftp-upload-plugin") version "0.5.0" id("io.papermc.paperweight.userdev") version "2.0.0-beta.11"
id("de.nilsdruyen.gradle-ftp-upload-plugin") version "0.5.0"
} }
group = "com.pobnellion" group = "com.pobnellion"
@ -18,13 +21,19 @@ repositories {
maven("https://repo.dmulloy2.net/repository/public/") { maven("https://repo.dmulloy2.net/repository/public/") {
name = "dmulloy2-repo" name = "dmulloy2-repo"
} }
maven("https://maven.enginehub.org/repo/") {
name = "enginehub"
}
} }
dependencies { dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.3-R0.1-SNAPSHOT") // compileOnly("io.papermc.paper:paper-api:1.21.3-R0.1-SNAPSHOT")
compileOnly("com.comphenix.protocol:ProtocolLib:5.3.0") // compileOnly("com.comphenix.protocol:ProtocolLib:5.3.0")
compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Bukkit")
compileOnly(files("libs/ProtocolLib.jar"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation("de.nilsdruyen.gradle-ftp-upload-plugin:de.nilsdruyen.gradle-ftp-upload-plugin.gradle.plugin:0.5.0") implementation(platform("com.intellectualsites.bom:bom-newest:1.51")) // Ref: https://github.com/IntellectualSites/bom
paperweight.paperDevBundle("1.21.4-R0.1-SNAPSHOT")
} }
val targetJavaVersion = 21 val targetJavaVersion = 21
@ -57,3 +66,16 @@ tasks.shadowJar {
} }
} }
configure<UploadExtension> {
host = properties.getOrDefault("ftp.host", "").toString()
port = properties.getOrDefault("ftp.port", 22).toString().toInt()
username = properties.getOrDefault("ftp.username", "").toString()
password = properties.getOrDefault("ftp.password", "").toString()
sourceDir = "${layout.buildDirectory.get()}/libs"
targetDir = "/plugins/"
clearDirectoryBeforeUpload = false
}
tasks.uploadFilesToFtp {
dependsOn("shadowJar")
}

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -1,20 +1,65 @@
package com.pobnellion.aoe package com.pobnellion.aoe
import com.pobnellion.aoe.command.TestCommand import com.pobnellion.aoe.command.TestCommand
import com.pobnellion.aoe.command.TestTabCompleter
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.PlaceHint
import com.pobnellion.aoe.ui.PlaceItem import com.pobnellion.aoe.ui.UiManager
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.GameMode
import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import java.io.File
import java.nio.file.Paths
class Aoe : JavaPlugin() { class Aoe : JavaPlugin() {
companion object {
private var gameInProgress: Boolean = false
private var instance: Aoe? = null
val civilisations: MutableMap<CivilisationType, Civilisation> = mutableMapOf()
val map: AoeMap = AoeMap()
val players: MutableMap<Player, CivilisationType> = mutableMapOf()
val uiManager: UiManager = UiManager()
fun isPlaying(player: Player) = players.containsKey(player)
fun getTeam(player: Player): Civilisation? {
return civilisations.values.singleOrNull { team -> team.players.contains(player) }
}
fun getSchematicFile(schematicName: String): File =
Paths.get(instance!!.dataFolder.path, "schematics", "$schematicName.schem").toFile()
fun startGame() {
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() }
UiManager.setup()
gameInProgress = true
}
}
override fun onEnable() { override fun onEnable() {
instance = this
// Events // Events
Bukkit.getPluginManager().registerEvents(PlaceHint(), this) Bukkit.getPluginManager().registerEvents(UiManager(), this)
Bukkit.getPluginManager().registerEvents(PlaceItem(), this) Bukkit.getPluginManager().registerEvents(PlaceHint, this)
// Commands // Commands
this.getCommand("debug")!!.setExecutor(TestCommand()) this.getCommand("aoe")!!.setExecutor(TestCommand())
this.getCommand("aoe")!!.tabCompleter = TestTabCompleter()
} }
override fun onDisable() { override fun onDisable() {

View File

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

@ -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

@ -1,23 +1,8 @@
package com.pobnellion.aoe.building package com.pobnellion.aoe.building
import com.pobnellion.aoe.ui.PlaceHint
import com.pobnellion.aoe.ui.PlaceHintValidators
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.entity.Player
class Blacksmith: Building { class Blacksmith(location: Location, variant: Int): Building(location, variant) {
override fun showPlaceHint(player: Player) { override var populationCapacity: Int = 0
PlaceHint.add(player, 4.0f, 3.0f, 4.0f, ::place, PlaceHintValidators::allReplaceable) override fun getSchematicName(variant: Int): String = ""
}
fun place(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float) {
for (x in 0..<sizeX.toInt())
for (y in 0..<sizeY.toInt())
for (z in 0..<sizeZ.toInt())
location.world.getBlockAt(
location.blockX + x,
location.blockY + y,
location.blockZ + z ).type = Material.SMOOTH_STONE
}
} }

View File

@ -1,8 +1,70 @@
package com.pobnellion.aoe.building package com.pobnellion.aoe.building
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.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.Location
import org.bukkit.entity.Player import org.bukkit.util.Vector
interface Building { abstract class Building(val location: Location, val variant: Int): EntityWorkTarget {
fun showPlaceHint(player: Player) override var currentProgressPercent: Float = 0f
override fun isComplete(): Boolean = currentProgressPercent >= 1.0f
override fun addProgress(amount: Float) {}
override fun setProgress(amount: Float) {}
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)
abstract var populationCapacity: Int
abstract fun getSchematicName(variant: Int): String
init {
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()
}
fun placeFull() {
currentProgressPercent = 1f
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)
.createPaste(editSession)
.to(BlockVector3.at(location.x, location.y, location.z).subtract(offset))
.build()
Operations.complete(operation)
}
}
}
interface BuildingInfo {
val buildingType: BuildingType
val schematicNames: List<String>
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,17 @@
package com.pobnellion.aoe.building
enum class BuildingType {
ARCHERY_RANGE,
BARRACKS,
DOCK,
FARM,
HOUSE,
LUMBER_CAMP,
MILL,
MINING_CAMP,
STABLE,
TOWN_CENTER,
UNIQUE,
WALL,
WATCH_TOWER,
}

View File

@ -1,17 +1,12 @@
package com.pobnellion.aoe.building package com.pobnellion.aoe.building
import com.pobnellion.aoe.ui.PlaceHint
import com.pobnellion.aoe.ui.PlaceHintValidators
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.entity.Player
class House: Building { abstract class House(location: Location, variant: Int): Building(location, variant) {
override fun showPlaceHint(player: Player) { override var populationCapacity: Int = 5
PlaceHint.add(player, 3.0f, 3.0f, 3.0f, ::place, PlaceHintValidators::allAir)
}
fun place(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float) { fun place(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float, int: Int) {
for (x in 0..<sizeX.toInt()) for (x in 0..<sizeX.toInt())
for (y in 0..<sizeY.toInt()) for (y in 0..<sizeY.toInt())
for (z in 0..<sizeZ.toInt()) for (z in 0..<sizeZ.toInt())

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

@ -0,0 +1,10 @@
package com.pobnellion.aoe.building
import org.bukkit.Location
abstract class TownCenter(location: Location, variant: Int): Building(location, variant) {
override var populationCapacity: Int = 5
abstract val villagerSpawnLocation: Location
}

View File

@ -0,0 +1,23 @@
package com.pobnellion.aoe.building.plains
import com.pobnellion.aoe.building.Building
import com.pobnellion.aoe.building.BuildingInfo
import com.pobnellion.aoe.building.BuildingType
import com.pobnellion.aoe.building.House
import com.pobnellion.aoe.ui.PlaceHintValidators
import org.bukkit.Location
class PlainsHouse(location: Location, variant: Int): House(location, variant) {
companion object Info: BuildingInfo {
override val buildingType: BuildingType = BuildingType.HOUSE
override val schematicNames: List<String> = listOf("house1")
override fun validate(location: Location): Boolean {
return PlaceHintValidators.allReplaceable(location, 5f, 5f, 5f, -1)
}
override fun create(location: Location, variant: Int): Building = PlainsHouse(location, variant)
}
override fun getSchematicName(variant: Int) = schematicNames[variant]
}

View File

@ -0,0 +1,27 @@
package com.pobnellion.aoe.building.plains
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.ui.PlaceHintValidators
import org.bukkit.Location
class PlainsTownCenter(location: Location, variant: Int): TownCenter(location, variant) {
companion object Info: BuildingInfo {
override val buildingType = BuildingType.TOWN_CENTER
override val schematicNames = listOf("plains_towncenter")
override fun validate(location: Location): Boolean {
return PlaceHintValidators.allReplaceable(location, 10f, 10f, 10f, -2)
}
override fun create(location: Location, variant: Int): Building {
return PlainsTownCenter(location, variant)
}
}
override val villagerSpawnLocation: Location = location.clone().add(7.0,2.0, 7.0)
override fun getSchematicName(variant: Int) = schematicNames[variant]
}

View File

@ -0,0 +1,77 @@
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.BuildingPlaceHint
import com.pobnellion.aoe.ui.inventory.BuildingInventory
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.entity.Player
import kotlin.math.min
import kotlin.random.Random
abstract class Civilisation(
private val setupLocation: Location
) {
private val buildings: MutableList<Building> = mutableListOf()
public val villagers: MutableList<AoeVillager> = mutableListOf()
val players get() = Aoe.players.filter { player -> player.value == this.type }.keys
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.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 {
val building = buildingInfo.create(location, variant)
buildings.add(building)
return building
}
fun showBuildingPlaceHint(type: BuildingType, player: Player) {
val info = getBuildingInfo(type)
val variant = Random.nextInt(info.schematicNames.size)
BuildingPlaceHint.add(player, info, ::addBuilding, variant)
}
fun initSetup() {
// Create town center
val townCenterInfo = getBuildingInfo(BuildingType.TOWN_CENTER)
val variant = Random.nextInt(townCenterInfo.schematicNames.size)
val townCenter = addBuilding(setupLocation, townCenterInfo, variant) as TownCenter
townCenter.placeFull()
// Spawn initial villagers
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 })
}

View File

@ -0,0 +1,5 @@
package com.pobnellion.aoe.civilisation
enum class CivilisationType {
PLAINS
}

View File

@ -0,0 +1,37 @@
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
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()
BuildingType.BARRACKS -> TODO()
BuildingType.DOCK -> TODO()
BuildingType.FARM -> TODO()
BuildingType.HOUSE -> PlainsHouse.Info
BuildingType.LUMBER_CAMP -> TODO()
BuildingType.MILL -> TODO()
BuildingType.MINING_CAMP -> TODO()
BuildingType.STABLE -> TODO()
BuildingType.TOWN_CENTER -> PlainsTownCenter.Info
BuildingType.UNIQUE -> TODO()
BuildingType.WALL -> TODO()
BuildingType.WATCH_TOWER -> TODO()
}
}
}

View File

@ -1,20 +1,70 @@
package com.pobnellion.aoe.command package com.pobnellion.aoe.command
import com.pobnellion.aoe.ui.PlaceHint import com.pobnellion.aoe.Aoe
import org.bukkit.Material import com.pobnellion.aoe.civilisation.CivilisationType
import com.pobnellion.aoe.civilisation.Plains
import org.bukkit.command.Command import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.command.TabCompleter
import org.bukkit.entity.Player import org.bukkit.entity.Player
class TestCommand : CommandExecutor { class TestCommand : CommandExecutor {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>?): Boolean { override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
if (sender !is Player) { if (sender !is Player) {
sender.sendMessage("Command can only be executed by players") sender.sendMessage("Command can only be executed by players")
return true return true
} }
val player: Player = sender if (args.isEmpty()) {
sender.sendMessage("invalid command")
return true return true
} }
when (args[0]) {
"team" -> team(sender, args)
"start" -> Aoe.startGame()
else -> sender.sendMessage("Invalid argument: ${args[0]}")
}
return true
}
private fun team(sender: CommandSender, args: Array<out String>) {
if (args.size == 1) {
sender.sendMessage("please provide a team")
return
}
when (args[1]) {
"plains" -> joinCiv(sender as Player, CivilisationType.PLAINS)
else -> sender.sendMessage("Invalid team: ${args[1]}")
}
}
private fun joinCiv(player: Player, civilisationType: CivilisationType) {
if (!Aoe.civilisations.containsKey(civilisationType))
when (civilisationType) {
CivilisationType.PLAINS -> Aoe.civilisations[CivilisationType.PLAINS] = Plains(Aoe.map.getStartingLocations()[0])
}
Aoe.players[player] = civilisationType
}
}
class TestTabCompleter: TabCompleter {
override fun onTabComplete(sender: CommandSender, command: Command, label: String, args: Array<out String>): MutableList<String>? {
val options: MutableList<String> = mutableListOf()
if (args.size == 1) {
options.add("team")
options.add("start")
}
if (args.size == 2)
if (args[0] == "team")
options.add("plains")
return options
}
} }

View File

@ -0,0 +1,30 @@
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, var civilisationType: CivilisationType)
: Villager(EntityType.VILLAGER, (location.world as CraftWorld).handle) {
init {
setPos(location.x, location.y, location.z)
level().addFreshEntity(this)
targetSelector.removeAllGoals { true }
}
var isActive: Boolean = false;
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,10 @@
package com.pobnellion.aoe.entity.goals
interface EntityWorkTarget {
var currentProgressPercent: Float
fun isComplete(): Boolean
fun addProgress(amount: Float)
fun setProgress(amount: Float)
fun removeProgress(amount: Float)
fun onComplete()
}

View File

@ -0,0 +1,35 @@
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 java.util.*
class GoToBuildingGoal(
private val mob: PathfinderMob,
private val building: Building,
private var speedModifier: Double
): Goal() {
private var recalculateTicks = 0
init {
this.setFlags(EnumSet.of(Flag.MOVE, Flag.JUMP))
}
override fun canUse(): Boolean {
return true
}
override fun start() {
mob.navigation.moveTo(building.location.x, building.location.y, building.location.z, speedModifier)
}
override fun tick() {
recalculateTicks++
if (recalculateTicks % 40 == 0)
mob.navigation.moveTo(building.location.x, building.location.y, building.location.z, speedModifier)
}
override fun requiresUpdateEveryTick(): Boolean = true
}

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,10 @@
package com.pobnellion.aoe.map
import org.bukkit.Bukkit
import org.bukkit.Location
class AoeMap {
fun getStartingLocations(): List<Location> {
return listOf(Location(Bukkit.getWorld("world"), -165.0, 63.0, 32.0))
}
}

View File

@ -1,7 +0,0 @@
package com.pobnellion.aoe.team
import com.pobnellion.aoe.building.Building
class Team {
public val Buildings: List<Building> = listOf()
}

View File

@ -0,0 +1,77 @@
package com.pobnellion.aoe.ui
import com.comphenix.protocol.wrappers.Pair
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.util.Vector
import org.joml.Math
import org.joml.Vector3f
import java.util.*
class AreaPlaceHint(
override val player: Player,
private val sizeX: Float,
private val sizeY: Float,
private val sizeZ: Float,
private val onConfirm: (Location, Float, Float, Float) -> Unit,
private val onTick: (Location, Float, Float, Float) -> Unit,
) : PlaceHint() {
companion object {
fun add(
player: Player,
sizeX: Float,
sizeY: Float,
sizeZ: Float,
onConfirm: (Location, Float, Float, Float) -> Unit,
onTick: (Location, Float, Float, Float) -> Unit,
) {
remove(player)
playerHints[player.uniqueId] = AreaPlaceHint(player, sizeX, sizeY, sizeZ, onConfirm, onTick)
val block = player.getTargetBlockExact(MAX_TARGET_DISTANCE)
playerHints[player.uniqueId]!!.moveTo(block?.location)
}
}
private var base: Pair<Int, UUID> = Pair((Math.random() * Int.MAX_VALUE).toInt(), UUID.randomUUID())
init {
this.offset = Vector(sizeX / 2.0, 0.0, sizeZ / 2.0)
}
override fun tick() {
currentLocation?.let { onTick(it, 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(),
scale = Vector3f(sizeX + 0.1f, 0.2f, sizeZ + 0.1f), offset = Vector3f(-0.05f, -0.05f, -0.05f))
}
// 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

@ -0,0 +1,88 @@
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
import com.sk89q.worldedit.extent.clipboard.Clipboard
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats
import com.sk89q.worldedit.math.BlockVector3
import org.bukkit.Location
import org.bukkit.entity.Player
import org.bukkit.util.Vector
import org.joml.Vector3f
import java.io.FileInputStream
import kotlin.math.floor
class BuildingPlaceHint(
override val player: Player,
private val buildingInfo: BuildingInfo,
private val onConfirm: (Location, BuildingInfo, Int) -> Unit,
private val buildingVariant: Int = 0
) : PlaceHint() {
companion object {
fun add(
player: Player,
buildingInfo: BuildingInfo,
onConfirm: (Location, BuildingInfo, Int) -> Unit,
buildingVariant: Int = 0
) {
remove(player)
playerHints[player.uniqueId] = BuildingPlaceHint(player, buildingInfo, onConfirm, buildingVariant)
val block = player.getTargetBlockExact(MAX_TARGET_DISTANCE)
playerHints[player.uniqueId]!!.moveTo(block?.location)
}
}
private val schematic: Clipboard
private var isValid = false
init {
val schematicName = buildingInfo.schematicNames[buildingVariant]
val file = Aoe.getSchematicFile(schematicName)
val format = ClipboardFormats.findByFile(file)
val reader = format?.getReader(FileInputStream(file))
val clipboard = reader?.read() ?: throw NullPointerException("Could not load schematic ${file.path}. Is it present?")
clipboard.origin = BlockVector3.ZERO
this.schematic = clipboard
this.offset = Vector(
-floor(clipboard.dimensions.x() / 2.0),
clipboard.minY.toDouble() + 1,
-floor(clipboard.dimensions.z() / 2.0))
}
override fun tick() {
// Update material
val valid = currentLocation?.let { buildingInfo.validate(it) } ?: false
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 ->
val block = BukkitAdapter.adapt(schematic.getBlock(blockPosition))
if (!block.material.isAir && !block.material.isEmpty) {
val x = (blockPosition.x() - schematic.minimumPoint.x()).toFloat()
val y = (blockPosition.y() - schematic.minimumPoint.y()).toFloat()
val z = (blockPosition.z() - schematic.minimumPoint.z()).toFloat()
displayIds.add(spawnBlockDisplay(initialLocation, block, offset = Vector3f(x, y, z), options = 0b01000000))
}
}
}
}

View File

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

View File

@ -1,14 +1,15 @@
package com.pobnellion.aoe.ui; package com.pobnellion.aoe.ui
import com.comphenix.protocol.PacketType import com.comphenix.protocol.PacketType
import com.comphenix.protocol.ProtocolLibrary import com.comphenix.protocol.ProtocolLibrary
import com.comphenix.protocol.events.PacketContainer import com.comphenix.protocol.events.PacketContainer
import com.comphenix.protocol.wrappers.* import com.comphenix.protocol.wrappers.*
import com.comphenix.protocol.wrappers.EnumWrappers.ChatFormatting
import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry
import it.unimi.dsi.fastutil.ints.IntList import com.destroystokyo.paper.MaterialSetTag
import org.bukkit.Bukkit import com.pobnellion.aoe.Constants
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.Material import org.bukkit.block.data.BlockData
import org.bukkit.entity.EntityType import org.bukkit.entity.EntityType
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
@ -16,35 +17,16 @@ import org.bukkit.event.Listener
import org.bukkit.event.block.Action import org.bukkit.event.block.Action
import org.bukkit.event.player.PlayerInteractEvent import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.event.player.PlayerMoveEvent import org.bukkit.event.player.PlayerMoveEvent
import org.bukkit.potion.PotionEffectType import org.bukkit.util.Vector
import org.joml.Math import org.joml.Math
import org.joml.Vector3f import org.joml.Vector3f
import java.util.* import java.util.*
import kotlin.math.floor
import kotlin.math.min
class PlaceHint : Listener { abstract class PlaceHint {
companion object : Listener {
companion object { const val MAX_TARGET_DISTANCE = 64
private const val MAX_TARGET_DISTANCE = 64 @JvmStatic
private val playerHints = HashMap<UUID, HintMarker>(); protected val playerHints = HashMap<UUID, PlaceHint>()
fun add(
player: Player,
sizeX: Float,
sizeY: Float,
sizeZ: Float,
onConfirm: (Location, Float, Float, Float) -> Unit,
validate: (Location, Float, Float, Float) -> Boolean,
materialValid: Material = Material.LIME_STAINED_GLASS,
materialInvalid: Material = Material.RED_STAINED_GLASS,
) {
remove(player)
createOutlineTeam(player)
playerHints[player.uniqueId] = HintMarker(player, sizeX, sizeY, sizeZ, onConfirm, validate, materialValid, materialInvalid)
val block = player.getTargetBlockExact(MAX_TARGET_DISTANCE);
playerHints[player.uniqueId]!!.moveTo(block?.location)
}
fun remove(player: Player) { fun remove(player: Player) {
if (!playerHints.containsKey(player.uniqueId)) if (!playerHints.containsKey(player.uniqueId))
@ -59,14 +41,8 @@ class PlaceHint : Listener {
playerHints.clear() playerHints.clear()
} }
private fun createOutlineTeam(player: Player) { fun contains(player: Player): Boolean {
val packet = PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM) return playerHints.containsKey(player.uniqueId)
packet.strings.write(0, "placeValid")
packet.integers.write(0, 0)
packet.optionalTeamParameters.write(0, Optional.of(Utils.teamParams))
ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet)
}
} }
@EventHandler @EventHandler
@ -74,101 +50,73 @@ class PlaceHint : Listener {
if (!playerHints.containsKey(event.player.uniqueId)) if (!playerHints.containsKey(event.player.uniqueId))
return return
val block = event.player.getTargetBlockExact(MAX_TARGET_DISTANCE); val target = event.player.getTargetBlockExact(MAX_TARGET_DISTANCE)
val block = if (target == null) null else event.player.getTargetBlock(MaterialSetTag.REPLACEABLE.values, MAX_TARGET_DISTANCE)
playerHints[event.player.uniqueId]!!.moveTo(block?.location) playerHints[event.player.uniqueId]!!.moveTo(block?.location)
} }
@EventHandler @EventHandler
fun onLeftClick(event: PlayerInteractEvent) { fun onClick(event: PlayerInteractEvent) {
if (!playerHints.containsKey(event.player.uniqueId) || (event.action != Action.LEFT_CLICK_BLOCK && event.action != Action.LEFT_CLICK_AIR)) if (!playerHints.containsKey(event.player.uniqueId))
return return
if (!playerHints[event.player.uniqueId]!!.isValid) event.isCancelled = true
return
val marker = playerHints[event.player.uniqueId]!! // left click - confirm
marker.onConfirm(marker.currentLocation!!, marker.sizeX, marker.sizeY, marker.sizeZ) if (event.action == Action.LEFT_CLICK_BLOCK || event.action == Action.LEFT_CLICK_AIR) {
if (playerHints[event.player.uniqueId]!!.confirm())
remove(event.player)
}
// right click - cancel
if (event.action == Action.RIGHT_CLICK_BLOCK || event.action == Action.RIGHT_CLICK_AIR) {
remove(event.player) remove(event.player)
} }
} }
}
private class HintMarker( protected abstract val player: Player
val player: Player, protected lateinit var offset: Vector
val sizeX: Float, private var marker: Pair<Int, UUID> = Pair((Math.random() * Int.MAX_VALUE).toInt(), UUID.randomUUID())
val sizeY: Float, protected val displayIds: MutableList<Pair<Int, UUID>> = mutableListOf()
val sizeZ: Float,
val onConfirm: (Location, Float, Float, Float) -> Unit,
val validate: (Location, Float, Float, Float) -> Boolean,
val materialValid: Material,
val materialInvalid: Material,
) {
private val markerUuid = UUID.randomUUID()
private val outlineUuid = UUID.randomUUID()
private val markerId = (Math.random() * Int.MAX_VALUE).toInt()
private val outlineId = markerId + 1
var currentLocation: Location? = null var currentLocation: Location? = null
var isValid = false
protected abstract fun addDisplays(initialLocation: Location)
protected abstract fun confirm(): Boolean
protected abstract fun tick()
private fun show(initialLocation: Location) { private fun show(initialLocation: Location) {
initialLocation.add(-floor(sizeX / 2.0), 1.0, -floor(sizeZ / 2.0)) initialLocation.add(this.offset)
currentLocation = initialLocation
spawnBlockDisplay(markerId, markerUuid, initialLocation, Material.GRAY_STAINED_GLASS, Vector3f(sizeX, 0.2f, sizeZ), 0b01000000) val spawnMarkerPacket = PacketContainer(PacketType.Play.Server.SPAWN_ENTITY)
// spawnBlockDisplay(outlineId, outlineUuid, initialLocation, Material.STONE, Vector3f(sizeX, sizeY, sizeZ), 0b00100000) spawnMarkerPacket.integers.write(0, marker.first)
spawnMarkerPacket.uuiDs.write(0, marker.second)
val spawnSlimePacket = PacketContainer(PacketType.Play.Server.SPAWN_ENTITY) spawnMarkerPacket.entityTypeModifier.write(0, EntityType.BLOCK_DISPLAY)
spawnSlimePacket.integers.write(0, outlineId) spawnMarkerPacket.doubles
spawnSlimePacket.uuiDs.write(0, outlineUuid)
spawnSlimePacket.entityTypeModifier.write(0, EntityType.SLIME)
spawnSlimePacket.doubles
.write(0, initialLocation.x) .write(0, initialLocation.x)
.write(1, initialLocation.y) .write(1, initialLocation.y)
.write(2, initialLocation.z) .write(2, initialLocation.z)
val dataPacket = PacketContainer(PacketType.Play.Server.ENTITY_METADATA) addDisplays(initialLocation)
dataPacket.integers.write(0, outlineId)
// TODO: 1 - display riding slime for nicer movement val passengerPacket = PacketContainer(PacketType.Play.Server.MOUNT)
// TODO: 2 - slime riding display for nicer movement passengerPacket.integers.write(0, marker.first)
// TODO: 3 - resource pack for outline with really low opacity passengerPacket.integerArrays.write(0, displayIds.map { it.first }.toIntArray())
val watcher = WrappedDataWatcher() UiManager.updateTeamEntities(player, Constants.PLACE_HINT_TEAM, displayIds.map { it.second.toString() })
watcher.setObject(0, Registry.get(Byte::class.javaObjectType), 0b01100000.toByte()) ProtocolLibrary.getProtocolManager().sendServerPacket(player, spawnMarkerPacket)
// watcher.setObject(5, Registry.get(Boolean::class.javaObjectType), true) ProtocolLibrary.getProtocolManager().sendServerPacket(player, passengerPacket)
watcher.setObject(16, Registry.get(Int::class.javaObjectType), min(sizeX, min(sizeY, sizeZ)).toInt())
dataPacket.dataValueCollectionModifier.write(0, watcher.watchableObjects tick()
.map { WrappedDataValue(it.watcherObject.index, it.watcherObject.serializer, it.rawValue) })
ProtocolLibrary.getProtocolManager().sendServerPacket(player, spawnSlimePacket)
ProtocolLibrary.getProtocolManager().sendServerPacket(player, dataPacket)
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, listOf(outlineUuid.toString()))
// val outlineInvisiblePacket = PacketContainer(PacketType.Play.Server.ENTITY_EFFECT)
// outlineInvisiblePacket.integers
// .write(0, outlineId)
// .write(1, 1)
// .write(2, -1)
//
// outlineInvisiblePacket.effectTypes.write(0, PotionEffectType.INVISIBILITY)
// outlineInvisiblePacket.bytes.write(0, 0b0000.toByte())
ProtocolLibrary.getProtocolManager().sendServerPacket(player, outlineTeamPacket)
// ProtocolLibrary.getProtocolManager().sendServerPacket(player, outlineInvisiblePacket)
isValid = validate(initialLocation, sizeX, sizeY, sizeZ)
updateColour()
currentLocation = initialLocation;
} }
private fun hide() { private fun hide() {
val packet = PacketContainer(PacketType.Play.Server.ENTITY_DESTROY) val packet = PacketContainer(PacketType.Play.Server.ENTITY_DESTROY)
packet.intLists.write(0, IntList.of(markerId, outlineId)) val ids = displayIds.map { it.first }.toMutableList()
ids.add(marker.first)
packet.intLists.write(0, ids)
ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet) ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet)
@ -187,45 +135,37 @@ private class HintMarker(
return return
} }
newLocation.add(-floor(sizeX / 2.0), 1.0, -floor(sizeZ / 2.0)) newLocation.add(this.offset)
val markerPacket = PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT) val basePacket = PacketContainer(PacketType.Play.Server.ENTITY_POSITION_SYNC)
markerPacket.integers.write(0, markerId) basePacket.integers.write(0, marker.first)
markerPacket.doubles basePacket.structures.values[0].structures.values[0].doubles
.write(0, newLocation.x) .write(0, newLocation.x)
.write(1, newLocation.y) .write(1, newLocation.y)
.write(2, newLocation.z) .write(2, newLocation.z)
markerPacket.booleans.write(0, true) basePacket.booleans.write(0, true)
ProtocolLibrary.getProtocolManager().sendServerPacket(player, markerPacket) ProtocolLibrary.getProtocolManager().sendServerPacket(player, basePacket)
val outlinePacket = PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT)
outlinePacket.integers.write(0, outlineId)
outlinePacket.doubles
.write(0, newLocation.x)
.write(1, newLocation.y)
.write(2, newLocation.z)
outlinePacket.booleans.write(0, true)
ProtocolLibrary.getProtocolManager().sendServerPacket(player, outlinePacket)
currentLocation = newLocation currentLocation = newLocation
// Update material this.tick()
val valid = validate(newLocation, sizeX, sizeY, sizeZ)
if (valid != isValid) {
isValid = valid
updateColour()
}
} }
// region helper functions // region helper functions
private fun spawnBlockDisplay(id: Int, uuid: UUID, location: Location, material: Material, scale: Vector3f, options: Byte) { protected fun spawnBlockDisplay(
location: Location,
block: BlockData,
scale: Vector3f = Vector3f(1f, 1f, 1f),
offset: Vector3f = Vector3f(0f, 0f, 0f),
options: Byte = 0b00000000
): Pair<Int, UUID> {
val id = (Math.random() * Int.MAX_VALUE).toInt()
val uuid = UUID.randomUUID()
val spawnPacket = PacketContainer(PacketType.Play.Server.SPAWN_ENTITY) val spawnPacket = PacketContainer(PacketType.Play.Server.SPAWN_ENTITY)
spawnPacket.integers.write(0, id) spawnPacket.integers.write(0, id)
spawnPacket.uuiDs.write(0, uuid) spawnPacket.uuiDs.write(0, uuid)
@ -239,13 +179,14 @@ private class HintMarker(
dataPacket.integers.write(0, id) dataPacket.integers.write(0, id)
// woated // woated
val blockData = BukkitConverters val blockData = BukkitConverters.getWrappedBlockDataConverter()
.getWrappedBlockDataConverter() .getGeneric(WrappedBlockData.createData(block))
.getGeneric(WrappedBlockData.createData(material))
val watcher = WrappedDataWatcher() val watcher = WrappedDataWatcher()
watcher.setObject(0, Registry.get(Byte::class.javaObjectType), options) watcher.setObject(0, Registry.get(Byte::class.javaObjectType), options)
watcher.setObject(11, Registry.get(Vector3f::class.javaObjectType), offset)
watcher.setObject(12, Registry.get(Vector3f::class.javaObjectType), scale) watcher.setObject(12, Registry.get(Vector3f::class.javaObjectType), scale)
watcher.setObject(16, Registry.get(Int::class.javaObjectType), 200)
watcher.setObject(23, Registry.getBlockDataSerializer(false), blockData) watcher.setObject(23, Registry.getBlockDataSerializer(false), blockData)
dataPacket.dataValueCollectionModifier.write(0, watcher.watchableObjects dataPacket.dataValueCollectionModifier.write(0, watcher.watchableObjects
@ -253,31 +194,17 @@ private class HintMarker(
ProtocolLibrary.getProtocolManager().sendServerPacket(player, spawnPacket) ProtocolLibrary.getProtocolManager().sendServerPacket(player, spawnPacket)
ProtocolLibrary.getProtocolManager().sendServerPacket(player, dataPacket) ProtocolLibrary.getProtocolManager().sendServerPacket(player, dataPacket)
return Pair<Int, UUID>(id, uuid)
} }
private fun updateColour() { protected open fun updateGlowColour(colour: ChatFormatting) {
// marker // outline glow
val dataPacket = PacketContainer(PacketType.Play.Server.ENTITY_METADATA)
dataPacket.integers.write(0,markerId);
val material = if (isValid) materialValid else materialInvalid
val blockData = BukkitConverters
.getWrappedBlockDataConverter()
.getGeneric(WrappedBlockData.createData(material))
val metadata = listOf(
WrappedDataValue(23, WrappedDataWatcher.Registry.getBlockDataSerializer(false), blockData)
)
dataPacket.dataValueCollectionModifier.write(0, metadata)
ProtocolLibrary.getProtocolManager().sendServerPacket(player, dataPacket)
// outline
val outlineTeamPacket = PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM) 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) outlineTeamPacket.integers.write(0, 2)
val params = WrappedTeamParameters.newBuilder(Utils.teamParams) val params = WrappedTeamParameters.newBuilder(UiManager.teamParamsTemplate)
.color(if (isValid) EnumWrappers.ChatFormatting.GREEN else EnumWrappers.ChatFormatting.RED) .color(colour)
.build() .build()
outlineTeamPacket.optionalTeamParameters.write(0, Optional.of(params)) outlineTeamPacket.optionalTeamParameters.write(0, Optional.of(params))
@ -286,17 +213,3 @@ private class HintMarker(
// endregion // endregion
} }
private class Utils {
companion object {
val teamParams: WrappedTeamParameters = WrappedTeamParameters.newBuilder()
.displayName(WrappedChatComponent.fromText(""))
.collisionRule("never")
.nametagVisibility("never")
.options(0)
.color(EnumWrappers.ChatFormatting.RED)
.prefix(WrappedChatComponent.fromText(""))
.suffix(WrappedChatComponent.fromText(""))
.build()
}
}

View File

@ -5,13 +5,13 @@ import org.bukkit.Location
class PlaceHintValidators { class PlaceHintValidators {
companion object { companion object {
fun alwaysTrue(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float): Boolean { fun alwaysTrue(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float, offsetY: Int): Boolean {
return true return true
} }
fun allAir(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float): Boolean { fun allAir(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float, offsetY: Int): Boolean {
for (x in 0..<sizeX.toInt()) for (x in 0..<sizeX.toInt())
for (y in 0..<sizeY.toInt()) for (y in -offsetY..<sizeY.toInt())
for (z in 0..<sizeZ.toInt()) for (z in 0..<sizeZ.toInt())
if (!location.world.getBlockAt( if (!location.world.getBlockAt(
location.blockX + x, location.blockX + x,
@ -22,9 +22,9 @@ class PlaceHintValidators {
return true return true
} }
fun allReplaceable(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float): Boolean { fun allReplaceable(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float, offsetY: Int): Boolean {
for (x in 0..<sizeX.toInt()) for (x in 0..<sizeX.toInt())
for (y in 0..<sizeY.toInt()) for (y in -offsetY..<sizeY.toInt())
for (z in 0..<sizeZ.toInt()) for (z in 0..<sizeZ.toInt())
if (!location.world.getBlockAt( if (!location.world.getBlockAt(
location.blockX + x, location.blockX + x,

View File

@ -1,67 +0,0 @@
package com.pobnellion.aoe.ui
import com.pobnellion.aoe.building.Blacksmith
import com.pobnellion.aoe.building.House
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
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 PlaceItem : Listener {
private val buildingInventory = Bukkit.createInventory(null, 9, Component.text("Buildings"))
init {
buildingInventory.addItem(createGuiItem(Material.OAK_LOG, "House", "Just a house bro"))
buildingInventory.addItem(createGuiItem(Material.ANVIL, "Blacksmith", "smith ."))
}
@EventHandler
fun onRightClickItem(event: PlayerInteractEvent) {
if (event.material != Material.BRICKS || (event.action != Action.RIGHT_CLICK_AIR && event.action != Action.RIGHT_CLICK_BLOCK))
return
event.isCancelled = true
event.player.openInventory(buildingInventory)
}
@EventHandler
fun onInventoryClick(event: InventoryClickEvent) {
if (event.inventory != buildingInventory)
return
event.isCancelled = true
var validClick = true
when (event.currentItem?.type) {
Material.OAK_LOG -> House().showPlaceHint(event.whoClicked as Player)
Material.ANVIL -> Blacksmith().showPlaceHint(event.whoClicked as Player)
else -> validClick = false
}
if (validClick)
event.whoClicked.closeInventory(InventoryCloseEvent.Reason.PLUGIN)
}
@EventHandler
fun onInventoryClick(event: InventoryDragEvent) {
if (event.inventory == buildingInventory)
event.isCancelled = true
}
private fun createGuiItem(material: Material, name: String, lore: String): ItemStack {
val item = ItemStack(material, 1)
item.itemMeta.displayName(Component.text(name))
item.itemMeta.lore(mutableListOf(Component.text(lore)))
return item
}
}

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]
}

View File

@ -4,7 +4,8 @@ main: com.pobnellion.aoe.Aoe
api-version: '1.21' api-version: '1.21'
depend: depend:
- ProtocolLib - ProtocolLib
- WorldEdit
commands: commands:
debug: aoe:
description: Debug command description: Debug command
usage: /debug usage: /aoe