From 3738651e8294eb264e8103ea287c25980ea8d3d1 Mon Sep 17 00:00:00 2001
From: Bibi Reden <redwitblue@gmail.com>
Date: Mon, 12 Aug 2024 11:16:44 -0500
Subject: [PATCH 1/3] crowdin.yml

---
 crowdin.yml | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 crowdin.yml

diff --git a/crowdin.yml b/crowdin.yml
new file mode 100644
index 00000000..1df3d57b
--- /dev/null
+++ b/crowdin.yml
@@ -0,0 +1,3 @@
+files:
+  - source: /src/main/resources/assets/playerex/lang/en_us.json
+    translation: /src/main/resources/assets/playerex/lang/%locale%.json

From 5d3f63ab7d4541cdefcd701eff0cad78a5342dee Mon Sep 17 00:00:00 2001
From: Bibi Reden <redwitblue@gmail.com>
Date: Mon, 12 Aug 2024 11:31:18 -0500
Subject: [PATCH 2/3] Update crowdin.yml

---
 crowdin.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crowdin.yml b/crowdin.yml
index 1df3d57b..6fd0cd30 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -1,3 +1,3 @@
 files:
   - source: /src/main/resources/assets/playerex/lang/en_us.json
-    translation: /src/main/resources/assets/playerex/lang/%locale%.json
+    translation: /src/main/resources/assets/playerex/lang/%locale_with_underscore%.json

From 2f7137cb5bbd02b464eeafba8c7102f4b6519b13 Mon Sep 17 00:00:00 2001
From: Bibi Reden <redwitblue@gmail.com>
Date: Tue, 13 Aug 2024 00:15:35 -0500
Subject: [PATCH 3/3] Beta Release Preparation (#33)

* Make everything go through event invoker for SHOULD_DAMAGE

* UP DA Version

* Housekeeping, changing `data` extension -> `dataComponent`

* Force lazy singleton init (by adding to API)

* Add a color changer (default is gold still)

Along with visual options

* Implement new sorting algorithm for screens

* Component adjustments, fix Knockback Resistance

* Write contributor information, separate entries, stage version

* Fields are forced to be UInt or empty

* Deprecate predicates, made bar faster

* Crowdin

* Format translations

* amend

* [chore] cleanup data-driven attributes

* [chore] update data attributes version

* [chore] update CL

* [feat] update all attributes with new format system

* [fix] Applied `lifesteal` properly to attacker(s).

* [ref] Using map over if condition for evasion

* [ref] Trimming return guard @ `shouldDamage`

* [fix] resolved refund to be valid

* [ref] mapped `onCritAttack`

* [change] `AttributeButtonComponentType` -> `ButtonType`

* [chore] update CL and increase `max_health`
---
 CHANGELOG.md                                  |  17 ++-
 crowdin.yml                                   |   2 +-
 gradle.properties                             |   4 +-
 .../playerex/mixin/EntityRendererMixin.java   |   5 +-
 .../playerex/mixin/ItemStackMixin.java        |  43 ++-----
 .../playerex/mixin/LocalPlayerMixin.java      |   2 +-
 .../registry/PlayerEXMenuRegistry.java        |  20 ++-
 .../bibireden/playerex/ui/PlayerEXScreen.kt   |  28 ++--
 .../ui/components/AttributeComponent.kt       |   8 +-
 .../ui/components/AttributeListComponent.kt   |  25 +---
 .../components/AttributeListEntryComponent.kt |  21 +--
 .../buttons/AttributeButtonComponent.kt       |   8 +-
 .../playerex/ui/helper/InputHelper.kt         |   5 +
 .../ui/menus/PlayerEXAttributesMenu.kt        |  69 +++++-----
 .../playerex/ui/util/FormattingPredicates.kt  |   2 +
 .../playerex/mixin/LivingEntityMixin.java     |   8 +-
 .../bibireden/playerex/mixin/PlayerMixin.java |   4 +-
 .../playerex/mixin/ServerPlayerMixin.java     |   2 +-
 .../kotlin/com/bibireden/playerex/PlayerEX.kt |  14 +-
 .../bibireden/playerex/PlayerEXCommands.kt    |  12 +-
 .../com/bibireden/playerex/api/PlayerEXAPI.kt |  12 ++
 .../api/attribute/PlayerEXAttributes.kt       |  53 ++++----
 .../api/attribute/TradeSkillAttributes.kt     |  16 +--
 .../components/player/PlayerDataComponent.kt  |   1 -
 .../playerex/config/PlayerEXConfigModel.kt    |  35 +++--
 .../bibireden/playerex/ext/PlayerEntity.kt    |   9 +-
 .../playerex/factory/EventFactory.kt          |  51 +++-----
 .../resources/assets/playerex/lang/en_us.json |   5 +-
 .../data_attributes/functions/stock.json      |   4 +-
 .../data_attributes/overrides/minecraft.json  |  24 ++++
 .../overrides/ranged_weapon.json              |   9 ++
 .../overrides/spell_power.json                |  22 ++++
 .../data_attributes/overrides/stock.json      | 120 ++++--------------
 src/main/resources/fabric.mod.json            |  53 +++++++-
 34 files changed, 369 insertions(+), 344 deletions(-)
 create mode 100644 src/client/kotlin/com/bibireden/playerex/ui/helper/InputHelper.kt
 create mode 100644 src/main/resources/data/playerex/data_attributes/overrides/minecraft.json
 create mode 100644 src/main/resources/data/playerex/data_attributes/overrides/ranged_weapon.json
 create mode 100644 src/main/resources/data/playerex/data_attributes/overrides/spell_power.json

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0855ec7..d5003586 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,15 @@
+## Additions 🍎
+- Added the ability to customize the color of the label (default is still gold).
+
 ## Changes 🌽
-- Using the latest data-attributes with a supplied "stock" data-pack.
-- You can clear your config if you wish to use stock, or keep your config with all the entries.
-- Applied a patch for missing id's, which is an edge-case (will be labeled `unresolved:id`).
\ No newline at end of file
+- The UI has gone through a breaking change. recent alpha builds of WizardEX will not work with this version, though `alpha.3` will.
+- Switching to the beta channel as most components of the mod are stable.
+  - Required changes before release is the UI and re-adjusting the config for the last time.
+- Rebalanced knockback resistance to a proper 1% gain and displayed it properly.
+- Added author & contributor information according to who assisted in the development of the project's codebase.
+- Fixed `lifesteal` attribute not applying to attacker(s).
+- Fixed refund issues on the screen.
+
+## Later Objectives ⚡
+- Fixing tooltips
+- Adjusting more attributes
\ No newline at end of file
diff --git a/crowdin.yml b/crowdin.yml
index 6fd0cd30..a4249abf 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -1,3 +1,3 @@
 files:
   - source: /src/main/resources/assets/playerex/lang/en_us.json
-    translation: /src/main/resources/assets/playerex/lang/%locale_with_underscore%.json
+    translation: /src/main/resources/assets/playerex/lang/%locale_with_underscore%.json
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 2d8648de..9e4b33f5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -17,7 +17,7 @@ parchment_version=1.20.1:2023.09.03
 quilt_mappings_version=23
 
 # Mod Properties
-mod_version=4.0.0+1.20.1-alpha.14
+mod_version=4.0.0+1.20.1-beta.1
 maven_group=com.bibireden.playerex
 archives_base_name=playerex-directors-cut
 
@@ -31,7 +31,7 @@ ranged_weapon_api_version=1.1.2+1.20.1
 
 # in-house
 opc_version=2.0.0+1.20.1-beta.4-fabric
-data_attributes_version=2.0.0+1.20.1-beta.10-fabric
+data_attributes_version=2.0.0+1.20.1-beta.13-fabric
 
 # owo
 owo_version=0.11.2+1.20
diff --git a/src/client/java/com/bibireden/playerex/mixin/EntityRendererMixin.java b/src/client/java/com/bibireden/playerex/mixin/EntityRendererMixin.java
index 993889bf..fadcf8dd 100644
--- a/src/client/java/com/bibireden/playerex/mixin/EntityRendererMixin.java
+++ b/src/client/java/com/bibireden/playerex/mixin/EntityRendererMixin.java
@@ -25,7 +25,10 @@ abstract class EntityRendererMixin<T extends Entity> {
         if (playerex$shouldRenderLevel() && entity instanceof Player livingEntity) {
             Optional<Double> maybeLevel = DataAttributesAPI.getValue(PlayerEXAttributes.LEVEL, livingEntity);
             if (maybeLevel.isPresent()) {
-                text = text.copy().append(" ").append(Component.translatable("playerex.ui.nameplate.level", maybeLevel.get().intValue()).withStyle((style) -> style.withColor(0xFFAA00)));
+                text = text.copy().append(" ").append(
+                    Component.translatable("playerex.ui.nameplate.level", maybeLevel.get().intValue())
+                        .withStyle((style) -> style.withColor(PlayerEX.CONFIG.getVisualSettings().getNameplateColor().rgb()))
+                );
             }
         }
         return text;
diff --git a/src/client/java/com/bibireden/playerex/mixin/ItemStackMixin.java b/src/client/java/com/bibireden/playerex/mixin/ItemStackMixin.java
index e09db198..e79f7c6a 100644
--- a/src/client/java/com/bibireden/playerex/mixin/ItemStackMixin.java
+++ b/src/client/java/com/bibireden/playerex/mixin/ItemStackMixin.java
@@ -4,6 +4,7 @@
 import com.bibireden.playerex.PlayerEX;
 import com.bibireden.playerex.config.PlayerEXConfigModel;
 import com.google.common.collect.Multimap;
+import com.llamalad7.mixinextras.sugar.Local;
 import net.minecraft.ChatFormatting;
 import net.minecraft.network.chat.Component;
 import net.minecraft.network.chat.MutableComponent;
@@ -78,25 +79,17 @@ abstract class ItemStackMixin {
     private double playerex$modifyAdditionAttributeKnockback(double original) { return original / 10.0; }
 
     // todo: not sure about the implementation(s) here...
-    @Inject(method = "getTooltipLines", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z", ordinal = 7, shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILHARD)
+    @Inject(method = "getTooltipLines", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z", ordinal = 7, shift = At.Shift.AFTER))
     private void playerex$insertModifierEqualsTooltip(
         Player player, TooltipFlag context,
         CallbackInfoReturnable<List<Component>> info,
-        List<Component> list,
-        MutableComponent arg3,
-        int arg4,
-        EquipmentSlot[] arg5,
-        int arg6,
-        int arg7,
-        EquipmentSlot arg8,
-        Multimap<?, ?> arg9,
-        Iterator<?> arg10,
-        Map.Entry<Attribute, AttributeModifier> entry,
-        AttributeModifier entityAttributeModifier,
-        double arg13, double e
+        @Local List<Component> list,
+        @Local Map.Entry<Attribute, AttributeModifier> entry,
+        @Local AttributeModifier modifier,
+        @Local(ordinal = 1) double e
     ) {
         list.set(list.size() - 1, Component.literal(" ")
-            .append(Component.translatable("attribute.modifier.equals." + entityAttributeModifier.getOperation().toValue(), playerex$value(e, entry, entityAttributeModifier), Component.translatable(entry.getKey().getDescriptionId())))
+            .append(Component.translatable("attribute.modifier.equals." + modifier.getOperation().toValue(), playerex$value(e, entry, modifier), Component.translatable(entry.getKey().getDescriptionId())))
             .withStyle(ChatFormatting.DARK_GREEN)
         );
     }
@@ -122,27 +115,19 @@ abstract class ItemStackMixin {
         );
     }
 
-    @Inject(method = "getTooltipLines", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z", ordinal = 9, shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILHARD)
+    @Inject(method = "getTooltipLines", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z", ordinal = 9, shift = At.Shift.AFTER))
     private void playerex$insertModifierTakeTooltip(
         Player player, TooltipFlag context,
         CallbackInfoReturnable<List<Component>> info,
-        List<Component> list,
-        MutableComponent arg3,
-        int arg4,
-        EquipmentSlot[] arg5,
-        int arg6,
-        int arg7,
-        EquipmentSlot arg8,
-        Multimap<?, ?> arg9,
-        Iterator<?> arg10,
-        Map.Entry<Attribute, AttributeModifier> entry,
-        AttributeModifier entityAttributeModifier,
-        double arg13, double e
+        @Local List<Component> list,
+        @Local Map.Entry<Attribute, AttributeModifier> entry,
+        @Local AttributeModifier modifier,
+        @Local(ordinal = 1) double e
     ) {
         list.set(
             list.size() - 1,
-            Component.translatable("attribute.modifier.take." + entityAttributeModifier.getOperation().toValue(),
-            playerex$value(e, entry, entityAttributeModifier),
+            Component.translatable("attribute.modifier.take." + modifier.getOperation().toValue(),
+            playerex$value(e, entry, modifier),
             Component.translatable(entry.getKey().getDescriptionId())).withStyle(ChatFormatting.RED)
         );
     }
diff --git a/src/client/java/com/bibireden/playerex/mixin/LocalPlayerMixin.java b/src/client/java/com/bibireden/playerex/mixin/LocalPlayerMixin.java
index b5872e8c..1d52d384 100644
--- a/src/client/java/com/bibireden/playerex/mixin/LocalPlayerMixin.java
+++ b/src/client/java/com/bibireden/playerex/mixin/LocalPlayerMixin.java
@@ -15,7 +15,7 @@ abstract class LocalPlayerMixin {
     @Shadow @Final protected Minecraft minecraft;
 
     @Inject(method = "setExperienceValues", at = @At("TAIL"))
-    private void setExperience(CallbackInfo ci) {
+    private void playerex$setExperienceValues(CallbackInfo ci) {
         if (minecraft.screen instanceof PlayerEXScreen screen) screen.onExperienceUpdated();
     }
 }
diff --git a/src/client/java/com/bibireden/playerex/registry/PlayerEXMenuRegistry.java b/src/client/java/com/bibireden/playerex/registry/PlayerEXMenuRegistry.java
index 2488195b..32f497a7 100644
--- a/src/client/java/com/bibireden/playerex/registry/PlayerEXMenuRegistry.java
+++ b/src/client/java/com/bibireden/playerex/registry/PlayerEXMenuRegistry.java
@@ -29,12 +29,20 @@ public final class PlayerEXMenuRegistry {
      * which will be applied to the {@link PlayerEXScreen} as a page.
      */
     public static void register(ResourceLocation id, @NotNull Class<? extends MenuComponent> menu) {
-        ENTRIES.add(new Pair<>(id, menu));
-        ENTRIES.sort((a, b) -> {
-            var order = PRIORITY_ORDER.getOrDefault(a.getFirst().getNamespace(), Integer.MAX_VALUE);
-            var order2 = PRIORITY_ORDER.getOrDefault(b.getFirst().getNamespace(), Integer.MAX_VALUE);
-            return order.compareTo(order2);
-        });
+        Pair<ResourceLocation, Class<? extends MenuComponent>> pair = new Pair<>(id, menu);
+        Integer insertingPriority = PRIORITY_ORDER.get(pair.getFirst().toString());
+
+        if (!ENTRIES.isEmpty()) {
+            for (int i = 0, size = ENTRIES.size(); i < size; i++) {
+                Pair<ResourceLocation, Class<? extends MenuComponent>> entry = ENTRIES.get(i);
+                Integer priority = PRIORITY_ORDER.get(entry.getFirst().toString());
+                if (priority > insertingPriority) {
+                    ENTRIES.add(i, pair);
+                    return;
+                }
+            }
+        }
+        ENTRIES.add(pair);
     }
 
     @NotNull
diff --git a/src/client/kotlin/com/bibireden/playerex/ui/PlayerEXScreen.kt b/src/client/kotlin/com/bibireden/playerex/ui/PlayerEXScreen.kt
index f1d10c6c..d4412c51 100644
--- a/src/client/kotlin/com/bibireden/playerex/ui/PlayerEXScreen.kt
+++ b/src/client/kotlin/com/bibireden/playerex/ui/PlayerEXScreen.kt
@@ -2,7 +2,7 @@ package com.bibireden.playerex.ui
 
 import com.bibireden.playerex.PlayerEXClient
 import com.bibireden.playerex.components.player.IPlayerDataComponent
-import com.bibireden.playerex.ext.data
+import com.bibireden.playerex.ext.component
 import com.bibireden.playerex.ext.level
 import com.bibireden.playerex.networking.NetworkingChannels
 import com.bibireden.playerex.networking.NetworkingPackets
@@ -11,6 +11,7 @@ import com.bibireden.playerex.registry.PlayerEXMenuRegistry
 import com.bibireden.playerex.ui.components.MenuComponent
 import com.bibireden.playerex.ui.components.MenuComponent.OnLevelUpdated
 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.owo.ui.base.BaseUIModelScreen
@@ -40,7 +41,9 @@ class PlayerEXScreen : BaseUIModelScreen<FlowLayout>(FlowLayout::class.java, Dat
     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()
@@ -51,7 +54,7 @@ class PlayerEXScreen : BaseUIModelScreen<FlowLayout>(FlowLayout::class.java, Dat
     fun onLevelUpdated(level: Int) {
         val root = this.uiAdapter.rootComponent
 
-        root.childById(LabelComponent::class, "level:current")?.apply {
+        currentLevel.apply {
             text(Component.translatable("playerex.ui.current_level", player.level.toInt(), PlayerEXUtil.getRequiredXpForNextLevel(player)))
         }
 
@@ -77,10 +80,13 @@ class PlayerEXScreen : BaseUIModelScreen<FlowLayout>(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(
-                Component.literal("${player.data.skillPoints}").withStyle {
-                    it.withColor(when (player.data.skillPoints) {
-                        0 -> Colors.GRAY else -> Colors.SATURATED_BLUE
-                    })
+                Component.literal("${player.component.skillPoints}").withStyle {
+                    it.withColor(
+                        when (player.component.skillPoints) {
+                            0 -> Colors.GRAY
+                            else -> Colors.SATURATED_BLUE
+                        }
+                    )
                 }).append("]")
             )
         }
@@ -100,7 +106,7 @@ class PlayerEXScreen : BaseUIModelScreen<FlowLayout>(FlowLayout::class.java, Dat
         val amount = levelAmount.value.toIntOrNull() ?: return
         val result = player.level + amount
 
-        this.uiAdapter.rootComponent.childById(ButtonComponent::class, "level:button")!!
+        levelButton
             .active(player.experienceLevel >= PlayerEXUtil.getRequiredXpForLevel(player, result))
             .tooltip(Component.translatable("playerex.ui.level_button", PlayerEXUtil.getRequiredXpForLevel(player, result), amount, player.experienceLevel))
     }
@@ -112,7 +118,7 @@ class PlayerEXScreen : BaseUIModelScreen<FlowLayout>(FlowLayout::class.java, Dat
             result = Mth.clamp((player.experienceLevel.toDouble() / required) * 100, 0.0, 100.0)
         }
        footer.childById(BoxComponent::class, "progress")!!
-            .horizontalSizing().animate(1000, Easing.CUBIC, Sizing.fill(result.toInt())).forwards()
+            .horizontalSizing().animate(250, Easing.CUBIC, Sizing.fill(result.toInt())).forwards()
     }
 
     override fun build(rootComponent: FlowLayout) {
@@ -121,6 +127,8 @@ class PlayerEXScreen : BaseUIModelScreen<FlowLayout>(FlowLayout::class.java, Dat
         val levelUpButton = rootComponent.childById(ButtonComponent::class, "level:button")!!
 
         updateLevelUpButton()
+
+        levelAmount.setFilter(InputHelper::isUIntInput)
         levelAmount.onChanged().subscribe { updateLevelUpButton() }
 
         val previousPage = rootComponent.childById(ButtonComponent::class, "previous")!!
@@ -130,7 +138,7 @@ class PlayerEXScreen : BaseUIModelScreen<FlowLayout>(FlowLayout::class.java, Dat
 
         PlayerEXMenuRegistry.get().forEach { (_, clazz) ->
             val instance = clazz.getDeclaredConstructor().newInstance()
-            instance.init(minecraft!!, this, player.data)
+            instance.init(minecraft!!, this, player.component)
             instance.build(content)
             pages.add(instance)
         }
@@ -179,7 +187,7 @@ class PlayerEXScreen : BaseUIModelScreen<FlowLayout>(FlowLayout::class.java, Dat
         return super.keyPressed(keyCode, scanCode, modifiers)
     }
 
-    enum class AttributeButtonComponentType {
+    enum class ButtonType {
         Add,
         Remove;
 
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 216ddc3a..afbffaf5 100644
--- a/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeComponent.kt
+++ b/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeComponent.kt
@@ -7,7 +7,7 @@ import com.bibireden.data_attributes.api.attribute.StackingBehavior
 import com.bibireden.data_attributes.api.attribute.StackingFormula
 import com.bibireden.playerex.components.player.IPlayerDataComponent
 import com.bibireden.playerex.ext.id
-import com.bibireden.playerex.ui.PlayerEXScreen.AttributeButtonComponentType
+import com.bibireden.playerex.ui.PlayerEXScreen.ButtonType
 import com.bibireden.playerex.ui.components.buttons.AttributeButtonComponent
 import com.bibireden.playerex.ui.components.labels.AttributeLabelComponent
 import com.bibireden.playerex.ui.util.Colors
@@ -65,18 +65,16 @@ class AttributeComponent(private val attribute: Attribute, private val player: P
                 .id("${attribute.id}:label")
         )
 
-        child(AttributeButtonComponent(attribute, player, component, AttributeButtonComponentType.Remove))
+        child(AttributeButtonComponent(attribute, player, component, ButtonType.Remove))
         child(
             AttributeLabelComponent(attribute, player).also { label = it }
                 .horizontalSizing(Sizing.fill(34))
         )
-        child(AttributeButtonComponent(attribute, player, component, AttributeButtonComponentType.Add))
+        child(AttributeButtonComponent(attribute, player, component, ButtonType.Add))
 
         horizontalAlignment(HorizontalAlignment.RIGHT)
         verticalAlignment(VerticalAlignment.CENTER)
 
-        verticalAlignment(VerticalAlignment.CENTER)
-
         refresh()
     }
 }
\ No newline at end of file
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 9bb8468e..943e0537 100644
--- a/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeListComponent.kt
+++ b/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeListComponent.kt
@@ -5,31 +5,18 @@ 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.Sizing
-import net.minecraft.world.entity.ai.attributes.Attribute
 import net.minecraft.world.entity.player.Player
 import net.minecraft.network.chat.Component
+import kotlin.jvm.optionals.getOrNull
 
-private fun transform(array: List<Pair<EntityAttributeSupplier, FormattingPredicate>>): List<Pair<Attribute, FormattingPredicate>> {
-    val filtered = mutableListOf<Pair<Attribute, FormattingPredicate>>()
-    for ((attribute, pred) in array) {
-        if (attribute.get().isPresent) filtered.add(Pair(attribute.get().get(), pred))
-    }
-    return filtered
-}
-
-class AttributeListComponent(translationKey: String, private val player: Player, private val gimmie: List<Pair<EntityAttributeSupplier, FormattingPredicate>>) : FlowLayout(Sizing.fill(25), Sizing.content(), Algorithm.VERTICAL) {
+class AttributeListComponent(translationKey: String, private val player: Player, val attributes: List<EntityAttributeSupplier>) : FlowLayout(Sizing.fill(25), Sizing.content(), Algorithm.VERTICAL) {
     val entriesSection: FlowLayout
 
     init {
-        child(
-            Components.label(Component.translatable(translationKey))
-                .horizontalSizing(Sizing.fill(100))
-        )
+        child(Components.label(Component.translatable(translationKey)).horizontalSizing(Sizing.fill(100)))
         child(Components.box(Sizing.fill(100), Sizing.fixed(2)))
         entriesSection = Containers.verticalFlow(Sizing.fill(100), Sizing.content())
-            .apply {
-                gap(4)
-            }.also(::child)
+            .apply { gap(4) }.also(::child)
 
         gap(4)
         refresh()
@@ -37,8 +24,8 @@ class AttributeListComponent(translationKey: String, private val player: Player,
 
     fun refresh() {
         entriesSection.children().filterIsInstance<AttributeListEntryComponent>().forEach(::removeChild)
-        entriesSection.children(transform(gimmie).map {
-            Containers.horizontalScroll(Sizing.fill(100), Sizing.content(), AttributeListEntryComponent(it.first, player, it.second)).scrollbarThiccness(2)
+        entriesSection.children(attributes.mapNotNull { it.get().getOrNull() }.map {
+            Containers.horizontalScroll(Sizing.fill(100), Sizing.content(), AttributeListEntryComponent(it, player)).scrollbarThiccness(2)
         })
     }
 }
\ No newline at end of file
diff --git a/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeListEntryComponent.kt b/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeListEntryComponent.kt
index a9e125db..3ac64edc 100644
--- a/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeListEntryComponent.kt
+++ b/src/client/kotlin/com/bibireden/playerex/ui/components/AttributeListEntryComponent.kt
@@ -1,6 +1,7 @@
 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
@@ -11,11 +12,9 @@ import net.minecraft.network.chat.Component
 
 typealias FormattingPredicate = (Double) -> String
 
-class AttributeListEntryComponent(
-    val attribute: Attribute,
-    val player: Player,
-    private val formattingPredicate: FormattingPredicate
-) : LabelComponent(Component.empty()) {
+class AttributeListEntryComponent(val attribute: Attribute, val player: Player) : LabelComponent(Component.empty()) {
+    private val BASE_VALUE_FACTOR_IDS = setOf("ranged_weapon:haste")
+
     init {
         horizontalTextAlignment(HorizontalAlignment.CENTER)
         verticalTextAlignment(VerticalAlignment.CENTER)
@@ -24,13 +23,17 @@ class AttributeListEntryComponent(
     }
 
     fun refresh() {
+        val formattedValue = if (attribute.id.toString() in BASE_VALUE_FACTOR_IDS) {
+            // this is literally to handle an edge case.
+            val value = DataAttributesAPI.getValue(attribute, player).orElse(0.0)
+            attribute.`data_attributes$format`().function(attribute.defaultValue, attribute.defaultValue * 2, value)
+        }
+        else DataAttributesAPI.getFormattedValue(attribute, player)
+
         text(
             Component.translatable(attribute.descriptionId)
                 .append(": ")
-                .append(Component.literal(
-                    DataAttributesAPI.getValue(attribute, player).map { formattingPredicate(it) }
-                        .orElse("N/A")).withStyle { it.withColor(Colors.GOLD) }
-                )
+                .append(Component.literal(formattedValue).withStyle { it.withColor(Colors.GOLD) })
         )
     }
 }
\ 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 b98d7f79..0b1a724f 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
@@ -15,10 +15,10 @@ import net.minecraft.world.entity.ai.attributes.Attribute
 import net.minecraft.world.entity.player.Player
 import net.minecraft.network.chat.Component
 
-class AttributeButtonComponent(val attribute: Attribute, private val player: Player, private val component: IPlayerDataComponent, val type: PlayerEXScreen.AttributeButtonComponentType) : ButtonComponent(
+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 needed value to send to server
+        // reference text-box to get the necessary value to send to server
         it.parent()?.parent()?.childById(TextBoxComponent::class, "input")?.let { box ->
             val amount = box.value.toDoubleOrNull() ?: return@let
             val points = type.getPointsFromComponent(component)
@@ -41,8 +41,8 @@ class AttributeButtonComponent(val attribute: Attribute, private val player: Pla
        DataAttributesAPI.getValue(attribute, player).ifPresent { value ->
            val max = (attribute as IEntityAttribute).`data_attributes$max`();
            this.active(when (type) {
-               PlayerEXScreen.AttributeButtonComponentType.Add -> component.skillPoints > 0 && max > value
-               PlayerEXScreen.AttributeButtonComponentType.Remove -> component.refundablePoints > 0 && value > 0
+               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/helper/InputHelper.kt b/src/client/kotlin/com/bibireden/playerex/ui/helper/InputHelper.kt
new file mode 100644
index 00000000..4b1d135e
--- /dev/null
+++ b/src/client/kotlin/com/bibireden/playerex/ui/helper/InputHelper.kt
@@ -0,0 +1,5 @@
+package com.bibireden.playerex.ui.helper
+
+object InputHelper {
+    fun isUIntInput(str: String) = str.isEmpty() || str.toUIntOrNull() != null
+}
\ No newline at end of file
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 313c5ba0..c5109784 100644
--- a/src/client/kotlin/com/bibireden/playerex/ui/menus/PlayerEXAttributesMenu.kt
+++ b/src/client/kotlin/com/bibireden/playerex/ui/menus/PlayerEXAttributesMenu.kt
@@ -2,17 +2,15 @@ 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.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.*
 import com.bibireden.playerex.ui.components.buttons.AttributeButtonComponent
 import com.bibireden.playerex.ui.components.labels.AttributeLabelComponent
-import com.bibireden.playerex.ui.util.FormattingPredicates
+import com.bibireden.playerex.ui.helper.InputHelper
 import de.dafuqs.additionalentityattributes.AdditionalEntityAttributes
 import io.wispforest.owo.ui.component.Components
 import io.wispforest.owo.ui.component.TextBoxComponent
@@ -28,44 +26,42 @@ import org.jetbrains.annotations.ApiStatus
 
 @ApiStatus.Internal
 class PlayerEXAttributesMenu : MenuComponent(algorithm = Algorithm.HORIZONTAL) {
-    private val MELEE_COMBAT_STATS: List<Pair<EntityAttributeSupplier, FormattingPredicate>> = listOf(
-        EntityAttributeSupplier(Attributes.ATTACK_DAMAGE.id) to FormattingPredicates.NORMAL,
-        EntityAttributeSupplier(Attributes.ATTACK_SPEED.id) to FormattingPredicates.NORMAL,
-        EntityAttributeSupplier(PlayerEXAttributes.MELEE_CRITICAL_DAMAGE.id) to FormattingPredicates.PERCENTAGE_MULTIPLY,
-        EntityAttributeSupplier(PlayerEXAttributes.MELEE_CRITICAL_CHANCE.id) to FormattingPredicates.PERCENTAGE_MULTIPLY
+    private val MELEE_COMBAT_STATS: List<EntityAttributeSupplier> = listOf(
+        EntityAttributeSupplier(Attributes.ATTACK_DAMAGE.id),
+        EntityAttributeSupplier(Attributes.ATTACK_SPEED.id),
+        EntityAttributeSupplier(PlayerEXAttributes.MELEE_CRITICAL_DAMAGE.id),
+        EntityAttributeSupplier(PlayerEXAttributes.MELEE_CRITICAL_CHANCE.id)
     )
 
-    private val RANGED_COMBAT_STATS: List<Pair<EntityAttributeSupplier, FormattingPredicate>> = listOf(
-        EntityAttributeSupplier(PlayerEXAttributes.RANGED_CRITICAL_DAMAGE.id) to FormattingPredicates.PERCENTAGE_MULTIPLY,
-        EntityAttributeSupplier(PlayerEXAttributes.RANGED_CRITICAL_CHANCE.id) to FormattingPredicates.PERCENTAGE_MULTIPLY,
-        EntityAttributeSupplier(EntityAttributes_RangedWeapon.HASTE.id) to FormattingPredicates.fromBaseValue(EntityAttributes_RangedWeapon.HASTE.attribute, true),
-        EntityAttributeSupplier(EntityAttributes_RangedWeapon.DAMAGE.id) to FormattingPredicates.NORMAL,
+    private val RANGED_COMBAT_STATS: List<EntityAttributeSupplier> = listOf(
+        EntityAttributeSupplier(PlayerEXAttributes.RANGED_CRITICAL_DAMAGE.id),
+        EntityAttributeSupplier(PlayerEXAttributes.RANGED_CRITICAL_CHANCE.id),
+        EntityAttributeSupplier(EntityAttributes_RangedWeapon.HASTE.id),
+        EntityAttributeSupplier(EntityAttributes_RangedWeapon.DAMAGE.id),
     )
 
-    private val DEFENSE_COMBAT_STATS: List<Pair<EntityAttributeSupplier, FormattingPredicate>> = listOf(
-        EntityAttributeSupplier(Attributes.ARMOR.id) to FormattingPredicates.NORMAL,
-        EntityAttributeSupplier(AdditionalEntityAttributes.MAGIC_PROTECTION.id) to FormattingPredicates.NORMAL,
-        EntityAttributeSupplier(Attributes.ARMOR_TOUGHNESS.id) to FormattingPredicates.NORMAL,
-        EntityAttributeSupplier(Attributes.KNOCKBACK_RESISTANCE.id) to FormattingPredicates.PERCENTAGE_DIVIDE,
-        EntityAttributeSupplier(PlayerEXAttributes.EVASION.id) to FormattingPredicates.PERCENTAGE_MULTIPLY,
+    private val DEFENSE_COMBAT_STATS: List<EntityAttributeSupplier> = listOf(
+        EntityAttributeSupplier(Attributes.ARMOR.id),
+        EntityAttributeSupplier(AdditionalEntityAttributes.MAGIC_PROTECTION.id),
+        EntityAttributeSupplier(Attributes.ARMOR_TOUGHNESS.id),
+        EntityAttributeSupplier(Attributes.KNOCKBACK_RESISTANCE.id),
+        EntityAttributeSupplier(PlayerEXAttributes.EVASION.id),
     )
 
-    private val VITALITY_STATS: List<Pair<EntityAttributeSupplier, FormattingPredicate>> = listOf(
-        EntityAttributeSupplier(PlayerEXAttributes.HEALTH_REGENERATION.id) to FormattingPredicates.PERCENTAGE_MULTIPLY,
-        EntityAttributeSupplier(PlayerEXAttributes.HEAL_AMPLIFICATION.id) to FormattingPredicates.PERCENTAGE_MULTIPLY,
-        EntityAttributeSupplier(PlayerEXAttributes.LIFESTEAL.id) to FormattingPredicates.PERCENTAGE_MULTIPLY,
-        EntityAttributeSupplier(Attributes.MOVEMENT_SPEED.id) to FormattingPredicates.NORMAL,
+    private val VITALITY_STATS: List<EntityAttributeSupplier> = listOf(
+        EntityAttributeSupplier(PlayerEXAttributes.HEALTH_REGENERATION.id),
+        EntityAttributeSupplier(PlayerEXAttributes.HEAL_AMPLIFICATION.id),
+        EntityAttributeSupplier(PlayerEXAttributes.LIFESTEAL.id),
+        EntityAttributeSupplier(Attributes.MOVEMENT_SPEED.id),
     )
 
-    private val RESISTANCE_STATS: List<Pair<EntityAttributeSupplier, FormattingPredicate>> = listOf(
-        EntityAttributeSupplier(PlayerEXAttributes.FIRE_RESISTANCE.id) to FormattingPredicates.PERCENTAGE_MULTIPLY,
-        EntityAttributeSupplier(PlayerEXAttributes.FREEZE_RESISTANCE.id) to FormattingPredicates.PERCENTAGE_MULTIPLY,
-        EntityAttributeSupplier(PlayerEXAttributes.LIGHTNING_RESISTANCE.id) to FormattingPredicates.PERCENTAGE_MULTIPLY,
-        EntityAttributeSupplier(PlayerEXAttributes.POISON_RESISTANCE.id) to FormattingPredicates.PERCENTAGE_MULTIPLY,
+    private val RESISTANCE_STATS: List<EntityAttributeSupplier> = listOf(
+        EntityAttributeSupplier(PlayerEXAttributes.FIRE_RESISTANCE.id),
+        EntityAttributeSupplier(PlayerEXAttributes.FREEZE_RESISTANCE.id),
+        EntityAttributeSupplier(PlayerEXAttributes.LIGHTNING_RESISTANCE.id),
+        EntityAttributeSupplier(PlayerEXAttributes.POISON_RESISTANCE.id),
     )
 
-    private fun onLevelUpdate(level: Int) {}
-
     /** Whenever ANY attribute gets updated. */
     private fun onAttributeUpdate() {
         // refresh all attribute labels
@@ -84,11 +80,11 @@ class PlayerEXAttributesMenu : MenuComponent(algorithm = Algorithm.HORIZONTAL) {
             val result = it.value.toDoubleOrNull() ?: return@also
             this.forEachDescendant { descendant ->
                 if (descendant is AttributeButtonComponent) {
-                    val max = (descendant.attribute as IEntityAttribute).`data_attributes$max`()
+                    val max = descendant.attribute.`data_attributes$max`()
                     val current = DataAttributesAPI.getValue(descendant.attribute, player).orElse(0.0)
                     when (descendant.type) {
-                        PlayerEXScreen.AttributeButtonComponentType.Add -> descendant.active(result > 0 && component.skillPoints >= result && (current + result) <= max)
-                        PlayerEXScreen.AttributeButtonComponentType.Remove -> descendant.active(result > 0 && component.refundablePoints > 0 && (current - result > 0))
+                        PlayerEXScreen.ButtonType.Add -> descendant.active(result > 0 && component.skillPoints >= result && (current + result) <= max)
+                        PlayerEXScreen.ButtonType.Remove -> descendant.active(result > 0 && component.refundablePoints > 0 && (current - result >= 0))
                     }
                 }
             }
@@ -108,11 +104,12 @@ class PlayerEXAttributesMenu : MenuComponent(algorithm = Algorithm.HORIZONTAL) {
                     child(Components.label(Component.translatable("playerex.ui.category.primary_attributes")))
                     child(
                         Components.textBox(Sizing.fixed(27))
+                            .text("1")
                             .also {
                                 it.setMaxLength(4)
+                                it.setFilter(InputHelper::isUIntInput)
                                 it.onChanged().subscribe { onInputFieldUpdated(player, component) }
                             }
-                            .text("1")
                             .verticalSizing(Sizing.fixed(10))
                             .positioning(Positioning.relative(100, 0))
                             .id("input")
@@ -154,11 +151,9 @@ class PlayerEXAttributesMenu : MenuComponent(algorithm = Algorithm.HORIZONTAL) {
 
         padding(Insets.both(8, 8))
 
-        onLevelUpdate(player.level.toInt())
         onAttributeUpdate()
         onInputFieldUpdated(player, component)
 
-        onLevelUpdated.subscribe(::onLevelUpdate)
         onAttributeUpdated.subscribe { _, _ ->
             onAttributeUpdate()
             onInputFieldUpdated(player, component)
diff --git a/src/client/kotlin/com/bibireden/playerex/ui/util/FormattingPredicates.kt b/src/client/kotlin/com/bibireden/playerex/ui/util/FormattingPredicates.kt
index b8b7d45d..19d18055 100644
--- a/src/client/kotlin/com/bibireden/playerex/ui/util/FormattingPredicates.kt
+++ b/src/client/kotlin/com/bibireden/playerex/ui/util/FormattingPredicates.kt
@@ -4,6 +4,8 @@ import com.bibireden.playerex.ui.components.FormattingPredicate
 import net.minecraft.world.entity.ai.attributes.Attribute
 import kotlin.math.round
 
+@Suppress("UNUSED")
+@Deprecated("Use DataAttributes value formatting instead.", level = DeprecationLevel.WARNING)
 object FormattingPredicates {
     @JvmField
     val NORMAL: FormattingPredicate = { "%.2f".format(it) }
diff --git a/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java b/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java
index d65c0e5f..19f2648a 100644
--- a/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java
+++ b/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java
@@ -13,8 +13,6 @@
 
 @Mixin(LivingEntity.class)
 public abstract class LivingEntityMixin {
-    @Unique
-    final private int TICKS_UNTIL_RESET = 20;
 
     @Unique
     private int playerex_ticks;
@@ -32,7 +30,8 @@ public abstract class LivingEntityMixin {
 
     @Inject(method = "tick", at = @At("TAIL"))
     private void playerex$tick(CallbackInfo ci) {
-        if (this.playerex_ticks < this.TICKS_UNTIL_RESET) {
+        final int TICKS_UNTIL_RESET = 20;
+        if (this.playerex_ticks < TICKS_UNTIL_RESET) {
             this.playerex_ticks++;
         }
         else {
@@ -47,7 +46,6 @@ public abstract class LivingEntityMixin {
 
     @ModifyReturnValue(method = "hurt", at = @At("RETURN"))
     private boolean playerex$damage(boolean original, DamageSource source, float damage) {
-        boolean cancelled = LivingEntityEvents.SHOULD_DAMAGE.invoker().shouldDamage((LivingEntity) (Object) this, source, damage);
-        return cancelled && original;
+        return LivingEntityEvents.SHOULD_DAMAGE.invoker().shouldDamage((LivingEntity) (Object) this, source, damage);
     }
 }
diff --git a/src/main/java/com/bibireden/playerex/mixin/PlayerMixin.java b/src/main/java/com/bibireden/playerex/mixin/PlayerMixin.java
index 01678415..62ad7a04 100644
--- a/src/main/java/com/bibireden/playerex/mixin/PlayerMixin.java
+++ b/src/main/java/com/bibireden/playerex/mixin/PlayerMixin.java
@@ -10,12 +10,12 @@
 @Mixin(Player.class)
 public abstract class PlayerMixin {
     @ModifyVariable(method = "attack", at = @At("STORE"), name = "bl3", ordinal = 2)
-    private boolean playerex_attack(boolean bl3, Entity target) {
+    private boolean playerex$attack(boolean bl3, Entity target) {
         return PlayerEntityEvents.SHOULD_CRITICAL.invoker().shouldCritical((Player)(Object) this, target, bl3);
     }
 
     @ModifyVariable(method = "attack", at = @At(value = "STORE", ordinal = 2), name = "f", ordinal = 0)
-    private float playerex_attack(float f, Entity target) {
+    private float playerex$attack(float f, Entity target) {
         return PlayerEntityEvents.ON_CRITICAL.invoker().onCriticalDamage((Player) (Object) this, target, f);
     }
 }
diff --git a/src/main/java/com/bibireden/playerex/mixin/ServerPlayerMixin.java b/src/main/java/com/bibireden/playerex/mixin/ServerPlayerMixin.java
index eb4c7b5b..43dea03f 100644
--- a/src/main/java/com/bibireden/playerex/mixin/ServerPlayerMixin.java
+++ b/src/main/java/com/bibireden/playerex/mixin/ServerPlayerMixin.java
@@ -22,7 +22,7 @@ public ServerPlayerMixin(Level level, BlockPos pos, float yRot, GameProfile game
     }
 
     @Inject(method = "giveExperienceLevels", at = @At("TAIL"))
-    private void addExperienceLevels(int levels, CallbackInfo ci) {
+    private void playerex$giveExperienceLevels(int levels, CallbackInfo ci) {
         PlayerDataComponent component = (PlayerDataComponent) this.getComponent(PlayerEXComponents.PLAYER_DATA);
 
         if (this.experienceLevel >= PlayerEXUtil.getRequiredXpForNextLevel(this)) {
diff --git a/src/main/kotlin/com/bibireden/playerex/PlayerEX.kt b/src/main/kotlin/com/bibireden/playerex/PlayerEX.kt
index c6a8af51..0ded06c7 100644
--- a/src/main/kotlin/com/bibireden/playerex/PlayerEX.kt
+++ b/src/main/kotlin/com/bibireden/playerex/PlayerEX.kt
@@ -8,12 +8,11 @@ import com.bibireden.playerex.api.PlayerEXAPI
 import com.bibireden.playerex.api.PlayerEXCachedKeys
 import com.bibireden.playerex.api.PlayerEXCachedKeys.Level
 import com.bibireden.playerex.api.attribute.PlayerEXAttributes
-import com.bibireden.playerex.api.attribute.TradeSkillAttributes
 import com.bibireden.playerex.api.event.LivingEntityEvents
 import com.bibireden.playerex.api.event.PlayerEXSoundEvents
 import com.bibireden.playerex.api.event.PlayerEntityEvents
-import com.bibireden.playerex.components.PlayerEXComponents
 import com.bibireden.playerex.config.PlayerEXConfig
+import com.bibireden.playerex.ext.component
 import com.bibireden.playerex.factory.*
 import com.bibireden.playerex.networking.NetworkingChannels
 import com.bibireden.playerex.networking.NetworkingPackets
@@ -41,10 +40,6 @@ object PlayerEX : ModInitializer {
 	@JvmField
 	val CONFIG = PlayerEXConfig.createAndLoad()
 
-	// this is literally here to initialize the singletons...
-	val PRIMARY_ATTRIBUTE_IDS = PlayerEXAttributes.PRIMARY_ATTRIBUTE_IDS
-	val TRADE_SKILL_IDS = TradeSkillAttributes.IDS
-
 	fun id(path: String) = ResourceLocation.tryBuild(MOD_ID, path)!!
 
 	private val gimmick = listOf(
@@ -56,15 +51,16 @@ object PlayerEX : ModInitializer {
 		NetworkingChannels.NOTIFICATIONS.registerClientboundDeferred(NetworkingPackets.Notify::class.java)
 
 		NetworkingChannels.MODIFY.registerServerbound(NetworkingPackets.Update::class) { (type, id, amount), ctx ->
+			val component = ctx.player.component
 			EntityAttributeSupplier(id).get().ifPresent {
 				when (type) {
-					UpdatePacketType.Skill -> PlayerEXComponents.PLAYER_DATA.get(ctx.player).skillUp(it, amount)
-					UpdatePacketType.Refund -> PlayerEXComponents.PLAYER_DATA.get(ctx.player).refund(it, amount)
+					UpdatePacketType.Skill -> component.skillUp(it, amount)
+					UpdatePacketType.Refund -> component.refund(it, amount)
 				}
 			}
 		}
 		NetworkingChannels.MODIFY.registerServerbound(NetworkingPackets.Level::class) { (amount), ctx ->
-			PlayerEXComponents.PLAYER_DATA.get(ctx.player).levelUp(amount)
+			ctx.player.component.levelUp(amount)
 		}
 
 		CommandRegistrationCallback.EVENT.register(PlayerEXCommands::register)
diff --git a/src/main/kotlin/com/bibireden/playerex/PlayerEXCommands.kt b/src/main/kotlin/com/bibireden/playerex/PlayerEXCommands.kt
index c163d29c..bf10133c 100644
--- a/src/main/kotlin/com/bibireden/playerex/PlayerEXCommands.kt
+++ b/src/main/kotlin/com/bibireden/playerex/PlayerEXCommands.kt
@@ -6,7 +6,7 @@ import com.bibireden.data_attributes.api.attribute.IEntityAttribute
 import com.bibireden.playerex.api.attribute.PlayerEXAttributes
 import com.bibireden.playerex.api.attribute.TradeSkillAttributes
 import com.bibireden.playerex.components.PlayerEXComponents
-import com.bibireden.playerex.ext.data
+import com.bibireden.playerex.ext.component
 import com.mojang.brigadier.CommandDispatcher
 import com.mojang.brigadier.arguments.IntegerArgumentType
 import com.mojang.brigadier.builder.RequiredArgumentBuilder
@@ -119,7 +119,7 @@ object PlayerEXCommands {
 
     private fun executeRefundGetCommand(ctx: Context): Int {
         val player = EntityArgument.getPlayer(ctx, "player")
-        ctx.source.sendSuccess({ Component.translatable("playerex.command.refund.get", player.name, player.data.refundablePoints) }, false)
+        ctx.source.sendSuccess({ Component.translatable("playerex.command.refund.get", player.name, player.component.refundablePoints) }, false)
         return 1
     }
 
@@ -132,7 +132,7 @@ object PlayerEXCommands {
             val attribute = supplier.get().get()
             val computed = Mth.clamp(amount, 0, it.toInt())
 
-            if (player.data.refund(attribute, computed)) {
+            if (player.component.refund(attribute, computed)) {
                 ctx.source.sendSuccess({ Component.translatable("playerex.command.refunded", amount, Component.translatable(attribute.descriptionId), player.name) }, false)
                 ctx.source.sendSuccess(updatedValueText(attribute, it - amount), false)
                 1
@@ -146,7 +146,7 @@ object PlayerEXCommands {
     private fun executeRefundAddCommand(ctx: Context, amount: Int = 1): Int {
         val player = EntityArgument.getPlayer(ctx, "player")
 
-        player.data.addRefundablePoints(amount)
+        player.component.addRefundablePoints(amount)
 
         ctx.source.sendSuccess({ Component.translatable("playerex.command.refund.add", amount, player.name) }, false)
 
@@ -161,7 +161,7 @@ object PlayerEXCommands {
             val attribute = supplier.get().get()
             val computed = Mth.clamp(amount, 0, (attribute as IEntityAttribute).`data_attributes$max`().toInt() - it.toInt())
 
-            if (player.data.skillUp(attribute, computed, true)) {
+            if (player.component.skillUp(attribute, computed, true)) {
                 ctx.source.sendSuccess({ Component.translatable("playerex.command.skill_up", computed, Component.translatable(attribute.descriptionId), player.name) }, false)
                 ctx.source.sendSuccess(updatedValueText(attribute, it + computed), false)
                 1
@@ -180,7 +180,7 @@ object PlayerEXCommands {
             val attribute = PlayerEXAttributes.LEVEL
             val computed = Mth.clamp(amount, 0, (attribute as IEntityAttribute).`data_attributes$max`().toInt() - value.toInt())
 
-            if (!player.data.levelUp(computed, true)) {
+            if (!player.component.levelUp(computed, true)) {
                 // todo: err message, for now just -1
                 return@map -1
             }
diff --git a/src/main/kotlin/com/bibireden/playerex/api/PlayerEXAPI.kt b/src/main/kotlin/com/bibireden/playerex/api/PlayerEXAPI.kt
index 59ba3b88..247c3b92 100644
--- a/src/main/kotlin/com/bibireden/playerex/api/PlayerEXAPI.kt
+++ b/src/main/kotlin/com/bibireden/playerex/api/PlayerEXAPI.kt
@@ -1,10 +1,22 @@
 package com.bibireden.playerex.api
 
+import com.bibireden.playerex.api.attribute.PlayerEXAttributes
+import com.bibireden.playerex.api.attribute.TradeSkillAttributes
 import com.bibireden.playerex.api.damage.DamageFunction
 import com.bibireden.playerex.api.damage.DamagePredicate
 import com.bibireden.playerex.registry.*
+import net.minecraft.resources.ResourceLocation
 
+@Suppress("UNUSED")
 object PlayerEXAPI {
+    /** Contains the attribute ids that are "skills", and are present on the main screens first page. */
+    @JvmField
+    val PRIMARY_ATTRIBUTE_IDS: Collection<ResourceLocation> = PlayerEXAttributes.PRIMARY_ATTRIBUTE_IDS
+    
+    /** Passive trade skill ids. */
+    @JvmField
+    val TRADE_SKILL_IDS: Collection<ResourceLocation> = TradeSkillAttributes.IDS
+
     /**
      * Registers a damage modification condition that is applied to living entities
      * under specific circumstances.
diff --git a/src/main/kotlin/com/bibireden/playerex/api/attribute/PlayerEXAttributes.kt b/src/main/kotlin/com/bibireden/playerex/api/attribute/PlayerEXAttributes.kt
index 78ecd556..b31236ef 100644
--- a/src/main/kotlin/com/bibireden/playerex/api/attribute/PlayerEXAttributes.kt
+++ b/src/main/kotlin/com/bibireden/playerex/api/attribute/PlayerEXAttributes.kt
@@ -8,81 +8,78 @@ import net.minecraft.resources.ResourceLocation
 import net.minecraft.world.entity.ai.attributes.RangedAttribute
 
 object PlayerEXAttributes {
+    @JvmField
+    val PRIMARY_ATTRIBUTE_IDS: Set<ResourceLocation>
+
     @JvmField
     val LEVEL = register("level", 0.0, 0.0, 100.0)
 
     @JvmField
-    val CONSTITUTION = register("constitution", 0.0, 0.0, 100.0);
+    val CONSTITUTION = register("constitution", 0.0, 0.0, 100.0)
 
     @JvmField
-    val STRENGTH = register("strength", 0.0, 0.0, 100.0);
+    val STRENGTH = register("strength", 0.0, 0.0, 100.0)
 
     @JvmField
-    val DEXTERITY = register("dexterity", 0.0, 0.0, 100.0);
+    val DEXTERITY = register("dexterity", 0.0, 0.0, 100.0)
 
     @JvmField
-    val INTELLIGENCE = register("intelligence", 0.0, 0.0, 100.0);
+    val INTELLIGENCE = register("intelligence", 0.0, 0.0, 100.0)
 
     @JvmField
-    val LUCKINESS = register("luckiness", 0.0, 0.0, 100.0);
+    val LUCKINESS = register("luckiness", 0.0, 0.0, 100.0)
 
     @JvmField
     val FOCUS = register("focus", 0.0, 0.0, 100.0)
 
     @JvmField
-    val HEALTH_REGENERATION = register("health_regeneration", 0.0, 0.0, 100.0);
+    val HEALTH_REGENERATION = register("health_regeneration", 0.0, 0.0, 1.0)
 
     @JvmField
-    val HEAL_AMPLIFICATION = register("heal_amplification", 0.0, 0.0, 100.0);
+    val HEAL_AMPLIFICATION = register("heal_amplification", 0.0, 0.0, 1.0)
 
     @JvmField
-    val LIFESTEAL = register("lifesteal", 0.0, 0.0, 100.0);
+    val LIFESTEAL = register("lifesteal", 0.0, 0.0, 1.0)
 
     @JvmField
-    val MELEE_CRITICAL_DAMAGE = register("melee_crit_damage", 0.0, 0.0, 1.0);
+    val BREAKING_SPEED = register("breaking_speed", 0.0, 0.0, 100.0)
 
     @JvmField
-    val MELEE_CRITICAL_CHANCE = register("melee_crit_chance", 0.0, 0.0, 1.0);
+    val FIRE_RESISTANCE = register("fire_resistance", 0.0, 0.0, 1.0)
 
     @JvmField
-    val BREAKING_SPEED = register("breaking_speed", 0.0, 0.0, 100.0);
+    val FREEZE_RESISTANCE = register("freeze_resistance", 0.0, 0.0, 1.0)
 
     @JvmField
-    val FIRE_RESISTANCE = register("fire_resistance", 0.0, 0.0, 1.0);
+    val LIGHTNING_RESISTANCE = register("lightning_resistance", 0.0, 0.0, 1.0)
 
     @JvmField
-    val FREEZE_RESISTANCE = register("freeze_resistance", 0.0, 0.0, 1.0);
+    val WITHER_RESISTANCE = register("wither_resistance", 0.0, 0.0, 1.0)
 
     @JvmField
-    val LIGHTNING_RESISTANCE = register("lightning_resistance", 0.0, 0.0, 1.0);
+    val POISON_RESISTANCE = register("poison_resistance", 0.0, 0.0, 1.0)
 
     @JvmField
-    val WITHER_RESISTANCE = register("wither_resistance", 0.0, 0.0, 1.0);
+    val EVASION = register("evasion", 0.0, 0.0, 1.0)
 
     @JvmField
-    val POISON_RESISTANCE = register("poison_resistance", 0.0, 0.0, 1.0);
+    val MELEE_CRITICAL_CHANCE = register("melee_crit_chance", 0.0, 0.0, 1.0)
 
     @JvmField
-    val EVASION = register("evasion", 0.0, 0.0, 100.0);
+    val MELEE_CRITICAL_DAMAGE = register("melee_crit_damage", 0.0, 0.0, 1_000_000.0)
 
     @JvmField
     val RANGED_CRITICAL_CHANCE = register("ranged_crit_chance", 0.0, 0.0, 1.0)
 
     @JvmField
-    val RANGED_CRITICAL_DAMAGE = register("ranged_crit_damage", 0.0, 0.0, 1000000.0)
+    val RANGED_CRITICAL_DAMAGE = register("ranged_crit_damage", 0.0, 0.0, 1_000_000.0)
 
     fun register(path: String, base: Double, min: Double, max: Double): RangedAttribute {
         val attribute = RangedAttribute("attribute.name.${PlayerEX.MOD_ID}.$path", base, min, max)
         return Registry.register(BuiltInRegistries.ATTRIBUTE, ResourceLocation.tryBuild(PlayerEX.MOD_ID, path)!!, attribute)
     }
-    
-    @JvmField
-    val PRIMARY_ATTRIBUTE_IDS: Set<ResourceLocation> = setOf(
-        CONSTITUTION.id,
-        STRENGTH.id,
-        DEXTERITY.id,
-        INTELLIGENCE.id,
-        LUCKINESS.id,
-        FOCUS.id,
-    )
+
+    init {
+        PRIMARY_ATTRIBUTE_IDS = setOf(CONSTITUTION.id, STRENGTH.id, DEXTERITY.id, INTELLIGENCE.id, LUCKINESS.id, FOCUS.id)
+    }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/bibireden/playerex/api/attribute/TradeSkillAttributes.kt b/src/main/kotlin/com/bibireden/playerex/api/attribute/TradeSkillAttributes.kt
index 950f0137..9f0de9f5 100644
--- a/src/main/kotlin/com/bibireden/playerex/api/attribute/TradeSkillAttributes.kt
+++ b/src/main/kotlin/com/bibireden/playerex/api/attribute/TradeSkillAttributes.kt
@@ -5,6 +5,9 @@ import com.bibireden.playerex.ext.id
 import net.minecraft.resources.ResourceLocation
 
 object TradeSkillAttributes {
+    @JvmField
+    val IDS: Set<ResourceLocation>
+    
     @JvmField
     val MINING = register("mining", 0.0, 0.0, 100.0);
 
@@ -26,14 +29,7 @@ object TradeSkillAttributes {
     @JvmField
     val FARMING = register("farming", 0.0, 0.0, 100.0);
 
-    @JvmField
-    val IDS: Set<ResourceLocation> = setOf(
-        MINING.id,
-        ALCHEMY.id,
-        FISHING.id,
-        FARMING.id,
-        LOGGING.id,
-        ENCHANTING.id,
-        ENCHANTING.id
-    )
+    init {
+        IDS = setOf(MINING.id, ALCHEMY.id, FISHING.id, FARMING.id, LOGGING.id, ENCHANTING.id, ENCHANTING.id)
+    }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/bibireden/playerex/components/player/PlayerDataComponent.kt b/src/main/kotlin/com/bibireden/playerex/components/player/PlayerDataComponent.kt
index bcad87c6..562abf68 100644
--- a/src/main/kotlin/com/bibireden/playerex/components/player/PlayerDataComponent.kt
+++ b/src/main/kotlin/com/bibireden/playerex/components/player/PlayerDataComponent.kt
@@ -2,7 +2,6 @@ package com.bibireden.playerex.components.player
 
 import com.bibireden.data_attributes.api.DataAttributesAPI
 import com.bibireden.data_attributes.api.attribute.IEntityAttribute
-import com.bibireden.data_attributes.api.attribute.IEntityAttributeInstance
 import com.bibireden.data_attributes.endec.Endecs
 import com.bibireden.data_attributes.endec.nbt.NbtDeserializer
 import com.bibireden.data_attributes.endec.nbt.NbtSerializer
diff --git a/src/main/kotlin/com/bibireden/playerex/config/PlayerEXConfigModel.kt b/src/main/kotlin/com/bibireden/playerex/config/PlayerEXConfigModel.kt
index eb344453..46f72688 100644
--- a/src/main/kotlin/com/bibireden/playerex/config/PlayerEXConfigModel.kt
+++ b/src/main/kotlin/com/bibireden/playerex/config/PlayerEXConfigModel.kt
@@ -1,9 +1,10 @@
 package com.bibireden.playerex.config
 
 import com.bibireden.playerex.PlayerEX
-import io.wispforest.owo.config.Option
+import io.wispforest.owo.config.Option.SyncMode
 
 import io.wispforest.owo.config.annotation.*
+import io.wispforest.owo.ui.core.Color
 
 @Suppress("UNUSED")
 @Modmenu(modId = PlayerEX.MOD_ID)
@@ -11,26 +12,26 @@ import io.wispforest.owo.config.annotation.*
 class PlayerEXConfigModel {
     @SectionHeader("client_options")
 
-    @Sync(Option.SyncMode.NONE)
+    @Sync(SyncMode.NONE)
     @JvmField
     var tooltip: Tooltip = Tooltip.Vanilla
 
-    @Sync(Option.SyncMode.NONE)
+    @Sync(SyncMode.NONE)
     @JvmField
     var showLevelOnNameplates: Boolean = true
 
     data class SoundSettings(
-        @Sync(Option.SyncMode.NONE)
+        @Sync(SyncMode.NONE)
         @JvmField
         @RangeConstraint(min = 0.0, max = 150.0)
         var levelUpVolume: Int = 100,
 
-        @Sync(Option.SyncMode.NONE)
+        @Sync(SyncMode.NONE)
         @JvmField
         @RangeConstraint(min = 0.0, max = 150.0)
         var skillUpVolume: Int = 100,
 
-        @Sync(Option.SyncMode.NONE)
+        @Sync(SyncMode.NONE)
         @JvmField
         @RangeConstraint(min = 0.0, max = 150.0)
         var refundVolume: Int = 100
@@ -38,20 +39,28 @@ class PlayerEXConfigModel {
 
     @JvmField @Nest var soundSettings = SoundSettings()
 
+    data class VisualSettings(
+        @Sync(SyncMode.NONE)
+        @JvmField
+        var nameplateColor: Color = Color.ofRgb(0xFFAA00),
+    )
+
+    @JvmField @Nest var visualSettings = VisualSettings()
+
     @SectionHeader("server_options")
-    @Sync(Option.SyncMode.OVERRIDE_CLIENT)
+    @Sync(SyncMode.OVERRIDE_CLIENT)
     @JvmField
     var resetOnDeath: Boolean = false
 
-    @Sync(Option.SyncMode.OVERRIDE_CLIENT)
+    @Sync(SyncMode.OVERRIDE_CLIENT)
     @JvmField
     var disableUI: Boolean = false
 
-    @Sync(Option.SyncMode.OVERRIDE_CLIENT)
+    @Sync(SyncMode.OVERRIDE_CLIENT)
     @JvmField
     var skillPointsPerLevelUp: Int = 1
 
-    @Sync(Option.SyncMode.OVERRIDE_CLIENT)
+    @Sync(SyncMode.OVERRIDE_CLIENT)
     @JvmField
     @Hook
     var levelFormula: String = "stairs(x,0.2,2.4,17,10,25)"
@@ -59,15 +68,15 @@ class PlayerEXConfigModel {
 //    @JvmField
 //    var expression: Expression
 
-    @Sync(Option.SyncMode.OVERRIDE_CLIENT)
+    @Sync(SyncMode.OVERRIDE_CLIENT)
     @JvmField
     var restorativeForceTicks: Int = 600
 
-    @Sync(Option.SyncMode.OVERRIDE_CLIENT)
+    @Sync(SyncMode.OVERRIDE_CLIENT)
     @JvmField
     var restorativeForceMultiplier: Int = 110
 
-    @Sync(Option.SyncMode.OVERRIDE_CLIENT)
+    @Sync(SyncMode.OVERRIDE_CLIENT)
     @JvmField
     var expNegationFactor: Int = 95
 
diff --git a/src/main/kotlin/com/bibireden/playerex/ext/PlayerEntity.kt b/src/main/kotlin/com/bibireden/playerex/ext/PlayerEntity.kt
index 0b88c9c9..fe2bf149 100644
--- a/src/main/kotlin/com/bibireden/playerex/ext/PlayerEntity.kt
+++ b/src/main/kotlin/com/bibireden/playerex/ext/PlayerEntity.kt
@@ -4,15 +4,10 @@ import com.bibireden.data_attributes.api.DataAttributesAPI
 import com.bibireden.playerex.api.attribute.PlayerEXAttributes
 import com.bibireden.playerex.components.PlayerEXComponents
 import com.bibireden.playerex.components.player.IPlayerDataComponent
-import com.bibireden.playerex.util.PlayerEXUtil
 import net.minecraft.world.entity.player.Player
 
 val Player.level: Double
     get() = DataAttributesAPI.getValue(PlayerEXAttributes.LEVEL, this).orElse(1.0)
 
-val Player.data: IPlayerDataComponent
-    get() = this.getComponent(PlayerEXComponents.PLAYER_DATA)
-
-fun Player.canLevelUp(amount: Int = 1): Boolean {
-    return this.experienceLevel >= PlayerEXUtil.getRequiredXpForLevel(this, this.level + amount)
-}
\ No newline at end of file
+val Player.component: IPlayerDataComponent
+    get() = this.getComponent(PlayerEXComponents.PLAYER_DATA)
\ No newline at end of file
diff --git a/src/main/kotlin/com/bibireden/playerex/factory/EventFactory.kt b/src/main/kotlin/com/bibireden/playerex/factory/EventFactory.kt
index 82860c96..7c03ff1c 100644
--- a/src/main/kotlin/com/bibireden/playerex/factory/EventFactory.kt
+++ b/src/main/kotlin/com/bibireden/playerex/factory/EventFactory.kt
@@ -3,7 +3,7 @@ package com.bibireden.playerex.factory
 import com.bibireden.data_attributes.api.DataAttributesAPI
 import com.bibireden.playerex.PlayerEX
 import com.bibireden.playerex.api.attribute.PlayerEXAttributes
-import com.bibireden.playerex.components.PlayerEXComponents
+import com.bibireden.playerex.ext.component
 import com.bibireden.playerex.registry.DamageModificationRegistry
 import net.minecraft.server.level.ServerPlayer
 import net.minecraft.world.damagesource.DamageSource
@@ -16,26 +16,26 @@ import net.minecraft.world.entity.projectile.AbstractArrow
 object EventFactory {
     fun reset(oldPlayer: ServerPlayer, newPlayer: ServerPlayer, isAlive: Boolean)
     {
-        PlayerEXComponents.PLAYER_DATA.get(newPlayer).reset(if (PlayerEX.CONFIG.resetOnDeath) 0 else 100)
+        newPlayer.component.reset(if (PlayerEX.CONFIG.resetOnDeath) 0 else 100)
     }
 
-    fun healed(livingEntity: LivingEntity, amount: Float): Float
+    fun healed(entity: LivingEntity, amount: Float): Float
     {
-        return DataAttributesAPI.getValue(PlayerEXAttributes.HEAL_AMPLIFICATION, livingEntity).map { (amount * (1.0 + it)).toFloat() }.orElse(amount)
+        return DataAttributesAPI.getValue(PlayerEXAttributes.HEAL_AMPLIFICATION, entity).map { (amount * (1 + it)).toFloat() }.orElse(amount)
     }
 
-    fun healthRegeneration(livingEntity: LivingEntity)
+    fun healthRegeneration(entity: LivingEntity)
     {
-        if (!livingEntity.level().isClientSide()) {
-            val healthRegenerationOption = DataAttributesAPI.getValue(PlayerEXAttributes.HEALTH_REGENERATION, livingEntity)
+        if (!entity.level().isClientSide()) {
+            val healthRegenerationOption = DataAttributesAPI.getValue(PlayerEXAttributes.HEALTH_REGENERATION, entity)
 
             if (healthRegenerationOption.isPresent)
             {
                 val healthRegeneration = healthRegenerationOption.get()
 
-                if (healthRegeneration > 0.0 && livingEntity.health < livingEntity.maxHealth)
+                if (healthRegeneration > 0.0 && entity.health < entity.maxHealth)
                 {
-                    livingEntity.heal(healthRegeneration.toFloat())
+                    entity.heal(healthRegeneration.toFloat())
                 }
 
                 return
@@ -62,44 +62,29 @@ object EventFactory {
 
     fun shouldDamage(livingEntity: LivingEntity, source: DamageSource, original: Float): Boolean
     {
-        if (original == 0.0F)
-        {
-            return true
-        }
+        if (original == 0.0F) return true
 
         val origin: Entity? = source.directEntity
         val attacker: Entity? = source.entity
 
         if (attacker is LivingEntity && (origin is LivingEntity || origin is AbstractArrow))
         {
-            DataAttributesAPI.getValue(PlayerEXAttributes.LIFESTEAL, livingEntity).ifPresent {
-                attacker.heal((original * it * 10.0).toFloat())
+            DataAttributesAPI.getValue(PlayerEXAttributes.LIFESTEAL, attacker).ifPresent {
+                attacker.heal((original * it).toFloat())
             }
         }
 
-        val evasionOption = DataAttributesAPI.getValue(PlayerEXAttributes.EVASION, livingEntity)
-
-        if (evasionOption.isPresent)
-        {
-            val chance = livingEntity.random.nextFloat()
-            return !(chance < evasionOption.get() && origin is AbstractArrow)
-        }
-
-        return true
+        return DataAttributesAPI.getValue(PlayerEXAttributes.EVASION, livingEntity).map {
+            !(livingEntity.random.nextFloat() < it && origin is AbstractArrow)
+        }.orElse(true)
     }
 
     fun onCritAttack(player: Player, target: Entity, amount: Float): Float
     {
         if (target !is LivingEntity) return amount
-
-        val meleeCritOption = DataAttributesAPI.getValue(PlayerEXAttributes.MELEE_CRITICAL_DAMAGE, player)
-
-        if (meleeCritOption.isPresent)
-        {
-            return (amount * (1.0 + (meleeCritOption.get() * 10.0)) / 1.5).toFloat()
-        }
-
-        return amount
+        return DataAttributesAPI.getValue(PlayerEXAttributes.MELEE_CRITICAL_DAMAGE, player)
+            .map { (amount * (1.0 + (it * 10.0)) / 1.5).toFloat() }
+            .orElse(amount)
     }
 
     fun attackIsCrit(player: Player, target: Entity, original: Boolean): Boolean
diff --git a/src/main/resources/assets/playerex/lang/en_us.json b/src/main/resources/assets/playerex/lang/en_us.json
index 7f511e20..33212281 100644
--- a/src/main/resources/assets/playerex/lang/en_us.json
+++ b/src/main/resources/assets/playerex/lang/en_us.json
@@ -21,7 +21,7 @@
         {"index": 0},
         {"text": " (", "color": "#757575"},
         {"index": 1, "color": "#757575", "italic": true, "underline": true },
-        {"text": " XP until the next level)", "color": "#757575", "italic": true }
+        {"text": " XP required until the next level)", "color": "#757575", "italic": true }
     ],
     "playerex.ui.level_button": [
         {"text": "", "color": "#F0C25E"},
@@ -177,6 +177,9 @@
     "text.config.playerex-config.option.soundSettings.skillUpVolume": "Skill Up Volume",
     "text.config.playerex-config.option.soundSettings.refundVolume": "Refund Volume",
 
+    "text.config.playerex-config.category.visualSettings": "Visual Options",
+    "text.config.playerex-config.option.visualSettings.nameplateColor": "Nameplate Color",
+
     "text.config.playerex-config.option.resetOnDeath": "Reset on Death",
     "text.config.playerex-config.option.skillPointsPerLevelUp": "Skill Points per. Level Up",
     "text.config.playerex-config.option.levelFormula": "Leveling Formula",
diff --git a/src/main/resources/data/playerex/data_attributes/functions/stock.json b/src/main/resources/data/playerex/data_attributes/functions/stock.json
index 57e991d4..3f786cbf 100644
--- a/src/main/resources/data/playerex/data_attributes/functions/stock.json
+++ b/src/main/resources/data/playerex/data_attributes/functions/stock.json
@@ -102,7 +102,7 @@
 			{
 				"id": "playerex:heal_amplification",
 				"behavior": "Add",
-				"value": 2.0E-4
+				"value": 0.0020
 			},
 			{
 				"id": "playerex:freeze_resistance",
@@ -129,7 +129,7 @@
 			{
 				"id": "minecraft:generic.knockback_resistance",
 				"behavior": "Add",
-				"value": 0.1
+				"value": 0.01
 			},
 			{
 				"id": "playerex:melee_crit_damage",
diff --git a/src/main/resources/data/playerex/data_attributes/overrides/minecraft.json b/src/main/resources/data/playerex/data_attributes/overrides/minecraft.json
new file mode 100644
index 00000000..5bd68de9
--- /dev/null
+++ b/src/main/resources/data/playerex/data_attributes/overrides/minecraft.json
@@ -0,0 +1,24 @@
+{
+    "entries": {
+        "minecraft:generic.knockback_resistance": {
+            "enabled": true,
+            "min": 0.0,
+            "max": 1.0,
+            "smoothness": 1.0,
+            "formula": "Diminished",
+            "format": "Percentage"
+        },
+        "minecraft:generic.luck": {
+            "enabled": true,
+            "smoothness": 1.0,
+            "formula": "Diminished"
+        },
+        "minecraft:generic.max_health": {
+            "enabled": true,
+            "min": 0.0,
+            "max": 1000000.0,
+            "smoothness": 1.0,
+            "formula": "Flat"
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/data/playerex/data_attributes/overrides/ranged_weapon.json b/src/main/resources/data/playerex/data_attributes/overrides/ranged_weapon.json
new file mode 100644
index 00000000..b9aa1f33
--- /dev/null
+++ b/src/main/resources/data/playerex/data_attributes/overrides/ranged_weapon.json
@@ -0,0 +1,9 @@
+{
+    "entries": {
+        "ranged_weapon:haste": {
+            "enabled": true,
+            "smoothness": 1,
+            "format": "Percentage"
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/data/playerex/data_attributes/overrides/spell_power.json b/src/main/resources/data/playerex/data_attributes/overrides/spell_power.json
new file mode 100644
index 00000000..ad6cdc5a
--- /dev/null
+++ b/src/main/resources/data/playerex/data_attributes/overrides/spell_power.json
@@ -0,0 +1,22 @@
+{
+    "entries": {
+        "spell_power:haste": {
+            "enabled": true,
+            "min": 100.0,
+            "max": 1000.0,
+            "smoothness": 1.0,
+            "min_fallback": 100.0,
+            "max_fallback": 1000.0,
+            "formula": "Diminished"
+        },
+        "spell_power:critical_chance": {
+            "enabled": true,
+            "min": 100.0,
+            "max": 1000.0,
+            "smoothness": 1.0,
+            "min_fallback": 100.0,
+            "max_fallback": 1000.0,
+            "formula": "Diminished"
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/data/playerex/data_attributes/overrides/stock.json b/src/main/resources/data/playerex/data_attributes/overrides/stock.json
index 8124a16f..54eb44cc 100644
--- a/src/main/resources/data/playerex/data_attributes/overrides/stock.json
+++ b/src/main/resources/data/playerex/data_attributes/overrides/stock.json
@@ -2,147 +2,77 @@
 	"entries": {
 		"playerex:lightning_resistance": {
 			"enabled": true,
-			"min": 0.0,
-			"max": 1.0,
 			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 1.0,
-			"formula": "Diminished"
-		},
-		"spell_power:haste": {
-			"enabled": true,
-			"min": 100.0,
-			"max": 1000.0,
-			"smoothness": 1.0,
-			"min_fallback": 100.0,
-			"max_fallback": 1000.0,
-			"formula": "Diminished"
+			"formula": "Diminished",
+			"format": "Percentage"
 		},
 		"playerex:ranged_crit_damage": {
 			"enabled": true,
-			"min": 0.0,
-			"max": 1000000.0,
 			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 1000000.0,
-			"formula": "Diminished"
-		},
-		"minecraft:generic.knockback_resistance": {
-			"enabled": true,
-			"min": 0.0,
-			"max": 1.0,
-			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 1.0,
-			"formula": "Diminished"
-		},
-		"minecraft:generic.luck": {
-			"enabled": true,
-			"min": -1024.0,
-			"max": 1024.0,
-			"smoothness": 1.0,
-			"min_fallback": -1024.0,
-			"max_fallback": 1024.0,
 			"formula": "Diminished"
 		},
 		"playerex:poison_resistance": {
 			"enabled": true,
-			"min": 0.0,
-			"max": 1.0,
 			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 1.0,
-			"formula": "Diminished"
+			"formula": "Diminished",
+			"format": "Percentage"
 		},
 		"playerex:melee_crit_damage": {
 			"enabled": true,
-			"min": 0.0,
-			"max": 1.0,
-			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 1.0,
+			"smoothness": 1,
 			"formula": "Diminished"
 		},
 		"playerex:wither_resistance": {
 			"enabled": true,
-			"min": 0.0,
-			"max": 1.0,
 			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 1.0,
-			"formula": "Diminished"
+			"formula": "Diminished",
+			"format": "Percentage"
 		},
 		"playerex:freeze_resistance": {
 			"enabled": true,
-			"min": 0.0,
-			"max": 1.0,
 			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 1.0,
-			"formula": "Diminished"
+			"formula": "Diminished",
+			"format": "Percentage"
 		},
 		"playerex:health_regeneration": {
 			"enabled": true,
-			"min": 0.0,
-			"max": 100.0,
 			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 100.0,
-			"formula": "Diminished"
+			"formula": "Diminished",
+			"format": "Percentage"
 		},
 		"playerex:heal_amplification": {
 			"enabled": true,
-			"min": 0.0,
-			"max": 100.0,
 			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 100.0,
-			"formula": "Diminished"
+			"formula": "Diminished",
+			"format": "Percentage"
 		},
 		"playerex:ranged_crit_chance": {
 			"enabled": true,
-			"min": 0.0,
-			"max": 1.0,
 			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 1.0,
-			"formula": "Diminished"
+			"formula": "Diminished",
+			"format": "Percentage"
 		},
 		"playerex:evasion": {
 			"enabled": true,
-			"min": 0.0,
-			"max": 100.0,
 			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 100.0,
-			"formula": "Diminished"
+			"formula": "Diminished",
+			"format": "Percentage"
 		},
-		"playerex:melee_crit_chance": {
+		"playerex:lifesteal": {
 			"enabled": true,
-			"min": 0.0,
-			"max": 1.0,
-			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 1.0,
-			"formula": "Diminished"
+			"format": "Percentage"
 		},
-		"playerex:fire_resistance": {
+		"playerex:melee_crit_chance": {
 			"enabled": true,
-			"min": 0.0,
-			"max": 1.0,
 			"smoothness": 1.0,
-			"min_fallback": 0.0,
-			"max_fallback": 1.0,
-			"formula": "Diminished"
+			"formula": "Diminished",
+			"format": "Percentage"
 		},
-		"spell_power:critical_chance": {
+		"playerex:fire_resistance": {
 			"enabled": true,
-			"min": 100.0,
-			"max": 1000.0,
 			"smoothness": 1.0,
-			"min_fallback": 100.0,
-			"max_fallback": 1000.0,
-			"formula": "Diminished"
+			"formula": "Diminished",
+			"format": "Percentage"
 		}
 	}
 }
\ No newline at end of file
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 31f768b4..7c29df0a 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -3,14 +3,51 @@
 	"id": "playerex",
 	"version": "${version}",
 	"name": "PlayerEX: Directors Cut",
-	"description": "TBD",
+	"description": "Adds RPG-themed attributes to the game, and a leveling and skill mechanic to the player.",
 	"authors": [
-		"Bare Minimum Studios",
-		"CleverNucleus (former author)"
+		{
+			"name": "bibireden",
+			"contact": {
+				"homepage": "https://github.com/bibi-reden"
+			}
+		},
+		{
+			"name": "naomi",
+			"contact": {
+				"homepage": "https://github.com/naomieow"
+			}
+		},
+		{
+			"name": "DataEncoded",
+			"contact": {
+				"homepage": "https://github.com/DataEncoded"
+			}
+		}
+	],
+	"contributors": [
+		{
+			"name": "pokesmells",
+			"contact": {
+				"homepage": "https://github.com/pokesmells"
+			}
+		},
+		{
+			"name": "OverlordsIII",
+			"contact": {
+				"homepage": "https://github.com/OverlordsIII"
+			}
+		},
+		{
+			"name": "CleverNucleus [former author]",
+			"contact": {
+				"homepage": "https://github.com/CleverNucleus"
+			}
+		}
 	],
 	"contact": {
 		"homepage": "https://github.com/BareMinimumStudios/playerex",
-		"sources": "https://github.com/BareMinimumStudios/playerex"
+		"sources": "https://github.com/BareMinimumStudios/playerex",
+		"issues": "https://github.com/BareMinimumStudios/playerex/issues"
 	},
 	"license": "MIT",
 	"icon": "assets/playerex/icon.png",
@@ -66,6 +103,14 @@
 				"cardinal-components@5.2.2(embedded){modrinth:K01OU20C}{curseforge:318449}#(ignore:github)",
 				"spell-engine@>=0.15.8+1.20.1(optional){modrinth:XvoWJaA2}{curseforge:807653}#(ignore:github)"
 			]
+		},
+		"modmenu": {
+			"links": {
+				"modmenu.discord": "https://discord.gg/pcRw79hwey",
+				"modmenu.wiki": "https://bareminimumstudios.github.io/Bare-Minimum-Docs/",
+				"modmenu.kofi": "https://ko-fi.com/bibiredens",
+				"modmenu.modrinth": "https://modrinth.com/mod/playerex-directors-cut"
+			}
 		}
 	}
 }
\ No newline at end of file