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.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<AndroidResearchType>, 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<AndroidResearchType>, 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%%")
}

View File

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

View File

@ -100,8 +100,6 @@ public final class OverdriveThatMatters {
ClientConfig.INSTANCE.register();
ServerConfig.INSTANCE.register();
NanobotsArmorFeature.Companion.register();
}
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)
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()

View File

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

View File

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

View File

@ -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<Pair<AndroidResearch, AndroidFeature>, Unit>
typealias ComponentSupplier = SerializedFunctionRegistry.BoundFunction<Unit, Component>
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
@ -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<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"
private val LOGGER = LogManager.getLogger()
@ -62,11 +97,23 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s
var researchMap: Map<ResourceLocation, AndroidResearchType> = mapOf()
private set
internal fun fireRegistrationEvent() {
if (!firedRegistrationEvent) {
try {
MinecraftForge.EVENT_BUS.post(RegisterFuncsEvent)
} finally {
firedRegistrationEvent = true
}
}
}
override fun apply(
jsonElementMap: Map<ResourceLocation, JsonElement>,
manager: ResourceManager,
profiler: ProfilerFiller
) {
fireRegistrationEvent()
val builder = ImmutableMap.builder<ResourceLocation, AndroidResearchType>()
for ((k, v) in jsonElementMap) {

View File

@ -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<FeatureReference>,
descriptionLines: Collection<Component>,
descriptionSuppliers: Collection<ComponentSupplier> = 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<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
val transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
val transformersUp: Collection<AndroidResultTransformer> = listOf(),
val transformersDown: Collection<AndroidResultTransformer> = 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<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>>()),
(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_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<AndroidResultTransformer>()),
)
} else {
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 descriptionSuppliers: List<ComponentSupplier> = ImmutableList.copyOf(descriptionSuppliers)
/**
* 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.
@ -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<MutableComponent>,
descriptionSuppliers = description_funcs.stream().map { AndroidResearchManager.descriptionFuncs.fromJson(it) }.toList() as List<ComponentSupplier>,
experienceLevels = experience,
customName = customName,
iconText = iconText,
@ -600,6 +608,7 @@ class AndroidResearchType(
var experience: Int = 0,
var customName: Component? = null,
var description: MutableList<Component>? = null,
var descriptionSuppliers: MutableList<ComponentSupplier>? = 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<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 {
this.experience = experience
return this
@ -653,11 +673,72 @@ class AndroidResearchType(
return this
}
fun withDescription(description: List<Component>): Builder {
fun withDescription(description: Collection<Component>): Builder {
this.description = ArrayList<Component>(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<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
* research tree render logic (yet).
@ -686,8 +767,8 @@ class AndroidResearchType(
id: ResourceLocation,
level: Int = 0,
rigid: Boolean = false,
transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
transformersUp: Collection<AndroidResultTransformer> = listOf(),
transformersDown: Collection<AndroidResultTransformer> = 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<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
transformersUp: Collection<AndroidResultTransformer> = listOf(),
transformersDown: Collection<AndroidResultTransformer> = 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<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
transformersUp: Collection<AndroidResultTransformer> = listOf(),
transformersDown: Collection<AndroidResultTransformer> = 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,

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
}
}
/**
* 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.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) })
}
}

View File

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

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.function.Consumer
class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<SerializedFunctionRegistry.BoundFunction<R>>, JsonDeserializer<SerializedFunctionRegistry.BoundFunction<R>>, TypeAdapter<SerializedFunctionRegistry.BoundFunction<R>>() {
fun interface AnonymousFunction<R> {
fun invoke(receiver: R, arguments: List<Any?>)
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, T> {
fun invoke(receiver: R, arguments: List<Any?>): T
}
data class BoundFunction<R>(
val function: Function<R>,
data class BoundFunction<R, T>(
val function: Function<R, T>,
val arguments: List<Any?>
) : Consumer<R> {
) : java.util.function.Function<R, T> {
fun toJson(): JsonElement {
return JsonObject().also {
it["function"] = function.toJson()
@ -67,22 +67,22 @@ class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<Se
}
}
override fun accept(t: R) {
function.body.invoke(t, arguments)
override fun apply(t: R): T {
return function.body.invoke(t, arguments)
}
}
data class Function<R>(
data class Function<R, T>(
val id: ResourceLocation,
val body: AnonymousFunction<R>,
val registry: SerializedFunctionRegistry<R>
val body: AnonymousFunction<R, T>,
val registry: SerializedFunctionRegistry<R, T>
) {
fun bind(vararg arguments: Any?): BoundFunction<R> {
fun bind(vararg arguments: Any?): BoundFunction<R, T> {
validate(arguments.iterator().withIndex())
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())
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) {
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 ->
check(args.size == 1) { "Invalid amount of arguments. 1 is required" }
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 ->
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 ->
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) {
return map[id]
}
}
fun fromNetwork(buff: FriendlyByteBuf): BoundFunction<R>? {
fun fromNetwork(buff: FriendlyByteBuf): BoundFunction<R, T>? {
val id = ResourceLocation(buff.readUtf())
val stream = DataInputStream(ByteBufInputStream(buff))
@ -176,7 +183,7 @@ class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<Se
return map[id]?.bind(arguments)
}
fun fromJson(value: JsonElement): BoundFunction<R>? {
fun fromJson(value: JsonElement): BoundFunction<R, T>? {
if (value !is JsonObject) {
return null
}
@ -193,7 +200,7 @@ class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<Se
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()
}
@ -201,15 +208,15 @@ class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<Se
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): BoundFunction<R> {
): BoundFunction<R, T> {
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())
}
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")
}
}

View File

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

View File

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

View File

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

View File

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