From feb0c4d55102dca8ecf9f1cbd1def589b61e6342 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Fri, 23 Sep 2022 16:52:00 +0700 Subject: [PATCH] Android Research is now loaded through JSON files --- .../ru/dbotthepony/mc/otm/datagen/DataGen.kt | 7 +- .../mc/otm/datagen/ResearchData.kt | 258 +++++++++ .../mc/otm/datagen/lang/English.kt | 33 -- .../datagen/lang/MatteryLanguageProvider.kt | 22 +- .../mc/otm/OverdriveThatMatters.java | 4 + .../mc/otm/android/AndroidResearch.kt | 224 +++++++- .../mc/otm/android/AndroidResearchBuilder.kt | 460 ---------------- .../android/AndroidResearchDataProvider.kt | 56 ++ .../mc/otm/android/AndroidResearchManager.kt | 133 +++++ .../mc/otm/android/AndroidResearchType.kt | 510 ++++++++++++++---- .../otm/android/feature/NightVisionFeature.kt | 6 +- .../otm/android/feature/ShockwaveFeature.kt | 4 +- .../otm/capability/MatteryPlayerCapability.kt | 29 +- .../mc/otm/client/render/ResearchIcons.kt | 63 +++ .../mc/otm/client/render/SkinElement.kt | 63 +++ .../otm/client/screen/AndroidStationScreen.kt | 21 +- .../kotlin/ru/dbotthepony/mc/otm/core/Ext.kt | 29 + .../ru/dbotthepony/mc/otm/core/ListSet.kt | 59 ++ .../dbotthepony/mc/otm/data/ItemStackCodec.kt | 76 ++- .../mc/otm/data/JsonArraySpliterator.kt | 30 ++ .../network/MatteryPlayerNetworkChannel.kt | 18 +- .../mc/otm/network/RegistryNetworkChannel.kt | 3 + .../mc/otm/registry/AndroidResearch.kt | 302 ----------- .../dbotthepony/mc/otm/registry/MRegistry.kt | 9 - 24 files changed, 1444 insertions(+), 975 deletions(-) create mode 100644 src/data/kotlin/ru/dbotthepony/mc/otm/datagen/ResearchData.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchBuilder.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/core/ListSet.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/data/JsonArraySpliterator.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidResearch.kt diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt index 1f8b49ec1..e0d531013 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt @@ -10,6 +10,7 @@ import net.minecraftforge.eventbus.api.SubscribeEvent import net.minecraftforge.fml.common.Mod import net.minecraftforge.data.event.GatherDataEvent import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.android.AndroidResearchDataProvider import ru.dbotthepony.mc.otm.block.* import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.datagen.blocks.BatteryBankProvider @@ -214,7 +215,6 @@ object DataGen { val tagsProvider = TagsProvider(event) addTags(tagsProvider) - AddEnglishLanguage(languageProvider) event.generator.addProvider(true, blockModelProvider) event.generator.addProvider(true, itemModelProvider) @@ -225,6 +225,9 @@ object DataGen { event.generator.addProvider(true, lootTableProvider) event.generator.addProvider(true, lootModifier) event.generator.addProvider(true, SoundDataProvider(event)) + event.generator.addProvider(true, AndroidResearchDataProvider(event.generator).also { it.exec { addResearchData(it, languageProvider) } }) + + AddEnglishLanguage(languageProvider) blockModelProvider.resourceCubeAll(MBlocks.TRITANIUM_ORE) blockModelProvider.resourceCubeAll(MBlocks.TRITANIUM_RAW_BLOCK) @@ -274,5 +277,7 @@ object DataGen { lootModifier.lambda { addLootModifiers(it) } + + languageProvider.registerProviders() } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/ResearchData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/ResearchData.kt new file mode 100644 index 000000000..8fc6176d5 --- /dev/null +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/ResearchData.kt @@ -0,0 +1,258 @@ +package ru.dbotthepony.mc.otm.datagen + +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.android.AndroidResearchType +import ru.dbotthepony.mc.otm.client.render.ResearchIcons +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider +import ru.dbotthepony.mc.otm.registry.AndroidFeatures +import ru.dbotthepony.mc.otm.registry.MNames +import java.util.LinkedList +import java.util.function.Consumer + +@Suppress("LocalVariableName") +fun addResearchData(serializer: Consumer, lang: MatteryLanguageProvider) { + val AIR_BAGS = AndroidResearchType.Builder(modLocation(MNames.AIR_BAGS)) + .withExperience(18) + .addFeatureResult(AndroidFeatures.AIR_BAGS) + .withDescription() + .withIcon(ResearchIcons.ICON_AIR_BAGS) + .build() + + serializer.accept(AIR_BAGS) + + val IMPROVED_LIMBS = AndroidResearchType.Builder(modLocation(MNames.IMPROVED_LIMBS)) + .withExperience(20) + .withDescription() + .withIcon(ResearchIcons.ICON_EXTENDED_REACH) + .build() + + serializer.accept(IMPROVED_LIMBS) + + val STEP_ASSIST = AndroidResearchType.Builder(modLocation(MNames.STEP_ASSIST)) + .withExperience(24) + .addFeatureResult(AndroidFeatures.STEP_ASSIST) + .withDescription() + .withIcon(ResearchIcons.ICON_STEP_ASSIST) + .addPrerequisite(IMPROVED_LIMBS) + .build() + + serializer.accept(STEP_ASSIST) + + val EXTENDED_REACH = AndroidResearchType.Builder(modLocation(MNames.EXTENDED_REACH)) + .withExperience(40) + .addFeatureResult(AndroidFeatures.EXTENDED_REACH) + .addPrerequisite(IMPROVED_LIMBS) + .withDescription() + .withIcon(ResearchIcons.ICON_EXTENDED_REACH) + .build() + + serializer.accept(EXTENDED_REACH) + + val NIGHT_VISION = AndroidResearchType.Builder(modLocation(MNames.NIGHT_VISION)) + .withExperience(40) + .withDescription() + .withIcon(ResearchIcons.ICON_NIGHT_VISION) + .addFeatureResult(AndroidFeatures.NIGHT_VISION) + .build() + + serializer.accept(NIGHT_VISION) + + val NANOBOTS = AndroidResearchType.Builder(modLocation(MNames.NANOBOTS)) + .withExperience(15) + .withDescription() + .withIcon(ResearchIcons.ICON_NANOBOTS) + .build() + + serializer.accept(NANOBOTS) + + val NANOBOTS_ARMOR = AndroidResearchType.Builder(modLocation(MNames.NANOBOTS_ARMOR)) + .withExperience(25) + .withDescription() + .addPrerequisite(OverdriveThatMatters.loc(MNames.NANOBOTS)) + .addFeatureResult(OverdriveThatMatters.loc(MNames.NANOBOTS_ARMOR)) + .withIcon(ResearchIcons.ICON_ARMOR) + .addBlocker(OverdriveThatMatters.loc(MNames.ATTACK_BOOST_1), rigid = true) + .build() + + serializer.accept(NANOBOTS_ARMOR) + + val limbList = LinkedList() + val regenList = LinkedList() + val armorStrengthList = LinkedList() + val armorSpeedList = LinkedList() + val attackBoostList = LinkedList() + + for (i in 0 until 4) { + limbList.add(run { + val research = AndroidResearchType.Builder(modLocation(MNames.LIMB_OVERCLOCKING_LIST[i])) + .withExperience(18 + i * 8) + .withIconText(TextComponent((i + 1).toString())) + .addPrerequisite(IMPROVED_LIMBS) + .withIcon(ResearchIcons.ICON_LIMB_OVERCLOCKING) + .withName(TranslatableComponent("android_research.overdrive_that_matters.limb_overclocking", i + 1)) + .withDescription( + TranslatableComponent( + "android_research.overdrive_that_matters.limb_overclocking.description", + (i + 1) * 8, + (i + 1) * 6 + ) + ) + .addFeatureResult(OverdriveThatMatters.loc(MNames.LIMB_OVERCLOCKING), i) + + if (i > 0) { + research.addPrerequisite(OverdriveThatMatters.loc(MNames.LIMB_OVERCLOCKING_LIST[i - 1]), rigid = true) + } + + research.build() + }) + + attackBoostList.add(run { + val research = AndroidResearchType.Builder(modLocation(MNames.ATTACK_BOOST_LIST[i])) + .withExperience(18 + i * 8) + .withIconText(TextComponent((i + 1).toString())) + .addPrerequisite(IMPROVED_LIMBS) + .withIcon(ResearchIcons.ICON_ATTACK_BOOST) + .withName(TranslatableComponent("android_research.overdrive_that_matters.attack_boost", i + 1)) + .withDescription( + TranslatableComponent( + "android_research.overdrive_that_matters.attack_boost.description", + (i + 1) * 6 + ) + ) + .addFeatureResult(AndroidFeatures.ATTACK_BOOST, i) + .addBlocker(NANOBOTS_ARMOR) + + if (i > 0) { + research.addPrerequisite(OverdriveThatMatters.loc(MNames.ATTACK_BOOST_LIST[i - 1]), rigid = true) + } + + research.build() + }) + + regenList.add(run { + val regeneration = AndroidResearchType.Builder(modLocation(MNames.NANOBOTS_REGENERATION_LIST[i])) + .withExperience(20 + i * 6) + .withIconText(TextComponent((i + 1).toString())) + .withIcon(ResearchIcons.ICON_NANOBOTS) + .withName(TranslatableComponent("android_research.overdrive_that_matters.nanobots_regeneration", i + 1)) + .withDescription( + if (i > 0) TranslatableComponent("android_research.overdrive_that_matters.nanobots_regeneration.description_improve") else TranslatableComponent( + "android_research.overdrive_that_matters.nanobots_regeneration.description" + ) + ) + .addFeatureResult(AndroidFeatures.NANOBOTS_REGENERATION, i) + + if (i > 0) { + regeneration.addPrerequisite(OverdriveThatMatters.loc(MNames.NANOBOTS_REGENERATION_LIST[i - 1]), rigid = true) + } else { + regeneration.addPrerequisite(OverdriveThatMatters.loc(MNames.NANOBOTS), rigid = true) + } + + regeneration.build() + }) + } + + for (i in 0 until 3) { + val level = i + 1 + + armorStrengthList.add(run { + AndroidResearchType.Builder(modLocation(MNames.NANOBOTS_ARMOR_STRENGTH_LIST[i])) + .withExperience(20 + i * 8) + .withIconText(TextComponent((i + 1).toString())) + .withIcon(ResearchIcons.ICON_ARMOR) + .addPrerequisite(OverdriveThatMatters.loc(if (i > 0) MNames.NANOBOTS_ARMOR_STRENGTH_LIST[i - 1] else MNames.NANOBOTS_ARMOR)) + .withName( + TranslatableComponent( + "android_research.overdrive_that_matters.nanobots_armor_strength", + i + 1 + ) + ) + .withDescription( + TranslatableComponent( + "android_research.overdrive_that_matters.nanobots_armor_strength.description", + (i + 1) * 8, + (i + 1) * 6 + ) + ) + .addFeatureResult(AndroidFeatures.NANOBOTS_ARMOR, 0) // TODO + .build() + }) + + armorSpeedList.add(run { + AndroidResearchType.Builder(modLocation(MNames.NANOBOTS_ARMOR_SPEED_LIST[i])) + .withExperience(18 + i * 6) + .withIconText(TextComponent((i + 1).toString())) + .withIcon(ResearchIcons.ICON_ARMOR) + .addPrerequisite(OverdriveThatMatters.loc(if (i > 0) MNames.NANOBOTS_ARMOR_SPEED_LIST[i - 1] else MNames.NANOBOTS_ARMOR)) + .withName( + TranslatableComponent( + "android_research.overdrive_that_matters.nanobots_armor_speed", + i + 1 + ) + ) + .withDescription( + TranslatableComponent( + "android_research.overdrive_that_matters.nanobots_armor_speed.description", + (i + 1) * 8, + (i + 1) * 6 + ) + ) + .addFeatureResult(AndroidFeatures.NANOBOTS_ARMOR, 0) // TODO + .build() + }) + } + + limbList.forEach(serializer::accept) + regenList.forEach(serializer::accept) + armorStrengthList.forEach(serializer::accept) + armorSpeedList.forEach(serializer::accept) + attackBoostList.forEach(serializer::accept) + + val SHOCKWAVE = + AndroidResearchType.Builder(modLocation(MNames.SHOCKWAVE)) + .withExperience(40) + .withDescription() + .withIcon(ResearchIcons.ICON_SHOCKWAVE) + .addFeatureResult(AndroidFeatures.SHOCKWAVE) + .addPrerequisite(attackBoostList[2]) + .build() + + serializer.accept(SHOCKWAVE) + + with(lang.english) { + add(limbList[0], "Limb Overclocking %s") + add(limbList[0], "description", "Boosts unit's mobility by %s%% and attack speed by %s%%") + + add(AIR_BAGS, "Air Bags") + add(NANOBOTS, "Nanobots") + add(AIR_BAGS, "description", "Allows unit to swim in water") + add(NANOBOTS, "description", "Various useful nanobots for doing various tasks") + + add(regenList[0], "Regeneration %s") + add(regenList[0], "description", "Nanobots get ability to repair unit's internal systems on the move") + add(regenList[0], "description_improve", "Improves regeneration speed") + + add(NANOBOTS_ARMOR, "Nanobots Armor") + add(NANOBOTS_ARMOR, "description", "Allows nanobots to align themselves in cell shape, reducing incoming damage by a %% by absorbing impacts") + + add(armorSpeedList[0], "Nanobots Armor Build Speed %s") + add(armorSpeedList[0], "description", "Reduces time required for nanobots to form protection layer") + + add(armorStrengthList[0], "Nanobots Armor Strength %s") + add(armorStrengthList[0], "description", "Increases impact absorption strength of nanobots") + + add(EXTENDED_REACH, "Extended Reach") + add(EXTENDED_REACH, "description", "Increases block interaction distance") + + add(IMPROVED_LIMBS, "Improved Limbs") + add(IMPROVED_LIMBS, "description", "Allows limbs to be upgraded") + + add(STEP_ASSIST, "Step Assist") + add(STEP_ASSIST, "description", "Allows unit to step up whole blocks") + + add(attackBoostList[0], "Improved Arms Servo %s") + add(attackBoostList[0], "description", "Increases total melee attack strength by %s%%") + } +} diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt index 73487aa28..b9df87def 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt @@ -455,39 +455,6 @@ private fun research(provider: MatteryLanguageProvider) { add("android_research.status.requires", "Requires %s to be researched") add("android_research.status.blocks", "Locks %s") add("android_research.status.blocked_by", "Locked by %s") - - add(AndroidResearch.LIMB_OVERCLOCKING[0], "Limb Overclocking %s") - add(AndroidResearch.LIMB_OVERCLOCKING[0], "description", "Boosts unit's mobility by %s%% and attack speed by %s%%") - - add(AndroidResearch.AIR_BAGS, "Air Bags") - add(AndroidResearch.NANOBOTS, "Nanobots") - add(AndroidResearch.AIR_BAGS, "description", "Allows unit to swim in water") - add(AndroidResearch.NANOBOTS, "description", "Various useful nanobots for doing various tasks") - - add(AndroidResearch.NANOBOTS_REGENERATION[0], "Regeneration %s") - add(AndroidResearch.NANOBOTS_REGENERATION[0], "description", "Nanobots get ability to repair unit's internal systems on the move") - add(AndroidResearch.NANOBOTS_REGENERATION[0], "description_improve", "Improves regeneration speed") - - add(AndroidResearch.NANOBOTS_ARMOR, "Nanobots Armor") - add(AndroidResearch.NANOBOTS_ARMOR, "description", "Allows nanobots to align themselves in cell shape, reducing incoming damage by a %% by absorbing impacts") - - add(AndroidResearch.NANOBOTS_ARMOR_SPEED[0], "Nanobots Armor Build Speed %s") - add(AndroidResearch.NANOBOTS_ARMOR_SPEED[0], "description", "Reduces time required for nanobots to form protection layer") - - add(AndroidResearch.NANOBOTS_ARMOR_STRENGTH[0], "Nanobots Armor Strength %s") - add(AndroidResearch.NANOBOTS_ARMOR_STRENGTH[0], "description", "Increases impact absorption strength of nanobots") - - add(AndroidResearch.EXTENDED_REACH, "Extended Reach") - add(AndroidResearch.EXTENDED_REACH, "description", "Increases block interaction distance") - - add(AndroidResearch.IMPROVED_LIMBS, "Improved Limbs") - add(AndroidResearch.IMPROVED_LIMBS, "description", "Allows limbs to be upgraded") - - add(AndroidResearch.STEP_ASSIST, "Step Assist") - add(AndroidResearch.STEP_ASSIST, "description", "Allows unit to step up whole blocks") - - add(AndroidResearch.ATTACK_BOOST[0], "Improved Arms Servo %s") - add(AndroidResearch.ATTACK_BOOST[0], "description", "Increases total melee attack strength by %s%%") } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/MatteryLanguageProvider.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/MatteryLanguageProvider.kt index 49a5bf39c..9a402b6d7 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/MatteryLanguageProvider.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/MatteryLanguageProvider.kt @@ -20,7 +20,7 @@ import ru.dbotthepony.mc.otm.android.AndroidFeatureType import ru.dbotthepony.mc.otm.android.AndroidResearchType import ru.dbotthepony.mc.otm.registry.objects.ColoredDecorativeBlock -private fun researchString(key: AndroidResearchType<*>): String { +private fun researchString(key: AndroidResearchType): String { val displayName = key.displayName if (displayName is MutableComponent) { @@ -36,17 +36,19 @@ private fun researchString(key: AndroidResearchType<*>): String { class MatteryLanguageProvider(private val gen: DataGenerator) { private inner class Slave(language: String) : LanguageProvider(gen, OverdriveThatMatters.MOD_ID, language) { - init { - gen.addProvider(true, this) - } - override fun addTranslations() {} } private val slaves = Object2ObjectArrayMap() + fun registerProviders() { + for (slave in slaves.values) { + gen.addProvider(true, slave) + } + } + inner class Builder(language: String) { - val slave: LanguageProvider = slaves.computeIfAbsent(language, ::Slave) + val slave: LanguageProvider by lazy { slaves.computeIfAbsent(language, ::Slave) } fun add(key: String, value: String) = slave.add(key, value) fun add(key: Block, value: String) = slave.add(key, value) @@ -61,9 +63,9 @@ class MatteryLanguageProvider(private val gen: DataGenerator) { fun death(key: String, value: String) = slave.add("death.attack.$key", value) fun stat(key: String, value: String) = slave.add("stat.${OverdriveThatMatters.MOD_ID}.$key", value) - fun add(key: AndroidResearchType<*>, value: String) = slave.add(researchString(key), value) + fun add(key: AndroidResearchType, value: String) = slave.add(researchString(key), value) fun research(key: String, value: String) = slave.add("android_research.overdrive_that_matters.$key", value) - fun add(key: AndroidResearchType<*>, suffix: String, value: String) = slave.add("${researchString(key)}.$suffix", value) + fun add(key: AndroidResearchType, suffix: String, value: String) = slave.add("${researchString(key)}.$suffix", value) fun research(key: String, suffix: String, value: String) = slave.add("android_research.overdrive_that_matters.$key.$suffix", value) fun add(key: AndroidFeatureType<*>, value: String) = slave.add(key.displayId, value) @@ -362,8 +364,8 @@ class MatteryLanguageProvider(private val gen: DataGenerator) { "Black", ) - val english = Builder("en_us") - val russian = Builder("ru_ru") + val english by lazy { Builder("en_us") } + val russian by lazy { Builder("ru_ru") } fun getProvider(language: String): LanguageProvider = slaves.computeIfAbsent(language, ::Slave) } diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index cf925c22e..6c9728c20 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -16,6 +16,7 @@ import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import ru.dbotthepony.mc.otm.android.AndroidResearchManager; import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue; import ru.dbotthepony.mc.otm.capability.MatteryCapability; import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability; @@ -130,6 +131,9 @@ public final class OverdriveThatMatters { EVENT_BUS.addListener(EventPriority.NORMAL, QuantumBatteryItem.Companion::tick); EVENT_BUS.addListener(EventPriority.LOWEST, PortableCondensationDriveItem.Companion::onPickupEvent); + EVENT_BUS.addListener(EventPriority.NORMAL, AndroidResearchManager.INSTANCE::reloadEvent); + EVENT_BUS.addListener(EventPriority.NORMAL, AndroidResearchManager.INSTANCE::syncEvent); + MatteryPlayerNetworkChannel.INSTANCE.register(); MenuNetworkChannel.INSTANCE.register(); WeaponNetworkChannel.INSTANCE.register(); diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt index 9e4a81881..0eb6aadff 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt @@ -1,31 +1,41 @@ package ru.dbotthepony.mc.otm.android import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import net.minecraft.ChatFormatting import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation import net.minecraft.world.entity.player.Player import net.minecraft.world.item.ItemStack import net.minecraftforge.common.util.INBTSerializable import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.capability.itemsStream import ru.dbotthepony.mc.otm.client.render.SkinElement +import ru.dbotthepony.mc.otm.container.iterator +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.addAll +import ru.dbotthepony.mc.otm.core.getCompoundList +import ru.dbotthepony.mc.otm.core.nonEmpty import ru.dbotthepony.mc.otm.core.set import ru.dbotthepony.mc.otm.network.FieldSynchronizer +import ru.dbotthepony.mc.otm.registry.MRegistry import java.io.DataInputStream import java.io.InputStream -abstract class AndroidResearch(val type: AndroidResearchType<*>, val capability: MatteryPlayerCapability) : INBTSerializable { +class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlayerCapability) : INBTSerializable { val ply: Player get() = capability.ply val synchronizer = FieldSynchronizer() var isResearched by synchronizer.bool() - protected set + private set /** * Called when it is required to network everything again */ - open fun invalidateNetwork() { + fun invalidateNetwork() { synchronizer.invalidate() } @@ -38,8 +48,52 @@ abstract class AndroidResearch(val type: AndroidResearchType<*>, val capability: isResearched = false } - abstract fun onUnResearch() - abstract fun onResearched() + private data class RememberResearchLevel(val level: Int?) + + private val oldResearchLevel = Object2ObjectArrayMap, RememberResearchLevel>() + + fun onUnResearch() { + for (feature in type.resolvedFeatures) { + val level = oldResearchLevel[feature.feature] + val get = capability.getFeature(feature.feature) + + if (level != null && get != null) { + if (get.level == feature.level) { + if (level.level == null) { + capability.removeFeature(feature.feature) + } else { + get.level = level.level + } + } + } + } + + oldResearchLevel.clear() + } + + fun onResearched() { + oldResearchLevel.clear() + + try { + for (feature in type.resolvedFeatures) { + var get = capability.getFeature(feature.feature) + + if (get == null) { + get = capability.addFeature(feature.feature) + get.level = feature.level + oldResearchLevel[feature.feature] = RememberResearchLevel(null) + } else { + if (get.level < feature.level) { + oldResearchLevel[feature.feature] = RememberResearchLevel(feature.level) + get.level = feature.level + } + } + } + } catch(err: Throwable) { + oldResearchLevel.clear() + throw err + } + } /** * Consumes required resources for researching this technology. @@ -48,24 +102,91 @@ abstract class AndroidResearch(val type: AndroidResearchType<*>, val capability: * * This function MUST be atomic, hence it SHOULD NOT consume ANY resources if it returns false. */ - abstract fun consumeResearchCost(simulate: Boolean): Boolean + fun consumeResearchCost(simulate: Boolean): Boolean { + if (capability.ply.abilities.instabuild) { + // creative + return true + } + + if (!simulate && !consumeResearchCost(true)) { + return false + } + + if (type.experienceLevels > 0) { + if (capability.ply.experienceLevel < type.experienceLevels) { + return false + } + + if (!simulate) { + capability.ply.giveExperienceLevels(-type.experienceLevels) + } + } + + for (item in type.items) { + var required = item.count + val iterator = capability.ply.inventory.iterator().nonEmpty() + + for (invItem in iterator) { + if (ItemStack.isSameItemSameTags(invItem, item)) { + val toExtract = required.coerceAtMost(invItem.count) + required -= toExtract + + if (!simulate) { + if (toExtract == invItem.count) { + iterator.remove() + } else { + invItem.count -= toExtract + } + } + + if (required <= 0) { + break + } + } + } + + if (required > 0) { + return false + } + } + + return true + } /** * Grants all (or some) resources back consumed by [consumeResearchCost]. * * Returns true whenever player accepted all resources refunded, false otherwise. */ - abstract fun refund(simulate: Boolean): Boolean + fun refund(simulate: Boolean): Boolean { + if (simulate) { + return true + } - open fun collectNetworkPayload(): FastByteArrayOutputStream? { + if (type.experienceLevels > 0) { + capability.ply.giveExperienceLevels(type.experienceLevels) + } + + for (item in type.items) { + capability.ply.inventory.add(item) + + if (!item.isEmpty) { + capability.ply.spawnAtLocation(item) + } + } + + return true + } + + fun collectNetworkPayload(): FastByteArrayOutputStream? { return synchronizer.collectNetworkPayload() } - open fun applyNetworkPayload(stream: InputStream) { + fun applyNetworkPayload(stream: InputStream) { synchronizer.applyNetworkPayload(DataInputStream(stream)) } - open val canResearch: Boolean get() { + val canResearch: Boolean get() { if (!consumeResearchCost(simulate = true)) { return false } @@ -155,37 +276,98 @@ abstract class AndroidResearch(val type: AndroidResearchType<*>, val capability: /** * List of all tooltip lines for this research */ - open val tooltipLines: List by lazy { - return@lazy listOf(type.displayName.copy().withStyle(ChatFormatting.WHITE)) + val tooltipLines: List get() { + val lines = ArrayList() + lines.add(type.displayName) + lines.addAll(type.description.iterator()) + + return lines } /** * List of all tooltip lines for this research, for local player at research screen (clientside only) */ - open val screenTooltipLines: List by this::tooltipLines + val screenTooltipLines: List get() { + val builder = tooltipLines as ArrayList - /** - * This should return whatever can be used by other code to display this research's name - */ - open val tooltipHeader: Component by lazy { tooltipLines[0] } + if (type.experienceLevels != 0) { + builder.add( + TranslatableComponent("otm.android_station.research.xp_cost", type.experienceLevels).withStyle( + if (capability.ply.experienceLevel >= type.experienceLevels) + ChatFormatting.DARK_GREEN + else + ChatFormatting.DARK_RED + )) + } + + for (value in this.type.flatPrerequisites) { + val instance = capability.getResearch(value) + + builder.add( + TranslatableComponent("android_research.status.requires", instance.screenTooltipHeader).withStyle( + if (instance.isResearched) + ChatFormatting.DARK_GREEN + else + ChatFormatting.DARK_RED + )) + } + + for (value in this.type.flatBlockedBy) { + builder.add(TranslatableComponent("android_research.status.blocked_by", capability.getResearch(value).screenTooltipHeader).withStyle(ChatFormatting.DARK_RED)) + } + + for (value in this.type.flatBlocking) { + builder.add(TranslatableComponent("android_research.status.blocks", capability.getResearch(value).screenTooltipHeader).withStyle(ChatFormatting.DARK_RED)) + } + + return builder + } /** * This should return whatever can be used by other code to display this research's name, * for local player at research screen (clientside only) */ - open val screenTooltipHeader: Component by lazy { screenTooltipLines[0] } + val screenTooltipHeader: Component get() = type.displayName - open val skinIcon: SkinElement? get() = null - open val stackIcon: ItemStack? get() = null - open val iconText: Component? get() = null + val skinIcon: SkinElement? get() = null + val stackIcon: ItemStack? get() = null + val iconText: Component? get() = null override fun serializeNBT(): CompoundTag { return CompoundTag().also { it["researched"] = isResearched + + it["oldResearchLevel"] = ListTag().also { + for ((k, v) in oldResearchLevel) { + it.add(CompoundTag().also { + it["key"] = k.registryName!!.toString() + it["value"] = CompoundTag().also { + it["isPresent"] = v.level != null + + if (v.level != null) { + it["value"] = v.level + } + } + }) + } + } } } override fun deserializeNBT(nbt: CompoundTag) { isResearched = nbt.getBoolean("researched") + + oldResearchLevel.clear() + + for (tag in nbt.getCompoundList("oldResearchLevel")) { + val key = tag.getString("key") + val type = MRegistry.ANDROID_FEATURES.getValue(ResourceLocation(key)) ?: continue + val value = tag.getCompound("value") + + val isPresent = value.getBoolean("isPresent") + val int = value.getInt("value") + + oldResearchLevel[type] = RememberResearchLevel(if (isPresent) int else null) + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchBuilder.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchBuilder.kt deleted file mode 100644 index f4872f0d5..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchBuilder.kt +++ /dev/null @@ -1,460 +0,0 @@ -package ru.dbotthepony.mc.otm.android - -import com.google.common.collect.ImmutableList -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap -import net.minecraft.ChatFormatting -import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.ListTag -import net.minecraft.network.chat.Component -import net.minecraft.resources.ResourceLocation -import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.client.render.SkinElement -import ru.dbotthepony.mc.otm.container.iterator -import ru.dbotthepony.mc.otm.core.getCompoundList -import ru.dbotthepony.mc.otm.core.map -import ru.dbotthepony.mc.otm.core.nonEmpty -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.registry.MRegistry -import kotlin.collections.ArrayList - -typealias ResearchCallback = ((research: AndroidResearch, feature: AndroidFeature) -> Unit) - -@Suppress("unused") -class AndroidResearchBuilder( - var experience: Int = 0, - var name: Component? = null, - var customDescription: MutableList? = null, - var hasDescription: Boolean = false, - var skinIcon: SkinElement? = null, - var iconText: Component? = null, -) { - private val items = ArrayList() - private val prerequisites = ArrayList ResourceLocation, Boolean>>() - private val blockers = ArrayList ResourceLocation, Boolean>>() - - private val features = ArrayList() - private val resolvedFeatures = ArrayList() - - private class DeferredFeature( - val id: ResourceLocation, - val level: Int, - val callbackResearched: ResearchCallback? - ) - - private class ResolvedFeature( - val feature: AndroidFeatureType<*>, - val level: Int, - val callbackResearched: ResearchCallback? - ) - - fun withIconText(icon: Component?): AndroidResearchBuilder { - this.iconText = icon - return this - } - - fun withIconText() = withIconText(null) - - fun withIcon(icon: SkinElement): AndroidResearchBuilder { - this.skinIcon = icon - return this - } - - fun withDescription(): AndroidResearchBuilder { - this.hasDescription = true - this.customDescription = null - return this - } - - fun withExperience(experience: Int): AndroidResearchBuilder { - this.experience = experience - return this - } - - fun withDescription(vararg description: Component): AndroidResearchBuilder { - this.customDescription = description.toMutableList() - return this - } - - fun withDescription(description: List): AndroidResearchBuilder { - this.customDescription = ArrayList(description.size).also { it.addAll(description) } - return this - } - - fun withName(name: Component): AndroidResearchBuilder { - this.name = name - return this - } - - @JvmOverloads - fun addPrerequisite(id: ResourceLocation, rigid: Boolean = false): AndroidResearchBuilder { - prerequisites.add({ id } to rigid) - return this - } - - /** - * Please avoid having multiple prerequisites as case with more than 1 prerequisite does not have proper - * research tree render logic (yet). - */ - @JvmOverloads - fun addPrerequisite(id: () -> ResourceLocation, rigid: Boolean = false): AndroidResearchBuilder { - prerequisites.add(id to rigid) - return this - } - - @JvmOverloads - fun addBlocker(id: ResourceLocation, rigid: Boolean = false): AndroidResearchBuilder { - blockers.add({ id } to rigid) - return this - } - - @JvmOverloads - fun addBlocker(id: () -> ResourceLocation, rigid: Boolean = false): AndroidResearchBuilder { - blockers.add(id to rigid) - return this - } - - /** - * Please avoid having multiple prerequisites as case with more than 1 prerequisite does not have proper - * research tree render logic (yet). - */ - fun addPrerequisite(type: AndroidResearchType<*>) = addPrerequisite({ type.registryName ?: throw NullPointerException("Provided $type has no registryName defined") }) - fun addBlocker(type: AndroidResearchType<*>) = addBlocker({ type.registryName ?: throw NullPointerException("Provided $type has no registryName defined") }) - - @JvmOverloads - fun addFeatureResult(id: ResourceLocation, level: Int = 0, callback: ResearchCallback? = null): AndroidResearchBuilder { - features.add(DeferredFeature(id, level, callback)) - return this - } - - @JvmOverloads - fun addFeatureResult(feature: AndroidFeatureType<*>, level: Int = 0, callback: ResearchCallback? = null): AndroidResearchBuilder { - resolvedFeatures.add(ResolvedFeature(feature, level, callback)) - return this - } - - fun addFeatureResult(id: ResourceLocation, callback: ResearchCallback): AndroidResearchBuilder { - features.add(DeferredFeature(id, 0, callback)) - return this - } - - fun addItem(cost: ItemStack): AndroidResearchBuilder { - items.add(cost) - return this - } - - private class RememberResearchLevel(val level: Int?) - - fun build(): AndroidResearchType { - val items: List = ImmutableList.builder().let { - for (item in this.items) { - it.add(item.copy()) - } - - it.build() - } - - val prerequisites: List ResourceLocation, Boolean>> = ImmutableList.copyOf(this.prerequisites) - val blockers: List ResourceLocation, Boolean>> = ImmutableList.copyOf(this.blockers) - val experience = this.experience - - val name = name?.copy() - val description = customDescription?.let { - val builder = ImmutableList.builder() - - for (component in it) { - builder.add(component.copy()) - } - - builder.build() - } - - val hasDescription = hasDescription - val defFeatures = ImmutableList.copyOf(features) - val defResolvedFeatures = ImmutableList.copyOf(resolvedFeatures) - - val skinIcon = skinIcon - val iconText = iconText?.copy() - - val features: List by lazy { - val builder = ImmutableList.builder() - - for (df in defFeatures) { - builder.add(ResolvedFeature( - feature = MRegistry.ANDROID_FEATURES.getValue(df.id) ?: throw NoSuchElementException("Can't find android feature ${df.id}"), - level = df.level, - callbackResearched = df.callbackResearched - )) - } - - for (df in defResolvedFeatures) { - df.feature.registryName ?: throw NullPointerException("Feature ${df.feature} is missing registry name") - builder.add(df) - } - - builder.build() - } - - return object : AndroidResearchType(factory@{ it, capability -> - return@factory object : AndroidResearch(it, capability) { - override fun onUnResearch() { - for (feature in features) { - val level = oldResearchLevel[feature.feature] - val get = capability.getFeature(feature.feature) - - if (level != null && get != null) { - if (get.level == feature.level) { - if (level.level == null) { - capability.removeFeature(feature.feature) - } else { - get.level = level.level - } - } - } - } - - oldResearchLevel.clear() - } - - private val oldResearchLevel = Object2ObjectArrayMap, RememberResearchLevel>() - - override fun onResearched() { - oldResearchLevel.clear() - - try { - for (feature in features) { - var get = capability.getFeature(feature.feature) - - if (get == null) { - get = capability.addFeature(feature.feature) - get.level = feature.level - oldResearchLevel[feature.feature] = RememberResearchLevel(null) - } else { - if (get.level < feature.level) { - oldResearchLevel[feature.feature] = RememberResearchLevel(feature.level) - get.level = feature.level - } - } - - feature.callbackResearched?.invoke(this, get) - } - } catch(err: Throwable) { - oldResearchLevel.clear() - throw err - } - } - - override fun serializeNBT(): CompoundTag { - return super.serializeNBT().also { - it["oldResearchLevel"] = ListTag().also { - for ((k, v) in oldResearchLevel) { - it.add(CompoundTag().also { - it["key"] = k.registryName!!.toString() - it["value"] = CompoundTag().also { - it["isPresent"] = v.level != null - - if (v.level != null) { - it["value"] = v.level - } - } - }) - } - } - } - } - - override fun deserializeNBT(nbt: CompoundTag) { - super.deserializeNBT(nbt) - - oldResearchLevel.clear() - - for (tag in nbt.getCompoundList("oldResearchLevel")) { - val key = tag.getString("key") - val type = MRegistry.ANDROID_FEATURES.getValue(ResourceLocation(key)) ?: continue - val value = tag.getCompound("value") - - val isPresent = value.getBoolean("isPresent") - val int = value.getInt("value") - - oldResearchLevel[type] = RememberResearchLevel(if (isPresent) int else null) - } - } - - override fun consumeResearchCost(simulate: Boolean): Boolean { - if (capability.ply.abilities.instabuild) { - // creative - return true - } - - if (!simulate && !consumeResearchCost(true)) { - return false - } - - if (experience > 0) { - if (capability.ply.experienceLevel < experience) { - return false - } - - if (!simulate) { - capability.ply.giveExperienceLevels(-experience) - } - } - - for (item in items) { - var required = item.count - val iterator = capability.ply.inventory.iterator().nonEmpty() - - for (invItem in iterator) { - if (ItemStack.isSameItemSameTags(invItem, item)) { - val toExtract = required.coerceAtMost(invItem.count) - required -= toExtract - - if (!simulate) { - if (toExtract == invItem.count) { - iterator.remove() - } else { - invItem.count -= toExtract - } - } - - if (required <= 0) { - break - } - } - } - - if (required > 0) { - return false - } - } - - return true - } - - override fun refund(simulate: Boolean): Boolean { - if (simulate) { - return true - } - - if (experience > 0) { - capability.ply.giveExperienceLevels(experience) - } - - for (item in items) { - val leftover = item.copy() - capability.ply.inventory.add(leftover) - - if (!leftover.isEmpty) { - capability.ply.spawnAtLocation(leftover) - } - } - - return true - } - - override val tooltipLines: List by lazy { - val builder = ImmutableList.builder() - - builder.add(name ?: this.type.displayName) - - if (hasDescription) { - builder.addAll(this.type.displayDescription) - } else if (description != null) { - builder.addAll(description) - } - - builder.build() - } - - override val screenTooltipHeader: Component - get() = name ?: this.type.displayName - - override val screenTooltipLines: List get() { - val builder = ArrayList() - - builder.add(name ?: this.type.displayName) - - if (hasDescription) { - builder.addAll(this.type.displayDescription) - } else if (description != null) { - builder.addAll(description) - } - - if (experience != 0) { - builder.add( - TranslatableComponent("otm.android_station.research.xp_cost", experience).withStyle( - if (capability.ply.experienceLevel >= experience) - ChatFormatting.DARK_GREEN - else - ChatFormatting.DARK_RED - )) - } - - for (value in this.type.flatPrerequisites) { - val instance = capability.getResearch(value) - - builder.add( - TranslatableComponent("android_research.status.requires", instance.screenTooltipHeader).withStyle( - if (instance.isResearched) - ChatFormatting.DARK_GREEN - else - ChatFormatting.DARK_RED - )) - } - - for (value in this.type.flatBlockedBy) { - builder.add(TranslatableComponent("android_research.status.blocked_by", capability.getResearch(value).screenTooltipHeader).withStyle(ChatFormatting.DARK_RED)) - } - - for (value in this.type.flatBlocking) { - builder.add(TranslatableComponent("android_research.status.blocks", capability.getResearch(value).screenTooltipHeader).withStyle(ChatFormatting.DARK_RED)) - } - - return builder - } - - override val skinIcon: SkinElement? = skinIcon - override val iconText: Component? = iconText - } - }) { - override val displayName: Component - get() = name ?: super.displayName - - override val displayDescription: List - get() = description ?: super.displayDescription - - override val definedPrerequisites: List> by lazy { - val builder = ImmutableList.builder>() - - for ((value, rigid) in prerequisites) { - val get = MRegistry.ANDROID_RESEARCH.getValue(value()) - - if (get == null && rigid) { - throw NullPointerException("Unable to find research with name $value") - } else if (get != null) { - builder.add(get) - } - } - - builder.build() - } - - override val definedBlockedBy: List> by lazy { - val builder = ImmutableList.builder>() - - for ((value, rigid) in blockers) { - val get = MRegistry.ANDROID_RESEARCH.getValue(value()) - - if (get == null && rigid) { - throw NullPointerException("Unable to find research with name $value") - } else if (get != null) { - builder.add(get) - } - } - - builder.build() - } - } - } -} - diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt new file mode 100644 index 000000000..99d95a4a7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt @@ -0,0 +1,56 @@ +package ru.dbotthepony.mc.otm.android + +import it.unimi.dsi.fastutil.objects.ObjectArraySet +import net.minecraft.data.CachedOutput +import net.minecraft.data.DataGenerator +import net.minecraft.data.DataProvider +import net.minecraft.resources.ResourceLocation +import java.util.LinkedList +import java.util.function.Consumer + +@Suppress("unused") +open class AndroidResearchDataProvider(protected val dataGenerator: DataGenerator) : DataProvider { + protected val pathProvider: DataGenerator.PathProvider = dataGenerator.createPathProvider(DataGenerator.Target.DATA_PACK, AndroidResearchManager.DIRECTORY) + + protected val callbacks = LinkedList<(Consumer) -> Unit>() + + /** + * override this + */ + protected open fun addEverything(serializer: Consumer) { + for (callback in callbacks) { + callback.invoke(serializer) + } + } + + /** + * or call this + */ + fun exec(callback: (Consumer) -> Unit): AndroidResearchDataProvider { + callbacks.add(callback) + return this + } + + final override fun run(output: CachedOutput) { + val set = ObjectArraySet() + val added = LinkedList() + + addEverything { + if (set.add(it.id)) { + DataProvider.saveStable(output, it.toJson(), pathProvider.json(it.id)) + AndroidResearchManager.put(it) + added.add(it) + } else { + throw IllegalStateException("Duplicate android research with ID ${it.id}") + } + } + + for (value in added) { + value.validate() + } + } + + override fun getName(): String { + return "Android Research" + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt new file mode 100644 index 000000000..522781bca --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt @@ -0,0 +1,133 @@ +package ru.dbotthepony.mc.otm.android + +import com.google.common.collect.ImmutableMap +import com.google.gson.GsonBuilder +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import net.minecraft.client.server.IntegratedServer +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.packs.resources.ResourceManager +import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener +import net.minecraft.util.profiling.ProfilerFiller +import net.minecraftforge.event.AddReloadListenerEvent +import net.minecraftforge.event.OnDatapackSyncEvent +import net.minecraftforge.network.NetworkEvent +import net.minecraftforge.network.PacketDistributor +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.MINECRAFT_SERVER +import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER +import ru.dbotthepony.mc.otm.SERVER_IS_LIVE +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.network.MatteryPacket +import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel +import ru.dbotthepony.mc.otm.network.enqueueWork +import ru.dbotthepony.mc.otm.network.packetHandled +import ru.dbotthepony.mc.otm.onceServer +import java.util.LinkedList +import java.util.function.Supplier + +object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_android_research"), Iterable { + const val DIRECTORY = "otm_android_research" + private val LOGGER = LogManager.getLogger() + + operator fun get(id: ResourceLocation): AndroidResearchType? { + return researchMap[id] ?: maskedMap[id] + } + + override fun iterator(): Iterator { + return researchMap.values.iterator() + } + + private val maskedMap = HashMap() + + /** + * Internal use only, used by [AndroidResearchDataProvider] for validation purposes + */ + internal fun put(value: AndroidResearchType) { + maskedMap[value.id] = value + } + + var researchMap: Map = mapOf() + private set + + override fun apply( + jsonElementMap: Map, + manager: ResourceManager, + profiler: ProfilerFiller + ) { + val builder = ImmutableMap.builder() + + for ((k, v) in jsonElementMap) { + if (v !is JsonObject) { + LOGGER.error("Skipping Android Feature $k because top value is not a Object") + continue + } + + builder.put(k, AndroidResearchType.fromJson(v, k)) + } + + researchMap = builder.build() + + for (value in researchMap.values) { + value.validate() + } + + if (SERVER_IS_LIVE) { + onceServer { + for (ply in MINECRAFT_SERVER.playerList.players) { + ply.matteryPlayer?.reloadResearch() + } + } + } + } + + fun reloadEvent(event: AddReloadListenerEvent) { + event.addListener(this) + } + + fun syncEvent(event: OnDatapackSyncEvent) { + val packet = SyncPacket(researchMap.values) + + if (event.player != null) { + RegistryNetworkChannel.send(event.player!!, packet) + } else { + RegistryNetworkChannel.send(PacketDistributor.ALL.noArg(), packet) + } + } + + class SyncPacket(val collection: Collection) : MatteryPacket { + override fun write(buff: FriendlyByteBuf) { + buff.writeCollection(collection) { a, b -> b.toNetwork(a) } + } + + override fun play(context: Supplier) { + context.packetHandled = true + + if (NULLABLE_MINECRAFT_SERVER is IntegratedServer) { + return + } + + val builder = ImmutableMap.builder() + + for (v in collection) { + builder.put(v.id, v) + } + + researchMap = builder.build() + + for (value in researchMap.values) { + value.validate() + } + + context.enqueueWork { + minecraft.player?.matteryPlayer?.reloadResearch() + } + } + } + + fun readSyncPacket(buff: FriendlyByteBuf): SyncPacket { + return SyncPacket(buff.readCollection({ LinkedList() }, AndroidResearchType.Companion::fromNetwork)) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt index 762070f98..00fb36210 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt @@ -1,43 +1,51 @@ package ru.dbotthepony.mc.otm.android import com.google.common.collect.ImmutableList -import com.google.common.collect.ImmutableSet +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSyntaxException +import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.chat.Component import net.minecraft.network.chat.ComponentContents import net.minecraft.network.chat.MutableComponent import net.minecraft.network.chat.contents.TranslatableContents +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.client.render.SkinElement +import ru.dbotthepony.mc.otm.core.ListSet import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.core.getKeyNullable +import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.stream +import ru.dbotthepony.mc.otm.core.toImmutableList +import ru.dbotthepony.mc.otm.data.ItemStackCodec import ru.dbotthepony.mc.otm.registry.MRegistry -import java.util.* +import java.util.ArrayList +import java.util.LinkedList +import java.util.stream.Stream import kotlin.collections.HashSet -import kotlin.collections.RandomAccess - -fun interface AndroidResearchFactory { - fun factory(type: AndroidResearchType<*>, capability: MatteryPlayerCapability): R -} private fun findPrerequisites( - initial: Collection>, - add: MutableSet> = HashSet(), + initial: Collection, + add: MutableSet = HashSet(), top: Boolean = true -): Set> { +): Set { for (value in initial) { if (!top) { add.add(value) } - findPrerequisites(value.definedPrerequisites, add, false) + findPrerequisites(value.resolvedPrerequisites, add, false) } return add } private fun findAllPrerequisites( - initial: Collection>, - add: MutableSet> = HashSet(), -): Set> { + initial: Collection, + add: MutableSet = HashSet(), +): Set { for (value in initial) { add.add(value) findAllPrerequisites(value.flatPrerequisites, add) @@ -47,9 +55,9 @@ private fun findAllPrerequisites( } private fun findAllChildren( - initial: Collection>, - add: MutableSet> = HashSet(), -): Set> { + initial: Collection, + add: MutableSet = HashSet(), +): Set { for (value in initial) { add.add(value) findAllChildren(value.flatUnlocks, add) @@ -58,62 +66,114 @@ private fun findAllChildren( return add } -class ListSet(private val list: ImmutableList) : List, Set, RandomAccess { - constructor(list: Collection) : this(ImmutableList.copyOf(list)) +class AndroidResearchType( + val id: ResourceLocation, + prerequisites: Collection, + blockedBy: Collection, - private val set: ImmutableSet by lazy { ImmutableSet.copyOf(list) } + items: Collection, + features: Collection, - override val size: Int - get() = list.size + descriptionLines: Collection, - override fun contains(element: T): Boolean { - return set.contains(element) - } + val experienceLevels: Int = 0, + private val customName: Component? = null, - override fun containsAll(elements: Collection): Boolean { - return set.containsAll(elements) - } - - override fun get(index: Int): T { - return list[index] - } - - override fun indexOf(element: T): Int { - return list.indexOf(element) - } - - override fun isEmpty(): Boolean { - return list.isEmpty() - } - - override fun iterator(): Iterator { - return list.iterator() - } - - override fun lastIndexOf(element: T): Int { - return list.lastIndexOf(element) - } - - override fun listIterator(): ListIterator { - return list.listIterator() - } - - override fun listIterator(index: Int): ListIterator { - return list.listIterator(index) - } - - override fun spliterator(): Spliterator { - return list.spliterator() - } - - override fun subList(fromIndex: Int, toIndex: Int): List { - return list.subList(fromIndex, toIndex) - } -} - -open class AndroidResearchType( - protected val factory: AndroidResearchFactory + val skinIcon: SkinElement? = null, + stackIcon: ItemStack? = null, + iconText: Component? = null, ) { + private val stackIconValue = stackIcon?.copy() + private val iconTextValue = iconText?.copy() + + val stackIcon get() = stackIconValue?.copy() + val iconText get() = iconTextValue?.copy() + + data class Reference( + val id: ResourceLocation, + val isRigid: Boolean + ) { + fun toJson(): JsonObject { + return JsonObject().also { + it["id"] = JsonPrimitive(id.toString()) + it["is_rigid"] = JsonPrimitive(isRigid) + } + } + + fun toNetwork(buff: FriendlyByteBuf) { + buff.writeUtf(id.toString()) + buff.writeBoolean(isRigid) + } + + companion object { + fun fromNetwork(buff: FriendlyByteBuf): Reference { + return Reference( + ResourceLocation(buff.readUtf()), + buff.readBoolean() + ) + } + + fun fromJson(value: JsonElement): Reference { + if (value is JsonPrimitive) { + return Reference(ResourceLocation(value.asString), true) + } else if (value is JsonObject) { + return Reference( + ResourceLocation((value["id"] as? JsonPrimitive ?: throw JsonSyntaxException("Invalid `id` value")).asString), + (value["is_rigid"] as JsonPrimitive?)?.asBoolean ?: true) + } else { + throw JsonSyntaxException("Unknown element type ${value::class.qualifiedName}") + } + } + } + } + + data class FeatureReference( + val id: ResourceLocation, + val level: Int = 0, + val isRigid: Boolean + ) { + fun toJson(): JsonObject { + return JsonObject().also { + it["id"] = JsonPrimitive(id.toString()) + it["level"] = JsonPrimitive(level) + it["is_rigid"] = JsonPrimitive(isRigid) + } + } + + fun toNetwork(buff: FriendlyByteBuf) { + buff.writeUtf(id.toString()) + buff.writeVarInt(level) + buff.writeBoolean(isRigid) + } + + companion object { + fun fromNetwork(buff: FriendlyByteBuf): FeatureReference { + return FeatureReference( + ResourceLocation(buff.readUtf()), + buff.readVarInt(), + buff.readBoolean() + ) + } + + fun fromJson(value: JsonElement): FeatureReference { + if (value is JsonPrimitive) { + return FeatureReference(ResourceLocation(value.asString), 0, true) + } else if (value is JsonObject) { + return FeatureReference( + ResourceLocation((value["id"] as? JsonPrimitive ?: throw JsonSyntaxException("Invalid `id` value")).asString), + (value["level"] as JsonPrimitive?)?.asInt ?: 0, (value["is_rigid"] as JsonPrimitive?)?.asBoolean ?: true) + } else { + throw JsonSyntaxException("Unknown element type ${value::class.qualifiedName}") + } + } + } + } + + data class ResolvedFeature( + val feature: AndroidFeatureType<*>, + val level: Int, + ) + val researchTreeDepth: Int by lazy { if (flatPrerequisites.isEmpty()) { return@lazy 0 @@ -134,12 +194,47 @@ open class AndroidResearchType( * Please avoid having more than one prerequisite, as this case doesn't have proper research tree * rendering code (yet). */ - open val definedPrerequisites: List> get() = emptyList() + val prerequisites: List = ImmutableList.copyOf(prerequisites) /** * Blocked by list as-is */ - open val definedBlockedBy: List> get() = emptyList() + val blockedBy: List = ImmutableList.copyOf(blockedBy) + + val resolvedPrerequisites: List by lazy { + ImmutableList.copyOf(this.prerequisites.mapNotNull { AndroidResearchManager[it.id].also { e -> if (e == null && it.isRigid) throw NoSuchElementException("Unable to find research ${it.id}") } }) + } + + val resolvedBlockedBy: List by lazy { + ImmutableList.copyOf(this.blockedBy.mapNotNull { AndroidResearchManager[it.id].also { e -> if (e == null && it.isRigid) throw NoSuchElementException("Unable to find research ${it.id}") } }) + } + + val features: List = ImmutableList.copyOf(features) + + val resolvedFeatures: List by lazy { + ImmutableList.copyOf(features.mapNotNull { + MRegistry.ANDROID_FEATURES.getValue(it.id) + .let { e -> if (e == null && it.isRigid) + throw NoSuchElementException("Unable to find research ${it.id}") + else if (e != null) + ResolvedFeature(e, it.level) + else + null } }) + } + + private val itemCollection: List = items.stream().filter { !it.isEmpty }.map { it.copy() }.toList().toImmutableList() + + /** + * Stream containing copies of original items in list + */ + val items: Stream get() = itemCollection.stream().map { it.copy() } + + private val descriptionLines: List = ImmutableList.copyOf(descriptionLines.map { it.copy() }) + + /** + * Stream containing copies of original [Component]s in list + */ + val description: Stream get() = descriptionLines.stream().map { it.copy() } /** * Flat list of research preceding this research. @@ -151,16 +246,16 @@ open class AndroidResearchType( * * C depends on B * * B depends on A * - * C specify both B and A as it's prerequisites, [flatPrerequisites] will contain only B, when [definedPrerequisites] will contain + * C specify both B and A as it's prerequisites, [flatPrerequisites] will contain only B, when [prerequisites] will contain * both B and A * * Returns list which also doubles as set (for contains method). */ - val flatPrerequisites: List> by lazy { - val parentPrerequisites = findPrerequisites(definedPrerequisites) - val builder = ImmutableList.builder>() + val flatPrerequisites: List by lazy { + val parentPrerequisites = findPrerequisites(resolvedPrerequisites) + val builder = ImmutableList.builder() - for (value in definedPrerequisites) { + for (value in resolvedPrerequisites) { if (value !in parentPrerequisites) { builder.add(value) } @@ -174,7 +269,7 @@ open class AndroidResearchType( * * Returns list which also doubles as set (for contains method). */ - val allPrerequisites: List> by lazy { + val allPrerequisites: List by lazy { ListSet(findAllPrerequisites(flatPrerequisites)) } @@ -193,15 +288,15 @@ open class AndroidResearchType( * * Returns list which also doubles as set (for contains method). */ - val flatBlocking: List> by lazy { - val list = ImmutableList.builder>() + val flatBlocking: List by lazy { + val list = ImmutableList.builder() - for (research in MRegistry.ANDROID_RESEARCH) { - if (this in research.definedBlockedBy) { + for (research in AndroidResearchManager.researchMap.values) { + if (this in research.resolvedBlockedBy) { var hit = false for (parent in research.allPrerequisites) { - if (this in parent.definedBlockedBy) { + if (this in parent.resolvedBlockedBy) { hit = true break } @@ -231,10 +326,10 @@ open class AndroidResearchType( * * Returns list which also doubles as set (for contains method). */ - val flatBlockedBy: ListSet> by lazy { - val list = ImmutableList.builder>() + val flatBlockedBy: ListSet by lazy { + val list = ImmutableList.builder() - for (blocker in definedBlockedBy) { + for (blocker in resolvedBlockedBy) { var hit = false for (research in allPrerequisites) { @@ -257,8 +352,8 @@ open class AndroidResearchType( * * Returns list which also doubles as set (for contains method). */ - val allBlockedBy: ListSet> by lazy { - val list = HashSet>() + val allBlockedBy: ListSet by lazy { + val list = HashSet() list.addAll(flatBlockedBy) @@ -274,10 +369,10 @@ open class AndroidResearchType( * * Returns list which also doubles as set (for contains method). */ - val flatUnlocks: List> by lazy { - val list = ImmutableList.builder>() + val flatUnlocks: List by lazy { + val list = ImmutableList.builder() - for (research in MRegistry.ANDROID_RESEARCH) { + for (research in AndroidResearchManager.researchMap.values) { if (this in research.flatPrerequisites) { list.add(research) } @@ -291,7 +386,7 @@ open class AndroidResearchType( * * Returns list which also doubles as set (for contains method). */ - val allUnlocks: List> by lazy { + val allUnlocks: List by lazy { ListSet(findAllChildren(flatUnlocks)) } @@ -300,8 +395,8 @@ open class AndroidResearchType( * * Returns list which also doubles as set (for contains method) */ - val allBlocking: List> by lazy { - val set = HashSet>() + val allBlocking: List by lazy { + val set = HashSet() for (research in flatBlocking) { set.add(research) @@ -318,30 +413,231 @@ open class AndroidResearchType( ListSet(set) } - fun factory(capability: MatteryPlayerCapability) = factory.factory(this, capability) - - val registryName by lazy { - MRegistry.ANDROID_RESEARCH.getKeyNullable(this) - } - val displayId by lazy { - val registryName = registryName ?: throw NullPointerException("No registry name present") - return@lazy "android_research.${registryName.namespace}.${registryName.path}".intern() + return@lazy "android_research.${id.namespace}.${id.path}".intern() } val descriptionId by lazy { return@lazy "$displayId.description".intern() } - open val displayContents: ComponentContents by lazy { + val displayContents: ComponentContents by lazy { TranslatableContents(displayId) } - open val displayName: Component by lazy { - MutableComponent.create(displayContents) + val displayName: Component get() { + return customName?.copy() ?: MutableComponent.create(displayContents) } - open val displayDescription: List by lazy { - listOf(TranslatableComponent(descriptionId)) + fun toJson(): JsonElement { + return JsonObject().also { + // it["id"] = JsonPrimitive(id.toString()) + + it["prerequisites"] = JsonArray().also { for (value in prerequisites) it.add(value.toJson()) } + it["blocked_by"] = JsonArray().also { for (value in blockedBy) it.add(value.toJson()) } + + it["required_items"] = JsonArray().also { for (item in itemCollection) it.add(ItemStackCodec.serialize(item)) } + + it["feature_result"] = JsonArray().also { for (feature in features) it.add(feature.toJson()) } + + it["description"] = JsonArray().also { for (line in descriptionLines) it.add(Component.Serializer.toJsonTree(line)) } + + it["experience"] = JsonPrimitive(experienceLevels) + + if (skinIcon != null) { + it["skin_icon"] = skinIcon.toJson() + } + + if (customName != null) { + it["custom_name"] = Component.Serializer.toJsonTree(customName) + } + } + } + + fun validate() { + resolvedFeatures + resolvedBlockedBy + resolvedPrerequisites + } + + fun toNetwork(buff: FriendlyByteBuf) { + buff.writeUtf(id.toString()) + buff.writeCollection(prerequisites) { a, b -> b.toNetwork(a) } + buff.writeCollection(blockedBy) { a, b -> b.toNetwork(a) } + buff.writeCollection(itemCollection) { a, b -> a.writeItem(b) } + buff.writeCollection(features) { a, b -> b.toNetwork(a) } + buff.writeCollection(descriptionLines) { a, b -> a.writeComponent(b) } + buff.writeVarInt(experienceLevels) + + buff.writeBoolean(customName != null) + + if (customName != null) { + buff.writeComponent(customName) + } + } + + companion object { + fun fromNetwork(buff: FriendlyByteBuf): AndroidResearchType { + val id = ResourceLocation(buff.readUtf()) + val prerequisites = buff.readCollection({ LinkedList() }, Reference::fromNetwork) + val blockedBy = buff.readCollection({ LinkedList() }, Reference::fromNetwork) + val items = buff.readCollection({ LinkedList() }, FriendlyByteBuf::readItem) + val features = buff.readCollection({ LinkedList() }, FeatureReference::fromNetwork) + val descriptionLines = buff.readCollection({ LinkedList() }, FriendlyByteBuf::readComponent) + val experienceLevels = buff.readVarInt() + + val customName: Component? = if (buff.readBoolean()) { + buff.readComponent() + } else { + null + } + + return AndroidResearchType( + id = id, + prerequisites = prerequisites, + blockedBy = blockedBy, + items = items, + features = features, + descriptionLines = descriptionLines, + experienceLevels = experienceLevels, + customName = customName + ) + } + + fun fromJson(value: JsonElement, id: ResourceLocation): AndroidResearchType { + if (value !is JsonObject) { + throw JsonSyntaxException("Android research type must be of Json Object") + } + + val prerequisites = value["prerequisites"] as JsonArray? ?: JsonArray() + val blocked_by = value["blocked_by"] as JsonArray? ?: JsonArray() + val items = value["required_items"] as JsonArray? ?: JsonArray() + val features = value["feature_result"] as JsonArray? ?: JsonArray() + val description = value["description"] as JsonArray? ?: JsonArray() + val experience = value["experience"]?.asInt ?: 0 + val customName = value["custom_name"]?.let(Component.Serializer::fromJson) + + return AndroidResearchType( + id = id, + prerequisites = prerequisites.stream().map { Reference.fromJson(it) }.toList(), + blockedBy = blocked_by.stream().map { Reference.fromJson(it) }.toList(), + features = features.stream().map { FeatureReference.fromJson(it) }.toList(), + items = items.stream().map { ItemStackCodec.deserialize(it) }.filter { !it.isEmpty }.toList(), + descriptionLines = description.stream().map { Component.Serializer.fromJson(it) }.toList() as List, + experienceLevels = experience, + customName = customName + ) + } + } + + @Suppress("unused") + class Builder( + val id: ResourceLocation, + var experience: Int = 0, + var customName: Component? = null, + var description: MutableList? = null, + var skinIcon: SkinElement? = null, + var iconText: Component? = null, + ) { + private val items = ArrayList() + private val prerequisites = LinkedList() + private val blockers = ArrayList() + + private val features = ArrayList() + + fun withIconText(icon: Component?): Builder { + this.iconText = icon + return this + } + + fun withIconText() = withIconText(null) + + fun withIcon(icon: SkinElement): Builder { + this.skinIcon = icon + return this + } + + fun withName(customName: Component): Builder { + this.customName = customName + return this + } + + fun withDescription(): Builder { + this.description = mutableListOf(TranslatableComponent("android_research.${id.namespace}.${id.path}.description")) + return this + } + + fun withExperience(experience: Int): Builder { + this.experience = experience + return this + } + + fun withDescription(vararg description: Component): Builder { + this.description = description.toMutableList() + return this + } + + fun withDescription(description: List): Builder { + this.description = ArrayList(description.size).also { it.addAll(description) } + return this + } + + /** + * Please avoid having multiple prerequisites as case with more than 1 prerequisite does not have proper + * research tree render logic (yet). + */ + @JvmOverloads + fun addPrerequisite(id: ResourceLocation, rigid: Boolean = false): Builder { + prerequisites.add(Reference(id, rigid)) + return this + } + + @JvmOverloads + fun addBlocker(id: ResourceLocation, rigid: Boolean = false): Builder { + blockers.add(Reference(id, rigid)) + return this + } + + /** + * Please avoid having multiple prerequisites as case with more than 1 prerequisite does not have proper + * research tree render logic (yet). + */ + fun addPrerequisite(type: AndroidResearchType, rigid: Boolean = true) = addPrerequisite(type.id, rigid) + fun addBlocker(type: AndroidResearchType, rigid: Boolean = true) = addBlocker(type.id, rigid) + + @JvmOverloads + fun addFeatureResult(id: ResourceLocation, level: Int = 0, rigid: Boolean = false): Builder { + features.add(FeatureReference(id, level, rigid)) + return this + } + + @JvmOverloads + fun addFeatureResult(feature: AndroidFeatureType<*>, level: Int = 0, rigid: Boolean = true): Builder { + features.add(FeatureReference(feature.registryName ?: throw NullPointerException("Feature $feature does not have registry name"), level, rigid)) + return this + } + + fun addFeatureResult(id: ResourceLocation, rigid: Boolean = false): Builder { + features.add(FeatureReference(id, 0, rigid)) + return this + } + + fun addItem(cost: ItemStack): Builder { + items.add(cost) + return this + } + + fun build(validate: Boolean = false): AndroidResearchType { + return AndroidResearchType( + id = id, + prerequisites = prerequisites, + blockedBy = blockers, + items = items, + features = features, + descriptionLines = description ?: listOf(), + experienceLevels = experience, + customName = customName + ).also { if (validate) it.validate() } + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt index b5c265587..b7c5dcb28 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt @@ -3,14 +3,12 @@ package ru.dbotthepony.mc.otm.android.feature import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.world.effect.MobEffectInstance import net.minecraft.world.effect.MobEffects -import net.minecraftforge.api.distmarker.Dist -import net.minecraftforge.api.distmarker.OnlyIn import ru.dbotthepony.mc.otm.ServerConfig import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact +import ru.dbotthepony.mc.otm.client.render.ResearchIcons import ru.dbotthepony.mc.otm.registry.AndroidFeatures -import ru.dbotthepony.mc.otm.registry.AndroidResearch class NightVisionFeature(android: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.NIGHT_VISION, android) { override fun tickServer() { @@ -33,6 +31,6 @@ class NightVisionFeature(android: MatteryPlayerCapability) : AndroidSwitchableFe } override fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) { - AndroidResearch.ICON_NIGHT_VISION.render(stack, x, y, width, height) + ResearchIcons.ICON_NIGHT_VISION.render(stack, x, y, width, height) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt index 1dcb185fc..3c1711eb9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt @@ -7,6 +7,7 @@ import net.minecraft.world.phys.AABB import ru.dbotthepony.mc.otm.ServerConfig import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.client.render.ResearchIcons import ru.dbotthepony.mc.otm.core.Vector import ru.dbotthepony.mc.otm.core.getEllipsoidBlockPositions import ru.dbotthepony.mc.otm.core.getExplosionResistance @@ -18,7 +19,6 @@ import ru.dbotthepony.mc.otm.core.times import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel import ru.dbotthepony.mc.otm.network.TriggerShockwavePacket import ru.dbotthepony.mc.otm.registry.AndroidFeatures -import ru.dbotthepony.mc.otm.registry.AndroidResearch import ru.dbotthepony.mc.otm.registry.ShockwaveDamageSource import kotlin.math.pow import kotlin.math.roundToInt @@ -148,6 +148,6 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF } override fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) { - AndroidResearch.ICON_SHOCKWAVE.render(stack, x, y, width, height) + ResearchIcons.ICON_SHOCKWAVE.render(stack, x, y, width, height) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt index 278cf23db..507bd9106 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt @@ -42,6 +42,7 @@ import ru.dbotthepony.mc.otm.* import ru.dbotthepony.mc.otm.android.AndroidFeature import ru.dbotthepony.mc.otm.android.AndroidFeatureType import ru.dbotthepony.mc.otm.android.AndroidResearch +import ru.dbotthepony.mc.otm.android.AndroidResearchManager import ru.dbotthepony.mc.otm.android.AndroidResearchType import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.core.* @@ -178,7 +179,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial private var tickedOnce = false private var shouldPlaySound = false - private val research = IdentityHashMap, AndroidResearch>() + private val research = IdentityHashMap() private var invalidateNetworkIn = 10 @@ -264,10 +265,24 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial deathLog.clear() } - fun getResearch(type: AndroidResearchType): T { + fun getResearch(type: AndroidResearchType): AndroidResearch { return research.computeIfAbsent(type) { - return@computeIfAbsent type.factory(this) - } as T + return@computeIfAbsent AndroidResearch(type, this) + } + } + + fun reloadResearch() { + val old = ArrayList(research.size) + old.addAll(research.values) + research.clear() + + for (v in old) { + val new = AndroidResearchManager.get(v.type.id) ?: continue + + research[new] = AndroidResearch(new, this).also { + it.deserializeNBT(v.serializeNBT()) + } + } } val features: Stream get() = featureMap.values.stream() @@ -413,7 +428,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial for ((type, instance) in research) { researchList.add(instance.serializeNBT().also { - it["id"] = type.registryName!!.toString() + it["id"] = type.id.toString() }) } @@ -472,10 +487,10 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } for (researchTag in tag.getCompoundList("research")) { - val research = MRegistry.ANDROID_RESEARCH.getValue(ResourceLocation(researchTag.getString("id"))) + val research = AndroidResearchManager[ResourceLocation(researchTag.getString("id"))] if (research != null) { - val instance = research.factory(this) + val instance = AndroidResearch(research, this) instance.deserializeNBT(researchTag) this.research[research] = instance } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt new file mode 100644 index 000000000..3226e96c7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt @@ -0,0 +1,63 @@ +package ru.dbotthepony.mc.otm.client.render + +import net.minecraft.resources.ResourceLocation +import ru.dbotthepony.mc.otm.OverdriveThatMatters + +object ResearchIcons { + val ICONS = ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/android_upgrades.png") + val ICON_TRANSFER: SkinElement + val ICON_ATTACK_BOOST: SkinElement + val ICON_PLASMA_SHIELD_BOOST: SkinElement + val ICON_CLOAK: SkinElement + val ICON_GRAVITATIONAL_STABILIZER: SkinElement + val ICON_AIR_BAGS: SkinElement + val ICON_JUMP_BOOST: SkinElement + + val ICON_FEATHER_FALLING: SkinElement + val ICON_ARC: SkinElement + val ICON_ARROW: SkinElement + val ICON_ARMOR: SkinElement + val ICON_NANOBOTS: SkinElement + val ICON_NIGHT_VISION: SkinElement + val ICON_OXYGEN_SUPPLY: SkinElement + + val ICON_PLASMA_SHIELD: SkinElement + val ICON_SHOCKWAVE: SkinElement + val ICON_LIMB_OVERCLOCKING: SkinElement + val ICON_STEP_ASSIST: SkinElement + val ICON_ENDER_TELEPORT: SkinElement + val ICON_WIRELESS_CHARGING: SkinElement + val ICON_UNKNOWN: SkinElement + + val ICON_EXTENDED_REACH: SkinElement + + init { + val grid = SkinGrid(ICONS, 18f, 18f, 7, 7) + + ICON_TRANSFER = grid.next() + ICON_ATTACK_BOOST = grid.next() + ICON_PLASMA_SHIELD_BOOST = grid.next() + ICON_CLOAK = grid.next() + ICON_GRAVITATIONAL_STABILIZER = grid.next() + ICON_AIR_BAGS = grid.next() + ICON_JUMP_BOOST = grid.next() + + ICON_FEATHER_FALLING = grid.next() + ICON_ARC = grid.next() + ICON_ARROW = grid.next() + ICON_ARMOR = grid.next() + ICON_NANOBOTS = grid.next() + ICON_NIGHT_VISION = grid.next() + ICON_OXYGEN_SUPPLY = grid.next() + + ICON_PLASMA_SHIELD = grid.next() + ICON_SHOCKWAVE = grid.next() + ICON_LIMB_OVERCLOCKING = grid.next() + ICON_STEP_ASSIST = grid.next() + ICON_ENDER_TELEPORT = grid.next() + ICON_WIRELESS_CHARGING = grid.next() + ICON_UNKNOWN = grid.next() + + ICON_EXTENDED_REACH = grid.next() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElement.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElement.kt index 2892999a1..6e048165d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElement.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElement.kt @@ -1,9 +1,23 @@ package ru.dbotthepony.mc.otm.client.render +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import com.google.gson.JsonSyntaxException +import com.google.gson.TypeAdapter +import com.google.gson.internal.bind.TypeAdapters +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.resources.ResourceLocation import ru.dbotthepony.mc.otm.client.screen.panels.DockProperty +import ru.dbotthepony.mc.otm.core.set +import java.lang.reflect.Type @Suppress("unused") data class SkinGrid( @@ -102,6 +116,55 @@ data class SkinElement @JvmOverloads constructor( val imageHeight: Float = 256f, val winding: UVWindingOrder = UVWindingOrder.NORMAL ) { + fun toJson(): JsonObject { + return JsonObject().also { + it["texture"] = JsonPrimitive(texture.toString()) + it["x"] = JsonPrimitive(x) + it["y"] = JsonPrimitive(y) + it["width"] = JsonPrimitive(width) + it["height"] = JsonPrimitive(height) + it["imageWidth"] = JsonPrimitive(imageWidth) + it["imageHeight"] = JsonPrimitive(imageHeight) + it["winding"] = JsonPrimitive(winding.name) + } + } + + companion object : TypeAdapter(), JsonSerializer, JsonDeserializer { + fun fromJson(value: JsonObject): SkinElement { + val texture = value["texture"]?.asString ?: throw JsonSyntaxException("Missing texture element") + val x = value["x"]?.asFloat ?: throw JsonSyntaxException("Missing x element") + val y = value["y"]?.asFloat ?: throw JsonSyntaxException("Missing y element") + val width = value["width"]?.asFloat ?: throw JsonSyntaxException("Missing width element") + val height = value["height"]?.asFloat ?: throw JsonSyntaxException("Missing height element") + val imageWidth = value["imageWidth"]?.asFloat ?: throw JsonSyntaxException("Missing imageWidth element") + val imageHeight = value["imageHeight"]?.asFloat ?: throw JsonSyntaxException("Missing imageHeight element") + val winding = value["winding"]?.asString ?: throw JsonSyntaxException("Missing winding element") + + return SkinElement(ResourceLocation(texture), x, y, width, height, imageWidth, imageHeight, UVWindingOrder.valueOf(winding)) + } + + override fun write(out: JsonWriter, value: SkinElement) { + TypeAdapters.JSON_ELEMENT.write(out, value.toJson()) + } + + override fun read(reader: JsonReader): SkinElement { + val read = TypeAdapters.JSON_ELEMENT.read(reader) + return fromJson(read as? JsonObject ?: throw JsonSyntaxException("Expected JsonObject, got ${read::class.qualifiedName}")) + } + + override fun serialize(src: SkinElement, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return src.toJson() + } + + override fun deserialize( + json: JsonElement, + typeOfT: Type, + context: JsonDeserializationContext + ): SkinElement { + return fromJson(json as? JsonObject ?: throw JsonSyntaxException("Expected JsonObject, got ${json::class.qualifiedName}")) + } + } + init { require(x >= 0f) { "Invalid x $x" } require(y >= 0f) { "Invalid y $y" } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt index c3b00da2d..64f75446e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt @@ -11,6 +11,7 @@ import net.minecraft.network.chat.Component import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.android.AndroidResearch +import ru.dbotthepony.mc.otm.android.AndroidResearchManager import ru.dbotthepony.mc.otm.android.AndroidResearchType import ru.dbotthepony.mc.otm.block.entity.AndroidStationBlockEntity import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability @@ -33,7 +34,7 @@ import java.util.* import kotlin.collections.ArrayList import kotlin.properties.Delegates -private fun exploreTree(research: AndroidResearchType<*>, seen: MutableSet>, result: MutableList>) { +private fun exploreTree(research: AndroidResearchType, seen: MutableSet, result: MutableList) { if (!seen.add(research)) { return } @@ -45,13 +46,13 @@ private fun exploreTree(research: AndroidResearchType<*>, seen: MutableSet, List>>> { - val seen = HashSet>() - val list = ArrayList, ArrayList>>>() +private fun findGraphs(): List>> { + val seen = HashSet() + val list = ArrayList>>() - for (research in MRegistry.ANDROID_RESEARCH) { + for (research in AndroidResearchManager) { if (research.flatPrerequisites.isEmpty()) { - val tree = ArrayList>() + val tree = ArrayList() exploreTree(research, seen, tree) @@ -64,7 +65,7 @@ private fun findGraphs(): List, List): Boolean { +private fun isTree(root: AndroidResearchType): Boolean { if (root.flatPrerequisites.size > 1) { return false } @@ -80,7 +81,7 @@ private fun isTree(root: AndroidResearchType<*>): Boolean { private typealias LinePos = Pair, Pair> -private class Tree(val node: AndroidResearchType<*>) : Iterable { +private class Tree(val node: AndroidResearchType) : Iterable { val subtrees = ArrayList() val height: Int @@ -161,7 +162,7 @@ private class Tree(val node: AndroidResearchType<*>) : Iterable { val totalWidth = width * 24f val lines = ArrayList() - val linesToResearch = IdentityHashMap, ArrayList>() + val linesToResearch = IdentityHashMap>() val button = AndroidResearchButton(rows[node.researchTreeDepth], capability.getResearch(node), lines, linesToResearch) button.x = left + totalWidth / 2f - button.width / 2f @@ -251,7 +252,7 @@ private class AndroidResearchButton( parent: EditablePanel, private val node: AndroidResearch, private val lines: List, - private val highlightLines: Map, List> + private val highlightLines: Map> ): EditablePanel( parent.screen, parent, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt index 07414d233..c0c69e155 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -31,6 +31,15 @@ import net.minecraftforge.registries.RegistryManager import ru.dbotthepony.mc.otm.ClientConfig import ru.dbotthepony.mc.otm.item.BatteryItem import java.math.BigInteger +import java.util.Spliterator +import java.util.Spliterators +import java.util.function.BiConsumer +import java.util.function.BinaryOperator +import java.util.function.Function +import java.util.function.Supplier +import java.util.stream.Collector +import java.util.stream.Stream +import java.util.stream.StreamSupport import kotlin.reflect.KProperty /** @@ -361,3 +370,23 @@ fun BlockState.getExplosionResistance(level: BlockGetter, pos: BlockPos): Float block.explosionResistance } } + +fun MutableCollection.addAll(elements: Iterator) { + for (item in elements) { + add(item) + } +} + +fun MutableCollection.addAll(elements: Stream) { + for (item in elements) { + add(item) + } +} + +fun Iterable.stream(): Stream { + return StreamSupport.stream(this.spliterator(), false) +} + +fun Iterator.stream(): Stream { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(this, 0), false) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ListSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ListSet.kt new file mode 100644 index 000000000..449e5b585 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ListSet.kt @@ -0,0 +1,59 @@ +package ru.dbotthepony.mc.otm.core + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSet +import java.util.* +import kotlin.collections.RandomAccess + +class ListSet(private val list: ImmutableList) : List, Set, RandomAccess { + constructor(list: Collection) : this(ImmutableList.copyOf(list)) + + private val set: ImmutableSet by lazy { ImmutableSet.copyOf(list) } + + override val size: Int + get() = list.size + + override fun contains(element: T): Boolean { + return set.contains(element) + } + + override fun containsAll(elements: Collection): Boolean { + return set.containsAll(elements) + } + + override fun get(index: Int): T { + return list[index] + } + + override fun indexOf(element: T): Int { + return list.indexOf(element) + } + + override fun isEmpty(): Boolean { + return list.isEmpty() + } + + override fun iterator(): Iterator { + return list.iterator() + } + + override fun lastIndexOf(element: T): Int { + return list.lastIndexOf(element) + } + + override fun listIterator(): ListIterator { + return list.listIterator() + } + + override fun listIterator(index: Int): ListIterator { + return list.listIterator(index) + } + + override fun spliterator(): Spliterator { + return list.spliterator() + } + + override fun subList(fromIndex: Int, toIndex: Int): List { + return list.subList(fromIndex, toIndex) + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/ItemStackCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/ItemStackCodec.kt index df087bd9a..e8d169708 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/ItemStackCodec.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/ItemStackCodec.kt @@ -1,5 +1,17 @@ package ru.dbotthepony.mc.otm.data +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import com.google.gson.JsonSyntaxException +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter import com.mojang.datafixers.util.Pair import com.mojang.serialization.Codec import com.mojang.serialization.DataResult @@ -9,8 +21,11 @@ import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items import net.minecraftforge.registries.ForgeRegistries +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.core.set +import java.lang.reflect.Type -object ItemStackCodec : Codec { +object ItemStackCodec : Codec, TypeAdapter(), JsonSerializer, JsonDeserializer { override fun encode(input: ItemStack, ops: DynamicOps, prefix: T): DataResult { require(prefix == ops.empty()) { "Non-empty prefix: $prefix" } @@ -36,4 +51,63 @@ object ItemStackCodec : Codec { } val LIST = ListCodec(this) + + override fun write(out: JsonWriter, value: ItemStack) { + out.beginObject() + + out.name("id") + out.value(value.item.registryName!!.toString()) + + out.name("count") + out.value(value.count) + + out.endObject() + } + + override fun read(reader: JsonReader): ItemStack { + reader.beginObject() + + var id: String? = null + var count: Int? = null + + while (reader.peek() != JsonToken.END_OBJECT) { + when (val it = reader.nextName()) { + "id" -> id = reader.nextString() + "count" -> count = reader.nextInt() + else -> throw JsonSyntaxException("Unknown json key $it") + } + } + + reader.endObject() + + val item = ForgeRegistries.ITEMS.getValue(ResourceLocation(id ?: return ItemStack.EMPTY)) ?: return ItemStack.EMPTY + + return ItemStack(item, count ?: 1) + } + + override fun serialize(src: ItemStack, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return serialize(src) + } + + fun serialize(src: ItemStack): JsonElement { + return JsonObject().also { + it["id"] = JsonPrimitive(src.item.registryName!!.toString()) + it["count"] = JsonPrimitive(src.count) + } + } + + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ItemStack { + return deserialize(json) + } + + fun deserialize(json: JsonElement): ItemStack { + if (json !is JsonObject) { + throw JsonSyntaxException("ItemStack json element must be JsonObject, ${json::class.qualifiedName} given") + } + + val item = ForgeRegistries.ITEMS.getValue(ResourceLocation(json["id"]?.asString ?: return ItemStack.EMPTY)) ?: return ItemStack.EMPTY + val count = json["count"]?.asInt ?: 1 + + return ItemStack(item, count) + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/JsonArraySpliterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/JsonArraySpliterator.kt new file mode 100644 index 000000000..9e4b2ed30 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/JsonArraySpliterator.kt @@ -0,0 +1,30 @@ +package ru.dbotthepony.mc.otm.data + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import it.unimi.dsi.fastutil.objects.ObjectSpliterator +import it.unimi.dsi.fastutil.objects.ObjectSpliterators +import java.util.stream.Stream +import java.util.stream.StreamSupport + +class JsonArraySpliterator(private val obj: JsonArray, offset: Int = 0, private val maxPos: Int = obj.size()) : ObjectSpliterators.AbstractIndexBasedSpliterator(offset) { + init { + require(offset >= 0) { "Invalid offset $offset" } + require(offset + maxPos <= obj.size()) { "$offset -> $maxPos while having only size of ${obj.size()}!" } + } + + override fun get(location: Int): JsonElement { + return obj[location] + } + + override fun getMaxPos(): Int { + return maxPos + } + + override fun makeForSplit(pos: Int, maxPos: Int): ObjectSpliterator { + return JsonArraySpliterator(obj, pos, maxPos) + } +} + +fun JsonArray.elementSpliterator() = JsonArraySpliterator(this) +fun JsonArray.stream(): Stream = StreamSupport.stream(elementSpliterator(), false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt index 72b04aa75..e13e5843b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.network import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.ItemStack import net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT import net.minecraftforge.network.NetworkDirection.PLAY_TO_SERVER @@ -11,6 +12,7 @@ import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.android.AndroidFeature import ru.dbotthepony.mc.otm.android.AndroidFeatureType import ru.dbotthepony.mc.otm.android.AndroidResearch +import ru.dbotthepony.mc.otm.android.AndroidResearchManager import ru.dbotthepony.mc.otm.android.AndroidResearchType import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature import ru.dbotthepony.mc.otm.android.feature.ShockwaveFeature @@ -47,14 +49,14 @@ class MatteryPlayerFieldPacket(val bytes: ByteArray, val length: Int) : MatteryP } } -class AndroidResearchRequestPacket(val type: AndroidResearchType<*>) : MatteryPacket { +class AndroidResearchRequestPacket(val type: AndroidResearchType) : MatteryPacket { override fun write(buff: FriendlyByteBuf) { - buff.writeInt(MRegistry.ANDROID_RESEARCH.getID(type)) + buff.writeUtf(type.id.toString()) } override fun play(context: Supplier) { - context.get().packetHandled = true - context.get().enqueueWork { + context.packetHandled = true + context.enqueueWork { val ply = context.get().sender ?: return@enqueueWork if (ply.isSpectator) return@enqueueWork val android = ply.matteryPlayer ?: return@enqueueWork @@ -68,15 +70,15 @@ class AndroidResearchRequestPacket(val type: AndroidResearchType<*>) : MatteryPa companion object { fun read(buff: FriendlyByteBuf): AndroidResearchRequestPacket { - return AndroidResearchRequestPacket(MRegistry.ANDROID_RESEARCH.getValue(buff.readInt())) + return AndroidResearchRequestPacket(AndroidResearchManager[ResourceLocation(buff.readUtf())] ?: throw NoSuchElementException()) } } } -class AndroidResearchSyncPacket(val type: AndroidResearchType<*>, val dataList: FastByteArrayOutputStream?, val dataBytes: ByteArray?) : MatteryPacket { +class AndroidResearchSyncPacket(val type: AndroidResearchType, val dataList: FastByteArrayOutputStream?, val dataBytes: ByteArray?) : MatteryPacket { override fun write(buff: FriendlyByteBuf) { dataList ?: throw NullPointerException("No byte list is present") - buff.writeInt(MRegistry.ANDROID_RESEARCH.getID(type)) + buff.writeUtf(type.id.toString()) buff.writeBytes(dataList.array, 0, dataList.length) } @@ -94,7 +96,7 @@ class AndroidResearchSyncPacket(val type: AndroidResearchType<*>, val dataList: companion object { fun read(buff: FriendlyByteBuf): AndroidResearchSyncPacket { return AndroidResearchSyncPacket( - MRegistry.ANDROID_RESEARCH.getValue(buff.readInt()), + AndroidResearchManager[ResourceLocation(buff.readUtf())] ?: throw NoSuchElementException(), null, ByteArray(buff.readableBytes()).also { buff.readBytes(it) } ) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/RegistryNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/RegistryNetworkChannel.kt index 04afdddae..1d9ec49a9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/RegistryNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/RegistryNetworkChannel.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.mc.otm.network import net.minecraftforge.network.NetworkDirection +import ru.dbotthepony.mc.otm.android.AndroidResearchManager import ru.dbotthepony.mc.otm.matter.RegistryPacketClear import ru.dbotthepony.mc.otm.matter.RegistryPacketFullUpdate import ru.dbotthepony.mc.otm.matter.RegistryPacketRemove @@ -15,5 +16,7 @@ object RegistryNetworkChannel : MatteryNetworkChannel( add(RegistryPacketClear::class.java, RegistryPacketClear.Companion::read, NetworkDirection.PLAY_TO_CLIENT) add(RegistryPacketFullUpdate::class.java, RegistryPacketFullUpdate.Companion::read, NetworkDirection.PLAY_TO_CLIENT) add(RegistryPacketUpdate::class.java, RegistryPacketUpdate.Companion::read, NetworkDirection.PLAY_TO_CLIENT) + + add(AndroidResearchManager.SyncPacket::class.java, AndroidResearchManager::readSyncPacket, NetworkDirection.PLAY_TO_CLIENT) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidResearch.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidResearch.kt deleted file mode 100644 index be799014c..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidResearch.kt +++ /dev/null @@ -1,302 +0,0 @@ - -@file:Suppress("unused") - -package ru.dbotthepony.mc.otm.registry - -import net.minecraft.resources.ResourceLocation -import net.minecraftforge.eventbus.api.IEventBus -import net.minecraftforge.registries.DeferredRegister -import net.minecraftforge.registries.RegistryObject -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.TextComponent -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.android.AndroidResearchBuilder -import ru.dbotthepony.mc.otm.android.AndroidResearchType -import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature -import ru.dbotthepony.mc.otm.client.render.SkinElement -import ru.dbotthepony.mc.otm.client.render.SkinGrid - -object AndroidResearch { - val ICONS = ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/android_upgrades.png") - val ICON_TRANSFER: SkinElement - val ICON_ATTACK_BOOST: SkinElement - val ICON_PLASMA_SHIELD_BOOST: SkinElement - val ICON_CLOAK: SkinElement - val ICON_GRAVITATIONAL_STABILIZER: SkinElement - val ICON_AIR_BAGS: SkinElement - val ICON_JUMP_BOOST: SkinElement - - val ICON_FEATHER_FALLING: SkinElement - val ICON_ARC: SkinElement - val ICON_ARROW: SkinElement - val ICON_ARMOR: SkinElement - val ICON_NANOBOTS: SkinElement - val ICON_NIGHT_VISION: SkinElement - val ICON_OXYGEN_SUPPLY: SkinElement - - val ICON_PLASMA_SHIELD: SkinElement - val ICON_SHOCKWAVE: SkinElement - val ICON_LIMB_OVERCLOCKING: SkinElement - val ICON_STEP_ASSIST: SkinElement - val ICON_ENDER_TELEPORT: SkinElement - val ICON_WIRELESS_CHARGING: SkinElement - val ICON_UNKNOWN: SkinElement - - val ICON_EXTENDED_REACH: SkinElement - - init { - val grid = SkinGrid(ICONS, 18f, 18f, 7, 7) - - ICON_TRANSFER = grid.next() - ICON_ATTACK_BOOST = grid.next() - ICON_PLASMA_SHIELD_BOOST = grid.next() - ICON_CLOAK = grid.next() - ICON_GRAVITATIONAL_STABILIZER = grid.next() - ICON_AIR_BAGS = grid.next() - ICON_JUMP_BOOST = grid.next() - - ICON_FEATHER_FALLING = grid.next() - ICON_ARC = grid.next() - ICON_ARROW = grid.next() - ICON_ARMOR = grid.next() - ICON_NANOBOTS = grid.next() - ICON_NIGHT_VISION = grid.next() - ICON_OXYGEN_SUPPLY = grid.next() - - ICON_PLASMA_SHIELD = grid.next() - ICON_SHOCKWAVE = grid.next() - ICON_LIMB_OVERCLOCKING = grid.next() - ICON_STEP_ASSIST = grid.next() - ICON_ENDER_TELEPORT = grid.next() - ICON_WIRELESS_CHARGING = grid.next() - ICON_UNKNOWN = grid.next() - - ICON_EXTENDED_REACH = grid.next() - } - - private val registry = DeferredRegister.create(MRegistry.ANDROID_RESEARCH_KEY, OverdriveThatMatters.MOD_ID) - - internal fun register(bus: IEventBus) { - registry.register(bus) - } - - val AIR_BAGS: AndroidResearchType<*> by registry.register(MNames.AIR_BAGS) { - AndroidResearchBuilder() - .withExperience(18) - .addFeatureResult(AndroidFeatures.AIR_BAGS) - .withDescription() - .withIcon(ICON_AIR_BAGS) - .build() - } - - val IMPROVED_LIMBS: AndroidResearchType<*> by registry.register(MNames.IMPROVED_LIMBS) { - AndroidResearchBuilder() - .withExperience(20) - .withDescription() - .withIcon(ICON_EXTENDED_REACH) - .build() - } - - val STEP_ASSIST: AndroidResearchType<*> by registry.register(MNames.STEP_ASSIST) { - AndroidResearchBuilder() - .withExperience(24) - .addFeatureResult(AndroidFeatures.STEP_ASSIST) - .withDescription() - .withIcon(ICON_STEP_ASSIST) - .addPrerequisite(IMPROVED_LIMBS) - .build() - } - - val EXTENDED_REACH: AndroidResearchType<*> by registry.register(MNames.EXTENDED_REACH) { - AndroidResearchBuilder() - .withExperience(40) - .addFeatureResult(AndroidFeatures.EXTENDED_REACH) - .addPrerequisite(IMPROVED_LIMBS) - .withDescription() - .withIcon(ICON_EXTENDED_REACH) - .build() - } - - val NIGHT_VISION: AndroidResearchType<*> by registry.register(MNames.NIGHT_VISION) { - AndroidResearchBuilder() - .withExperience(40) - .withDescription() - .withIcon(ICON_NIGHT_VISION) - .addFeatureResult(AndroidFeatures.NIGHT_VISION) - .build() - } - - val NANOBOTS: AndroidResearchType<*> by registry.register(MNames.NANOBOTS) { - AndroidResearchBuilder() - .withExperience(15) - .withDescription() - .withIcon(ICON_NANOBOTS) - .build() - } - - val NANOBOTS_ARMOR: AndroidResearchType<*> by registry.register(MNames.NANOBOTS_ARMOR) { - AndroidResearchBuilder() - .withExperience(25) - .withDescription() - .addPrerequisite(OverdriveThatMatters.loc(MNames.NANOBOTS)) - .addFeatureResult(OverdriveThatMatters.loc(MNames.NANOBOTS_ARMOR)) - .withIcon(ICON_ARMOR) - .addBlocker(OverdriveThatMatters.loc(MNames.ATTACK_BOOST_1)) - .build() - } - - val LIMB_OVERCLOCKING: List> - val NANOBOTS_ARMOR_SPEED: List> - val NANOBOTS_ARMOR_STRENGTH: List> - val NANOBOTS_REGENERATION: List> - val ATTACK_BOOST: List> - - init { - val limbList = ArrayList>>() - val regenList = ArrayList>>() - val armorStrengthList = ArrayList>>() - val armorSpeedList = ArrayList>>() - val attackBoostList = ArrayList>>() - - for (i in 0 until 4) { - limbList.add(registry.register(MNames.LIMB_OVERCLOCKING_LIST[i]) { - val research = AndroidResearchBuilder() - .withExperience(18 + i * 8) - .withIconText(TextComponent((i + 1).toString())) - .addPrerequisite(IMPROVED_LIMBS) - .withIcon(ICON_LIMB_OVERCLOCKING) - .withName(TranslatableComponent("android_research.overdrive_that_matters.limb_overclocking", i + 1)) - .withDescription( - TranslatableComponent( - "android_research.overdrive_that_matters.limb_overclocking.description", - (i + 1) * 8, - (i + 1) * 6 - ) - ) - .addFeatureResult(OverdriveThatMatters.loc(MNames.LIMB_OVERCLOCKING), i) - - if (i > 0) { - research.addPrerequisite(OverdriveThatMatters.loc(MNames.LIMB_OVERCLOCKING_LIST[i - 1])) - } - - research.build() - }) - - attackBoostList.add(registry.register(MNames.ATTACK_BOOST_LIST[i]) { - val research = AndroidResearchBuilder() - .withExperience(18 + i * 8) - .withIconText(TextComponent((i + 1).toString())) - .addPrerequisite(IMPROVED_LIMBS) - .withIcon(ICON_ATTACK_BOOST) - .withName(TranslatableComponent("android_research.overdrive_that_matters.attack_boost", i + 1)) - .withDescription( - TranslatableComponent( - "android_research.overdrive_that_matters.attack_boost.description", - (i + 1) * 6 - ) - ) - .addFeatureResult(OverdriveThatMatters.loc(MNames.ATTACK_BOOST), i) - .addBlocker(NANOBOTS_ARMOR) - - if (i > 0) { - research.addPrerequisite(OverdriveThatMatters.loc(MNames.ATTACK_BOOST_LIST[i - 1])) - } - - research.build() - }) - - regenList.add(registry.register(MNames.NANOBOTS_REGENERATION_LIST[i]) { - val regeneration = AndroidResearchBuilder() - .withExperience(20 + i * 6) - .withIconText(TextComponent((i + 1).toString())) - .withIcon(ICON_NANOBOTS) - .withName(TranslatableComponent("android_research.overdrive_that_matters.nanobots_regeneration", i + 1)) - .withDescription( - if (i > 0) TranslatableComponent("android_research.overdrive_that_matters.nanobots_regeneration.description_improve") else TranslatableComponent( - "android_research.overdrive_that_matters.nanobots_regeneration.description" - ) - ) - .addFeatureResult(OverdriveThatMatters.loc(MNames.NANOBOTS_REGENERATION), i) - - - if (i > 0) { - regeneration.addPrerequisite(OverdriveThatMatters.loc(MNames.NANOBOTS_REGENERATION_LIST[i - 1])) - } else { - regeneration.addPrerequisite(OverdriveThatMatters.loc(MNames.NANOBOTS)) - } - - regeneration.build() - }) - } - - for (i in 0 until 3) { - val level = i + 1 - - armorStrengthList.add(registry.register(MNames.NANOBOTS_ARMOR_STRENGTH_LIST[i]) { - AndroidResearchBuilder() - .withExperience(20 + i * 8) - .withIconText(TextComponent((i + 1).toString())) - .withIcon(ICON_ARMOR) - .addPrerequisite(OverdriveThatMatters.loc(if (i > 0) MNames.NANOBOTS_ARMOR_STRENGTH_LIST[i - 1] else MNames.NANOBOTS_ARMOR)) - .withName( - TranslatableComponent( - "android_research.overdrive_that_matters.nanobots_armor_strength", - i + 1 - ) - ) - .withDescription( - TranslatableComponent( - "android_research.overdrive_that_matters.nanobots_armor_strength.description", - (i + 1) * 8, - (i + 1) * 6 - ) - ) - .addFeatureResult(OverdriveThatMatters.loc(MNames.NANOBOTS_ARMOR), 0) { _, feature -> - if ((feature as NanobotsArmorFeature).strength < level) feature.strength = level - } - .build() - }) - - armorSpeedList.add(registry.register(MNames.NANOBOTS_ARMOR_SPEED_LIST[i]) { - AndroidResearchBuilder() - .withExperience(18 + i * 6) - .withIconText(TextComponent((i + 1).toString())) - .withIcon(ICON_ARMOR) - .addPrerequisite(OverdriveThatMatters.loc(if (i > 0) MNames.NANOBOTS_ARMOR_SPEED_LIST[i - 1] else MNames.NANOBOTS_ARMOR)) - .withName( - TranslatableComponent( - "android_research.overdrive_that_matters.nanobots_armor_speed", - i + 1 - ) - ) - .withDescription( - TranslatableComponent( - "android_research.overdrive_that_matters.nanobots_armor_speed.description", - (i + 1) * 8, - (i + 1) * 6 - ) - ) - .addFeatureResult(OverdriveThatMatters.loc(MNames.NANOBOTS_ARMOR), 0) { _, feature -> - if ((feature as NanobotsArmorFeature).speed < level) feature.speed = level - } - .build() - }) - } - - LIMB_OVERCLOCKING = RegistryObjectList(limbList) - NANOBOTS_REGENERATION = RegistryObjectList(regenList) - NANOBOTS_ARMOR_SPEED = RegistryObjectList(armorSpeedList) - NANOBOTS_ARMOR_STRENGTH = RegistryObjectList(armorStrengthList) - ATTACK_BOOST = RegistryObjectList(attackBoostList) - } - - val SHOCKWAVE: AndroidResearchType<*> by registry.register(MNames.SHOCKWAVE) { - AndroidResearchBuilder() - .withExperience(40) - .withDescription() - .withIcon(ICON_SHOCKWAVE) - .addFeatureResult(AndroidFeatures.SHOCKWAVE) - .addPrerequisite(ATTACK_BOOST[2]) - .build() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt index 5587f6dd1..c052e372b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt @@ -85,20 +85,12 @@ class WriteOnce : ReadWriteProperty { object MRegistry { private val features = RegistryDelegate>("android_features") - private val research = RegistryDelegate>("android_research") - val ANDROID_FEATURES by features - val ANDROID_RESEARCH by research - val ANDROID_FEATURES_LOCATION get() = features.location - val ANDROID_RESEARCH_LOCATION get() = research.location - val ANDROID_FEATURES_KEY get() = features.key - val ANDROID_RESEARCH_KEY get() = research.key private fun register(event: NewRegistryEvent) { features.build(event) - research.build(event) } val CARGO_CRATES = DecorativeBlock(MNames.CARGO_CRATE, ::CargoCrateBlock, baseItemGroup = OverdriveThatMatters.INSTANCE.CREATIVE_TAB) @@ -234,7 +226,6 @@ object MRegistry { MMenus.register(bus) MItems.register(bus) AndroidFeatures.register(bus) - AndroidResearch.register(bus) MSoundEvents.register(bus) LootModifiers.register(bus)