Quantum batteries

This commit is contained in:
DBotThePony 2022-08-24 15:36:20 +07:00
parent 2da5219739
commit 69b8115395
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 645 additions and 20 deletions

View File

@ -191,6 +191,13 @@ private fun misc(provider: MatteryLanguageProvider) {
misc("matter_bottler.switch_mode", "Switch work mode")
misc("container.matter_panel.number_input", "Input replication task count")
misc("item.quantum_battery.creative", "Fill this to win Minecraft.")
misc("item.quantum_battery.creative2", "See ya after millions of stars burn out.")
misc("item.quantum_battery.creative_power", "Stored energy: %s / Infinity")
misc("item.quantum_link_id", "Quantum link ID: %s")
misc("item.quantum_description", "This item is sharing it's contents with other similar items using quantum entanglement")
}
}
@ -287,6 +294,10 @@ private fun items(provider: MatteryLanguageProvider) {
add(MItems.BATTERY_CAPACITOR, "Capacitor Battery")
add(MItems.BATTERY_CREATIVE, "Creative Battery")
add(MItems.QUANTUM_BATTERY, "Quantum Battery")
add(MItems.QUANTUM_CAPACITOR, "Quantum Capacitor")
add(MItems.QUANTUM_BATTERY_CREATIVE, "Quantum Creative Battery")
add(MItems.TRITANIUM_SWORD, "Tritanium Sword")
add(MItems.TRITANIUM_PICKAXE, "Tritanium Pickaxe")
add(MItems.TRITANIUM_SHOVEL, "Tritanium Shovel")

View File

@ -3,6 +3,7 @@
package ru.dbotthepony.mc.otm
import net.minecraft.server.MinecraftServer
import net.minecraft.world.level.Level
import net.minecraftforge.event.TickEvent
import net.minecraftforge.event.TickEvent.ServerTickEvent
@ -26,6 +27,18 @@ private val postWorldTick = WeakHashMap<Level, ArrayList<IConditionalTickable>>(
private val preWorldTickOnce = WeakHashMap<Level, ArrayList<ITickable>>()
private val postWorldTickOnce = WeakHashMap<Level, ArrayList<ITickable>>()
private var _server: MinecraftServer? = null
private var _serverThread: Thread? = null
fun isServerThread(): Boolean {
return Thread.currentThread() === _serverThread
}
val MINECRAFT_SERVER: MinecraftServer
get() {
return _server ?: throw NullPointerException("Not running a server!")
}
// Flag whenever is server alive
// To avoid baby deadlocks caused by minecraft engine design issues
var SERVER_IS_DYING = true
@ -216,23 +229,28 @@ private fun clear() {
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
fun onServerStarting(event: ServerAboutToStartEvent?) {
fun onServerStarting(event: ServerAboutToStartEvent) {
clear()
SERVER_IS_DYING = false
_server = event.server
_serverThread = Thread.currentThread()
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
fun onServerStopping(event: ServerStoppingEvent?) {
fun onServerStopping(event: ServerStoppingEvent) {
clear()
SERVER_IS_DYING = true
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
fun onServerStopped(event: ServerStoppedEvent?) {
fun onServerStopped(event: ServerStoppedEvent) {
if (!SERVER_IS_DYING) {
LOGGER.fatal("ServerStoppingEvent did not fire. If server has crashed this is normal. However, if server finished it's work 'gracefully' this is a bug.")
clear()
SERVER_IS_DYING = true
}
_server = null
_serverThread = null
}

View File

@ -31,7 +31,7 @@ import java.util.ArrayList
* 3. This data can not be stored inside unshared (server only) nbt tag because, again, mods prone to use and interact with
* it wrong, causing loss of stored data or mods exposing full content of a drive inside their own tag (which cause very real "NBT size too large"
* network kicks, often locking players out of server/singleplayer worlds
* 4. net.minecraft.world.level.saveddata.SaveData is for storing everything inside one dat file, which
* 4. [net.minecraft.world.level.saveddata.SavedData] is for storing everything inside one dat file, which
* is performance tanking, because we have to write *entire* NBT on each save, not the data of Drives that are dirty
* 5. Mods which check items for being stack-able even with stack size of 1 gonna compare nbt tag,
* which will be performance tanking due to clause 1.

View File

@ -0,0 +1,193 @@
package ru.dbotthepony.mc.otm.item
import net.minecraft.ChatFormatting
import net.minecraft.core.Direction
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.IntTag
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Rarity
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.level.Level
import net.minecraftforge.common.capabilities.Capability
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.capabilities.ICapabilityProvider
import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.common.util.LazyOptional
import ru.dbotthepony.mc.otm.*
import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
import ru.dbotthepony.mc.otm.core.formatPower
import ru.dbotthepony.mc.otm.saveddata.SavedCountingMap
import ru.dbotthepony.mc.otm.saveddata.SavedMapDelegate
class QuantumBatteryItem : Item {
private inner class Power : IMatteryEnergyStorage, ICapabilityProvider, INBTSerializable<IntTag> {
private val resolver = LazyOptional.of { this }
var delegate = SavedMapDelegate(ImpreciseFraction.ZERO)
override fun <T : Any?> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
if (cap == ForgeCapabilities.ENERGY || cap == MatteryCapability.ENERGY) {
return resolver.cast()
}
return LazyOptional.empty()
}
override fun extractEnergyOuter(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
return extractEnergyInner(howMuch, simulate)
}
override fun extractEnergyInner(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
if (howMuch.isNegative) {
return ImpreciseFraction.ZERO
}
if (isCreative) {
val newEnergy = (delegate.value - howMuch).moreThanZero()
if (!simulate) {
delegate.value = newEnergy
}
return delegate.value - newEnergy
}
val newEnergy = (delegate.value - howMuch.coerceAtMost(throughput!!)).moreThanZero()
if (!simulate) {
delegate.value = newEnergy
}
return delegate.value - newEnergy
}
override fun receiveEnergyOuter(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
return receiveEnergyInner(howMuch, simulate)
}
override fun receiveEnergyInner(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
if (howMuch.isNegative) {
return ImpreciseFraction.ZERO
}
if (isCreative) {
if (!simulate) {
delegate.value += howMuch
}
return howMuch
}
val newEnergy = (delegate.value + howMuch.coerceAtMost(throughput!!)).coerceAtMost(capacity!!)
if (!simulate) {
delegate.value = newEnergy
}
return newEnergy - delegate.value
}
override val batteryLevel: ImpreciseFraction
get() = delegate.value
override val maxBatteryLevel: ImpreciseFraction
get() = capacity ?: (batteryLevel + ImpreciseFraction.LONG_MAX_VALUE)
override val missingPower: ImpreciseFraction
get() = if (isCreative) ImpreciseFraction.LONG_MAX_VALUE else super.missingPower
override fun canExtract(): Boolean {
return true
}
override fun canReceive(): Boolean {
return true
}
override fun serializeNBT(): IntTag {
if (isServerThread() && delegate.parent == null) {
val old = delegate
delegate = saveData!!.factorize()
delegate.value = old.value
}
return IntTag.valueOf(if (delegate.parent == null) Int.MIN_VALUE else delegate.index)
}
override fun deserializeNBT(nbt: IntTag) {
if (nbt.asInt == Int.MIN_VALUE) {
delegate = SavedMapDelegate(ImpreciseFraction.ZERO)
return
}
delegate = saveData?.computeIfAbsent(nbt.asInt) ?: SavedMapDelegate(null, nbt.asInt, delegate.value)
}
}
val isCreative: Boolean
val capacity: ImpreciseFraction?
val throughput: ImpreciseFraction?
val saveDataID: String
val saveData: SavedCountingMap<SavedMapDelegate<ImpreciseFraction>>? get() {
if (isServerThread()) {
return MINECRAFT_SERVER.overworld().dataStorage.computeIfAbsent({
SavedMapDelegate.makeMap(ImpreciseFraction::serializeNBT, ImpreciseFraction.Companion::deserializeNBT, ImpreciseFraction.ZERO).load(it)
}, {
SavedMapDelegate.makeMap(ImpreciseFraction::serializeNBT, ImpreciseFraction.Companion::deserializeNBT, ImpreciseFraction.ZERO)
}, saveDataID) ?: throw NullPointerException("Unable to get save data for $this in ${MINECRAFT_SERVER.overworld()}")
}
return null
}
constructor(saveDataID: String) : super(Properties().stacksTo(1).rarity(Rarity.EPIC).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) {
isCreative = true
capacity = null
throughput = null
this.saveDataID = "otm_$saveDataID".intern()
}
constructor(saveDataID: String, capacity: ImpreciseFraction, io: ImpreciseFraction) : super(Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) {
isCreative = false
this.capacity = capacity
this.throughput = io
this.saveDataID = "otm_$saveDataID".intern()
}
override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
return Power()
}
override fun appendHoverText(
itemStack: ItemStack,
p_41422_: Level?,
components: MutableList<Component>,
p_41424_: TooltipFlag
) {
super.appendHoverText(itemStack, p_41422_, components, p_41424_)
val power = itemStack.getCapability(MatteryCapability.ENERGY).orThrow() as Power
components.add(TranslatableComponent("otm.item.quantum_description").withStyle(ChatFormatting.DARK_GRAY))
if (isCreative) {
components.add(TranslatableComponent("otm.item.quantum_battery.creative_power", power.batteryLevel.formatPower()).withStyle(ChatFormatting.GRAY))
components.add(TranslatableComponent("otm.item.quantum_battery.creative").withStyle(ChatFormatting.DARK_GRAY))
components.add(TranslatableComponent("otm.item.quantum_battery.creative2").withStyle(ChatFormatting.DARK_GRAY))
} else {
components.add(TranslatableComponent("otm.item.power.normal.storage", power.batteryLevel.formatPower(), capacity!!.formatPower()).withStyle(ChatFormatting.GRAY))
components.add(TranslatableComponent(
"otm.item.power.normal.throughput",
throughput!!.formatPower(),
throughput.formatPower()
).withStyle(ChatFormatting.GRAY))
}
components.add(TranslatableComponent("otm.item.quantum_link_id", power.delegate.index).withStyle(ChatFormatting.DARK_GRAY))
}
}

View File

@ -176,6 +176,10 @@ object MItems {
val BATTERY_CAPACITOR: Item by registry.register(MNames.BATTERY_CAPACITOR) { BatteryItem(ImpreciseFraction(150_000), ImpreciseFraction(15000), ImpreciseFraction(15000)) }
val BATTERY_CREATIVE: Item by registry.register(MNames.BATTERY_CREATIVE) { BatteryItem() }
val QUANTUM_BATTERY: Item by registry.register(MNames.QUANTUM_BATTERY) { QuantumBatteryItem(MNames.QUANTUM_BATTERY, ImpreciseFraction(20_000_000), ImpreciseFraction(10_000)) }
val QUANTUM_CAPACITOR: Item by registry.register(MNames.QUANTUM_CAPACITOR) { QuantumBatteryItem(MNames.QUANTUM_CAPACITOR, ImpreciseFraction(1_000_000), ImpreciseFraction(200_000)) }
val QUANTUM_BATTERY_CREATIVE: Item by registry.register(MNames.QUANTUM_BATTERY_CREATIVE) { QuantumBatteryItem(MNames.QUANTUM_BATTERY_CREATIVE) }
val BATTERIES = LazyList(
{ BATTERY_CRUDE },
{ BATTERY_BASIC },

View File

@ -34,22 +34,6 @@ object MNames {
const val GRAVITATION_STABILIZER_LENS = "gravitation_stabilizer_lens"
const val CARGO_CRATE = "cargo_crate"
const val CARGO_CRATE_WHITE = "cargo_crate_white" // нужен рецепт
const val CARGO_CRATE_ORANGE = "cargo_crate_orange" // нужен рецепт
const val CARGO_CRATE_MAGENTA = "cargo_crate_magenta" // нужен рецепт
const val CARGO_CRATE_LIGHT_BLUE = "cargo_crate_light_blue" // нужен рецепт
const val CARGO_CRATE_YELLOW = "cargo_crate_yellow" // нужен рецепт
const val CARGO_CRATE_LIME = "cargo_crate_lime" // нужен рецепт
const val CARGO_CRATE_PINK = "cargo_crate_pink" // нужен рецепт
const val CARGO_CRATE_GRAY = "cargo_crate_gray" // нужен рецепт
const val CARGO_CRATE_LIGHT_GRAY = "cargo_crate_light_gray" // нужен рецепт
const val CARGO_CRATE_CYAN = "cargo_crate_cyan" // нужен рецепт
const val CARGO_CRATE_PURPLE = "cargo_crate_purple" // нужен рецепт
const val CARGO_CRATE_BLUE = "cargo_crate_blue" // нужен рецепт
const val CARGO_CRATE_BROWN = "cargo_crate_brown" // нужен рецепт
const val CARGO_CRATE_GREEN = "cargo_crate_green" // нужен рецепт
const val CARGO_CRATE_RED = "cargo_crate_red" // нужен рецепт
const val CARGO_CRATE_BLACK = "cargo_crate_black" // нужен рецепт
const val STORAGE_BUS = "storage_bus"
const val STORAGE_IMPORTER = "storage_importer"
@ -89,6 +73,10 @@ object MNames {
const val BATTERY_CAPACITOR = "battery_capacitor"
const val BATTERY_CREATIVE = "battery_creative"
const val QUANTUM_BATTERY = "quantum_battery"
const val QUANTUM_CAPACITOR = "quantum_capacitor"
const val QUANTUM_BATTERY_CREATIVE = "quantum_battery_creative"
const val MATTER_CAPACITOR_PARTS = "matter_capacitor_parts"
const val MATTER_CAPACITOR_BASIC = "matter_capacitor_basic"
const val MATTER_CAPACITOR_NORMAL = "matter_capacitor_normal"

View File

@ -0,0 +1,411 @@
package ru.dbotthepony.mc.otm.saveddata
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.IntTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import net.minecraft.world.level.saveddata.SavedData
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.set
class SavedMapDelegate<V>(val parent: SavedCountingMap<SavedMapDelegate<V>>?, val index: Int, value: V) {
constructor(value: V) : this(null, -1, value)
var value: V = value
set(value) {
if (field != value) {
field = value
parent?.isDirty = true
}
}
companion object {
fun <V> makeMap(
serializer: (value: V) -> Tag,
deserializer: (nbt: Tag) -> V,
defaultValue: V
): SavedCountingMap<SavedMapDelegate<V>> {
return SavedCountingMap(
serializer = { _, value, _ -> serializer.invoke(value.value) },
deserializer = { map, nbt, index -> SavedMapDelegate(map, index, deserializer.invoke(nbt)) },
factory = { map, index -> SavedMapDelegate(map, index, defaultValue) }
)
}
}
}
class SavedCountingMap<T>(
val serializer: (map: SavedCountingMap<T>, value: T, index: Int) -> Tag,
val deserializer: (map: SavedCountingMap<T>, nbt: Tag, index: Int) -> T,
val factory: (map: SavedCountingMap<T>, index: Int) -> T
) : SavedData(), MutableMap<Int, T> {
var nextIndex = 0
private set
fun computeIfAbsent(index: Int): T {
return map.computeIfAbsent(index, Int2ObjectFunction {
isDirty = true
return@Int2ObjectFunction factory.invoke(this, it)
})
}
fun factorize(): T {
return computeIfAbsent(nextIndex++)
}
private val map = Int2ObjectAVLTreeMap<T>()
override val size: Int
get() = map.size
override fun containsKey(key: Int): Boolean {
return map.containsKey(key)
}
override fun containsValue(value: T): Boolean {
return map.containsValue(value)
}
override fun get(key: Int): T? {
return map[key]
}
override fun isEmpty(): Boolean {
return map.isEmpty()
}
override val entries: MutableSet<MutableMap.MutableEntry<Int, T>> by lazy {
object : MutableSet<MutableMap.MutableEntry<Int, T>> {
override fun add(element: MutableMap.MutableEntry<Int, T>): Boolean {
this@SavedCountingMap[element.key] = element.value
return true
}
override fun addAll(elements: Collection<MutableMap.MutableEntry<Int, T>>): Boolean {
for (value in elements) {
add(value)
}
return true
}
override fun clear() {
this@SavedCountingMap.clear()
}
private val setParent = this@SavedCountingMap.map.int2ObjectEntrySet()
override fun iterator(): MutableIterator<MutableMap.MutableEntry<Int, T>> {
return object : MutableIterator<MutableMap.MutableEntry<Int, T>> {
private val parent = setParent.iterator()
override fun hasNext(): Boolean {
return parent.hasNext()
}
override fun next(): MutableMap.MutableEntry<Int, T> {
return parent.next()
}
override fun remove() {
parent.remove()
isDirty = true
}
}
}
override fun remove(element: MutableMap.MutableEntry<Int, T>): Boolean {
if (setParent.remove(element)) {
isDirty = true
return true
}
return false
}
override fun removeAll(elements: Collection<MutableMap.MutableEntry<Int, T>>): Boolean {
if (setParent.removeAll(elements)) {
isDirty = true
return true
}
return false
}
override fun retainAll(elements: Collection<MutableMap.MutableEntry<Int, T>>): Boolean {
if (setParent.retainAll(elements)) {
isDirty = true
return true
}
return false
}
override val size: Int
get() = setParent.size
override fun contains(element: MutableMap.MutableEntry<Int, T>): Boolean {
return setParent.contains(element)
}
override fun containsAll(elements: Collection<MutableMap.MutableEntry<Int, T>>): Boolean {
return setParent.containsAll(elements)
}
override fun isEmpty(): Boolean {
return setParent.isEmpty()
}
}
}
override val keys: MutableSet<Int> by lazy {
object : MutableSet<Int> {
val parent = this@SavedCountingMap.map.keys
override fun add(element: Int): Boolean {
throw UnsupportedOperationException()
}
override fun addAll(elements: Collection<Int>): Boolean {
throw UnsupportedOperationException()
}
override fun clear() {
this@SavedCountingMap.clear()
}
override fun iterator(): MutableIterator<Int> {
return object : MutableIterator<Int> {
private val parentIterator = parent.iterator()
override fun hasNext(): Boolean {
return parentIterator.hasNext()
}
override fun next(): Int {
return parentIterator.nextInt()
}
override fun remove() {
parentIterator.remove()
isDirty = true
}
}
}
override fun remove(element: Int): Boolean {
return this@SavedCountingMap.remove(element) != null
}
override fun removeAll(elements: Collection<Int>): Boolean {
var changes = false
for (element in elements) {
changes = remove(element) || changes
}
return changes
}
override fun retainAll(elements: Collection<Int>): Boolean {
val iterator = this.iterator()
var changes = false
for (element in iterator) {
if (element !in elements) {
iterator.remove()
changes = true
}
}
return changes
}
override val size: Int
get() = parent.size
override fun contains(element: Int): Boolean {
return parent.contains(element)
}
override fun containsAll(elements: Collection<Int>): Boolean {
return parent.containsAll(elements)
}
override fun isEmpty(): Boolean {
return parent.isEmpty()
}
}
}
override val values: MutableCollection<T> by lazy {
object : MutableCollection<T> {
private val parent = this@SavedCountingMap.map.values
override val size: Int
get() = parent.size
override fun contains(element: T): Boolean {
return parent.contains(element)
}
override fun containsAll(elements: Collection<T>): Boolean {
return parent.containsAll(elements)
}
override fun isEmpty(): Boolean {
return parent.isEmpty()
}
override fun add(element: T): Boolean {
throw UnsupportedOperationException()
}
override fun addAll(elements: Collection<T>): Boolean {
throw UnsupportedOperationException()
}
override fun clear() {
this@SavedCountingMap.clear()
}
override fun iterator(): MutableIterator<T> {
return object : MutableIterator<T> {
private val parentIterator = parent.iterator()
override fun hasNext(): Boolean {
return parentIterator.hasNext()
}
override fun next(): T {
return parentIterator.next()
}
override fun remove() {
parentIterator.remove()
isDirty = true
}
}
}
override fun remove(element: T): Boolean {
val indexOf = this@SavedCountingMap.map.firstNotNullOfOrNull { if (it.value == element) it.key else null } ?: return false
this@SavedCountingMap.remove(indexOf)
return true
}
override fun removeAll(elements: Collection<T>): Boolean {
var changes = false
for (element in elements) {
changes = remove(element) || changes
}
return changes
}
override fun retainAll(elements: Collection<T>): Boolean {
val iterator = this.iterator()
var changes = false
for (element in iterator) {
if (element !in elements) {
iterator.remove()
changes = true
}
}
return changes
}
}
}
override fun clear() {
isDirty = true
return map.clear()
}
override fun put(key: Int, value: T): T? {
val existing = map.put(key, value)
if (existing == value) {
return existing
}
isDirty = true
return existing
}
override fun putAll(from: Map<out Int, T>) {
isDirty = true
return map.putAll(from)
}
override fun remove(key: Int): T? {
val removed = map.remove(key)
if (removed != null) {
isDirty = true
}
return removed
}
override fun save(output: CompoundTag): CompoundTag {
output["map"] = ListTag().also {
for ((key, value) in this.map) {
val compound = CompoundTag()
try {
compound["value"] = this.serializer.invoke(this, value, key)
it.add(compound)
} catch(err: Exception) {
LOGGER.error("Unable to serialize $value at $key", err)
continue
}
compound["key"] = key
}
}
output["next_index"] = nextIndex
return output
}
fun load(input: CompoundTag): SavedCountingMap<T> {
this.map.clear()
val map = input["map"] as? ListTag ?: ListTag()
var maximal = -1
for (value in map) {
if (value !is CompoundTag) {
continue
}
val key = (value["key"] as IntTag?)?.asInt ?: continue
val nbt = value["value"] ?: continue
try {
val deserialized = this.deserializer.invoke(this, nbt, key)
this.map.put(key, deserialized)
} catch(err: Exception) {
LOGGER.error("Unable to deserialize value in $this at $key", err)
}
maximal = maximal.coerceAtLeast(key)
}
nextIndex = (input["next_index"] as IntTag?)?.asInt ?: (maximal + 1)
isDirty = false
return this
}
companion object {
private val LOGGER = LogManager.getLogger()
}
}