Skip to content

Commit

Permalink
Implement UI elements for skill info & temp menu
Browse files Browse the repository at this point in the history
  • Loading branch information
bibi-reden committed Jul 28, 2024
1 parent efec623 commit a88c7d5
Show file tree
Hide file tree
Showing 11 changed files with 254 additions and 62 deletions.
11 changes: 6 additions & 5 deletions src/client/kotlin/com/bibireden/playerex/PlayerEXClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import com.bibireden.playerex.networking.registerClientbound
import com.bibireden.playerex.networking.types.NotificationType
import com.bibireden.playerex.registry.AttributesMenuRegistry
import com.bibireden.playerex.ui.PlayerEXScreen
import com.bibireden.playerex.ui.menus.AttributesMenu
import com.bibireden.playerex.ui.menus.PlayerEXAttributesMenu
import com.bibireden.playerex.ui.menus.PlayerEXStatsMenu
import net.fabricmc.api.ClientModInitializer
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper
import net.minecraft.client.MinecraftClient
import net.minecraft.client.network.ClientPlayerEntity
import net.minecraft.client.option.KeyBinding
import net.minecraft.client.util.InputUtil
import net.minecraft.entity.player.PlayerEntity
Expand All @@ -37,9 +39,7 @@ object PlayerEXClient : ClientModInitializer {
}

EntityAttributeModifiedEvents.MODIFIED.register { attribute, entity, _, _, _ ->
if (entity is PlayerEntity && entity.world.isClient) {
if (entity != MinecraftClient.getInstance().player) return@register

if (entity is ClientPlayerEntity) {
val screen = MinecraftClient.getInstance().currentScreen
if (screen is PlayerEXScreen) {
if (attribute == PlayerEXAttributes.LEVEL) {
Expand All @@ -54,7 +54,8 @@ object PlayerEXClient : ClientModInitializer {
}
}

AttributesMenuRegistry.register(AttributesMenu::class.java)
AttributesMenuRegistry.register(PlayerEXAttributesMenu::class.java)
AttributesMenuRegistry.register(PlayerEXStatsMenu::class.java)

ClientTickEvents.END_CLIENT_TICK.register { client ->
if (PlayerEX.CONFIG.disableUI) return@register
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.bibireden.playerex.ui.components

import com.bibireden.data_attributes.api.attribute.EntityAttributeSupplier
import com.bibireden.playerex.ui.util.FormattingPredicates
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.Positioning
import io.wispforest.owo.ui.core.Sizing
import net.minecraft.entity.attribute.EntityAttribute
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.text.Text

private fun transform(array: List<Pair<EntityAttributeSupplier, FormattingPredicates>>): List<Pair<EntityAttribute, FormattingPredicate>> {
val filtered = mutableListOf<Pair<EntityAttribute, FormattingPredicate>>()
for ((attribute, pred) in array) {
if (attribute.get().isPresent) filtered.add(Pair(attribute.get().get(), pred.predicate))
}
return filtered
}

class AttributeListComponent(translationKey: String, private val player: PlayerEntity, private val gimmie: List<Pair<EntityAttributeSupplier, FormattingPredicates>>) : FlowLayout(Sizing.fill(45), Sizing.content(), Algorithm.VERTICAL) {
val entriesSection: FlowLayout

init {
child(
Components.label(Text.translatable(translationKey))
.horizontalSizing(Sizing.fill(100))
)
entriesSection = Containers.verticalFlow(Sizing.fill(100), Sizing.content())
.apply {
gap(4)
}.also(::child)

gap(4)
refresh()
}

fun refresh() {
entriesSection.children().filterIsInstance<AttributeListEntryComponent>().forEach(::removeChild)
entriesSection.children(transform(gimmie).map { AttributeListEntryComponent(it.first, player, it.second) })
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.bibireden.playerex.ui.components

import com.bibireden.data_attributes.api.DataAttributesAPI
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.VerticalAlignment
import net.minecraft.entity.attribute.EntityAttribute
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.text.Text
import net.minecraft.util.Formatting

typealias FormattingPredicate = (Double) -> String

class AttributeListEntryComponent(
val attribute: EntityAttribute,
val player: PlayerEntity,
private val formattingPredicate: FormattingPredicate
) : LabelComponent(Text.empty()) {
init {
horizontalTextAlignment(HorizontalAlignment.CENTER)
verticalTextAlignment(VerticalAlignment.CENTER)

refresh()
}

fun refresh() {
text(
Text.translatable(attribute.translationKey)
.append(": ")
.append(Text.literal(
DataAttributesAPI.getValue(attribute, player).map { formattingPredicate(it) }
.orElse("N/A")).styled { it.withColor(Colors.GOLD) }
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import org.jetbrains.annotations.ApiStatus
* and have the benefits of a unique instance that is attached to the primary mod.
*/
@ApiStatus.OverrideOnly
abstract class MenuComponent(horizontalSizing: Sizing, verticalSizing: Sizing, algorithm: Algorithm) : FlowLayout(horizontalSizing, verticalSizing, algorithm) {
abstract class MenuComponent(horizontalSizing: Sizing = Sizing.fill(100), verticalSizing: Sizing = Sizing.fill(100), algorithm: Algorithm) : FlowLayout(horizontalSizing, verticalSizing, algorithm) {
val onLevelUpdatedEvents = OnLevelUpdated.stream
val onAttributeUpdatedEvents = OnAttributeUpdated.stream

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ private fun createTextFromAttribute(attribute: EntityAttribute, player: PlayerEn
})
.append("/${(attribute as IEntityAttribute).`data_attributes$max`().toInt()})")

class AttributeLabelComponent(private val attribute: EntityAttribute, private val player: PlayerEntity) : LabelComponent(createTextFromAttribute(attribute, player)) {
open class AttributeLabelComponent(private val attribute: EntityAttribute, private val player: PlayerEntity) : LabelComponent(Text.empty()) {
init {
horizontalTextAlignment(HorizontalAlignment.CENTER)
verticalTextAlignment(VerticalAlignment.CENTER)
this.horizontalTextAlignment(HorizontalAlignment.CENTER)
this.verticalTextAlignment(VerticalAlignment.CENTER)

id("${attribute.id}:current_level")
this.id("${attribute.id}:current_level")

this.refresh()
}

fun refresh(): LabelComponent = text(createTextFromAttribute(attribute, player))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
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.api.attribute.IEntityAttribute
import com.bibireden.playerex.api.attribute.ModdedAttributes
import com.bibireden.playerex.api.attribute.PlayerEXAttributes
import com.bibireden.playerex.components.player.IPlayerDataComponent
import com.bibireden.playerex.ext.id
import com.bibireden.playerex.ext.level
import com.bibireden.playerex.ui.PlayerEXScreen
import com.bibireden.playerex.ui.childById
import com.bibireden.playerex.ui.components.MenuComponent
import com.bibireden.playerex.ui.components.AttributeComponent
import com.bibireden.playerex.ui.components.*
import com.bibireden.playerex.ui.components.buttons.AttributeButtonComponent
import com.bibireden.playerex.ui.components.labels.AttributeLabelComponent
import com.bibireden.playerex.util.PlayerEXUtil
import com.bibireden.playerex.ui.util.FormattingPredicates
import de.dafuqs.additionalentityattributes.AdditionalEntityAttributes
import io.wispforest.owo.ui.component.Components
import io.wispforest.owo.ui.component.LabelComponent
import io.wispforest.owo.ui.component.TextBoxComponent
import io.wispforest.owo.ui.container.Containers
import io.wispforest.owo.ui.container.FlowLayout
import io.wispforest.owo.ui.core.Insets
import io.wispforest.owo.ui.core.OwoUIAdapter
import io.wispforest.owo.ui.core.Positioning
import io.wispforest.owo.ui.core.Sizing
import io.wispforest.owo.ui.core.*
import net.fabric_extras.ranged_weapon.api.EntityAttributes_RangedWeapon
import net.minecraft.client.network.ClientPlayerEntity
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.registry.Registries
import net.minecraft.text.Text
import org.jetbrains.annotations.ApiStatus

// todo: cache buttons/certain UI elements

class AttributesMenu : MenuComponent(Sizing.fill(100), Sizing.fill(100), Algorithm.VERTICAL) {
@ApiStatus.Internal
class PlayerEXAttributesMenu : MenuComponent(algorithm = Algorithm.VERTICAL) {

private fun onLevelUpdate(level: Int) {}

/** Whenever ANY attribute gets updated. */
private fun onAttributeUpdate() {
// refresh all attribute labels
this.forEachDescendant { component ->
// todo: use derived interface to check instance
if (component is AttributeComponent) {
component.refresh()
return@forEachDescendant
Expand Down Expand Up @@ -63,25 +70,32 @@ class AttributesMenu : MenuComponent(Sizing.fill(100), Sizing.fill(100), Algorit
}

override fun build(player: ClientPlayerEntity, adapter: OwoUIAdapter<FlowLayout>, component: IPlayerDataComponent) {
child(Containers.verticalFlow(Sizing.fill(35), Sizing.fill(100)).apply {
child(Components.label(Text.translatable("playerex.ui.category.primary_attributes")))
child(
Components.textBox(Sizing.fixed(27))
.also {
it.setMaxLength(4)
it.onChanged().subscribe { onInputFieldUpdated(player, component) }
}
.text("1")
.verticalSizing(Sizing.fixed(10))
.positioning(Positioning.relative(100, 0))
.id("input")
)
child(Components.box(Sizing.fill(100), Sizing.fixed(2)))
gap(5)
children(PlayerEXAttributes.PRIMARY_ATTRIBUTE_IDS.mapNotNull(Registries.ATTRIBUTE::get).map { AttributeComponent(it, player, component) })
}.id("attributes"))
child(Containers.verticalScroll(
Sizing.fill(35),
Sizing.fill(100),
Containers.verticalFlow(Sizing.fill(100), Sizing.content()).apply {
child(Containers.horizontalFlow(Sizing.fill(100), Sizing.content(2)).apply {
child(Components.label(Text.translatable("playerex.ui.category.primary_attributes")))
child(
Components.textBox(Sizing.fixed(27))
.also {
it.setMaxLength(4)
it.onChanged().subscribe { onInputFieldUpdated(player, component) }
}
.text("1")
.verticalSizing(Sizing.fixed(10))
.positioning(Positioning.relative(100, 0))
.id("input")
)
})
child(Components.box(Sizing.fill(100), Sizing.fixed(2)))
verticalAlignment(VerticalAlignment.CENTER)
gap(5)
children(PlayerEXAttributes.PRIMARY_ATTRIBUTE_IDS.mapNotNull(Registries.ATTRIBUTE::get).map { AttributeComponent(it, player, component) })
}.id("attributes"))
)

padding(Insets.both(12, 12))
padding(Insets.both(8, 8))

onLevelUpdate(player.level.toInt())
onAttributeUpdate()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.bibireden.playerex.ui.menus

import com.bibireden.data_attributes.api.attribute.EntityAttributeSupplier
import com.bibireden.playerex.api.attribute.ModdedAttributes
import com.bibireden.playerex.api.attribute.PlayerEXAttributes
import com.bibireden.playerex.components.player.IPlayerDataComponent
import com.bibireden.playerex.ext.id
import com.bibireden.playerex.ui.components.AttributeListComponent
import com.bibireden.playerex.ui.components.AttributeListEntryComponent
import com.bibireden.playerex.ui.components.MenuComponent
import com.bibireden.playerex.ui.util.FormattingPredicates
import de.dafuqs.additionalentityattributes.AdditionalEntityAttributes
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.Insets
import io.wispforest.owo.ui.core.OwoUIAdapter
import io.wispforest.owo.ui.core.Positioning
import io.wispforest.owo.ui.core.Sizing
import net.fabric_extras.ranged_weapon.api.EntityAttributes_RangedWeapon
import net.minecraft.client.network.ClientPlayerEntity
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.text.Text
import org.jetbrains.annotations.ApiStatus

@ApiStatus.Internal
class PlayerEXStatsMenu : MenuComponent(algorithm = Algorithm.VERTICAL) {
val MELEE_COMBAT_STATS: List<Pair<EntityAttributeSupplier, FormattingPredicates>> = listOf(
EntityAttributeSupplier(EntityAttributes.GENERIC_ATTACK_DAMAGE.id) to FormattingPredicates.Normal,
EntityAttributeSupplier(EntityAttributes.GENERIC_ATTACK_SPEED.id) to FormattingPredicates.Normal,
ModdedAttributes.ATTACK_RANGE to FormattingPredicates.Normal,
EntityAttributeSupplier(PlayerEXAttributes.MELEE_CRIT_DAMAGE.id) to FormattingPredicates.PercentageDiv,
EntityAttributeSupplier(PlayerEXAttributes.MELEE_CRIT_CHANCE.id) to FormattingPredicates.PercentageDiv
)

val RANGED_COMBAT_STATS: List<Pair<EntityAttributeSupplier, FormattingPredicates>> = listOf(
EntityAttributeSupplier(PlayerEXAttributes.RANGED_DAMAGE.id) to FormattingPredicates.Normal,
EntityAttributeSupplier(PlayerEXAttributes.RANGED_CRITICAL_DAMAGE.id) to FormattingPredicates.PercentageDiv,
EntityAttributeSupplier(PlayerEXAttributes.RANGED_CRITICAL_CHANCE.id) to FormattingPredicates.PercentageDiv,
EntityAttributeSupplier(EntityAttributes_RangedWeapon.HASTE.id) to FormattingPredicates.Percent,
)

val DEFENSE_COMBAT_STATS: List<Pair<EntityAttributeSupplier, FormattingPredicates>> = listOf(
EntityAttributeSupplier(EntityAttributes.GENERIC_ARMOR.id) to FormattingPredicates.Normal,
EntityAttributeSupplier(AdditionalEntityAttributes.MAGIC_PROTECTION.id) to FormattingPredicates.Normal,
EntityAttributeSupplier(EntityAttributes.GENERIC_ARMOR_TOUGHNESS.id) to FormattingPredicates.Normal,
EntityAttributeSupplier(EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE.id) to FormattingPredicates.PercentageDiv,
EntityAttributeSupplier(PlayerEXAttributes.EVASION.id) to FormattingPredicates.PercentageDiv,
)

override fun build(player: ClientPlayerEntity, adapter: OwoUIAdapter<FlowLayout>, component: IPlayerDataComponent) {
child(
Containers.verticalScroll(
Sizing.fill(100),
Sizing.fill(100),
Containers.verticalFlow(Sizing.fill(100), 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))
gap(10)
}.id("combat_stats")
)
)

this.onAttributeUpdated.subscribe { _, _ ->
this.forEachDescendant {
if (it is AttributeListEntryComponent) it.refresh()
}
}

padding(Insets.both(8, 12))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.bibireden.playerex.ui.util

import com.bibireden.playerex.ui.components.FormattingPredicate

enum class FormattingPredicates(val predicate: FormattingPredicate) {
Normal({ "%.2f".format(it) }),
Percent({ "${it.toInt()}%" }),
PercentageMul({ "${(it * 100.0).toInt()}%" }),
PercentageDiv({ "${(it / 100.0).toInt()}%" }),
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
<children/>

<sizing><vertical method="fill">85</vertical><horizontal method="fill">100</horizontal></sizing>
<positioning type="relative">0,50</positioning>
<positioning type="relative">0,40</positioning>

<padding><all>10</all></padding>
<vertical-alignment>center</vertical-alignment>
Expand Down
Loading

0 comments on commit a88c7d5

Please sign in to comment.