Skip to content

Commit

Permalink
Basic rockets
Browse files Browse the repository at this point in the history
  • Loading branch information
Seggan committed Mar 14, 2024
1 parent d3c0506 commit 7df7158
Show file tree
Hide file tree
Showing 17 changed files with 364 additions and 128 deletions.
1 change: 1 addition & 0 deletions plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {
library(kotlin("stdlib"))
libraryAndTest("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0-RC2")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0") // For some reason libraryloader doesn't like this
libraryAndTest(kotlin("reflect"))

libraryAndTest(kotlin("scripting-common"))
libraryAndTest(kotlin("scripting-jvm"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.addoncommunity.galactifun.api.objects.properties.atmosphere

import io.github.addoncommunity.galactifun.units.Density
import io.github.addoncommunity.galactifun.units.Density.Companion.kilogramsPerLiter
import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup
import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack
import io.github.thebusybiscuit.slimefun4.api.recipes.RecipeType
Expand All @@ -9,22 +11,22 @@ import io.github.thebusybiscuit.slimefun4.utils.SlimefunUtils
import org.bukkit.inventory.ItemStack

/**
* @property liquidDensity density of the gas in the liquid state, in kg/l
* @property liquidDensity density of the gas in the liquid state
*/
enum class Gas(texture: String?, val liquidDensity: Double) {
OXYGEN("5b3ad76aadb80ecf4b4cdbe76b8704b0f2dc090b49b65c36d87ed879f1065ef2", 1.146),
NITROGEN("5b3ad76aadb80ecf4b4cdbe76b8704b0f2dc090b49b65c36d87ed879f1065ef2", 0.811),
CARBON_DIOXIDE("5b3ad76aadb80ecf4b4cdbe76b8704b0f2dc090b49b65c36d87ed879f1065ef2", 0.002),
WATER("5b3ad76aadb80ecf4b4cdbe76b8704b0f2dc090b49b65c36d87ed879f1065ef2", 0.997),
HELIUM("93dfa904fe3d0306666a573c22eec1dd0a8051e32a38ea2d19c4b5867e232a49", 0.141),
ARGON("ea005531b6167a86fb09d6c0f3db60f2650162d0656c2908d07b377111d8f2a2", 1.401),
METHANE("ea005531b6167a86fb09d6c0f3db60f2650162d0656c2908d07b377111d8f2a2", 0.424),
enum class Gas(texture: String?, val liquidDensity: Density) {
OXYGEN("5b3ad76aadb80ecf4b4cdbe76b8704b0f2dc090b49b65c36d87ed879f1065ef2", 1.146.kilogramsPerLiter),
NITROGEN("5b3ad76aadb80ecf4b4cdbe76b8704b0f2dc090b49b65c36d87ed879f1065ef2", 0.811.kilogramsPerLiter),
CARBON_DIOXIDE("5b3ad76aadb80ecf4b4cdbe76b8704b0f2dc090b49b65c36d87ed879f1065ef2", 0.002.kilogramsPerLiter),
WATER("5b3ad76aadb80ecf4b4cdbe76b8704b0f2dc090b49b65c36d87ed879f1065ef2", 0.997.kilogramsPerLiter),
HELIUM("93dfa904fe3d0306666a573c22eec1dd0a8051e32a38ea2d19c4b5867e232a49", 0.141.kilogramsPerLiter),
ARGON("ea005531b6167a86fb09d6c0f3db60f2650162d0656c2908d07b377111d8f2a2", 1.401.kilogramsPerLiter),
METHANE("ea005531b6167a86fb09d6c0f3db60f2650162d0656c2908d07b377111d8f2a2", 0.424.kilogramsPerLiter),
// Using hexane for reference density
HYDROCARBONS("725691372e0734bfb57bb03690490661a83f053a3488860df3436ce1caa24d11", 0.655),
HYDROGEN("725691372e0734bfb57bb03690490661a83f053a3488860df3436ce1caa24d11", 0.072),
SULFUR("c7a1ece691ad28d17bbbcecb22270c85e1c9581485806264c676de67c272e2d0", 1.819),
AMMONIA("c7a1ece691ad28d17bbbcecb22270c85e1c9581485806264c676de67c272e2d0", 0.683),
OTHER(null, Double.NaN);
HYDROCARBONS("725691372e0734bfb57bb03690490661a83f053a3488860df3436ce1caa24d11", 0.655.kilogramsPerLiter),
HYDROGEN("725691372e0734bfb57bb03690490661a83f053a3488860df3436ce1caa24d11", 0.072.kilogramsPerLiter),
SULFUR("c7a1ece691ad28d17bbbcecb22270c85e1c9581485806264c676de67c272e2d0", 1.819.kilogramsPerLiter),
AMMONIA("c7a1ece691ad28d17bbbcecb22270c85e1c9581485806264c676de67c272e2d0", 0.683.kilogramsPerLiter),
OTHER(null, Double.NaN.kilogramsPerLiter);

val item = texture?.let {
SlimefunItemStack(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,101 @@
package io.github.addoncommunity.galactifun.api.rockets

import io.github.addoncommunity.galactifun.Constants
import io.github.addoncommunity.galactifun.api.objects.properties.atmosphere.Gas
import io.github.addoncommunity.galactifun.impl.items.FuelTank
import io.github.addoncommunity.galactifun.impl.items.RocketEngine
import io.github.addoncommunity.galactifun.impl.managers.PlanetManager
import io.github.addoncommunity.galactifun.units.Acceleration
import io.github.addoncommunity.galactifun.units.Force
import io.github.addoncommunity.galactifun.units.Mass
import io.github.addoncommunity.galactifun.units.times
import io.github.addoncommunity.galactifun.util.items.mass
import io.github.addoncommunity.galactifun.units.*
import io.github.addoncommunity.galactifun.util.general.mergeMaps
import io.github.addoncommunity.galactifun.util.items.wetMass
import io.github.addoncommunity.galactifun.util.processSlimefunBlocks
import io.github.thebusybiscuit.slimefun4.libraries.dough.blocks.BlockPosition
import kotlin.math.ln
import kotlin.time.Duration.Companion.seconds

data class RocketInfo(
class RocketInfo(
val commandComputer: BlockPosition,
val blocks: Set<BlockPosition>,
val engines: List<Pair<RocketEngine, Set<BlockPosition>>>
engineData: Map<Pair<RocketEngine, BlockPosition>, Set<BlockPosition>>
) {
val thrust: Force
get() = engines.fold(Force.ZERO) { acc, engine -> acc + engine.first.thrust }
val mass: Mass
get() = blocks.fold(Mass.ZERO) { acc, block -> acc + block.block.mass }

fun twr(gravity: Acceleration): Double {
if (gravity == Acceleration.ZERO) return Double.POSITIVE_INFINITY
return thrust / (mass * gravity)
}
val engines = engineData.keys.map { it.first }

val thrust = engines.sumBy { it.thrust }
val wetMass = blocks.sumBy { it.block.wetMass }

val stages: List<Stage>

init {
stages = if (engineData.isEmpty()) {
emptyList()
} else {
val sortedEngines = engineData.toList().sortedBy { it.first.second.y }
val stageList = mutableListOf<Stage>()

val fuel: List<Pair<List<RocketEngine>, Map<Gas, Double>>>
get() {
TODO()
for ((engine, fuelBlocks) in sortedEngines) {
val engines = stageList.flatMap { it.engines }.map { it.second }
if (stageList.any { engine.second in engines }) continue
val stage = sortedEngines.filter { it.second == fuelBlocks }.map { it.first }
stageList.add(Stage(stage, fuelBlocks))
}

stageList
}
}

val info: String
get() = buildString {
val planet = PlanetManager.getByWorld(commandComputer.world) ?: error("Planet not found")
appendLine("Fuel:")
for ((gas, amount) in fuel) {
append(" ".repeat(4))
append(gas)
//appendLine(": %.2f l, %.2f kg".format(amount, amount * gas.liquidDensity))
val dryMass = wetMass - stages.sumBy { it.fuelMass }

val info = buildString {
val planet = PlanetManager.getByWorld(commandComputer.world) ?: error("Planet not found")
appendLine("Stages:")
var stageNum = 1
for (stage in stages) {
appendLine(" Stage ${stageNum++}: ")
appendLine(" Fuel:")
for ((gas, volume) in stage.fuel) {
appendLine(" $gas: %.2f liters, %.2s".format(volume.liters, volume * gas.liquidDensity))
}
appendLine(" Engines:")
var engineNum = 1
for (engine in stage.engines) {
appendLine(" Engine ${engineNum++}: %.2f kilonewtons".format(engine.first.thrust.kilonewtons))
}
appendLine("Thrust: %.2f kN".format(thrust.kilonewtons))
appendLine("Mass: %.2f kg".format(mass.kilograms))
appendLine("TWR: %.2f".format(twr(planet.gravity)))
appendLine(" Delta-V: %.2f m/s".format(stage.deltaV.metersPerSecond))
}
appendLine("Thrust: %.2f kilonewtons".format(thrust.kilonewtons))
appendLine("Wet mass: %.2s".format(wetMass))
appendLine("Dry mass: %.2s".format(dryMass))
appendLine("TWR: %.2f".format(twr(planet.gravity)))
appendLine("Delta-V: %.2f m/s".format(deltaV(engines, wetMass, dryMass).metersPerSecond))
}

fun twr(gravity: Acceleration): Double {
if (gravity == Acceleration.ZERO) return Double.POSITIVE_INFINITY
return thrust / (wetMass * gravity)
}

inner class Stage(
val engines: List<Pair<RocketEngine, BlockPosition>>,
val fuelBlocks: Set<BlockPosition>
) {
val fuel: Map<Gas, Volume> = fuelBlocks.processSlimefunBlocks<FuelTank, _>(FuelTank::getFuelLevel)
.fold(mapOf()) { acc, fuel -> acc.mergeMaps(fuel, Volume::plus) }

val fuelMass = fuel.toList().sumBy { (gas, volume) -> gas.liquidDensity * volume }

val deltaV = deltaV(
engines.map { it.first },
wetMass,
wetMass - fuelMass
)
}
}

private fun deltaV(engines: List<RocketEngine>, wetMass: Mass, dryMass: Mass): Velocity {
val ispNeum = engines.sumBy { it.thrust }.newtons
val ispDenom = engines.sumOf { it.thrust.newtons / it.specificImpulse.doubleSeconds }
val isp = (ispNeum / ispDenom).seconds

return Constants.EARTH_GRAVITY * isp * ln(wetMass / dryMass)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.github.addoncommunity.galactifun.blocks

import io.github.addoncommunity.galactifun.units.Mass
import org.bukkit.block.Block

interface CustomMass {
fun getMass(block: Block): Mass

fun getWetMass(block: Block): Mass = getMass(block)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.github.addoncommunity.galactifun.blocks

import io.github.addoncommunity.galactifun.units.Mass
import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup
import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem
import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack
import io.github.thebusybiscuit.slimefun4.api.recipes.RecipeType
import io.github.thebusybiscuit.slimefun4.core.handlers.BlockUseHandler
import org.bukkit.block.Block
import org.bukkit.inventory.ItemStack

open class BasicMassedBlock(
itemGroup: ItemGroup,
item: SlimefunItemStack,
recipeType: RecipeType,
recipe: Array<out ItemStack?>,
private val mass: Mass
) : SlimefunItem(itemGroup, item, recipeType, recipe), CustomMass {

init {
addItemHandler(BlockUseHandler {
it.cancel()
})
}

override fun getMass(block: Block): Mass = mass
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,57 @@ package io.github.addoncommunity.galactifun.impl
import io.github.addoncommunity.galactifun.api.objects.properties.atmosphere.Gas
import io.github.addoncommunity.galactifun.impl.items.CommandComputer
import io.github.addoncommunity.galactifun.impl.items.FuelTank
import io.github.addoncommunity.galactifun.impl.items.RocketEngine
import io.github.addoncommunity.galactifun.pluginInstance
import io.github.addoncommunity.galactifun.units.Force.Companion.kilonewtons
import io.github.addoncommunity.galactifun.units.Volume.Companion.liters
import io.github.addoncommunity.galactifun.util.items.MaterialType
import io.github.addoncommunity.galactifun.util.items.buildSlimefunItem
import io.github.thebusybiscuit.slimefun4.api.recipes.RecipeType
import org.bukkit.Material
import kotlin.time.Duration.Companion.seconds

object GalactifunItems {

val COMMAND_COMPUTER = buildSlimefunItem<CommandComputer> {
category = GalactifunCategories.ROCKET_COMPONENTS
id = "COMMAND_COMPUTER"
name = "&fCommand Computer"
name = "<white>Command Computer"
material = MaterialType.Material(Material.CHISELED_QUARTZ_BLOCK)
recipeType = RecipeType.NULL
recipe = emptyArray()

+"&7The core for any rocket"
}

val FUEL_TANK_I = buildSlimefunItem<FuelTank>(1000.0) {
val FUEL_TANK_I = buildSlimefunItem<FuelTank>(1000.liters) {
category = GalactifunCategories.ROCKET_COMPONENTS
id = "FUEL_TANK_I"
name = "&fFuel Tank I"
name = "<white>TNK-1000 \"Big Boy\""
material = MaterialType.Material(Material.IRON_BLOCK)
recipeType = RecipeType.NULL
recipe = emptyArray()

+"<gray>Definitely not a tin can"
+""
+"<gray>Manufacturer: Found by the side of the road"
+"<yellow>Capacity: 1000 L"
}

val ROCKET_ENGINE_I = buildSlimefunItem<RocketEngine>(300.seconds, 100.kilonewtons, Gas.HYDROGEN) {
category = GalactifunCategories.ROCKET_COMPONENTS
id = "ROCKET_ENGINE_I"
name = "<white>BMR-50 \"Lil' Boomer\""
material = MaterialType.Material(Material.FURNACE)
recipeType = RecipeType.NULL
recipe = emptyArray()

+"<gray>Not to be used for grilling burgers"
+""
+"<gray>Manufacturer: Boomer & Bros. Explosives, Inc."
+"<yellow>Thrust: 100 kN"
+"<yellow>Specific impulse: 300 s"
+"<yellow>Fuel: Hydrogen"
}

init {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@ import io.github.addoncommunity.galactifun.util.checkBlock
import io.github.addoncommunity.galactifun.util.floodSearch
import io.github.addoncommunity.galactifun.util.items.TickingBlock
import io.github.addoncommunity.galactifun.util.plus
import io.github.addoncommunity.galactifun.util.processSlimefunBlocks
import io.github.seggan.sf4k.location.position
import io.github.thebusybiscuit.slimefun4.api.events.PlayerRightClickEvent
import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup
import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem
import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack
import io.github.thebusybiscuit.slimefun4.api.recipes.RecipeType
import io.github.thebusybiscuit.slimefun4.core.handlers.BlockPlaceHandler
import io.github.thebusybiscuit.slimefun4.core.handlers.BlockUseHandler
import io.github.thebusybiscuit.slimefun4.libraries.dough.blocks.BlockPosition
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import me.mrCookieSlime.Slimefun.api.BlockStorage
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.block.Block
import org.bukkit.event.block.BlockPlaceEvent
Expand Down Expand Up @@ -51,12 +50,14 @@ class CommandComputer(
}

private fun rescanRocket(pos: BlockPosition) {
val rocketBlocks = pos.floodSearch { this.checkBlock<RocketEngine>() != null }
val rocketBlocks = pos.floodSearch { this.checkBlock<RocketEngine>() == null }
val detected = if (!rocketBlocks.exceededMax) rocketBlocks.found else emptySet()
val blocks = detected.map(BlockPosition::getBlock)
val engines = blocks.processSlimefunBlocks<RocketEngine, _> {
this to it.position.floodSearch { this.checkBlock<FuelTank>() != null }.found
}
val engines = blocks.processSlimefunBlocks<RocketEngine, _> { b ->
val fuel = b.position.floodSearch { it.checkBlock<FuelTank>() != null }.found.toMutableSet()
fuel.remove(b.position)
(this to b.position) to fuel
}.toMap()
RocketManager.register(RocketInfo(pos, detected, engines))
}

Expand All @@ -70,17 +71,4 @@ class CommandComputer(
}
e.player.sendMessage(NamedTextColor.GOLD + info.info)
}
}

private inline fun <reified S : SlimefunItem, T> Iterable<Block>.processSlimefunBlocks(
processor: S.(Block) -> T
): List<T> {
val result = mutableListOf<T>()
for (b in this) {
val item = BlockStorage.check(b)
if (item is S) {
result.add(item.processor(b))
}
}
return result
}
Loading

0 comments on commit 7df7158

Please sign in to comment.