diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/ResearchData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/ResearchData.kt index 55c0b077b..210bc0ef4 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/ResearchData.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/ResearchData.kt @@ -2,7 +2,9 @@ package ru.dbotthepony.mc.otm.datagen import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.android.AndroidResearchType +import ru.dbotthepony.mc.otm.android.feature.ItemMagnetFeature import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature +import ru.dbotthepony.mc.otm.android.feature.ShockwaveFeature import ru.dbotthepony.mc.otm.client.render.ResearchIcons import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent @@ -221,12 +223,24 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang AndroidResearchType.Builder(modLocation(MNames.SHOCKWAVE)) .withExperience(40) .withDescription() + .appendDescription(ShockwaveFeature.POWER_COST_DESCRIPTION) .withIcon(ResearchIcons.ICON_SHOCKWAVE) .addFeatureResult(AndroidFeatures.SHOCKWAVE) .addPrerequisite(attackBoostList[2]) .build() + val ITEM_MAGNET = + AndroidResearchType.Builder(modLocation(MNames.ITEM_MAGNET)) + .withExperience(28) + .withDescription(0 .. 1) + .appendDescription(ItemMagnetFeature.POWER_COST_DESCRIPTION) + .withIcon(ResearchIcons.ICON_ITEM_MAGNET) + .addFeatureResult(AndroidFeatures.ITEM_MAGNET) + .addPrerequisite(STEP_ASSIST) + .build() + serializer.accept(SHOCKWAVE) + serializer.accept(ITEM_MAGNET) with(lang.english) { add(limbList[0], "Limb Overclocking %s") @@ -259,6 +273,13 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang add(STEP_ASSIST, "Step Assist") add(STEP_ASSIST, "description", "Allows unit to step up whole blocks") + add(ITEM_MAGNET, "Item Magnet") + add(ITEM_MAGNET, "description0", "Pulls nearby items to you while active") + add(ITEM_MAGNET, "description1", "Drains energy for each item stack it pulls") + + add(SHOCKWAVE, "Shockwave Pulsator") + add(SHOCKWAVE, "description", "Releases a shockwave around you, damaging everything in small radius, as you quickly land on ground") + add(attackBoostList[0], "Attack Boost %s") add(attackBoostList[0], "description", "Increases total melee attack strength by %s%%") } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt index 6ba5a545e..4dc0f280d 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt @@ -51,6 +51,9 @@ private fun sounds(provider: MatteryLanguageProvider) { private fun misc(provider: MatteryLanguageProvider) { with(provider.english) { + gui("power_cost_per_use", "Power cost per use: %s") + gui("power_cost_per_tick", "Power cost per tick: %s") + gui("cancel", "Cancel") gui("confirm", "Confirm") @@ -464,6 +467,7 @@ private fun androidFeatures(provider: MatteryLanguageProvider) { add(AndroidFeatures.SHOCKWAVE, "Shockwave") add(AndroidFeatures.NIGHT_VISION, "Night Vision") add(AndroidFeatures.NANOBOTS_ARMOR, "Nanobots Armor") + add(AndroidFeatures.ITEM_MAGNET, "Item Magnet") } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index 7e3ab4f71..01f90c315 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -100,8 +100,6 @@ public final class OverdriveThatMatters { ClientConfig.INSTANCE.register(); ServerConfig.INSTANCE.register(); - - NanobotsArmorFeature.Companion.register(); } private void setup(final FMLCommonSetupEvent event) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt index d29bded02..7ca1fdd61 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt @@ -141,6 +141,20 @@ object ServerConfig { val NIGHT_VISION_POWER_DRAW by specBuilder.defineImpreciseFraction("nightVisionPowerDraw", ImpreciseFraction(8), ImpreciseFraction.ZERO) + object AndroidItemMagnet { + init { + specBuilder.comment("Item magnet ability").push("item_magnet") + } + + val POWER_DRAW by specBuilder.comment("Per tick per stack").defineImpreciseFraction("powerDraw", ImpreciseFraction(8), ImpreciseFraction.ZERO) + val RADIUS_HORIZONTAL: Double by specBuilder.defineInRange("radiusHorizontal", 6.0, 0.0, Double.MAX_VALUE / 4.0) + val RADIUS_VERTICAL: Double by specBuilder.defineInRange("radiusVertical", 3.0, 0.0, Double.MAX_VALUE / 4.0) + + init { + specBuilder.pop() + } + } + object Shockwave { init { specBuilder.comment("Shockwave ability").push("shockwave") @@ -162,6 +176,7 @@ object ServerConfig { init { // access shockwave class so spec is built + AndroidItemMagnet Shockwave specBuilder.pop() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt index bf8a825ea..27c514a29 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt @@ -65,7 +65,7 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay get.level = level.level for (transformer in type.features.first { it.id == feature.feature.registryName }.transformersDown) { - transformer.accept(this to get) + transformer.apply(this to get) } } } @@ -94,7 +94,7 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay } for (transformer in type.features.first { it.id == feature.feature.registryName }.transformersUp) { - transformer.accept(this to get) + transformer.apply(this to get) } } } catch(err: Throwable) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt index 99d95a4a7..191119617 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt @@ -32,6 +32,8 @@ open class AndroidResearchDataProvider(protected val dataGenerator: DataGenerato } final override fun run(output: CachedOutput) { + AndroidResearchManager.fireRegistrationEvent() + val set = ObjectArraySet() val added = LinkedList() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt index a5dcbcd08..bddc41cd8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt @@ -6,21 +6,24 @@ import com.google.gson.JsonElement import com.google.gson.JsonObject import net.minecraft.client.server.IntegratedServer import net.minecraft.network.FriendlyByteBuf +import net.minecraft.network.chat.Component 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.common.MinecraftForge import net.minecraftforge.event.AddReloadListenerEvent import net.minecraftforge.event.OnDatapackSyncEvent +import net.minecraftforge.eventbus.api.Event 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.OverdriveThatMatters 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.core.TranslatableComponent import ru.dbotthepony.mc.otm.data.SerializedFunctionRegistry import ru.dbotthepony.mc.otm.network.MatteryPacket import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel @@ -30,6 +33,9 @@ import ru.dbotthepony.mc.otm.onceServer import java.util.LinkedList import java.util.function.Supplier +typealias AndroidResultTransformer = SerializedFunctionRegistry.BoundFunction, Unit> +typealias ComponentSupplier = SerializedFunctionRegistry.BoundFunction + object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_android_research"), Iterable { /** * Feel free to register functions inside this thing from anywhere in your code @@ -37,7 +43,36 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s * * Just make sure client and server has the same set of functions defined. */ - val featureResultTransformers = SerializedFunctionRegistry>() + val featureResultTransformers = SerializedFunctionRegistry, Unit>() + + /** + * Feel free to register functions inside this thing from anywhere in your code + * (registration and querying is completely thread safe). + * + * Just make sure client and server has the same set of functions defined. + */ + val descriptionFuncs = SerializedFunctionRegistry() + + fun descriptionFunc(name: ResourceLocation, base: String, vararg argument: Supplier): ComponentSupplier { + return descriptionFuncs.register(name) {-> + return@register TranslatableComponent(base, *argument.map { it.get() }.toTypedArray()) + }.bind() + } + + private var firedRegistrationEvent = false + + /** + * Event-style registration of serializable functions, for those who prefer/need it + * + * Fired *once* on [MinecraftForge.EVENT_BUS] before loading android research + */ + object RegisterFuncsEvent : Event() { + val manager get() = AndroidResearchManager + val featureResults by ::featureResultTransformers + val descriptionFunctions by ::descriptionFuncs + + fun descriptionFunc(name: ResourceLocation, base: String, vararg argument: Supplier): ComponentSupplier = AndroidResearchManager.descriptionFunc(name, base, *argument) + } const val DIRECTORY = "otm_android_research" private val LOGGER = LogManager.getLogger() @@ -62,11 +97,23 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s var researchMap: Map = mapOf() private set + internal fun fireRegistrationEvent() { + if (!firedRegistrationEvent) { + try { + MinecraftForge.EVENT_BUS.post(RegisterFuncsEvent) + } finally { + firedRegistrationEvent = true + } + } + } + override fun apply( jsonElementMap: Map, manager: ResourceManager, profiler: ProfilerFiller ) { + fireRegistrationEvent() + val builder = ImmutableMap.builder() for ((k, v) in jsonElementMap) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt index ba5e04074..2e20e8cb7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.mc.otm.android import com.google.common.collect.ImmutableList +import com.google.common.collect.Streams import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonObject @@ -21,15 +22,13 @@ import ru.dbotthepony.mc.otm.core.ListSet import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.registryName 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.data.SerializedFunctionRegistry import ru.dbotthepony.mc.otm.data.stream import ru.dbotthepony.mc.otm.registry.MRegistry -import java.util.ArrayList import java.util.LinkedList import java.util.stream.Stream +import kotlin.collections.ArrayList import kotlin.collections.HashSet private fun findPrerequisites( @@ -81,6 +80,7 @@ class AndroidResearchType( features: Collection, descriptionLines: Collection, + descriptionSuppliers: Collection = listOf(), val experienceLevels: Int = 0, private val customName: Component? = null, @@ -137,8 +137,8 @@ class AndroidResearchType( val id: ResourceLocation, val level: Int = 0, val isRigid: Boolean, - val transformersUp: Collection>> = listOf(), - val transformersDown: Collection>> = listOf(), + val transformersUp: Collection = listOf(), + val transformersDown: Collection = listOf(), ) { fun toJson(): JsonObject { return JsonObject().also { @@ -186,8 +186,8 @@ class AndroidResearchType( ResourceLocation((value["id"] as? JsonPrimitive ?: throw JsonSyntaxException("Invalid `id` value")).asString), (value["level"] as JsonPrimitive?)?.asInt ?: 0, (value["is_rigid"] as JsonPrimitive?)?.asBoolean ?: true, - (value["functions_up"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList>>()), - (value["functions_down"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList>>()), + (value["functions_up"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList()), + (value["functions_down"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList()), ) } else { throw JsonSyntaxException("Unknown element type ${value::class.qualifiedName}") @@ -258,10 +258,12 @@ class AndroidResearchType( private val descriptionLines: List = ImmutableList.copyOf(descriptionLines.map { it.copy() }) + private val descriptionSuppliers: List = ImmutableList.copyOf(descriptionSuppliers) + /** * Stream containing copies of original [Component]s in list */ - val description: Stream get() = descriptionLines.stream().map { it.copy() } + val description: Stream get() = Streams.concat(descriptionLines.stream().map { it.copy() }, descriptionSuppliers.stream().map { it.apply(Unit) }) /** * Flat list of research preceding this research. @@ -465,6 +467,7 @@ class AndroidResearchType( 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["description_funcs"] = JsonArray().also { for (line in descriptionSuppliers) it.add(line.toJson()) } it["experience"] = JsonPrimitive(experienceLevels) if (skinIcon != null) { @@ -498,6 +501,7 @@ class AndroidResearchType( 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.writeCollection(descriptionSuppliers) { a, b -> b.toNetwork(a) } buff.writeVarInt(experienceLevels) buff.writeBoolean(customName != null) @@ -521,6 +525,7 @@ class AndroidResearchType( val items = buff.readCollection({ LinkedList() }, FriendlyByteBuf::readItem) val features = buff.readCollection({ LinkedList() }, FeatureReference::fromNetwork) val descriptionLines = buff.readCollection({ LinkedList() }, FriendlyByteBuf::readComponent) + val descriptionSuppliers = buff.readCollection({ LinkedList() }, { AndroidResearchManager.descriptionFuncs.fromNetwork(it) }) val experienceLevels = buff.readVarInt() val customName = if (buff.readBoolean()) { @@ -554,6 +559,7 @@ class AndroidResearchType( items = items, features = features, descriptionLines = descriptionLines, + descriptionSuppliers = descriptionSuppliers.filterNotNull(), experienceLevels = experienceLevels, customName = customName, iconText = iconTextValue, @@ -572,6 +578,7 @@ class AndroidResearchType( val items = value["required_items"] as JsonArray? ?: JsonArray() val features = value["feature_result"] as JsonArray? ?: JsonArray() val description = value["description"] as JsonArray? ?: JsonArray() + val description_funcs = value["description_funcs"] as JsonArray? ?: JsonArray() val experience = value["experience"]?.asInt ?: 0 val customName = value["custom_name"]?.let(Component.Serializer::fromJson) val iconText = value["icon_text"]?.let(Component.Serializer::fromJson) @@ -585,6 +592,7 @@ class AndroidResearchType( 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, + descriptionSuppliers = description_funcs.stream().map { AndroidResearchManager.descriptionFuncs.fromJson(it) }.toList() as List, experienceLevels = experience, customName = customName, iconText = iconText, @@ -600,6 +608,7 @@ class AndroidResearchType( var experience: Int = 0, var customName: Component? = null, var description: MutableList? = null, + var descriptionSuppliers: MutableList? = null, var itemIcon: Item? = null, var skinIcon: SkinElement? = null, var iconText: Component? = null, @@ -643,6 +652,17 @@ class AndroidResearchType( return this } + fun withDescription(range: IntRange): Builder { + val result = ArrayList() + + for (i in range) { + result.add(TranslatableComponent("android_research.${id.namespace}.${id.path}.description$i")) + } + + this.description = result + return this + } + fun withExperience(experience: Int): Builder { this.experience = experience return this @@ -653,11 +673,72 @@ class AndroidResearchType( return this } - fun withDescription(description: List): Builder { + fun withDescription(description: Collection): Builder { this.description = ArrayList(description.size).also { it.addAll(description) } return this } + fun appendDescription(range: IntRange): Builder { + val result = this.description ?: ArrayList() + + for (i in range) { + result.add(TranslatableComponent("android_research.${id.namespace}.${id.path}.description$i")) + } + + this.description = result + return this + } + + fun appendDescription(description: Component): Builder { + this.description = (this.description ?: mutableListOf()).also { it.add(description) } + return this + } + + fun appendDescription(vararg description: Component): Builder { + this.description = (this.description ?: mutableListOf()).also { it.addAll(description) } + return this + } + + fun appendDescription(description: Collection): Builder { + this.description = (this.description ?: mutableListOf()).also { it.addAll(description) } + return this + } + + fun withDescription(vararg description: ComponentSupplier): Builder { + this.descriptionSuppliers = description.toMutableList() + return this + } + + fun withDescriptionSupplier(description: Collection): Builder { + this.descriptionSuppliers = ArrayList(description.size).also { it.addAll(description) } + return this + } + + fun appendDescription(description: ComponentSupplier): Builder { + this.descriptionSuppliers = (this.descriptionSuppliers ?: mutableListOf()).also { it.add(description) } + return this + } + + fun appendDescriptionSupplier(description: ComponentSupplier): Builder { + this.descriptionSuppliers = (this.descriptionSuppliers ?: mutableListOf()).also { it.add(description) } + return this + } + + fun appendDescription(vararg description: ComponentSupplier): Builder { + this.descriptionSuppliers = (this.descriptionSuppliers ?: mutableListOf()).also { it.addAll(description) } + return this + } + + fun appendDescriptionSupplier(vararg description: ComponentSupplier): Builder { + this.descriptionSuppliers = (this.descriptionSuppliers ?: mutableListOf()).also { it.addAll(description) } + return this + } + + fun appendDescriptionSupplier(description: Collection): Builder { + this.descriptionSuppliers = (this.descriptionSuppliers ?: mutableListOf()).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). @@ -686,8 +767,8 @@ class AndroidResearchType( id: ResourceLocation, level: Int = 0, rigid: Boolean = false, - transformersUp: Collection>> = listOf(), - transformersDown: Collection>> = listOf(), + transformersUp: Collection = listOf(), + transformersDown: Collection = listOf(), ): Builder { features.add(FeatureReference(id, level, rigid, transformersUp, transformersDown)) return this @@ -698,8 +779,8 @@ class AndroidResearchType( feature: AndroidFeatureType<*>, level: Int = 0, rigid: Boolean = true, - transformersUp: Collection>> = listOf(), - transformersDown: Collection>> = listOf(), + transformersUp: Collection = listOf(), + transformersDown: Collection = listOf(), ): Builder { features.add(FeatureReference(feature.registryName ?: throw NullPointerException("Feature $feature does not have registry name"), level, rigid, transformersUp, transformersDown)) return this @@ -708,8 +789,8 @@ class AndroidResearchType( fun addFeatureResult( id: ResourceLocation, rigid: Boolean = false, - transformersUp: Collection>> = listOf(), - transformersDown: Collection>> = listOf(), + transformersUp: Collection = listOf(), + transformersDown: Collection = listOf(), ): Builder { features.add(FeatureReference(id, 0, rigid, transformersUp, transformersDown)) return this @@ -733,6 +814,7 @@ class AndroidResearchType( items = items, features = features, descriptionLines = description ?: listOf(), + descriptionSuppliers = descriptionSuppliers ?: listOf(), experienceLevels = experience, customName = customName, skinIcon = skinIcon, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ItemMagnetFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ItemMagnetFeature.kt new file mode 100644 index 000000000..5948f289c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ItemMagnetFeature.kt @@ -0,0 +1,147 @@ +package ru.dbotthepony.mc.otm.android.feature + +import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.ChatFormatting +import net.minecraft.client.multiplayer.ClientLevel +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.item.ItemEntity +import net.minecraftforge.event.ForgeEventFactory +import net.minecraftforge.network.NetworkEvent +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.ServerConfig +import ru.dbotthepony.mc.otm.android.AndroidResearchManager +import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.capability.extractEnergyInner +import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.render.ResearchIcons +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.Vector +import ru.dbotthepony.mc.otm.core.formatPower +import ru.dbotthepony.mc.otm.core.formatSi +import ru.dbotthepony.mc.otm.core.getEntitiesInEllipsoid +import ru.dbotthepony.mc.otm.core.minus +import ru.dbotthepony.mc.otm.core.plus +import ru.dbotthepony.mc.otm.core.position +import ru.dbotthepony.mc.otm.core.times +import ru.dbotthepony.mc.otm.network.MatteryPacket +import ru.dbotthepony.mc.otm.network.WorldNetworkChannel +import ru.dbotthepony.mc.otm.network.packetHandled +import ru.dbotthepony.mc.otm.registry.AndroidFeatures +import ru.dbotthepony.mc.otm.registry.MNames +import java.util.UUID +import java.util.WeakHashMap +import java.util.function.Predicate +import java.util.function.Supplier + +private data class SharedItemEntityData(val owner: UUID? = null, val age: Int = 0, val lifespan: Int = 0, val hasPickupDelay: Boolean = true) { + companion object { + val EMPTY = SharedItemEntityData() + } +} + +private val datatable = WeakHashMap() + +class ItemEntityDataPacket(val itemUUID: Int, val owner: UUID? = null, val age: Int = 0, val lifespan: Int = 0, val hasPickupDelay: Boolean = true) : MatteryPacket { + override fun write(buff: FriendlyByteBuf) { + buff.writeVarInt(itemUUID) + buff.writeBoolean(owner != null) + if (owner != null) buff.writeUUID(owner) + buff.writeVarInt(age) + buff.writeVarInt(lifespan) + buff.writeBoolean(hasPickupDelay) + } + + override fun play(context: Supplier) { + context.packetHandled = true + val level = minecraft.player?.level as ClientLevel? ?: return + val entity = level.getEntity(itemUUID) as ItemEntity? ?: return + datatable[entity] = SharedItemEntityData(owner, age, lifespan, hasPickupDelay) + } + + companion object { + fun read(buff: FriendlyByteBuf): ItemEntityDataPacket { + return ItemEntityDataPacket(buff.readVarInt(), if (buff.readBoolean()) buff.readUUID() else null, buff.readVarInt(), buff.readVarInt(), buff.readBoolean()) + } + } +} + +class ItemMagnetFeature(capability: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.ITEM_MAGNET, capability) { + private data class ItemPos(var position: Vector, var ticksSinceActivity: Int) + private val rememberPositions = WeakHashMap() + + private val serverPredicate = Predicate { it is ItemEntity && !it.hasPickUpDelay() && (it.owner == null || it.owner != ply.uuid || it.lifespan - it.age <= 200) } + private val clientPredicate = Predicate { it is ItemEntity && (datatable[it] ?: SharedItemEntityData.EMPTY).let { !it.hasPickupDelay && (it.owner == null || it.owner != ply.uuid || it.lifespan - it.age <= 200) } } + + private fun doTick(server: Boolean) { + if (server && !android.androidEnergy.extractEnergyInnerExact(ServerConfig.AndroidItemMagnet.POWER_DRAW, true).isPositive) { + return + } + + val entities = ply.level.getEntitiesInEllipsoid( + ply.position, + Vector(ServerConfig.AndroidItemMagnet.RADIUS_HORIZONTAL, ServerConfig.AndroidItemMagnet.RADIUS_VERTICAL, ServerConfig.AndroidItemMagnet.RADIUS_HORIZONTAL), + if (server) Predicate { it is ItemEntity } else clientPredicate + ) + + for ((ent, distance) in entities) { + ent as ItemEntity + + if (server) { + WorldNetworkChannel.send(ply, ItemEntityDataPacket(ent.id, ent.owner, ent.age, ent.lifespan, ent.hasPickUpDelay())) + + if (!serverPredicate.test(ent)) { + continue + } + + val data = rememberPositions.computeIfAbsent(ent) { ItemPos(it.position, 0) } + + if (data.position.distanceToSqr(ent.position) < 1.0) { + data.ticksSinceActivity++ + } else { + if (!android.androidEnergy.extractEnergyInnerExact(ServerConfig.AndroidItemMagnet.POWER_DRAW, false).isPositive) { + return + } + + data.position = ent.position + data.ticksSinceActivity = 0 + } + } + + ent.deltaMovement += (ply.position - ent.position).normalize() * ((1.0 - distance) * 0.2) + } + } + + override fun tickClient() { + super.tickClient() + + if (isActive && android.androidEnergy.extractEnergyInnerExact(ServerConfig.AndroidItemMagnet.POWER_DRAW, true).isPositive) { + doTick(false) + } + } + + override fun tickServer() { + super.tickServer() + + if (isActive) { + doTick(true) + } + } + + override fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) { + ResearchIcons.ICON_ITEM_MAGNET.render(stack, x, y, width, height) + } + + companion object { + val POWER_COST_DESCRIPTION = + AndroidResearchManager.descriptionFunc(ResourceLocation( + OverdriveThatMatters.MOD_ID, MNames.ITEM_MAGNET), + "otm.gui.power_cost_per_tick", + { ServerConfig.AndroidItemMagnet.POWER_DRAW.formatPower().copy().withStyle(ChatFormatting.YELLOW) }) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmorFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmorFeature.kt index 250d68319..c2bbf16ef 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmorFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmorFeature.kt @@ -117,12 +117,5 @@ class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(An (second as NanobotsArmorFeature).speed = level - 1 } } - - /** - * Dummy method just for static initializer to execute - */ - fun register() { - - } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt index 5e090eb66..3f7ead165 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt @@ -2,16 +2,24 @@ package ru.dbotthepony.mc.otm.android.feature import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.ChatFormatting +import net.minecraft.resources.ResourceLocation import net.minecraft.world.entity.LivingEntity import net.minecraft.world.level.block.Block import net.minecraft.world.phys.AABB +import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.ServerConfig +import ru.dbotthepony.mc.otm.android.AndroidResearchManager 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.core.TextComponent import ru.dbotthepony.mc.otm.core.Vector +import ru.dbotthepony.mc.otm.core.formatPower +import ru.dbotthepony.mc.otm.core.formatSi import ru.dbotthepony.mc.otm.core.getEllipsoidBlockPositions +import ru.dbotthepony.mc.otm.core.getEntitiesInEllipsoid import ru.dbotthepony.mc.otm.core.getExplosionResistance import ru.dbotthepony.mc.otm.core.minus import ru.dbotthepony.mc.otm.core.plus @@ -21,6 +29,7 @@ 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.MNames import ru.dbotthepony.mc.otm.registry.ShockwaveDamageSource import kotlin.math.pow import kotlin.math.roundToInt @@ -49,45 +58,21 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF cooldown = ServerConfig.Shockwave.COOLDOWN // TODO: raycasting - val entities = ply.level.getEntities(ply, AABB( - ply.position.x - ServerConfig.Shockwave.RADIUS_HORIZONTAL, - ply.position.y - ServerConfig.Shockwave.RADIUS_VERTICAL, - ply.position.z - ServerConfig.Shockwave.RADIUS_HORIZONTAL, - - ply.position.x + ServerConfig.Shockwave.RADIUS_HORIZONTAL, - ply.position.y + ServerConfig.Shockwave.RADIUS_VERTICAL, - ply.position.z + ServerConfig.Shockwave.RADIUS_HORIZONTAL, - )) { (it !is LivingEntity || !it.isSpectator && it.isAlive) && ((it.position - ply.position).let { vec -> - vec.x.pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) + - vec.y.pow(2.0) / ServerConfig.Shockwave.RADIUS_VERTICAL.pow(2.0) + - vec.z.pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) <= 1.0 - }) || it.boundingBox.center.let { vec -> - (vec.x - ply.position.x).pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) + - (vec.y - ply.position.y).pow(2.0) / ServerConfig.Shockwave.RADIUS_VERTICAL.pow(2.0) + - (vec.z - ply.position.z).pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) <= 1.0 - } } - - for (entity in entities) { - val diff = entity.position - ply.position - - val distanceMultiplier = diff.let { - it.x.pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) + - it.y.pow(2.0) / ServerConfig.Shockwave.RADIUS_VERTICAL.pow(2.0) + - it.z.pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) - }.coerceAtMost(entity.boundingBox.center.let { vec -> - (vec.x - ply.position.x).pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) + - (vec.y - ply.position.y).pow(2.0) / ServerConfig.Shockwave.RADIUS_VERTICAL.pow(2.0) + - (vec.z - ply.position.z).pow(2.0) / ServerConfig.Shockwave.RADIUS_HORIZONTAL.pow(2.0) - }) + val entities = ply.level.getEntitiesInEllipsoid( + ply.position, + Vector(ServerConfig.Shockwave.RADIUS_HORIZONTAL, ServerConfig.Shockwave.RADIUS_VERTICAL, ServerConfig.Shockwave.RADIUS_HORIZONTAL), + except = ply, + ) { (it !is LivingEntity || !it.isSpectator && it.isAlive) } + for ((entity, distanceMultiplier) in entities) { val multiplier = (1.0 - distanceMultiplier).pow(1.25) // don't hurt items, arrows, etc etc if (entity is LivingEntity) { entity.hurt(ShockwaveDamageSource(ply), multiplier.toFloat() * ServerConfig.Shockwave.DAMAGE.toFloat()) - entity.deltaMovement += diff.normalize() * (multiplier * 3.0) + entity.deltaMovement += (entity.position - ply.position).normalize() * (multiplier * 3.0) } else { - entity.deltaMovement += diff.normalize() * (multiplier * 6.0) + entity.deltaMovement += (entity.position - ply.position).normalize() * (multiplier * 6.0) } } @@ -165,4 +150,12 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF RenderSystem.setShaderColor(1f, 1f, 1f, 1f) } } + + companion object { + val POWER_COST_DESCRIPTION = + AndroidResearchManager.descriptionFunc( + ResourceLocation(OverdriveThatMatters.MOD_ID, MNames.SHOCKWAVE), + "otm.gui.power_cost_per_use", + { ServerConfig.Shockwave.ENERGY_COST.formatPower().copy().withStyle(ChatFormatting.YELLOW) }) + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt index 3226e96c7..c6095676e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt @@ -14,7 +14,7 @@ object ResearchIcons { val ICON_JUMP_BOOST: SkinElement val ICON_FEATHER_FALLING: SkinElement - val ICON_ARC: SkinElement + val ICON_ITEM_MAGNET: SkinElement val ICON_ARROW: SkinElement val ICON_ARMOR: SkinElement val ICON_NANOBOTS: SkinElement @@ -43,7 +43,7 @@ object ResearchIcons { ICON_JUMP_BOOST = grid.next() ICON_FEATHER_FALLING = grid.next() - ICON_ARC = grid.next() + ICON_ITEM_MAGNET = grid.next() ICON_ARROW = grid.next() ICON_ARMOR = grid.next() ICON_NANOBOTS = grid.next() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/LevelExt.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/LevelExt.kt new file mode 100644 index 000000000..9bc570b21 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/LevelExt.kt @@ -0,0 +1,67 @@ +package ru.dbotthepony.mc.otm.core + +import net.minecraft.world.entity.Entity +import net.minecraft.world.level.Level +import net.minecraft.world.phys.AABB +import java.util.function.Predicate +import kotlin.math.pow + +/** + * Pair of entity and distance fraction to center of ellipsoid + */ +fun Level.getEntitiesInEllipsoid(pos: Vector, dimensions: Vector, except: Entity?, predicate: Predicate): List> { + val entities = getEntities(except, AABB( + pos.x - dimensions.x, + pos.y - dimensions.y, + pos.z - dimensions.z, + + pos.x + dimensions.x, + pos.y + dimensions.y, + pos.z + dimensions.z, + ), predicate) + + val result = ArrayList>() + + for (it in entities) { + val a = (it.position - pos).let { vec -> + vec.x.pow(2.0) / dimensions.x.pow(2.0) + + vec.y.pow(2.0) / dimensions.y.pow(2.0) + + vec.z.pow(2.0) / dimensions.z.pow(2.0) + } + + val b = it.boundingBox.center.let { vec -> + (vec.x - pos.x).pow(2.0) / dimensions.x.pow(2.0) + + (vec.y - pos.y).pow(2.0) / dimensions.y.pow(2.0) + + (vec.z - pos.z).pow(2.0) / dimensions.z.pow(2.0) + } + + val min = a.coerceAtMost(b) + + if (min <= 1.0) { + result.add(it to min) + } + } + + return result +} + +/** + * Pair of entity and distance fraction to center of ellipsoid + */ +fun Level.getEntitiesInEllipsoid(pos: Vector, dimensions: Vector, predicate: Predicate): List> { + return getEntitiesInEllipsoid(pos, dimensions, null, predicate) +} + +/** + * Pair of entity and distance fraction to center of ellipsoid + */ +fun Level.getEntitiesInSphere(pos: Vector, radius: Double, except: Entity?, predicate: Predicate): List> { + return getEntitiesInEllipsoid(pos, Vector(radius, radius, radius), except, predicate) +} + +/** + * Pair of entity and distance fraction to center of ellipsoid + */ +fun Level.getEntitiesInSphere(pos: Vector, radius: Double, predicate: Predicate): List> { + return getEntitiesInEllipsoid(pos, Vector(radius, radius, radius), null, predicate) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/SerializedFunctionRegistry.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/SerializedFunctionRegistry.kt index a75456aed..24f85d16e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/SerializedFunctionRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/SerializedFunctionRegistry.kt @@ -32,15 +32,15 @@ import java.util.Collections import java.util.LinkedList import java.util.function.Consumer -class SerializedFunctionRegistry(val gson: Gson = Gson()) : JsonSerializer>, JsonDeserializer>, TypeAdapter>() { - fun interface AnonymousFunction { - fun invoke(receiver: R, arguments: List) +class SerializedFunctionRegistry(val gson: Gson = Gson()) : JsonSerializer>, JsonDeserializer>, TypeAdapter>() { + fun interface AnonymousFunction { + fun invoke(receiver: R, arguments: List): T } - data class BoundFunction( - val function: Function, + data class BoundFunction( + val function: Function, val arguments: List - ) : Consumer { + ) : java.util.function.Function { fun toJson(): JsonElement { return JsonObject().also { it["function"] = function.toJson() @@ -67,22 +67,22 @@ class SerializedFunctionRegistry(val gson: Gson = Gson()) : JsonSerializer( + data class Function( val id: ResourceLocation, - val body: AnonymousFunction, - val registry: SerializedFunctionRegistry + val body: AnonymousFunction, + val registry: SerializedFunctionRegistry ) { - fun bind(vararg arguments: Any?): BoundFunction { + fun bind(vararg arguments: Any?): BoundFunction { validate(arguments.iterator().withIndex()) return BoundFunction(this, Collections.unmodifiableList(LinkedList().also { it.addAll(arguments) })) } - fun bind(arguments: List): BoundFunction { + fun bind(arguments: List): BoundFunction { validate(arguments.iterator().withIndex()) return BoundFunction(this, Collections.unmodifiableList(LinkedList().also { it.addAll(arguments) })) } @@ -114,15 +114,22 @@ class SerializedFunctionRegistry(val gson: Gson = Gson()) : JsonSerializer>() + private val map = HashMap>() - fun register(id: ResourceLocation, function: AnonymousFunction): Function { + fun register(id: ResourceLocation, function: AnonymousFunction): Function { synchronized(map) { return map.computeIfAbsent(id) { Function(id, function, this) } } } - inline fun register(id: ResourceLocation, noinline function: R.(A) -> Unit): Function { + fun register(id: ResourceLocation, function: R.() -> T): Function { + return register(id, AnonymousFunction { r, args -> + check(args.isEmpty()) { "Invalid amount of arguments. No arguments are required" } + function.invoke(r) + }) + } + + inline fun register(id: ResourceLocation, noinline function: R.(A) -> T): Function { return register(id, AnonymousFunction { r, args -> check(args.size == 1) { "Invalid amount of arguments. 1 is required" } function.invoke( @@ -132,7 +139,7 @@ class SerializedFunctionRegistry(val gson: Gson = Gson()) : JsonSerializer register(id: ResourceLocation, noinline function: R.(A, B) -> Unit): Function { + inline fun register(id: ResourceLocation, noinline function: R.(A, B) -> T): Function { return register(id, AnonymousFunction { r, args -> check(args.size == 2) { "Invalid amount of arguments. 2 is required" } @@ -144,7 +151,7 @@ class SerializedFunctionRegistry(val gson: Gson = Gson()) : JsonSerializer register(id: ResourceLocation, noinline function: R.(A, B, C) -> Unit): Function { + inline fun register(id: ResourceLocation, noinline function: R.(A, B, C) -> T): Function { return register(id, AnonymousFunction { r, args -> check(args.size == 3) { "Invalid amount of arguments. 3 is required" } @@ -157,13 +164,13 @@ class SerializedFunctionRegistry(val gson: Gson = Gson()) : JsonSerializer? { + fun get(id: ResourceLocation): Function? { synchronized(map) { return map[id] } } - fun fromNetwork(buff: FriendlyByteBuf): BoundFunction? { + fun fromNetwork(buff: FriendlyByteBuf): BoundFunction? { val id = ResourceLocation(buff.readUtf()) val stream = DataInputStream(ByteBufInputStream(buff)) @@ -176,7 +183,7 @@ class SerializedFunctionRegistry(val gson: Gson = Gson()) : JsonSerializer? { + fun fromJson(value: JsonElement): BoundFunction? { if (value !is JsonObject) { return null } @@ -193,7 +200,7 @@ class SerializedFunctionRegistry(val gson: Gson = Gson()) : JsonSerializer, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + override fun serialize(src: BoundFunction, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { return src.toJson() } @@ -201,15 +208,15 @@ class SerializedFunctionRegistry(val gson: Gson = Gson()) : JsonSerializer { + ): BoundFunction { return fromJson(json) ?: throw JsonSyntaxException("Function is invalid") } - override fun write(out: JsonWriter, value: BoundFunction) { + override fun write(out: JsonWriter, value: BoundFunction) { TypeAdapters.JSON_ELEMENT.write(out, value.toJson()) } - override fun read(`in`: JsonReader): BoundFunction { + override fun read(`in`: JsonReader): BoundFunction { return fromJson(TypeAdapters.JSON_ELEMENT.read(`in`)) ?: throw JsonSyntaxException("Function is invalid") } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/WorldNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/WorldNetworkChannel.kt index 242eab3fb..d7fb296c9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/WorldNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/WorldNetworkChannel.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.mc.otm.network import net.minecraftforge.network.NetworkDirection +import ru.dbotthepony.mc.otm.android.feature.ItemEntityDataPacket import ru.dbotthepony.mc.otm.block.entity.EnergyCounterPacket object WorldNetworkChannel : MatteryNetworkChannel( @@ -9,5 +10,6 @@ object WorldNetworkChannel : MatteryNetworkChannel( ) { fun register() { add(EnergyCounterPacket::class.java, EnergyCounterPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) + add(ItemEntityDataPacket::class.java, ItemEntityDataPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt index 00a415a32..f2ce9434b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt @@ -19,6 +19,7 @@ object AndroidFeatures { val EXTENDED_REACH: AndroidFeatureType<*> by registry.register(MNames.EXTENDED_REACH) { AndroidFeatureType(::ExtendedReachFeature) } val NIGHT_VISION: AndroidFeatureType<*> by registry.register(MNames.NIGHT_VISION) { AndroidFeatureType(::NightVisionFeature) } val SHOCKWAVE: AndroidFeatureType<*> by registry.register(MNames.SHOCKWAVE) { AndroidFeatureType(::ShockwaveFeature) } + val ITEM_MAGNET: AndroidFeatureType<*> by registry.register(MNames.ITEM_MAGNET) { AndroidFeatureType(::ItemMagnetFeature) } internal fun register(bus: IEventBus) { registry.register(bus) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt index 3180a46ad..2f2b1f964 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt @@ -209,6 +209,7 @@ object MNames { const val EXTENDED_REACH = "extended_reach" const val NIGHT_VISION = "night_vision" const val SHOCKWAVE = "shockwave" + const val ITEM_MAGNET = "item_magnet" const val IMPROVED_LIMBS = "improved_limbs" // stats diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt index c052e372b..a45dc45e3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt @@ -24,6 +24,9 @@ import net.minecraftforge.registries.RegistryBuilder import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.android.AndroidFeatureType import ru.dbotthepony.mc.otm.android.AndroidResearchType +import ru.dbotthepony.mc.otm.android.feature.ItemMagnetFeature +import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature +import ru.dbotthepony.mc.otm.android.feature.ShockwaveFeature import ru.dbotthepony.mc.otm.block.CargoCrateBlock import ru.dbotthepony.mc.otm.registry.objects.ColoredDecorativeBlock import ru.dbotthepony.mc.otm.registry.objects.CrateProperties @@ -230,5 +233,10 @@ object MRegistry { LootModifiers.register(bus) MRecipes.register(bus) + + // call static constructors + NanobotsArmorFeature.Companion + ShockwaveFeature.Companion + ItemMagnetFeature.Companion } }