diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt index 275921753..737636d81 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt @@ -9,11 +9,11 @@ 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.block.waila.Waila import io.github.pylonmc.pylon.core.command.ROOT_COMMAND import io.github.pylonmc.pylon.core.command.ROOT_COMMAND_PY_ALIAS import io.github.pylonmc.pylon.core.config.Config import io.github.pylonmc.pylon.core.config.ConfigSection +import io.github.pylonmc.pylon.core.config.PylonConfig import io.github.pylonmc.pylon.core.content.debug.DebugWaxedWeatheredCutCopperStairs import io.github.pylonmc.pylon.core.content.fluid.* import io.github.pylonmc.pylon.core.content.guide.PylonGuide @@ -21,6 +21,9 @@ import io.github.pylonmc.pylon.core.entity.EntityListener import io.github.pylonmc.pylon.core.entity.EntityStorage import io.github.pylonmc.pylon.core.entity.PylonEntity import io.github.pylonmc.pylon.core.fluid.connecting.ConnectingService +import io.github.pylonmc.pylon.core.guide.button.PageButton +import io.github.pylonmc.pylon.core.guide.button.setting.TogglePlayerSettingButton +import io.github.pylonmc.pylon.core.guide.pages.PlayerSettingsPage import io.github.pylonmc.pylon.core.i18n.PylonTranslator import io.github.pylonmc.pylon.core.item.PylonItem import io.github.pylonmc.pylon.core.item.PylonItemListener @@ -30,10 +33,12 @@ 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.util.mergeGlobalConfig 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.armor.ArmorTextureEngine.hasCustomArmorTextures import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureEngine -import io.github.pylonmc.pylon.core.util.mergeGlobalConfig +import io.github.pylonmc.pylon.core.util.pylonKey +import io.github.pylonmc.pylon.core.waila.Waila import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents import kotlinx.coroutines.delay @@ -72,8 +77,6 @@ object PylonCore : JavaPlugin(), PylonAddon { val packetEvents = PacketEvents.getAPI() packetEvents.init() - packetEvents.eventManager.registerListener(ArmorTextureEngine, PacketListenerPriority.HIGHEST) - val entityLibPlatform = SpigotEntityLibPlatform(this) val entityLibSettings = APIConfig(packetEvents).tickTickables() EntityLib.init(entityLibPlatform, entityLibSettings) @@ -99,7 +102,6 @@ object PylonCore : JavaPlugin(), PylonAddon { Bukkit.getPluginManager().registerEvents(MultiblockCache, this) Bukkit.getPluginManager().registerEvents(EntityStorage, this) Bukkit.getPluginManager().registerEvents(EntityListener, this) - Bukkit.getPluginManager().registerEvents(Waila, this) Bukkit.getPluginManager().registerEvents(Research, this) Bukkit.getPluginManager().registerEvents(PylonGuiBlock, this) Bukkit.getPluginManager().registerEvents(PylonEntityHolderBlock, this) @@ -111,11 +113,34 @@ object PylonCore : JavaPlugin(), PylonAddon { Bukkit.getPluginManager().registerEvents(PylonTickingBlock, this) Bukkit.getPluginManager().registerEvents(PylonGuide, this) - if (BlockTextureConfig.customBlockTexturesEnabled) { + if (PylonConfig.WailaConfig.enabled) { + PylonGuide.settingsPage.addSetting(PageButton(PlayerSettingsPage.wailaSettings)) + Bukkit.getPluginManager().registerEvents(Waila, this) + } + + PylonGuide.settingsPage.addSetting(PageButton(PlayerSettingsPage.resourcePackSettings)) + + if (PylonConfig.ArmorTextureConfig.enabled) { + if (!PylonConfig.ArmorTextureConfig.forced) { + PlayerSettingsPage.resourcePackSettings.addSetting(TogglePlayerSettingButton( + pylonKey("toggle-armor-textures"), + toggle = { player -> player.hasCustomArmorTextures = !player.hasCustomArmorTextures }, + isEnabled = { player -> player.hasCustomArmorTextures }, + )) + } + packetEvents.eventManager.registerListener(ArmorTextureEngine, PacketListenerPriority.HIGHEST) + } + + if (PylonConfig.BlockTextureConfig.enabled) { + PlayerSettingsPage.resourcePackSettings.addSetting(PageButton(PlayerSettingsPage.blockTextureSettings)) Bukkit.getPluginManager().registerEvents(BlockTextureEngine, this) BlockTextureEngine.updateOccludingCacheJob.start() } + if (PylonConfig.researchesEnabled) { + PylonGuide.settingsPage.addSetting(PlayerSettingsPage.researchEffects) + } + Bukkit.getScheduler().runTaskTimer( this, MultiblockCache.MultiblockChecker, diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PhantomBlock.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PhantomBlock.kt index 71f0030d3..3e6801db8 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PhantomBlock.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PhantomBlock.kt @@ -2,12 +2,12 @@ package io.github.pylonmc.pylon.core.block import io.github.pylonmc.pylon.core.block.context.BlockBreakContext import io.github.pylonmc.pylon.core.block.context.BlockCreateContext -import io.github.pylonmc.pylon.core.block.waila.WailaConfig import io.github.pylonmc.pylon.core.datatypes.PylonSerializers import io.github.pylonmc.pylon.core.i18n.PylonArgument import io.github.pylonmc.pylon.core.item.PylonItem import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder import io.github.pylonmc.pylon.core.util.pylonKey +import io.github.pylonmc.pylon.core.waila.WailaDisplay import net.kyori.adventure.bossbar.BossBar import org.bukkit.Material import org.bukkit.NamespacedKey @@ -50,8 +50,8 @@ class PhantomBlock( throw UnsupportedOperationException("Phantom block cannot be loaded") } - override fun getWaila(player: Player): WailaConfig? { - return WailaConfig( + override fun getWaila(player: Player): WailaDisplay? { + return WailaDisplay( text = defaultWailaTranslationKey.arguments(PylonArgument.of("block", erroredBlockKey.toString())), color = BossBar.Color.RED ) diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlock.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlock.kt index d9c449bb0..d89c089e8 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlock.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlock.kt @@ -10,18 +10,18 @@ import io.github.pylonmc.pylon.core.block.base.PylonEntityHolderBlock import io.github.pylonmc.pylon.core.block.base.PylonGuiBlock import io.github.pylonmc.pylon.core.block.context.BlockBreakContext import io.github.pylonmc.pylon.core.block.context.BlockCreateContext -import io.github.pylonmc.pylon.core.block.waila.WailaConfig import io.github.pylonmc.pylon.core.config.Config +import io.github.pylonmc.pylon.core.config.PylonConfig import io.github.pylonmc.pylon.core.config.Settings import io.github.pylonmc.pylon.core.content.debug.DebugWaxedWeatheredCutCopperStairs import io.github.pylonmc.pylon.core.datatypes.PylonSerializers import io.github.pylonmc.pylon.core.event.PylonBlockDeserializeEvent import io.github.pylonmc.pylon.core.event.PylonBlockSerializeEvent import io.github.pylonmc.pylon.core.registry.PylonRegistry -import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureConfig import io.github.pylonmc.pylon.core.util.position.BlockPosition import io.github.pylonmc.pylon.core.util.position.position import io.github.pylonmc.pylon.core.util.pylonKey +import io.github.pylonmc.pylon.core.waila.WailaDisplay import io.github.retrooper.packetevents.util.SpigotConversionUtil import io.papermc.paper.datacomponent.DataComponentTypes import me.tofaa.entitylib.meta.display.ItemDisplayMeta @@ -93,7 +93,7 @@ open class PylonBlock internal constructor(val block: Block) { * you can use [updateBlockTexture] to change the entity's item to reflect the lit/unlit state. */ val blockTextureEntity: WrapperEntity? by lazy { - if (!BlockTextureConfig.customBlockTexturesEnabled || disableBlockTextureEntity) { + if (!PylonConfig.BlockTextureConfig.enabled || disableBlockTextureEntity) { null } else { val entity = WrapperEntity(EntityTypes.ITEM_DISPLAY) @@ -212,8 +212,8 @@ open class PylonBlock internal constructor(val block: Block) { * * @return the WAILA configuration, or null if WAILA should not be shown for this block. */ - open fun getWaila(player: Player): WailaConfig? { - return WailaConfig(defaultWailaTranslationKey) + open fun getWaila(player: Player): WailaDisplay? { + return WailaDisplay(defaultWailaTranslationKey) } /** diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/waila/Waila.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/waila/Waila.kt deleted file mode 100644 index 0f2d3a3a1..000000000 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/waila/Waila.kt +++ /dev/null @@ -1,174 +0,0 @@ -package io.github.pylonmc.pylon.core.block.waila - -import com.github.shynixn.mccoroutine.bukkit.launch -import com.github.shynixn.mccoroutine.bukkit.ticks -import io.github.pylonmc.pylon.core.PylonCore -import io.github.pylonmc.pylon.core.block.BlockStorage -import io.github.pylonmc.pylon.core.config.PylonConfig -import io.github.pylonmc.pylon.core.entity.EntityStorage -import io.github.pylonmc.pylon.core.util.position.BlockPosition -import io.github.pylonmc.pylon.core.util.position.position -import io.github.pylonmc.pylon.core.util.pylonKey -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import net.kyori.adventure.bossbar.BossBar -import net.kyori.adventure.text.Component -import org.bukkit.attribute.Attribute -import org.bukkit.entity.Player -import org.bukkit.event.EventHandler -import org.bukkit.event.EventPriority -import org.bukkit.event.Listener -import org.bukkit.event.player.PlayerJoinEvent -import org.bukkit.event.player.PlayerQuitEvent -import org.bukkit.persistence.PersistentDataType -import java.util.UUID - -/** - * Handles WAILAs (the text that displays a block's name when looking - * at the block). - * - * You should not need to use this if you just want to change the WAILA of - * a [io.github.pylonmc.pylon.core.block.PylonBlock]. For that, see - * [io.github.pylonmc.pylon.core.block.PylonBlock.getWaila] - */ -class Waila private constructor(private val player: Player, private val job: Job) { - - private val bossbar = BossBar.bossBar( - Component.empty(), - 1F, - BossBar.Color.WHITE, - BossBar.Overlay.PROGRESS - ) - - private fun on() { - val bossbars = player.activeBossBars() - for (bossbar in bossbars) { - player.hideBossBar(bossbar) - } - player.showBossBar(this.bossbar) - for (bossbar in bossbars) { - player.showBossBar(bossbar) - } - } - - private fun off() { - player.hideBossBar(bossbar) - } - - private fun destroy() { - off() - job.cancel() - } - - private fun updateDisplay() { - val entityReach = player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE)?.value ?: 3 - val targetEntity = player.rayTraceEntities(entityReach.toInt())?.hitEntity - if (targetEntity != null) { - val config = try { - EntityStorage.get(targetEntity)?.getWaila(player) - } catch (e: Exception) { - e.printStackTrace() - off() - return - } - if (config != null) { - config.apply(bossbar) - on() - } else { - off() - } - } else { - val blockReach = player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE)?.value ?: 4.5 - val block = player.rayTraceBlocks(blockReach)?.hitBlock - if (block == null) { - off() - return - } - val config = try { - overrides[block.position]?.invoke(player) ?: block.let(BlockStorage::get)?.getWaila(player) - } catch (e: Exception) { - e.printStackTrace() - off() - return - } - if (config != null) { - config.apply(bossbar) - on() - } else { - off() - } - } - } - - companion object : Listener { - - private val wailaKey = pylonKey("waila") - private val wailas = mutableMapOf() - - private val overrides = mutableMapOf WailaConfig?>() - - /** - * Forcibly adds a WAILA display for the given player. - */ - @JvmStatic - fun addPlayer(player: Player) { - wailas[player.uniqueId] = Waila(player, PylonCore.launch { - delay(1.ticks) - val waila = wailas[player.uniqueId]!! - while (true) { - waila.updateDisplay() - delay(PylonConfig.wailaTickInterval.ticks) - } - }) - } - - /** - * Forcibly removes a WAILA display for the given player. - */ - @JvmStatic - fun removePlayer(player: Player) { - wailas.remove(player.uniqueId)?.destroy() - } - - @JvmStatic - var Player.wailaEnabled: Boolean - get() = this.persistentDataContainer.getOrDefault(wailaKey, PersistentDataType.BOOLEAN, true) - set(value) { - this.persistentDataContainer.set(wailaKey, PersistentDataType.BOOLEAN, value) - if (value) { - addPlayer(this) - } else { - removePlayer(this) - } - } - - /** - * Adds a WAILA override for the given position. This will always show the - * provided WAILA config when a WAILA-enabled player looks at the block at - * the given position, regardless of the block type or even if the block is - * not a Pylon block. - */ - @JvmStatic - fun addWailaOverride(position: BlockPosition, provider: (Player) -> WailaConfig?) { - overrides[position] = provider - } - - @JvmStatic - fun removeWailaOverride(position: BlockPosition) { - overrides.remove(position) - } - - @EventHandler(priority = EventPriority.MONITOR) - private fun onPlayerJoin(event: PlayerJoinEvent) { - val player = event.player - if (player.wailaEnabled) { - addPlayer(player) - } - } - - @EventHandler(priority = EventPriority.MONITOR) - private fun onPlayerQuit(event: PlayerQuitEvent) { - removePlayer(event.player) - } - } -} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/waila/WailaConfig.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/waila/WailaConfig.kt deleted file mode 100644 index 13974f089..000000000 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/waila/WailaConfig.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.pylonmc.pylon.core.block.waila - -import net.kyori.adventure.bossbar.BossBar -import net.kyori.adventure.text.Component - -/** - * The configuration for a WAILA bossbar (the bar shown at the top of your - * screen when looking at a block). - */ -@JvmRecord -data class WailaConfig @JvmOverloads constructor( - val text: Component, - val color: BossBar.Color = BossBar.Color.WHITE, - val style: BossBar.Overlay = BossBar.Overlay.PROGRESS, - val progress: Float = 1F -) { - - @JvmSynthetic - internal fun apply(bar: BossBar) { - bar.name(text) - bar.color(color) - bar.overlay(style) - bar.progress(progress) - } -} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/command/PylonCommand.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/command/PylonCommand.kt index 4a62832f5..25f34192c 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/command/PylonCommand.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/command/PylonCommand.kt @@ -12,7 +12,7 @@ import io.github.pylonmc.pylon.core.PylonCore import io.github.pylonmc.pylon.core.addon.PylonAddon import io.github.pylonmc.pylon.core.block.BlockStorage import io.github.pylonmc.pylon.core.block.PylonBlockSchema -import io.github.pylonmc.pylon.core.block.waila.Waila.Companion.wailaEnabled +import io.github.pylonmc.pylon.core.config.PylonConfig 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 @@ -33,6 +33,7 @@ import io.github.pylonmc.pylon.core.registry.PylonRegistry 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 +import io.github.pylonmc.pylon.core.waila.Waila.Companion.wailaConfig import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.argument.ArgumentTypes import io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver @@ -172,8 +173,9 @@ private val waila = buildCommand("waila") { permission("pylon.command.waila") executesWithPlayer { player -> PylonMetrics.onCommandRun("/py waila") - player.wailaEnabled = !player.wailaEnabled - player.sendFeedback(if (player.wailaEnabled) "waila.enabled" else "waila.disabled") + val config = player.wailaConfig + config.enabled = !config.enabled + player.sendFeedback(if (config.enabled) "waila.enabled" else "waila.disabled") } } @@ -516,7 +518,9 @@ internal val ROOT_COMMAND = buildCommand("pylon") { then(debug) then(key) then(setblock) - then(waila) + if (PylonConfig.WailaConfig.enabled) { + then(waila) + } then(gametest) then(research) then(exposeRecipeConfig) diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/config/PylonConfig.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/config/PylonConfig.kt index 88baf6011..2ab32bfb1 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/config/PylonConfig.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/config/PylonConfig.kt @@ -2,6 +2,8 @@ package io.github.pylonmc.pylon.core.config import io.github.pylonmc.pylon.core.PylonCore import io.github.pylonmc.pylon.core.config.adapter.ConfigAdapter +import io.github.pylonmc.pylon.core.waila.Waila +import net.kyori.adventure.bossbar.BossBar /** * The config options for Pylon Core. @@ -19,9 +21,6 @@ object PylonConfig { @JvmStatic val allowedBlockErrors = config.getOrThrow("allowed-block-errors", ConfigAdapter.INT) - @JvmStatic - val wailaTickInterval = config.getOrThrow("waila-tick-interval", ConfigAdapter.INT) - @JvmStatic val allowedEntityErrors = config.getOrThrow("allowed-entity-errors", ConfigAdapter.INT) @@ -64,4 +63,83 @@ object PylonConfig { @JvmStatic val disabledItems = config.getOrThrow("disabled-items", ConfigAdapter.SET.from(ConfigAdapter.NAMESPACED_KEY)) + object WailaConfig { + private val config = Config(PylonCore, "config.yml") + + @JvmStatic + val enabled + get() = tickInterval > 0 && enabledTypes.isNotEmpty() + + @JvmStatic + val tickInterval = config.getOrThrow("waila.tick-interval", ConfigAdapter.INT) + + @JvmStatic + val enabledTypes = config.getOrThrow("waila.enabled-types", ConfigAdapter.LIST.from(ConfigAdapter.ENUM.from(Waila.Type::class.java))) + + @JvmStatic + val defaultType = config.getOrThrow("waila.default-type", ConfigAdapter.ENUM.from(Waila.Type::class.java)).apply { + if (!enabledTypes.contains(this)) { + throw IllegalStateException("Default Waila type $this is not in the list of enabled types: $enabledTypes") + } + } + + @JvmStatic + val allowedBossBarColors = config.getOrThrow("waila.bossbar.allowed-colors", ConfigAdapter.SET.from(ConfigAdapter.ENUM.from(BossBar.Color::class.java))) + + @JvmStatic + val allowedBossBarOverlays = config.getOrThrow("waila.bossbar.allowed-overlays", ConfigAdapter.SET.from(ConfigAdapter.ENUM.from(BossBar.Overlay::class.java))) + + @JvmStatic + val defaultDisplay = config.getOrThrow("waila.default-display.bossbar", ConfigAdapter.WAILA_DISPLAY).apply { + if (!allowedBossBarColors.contains(color)) { + throw IllegalStateException("Default bossbar color $color is not in the list of allowed colors: $allowedBossBarColors") + } + if (!allowedBossBarOverlays.contains(overlay)) { + throw IllegalStateException("Default bossbar overlay $overlay is not in the list of allowed overlays: $allowedBossBarOverlays") + } + } + } + + object ArmorTextureConfig { + + private val config = Config(PylonCore, "config.yml") + + @JvmStatic + val enabled = config.getOrThrow("custom-armor-textures.enabled", ConfigAdapter.BOOLEAN) + + @JvmStatic + val forced = config.getOrThrow("custom-armor-textures.force", ConfigAdapter.BOOLEAN) + + } + + object BlockTextureConfig { + + private val config = Config(PylonCore, "config.yml") + + @JvmStatic + val enabled = config.getOrThrow("custom-block-textures.enabled", ConfigAdapter.BOOLEAN) + + @JvmStatic + val default = config.getOrThrow("custom-block-textures.default", ConfigAdapter.BOOLEAN) + + @JvmStatic + val forced = config.getOrThrow("custom-block-textures.force", ConfigAdapter.BOOLEAN) + + @JvmStatic + val occludingCacheRefreshInterval = config.getOrThrow("custom-block-textures.culling.occluding-cache-refresh-interval", ConfigAdapter.INT) + + @JvmStatic + val occludingCacheRefreshShare = config.getOrThrow("custom-block-textures.culling.occluding-cache-refresh-share", ConfigAdapter.DOUBLE) + + @JvmStatic + val cullingPresets = config.getOrThrow("custom-block-textures.culling.presets", ConfigAdapter.MAP.from(ConfigAdapter.STRING, ConfigAdapter.CULLING_PRESET)) + + @JvmStatic + val defaultCullingPreset = run { + val key = config.getOrThrow("custom-block-textures.culling.default-preset", ConfigAdapter.STRING) + cullingPresets[key] ?: error("No culling preset with id '$key' found") + } + + } + } \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/config/adapter/ConfigAdapter.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/config/adapter/ConfigAdapter.kt index b83349d19..84cf384a4 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/config/adapter/ConfigAdapter.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/config/adapter/ConfigAdapter.kt @@ -94,6 +94,7 @@ interface ConfigAdapter { @JvmField val ITEM_TAG = ItemTagConfigAdapter @JvmField val WEIGHTED_SET = WeightedSetConfigAdapter @JvmField val CULLING_PRESET = CullingPresetConfigAdapter + @JvmField val WAILA_DISPLAY = WailaDisplayConfigAdapter // @formatter:on } } diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/config/adapter/WailaDisplayConfigAdapter.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/config/adapter/WailaDisplayConfigAdapter.kt new file mode 100644 index 000000000..4f8edc58b --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/config/adapter/WailaDisplayConfigAdapter.kt @@ -0,0 +1,18 @@ +package io.github.pylonmc.pylon.core.config.adapter + +import io.github.pylonmc.pylon.core.waila.WailaDisplay +import net.kyori.adventure.bossbar.BossBar +import net.kyori.adventure.text.Component + +object WailaDisplayConfigAdapter : ConfigAdapter { + override val type = WailaDisplay::class.java + + override fun convert(value: Any): WailaDisplay { + val map = MapConfigAdapter.STRING_TO_ANY.convert(value) + val text = Component.translatable(ConfigAdapter.STRING.convert(map["text"] ?: throw IllegalArgumentException("WailaDisplay is missing 'text' field"))) + val color = ConfigAdapter.ENUM.from().convert(map["color"] ?: throw IllegalArgumentException("WailaDisplay is missing 'color' field")) + val overlay = ConfigAdapter.ENUM.from().convert(map["overlay"] ?: throw IllegalArgumentException("WailaDisplay is missing 'overlay' field")) + val progress = ConfigAdapter.FLOAT.convert(map["progress"] ?: throw IllegalArgumentException("WailaDisplay is missing 'progress' field")) + return WailaDisplay(text, color, overlay, progress) + } +} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/fluid/FluidPipeConnector.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/fluid/FluidPipeConnector.kt index 9357cf148..757480adf 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/fluid/FluidPipeConnector.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/fluid/FluidPipeConnector.kt @@ -6,12 +6,12 @@ import io.github.pylonmc.pylon.core.block.base.PylonEntityHolderBlock import io.github.pylonmc.pylon.core.block.context.BlockBreakContext import io.github.pylonmc.pylon.core.block.context.BlockBreakContext.PlayerBreak import io.github.pylonmc.pylon.core.block.context.BlockCreateContext -import io.github.pylonmc.pylon.core.block.waila.WailaConfig import io.github.pylonmc.pylon.core.entity.EntityStorage import io.github.pylonmc.pylon.core.fluid.FluidPointType import io.github.pylonmc.pylon.core.i18n.PylonArgument import io.github.pylonmc.pylon.core.item.PylonItem import io.github.pylonmc.pylon.core.util.pylonKey +import io.github.pylonmc.pylon.core.waila.WailaDisplay import org.bukkit.block.Block import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack @@ -50,8 +50,8 @@ class FluidPipeConnector : PylonBlock, PylonEntityHolderBlock, PylonBreakHandler } } - override fun getWaila(player: Player): WailaConfig? - = WailaConfig(defaultWailaTranslationKey.arguments(PylonArgument.of("pipe", this.pipe.stack.effectiveName()))) + override fun getWaila(player: Player): WailaDisplay? + = WailaDisplay(defaultWailaTranslationKey.arguments(PylonArgument.of("pipe", this.pipe.stack.effectiveName()))) val pipe: PylonItem get() { diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/fluid/FluidPipeMarker.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/fluid/FluidPipeMarker.kt index d27276c0f..edae0eb75 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/fluid/FluidPipeMarker.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/fluid/FluidPipeMarker.kt @@ -6,11 +6,11 @@ import io.github.pylonmc.pylon.core.block.context.BlockBreakContext import io.github.pylonmc.pylon.core.block.context.BlockBreakContext.PlayerBreak import io.github.pylonmc.pylon.core.block.context.BlockBreakContext.PluginBreak import io.github.pylonmc.pylon.core.block.context.BlockCreateContext -import io.github.pylonmc.pylon.core.block.waila.WailaConfig import io.github.pylonmc.pylon.core.datatypes.PylonSerializers import io.github.pylonmc.pylon.core.entity.EntityStorage import io.github.pylonmc.pylon.core.i18n.PylonArgument import io.github.pylonmc.pylon.core.util.pylonKey +import io.github.pylonmc.pylon.core.waila.WailaDisplay import org.bukkit.block.Block import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack @@ -69,8 +69,8 @@ class FluidPipeMarker : PylonBlock, PylonBreakHandler { } } - override fun getWaila(player: Player): WailaConfig? - = WailaConfig(defaultWailaTranslationKey.arguments(PylonArgument.of("pipe", getPipeDisplay()!!.pipe.stack.effectiveName()))) + override fun getWaila(player: Player): WailaDisplay? + = WailaDisplay(defaultWailaTranslationKey.arguments(PylonArgument.of("pipe", getPipeDisplay()!!.pipe.stack.effectiveName()))) override fun getDropItem(context: BlockBreakContext) = null diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/guide/PylonGuide.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/guide/PylonGuide.kt index 4dfda8e41..c69ccba7d 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/guide/PylonGuide.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/guide/PylonGuide.kt @@ -2,9 +2,9 @@ package io.github.pylonmc.pylon.core.content.guide import io.github.pylonmc.pylon.core.config.PylonConfig import io.github.pylonmc.pylon.core.guide.pages.InfoPage +import io.github.pylonmc.pylon.core.guide.pages.PlayerSettingsPage import io.github.pylonmc.pylon.core.guide.pages.RootPage import io.github.pylonmc.pylon.core.guide.pages.SearchItemsAndFluidsPage -import io.github.pylonmc.pylon.core.guide.pages.SettingsPage import io.github.pylonmc.pylon.core.guide.pages.base.GuidePage import io.github.pylonmc.pylon.core.guide.pages.fluid.FluidsPage import io.github.pylonmc.pylon.core.guide.pages.item.ItemIngredientsPage @@ -90,7 +90,7 @@ class PylonGuide(stack: ItemStack) : PylonItem(stack), PylonInteractor { val searchItemsAndFluidsPage = SearchItemsAndFluidsPage() @JvmStatic - val settingsPageAndInfoPage = SettingsPage() + val settingsPage = PlayerSettingsPage() /** * Lowest priority to avoid another plugin saving the players data or doing something diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/PlayerWailaConfigPersistentDataType.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/PlayerWailaConfigPersistentDataType.kt new file mode 100644 index 000000000..fafa287d8 --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/PlayerWailaConfigPersistentDataType.kt @@ -0,0 +1,38 @@ +package io.github.pylonmc.pylon.core.datatypes + +import io.github.pylonmc.pylon.core.util.pylonKey +import io.github.pylonmc.pylon.core.waila.PlayerWailaConfig +import org.bukkit.persistence.PersistentDataAdapterContext +import org.bukkit.persistence.PersistentDataContainer +import org.bukkit.persistence.PersistentDataType + +object PlayerWailaConfigPersistentDataType : PersistentDataType { + val enabledKey = pylonKey("waila_enabled") + val vanillaWailaEnabledKey = pylonKey("waila_vanilla_enabled") + val typeKey = pylonKey("waila_type") + + override fun getPrimitiveType(): Class = PersistentDataContainer::class.java + + override fun getComplexType(): Class = PlayerWailaConfig::class.java + + override fun fromPrimitive( + primitive: PersistentDataContainer, + context: PersistentDataAdapterContext + ): PlayerWailaConfig { + val enabled = primitive.get(enabledKey, PylonSerializers.BOOLEAN)!! + val vanillaWailaEnabled = primitive.get(vanillaWailaEnabledKey, PylonSerializers.BOOLEAN)!! + val type = primitive.get(typeKey, PylonSerializers.WAILA_TYPE)!! + return PlayerWailaConfig(enabled, vanillaWailaEnabled, type) + } + + override fun toPrimitive( + complex: PlayerWailaConfig, + context: PersistentDataAdapterContext + ): PersistentDataContainer { + val pdc = context.newPersistentDataContainer() + pdc.set(enabledKey, PylonSerializers.BOOLEAN, complex.enabled) + pdc.set(vanillaWailaEnabledKey, PylonSerializers.BOOLEAN, complex.vanillaWailaEnabled) + pdc.set(typeKey, PylonSerializers.WAILA_TYPE, complex.type) + return pdc + } +} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/PylonSerializers.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/PylonSerializers.kt index 89724f2e4..d05b284df 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/PylonSerializers.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/PylonSerializers.kt @@ -2,6 +2,7 @@ package io.github.pylonmc.pylon.core.datatypes import io.github.pylonmc.pylon.core.fluid.PylonFluid import io.github.pylonmc.pylon.core.registry.PylonRegistry +import io.github.pylonmc.pylon.core.waila.Waila import org.bukkit.Material import org.bukkit.Registry import org.bukkit.block.BlockFace @@ -116,4 +117,10 @@ object PylonSerializers { @JvmField val FLUID_CONNECTION_POINT = FluidConnectionPointDataType + + @JvmSynthetic + internal val WAILA_TYPE = EnumPersistentDataType(Waila.Type::class.java) + + @JvmSynthetic + internal val PLAYER_WAILA_CONFIG = PlayerWailaConfigPersistentDataType } diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/entity/PylonEntity.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/entity/PylonEntity.kt index 4264d6024..dc2d1c6bc 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/entity/PylonEntity.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/entity/PylonEntity.kt @@ -1,13 +1,13 @@ package io.github.pylonmc.pylon.core.entity import io.github.pylonmc.pylon.core.PylonCore -import io.github.pylonmc.pylon.core.block.waila.WailaConfig import io.github.pylonmc.pylon.core.config.Config import io.github.pylonmc.pylon.core.config.Settings import io.github.pylonmc.pylon.core.content.debug.DebugWaxedWeatheredCutCopperStairs import io.github.pylonmc.pylon.core.datatypes.PylonSerializers import io.github.pylonmc.pylon.core.registry.PylonRegistry import io.github.pylonmc.pylon.core.util.pylonKey +import io.github.pylonmc.pylon.core.waila.WailaDisplay import org.bukkit.NamespacedKey import org.bukkit.entity.Entity import org.bukkit.entity.Player @@ -42,7 +42,7 @@ abstract class PylonEntity(val entity: E) { * * @return the WAILA configuration, or null if WAILA should not be shown for this block. */ - open fun getWaila(player: Player): WailaConfig? = null + open fun getWaila(player: Player): WailaDisplay? = null /** * Called when debug info is requested for the entity by someone diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonBlockWailaEvent.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonBlockWailaEvent.kt new file mode 100644 index 000000000..ef7b7610e --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonBlockWailaEvent.kt @@ -0,0 +1,35 @@ +package io.github.pylonmc.pylon.core.event + +import io.github.pylonmc.pylon.core.waila.Waila +import io.github.pylonmc.pylon.core.waila.WailaDisplay +import org.bukkit.block.Block +import org.bukkit.entity.Player +import org.bukkit.event.Cancellable +import org.bukkit.event.HandlerList +import org.bukkit.event.player.PlayerEvent + +/** + * Called when the players [WAILA display][WailaDisplay] is being generated for a targeted [Block]. + * This is called if and only if the player has WAILA enabled and the block already has a generated display. + * + * @see Waila + */ +class PylonBlockWailaEvent( + player: Player, + val block: Block, + var display: WailaDisplay? +) : PlayerEvent(player), Cancellable { + private var cancelled = false + + override fun isCancelled(): Boolean = cancelled + override fun setCancelled(cancel: Boolean) { + cancelled = cancel + } + + override fun getHandlers(): HandlerList = handlerList + + companion object { + @JvmStatic + val handlerList: HandlerList = HandlerList() + } +} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonEntityWailaEvent.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonEntityWailaEvent.kt new file mode 100644 index 000000000..67c9e7684 --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/event/PylonEntityWailaEvent.kt @@ -0,0 +1,35 @@ +package io.github.pylonmc.pylon.core.event + +import io.github.pylonmc.pylon.core.waila.Waila +import io.github.pylonmc.pylon.core.waila.WailaDisplay +import org.bukkit.entity.Entity +import org.bukkit.entity.Player +import org.bukkit.event.Cancellable +import org.bukkit.event.HandlerList +import org.bukkit.event.player.PlayerEvent + +/** + * Called when the players [WAILA display][WailaDisplay] is being generated for a targeted [Entity]. + * This is called if and only if the player has WAILA enabled and the entity already has a generated display. + * + * @see Waila + */ +class PylonEntityWailaEvent( + player: Player, + val block: Entity, + var display: WailaDisplay? +) : PlayerEvent(player), Cancellable { + private var cancelled = false + + override fun isCancelled(): Boolean = cancelled + override fun setCancelled(cancel: Boolean) { + cancelled = cancel + } + + override fun getHandlers(): HandlerList = handlerList + + companion object { + @JvmStatic + val handlerList: HandlerList = HandlerList() + } +} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/CullingPresetButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/CullingPresetButton.kt deleted file mode 100644 index 9b3b7bff8..000000000 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/CullingPresetButton.kt +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.pylonmc.pylon.core.guide.button - -import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureEngine.cullingPreset -import io.github.pylonmc.pylon.core.i18n.PylonArgument -import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder -import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureConfig -import io.github.pylonmc.pylon.core.util.pylonKey -import net.kyori.adventure.text.Component -import org.bukkit.entity.Player -import org.bukkit.event.inventory.ClickType -import org.bukkit.event.inventory.InventoryClickEvent -import xyz.xenondevs.invui.item.ItemProvider -import xyz.xenondevs.invui.item.impl.AbstractItem - -class CullingPresetButton : AbstractItem() { - override fun getItemProvider(player: Player): ItemProvider? { - val preset = player.cullingPreset - return ItemStackBuilder.gui(preset.material, pylonKey("guide_culling_preset_${preset.id}")) - .name(Component.translatable("pylon.pyloncore.guide.button.culling-preset.${preset.id}.name")) - .lore( - Component.translatable("pylon.pyloncore.guide.button.culling-preset.${preset.id}.lore") - .arguments( - PylonArgument.of("hiddenInterval", preset.hiddenInterval), - PylonArgument.of("visibleInterval", preset.visibleInterval), - PylonArgument.of("alwaysShowRadius", preset.alwaysShowRadius), - PylonArgument.of("cullRadius", preset.cullRadius), - PylonArgument.of("maxOccludingCount", preset.maxOccludingCount) - ) - ) - } - - override fun handleClick(clickType: ClickType, player: Player, event: InventoryClickEvent) { - val presets = BlockTextureConfig.cullingPresets.values.toMutableList() - presets.sortBy { it.index } - val currentIndex = presets.indexOfFirst { it.id == player.cullingPreset.id } - val nextIndex = (currentIndex + 1) % presets.size - player.cullingPreset = presets[nextIndex] - notifyWindows() - } -} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleArmorTexturesButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleArmorTexturesButton.kt deleted file mode 100644 index 8c4b1e646..000000000 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleArmorTexturesButton.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.pylonmc.pylon.core.guide.button - -import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder -import io.github.pylonmc.pylon.core.resourcepack.armor.ArmorTextureEngine.hasCustomArmorTextures -import io.github.pylonmc.pylon.core.util.pylonKey -import net.kyori.adventure.text.Component -import org.bukkit.Material -import org.bukkit.entity.Player -import org.bukkit.event.inventory.ClickType -import org.bukkit.event.inventory.InventoryClickEvent -import xyz.xenondevs.invui.item.impl.AbstractItem - -class ToggleArmorTexturesButton : AbstractItem() { - override fun getItemProvider(player: Player) - = ItemStackBuilder.gui(if (player.hasCustomArmorTextures) Material.LIME_CONCRETE else Material.RED_CONCRETE, pylonKey("toggle_armor_textures")) - .name(Component.translatable("pylon.pyloncore.guide.button.toggle-armor-textures.name")) - .lore(Component.translatable("pylon.pyloncore.guide.button.toggle-armor-textures.lore")) - - override fun handleClick(clickType: ClickType, player: Player, event: InventoryClickEvent) { - player.hasCustomArmorTextures = !player.hasCustomArmorTextures - notifyWindows() - } -} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleBlockTexturesButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleBlockTexturesButton.kt deleted file mode 100644 index b6a1dd480..000000000 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleBlockTexturesButton.kt +++ /dev/null @@ -1,27 +0,0 @@ -package io.github.pylonmc.pylon.core.guide.button - -import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureEngine.hasCustomBlockTextures -import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder -import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureEngine -import io.github.pylonmc.pylon.core.util.pylonKey -import net.kyori.adventure.text.Component -import org.bukkit.Material -import org.bukkit.entity.Player -import org.bukkit.event.inventory.ClickType -import org.bukkit.event.inventory.InventoryClickEvent -import xyz.xenondevs.invui.item.impl.AbstractItem - -class ToggleBlockTexturesButton : AbstractItem() { - override fun getItemProvider(player: Player) - = ItemStackBuilder.gui(if (player.hasCustomBlockTextures) Material.LIME_CONCRETE else Material.RED_CONCRETE, pylonKey("toggle_block_textures")) - .name(Component.translatable("pylon.pyloncore.guide.button.toggle-block-textures.name")) - .lore(Component.translatable("pylon.pyloncore.guide.button.toggle-block-textures.lore")) - - override fun handleClick(clickType: ClickType, player: Player, event: InventoryClickEvent) { - player.hasCustomBlockTextures = !player.hasCustomBlockTextures - if (player.hasCustomBlockTextures) { - BlockTextureEngine.launchBlockTextureJob(player) - } - notifyWindows() - } -} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleWailaButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleWailaButton.kt deleted file mode 100644 index 8bbbe50ee..000000000 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleWailaButton.kt +++ /dev/null @@ -1,27 +0,0 @@ -package io.github.pylonmc.pylon.core.guide.button - -import io.github.pylonmc.pylon.core.block.waila.Waila.Companion.wailaEnabled -import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder -import io.github.pylonmc.pylon.core.util.pylonKey -import net.kyori.adventure.text.Component -import org.bukkit.Material -import org.bukkit.entity.Player -import org.bukkit.event.inventory.ClickType -import org.bukkit.event.inventory.InventoryClickEvent -import xyz.xenondevs.invui.item.impl.AbstractItem - -/** - * A settings button for toggling the WAILA overlay. - */ -class ToggleWailaButton : AbstractItem() { - - override fun getItemProvider(player: Player) - = ItemStackBuilder.gui(if (player.wailaEnabled) Material.LIME_CONCRETE else Material.RED_CONCRETE, pylonKey("toggle_waila")) - .name(Component.translatable("pylon.pyloncore.guide.button.toggle-waila.name")) - .lore(Component.translatable("pylon.pyloncore.guide.button.toggle-waila.lore")) - - override fun handleClick(clickType: ClickType, player: Player, event: InventoryClickEvent) { - player.wailaEnabled = !player.wailaEnabled - notifyWindows() - } -} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/setting/CyclePlayerSettingButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/setting/CyclePlayerSettingButton.kt new file mode 100644 index 000000000..052c10ee4 --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/setting/CyclePlayerSettingButton.kt @@ -0,0 +1,65 @@ +package io.github.pylonmc.pylon.core.guide.button.setting + +import io.github.pylonmc.pylon.core.i18n.PylonArgument +import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.ComponentLike +import net.kyori.adventure.text.TranslatableComponent +import org.bukkit.NamespacedKey +import org.bukkit.entity.Player +import org.bukkit.event.inventory.ClickType +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import xyz.xenondevs.invui.item.ItemProvider +import xyz.xenondevs.invui.item.impl.AbstractItem + +/** + * A button which cycles through a fixed set of values for a player setting. (Think enums for example.) + * + * [key] The namespaced key for this setting. Used for translation keys and persistent data. + * + * [sortedValues] The possible setting values to cycle through, in order. + * + * [identifier] A function which returns a string identifier for each setting value. Used for translation keys. + * - The identifier must be unique for each value in [sortedValues] and must be constant for each value. + * - For example, if the setting is an enum, you could use `Enum::name`. + * - The name and lore of the button will be based on this identifier with the following format: + * - `pylon..guide.button...(name|lore)` + * + * [getter] Gets the current setting value for a player. + * + * [setter] Sets the current setting value for a player. + * + * [decorator] Determines what the base ItemStack of the button looks like for a player their current setting value. + * - This is useful for adding visual indicators of the current setting value, such as changing the material or custom model data. + * - The name and lore of the button will be applied on top of this ItemStack. + * + * [placeholderProvider] Provides additional placeholders for the translation. (See [TranslatableComponent.arguments] and [PylonArgument]) + */ +data class CyclePlayerSettingButton ( + val key: NamespacedKey, + val sortedValues: List, + val identifier: (S) -> String, + + val getter: (Player) -> S, + val setter: (Player, S) -> Unit, + + val decorator: (Player, S) -> ItemStack, + val placeholderProvider: (Player, S) -> MutableList = { _, _ -> mutableListOf() } +) : AbstractItem() { + override fun getItemProvider(player: Player): ItemProvider? { + val setting = getter(player) + val identifier = identifier(setting) + val placeholders = placeholderProvider(player, setting) + return ItemStackBuilder.of(decorator(player, setting)) + .name(Component.translatable("pylon.${key.namespace}.guide.button.${key.key}.${identifier}.name").arguments(placeholders)) + .lore(Component.translatable("pylon.${key.namespace}.guide.button.${key.key}.${identifier}.lore").arguments(placeholders)) + } + + override fun handleClick(clickType: ClickType, player: Player, event: InventoryClickEvent) { + val currentIndex = sortedValues.indexOfFirst { identifier(it) == identifier(getter(player)) } + val nextIndex = (currentIndex + (if (clickType.isLeftClick) 1 else -1)) % sortedValues.size + setter(player, sortedValues[nextIndex]) + notifyWindows() + } +} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/setting/NumericPlayerSettingButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/setting/NumericPlayerSettingButton.kt new file mode 100644 index 000000000..996323420 --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/setting/NumericPlayerSettingButton.kt @@ -0,0 +1,74 @@ +package io.github.pylonmc.pylon.core.guide.button.setting + +import io.github.pylonmc.pylon.core.i18n.PylonArgument +import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.ComponentLike +import org.bukkit.NamespacedKey +import org.bukkit.entity.Player +import org.bukkit.event.inventory.ClickType +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import xyz.xenondevs.invui.item.ItemProvider +import xyz.xenondevs.invui.item.impl.AbstractItem + +/** + * A button for changing a numeric setting for a player. (e.g. volume, brightness, etc.) + * + * [key] The namespaced key for this setting. Used for translation keys and persistent data. + * + * [min] The minimum value for the setting. + * + * [max] The maximum value for the setting. + * + * [step] The amount to increase or decrease the setting by on a normal click. + * + * [shiftStep] The amount to increase or decrease the setting by on a shift click. + * + * [type] Converts a [Number] to the specific setting type [N]. (e.g. [Number.toInt], [Number.toDouble], etc.) + * + * [getter] Gets the current setting value for a player. + * + * [setter] Sets the current setting value for a player. + * + * [decorator] Determines what the base ItemStack of the button looks like for a player their current setting value. + * - This is useful for adding visual indicators of the current setting value, such as changing the material or custom model data. + * - The name and lore of the button will be applied on top of this ItemStack. + * + * [placeholderProvider] Provides additional placeholders for the translation. (See [TranslatableComponent.arguments] and [PylonArgument]) + * - By default there is a placeholder "value" which contains the current setting value. + */ +data class NumericPlayerSettingButton( + val key: NamespacedKey, + + val min: N, + val max: N, + val step: N, + val shiftStep: N, + val type: (Number) -> N, + + val getter: (Player) -> N, + val setter: (Player, N) -> Unit, + + val decorator: (Player, N) -> ItemStack, + val placeholderProvider: (Player, N) -> MutableList = { _, setting -> mutableListOf( + PylonArgument.of("value", Component.text(setting.toString())) + ) } +) : AbstractItem() { + override fun getItemProvider(player: Player): ItemProvider? { + val setting = getter(player) + val placeholders = placeholderProvider(player, setting) + return ItemStackBuilder.of(decorator(player, setting)) + .name(Component.translatable("pylon.${key.namespace}.guide.button.${key.key}.name").arguments(placeholders)) + .lore(Component.translatable("pylon.${key.namespace}.guide.button.${key.key}.lore").arguments(placeholders)) + } + + override fun handleClick(clickType: ClickType, player: Player, event: InventoryClickEvent) { + var value = getter(player).toDouble() + val step = if (clickType.isShiftClick) shiftStep.toDouble() else step.toDouble() + value += if (clickType.isLeftClick) step else -step + value = value.coerceIn(min.toDouble(), max.toDouble()) + setter(player, type(value)) + notifyWindows() + } +} diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/setting/TogglePlayerSettingButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/setting/TogglePlayerSettingButton.kt new file mode 100644 index 000000000..a70b7c3d9 --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/setting/TogglePlayerSettingButton.kt @@ -0,0 +1,55 @@ +package io.github.pylonmc.pylon.core.guide.button.setting + +import io.github.pylonmc.pylon.core.i18n.PylonArgument +import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.ComponentLike +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.entity.Player +import org.bukkit.event.inventory.ClickType +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import xyz.xenondevs.invui.item.ItemProvider +import xyz.xenondevs.invui.item.impl.AbstractItem + +/** + * A button which toggles a boolean setting for a player. + * + * [key] The namespaced key for this setting. Used for translation keys and persistent data. + * + * [isEnabled] Gets the current setting value for a player. + * + * [toggle] Toggles the current setting value for a player. + * + * [decorator] Determines what the base ItemStack of the button looks like for a player their current setting value. + * - This is useful for adding visual indicators of the current setting value, such as changing the material or custom model data. + * - The name and lore of the button will be applied on top of this ItemStack. + * - By default the button is lime concrete when enabled and red concrete when disabled with custom model data of "{key}_enabled" and "{key}_disabled" respectively. + * + * [placeholderProvider] Provides additional placeholders for the translation. (See [TranslatableComponent.arguments] and [PylonArgument]) + */ +data class TogglePlayerSettingButton( + val key: NamespacedKey, + + val isEnabled: (Player) -> Boolean, + val toggle: (Player) -> Unit, + + val decorator: (Player, Boolean) -> ItemStack = { _, toggled -> if (toggled) ItemStack(Material.LIME_CONCRETE) else ItemStack(Material.RED_CONCRETE) }, + val placeholderProvider: (Player, Boolean) -> MutableList = { _, _ -> mutableListOf() } +) : AbstractItem() { + override fun getItemProvider(player: Player) : ItemProvider? { + val toggled = isEnabled(player) + val identifier = if (toggled) "enabled" else "disabled" + val placeholders = placeholderProvider(player, toggled) + return ItemStackBuilder.of(decorator(player, toggled)) + .name(Component.translatable("pylon.${key.namespace}.guide.button.${key.key}.${identifier}.name").arguments(placeholders)) + .lore(Component.translatable("pylon.${key.namespace}.guide.button.${key.key}.${identifier}.lore").arguments(placeholders)) + .addCustomModelDataString("${key}_${identifier}") + } + + override fun handleClick(clickType: ClickType, player: Player, event: InventoryClickEvent) { + toggle(player) + notifyWindows() + } +} diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/PlayerSettingsPage.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/PlayerSettingsPage.kt new file mode 100644 index 000000000..7019569ee --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/PlayerSettingsPage.kt @@ -0,0 +1,133 @@ +package io.github.pylonmc.pylon.core.guide.pages + +import io.github.pylonmc.pylon.core.config.PylonConfig +import io.github.pylonmc.pylon.core.content.guide.PylonGuide +import io.github.pylonmc.pylon.core.guide.button.BackButton +import io.github.pylonmc.pylon.core.guide.button.PageButton +import io.github.pylonmc.pylon.core.guide.button.setting.CyclePlayerSettingButton +import io.github.pylonmc.pylon.core.guide.button.setting.TogglePlayerSettingButton +import io.github.pylonmc.pylon.core.guide.pages.base.SimpleStaticGuidePage +import io.github.pylonmc.pylon.core.i18n.PylonArgument +import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder +import io.github.pylonmc.pylon.core.item.research.Research.Companion.researchEffects +import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureEngine.cullingPreset +import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureEngine.hasCustomBlockTextures +import io.github.pylonmc.pylon.core.util.gui.GuiItems +import io.github.pylonmc.pylon.core.util.pylonKey +import io.github.pylonmc.pylon.core.waila.Waila.Companion.wailaConfig +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.entity.Player +import xyz.xenondevs.invui.gui.Gui +import xyz.xenondevs.invui.gui.PagedGui +import xyz.xenondevs.invui.gui.structure.Markers +import xyz.xenondevs.invui.item.Item + +/** + * Contains buttons to change player settings. + */ +class PlayerSettingsPage( + key: NamespacedKey = pylonKey("settings"), + material: Material = Material.COMPARATOR, + buttons: MutableList = mutableListOf(), +) : SimpleStaticGuidePage(key, material, buttons) { + override fun getGui(player: Player): Gui { + val buttons = buttonSupplier.get() + val gui = PagedGui.items() + .setStructure( + "# b # # # # # s #", + "# # # # # # # # #", + "# x x x x x x x #", + "# # # # # # # # #", + ) + .addIngredient('#', GuiItems.background()) + .addIngredient('b', BackButton()) + .addIngredient('s', PageButton(PylonGuide.searchItemsAndFluidsPage)) + .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) + .addPageChangeHandler { _, newPage -> saveCurrentPage(player, newPage) } + .setContent(buttons.filter { it !is PageButton || it.page !is PlayerSettingsPage || it.page.buttons.isNotEmpty() }) + return gui.build().apply { loadCurrentPage(player, this) } + } + + fun addSetting(item: Item) { + buttons.add(item) + buttons.sortBy { if (it is PageButton) 0 else 1 } + } + + companion object { + @JvmStatic + val wailaSettings = PlayerSettingsPage( + pylonKey("waila_settings"), + Material.SPYGLASS + ).apply { + addSetting(TogglePlayerSettingButton( + pylonKey("toggle-waila"), + toggle = { player -> player.wailaConfig.enabled = !player.wailaConfig.enabled }, + isEnabled = { player -> player.wailaConfig.enabled } + )) + addSetting(TogglePlayerSettingButton( + pylonKey("toggle-vanilla-waila"), + toggle = { player -> player.wailaConfig.vanillaWailaEnabled = !player.wailaConfig.vanillaWailaEnabled }, + isEnabled = { player -> player.wailaConfig.vanillaWailaEnabled } + )) + if (PylonConfig.WailaConfig.enabledTypes.size > 1) { + addSetting(CyclePlayerSettingButton( + pylonKey("cycle-waila-type"), + PylonConfig.WailaConfig.enabledTypes, + identifier = { type -> type.name.lowercase() }, + getter = { player -> player.wailaConfig.type }, + setter = { player, type -> player.wailaConfig.type = type }, + decorator = { player, type -> ItemStackBuilder.of(Material.PAPER) + .addCustomModelDataString("waila_type=${type.name.lowercase()}") + .build() + } + )) + } + } + + @JvmStatic + val resourcePackSettings = PlayerSettingsPage( + pylonKey("resource_pack_settings"), + Material.PAINTING + ) + + @JvmStatic + val blockTextureSettings = PlayerSettingsPage( + pylonKey("block_texture_settings"), + Material.BOOKSHELF + ).apply { + if (!PylonConfig.BlockTextureConfig.forced) { + addSetting(TogglePlayerSettingButton( + pylonKey("toggle-block-textures"), + toggle = { player -> player.hasCustomBlockTextures = !player.hasCustomBlockTextures }, + isEnabled = { player -> player.hasCustomBlockTextures } + )) + } + addSetting(CyclePlayerSettingButton( + pylonKey("cycle-culling-preset"), + PylonConfig.BlockTextureConfig.cullingPresets.values.sortedBy { it.index }, + identifier = { preset -> preset.id }, + getter = { player -> player.cullingPreset }, + setter = { player, preset -> player.cullingPreset = preset }, + decorator = { player, preset -> ItemStackBuilder.of(preset.material) + .addCustomModelDataString("culling_preset=${preset.id}") + .build() + }, + placeholderProvider = { player, preset -> mutableListOf( + PylonArgument.of("hiddenInterval", preset.hiddenInterval), + PylonArgument.of("visibleInterval", preset.visibleInterval), + PylonArgument.of("alwaysShowRadius", preset.alwaysShowRadius), + PylonArgument.of("cullRadius", preset.cullRadius), + PylonArgument.of("maxOccludingCount", preset.maxOccludingCount) + )} + )) + } + + @JvmStatic + val researchEffects = TogglePlayerSettingButton( + pylonKey("toggle-research-effects"), + toggle = { player -> player.researchEffects = !player.researchEffects }, + isEnabled = { player -> player.researchEffects } + ) + } +} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/RootPage.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/RootPage.kt index 05d3f4687..08007fffb 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/RootPage.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/RootPage.kt @@ -38,7 +38,7 @@ class RootPage() : SimpleStaticGuidePage( "x x x x x x x x x", ) .addIngredient('#', GuiItems.background()) - .addIngredient('e', PageButton(PylonGuide.settingsPageAndInfoPage)) + .addIngredient('e', PageButton(PylonGuide.settingsPage)) .addIngredient('s', PageButton(PylonGuide.searchItemsAndFluidsPage)) .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) .addPageChangeHandler { _, newPage -> saveCurrentPage(player, newPage) } diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/SettingsPage.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/SettingsPage.kt deleted file mode 100644 index 62bab2904..000000000 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/SettingsPage.kt +++ /dev/null @@ -1,49 +0,0 @@ -package io.github.pylonmc.pylon.core.guide.pages - -import io.github.pylonmc.pylon.core.content.guide.PylonGuide -import io.github.pylonmc.pylon.core.guide.button.BackButton -import io.github.pylonmc.pylon.core.guide.button.CullingPresetButton -import io.github.pylonmc.pylon.core.guide.button.PageButton -import io.github.pylonmc.pylon.core.guide.button.ToggleArmorTexturesButton -import io.github.pylonmc.pylon.core.guide.button.ToggleBlockTexturesButton -import io.github.pylonmc.pylon.core.guide.button.ToggleWailaButton -import io.github.pylonmc.pylon.core.guide.pages.base.SimpleStaticGuidePage -import io.github.pylonmc.pylon.core.util.gui.GuiItems -import io.github.pylonmc.pylon.core.util.pylonKey -import org.bukkit.Material -import org.bukkit.entity.Player -import xyz.xenondevs.invui.gui.Gui -import xyz.xenondevs.invui.gui.PagedGui - -/** - * Contains buttons to change settings. - */ -class SettingsPage : SimpleStaticGuidePage( - pylonKey("settings_and_info"), - Material.COMPARATOR -) { - override fun getGui(player: Player): Gui { - val buttons = buttonSupplier.get() - val gui = PagedGui.items() - .setStructure( - "# b # # # # # s #", - "# # # # # # # # #", - "# w t c a . . . #", - "# # # # # # # # #", - ) - .addIngredient('#', GuiItems.background()) - .addIngredient('b', BackButton()) - .addIngredient('s', PageButton(PylonGuide.searchItemsAndFluidsPage)) - .addIngredient('w', ToggleWailaButton()) - .addIngredient('t', ToggleBlockTexturesButton()) - .addIngredient('c', CullingPresetButton()) - .addIngredient('a', ToggleArmorTexturesButton()) - .addPageChangeHandler { _, newPage -> saveCurrentPage(player, newPage) } - - for (button in buttons) { - gui.addContent(button) - } - - return gui.build().apply { loadCurrentPage(player, this) } - } -} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/item/research/Research.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/item/research/Research.kt index 33581b634..db869ddc4 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/item/research/Research.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/item/research/Research.kt @@ -95,7 +95,7 @@ data class Research( ) } - if (effects) { + if (effects && player.researchEffects) { val multiplier = (cost?.toDouble() ?: 0.0) * PylonConfig.researchMultiplierConfettiAmount val amount = (PylonConfig.researchBaseConfettiAmount * multiplier).toInt() val spawnedConfetti = min(amount, PylonConfig.researchMaxConfettiAmount) @@ -140,12 +140,16 @@ data class Research( companion object : Listener { private val researchesKey = pylonKey("researches") private val researchPointsKey = pylonKey("research_points") + private val researchEffectsKey = pylonKey("research_effects") private val researchesType = PylonSerializers.SET.setTypeFrom(PylonSerializers.KEYED.keyedTypeFrom(PylonRegistry.RESEARCHES::getOrThrow)) @JvmStatic var Player.researchPoints: Long by persistentData(researchPointsKey, PylonSerializers.LONG, 0) + @JvmStatic + var Player.researchEffects: Boolean by persistentData(researchEffectsKey, PylonSerializers.BOOLEAN, true) + @JvmStatic fun getResearches(player: OfflinePlayer): Set { val researches = player.persistentDataContainer.get(researchesKey, researchesType) diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/armor/ArmorTextureEngine.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/armor/ArmorTextureEngine.kt index f8e938033..d46cf1da1 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/armor/ArmorTextureEngine.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/armor/ArmorTextureEngine.kt @@ -12,6 +12,7 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEn import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetPlayerInventory import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetSlot import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowItems +import io.github.pylonmc.pylon.core.config.PylonConfig import io.github.pylonmc.pylon.core.item.PylonItem import io.github.pylonmc.pylon.core.item.base.PylonArmor import io.github.pylonmc.pylon.core.util.pylonKey @@ -27,8 +28,8 @@ object ArmorTextureEngine : PacketListener { @JvmStatic var Player.hasCustomArmorTextures: Boolean - get() = this.persistentDataContainer.getOrDefault(customArmorTexturesKey, PersistentDataType.BOOLEAN, false) - set(value) = this.persistentDataContainer.set(customArmorTexturesKey, PersistentDataType.BOOLEAN, value) + get() = PylonConfig.ArmorTextureConfig.forced || this.persistentDataContainer.getOrDefault(customArmorTexturesKey, PersistentDataType.BOOLEAN, false) + set(value) = this.persistentDataContainer.set(customArmorTexturesKey, PersistentDataType.BOOLEAN, PylonConfig.ArmorTextureConfig.forced || value) override fun onPacketSend(event: PacketSendEvent?) { if (event == null) return diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/block/BlockTextureConfig.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/block/BlockTextureConfig.kt deleted file mode 100644 index c97113f8e..000000000 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/block/BlockTextureConfig.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.pylonmc.pylon.core.resourcepack.block - -import io.github.pylonmc.pylon.core.PylonCore -import io.github.pylonmc.pylon.core.config.Config -import io.github.pylonmc.pylon.core.config.adapter.ConfigAdapter - -object BlockTextureConfig { - - private val config = Config(PylonCore, "config.yml") - - @JvmStatic - val customBlockTexturesEnabled = config.getOrThrow("custom-block-textures.enabled", ConfigAdapter.BOOLEAN) - - @JvmStatic - val occludingCacheRefreshInterval = config.getOrThrow("custom-block-textures.culling.occluding-cache-refresh-interval", ConfigAdapter.INT) - - @JvmStatic - val occludingCacheRefreshShare = config.getOrThrow("custom-block-textures.culling.occluding-cache-refresh-share", ConfigAdapter.DOUBLE) - - @JvmStatic - val cullingPresets = config.getOrThrow("custom-block-textures.culling.presets", ConfigAdapter.MAP.from(ConfigAdapter.STRING, ConfigAdapter.CULLING_PRESET)) - - @JvmStatic - val defaultCullingPreset = run { - val key = config.getOrThrow("custom-block-textures.culling.default-preset", ConfigAdapter.STRING) - cullingPresets[key] ?: error("No culling preset with id '$key' found") - } - -} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/block/BlockTextureEngine.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/block/BlockTextureEngine.kt index 47b412f29..41de29089 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/block/BlockTextureEngine.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/resourcepack/block/BlockTextureEngine.kt @@ -7,12 +7,12 @@ import com.github.shynixn.mccoroutine.bukkit.launch import com.github.shynixn.mccoroutine.bukkit.ticks import io.github.pylonmc.pylon.core.PylonCore import io.github.pylonmc.pylon.core.block.PylonBlock +import io.github.pylonmc.pylon.core.config.PylonConfig import io.github.pylonmc.pylon.core.event.PylonBlockBreakEvent import io.github.pylonmc.pylon.core.util.Octree import io.github.pylonmc.pylon.core.util.position.BlockPosition import io.github.pylonmc.pylon.core.util.position.ChunkPosition import io.github.pylonmc.pylon.core.util.pylonKey -import io.papermc.paper.command.brigadier.argument.ArgumentTypes.uuid import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -49,7 +49,7 @@ object BlockTextureEngine : Listener { /** * Periodically updates a share of the occluding cache, to ensure it stays up to date with changes in the world. - * Every [BlockTextureConfig.occludingCacheRefreshInterval] ticks, it will refresh [BlockTextureConfig.occludingCacheRefreshShare] + * Every [PylonConfig.BlockTextureConfig.occludingCacheRefreshInterval] ticks, it will refresh [PylonConfig.BlockTextureConfig.occludingCacheRefreshShare] * percent of the cache, starting with the oldest entries. * * Normally, blocks occluding state is cached the first time its requested, and is only updated when placed or broken. @@ -58,16 +58,16 @@ object BlockTextureEngine : Listener { @JvmSynthetic internal val updateOccludingCacheJob = PylonCore.launch(start = CoroutineStart.LAZY) { while (true) { - delay(BlockTextureConfig.occludingCacheRefreshInterval.ticks) + delay(PylonConfig.BlockTextureConfig.occludingCacheRefreshInterval.ticks) val now = System.currentTimeMillis() var refreshed = 0 - var toRefresh = ceil(occludingCache.size * BlockTextureConfig.occludingCacheRefreshShare) + var toRefresh = ceil(occludingCache.size * PylonConfig.BlockTextureConfig.occludingCacheRefreshShare) var entries = mutableListOf>() entries.addAll(occludingCache.entries) entries.sortBy { it.value.timestamp } for ((pos, data) in entries) { - if (now - data.timestamp <= BlockTextureConfig.occludingCacheRefreshInterval) continue + if (now - data.timestamp <= PylonConfig.BlockTextureConfig.occludingCacheRefreshInterval) continue val world = pos.world ?: continue if (world.isChunkLoaded(pos.x, pos.z)) { @@ -87,25 +87,25 @@ object BlockTextureEngine : Listener { @JvmStatic var Player.hasCustomBlockTextures: Boolean - get() = this.persistentDataContainer.getOrDefault(customBlockTexturesKey, PersistentDataType.BOOLEAN, true) - set(value) = this.persistentDataContainer.set(customBlockTexturesKey, PersistentDataType.BOOLEAN, value) + get() = (this.persistentDataContainer.getOrDefault(customBlockTexturesKey, PersistentDataType.BOOLEAN, PylonConfig.BlockTextureConfig.default) || PylonConfig.BlockTextureConfig.forced) + set(value) = this.persistentDataContainer.set(customBlockTexturesKey, PersistentDataType.BOOLEAN, value || PylonConfig.BlockTextureConfig.forced) @JvmStatic var Player.cullingPreset: CullingPreset - get() = BlockTextureConfig.cullingPresets.getOrElse(this.persistentDataContainer.getOrDefault(presetKey, PersistentDataType.STRING, BlockTextureConfig.defaultCullingPreset.id)) { - BlockTextureConfig.defaultCullingPreset + get() = PylonConfig.BlockTextureConfig.cullingPresets.getOrElse(this.persistentDataContainer.getOrDefault(presetKey, PersistentDataType.STRING, PylonConfig.BlockTextureConfig.defaultCullingPreset.id)) { + PylonConfig.BlockTextureConfig.defaultCullingPreset } set(value) = this.persistentDataContainer.set(presetKey, PersistentDataType.STRING, value.id) @JvmSynthetic internal fun insert(block: PylonBlock) { - if (!BlockTextureConfig.customBlockTexturesEnabled || block.disableBlockTextureEntity) return + if (!PylonConfig.BlockTextureConfig.enabled || block.disableBlockTextureEntity) return getOctree(block.block.world).insert(block) } @JvmSynthetic internal fun remove(block: PylonBlock) { - if (!BlockTextureConfig.customBlockTexturesEnabled || block.disableBlockTextureEntity) return + if (!PylonConfig.BlockTextureConfig.enabled || block.disableBlockTextureEntity) return getOctree(block.block.world).remove(block) block.blockTextureEntity?.let { for (viewer in it.viewers.toSet()) { @@ -116,7 +116,7 @@ object BlockTextureEngine : Listener { @JvmSynthetic internal fun getOctree(world: World): Octree { - check(BlockTextureConfig.customBlockTexturesEnabled) { "Tried to access BlockTextureEngine octree while custom block textures are disabled" } + check(PylonConfig.BlockTextureConfig.enabled) { "Tried to access BlockTextureEngine octree while custom block textures are disabled" } val border = world.worldBorder return octrees.getOrPut(world.uid) { @@ -134,7 +134,7 @@ object BlockTextureEngine : Listener { @JvmSynthetic internal fun launchBlockTextureJob(player: Player) { val uuid = player.uniqueId - if (!BlockTextureConfig.customBlockTexturesEnabled || !player.hasCustomBlockTextures || jobs.containsKey(uuid)) return + if (!PylonConfig.BlockTextureConfig.enabled || !player.hasCustomBlockTextures || jobs.containsKey(uuid)) return jobs[uuid] = PylonCore.launch(PylonCore.asyncDispatcher) { val visible = mutableSetOf() diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/waila/PlayerWailaConfig.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/waila/PlayerWailaConfig.kt new file mode 100644 index 000000000..6446f1fa8 --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/waila/PlayerWailaConfig.kt @@ -0,0 +1,35 @@ +package io.github.pylonmc.pylon.core.waila + +import io.github.pylonmc.pylon.core.config.PylonConfig +import io.github.pylonmc.pylon.core.waila.Waila.Companion.wailaConfig +import org.bukkit.entity.Player + +class PlayerWailaConfig { + var player: Player? = null + + var enabled: Boolean = true + set(value) { + field = value + player?.wailaConfig = this + } + + var vanillaWailaEnabled: Boolean = false + set(value) { + field = value + player?.wailaConfig = this + } + + var type: Waila.Type = PylonConfig.WailaConfig.defaultType + set(value) { + field = value + player?.wailaConfig = this + } + + constructor() + + constructor(enabled: Boolean, vanillaWailaEnabled: Boolean, type: Waila.Type) { + this.enabled = enabled + this.vanillaWailaEnabled = vanillaWailaEnabled + this.type = type + } +} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/waila/Waila.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/waila/Waila.kt new file mode 100644 index 000000000..f601233aa --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/waila/Waila.kt @@ -0,0 +1,297 @@ +package io.github.pylonmc.pylon.core.waila + +import com.github.shynixn.mccoroutine.bukkit.launch +import com.github.shynixn.mccoroutine.bukkit.ticks +import io.github.pylonmc.pylon.core.PylonCore +import io.github.pylonmc.pylon.core.block.BlockStorage +import io.github.pylonmc.pylon.core.block.PylonBlock +import io.github.pylonmc.pylon.core.config.PylonConfig +import io.github.pylonmc.pylon.core.datatypes.PylonSerializers +import io.github.pylonmc.pylon.core.entity.EntityStorage +import io.github.pylonmc.pylon.core.entity.PylonEntity +import io.github.pylonmc.pylon.core.event.PylonBlockWailaEvent +import io.github.pylonmc.pylon.core.event.PylonEntityWailaEvent +import io.github.pylonmc.pylon.core.i18n.PylonArgument +import io.github.pylonmc.pylon.core.util.position.BlockPosition +import io.github.pylonmc.pylon.core.util.position.position +import io.github.pylonmc.pylon.core.util.pylonKey +import io.papermc.paper.raytracing.RayTraceTarget +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import net.kyori.adventure.bossbar.BossBar +import net.kyori.adventure.text.Component +import org.bukkit.attribute.Attribute +import org.bukkit.entity.Entity +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerQuitEvent +import java.util.UUID +import kotlin.math.max + +/** + * Handles WAILAs (the text that displays a block's name when looking + * at the block). + * + * If you want to change the WAILA display for your [PylonBlock] or [PylonEntity], see + * [PylonBlock.getWaila] and [PylonEntity.getWaila], if you need to change the WAILA + * display for a different block/entity, see [addWailaOverride]. + */ +class Waila private constructor(private val player: Player, playerConfig: PlayerWailaConfig, private val job: Job) { + + private var config = playerConfig + set(value) { + if (field.type != value.type) { + hide() + } + field = value + } + + private val bossBar = BossBar.bossBar( + Component.empty(), + PylonConfig.WailaConfig.defaultDisplay.progress, + PylonConfig.WailaConfig.defaultDisplay.color, + PylonConfig.WailaConfig.defaultDisplay.overlay + ) + + private fun send(display: WailaDisplay) { + when (config.type) { + Type.BOSSBAR -> { + player.hideBossBar(bossBar) + val color = if (display.color in PylonConfig.WailaConfig.allowedBossBarColors) { + display.color + } else { + PylonConfig.WailaConfig.defaultDisplay.color + } + val overlay = if (display.overlay in PylonConfig.WailaConfig.allowedBossBarOverlays) { + display.overlay + } else { + PylonConfig.WailaConfig.defaultDisplay.overlay + } + + bossBar.name(display.text) + bossBar.color(color) + bossBar.overlay(overlay) + bossBar.progress(display.progress) + player.showBossBar(bossBar) + } + Type.ACTIONBAR -> player.sendActionBar(display.text) + } + } + + private fun hide() { + when (config.type) { + Type.BOSSBAR -> { + if (player.activeBossBars().contains(bossBar)) { + player.hideBossBar(bossBar) + } + } + Type.ACTIONBAR -> player.sendActionBar(Component.empty()) + } + } + + private fun destroy() { + hide() + job.cancel() + } + + private fun updateDisplay() { + val entityReach = player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE)?.value ?: 3.0 + val blockReach = player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE)?.value ?: 4.5 + + val rayTraceResult = player.world.rayTrace { builder -> + builder.start(player.eyeLocation) + builder.direction(player.eyeLocation.direction) + builder.maxDistance(max(entityReach, blockReach)) + builder.entityFilter { entity -> entity != player && entity.location.distanceSquared(player.location) <= entityReach * entityReach } + builder.blockFilter { block -> block.location.distanceSquared(player.location) <= blockReach * blockReach } + builder.targets(RayTraceTarget.ENTITY, RayTraceTarget.BLOCK) + } + + if (rayTraceResult == null) { + hide() + return + } + + rayTraceResult.hitEntity?.let { entity -> + try { + var display = entityOverrides[entity.uniqueId]?.invoke(player) + ?: entity.let(EntityStorage::get)?.getWaila(player) + + if (display == null && player.wailaConfig.vanillaWailaEnabled) { + display = WailaDisplay(Component.translatable(entity.type.translationKey())) + } + + if (display != null) { + val event = PylonEntityWailaEvent(player, entity, display) + event.callEvent() + if (!event.isCancelled && event.display != null) { + send(event.display!!) + } else { + hide() + } + } else { + hide() + } + } catch(e: Exception) { + e.printStackTrace() + hide() + } + } + + rayTraceResult.hitBlock?.let { block -> + try { + var display = blockOverrides[block.position]?.invoke(player) + ?: block.let(BlockStorage::get)?.getWaila(player) + + if (display == null && player.wailaConfig.vanillaWailaEnabled) { + display = WailaDisplay(Component.translatable(block.type.translationKey())) + } + + if (display != null) { + val event = PylonBlockWailaEvent(player, block, display) + event.callEvent() + if (!event.isCancelled && event.display != null) { + send(event.display!!) + } else { + hide() + } + } else { + hide() + } + } catch(e: Exception) { + e.printStackTrace() + hide() + } + } + } + + enum class Type { + BOSSBAR, + ACTIONBAR + } + + companion object : Listener { + + private val wailaKey = pylonKey("waila") + private val wailas = mutableMapOf() + + private val blockOverrides = mutableMapOf WailaDisplay?>() + private val entityOverrides = mutableMapOf WailaDisplay?>() + + /** + * Forcibly adds a WAILA display for the given player. + */ + @JvmStatic + fun addPlayer(player: Player, config: PlayerWailaConfig = player.wailaConfig) { + if (wailas.containsKey(player.uniqueId) || !config.enabled) { + return + } + + wailas[player.uniqueId] = Waila(player, config, PylonCore.launch { + delay(1.ticks) + val waila = wailas[player.uniqueId]!! + while (true) { + waila.updateDisplay() + delay(PylonConfig.WailaConfig.tickInterval.ticks) + } + }) + } + + /** + * Forcibly removes a WAILA display for the given player. + */ + @JvmStatic + fun removePlayer(player: Player) { + wailas.remove(player.uniqueId)?.destroy() + } + + @JvmStatic + var Player.wailaConfig: PlayerWailaConfig + get() = this.persistentDataContainer.getOrDefault(wailaKey, PylonSerializers.PLAYER_WAILA_CONFIG, PlayerWailaConfig()).apply { + player = this@wailaConfig + if (!PylonConfig.WailaConfig.enabledTypes.contains(type)) { + sendMessage(Component.translatable("pylon.pyloncore.message.waila.type-disabled").arguments( + PylonArgument.of("type", type.name.lowercase()) + )) + type = PylonConfig.WailaConfig.defaultType + } + } + set(value) { + this.persistentDataContainer.set(wailaKey, PylonSerializers.PLAYER_WAILA_CONFIG, value) + if (value.enabled) { + if (!wailas.containsKey(uniqueId)) { + addPlayer(this, value) + } else { + wailas[this.uniqueId]?.config = value + } + } else { + removePlayer(this) + } + } + + /** + * Adds a WAILA override for the given position. This will always show the + * provided WAILA config when a WAILA-enabled player looks at the block at + * the given position, regardless of the block type or even if the block is + * not a Pylon block. + * + * If an override is added for a position that already has an override, the + * old override will be replaced. + */ + @JvmStatic + fun addWailaOverride(position: BlockPosition, provider: (Player) -> WailaDisplay?) { + blockOverrides[position] = provider + } + + /** + * Adds a WAILA override for the given entity. This will always show the + * provided WAILA config when a WAILA-enabled player looks at the entity + * regardless of any other factors. + * + * If an override is added for an entity that already has an override, the + * old override will be replaced. + */ + @JvmStatic + fun addWailaOverride(entity: Entity, provider: (Player) -> WailaDisplay?) { + entityOverrides[entity.uniqueId] = provider + } + + /** + * Removes any existing WAILA override for the given position. + */ + @JvmStatic + fun removeWailaOverride(position: BlockPosition) { + blockOverrides.remove(position) + } + + /** + * Removes any existing WAILA override for the given entity. + */ + @JvmStatic + fun removeWailaOverride(entity: Entity) { + entityOverrides.remove(entity.uniqueId) + } + + @EventHandler(priority = EventPriority.MONITOR) + private fun onPlayerJoin(event: PlayerJoinEvent) { + val player = event.player + + // TODO: Remove this migration code in a future version + if (player.persistentDataContainer.has(wailaKey, PylonSerializers.BOOLEAN)) { + player.persistentDataContainer.remove(wailaKey) + } + + if (player.wailaConfig.enabled) { + addPlayer(player) + } + } + + @EventHandler(priority = EventPriority.MONITOR) + private fun onPlayerQuit(event: PlayerQuitEvent) { + removePlayer(event.player) + } + } +} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/waila/WailaDisplay.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/waila/WailaDisplay.kt new file mode 100644 index 000000000..66c33ba89 --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/waila/WailaDisplay.kt @@ -0,0 +1,17 @@ +package io.github.pylonmc.pylon.core.waila + +import io.github.pylonmc.pylon.core.config.PylonConfig +import net.kyori.adventure.bossbar.BossBar +import net.kyori.adventure.text.Component + +/** + * The configuration for a WAILA bossbar (the bar shown at the top of your + * screen when looking at a block). + */ +@JvmRecord +data class WailaDisplay @JvmOverloads constructor( + val text: Component, + val color: BossBar.Color = PylonConfig.WailaConfig.defaultDisplay.color, + val overlay: BossBar.Overlay = PylonConfig.WailaConfig.defaultDisplay.overlay, + val progress: Float = PylonConfig.WailaConfig.defaultDisplay.progress +) \ No newline at end of file diff --git a/pylon-core/src/main/resources/config.yml b/pylon-core/src/main/resources/config.yml index c77b9db8a..bce246b28 100644 --- a/pylon-core/src/main/resources/config.yml +++ b/pylon-core/src/main/resources/config.yml @@ -11,9 +11,6 @@ allowed-block-errors: 5 # The number of errors a entity can throw before it is disabled allowed-entity-errors: 5 -# The interval (in Minecraft ticks), between WAILA checks -waila-tick-interval: 1 - # The interval (in Minecraft ticks) between fluid network ticks fluid-tick-interval: 5 @@ -21,6 +18,38 @@ block-data-autosave-interval-seconds: 60 entity-data-autosave-interval-seconds: 60 +waila: + # The interval (in Minecraft ticks), between WAILA checks + # Set to 0 to disable WAILA globally + tick-interval: 1 + + # The WAILA display types players can choose between + enabled-types: + - actionbar + - bossbar + + # The default display type for players, if you allow multiple types, they can change between them in the guide settings + # Note: If you default to actionbar, or enforce it, and have a long tick-interval, the WAILA may not be visible all the time + # as actionbar messages fade after a few seconds + default-type: bossbar + + # The default bossbar display options if the block doesn't specify one + # There are no defaults for actionbar, as it has no options + default-display: + bossbar: + text: "" + color: white + overlay: progress + progress: 1.0 + + # Here you can control what bossbar colors & overlays can be used within the WAILA + # Some servers modify the textures for both in resource packs, so they may want to + # prevent them being used by the WAILA. + # If any colors/overlays not in these lists are used, they will default to the values in default-display + bossbar: + allowed-colors: [ pink, blue, red, green, yellow, purple, white ] + allowed-overlays: [ progress, notched_6, notched_10, notched_12, notched_20 ] + research: enabled: true # The interval (in Minecraft ticks) between when Pylon checks for unresearched items in inventories @@ -90,12 +119,29 @@ metrics-save-interval-ticks: 6000 # those items can't be crafted. disabled-items: [] -# Should Pylon send item displays to the client for custom block textures? -# If a player has no resource pack that adds textures installed, the entities will appear invisible. -# Players can also disable this on a per-player basis, and can configure with culling preset they want. +custom-armor-textures: + # Should Pylon support modifying the equipment component of armor items to use custom armor textures? + # If a player has no resource pack that adds textures installed, and has armor textures enabled the armor will appear invisible. + # By default, armor textures are disabled for players, but they can enable it themselves in the guide settings. + enabled: true + + # If true, players will not be able to disable custom armor textures and it will default to enabled + force: false + custom-block-textures: + # Should Pylon send item displays to the client for custom block textures? + # If a player has no resource pack that adds textures installed, the entities will appear invisible. + # Players can also disable this on a per-player basis, and can configure with culling preset they want. enabled: true + # If true, players will not be able to disable custom block textures themselves + force: false + + # If not forced, should custom block textures be enabled by default for players? + # Remember that if a player has no resource pack that adds textures installed, even with this enabled + # the entities will appear invisible (no visual change). + default: true + # Custom Block Textures uses ray tracing to cull entities not visible to the player. # There are 4 default presets to choose from, each with different client performance and visual quality tradeoffs. # It is not recommended to change these settings (excluding default-preset) unless you know what you're doing diff --git a/pylon-core/src/main/resources/lang/en.yml b/pylon-core/src/main/resources/lang/en.yml index dfe9983fe..a12dbb594 100644 --- a/pylon-core/src/main/resources/lang/en.yml +++ b/pylon-core/src/main/resources/lang/en.yml @@ -91,6 +91,13 @@ message: not-configurable: "This recipe type is not configurable" warning: "Warning: exposing recipe configs will keep them from updating automatically. Delete %file% to revert" + guide: + ingredients-page: + item: "%item_ingredients_page_amount%x %item_ingredients_page_item%" + + waila: + type-disabled: "This server no longer permits the your previously set %type% WAILA display type, it has been reset to default" + gui: scroll: up: "Scroll up" @@ -141,8 +148,26 @@ guide: Shift right click to see other research unlocks error: "Item failed to load" toggle-waila: - name: "Toggle WAILA" - lore: " WAILA is the bossbar at the top of your screen that shows when you look at a Pylon block" + enabled: + name: "WAILA: Enabled (Click to toggle)" + lore: " WAILA is a display that shows information about the pylon block/entity you're looking at" + disabled: + name: "WAILA: Disabled (Click to toggle)" + lore: " WAILA is a display that shows information about the pylon block/entity you're looking at" + toggle-vanilla-waila: + enabled: + name: "Vanilla WAILA: Enabled (Click to toggle)" + lore: " Should the WAILA display vanilla block/entity information as well?" + disabled: + name: "Vanilla WAILA: Disabled (Click to toggle)" + lore: " Should the WAILA display vanilla block/entity information as well?" + cycle-waila-type: + bossbar: + name: "WAILA Display Type: Bossbar (Click to cycle)" + lore: " The WAILA is displayed as a bossbar at the top of your screen" + actionbar: + name: "WAILA Display Type: Actionbar (Click to cycle)" + lore: " The WAILA is displayed on the actionbar above your hotbar" intermediates: name: "↑ Inputs ↑" lore: |- @@ -153,9 +178,13 @@ guide: ↓ Main product ↓ ingredient: "%item_ingredients_page_amount%x %item_ingredients_page_item%" toggle-block-textures: - name: "Toggle Custom Block Textures" - lore: " If you have a resource-pack that adds Pylon block textures, enable this to use them, if you don't you can safely disable this, however be aware that this can have negative effects on your fps with large numbers of Pylon blocks" - culling-preset: + enabled: + name: "Custom Block Textures: Enabled (Click to toggle)" + lore: " If you have a resource-pack that adds Pylon block textures, enable this to use them, if you don't you can safely disable this, however be aware that this can have negative effects on your fps with large numbers of Pylon blocks" + disabled: + name: "Custom Block Textures: Disabled (Click to toggle)" + lore: " If you have a resource-pack that adds Pylon block textures, enable this to use them, if you don't you can safely disable this, however be aware that this can have negative effects on your fps with large numbers of Pylon blocks" + cycle-culling-preset: disabled: name: "Culling Preset: No Culling (Click to cycle)" lore: " All Pylon Block entities within view distance will be sent to the client, this can have a large negative effect on fps depending on how many there are, and how good your device is" @@ -169,8 +198,19 @@ guide: name: "Culling Preset: High (Click to cycle)" lore: " Aggressive culling always showing entities within %alwaysShowRadius% blocks and heavily culling entities within %cullRadius% blocks (only culling if there are %maxOccludingCount% occluding blocks between your eyes and the block)" toggle-armor-textures: - name: "Toggle Custom Armor Textures" - lore: " If you have a resource pack that adds worn (not item) Pylon armor textures, enable this to use them, otherwise leave it disabled" + enabled: + name: "Custom Armor Textures: Enabled (Click to toggle)" + lore: " If you have a resource pack that adds worn (not item) Pylon armor textures, enable this to use them, otherwise leave it disabled" + disabled: + name: "Custom Armor Textures: Disabled (Click to toggle)" + lore: " If you have a resource pack that adds worn (not item) Pylon armor textures, enable this to use them, otherwise leave it disabled" + toggle-research-effects: + enabled: + name: "Research Effects: Enabled (Click to toggle)" + lore: " Should sounds play and confetti appear when you research something new?" + disabled: + name: "Research Effects: Disabled (Click to toggle)" + lore: " Should sounds play and confetti appear when you research something new?" recipe: cooking: "Cooking time: %time%" @@ -181,7 +221,10 @@ guide: research-items: "Research items" root: "Pylon guide" search: "Search" - settings_and_info: "Settings & info" + settings: "Settings" + waila_settings: "WAILA settings" + resource_pack_settings: "Resource pack settings" + block_texture_settings: "Block texture settings" item_recipes: "Item recipes" fluid_recipes: "Fluid recipes" item_usages: "Item usages"