Skip to content

Commit

Permalink
[feat] Finalize PlayerEX UI and stage release
Browse files Browse the repository at this point in the history
  • Loading branch information
bibi-reden committed Dec 13, 2024
1 parent 2779603 commit b9b98b1
Show file tree
Hide file tree
Showing 16 changed files with 533 additions and 247 deletions.
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
**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 🍎

Check notice on line 3 in CHANGELOG.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

CHANGELOG.md#L3

Expected: 1; Actual: 0; Below
- Finished the PlayerEX UI to the point where it is ready for release.

Check notice on line 4 in CHANGELOG.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

CHANGELOG.md#L4

Lists should be surrounded by blank lines
- 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.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<? extends LivingEntity> 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));
}
}
}
249 changes: 141 additions & 108 deletions src/client/kotlin/com/bibireden/playerex/ui/PlayerEXScreen.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -14,164 +15,196 @@ 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
fun <T : OwoComponent> ParentComponent.childById(clazz: KClass<T>, id: String) = this.childById(clazz.java, id)

/** Primary screen for the mod that brings everything intended together. */
class PlayerEXScreen : BaseUIModelScreen<FlowLayout>(FlowLayout::class.java, DataSource.asset(PlayerEXClient.MAIN_UI_SCREEN_ID)) {
private var listCollapsed = false

private var currentPage = 0

private val pages: MutableList<MenuComponent> = mutableListOf()
private val pages: MutableList<Pair<ResourceLocation, MenuComponent>> = mutableListOf()

private val player by lazy { this.minecraft!!.player!! }

private val content by lazy { uiAdapter.rootComponent.childById(FlowLayout::class, "content")!! }
private val footer by lazy { uiAdapter.rootComponent.childById(FlowLayout::class, "footer")!! }

private val currentLevel by lazy { uiAdapter.rootComponent.childById(LabelComponent::class, "level:current")!! }
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<OnLevelUpdated> = onLevelUpdatedEvents.source()

override fun isPauseScreen(): Boolean = false

/** Whenever the level attribute gets modified, and on initialization of the screen, this will be called. */
fun onLevelUpdated(level: Int) {
// currentLevel.apply {
// text(Component.translatable("playerex.ui.current_level", player.level.toInt(), PlayerEXUtil.getRequiredXpForNextLevel(player)))
// }
//
// updatePointsAvailable()
// updateLevelUpButton()
// updateProgressBar()
//
// this.uiAdapter.rootComponent.forEachDescendant { descendant ->
// if (descendant is MenuComponent) descendant.onLevelUpdatedEvents.sink().onLevelUpdated(level)
// if (descendant is AttributeButtonComponent) descendant.refresh()
// }
currentLevel.apply {
text(Component.translatable("playerex.ui.current_level", player.level.toInt(), PlayerEXUtil.getRequiredXpForNextLevel(player)))
}

updatePointsAvailable()
updateLevelUpButton()
updateProgressBar()

this.uiAdapter.rootComponent.forEachDescendant { descendant ->
if (descendant is MenuComponent) descendant.onLevelUpdatedEvents.sink().onLevelUpdated(level)
if (descendant is AttributeButtonComponent) descendant.refresh()
}
}

/** Whenever any attribute is updated, this will be called. */
fun onAttributeUpdated(attribute: Attribute, value: Double) {
// this.uiAdapter.rootComponent.forEachDescendant { descendant ->
// if (descendant is MenuComponent) descendant.onAttributeUpdatedEvents.sink().onAttributeUpdated(attribute, value)
// if (descendant is AttributeButtonComponent) descendant.refresh()
// }
// updatePointsAvailable()
this.uiAdapter.rootComponent.forEachDescendant { descendant ->
if (descendant is MenuComponent) descendant.onAttributeUpdatedEvents.sink().onAttributeUpdated(attribute, value)
if (descendant is AttributeButtonComponent) descendant.refresh()
}
updatePointsAvailable()
}

private fun updatePointsAvailable() {
// this.uiAdapter.rootComponent.childById(LabelComponent::class, "points_available")?.apply {
// text(Component.translatable("playerex.ui.main.skill_points_header").append(": [").append(
// Component.literal("${player.component.skillPoints}").withStyle {
// it.withColor(
// when (player.component.skillPoints) {
// 0 -> Colors.GRAY
// else -> Colors.SATURATED_BLUE
// }
// )
// }).append("]")
// )
// }
this.uiAdapter.rootComponent.childById(LabelComponent::class, "points_available")?.apply {
text(Component.translatable("playerex.ui.main.skill_points_header").append(": ").append(
Component.literal("${player.component.skillPoints}").withStyle {
it.withColor(
when (player.component.skillPoints) {
0 -> Colors.GRAY
else -> Colors.SATURATED_BLUE
}
)
})
)
}
}

private fun onPagesUpdated() {
// 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])
/** changes the page based on the index. */
private fun switchPage(to: Int) {

Check warning on line 100 in src/client/kotlin/com/bibireden/playerex/ui/PlayerEXScreen.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/client/kotlin/com/bibireden/playerex/ui/PlayerEXScreen.kt#L100

The function switchPage has a comment. Prefer renaming the function giving it a more self-explanatory name.
val root = this.uiAdapter.rootComponent

val content = root.childById(FlowLayout::class, "content")!!

content.clearChildren()
content.child(pages[to].second)
}

private fun updateLevelUpButton() {
// val amount = levelAmount.value.toIntOrNull() ?: return
// val result = player.level + amount
//
// levelButton
// .active(player.experienceLevel >= PlayerEXUtil.getRequiredXpForLevel(player, result))
// .tooltip(Component.translatable("playerex.ui.level_button", PlayerEXUtil.getRequiredXpForLevel(player, result), amount, player.experienceLevel))
val amount = levelAmount.value.toIntOrNull() ?: return
val result = player.level + amount

levelButton
.active(player.experienceLevel >= PlayerEXUtil.getRequiredXpForLevel(player, result))
.tooltip(Component.translatable("playerex.ui.level_button", PlayerEXUtil.getRequiredXpForLevel(player, result), amount, player.experienceLevel))
}

private fun updateProgressBar() {
// var result = 0.0
// if (player.experienceLevel > 0) {
// 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()
var result = 0.0
if (player.experienceLevel > 0) {
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()
}

override fun build(rootComponent: FlowLayout) {
private fun updateCollapseState(content: FlowLayout, listLayout: FlowLayout, button: ButtonComponent) {
button.message = Component.literal(if (listCollapsed) "<" else ">").withStyle(Style.EMPTY.withBold(true))

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()
}

override fun build(rootComponent: FlowLayout) {
val listLayout = rootComponent.childById(FlowLayout::class, "list")!!
val contentLayout = rootComponent.childById(FlowLayout::class, "content")!!

PlayerEXMenuRegistry.get().forEach { (resource, clazz) ->
val instance = clazz.getDeclaredConstructor().newInstance()
instance.init(minecraft!!, this, player.component)
instance.build(contentLayout)
pages.add(Pair(resource, instance))
}

// val player = minecraft?.player ?: return
//
// val levelUpButton = rootComponent.childById(ButtonComponent::class, "level:button")!!
//
// updateLevelUpButton()
//
// levelAmount.setFilter(InputHelper::isUIntInput)
// levelAmount.onChanged().subscribe { updateLevelUpButton() }
//
// 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")!!
//
// PlayerEXMenuRegistry.get().forEach { (_, clazz) ->
// val instance = clazz.getDeclaredConstructor().newInstance()
// instance.init(minecraft!!, this, player.component)
// instance.build(content)
// pages.add(instance)
// }
//
// this.onLevelUpdated(player.level.toInt())
// this.onPagesUpdated()
//
// pageCounter.text(Component.nullToEmpty("${currentPage + 1}/${pages.size}"))
//
// content.clearChildren()
// content.child(pages[currentPage])
//
// previousPage.onPress {
// if (currentPage > 0) {
// currentPage--
// this.onPagesUpdated()
// }
// }
// nextPage.onPress {
// if (currentPage < pages.lastIndex) {
// currentPage++
// this.onPagesUpdated()
// }
// }
//
// levelUpButton.onPress {
// levelAmount.value.toIntOrNull()?.let { NetworkingChannels.MODIFY.clientHandle().send(NetworkingPackets.Level(it)) }
// }
//
// onLevelUpdated.subscribe { this.updateLevelUpButton() }
//
// exit.onPress { this.onClose() }
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")
)

val levelUpButton = rootComponent.childById(ButtonComponent::class, "level:button")!!
val exit = rootComponent.childById(ButtonComponent::class, "exit")!!


levelAmount.setFilter(InputHelper::isUIntInput)
levelAmount.onChanged().subscribe { updateLevelUpButton() }

onLevelUpdated(player.level.toInt())
onExperienceUpdated()

switchPage(0)

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() {
// updateLevelUpButton()
// updateProgressBar()
this.uiAdapter.rootComponent.childById(LabelComponent::class.java, "experience")!!.apply {
text(Component.literal(player.experienceLevel.toString()))
}
updateLevelUpButton()
updateProgressBar()
}

override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
Expand Down
10 changes: 10 additions & 0 deletions src/client/kotlin/com/bibireden/playerex/ui/PlayerEXTextures.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.bibireden.playerex.ui

import com.bibireden.playerex.PlayerEX
import org.jetbrains.annotations.ApiStatus

@ApiStatus.Internal
object PlayerEXTextures {

Check warning on line 7 in src/client/kotlin/com/bibireden/playerex/ui/PlayerEXTextures.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/client/kotlin/com/bibireden/playerex/ui/PlayerEXTextures.kt#L7

PlayerEXTextures is missing required documentation.
@JvmField
val RHOMBUS = PlayerEX.id("textures/ui/rhombus.png")
}
Loading

0 comments on commit b9b98b1

Please sign in to comment.