From 194d384a3f57abbe725e97fbbcb06e3f159258f2 Mon Sep 17 00:00:00 2001 From: Bibi Reden Date: Fri, 11 Oct 2024 14:47:59 -0500 Subject: [PATCH] 2.0.0+1.20.1 (release) (#33) * [chore] update `fabric.mod.json` to include all authors/contributors * [ref] provide `MANAGER` directly in `reload` * [fix] let identifier serializer throw exception when parsing fails * [chore] Change to `workflow_dispatch` * [partial-feat] Implement new AttributeOverrideProvider * [partial-feat] Added ability to remove attribute overrides & def.tooltip * [chore] Make `Validators` internal * [feat] Make SearchAnchor & Ability to Add/Edit Overrides * [ref] use `xor` for switching, remove shadow of id * [chore&semver] update version & move to actions v3 * [chore] CL and small cleanup * [feat] Incorporate Attribute Function V2 * [fix] Push attribute function buttons to first position * [feat] Integrate EntityTypesProvider, remove old providers * [fix] Fix component rendering * [fix] Resolve problems with tooltips * [feat] Add `ParsedTextBoxComponent` * [ref] Refractored all field components, added parsing * [fix] Allow adding of entity-types with default data * [feat] Add the ability to disable/enable functions, update CL * [chore] Adjust CL, make small refractoring change to AF provider * [removal] Remove `DefaultAttributeFactory` * [ref] Make `List` into `Map` with identifier * [fix] Fixed UI components not rendering with new links * [feat] Introduce `EntityTypeEntry` and fallback * [chore] update CL * [fix] Force unknowns to have priority render * [ref] Changed edit field and text box to be more agnostic * [chore] comment clearing * [fix] revert typing on `Endec.keyOf` extension * [ref] Remove `RemoveButtonComponent`, add `ButtonComponents` * [feat] added autocomplete for edit fields * [feat] implement diminishing as it was intended... --- .github/workflows/audit-pr-build.yml | 2 +- .github/workflows/build-and-deploy.yml | 9 +- CHANGELOG.md | 42 ++- gradle.properties | 2 +- .../mixin/EntityAttributeInstanceMixin.java | 4 +- .../data_attributes/DataAttributes.kt | 9 +- .../api/attribute/EntityAttributeSupplier.kt | 3 + .../api/attribute/StackingFormula.kt | 42 +-- .../api/factory/DefaultAttributeFactory.kt | 64 ----- .../data_attributes/api/parser/Parsing.kt | 3 + .../config/AttributeConfigManager.kt | 12 +- .../config/AttributeContainerHandler.kt | 2 +- .../config/DataAttributesConfigProviders.kt | 44 +-- .../data_attributes/config/Validators.kt | 10 + .../entities}/EntityTypeData.kt | 12 +- .../config/entities/EntityTypeEntry.kt | 20 ++ .../config/entry/ConfigMerger.kt | 34 +-- .../entry/DefaultAttributesReloadListener.kt | 15 +- .../config/functions/AttributeFunction.kt | 7 +- .../functions/AttributeFunctionConfig.kt | 6 +- .../config/models/EntityTypesConfigModel.kt | 6 +- .../config/models/FunctionsConfigModel.kt | 4 +- .../config/models/OverridesConfigModel.kt | 8 +- .../providers/AttributeEntityTypesProvider.kt | 70 ----- .../providers/AttributeFunctionsProvider.kt | 110 -------- .../providers/AttributeOverrideProvider.kt | 211 -------------- .../data/EntityAttributeData.kt | 8 +- .../networking/ConfigPacketBufs.kt | 4 +- .../serde/IdentifierSerializer.kt | 6 +- .../data_attributes/serde/JanksonBuilders.kt | 16 +- .../ui/DataAttributesConfigScreen.kt | 6 +- .../data_attributes/ui/colors/ColorCodes.kt | 3 + .../boxes/ParsedTextBoxComponent.kt | 29 ++ .../ui/components/buttons/ButtonComponents.kt | 10 + .../components/fields/EditFieldComponent.kt | 82 ++++++ .../ui/components/fields/FieldComponents.kt | 9 + .../providers/AttributeFunctionProvider.kt | 260 +++++++++++++++++ .../providers/AttributeOverrideProvider.kt | 267 ++++++++++++++++++ .../config/providers/EntityTypesProvider.kt | 245 ++++++++++++++++ .../assets/data_attributes/lang/en_us.json | 40 +++ src/main/resources/fabric.mod.json | 38 ++- 41 files changed, 1171 insertions(+), 603 deletions(-) delete mode 100644 src/main/kotlin/com/bibireden/data_attributes/api/factory/DefaultAttributeFactory.kt create mode 100644 src/main/kotlin/com/bibireden/data_attributes/api/parser/Parsing.kt rename src/main/kotlin/com/bibireden/data_attributes/{data => config/entities}/EntityTypeData.kt (76%) create mode 100644 src/main/kotlin/com/bibireden/data_attributes/config/entities/EntityTypeEntry.kt delete mode 100644 src/main/kotlin/com/bibireden/data_attributes/config/providers/AttributeEntityTypesProvider.kt delete mode 100644 src/main/kotlin/com/bibireden/data_attributes/config/providers/AttributeFunctionsProvider.kt delete mode 100644 src/main/kotlin/com/bibireden/data_attributes/config/providers/AttributeOverrideProvider.kt create mode 100644 src/main/kotlin/com/bibireden/data_attributes/ui/components/boxes/ParsedTextBoxComponent.kt create mode 100644 src/main/kotlin/com/bibireden/data_attributes/ui/components/buttons/ButtonComponents.kt create mode 100644 src/main/kotlin/com/bibireden/data_attributes/ui/components/fields/EditFieldComponent.kt create mode 100644 src/main/kotlin/com/bibireden/data_attributes/ui/components/fields/FieldComponents.kt create mode 100644 src/main/kotlin/com/bibireden/data_attributes/ui/config/providers/AttributeFunctionProvider.kt create mode 100644 src/main/kotlin/com/bibireden/data_attributes/ui/config/providers/AttributeOverrideProvider.kt create mode 100644 src/main/kotlin/com/bibireden/data_attributes/ui/config/providers/EntityTypesProvider.kt diff --git a/.github/workflows/audit-pr-build.yml b/.github/workflows/audit-pr-build.yml index 35c8a840..b6262724 100644 --- a/.github/workflows/audit-pr-build.yml +++ b/.github/workflows/audit-pr-build.yml @@ -26,7 +26,7 @@ jobs: run: ./gradlew build - name: capture build artifacts if: ${{ runner.os == 'Linux' && matrix.java == '17' }} # Only upload artifacts built from latest java on one OS - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: Artifacts path: build/libs/ diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 47505dc2..e264d218 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -1,12 +1,5 @@ name: Deployment (J17) -on: - push: - branches-ignore: - - master - - "**/dev**" - - "**-dev" - - "**-dev**" - - dev +on: [workflow_dispatch] jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index b869cc43..2db26135 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,40 @@ -## Fixes 🍋 -- Adjusted formatting of `Whole` formats to not go past two decimals. \ No newline at end of file +## Overview +**There are breaking changes in this release. Ensure you back up your config(s) if possible. Mods relying on the beta version will have to be updated.** + +*Welcome to the release of the new Data Attributes. This has taken a considerable amount of time to complete.* + +*I am glad to have this opportunity to work on this project and be supported with the first ever public project I will personally release.* + +## Additions 🍎 +- No more needing to leave the UI to your config json~ you can do everything you need to do in the UI! +- Allowed easier control of the config menu, and added some new features. + - A `Reset` option to reset your targeted attribute, refreshing all its entries to start anew. + - A `Remove` option to remove the targeted entry of your choice. + - A `Edit` option to edit the identifier to target a different entry. + - A `Add` option to include new entries. + - You can also add into default entries. +- Added autocomplete that will appear while editing fields. This will show you currently registered entities and attributes when editing the respective id you want. + - Press [ENTER] to quickly grab the first entry on the autocomplete. +- Attribute components in configuration now will re-render in certain scenarios, allowing for a better experience with working with multiple attributes. +- Improved logic with entering fields, commit changes by pressing [Done], and [Reload] to refresh to the latest config. +- You can now actually use the search bar to look up the specific entries you wish to find. + - Translations should be compatible in the language you choose as well as the attribute id. + - e.g., looking up `playerex:luck`, or `Luck` should work. +- You can now enable/disable Attribute Functions. + +## Changes 🌽 +- **[BREAKING]** Changed mockup of config JSON. + - Config keys such as `"functions"/"overrides"/"entity_types": { ... }` have been replaced with `"entries": { ... }`. + - This retains parity with the data-pack format. +- **[BREAKING]** Changed `Map` to `Map` for `EntityTypeData`. + - This also includes a `fallback` value that gets the default registered base value for the specific attribute under that entity. +- **[BREAKING]** Changed overall structure of config related class definitions. This will affect your config file considerably. +- **[BREAKING]** Removed `DefaultAttributeFactory`. +- **[BREAKING]** Changed `Map>` to `Map>` + - This existed to avoid an odd situation that does not exist anymore. +- Made some changes to certain logic internally and micro-optimizations. +- Fixed CTD issues with editing function values. +- Separated config entries from defaults using color coding & tooltips. +- **[BREAKING]** Integrated diminishing returns as intended. + - You must keep and/or target attribute min/max ranges between -1 & 1. + - It is how it usually worked in the original DataAttributes, as unfortunately, a solution to implement diminishing returns on all attributes was not possible (at this time). \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 2ddb777a..5b724bbd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ loom_version=1.7-SNAPSHOT minecraft_version=1.20.1 fabric_kotlin_version=1.11.0+kotlin.2.0.0 fabric_api_version=0.92.2+1.20.1 -mod_version=2.0.0+1.20.1-beta.13 +mod_version=2.0.0+1.20.1 loader=fabric # Mappings diff --git a/src/main/java/com/bibireden/data_attributes/mixin/EntityAttributeInstanceMixin.java b/src/main/java/com/bibireden/data_attributes/mixin/EntityAttributeInstanceMixin.java index c9035f2e..9e47a586 100644 --- a/src/main/java/com/bibireden/data_attributes/mixin/EntityAttributeInstanceMixin.java +++ b/src/main/java/com/bibireden/data_attributes/mixin/EntityAttributeInstanceMixin.java @@ -116,7 +116,7 @@ protected void onUpdate() {} if (this.data_attributes$container != null) { attribute.data_attributes$parentsMutable().forEach((parentAttribute, function) -> { - if (function.getBehavior() != StackingBehavior.Add) return; + if (!function.getEnabled() || function.getBehavior() != StackingBehavior.Add) return; EntityAttributeInstance parentInstance = this.data_attributes$container.getCustomInstance((EntityAttribute) parentAttribute); if (parentInstance == null) return; @@ -145,7 +145,7 @@ protected void onUpdate() {} if (this.data_attributes$container != null) { attribute.data_attributes$parentsMutable().forEach((parentAttribute, function) -> { - if (function.getBehavior() != StackingBehavior.Multiply) return; + if (!function.getEnabled() || function.getBehavior() != StackingBehavior.Multiply) return; EntityAttributeInstance parentInstance = this.data_attributes$container.getCustomInstance((EntityAttribute) parentAttribute); if (parentInstance == null) return; diff --git a/src/main/kotlin/com/bibireden/data_attributes/DataAttributes.kt b/src/main/kotlin/com/bibireden/data_attributes/DataAttributes.kt index 87ecbcc7..707a7f14 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/DataAttributes.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/DataAttributes.kt @@ -1,6 +1,5 @@ package com.bibireden.data_attributes -import com.bibireden.data_attributes.api.DataAttributesAPI.serverManager import com.bibireden.data_attributes.config.AttributeConfigManager import com.bibireden.data_attributes.config.entry.DefaultAttributesReloadListener import com.bibireden.data_attributes.config.models.DataAttributesConfig @@ -72,12 +71,10 @@ class DataAttributes : ModInitializer { fun reload(server: MinecraftServer) { reloadConfigs() - val manager = serverManager + MANAGER.update() + MANAGER.nextUpdateFlag() - manager.update() - manager.nextUpdateFlag() - - val buf = AttributeConfigManager.Packet.ENDEC.encodeFully({ ByteBufSerializer.of(PacketByteBufs.create()) }, manager.toPacket()) + val buf = AttributeConfigManager.Packet.ENDEC.encodeFully({ ByteBufSerializer.of(PacketByteBufs.create()) }, MANAGER.toPacket()) PlayerLookup.all(server).forEach { player -> ServerPlayNetworking.send(player, NetworkingChannels.RELOAD, buf) } } diff --git a/src/main/kotlin/com/bibireden/data_attributes/api/attribute/EntityAttributeSupplier.kt b/src/main/kotlin/com/bibireden/data_attributes/api/attribute/EntityAttributeSupplier.kt index 41ad0a9e..35431675 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/api/attribute/EntityAttributeSupplier.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/api/attribute/EntityAttributeSupplier.kt @@ -5,10 +5,13 @@ import net.minecraft.registry.Registries import net.minecraft.util.Identifier import java.util.* import java.util.function.Supplier +import kotlin.jvm.optionals.getOrNull /** * Supplier classes to provide dynamic attribute references. */ class EntityAttributeSupplier(val id: Identifier) : Supplier> { override fun get() = Optional.ofNullable(Registries.ATTRIBUTE[this.id]) + + fun getOrNull() = Optional.ofNullable(Registries.ATTRIBUTE[this.id]).getOrNull() } \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/api/attribute/StackingFormula.kt b/src/main/kotlin/com/bibireden/data_attributes/api/attribute/StackingFormula.kt index c971c3d9..cbeec2c9 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/api/attribute/StackingFormula.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/api/attribute/StackingFormula.kt @@ -2,17 +2,15 @@ package com.bibireden.data_attributes.api.attribute import io.wispforest.endec.Endec import net.minecraft.entity.attribute.EntityAttributeInstance +import net.minecraft.util.math.MathHelper import kotlin.math.abs - -// CN: ((1.0 - v2) * (1.0 - m).pow((v - v2) / m)) - ((1.0 - k2) * (1.0 - m).pow((k - k2) / m)) +import kotlin.math.pow enum class StackingFormula(val function: (k: Double, k2: Double, v: Double, v2: Double, instance: EntityAttributeInstance) -> Double) { Flat({ k, _, v, _, _ -> k - v }), Diminished({ k, k2, v, v2, instance -> - val base = instance.baseValue - val smoothness = (instance.attribute as IEntityAttribute).`data_attributes$smoothness`() - val result = base + (((k - base) - v) * smoothness) - result + val s = (instance.attribute as IEntityAttribute).`data_attributes$smoothness`() + ((1.0 - v2) * (1.0 - s).pow((v - v2) / s)) - ((1.0 - k2) * (1.0 - s).pow((k - k2) / s)) }); companion object { @@ -26,29 +24,13 @@ enum class StackingFormula(val function: (k: Double, k2: Double, v: Double, v2: fun max(current: Double, input: Double): Double = kotlin.math.max(current, abs(input)) - fun stack(current: Double, input: Double): Double = current + abs(input) + fun stack(current: Double, input: Double): Double { + var final = input + if (this == Diminished) { + final = MathHelper.clamp(final, -1.0, 1.0) + } + return current + abs(final) + } fun result(k: Double, k2: Double, v: Double, v2: Double, instance: EntityAttributeInstance): Double = this.function(k, k2, v, v2, instance) -} - - -// the solution? -//val min = attribute.`data_attributes$min`() -//val max = attribute.`data_attributes$max`() -// -//val k = k / 100 -//val k2 = k2 / 100 -//val v = v / 100 -//val v2 = v2 / 100 -// -//val s = MathHelper.clamp(attribute.`data_attributes$smoothness`(), 0.01, 1.0) -// -//val result1 = (1.0 - v2) -//val result2 = (1.0 - s).pow((v - v2) / s) -//val result3 = result1 * result2 -// -//val result4 = (1.0 - k2) -//val result5 = (1.0 - s).pow((k - k2) / s) -//val result6 = (result4 * result5) -// -//((((result3-result6)) * (max-min)) - min).round(2) \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/api/factory/DefaultAttributeFactory.kt b/src/main/kotlin/com/bibireden/data_attributes/api/factory/DefaultAttributeFactory.kt deleted file mode 100644 index 9128c36b..00000000 --- a/src/main/kotlin/com/bibireden/data_attributes/api/factory/DefaultAttributeFactory.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.bibireden.data_attributes.api.factory - -import com.bibireden.data_attributes.DataAttributes -import com.bibireden.data_attributes.config.AttributeConfigManager -import com.bibireden.data_attributes.config.functions.AttributeFunction -import com.bibireden.data_attributes.config.models.OverridesConfigModel.AttributeOverride -import com.bibireden.data_attributes.data.EntityTypeData -import net.minecraft.util.Identifier - -/** - * Meant to register attributes into the game's [AttributeConfigManager] directly. - * - * This is useful for mods that wish to implement their own defaults, so they can be applied to the world. - * Ensure that it is not done through static initialization, the config is not guaranteed to exist at that time. - * - * Instead, register afterward, such as on **mod initialization**. - */ -object DefaultAttributeFactory { - @Deprecated("Use the new data-pack based system to register default entries.", level = DeprecationLevel.WARNING) - @JvmStatic - /** Registers default [AttributeOverride]'s to the config if they are not present currently within the config. */ - fun registerOverrides(overrides: Map) { - val current = DataAttributes.OVERRIDES_CONFIG.overrides.toMutableMap() - overrides.forEach { (id, ao) -> current.computeIfAbsent(id) { ao } } - DataAttributes.OVERRIDES_CONFIG.overrides = current - DataAttributes.OVERRIDES_CONFIG.save() - } - - @Deprecated("Use the new data-pack based system to register default entries.", level = DeprecationLevel.WARNING) - @JvmStatic - /** Registers default [AttributeFunction]'s to the config if they are not present currently within the config. */ - fun registerFunctions(functions: Map>) { - val config = DataAttributes.FUNCTIONS_CONFIG - val current = config.functions.data.toMutableMap() - for ((id, af) in functions) { - val currentFunctions = current.getOrPut(id) { listOf() }.toMutableList() - // I made my own bed, now I have to sit in it for a bit... - af.forEach { - if (current[id]?.find { existing -> existing.id == it.id } == null) { - currentFunctions.add(it) - } - } - current[id] = currentFunctions - } - DataAttributes.FUNCTIONS_CONFIG.functions.data = current - DataAttributes.FUNCTIONS_CONFIG.save() - } - - @Deprecated("Use the new data-pack based system to register default entries.", level = DeprecationLevel.WARNING) - @JvmStatic - /** Registers default [EntityTypeData]'s to the config if they are not present currently within the config. */ - fun registerEntityTypes(entityTypes: Map) { - val current = DataAttributes.ENTITY_TYPES_CONFIG.entity_types.toMutableMap() - for ((id, type) in entityTypes) { - val types = current.getOrPut(id) { EntityTypeData() }.data.toMutableMap() - type.data.forEach { (typeID, value) -> - if (!types.contains(typeID)) types[typeID] = value - } - current[id] = EntityTypeData(types) - } - DataAttributes.ENTITY_TYPES_CONFIG.entity_types = current - DataAttributes.ENTITY_TYPES_CONFIG.save() - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/api/parser/Parsing.kt b/src/main/kotlin/com/bibireden/data_attributes/api/parser/Parsing.kt new file mode 100644 index 00000000..748a5a57 --- /dev/null +++ b/src/main/kotlin/com/bibireden/data_attributes/api/parser/Parsing.kt @@ -0,0 +1,3 @@ +package com.bibireden.data_attributes.api.parser + +typealias Parser = (I) -> O? \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/AttributeConfigManager.kt b/src/main/kotlin/com/bibireden/data_attributes/config/AttributeConfigManager.kt index 7f59e696..36890e31 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/config/AttributeConfigManager.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/config/AttributeConfigManager.kt @@ -9,7 +9,7 @@ import com.bibireden.data_attributes.config.entry.DefaultAttributesReloadListene import com.bibireden.data_attributes.config.functions.AttributeFunction import com.bibireden.data_attributes.config.models.OverridesConfigModel.AttributeOverride import com.bibireden.data_attributes.data.EntityAttributeData -import com.bibireden.data_attributes.data.EntityTypeData +import com.bibireden.data_attributes.config.entities.EntityTypeData import com.bibireden.data_attributes.endec.Endecs import com.bibireden.data_attributes.ext.keyOf import com.bibireden.data_attributes.mutable.MutableEntityAttribute @@ -30,7 +30,7 @@ import net.minecraft.util.Identifier /** * Used to manage config data, and contains an [AttributeContainerHandler] to build related [EntityTypeData]. */ -class AttributeConfigManager(var data: Data = Data(), val handler: AttributeContainerHandler = AttributeContainerHandler()) { +class AttributeConfigManager(var data: Data = Data(), private val handler: AttributeContainerHandler = AttributeContainerHandler()) { var updateFlag: Int = 0 var defaults: DefaultAttributesReloadListener.Cache = DefaultAttributesReloadListener.Cache() @@ -55,14 +55,14 @@ class AttributeConfigManager(var data: Data = Data(), val handler: AttributeCont */ data class Data( var overrides: Map = mapOf(), - var functions: Map> = mapOf(), + var functions: Map> = mapOf(), var entity_types: Map = mapOf() ) { companion object { val ENDEC = StructEndecBuilder.of( Endecs.IDENTIFIER.keyOf(AttributeOverride.ENDEC).fieldOf("overrides") { it.overrides }, - Endecs.IDENTIFIER.keyOf(AttributeFunction.ENDEC.listOf()).fieldOf("functions") { it.functions }, + Endecs.IDENTIFIER.keyOf(Endecs.IDENTIFIER.keyOf(AttributeFunction.ENDEC)).fieldOf("functions") { it.functions }, Endecs.IDENTIFIER.keyOf(EntityTypeData.ENDEC).fieldOf("entity_types") { it.entity_types }, ::Data ) @@ -93,7 +93,7 @@ class AttributeConfigManager(var data: Data = Data(), val handler: AttributeCont get() = this.data.overrides /** Currently applied [AttributeFunction]'s tied to the parent [EntityAttribute]'s [Identifier]. */ - val functions: Map> + val functions: Map> get() = this.data.functions /** Currently applied [EntityTypeData] tied to an [EntityType]'s [Identifier]. */ @@ -179,7 +179,7 @@ class AttributeConfigManager(var data: Data = Data(), val handler: AttributeCont } } - private fun insertFunctions(store: Map>, data: MutableMap) { + private fun insertFunctions(store: Map>, data: MutableMap) { for ((id, functions) in store) { if (!Registries.ATTRIBUTE.containsId(id)) { DataAttributes.LOGGER.warn("Function parent [$id] that was defined in config is not registered. This has been skipped.") diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/AttributeContainerHandler.kt b/src/main/kotlin/com/bibireden/data_attributes/config/AttributeContainerHandler.kt index afc54a31..3e2e8d01 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/config/AttributeContainerHandler.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/config/AttributeContainerHandler.kt @@ -2,7 +2,7 @@ package com.bibireden.data_attributes.config import com.bibireden.data_attributes.config.AttributeConfigManager.Companion.ENTITY_TYPE_INSTANCES import com.bibireden.data_attributes.config.AttributeConfigManager.Tuple -import com.bibireden.data_attributes.data.EntityTypeData +import com.bibireden.data_attributes.config.entities.EntityTypeData import com.bibireden.data_attributes.mutable.MutableAttributeContainer import com.bibireden.data_attributes.mutable.MutableDefaultAttributeContainer import net.minecraft.entity.EntityType diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/DataAttributesConfigProviders.kt b/src/main/kotlin/com/bibireden/data_attributes/config/DataAttributesConfigProviders.kt index e971e9ba..826103dd 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/config/DataAttributesConfigProviders.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/config/DataAttributesConfigProviders.kt @@ -1,52 +1,51 @@ package com.bibireden.data_attributes.config -import com.bibireden.data_attributes.config.providers.AttributeEntityTypesProvider -import com.bibireden.data_attributes.config.providers.AttributeFunctionsProvider -import com.bibireden.data_attributes.config.providers.AttributeOverrideProvider import com.bibireden.data_attributes.ui.colors.ColorCodes +import com.bibireden.data_attributes.ui.config.providers.AttributeFunctionProvider +import com.bibireden.data_attributes.ui.config.providers.AttributeOverrideProvider +import com.bibireden.data_attributes.ui.config.providers.EntityTypesProvider import com.google.common.base.Predicate import io.wispforest.owo.config.ui.OptionComponentFactory import io.wispforest.owo.ui.component.Components import io.wispforest.owo.ui.container.Containers import io.wispforest.owo.ui.container.FlowLayout import io.wispforest.owo.ui.core.* +import net.minecraft.entity.attribute.EntityAttribute import net.minecraft.registry.Registries +import net.minecraft.registry.Registry import net.minecraft.text.MutableText import net.minecraft.text.Style import net.minecraft.text.Text import net.minecraft.util.Identifier object DataAttributesConfigProviders { - fun entityTypeIdentifierToText(id: Identifier): MutableText { - val type = Registries.ENTITY_TYPE[id] - return Text.empty().apply { - append(Text.translatable(type.translationKey).append(" ").setStyle(Style.EMPTY.withColor(ColorCodes.BEE_YELLOW))) - append(Text.literal("($id)").setStyle(Style.EMPTY.withColor(ColorCodes.BEE_BLACK))) - } - } - fun attributeIdentifierToText(id: Identifier): MutableText { - val attribute = Registries.ATTRIBUTE[id] - return Text.empty().apply { - if (attribute != null) { - append(Text.translatable(attribute.translationKey).append(" ")).setStyle(Style.EMPTY.withColor(0xE7C14B)) - } - append(Text.literal("($id)").also { t -> - t.setStyle(Style.EMPTY.withColor(if (attribute != null) ColorCodes.BEE_BLACK else ColorCodes.UNEDITABLE)) - }) + fun registryEntryToText(id: Identifier, registry: Registry, representation: (T) -> String, isDefault: Boolean = false): MutableText { + val entry = registry[id] + val text = Text.empty() + if (entry != null) { + text.append(Text.translatable(representation(entry)).append(" ")) + .setStyle(Style.EMPTY.withColor(if (isDefault) 0x84de56 else 0xE7C14B)) } + text.append(Text.literal("($id)").also { t -> + t.setStyle( + Style.EMPTY.withColor( + if (entry != null) ColorCodes.BEE_BLACK else ColorCodes.UNEDITABLE + ) + ) + }) + return text } - fun isAttributeUnregistered(id: Identifier) = !Registries.ATTRIBUTE.containsId(id) val ATTRIBUTE_OVERRIDE_FACTORY = OptionComponentFactory { _, option -> return@OptionComponentFactory AttributeOverrideProvider(option).let { OptionComponentFactory.Result(it, it) } } val ATTRIBUTE_FUNCTIONS_FACTORY = OptionComponentFactory { _, option -> - return@OptionComponentFactory AttributeFunctionsProvider(option).let { OptionComponentFactory.Result(it, it) } + return@OptionComponentFactory AttributeFunctionProvider(option).let { OptionComponentFactory.Result(it, it) } } val ENTITY_TYPES_FACTORY = OptionComponentFactory { _, option -> - return@OptionComponentFactory AttributeEntityTypesProvider(option).let { OptionComponentFactory.Result(it, it) } + return@OptionComponentFactory EntityTypesProvider(option).let { OptionComponentFactory.Result(it, it) } } fun textBoxComponent(txt: Text, obj: Any, predicate: Predicate? = null, onChange: ((String) -> Unit)? = null, textBoxID: String? = null): FlowLayout { @@ -75,6 +74,7 @@ object DataAttributesConfigProviders { tb.setTextPredicate { predicate == null || predicate.apply(it) } tb.onChanged().subscribe(onChange::invoke) } + tb.id("text") }.positioning(Positioning.relative(100, 50)).id(textBoxID) ) } diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/Validators.kt b/src/main/kotlin/com/bibireden/data_attributes/config/Validators.kt index 73dfa3c0..78b3bccd 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/config/Validators.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/config/Validators.kt @@ -1,6 +1,16 @@ package com.bibireden.data_attributes.config +import net.minecraft.registry.Registries +import net.minecraft.util.Identifier +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal object Validators { /** Checks if the [String] is a valid number via. Regex. */ fun isNumeric(str: String) = str.matches("^[0-9.-]*\$".toRegex()) + + fun isRegisteredAttributeId(str: String): Boolean { + val id = Identifier.tryParse(str) ?: return false + return Registries.ATTRIBUTE[id] != null + } } \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/data/EntityTypeData.kt b/src/main/kotlin/com/bibireden/data_attributes/config/entities/EntityTypeData.kt similarity index 76% rename from src/main/kotlin/com/bibireden/data_attributes/data/EntityTypeData.kt rename to src/main/kotlin/com/bibireden/data_attributes/config/entities/EntityTypeData.kt index 38f54f88..2e19dfb9 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/data/EntityTypeData.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/config/entities/EntityTypeData.kt @@ -1,6 +1,6 @@ @file:UseSerializers(IdentifierSerializer::class) -package com.bibireden.data_attributes.data +package com.bibireden.data_attributes.config.entities import com.bibireden.data_attributes.endec.Endecs import com.bibireden.data_attributes.ext.keyOf @@ -14,13 +14,13 @@ import net.minecraft.registry.Registries import net.minecraft.util.Identifier /** - * Container for data that modifies the specific [Registries.ENTITY_TYPE] entry with an associated base value. + * Container for data that modifies the specific [Registries.ENTITY_TYPE] entry with an associated [EntityTypeEntry]. */ @Serializable -data class EntityTypeData(val data: Map = mapOf()) { +data class EntityTypeData(val data: Map = mapOf()) { companion object { @JvmField - val ENDEC: Endec = Endecs.IDENTIFIER.keyOf(Endec.DOUBLE).xmap(::EntityTypeData) { it.data } + val ENDEC: Endec = Endecs.IDENTIFIER.keyOf(EntityTypeEntry.ENDEC).xmap(::EntityTypeData) { it.data } } /** @@ -29,9 +29,9 @@ data class EntityTypeData(val data: Map = mapOf()) { */ fun build(builder: DefaultAttributeContainer.Builder, container: DefaultAttributeContainer?) { (container as MutableDefaultAttributeContainer).`data_attributes$copy`(builder) - for ((key, value) in this.data) { + for ((key, entry) in this.data) { val attribute = Registries.ATTRIBUTE[key] ?: continue - builder.add(attribute, attribute.clamp(value)) + builder.add(attribute, attribute.clamp(entry.value)) } } } \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/entities/EntityTypeEntry.kt b/src/main/kotlin/com/bibireden/data_attributes/config/entities/EntityTypeEntry.kt new file mode 100644 index 00000000..fb9086a5 --- /dev/null +++ b/src/main/kotlin/com/bibireden/data_attributes/config/entities/EntityTypeEntry.kt @@ -0,0 +1,20 @@ +package com.bibireden.data_attributes.config.entities + +import io.wispforest.endec.Endec +import io.wispforest.endec.impl.StructEndecBuilder +import kotlinx.serialization.Serializable + +/** + * Inner structure for [EntityTypeData] that contains the current base value and a former value + * (if the attribute was attached to this entity before). + */ +@Serializable +data class EntityTypeEntry(var value: Double = 0.0, var fallback: Double? = null) { + companion object { + val ENDEC: Endec = StructEndecBuilder.of( + Endec.DOUBLE.fieldOf("value") { it.value }, + Endec.DOUBLE.nullableOf().fieldOf("fallback", { it.fallback }), + ::EntityTypeEntry + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/entry/ConfigMerger.kt b/src/main/kotlin/com/bibireden/data_attributes/config/entry/ConfigMerger.kt index 913235dc..81fde200 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/config/entry/ConfigMerger.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/config/entry/ConfigMerger.kt @@ -3,39 +3,29 @@ package com.bibireden.data_attributes.config.entry import com.bibireden.data_attributes.DataAttributes import com.bibireden.data_attributes.config.functions.AttributeFunction import com.bibireden.data_attributes.config.models.OverridesConfigModel.AttributeOverride -import com.bibireden.data_attributes.data.EntityTypeData +import com.bibireden.data_attributes.config.entities.EntityTypeData +import com.bibireden.data_attributes.config.entities.EntityTypeEntry import net.minecraft.util.Identifier object ConfigMerger { fun mergeOverrides(values: Map): Map { val entries = values.toMutableMap() - for ((id, override) in DataAttributes.OVERRIDES_CONFIG.overrides) { + for ((id, override) in DataAttributes.OVERRIDES_CONFIG.entries) { entries[id] = override } return entries } - fun mergeFunctions(values: Map>): Map> { + fun mergeFunctions(values: Map>): Map> { val entries = values.toMutableMap() - for ((primaryId, primaryFunctions) in DataAttributes.FUNCTIONS_CONFIG.functions.data) { - val entriesMap = entries[primaryId] - if (entriesMap == null) { - entries[primaryId] = primaryFunctions + for ((primaryId, primaryEntry) in DataAttributes.FUNCTIONS_CONFIG.entries.data) { + val secondaryEntry = entries[primaryId]?.toMutableMap() + if (secondaryEntry == null) { + entries[primaryId] = primaryEntry } else { - val secondaryEntry = entriesMap.toMutableList() - primaryFunctions.forEach { primaryFunction -> - var replaced = false - entriesMap.forEachIndexed { index, entry -> - if (entry.id == primaryFunction.id) { - secondaryEntry.removeAt(index) - secondaryEntry.add(index, primaryFunction) - replaced = true - } - } - if (!replaced) { - secondaryEntry.add(primaryFunction) - } + for ((id, value) in primaryEntry) { + secondaryEntry[id] = value } entries[primaryId] = secondaryEntry } @@ -43,9 +33,9 @@ object ConfigMerger { return entries } - fun mergeEntityTypes(values: Map>): Map { + fun mergeEntityTypes(values: Map>): Map { val entries = values.toMutableMap() - for ((primaryId, primaryEntry) in DataAttributes.ENTITY_TYPES_CONFIG.entity_types) { + for ((primaryId, primaryEntry) in DataAttributes.ENTITY_TYPES_CONFIG.entries) { val secondaryEntry = entries[primaryId]?.toMutableMap() if (secondaryEntry == null) { entries[primaryId] = primaryEntry.data diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/entry/DefaultAttributesReloadListener.kt b/src/main/kotlin/com/bibireden/data_attributes/config/entry/DefaultAttributesReloadListener.kt index 024ef9b0..80246879 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/config/entry/DefaultAttributesReloadListener.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/config/entry/DefaultAttributesReloadListener.kt @@ -6,6 +6,7 @@ import com.bibireden.data_attributes.serde.IdentifierSerializer import kotlinx.serialization.UseSerializers import com.bibireden.data_attributes.DataAttributes +import com.bibireden.data_attributes.config.entities.EntityTypeEntry import com.bibireden.data_attributes.config.functions.AttributeFunction import com.bibireden.data_attributes.config.models.OverridesConfigModel.AttributeOverride import kotlinx.serialization.ExperimentalSerializationApi @@ -31,9 +32,9 @@ class DefaultAttributesReloadListener : SimpleResourceReloadListener = LinkedHashMap()) @Serializable - data class Functions(var entries: LinkedHashMap> = LinkedHashMap()) + data class Functions(var entries: LinkedHashMap> = LinkedHashMap()) @Serializable - data class EntityTypes(var entries: LinkedHashMap> = LinkedHashMap()) + data class EntityTypes(var entries: LinkedHashMap> = LinkedHashMap()) @Serializable data class Cache(val overrides: Overrides = Overrides(), val functions: Functions = Functions(), val types: EntityTypes = EntityTypes()) @@ -61,7 +62,7 @@ class DefaultAttributesReloadListener : SimpleResourceReloadListener Registries.ATTRIBUTE.containsId(f.id) } + cache.functions.entries[id] = LinkedHashMap(functions.filter { (id2) -> Registries.ATTRIBUTE.containsId(id2) }) } cache.types.entries.forEach { (id, data) -> if (!Registries.ENTITY_TYPE.containsId(id)) { @@ -105,12 +106,10 @@ class DefaultAttributesReloadListener : SimpleResourceReloadListener x.name.uppercase() }.fieldOf("behavior") { it.behavior }, Endec.DOUBLE.fieldOf("value") { it.value }, ::AttributeFunction diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/functions/AttributeFunctionConfig.kt b/src/main/kotlin/com/bibireden/data_attributes/config/functions/AttributeFunctionConfig.kt index e79627e6..e5b0a1f9 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/config/functions/AttributeFunctionConfig.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/config/functions/AttributeFunctionConfig.kt @@ -9,9 +9,11 @@ import net.minecraft.util.Identifier /** * Container for data that applies modifiers to specific [EntityAttribute]'s based on a [AttributeFunction]. */ -data class AttributeFunctionConfig(var data: Map> = mapOf()) { +data class AttributeFunctionConfig( + val data: MutableMap> = mutableMapOf() +) { companion object { @JvmField - val ENDEC = Endecs.IDENTIFIER.keyOf(AttributeFunction.ENDEC.listOf()).xmap(::AttributeFunctionConfig) { it.data } + val ENDEC = Endecs.IDENTIFIER.keyOf(Endecs.IDENTIFIER.keyOf(AttributeFunction.ENDEC)).xmap(::AttributeFunctionConfig) { it.data } } } \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/models/EntityTypesConfigModel.kt b/src/main/kotlin/com/bibireden/data_attributes/config/models/EntityTypesConfigModel.kt index 889d0036..7776c1e7 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/config/models/EntityTypesConfigModel.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/config/models/EntityTypesConfigModel.kt @@ -1,7 +1,8 @@ package com.bibireden.data_attributes.config.models +import blue.endless.jankson.Comment import com.bibireden.data_attributes.DataAttributes -import com.bibireden.data_attributes.data.EntityTypeData +import com.bibireden.data_attributes.config.entities.EntityTypeData import io.wispforest.owo.config.Option.SyncMode import io.wispforest.owo.config.annotation.Config import io.wispforest.owo.config.annotation.Hook @@ -17,5 +18,6 @@ class EntityTypesConfigModel { @JvmField @Hook - var entity_types: Map = mapOf() + @Comment("entity types are able to target specific entities in-game to attach certain attributes to them.") + var entries: Map = mapOf() } \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/models/FunctionsConfigModel.kt b/src/main/kotlin/com/bibireden/data_attributes/config/models/FunctionsConfigModel.kt index 019e4d65..064d3957 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/config/models/FunctionsConfigModel.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/config/models/FunctionsConfigModel.kt @@ -1,5 +1,6 @@ package com.bibireden.data_attributes.config.models +import blue.endless.jankson.Comment import com.bibireden.data_attributes.DataAttributes import com.bibireden.data_attributes.config.functions.AttributeFunctionConfig import io.wispforest.owo.config.Option.SyncMode @@ -16,5 +17,6 @@ class FunctionsConfigModel { @JvmField @Hook - var functions: AttributeFunctionConfig = AttributeFunctionConfig() + @Comment("attribute functions are able to compute child attributes based on an increment/decrement, or multiplication of a parent attribute.") + var entries: AttributeFunctionConfig = AttributeFunctionConfig() } \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/models/OverridesConfigModel.kt b/src/main/kotlin/com/bibireden/data_attributes/config/models/OverridesConfigModel.kt index 9ee64c94..38cfc1a8 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/config/models/OverridesConfigModel.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/config/models/OverridesConfigModel.kt @@ -1,5 +1,6 @@ package com.bibireden.data_attributes.config.models +import blue.endless.jankson.Comment import com.bibireden.data_attributes.DataAttributes import com.bibireden.data_attributes.api.attribute.StackingFormula import com.bibireden.data_attributes.api.attribute.AttributeFormat @@ -20,7 +21,8 @@ class OverridesConfigModel { @JvmField @Hook - var overrides: Map = mapOf() + @Comment("attribute overrides can change the range of the attribute, and can apply different formulas to modify its behavior when computed.") + var entries: Map = mapOf() @Serializable data class AttributeOverride( @@ -31,7 +33,7 @@ class OverridesConfigModel { @JvmField var max: Double = Double.NaN, @JvmField - var smoothness: Double = 0.01, + var smoothness: Double = 1.0, @JvmField var min_fallback: Double = 0.0, @JvmField @@ -49,7 +51,7 @@ class OverridesConfigModel { companion object { @JvmField val ENDEC: Endec = StructEndecBuilder.of( - Endec.BOOLEAN.optionalFieldOf("enabled", { it.enabled }, false), + Endec.BOOLEAN.optionalFieldOf("enabled", { it.enabled }, true), Endec.DOUBLE.optionalFieldOf("min", { it.min }, 0.0), Endec.DOUBLE.optionalFieldOf("max", { it.max }, 1_000_000.0), Endec.DOUBLE.optionalFieldOf("smoothness", { it.smoothness }, 0.01), diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/providers/AttributeEntityTypesProvider.kt b/src/main/kotlin/com/bibireden/data_attributes/config/providers/AttributeEntityTypesProvider.kt deleted file mode 100644 index 8cf9c8ca..00000000 --- a/src/main/kotlin/com/bibireden/data_attributes/config/providers/AttributeEntityTypesProvider.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.bibireden.data_attributes.config.providers - -import com.bibireden.data_attributes.api.DataAttributesAPI -import com.bibireden.data_attributes.config.DataAttributesConfigProviders.attributeIdentifierToText -import com.bibireden.data_attributes.config.DataAttributesConfigProviders.entityTypeIdentifierToText -import com.bibireden.data_attributes.config.DataAttributesConfigProviders.textBoxComponent -import com.bibireden.data_attributes.config.Validators -import com.bibireden.data_attributes.config.entry.ConfigMerger -import com.bibireden.data_attributes.data.EntityTypeData -import com.bibireden.data_attributes.ui.components.CollapsibleFoldableContainer -import io.wispforest.owo.config.Option -import io.wispforest.owo.config.ui.component.OptionValueProvider -import io.wispforest.owo.ui.container.Containers -import io.wispforest.owo.ui.container.FlowLayout -import io.wispforest.owo.ui.core.Sizing -import net.minecraft.entity.attribute.ClampedEntityAttribute -import net.minecraft.registry.Registries -import net.minecraft.text.Text -import net.minecraft.util.Formatting -import net.minecraft.util.Identifier - -class AttributeEntityTypesProvider(val option: Option>) : FlowLayout(Sizing.fill(100), Sizing.content(), Algorithm.VERTICAL), OptionValueProvider { - val backing = HashMap(option.value()) - - init { - ConfigMerger.mergeEntityTypes(DataAttributesAPI.serverManager.defaults.types.entries).forEach { (topID, types) -> - CollapsibleFoldableContainer(Sizing.content(), Sizing.content(), entityTypeIdentifierToText(topID), true).also { ct -> - ct.gap(15) - types.data.forEach { (id, value) -> - Containers.collapsible(Sizing.content(), Sizing.content(), attributeIdentifierToText(id), true).also { - it.gap(8) - - it.child(textBoxComponent( - Text.translatable("text.config.data_attributes.data_entry.entity_types.value"), - value, - Validators::isNumeric, - onChange = { - it.toDoubleOrNull()?.let { value -> - val data = this.backing.remove(topID) ?: EntityTypeData() - val mapping = data.data.toMutableMap() - mapping[id] = value - this.backing.put(topID, data.copy(data = mapping)) - } - } - )) - - val attribute = Registries.ATTRIBUTE[id] - if (attribute is ClampedEntityAttribute) { - it.tooltip( - Text.translatable( - "text.config.data_attributes.data_entry.entity_type_value", - id, - attribute.minValue, - attribute.maxValue - ) - ) - } - - ct.child(it) - } - } - } - .also(this::child) - } - } - - override fun isValid() = !this.option.detached() - - override fun parsedValue() = backing -} diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/providers/AttributeFunctionsProvider.kt b/src/main/kotlin/com/bibireden/data_attributes/config/providers/AttributeFunctionsProvider.kt deleted file mode 100644 index 84ecc96c..00000000 --- a/src/main/kotlin/com/bibireden/data_attributes/config/providers/AttributeFunctionsProvider.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.bibireden.data_attributes.config.providers - -import com.bibireden.data_attributes.api.DataAttributesAPI -import com.bibireden.data_attributes.api.attribute.StackingBehavior -import com.bibireden.data_attributes.config.DataAttributesConfigProviders.attributeIdentifierToText -import com.bibireden.data_attributes.config.DataAttributesConfigProviders.isAttributeUnregistered -import com.bibireden.data_attributes.config.DataAttributesConfigProviders.textBoxComponent -import com.bibireden.data_attributes.config.Validators -import com.bibireden.data_attributes.config.entry.ConfigMerger -import com.bibireden.data_attributes.config.functions.AttributeFunctionConfig -import com.bibireden.data_attributes.ui.components.CollapsibleFoldableContainer -import com.bibireden.data_attributes.ui.renderers.ButtonRenderers -import io.wispforest.owo.config.Option -import io.wispforest.owo.config.ui.component.OptionValueProvider -import io.wispforest.owo.ui.component.Components -import io.wispforest.owo.ui.container.Containers -import io.wispforest.owo.ui.container.FlowLayout -import io.wispforest.owo.ui.core.Positioning -import io.wispforest.owo.ui.core.Sizing -import io.wispforest.owo.ui.core.VerticalAlignment -import net.minecraft.entity.attribute.ClampedEntityAttribute -import net.minecraft.registry.Registries -import net.minecraft.text.Text - -class AttributeFunctionsProvider(val option: Option) : FlowLayout(Sizing.fill(100), Sizing.content(), Algorithm.VERTICAL), OptionValueProvider { - val backing = option.value().data.toMutableMap() - - init { - ConfigMerger.mergeFunctions(DataAttributesAPI.serverManager.defaults.functions.entries).forEach { (topID, functions) -> - val isFunctionParentUnregistered = isAttributeUnregistered(topID) - CollapsibleFoldableContainer(Sizing.content(), Sizing.content(), attributeIdentifierToText(topID), true).also { ct -> - ct.gap(15) - if (isFunctionParentUnregistered) { - ct.titleLayout().tooltip(Text.translatable("text.config.data_attributes.data_entry.invalid")) - } - functions.forEachIndexed { index, function -> - val isFunctionChildUnregistered = isAttributeUnregistered(function.id) - - Containers.collapsible(Sizing.content(), Sizing.content(), attributeIdentifierToText(function.id), true).also { - it.gap(8) - if (isFunctionChildUnregistered) { - it.titleLayout().tooltip(Text.translatable("text.config.data_attributes.data_entry.invalid")) - } - else { - val attribute = Registries.ATTRIBUTE[function.id] - if (attribute is ClampedEntityAttribute) { - it.tooltip( - Text.translatable( - "text.config.data_attributes.data_entry.function_child", - function.id, - attribute.minValue, - attribute.maxValue - ) - ) - } - } - it.child(textBoxComponent( - Text.translatable("text.config.data_attributes.data_entry.functions.value"), - function.value, - Validators::isNumeric, - onChange = { - it.toDoubleOrNull()?.let { v -> - val popped = this.backing.remove(topID)?.toMutableList() ?: mutableListOf() - if (popped.size - 1 < index) { - popped.add(function.copy(value = v)) - } - else { - popped[index] = function.copy(value = v) - } - this.backing.put(topID, popped) - } - } - )) - - it.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(20)).also { hf -> - hf.verticalAlignment(VerticalAlignment.CENTER) - hf.child( - Components.label(Text.translatable("text.config.data_attributes.data_entry.functions.behavior")) - .sizing(Sizing.content(), Sizing.fixed(20)) - ) - hf.child( - Components.button(Text.translatable("text.config.data_attributes.enum.functionBehavior.${function.behavior.name.lowercase()}")) { - function.behavior = when (function.behavior) { - StackingBehavior.Add -> StackingBehavior.Multiply - StackingBehavior.Multiply -> StackingBehavior.Add - } - it.message = Text.translatable("text.config.data_attributes.enum.functionBehavior.${function.behavior.name.lowercase()}") - val popped = this.backing.remove(topID)?.toMutableList() ?: mutableListOf() - if (popped.isEmpty()) { - popped.add(function.copy(behavior = function.behavior)) - } else { - popped[index] = function.copy(behavior = function.behavior) - } - this.backing.put(topID, popped) - } - .renderer(ButtonRenderers.STANDARD) - .positioning(Positioning.relative(100, 0)).horizontalSizing(Sizing.fixed(65)) - ) - }) - ct.child(it) - } - } - } - .also(this::child) - } - } - - override fun isValid() = !this.option.detached() - override fun parsedValue() = AttributeFunctionConfig(backing) -} diff --git a/src/main/kotlin/com/bibireden/data_attributes/config/providers/AttributeOverrideProvider.kt b/src/main/kotlin/com/bibireden/data_attributes/config/providers/AttributeOverrideProvider.kt deleted file mode 100644 index f945de30..00000000 --- a/src/main/kotlin/com/bibireden/data_attributes/config/providers/AttributeOverrideProvider.kt +++ /dev/null @@ -1,211 +0,0 @@ -package com.bibireden.data_attributes.config.providers - -import com.bibireden.data_attributes.api.attribute.AttributeFormat -import com.bibireden.data_attributes.api.DataAttributesAPI -import com.bibireden.data_attributes.api.attribute.StackingFormula -import com.bibireden.data_attributes.config.DataAttributesConfigProviders.attributeIdentifierToText -import com.bibireden.data_attributes.config.DataAttributesConfigProviders.isAttributeUnregistered -import com.bibireden.data_attributes.config.DataAttributesConfigProviders.textBoxComponent -import com.bibireden.data_attributes.config.Validators -import com.bibireden.data_attributes.config.entry.ConfigMerger -import com.bibireden.data_attributes.config.models.OverridesConfigModel.AttributeOverride -import com.bibireden.data_attributes.mutable.MutableEntityAttribute -import com.bibireden.data_attributes.ui.renderers.ButtonRenderers -import com.bibireden.data_attributes.ext.round -import io.wispforest.owo.config.Option -import io.wispforest.owo.config.ui.component.ConfigToggleButton -import io.wispforest.owo.config.ui.component.OptionValueProvider -import io.wispforest.owo.ui.component.Components -import io.wispforest.owo.ui.container.Containers -import io.wispforest.owo.ui.container.FlowLayout -import io.wispforest.owo.ui.core.Positioning -import io.wispforest.owo.ui.core.Sizing -import io.wispforest.owo.ui.core.VerticalAlignment -import net.minecraft.registry.Registries -import net.minecraft.text.Text -import net.minecraft.util.Identifier -import kotlin.math.max - -class AttributeOverrideProvider(val option: Option>) : FlowLayout(Sizing.fill(100), Sizing.content(), Algorithm.VERTICAL), OptionValueProvider { - val backing = option.value().toMutableMap() - - init { - ConfigMerger.mergeOverrides(DataAttributesAPI.serverManager.defaults.overrides.entries).forEach { (id, config) -> - var override = config - // extract min & max fallbacks. - val attribute = Registries.ATTRIBUTE[id] as MutableEntityAttribute? - if (attribute != null) { - override = override.copy( - min_fallback = attribute.`data_attributes$min_fallback`(), - max_fallback = attribute.`data_attributes$max_fallback`() - ) - this.backing.replace(id, override) - } - - val isOverrideInvalid = isAttributeUnregistered(id) - Containers.collapsible(Sizing.content(), Sizing.content(), attributeIdentifierToText(id), true) - .also { topContainer -> - if (isOverrideInvalid) { - topContainer.titleLayout().tooltip(Text.translatable("text.config.data_attributes.data_entry.invalid")) - } - - topContainer.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(15)).also { hf -> - hf.verticalAlignment(VerticalAlignment.BOTTOM) - - hf.gap(10) - - hf.child(ConfigToggleButton().apply { - enabled(override.enabled) - onPress { backing.replace(id, override.copy(enabled = !override.enabled)) } - renderer(ButtonRenderers.STANDARD) - }) - -// hf.child(Components.button(Text.translatable("text.config.data_attributes.data_entry.reset")) -// { -// override = override.copy(min = override.min_fallback, max = override.max_fallback) -// -// val inputMinBox = this.childById(TextBoxComponent::class.java, "inputs.min")!! -// inputMinBox.text = override.min.toString() -// -// val inputMaxBox = this.childById(TextBoxComponent::class.java, "inputs.max")!! -// inputMaxBox.text = override.max.toString() -// -// this.backing.replace(id, override) -// } -// .renderer(ButtonRenderers.STANDARD) -// ) - }) - - topContainer.child( - textBoxComponent( - Text.translatable("text.config.data_attributes.data_entry.overrides.min"), - override.min, - Validators::isNumeric, - onChange = { - it.toDoubleOrNull()?.let { v -> - this.backing[id] = override.copy(min = v) - } - }, - "inputs.min" - ) - ) - - topContainer.child( - textBoxComponent( - Text.translatable("text.config.data_attributes.data_entry.overrides.max"), - override.max, - Validators::isNumeric, - onChange = { - it.toDoubleOrNull()?.let { v -> - this.backing[id] = override.copy(max = v) - } - }, - "inputs.max" - ) - ) - - topContainer.child( - textBoxComponent( - Text.translatable("text.config.data_attributes.data_entry.overrides.min_fallback"), - override.min_fallback, - Validators::isNumeric, - ) - ) - - topContainer.child( - textBoxComponent( - Text.translatable("text.config.data_attributes.data_entry.overrides.max_fallback"), - override.max_fallback, - Validators::isNumeric, - ) - ) - - topContainer.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(30)).also { hf -> - hf.verticalAlignment(VerticalAlignment.CENTER) - hf.gap(8) - hf.child( - Components.label(Text.translatable("text.config.data_attributes.data_entry.overrides.smoothness")) - .sizing(Sizing.content(), Sizing.fixed(20))) - - hf.child( - Components.discreteSlider(Sizing.fill(30), 0.01, 100.0).value(override.smoothness).also { slider -> - slider.onChanged().subscribe { - this.backing.remove(id) - this.backing[id] = override.copy(smoothness = max(slider.value().round(2), 0.01)) - } - } - .positioning(Positioning.relative(100, 0)) - ) - }) - - topContainer.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(20)).also { hf -> - hf.verticalAlignment(VerticalAlignment.CENTER) - hf.gap(8) - hf.child( - Components.label(Text.translatable("text.config.data_attributes.data_entry.overrides.formula")) - .sizing(Sizing.content(), Sizing.fixed(20)) - ) - hf.child( - Components.button(Text.translatable("text.config.data_attributes.enum.stackingFormula.${override.formula.name.lowercase()}"), { - override.formula = when (override.formula) { - StackingFormula.Flat -> StackingFormula.Diminished - StackingFormula.Diminished -> StackingFormula.Flat - } - it.message = Text.translatable("text.config.data_attributes.enum.stackingFormula.${override.formula.name.lowercase()}") - this.backing[id] = override.copy(formula = override.formula) - }) - .renderer(ButtonRenderers.STANDARD) - .positioning(Positioning.relative(100, 0)).horizontalSizing(Sizing.fixed(65)) - ) - }) - - topContainer.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(20)).also { hf -> - hf.verticalAlignment(VerticalAlignment.CENTER) - hf.gap(8) - hf.child( - Components.label(Text.translatable("text.config.data_attributes.data_entry.overrides.format")) - .sizing(Sizing.content(), Sizing.fixed(20)) - ) - hf.child( - Components.button(Text.translatable("text.config.data_attributes.enum.format.${override.format.name.lowercase()}"), { - override.format = when (override.format) { - AttributeFormat.Percentage -> AttributeFormat.Whole - AttributeFormat.Whole -> AttributeFormat.Percentage - } - it.message = Text.translatable("text.config.data_attributes.enum.format.${override.format.name.lowercase()}") - this.backing.replace(id, override.copy(formula = override.formula)) - }) - .renderer(ButtonRenderers.STANDARD) - .positioning(Positioning.relative(100, 0)).horizontalSizing(Sizing.fixed(65)) - ) - }) -/* - topContainer.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(20))).also { hf -> - hf.verticalAlignment(VerticalAlignment.CENTER) - hf.gap(8) - hf.child( - Components.label(Text.translatable("text.config.data_attributes.data_entry.overrides.format")) - .sizing(Sizing.content(), Sizing.fixed(20)) - ) - hf.child( - Components.button(Text.translatable("text.config.data_attributes.enum.format.${override.format.name.lowercase()}"), { - override.format = when (override.format) { - AttributeFormat.Whole -> AttributeFormat.Percentage - AttributeFormat.Percentage -> AttributeFormat.Whole - } - it.message = Text.translatable("text.config.data_attributes.enum.format.${override.format.name.lowercase()}") - this.backing.replace(id, override.copy(format = override.format)) - }) - .renderer(ButtonRenderers.STANDARD) - .positioning(Positioning.relative(100, 0)).horizontalSizing(Sizing.fixed(65)) - ) - }*/ - } - .also(this::child) - } - } - - override fun isValid() = !this.option.detached() - - override fun parsedValue() = backing -} \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/data/EntityAttributeData.kt b/src/main/kotlin/com/bibireden/data_attributes/data/EntityAttributeData.kt index 9ba7e363..0a4a8a52 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/data/EntityAttributeData.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/data/EntityAttributeData.kt @@ -47,13 +47,13 @@ class EntityAttributeData(val override: AttributeOverride? = null, val functions } } - /** Joins a [List] of [AttributeFunction]'s with the data in this class. */ - fun putFunctions(functions: List) { - functions.forEach { (id, behavior, value) -> + /** Joins a [Map] of [AttributeFunction]'s with the data in this class. */ + fun putFunctions(functions: Map) { + for ((id, function) in functions) { if (!Registries.ATTRIBUTE.containsId(id)) { DataAttributes.LOGGER.warn("The attribute function child [$id] does not seem to be registered. This could allude to a missing mod or registered attribute.") } - this.functions[id] = AttributeFunction(id, behavior, value) + this.functions[id] = function } } } \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/networking/ConfigPacketBufs.kt b/src/main/kotlin/com/bibireden/data_attributes/networking/ConfigPacketBufs.kt index a94da53d..22e3c70c 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/networking/ConfigPacketBufs.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/networking/ConfigPacketBufs.kt @@ -2,7 +2,8 @@ package com.bibireden.data_attributes.networking import com.bibireden.data_attributes.config.models.OverridesConfigModel.AttributeOverride import com.bibireden.data_attributes.config.functions.AttributeFunction -import com.bibireden.data_attributes.data.EntityTypeData +import com.bibireden.data_attributes.config.entities.EntityTypeData +import com.bibireden.data_attributes.config.entities.EntityTypeEntry import io.wispforest.endec.Endec import io.wispforest.endec.format.bytebuf.ByteBufDeserializer import io.wispforest.endec.format.bytebuf.ByteBufSerializer @@ -22,5 +23,6 @@ object ConfigPacketBufs { registerSerializer(AttributeOverride::class, AttributeOverride.ENDEC) registerSerializer(AttributeFunction::class, AttributeFunction.ENDEC) registerSerializer(EntityTypeData::class, EntityTypeData.ENDEC) + registerSerializer(EntityTypeEntry::class, EntityTypeEntry.ENDEC) } } \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/serde/IdentifierSerializer.kt b/src/main/kotlin/com/bibireden/data_attributes/serde/IdentifierSerializer.kt index 3634be2d..dcd44261 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/serde/IdentifierSerializer.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/serde/IdentifierSerializer.kt @@ -1,6 +1,7 @@ package com.bibireden.data_attributes.serde import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor @@ -12,7 +13,10 @@ class IdentifierSerializer : KSerializer { override val descriptor: SerialDescriptor get() = PrimitiveSerialDescriptor("Identifier", PrimitiveKind.STRING) - override fun deserialize(decoder: Decoder): Identifier = Identifier.tryParse(decoder.decodeString())!! + override fun deserialize(decoder: Decoder): Identifier { + val str = decoder.decodeString() + return Identifier.tryParse(str) ?: throw SerializationException("failed to parse identifier $str") + } override fun serialize(encoder: Encoder, value: Identifier) = encoder.encodeString(value.toString()) } \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/serde/JanksonBuilders.kt b/src/main/kotlin/com/bibireden/data_attributes/serde/JanksonBuilders.kt index c300fb1a..64a1be9e 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/serde/JanksonBuilders.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/serde/JanksonBuilders.kt @@ -1,12 +1,12 @@ package com.bibireden.data_attributes.serde import blue.endless.jankson.Jankson -import blue.endless.jankson.JsonArray import blue.endless.jankson.JsonObject import blue.endless.jankson.JsonPrimitive import com.bibireden.data_attributes.config.functions.AttributeFunction import com.bibireden.data_attributes.config.functions.AttributeFunctionConfig -import com.bibireden.data_attributes.data.EntityTypeData +import com.bibireden.data_attributes.config.entities.EntityTypeData +import com.bibireden.data_attributes.config.entities.EntityTypeEntry import net.minecraft.util.Identifier import org.jetbrains.annotations.ApiStatus @@ -15,9 +15,9 @@ object JanksonBuilders { fun applyEntityTypes(builder: Jankson.Builder) { builder.registerSerializer(EntityTypeData::class.java) { cfg, marshaller -> marshaller.serialize(cfg.data) } builder.registerDeserializer(JsonObject::class.java, EntityTypeData::class.java) { json, marshaller -> - EntityTypeData(marshaller.marshall>(Map::class.java, json).entries + EntityTypeData(marshaller.marshall>(Map::class.java, json).entries .associate { (k, v) -> - Identifier.tryParse(k)!! to marshaller.marshallCarefully(Double::class.java, v) + Identifier.tryParse(k)!! to marshaller.marshallCarefully(EntityTypeEntry::class.java, v) } ) } @@ -28,10 +28,10 @@ object JanksonBuilders { marshaller.serialize(cfg.data) } builder.registerDeserializer(JsonObject::class.java, AttributeFunctionConfig::class.java) { obj, marshaller -> - AttributeFunctionConfig(marshaller.marshall>(Map::class.java, obj).entries - .associate { (id, array) -> - Identifier.tryParse(id)!! to array.map { marshaller.marshall(AttributeFunction::class.java, it) } - } + AttributeFunctionConfig(marshaller.marshall>>(Map::class.java, obj).entries + .associate { (a, b) -> Identifier.tryParse(a)!! to b.entries + .associate { (k, v) -> Identifier.tryParse(k)!! to marshaller.marshallCarefully(AttributeFunction::class.java, v) }.toMutableMap() + }.toMutableMap() ) } } diff --git a/src/main/kotlin/com/bibireden/data_attributes/ui/DataAttributesConfigScreen.kt b/src/main/kotlin/com/bibireden/data_attributes/ui/DataAttributesConfigScreen.kt index 284289ed..f56a99a4 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/ui/DataAttributesConfigScreen.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/ui/DataAttributesConfigScreen.kt @@ -36,13 +36,13 @@ import kotlin.math.min class DataAttributesConfigScreen(val overrides: OverridesConfig, val functions: FunctionsConfig, val entity_types: EntityTypesConfig, parent: Screen?) : ConfigScreen(DEFAULT_MODEL_ID, DataAttributes.CONFIG, parent) { override fun build(rootComponent: FlowLayout) { - this.extraFactories.put({ it.backingField().field.name.equals("entity_types") }, + this.extraFactories.put({ it.configName() == "data_attributes/entity_types" && it.backingField().field.name.equals("entries") }, DataAttributesConfigProviders.ENTITY_TYPES_FACTORY ) - this.extraFactories.put({ it.backingField().field.name.equals("overrides") }, + this.extraFactories.put({ it.configName() == "data_attributes/overrides" && it.backingField().field.name.equals("entries") }, DataAttributesConfigProviders.ATTRIBUTE_OVERRIDE_FACTORY ) - this.extraFactories.put({ it.backingField().field.name.equals("functions") }, + this.extraFactories.put({ it.configName() == "data_attributes/functions" && it.backingField().field.name.equals("entries") }, DataAttributesConfigProviders.ATTRIBUTE_FUNCTIONS_FACTORY ) diff --git a/src/main/kotlin/com/bibireden/data_attributes/ui/colors/ColorCodes.kt b/src/main/kotlin/com/bibireden/data_attributes/ui/colors/ColorCodes.kt index f3f56e0e..347fcf47 100644 --- a/src/main/kotlin/com/bibireden/data_attributes/ui/colors/ColorCodes.kt +++ b/src/main/kotlin/com/bibireden/data_attributes/ui/colors/ColorCodes.kt @@ -1,8 +1,11 @@ package com.bibireden.data_attributes.ui.colors object ColorCodes { + val TAN = 0xf2e1c0 val BEE_YELLOW = 0xE7C14B val BEE_BLACK = 0x707070 + val GREEN = 0x69d699 + val RED = 0xe54d48 val UNEDITABLE = 0xEB1D36 } \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/ui/components/boxes/ParsedTextBoxComponent.kt b/src/main/kotlin/com/bibireden/data_attributes/ui/components/boxes/ParsedTextBoxComponent.kt new file mode 100644 index 00000000..4b4ee2e1 --- /dev/null +++ b/src/main/kotlin/com/bibireden/data_attributes/ui/components/boxes/ParsedTextBoxComponent.kt @@ -0,0 +1,29 @@ +package com.bibireden.data_attributes.ui.components.boxes + +import com.bibireden.data_attributes.api.parser.Parser +import com.bibireden.data_attributes.ui.colors.ColorCodes +import io.wispforest.owo.ui.component.TextBoxComponent +import io.wispforest.owo.ui.core.Sizing +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +class ParsedTextBoxComponent(val parser: Parser, horizontalSizing: Sizing?) : TextBoxComponent(horizontalSizing) { + private var defaultColor = ColorCodes.TAN + private var parsed: T? = null + + var predicate: ((T) -> Boolean)? = null + + fun validate(onSuccess: ((T) -> Unit)? = null): Boolean = parsed + .let { z -> z != null && predicate.let { it == null || it(z) } + .also { if (it && onSuccess != null) onSuccess(z) } } + + init { + setMaxLength(500) + textValue.observe { + parsed = parser(it) + if (!validate { setEditableColor(ColorCodes.GREEN) }) { + setEditableColor(defaultColor) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/ui/components/buttons/ButtonComponents.kt b/src/main/kotlin/com/bibireden/data_attributes/ui/components/buttons/ButtonComponents.kt new file mode 100644 index 00000000..cfe61571 --- /dev/null +++ b/src/main/kotlin/com/bibireden/data_attributes/ui/components/buttons/ButtonComponents.kt @@ -0,0 +1,10 @@ +package com.bibireden.data_attributes.ui.components.buttons + +import io.wispforest.owo.ui.component.ButtonComponent +import io.wispforest.owo.ui.component.Components +import net.minecraft.text.Text +import java.util.function.Consumer + +object ButtonComponents { + fun remove(onPress: Consumer) = Components.button(Text.translatable("text.config.data_attributes.data_entry.remove"), onPress) +} \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/ui/components/fields/EditFieldComponent.kt b/src/main/kotlin/com/bibireden/data_attributes/ui/components/fields/EditFieldComponent.kt new file mode 100644 index 00000000..0cbed004 --- /dev/null +++ b/src/main/kotlin/com/bibireden/data_attributes/ui/components/fields/EditFieldComponent.kt @@ -0,0 +1,82 @@ +package com.bibireden.data_attributes.ui.components.fields + +import com.bibireden.data_attributes.api.parser.Parser +import com.bibireden.data_attributes.ui.colors.ColorCodes +import com.bibireden.data_attributes.ui.components.boxes.ParsedTextBoxComponent +import com.bibireden.data_attributes.ui.renderers.ButtonRenderers +import io.wispforest.owo.ui.component.Components +import io.wispforest.owo.ui.container.Containers +import io.wispforest.owo.ui.container.FlowLayout +import io.wispforest.owo.ui.core.Insets +import io.wispforest.owo.ui.core.Sizing +import io.wispforest.owo.ui.core.VerticalAlignment +import net.minecraft.text.Style +import net.minecraft.text.Text +import org.jetbrains.annotations.ApiStatus +import org.lwjgl.glfw.GLFW + +typealias EditFieldDecision = (T, EditFieldComponent) -> Unit +typealias EditFieldCancellation = (EditFieldComponent) -> Unit + +@ApiStatus.Internal +class EditFieldComponent(parser: Parser, private val onConfirmation: EditFieldDecision, private val onCancel: EditFieldCancellation? = null, private val autocomplete: Collection? = null) : FlowLayout(Sizing.fill(70), Sizing.content(), Algorithm.VERTICAL) { + val textBox: ParsedTextBoxComponent + + private val choices = mutableListOf() + + init { + padding(Insets.vertical(2)) + + child(Containers.horizontalFlow(Sizing.fill(100), Sizing.content(2)).also { hf -> + hf.verticalAlignment(VerticalAlignment.CENTER) + hf.id("edit-field") + + this.textBox = ParsedTextBoxComponent(parser, Sizing.fill(60)) + .apply { verticalSizing(Sizing.fixed(12)) } + .also(hf::child) + + hf.child(Components.button(Text.translatable("text.config.data_attributes.data_entry.yes")) { + textBox.validate { + this.remove() + onConfirmation(it, this) + } + } + .renderer(ButtonRenderers.STANDARD) + ) + hf.child(Components.button(Text.translatable("text.config.data_attributes.data_entry.no")) { + this.remove() + onCancel?.let { it(this) } + } + .renderer(ButtonRenderers.STANDARD) + ) + }) + + if (autocomplete != null) { + child(Containers.verticalFlow(Sizing.fill(100), Sizing.content()).also { sl -> + sl.gap(2) + sl.clearChildren() + + textBox.onChanged().subscribe { txt -> + sl.clearChildren() + choices.clear() + + if (txt.isEmpty()) return@subscribe + + for (k in autocomplete) { + if (k.toString().contains(txt)) { + choices.add(k) + sl.child(Components.label(Text.literal(k.toString()).setStyle(Style.EMPTY.withColor(ColorCodes.TAN)))) + } + } + } + + textBox.keyPress().subscribe { code, _, _ -> + if (choices.isNotEmpty() && code == GLFW.GLFW_KEY_ENTER) { + textBox.text = choices.first().toString() + } + true + } + }) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/ui/components/fields/FieldComponents.kt b/src/main/kotlin/com/bibireden/data_attributes/ui/components/fields/FieldComponents.kt new file mode 100644 index 00000000..61fbbdfa --- /dev/null +++ b/src/main/kotlin/com/bibireden/data_attributes/ui/components/fields/FieldComponents.kt @@ -0,0 +1,9 @@ +package com.bibireden.data_attributes.ui.components.fields + +import net.minecraft.util.Identifier + +object FieldComponents { + fun identifier(onConfirmation: EditFieldDecision, onCancel: EditFieldCancellation? = null, autocomplete: Collection? = null): EditFieldComponent { + return EditFieldComponent(Identifier::tryParse, onConfirmation, onCancel, autocomplete) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/ui/config/providers/AttributeFunctionProvider.kt b/src/main/kotlin/com/bibireden/data_attributes/ui/config/providers/AttributeFunctionProvider.kt new file mode 100644 index 00000000..7a1021e9 --- /dev/null +++ b/src/main/kotlin/com/bibireden/data_attributes/ui/config/providers/AttributeFunctionProvider.kt @@ -0,0 +1,260 @@ +package com.bibireden.data_attributes.ui.config.providers + +import com.bibireden.data_attributes.api.DataAttributesAPI +import com.bibireden.data_attributes.api.attribute.StackingBehavior +import com.bibireden.data_attributes.config.DataAttributesConfigProviders.registryEntryToText +import com.bibireden.data_attributes.config.DataAttributesConfigProviders.textBoxComponent +import com.bibireden.data_attributes.config.Validators +import com.bibireden.data_attributes.config.functions.AttributeFunction +import com.bibireden.data_attributes.config.functions.AttributeFunctionConfig +import com.bibireden.data_attributes.ui.components.CollapsibleFoldableContainer +import com.bibireden.data_attributes.ui.components.buttons.ButtonComponents +import com.bibireden.data_attributes.ui.components.fields.FieldComponents +import com.bibireden.data_attributes.ui.renderers.ButtonRenderers +import io.wispforest.owo.config.Option +import io.wispforest.owo.config.ui.component.ConfigToggleButton +import io.wispforest.owo.config.ui.component.OptionValueProvider +import io.wispforest.owo.config.ui.component.SearchAnchorComponent +import io.wispforest.owo.ui.component.Components +import io.wispforest.owo.ui.container.CollapsibleContainer +import io.wispforest.owo.ui.container.Containers +import io.wispforest.owo.ui.container.FlowLayout +import io.wispforest.owo.ui.core.Component +import io.wispforest.owo.ui.core.Positioning +import io.wispforest.owo.ui.core.Sizing +import io.wispforest.owo.ui.core.VerticalAlignment +import net.minecraft.entity.attribute.ClampedEntityAttribute +import net.minecraft.entity.attribute.EntityAttribute +import net.minecraft.registry.Registries +import net.minecraft.text.Text +import net.minecraft.util.Identifier + +class AttributeFunctionProvider(val option: Option) : FlowLayout(Sizing.fill(100), Sizing.content(), Algorithm.VERTICAL), OptionValueProvider { + private val backing = option.value().data + + private val headerComponents: MutableMap = mutableMapOf() + private val entryComponents: MutableMap = mutableMapOf() + + private fun getOrCreateEntryContainer(id: Identifier, isDefault: Boolean): CollapsibleFoldableContainer { + return childById(CollapsibleFoldableContainer::class.java, id.toString()) ?: CollapsibleFoldableContainer(Sizing.content(), Sizing.content(), registryEntryToText(id, Registries.ATTRIBUTE, { it.translationKey }, isDefault), true).also { cf -> + headerComponents[id] = cf + cf.id(id.toString()) + + cf.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(15)) + .apply { + verticalAlignment(VerticalAlignment.CENTER) + gap(10) + id("dock") + } + .also { fl -> + if (!isDefault) { + fl.child(ButtonComponents.remove { backing.remove(id); refreshAndDisplayEntries() } + .renderer(ButtonRenderers.STANDARD)) + + fl.child(Components.button(Text.translatable("text.config.data_attributes.data_entry.edit")) { + if (cf.childById(FlowLayout::class.java, "edit-field") == null) { + val field = FieldComponents.identifier( + { newId, _ -> + if (backing.containsKey(newId) || !Registries.ATTRIBUTE.containsId(newId)) return@identifier + + backing.remove(id)?.let { backing[newId] = it } + + refreshAndDisplayEntries() + }, + autocomplete = Registries.ATTRIBUTE.ids + ) + + field.textBox.predicate = { backing[id]?.get(it) == null && Registries.ATTRIBUTE.containsId(it) } + + cf.child(0, field) + } + } + .renderer(ButtonRenderers.STANDARD) + ) + } + + fl.child( + Components.button(Text.translatable("text.config.data_attributes.buttons.add")) { + val map = backing[id] ?: mutableMapOf() + map[Identifier("unknown")] = AttributeFunction() + backing[id] = map + refreshAndDisplayEntries(true) + } + .renderer(ButtonRenderers.STANDARD) + ) + } + ) + + if (id.toString() == "minecraft:unknown" && children.size > 1) child(1, cf) + else child(cf) + } + } + + private fun createEntry(id: Identifier, function: AttributeFunction, parentId: Identifier, isDefault: Boolean, parent: CollapsibleFoldableContainer): CollapsibleContainer { + // find if it exists already to ignore the default + val located = parent.childById(CollapsibleContainer::class.java, "${id}#child-fn") + if (located != null) return located + + val container = Containers.collapsible(Sizing.content(), Sizing.content(), registryEntryToText(id, Registries.ATTRIBUTE, { it.translationKey }, isDefault), true).apply { + gap(4) + id("${id}#child-fn") + + val attribute = Registries.ATTRIBUTE[id] + if (attribute !is EntityAttribute) { + titleLayout().tooltip(Text.translatable("text.config.data_attributes.data_entry.invalid")) + } else if (isDefault) { + titleLayout().tooltip(Text.translatable("text.config.data_attributes_data_entry.default")) + } + + if (!isDefault) { + child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(15)) + .apply { + verticalAlignment(VerticalAlignment.BOTTOM) + gap(10) + } + .also { fl -> + fl.child( + ConfigToggleButton() + .enabled(function.enabled) + .onPress { + backing[parentId]?.replace(id, function.copy(enabled = !function.enabled)) + refreshAndDisplayEntries() + } + .renderer(ButtonRenderers.STANDARD) + ) + fl.child( + ButtonComponents.remove { + backing[parentId]?.remove(id) + refreshAndDisplayEntries() + } + .renderer(ButtonRenderers.STANDARD) + ) + fl.child(Components.button(Text.translatable("text.config.data_attributes.data_entry.edit")) { + if (fl.childById(FlowLayout::class.java, "edit-field") == null) { + val field = FieldComponents.identifier( + { newId, _ -> + val entry = backing[parentId] ?: return@identifier + if (!Registries.ATTRIBUTE.containsId(newId) || entry[newId] != null) return@identifier + + entry[newId] = entry.remove(id) ?: return@identifier + backing[parentId] = entry + + refreshAndDisplayEntries() + }, + autocomplete = Registries.ATTRIBUTE.ids + ) + + field.textBox.predicate = { backing[parentId]?.get(it) == null && Registries.ATTRIBUTE.containsId(it) } + + child(0, field) + } + } + .renderer(ButtonRenderers.STANDARD) + ) + } + ) + } + + child(textBoxComponent( + Text.translatable("text.config.data_attributes.data_entry.functions.value"), + function.value, + Validators::isNumeric, + onChange = { + it.toDoubleOrNull()?.let { v -> + backing[parentId]?.replace(id, function.copy(value = v)) + refreshAndDisplayEntries(isDefault) + } + } + ).also { + if (attribute is ClampedEntityAttribute) { + it.tooltip(Text.translatable("text.config.data_attributes.data_entry.function_child", id, attribute.minValue, attribute.maxValue)) + } + }) + + child( + Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(20)).apply { + verticalAlignment(VerticalAlignment.CENTER) + + child( + Components.label(Text.translatable("text.config.data_attributes.data_entry.functions.behavior")) + .sizing(Sizing.content(), Sizing.fixed(20)) + ) + child( + Components.button(Text.translatable("text.config.data_attributes.enum.functionBehavior.${function.behavior.name.lowercase()}")) { + function.behavior = StackingBehavior.entries[function.behavior.ordinal xor 1] + + it.message = Text.translatable("text.config.data_attributes.enum.functionBehavior.${function.behavior.name.lowercase()}") + + backing[parentId]?.replace(id, function.copy(behavior = function.behavior)) + refreshAndDisplayEntries(isDefault) + } + .renderer(ButtonRenderers.STANDARD) + .positioning(Positioning.relative(100, 0)).horizontalSizing(Sizing.fixed(65)) + ) + } + ) + + child(SearchAnchorComponent(titleLayout(), Option.Key.ROOT, { id.toString() }, { Text.translatable(id.toTranslationKey()).toString() })) + } + + if (id.toString() == "minecraft:unknown" && parent.children().size > 1) parent.child(1, container) + else parent.child(container) + + entryComponents[id] = container + + // force a rearrangement to bring the dock up top~ + parent.childById(FlowLayout::class.java, "dock")?.let { + parent.removeChild(it) + parent.child(0, it) + } + + return container + } + + private fun createFunctionEntries(functions: Map, parentId: Identifier, parent: CollapsibleFoldableContainer) { + functions.forEach { (id, function) -> + val isDefault = backing[parentId]?.get(id) == null + createEntry(id, function, parentId, isDefault, parent) + } + } + + private fun refreshAndDisplayEntries(clearHeaders: Boolean = true) { + if (clearHeaders) { + headerComponents.values.forEach { component -> + component.id(null) + component.remove() + } + headerComponents.clear() + } + entryComponents.values.forEach { component -> + component.id(null) + component.remove() + } + entryComponents.clear() + + for ((id, functions) in backing) { + createFunctionEntries(functions, id, getOrCreateEntryContainer(id, !backing.containsKey(id))) + } + + for ((id, functions) in DataAttributesAPI.serverManager.defaults.functions.entries) { + createFunctionEntries(functions, id, getOrCreateEntryContainer(id, !backing.containsKey(id))) + } + } + + init { + child( + Components.button(Text.translatable("text.config.data_attributes.buttons.add")) { + backing[Identifier("unknown")] = mutableMapOf() + refreshAndDisplayEntries() + } + .renderer(ButtonRenderers.STANDARD) + .horizontalSizing(Sizing.content()) + .verticalSizing(Sizing.fixed(20)) + ) + + refreshAndDisplayEntries() + } + + override fun isValid() = !this.option.detached() + override fun parsedValue() = AttributeFunctionConfig(backing) +} \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/ui/config/providers/AttributeOverrideProvider.kt b/src/main/kotlin/com/bibireden/data_attributes/ui/config/providers/AttributeOverrideProvider.kt new file mode 100644 index 00000000..95f6317c --- /dev/null +++ b/src/main/kotlin/com/bibireden/data_attributes/ui/config/providers/AttributeOverrideProvider.kt @@ -0,0 +1,267 @@ +package com.bibireden.data_attributes.ui.config.providers + +import com.bibireden.data_attributes.api.DataAttributesAPI +import com.bibireden.data_attributes.api.attribute.AttributeFormat +import com.bibireden.data_attributes.api.attribute.StackingFormula +import com.bibireden.data_attributes.config.DataAttributesConfigProviders.registryEntryToText +import com.bibireden.data_attributes.config.DataAttributesConfigProviders.textBoxComponent +import com.bibireden.data_attributes.config.Validators +import com.bibireden.data_attributes.config.models.OverridesConfigModel.AttributeOverride +import com.bibireden.data_attributes.ext.round +import com.bibireden.data_attributes.mutable.MutableEntityAttribute +import com.bibireden.data_attributes.ui.components.fields.FieldComponents +import com.bibireden.data_attributes.ui.renderers.ButtonRenderers +import io.wispforest.owo.config.Option +import io.wispforest.owo.config.ui.component.ConfigToggleButton +import io.wispforest.owo.config.ui.component.OptionValueProvider +import io.wispforest.owo.config.ui.component.SearchAnchorComponent +import io.wispforest.owo.ui.component.Components +import io.wispforest.owo.ui.container.CollapsibleContainer +import io.wispforest.owo.ui.container.Containers +import io.wispforest.owo.ui.container.FlowLayout +import io.wispforest.owo.ui.core.* +import net.minecraft.registry.Registries +import net.minecraft.text.Text +import net.minecraft.util.Identifier +import kotlin.math.max + +class AttributeOverrideProvider(val option: Option>) : FlowLayout(Sizing.fill(100), Sizing.content(), Algorithm.VERTICAL), OptionValueProvider { + private val backing = option.value().toMutableMap() + + private val trackedContainers: MutableMap = mutableMapOf() + + private fun replaceEntry(id: Identifier, override: AttributeOverride) { + val noEntry = id !in backing + this.backing[id] = override + if (noEntry) refreshAndDisplayAttributes() + } + + /** Construct single override entry that discerns defaults from config-based ones. */ + private fun createOverrideEntry(id: Identifier, override: AttributeOverride, isDefault: Boolean) { + val attribute = Registries.ATTRIBUTE[id] as? MutableEntityAttribute + val isRegistered = attribute != null + + var override = override + + if (attribute != null) { + // extract min & max fallbacks from internals + override = override.copy( + min_fallback = attribute.`data_attributes$min_fallback`(), + max_fallback = attribute.`data_attributes$max_fallback`() + ) + } + + Containers.collapsible(Sizing.content(), Sizing.content(), registryEntryToText(id, Registries.ATTRIBUTE, { it.translationKey }, isDefault), true).also { container -> + container.child(SearchAnchorComponent(container.titleLayout(), Option.Key.ROOT, { id.toString() }, { Text.translatable(id.toTranslationKey()).toString() })) + + if (!isRegistered) { + container.titleLayout().tooltip(Text.translatable("text.config.data_attributes.data_entry.invalid")) + } + else if (isDefault) { + container.titleLayout().tooltip(Text.translatable("text.config.data_attributes_data_entry.default")) + } + + container.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(15)) + .also { content -> + content.verticalAlignment(VerticalAlignment.BOTTOM) + content.gap(10) + + content.child(ConfigToggleButton().also { button -> + button.enabled(override.enabled) + button.onPress { replaceEntry(id, override.copy(enabled = !override.enabled)) } + button.renderer(ButtonRenderers.STANDARD) + }) + + if (!isDefault) { + content.child(Components.button(Text.translatable("text.config.data_attributes.data_entry.reset")) + { + this.backing[id] = override.copy( + min = attribute?.`data_attributes$min_fallback`() ?: override.min_fallback, + max = attribute?.`data_attributes$max_fallback`() ?: override.max_fallback, + smoothness = 1.0, + formula = StackingFormula.Flat, + format = AttributeFormat.Whole + ) + refreshAndDisplayAttributes() + } + .renderer(ButtonRenderers.STANDARD) + ) + + content.child(Components.button(Text.translatable("text.config.data_attributes.data_entry.edit")) { + if (container.childById(FlowLayout::class.java, "edit-field") == null) { + val field = FieldComponents.identifier( + { newId, _ -> + val newAttribute = Registries.ATTRIBUTE[newId] as? MutableEntityAttribute ?: return@identifier + if (backing.containsKey(newId)) return@identifier + + this.backing.remove(id) + this.backing[newId] = override.copy( + min = newAttribute.`data_attributes$min_fallback`(), + max = newAttribute.`data_attributes$max_fallback`(), + smoothness = 1.0, + formula = StackingFormula.Flat, + format = AttributeFormat.Whole + ) + + refreshAndDisplayAttributes() + }, + autocomplete = Registries.ATTRIBUTE.ids + ) + + field.textBox.predicate = { it !in backing && Registries.ATTRIBUTE.containsId(it) } + + container.child(0, field) + } + } + .renderer(ButtonRenderers.STANDARD) + ) + + content.child(Components.button(Text.translatable("text.config.data_attributes.data_entry.remove")) + { + this.backing.remove(id) + refreshAndDisplayAttributes() + } + .renderer(ButtonRenderers.STANDARD) + ) + } + } + ) + + container.child(textBoxComponent( + Text.translatable("text.config.data_attributes.data_entry.overrides.min"), + override.min, + Validators::isNumeric, + onChange = { + it.toDoubleOrNull()?.let { v -> + replaceEntry(id, override.copy(min = v)) + } + }, + "inputs.min" + )) + + container.child( + textBoxComponent( + Text.translatable("text.config.data_attributes.data_entry.overrides.max"), + override.max, + Validators::isNumeric, + onChange = { + it.toDoubleOrNull()?.let { v -> + replaceEntry(id, override.copy(max = v)) + } + }, + "inputs.max" + ) + ) + + container.child( + textBoxComponent( + Text.translatable("text.config.data_attributes.data_entry.overrides.min_fallback"), + override.min_fallback, + Validators::isNumeric, + ) + ) + + container.child( + textBoxComponent( + Text.translatable("text.config.data_attributes.data_entry.overrides.max_fallback"), + override.max_fallback, + Validators::isNumeric, + ) + ) + + container.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(30)).also { hf -> + hf.verticalAlignment(VerticalAlignment.CENTER) + hf.gap(8) + hf.child( + Components.label(Text.translatable("text.config.data_attributes.data_entry.overrides.smoothness")) + .sizing(Sizing.content(), Sizing.fixed(20)) + ) + hf.child( + Components.discreteSlider(Sizing.fill(30), 0.01, 100.0).value(override.smoothness).also { slider -> + slider.onChanged().subscribe { + replaceEntry(id, override.copy(smoothness = max(slider.value().round(2), 0.01))) + } + } + .positioning(Positioning.relative(100, 0)) + ) + }) + + container.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(20)).also { hf -> + hf.verticalAlignment(VerticalAlignment.CENTER) + hf.gap(8) + hf.child( + Components.label(Text.translatable("text.config.data_attributes.data_entry.overrides.formula")) + .sizing(Sizing.content(), Sizing.fixed(20)) + ) + hf.child( + Components.button(Text.translatable("text.config.data_attributes.enum.stackingFormula.${override.formula.name.lowercase()}")) { + override.formula = StackingFormula.entries[override.formula.ordinal xor 1] + it.message = Text.translatable("text.config.data_attributes.enum.stackingFormula.${override.formula.name.lowercase()}") + replaceEntry(id, override.copy(formula = override.formula)) + } + .renderer(ButtonRenderers.STANDARD) + .positioning(Positioning.relative(100, 0)).horizontalSizing(Sizing.fixed(65)) + ) + }) + + container.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(20)).also { hf -> + hf.verticalAlignment(VerticalAlignment.CENTER) + hf.gap(8) + hf.child( + Components.label(Text.translatable("text.config.data_attributes.data_entry.overrides.format")) + .sizing(Sizing.content(), Sizing.fixed(20)) + ) + hf.child( + Components.button(Text.translatable("text.config.data_attributes.enum.format.${override.format.name.lowercase()}")) { + override.format = AttributeFormat.entries[override.format.ordinal xor 1] + it.message = Text.translatable("text.config.data_attributes.enum.format.${override.format.name.lowercase()}") + replaceEntry(id, override.copy(formula = override.formula)) + } + .renderer(ButtonRenderers.STANDARD) + .positioning(Positioning.relative(100, 0)).horizontalSizing(Sizing.fixed(65)) + ) + }) + container.id(id.toString()) + } + .also { + if (id.toString() == "minecraft:unknown" && children.size > 1) child(1, it) + else child(it) + } + .also { trackedContainers[id] = it } + } + + init { + child( + Components.button(Text.translatable("text.config.data_attributes.buttons.add")) { + backing[Identifier("unknown")] = AttributeOverride() + refreshAndDisplayAttributes() + } + .renderer(ButtonRenderers.STANDARD) + .horizontalSizing(Sizing.content()) + .verticalSizing(Sizing.fixed(20)) + ) + + refreshAndDisplayAttributes() + } + + /** Initiates a deletion of all config components and replaces them with fresh ones provided from the map. */ + private fun refreshAndDisplayAttributes() { + trackedContainers.values.forEach(CollapsibleContainer::remove) + + val manager = DataAttributesAPI.serverManager + + // config first + for ((id, override) in backing) { // DataAttributes.OVERRIDES_CONFIG.overrides + createOverrideEntry(id, override, false) + } + + // defaults + for ((id, override) in manager.defaults.overrides.entries) { + if (id !in backing) createOverrideEntry(id, override, true) + } + } + + override fun isValid() = !this.option.detached() + + override fun parsedValue() = backing +} \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/data_attributes/ui/config/providers/EntityTypesProvider.kt b/src/main/kotlin/com/bibireden/data_attributes/ui/config/providers/EntityTypesProvider.kt new file mode 100644 index 00000000..84719237 --- /dev/null +++ b/src/main/kotlin/com/bibireden/data_attributes/ui/config/providers/EntityTypesProvider.kt @@ -0,0 +1,245 @@ +package com.bibireden.data_attributes.ui.config.providers + +import com.bibireden.data_attributes.api.DataAttributesAPI +import com.bibireden.data_attributes.config.DataAttributesConfigProviders.registryEntryToText +import com.bibireden.data_attributes.config.DataAttributesConfigProviders.textBoxComponent +import com.bibireden.data_attributes.config.Validators +import com.bibireden.data_attributes.config.entities.EntityTypeData +import com.bibireden.data_attributes.config.entities.EntityTypeEntry +import com.bibireden.data_attributes.ext.round +import com.bibireden.data_attributes.ui.components.CollapsibleFoldableContainer +import com.bibireden.data_attributes.ui.components.buttons.ButtonComponents +import com.bibireden.data_attributes.ui.components.fields.FieldComponents +import com.bibireden.data_attributes.ui.renderers.ButtonRenderers +import io.wispforest.owo.config.Option +import io.wispforest.owo.config.ui.component.OptionValueProvider +import io.wispforest.owo.config.ui.component.SearchAnchorComponent +import io.wispforest.owo.ui.component.Components +import io.wispforest.owo.ui.container.CollapsibleContainer +import io.wispforest.owo.ui.container.Containers +import io.wispforest.owo.ui.container.FlowLayout +import io.wispforest.owo.ui.core.Component +import io.wispforest.owo.ui.core.Sizing +import io.wispforest.owo.ui.core.VerticalAlignment +import net.minecraft.entity.EntityType +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.attribute.ClampedEntityAttribute +import net.minecraft.entity.attribute.DefaultAttributeRegistry +import net.minecraft.registry.Registries +import net.minecraft.text.Text +import net.minecraft.util.Identifier + +class EntityTypesProvider(val option: Option>) : FlowLayout(Sizing.fill(100), Sizing.content(), Algorithm.VERTICAL), OptionValueProvider { + private val backing = HashMap(option.value()) + + private val headerComponents: MutableMap = mutableMapOf() + private val entryComponents: MutableMap = mutableMapOf() + + private fun createEntries(id: Identifier, types: Map, isDefault: Boolean = false): CollapsibleFoldableContainer { + val container = childById(CollapsibleFoldableContainer::class.java, id.toString()) + ?: CollapsibleFoldableContainer(Sizing.content(), Sizing.content(), registryEntryToText(id, Registries.ENTITY_TYPE, { it.translationKey }, isDefault), true) + .also { ct -> + ct.gap(4) + ct.id(id.toString()) + + ct.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(15)) + .apply { + verticalAlignment(VerticalAlignment.CENTER) + gap(10) + id("dock") + } + .also { fl -> + if (!isDefault) { + fl.child(ButtonComponents.remove { backing.remove(id); refreshAndDisplayEntries(true) } + .renderer(ButtonRenderers.STANDARD)) + + fl.child(Components.button(Text.translatable("text.config.data_attributes.data_entry.edit")) { + if (ct.childById(FlowLayout::class.java, "edit-field") == null) { + val field = FieldComponents.identifier( + { newId, _ -> + if (backing.containsKey(newId) || !Registries.ENTITY_TYPE.containsId(newId)) return@identifier + + backing.remove(id)?.let { backing[newId] = it } + refreshAndDisplayEntries(true) + }, + autocomplete = Registries.ENTITY_TYPE.ids + ) + + field.textBox.predicate = { it !in backing && Registries.ENTITY_TYPE.containsId(it) } + + ct.child(0, field) + } + } + .renderer(ButtonRenderers.STANDARD) + ) + } + + fl.child( + Components.button(Text.translatable("text.config.data_attributes.buttons.add")) { + val map = backing[id]?.data?.toMutableMap() ?: mutableMapOf() + map[Identifier("unknown")] = EntityTypeEntry() + backing[id] = EntityTypeData(map) + refreshAndDisplayEntries(true) + } + .renderer(ButtonRenderers.STANDARD) + ) + } + ) + + headerComponents[id] = ct + + child(SearchAnchorComponent(ct.titleLayout(), Option.Key.ROOT, id::toString, { Text.translatable(id.toTranslationKey()).toString() })) + + + if (id.toString() == "minecraft:unknown" && children.size > 1) child(1, ct) + else child(ct) + } + + for ((entryId, entry) in types) { + createEntry(entryId, entry, id, container, backing[id]?.data?.get(entryId) == null) + } + + return container + } + + private fun createEntry(id: Identifier, entityTypeEntry: EntityTypeEntry, parentId: Identifier, parent: CollapsibleContainer, isDefault: Boolean) { + if (parent.childById(CollapsibleContainer::class.java, id.toString()) != null) return + + Containers.collapsible(Sizing.content(), Sizing.content(), registryEntryToText(id, Registries.ATTRIBUTE, { it.translationKey }, isDefault), true).also { ct -> + ct.gap(4) + ct.id(id.toString()) + + // find fallback + entityTypeEntry.fallback = Registries.ENTITY_TYPE.get(parentId)?.let { entityType -> + val defaultContainer = DefaultAttributeRegistry.get(entityType as EntityType) + Registries.ATTRIBUTE.get(id)?.let(defaultContainer::getBaseValue) + }?.round(2) + + if (!isDefault) { + ct.child(Containers.horizontalFlow(Sizing.fill(100), Sizing.fixed(15)) + .apply { + verticalAlignment(VerticalAlignment.BOTTOM) + gap(10) + } + .also { fl -> + fl.child(ButtonComponents.remove { + val entry = backing[parentId]?.data?.toMutableMap() ?: return@remove + entry.remove(id) + backing[parentId] = EntityTypeData(entry) + refreshAndDisplayEntries(true) + } + .renderer(ButtonRenderers.STANDARD)) + + fl.child(Components.button(Text.translatable("text.config.data_attributes.data_entry.edit")) { + if (ct.childById(FlowLayout::class.java, "edit-field") == null) { + val field = FieldComponents.identifier( + { newId, _ -> + val entry = backing[parentId]?.data?.toMutableMap() ?: return@identifier + if (entry.containsKey(newId) || !Registries.ATTRIBUTE.containsId(newId)) return@identifier + + entry[newId] = entry.remove(id) ?: return@identifier + backing[parentId] = EntityTypeData(entry) + + refreshAndDisplayEntries(true) + }, + autocomplete = Registries.ATTRIBUTE.ids + ) + + field.textBox.predicate = { backing[parentId]?.data?.get(it) == null && Registries.ATTRIBUTE.containsId(it) } + + ct.child(0, field) + } + } + .renderer(ButtonRenderers.STANDARD) + ) + } + ) + } + + ct.child(textBoxComponent( + Text.translatable("text.config.data_attributes.data_entry.entity_types.value"), + entityTypeEntry.value, + Validators::isNumeric, + onChange = { + it.toDoubleOrNull()?.let { value -> + val data = this.backing.remove(parentId) ?: EntityTypeData() + val mapping = data.data.toMutableMap() + mapping[id] = entityTypeEntry.copy(value = value) + this.backing[parentId] = data.copy(data = mapping) + + if (isDefault) refreshAndDisplayEntries(true) + } + } + ).apply { + val attribute = Registries.ATTRIBUTE[id] as? ClampedEntityAttribute ?: return@apply + tooltip(Text.translatable("text.config.data_attributes.data_entry.entity_type_value", id, attribute.minValue, attribute.maxValue)) + }) + + val fallback = entityTypeEntry.fallback + if (fallback != null) { + ct.child(textBoxComponent( + Text.translatable("text.config.data_attributes.data_entry.fallback"), + fallback, + Validators::isNumeric, + )) + } + + if (isDefault) { + ct.titleLayout().tooltip(Text.translatable("text.config.data_attributes_data_entry.default")) + } + + ct.child(SearchAnchorComponent(ct.titleLayout(), Option.Key.ROOT, id::toString, { Text.translatable(id.toTranslationKey()).toString() })) + + entryComponents[id] = ct + + if (id.toString() == "minecraft:unknown" && parent.children().size > 1) parent.child(1, ct) + else parent.child(ct) + } + + // force a rearrangement to bring the dock up top~ + parent.childById(FlowLayout::class.java, "dock")?.let { + parent.removeChild(it) + parent.child(0, it) + } + } + + private fun refreshAndDisplayEntries(clearHeaders: Boolean = false) { + if (clearHeaders) { + headerComponents.values.forEach { component -> + component.id(null) + component.remove() + } + headerComponents.clear() + } + entryComponents.values.forEach { component -> + component.id(null) + component.remove() + } + entryComponents.clear() + + for ((id, types) in backing) { + createEntries(id, types.data, false) + } + + for ((id, types) in DataAttributesAPI.serverManager.defaults.types.entries) { + createEntries(id, types, id !in backing) + } + } + + init { + child( + Components.button(Text.translatable("text.config.data_attributes.buttons.add")) { + backing[Identifier("unknown")] = EntityTypeData() + refreshAndDisplayEntries(true) + } + .renderer(ButtonRenderers.STANDARD) + .horizontalSizing(Sizing.content()) + .verticalSizing(Sizing.fixed(20)) + ) + + refreshAndDisplayEntries() + } + + override fun isValid() = !this.option.detached() + override fun parsedValue() = backing +} \ No newline at end of file diff --git a/src/main/resources/assets/data_attributes/lang/en_us.json b/src/main/resources/assets/data_attributes/lang/en_us.json index 27515405..ad75e300 100644 --- a/src/main/resources/assets/data_attributes/lang/en_us.json +++ b/src/main/resources/assets/data_attributes/lang/en_us.json @@ -48,6 +48,33 @@ {"text": "Reset", "color": "white"} ], + "text.config.data_attributes.data_entry.edit": [ + {"text": "✎ ", "color": "#efb13f"}, + {"text": "Edit", "color": "white"} + ], + + "text.config.data_attributes.data_entry.remove": [ + {"text": "\uD83D\uDDD1 ", "color": "#EB1D36"}, + {"text": "Remove", "color": "white"} + ], + + "text.config.data_attributes.data_entry.yes": [ + {"text": "[", "color": "white"}, + {"text": "✔", "color": "#84de56"}, + {"text": "]"} + ], + + "text.config.data_attributes.data_entry.no": [ + {"text": "[", "color": "white"}, + {"text": "X", "color": "#EB1D36"}, + {"text": "]"} + ], + + "text.config.data_attributes.data_entry.fallback": [ + {"text": "⬸ ", "color": "#979797"}, + {"text": "Fallback", "color": "white"} + ], + "text.config.data_attributes.data_entry.overrides.min_fallback": [ {"text": "⭳ ", "color": "#979797"}, {"text": "Min Fallback", "color": "white"} @@ -117,6 +144,19 @@ {"text": "when ready.", "italic": true, "color": "gray"} ], + "text.config.data_attributes_data_entry.default": [ + {"text": "✱ ", "color": "#84de56"}, + {"text": "Default Entry\n\n", "color": "white"}, + {"text": "This entry is from another mod or datapack, and can be overrided by editing it.", "color": "white"} + ], + + "text.config.data_attributes.buttons.add": [ + {"text": "(", "color": "white"}, + {"text": "+", "color": "#84de56"}, + {"text": ")"}, + {"text": " Add"} + ], + "ui.data_attributes.cfc.collapse_all": "Collapse", "ui.data_attributes.cfc.uncollapse_all": "Uncollapse", diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 2df57b17..2ee4aca2 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -5,8 +5,38 @@ "name": "Data Attributes: Directors Cut", "description": "This mod serves to overhaul the Entity Attribute system to be a lot more dynamic.", "authors": [ - "Bare Minimum Studios", - "CleverNucleus (former author)" + { + "name": "bibireden", + "contact": { + "homepage": "https://github.com/bibi-reden" + } + }, + { + "name": "OverlordsIII", + "contact": { + "homepage": "https://github.com/OverlordsIII" + } + }, + { + "name": "DataEncoded", + "contact": { + "homepage": "https://github.com/DataEncoded" + } + } + ], + "contributors": [ + { + "name": "pokesmells", + "contact": { + "homepage": "https://github.com/pokesmells" + } + }, + { + "name": "CleverNucleus [former author]", + "contact": { + "homepage": "https://github.com/CleverNucleus" + } + } ], "contact": { "homepage": "https://github.com/BareMinimumStudios/data-attributes", @@ -49,8 +79,8 @@ "curseforge": 955929, "loaders": ["fabric", "quilt"], "dependencies": [ - "fabric-api@0.92.2+1.20.1(required){modrinth:P7dR8mSH}{curseforge:306612}#(ignore:github)", - "fabric-language-kotlin@1.12.0+kotlin.2.0.10(required){modrinth:Ha28R6CL}{curseforge:308769}#(ignore:github)", + "fabric-api@>=0.92.2+1.20.1(required){modrinth:P7dR8mSH}{curseforge:306612}#(ignore:github)", + "fabric-language-kotlin@>=1.12.0+kotlin.2.0.10(required){modrinth:Ha28R6CL}{curseforge:308769}#(ignore:github)", "owo-lib@0.11.2(required){modrinth:ccKDOlHs}{curseforge:532610}#(ignore:github)" ] },