Android Research is now loaded through JSON files

This commit is contained in:
DBotThePony 2022-09-23 16:52:00 +07:00
parent 672441021d
commit feb0c4d551
Signed by: DBot
GPG Key ID: DCC23B5715498507
24 changed files with 1444 additions and 975 deletions

View File

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

View File

@ -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<AndroidResearchType>, 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<AndroidResearchType>()
val regenList = LinkedList<AndroidResearchType>()
val armorStrengthList = LinkedList<AndroidResearchType>()
val armorSpeedList = LinkedList<AndroidResearchType>()
val attackBoostList = LinkedList<AndroidResearchType>()
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%%")
}
}

View File

@ -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%%")
}
}

View File

@ -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<String, Slave>()
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)
}

View File

@ -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();

View File

@ -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<CompoundTag> {
class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlayerCapability) : INBTSerializable<CompoundTag> {
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<AndroidFeatureType<*>, 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<Component> by lazy {
return@lazy listOf(type.displayName.copy().withStyle(ChatFormatting.WHITE))
val tooltipLines: List<Component> get() {
val lines = ArrayList<Component>()
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<Component> by this::tooltipLines
val screenTooltipLines: List<Component> get() {
val builder = tooltipLines as ArrayList<Component>
/**
* 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)
}
}
}

View File

@ -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<Component>? = null,
var hasDescription: Boolean = false,
var skinIcon: SkinElement? = null,
var iconText: Component? = null,
) {
private val items = ArrayList<ItemStack>()
private val prerequisites = ArrayList<Pair<() -> ResourceLocation, Boolean>>()
private val blockers = ArrayList<Pair<() -> ResourceLocation, Boolean>>()
private val features = ArrayList<DeferredFeature>()
private val resolvedFeatures = ArrayList<ResolvedFeature>()
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<Component>): AndroidResearchBuilder {
this.customDescription = ArrayList<Component>(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<AndroidResearch> {
val items: List<ItemStack> = ImmutableList.builder<ItemStack>().let {
for (item in this.items) {
it.add(item.copy())
}
it.build()
}
val prerequisites: List<Pair<() -> ResourceLocation, Boolean>> = ImmutableList.copyOf(this.prerequisites)
val blockers: List<Pair<() -> ResourceLocation, Boolean>> = ImmutableList.copyOf(this.blockers)
val experience = this.experience
val name = name?.copy()
val description = customDescription?.let {
val builder = ImmutableList.builder<Component>()
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<ResolvedFeature> by lazy {
val builder = ImmutableList.builder<ResolvedFeature>()
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<AndroidResearch>(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<AndroidFeatureType<*>, 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<Component> by lazy {
val builder = ImmutableList.builder<Component>()
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<Component> get() {
val builder = ArrayList<Component>()
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<Component>
get() = description ?: super.displayDescription
override val definedPrerequisites: List<AndroidResearchType<*>> by lazy {
val builder = ImmutableList.builder<AndroidResearchType<*>>()
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<AndroidResearchType<*>> by lazy {
val builder = ImmutableList.builder<AndroidResearchType<*>>()
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()
}
}
}
}

View File

@ -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<AndroidResearchType>) -> Unit>()
/**
* override this
*/
protected open fun addEverything(serializer: Consumer<AndroidResearchType>) {
for (callback in callbacks) {
callback.invoke(serializer)
}
}
/**
* or call this
*/
fun exec(callback: (Consumer<AndroidResearchType>) -> Unit): AndroidResearchDataProvider {
callbacks.add(callback)
return this
}
final override fun run(output: CachedOutput) {
val set = ObjectArraySet<ResourceLocation>()
val added = LinkedList<AndroidResearchType>()
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"
}
}

View File

@ -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<AndroidResearchType> {
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<AndroidResearchType> {
return researchMap.values.iterator()
}
private val maskedMap = HashMap<ResourceLocation, AndroidResearchType>()
/**
* Internal use only, used by [AndroidResearchDataProvider] for validation purposes
*/
internal fun put(value: AndroidResearchType) {
maskedMap[value.id] = value
}
var researchMap: Map<ResourceLocation, AndroidResearchType> = mapOf()
private set
override fun apply(
jsonElementMap: Map<ResourceLocation, JsonElement>,
manager: ResourceManager,
profiler: ProfilerFiller
) {
val builder = ImmutableMap.builder<ResourceLocation, AndroidResearchType>()
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<AndroidResearchType>) : MatteryPacket {
override fun write(buff: FriendlyByteBuf) {
buff.writeCollection(collection) { a, b -> b.toNetwork(a) }
}
override fun play(context: Supplier<NetworkEvent.Context>) {
context.packetHandled = true
if (NULLABLE_MINECRAFT_SERVER is IntegratedServer) {
return
}
val builder = ImmutableMap.builder<ResourceLocation, AndroidResearchType>()
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))
}
}

View File

@ -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<R : AndroidResearch> {
fun factory(type: AndroidResearchType<*>, capability: MatteryPlayerCapability): R
}
private fun findPrerequisites(
initial: Collection<AndroidResearchType<*>>,
add: MutableSet<AndroidResearchType<*>> = HashSet(),
initial: Collection<AndroidResearchType>,
add: MutableSet<AndroidResearchType> = HashSet(),
top: Boolean = true
): Set<AndroidResearchType<*>> {
): Set<AndroidResearchType> {
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<AndroidResearchType<*>>,
add: MutableSet<AndroidResearchType<*>> = HashSet(),
): Set<AndroidResearchType<*>> {
initial: Collection<AndroidResearchType>,
add: MutableSet<AndroidResearchType> = HashSet(),
): Set<AndroidResearchType> {
for (value in initial) {
add.add(value)
findAllPrerequisites(value.flatPrerequisites, add)
@ -47,9 +55,9 @@ private fun findAllPrerequisites(
}
private fun findAllChildren(
initial: Collection<AndroidResearchType<*>>,
add: MutableSet<AndroidResearchType<*>> = HashSet(),
): Set<AndroidResearchType<*>> {
initial: Collection<AndroidResearchType>,
add: MutableSet<AndroidResearchType> = HashSet(),
): Set<AndroidResearchType> {
for (value in initial) {
add.add(value)
findAllChildren(value.flatUnlocks, add)
@ -58,62 +66,114 @@ private fun findAllChildren(
return add
}
class ListSet<T>(private val list: ImmutableList<T>) : List<T>, Set<T>, RandomAccess {
constructor(list: Collection<T>) : this(ImmutableList.copyOf(list))
class AndroidResearchType(
val id: ResourceLocation,
prerequisites: Collection<Reference>,
blockedBy: Collection<Reference>,
private val set: ImmutableSet<T> by lazy { ImmutableSet.copyOf(list) }
items: Collection<ItemStack>,
features: Collection<FeatureReference>,
override val size: Int
get() = list.size
descriptionLines: Collection<Component>,
override fun contains(element: T): Boolean {
return set.contains(element)
}
val experienceLevels: Int = 0,
private val customName: Component? = null,
override fun containsAll(elements: Collection<T>): 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<T> {
return list.iterator()
}
override fun lastIndexOf(element: T): Int {
return list.lastIndexOf(element)
}
override fun listIterator(): ListIterator<T> {
return list.listIterator()
}
override fun listIterator(index: Int): ListIterator<T> {
return list.listIterator(index)
}
override fun spliterator(): Spliterator<T> {
return list.spliterator()
}
override fun subList(fromIndex: Int, toIndex: Int): List<T> {
return list.subList(fromIndex, toIndex)
}
}
open class AndroidResearchType<R : AndroidResearch>(
protected val factory: AndroidResearchFactory<R>
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<R : AndroidResearch>(
* Please avoid having more than one prerequisite, as this case doesn't have proper research tree
* rendering code (yet).
*/
open val definedPrerequisites: List<AndroidResearchType<*>> get() = emptyList()
val prerequisites: List<Reference> = ImmutableList.copyOf(prerequisites)
/**
* Blocked by list as-is
*/
open val definedBlockedBy: List<AndroidResearchType<*>> get() = emptyList()
val blockedBy: List<Reference> = ImmutableList.copyOf(blockedBy)
val resolvedPrerequisites: List<AndroidResearchType> 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<AndroidResearchType> 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<FeatureReference> = ImmutableList.copyOf(features)
val resolvedFeatures: List<ResolvedFeature> 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<ItemStack> = items.stream().filter { !it.isEmpty }.map { it.copy() }.toList().toImmutableList()
/**
* Stream containing copies of original items in list
*/
val items: Stream<out ItemStack> get() = itemCollection.stream().map { it.copy() }
private val descriptionLines: List<MutableComponent> = ImmutableList.copyOf(descriptionLines.map { it.copy() })
/**
* Stream containing copies of original [Component]s in list
*/
val description: Stream<out Component> get() = descriptionLines.stream().map { it.copy() }
/**
* Flat list of research preceding this research.
@ -151,16 +246,16 @@ open class AndroidResearchType<R : AndroidResearch>(
* * 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<AndroidResearchType<*>> by lazy {
val parentPrerequisites = findPrerequisites(definedPrerequisites)
val builder = ImmutableList.builder<AndroidResearchType<*>>()
val flatPrerequisites: List<AndroidResearchType> by lazy {
val parentPrerequisites = findPrerequisites(resolvedPrerequisites)
val builder = ImmutableList.builder<AndroidResearchType>()
for (value in definedPrerequisites) {
for (value in resolvedPrerequisites) {
if (value !in parentPrerequisites) {
builder.add(value)
}
@ -174,7 +269,7 @@ open class AndroidResearchType<R : AndroidResearch>(
*
* Returns list which also doubles as set (for contains method).
*/
val allPrerequisites: List<AndroidResearchType<*>> by lazy {
val allPrerequisites: List<AndroidResearchType> by lazy {
ListSet(findAllPrerequisites(flatPrerequisites))
}
@ -193,15 +288,15 @@ open class AndroidResearchType<R : AndroidResearch>(
*
* Returns list which also doubles as set (for contains method).
*/
val flatBlocking: List<AndroidResearchType<*>> by lazy {
val list = ImmutableList.builder<AndroidResearchType<*>>()
val flatBlocking: List<AndroidResearchType> by lazy {
val list = ImmutableList.builder<AndroidResearchType>()
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<R : AndroidResearch>(
*
* Returns list which also doubles as set (for contains method).
*/
val flatBlockedBy: ListSet<AndroidResearchType<*>> by lazy {
val list = ImmutableList.builder<AndroidResearchType<*>>()
val flatBlockedBy: ListSet<AndroidResearchType> by lazy {
val list = ImmutableList.builder<AndroidResearchType>()
for (blocker in definedBlockedBy) {
for (blocker in resolvedBlockedBy) {
var hit = false
for (research in allPrerequisites) {
@ -257,8 +352,8 @@ open class AndroidResearchType<R : AndroidResearch>(
*
* Returns list which also doubles as set (for contains method).
*/
val allBlockedBy: ListSet<AndroidResearchType<*>> by lazy {
val list = HashSet<AndroidResearchType<*>>()
val allBlockedBy: ListSet<AndroidResearchType> by lazy {
val list = HashSet<AndroidResearchType>()
list.addAll(flatBlockedBy)
@ -274,10 +369,10 @@ open class AndroidResearchType<R : AndroidResearch>(
*
* Returns list which also doubles as set (for contains method).
*/
val flatUnlocks: List<AndroidResearchType<*>> by lazy {
val list = ImmutableList.builder<AndroidResearchType<*>>()
val flatUnlocks: List<AndroidResearchType> by lazy {
val list = ImmutableList.builder<AndroidResearchType>()
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<R : AndroidResearch>(
*
* Returns list which also doubles as set (for contains method).
*/
val allUnlocks: List<AndroidResearchType<*>> by lazy {
val allUnlocks: List<AndroidResearchType> by lazy {
ListSet(findAllChildren(flatUnlocks))
}
@ -300,8 +395,8 @@ open class AndroidResearchType<R : AndroidResearch>(
*
* Returns list which also doubles as set (for contains method)
*/
val allBlocking: List<AndroidResearchType<*>> by lazy {
val set = HashSet<AndroidResearchType<*>>()
val allBlocking: List<AndroidResearchType> by lazy {
val set = HashSet<AndroidResearchType>()
for (research in flatBlocking) {
set.add(research)
@ -318,30 +413,231 @@ open class AndroidResearchType<R : AndroidResearch>(
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<Component> 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<MutableComponent>,
experienceLevels = experience,
customName = customName
)
}
}
@Suppress("unused")
class Builder(
val id: ResourceLocation,
var experience: Int = 0,
var customName: Component? = null,
var description: MutableList<Component>? = null,
var skinIcon: SkinElement? = null,
var iconText: Component? = null,
) {
private val items = ArrayList<ItemStack>()
private val prerequisites = LinkedList<Reference>()
private val blockers = ArrayList<Reference>()
private val features = ArrayList<FeatureReference>()
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<Component>): Builder {
this.description = ArrayList<Component>(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() }
}
}
}

View File

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

View File

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

View File

@ -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<AndroidResearchType<*>, AndroidResearch>()
private val research = IdentityHashMap<AndroidResearchType, AndroidResearch>()
private var invalidateNetworkIn = 10
@ -264,10 +265,24 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
deathLog.clear()
}
fun <T : AndroidResearch> getResearch(type: AndroidResearchType<T>): 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<AndroidResearch>(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<out AndroidFeature> 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
}

View File

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

View File

@ -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<SkinElement>(), JsonSerializer<SkinElement>, JsonDeserializer<SkinElement> {
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" }

View File

@ -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<AndroidResearchType<*>>, result: MutableList<AndroidResearchType<*>>) {
private fun exploreTree(research: AndroidResearchType, seen: MutableSet<AndroidResearchType>, result: MutableList<AndroidResearchType>) {
if (!seen.add(research)) {
return
}
@ -45,13 +46,13 @@ private fun exploreTree(research: AndroidResearchType<*>, seen: MutableSet<Andro
}
}
private fun findGraphs(): List<Pair<AndroidResearchType<*>, List<AndroidResearchType<*>>>> {
val seen = HashSet<AndroidResearchType<*>>()
val list = ArrayList<Pair<AndroidResearchType<*>, ArrayList<AndroidResearchType<*>>>>()
private fun findGraphs(): List<Pair<AndroidResearchType, List<AndroidResearchType>>> {
val seen = HashSet<AndroidResearchType>()
val list = ArrayList<Pair<AndroidResearchType, ArrayList<AndroidResearchType>>>()
for (research in MRegistry.ANDROID_RESEARCH) {
for (research in AndroidResearchManager) {
if (research.flatPrerequisites.isEmpty()) {
val tree = ArrayList<AndroidResearchType<*>>()
val tree = ArrayList<AndroidResearchType>()
exploreTree(research, seen, tree)
@ -64,7 +65,7 @@ private fun findGraphs(): List<Pair<AndroidResearchType<*>, List<AndroidResearch
return list
}
private fun isTree(root: AndroidResearchType<*>): 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<Float, Float>, Pair<Float, Float>>
private class Tree(val node: AndroidResearchType<*>) : Iterable<Tree> {
private class Tree(val node: AndroidResearchType) : Iterable<Tree> {
val subtrees = ArrayList<Tree>()
val height: Int
@ -161,7 +162,7 @@ private class Tree(val node: AndroidResearchType<*>) : Iterable<Tree> {
val totalWidth = width * 24f
val lines = ArrayList<LinePos>()
val linesToResearch = IdentityHashMap<AndroidResearchType<*>, ArrayList<LinePos>>()
val linesToResearch = IdentityHashMap<AndroidResearchType, ArrayList<LinePos>>()
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<AndroidStationScreen>,
private val node: AndroidResearch,
private val lines: List<LinePos>,
private val highlightLines: Map<AndroidResearchType<*>, List<LinePos>>
private val highlightLines: Map<AndroidResearchType, List<LinePos>>
): EditablePanel<AndroidStationScreen>(
parent.screen,
parent,

View File

@ -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 <E> MutableCollection<E>.addAll(elements: Iterator<E>) {
for (item in elements) {
add(item)
}
}
fun <E> MutableCollection<E>.addAll(elements: Stream<out E>) {
for (item in elements) {
add(item)
}
}
fun <E> Iterable<E>.stream(): Stream<out E> {
return StreamSupport.stream(this.spliterator(), false)
}
fun <E> Iterator<E>.stream(): Stream<out E> {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(this, 0), false)
}

View File

@ -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<T>(private val list: ImmutableList<T>) : List<T>, Set<T>, RandomAccess {
constructor(list: Collection<T>) : this(ImmutableList.copyOf(list))
private val set: ImmutableSet<T> 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<T>): 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<T> {
return list.iterator()
}
override fun lastIndexOf(element: T): Int {
return list.lastIndexOf(element)
}
override fun listIterator(): ListIterator<T> {
return list.listIterator()
}
override fun listIterator(index: Int): ListIterator<T> {
return list.listIterator(index)
}
override fun spliterator(): Spliterator<T> {
return list.spliterator()
}
override fun subList(fromIndex: Int, toIndex: Int): List<T> {
return list.subList(fromIndex, toIndex)
}
}

View File

@ -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<ItemStack> {
object ItemStackCodec : Codec<ItemStack>, TypeAdapter<ItemStack>(), JsonSerializer<ItemStack>, JsonDeserializer<ItemStack> {
override fun <T : Any?> encode(input: ItemStack, ops: DynamicOps<T>, prefix: T): DataResult<T> {
require(prefix == ops.empty()) { "Non-empty prefix: $prefix" }
@ -36,4 +51,63 @@ object ItemStackCodec : Codec<ItemStack> {
}
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)
}
}

View File

@ -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<JsonElement>(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<JsonElement> {
return JsonArraySpliterator(obj, pos, maxPos)
}
}
fun JsonArray.elementSpliterator() = JsonArraySpliterator(this)
fun JsonArray.stream(): Stream<out JsonElement> = StreamSupport.stream(elementSpliterator(), false)

View File

@ -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<NetworkEvent.Context>) {
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) }
)
}

View File

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

View File

@ -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<AndroidResearchType<*>>
val NANOBOTS_ARMOR_SPEED: List<AndroidResearchType<*>>
val NANOBOTS_ARMOR_STRENGTH: List<AndroidResearchType<*>>
val NANOBOTS_REGENERATION: List<AndroidResearchType<*>>
val ATTACK_BOOST: List<AndroidResearchType<*>>
init {
val limbList = ArrayList<RegistryObject<AndroidResearchType<*>>>()
val regenList = ArrayList<RegistryObject<AndroidResearchType<*>>>()
val armorStrengthList = ArrayList<RegistryObject<AndroidResearchType<*>>>()
val armorSpeedList = ArrayList<RegistryObject<AndroidResearchType<*>>>()
val attackBoostList = ArrayList<RegistryObject<AndroidResearchType<*>>>()
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()
}
}

View File

@ -85,20 +85,12 @@ class WriteOnce<V> : ReadWriteProperty<Any?, V> {
object MRegistry {
private val features = RegistryDelegate<AndroidFeatureType<*>>("android_features")
private val research = RegistryDelegate<AndroidResearchType<*>>("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)