Compare commits
2 commits
a074ba56ae
...
2d9e35312b
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d9e35312b | |||
| 20541c744e |
9 changed files with 174 additions and 13 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
package ch.offbeatwit.bestbuds
|
package ch.offbeatwit.bestbuds
|
||||||
|
|
||||||
import ch.offbeatwit.bestbuds.managers.Context
|
import ch.offbeatwit.bestbuds.managers.Context
|
||||||
|
import ch.offbeatwit.bestbuds.modules.AdoptionModule
|
||||||
import ch.offbeatwit.bestbuds.modules.AntiGriefModule
|
import ch.offbeatwit.bestbuds.modules.AntiGriefModule
|
||||||
import ch.offbeatwit.bestbuds.modules.CommandsModule
|
import ch.offbeatwit.bestbuds.modules.CommandsModule
|
||||||
import ch.offbeatwit.bestbuds.modules.InteractionModule
|
import ch.offbeatwit.bestbuds.modules.InteractionModule
|
||||||
|
|
@ -16,6 +17,7 @@ class BestBudsPlugin : JavaPlugin() {
|
||||||
moduleLoader.load(::InteractionModule)
|
moduleLoader.load(::InteractionModule)
|
||||||
moduleLoader.load(::CommandsModule)
|
moduleLoader.load(::CommandsModule)
|
||||||
moduleLoader.load(::AntiGriefModule)
|
moduleLoader.load(::AntiGriefModule)
|
||||||
|
moduleLoader.load(::AdoptionModule)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisable() {
|
override fun onDisable() {
|
||||||
|
|
|
||||||
6
src/main/kotlin/ch/offbeatwit/bestbuds/DataKeys.kt
Normal file
6
src/main/kotlin/ch/offbeatwit/bestbuds/DataKeys.kt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
package ch.offbeatwit.bestbuds
|
||||||
|
|
||||||
|
import org.bukkit.NamespacedKey
|
||||||
|
|
||||||
|
val ADOPTION_TARGET = NamespacedKey("bestbuds", "adoption_target")
|
||||||
|
val IS_ADOPTABLE = NamespacedKey("bestbuds", "is_adoptable")
|
||||||
106
src/main/kotlin/ch/offbeatwit/bestbuds/modules/AdoptionModule.kt
Normal file
106
src/main/kotlin/ch/offbeatwit/bestbuds/modules/AdoptionModule.kt
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
package ch.offbeatwit.bestbuds.modules
|
||||||
|
|
||||||
|
import ch.offbeatwit.bestbuds.ADOPTION_TARGET
|
||||||
|
import ch.offbeatwit.bestbuds.isOwnerOf
|
||||||
|
import ch.offbeatwit.bestbuds.managers.Context
|
||||||
|
import ch.offbeatwit.bestbuds.pet.PetInfo
|
||||||
|
import ch.offbeatwit.bestbuds.util.UUIDDataType
|
||||||
|
import ch.offbeatwit.bestbuds.util.error
|
||||||
|
import ch.offbeatwit.bestbuds.util.tr
|
||||||
|
import net.kyori.adventure.text.Component
|
||||||
|
import net.kyori.adventure.text.Component.text
|
||||||
|
import net.kyori.adventure.text.JoinConfiguration
|
||||||
|
import org.bukkit.Material
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
import org.bukkit.entity.Tameable
|
||||||
|
import org.bukkit.event.EventHandler
|
||||||
|
import org.bukkit.event.Listener
|
||||||
|
import org.bukkit.event.player.PlayerInteractEntityEvent
|
||||||
|
import org.bukkit.inventory.EquipmentSlot
|
||||||
|
import org.bukkit.inventory.ItemStack
|
||||||
|
import org.bukkit.inventory.meta.BookMeta
|
||||||
|
|
||||||
|
class AdoptionModule(ctx: Context) : BestBudsModule(ctx), Listener {
|
||||||
|
val adoptionItem = Material.PAPER
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onInteract(ev: PlayerInteractEntityEvent) {
|
||||||
|
val target = ev.rightClicked
|
||||||
|
if (target !is Tameable || !target.isTamed) return
|
||||||
|
val info = PetInfo(target)
|
||||||
|
|
||||||
|
val heldItem = ev.player.inventory.getItem(ev.hand)
|
||||||
|
|
||||||
|
if (ev.player.isSneaking && ev.player.isOwnerOf(target) && heldItem.type == adoptionItem) {
|
||||||
|
ev.isCancelled = true
|
||||||
|
setupAdoption(ev.player, ev.hand, target)
|
||||||
|
} else if (heldItem.type == Material.WRITTEN_BOOK) {
|
||||||
|
val bookTarget = heldItem.persistentDataContainer[ADOPTION_TARGET, UUIDDataType] ?: return
|
||||||
|
ev.isCancelled = true
|
||||||
|
|
||||||
|
if (target.uniqueId == bookTarget && info.isAdoptable) {
|
||||||
|
info.isAdoptable = false
|
||||||
|
heldItem.subtract()
|
||||||
|
|
||||||
|
if (ev.player.isOwnerOf(target)) {
|
||||||
|
ev.player.sendActionBar(tr(info.nameComponent, text("is no longer adoptable")))
|
||||||
|
} else {
|
||||||
|
target.owner = ev.player
|
||||||
|
|
||||||
|
ev.player.sendActionBar(
|
||||||
|
tr(
|
||||||
|
text("Adopted ")
|
||||||
|
.append(info.nameComponent)
|
||||||
|
.append(text("! Take good care of them!"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (target.uniqueId == bookTarget) {
|
||||||
|
ev.player.sendActionBar(error("That pet is not currently adoptable"))
|
||||||
|
} else {
|
||||||
|
ev.player.sendActionBar(error("Those papers aren't for this pet!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setupAdoption(sender: Player, hand: EquipmentSlot, pet: Tameable) {
|
||||||
|
val info = PetInfo(pet)
|
||||||
|
val stack = sender.inventory.getItem(hand)
|
||||||
|
if (stack.type != adoptionItem) return // sanity check
|
||||||
|
if (stack.amount == 0) return
|
||||||
|
|
||||||
|
stack.subtract()
|
||||||
|
|
||||||
|
val adoptionPaper = ItemStack(Material.WRITTEN_BOOK, 1).apply {
|
||||||
|
editMeta(BookMeta::class.java) {
|
||||||
|
it.title(Component.join(JoinConfiguration.spaces(),
|
||||||
|
text("Adoption papers for"), info.nameComponent))
|
||||||
|
it.author(sender.displayName())
|
||||||
|
|
||||||
|
Component.join(JoinConfiguration.newlines(),
|
||||||
|
text("Adopt ").append(info.nameComponent).append(text(" today!")),
|
||||||
|
text(""),
|
||||||
|
text("Right-click on ").append(info.nameComponent)
|
||||||
|
.append(text(" with this book to adopt them."))
|
||||||
|
).apply { it.addPages(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
editPersistentDataContainer {
|
||||||
|
it.set(ADOPTION_TARGET, UUIDDataType, pet.uniqueId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.isEmpty) {
|
||||||
|
sender.inventory.setItem(hand, adoptionPaper)
|
||||||
|
} else {
|
||||||
|
val res = sender.inventory.addItem(adoptionPaper)
|
||||||
|
if (res.isNotEmpty()) {
|
||||||
|
sender.world.dropItem(pet.location, adoptionPaper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info.isAdoptable = true
|
||||||
|
|
||||||
|
sender.sendActionBar(tr("Set pet as adoptable!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,8 +24,8 @@ class CommandsModule(ctx: Context) : BestBudsModule(ctx), CommandRegistrar {
|
||||||
val root = literal("bestbuds")
|
val root = literal("bestbuds")
|
||||||
.then(transfer())
|
.then(transfer())
|
||||||
.then(release())
|
.then(release())
|
||||||
.then(allow())
|
.then(trust())
|
||||||
.then(disallow())
|
.then(untrust())
|
||||||
.executes(BestBudsHelpTopic)
|
.executes(BestBudsHelpTopic)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|
@ -78,8 +78,8 @@ class CommandsModule(ctx: Context) : BestBudsModule(ctx), CommandRegistrar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allow(): CommandNode<CommandSourceStack> {
|
fun trust(): CommandNode<CommandSourceStack> {
|
||||||
return literal("allow")
|
return literal("trust")
|
||||||
.then(argument("player", ArgumentTypes.playerProfiles())
|
.then(argument("player", ArgumentTypes.playerProfiles())
|
||||||
.executes { ctx ->
|
.executes { ctx ->
|
||||||
val sender = ctx.source.sender
|
val sender = ctx.source.sender
|
||||||
|
|
@ -95,7 +95,7 @@ class CommandsModule(ctx: Context) : BestBudsModule(ctx), CommandRegistrar {
|
||||||
if (this.ctx.whitelistManager.addToWhitelist(sender, target)) {
|
if (this.ctx.whitelistManager.addToWhitelist(sender, target)) {
|
||||||
sender.sendMessage(tr("${target.name} can now interact with your pets"))
|
sender.sendMessage(tr("${target.name} can now interact with your pets"))
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage(error("${target.name} is already on the allowlist"))
|
sender.sendMessage(error("${target.name} is already trusted"))
|
||||||
}
|
}
|
||||||
|
|
||||||
Command.SINGLE_SUCCESS
|
Command.SINGLE_SUCCESS
|
||||||
|
|
@ -103,8 +103,8 @@ class CommandsModule(ctx: Context) : BestBudsModule(ctx), CommandRegistrar {
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disallow(): CommandNode<CommandSourceStack> {
|
fun untrust(): CommandNode<CommandSourceStack> {
|
||||||
return literal("disallow")
|
return literal("untrust")
|
||||||
.then(argument("player", ArgumentTypes.playerProfiles())
|
.then(argument("player", ArgumentTypes.playerProfiles())
|
||||||
.executes { ctx ->
|
.executes { ctx ->
|
||||||
val sender = ctx.source.sender
|
val sender = ctx.source.sender
|
||||||
|
|
@ -120,7 +120,7 @@ class CommandsModule(ctx: Context) : BestBudsModule(ctx), CommandRegistrar {
|
||||||
if (this.ctx.whitelistManager.removeFromWhitelist(sender, target)) {
|
if (this.ctx.whitelistManager.removeFromWhitelist(sender, target)) {
|
||||||
sender.sendMessage(tr("${target.name} can no longer interact with your pets"))
|
sender.sendMessage(tr("${target.name} can no longer interact with your pets"))
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage(error("${target.name} isn't on the allowlist"))
|
sender.sendMessage(error("${target.name} isn't trusted"))
|
||||||
}
|
}
|
||||||
|
|
||||||
Command.SINGLE_SUCCESS
|
Command.SINGLE_SUCCESS
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ object BestBudsHelpTopic : HelpTopic(), Command<CommandSourceStack> {
|
||||||
val commands = listOf(
|
val commands = listOf(
|
||||||
CommandHelp("/bb transfer <player>", "Change the owner of a pet to another player"),
|
CommandHelp("/bb transfer <player>", "Change the owner of a pet to another player"),
|
||||||
CommandHelp("/bb release", "Un-tame a pet, releasing them back into the wild"),
|
CommandHelp("/bb release", "Un-tame a pet, releasing them back into the wild"),
|
||||||
CommandHelp("/bb allow <player>", "Allow another player to ride, leash, hurt and rename your pets"),
|
CommandHelp("/bb trust <player>", "Allow another player to ride, leash, hurt and rename your pets"),
|
||||||
CommandHelp("/bb disallow <player>", "Remove a player from your allowlist"),
|
CommandHelp("/bb untrust <player>", "Remove a player from your trusted list"),
|
||||||
)
|
)
|
||||||
|
|
||||||
return Component.join(JoinConfiguration.newlines(), commands.map {
|
return Component.join(JoinConfiguration.newlines(), commands.map {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import org.bukkit.entity.ChestedHorse
|
||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
import org.bukkit.entity.Tameable
|
import org.bukkit.entity.Tameable
|
||||||
import org.bukkit.event.EventHandler
|
import org.bukkit.event.EventHandler
|
||||||
|
import org.bukkit.event.EventPriority
|
||||||
import org.bukkit.event.Listener
|
import org.bukkit.event.Listener
|
||||||
import org.bukkit.event.player.PlayerInteractEntityEvent
|
import org.bukkit.event.player.PlayerInteractEntityEvent
|
||||||
import org.bukkit.util.Transformation
|
import org.bukkit.util.Transformation
|
||||||
|
|
@ -23,8 +24,9 @@ import org.joml.Vector3f
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class InteractionModule(ctx: Context) : BestBudsModule(ctx), Listener {
|
class InteractionModule(ctx: Context) : BestBudsModule(ctx), Listener {
|
||||||
@EventHandler
|
@EventHandler(priority = EventPriority.HIGH)
|
||||||
fun onClickPet(ev: PlayerInteractEntityEvent) {
|
fun onClickPet(ev: PlayerInteractEntityEvent) {
|
||||||
|
if (ev.isCancelled) return
|
||||||
if (!ev.player.isSneaking) return
|
if (!ev.player.isSneaking) return
|
||||||
|
|
||||||
val target = ev.rightClicked
|
val target = ev.rightClicked
|
||||||
|
|
@ -54,11 +56,13 @@ class InteractionModule(ctx: Context) : BestBudsModule(ctx), Listener {
|
||||||
?.also { it.completeFromCache() }
|
?.also { it.completeFromCache() }
|
||||||
?.let { text(it.name ?: "unknown", NamedTextColor.GOLD) }
|
?.let { text(it.name ?: "unknown", NamedTextColor.GOLD) }
|
||||||
?: text("nobody", NamedTextColor.RED)
|
?: text("nobody", NamedTextColor.RED)
|
||||||
|
|
||||||
val component = Component.join(
|
val component = Component.join(
|
||||||
JoinConfiguration.newlines(),
|
JoinConfiguration.newlines(),
|
||||||
info.nameComponent,
|
info.nameComponent,
|
||||||
text("Owned by ").append(owner),
|
text("Owned by ").append(owner),
|
||||||
text("Health: ").append(info.healthComponent)
|
text("Health: ").append(info.healthComponent),
|
||||||
|
*(if (info.isAdoptable) arrayOf(text("Adoptable").color(NamedTextColor.GREEN)) else emptyArray())
|
||||||
).style(INFO_TEXT_STYLE)
|
).style(INFO_TEXT_STYLE)
|
||||||
|
|
||||||
ctx.hologramManager.showSingle(this, pet.location, component) {
|
ctx.hologramManager.showSingle(this, pet.location, component) {
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,15 @@ class InfoDialog(val pet: Tameable) : Supplier<DialogLike> {
|
||||||
@Suppress("UnstableApiUsage")
|
@Suppress("UnstableApiUsage")
|
||||||
override fun get(): DialogLike {
|
override fun get(): DialogLike {
|
||||||
return Dialog.create { builder ->
|
return Dialog.create { builder ->
|
||||||
val body = listOf(
|
val body = mutableListOf(
|
||||||
DialogBody.plainMessage(text("Type: ").append(text(pet.type.name, NamedTextColor.GOLD))),
|
DialogBody.plainMessage(text("Type: ").append(text(pet.type.name, NamedTextColor.GOLD))),
|
||||||
DialogBody.plainMessage(text("Health: ").append(info.healthComponent)),
|
DialogBody.plainMessage(text("Health: ").append(info.healthComponent)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (info.isAdoptable) {
|
||||||
|
body.add(DialogBody.plainMessage(text("Adoptable!", NamedTextColor.GREEN)))
|
||||||
|
}
|
||||||
|
|
||||||
val base = DialogBase.builder(text("Managing ").append(pet.customName() ?: text(pet.name)))
|
val base = DialogBase.builder(text("Managing ").append(pet.customName() ?: text(pet.name)))
|
||||||
.body(body)
|
.body(body)
|
||||||
.canCloseWithEscape(true)
|
.canCloseWithEscape(true)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
package ch.offbeatwit.bestbuds.pet
|
package ch.offbeatwit.bestbuds.pet
|
||||||
|
|
||||||
|
import ch.offbeatwit.bestbuds.IS_ADOPTABLE
|
||||||
import ch.offbeatwit.bestbuds.util.PET_NAME_STYLE
|
import ch.offbeatwit.bestbuds.util.PET_NAME_STYLE
|
||||||
import net.kyori.adventure.text.Component
|
import net.kyori.adventure.text.Component
|
||||||
import net.kyori.adventure.text.JoinConfiguration
|
import net.kyori.adventure.text.JoinConfiguration
|
||||||
import net.kyori.adventure.text.format.NamedTextColor
|
import net.kyori.adventure.text.format.NamedTextColor
|
||||||
import org.bukkit.attribute.Attribute
|
import org.bukkit.attribute.Attribute
|
||||||
import org.bukkit.entity.Tameable
|
import org.bukkit.entity.Tameable
|
||||||
|
import org.bukkit.persistence.PersistentDataType
|
||||||
|
|
||||||
class PetInfo(val pet: Tameable) {
|
class PetInfo(val pet: Tameable) {
|
||||||
val nameComponent get() = (pet.customName() ?: Component.text(pet.name)).style(PET_NAME_STYLE)
|
val nameComponent get() = (pet.customName() ?: Component.text(pet.name)).style(PET_NAME_STYLE)
|
||||||
|
|
@ -21,4 +23,8 @@ class PetInfo(val pet: Tameable) {
|
||||||
Component.text(pet.health, healthColor), Component.text(maxHealth?.toString() ?: "???", healthColor)
|
Component.text(pet.health, healthColor), Component.text(maxHealth?.toString() ?: "???", healthColor)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isAdoptable
|
||||||
|
get() = pet.persistentDataContainer[IS_ADOPTABLE, PersistentDataType.BOOLEAN] ?: false
|
||||||
|
set(value) = pet.persistentDataContainer.set(IS_ADOPTABLE, PersistentDataType.BOOLEAN, value)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
src/main/kotlin/ch/offbeatwit/bestbuds/util/UUIDDataType.kt
Normal file
32
src/main/kotlin/ch/offbeatwit/bestbuds/util/UUIDDataType.kt
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package ch.offbeatwit.bestbuds.util
|
||||||
|
|
||||||
|
import org.bukkit.persistence.PersistentDataAdapterContext
|
||||||
|
import org.bukkit.persistence.PersistentDataType
|
||||||
|
import java.lang.Long
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.ByteArray
|
||||||
|
|
||||||
|
object UUIDDataType : PersistentDataType<ByteArray, UUID> {
|
||||||
|
override fun getPrimitiveType(): Class<ByteArray> {
|
||||||
|
return ByteArray::class.java
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getComplexType(): Class<UUID> {
|
||||||
|
return UUID::class.java
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toPrimitive(complex: UUID, context: PersistentDataAdapterContext): ByteArray {
|
||||||
|
val bb = ByteBuffer.allocate(Long.BYTES * 2)
|
||||||
|
bb.putLong(complex.mostSignificantBits)
|
||||||
|
bb.putLong(complex.leastSignificantBits)
|
||||||
|
return bb.array()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fromPrimitive(primitive: ByteArray, context: PersistentDataAdapterContext): UUID {
|
||||||
|
val bb = ByteBuffer.wrap(primitive)
|
||||||
|
val msb = bb.getLong()
|
||||||
|
val lsb = bb.getLong()
|
||||||
|
return UUID(msb, lsb)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue