diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkAugmentations.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkAugmentations.kt index fb1e8bdcd..47b1f44b7 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkAugmentations.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkAugmentations.kt @@ -1,5 +1,7 @@ package org.valkyrienskies.clockwork +import org.valkyrienskies.clockwork.util.DoubleAvgAugmentation +import org.valkyrienskies.clockwork.util.DoubleAvgComponentAugmentation import org.valkyrienskies.core.api.world.connectivity.DoubleAugmentation import org.valkyrienskies.core.api.world.connectivity.DoubleComponentAugmentation import org.valkyrienskies.core.apigame.world.ServerShipWorldCore @@ -8,18 +10,26 @@ object ClockworkAugmentations { private val augmentKeys: HashMap = HashMap() private val componentAugmentKeys: HashMap = HashMap() - fun registerAugmentation(key: String, shipObjectWorld: ServerShipWorldCore) { + fun registerSumAugmentation(key: String, shipObjectWorld: ServerShipWorldCore) { augmentKeys[key] = shipObjectWorld.createDoubleSumAugmentation("clockwork", key) } + fun registerAvgAugmentation(key: String, shipObjectWorld: ServerShipWorldCore) { + augmentKeys[key] = DoubleAvgAugmentation("clockwork:$key") + } + fun getAugmentation(key: String): DoubleAugmentation { return augmentKeys[key] ?: error("No augmentation found with key $key") } - fun registerComponentAugmentation(key: String, shipObjectWorld: ServerShipWorldCore) { + fun registerComponentSumAugmentation(key: String, shipObjectWorld: ServerShipWorldCore) { componentAugmentKeys[key] = shipObjectWorld.createDoubleSumComponentAugmentation("clockwork", key) } + fun registerComponentAvgAugmentation(key: String, shipObjectWorld: ServerShipWorldCore) { + componentAugmentKeys[key] = DoubleAvgComponentAugmentation("clockwork:$key") + } + fun getComponentAugmentation(key: String): DoubleComponentAugmentation { return componentAugmentKeys[key] ?: error("No component augmentation found with key $key") } diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkBlockEntities.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkBlockEntities.kt index 6ffd51313..18a5eefda 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkBlockEntities.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkBlockEntities.kt @@ -2,10 +2,6 @@ package org.valkyrienskies.clockwork import com.jozufozu.flywheel.api.MaterialManager import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstance -import com.simibubi.create.AllBlocks -import com.simibubi.create.Create -import com.simibubi.create.content.fluids.pump.PumpBlockEntity -import com.tterrag.registrate.builders.BlockEntityBuilder import com.tterrag.registrate.util.entry.BlockEntityEntry import com.tterrag.registrate.util.nullness.NonNullFunction import net.minecraft.client.renderer.blockentity.BlockEntityRenderer @@ -46,6 +42,9 @@ import org.valkyrienskies.clockwork.content.logistics.gas.pump.PumpDuctBlockEnti import org.valkyrienskies.clockwork.content.logistics.gas.pump.PumpDuctCogInstance import org.valkyrienskies.clockwork.content.logistics.gas.pump.PumpDuctRenderer import org.valkyrienskies.clockwork.content.logistics.gas.storage.tank.DuctTankBlockEntity +import org.valkyrienskies.clockwork.content.logistics.gas.pockets.nozzle.GasNozzleBlockEntity +import org.valkyrienskies.clockwork.content.logistics.gas.pockets.nozzle.GasNozzleInstance +import org.valkyrienskies.clockwork.content.logistics.gas.pockets.nozzle.GasNozzleRenderer import java.util.function.BiFunction import org.valkyrienskies.clockwork.content.logistics.solid.delivery.cannon.DeliveryCannonBlockEntity import org.valkyrienskies.clockwork.content.logistics.solid.delivery.chute.DeliveryChuteBlockEntity @@ -354,6 +353,33 @@ object ClockworkBlockEntities { } .register() + @JvmField + val GAS_NOZZLE: BlockEntityEntry = ClockworkMod.REGISTRATE + .blockEntity("gas_nozzle") { type: BlockEntityType<*>, pos: BlockPos, state: BlockState -> + GasNozzleBlockEntity( + type, + pos, + state + ) + } + .instance { + BiFunction> { materialManager: MaterialManager?, blockEntity: GasNozzleBlockEntity? -> + GasNozzleInstance( + materialManager, + blockEntity + ) + } + } + .validBlocks(ClockworkBlocks.GAS_NOZZLE) + .renderer { + NonNullFunction> { context: BlockEntityRendererProvider.Context? -> + GasNozzleRenderer( + context + ) + } + } + .register() + @JvmField val GOO_BLOCK = ClockworkMod.REGISTRATE.blockEntity("goo_block") { type: BlockEntityType<*>, pos: BlockPos, state: BlockState -> GooBlockEntity( diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkBlocks.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkBlocks.kt index 4c8775b47..0c0713729 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkBlocks.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkBlocks.kt @@ -20,6 +20,7 @@ import net.minecraft.world.item.Item import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.SlabBlock +import net.minecraft.world.level.block.SoundType import net.minecraft.world.level.block.StairBlock import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockBehaviour @@ -35,6 +36,7 @@ import org.valkyrienskies.clockwork.content.contraptions.phys.infuser.PhysicsInf import org.valkyrienskies.clockwork.content.contraptions.phys.slicker.GooBlock import org.valkyrienskies.clockwork.content.contraptions.phys.slicker.SlickerBlock import org.valkyrienskies.clockwork.content.contraptions.propeller.PropellerBearingBlock +import org.valkyrienskies.clockwork.content.kinetics.casing.ExtendedEncasedShaftBlock import org.valkyrienskies.clockwork.content.curiosities.clock.ClockBlock import org.valkyrienskies.clockwork.content.kinetics.resistor.RedstoneResistorBlock import org.valkyrienskies.clockwork.content.kinetics.sequenced_seat.SequencedSeatBlock @@ -49,6 +51,7 @@ import org.valkyrienskies.clockwork.content.logistics.solid.delivery.cannon.Deli import org.valkyrienskies.clockwork.content.logistics.solid.delivery.cannon.DeliveryCannonBlockEntity import org.valkyrienskies.clockwork.content.logistics.solid.delivery.chute.DeliveryChuteBlock import org.valkyrienskies.clockwork.content.physicalities.ballast.BallastBlock +import org.valkyrienskies.clockwork.content.logistics.gas.pockets.nozzle.GasNozzleBlock import org.valkyrienskies.clockwork.content.physicalities.wing.DyedWingBlockItem import org.valkyrienskies.clockwork.content.physicalities.wing.FlapBlock import org.valkyrienskies.clockwork.content.physicalities.wing.WingBlock @@ -351,6 +354,21 @@ object ClockworkBlocks { .transform(BlockStressDefaults.setImpact(4.0)) .register() + @JvmField + val GAS_NOZZLE: BlockEntry = + REGISTRATE.block("gas_nozzle") { properties: BlockBehaviour.Properties? -> + GasNozzleBlock(properties!!) + } + .initialProperties { SharedProperties.stone() } + .transform(TagGen.axeOrPickaxe()) + .properties { it.noOcclusion() } + .addLayer { Supplier { RenderType.cutout() } } + .tag(AllTags.AllBlockTags.SAFE_NBT.tag) + .item() + .tab { ClockworkMod.BASE_CREATIVE_TAB } + .model(AssetLookup.customBlockItemModel("gas_nozzle")) + .build() + .register() @JvmField val GOO_BLOCK = REGISTRATE.block("goo_block") { properties: BlockBehaviour.Properties? -> @@ -548,6 +566,20 @@ object ClockworkBlocks { .build() .register() + @JvmField + val BALLOON_CASING = REGISTRATE.block( + "balloon_casing" + ) { properties: BlockBehaviour.Properties? -> + ExtendedEncasedShaftBlock.balloon( + properties!! + ) + } + .initialProperties { SharedProperties.wooden() } + .item() + .tab { ClockworkMod.BASE_CREATIVE_TAB } + .build() + .register() + @JvmField val CLOCK: BlockEntry = REGISTRATE.block( "clock" @@ -593,7 +625,6 @@ object ClockworkBlocks { .build() .register() - @JvmStatic fun register() { diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkConfig.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkConfig.kt index 350bec265..c8046e238 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkConfig.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkConfig.kt @@ -40,7 +40,10 @@ object ClockworkConfig { @JsonSchema(description = "Max Gravitron mass in 1000 kg") var maxGravitronMass = 256 - @JsonSchema(description = "Force multiplier for balloons. Realism is 1.0, default is 10.0. Range: > 0.0", min = 0.0) - var balloonForceMult: Double = 10.0 + @JsonSchema(description = "Force multiplier for balloons. Realism is 1.0, default is 100.0. Range: > 0.0", min = 0.0) + var balloonForceMult: Double = 100.0 + + @JsonSchema(description = "Leakage rate of pockets. Determines how fast pressure in an unsealed pocket attempts to 'normalize'. Default is 0.5.", min = 0.0, max = 1.0) + var pocketLeakageRate = 0.5 } } \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkDamageSources.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkDamageSources.kt new file mode 100644 index 000000000..eda501a3f --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkDamageSources.kt @@ -0,0 +1,20 @@ +package org.valkyrienskies.clockwork + +import net.minecraft.world.damagesource.DamageSource + +object ClockworkDamageSources { + + @JvmStatic + val GAS_EXPLOSION = ClockworkDamageSource("gas_explosion") + + @JvmStatic + val GAS_BURN = ClockworkDamageSource("gas_burn") + + fun init() { + GAS_EXPLOSION.setExplosion() + } + + class ClockworkDamageSource: DamageSource { + constructor(name: String) : super(name) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkMod.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkMod.kt index 7be605a55..cb38d8a29 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkMod.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkMod.kt @@ -14,7 +14,6 @@ import org.valkyrienskies.clockwork.content.forces.WanderShipControl import org.valkyrienskies.clockwork.kelvin.api.DuctNetwork import org.valkyrienskies.clockwork.kelvin.impl.DuctNetworkImpl import org.valkyrienskies.clockwork.platform.PlatformUtils -import org.valkyrienskies.core.impl.config.VSConfigClass import org.valkyrienskies.core.impl.hooks.VSEvents import org.valkyrienskies.mod.common.ValkyrienSkiesMod import org.valkyrienskies.mod.common.shipObjectWorld @@ -43,7 +42,7 @@ object ClockworkMod { ClockworkPackets.init() ClockworkTags.init() ClockworkWorldgen.init() - + ClockworkDamageSources.init() ValkyrienSkiesMod.vsCore.registerConfigLegacy("clockwork", ClockworkConfig::class.java) VSEvents.ShipLoadEvent.on { event -> @@ -61,10 +60,14 @@ object ClockworkMod { } LifecycleEvent.SERVER_STARTED.register { - ClockworkAugmentations.registerComponentAugmentation("temperature", it.shipObjectWorld) - ClockworkAugmentations.registerComponentAugmentation("gas_air", it.shipObjectWorld) - ClockworkAugmentations.registerComponentAugmentation("gas_phlogiston", it.shipObjectWorld) - ClockworkAugmentations.registerComponentAugmentation("gas_helium", it.shipObjectWorld) + ClockworkAugmentations.registerComponentAvgAugmentation("temperature", it.shipObjectWorld) + ClockworkAugmentations.registerComponentAvgAugmentation("pressure", it.shipObjectWorld) + ClockworkAugmentations.registerComponentSumAugmentation("gas_air", it.shipObjectWorld) + ClockworkAugmentations.registerComponentSumAugmentation("gas_phlogiston", it.shipObjectWorld) + ClockworkAugmentations.registerComponentSumAugmentation("gas_helium", it.shipObjectWorld) + //todo: gas registry + ClockworkAugmentations.registerComponentSumAugmentation("airupdated", it.shipObjectWorld) + ClockworkAugmentations.registerSumAugmentation("sealed", it.shipObjectWorld) } TickEvent.SERVER_LEVEL_POST.register { diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkPackets.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkPackets.kt index 1cb033193..75dc4b515 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkPackets.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkPackets.kt @@ -20,6 +20,7 @@ import org.valkyrienskies.clockwork.content.logistics.gas.filter.FilterClosePack import org.valkyrienskies.clockwork.content.logistics.gas.filter.FilterScreenOpenPacket import org.valkyrienskies.clockwork.content.logistics.gas.generation.compressor.AirCompressorPacket import org.valkyrienskies.clockwork.content.logistics.gas.generation.creative_generator.CreativeGeneratorPacket +import org.valkyrienskies.clockwork.content.logistics.gas.pockets.nozzle.GasNozzlePacket import org.valkyrienskies.clockwork.content.logistics.solid.delivery.frequency_slot.UpdateFrequencySlotPacket import org.valkyrienskies.clockwork.content.logistics.solid.delivery.cannon.DeliveryCannonSyncPacket import org.valkyrienskies.clockwork.content.physicalities.wing.BlockEntityColorPacket @@ -63,6 +64,7 @@ enum class ClockworkPackets( UPDATE_DUCT_EDGE(DuctEdgeSyncPacket::class.java, ::DuctEdgeSyncPacket), AIR_COMPRESSOR_PACKET(AirCompressorPacket::class.java, ::AirCompressorPacket), + GAS_NOZZLE_PACKET(GasNozzlePacket::class.java, ::GasNozzlePacket) ; diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkPartials.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkPartials.kt index 6a7e2ee9f..19cd2b5e4 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkPartials.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/ClockworkPartials.kt @@ -108,6 +108,9 @@ object ClockworkPartials { val PUMP_COG: PartialModel = AllPartialModels.MECHANICAL_PUMP_COG + val NOZZLE_DIAL: PartialModel = PartialModel(ClockworkMod.asResource("block/gas_nozzle/dial")) + val NOZZLE_AXIS: PartialModel = PartialModel(ClockworkMod.asResource("block/gas_nozzle/axis")) + val HAND_SECOND: PartialModel = PartialModel(ClockworkMod.asResource("block/clock/hand_second")) val HAND_MINUTE: PartialModel = PartialModel(ClockworkMod.asResource("block/clock/hand_minute")) val HAND_HOUR: PartialModel = PartialModel(ClockworkMod.asResource("block/clock/hand_hour")) diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/forces/DragController.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/forces/DragController.kt index 08ddfe9fc..6896cad8c 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/forces/DragController.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/forces/DragController.kt @@ -23,6 +23,7 @@ import org.valkyrienskies.mod.common.util.toJOMLD import org.valkyrienskies.mod.util.logger import java.util.* import java.util.concurrent.ConcurrentLinkedQueue +import kotlin.collections.HashMap @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) class DragController : ShipForcesInducer { @@ -48,6 +49,9 @@ class DragController : ShipForcesInducer { @JsonIgnore private var max_height: Double = 563.0 + @JsonIgnore + private var lodDetail: Int = 5 + override fun applyForces(physShip: PhysShip) { val impl = physShip if (blockUpdateQueue.isNotEmpty()) { @@ -80,9 +84,10 @@ class DragController : ShipForcesInducer { if (drag.isFinite && dragPos.isFinite) { physShip.applyInvariantForceToPos(drag, dragPos) } - if (rotDrag.isFinite) { - //dragLogger.info("Center of Pressure: $dragPos") - physShip.applyInvariantTorque(rotDrag) + for (rot in rotDrag) { + if (rot.value.isFinite) { + physShip.applyInvariantForceToPos(rot.value, rot.key) + } } } } @@ -256,31 +261,36 @@ class DragController : ShipForcesInducer { return motionNormal.mul(dragForce, Vector3d()) } - private fun calculateRotationalDrag(ship: PhysShip): Vector3dc { -// val motionVector: Vector3dc = ship.poseVel.omega -// val motionNormal: Vector3dc = motionVector.normalize(Vector3d()).mul(-1.0) -// -// val density = getAirDensityForY(ship.poseVel.pos.y()) -// -// var exposedArea = 0.0 -// -// for (dir in Direction.values()) { -// val surfaceArea = surfaceAreaByDirection[dir]?: continue -// val dot = motionNormal.dot(dir.normal.toJOMLD()) -// if (dot > 0) { -// exposedArea += surfaceArea * dot -// } -// } -// -// val dragForce = DRAG_COEFFICIENT * density * (motionVector.lengthSquared()/2.0) * exposedArea -// -// return motionNormal.mul(dragForce, Vector3d()) + private fun calculateRotationalDrag(ship: PhysShip): Map { + val rotationVector: Vector3dc = ship.omega + + val density = getAirDensityForY(ship.transform.positionInWorld.y(), max_height) + + val totalDragForce: HashMap = HashMap() + val centersOfPressure: EnumMap = EnumMap(net.minecraft.core.Direction::class.java) + + for ((dir, faces) in exposedFaces.entries) { + if (faces.isEmpty()) continue + val centerOfPressure: Vector3d = Vector3d() + faces.forEach { + centerOfPressure.add(it.x().toDouble(), it.y().toDouble(), it.z().toDouble()).add(0.5, 0.5, 0.5) + .add(dir.normal.toJOMLD().mul(0.5, Vector3d())) + } + centerOfPressure.div(faces.size.toDouble()) + centersOfPressure[dir] = centerOfPressure + } - //temporarily just *.99 - var resistance = 0.02 + for (dir in Direction.values()) { + val centerOfPressure = centersOfPressure[dir]?: continue + val distanceToCenter = centerOfPressure.sub(ship.transform.positionInShip) + val velocityVector : Vector3dc = rotationVector.cross(distanceToCenter, Vector3d()) + val dot = velocityVector.normalize(Vector3d()).dot(dir.normal.toJOMLD()) + if (dot < 0) continue + val dragForce = DRAG_COEFFICIENT * density * (velocityVector.lengthSquared()/2.0) * (surfaceAreaByDirection[dir]!! * dot) + totalDragForce[centerOfPressure] = velocityVector.normalize(Vector3d()).mul(-1.0).mul(dragForce, Vector3d()) + } - return ship.omega.mul(ship.momentOfInertia, Vector3d()) - .mul(-resistance) + return totalDragForce } private fun calculateDragPosition(ship: PhysShip): Vector3dc { diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/forces/PocketForcesController.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/forces/PocketForcesController.kt index 0f479c1e3..4cb295238 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/forces/PocketForcesController.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/forces/PocketForcesController.kt @@ -4,18 +4,27 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect import com.fasterxml.jackson.annotation.JsonIgnore import net.minecraft.nbt.CompoundTag import net.minecraft.server.level.ServerLevel +import net.minecraft.util.Mth import org.joml.Vector3d import org.joml.Vector3dc import org.joml.Vector3i import org.joml.Vector3ic import org.valkyrienskies.clockwork.ClockworkAugmentations import org.valkyrienskies.clockwork.ClockworkConfig +import org.valkyrienskies.clockwork.ClockworkMod import org.valkyrienskies.clockwork.content.logistics.gas.utilities.PocketForcesQueueable import org.valkyrienskies.clockwork.kelvin.api.GasType import org.valkyrienskies.clockwork.util.AerodynamicUtils -import org.valkyrienskies.clockwork.util.MathFunctions.chunkPos -import org.valkyrienskies.clockwork.util.MathFunctions.toTriple -import org.valkyrienskies.clockwork.util.MathFunctions.toVector3i +import org.valkyrienskies.clockwork.util.AerodynamicUtils.GRAVITATIONAL_ACCELERATION +import org.valkyrienskies.clockwork.util.AerodynamicUtils.UNIVERSAL_GAS_CONSTANT +import org.valkyrienskies.clockwork.util.AerodynamicUtils.calcPressure +import org.valkyrienskies.clockwork.util.AerodynamicUtils.densityAverage +import org.valkyrienskies.clockwork.util.AerodynamicUtils.densityFromPressureAverage +import org.valkyrienskies.clockwork.util.AerodynamicUtils.extraHeatInfoAverage +import org.valkyrienskies.clockwork.util.AerodynamicUtils.specificHeatAverage +import org.valkyrienskies.clockwork.util.ClockworkUtils.getAirComponentsInChunkClaim +import org.valkyrienskies.clockwork.util.ClockworkUtils.retrieveGasInfoFromPocket +import org.valkyrienskies.clockwork.util.PIDstance import org.valkyrienskies.core.api.ships.PhysShip import org.valkyrienskies.core.api.ships.ServerShip import org.valkyrienskies.core.api.ships.ShipForcesInducer @@ -24,6 +33,7 @@ import org.valkyrienskies.mod.common.shipObjectWorld import java.util.* import java.util.concurrent.ConcurrentLinkedQueue import kotlin.collections.HashMap +import kotlin.math.* @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) class PocketForcesController: ShipForcesInducer { @@ -40,10 +50,19 @@ class PocketForcesController: ShipForcesInducer { @JsonIgnore val temperatures: HashMap = HashMap() @JsonIgnore + val pressures: HashMap = HashMap() + @JsonIgnore private val pocketQueue: ConcurrentLinkedQueue = ConcurrentLinkedQueue() @JsonIgnore private val pocketRemoveQueue: ConcurrentLinkedQueue = ConcurrentLinkedQueue() + @JsonIgnore + private val pocketsOnRemoval = HashMap() + + @JsonIgnore + private val pidstances = HashMap() + + // Todo: Implement serialization override fun applyForces(physShip: PhysShip) { @@ -63,8 +82,12 @@ class PocketForcesController: ShipForcesInducer { val buoyancyForce = calculateBuoyancyForce(physShip) + //ClockworkMod.LOGGER.info(physShip.mass.toString()) + buoyancyForce.forEach { - physShipImpl.applyInvariantForceToPos(Vector3d(0.0, it.value, 0.0), Vector3d(pocketCenters[it.key]!!)) + if (it.value.isFinite() && !it.value.isNaN()) { //just to be safe + physShipImpl.applyInvariantForceToPos(Vector3d(0.0, it.value, 0.0), Vector3d(pocketCenters[it.key]!!).sub(physShip.transform.positionInShip)) + } } } @@ -74,6 +97,7 @@ class PocketForcesController: ShipForcesInducer { pocketCenters[pocket.rootPos] = pocket.pocketCenter gasMasses[pocket.rootPos] = pocket.gasMasses temperatures[pocket.rootPos] = pocket.temperature + pressures[pocket.rootPos] = pocket.pressure } fun calculateBuoyancyForce(physShip: PhysShip): Map { @@ -85,19 +109,23 @@ class PocketForcesController: ShipForcesInducer { if (pocketRoots[it]!! > 0) { if (gasMasses.containsKey(it) && temperatures.containsKey(it)) { val gasMassesLocal = gasMasses[it]!! - val temperature = temperatures[it]!! - var totalInternalDensity = 0.0 - for (gas in GasType.entries) { - if (gasMassesLocal.containsKey(gas)) { - val gasMass = gasMassesLocal[gas] ?: 0.0 - - val density = getDensityFromTemperature(pocketRoots[it]!!.toDouble(), gasMass, temperature, gas) - totalInternalDensity += density - } - } + val temperature = max(temperatures[it]!!, 0.0001) + val pressure = pressures[it]!! + var totalInternalDensity = gasMassesLocal.values.sum() / pocketRoots[it]!!.toDouble() //gasMassesLocal.values.sum() / pocketRoots[it]!!.toDouble() +// for (gas in GasType.entries) { +// if (gasMassesLocal.containsKey(gas)) { +// val gasMass = gasMassesLocal[gas] ?: 0.0 +// if (gasMass == 0.0) continue +// +// val density = getDensityFromTemperature(pocketRoots[it]!!.toDouble(), gasMass, temperature, gas) +// totalInternalDensity += density +// } +// } if (totalInternalDensity != 0.0) { - val buoyantForce = pocketRoots[it]!!.toDouble() * (AerodynamicUtils.getAirDensityForY(physShip.transform.positionInWorld.y(), max_height) - totalInternalDensity) * ClockworkConfig.SERVER.balloonForceMult - totalBuoyantForce[it] = buoyantForce + //ClockworkMod.LOGGER.info("Density at Y: " + AerodynamicUtils.getAirDensityForY(physShip.transform.positionInWorld.y(), max_height).toString()) + //ClockworkMod.LOGGER.info("Internal Density: $totalInternalDensity") + val buoyantForce = (pocketRoots[it]!!.toDouble() * (AerodynamicUtils.getAirDensityForY(physShip.transform.positionInWorld.y(), max_height) - totalInternalDensity) * GRAVITATIONAL_ACCELERATION) * ClockworkConfig.SERVER.balloonForceMult + totalBuoyantForce[it] = max(buoyantForce, 0.0) } } } @@ -106,63 +134,201 @@ class PocketForcesController: ShipForcesInducer { return totalBuoyantForce } - private fun getDensityFromTemperature(volume: Double, mass: Double, temperature: Double, gasType: GasType): Double { - if (volume == 0.0) return 0.0 + fun gameTick(level: ServerLevel, ship: ServerShip) { + val loadedShip = level.shipObjectWorld.loadedShips.getById(ship.id) ?: return - var density = mass / volume + val roots = getAirComponentsInChunkClaim(loadedShip.chunkClaim, level, ClockworkAugmentations.getComponentAugmentation("temperature")) + for (root: Vector3i in roots.keys) { + val gasMap = retrieveGasInfoFromPocket(root, level) + val pressure = level.shipObjectWorld.getAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("pressure"), root.x(), root.y(), root.z(), level.dimensionId) - if (temperature != 0.0) { - val molarMass = gasType.density * 22.4 - val pressure = calcPressure(mass, volume, temperature, gasType) - density = (molarMass * pressure) / (8.31446261815324 * temperature) + val componentSize = roots[root] ?: continue + + val collectX = level.shipObjectWorld.collectAirAugmentation(level.shipObjectWorld.createDoubleSumAugmentation("core", "x"), root.x(), root.y(), root.z(), level.dimensionId) + val collectY = level.shipObjectWorld.collectAirAugmentation(level.shipObjectWorld.createDoubleSumAugmentation("core", "y"), root.x(), root.y(), root.z(), level.dimensionId) + val collectZ = level.shipObjectWorld.collectAirAugmentation(level.shipObjectWorld.createDoubleSumAugmentation("core", "z"), root.x(), root.y(), root.z(), level.dimensionId) + + val center = Vector3d(collectX / componentSize.toDouble(), collectY / componentSize.toDouble(), collectZ / componentSize.toDouble()) + + center.add(0.5, 0.5, 0.5) + + pocketQueue.add(PocketForcesQueueable(root, center, componentSize, gasMap.first, gasMap.second, pressure)) + gametickKnownPocketRoots.add(root) + //ClockworkMod.LOGGER.info("Added pocket with root: ($root)") } - return density - } - /** - * Calculates pressure using the ideal gas law. - */ - private fun calcPressure(mass: Double, volume: Double, temp: Double, gasType: GasType): Double { - var pressure = 0.0 - val molarMass = gasType.density * 22.4 - val moles = mass / molarMass - pressure = ((moles) * 8.31446261815324 * temp) / volume - return pressure - } + for (root in gametickKnownPocketRoots) { + if (roots[root] != null) { + val rootYInWorld = ship.transform.shipToWorld.transformPosition(Vector3d(root.x().toDouble(), root.y().toDouble(), root.z().toDouble())).y + 0.5 + val atmosphericDensityAtRoot = AerodynamicUtils.getAirDensityForY(rootYInWorld, max_height) + val atmosphericPressureAtRoot = AerodynamicUtils.getAirPressureForY(rootYInWorld, max_height) + val atmosphericTemperatureAtRoot = AerodynamicUtils.getAirTemperatureForY(rootYInWorld, max_height) + val requiredMassForDensity = atmosphericDensityAtRoot * roots[root]!!.toDouble() + if (level.shipObjectWorld.getAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("airupdated"), root.x(), root.y(), root.z(), level.dimensionId) < 1.0 && roots[root]!! > 0) { - fun gameTick(level: ServerLevel, ship: ServerShip) { - val loadedShip = level.shipObjectWorld.loadedShips.getById(ship.id) ?: return + level.shipObjectWorld.setAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("gas_air"), root.x(), root.y(), root.z(), level.dimensionId, requiredMassForDensity) + level.shipObjectWorld.setAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("airupdated"), root.x(), root.y(), root.z(), level.dimensionId, 1.0) + //ClockworkMod.LOGGER.info("Removed pocket with root: ($root)") + } + val isSealed = level.shipObjectWorld.collectAirAugmentation(ClockworkAugmentations.getAugmentation("sealed"), root.x(), root.y(), root.z(), level.dimensionId) > 0.0 + if (!isSealed && roots[root]!! > 0) { + + if (!pidstances.containsKey(root)) { + pidstances[root] = PIDstance(p = 0.5) + } - val temperature = level.shipObjectWorld.getFromEachAirComponent(ClockworkAugmentations.getComponentAugmentation("temperature"), level.dimensionId).filter { loadedShip.chunkClaim.contains(it.key.chunkPos().x, it.key.chunkPos().z) } - val air = level.shipObjectWorld.getFromEachAirComponent(ClockworkAugmentations.getComponentAugmentation("gas_air"), level.dimensionId).filter { loadedShip.chunkClaim.contains(it.key.chunkPos().x, it.key.chunkPos().z) } - val phlogiston = level.shipObjectWorld.getFromEachAirComponent(ClockworkAugmentations.getComponentAugmentation("gas_phlogiston"), level.dimensionId).filter { loadedShip.chunkClaim.contains(it.key.chunkPos().x, it.key.chunkPos().z) } - val helium = level.shipObjectWorld.getFromEachAirComponent(ClockworkAugmentations.getComponentAugmentation("gas_helium"), level.dimensionId).filter { loadedShip.chunkClaim.contains(it.key.chunkPos().x, it.key.chunkPos().z) } + var static = false // this causes problems,,,, very annoying!!!!1! - for (root: Triple in temperature.keys) { - val gasMap = EnumMap(GasType::class.java) - gasMap[GasType.AIR] = air[root] ?: 0.0 - gasMap[GasType.PHLOGISTON] = phlogiston[root] ?: 0.0 - gasMap[GasType.HELIUM] = helium[root] ?: 0.0 + //leaking + var (gasMassesInternal, temperatureInternal) = retrieveGasInfoFromPocket(root, level) + temperatureInternal = max(temperatureInternal, 0.0001) + val internalPressure = calcPressure(gasMassesInternal.values.sum(), roots[root]!!.toDouble(), temperatureInternal, + densityAverage(gasMassesInternal)) + var internalDensity = densityFromPressureAverage(gasMassesInternal, temperatureInternal, internalPressure) - val componentSize = level.shipObjectWorld.getAirComponentSize(root.first, root.second, root.third, level.dimensionId) + //if (internalDensity.absoluteValue < 0.001) static = true - val collectX = level.shipObjectWorld.collectAirAugmentation(level.shipObjectWorld.createDoubleSumAugmentation("core", "x"), root.first, root.second, root.third, level.dimensionId) - val collectY = level.shipObjectWorld.collectAirAugmentation(level.shipObjectWorld.createDoubleSumAugmentation("core", "y"), root.first, root.second, root.third, level.dimensionId) - val collectZ = level.shipObjectWorld.collectAirAugmentation(level.shipObjectWorld.createDoubleSumAugmentation("core", "z"), root.first, root.second, root.third, level.dimensionId) + val leakRate = ClockworkConfig.SERVER.pocketLeakageRate + val leakOrificeSize = ((roots[root]!!.toDouble() * 2.0) / 3.0) * 0.1 - val center = Vector3d(collectX / componentSize.toDouble(), collectY / componentSize.toDouble(), collectZ / componentSize.toDouble()) + val massInFlowRate: Double + val massOutFlowRate: Double + var combinedMassFlowRate: Double = 0.0 + var totalDeltaMass: Double = 0.0 + + if (leakOrificeSize < 0.001 && leakOrificeSize != 0.0) static = true + + if (leakOrificeSize != 0.0) { + val newGasMasses = gasMassesInternal.clone() as EnumMap + + val pressureDifference = internalPressure - atmosphericPressureAtRoot + val chokedFlow = (internalPressure/atmosphericPressureAtRoot > 1.89) + val adjustment = pidstances[root]!!.control(requiredMassForDensity, gasMassesInternal.values.sum(), 0.75) + val (averageSpecificGasConstant, averageSutherlandConstant, averageAdiabaticIndex) = extraHeatInfoAverage(gasMassesInternal) + if (pressureDifference >= 0.001 || pressureDifference <= -0.001 && !static) { + //leaking out + if (pressureDifference >= 0.0001) { + massOutFlowRate = if (!chokedFlow) { + leakRate * leakOrificeSize * sqrt(2.0 * internalDensity * pressureDifference.absoluteValue) + } else { + leakRate * leakOrificeSize * internalPressure * sqrt(averageAdiabaticIndex / (averageSpecificGasConstant * temperatureInternal)) * Math.pow( + (averageAdiabaticIndex + 1.0) / 2.0, + -((averageAdiabaticIndex + 1.0) / (2.0 * (averageAdiabaticIndex - 1.0))) + ) + } * -1.0 - adjustment - pocketQueue.add(PocketForcesQueueable(root.toVector3i(), center, componentSize, gasMap, temperature[root] ?: 0.0)) - gametickKnownPocketRoots.add(root.toVector3i()) + + if (massOutFlowRate.absoluteValue <= 0.001 || massOutFlowRate.isNaN() || massOutFlowRate.isInfinite()) { + static = true + } + } else { + massOutFlowRate = 0.0 + } + // leaking in + if (pressureDifference <= -0.0001) { + massInFlowRate = if (!chokedFlow) { + (leakRate * leakOrificeSize * sqrt(2.0 * atmosphericDensityAtRoot * pressureDifference.absoluteValue)) + } else { + (leakRate * leakOrificeSize * atmosphericPressureAtRoot * sqrt(1.4 / (287.0 * atmosphericTemperatureAtRoot)) * Math.pow( + (1.4 + 1.0) / 2.0, + -((1.4 + 1.0) / (2.0 * (1.4 - 1.0))) + )) + } + adjustment + + + if (massInFlowRate.absoluteValue <= 0.001 || massInFlowRate.isNaN() || massInFlowRate.isInfinite()) { + static = true + } + } else { + massInFlowRate = 0.0 + } + } else { + massInFlowRate = 0.0 + massOutFlowRate = 0.0 + totalDeltaMass = 0.0 + static = true + } + + combinedMassFlowRate = (massInFlowRate + massOutFlowRate) / 20.0 + + forEachGas@ for (gas in GasType.entries) { + if (gasMassesInternal.containsKey(gas)) { + if (gasMassesInternal[gas]!! <= 0.0) { + newGasMasses[gas] = 0.0 + continue + } + val limit: Double = gasMassesInternal[gas]!! / 2.0 + + val deltaMass = Mth.clamp(combinedMassFlowRate, -limit, if (gas == GasType.AIR) requiredMassForDensity/2.0 else 0.0) * 0.1 + + newGasMasses[gas] = gasMassesInternal[gas]!! + deltaMass + newGasMasses[gas] = max(newGasMasses[gas]!!, 0.0) + totalDeltaMass += deltaMass + } + } + + if (newGasMasses.values.sum() <= 0.0 || (totalDeltaMass < 0.0001 && totalDeltaMass > -0.0001)) { + static = true + } + ////outflow + // temperatureInternal * ((newGasMasses.values.sum() / gasMassesInternal.values.sum())).pow(averageAdiabaticIndex - 1.0) + // else if (pressureDifference <= -0.001) { + // //inflow + // (gasMassesInternal.values.sum() * temperatureInternal + totalDeltaMass * atmosphericTemperatureAtRoot) / (gasMassesInternal.values.sum() + totalDeltaMass) + // } + + val pretendSurfaceArea = 4 * Math.PI * (roots[root]!!.toDouble().pow(2.0 / 3.0)) + + val pretendConductionCoefficient = 0.025 + + val specificHeatAverage = min(specificHeatAverage(newGasMasses), 0.0001) + + val conduction = Mth.clamp((pretendConductionCoefficient * pretendSurfaceArea * (atmosphericTemperatureAtRoot - temperatureInternal) / 20.0) / (newGasMasses.values.sum() * specificHeatAverage), -5.0, 5.0) + +// val deltaPocketTemperature = Mth.clamp(if (pressureDifference >= 0.001 || pressureDifference <= -0.001) { +// (gasMassesInternal.values.sum() * temperatureInternal + massInFlowRate * atmosphericTemperatureAtRoot / 20.0 - massOutFlowRate * temperatureInternal / 20.0) / if(newGasMasses.values.sum() >= 0.0001) newGasMasses.values.sum() else 0.0001 +// } else { +// temperatureInternal +// } + conduction, 0.0001, 5772.0) - temperatureInternal + + val newPocketTemperature = max(min(temperatureInternal + conduction, 5772.0), 0.0001) + + val newPocketPressure = calcPressure(newGasMasses.values.sum(), roots[root]!!.toDouble(), newPocketTemperature, densityAverage(newGasMasses)) + if (!newPocketPressure.isNaN() && newPocketPressure.isFinite()) level.shipObjectWorld.setAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("pressure"), root.x(), root.y(), root.z(), level.dimensionId, newPocketPressure) + if (!newPocketTemperature.isNaN() && newPocketTemperature.isFinite()) level.shipObjectWorld.setAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("temperature"), root.x(), root.y(), root.z(), level.dimensionId, newPocketTemperature) + + + //ClockworkMod.LOGGER.info("delta mass: $totalDeltaMass // pressure diff: $pressureDifference // temperature: $temperatureInternal // conduction: $conduction, // new temperature: $newPocketTemperature") + if (!static) { + for (gas in GasType.entries) { + if (newGasMasses[gas] != null && newGasMasses[gas]!!.isFinite() && !newGasMasses[gas]!!.isNaN()) level.shipObjectWorld.setAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("gas_" + gas.name.lowercase()), root.x(), root.y(), root.z(), level.dimensionId, newGasMasses[gas]!!) + } + } else { + //ClockworkMod.LOGGER.info("Static pocket at: $root") + } + + //incredible fix. insanity. truly. + } + } + } } - val toRemove = HashSet() - for (root in gametickKnownPocketRoots) { - if (!temperature.containsKey(root.toTriple())) { - pocketRemoveQueue.add(root) - toRemove.add(root) + + val toRemove = ArrayList(gametickKnownPocketRoots.filterNot { roots[it] != null }) + while (toRemove.isNotEmpty()) { + val polled = toRemove.removeFirst() + if (pocketsOnRemoval.containsKey(polled)) { + pocketsOnRemoval[polled] = pocketsOnRemoval[polled]!! + 1 + } else { + pocketsOnRemoval[polled] = 1 + } + if (pocketsOnRemoval[polled]!! > 4) { + pocketRemoveQueue.add(polled) + pocketsOnRemoval.remove(polled) + gametickKnownPocketRoots.remove(polled) + pidstances.remove(polled) + //ClockworkMod.LOGGER.info("Removed pocket with root: ($polled)") } } - gametickKnownPocketRoots.removeAll(toRemove) } companion object { diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/kinetics/casing/ExtendedEncasedShaftBlock.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/kinetics/casing/ExtendedEncasedShaftBlock.kt index e72ead22a..5eeb3525f 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/kinetics/casing/ExtendedEncasedShaftBlock.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/kinetics/casing/ExtendedEncasedShaftBlock.kt @@ -1,66 +1,68 @@ -//package org.valkyrienskies.clockwork.content.kinetics.casing -// -// -//import com.simibubi.create.AllBlocks -//import com.simibubi.create.content.decoration.encasing.CasingBlock -//import com.simibubi.create.content.decoration.encasing.EncasedBlock -//import com.simibubi.create.content.kinetics.base.AbstractEncasedShaftBlock -//import com.simibubi.create.content.kinetics.base.KineticBlockEntity -//import com.simibubi.create.content.schematics.requirement.ISpecialBlockItemRequirement -//import com.simibubi.create.content.schematics.requirement.ItemRequirement -//import com.simibubi.create.foundation.block.IBE -//import com.tterrag.registrate.util.entry.BlockEntry -//import net.minecraft.core.NonNullList -//import net.minecraft.world.InteractionResult -//import net.minecraft.world.item.CreativeModeTab -//import net.minecraft.world.item.ItemStack -//import net.minecraft.world.item.context.UseOnContext -//import net.minecraft.world.level.block.Block -//import net.minecraft.world.level.block.entity.BlockEntity -//import net.minecraft.world.level.block.entity.BlockEntityType -//import net.minecraft.world.level.block.state.BlockState -//import org.valkyrienskies.clockwork.ClockworkBlockEntities -//import org.valkyrienskies.clockwork.ClockworkBlocks -// -//class ExtendedEncasedShaftBlock // public static EncasedShaftBlock brass(Properties properties) { -//// return new EncasedShaftBlock(properties, AllBlocks.BRASS_CASING); -//// } -//protected constructor(properties: Properties, private val casing: BlockEntry) : -// AbstractEncasedShaftBlock(properties), -// IBE, ISpecialBlockItemRequirement, -// EncasedBlock { -// override fun getCasing(): Block { -// return this +package org.valkyrienskies.clockwork.content.kinetics.casing + + +import com.simibubi.create.AllBlocks +import com.simibubi.create.content.decoration.encasing.CasingBlock +import com.simibubi.create.content.decoration.encasing.EncasedBlock +import com.simibubi.create.content.kinetics.base.AbstractEncasedShaftBlock +import com.simibubi.create.content.kinetics.base.KineticBlockEntity +import com.simibubi.create.content.schematics.requirement.ISpecialBlockItemRequirement +import com.simibubi.create.content.schematics.requirement.ItemRequirement +import com.simibubi.create.foundation.block.IBE +import com.tterrag.registrate.util.entry.BlockEntry +import net.minecraft.core.NonNullList +import net.minecraft.world.InteractionResult +import net.minecraft.world.item.CreativeModeTab +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.context.UseOnContext +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import org.valkyrienskies.clockwork.ClockworkBlockEntities +import org.valkyrienskies.clockwork.ClockworkBlocks + +class ExtendedEncasedShaftBlock +// public static EncasedShaftBlock brass(Properties properties) { +// return new EncasedShaftBlock(properties, AllBlocks.BRASS_CASING); // } -// -// override fun fillItemCategory(pTab: CreativeModeTab, pItems: NonNullList) {} -// override fun onSneakWrenched(state: BlockState, context: UseOnContext): InteractionResult { -// if (context.level.isClientSide) return InteractionResult.SUCCESS -// context.level -// .levelEvent(2001, context.clickedPos, getId(state)) -// KineticBlockEntity.switchToBlockState( -// context.level, context.clickedPos, -// AllBlocks.SHAFT.defaultState -// .setValue(AXIS, state.getValue(AXIS)) -// ) -// return InteractionResult.SUCCESS -// } -// -// override fun getRequiredItems(state: BlockState, te: BlockEntity): ItemRequirement { -// return ItemRequirement.of(AllBlocks.SHAFT.defaultState, te) -// } -// -// override fun getBlockEntityClass(): Class { -// return KineticBlockEntity::class.java -// } -// -// override fun getBlockEntityType(): BlockEntityType { -// return ClockworkBlockEntities.EXTENDED_ENCASED_SHAFT.get() -// } -// -// companion object { -// fun balloon(properties: Properties): ExtendedEncasedShaftBlock { -// return ExtendedEncasedShaftBlock(properties, ClockworkBlocks.BALLOON_CASING) -// } -// } -//} \ No newline at end of file +protected constructor(properties: Properties, private val casing: BlockEntry) : + AbstractEncasedShaftBlock(properties), + IBE, ISpecialBlockItemRequirement, + EncasedBlock { + override fun getCasing(): Block { + return this + } + + override fun fillItemCategory(pTab: CreativeModeTab, pItems: NonNullList) {} + override fun onSneakWrenched(state: BlockState, context: UseOnContext): InteractionResult { + if (context.level.isClientSide) return InteractionResult.SUCCESS + context.level + .levelEvent(2001, context.clickedPos, getId(state)) + KineticBlockEntity.switchToBlockState( + context.level, context.clickedPos, + AllBlocks.SHAFT.defaultState + .setValue(AXIS, state.getValue(AXIS)) + ) + return InteractionResult.SUCCESS + } + + override fun getRequiredItems(state: BlockState, te: BlockEntity): ItemRequirement { + return ItemRequirement.of(AllBlocks.SHAFT.defaultState, te) + } + + override fun getBlockEntityClass(): Class { + return KineticBlockEntity::class.java + } + + override fun getBlockEntityType(): BlockEntityType { + TODO() + //return ClockworkBlockEntities.EXTENDED_ENCASED_SHAFT.get() + } + + companion object { + fun balloon(properties: Properties): ExtendedEncasedShaftBlock { + return ExtendedEncasedShaftBlock(properties, ClockworkBlocks.BALLOON_CASING) + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/kinetics/resistor/RedstoneResistorRenderer.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/kinetics/resistor/RedstoneResistorRenderer.kt index e2ffa4a69..0cd860e80 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/kinetics/resistor/RedstoneResistorRenderer.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/kinetics/resistor/RedstoneResistorRenderer.kt @@ -45,7 +45,6 @@ class RedstoneResistorRenderer(context: BlockEntityRendererProvider.Context?) : kineticRotationTransform(superByteBuffer, te, axis, angle, light) superByteBuffer.renderInto(ms, buffer.getBuffer(RenderType.solid())) } - val state = 0f val resistorState = te.blockState val vb = buffer.getBuffer(RenderType.solid()) val color = Color.mixColors(0x2C0300, 0xCD0000, resistor.state / 15f) diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/IHeatableBlockEntity.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/IHeatableBlockEntity.kt index 84d019075..69db270b1 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/IHeatableBlockEntity.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/IHeatableBlockEntity.kt @@ -3,7 +3,6 @@ package org.valkyrienskies.clockwork.content.logistics.gas import com.simibubi.create.content.equipment.goggles.IHaveGoggleInformation import net.minecraft.ChatFormatting import net.minecraft.network.chat.Component -import net.minecraft.network.chat.Style import net.minecraft.network.chat.TextComponent import org.valkyrienskies.clockwork.ClockworkMod import org.valkyrienskies.clockwork.kelvin.api.DuctNodePos @@ -25,10 +24,10 @@ interface IHeatableBlockEntity: IHaveGoggleInformation { tooltip.add(TextComponent("Pressure: ${(ClockworkMod.getKelvin().getPressureAt(this.getDuctNodePosition())/1000).roundToInt()} KPa").withStyle(ChatFormatting.BLUE)) found = true } - if (ClockworkMod.getKelvin().getGasVolumesAt(this.getDuctNodePosition()).isNotEmpty()) { - tooltip.add(TextComponent("Gas Volumes:")) - for (entry in ClockworkMod.getKelvin().getGasVolumesAt(this.getDuctNodePosition()).entries) { - tooltip.add(TextComponent("${entry.key}: ${entry.value.roundToInt()} m^3")) + if (ClockworkMod.getKelvin().getGasMassAt(this.getDuctNodePosition()).isNotEmpty()) { + tooltip.add(TextComponent("Gas Masses:")) + for (entry in ClockworkMod.getKelvin().getGasMassAt(this.getDuctNodePosition()).entries) { + tooltip.add(TextComponent("${entry.key}: ${entry.value.roundToInt()} kg")) } found = true } diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/duct/INodeBlock.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/duct/INodeBlock.kt index 88b33518d..5f0c11ad9 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/duct/INodeBlock.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/duct/INodeBlock.kt @@ -44,6 +44,7 @@ interface INodeBlock : IDuct { } } + //TODO: Remove this fun _updateShape(state: BlockState, direction: Direction, neighborState: BlockState, diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/coal_burner/CoalBurnerBlockEntity.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/coal_burner/CoalBurnerBlockEntity.kt index 8671ddb56..47f676c1e 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/coal_burner/CoalBurnerBlockEntity.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/coal_burner/CoalBurnerBlockEntity.kt @@ -33,7 +33,7 @@ class CoalBurnerBlockEntity(type: BlockEntityType<*>?, pos: BlockPos?, state: Bl if (fuelTicks>0) { fuelTicks-=1 - if (node.network.getTemperatureAt(blockPos.toJOMLD())<450.0) node.network.modTemperature(blockPos.toJOMLD(),30.0) + if (node.network.getTemperatureAt(blockPos.toJOMLD())<2000.0) node.network.modTemperature(blockPos.toJOMLD(),30.0) if (blockState.getValue(CoalBurnerBlock.LIT)==false) level!!.setBlock(blockPos,blockState.setValue(CoalBurnerBlock.LIT,true), 15) } else { diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/compressor/AirCompressorBlockEntity.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/compressor/AirCompressorBlockEntity.kt index a25a7ec86..639f4b920 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/compressor/AirCompressorBlockEntity.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/compressor/AirCompressorBlockEntity.kt @@ -29,7 +29,7 @@ class AirCompressorBlockEntity(typeIn: BlockEntityType<*>?, pos: BlockPos?, stat if (level!!.isClientSide) return val node = ClockworkMod.getKelvin().getNodeAt(blockPos.toJOMLD()) ?: return val speed = abs(getSpeed()) - val currentAirVolume = node.network.getGasVolumesAt(blockPos.toJOMLD())[GasType.AIR]?: 0.0 + val currentAirVolume = node.network.getGasMassAt(blockPos.toJOMLD())[GasType.AIR]?: 0.0 if (speed>0 && currentAirVolume?, pos: BlockPos?, stat isOn = true val deltaVolume = Mth.clamp(maxGas-currentAirVolume,0.0001, baselineSpeed*speed) - node.network.modGasVolumeOfTemperature(getDuctNodePosition(),GasType.AIR, deltaVolume, 300.0) + node.network.modGasMassOfTemperature(getDuctNodePosition(),GasType.AIR, deltaVolume, 300.0) } else { if (isOn) syncOn(false) isOn = false diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/compressor/AirCompressorPacket.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/compressor/AirCompressorPacket.kt index c47b06a69..6a371d3a7 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/compressor/AirCompressorPacket.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/compressor/AirCompressorPacket.kt @@ -28,7 +28,7 @@ class AirCompressorPacket: S2CCWPacket { override fun handle(context: ClientNetworkContext) { context.enqueueWork { - val be = Minecraft.getInstance().level?.getBlockEntity(pos) as AirCompressorBlockEntity + val be = Minecraft.getInstance().level?.getBlockEntity(pos) as AirCompressorBlockEntity? ?: return@enqueueWork be.isOn = isOn } diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/creative_generator/CreativeGeneratorBlockEntity.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/creative_generator/CreativeGeneratorBlockEntity.kt index 577070f3c..fa6d9814f 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/creative_generator/CreativeGeneratorBlockEntity.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/generation/creative_generator/CreativeGeneratorBlockEntity.kt @@ -26,14 +26,14 @@ class CreativeGeneratorBlockEntity(type: BlockEntityType<*>?, pos: BlockPos?, st super.tick() if (level!!.isClientSide) return val node = ClockworkMod.getKelvin().getNodeAt(blockPos.toJOMLD()) ?: return - val volumes = node.network.getGasVolumesAt(getDuctNodePosition()) + val volumes = node.network.getGasMassAt(getDuctNodePosition()) for (gas in gasValues.keys) { if (gasValues[gas] == 0 ) continue val gasVolume: Double if (volumes[gas] == null) gasVolume = 0.0 else gasVolume = volumes[gas]!! - node.network.modGasVolumeOfTemperature(getDuctNodePosition(), gas, max(gasValues[gas]!!.toDouble()-gasVolume, 0.0),temperature) + node.network.modGasMassOfTemperature(getDuctNodePosition(), gas, max(gasValues[gas]!!.toDouble()-gasVolume, 0.0),temperature) } diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzleBlock.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzleBlock.kt new file mode 100644 index 000000000..677264a16 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzleBlock.kt @@ -0,0 +1,44 @@ +package org.valkyrienskies.clockwork.content.logistics.gas.pockets.nozzle + +import com.simibubi.create.content.kinetics.base.HorizontalKineticBlock +import com.simibubi.create.foundation.block.IBE +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.world.level.Level +import net.minecraft.world.level.LevelReader +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import org.valkyrienskies.clockwork.ClockworkBlockEntities +import org.valkyrienskies.clockwork.content.logistics.gas.duct.INodeBlock + +class GasNozzleBlock(properties: Properties): HorizontalKineticBlock(properties), IBE, INodeBlock { + override fun getBlockEntityClass(): Class { + return GasNozzleBlockEntity::class.java + } + + override fun getBlockEntityType(): BlockEntityType { + return ClockworkBlockEntities.GAS_NOZZLE.get() + } + + override fun onPlace(state: BlockState, level: Level, pos: BlockPos, oldState: BlockState, isMoving: Boolean) { + super.onPlace(state, level, pos, oldState, isMoving) + _onPlace(state, level, pos, oldState, isMoving) + } + + override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, isMoving: Boolean) { + _onRemove(state, level, pos, newState, isMoving) + super.onRemove(state, level, pos, newState, isMoving) + } + + override fun getRotationAxis(state: BlockState): Direction.Axis { + return state.getValue(HORIZONTAL_FACING).clockWise.axis + } + + override fun hasShaftTowards(world: LevelReader, pos: BlockPos, state: BlockState, face: Direction): Boolean { + if (face.axis == state.getValue(HORIZONTAL_FACING).clockWise.axis) { + return true + } + return super.hasShaftTowards(world, pos, state, face) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzleBlockEntity.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzleBlockEntity.kt new file mode 100644 index 000000000..fc1dc3d77 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzleBlockEntity.kt @@ -0,0 +1,293 @@ +package org.valkyrienskies.clockwork.content.logistics.gas.pockets.nozzle + +import com.simibubi.create.content.kinetics.base.KineticBlockEntity +import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour +import com.simibubi.create.foundation.utility.animation.LerpedFloat +import net.minecraft.ChatFormatting +import net.minecraft.core.BlockPos +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.TextComponent +import net.minecraft.server.level.ServerLevel +import net.minecraft.util.Mth +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import org.valkyrienskies.clockwork.ClockworkAugmentations +import org.valkyrienskies.clockwork.ClockworkMod +import org.valkyrienskies.clockwork.ClockworkPackets +import org.valkyrienskies.clockwork.content.logistics.gas.IHeatableBlockEntity +import org.valkyrienskies.clockwork.content.logistics.gas.generation.compressor.AirCompressorPacket +import org.valkyrienskies.clockwork.kelvin.api.DuctNodePos +import org.valkyrienskies.clockwork.kelvin.api.GasType +import org.valkyrienskies.clockwork.util.AerodynamicUtils +import org.valkyrienskies.clockwork.util.AerodynamicUtils.calcPressure +import org.valkyrienskies.clockwork.util.AerodynamicUtils.calculateFlow +import org.valkyrienskies.clockwork.util.AerodynamicUtils.densityAverage +import org.valkyrienskies.clockwork.util.AerodynamicUtils.densityFromPressureAverage +import org.valkyrienskies.clockwork.util.AerodynamicUtils.dynamicViscosityAverage +import org.valkyrienskies.clockwork.util.AerodynamicUtils.specificHeatAverage +import org.valkyrienskies.clockwork.util.ClockworkUtils.retrieveGasInfoFromPocket +import org.valkyrienskies.clockwork.util.PIDstance +import org.valkyrienskies.mod.common.dimensionId +import org.valkyrienskies.mod.common.getShipObjectManagingPos +import org.valkyrienskies.mod.common.shipObjectWorld +import org.valkyrienskies.mod.common.util.toJOML +import org.valkyrienskies.mod.common.util.toJOMLD +import java.util.* +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +class GasNozzleBlockEntity(type: BlockEntityType<*>?, pos: BlockPos?, state: BlockState?): KineticBlockEntity(type, pos, state), IHeatableBlockEntity { + + var hasPocket = false + val pointer: LerpedFloat = LerpedFloat.linear() + .startWithValue(0.5) + .chase(0.5, 0.0, LerpedFloat.Chaser.LINEAR) + + var currentIdealOutput: Double = 0.0 + + val pid = PIDstance() + + var clientPocketTemperature: Double = 0.0 + + override fun addBehaviours(behaviours: MutableList?) { + return + } + + override fun tick() { + super.tick() + + pointer.tickChaser() + if (level == null || level!!.isClientSide) return + + val serverLevel = level!! as ServerLevel + + hasPocket = try { + serverLevel.shipObjectWorld.getAirComponentSize(blockPos.x, blockPos.y+1, blockPos.z, serverLevel.dimensionId) > 0 + } catch (e: IllegalArgumentException) { + pid.resetIntegral() + false + } + + if (hasPocket) { + //flowIntoPocket() + if (serverLevel.shipObjectWorld.getAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("airupdated"), blockPos.x, blockPos.y +1, blockPos.z, serverLevel.dimensionId) < 1.0) { + serverLevel.shipObjectWorld.setAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("airupdated"), blockPos.x, blockPos.y +1, blockPos.z, serverLevel.dimensionId, 0.0) + } + heatPocket() + } + + } + + override fun onSpeedChanged(previousSpeed: Float) { + super.onSpeedChanged(previousSpeed) + val speed = getSpeed() + val target = (if (speed > 0) 1 else 0).toDouble() + pointer.chase(target, getChaseSpeed().toDouble(), LerpedFloat.Chaser.LINEAR) + ClockworkPackets.sendToNear(level, blockPos, 100, GasNozzlePacket(target, blockPos)) + sendData() + } + + fun getChaseSpeed(): Float { + return Mth.clamp(abs(getSpeed()) / 16f / 20f / 10f, 0f, 1f) + } + + private fun heatPocket() { + val serverLevel = level!! as ServerLevel + + val realY = if (level.getShipObjectManagingPos(blockPos) != null) { + level.getShipObjectManagingPos(blockPos)!!.transform.shipToWorld.transformPosition(blockPos.toJOMLD()).y + 0.5 + } else { + blockPos.y + 0.5 + } + + val pocketRef = blockPos.above() + val (pocketGasVolumes, pocketTemperature) = retrieveGasInfoFromPocket(pocketRef.toJOML(), serverLevel) + val pocketVolume = serverLevel.shipObjectWorld.getAirComponentSize(pocketRef.x, pocketRef.y, pocketRef.z, serverLevel.dimensionId).toDouble() + val pocketTotalMass = pocketGasVolumes.values.sum() + val pocketAvgDensity = densityAverage(pocketGasVolumes) + val pocketAvgViscosity = dynamicViscosityAverage(pocketGasVolumes, pocketTemperature) + val pocketPressure = serverLevel.shipObjectWorld.getAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("pressure"), pocketRef.x, pocketRef.y, pocketRef.z, serverLevel.dimensionId) + + val currentNodeTemperature = ClockworkMod.getKelvin().getTemperatureAt(blockPos.toJOMLD()) + val currentNodePressure = ClockworkMod.getKelvin().getPressureAt(blockPos.toJOMLD()) + val currentNodeGasVolumes = ClockworkMod.getKelvin().getGasMassAt(blockPos.toJOMLD()) + val currentNodeTotalMass = currentNodeGasVolumes.values.sum() + val currentNodeAvgViscosity = dynamicViscosityAverage(currentNodeGasVolumes, currentNodeTemperature) + val currentNodeAvgSpecificHeat = specificHeatAverage(currentNodeGasVolumes) + + val newNodeMasses = EnumMap(GasType::class.java) + + if (currentNodeTotalMass <= 0.0001 || pocketTotalMass <= 0.0001 || pocketTemperature >= currentNodeTemperature) return + + val outsideAirTemp = AerodynamicUtils.getAirTemperatureForY(realY, 563.0) + + // Gas consumption + + val outputRateMult = pointer.value.toDouble() + val consumedGasses = EnumMap(GasType::class.java) + + val idealOutputEnergy = 100000.0 //100 kW * closed off valve amount + + val targetTemperature = currentNodeTemperature * outputRateMult + + currentIdealOutput = Mth.lerp(1.0/60.0, currentIdealOutput, idealOutputEnergy) + + var actualOutputEnergy = 0.0 + + val temperatureDiff = if (currentNodeTemperature - outsideAirTemp >= 0.001) { + currentNodeTemperature - outsideAirTemp + } else { + -0.001 + } + + val idealFlowRate = currentIdealOutput / (temperatureDiff * currentNodeAvgSpecificHeat) + + val flowRate = Mth.clamp(idealFlowRate, 0.0, currentNodeTotalMass) / 20.0 + + actualOutputEnergy = flowRate * (temperatureDiff * currentNodeAvgSpecificHeat) + + // Heat transfer + + var temperatureChangeInPocket = (actualOutputEnergy / 20.0) / (pocketTotalMass * specificHeatAverage(pocketGasVolumes)) + + if (temperatureChangeInPocket.isInfinite() || temperatureChangeInPocket.isNaN() || temperatureChangeInPocket < 0.0) return + + temperatureChangeInPocket = Mth.clamp(temperatureChangeInPocket, -pocketTemperature, currentNodeTemperature - pocketTemperature) + + var newPocketTemperature = max(Mth.clamp(pocketTemperature + temperatureChangeInPocket, 0.0001, currentNodeTemperature), pocketTemperature) + + val adjustment = pid.control(targetTemperature, pocketTemperature) + + newPocketTemperature += adjustment + + var newCurrentNodeTemperature = currentNodeTemperature - (actualOutputEnergy / (currentNodeTotalMass * currentNodeAvgSpecificHeat)) + if (newCurrentNodeTemperature <= 0.0001 || newCurrentNodeTemperature.isNaN() && newCurrentNodeTemperature.isInfinite()) newCurrentNodeTemperature = 0.0001 + for (gas in GasType.entries) { + val currentMass = currentNodeGasVolumes[gas] ?: 0.0 + val deltaMass = Mth.clamp(flowRate, 0.0, currentMass) + newNodeMasses[gas] = max(currentMass - deltaMass, 0.0) + consumedGasses[gas] = deltaMass + } + + //apply stuff + + for (gas in GasType.entries) { + ClockworkMod.getKelvin().modGasMass(blockPos.toJOMLD(), gas, -(consumedGasses[gas] ?: 0.0)) + } + ClockworkMod.getKelvin().modTemperature(blockPos.toJOMLD(), max(newCurrentNodeTemperature - currentNodeTemperature, -currentNodeTemperature)) + serverLevel.shipObjectWorld.setAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("temperature"), pocketRef.x, pocketRef.y, pocketRef.z, serverLevel.dimensionId, newPocketTemperature) + } + + private fun flowIntoPocket() { + val serverLevel = level!! as ServerLevel + + val currentNodeTemperature = ClockworkMod.getKelvin().getTemperatureAt(blockPos.toJOMLD()) + val currentNodePressure = ClockworkMod.getKelvin().getPressureAt(blockPos.toJOMLD()) + val currentNodeGasVolumes = ClockworkMod.getKelvin().getGasMassAt(blockPos.toJOMLD()) + val currentNodeTotalMass = currentNodeGasVolumes.values.sum() + val currentNodeAvgViscosity = dynamicViscosityAverage(currentNodeGasVolumes, currentNodeTemperature) + val currentNodeAvgSpecificHeat = specificHeatAverage(currentNodeGasVolumes) + + + val pocketRef = blockPos.above() + val (pocketGasVolumes, pocketTemperature) = retrieveGasInfoFromPocket(pocketRef.toJOML(), serverLevel) + val pocketVolume = serverLevel.shipObjectWorld.getAirComponentSize(pocketRef.x, pocketRef.y, pocketRef.z, serverLevel.dimensionId).toDouble() + val pocketTotalMass = pocketGasVolumes.values.sum() + val pocketAvgDensity = densityAverage(pocketGasVolumes) + val pocketAvgViscosity = dynamicViscosityAverage(pocketGasVolumes, pocketTemperature) + val pocketPressure = serverLevel.shipObjectWorld.getAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("pressure"), pocketRef.x, pocketRef.y, pocketRef.z, serverLevel.dimensionId) + + var newCurrentNodeTemperature: Double = currentNodeTemperature + var newPocketTemperature: Double = pocketTemperature + val newCurrentNodeMasses: EnumMap = EnumMap(GasType::class.java) + val newPocketMasses: EnumMap = EnumMap(GasType::class.java) + + val realDensityNode = densityFromPressureAverage(currentNodeGasVolumes, currentNodeTemperature, currentNodePressure) + val realDensityPocket = densityFromPressureAverage(pocketGasVolumes, pocketTemperature, pocketPressure) + val viscosity = (currentNodeAvgViscosity + pocketAvgViscosity) / 2.0 + + var flow = calculateFlow( + currentNodePressure, + pocketPressure, + 0.5 - (pointer.value/2.0), + 0.5, + realDensityNode, + realDensityPocket, + viscosity + ) + + if (flow < 0) { + flow = 0.0 + } + + if (flow.isInfinite() || flow.isNaN()) flow = 0.0 + + var totalDeltaMass: Double = 0.0 + val transferredGasses = EnumMap(GasType::class.java) + for (gas in GasType.entries) { + val currentMass = currentNodeGasVolumes[gas] ?: 0.0 + val pocketMass = pocketGasVolumes[gas] ?: 0.0 + + val deltaMass = Mth.clamp(flow, 0.0, currentMass) + newCurrentNodeMasses[gas] = max(currentMass - deltaMass, 0.0) + newPocketMasses[gas] = max(pocketMass + deltaMass, 0.0) + totalDeltaMass += deltaMass + transferredGasses[gas] = deltaMass + } + + val transferSpecificHeat = specificHeatAverage(transferredGasses) + + var deltaThermalEnergy = if (flow > 0) { + (totalDeltaMass * transferSpecificHeat * (currentNodeTemperature - pocketTemperature)) + } else { + 0.0 + } + + val thermalLimit = if (flow > 0) { + currentNodeTotalMass * currentNodeAvgSpecificHeat * currentNodeTemperature + } else { + 0.0 + } + deltaThermalEnergy = Mth.clamp(deltaThermalEnergy, -thermalLimit, thermalLimit) + + if (deltaThermalEnergy.isInfinite() || deltaThermalEnergy.isNaN()) deltaThermalEnergy = 0.0 + + + //if (nodeA.currentTemperature > 300.0 || nodeB.currentTemperature > 300.0) KELVINLOGGER.logger.warn("High Temp! DeltaThermalEnergy: $deltaThermalEnergy, flowHeat: $flowHeatCapacity, ThermalLimit: $thermalLimit, totalGasMassA: $newTotalGasMassesA, totalGasMassB: $newTotalGasMassesB") + if (newCurrentNodeMasses.values.sum() >= 0.0001 && newPocketMasses.values.sum() >= 0.0001) { + newCurrentNodeTemperature -= (deltaThermalEnergy) / (newCurrentNodeMasses.values.sum() * specificHeatAverage(newCurrentNodeMasses)) + newPocketTemperature += (deltaThermalEnergy) / (newPocketMasses.values.sum() * specificHeatAverage(newPocketMasses)) + } + + if (newPocketTemperature <= 0.0001) newPocketTemperature = 0.0001 + if (newCurrentNodeTemperature <= 0.0001) newCurrentNodeTemperature = 0.0001 + + newPocketTemperature = min(Mth.clamp(newPocketTemperature, 0.0001, currentNodeTemperature), 5772.0) + + //var newPocketPressure = calcPressure(newPocketMasses.values.sum(), pocketVolume, newPocketTemperature, densityAverage(newPocketMasses)) + + for (gas in GasType.entries) { + ClockworkMod.getKelvin().modGasMass(blockPos.toJOMLD(), gas, -(transferredGasses[gas]?: 0.0)) + serverLevel.shipObjectWorld.setAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("gas_" + gas.name.lowercase()), pocketRef.x, pocketRef.y, pocketRef.z, serverLevel.dimensionId, newPocketMasses[gas]!!) + } + ClockworkMod.getKelvin().modTemperature(blockPos.toJOMLD(), newCurrentNodeTemperature - currentNodeTemperature) + serverLevel.shipObjectWorld.setAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("temperature"), pocketRef.x, pocketRef.y, pocketRef.z, serverLevel.dimensionId, newPocketTemperature) + //serverLevel.shipObjectWorld.setAirComponentAugmentation(ClockworkAugmentations.getComponentAugmentation("pressure"), pocketRef.x, pocketRef.y, pocketRef.z, serverLevel.dimensionId, newPocketPressure) + } + + override fun getDuctNodePosition(): DuctNodePos { + return blockPos.toJOMLD() + } + + override fun addToGoggleTooltip(tooltip: MutableList, isPlayerSneaking: Boolean): Boolean { + super.addToGoggleTooltip(tooltip, isPlayerSneaking) + if (!hasPocket) { + tooltip.add(TextComponent("Missing pocket.").withStyle(ChatFormatting.GRAY).withStyle(ChatFormatting.ITALIC)) + return false + } else { + tooltip.add(TextComponent("Pocket Temperature: $clientPocketTemperature").withStyle(ChatFormatting.RED)) + } + return super.addToGoggleTooltip(tooltip, isPlayerSneaking) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzleInstance.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzleInstance.kt new file mode 100644 index 000000000..6e30b41bd --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzleInstance.kt @@ -0,0 +1,20 @@ +package org.valkyrienskies.clockwork.content.logistics.gas.pockets.nozzle + +import com.jozufozu.flywheel.api.Instancer +import com.jozufozu.flywheel.api.MaterialManager +import com.simibubi.create.content.kinetics.base.SingleRotatingInstance +import com.simibubi.create.content.kinetics.base.flwdata.RotatingData +import net.minecraft.world.level.block.HorizontalDirectionalBlock +import net.minecraft.world.level.block.Rotation +import org.valkyrienskies.clockwork.ClockworkPartials + +class GasNozzleInstance(materialManager: MaterialManager?, blockEntity: GasNozzleBlockEntity? +) : SingleRotatingInstance( + materialManager, blockEntity +) { + + override fun getModel(): Instancer { + val referenceState = blockState.rotate(Rotation.CLOCKWISE_180) + return rotatingMaterial.getModel(ClockworkPartials.NOZZLE_AXIS, referenceState, blockState.getValue(HorizontalDirectionalBlock.FACING)) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzlePacket.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzlePacket.kt new file mode 100644 index 000000000..54a0acf07 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzlePacket.kt @@ -0,0 +1,46 @@ +package org.valkyrienskies.clockwork.content.logistics.gas.pockets.nozzle + +import com.simibubi.create.foundation.utility.animation.LerpedFloat +import net.minecraft.client.Minecraft +import net.minecraft.core.BlockPos +import net.minecraft.network.FriendlyByteBuf +import org.valkyrienskies.clockwork.content.logistics.gas.generation.compressor.AirCompressorBlockEntity +import org.valkyrienskies.clockwork.platform.api.network.ClientNetworkContext +import org.valkyrienskies.clockwork.platform.api.network.S2CCWPacket + +class GasNozzlePacket: S2CCWPacket { + + + + private val chaseTarget: Double + private val pos: BlockPos + + + constructor(buffer: FriendlyByteBuf) { + chaseTarget = buffer.readDouble() + pos = buffer.readBlockPos() + + } + + constructor(newChaseTarget: Double, newPos: BlockPos) { + chaseTarget = newChaseTarget + pos = newPos + } + + override fun handle(context: ClientNetworkContext) { + context.enqueueWork { + + val be = + Minecraft.getInstance().level?.getBlockEntity(pos) as GasNozzleBlockEntity? ?: return@enqueueWork + be.pointer.chase(chaseTarget, be.getChaseSpeed().toDouble(), LerpedFloat.Chaser.LINEAR) + + } + context.setPacketHandled(true) + } + + override fun write(buffer: FriendlyByteBuf) { + buffer.writeDouble(chaseTarget) + buffer.writeBlockPos(pos) + + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzleRenderer.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzleRenderer.kt new file mode 100644 index 000000000..427eb1054 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pockets/nozzle/GasNozzleRenderer.kt @@ -0,0 +1,65 @@ +package org.valkyrienskies.clockwork.content.logistics.gas.pockets.nozzle + +import com.jozufozu.flywheel.backend.Backend +import com.mojang.blaze3d.vertex.PoseStack +import com.simibubi.create.AllPartialModels +import com.simibubi.create.content.kinetics.base.HorizontalKineticBlock +import com.simibubi.create.content.kinetics.base.KineticBlockEntityRenderer +import com.simibubi.create.foundation.render.CachedBufferer +import com.simibubi.create.foundation.render.SuperByteBuffer +import com.simibubi.create.foundation.utility.AngleHelper +import com.simibubi.create.foundation.utility.AnimationTickHolder +import net.minecraft.client.renderer.MultiBufferSource +import net.minecraft.client.renderer.RenderType +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider +import net.minecraft.core.Direction +import net.minecraft.core.Direction.Axis +import net.minecraft.util.Mth +import net.minecraft.world.phys.Vec3 +import org.valkyrienskies.clockwork.ClockworkPartials + +class GasNozzleRenderer(context: BlockEntityRendererProvider.Context?) : KineticBlockEntityRenderer( + context +) { + override fun renderSafe( + be: GasNozzleBlockEntity, + partialTicks: Float, + ms: PoseStack, + buffer: MultiBufferSource, + light: Int, + overlay: Int + ) { + super.renderSafe(be, partialTicks, ms, buffer, light, overlay) + + val blockState = be.blockState + var pointer = CachedBufferer.partial(ClockworkPartials.NOZZLE_DIAL, blockState) + val facing = blockState.getValue(HorizontalKineticBlock.HORIZONTAL_FACING) + + val pointerRotation = Mth.DEG_TO_RAD * Mth.lerp(be.pointer.getValue(partialTicks), 225f, 135f) + + val dialOffset = Vec3(0.0,-0.1,0.0) + + rotateBufferTowards(pointer, facing.clockWise) + .translate(dialOffset) + .rotateCentered(Direction.NORTH, pointerRotation) + .translate(dialOffset.reverse()) + .light(light) + .renderInto(ms, buffer.getBuffer(RenderType.solid())) + + if (Backend.canUseInstancing(be.level)) return + val time = AnimationTickHolder.getRenderTime(be.level) + val rotdir = blockState.getValue(HorizontalKineticBlock.HORIZONTAL_FACING).clockWise + val rotaxis = rotdir.axis + val offset = getRotationOffsetForPosition(be, be.blockPos, rotaxis) + var angle = time * be.speed * 3f / 10 % 360 + angle += offset + angle = angle / 180f * Math.PI.toFloat() + val axis = CachedBufferer.partialFacing(ClockworkPartials.NOZZLE_AXIS, blockState, rotdir) + kineticRotationTransform(axis, be, rotaxis, angle, light) + axis.renderInto(ms, buffer.getBuffer(RenderType.solid())) + } + + fun rotateBufferTowards(buffer: SuperByteBuffer, target: Direction): SuperByteBuffer { + return buffer.rotateCentered(Direction.UP, ((-target.toYRot() - 90) / 180 * Math.PI).toFloat()) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pump/PumpDuctBlock.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pump/PumpDuctBlock.kt index 4a14f1897..a0544cfc6 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pump/PumpDuctBlock.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/pump/PumpDuctBlock.kt @@ -6,7 +6,6 @@ import com.simibubi.create.content.kinetics.simpleRelays.ICogWheel import com.simibubi.create.foundation.block.IBE import net.minecraft.core.BlockPos import net.minecraft.core.Direction -import net.minecraft.world.item.context.BlockPlaceContext import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.Level import net.minecraft.world.level.LevelAccessor diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/storage/tank/DuctTankBlock.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/storage/tank/DuctTankBlock.kt index 6068f9d8d..4488767e2 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/storage/tank/DuctTankBlock.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/storage/tank/DuctTankBlock.kt @@ -1,27 +1,16 @@ package org.valkyrienskies.clockwork.content.logistics.gas.storage.tank import com.simibubi.create.foundation.block.IBE -import dev.architectury.registry.fuel.FuelRegistry import net.minecraft.core.BlockPos import net.minecraft.core.Direction -import net.minecraft.world.InteractionHand -import net.minecraft.world.InteractionResult -import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.context.BlockPlaceContext import net.minecraft.world.level.Level import net.minecraft.world.level.LevelAccessor import net.minecraft.world.level.block.Block -import net.minecraft.world.level.block.HorizontalDirectionalBlock import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState -import net.minecraft.world.level.block.state.StateDefinition -import net.minecraft.world.level.block.state.properties.BlockStateProperties -import net.minecraft.world.phys.BlockHitResult import org.valkyrienskies.clockwork.ClockworkBlockEntities import org.valkyrienskies.clockwork.content.logistics.gas.duct.INodeBlock import org.valkyrienskies.clockwork.kelvin.api.* -import org.valkyrienskies.clockwork.kelvin.api.nodes.PipeDuctNode import org.valkyrienskies.clockwork.kelvin.api.nodes.TankDuctNode diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/utilities/GasExplosionDamageCalculator.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/utilities/GasExplosionDamageCalculator.kt new file mode 100644 index 000000000..215b39bc8 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/utilities/GasExplosionDamageCalculator.kt @@ -0,0 +1,44 @@ +package org.valkyrienskies.clockwork.content.logistics.gas.utilities + +import net.minecraft.core.BlockPos +import net.minecraft.world.damagesource.DamageSource +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.Explosion +import net.minecraft.world.level.ExplosionDamageCalculator +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.material.FluidState +import org.valkyrienskies.clockwork.content.logistics.gas.duct.INodeBlock +import java.util.* + +class GasExplosionDamageCalculator : ExplosionDamageCalculator() { + + override fun getBlockExplosionResistance( + explosion: Explosion, + reader: BlockGetter, + pos: BlockPos, + state: BlockState, + fluid: FluidState + ): Optional { + if (state.block is INodeBlock) { + return Optional.of(0.0f) + } + return super.getBlockExplosionResistance(explosion, reader, pos, state, fluid) + } + + override fun shouldBlockExplode( + explosion: Explosion, + reader: BlockGetter, + pos: BlockPos, + state: BlockState, + power: Float + ): Boolean { + if (state.block is INodeBlock) { + return true + } + return super.shouldBlockExplode(explosion, reader, pos, state, power) + } + + companion object { + //val GAS_EXPLOSION : DamageSource + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/utilities/PocketForcesQueueable.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/utilities/PocketForcesQueueable.kt index 0b263039e..87f32a82f 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/utilities/PocketForcesQueueable.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/content/logistics/gas/utilities/PocketForcesQueueable.kt @@ -6,4 +6,4 @@ import org.joml.Vector3ic import org.valkyrienskies.clockwork.kelvin.api.GasType import java.util.* -data class PocketForcesQueueable(val rootPos: Vector3ic, val pocketCenter: Vector3dc, val pocketSize: Long, val gasMasses: EnumMap, val temperature: Double) +data class PocketForcesQueueable(val rootPos: Vector3ic, val pocketCenter: Vector3dc, val pocketSize: Long, val gasMasses: EnumMap, val temperature: Double, val pressure: Double) diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/api/DuctNetwork.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/api/DuctNetwork.kt index beffbe12e..ab95a81b5 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/api/DuctNetwork.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/api/DuctNetwork.kt @@ -40,7 +40,7 @@ interface DuctNetwork { /** * Returns the gas volumes at a node from the previous tick. */ - fun getGasVolumesAt(node: DuctNodePos): EnumMap + fun getGasMassAt(node: DuctNodePos): EnumMap fun getEdgeBetween(from: DuctNodePos, to: DuctNodePos): DuctEdge? fun getNodeAt(pos: DuctNodePos): DuctNode? @@ -53,9 +53,9 @@ interface DuctNetwork { fun modTemperature(pos: DuctNodePos, deltaTemperature: Double) fun modPressure(pos: DuctNodePos, deltaPressure: Double) - fun modGasVolume(pos: DuctNodePos, gasType: GasType, deltaVolume: Double) + fun modGasMass(pos: DuctNodePos, gasType: GasType, deltaMass: Double) - fun modGasVolumeOfTemperature(pos: DuctNodePos, gasType: GasType, deltaVolume: Double, gasTemperature: Double) + fun modGasMassOfTemperature(pos: DuctNodePos, gasType: GasType, deltaMass: Double, gasTemperature: Double) // the real meat fun tick(level: ServerLevel, subSteps: Int = 1) diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/api/GasType.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/api/GasType.kt index 822cf0b30..d58826d30 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/api/GasType.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/api/GasType.kt @@ -4,11 +4,11 @@ enum class GasType( val density: Double, // Density of gas at STP (kg / m^3) val viscosity: Double, // (kg / (m * s)) (see https://www.sciencedirect.com/topics/engineering/air-viscosity) val specificHeatCapacity: Double, // (J / (K * g) + val thermalConductivity: Double, // (W / (m * K)) + val sutherlandConstant: Double = 111.0, // (dimensionless) (see https://en.wikipedia.org/wiki/Viscosity#Temperature_dependence) + val adiabaticIndex: Double = 1.4, // (dimensionless) (see https://en.wikipedia.org/wiki/Adiabatic_index) Not required, 1.4 is air's. Technically an approximation, only useful for pockets, but oh well. ) { - AIR(1.293, 1.81e-5, 1.005), - PHLOGISTON(3.0, 0.75e-5, 14.30), - HELIUM(0.166, 1.81e-5, 5.1832), - EXAMPLE_01(0.1,1.81e-5,1.0), - EXAMPLE_02(0.1,1.81e-5,1.0), - EXAMPLE_03(0.1,1.81e-5,1.0) + AIR(1.293, 1.716e-5, 1.005, 0.026), + PHLOGISTON(3.0, 2.0e-5, 14.30, 0.240, 150.0, 1.008), + HELIUM(0.166, 1.96e-5, 5.1832, 0.151, 79.4, 1.66), } diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/impl/DuctNetworkImpl.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/impl/DuctNetworkImpl.kt index 2be11ea4f..a4cdc1df1 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/impl/DuctNetworkImpl.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/impl/DuctNetworkImpl.kt @@ -4,9 +4,11 @@ import net.minecraft.core.BlockPos import net.minecraft.server.level.ServerLevel import net.minecraft.util.Mth import net.minecraft.world.level.Explosion +import org.valkyrienskies.clockwork.ClockworkDamageSources import org.valkyrienskies.clockwork.content.logistics.gas.GasHeatLevel import org.valkyrienskies.clockwork.content.logistics.gas.IHeatableBlock import org.valkyrienskies.clockwork.content.logistics.gas.duct.DuctBlock +import org.valkyrienskies.clockwork.content.logistics.gas.utilities.GasExplosionDamageCalculator import org.valkyrienskies.clockwork.kelvin.api.* import org.valkyrienskies.clockwork.kelvin.api.edges.ApertureEdge import org.valkyrienskies.clockwork.kelvin.api.edges.FilteredEdge @@ -17,9 +19,7 @@ import org.valkyrienskies.mod.common.util.toMinecraft import java.util.* import kotlin.collections.HashMap import kotlin.collections.HashSet -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.pow +import kotlin.math.* class DuctNetworkImpl( override var disabled: Boolean = true, @@ -49,15 +49,16 @@ class DuctNetworkImpl( } override fun getPressureAt(node: DuctNodePos): Double { + if (nodeInfo[node]?.currentPressure?.isNaN() == true) return 0.0 return nodeInfo[node]?.currentPressure ?: 0.0 } override fun getTemperatureAt(node: DuctNodePos): Double { - return nodeInfo[node]?.currentTemperature ?: 0.0 + return nodeInfo[node]?.currentTemperature ?: 0.0001 } - override fun getGasVolumesAt(node: DuctNodePos): EnumMap { - return nodeInfo[node]?.currentGasVolumes ?: EnumMap(GasType::class.java) + override fun getGasMassAt(node: DuctNodePos): EnumMap { + return nodeInfo[node]?.currentGasMasses ?: EnumMap(GasType::class.java) } override fun getEdgeBetween(from: DuctNodePos, to: DuctNodePos): DuctEdge? { @@ -74,7 +75,7 @@ class DuctNetworkImpl( return } nodes[pos] = node - nodeInfo[pos] = DuctNodeInfo(node.behavior, 0.0, 0.0, EnumMap(GasType::class.java)) + nodeInfo[pos] = DuctNodeInfo(node.behavior, 273.15, 0.0, EnumMap(GasType::class.java)) KELVINLOGGER.logger.info("Added node at $pos") } @@ -112,34 +113,30 @@ class DuctNetworkImpl( } override fun modTemperature(pos: DuctNodePos, deltaTemperature: Double) { - nodeInfo[pos]?.currentTemperature = nodeInfo[pos]?.currentTemperature?.plus(deltaTemperature) ?: 0.0 + if (deltaTemperature.isNaN() || deltaTemperature.isInfinite()) nodeInfo[pos]?.currentTemperature = 0.0001 + nodeInfo[pos]?.currentTemperature = max(nodeInfo[pos]?.currentTemperature?.plus(deltaTemperature) ?: 0.0001, 0.0001) } override fun modPressure(pos: DuctNodePos, deltaPressure: Double) { nodeInfo[pos]?.currentPressure = nodeInfo[pos]?.currentPressure?.plus(deltaPressure) ?: 0.0 } - override fun modGasVolume(pos: DuctNodePos, gasType: GasType, deltaVolume: Double) { - nodeInfo[pos]?.currentGasVolumes?.put(gasType, nodeInfo[pos]?.currentGasVolumes?.get(gasType)?.plus(deltaVolume) ?: 0.0) + override fun modGasMass(pos: DuctNodePos, gasType: GasType, deltaMass: Double) { + nodeInfo[pos]?.currentGasMasses?.put(gasType, nodeInfo[pos]?.currentGasMasses?.get(gasType)?.plus(deltaMass) ?: 0.0) } - override fun modGasVolumeOfTemperature(pos: DuctNodePos, gasType: GasType, deltaVolume: Double, deltaTemperature: Double ) { + override fun modGasMassOfTemperature(pos: DuctNodePos, gasType: GasType, deltaMass: Double, gasTemperature: Double ) { var massInNode = 0.0 - nodeInfo[pos]?.currentGasVolumes?.forEach { massInNode += it.value*it.key.density } ?: return - val specificHeatOfNode = specificHeatAverage(nodeInfo[pos]?.currentGasVolumes!!) + nodeInfo[pos]?.currentGasMasses?.forEach { massInNode += it.value } ?: return + val specificHeatOfNode = specificHeatAverage(nodeInfo[pos]?.currentGasMasses!!) val tempInNode = nodeInfo[pos]!!.currentTemperature - val energyInNode = massInNode * specificHeatOfNode * tempInNode - val deltaMass = deltaVolume * gasType.density + val temp = (massInNode*specificHeatOfNode*tempInNode + deltaMass*gasTemperature*gasType.specificHeatCapacity) / (massInNode*specificHeatOfNode + deltaMass*gasType.specificHeatCapacity) - val temp = (massInNode*specificHeatOfNode*tempInNode + deltaMass*deltaTemperature*gasType.specificHeatCapacity) / (massInNode*specificHeatOfNode + deltaMass*gasType.specificHeatCapacity) + nodeInfo[pos]!!.currentTemperature = max(temp, 0.0001) - // = (deltaVolume*gasType.density*gasType.specificHeatCapacity*gasTemperature + massInNode*specificHeatOfNode*)/(deltaVolume*gasType.specificHeatCapacity + massInNode*specificHeatOfNode) - - nodeInfo[pos]!!.currentTemperature = temp - - modGasVolume(pos, gasType, deltaVolume) + modGasMass(pos, gasType, deltaMass) } @@ -171,11 +168,11 @@ class DuctNetworkImpl( var madeNewB = false if (nodeA == null) { - nodeInfo[edge.nodeA] = DuctNodeInfo(nodes[edge.nodeA]!!.behavior,0.0, 0.0, EnumMap(GasType::class.java)) + nodeInfo[edge.nodeA] = DuctNodeInfo(nodes[edge.nodeA]!!.behavior,273.15, 0.0, EnumMap(GasType::class.java)) madeNewA = true } if (nodeB == null) { - nodeInfo[edge.nodeB] = DuctNodeInfo(nodes[edge.nodeB]!!.behavior,0.0, 0.0, EnumMap(GasType::class.java)) + nodeInfo[edge.nodeB] = DuctNodeInfo(nodes[edge.nodeB]!!.behavior,273.15, 0.0, EnumMap(GasType::class.java)) madeNewB = true } @@ -186,17 +183,20 @@ class DuctNetworkImpl( var totalGasMassA = 0.0 var totalGasMassB = 0.0 - nodeA!!.currentGasVolumes.forEach { totalGasMassA += it.value } - nodeB!!.currentGasVolumes.forEach { totalGasMassB += it.value } + nodeA!!.currentGasMasses.forEach { totalGasMassA += it.value } + nodeB!!.currentGasMasses.forEach { totalGasMassB += it.value } + + val heatCapacityA = specificHeatAverage(nodeA.currentGasMasses) + val heatCapacityB = specificHeatAverage(nodeB.currentGasMasses) if (totalGasMassA == 0.0 && totalGasMassB == 0.0) { continue } - val densityA = densityAverage(nodeA.currentGasVolumes) - val densityB = densityAverage(nodeB.currentGasVolumes) + val densityA = densityAverage(nodeA.currentGasMasses) + val densityB = densityAverage(nodeB.currentGasMasses) - val tankMultA = if (nodeA.nodeType == NodeBehaviorType.TANK) (nodeDataB as TankDuctNode).size else 1.0 + val tankMultA = if (nodeA.nodeType == NodeBehaviorType.TANK) (nodeDataA as TankDuctNode).size else 1.0 val tankMultB = if (nodeB.nodeType == NodeBehaviorType.TANK) (nodeDataB as TankDuctNode).size else 1.0 @@ -208,8 +208,8 @@ class DuctNetworkImpl( - val viscosityA = viscosityAverage(nodeA.currentGasVolumes) - val viscosityB = viscosityAverage(nodeB.currentGasVolumes) + val viscosityA = dynamicViscosityAverage(nodeA.currentGasMasses, nodeA.currentTemperature) + val viscosityB = dynamicViscosityAverage(nodeB.currentGasMasses, nodeB.currentTemperature) @@ -231,6 +231,7 @@ class DuctNetworkImpl( var pumpPressureA = 0.0 var pumpPressureB = 0.0 + // Determines pump pressure based on target node if (nodeDataA.behavior == NodeBehaviorType.PUMP) { if (aTarget) pumpPressureA = (nodeDataA as PumpDuctNode).pumpPressure else pumpPressureA = -(nodeDataA as PumpDuctNode).pumpPressure @@ -248,7 +249,10 @@ class DuctNetworkImpl( aperture = Math.max(edge.aperture, -edge.radius) } - var flowRate = calculateFlow(pressureA, pressureB, edge.radius + aperture, viscosity, pumpPressure) + val pressureDependentDensityA = densityFromPressureAverage(nodeA.currentGasMasses, nodeA.currentTemperature, pressureA) + val pressureDependentDensityB = densityFromPressureAverage(nodeB.currentGasMasses, nodeB.currentTemperature, pressureB) + + var flowRate = calculateFlow(pressureA, pressureB, edge.radius + aperture, edge.length, pressureDependentDensityA, pressureDependentDensityB, viscosity, pumpPressure, edge.currentFlowRate) if (edge is OneWayEdge) { @@ -264,14 +268,46 @@ class DuctNetworkImpl( flowRate = 0.0 } + val aFlowOut = flowRate>0 + val bFlowOut = flowRate<0 + + if (aFlowOut) { + flowRate = flowRate.coerceAtMost(totalGasMassA) + } + if (bFlowOut) { + flowRate = -flowRate.absoluteValue.coerceAtMost(totalGasMassB) + } + val flowRateA = -flowRate val flowRateB = flowRate - val aFlowOut = flowRateA<0 - val bFlowOut = flowRateB<0 + var totalDeltaMassA = 0.0 + var totalDeltaMassB = 0.0 + + val heatConductivityA = heatConductivityAverage(nodeA.currentGasMasses, pressureA, nodeA.currentTemperature) + val heatConductivityB = heatConductivityAverage(nodeB.currentGasMasses, pressureB, nodeB.currentTemperature) + val totalAvgHeatConductivity = (heatConductivityA + heatConductivityB) / 2.0 - for (gas in GasType.values()) { + + //Calculates passive heat transfer between nodes + val passiveHeatDelta = (totalAvgHeatConductivity * (Math.PI * edge.radius * 2.0) * ((nodeA.currentTemperature - nodeB.currentTemperature) / edge.length)) + val passiveHeatLimit = ((totalGasMassA * heatCapacityA * nodeA.currentTemperature) + (totalGasMassB * heatCapacityB * nodeB.currentTemperature))/2.0 + + if (!passiveHeatDelta.isNaN() && passiveHeatLimit.isFinite()) { + if (totalGasMassA >= 0.1 && totalGasMassB >= 0.1 && heatCapacityA >= 0.001 && heatCapacityB >= 0.001) { + val deltaPassiveEnergy = Mth.clamp(passiveHeatDelta, -passiveHeatLimit, passiveHeatLimit) / subSteps.toDouble() + nodeA.currentTemperature -= deltaPassiveEnergy / (totalGasMassA * heatCapacityA) + nodeB.currentTemperature += deltaPassiveEnergy / (totalGasMassB * heatCapacityB) + } + } + + nodeA.currentTemperature = max(nodeA.currentTemperature, 0.0001) + nodeB.currentTemperature = max(nodeB.currentTemperature, 0.0001) + + val transferredGasses = EnumMap(GasType::class.java) + + for (gas in GasType.entries) { if (flowRate == 0.0) { continue } @@ -287,84 +323,95 @@ class DuctNetworkImpl( } } - if (nodeA.currentGasVolumes[gas] == null) { - nodeA.currentGasVolumes[gas] = 0.0 + if (nodeA.currentGasMasses[gas] == null) { + nodeA.currentGasMasses[gas] = 0.0 } - if (nodeB.currentGasVolumes[gas] == null) { - nodeB.currentGasVolumes[gas] = 0.0 + if (nodeB.currentGasMasses[gas] == null) { + nodeB.currentGasMasses[gas] = 0.0 } + val massA = nodeA.currentGasMasses[gas]!! + val massB = nodeB.currentGasMasses[gas]!! - val volumeA = nodeA.currentGasVolumes[gas]!! - val volumeB = nodeB.currentGasVolumes[gas]!! - - - // This entire block is quite disgusting, but it serves a simple function. - // It lets pumps intake the entire volume of the node behind it, and outtake its own volume into the targetNode + // Calculate flow limit based on pump behavior: + // - For pumps: Allow full extraction from source node when pumping in or out + // - For normal pipes: Limit to half the mass difference between nodes + // - For invalid pump configurations: No flow allowed + // Plus extra code for tanks, so that their limit was bigger to compensate for the mass they store val limit: Double - if (aTarget && aFlowOut || bPump && !bTarget && aFlowOut) limit = volumeA - else if (bTarget && bFlowOut || aPump && !aTarget && bFlowOut) limit = volumeB - else if (!aPump && !bPump) limit = abs(volumeA/tankMultA-volumeB/tankMultB)/2.0 + if (aTarget && aFlowOut || bPump && !bTarget && aFlowOut) limit = massA + else if (bTarget && bFlowOut || aPump && !aTarget && bFlowOut) limit = massB + else if (!aPump && !bPump) limit = abs(massA/tankMultA-massB/tankMultB)/2.0 else limit = 0.0 + //KELVINLOGGER.logger.info("MassA: $massA, MassB: $massB, Limit: $limit") - - val deltaVolumeA = Mth.clamp(flowRateA, -limit, limit) / subSteps - val deltaVolumeB = Mth.clamp(flowRateB, -limit, limit) / subSteps + val deltaMassA = Mth.clamp(flowRateA, -(limit.coerceAtMost(massA)), limit.coerceAtMost(massB)) + val deltaMassB = Mth.clamp(flowRateB, -(limit.coerceAtMost(massB)), limit.coerceAtMost(massA)) - nodeA.currentGasVolumes[gas] = max(volumeA + deltaVolumeA, 0.0) - nodeB.currentGasVolumes[gas] = max(volumeB + deltaVolumeB, 0.0) - + nodeA.currentGasMasses[gas] = max(massA + (deltaMassA/subSteps.toDouble()), 0.0) + nodeB.currentGasMasses[gas] = max(massB + (deltaMassB/subSteps.toDouble()), 0.0) + totalDeltaMassA += deltaMassA + totalDeltaMassB += deltaMassB + transferredGasses[gas] = deltaMassA } - totalGasMassA = nodeA.currentGasVolumes.values.sum() - totalGasMassB = nodeB.currentGasVolumes.values.sum() - - val heatCapacityA = specificHeatAverage(nodeA.currentGasVolumes) - val heatCapacityB = specificHeatAverage(nodeB.currentGasVolumes) + val totalTransferredMass = transferredGasses.values.sum() + val flowHeatCapacity = specificHeatAverage(transferredGasses) - edge.currentFlowRate = flowRate + val newTotalGasMassesA = nodeA.currentGasMasses.values.sum() + val newTotalGasMassesB = nodeB.currentGasMasses.values.sum() + val newHeatCapacityA = specificHeatAverage(nodeA.currentGasMasses) + val newHeatCapacityB = specificHeatAverage(nodeB.currentGasMasses) - var deltaThermalEnergy = if (flowRate > 0) { - (totalGasMassA * heatCapacityA * (nodeA.currentTemperature - nodeB.currentTemperature)) - } else if (flowRate < 0) { - (totalGasMassB * heatCapacityB * (nodeB.currentTemperature - nodeA.currentTemperature)) + var deltaThermalEnergy = if (flowRate > 0.0) { + (totalTransferredMass * flowHeatCapacity * (nodeA.currentTemperature - nodeB.currentTemperature)) + } else if (flowRate < 0.0) { + (totalTransferredMass * flowHeatCapacity * (nodeB.currentTemperature - nodeA.currentTemperature)) } else { 0.0 } - val thermalLimit = abs((totalGasMassA*heatCapacityA*nodeA.currentTemperature)-(totalGasMassB*heatCapacityB*nodeB.currentTemperature))/2.0 - deltaThermalEnergy = Mth.clamp(deltaThermalEnergy, -thermalLimit, thermalLimit)/subSteps.toDouble() - - if (deltaThermalEnergy.isInfinite() || deltaThermalEnergy.isNaN()) return + val thermalLimit = if (flowRate > 0) { + totalGasMassA * heatCapacityA * nodeA.currentTemperature + } else if (flowRate < 0) { + totalGasMassB * heatCapacityB * nodeB.currentTemperature + } else { + 0.0 + } + deltaThermalEnergy = Mth.clamp(deltaThermalEnergy, -thermalLimit, thermalLimit) + if (deltaThermalEnergy.isInfinite() || deltaThermalEnergy.isNaN()) continue - if (flowRate > 0) { - if (totalGasMassA > 0) nodeA.currentTemperature -= deltaThermalEnergy / (totalGasMassA * heatCapacityA) - if (totalGasMassB > 0) nodeB.currentTemperature += deltaThermalEnergy / (totalGasMassB * heatCapacityB) - } else { - if (totalGasMassA > 0) nodeA.currentTemperature += deltaThermalEnergy / (totalGasMassA * heatCapacityA) - if (totalGasMassB > 0) nodeB.currentTemperature -= deltaThermalEnergy / (totalGasMassB * heatCapacityB) + + //if (nodeA.currentTemperature > 300.0 || nodeB.currentTemperature > 300.0) KELVINLOGGER.logger.warn("High Temp! DeltaThermalEnergy: $deltaThermalEnergy, flowHeat: $flowHeatCapacity, ThermalLimit: $thermalLimit, totalGasMassA: $newTotalGasMassesA, totalGasMassB: $newTotalGasMassesB") + if (newTotalGasMassesA >= 0.0001 && newTotalGasMassesB >= 0.0001 && newHeatCapacityA >= 0.0001 && newHeatCapacityB >= 0.0001) { + nodeA.currentTemperature += (deltaThermalEnergy / subSteps.toDouble()) / (newTotalGasMassesA * newHeatCapacityA) + nodeB.currentTemperature -= (deltaThermalEnergy / subSteps.toDouble()) / (newTotalGasMassesB * newHeatCapacityB) } + // Clamps temperature to prevent impossible values + nodeA.currentTemperature = max(nodeA.currentTemperature, 0.0001) + nodeB.currentTemperature = max(nodeB.currentTemperature, 0.0001) - nodeA.currentTemperature = max(nodeA.currentTemperature , 0.0) - nodeA.currentTemperature = max(nodeA.currentTemperature , 0.0) + edge.currentFlowRate = totalTransferredMass * flowRate.sign } } val nodesToSync = HashMap() + val explnodes = HashSet() - for (nodePos in nodeInfo.keys) { + val nodeInfoToProcess = HashMap(nodeInfo) + for (nodePos in nodeInfoToProcess.keys) { if (nodeInfo[nodePos] == null || nodes[nodePos] == null) { continue } @@ -373,7 +420,8 @@ class DuctNetworkImpl( val info = nodeInfo[nodePos]!! if (info.currentPressure > node.maxPressure) { - level.explode(null, nodePos.x() + 0.5, nodePos.y() + 0.5, nodePos.z() + 0.5, 1f, Explosion.BlockInteraction.BREAK) + explnodes.add(nodePos) + KELVINLOGGER.logger.info("Node at $nodePos exploded due to overpressure. Pressure at time of failure: ${info.currentPressure}") } // if (info.currentPressure < node.minPressure) { @@ -381,7 +429,15 @@ class DuctNetworkImpl( // } //copilot wrote this so im immortalizing it - //temperature control stuff + + /** Update visual heat level of ducts based on temperature thresholds: + COOL: < 20% of max temp + WARM: 20-40% of max temp + HOT: 40-60% of max temp + VERY_HOT: 60-80% of max temp + SUPER_HOT: 80-100% of max temp + MOLTEN: >= 100% of max temp + */ if (info.currentTemperature < (node.maxTemperature/5) && info.previousTemperatureLevel != 0) { info.previousTemperatureLevel = 0 if (level.getBlockState(BlockPos(nodePos.toMinecraft())).block is IHeatableBlock) { @@ -420,28 +476,136 @@ class DuctNetworkImpl( if (state.block is DuctBlock) state.setValue(IHeatableBlock.GAS_HEAT_LEVEL, nodesToSync[node]!!) level.setBlockAndUpdate(BlockPos(node.toMinecraft()), state) } - } - + explnodes.forEach { + level.explode(null, ClockworkDamageSources.GAS_EXPLOSION, GasExplosionDamageCalculator(),it.x() + 0.5, it.y() + 0.5, it.z() + 0.5, 1f, true, Explosion.BlockInteraction.BREAK) + } + } /** * Calculates pressure using the ideal gas law. */ - private fun calcPressure(mass: Double, volume: Double, temp: Double, density: Double): Double { - if (volume == 0.0 || density == 0.0) return 0.0 - val adjustedTemp = max(temp,0.001) + private fun calcPressure(mass: Double, volume: Double, temp: Double, standardDensity: Double): Double { + if (volume == 0.0 || mass == 0.0) return 0.0 + val adjustedTemp = max(temp,0.0001) val pressure: Double - val molarMass = density * 22.4 - val moles = mass / molarMass - pressure = (moles * idealGasConstant * adjustedTemp) / volume + val density: Double = mass / volume + val molarMass = standardDensity * 22.4 + val specificGasConstant = idealGasConstant / molarMass + pressure = (density * specificGasConstant * adjustedTemp) + return pressure } - /** - * Calculates the flow of gas based off pressure differentia, pipe radius, and viscosity using Poiseuille's Law. - */ - private fun calculateFlow(pressureOne: Double, pressureTwo: Double, radius: Double, viscosity: Double, pumpPressure: Double = 0.0): Double { - return ((pressureOne - pressureTwo + pumpPressure) * radius.pow(4.0)) / ((8.0/Math.PI) * viscosity * (10.0/16.0)) + private fun densityFromPressureAverage(gasMasses: EnumMap, temp: Double, pressure: Double): Double { + val totalMass = gasMasses.values.sum() + if (totalMass == 0.0) { + return 0.0 + } + + val massPerGas = EnumMap(GasType::class.java) + + val gasWeight = EnumMap(GasType::class.java) + + gasMasses.keys.forEach { + if (gasMasses[it] != 0.0 ) { + massPerGas[it] = gasMasses[it]!! + } + + } + + for (gas in massPerGas.keys) { + gasWeight[gas] = massPerGas[gas]!! / totalMass + } + + var density = 0.0 + + for (gas in gasWeight.keys) { + val molarMass = gas.density * 22.4 + val specificGasConstant = idealGasConstant / molarMass + density += gasWeight[gas]!! * (pressure / (specificGasConstant * temp)) + } + + return density + } + + private fun dynamicViscosityAverage(gasMasses: EnumMap, temp: Double): Double { + val totalMass = gasMasses.values.sum() + if (totalMass == 0.0) { + return 0.0 + } + + val massPerGas = EnumMap(GasType::class.java) + + val gasWeight = EnumMap(GasType::class.java) + + gasMasses.keys.forEach { + if (gasMasses[it] != 0.0 ) { + massPerGas[it] = gasMasses[it]!! + } + + } + + for (gas in massPerGas.keys) { + gasWeight[gas] = massPerGas[gas]!! / totalMass + } + + var viscosity = 0.0 + + for (gas in gasWeight.keys) { + viscosity += gasWeight[gas]!! * (gas.viscosity * (temp / 273.15) * ((273.15 + gas.sutherlandConstant) / (temp + gas.sutherlandConstant))) + } + + return viscosity + } + +// /** +// * Calculates the flow of gas based off pressure differentia, pipe radius, and viscosity using Poiseuille's Law. +// */ +// private fun calculateFlow(pressureOne: Double, pressureTwo: Double, radius: Double, viscosity: Double, pumpPressure: Double = 0.0): Double { +// return ((pressureOne - pressureTwo + pumpPressure) * radius.pow(4.0)) / ((8.0/Math.PI) * viscosity * (10.0/16.0)) +// } + + private fun calculateFlow(pressureOne: Double, pressureTwo: Double, radius: Double, length: Double, densityA: Double, densityB: Double, viscosity: Double, pumpPressure: Double = 0.0, previousFlowRate: Double = 0.0): Double { + var flowRate = 0.0 + if (densityA <= 0 && densityB <= 0) { + return flowRate + } + val density = if (pressureOne > pressureTwo) densityA else densityB + // -- constants + // (meters) + val pipeRoughness = 0.00012 + val pipeDiameter = radius * 2.0 + + var pressureDrop = (pressureOne - pressureTwo + pumpPressure) + + if (pressureOne <= 0.0001 && pumpPressure.absoluteValue > 0.0) { + pressureDrop = min(pressureDrop, 0.0) + } + + if (pressureTwo <= 0.0001 && pumpPressure < 0.0) { + pressureDrop = max(pressureDrop, 0.0) + } + + val finalPressureDrop = pressureDrop + + val Re = max((density * previousFlowRate * pipeDiameter) / viscosity, 0.0001) + + var f: Double = if (Re < 2000) { + 64.0/Re + } else if (Re > 4000) { + 0.25 / (Math.pow(Math.log10(((pipeRoughness / pipeDiameter) / 3.7) + (5.74 / Math.pow(Re, 0.9))), 2.0)) + } else { + Mth.clampedLerp(64.0/Re, 0.25 / (Math.pow(Math.log10(((pipeRoughness / pipeDiameter) / 3.7) + (5.74 / Math.pow(Re, 0.9))), 2.0)),(Re-2000.0)/(4000.0-2000.0)) + } + + val flowSpeed = (2.0*finalPressureDrop.absoluteValue)/(f * (length/pipeDiameter) * density) + val sqrtFlowSpeed = sign(finalPressureDrop) * sqrt(flowSpeed) + val volumetricFlowRate = sqrtFlowSpeed * (Math.pow(Math.PI * radius, 2.0) / 4.0) + + flowRate = volumetricFlowRate * density + + return flowRate } private fun densityAverage(gasMasses: EnumMap): Double { @@ -540,6 +704,36 @@ class DuctNetworkImpl( return specificHeat } + private fun heatConductivityAverage(gasMasses: EnumMap, pressure: Double, temperature: Double): Double { + val totalMass = gasMasses.values.sum() + if (totalMass == 0.0) { + return 0.0 + } + + val massPerGas = EnumMap(GasType::class.java) + + val gasWeight = EnumMap(GasType::class.java) + + gasMasses.keys.forEach { + if (gasMasses[it] != 0.0 ) { + massPerGas[it] = gasMasses[it]!! + } + + } + + for (gas in massPerGas.keys) { + gasWeight[gas] = massPerGas[gas]!! / totalMass + } + + var heatConductivity = 0.0 + + for (gas in gasWeight.keys) { + heatConductivity += gasWeight[gas]!! * ((gas.thermalConductivity * (temperature/300.0)) * (1.0 + (0.0075 * (pressure/101325.0)))) + } + + return heatConductivity + } + override fun dump() { KELVINLOGGER.logger.info("Disabling Kelvin...") diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/impl/DuctNodeInfo.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/impl/DuctNodeInfo.kt index 1fb077310..c690730c7 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/impl/DuctNodeInfo.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/kelvin/impl/DuctNodeInfo.kt @@ -4,4 +4,4 @@ import org.valkyrienskies.clockwork.kelvin.api.GasType import org.valkyrienskies.clockwork.kelvin.api.NodeBehaviorType import java.util.EnumMap -data class DuctNodeInfo(var nodeType: NodeBehaviorType, var currentTemperature: Double, var currentPressure: Double, val currentGasVolumes: EnumMap, var previousTemperatureLevel: Int = 0, var previousPressure: Double = 0.0) +data class DuctNodeInfo(var nodeType: NodeBehaviorType, var currentTemperature: Double, var currentPressure: Double, val currentGasMasses: EnumMap, var previousTemperatureLevel: Int = 0, var previousPressure: Double = 0.0) diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/util/AerodynamicUtils.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/util/AerodynamicUtils.kt index 7acb2462c..36775cc80 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/util/AerodynamicUtils.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/util/AerodynamicUtils.kt @@ -1,6 +1,12 @@ package org.valkyrienskies.clockwork.util +import net.minecraft.util.Mth +import org.valkyrienskies.clockwork.ClockworkMod import org.valkyrienskies.clockwork.content.forces.DragController +import org.valkyrienskies.clockwork.kelvin.api.GasType +import org.valkyrienskies.clockwork.kelvin.impl.DuctNetworkImpl.Companion.idealGasConstant +import java.util.* +import kotlin.math.* /** * Contains useful functions for features that need funny wind maths. Mainly drag and balloons. @@ -37,7 +43,11 @@ object AerodynamicUtils { fun getAirDensityForY(y: Double, maxHeight: Double): Double { val worldScale = 11000.0 / (maxHeight - 63.0) - val realAltitude = (y - 63.0) * worldScale + val realAltitude = if ((y - 63.0) * worldScale >= 0) { + (y - 63.0) * worldScale + } else { + 0.0 + } val layer = when { realAltitude < 11000 -> 0 @@ -105,12 +115,391 @@ object AerodynamicUtils { } } + fun getAirPressureForY(y: Double, maxHeight: Double): Double { + val worldScale = 11000.0 / (maxHeight - 63.0) + + val realAltitude = if ((y - 63.0) * worldScale >= 0) { + (y - 63.0) * worldScale + } else { + 0.0 + } + + val layer = when { + realAltitude < 11000 -> 0 + realAltitude < 20000 -> 1 + realAltitude < 32000 -> 2 + realAltitude < 47000 -> 3 + realAltitude < 51000 -> 4 + realAltitude < 71000 -> 5 + else -> 6 + } + + val hb = when (layer) { + 0 -> 0.0 + 1 -> 11000.0 + 2 -> 20000.0 + 3 -> 32000.0 + 4 -> 47000.0 + 5 -> 51000.0 + 6 -> 71000.0 + else -> 0.0 + } + + val pb = when (layer) { + 0 -> 101325.0 + 1 -> 22632.1 + 2 -> 5474.89 + 3 -> 868.02 + 4 -> 110.91 + 5 -> 66.94 + 6 -> 3.96 + else -> 0.0 + } + + val Tb = when (layer) { + 0 -> 288.15 + 1 -> 216.65 + 2 -> 216.65 + 3 -> 228.65 + 4 -> 270.65 + 5 -> 270.65 + 6 -> 214.65 + else -> 0.0 + } + + val g0 = GRAVITATIONAL_ACCELERATION + + val R = UNIVERSAL_GAS_CONSTANT + + val M = AIR_MOLAR_MASS + + val L = when (layer) { + 0 -> 0.0065 + 1 -> 0.0 + 2 -> -0.001 + 3 -> -0.0028 + 4 -> 0.0 + 5 -> 0.0028 + 6 -> 0.002 + else -> 0.0 + } + + return when (L != 0.0) { + true -> pb * Math.pow(1.0 - (L / Tb) * (realAltitude - hb), ((g0 * M) / (R * L))) + else -> pb * Math.exp((-g0 * M * (realAltitude - hb)) / (R * Tb)) + } + } + + fun getAirTemperatureForY(y: Double, maxHeight: Double): Double { + val worldScale = 11000.0 / (maxHeight - 63.0) + + val realAltitude = if ((y - 63.0) * worldScale >= 0) { + (y - 63.0) * worldScale + } else { + 0.0 + } + + val layer = when { + realAltitude < 11000 -> 0 + realAltitude < 20000 -> 1 + realAltitude < 32000 -> 2 + realAltitude < 47000 -> 3 + realAltitude < 51000 -> 4 + realAltitude < 71000 -> 5 + else -> 6 + } + + val hb = when (layer) { + 0 -> 0.0 + 1 -> 11000.0 + 2 -> 20000.0 + 3 -> 32000.0 + 4 -> 47000.0 + 5 -> 51000.0 + 6 -> 71000.0 + else -> 0.0 + } + + val Tb = when (layer) { + 0 -> 288.15 + 1 -> 216.65 + 2 -> 216.65 + 3 -> 228.65 + 4 -> 270.65 + 5 -> 270.65 + 6 -> 214.65 + else -> 0.0 + } + + val L = when (layer) { + 0 -> 0.0065 + 1 -> 0.0 + 2 -> -0.001 + 3 -> -0.0028 + 4 -> 0.0 + 5 -> 0.0028 + 6 -> 0.002 + else -> 0.0 + } + + return when (L != 0.0) { + true -> Tb + (L * (realAltitude - hb)) + else -> Tb + } + } + + fun getDensityFromTemperature(volume: Double, mass: Double, temperature: Double, gasType: GasType): Double { + if (volume == 0.0) return 0.0 + + var density = (mass) / volume + + if (temperature != 0.0) { + val molarMass = gasType.density * 22.4 + val pressure = calcPressure(mass, volume, temperature, gasType) + density = (molarMass * pressure) / (UNIVERSAL_GAS_CONSTANT * temperature) + } + return density + } + + /** + * Calculates pressure using the ideal gas law. + */ + fun calcPressure(mass: Double, volume: Double, temp: Double, gasType: GasType): Double { + if (volume == 0.0) return 0.0 + val adjustedTemp = max(temp,0.001) + val pressure: Double + val molarMass = gasType.density * 22.4 + val moles = mass / molarMass + pressure = ((moles) * UNIVERSAL_GAS_CONSTANT * adjustedTemp) / volume + return pressure + } + + /** + * Calculates pressure using the ideal gas law. For use with the average of multiple gas types rather than one. + */ + fun calcPressure(mass: Double, volume: Double, temp: Double, standardDensity: Double): Double { + if (volume == 0.0 || mass == 0.0) return 0.0 + val adjustedTemp = max(temp,0.0001) + val pressure: Double + val density: Double = mass / volume + val molarMass = standardDensity * 22.4 + val specificGasConstant = (idealGasConstant / molarMass) * 1000.0 + val moles = mass / molarMass + pressure = (density * specificGasConstant * adjustedTemp) + + return pressure + } + + fun densityAverage(gasMasses: EnumMap): Double { + val totalMass = gasMasses.values.sum() + + if (totalMass == 0.0) { + return 0.0 + } + + val massPerGas = EnumMap(GasType::class.java) + + val gasWeight = EnumMap(GasType::class.java) + + gasMasses.keys.forEach { + if (gasMasses[it] != 0.0 ) { + + + massPerGas[it] = gasMasses[it]!! + + } + + } + + for (gas in massPerGas.keys) { + + gasWeight[gas] = massPerGas[gas]!! / totalMass + } + + var density = 0.0 + + for (gas in gasWeight.keys) { + density += gasWeight[gas]!! * gas.density + } + + + return density + } + + fun densityFromPressureAverage(gasMasses: EnumMap, temp: Double, pressure: Double): Double { + val totalMass = gasMasses.values.sum() + if (totalMass == 0.0) { + return 0.0 + } + + val massPerGas = EnumMap(GasType::class.java) + + val gasWeight = EnumMap(GasType::class.java) + + gasMasses.keys.forEach { + if (gasMasses[it] != 0.0 ) { + massPerGas[it] = gasMasses[it]!! + } + + } + + for (gas in massPerGas.keys) { + gasWeight[gas] = massPerGas[gas]!! / totalMass + } + + var density = 0.0 + + for (gas in gasWeight.keys) { + val molarMass = gas.density * 22.4 + val specificGasConstant = idealGasConstant / molarMass + density += gasWeight[gas]!! * (pressure / (specificGasConstant * temp)) + } + + return density + } + + fun dynamicViscosityAverage(gasMasses: EnumMap, temp: Double): Double { + val totalMass = gasMasses.values.sum() + if (totalMass == 0.0) { + return 0.0 + } + + val massPerGas = EnumMap(GasType::class.java) + + val gasWeight = EnumMap(GasType::class.java) + + gasMasses.keys.forEach { + if (gasMasses[it] != 0.0 ) { + massPerGas[it] = gasMasses[it]!! + } + + } + + for (gas in massPerGas.keys) { + gasWeight[gas] = massPerGas[gas]!! / totalMass + } + + var viscosity = 0.0 + + for (gas in gasWeight.keys) { + viscosity += gasWeight[gas]!! * (gas.viscosity * (temp / 273.15) * ((273.15 + gas.sutherlandConstant) / (temp + gas.sutherlandConstant))) + } + + return viscosity + } + + fun calculateFlow(pressureOne: Double, pressureTwo: Double, radius: Double, length: Double, densityA: Double, densityB: Double, viscosity: Double, pumpPressure: Double = 0.0, previousFlowRate: Double = 0.0): Double { + var flowRate = 0.0 + if (densityA <= 0 && densityB <= 0) { + return flowRate + } + val density = if (pressureOne > pressureTwo) densityA else densityB + // -- constants + // (meters) + val pipeRoughness = 0.00012 + val pipeDiameter = radius * 2.0 + + var pressureDrop = (pressureOne - pressureTwo + pumpPressure) + + if (pressureOne <= 0.0001 && pumpPressure.absoluteValue > 0.0) { + pressureDrop = min(pressureDrop, 0.0) + } + + if (pressureTwo <= 0.0001 && pumpPressure < 0.0) { + pressureDrop = max(pressureDrop, 0.0) + } + + val finalPressureDrop = pressureDrop + + val Re = max((density * previousFlowRate * pipeDiameter) / viscosity, 0.0001) + + var f: Double = if (Re < 2000) { + 64.0/Re + } else if (Re > 4000) { + 0.25 / (Math.pow(Math.log10(((pipeRoughness / pipeDiameter) / 3.7) + (5.74 / Math.pow(Re, 0.9))), 2.0)) + } else { + Mth.clampedLerp(64.0/Re, 0.25 / (Math.pow(Math.log10(((pipeRoughness / pipeDiameter) / 3.7) + (5.74 / Math.pow(Re, 0.9))), 2.0)),(Re-2000.0)/(4000.0-2000.0)) + } + + val flowSpeed = (2.0*finalPressureDrop.absoluteValue)/(f * (length/pipeDiameter) * density) + val sqrtFlowSpeed = sign(finalPressureDrop) * sqrt(flowSpeed) + val volumetricFlowRate = sqrtFlowSpeed * (Math.pow(Math.PI * radius, 2.0) / 4.0) + + flowRate = volumetricFlowRate * density + + return flowRate + } + + fun specificHeatAverage(gasMasses: EnumMap): Double { + val totalMass = gasMasses.values.sum() + if (totalMass == 0.0) { + return 0.0 + } + + val massPerGas = EnumMap(GasType::class.java) + + val gasWeight = EnumMap(GasType::class.java) + + gasMasses.keys.forEach { + if (gasMasses[it] != 0.0 ) { + massPerGas[it] = gasMasses[it]!! + } + + } + + for (gas in massPerGas.keys) { + gasWeight[gas] = massPerGas[gas]!! / totalMass + } + + var specificHeat = 0.0 + + for (gas in gasWeight.keys) { + specificHeat += gasWeight[gas]!! * gas.specificHeatCapacity + } + + return specificHeat + } + + //Returns an average Specific Gas Constant, Sutherland Constant, and Adiabatic Index for a given gas mixture + fun extraHeatInfoAverage(gasMasses: EnumMap): Triple { + val totalMass = gasMasses.values.sum() + if (totalMass == 0.0) { + return Triple(0.0,0.0,0.0) + } + + val massPerGas = EnumMap(GasType::class.java) + + val gasWeight = EnumMap(GasType::class.java) + + gasMasses.keys.forEach { + if (gasMasses[it] != 0.0 ) { + massPerGas[it] = gasMasses[it]!! + } + + } + + for (gas in massPerGas.keys) { + gasWeight[gas] = massPerGas[gas]!! / totalMass + } + + var specificGasConstant = 0.0 + var sutherlandConstant = 0.0 + var adiabaticIndex = 0.0 + + for (gas in gasWeight.keys) { + specificGasConstant += gasWeight[gas]!! * (UNIVERSAL_GAS_CONSTANT / (gas.density * 22.4)) + sutherlandConstant += gasWeight[gas]!! * gas.sutherlandConstant + adiabaticIndex += gasWeight[gas]!! * gas.adiabaticIndex + } + + return Triple(specificGasConstant, sutherlandConstant, adiabaticIndex) + } // useful values - const val DRAG_COEFFICIENT = 3.15 + const val DRAG_COEFFICIENT = 4.15 const val GRAVITATIONAL_ACCELERATION = 9.80665 - const val UNIVERSAL_GAS_CONSTANT = 8.3144598 + const val UNIVERSAL_GAS_CONSTANT = 8.314 const val AIR_MOLAR_MASS = 0.0289644 } \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/util/ClockworkUtils.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/util/ClockworkUtils.kt index b933f5503..88ab94ae3 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/util/ClockworkUtils.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/util/ClockworkUtils.kt @@ -14,7 +14,14 @@ import org.joml.Vector3i import org.joml.Vector3ic import org.joml.primitives.AABBi import org.joml.primitives.AABBic +import org.valkyrienskies.clockwork.ClockworkAugmentations +import org.valkyrienskies.clockwork.ClockworkMod import org.valkyrienskies.clockwork.content.curiosities.tools.wanderwand.SelectedAreaToolkit +import org.valkyrienskies.clockwork.kelvin.api.GasType +import org.valkyrienskies.clockwork.util.MathFunctions.chunkPos +import org.valkyrienskies.clockwork.util.MathFunctions.toVector3i +import org.valkyrienskies.core.api.ships.properties.ChunkClaim +import org.valkyrienskies.core.api.world.connectivity.DoubleComponentAugmentation import org.valkyrienskies.core.impl.util.serialization.VSJacksonUtil.defaultMapper import org.valkyrienskies.mod.common.BlockStateInfo import org.valkyrienskies.mod.common.dimensionId @@ -133,4 +140,66 @@ object ClockworkUtils { } return nbt } + + fun retrieveGasInfoFromPocket(pos: Vector3ic, level: ServerLevel): Pair, Double> { + val gasMap = EnumMap(GasType::class.java) + for (type in GasType.entries) { + val key = ClockworkAugmentations.getComponentAugmentation("gas_" + type.name.lowercase(Locale.getDefault())) + val gas = level.shipObjectWorld.getAirComponentAugmentation(key, pos.x(), pos.y(), pos.z(), level.dimensionId) + gasMap[type] = gas + } + + val temperature = level.shipObjectWorld.getAirComponentAugmentation( + ClockworkAugmentations.getComponentAugmentation("temperature"), + pos.x(), + pos.y(), + pos.z(), + level.dimensionId + ) + + return Pair(gasMap, temperature) + } + + /** + * Retrieves all components within a given chunk claim, using a key as reference. + * + * Temporary implementation until it's properly indexed in VS Core. + */ + @Deprecated("Temporary. Will be replaced with proper implementation in VS Core.") + fun getAirComponentsInChunkClaim(claim: ChunkClaim, level: ServerLevel, referenceKey: DoubleComponentAugmentation): HashMap { + val map = HashMap() + level.shipObjectWorld.getFromEachAirComponentRoot(referenceKey, level.dimensionId).keys.forEach { pos -> + if (claim.contains(pos.chunkPos().x, pos.chunkPos().z)) { + map[pos.toVector3i()] = try { + level.shipObjectWorld.getAirComponentSize(pos.first, pos.second, pos.third, level.dimensionId) + } catch (e: IllegalArgumentException) { + -1 + } + if (map[pos.toVector3i()] == -1L) { + ClockworkMod.LOGGER.warn("Failed to get air component size at $pos") + } + } + } + return HashMap(map.filterNot { it.value == -1L }) + } + + /** + * Retrieves all components within a given chunk claim, using a key as reference. + * + * Temporary implementation until it's properly indexed in VS Core. + */ + @Deprecated("Temporary. Will be replaced with proper implementation in VS Core.") + fun getSolidComponentsInChunkClaim(claim: ChunkClaim, level: ServerLevel, referenceKey: DoubleComponentAugmentation): HashMap { + val map = HashMap() + level.shipObjectWorld.getFromEachSolidComponentRoot(referenceKey, level.dimensionId).keys.forEach { pos -> + if (claim.contains(pos.chunkPos().x, pos.chunkPos().z)) { + map[pos.toVector3i()] = try { + level.shipObjectWorld.getSolidComponentSize(pos.first, pos.second, pos.third, level.dimensionId) + } catch (e: IllegalArgumentException) { + -1 + } + } + } + return HashMap(map.filterNot { it.value == -1L }) + } } \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/util/DoubleAvgAugmentation.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/util/DoubleAvgAugmentation.kt new file mode 100644 index 000000000..fba74012d --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/util/DoubleAvgAugmentation.kt @@ -0,0 +1,9 @@ +package org.valkyrienskies.clockwork.util + +import org.valkyrienskies.core.api.world.connectivity.DoubleAugmentation + +class DoubleAvgAugmentation(override val key: String) : DoubleAugmentation { + override fun combineDouble(a: Double, b: Double): Double { + return (a + b) / 2.0 + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/util/DoubleAvgComponentAugmentation.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/util/DoubleAvgComponentAugmentation.kt new file mode 100644 index 000000000..76c34fa2f --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/util/DoubleAvgComponentAugmentation.kt @@ -0,0 +1,15 @@ +package org.valkyrienskies.clockwork.util + +import org.valkyrienskies.core.api.util.DoublePair +import org.valkyrienskies.core.api.world.connectivity.Component +import org.valkyrienskies.core.api.world.connectivity.DoubleComponentAugmentation + +class DoubleAvgComponentAugmentation(override val key: String) : DoubleComponentAugmentation { + override fun combineDouble(a: Double, b: Double): Double { + return (a + b) / 2.0 + } + + override fun splitDouble(value: Double, component1: Component, component2: Component): DoublePair { + return DoublePair(value, value) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/util/MathFunctions.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/util/MathFunctions.kt index d69a38015..e233acc9e 100644 --- a/common/src/main/kotlin/org/valkyrienskies/clockwork/util/MathFunctions.kt +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/util/MathFunctions.kt @@ -32,7 +32,7 @@ object MathFunctions { } fun Triple.chunkPos(): ChunkPos { - return ChunkPos(this.first shr 4, this.third shr 4) + return ChunkPos((this.first shr 4), (this.third shr 4)) } fun Vector3ic.toTriple(): Triple { diff --git a/common/src/main/kotlin/org/valkyrienskies/clockwork/util/PIDstance.kt b/common/src/main/kotlin/org/valkyrienskies/clockwork/util/PIDstance.kt new file mode 100644 index 000000000..cd87d40c1 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/clockwork/util/PIDstance.kt @@ -0,0 +1,29 @@ +package org.valkyrienskies.clockwork.util + +class PIDstance(val p: Double = 1.0, val i: Double = 0.1, val d: Double = 0.05) { + private var integral = 0.0 + private var lastError = 0.0 + + val isIntegralReset: Boolean + get() = integral == 0.0 + + fun resetIntegral() { + integral = 0.0 + } + + fun control(targetPressure: Double, currentPressure: Double, Kp: Double = p, Ki: Double = i, Kd: Double = d): Double { + val error = targetPressure - currentPressure + + val proportional = Kp * error + + integral += error / 20.0 + val integralTerm = Ki * integral + + val derivative = (error - lastError) / 20.0 + val derivativeTerm = Kd * derivative + + lastError = error + + return proportional + integralTerm + derivativeTerm + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/vs_clockwork/blockstates/ballooner.json b/common/src/main/resources/assets/vs_clockwork/blockstates/ballooner.json new file mode 100644 index 000000000..2e01b9805 --- /dev/null +++ b/common/src/main/resources/assets/vs_clockwork/blockstates/ballooner.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "vs_clockwork:block/ballooner/block" + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/vs_clockwork/models/block/gas_nozzle/axis.json b/common/src/main/resources/assets/vs_clockwork/models/block/gas_nozzle/axis.json new file mode 100644 index 000000000..c037f3b88 --- /dev/null +++ b/common/src/main/resources/assets/vs_clockwork/models/block/gas_nozzle/axis.json @@ -0,0 +1,42 @@ +{ + "credit": "Made with Blockbench", + "textures": { + "0": "create:block/axis", + "1": "create:block/axis_top", + "particle": "create:block/axis" + }, + "elements": [ + { + "from": [0, 6, 6], + "to": [1, 10, 10], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 8, 8]}, + "faces": { + "north": {"uv": [6, 0, 10, 1], "rotation": 90, "texture": "#0"}, + "south": {"uv": [6, 0, 10, 1], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 6, 10, 10], "texture": "#1"}, + "up": {"uv": [6, 0, 10, 1], "rotation": 90, "texture": "#0"}, + "down": {"uv": [6, 0, 10, 1], "rotation": 90, "texture": "#0"} + } + }, + { + "from": [15, 6, 6], + "to": [16, 10, 10], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 8, 8]}, + "faces": { + "north": {"uv": [6, 15, 10, 16], "rotation": 90, "texture": "#0"}, + "east": {"uv": [6, 6, 10, 10], "texture": "#1"}, + "south": {"uv": [6, 15, 10, 16], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 15, 10, 16], "rotation": 90, "texture": "#0"}, + "down": {"uv": [6, 15, 10, 16], "rotation": 90, "texture": "#0"} + } + } + ], + "groups": [ + { + "name": "shaft", + "origin": [0, -1, 0], + "color": 0, + "children": [0, 1] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/assets/vs_clockwork/models/block/peepotron.json b/common/src/main/resources/assets/vs_clockwork/models/block/peepotron.json new file mode 100644 index 000000000..6ea395093 --- /dev/null +++ b/common/src/main/resources/assets/vs_clockwork/models/block/peepotron.json @@ -0,0 +1,78 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [24, 16], + "textures": { + "0": "vs_clockwork:block/peepulon", + "particle": "vs_clockwork:block/peepulon" + }, + "elements": [ + { + "from": [0, 0, 0], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 0, 5.33333, 8], "texture": "#0"}, + "east": {"uv": [5.33333, 8, 10.66667, 16], "texture": "#0"}, + "south": {"uv": [10.66667, 8, 16, 16], "texture": "#0"}, + "west": {"uv": [5.33333, 8, 10.66667, 16], "texture": "#0"}, + "up": {"uv": [5.33333, 0, 10.66667, 8], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 8, 5.33333, 16], "rotation": 180, "texture": "#0"} + } + }, + { + "from": [9, 6, -1], + "to": [16, 12, 0], + "faces": { + "north": {"uv": [0, 2, 2.33333, 5], "texture": "#0"}, + "east": {"uv": [0, 2, 0.33333, 5], "texture": "#0"}, + "south": {"uv": [0, 2, 2.33333, 5], "texture": "#0"}, + "west": {"uv": [2, 2, 2.33333, 5], "texture": "#0"}, + "up": {"uv": [2.33333, 2, 0, 2.5], "texture": "#0"}, + "down": {"uv": [2.33333, 4.5, 0, 5], "texture": "#0"} + } + }, + { + "from": [0, 6, -1], + "to": [7, 12, 0], + "rotation": {"angle": 0, "axis": "y", "origin": [16, 0, 0]}, + "faces": { + "north": {"uv": [3, 2, 5.33333, 5], "texture": "#0"}, + "east": {"uv": [5.33333, 2, 5, 5], "texture": "#0"}, + "south": {"uv": [3, 2, 5.33333, 5], "texture": "#0"}, + "west": {"uv": [3.33333, 2, 3, 5], "texture": "#0"}, + "up": {"uv": [5.33333, 2, 3, 2.5], "texture": "#0"}, + "down": {"uv": [3, 4.5, 5.33333, 5], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/vs_clockwork/textures/block/peepulon.png b/common/src/main/resources/assets/vs_clockwork/textures/block/peepulon.png new file mode 100644 index 000000000..df0bc9318 Binary files /dev/null and b/common/src/main/resources/assets/vs_clockwork/textures/block/peepulon.png differ diff --git a/gradle.properties b/gradle.properties index 2e55e66bb..e1e1e48b9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,8 +19,8 @@ loader_platform = Fabric parchment_version = 2022.11.06 # VS2 -vs2_version=2.1.1-beta.6+ed0ef81d50 -vscore_version=1.1.0+13b73ea871 +vs2_version=2.1.1-beta.6+635678ad92 +vscore_version=1.1.0+e213cc9916 # Fabric # https://fabricmc.net/develop/ diff --git a/settings.gradle.kts b/settings.gradle.kts index b2daf22c9..f2c36e934 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,4 +33,11 @@ include("fabric") //include("api") //include("impl") +try { + val kelvin = file("../kelvin") + if (kelvin.isDirectory) { + includeBuild(kelvin) + } +} catch (ignore: SecurityException) {} + rootProject.name = "vs-clockwork-mod"