Item Magnet for androids

This commit is contained in:
DBotThePony 2022-09-25 15:26:45 +07:00
parent c7dad0a48c
commit b89d59358c
Signed by: DBot
GPG Key ID: DCC23B5715498507
18 changed files with 475 additions and 87 deletions

View File

@ -2,7 +2,9 @@ package ru.dbotthepony.mc.otm.datagen
import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.android.AndroidResearchType 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.NanobotsArmorFeature
import ru.dbotthepony.mc.otm.android.feature.ShockwaveFeature
import ru.dbotthepony.mc.otm.client.render.ResearchIcons import ru.dbotthepony.mc.otm.client.render.ResearchIcons
import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
@ -221,12 +223,24 @@ fun addResearchData(serializer: Consumer<AndroidResearchType>, lang: MatteryLang
AndroidResearchType.Builder(modLocation(MNames.SHOCKWAVE)) AndroidResearchType.Builder(modLocation(MNames.SHOCKWAVE))
.withExperience(40) .withExperience(40)
.withDescription() .withDescription()
.appendDescription(ShockwaveFeature.POWER_COST_DESCRIPTION)
.withIcon(ResearchIcons.ICON_SHOCKWAVE) .withIcon(ResearchIcons.ICON_SHOCKWAVE)
.addFeatureResult(AndroidFeatures.SHOCKWAVE) .addFeatureResult(AndroidFeatures.SHOCKWAVE)
.addPrerequisite(attackBoostList[2]) .addPrerequisite(attackBoostList[2])
.build() .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(SHOCKWAVE)
serializer.accept(ITEM_MAGNET)
with(lang.english) { with(lang.english) {
add(limbList[0], "Limb Overclocking %s") add(limbList[0], "Limb Overclocking %s")
@ -259,6 +273,13 @@ fun addResearchData(serializer: Consumer<AndroidResearchType>, lang: MatteryLang
add(STEP_ASSIST, "Step Assist") add(STEP_ASSIST, "Step Assist")
add(STEP_ASSIST, "description", "Allows unit to step up whole blocks") 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], "Attack Boost %s")
add(attackBoostList[0], "description", "Increases total melee attack strength by %s%%") add(attackBoostList[0], "description", "Increases total melee attack strength by %s%%")
} }

View File

@ -51,6 +51,9 @@ private fun sounds(provider: MatteryLanguageProvider) {
private fun misc(provider: MatteryLanguageProvider) { private fun misc(provider: MatteryLanguageProvider) {
with(provider.english) { 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("cancel", "Cancel")
gui("confirm", "Confirm") gui("confirm", "Confirm")
@ -464,6 +467,7 @@ private fun androidFeatures(provider: MatteryLanguageProvider) {
add(AndroidFeatures.SHOCKWAVE, "Shockwave") add(AndroidFeatures.SHOCKWAVE, "Shockwave")
add(AndroidFeatures.NIGHT_VISION, "Night Vision") add(AndroidFeatures.NIGHT_VISION, "Night Vision")
add(AndroidFeatures.NANOBOTS_ARMOR, "Nanobots Armor") add(AndroidFeatures.NANOBOTS_ARMOR, "Nanobots Armor")
add(AndroidFeatures.ITEM_MAGNET, "Item Magnet")
} }
} }

View File

@ -100,8 +100,6 @@ public final class OverdriveThatMatters {
ClientConfig.INSTANCE.register(); ClientConfig.INSTANCE.register();
ServerConfig.INSTANCE.register(); ServerConfig.INSTANCE.register();
NanobotsArmorFeature.Companion.register();
} }
private void setup(final FMLCommonSetupEvent event) { private void setup(final FMLCommonSetupEvent event) {

View File

@ -141,6 +141,20 @@ object ServerConfig {
val NIGHT_VISION_POWER_DRAW by specBuilder.defineImpreciseFraction("nightVisionPowerDraw", ImpreciseFraction(8), ImpreciseFraction.ZERO) 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 { object Shockwave {
init { init {
specBuilder.comment("Shockwave ability").push("shockwave") specBuilder.comment("Shockwave ability").push("shockwave")
@ -162,6 +176,7 @@ object ServerConfig {
init { init {
// access shockwave class so spec is built // access shockwave class so spec is built
AndroidItemMagnet
Shockwave Shockwave
specBuilder.pop() specBuilder.pop()

View File

@ -65,7 +65,7 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
get.level = level.level get.level = level.level
for (transformer in type.features.first { it.id == feature.feature.registryName }.transformersDown) { 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) { 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) { } catch(err: Throwable) {

View File

@ -32,6 +32,8 @@ open class AndroidResearchDataProvider(protected val dataGenerator: DataGenerato
} }
final override fun run(output: CachedOutput) { final override fun run(output: CachedOutput) {
AndroidResearchManager.fireRegistrationEvent()
val set = ObjectArraySet<ResourceLocation>() val set = ObjectArraySet<ResourceLocation>()
val added = LinkedList<AndroidResearchType>() val added = LinkedList<AndroidResearchType>()

View File

@ -6,21 +6,24 @@ import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import net.minecraft.client.server.IntegratedServer import net.minecraft.client.server.IntegratedServer
import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.ResourceManager import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener
import net.minecraft.util.profiling.ProfilerFiller import net.minecraft.util.profiling.ProfilerFiller
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.AddReloadListenerEvent import net.minecraftforge.event.AddReloadListenerEvent
import net.minecraftforge.event.OnDatapackSyncEvent import net.minecraftforge.event.OnDatapackSyncEvent
import net.minecraftforge.eventbus.api.Event
import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.NetworkEvent
import net.minecraftforge.network.PacketDistributor import net.minecraftforge.network.PacketDistributor
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.MINECRAFT_SERVER import ru.dbotthepony.mc.otm.MINECRAFT_SERVER
import ru.dbotthepony.mc.otm.NULLABLE_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.SERVER_IS_LIVE
import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.minecraft 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.data.SerializedFunctionRegistry
import ru.dbotthepony.mc.otm.network.MatteryPacket import ru.dbotthepony.mc.otm.network.MatteryPacket
import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel
@ -30,6 +33,9 @@ import ru.dbotthepony.mc.otm.onceServer
import java.util.LinkedList import java.util.LinkedList
import java.util.function.Supplier import java.util.function.Supplier
typealias AndroidResultTransformer = SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>, Unit>
typealias ComponentSupplier = SerializedFunctionRegistry.BoundFunction<Unit, Component>
object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_android_research"), Iterable<AndroidResearchType> { object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_android_research"), Iterable<AndroidResearchType> {
/** /**
* Feel free to register functions inside this thing from anywhere in your code * 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. * Just make sure client and server has the same set of functions defined.
*/ */
val featureResultTransformers = SerializedFunctionRegistry<Pair<AndroidResearch, AndroidFeature>>() val featureResultTransformers = SerializedFunctionRegistry<Pair<AndroidResearch, AndroidFeature>, 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<Unit, Component>()
fun descriptionFunc(name: ResourceLocation, base: String, vararg argument: Supplier<Any>): 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<Any>): ComponentSupplier = AndroidResearchManager.descriptionFunc(name, base, *argument)
}
const val DIRECTORY = "otm_android_research" const val DIRECTORY = "otm_android_research"
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
@ -62,11 +97,23 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s
var researchMap: Map<ResourceLocation, AndroidResearchType> = mapOf() var researchMap: Map<ResourceLocation, AndroidResearchType> = mapOf()
private set private set
internal fun fireRegistrationEvent() {
if (!firedRegistrationEvent) {
try {
MinecraftForge.EVENT_BUS.post(RegisterFuncsEvent)
} finally {
firedRegistrationEvent = true
}
}
}
override fun apply( override fun apply(
jsonElementMap: Map<ResourceLocation, JsonElement>, jsonElementMap: Map<ResourceLocation, JsonElement>,
manager: ResourceManager, manager: ResourceManager,
profiler: ProfilerFiller profiler: ProfilerFiller
) { ) {
fireRegistrationEvent()
val builder = ImmutableMap.builder<ResourceLocation, AndroidResearchType>() val builder = ImmutableMap.builder<ResourceLocation, AndroidResearchType>()
for ((k, v) in jsonElementMap) { for ((k, v) in jsonElementMap) {

View File

@ -1,6 +1,7 @@
package ru.dbotthepony.mc.otm.android package ru.dbotthepony.mc.otm.android
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.common.collect.Streams
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonObject 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.TranslatableComponent
import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.core.set 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.core.toImmutableList
import ru.dbotthepony.mc.otm.data.ItemStackCodec 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.data.stream
import ru.dbotthepony.mc.otm.registry.MRegistry import ru.dbotthepony.mc.otm.registry.MRegistry
import java.util.ArrayList
import java.util.LinkedList import java.util.LinkedList
import java.util.stream.Stream import java.util.stream.Stream
import kotlin.collections.ArrayList
import kotlin.collections.HashSet import kotlin.collections.HashSet
private fun findPrerequisites( private fun findPrerequisites(
@ -81,6 +80,7 @@ class AndroidResearchType(
features: Collection<FeatureReference>, features: Collection<FeatureReference>,
descriptionLines: Collection<Component>, descriptionLines: Collection<Component>,
descriptionSuppliers: Collection<ComponentSupplier> = listOf(),
val experienceLevels: Int = 0, val experienceLevels: Int = 0,
private val customName: Component? = null, private val customName: Component? = null,
@ -137,8 +137,8 @@ class AndroidResearchType(
val id: ResourceLocation, val id: ResourceLocation,
val level: Int = 0, val level: Int = 0,
val isRigid: Boolean, val isRigid: Boolean,
val transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(), val transformersUp: Collection<AndroidResultTransformer> = listOf(),
val transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(), val transformersDown: Collection<AndroidResultTransformer> = listOf(),
) { ) {
fun toJson(): JsonObject { fun toJson(): JsonObject {
return JsonObject().also { return JsonObject().also {
@ -186,8 +186,8 @@ class AndroidResearchType(
ResourceLocation((value["id"] as? JsonPrimitive ?: throw JsonSyntaxException("Invalid `id` value")).asString), ResourceLocation((value["id"] as? JsonPrimitive ?: throw JsonSyntaxException("Invalid `id` value")).asString),
(value["level"] as JsonPrimitive?)?.asInt ?: 0, (value["level"] as JsonPrimitive?)?.asInt ?: 0,
(value["is_rigid"] as JsonPrimitive?)?.asBoolean ?: true, (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<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>>()), (value["functions_up"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList<AndroidResultTransformer>()),
(value["functions_down"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>>()), (value["functions_down"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList<AndroidResultTransformer>()),
) )
} else { } else {
throw JsonSyntaxException("Unknown element type ${value::class.qualifiedName}") throw JsonSyntaxException("Unknown element type ${value::class.qualifiedName}")
@ -258,10 +258,12 @@ class AndroidResearchType(
private val descriptionLines: List<MutableComponent> = ImmutableList.copyOf(descriptionLines.map { it.copy() }) private val descriptionLines: List<MutableComponent> = ImmutableList.copyOf(descriptionLines.map { it.copy() })
private val descriptionSuppliers: List<ComponentSupplier> = ImmutableList.copyOf(descriptionSuppliers)
/** /**
* Stream containing copies of original [Component]s in list * Stream containing copies of original [Component]s in list
*/ */
val description: Stream<out Component> get() = descriptionLines.stream().map { it.copy() } val description: Stream<out Component> get() = Streams.concat(descriptionLines.stream().map { it.copy() }, descriptionSuppliers.stream().map { it.apply(Unit) })
/** /**
* Flat list of research preceding this research. * 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["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["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"] = 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) it["experience"] = JsonPrimitive(experienceLevels)
if (skinIcon != null) { if (skinIcon != null) {
@ -498,6 +501,7 @@ class AndroidResearchType(
buff.writeCollection(itemCollection) { a, b -> a.writeItem(b) } buff.writeCollection(itemCollection) { a, b -> a.writeItem(b) }
buff.writeCollection(features) { a, b -> b.toNetwork(a) } buff.writeCollection(features) { a, b -> b.toNetwork(a) }
buff.writeCollection(descriptionLines) { a, b -> a.writeComponent(b) } buff.writeCollection(descriptionLines) { a, b -> a.writeComponent(b) }
buff.writeCollection(descriptionSuppliers) { a, b -> b.toNetwork(a) }
buff.writeVarInt(experienceLevels) buff.writeVarInt(experienceLevels)
buff.writeBoolean(customName != null) buff.writeBoolean(customName != null)
@ -521,6 +525,7 @@ class AndroidResearchType(
val items = buff.readCollection({ LinkedList() }, FriendlyByteBuf::readItem) val items = buff.readCollection({ LinkedList() }, FriendlyByteBuf::readItem)
val features = buff.readCollection({ LinkedList() }, FeatureReference::fromNetwork) val features = buff.readCollection({ LinkedList() }, FeatureReference::fromNetwork)
val descriptionLines = buff.readCollection({ LinkedList() }, FriendlyByteBuf::readComponent) val descriptionLines = buff.readCollection({ LinkedList() }, FriendlyByteBuf::readComponent)
val descriptionSuppliers = buff.readCollection({ LinkedList() }, { AndroidResearchManager.descriptionFuncs.fromNetwork(it) })
val experienceLevels = buff.readVarInt() val experienceLevels = buff.readVarInt()
val customName = if (buff.readBoolean()) { val customName = if (buff.readBoolean()) {
@ -554,6 +559,7 @@ class AndroidResearchType(
items = items, items = items,
features = features, features = features,
descriptionLines = descriptionLines, descriptionLines = descriptionLines,
descriptionSuppliers = descriptionSuppliers.filterNotNull(),
experienceLevels = experienceLevels, experienceLevels = experienceLevels,
customName = customName, customName = customName,
iconText = iconTextValue, iconText = iconTextValue,
@ -572,6 +578,7 @@ class AndroidResearchType(
val items = value["required_items"] as JsonArray? ?: JsonArray() val items = value["required_items"] as JsonArray? ?: JsonArray()
val features = value["feature_result"] as JsonArray? ?: JsonArray() val features = value["feature_result"] as JsonArray? ?: JsonArray()
val description = value["description"] 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 experience = value["experience"]?.asInt ?: 0
val customName = value["custom_name"]?.let(Component.Serializer::fromJson) val customName = value["custom_name"]?.let(Component.Serializer::fromJson)
val iconText = value["icon_text"]?.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(), features = features.stream().map { FeatureReference.fromJson(it) }.toList(),
items = items.stream().map { ItemStackCodec.deserialize(it) }.filter { !it.isEmpty }.toList(), items = items.stream().map { ItemStackCodec.deserialize(it) }.filter { !it.isEmpty }.toList(),
descriptionLines = description.stream().map { Component.Serializer.fromJson(it) }.toList() as List<MutableComponent>, descriptionLines = description.stream().map { Component.Serializer.fromJson(it) }.toList() as List<MutableComponent>,
descriptionSuppliers = description_funcs.stream().map { AndroidResearchManager.descriptionFuncs.fromJson(it) }.toList() as List<ComponentSupplier>,
experienceLevels = experience, experienceLevels = experience,
customName = customName, customName = customName,
iconText = iconText, iconText = iconText,
@ -600,6 +608,7 @@ class AndroidResearchType(
var experience: Int = 0, var experience: Int = 0,
var customName: Component? = null, var customName: Component? = null,
var description: MutableList<Component>? = null, var description: MutableList<Component>? = null,
var descriptionSuppliers: MutableList<ComponentSupplier>? = null,
var itemIcon: Item? = null, var itemIcon: Item? = null,
var skinIcon: SkinElement? = null, var skinIcon: SkinElement? = null,
var iconText: Component? = null, var iconText: Component? = null,
@ -643,6 +652,17 @@ class AndroidResearchType(
return this return this
} }
fun withDescription(range: IntRange): Builder {
val result = ArrayList<Component>()
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 { fun withExperience(experience: Int): Builder {
this.experience = experience this.experience = experience
return this return this
@ -653,11 +673,72 @@ class AndroidResearchType(
return this return this
} }
fun withDescription(description: List<Component>): Builder { fun withDescription(description: Collection<Component>): Builder {
this.description = ArrayList<Component>(description.size).also { it.addAll(description) } this.description = ArrayList<Component>(description.size).also { it.addAll(description) }
return this 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<Component>): 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<ComponentSupplier>): Builder {
this.descriptionSuppliers = ArrayList<ComponentSupplier>(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<ComponentSupplier>): 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 * Please avoid having multiple prerequisites as case with more than 1 prerequisite does not have proper
* research tree render logic (yet). * research tree render logic (yet).
@ -686,8 +767,8 @@ class AndroidResearchType(
id: ResourceLocation, id: ResourceLocation,
level: Int = 0, level: Int = 0,
rigid: Boolean = false, rigid: Boolean = false,
transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(), transformersUp: Collection<AndroidResultTransformer> = listOf(),
transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(), transformersDown: Collection<AndroidResultTransformer> = listOf(),
): Builder { ): Builder {
features.add(FeatureReference(id, level, rigid, transformersUp, transformersDown)) features.add(FeatureReference(id, level, rigid, transformersUp, transformersDown))
return this return this
@ -698,8 +779,8 @@ class AndroidResearchType(
feature: AndroidFeatureType<*>, feature: AndroidFeatureType<*>,
level: Int = 0, level: Int = 0,
rigid: Boolean = true, rigid: Boolean = true,
transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(), transformersUp: Collection<AndroidResultTransformer> = listOf(),
transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(), transformersDown: Collection<AndroidResultTransformer> = listOf(),
): Builder { ): Builder {
features.add(FeatureReference(feature.registryName ?: throw NullPointerException("Feature $feature does not have registry name"), level, rigid, transformersUp, transformersDown)) features.add(FeatureReference(feature.registryName ?: throw NullPointerException("Feature $feature does not have registry name"), level, rigid, transformersUp, transformersDown))
return this return this
@ -708,8 +789,8 @@ class AndroidResearchType(
fun addFeatureResult( fun addFeatureResult(
id: ResourceLocation, id: ResourceLocation,
rigid: Boolean = false, rigid: Boolean = false,
transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(), transformersUp: Collection<AndroidResultTransformer> = listOf(),
transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(), transformersDown: Collection<AndroidResultTransformer> = listOf(),
): Builder { ): Builder {
features.add(FeatureReference(id, 0, rigid, transformersUp, transformersDown)) features.add(FeatureReference(id, 0, rigid, transformersUp, transformersDown))
return this return this
@ -733,6 +814,7 @@ class AndroidResearchType(
items = items, items = items,
features = features, features = features,
descriptionLines = description ?: listOf(), descriptionLines = description ?: listOf(),
descriptionSuppliers = descriptionSuppliers ?: listOf(),
experienceLevels = experience, experienceLevels = experience,
customName = customName, customName = customName,
skinIcon = skinIcon, skinIcon = skinIcon,

View File

@ -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<ItemEntity, SharedItemEntityData>()
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<NetworkEvent.Context>) {
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<ItemEntity, ItemPos>()
private val serverPredicate = Predicate<Entity> { it is ItemEntity && !it.hasPickUpDelay() && (it.owner == null || it.owner != ply.uuid || it.lifespan - it.age <= 200) }
private val clientPredicate = Predicate<Entity> { 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<Entity> { 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) })
}
}

View File

@ -117,12 +117,5 @@ class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(An
(second as NanobotsArmorFeature).speed = level - 1 (second as NanobotsArmorFeature).speed = level - 1
} }
} }
/**
* Dummy method just for static initializer to execute
*/
fun register() {
}
} }
} }

View File

@ -2,16 +2,24 @@ package ru.dbotthepony.mc.otm.android.feature
import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.PoseStack 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.entity.LivingEntity
import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Block
import net.minecraft.world.phys.AABB import net.minecraft.world.phys.AABB
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.ServerConfig 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.android.AndroidSwitchableFeature
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact
import ru.dbotthepony.mc.otm.client.render.ResearchIcons 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.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.getEllipsoidBlockPositions
import ru.dbotthepony.mc.otm.core.getEntitiesInEllipsoid
import ru.dbotthepony.mc.otm.core.getExplosionResistance import ru.dbotthepony.mc.otm.core.getExplosionResistance
import ru.dbotthepony.mc.otm.core.minus import ru.dbotthepony.mc.otm.core.minus
import ru.dbotthepony.mc.otm.core.plus 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.MatteryPlayerNetworkChannel
import ru.dbotthepony.mc.otm.network.TriggerShockwavePacket import ru.dbotthepony.mc.otm.network.TriggerShockwavePacket
import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.AndroidFeatures
import ru.dbotthepony.mc.otm.registry.MNames
import ru.dbotthepony.mc.otm.registry.ShockwaveDamageSource import ru.dbotthepony.mc.otm.registry.ShockwaveDamageSource
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -49,45 +58,21 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF
cooldown = ServerConfig.Shockwave.COOLDOWN cooldown = ServerConfig.Shockwave.COOLDOWN
// TODO: raycasting // TODO: raycasting
val entities = ply.level.getEntities(ply, AABB( val entities = ply.level.getEntitiesInEllipsoid(
ply.position.x - ServerConfig.Shockwave.RADIUS_HORIZONTAL, ply.position,
ply.position.y - ServerConfig.Shockwave.RADIUS_VERTICAL, Vector(ServerConfig.Shockwave.RADIUS_HORIZONTAL, ServerConfig.Shockwave.RADIUS_VERTICAL, ServerConfig.Shockwave.RADIUS_HORIZONTAL),
ply.position.z - ServerConfig.Shockwave.RADIUS_HORIZONTAL, except = ply,
) { (it !is LivingEntity || !it.isSpectator && it.isAlive) }
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)
})
for ((entity, distanceMultiplier) in entities) {
val multiplier = (1.0 - distanceMultiplier).pow(1.25) val multiplier = (1.0 - distanceMultiplier).pow(1.25)
// don't hurt items, arrows, etc etc // don't hurt items, arrows, etc etc
if (entity is LivingEntity) { if (entity is LivingEntity) {
entity.hurt(ShockwaveDamageSource(ply), multiplier.toFloat() * ServerConfig.Shockwave.DAMAGE.toFloat()) 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 { } 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) 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) })
}
} }

View File

@ -14,7 +14,7 @@ object ResearchIcons {
val ICON_JUMP_BOOST: SkinElement val ICON_JUMP_BOOST: SkinElement
val ICON_FEATHER_FALLING: SkinElement val ICON_FEATHER_FALLING: SkinElement
val ICON_ARC: SkinElement val ICON_ITEM_MAGNET: SkinElement
val ICON_ARROW: SkinElement val ICON_ARROW: SkinElement
val ICON_ARMOR: SkinElement val ICON_ARMOR: SkinElement
val ICON_NANOBOTS: SkinElement val ICON_NANOBOTS: SkinElement
@ -43,7 +43,7 @@ object ResearchIcons {
ICON_JUMP_BOOST = grid.next() ICON_JUMP_BOOST = grid.next()
ICON_FEATHER_FALLING = grid.next() ICON_FEATHER_FALLING = grid.next()
ICON_ARC = grid.next() ICON_ITEM_MAGNET = grid.next()
ICON_ARROW = grid.next() ICON_ARROW = grid.next()
ICON_ARMOR = grid.next() ICON_ARMOR = grid.next()
ICON_NANOBOTS = grid.next() ICON_NANOBOTS = grid.next()

View File

@ -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<in Entity>): List<Pair<Entity, Double>> {
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<Pair<Entity, Double>>()
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<in Entity>): List<Pair<Entity, Double>> {
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<in Entity>): List<Pair<Entity, Double>> {
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<in Entity>): List<Pair<Entity, Double>> {
return getEntitiesInEllipsoid(pos, Vector(radius, radius, radius), null, predicate)
}

View File

@ -32,15 +32,15 @@ import java.util.Collections
import java.util.LinkedList import java.util.LinkedList
import java.util.function.Consumer import java.util.function.Consumer
class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<SerializedFunctionRegistry.BoundFunction<R>>, JsonDeserializer<SerializedFunctionRegistry.BoundFunction<R>>, TypeAdapter<SerializedFunctionRegistry.BoundFunction<R>>() { class SerializedFunctionRegistry<R, T>(val gson: Gson = Gson()) : JsonSerializer<SerializedFunctionRegistry.BoundFunction<R, T>>, JsonDeserializer<SerializedFunctionRegistry.BoundFunction<R, T>>, TypeAdapter<SerializedFunctionRegistry.BoundFunction<R, T>>() {
fun interface AnonymousFunction<R> { fun interface AnonymousFunction<R, T> {
fun invoke(receiver: R, arguments: List<Any?>) fun invoke(receiver: R, arguments: List<Any?>): T
} }
data class BoundFunction<R>( data class BoundFunction<R, T>(
val function: Function<R>, val function: Function<R, T>,
val arguments: List<Any?> val arguments: List<Any?>
) : Consumer<R> { ) : java.util.function.Function<R, T> {
fun toJson(): JsonElement { fun toJson(): JsonElement {
return JsonObject().also { return JsonObject().also {
it["function"] = function.toJson() it["function"] = function.toJson()
@ -67,22 +67,22 @@ class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<Se
} }
} }
override fun accept(t: R) { override fun apply(t: R): T {
function.body.invoke(t, arguments) return function.body.invoke(t, arguments)
} }
} }
data class Function<R>( data class Function<R, T>(
val id: ResourceLocation, val id: ResourceLocation,
val body: AnonymousFunction<R>, val body: AnonymousFunction<R, T>,
val registry: SerializedFunctionRegistry<R> val registry: SerializedFunctionRegistry<R, T>
) { ) {
fun bind(vararg arguments: Any?): BoundFunction<R> { fun bind(vararg arguments: Any?): BoundFunction<R, T> {
validate(arguments.iterator().withIndex()) validate(arguments.iterator().withIndex())
return BoundFunction(this, Collections.unmodifiableList(LinkedList<Any?>().also { it.addAll(arguments) })) return BoundFunction(this, Collections.unmodifiableList(LinkedList<Any?>().also { it.addAll(arguments) }))
} }
fun bind(arguments: List<Any?>): BoundFunction<R> { fun bind(arguments: List<Any?>): BoundFunction<R, T> {
validate(arguments.iterator().withIndex()) validate(arguments.iterator().withIndex())
return BoundFunction(this, Collections.unmodifiableList(LinkedList<Any?>().also { it.addAll(arguments) })) return BoundFunction(this, Collections.unmodifiableList(LinkedList<Any?>().also { it.addAll(arguments) }))
} }
@ -114,15 +114,22 @@ class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<Se
} }
} }
private val map = HashMap<ResourceLocation, Function<R>>() private val map = HashMap<ResourceLocation, Function<R, T>>()
fun register(id: ResourceLocation, function: AnonymousFunction<R>): Function<R> { fun register(id: ResourceLocation, function: AnonymousFunction<R, T>): Function<R, T> {
synchronized(map) { synchronized(map) {
return map.computeIfAbsent(id) { Function(id, function, this) } return map.computeIfAbsent(id) { Function(id, function, this) }
} }
} }
inline fun <reified A : Any> register(id: ResourceLocation, noinline function: R.(A) -> Unit): Function<R> { fun register(id: ResourceLocation, function: R.() -> T): Function<R, T> {
return register(id, AnonymousFunction { r, args ->
check(args.isEmpty()) { "Invalid amount of arguments. No arguments are required" }
function.invoke(r)
})
}
inline fun <reified A : Any> register(id: ResourceLocation, noinline function: R.(A) -> T): Function<R, T> {
return register(id, AnonymousFunction { r, args -> return register(id, AnonymousFunction { r, args ->
check(args.size == 1) { "Invalid amount of arguments. 1 is required" } check(args.size == 1) { "Invalid amount of arguments. 1 is required" }
function.invoke( function.invoke(
@ -132,7 +139,7 @@ class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<Se
}) })
} }
inline fun <reified A : Any, reified B : Any> register(id: ResourceLocation, noinline function: R.(A, B) -> Unit): Function<R> { inline fun <reified A : Any, reified B : Any> register(id: ResourceLocation, noinline function: R.(A, B) -> T): Function<R, T> {
return register(id, AnonymousFunction { r, args -> return register(id, AnonymousFunction { r, args ->
check(args.size == 2) { "Invalid amount of arguments. 2 is required" } check(args.size == 2) { "Invalid amount of arguments. 2 is required" }
@ -144,7 +151,7 @@ class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<Se
}) })
} }
inline fun <reified A : Any, reified B : Any, reified C : Any> register(id: ResourceLocation, noinline function: R.(A, B, C) -> Unit): Function<R> { inline fun <reified A : Any, reified B : Any, reified C : Any> register(id: ResourceLocation, noinline function: R.(A, B, C) -> T): Function<R, T> {
return register(id, AnonymousFunction { r, args -> return register(id, AnonymousFunction { r, args ->
check(args.size == 3) { "Invalid amount of arguments. 3 is required" } check(args.size == 3) { "Invalid amount of arguments. 3 is required" }
@ -157,13 +164,13 @@ class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<Se
}) })
} }
fun get(id: ResourceLocation): Function<R>? { fun get(id: ResourceLocation): Function<R, T>? {
synchronized(map) { synchronized(map) {
return map[id] return map[id]
} }
} }
fun fromNetwork(buff: FriendlyByteBuf): BoundFunction<R>? { fun fromNetwork(buff: FriendlyByteBuf): BoundFunction<R, T>? {
val id = ResourceLocation(buff.readUtf()) val id = ResourceLocation(buff.readUtf())
val stream = DataInputStream(ByteBufInputStream(buff)) val stream = DataInputStream(ByteBufInputStream(buff))
@ -176,7 +183,7 @@ class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<Se
return map[id]?.bind(arguments) return map[id]?.bind(arguments)
} }
fun fromJson(value: JsonElement): BoundFunction<R>? { fun fromJson(value: JsonElement): BoundFunction<R, T>? {
if (value !is JsonObject) { if (value !is JsonObject) {
return null return null
} }
@ -193,7 +200,7 @@ class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<Se
return map[ResourceLocation(id)]?.bind(arguments) return map[ResourceLocation(id)]?.bind(arguments)
} }
override fun serialize(src: BoundFunction<R>, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { override fun serialize(src: BoundFunction<R, T>, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return src.toJson() return src.toJson()
} }
@ -201,15 +208,15 @@ class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<Se
json: JsonElement, json: JsonElement,
typeOfT: Type, typeOfT: Type,
context: JsonDeserializationContext context: JsonDeserializationContext
): BoundFunction<R> { ): BoundFunction<R, T> {
return fromJson(json) ?: throw JsonSyntaxException("Function is invalid") return fromJson(json) ?: throw JsonSyntaxException("Function is invalid")
} }
override fun write(out: JsonWriter, value: BoundFunction<R>) { override fun write(out: JsonWriter, value: BoundFunction<R, T>) {
TypeAdapters.JSON_ELEMENT.write(out, value.toJson()) TypeAdapters.JSON_ELEMENT.write(out, value.toJson())
} }
override fun read(`in`: JsonReader): BoundFunction<R> { override fun read(`in`: JsonReader): BoundFunction<R, T> {
return fromJson(TypeAdapters.JSON_ELEMENT.read(`in`)) ?: throw JsonSyntaxException("Function is invalid") return fromJson(TypeAdapters.JSON_ELEMENT.read(`in`)) ?: throw JsonSyntaxException("Function is invalid")
} }
} }

View File

@ -1,6 +1,7 @@
package ru.dbotthepony.mc.otm.network package ru.dbotthepony.mc.otm.network
import net.minecraftforge.network.NetworkDirection import net.minecraftforge.network.NetworkDirection
import ru.dbotthepony.mc.otm.android.feature.ItemEntityDataPacket
import ru.dbotthepony.mc.otm.block.entity.EnergyCounterPacket import ru.dbotthepony.mc.otm.block.entity.EnergyCounterPacket
object WorldNetworkChannel : MatteryNetworkChannel( object WorldNetworkChannel : MatteryNetworkChannel(
@ -9,5 +10,6 @@ object WorldNetworkChannel : MatteryNetworkChannel(
) { ) {
fun register() { fun register() {
add(EnergyCounterPacket::class.java, EnergyCounterPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) add(EnergyCounterPacket::class.java, EnergyCounterPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
add(ItemEntityDataPacket::class.java, ItemEntityDataPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
} }
} }

View File

@ -19,6 +19,7 @@ object AndroidFeatures {
val EXTENDED_REACH: AndroidFeatureType<*> by registry.register(MNames.EXTENDED_REACH) { AndroidFeatureType(::ExtendedReachFeature) } 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 NIGHT_VISION: AndroidFeatureType<*> by registry.register(MNames.NIGHT_VISION) { AndroidFeatureType(::NightVisionFeature) }
val SHOCKWAVE: AndroidFeatureType<*> by registry.register(MNames.SHOCKWAVE) { AndroidFeatureType(::ShockwaveFeature) } 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) { internal fun register(bus: IEventBus) {
registry.register(bus) registry.register(bus)

View File

@ -209,6 +209,7 @@ object MNames {
const val EXTENDED_REACH = "extended_reach" const val EXTENDED_REACH = "extended_reach"
const val NIGHT_VISION = "night_vision" const val NIGHT_VISION = "night_vision"
const val SHOCKWAVE = "shockwave" const val SHOCKWAVE = "shockwave"
const val ITEM_MAGNET = "item_magnet"
const val IMPROVED_LIMBS = "improved_limbs" const val IMPROVED_LIMBS = "improved_limbs"
// stats // stats

View File

@ -24,6 +24,9 @@ import net.minecraftforge.registries.RegistryBuilder
import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.android.AndroidFeatureType import ru.dbotthepony.mc.otm.android.AndroidFeatureType
import ru.dbotthepony.mc.otm.android.AndroidResearchType 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.block.CargoCrateBlock
import ru.dbotthepony.mc.otm.registry.objects.ColoredDecorativeBlock import ru.dbotthepony.mc.otm.registry.objects.ColoredDecorativeBlock
import ru.dbotthepony.mc.otm.registry.objects.CrateProperties import ru.dbotthepony.mc.otm.registry.objects.CrateProperties
@ -230,5 +233,10 @@ object MRegistry {
LootModifiers.register(bus) LootModifiers.register(bus)
MRecipes.register(bus) MRecipes.register(bus)
// call static constructors
NanobotsArmorFeature.Companion
ShockwaveFeature.Companion
ItemMagnetFeature.Companion
} }
} }