diff --git a/.github/workflows/audit-pr-build.yml b/.github/workflows/audit-pr-build.yml index 32e6eeb9..a0175ca6 100644 --- a/.github/workflows/audit-pr-build.yml +++ b/.github/workflows/audit-pr-build.yml @@ -12,11 +12,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v2 - name: validate gradle wrapper uses: gradle/wrapper-validation-action@v1 - name: setup jdk ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v1 with: java-version: ${{ matrix.java }} - name: make gradle wrapper executable @@ -29,4 +29,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: Artifacts - path: build/libs/ + path: build/libs/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 00590ceb..9339ee4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ -## Fixes 🌽 -- Resolved a mixin target for Forge users (on Sinytra) that caused items to break immediately. - - Thank you **`AndyNoob` https://github.com/AndyNoob** for this contribution. \ No newline at end of file +**This slates the first official release of the new PlayerEX. It had been stable for a while, although the UI was not fully completed.** + +## Additions 🍎 +- Finished the PlayerEX UI to the point where it is ready for release. + - There is now a component set on the right of the UI that has a list of current pages. Use it in order to navigate between multiple mods who use the registry. diff --git a/build.gradle b/build.gradle index 57cf8252..dc2273ec 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - id 'fabric-loom' version "1.7-SNAPSHOT" + id 'fabric-loom' version "1.9-SNAPSHOT" id 'maven-publish' - id "org.jetbrains.kotlin.jvm" version "2.0.0" - id 'org.jetbrains.kotlin.plugin.serialization' version '2.0.0' - id("com.google.devtools.ksp") version "2.0.0-1.0.21" + id "org.jetbrains.kotlin.jvm" version "2.1.0" + id 'org.jetbrains.kotlin.plugin.serialization' version '2.1.0' + id "com.google.devtools.ksp" version "2.1.0-1.0.29" } version = project.mod_version diff --git a/gradle.properties b/gradle.properties index 3194e9f4..ff66ead5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx4G +org.gradle.jvmargs=-Xmx8G org.gradle.parallel=true ksp_version=2.0.0-1.0.21 @@ -9,8 +9,8 @@ loader=fabric # Fabric Properties minecraft_version=1.20.1 loader_version=0.16.7 -fabric_kotlin_version=1.12.3+kotlin.2.0.21 -loom_version=1.8-SNAPSHOT +fabric_kotlin_version=1.13.0+kotlin.2.1.0 +loom_version=1.9-SNAPSHOT # Mappings parchment_version=1.20.1:2023.09.03 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9355b415..94113f20 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/client/java/com/bibireden/playerex/mixin/LocalPlayerMixin.java b/src/client/java/com/bibireden/playerex/mixin/LocalPlayerMixin.java index 1d52d384..2e5deb94 100644 --- a/src/client/java/com/bibireden/playerex/mixin/LocalPlayerMixin.java +++ b/src/client/java/com/bibireden/playerex/mixin/LocalPlayerMixin.java @@ -1,8 +1,18 @@ package com.bibireden.playerex.mixin; +import com.bibireden.data_attributes.api.DataAttributesAPI; import com.bibireden.playerex.ui.PlayerEXScreen; +import com.mojang.authlib.GameProfile; +import de.dafuqs.additionalentityattributes.AdditionalEntityAttributes; import net.minecraft.client.Minecraft; import net.minecraft.client.player.LocalPlayer; +import net.minecraft.core.BlockPos; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -11,11 +21,33 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(LocalPlayer.class) -abstract class LocalPlayerMixin { +abstract class LocalPlayerMixin extends LivingEntity { @Shadow @Final protected Minecraft minecraft; + protected LocalPlayerMixin(EntityType entityType, Level level) { + super(entityType, level); + } + @Inject(method = "setExperienceValues", at = @At("TAIL")) private void playerex$setExperienceValues(CallbackInfo ci) { if (minecraft.screen instanceof PlayerEXScreen screen) screen.onExperienceUpdated(); } + + @Override + public void setHealth(float health) { + super.setHealth(health); + if (minecraft == null) return; + if (minecraft.screen instanceof PlayerEXScreen screen) { + screen.onAttributeUpdated(Attributes.MAX_HEALTH, getMaxHealth()); + } + } + + @Override + public void setAirSupply(int air) { + super.setAirSupply(air); + if (minecraft == null) return; + if (minecraft.screen instanceof PlayerEXScreen screen) { + screen.onAttributeUpdated(AdditionalEntityAttributes.LUNG_CAPACITY, DataAttributesAPI.getValue(AdditionalEntityAttributes.LUNG_CAPACITY, this).orElse(0.0)); + } + } } diff --git a/src/client/java/com/bibireden/playerex/registry/PlayerEXMenuRegistry.java b/src/client/java/com/bibireden/playerex/registry/PlayerEXMenuRegistry.java index dbc08e21..716317f6 100644 --- a/src/client/java/com/bibireden/playerex/registry/PlayerEXMenuRegistry.java +++ b/src/client/java/com/bibireden/playerex/registry/PlayerEXMenuRegistry.java @@ -56,7 +56,7 @@ public static List getIds() { } @NotNull - public static List> getDefs() { + public static List> getComponents() { return ENTRIES.stream().map(Pair::component2).collect(Collectors.toUnmodifiableList()); } diff --git a/src/client/kotlin/com/bibireden/playerex/PlayerEXClient.kt b/src/client/kotlin/com/bibireden/playerex/PlayerEXClient.kt index 0e5066cc..c1cf9456 100644 --- a/src/client/kotlin/com/bibireden/playerex/PlayerEXClient.kt +++ b/src/client/kotlin/com/bibireden/playerex/PlayerEXClient.kt @@ -23,6 +23,7 @@ import org.lwjgl.glfw.GLFW object PlayerEXClient : ClientModInitializer { val MAIN_UI_SCREEN_ID = PlayerEX.id("main_ui_model") + val MAIN_UI_SCREEN_ID_TEMP = PlayerEX.id("main_ui_model_v2") val KEYBINDING_MAIN_SCREEN: KeyMapping = KeyBindingHelper.registerKeyBinding(KeyMapping("${PlayerEX.MOD_ID}.key.main_screen", InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_MINUS, "key.categories.${PlayerEX.MOD_ID}")) diff --git a/src/client/kotlin/com/bibireden/playerex/ui/PlayerEXScreen.kt b/src/client/kotlin/com/bibireden/playerex/ui/PlayerEXScreen.kt index e8b17622..6347c51b 100644 --- a/src/client/kotlin/com/bibireden/playerex/ui/PlayerEXScreen.kt +++ b/src/client/kotlin/com/bibireden/playerex/ui/PlayerEXScreen.kt @@ -1,5 +1,6 @@ package com.bibireden.playerex.ui +import com.bibireden.data_attributes.ui.renderers.ButtonRenderers import com.bibireden.playerex.PlayerEXClient import com.bibireden.playerex.components.player.IPlayerDataComponent import com.bibireden.playerex.ext.component @@ -14,18 +15,19 @@ import com.bibireden.playerex.ui.components.buttons.AttributeButtonComponent import com.bibireden.playerex.ui.helper.InputHelper import com.bibireden.playerex.ui.util.Colors import com.bibireden.playerex.util.PlayerEXUtil +import io.wispforest.endec.impl.StructEndecBuilder import io.wispforest.owo.ui.base.BaseUIModelScreen import io.wispforest.owo.ui.component.* +import io.wispforest.owo.ui.container.Containers import io.wispforest.owo.ui.container.FlowLayout +import io.wispforest.owo.ui.core.* import io.wispforest.owo.ui.core.Component as OwoComponent -import io.wispforest.owo.ui.core.Easing -import io.wispforest.owo.ui.core.OwoUIDrawContext -import io.wispforest.owo.ui.core.ParentComponent -import io.wispforest.owo.ui.core.Sizing import io.wispforest.owo.util.EventSource +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.Style +import net.minecraft.resources.ResourceLocation import net.minecraft.util.Mth import net.minecraft.world.entity.ai.attributes.Attribute -import net.minecraft.network.chat.Component import kotlin.reflect.KClass // Transformers @@ -33,9 +35,11 @@ fun ParentComponent.childById(clazz: KClass, id: String) = /** Primary screen for the mod that brings everything intended together. */ class PlayerEXScreen : BaseUIModelScreen(FlowLayout::class.java, DataSource.asset(PlayerEXClient.MAIN_UI_SCREEN_ID)) { + private var listCollapsed = false + private var currentPage = 0 - private val pages: MutableList = mutableListOf() + private val pages: MutableList> = mutableListOf() private val player by lazy { this.minecraft!!.player!! } @@ -46,6 +50,7 @@ class PlayerEXScreen : BaseUIModelScreen(FlowLayout::class.java, Dat private val levelAmount by lazy { uiAdapter.rootComponent.childById(TextBoxComponent::class, "level:amount")!! } private val levelButton by lazy { uiAdapter.rootComponent.childById(ButtonComponent::class, "level:button")!! } + private val onLevelUpdatedEvents = OnLevelUpdated.stream private val onLevelUpdated: EventSource = onLevelUpdatedEvents.source() @@ -78,7 +83,7 @@ class PlayerEXScreen : BaseUIModelScreen(FlowLayout::class.java, Dat private fun updatePointsAvailable() { this.uiAdapter.rootComponent.childById(LabelComponent::class, "points_available")?.apply { - text(Component.translatable("playerex.ui.main.skill_points_header").append(": [").append( + text(Component.translatable("playerex.ui.main.skill_points_header").append(": ").append( Component.literal("${player.component.skillPoints}").withStyle { it.withColor( when (player.component.skillPoints) { @@ -86,19 +91,19 @@ class PlayerEXScreen : BaseUIModelScreen(FlowLayout::class.java, Dat else -> Colors.SATURATED_BLUE } ) - }).append("]") + }) ) } } - private fun onPagesUpdated() { + /** changes the page based on the index. */ + private fun switchPage(to: Int) { val root = this.uiAdapter.rootComponent - val pageCounter = root.childById(LabelComponent::class, "counter")!! + val content = root.childById(FlowLayout::class, "content")!! - pageCounter.text(Component.nullToEmpty("${currentPage + 1}/${pages.size}")) content.clearChildren() - content.child(pages[currentPage]) + content.child(pages[to].second) } private fun updateLevelUpButton() { @@ -116,64 +121,88 @@ class PlayerEXScreen : BaseUIModelScreen(FlowLayout::class.java, Dat val required = PlayerEXUtil.getRequiredXpForNextLevel(player) result = Mth.clamp((player.experienceLevel.toDouble() / required) * 100, 0.0, 100.0) } - footer.childById(BoxComponent::class, "progress")!! - .horizontalSizing().animate(250, Easing.CUBIC, Sizing.fill(result.toInt())).forwards() + footer.childById(BoxComponent::class, "progress")?.horizontalSizing()?.animate(250, Easing.CUBIC, Sizing.fill(result.toInt()))?.forwards() } - override fun build(rootComponent: FlowLayout) { - val player = minecraft?.player ?: return - - val levelUpButton = rootComponent.childById(ButtonComponent::class, "level:button")!! + private fun updateCollapseState(content: FlowLayout, listLayout: FlowLayout, button: ButtonComponent) { + button.message = Component.literal(if (listCollapsed) "<" else ">").withStyle(Style.EMPTY.withBold(true)) - updateLevelUpButton() - - levelAmount.setFilter(InputHelper::isUIntInput) - levelAmount.onChanged().subscribe { updateLevelUpButton() } + content.horizontalSizing().animate(500, Easing.SINE, if (listCollapsed) Sizing.fill(97) else Sizing.fill(85)).forwards() + button.horizontalSizing().animate(500, Easing.SINE, if (listCollapsed) Sizing.fixed(10) else Sizing.fill(10)).forwards() + listLayout.horizontalSizing().animate(500, Easing.SINE, Sizing.fill(if (listCollapsed) 3 else 15)).forwards() + } - val previousPage = rootComponent.childById(ButtonComponent::class, "previous")!! - val pageCounter = rootComponent.childById(LabelComponent::class, "counter")!! - val nextPage = rootComponent.childById(ButtonComponent::class, "next")!! - val exit = rootComponent.childById(ButtonComponent::class, "exit")!! + override fun build(rootComponent: FlowLayout) { + val listLayout = rootComponent.childById(FlowLayout::class, "list")!! + val contentLayout = rootComponent.childById(FlowLayout::class, "content")!! - PlayerEXMenuRegistry.get().forEach { (_, clazz) -> + PlayerEXMenuRegistry.get().forEach { (resource, clazz) -> val instance = clazz.getDeclaredConstructor().newInstance() instance.init(minecraft!!, this, player.component) - instance.build(content) - pages.add(instance) + instance.build(contentLayout) + pages.add(Pair(resource, instance)) } - this.onLevelUpdated(player.level.toInt()) - this.onPagesUpdated() + listLayout.child( + Components.button(Component.empty()) { + listCollapsed = !listCollapsed + updateCollapseState(contentLayout, listLayout, it) + } + .apply { + renderer(ButtonRenderers.STANDARD) + textShadow(false) + verticalSizing(Sizing.fill(100)) + + updateCollapseState(contentLayout, listLayout, this) + } + ) + + listLayout.child( + Containers.verticalScroll(Sizing.fill(90), Sizing.fill(100), Containers.verticalFlow(Sizing.content(), Sizing.content()) + .apply { + gap(2) + padding(Insets.of(4)) + verticalAlignment(VerticalAlignment.TOP) + horizontalAlignment(HorizontalAlignment.CENTER) + } + .also { vf -> + pages.forEachIndexed { index, (resource) -> + vf.child( + Components.button(Component.translatable("playerex.ui.menu.${resource.toLanguageKey()}")) { this.switchPage(index) } + .renderer(ButtonRenderers.STANDARD) + ) + } + } + ) + .apply { + positioning(Positioning.relative(100, 0)) + } + .id("scroll") + ) - pageCounter.text(Component.nullToEmpty("${currentPage + 1}/${pages.size}")) + val levelUpButton = rootComponent.childById(ButtonComponent::class, "level:button")!! + val exit = rootComponent.childById(ButtonComponent::class, "exit")!! - content.clearChildren() - content.child(pages[currentPage]) - previousPage.onPress { - if (currentPage > 0) { - currentPage-- - this.onPagesUpdated() - } - } - nextPage.onPress { - if (currentPage < pages.lastIndex) { - currentPage++ - this.onPagesUpdated() - } - } + levelAmount.setFilter(InputHelper::isUIntInput) + levelAmount.onChanged().subscribe { updateLevelUpButton() } - levelUpButton.onPress { - levelAmount.value.toIntOrNull()?.let { NetworkingChannels.MODIFY.clientHandle().send(NetworkingPackets.Level(it)) } - } + onLevelUpdated(player.level.toInt()) + onExperienceUpdated() + + switchPage(0) - onLevelUpdated.subscribe { this.updateLevelUpButton() } + onLevelUpdated.subscribe { updateLevelUpButton() } + levelUpButton.onPress { levelAmount.value.toIntOrNull()?.let { NetworkingChannels.MODIFY.clientHandle().send(NetworkingPackets.Level(it)) } } exit.onPress { this.onClose() } } /** Whenever the player's experience is changed, refreshing the current status of experience-tied ui elements. */ fun onExperienceUpdated() { + this.uiAdapter.rootComponent.childById(LabelComponent::class.java, "experience")!!.apply { + text(Component.literal(player.experienceLevel.toString())) + } updateLevelUpButton() updateProgressBar() } diff --git a/src/client/kotlin/com/bibireden/playerex/ui/PlayerEXTextures.kt b/src/client/kotlin/com/bibireden/playerex/ui/PlayerEXTextures.kt new file mode 100644 index 00000000..b2997355 --- /dev/null +++ b/src/client/kotlin/com/bibireden/playerex/ui/PlayerEXTextures.kt @@ -0,0 +1,10 @@ +package com.bibireden.playerex.ui + +import com.bibireden.playerex.PlayerEX +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +object PlayerEXTextures { + @JvmField + val RHOMBUS = PlayerEX.id("textures/ui/rhombus.png") +} \ No newline at end of file diff --git a/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeComponent.kt b/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeComponent.kt index 274fd5df..49fa3efa 100644 --- a/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeComponent.kt +++ b/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeComponent.kt @@ -12,6 +12,7 @@ import com.bibireden.playerex.ui.components.buttons.AttributeButtonComponent import com.bibireden.playerex.ui.components.labels.AttributeLabelComponent import com.bibireden.playerex.ui.util.Colors import io.wispforest.owo.ui.component.Components +import io.wispforest.owo.ui.container.Containers import io.wispforest.owo.ui.container.FlowLayout import io.wispforest.owo.ui.core.* import net.minecraft.ChatFormatting @@ -25,7 +26,7 @@ private val StackingBehavior.symbol: String get() = if (this == StackingBehavior.Add) "+" else "×" class AttributeComponent(private val attribute: Attribute, private val player: Player, val component: IPlayerDataComponent) : FlowLayout(Sizing.fill(100), Sizing.fixed(18), Algorithm.HORIZONTAL) { - val label: AttributeLabelComponent + private val label: AttributeLabelComponent fun refresh() { val entries = DataAttributesAPI.clientManager.functions[attribute.id] @@ -60,17 +61,21 @@ class AttributeComponent(private val attribute: Attribute, private val player: P child( Components.label(Component.translatable(attribute.descriptionId)) .verticalTextAlignment(VerticalAlignment.CENTER) - .sizing(Sizing.content(), Sizing.fill(100)) - .positioning(Positioning.relative(0, 50)) + .horizontalTextAlignment(HorizontalAlignment.LEFT) + .sizing(Sizing.fill(40), Sizing.content()) .id("${attribute.id}:label") ) - child(AttributeButtonComponent(attribute, player, component, ButtonType.Remove)) - child( - AttributeLabelComponent(attribute, player).also { label = it } - .horizontalSizing(Sizing.fill(34)) - ) - child(AttributeButtonComponent(attribute, player, component, ButtonType.Add)) + child(Containers.horizontalFlow(Sizing.fill(60), Sizing.content()).apply { + horizontalAlignment(HorizontalAlignment.RIGHT) + verticalAlignment(VerticalAlignment.CENTER) + + gap(6) + + child(AttributeButtonComponent(attribute, player, component, ButtonType.Remove)) + child(AttributeLabelComponent(attribute, player).also { label = it }) + child(AttributeButtonComponent(attribute, player, component, ButtonType.Add)) + }) horizontalAlignment(HorizontalAlignment.RIGHT) verticalAlignment(VerticalAlignment.CENTER) diff --git a/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeListComponent.kt b/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeListComponent.kt index 943e0537..a89b6c8d 100644 --- a/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeListComponent.kt +++ b/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeListComponent.kt @@ -4,19 +4,20 @@ import com.bibireden.data_attributes.api.attribute.EntityAttributeSupplier import io.wispforest.owo.ui.component.Components import io.wispforest.owo.ui.container.Containers import io.wispforest.owo.ui.container.FlowLayout +import io.wispforest.owo.ui.core.Color import io.wispforest.owo.ui.core.Sizing import net.minecraft.world.entity.player.Player import net.minecraft.network.chat.Component import kotlin.jvm.optionals.getOrNull class AttributeListComponent(translationKey: String, private val player: Player, val attributes: List) : FlowLayout(Sizing.fill(25), Sizing.content(), Algorithm.VERTICAL) { - val entriesSection: FlowLayout + private val entriesSection: FlowLayout init { child(Components.label(Component.translatable(translationKey)).horizontalSizing(Sizing.fill(100))) - child(Components.box(Sizing.fill(100), Sizing.fixed(2))) + child(Components.box(Sizing.fill(100), Sizing.fixed(1)).color(Color.ofArgb(0x10FFFFFF))) entriesSection = Containers.verticalFlow(Sizing.fill(100), Sizing.content()) - .apply { gap(4) }.also(::child) + .apply { gap(10) }.also(::child) gap(4) refresh() diff --git a/src/client/kotlin/com/bibireden/playerex/ui/components/ProgressBarComponent.kt b/src/client/kotlin/com/bibireden/playerex/ui/components/ProgressBarComponent.kt new file mode 100644 index 00000000..8e8c9f50 --- /dev/null +++ b/src/client/kotlin/com/bibireden/playerex/ui/components/ProgressBarComponent.kt @@ -0,0 +1,58 @@ +package com.bibireden.playerex.ui.components + +import io.wispforest.owo.ui.component.BoxComponent +import io.wispforest.owo.ui.component.Components +import io.wispforest.owo.ui.container.FlowLayout +import io.wispforest.owo.ui.core.Color +import io.wispforest.owo.ui.core.Easing +import io.wispforest.owo.ui.core.Sizing +import org.jetbrains.annotations.ApiStatus +import kotlin.jvm.Throws + +@ApiStatus.Internal +class ProgressBarComponent(horizontalSizing: Sizing, verticalSizing: Sizing, private val easing: Easing = Easing.SINE, private val fill: Boolean = true) : FlowLayout(horizontalSizing, verticalSizing, Algorithm.HORIZONTAL) { + /** The current percentage of the progress bar. */ + var percentage: Double = 0.0 + set(amount) { + progress((amount * 100).toInt()) + field = amount + } + + private val progressBar = Components.box(Sizing.fill(0), verticalSizing) + .fill(fill) + .direction(BoxComponent.GradientDirection.LEFT_TO_RIGHT) + + private val progressBarBackground = Components.box(Sizing.fill(100), verticalSizing) + .apply { + color(Color.WHITE) + fill(fill) + zIndex(-1) + } + + init { + child(progressBar) + child(progressBarBackground) + } + + fun color(start: Color, end: Color? = null): ProgressBarComponent { + if (end == null) { + progressBar.color(start) + } + else { + progressBar.startColor(start) + progressBar.endColor(end) + } + return this + } + + fun backgroundColor(color: Color): ProgressBarComponent { + progressBarBackground.color(color) + return this + } + + @Throws(IllegalArgumentException::class) + private fun progress(percentage: Int) { + if (percentage < 0 || percentage > 100) throw IllegalArgumentException("Percentage must be between 0 and 100") + progressBar.horizontalSizing(Sizing.fill(percentage)) + } +} \ No newline at end of file diff --git a/src/client/kotlin/com/bibireden/playerex/ui/components/buttons/AttributeButtonComponent.kt b/src/client/kotlin/com/bibireden/playerex/ui/components/buttons/AttributeButtonComponent.kt index 0b1a724f..84b67b8f 100644 --- a/src/client/kotlin/com/bibireden/playerex/ui/components/buttons/AttributeButtonComponent.kt +++ b/src/client/kotlin/com/bibireden/playerex/ui/components/buttons/AttributeButtonComponent.kt @@ -2,6 +2,7 @@ package com.bibireden.playerex.ui.components.buttons import com.bibireden.data_attributes.api.DataAttributesAPI import com.bibireden.data_attributes.api.attribute.IEntityAttribute +import com.bibireden.data_attributes.ui.renderers.ButtonRenderers import com.bibireden.playerex.components.player.IPlayerDataComponent import com.bibireden.playerex.ext.id import com.bibireden.playerex.networking.NetworkingChannels @@ -10,6 +11,7 @@ import com.bibireden.playerex.ui.PlayerEXScreen import com.bibireden.playerex.ui.childById import io.wispforest.owo.ui.component.ButtonComponent import io.wispforest.owo.ui.component.TextBoxComponent +import io.wispforest.owo.ui.core.Insets import io.wispforest.owo.ui.core.Sizing import net.minecraft.world.entity.ai.attributes.Attribute import net.minecraft.world.entity.player.Player @@ -18,8 +20,8 @@ import net.minecraft.network.chat.Component class AttributeButtonComponent(val attribute: Attribute, private val player: Player, private val component: IPlayerDataComponent, val type: PlayerEXScreen.ButtonType) : ButtonComponent( Component.literal(type.symbol), { - // reference text-box to get the necessary value to send to server - it.parent()?.parent()?.childById(TextBoxComponent::class, "input")?.let { box -> + // reference text-box to get the necessary value to send to server (ik it's bad, we fix later) + it.parent()?.parent()?.parent()?.childById(TextBoxComponent::class, "input")?.let { box -> val amount = box.value.toDoubleOrNull() ?: return@let val points = type.getPointsFromComponent(component) @@ -32,14 +34,14 @@ class AttributeButtonComponent(val attribute: Attribute, private val player: Pla } ) { init { - sizing(Sizing.fixed(12), Sizing.fixed(12)) - + sizing(Sizing.content(), Sizing.fixed(9)) + renderer(ButtonRenderers.STANDARD) refresh() } fun refresh() { DataAttributesAPI.getValue(attribute, player).ifPresent { value -> - val max = (attribute as IEntityAttribute).`data_attributes$max`(); + val max = (attribute as IEntityAttribute).`data_attributes$max`() this.active(when (type) { PlayerEXScreen.ButtonType.Add -> component.skillPoints > 0 && max > value PlayerEXScreen.ButtonType.Remove -> component.refundablePoints > 0 && value > 0 diff --git a/src/client/kotlin/com/bibireden/playerex/ui/components/labels/AttributeLabelComponent.kt b/src/client/kotlin/com/bibireden/playerex/ui/components/labels/AttributeLabelComponent.kt index 1bdb60ad..6627571e 100644 --- a/src/client/kotlin/com/bibireden/playerex/ui/components/labels/AttributeLabelComponent.kt +++ b/src/client/kotlin/com/bibireden/playerex/ui/components/labels/AttributeLabelComponent.kt @@ -7,6 +7,7 @@ import com.bibireden.playerex.ext.id import com.bibireden.playerex.ui.util.Colors import io.wispforest.owo.ui.component.LabelComponent import io.wispforest.owo.ui.core.HorizontalAlignment +import io.wispforest.owo.ui.core.Sizing import io.wispforest.owo.ui.core.VerticalAlignment import net.minecraft.world.entity.ai.attributes.Attribute import net.minecraft.world.entity.player.Player @@ -16,11 +17,11 @@ private fun createTextFromAttribute(attribute: Attribute, player: Player): Compo val allocatedPoints = player.component.get(attribute).toInt() val actual = DataAttributesAPI.getValue(attribute, player).map(Double::toInt).orElse(0) - val text = Component.literal("(") + val text = Component.literal("[ ") .append(Component.literal("$allocatedPoints").withStyle { it.withColor(Colors.GOLD) }) - .append("/${(attribute as IEntityAttribute).`data_attributes$max`().toInt()})") + .append("/${(attribute as IEntityAttribute).`data_attributes$max`().toInt()} ]") val difference = actual - allocatedPoints if (difference > 0) { @@ -37,6 +38,8 @@ open class AttributeLabelComponent(private val attribute: Attribute, private val this.horizontalTextAlignment(HorizontalAlignment.CENTER) this.verticalTextAlignment(VerticalAlignment.CENTER) + this.horizontalSizing(Sizing.content()) + this.id("${attribute.id}:current_level") this.refresh() diff --git a/src/client/kotlin/com/bibireden/playerex/ui/menus/PlayerEXAttributesMenu.kt b/src/client/kotlin/com/bibireden/playerex/ui/menus/PlayerEXAttributesMenu.kt index 8eeec712..51e0706f 100644 --- a/src/client/kotlin/com/bibireden/playerex/ui/menus/PlayerEXAttributesMenu.kt +++ b/src/client/kotlin/com/bibireden/playerex/ui/menus/PlayerEXAttributesMenu.kt @@ -1,6 +1,8 @@ package com.bibireden.playerex.ui.menus +import com.bibireden.data_attributes.api.DataAttributesAPI import com.bibireden.data_attributes.api.attribute.EntityAttributeSupplier +import com.bibireden.data_attributes.ext.round import com.bibireden.playerex.api.attribute.PlayerEXAttributes import com.bibireden.playerex.ext.component import com.bibireden.playerex.ext.id @@ -11,6 +13,7 @@ import com.bibireden.playerex.ui.components.buttons.AttributeButtonComponent import com.bibireden.playerex.ui.components.labels.AttributeLabelComponent import com.bibireden.playerex.ui.helper.InputHelper import de.dafuqs.additionalentityattributes.AdditionalEntityAttributes +import io.wispforest.owo.ui.component.BoxComponent import io.wispforest.owo.ui.component.Components import io.wispforest.owo.ui.component.TextBoxComponent import io.wispforest.owo.ui.container.Containers @@ -21,10 +24,31 @@ import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.world.entity.ai.attributes.Attributes import net.minecraft.world.entity.player.Player import net.minecraft.network.chat.Component +import net.minecraft.util.Mth import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal class PlayerEXAttributesMenu : MenuComponent(algorithm = Algorithm.HORIZONTAL) { + private val lungProgressBar = ProgressBarComponent(Sizing.fill(65), Sizing.fixed(4)) + .color(Color.ofRgb(0x399ce9), Color.ofRgb(0x68a8d9)) + .apply { positioning(Positioning.relative(50, 100)).id("progress:lung") } + + private val progressHealthBar = ProgressBarComponent(Sizing.fill(65), Sizing.fixed(4)) + .color(Color.ofRgb(0xea233a), Color.ofRgb(0xf3394f)) + .apply { positioning(Positioning.relative(50, 80)).id("progress:health") } + + private val labelHealth = Components.label(Component.literal("")) + .apply { + color(Color.ofArgb(0x32FFFFFF)) + positioning(Positioning.relative(50, 74)) + } + + private val labelLung = Components.label(Component.literal("")) + .apply { + color(Color.ofArgb(0x32FFFFFF)) + positioning(Positioning.relative(50, 94)) + } + private val MELEE_COMBAT_STATS: List = listOf( EntityAttributeSupplier(Attributes.ATTACK_DAMAGE.id), EntityAttributeSupplier(Attributes.ATTACK_SPEED.id), @@ -59,6 +83,7 @@ class PlayerEXAttributesMenu : MenuComponent(algorithm = Algorithm.HORIZONTAL) { EntityAttributeSupplier(PlayerEXAttributes.FREEZE_RESISTANCE.id), EntityAttributeSupplier(PlayerEXAttributes.LIGHTNING_RESISTANCE.id), EntityAttributeSupplier(PlayerEXAttributes.POISON_RESISTANCE.id), + EntityAttributeSupplier(PlayerEXAttributes.WITHER_RESISTANCE.id), ) /** Whenever ANY attribute gets updated. */ @@ -91,59 +116,131 @@ class PlayerEXAttributesMenu : MenuComponent(algorithm = Algorithm.HORIZONTAL) { } + private fun onHealthUpdated() { + val player = client!!.player!! + val percentage = Mth.clamp((player.health / player.maxHealth).toDouble(), 0.0, 100.0) + progressHealthBar.percentage = percentage + labelHealth.text(Component.translatable("playerex.ui.main.labels.health").append(" - ${player.health.toDouble().round(1)}")) + } + + private fun onLungCapacityUpdated() { + val player = client!!.player!! + val percentage = Mth.clamp(player.airSupply.toDouble() / player.maxAirSupply, 0.0, 100.0) + lungProgressBar.percentage = percentage + labelLung.text(Component.translatable("attribute.name.additionalentityattributes.generic.lung_capacity").append(" - ${player.airSupply}")) + } + override fun build(screenRoot: FlowLayout) { val player = client?.player ?: return val component = playerComponent ?: return - child(Containers.verticalScroll( - Sizing.fill(45), - Sizing.fill(100), - Containers.verticalFlow(Sizing.fill(100), Sizing.content()).apply { - child(Containers.horizontalFlow(Sizing.fill(100), Sizing.content(2)).apply { - child(Components.label(Component.translatable("playerex.ui.category.attributes"))) - child( - Components.textBox(Sizing.fixed(27)) - .text("1") - .also { - it.setMaxLength(4) - it.setFilter(InputHelper::isUIntInput) - it.onChanged().subscribe { onInputFieldUpdated(player) } - } - .verticalSizing(Sizing.fixed(10)) - .positioning(Positioning.relative(100, 0)) - .id("input") + // -- divider line -- // + + child(Components.box(Sizing.fixed(2), Sizing.fill(100)).color(Color.ofArgb(0x32FFFFFF)).positioning(Positioning.relative(45, 50))) + child(Components.box(Sizing.fill(55), Sizing.fixed(2)).color(Color.ofArgb(0x32FFFFFF)).positioning(Positioning.relative(100, 50))) + + child(Containers.verticalScroll(Sizing.fill(40), Sizing.fill(70), Containers.verticalFlow(Sizing.fill(100), Sizing.content(6)).apply { + verticalAlignment(VerticalAlignment.CENTER) + gap(10) + padding(Insets.right(5)) + + child(Containers.horizontalFlow(Sizing.fill(100), Sizing.content(4)).apply { + verticalAlignment(VerticalAlignment.CENTER) + + child(Components.label(Component.translatable("playerex.ui.category.attributes")).color(Color.ofArgb(0x32FFFFFF))) + child( + Components.textBox(Sizing.fixed(27)) + .text("1") + .also { + it.setMaxLength(3) + it.setFilter(InputHelper::isUIntInput) + it.onChanged().subscribe { onInputFieldUpdated(player) } + } + .verticalSizing(Sizing.fixed(15)) + .positioning(Positioning.relative(100, 50)) + .id("input") + ) + }) + + child( + Components.box(Sizing.fill(100), Sizing.fixed(2)) + .color(Color.ofArgb(0x36FFFFFF)) + ) + + for (id in PlayerEXAttributes.PRIMARY_ATTRIBUTE_IDS) { + val attribute = BuiltInRegistries.ATTRIBUTE.get(id) ?: continue + child(AttributeComponent(attribute, player, component)) + child( + Components.box(Sizing.fill(100), Sizing.fixed(2)).fill(true) + .color(Color.ofArgb(0x10FFFFFF)) + ) + } + } + .verticalAlignment(VerticalAlignment.CENTER) + .id("attributes") + ) + .positioning(Positioning.relative(5, 50)) + ) + + // VIGOR + + child( + Containers.verticalFlow(Sizing.fill(54), Sizing.fill(46)) + .apply { + child(Components.label(Component.translatable("playerex.ui.main.sections.vigor")).color(Color.ofArgb(0x32FFFFFF)) + .horizontalSizing(Sizing.content())) + + // health progress + child(labelHealth) + child(progressHealthBar) + + // lung progress + child(labelLung) + child(lungProgressBar) + + child(Containers.verticalScroll( + Sizing.fill(100), + Sizing.fill(50), + Containers.horizontalFlow(Sizing.fill(100), Sizing.content()).apply { + child(AttributeListComponent("playerex.ui.main.categories.vitality", player, VITALITY_STATS).horizontalSizing(Sizing.fill(45))) + child(AttributeListComponent("playerex.ui.main.categories.resistance", player, RESISTANCE_STATS).horizontalSizing(Sizing.fill(45))) + gap(8) + alignment(HorizontalAlignment.CENTER, VerticalAlignment.TOP) + } + ) + .positioning(Positioning.relative(50, 20)) ) - }) - child(Components.box(Sizing.fill(100), Sizing.fixed(2))) - verticalAlignment(VerticalAlignment.TOP) - gap(5) - padding(Insets.right(5)) - children(PlayerEXAttributes.PRIMARY_ATTRIBUTE_IDS.mapNotNull(BuiltInRegistries.ATTRIBUTE::get).map { AttributeComponent(it, player, component) }) - }.id("attributes")) + } + .horizontalAlignment(HorizontalAlignment.CENTER) + .positioning(Positioning.relative(100, 100)) ) - child(Containers.verticalScroll( - Sizing.content(), - Sizing.fill(100), - Containers.verticalFlow(Sizing.content(), Sizing.content()).apply { - child(AttributeListComponent("playerex.ui.main.categories.vitality", player, VITALITY_STATS)) - child(AttributeListComponent("playerex.ui.main.categories.resistance", player, RESISTANCE_STATS)) - padding(Insets.right(5)) - gap(8) - } - )) + // COMBAT STATS child( - Containers.verticalScroll( - Sizing.content(), - Sizing.fill(100), - Containers.verticalFlow(Sizing.content(), Sizing.content()).apply { - child(AttributeListComponent("playerex.ui.main.categories.melee_combat", player, MELEE_COMBAT_STATS)) - child(AttributeListComponent("playerex.ui.main.categories.ranged_combat", player, RANGED_COMBAT_STATS)) - child(AttributeListComponent("playerex.ui.main.categories.defense_combat", player, DEFENSE_COMBAT_STATS)) - padding(Insets.right(5)) - gap(10) - }.id("combat_stats") - ) + Containers.verticalFlow(Sizing.fill(54), Sizing.fill(46)) + .apply { + child(Components.label(Component.translatable("playerex.ui.main.sections.combat_stats")).color(Color.ofArgb(0x32FFFFFF)) + .horizontalSizing(Sizing.content())) + + child( + Containers.verticalScroll( + Sizing.fill(100), + Sizing.fill(90), + Containers.horizontalFlow(Sizing.fill(100), Sizing.content()).apply { + child(AttributeListComponent("playerex.ui.main.categories.melee_combat", player, MELEE_COMBAT_STATS).horizontalSizing(Sizing.fill(30))) + child(AttributeListComponent("playerex.ui.main.categories.ranged_combat", player, RANGED_COMBAT_STATS).horizontalSizing(Sizing.fill(30))) + child(AttributeListComponent("playerex.ui.main.categories.defense_combat", player, DEFENSE_COMBAT_STATS).horizontalSizing(Sizing.fill(30))) + padding(Insets.vertical(15)) + gap(10) + alignment(HorizontalAlignment.CENTER, VerticalAlignment.TOP) + } + .positioning(Positioning.relative(50, 100)) + .id("combat_stats") + ) + ) + } + .horizontalAlignment(HorizontalAlignment.CENTER) + .positioning(Positioning.relative(100, 0)) ) gap(10) @@ -153,9 +250,19 @@ class PlayerEXAttributesMenu : MenuComponent(algorithm = Algorithm.HORIZONTAL) { onAttributeUpdate() onInputFieldUpdated(player) - onAttributeUpdated.subscribe { _, _ -> + onLungCapacityUpdated() + onHealthUpdated() + + onAttributeUpdated.subscribe { attribute, _ -> onAttributeUpdate() onInputFieldUpdated(player) + + if (attribute == AdditionalEntityAttributes.LUNG_CAPACITY) { + onLungCapacityUpdated() + } + else if (attribute == Attributes.MAX_HEALTH) { + onHealthUpdated() + } } } } \ No newline at end of file diff --git a/src/client/resources/assets/playerex/owo_ui/main_ui_model.xml b/src/client/resources/assets/playerex/owo_ui/main_ui_model.xml index 3380907f..10748f1e 100644 --- a/src/client/resources/assets/playerex/owo_ui/main_ui_model.xml +++ b/src/client/resources/assets/playerex/owo_ui/main_ui_model.xml @@ -44,29 +44,11 @@ - - - - - - - @@ -90,62 +72,111 @@ - 85100 + 8585 0,40 10 center + + + + + 8515 + 100,40 + + 10 + center + + + + + + + 100100 + + + + + + + 6464 + 4,95 + + - - 1002 - true - 0,10 - + + + + + + 1 - - 02 + + 20 + 27 + - true + 3 + bottom + + + - 0,10 - 1 - + 00 - - - - - 1 + 100,50 + - - 10 - 27 - + + 802 + true + white + - 3 - bottom - - - + true - 65100 - bottom + left-to-right + gold + red - 2 + 0,0 + + + + + + + + 40 + + 50,80 diff --git a/src/client/resources/assets/playerex/textures/gui/rhombus.png b/src/client/resources/assets/playerex/textures/gui/rhombus.png new file mode 100644 index 00000000..a065c452 Binary files /dev/null and b/src/client/resources/assets/playerex/textures/gui/rhombus.png differ diff --git a/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java b/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java index 21c39417..9e0765f7 100644 --- a/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java +++ b/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java @@ -7,6 +7,8 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -15,6 +17,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import java.io.IOException; + @Mixin(LivingEntity.class) public abstract class LivingEntityMixin { diff --git a/src/main/kotlin/com/bibireden/playerex/PlayerEX.kt b/src/main/kotlin/com/bibireden/playerex/PlayerEX.kt index 5f8a2e9e..793338bf 100644 --- a/src/main/kotlin/com/bibireden/playerex/PlayerEX.kt +++ b/src/main/kotlin/com/bibireden/playerex/PlayerEX.kt @@ -44,10 +44,7 @@ object PlayerEX : ModInitializer { fun id(path: String) = ResourceLocation.tryBuild(MOD_ID, path)!! - private val gimmick = listOf( - "Let's do it right this time...", - "We test in production (not really).", - ).random() + private val gimmick = listOf("Let's do it right this time...", "We test in production (not really).", "Extraordinary solutions to extraordinary problems.").random() override fun onInitialize() { NetworkingChannels.NOTIFICATIONS.registerClientboundDeferred(NetworkingPackets.Notify::class.java) diff --git a/src/main/kotlin/com/bibireden/playerex/compat/CompatUtils.kt b/src/main/kotlin/com/bibireden/playerex/compat/CompatUtils.kt deleted file mode 100644 index 70636681..00000000 --- a/src/main/kotlin/com/bibireden/playerex/compat/CompatUtils.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.bibireden.playerex.compat - -import net.fabricmc.loader.api.FabricLoader -import org.jetbrains.annotations.ApiStatus - -@ApiStatus.Internal -object CompatUtils { - fun isModLoaded(id: String): Boolean = FabricLoader.getInstance().isModLoaded(id) -} \ No newline at end of file diff --git a/src/main/resources/assets/playerex/lang/en_us.json b/src/main/resources/assets/playerex/lang/en_us.json index 9f103e90..99e4a61a 100644 --- a/src/main/resources/assets/playerex/lang/en_us.json +++ b/src/main/resources/assets/playerex/lang/en_us.json @@ -13,7 +13,7 @@ "playerex.ui.main.skill_points_header": "SKILL POINTS", "playerex.ui.main.modified_attributes": [ {"text": "❤ ", "color": "#48D19B"}, {"text": "Modified Attributes", "color": "white"} ], - "playerex.ui.category.attributes": "Attributes", + "playerex.ui.category.attributes": "ATTRIBUTES", "playerex.ui.current_level": [ {"text": "❤ ", "color": "#F0C25E"}, @@ -37,6 +37,16 @@ "playerex.ui.nameplate.level": "", + "playerex.ui.menu.playerex.attributes": [ + {"text": "✎ ", "color": "#F0C25E"}, + {"text": "PlayerEX Attributes", "color": "white"} + ], + + "playerex.ui.main.labels.health": "Health", + + "playerex.ui.main.sections.vigor": "VIGOR", + "playerex.ui.main.sections.combat_stats": "COMBAT STATS", + "playerex.ui.main.categories.melee_combat": "⚔ MELEE", "playerex.ui.main.categories.ranged_combat": "🏹 RANGED", "playerex.ui.main.categories.defense_combat": "🛡 DEFENSE",