Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,39 @@ package io.github.pylonmc.pylon.core.i18n.packet
import io.github.pylonmc.pylon.core.PylonCore
import io.github.pylonmc.pylon.core.i18n.PlayerTranslationHandler
import io.github.pylonmc.pylon.core.item.PylonItem
import io.github.pylonmc.pylon.core.item.PylonItemSchema
import io.github.pylonmc.pylon.core.util.editData
import io.netty.channel.ChannelDuplexHandler
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelPromise
import io.papermc.paper.datacomponent.DataComponentTypes
import io.papermc.paper.datacomponent.item.ItemLore
import net.kyori.adventure.text.Component
import net.minecraft.network.HashedPatchMap
import net.minecraft.network.HashedStack
import net.minecraft.network.protocol.game.*
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.HashOps
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.display.*
import org.bukkit.craftbukkit.inventory.CraftItemStack
import java.util.WeakHashMap
import java.util.logging.Level


// Much inspiration has been taken from https://github.com/GuizhanCraft/SlimefunTranslation
// with permission from the author
class PlayerPacketHandler(private val player: ServerPlayer, private val handler: PlayerTranslationHandler) {

private val connection = player.connection
private val channel = connection.connection.channel
private val channel = player.connection.connection.channel

private val hashGenerator: HashedPatchMap.HashGenerator

init {
// (PaperMC server) https://discord.com/channels/289587909051416579/555462289851940864/1371651093972385823
val registryOps = player.registryAccess().createSerializationContext(HashOps.CRC32C_INSTANCE)
hashGenerator = HashedPatchMap.HashGenerator { component ->
component.encodeValue(registryOps)
.getOrThrow { IllegalArgumentException("Failed to hash $component: $it") }
.asInt()
}
}

fun register() {
channel.pipeline().addBefore("packet_handler", HANDLER_NAME, PacketHandler())
Expand All @@ -37,24 +49,17 @@ class PlayerPacketHandler(private val player: ServerPlayer, private val handler:
}
}

fun resendInventory() {
val inventory = player.containerMenu
for (slot in 0..45) {
val item = inventory.getSlot(slot).item
player.containerSynchronizer.sendSlotChange(inventory, slot, item)
}
}

private inner class PacketHandler : ChannelDuplexHandler() {
override fun write(ctx: ChannelHandlerContext, packet: Any, promise: ChannelPromise) {
var packet = packet
when (packet) {
is ClientboundContainerSetContentPacket -> {
packet.items.forEach(::translateItem)
translateItem(packet.carriedItem)
packet.items.forEach(::translate)
translate(packet.carriedItem)
}

is ClientboundContainerSetSlotPacket -> translateItem(packet.item)
is ClientboundContainerSetSlotPacket -> translate(packet.item)
is ClientboundSetCursorItemPacket -> translate(packet.contents)
is ClientboundRecipeBookAddPacket -> {
// This requires a full copy for some reason
packet = ClientboundRecipeBookAddPacket(
Expand Down Expand Up @@ -86,21 +91,30 @@ class PlayerPacketHandler(private val player: ServerPlayer, private val handler:

override fun channelRead(ctx: ChannelHandlerContext, packet: Any) {
var packet = packet
when (packet) {
is ServerboundContainerClickPacket -> {
// force server to resend the item
packet = ServerboundContainerClickPacket(
packet.containerId,
-1,
packet.slotNum,
packet.buttonNum,
packet.clickType,
packet.changedSlots,
packet.carriedItem,
)
}

is ServerboundSetCreativeModeSlotPacket -> resetItem(packet.itemStack)
packet = when (packet) {
is ServerboundContainerClickPacket -> ServerboundContainerClickPacket(
packet.containerId,
packet.stateId,
packet.slotNum,
packet.buttonNum,
packet.clickType,
packet.changedSlots,
if (packet.changedSlots.size == 1) {
HashedStack.create(
player.containerMenu.getSlot(packet.changedSlots.keys.single()).item,
hashGenerator
)
} else {
HashedStack.create(player.containerMenu.carried, hashGenerator)
}
)

is ServerboundSetCreativeModeSlotPacket -> ServerboundSetCreativeModeSlotPacket(
packet.slotNum,
reset(packet.itemStack)
)

else -> packet
}
super.channelRead(ctx, packet)
}
Expand Down Expand Up @@ -153,7 +167,7 @@ class PlayerPacketHandler(private val player: ServerPlayer, private val handler:

is SlotDisplay.Composite -> SlotDisplay.Composite(display.contents.map(::handleSlotDisplay))
is SlotDisplay.ItemStackSlotDisplay -> SlotDisplay.ItemStackSlotDisplay(
display.stack.copy().apply(::translateItem)
display.stack.copy().apply(::translate)
)

is SlotDisplay.SmithingTrimDemoSlotDisplay -> SlotDisplay.SmithingTrimDemoSlotDisplay(
Expand All @@ -171,10 +185,10 @@ class PlayerPacketHandler(private val player: ServerPlayer, private val handler:
}
}

private inline fun handleItem(item: ItemStack, handler: (PylonItem) -> Unit) {
private fun translate(item: ItemStack) {
if (item.isEmpty) return
try {
handler(PylonItem.fromStack(CraftItemStack.asCraftMirror(item)) ?: return)
handler.handleItem(CraftItemStack.asCraftMirror(item))
} catch (e: Throwable) {
// Log the error nicely instead of kicking the player off
// and causing two days of headache. True story.
Expand All @@ -186,24 +200,38 @@ class PlayerPacketHandler(private val player: ServerPlayer, private val handler:
}
}

private fun translateItem(item: ItemStack) = handleItem(item, handler::handleItem)
private fun resetItem(item: ItemStack) = handleItem(item, ::reset)
// no, I have no idea what this does either
private fun reset(stack: ItemStack): ItemStack {
if (stack.isEmpty) return stack
val bukkitStack = CraftItemStack.asCraftMirror(stack)
val item = PylonItem.fromStack(bukkitStack) ?: return stack
val prototype = item.schema.getItemStack()
prototype.copyDataFrom(bukkitStack) { it != DataComponentTypes.ITEM_NAME && it != DataComponentTypes.LORE }
val translatedPrototype = prototype.clone()
try {
handler.handleItem(translatedPrototype)
} catch (e: Throwable) {
PylonCore.logger.log(
Level.SEVERE,
"An error occurred while handling item translations",
e
)
return stack
}
prototype.editData(DataComponentTypes.ITEM_NAME) {
val protoName = translatedPrototype.getData(DataComponentTypes.ITEM_NAME)!!
val newName = bukkitStack.getData(DataComponentTypes.ITEM_NAME)!!
if (protoName != newName) newName else it
}
prototype.editData(DataComponentTypes.LORE) {
val protoLore = translatedPrototype.getData(DataComponentTypes.LORE)!!
val newLore = bukkitStack.getData(DataComponentTypes.LORE)!!
if (protoLore != newLore) newLore else it
}
return CraftItemStack.unwrap(prototype)
}

companion object {
private const val HANDLER_NAME = "pylon_packet_handler"
}
}

private val names = WeakHashMap<PylonItemSchema, Component>()
private val lores = WeakHashMap<PylonItemSchema, ItemLore>()

private fun reset(item: PylonItem) {
val name = names.getOrPut(item.schema) {
item.schema.itemStack.getData(DataComponentTypes.ITEM_NAME)!!
}
val lore = lores.getOrPut(item.schema) {
item.schema.itemStack.getData(DataComponentTypes.LORE)!!
}
item.stack.setData(DataComponentTypes.ITEM_NAME, name)
item.stack.setData(DataComponentTypes.LORE, lore)
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ object NmsAccessorImpl : NmsAccessor {
}

override fun resendInventory(player: Player) {
val handler = players[player.uniqueId] ?: return
handler.resendInventory()
val player = (player as CraftPlayer).handle
val inventory = player.containerMenu
for (slot in 0..45) {
val item = inventory.getSlot(slot).item
player.containerSynchronizer.sendSlotChange(inventory, slot, item)
}
}

override fun resendRecipeBook(player: Player) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import com.github.shynixn.mccoroutine.bukkit.ticks
import io.github.pylonmc.pylon.core.addon.PylonAddon
import io.github.pylonmc.pylon.core.block.*
import io.github.pylonmc.pylon.core.block.base.*
import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureEngine
import io.github.pylonmc.pylon.core.block.waila.Waila
import io.github.pylonmc.pylon.core.command.ROOT_COMMAND
import io.github.pylonmc.pylon.core.command.ROOT_COMMAND_PY_ALIAS
Expand All @@ -31,8 +30,9 @@ import io.github.pylonmc.pylon.core.recipe.ConfigurableRecipeType
import io.github.pylonmc.pylon.core.recipe.PylonRecipeListener
import io.github.pylonmc.pylon.core.recipe.RecipeType
import io.github.pylonmc.pylon.core.registry.PylonRegistry
import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureConfig
import io.github.pylonmc.pylon.core.resourcepack.armor.ArmorTextureEngine
import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureConfig
import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureEngine
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents
import kotlinx.coroutines.delay
Expand All @@ -44,14 +44,13 @@ import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.configuration.file.YamlConfiguration
import org.bukkit.entity.BlockDisplay
import org.bukkit.entity.Interaction
import org.bukkit.entity.ItemDisplay
import org.bukkit.permissions.Permission
import org.bukkit.permissions.PermissionDefault
import org.bukkit.plugin.java.JavaPlugin
import xyz.xenondevs.invui.InvUI
import java.util.*
import java.util.Locale
import kotlin.io.path.*

/**
Expand All @@ -75,7 +74,10 @@ object PylonCore : JavaPlugin(), PylonAddon {
packetEvents.eventManager.registerListener(ArmorTextureEngine, PacketListenerPriority.HIGHEST)

val entityLibPlatform = SpigotEntityLibPlatform(this)
entityLibPlatform.entityIdProvider = EntityIdProvider { uuid, type -> Bukkit.getUnsafe().nextEntityId() }
entityLibPlatform.entityIdProvider = EntityIdProvider { uuid, type ->
@Suppress("DEPRECATION")
Bukkit.getUnsafe().nextEntityId()
}
val entityLibSettings = APIConfig(packetEvents)
.tickTickables()
.trackPlatformEntities()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes
import com.github.retrooper.packetevents.protocol.world.Location
import com.github.retrooper.packetevents.util.Vector3f
import io.github.pylonmc.pylon.core.PylonCore
import io.github.pylonmc.pylon.core.block.PylonBlock.Companion.register
import io.github.pylonmc.pylon.core.block.base.PylonDirectionalBlock
import io.github.pylonmc.pylon.core.block.base.PylonEntityHolderBlock
import io.github.pylonmc.pylon.core.block.base.PylonGuiBlock
Expand Down Expand Up @@ -215,7 +216,7 @@ open class PylonBlock internal constructor(val block: Block) {
*/
open fun getDropItem(context: BlockBreakContext): ItemStack? {
return if (context.normallyDrops) {
defaultItem?.itemStack
defaultItem?.getItemStack()
} else {
null
}
Expand All @@ -229,7 +230,7 @@ open class PylonBlock internal constructor(val block: Block) {
*
* @return the item the block should give when middle clicked, or null if none
*/
open fun getPickItem() = defaultItem?.itemStack
open fun getPickItem() = defaultItem?.getItemStack()

/**
* Returns the item that should be used to display the block's texture.
Expand All @@ -238,7 +239,7 @@ open class PylonBlock internal constructor(val block: Block) {
*
* @return the item that should be used to display the block's texture
*/
open fun getBlockTextureItem() = defaultItem?.itemStack?.apply {
open fun getBlockTextureItem() = defaultItem?.getItemStack()?.apply {
itemMeta.persistentDataContainer.set(pylonBlockTextureEntityKey, PylonSerializers.BOOLEAN, true)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import io.github.pylonmc.pylon.core.block.waila.Waila.Companion.wailaEnabled
import io.github.pylonmc.pylon.core.content.debug.DebugWaxedWeatheredCutCopperStairs
import io.github.pylonmc.pylon.core.content.guide.PylonGuide
import io.github.pylonmc.pylon.core.entity.display.transform.Rotation
import io.github.pylonmc.pylon.core.gametest.GameTestConfig
import io.github.pylonmc.pylon.core.i18n.PylonArgument
import io.github.pylonmc.pylon.core.item.PylonItem
import io.github.pylonmc.pylon.core.item.PylonItemSchema
Expand All @@ -29,7 +30,6 @@ import io.github.pylonmc.pylon.core.particles.ConfettiParticle
import io.github.pylonmc.pylon.core.recipe.ConfigurableRecipeType
import io.github.pylonmc.pylon.core.recipe.RecipeType
import io.github.pylonmc.pylon.core.registry.PylonRegistry
import io.github.pylonmc.pylon.core.gametest.GameTestConfig
import io.github.pylonmc.pylon.core.util.mergeGlobalConfig
import io.github.pylonmc.pylon.core.util.position.BlockPosition
import io.github.pylonmc.pylon.core.util.vanillaDisplayName
Expand Down Expand Up @@ -91,12 +91,12 @@ private val give = buildCommand("give") {
val players = context.getArgument<List<Player>>("players")
val singular = players.size == 1
for (player in players) {
player.inventory.addItem(item.itemStack.asQuantity(amount))
player.inventory.addItem(item.getItemStack().asQuantity(amount))
}
context.source.sender.sendVanillaFeedback(
"give.success." + if (singular) "single" else "multiple",
Component.text(amount),
item.itemStack.vanillaDisplayName(),
item.getItemStack().vanillaDisplayName(),
if (singular) players[0].name() else Component.text(players.size)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ open class PylonFluid(
val addon = PylonRegistry.ADDONS[NamespacedKey(key.namespace, key.namespace)]!!
for (locale in addon.languages) {
val translationKey = "pylon.${key.namespace}.fluid.${key.key}"
check(addon.translator.canTranslate(translationKey, locale)) {
if (!addon.translator.canTranslate(translationKey, locale)) {
PylonCore.logger.warning("${key.namespace} is missing a translation key for fluid ${key.key} (locale: ${locale.displayName} | expected translation key: $translationKey")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.github.pylonmc.pylon.core.guide.button

import com.github.shynixn.mccoroutine.bukkit.launch
import io.github.pylonmc.pylon.core.PylonCore
import io.github.pylonmc.pylon.core.content.guide.PylonGuide
import io.github.pylonmc.pylon.core.guide.button.ResearchButton.Companion.addResearchCostLore
import io.github.pylonmc.pylon.core.guide.pages.item.ItemRecipesPage
import io.github.pylonmc.pylon.core.guide.pages.item.ItemUsagesPage
Expand All @@ -14,9 +13,7 @@ import io.github.pylonmc.pylon.core.item.research.Research.Companion.canCraft
import io.github.pylonmc.pylon.core.item.research.Research.Companion.canUse
import io.github.pylonmc.pylon.core.item.research.Research.Companion.researchPoints
import io.github.pylonmc.pylon.core.recipe.RecipeInput
import io.github.pylonmc.pylon.core.util.withArguments
import io.papermc.paper.datacomponent.DataComponentTypes
import io.papermc.paper.datacomponent.item.ItemLore
import kotlinx.coroutines.delay
import net.kyori.adventure.text.Component
import org.bukkit.Material
Expand Down Expand Up @@ -86,18 +83,7 @@ class ItemButton @JvmOverloads constructor(
return ItemStackBuilder.of(displayStack)
}

val placeholders = item.getPlaceholders()
val builder = ItemStackBuilder.of(displayStack.clone())
.editData(DataComponentTypes.LORE) { lore ->
ItemLore.lore(lore.lines().map { it.withArguments(placeholders) })
}

// buffoonery to bypass InvUI's translation mess
// Search message 'any idea why items displayed in InvUI are not having placeholders' on Pylon's Discord for more info
builder.editData(DataComponentTypes.ITEM_NAME) {
it.withArguments(placeholders)
}

if (item.isDisabled) {
builder.set(DataComponentTypes.ITEM_MODEL, Material.STRUCTURE_VOID.key)
}
Expand Down Expand Up @@ -178,8 +164,6 @@ class ItemButton @JvmOverloads constructor(
}

companion object {
const val CYCLE_INTERVAL = 10

@JvmStatic
fun from(stack: ItemStack?): Item {
if (stack == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class SearchItemsAndFluidsPage : SearchPage(
it.key !in PylonGuide.hiddenItems
}.map { item ->
val name = GlobalTranslator.render(Component.translatable("pylon.${item.key.namespace}.item.${item.key.key}.name"), player.locale())
ItemButton(item.itemStack) to name.plainText.lowercase()
ItemButton(item.getItemStack()) to name.plainText.lowercase()
}.toMutableList()

fun getFluidButtons(player: Player): MutableList<Pair<Item, String>> = PylonRegistry.FLUIDS.filter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ open class ItemIngredientsPage(val stack: ItemStack) : SimpleStaticGuidePage(
is Container.Item -> ItemButton.from(container.item) { item: ItemStack, player: Player ->
ItemStackBuilder.of(item).name(
GlobalTranslator.render(Component.translatable(
"pylon.pyloncore.message.guide.ingredients-page.item",
"pylon.pyloncore.guide.ingredient",
PylonArgument.of("item_ingredients_page_amount", container.item.amount),
PylonArgument.of("item_ingredients_page_item", container.item.getData(DataComponentTypes.ITEM_NAME)!!)),
player.locale())
Expand Down
Loading