diff --git a/build.gradle.kts b/build.gradle.kts index 11b335e..ce3c4dd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { kotlin("jvm") version "2.0.0" apply false + kotlin("plugin.serialization") version "1.9.23" apply false } allprojects { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34..09523c0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index b1a4967..fcb2b03 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -1,11 +1,11 @@ plugins { kotlin("jvm") - kotlin("plugin.serialization") version "1.9.23" + kotlin("plugin.serialization") id("com.google.devtools.ksp") version "2.0.0-1.0.21" id("com.github.johnrengelman.shadow") version "8.1.1" id("net.minecrell.plugin-yml.bukkit") version "0.6.0" - id("xyz.jpenilla.run-paper") version "2.2.0" + id("xyz.jpenilla.run-paper") version "2.3.0" } repositories { @@ -41,15 +41,14 @@ dependencies { implementation("org.bstats:bstats-bukkit:3.0.2") implementation("co.aikar:acf-paper:0.5.1-SNAPSHOT") - implementation("com.jeff_media:MorePersistentDataTypes:2.4.0") - implementation("io.github.seggan:sf4k:0.3.2") + implementation("io.github.seggan:sf4k:0.4.1") implementation(project(":uom")) ksp(project(":uom-processor")) testImplementation(kotlin("test")) - testImplementation("io.kotest:kotest-assertions-core:5.8.0") + testImplementation("io.strikt:strikt-core:0.34.0") testImplementation("com.github.seeseemelk:MockBukkit-v1.20:3.80.0") testImplementation("com.github.shynixn.mccoroutine:mccoroutine-bukkit-test:2.14.0") } @@ -81,11 +80,15 @@ tasks.shadowJar { relocate(from, "io.github.addoncommunity.galactifun.shadowlibs.$last") } - doRelocate("io.github.seggan.kfun") + // Relocate if true or not set, always relocate bstats doRelocate("org.bstats") - doRelocate("co.aikar.commands") - doRelocate("co.aikar.locales") - doRelocate("com.jeff_media.morepersistentdatatypes") + if (System.getenv("RELOCATE") != "false") { + doRelocate("io.github.seggan.kfun") + doRelocate("co.aikar.commands") + doRelocate("co.aikar.locales") + } else { + archiveClassifier = "unrelocated" + } dependencies { exclude(dependency("org.jetbrains.kotlin:kotlin-stdlib")) @@ -109,6 +112,12 @@ bukkit { } tasks.runServer { + javaLauncher = javaToolchains.launcherFor { + @Suppress("UnstableApiUsage") + vendor = JvmVendorSpec.JETBRAINS + languageVersion = JavaLanguageVersion.of(17) + } + jvmArgs("-XX:+AllowEnhancedClassRedefinition", "-XX:+AllowRedefinitionToAddDeleteMethods") downloadPlugins { url("https://blob.build/dl/Slimefun4/Dev/1116") } diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/Galactifun2.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/Galactifun2.kt index 11780e0..17f54cb 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/Galactifun2.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/Galactifun2.kt @@ -19,8 +19,8 @@ import io.github.addoncommunity.galactifun.units.Angle.Companion.degrees import io.github.addoncommunity.galactifun.units.Distance.Companion.au import io.github.addoncommunity.galactifun.units.Distance.Companion.kilometers import io.github.addoncommunity.galactifun.units.Mass.Companion.kilograms +import io.github.addoncommunity.galactifun.util.bukkit.plus import io.github.addoncommunity.galactifun.util.general.log -import io.github.addoncommunity.galactifun.util.plus import io.github.seggan.sf4k.AbstractAddon import io.github.thebusybiscuit.slimefun4.api.MinecraftVersion import io.github.thebusybiscuit.slimefun4.implementation.Slimefun @@ -29,7 +29,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.withContext import kotlinx.datetime.Instant +import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.Style +import net.kyori.adventure.text.format.TextDecoration import org.bstats.bukkit.Metrics import org.bukkit.Bukkit import org.bukkit.Material @@ -47,6 +50,7 @@ import kotlin.time.Duration.Companion.hours open class Galactifun2 : AbstractAddon() { private lateinit var manager: PaperCommandManager + lateinit var launchMessages: List private set var isTest = classLoader.javaClass.packageName.startsWith("be.seeseemelk.mockbukkit") @@ -61,7 +65,7 @@ open class Galactifun2 : AbstractAddon() { var shouldDisable = false if (!PaperLib.isPaper() && !isTest) { - logger.log(Level.SEVERE, "Galactifun2 only supports Paper and its forks (e.x. Airplane and Purpur)") + logger.log(Level.SEVERE, "Galactifun2 only supports Paper and its forks (e.x. Airplane or Purpur)") logger.log(Level.SEVERE, "Please use Paper or a fork of Paper") shouldDisable = true } @@ -108,6 +112,8 @@ open class Galactifun2 : AbstractAddon() { } manager.registerCommand(Gf2Command) + launchMessages = config.getStringList("rockets.launch-msgs") + BaseUniverse.init() val scriptsFolder = dataFolder.resolve("planets") @@ -161,8 +167,9 @@ open class Galactifun2 : AbstractAddon() { override suspend fun onDisableAsync() { Bukkit.getConsoleSender().sendMessage( - NamedTextColor.GREEN + - "YOU MAY SAFELY IGNORE THE COROUTINE CANCELLATION EXCEPTION BELOW, I HAVE NO IDEA HOW TO FIX IT" + Component.text() + .content("YOU MAY SAFELY IGNORE THE COROUTINE CANCELLATION EXCEPTION BELOW, I HAVE NO IDEA HOW TO FIX IT") + .style(Style.style(NamedTextColor.GREEN, TextDecoration.BOLD)) ) } diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/betteritem/BetterSlimefunItem.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/betteritem/BetterSlimefunItem.kt index ec08c38..81b65d1 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/betteritem/BetterSlimefunItem.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/betteritem/BetterSlimefunItem.kt @@ -6,6 +6,8 @@ import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack import io.github.thebusybiscuit.slimefun4.api.recipes.RecipeType import io.github.thebusybiscuit.slimefun4.core.handlers.* import io.github.thebusybiscuit.slimefun4.implementation.handlers.SimpleBlockBreakHandler +import me.mrCookieSlime.CSCoreLibPlugin.Configuration.Config +import me.mrCookieSlime.Slimefun.Objects.handlers.BlockTicker import org.bukkit.block.Block import org.bukkit.event.block.BlockBreakEvent import org.bukkit.event.block.BlockPlaceEvent @@ -97,11 +99,24 @@ open class BetterSlimefunItem : SlimefunItem { for (method in javaClass.getAllMethods()) { if (method.isAnnotationPresent(ItemHandler::class.java)) { method.isAccessible = true - val handle = MethodHandles.lookup().unreflect(method) + val handle = MethodHandles.lookup().unreflect(method).bindTo(this) val handler = method.getAnnotation(ItemHandler::class.java).handler - val handlerInstance = handlerMap[handler]?.invoke(handle.bindTo(this)) + val handlerInstance = handlerMap[handler]?.invoke(handle) ?: throw IllegalStateException("Handler $handler is not registered for BetterSlimefunItem") addItemHandler(handlerInstance) + } else if (method.isAnnotationPresent(Ticker::class.java)) { + method.isAccessible = true + val handle = MethodHandles.lookup().unreflect(method).bindTo(this) + val ticker = method.getAnnotation(Ticker::class.java) + addItemHandler(object : BlockTicker() { + override fun tick(b: Block, item: SlimefunItem, data: Config) { + handle.invoke(b) + } + + override fun isSynchronized(): Boolean { + return !ticker.async + } + }) } } } diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/betteritem/Ticker.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/betteritem/Ticker.kt new file mode 100644 index 0000000..18e95b8 --- /dev/null +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/betteritem/Ticker.kt @@ -0,0 +1,5 @@ +package io.github.addoncommunity.galactifun.api.betteritem + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class Ticker(val async: Boolean = false) diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/CelestialObject.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/CelestialObject.kt index a3df1fb..fe01ef4 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/CelestialObject.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/CelestialObject.kt @@ -7,10 +7,13 @@ import io.github.addoncommunity.galactifun.units.Angle.Companion.degrees import io.github.addoncommunity.galactifun.units.Distance import io.github.addoncommunity.galactifun.units.Mass import io.github.addoncommunity.galactifun.units.Velocity.Companion.metersPerSecond +import io.github.addoncommunity.galactifun.util.bukkit.plus import io.github.addoncommunity.galactifun.util.general.LazyDouble import io.github.thebusybiscuit.slimefun4.libraries.dough.items.CustomItemStack import io.github.thebusybiscuit.slimefun4.utils.ChatUtils import kotlinx.datetime.Instant +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor import org.bukkit.inventory.ItemStack import kotlin.math.sqrt @@ -19,7 +22,14 @@ sealed class CelestialObject(name: String, baseItem: ItemStack) { val name = ChatUtils.removeColorCodes(name) val id = this.name.lowercase().replace(' ', '_') - val item = CustomItemStack(baseItem, name) + val item: ItemStack by lazy { + CustomItemStack(baseItem) { meta -> + meta.displayName(Component.text(name)) + val lore = mutableListOf() + addLoreProperties(lore) + meta.lore(lore.map { NamedTextColor.GRAY + it }) + } + } abstract val mass: Mass abstract val radius: Distance @@ -49,6 +59,13 @@ sealed class CelestialObject(name: String, baseItem: ItemStack) { abstract fun distanceTo(other: CelestialObject, time: Instant): Distance + protected open fun addLoreProperties(lore: MutableList) { + lore.add("Mass: $mass") + lore.add("Radius: $radius") + lore.add("Gravity: $gravity") + lore.add("Escape Velocity: $escapeVelocity") + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is CelestialObject) return false diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/PlanetaryObject.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/PlanetaryObject.kt index 7ec7995..bd80825 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/PlanetaryObject.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/PlanetaryObject.kt @@ -13,6 +13,7 @@ import io.github.addoncommunity.galactifun.units.Velocity.Companion.metersPerSec import kotlinx.datetime.Instant import org.bukkit.inventory.ItemStack import kotlin.math.sqrt +import kotlin.time.Duration abstract class PlanetaryObject(name: String, baseItem: ItemStack) : CelestialObject(name, baseItem) { @@ -114,4 +115,44 @@ abstract class PlanetaryObject(name: String, baseItem: ItemStack) : CelestialObj return thisSibling.orbit.arbitraryTransfer(otherSibling.orbit, time) + thisDeltaV + otherDeltaV } } + + override fun addLoreProperties(lore: MutableList) { + super.addLoreProperties(lore) + lore.add("") + lore.add("Day length: $dayCycle") + lore.add("") + lore.add("Semimajor axis: %,d kilometers".format(orbit.semimajorAxis.kilometers)) + val c = orbit.semimajorAxis * orbit.eccentricity + lore.add("Periapasis: %,d kilometers".format((orbit.semimajorAxis - c).kilometers)) + lore.add("Apoapsis: %,d kilometers".format((orbit.semimajorAxis + c).kilometers)) + lore.add("Eccentricity: %.2f".format(orbit.eccentricity)) + lore.add("Longitude of periapsis: %.2f°".format(orbit.longitudeOfPeriapsis.degrees)) + lore.add("Orbital period (year length): ${durationToYearsAndDays(orbit.period)}") + if (atmosphere.pressure > 0) { + lore.add("") + lore.add("Atmospheric pressure: %.2f atmospheres".format(atmosphere.pressure)) + } + if (orbiters.isNotEmpty()) { + lore.add("") + lore.add("Number of moons: ${orbiters.size}") + } + } +} + +private fun durationToYearsAndDays(duration: Duration): String { + val sb = StringBuilder() + val years = duration.inWholeDays / 365 + val days = duration.inWholeDays % 365 + if (years > 0) { + sb.append(years) + sb.append(" year") + if (years > 1) sb.append('s') + } + if (days > 0) { + if (years > 0) sb.append(", ") + sb.append(days) + sb.append(" day") + if (days > 1) sb.append('s') + } + return sb.toString() } diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/DayCycle.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/DayCycle.kt index aea6993..a2b1ed6 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/DayCycle.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/DayCycle.kt @@ -27,4 +27,20 @@ class DayCycle(val duration: Duration) { fun tick(world: World) { world.time += perFiveSeconds } + + override fun toString(): String { + val sb = StringBuilder() + if (duration.inWholeDays > 0) { + sb.append(duration.inWholeDays) + sb.append(" day") + if (duration.inWholeDays > 1) sb.append('s') + } + if (duration.inWholeHours > 0) { + if (sb.isNotEmpty()) sb.append(", ") + sb.append(duration.inWholeHours % 24) + sb.append(" hour") + if (duration.inWholeHours % 24 > 1) sb.append('s') + } + return sb.toString() + } } \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/OrbitPosition.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/OrbitPosition.kt index 3947152..2f4f6f4 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/OrbitPosition.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/OrbitPosition.kt @@ -1,11 +1,11 @@ package io.github.addoncommunity.galactifun.api.objects.properties import io.github.addoncommunity.galactifun.impl.managers.PlanetManager -import io.github.seggan.sf4k.location.plus +import io.github.addoncommunity.galactifun.util.bukkit.copy +import kotlinx.serialization.Serializable import org.bukkit.Location -import org.bukkit.persistence.PersistentDataAdapterContext -import org.bukkit.persistence.PersistentDataType +@Serializable data class OrbitPosition(val x: Int, val z: Int) { companion object { @@ -25,17 +25,9 @@ data class OrbitPosition(val x: Int, val z: Int) { z * ORBIT_SIZE + ORBIT_SIZE / 2.0 ) - fun offset(location: Location): Location = centerLocation + location - - object DataType : PersistentDataType { - override fun getPrimitiveType() = IntArray::class.java - override fun getComplexType() = OrbitPosition::class.java - - override fun fromPrimitive(primitive: IntArray, context: PersistentDataAdapterContext): OrbitPosition { - return OrbitPosition(primitive[0], primitive[1]) - } - override fun toPrimitive(complex: OrbitPosition, context: PersistentDataAdapterContext): IntArray { - return intArrayOf(complex.x, complex.z) - } - } + fun offset(x: Double, y: Double, z: Double) = centerLocation.copy( + x = centerLocation.x + x, + y = centerLocation.y + y, + z = centerLocation.z + z + ) } diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/atmosphere/Atmosphere.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/atmosphere/Atmosphere.kt index 03c4936..ee5bc1c 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/atmosphere/Atmosphere.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/atmosphere/Atmosphere.kt @@ -1,6 +1,6 @@ package io.github.addoncommunity.galactifun.api.objects.properties.atmosphere -import io.github.addoncommunity.galactifun.util.set +import io.github.addoncommunity.galactifun.util.bukkit.set import io.github.thebusybiscuit.slimefun4.libraries.dough.collections.RandomizedSet import org.bukkit.GameRule import org.bukkit.Material @@ -10,7 +10,7 @@ class Atmosphere private constructor( private val weatherEnabled: Boolean, private val storming: Boolean, private val thundering: Boolean, - private val pressure: Double, + val pressure: Double, private val composition: Map ) { diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/atmosphere/Gas.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/atmosphere/Gas.kt index 44c409e..487123a 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/atmosphere/Gas.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/objects/properties/atmosphere/Gas.kt @@ -30,10 +30,12 @@ enum class Gas(texture: String?, val liquidDensity: Density) { val item = texture?.let { SlimefunItemStack( - "ATMOSPHERIC_GAS_$name", + "GF2_ATMOSPHERIC_GAS_$name", SlimefunUtils.getCustomHead(texture), "&f$this Gas Canister", "", + "&7Contains one kilogram of $this", + "", "&f&oTexture by Sefiraat" ) } diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/rockets/RocketInfo.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/rockets/RocketInfo.kt index 9f00e73..e9aee35 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/rockets/RocketInfo.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/api/rockets/RocketInfo.kt @@ -21,8 +21,8 @@ class RocketInfo( val engines = engineData.keys.map { it.first } - val thrust = engines.sumBy { it.thrust } - val wetMass = blocks.sumBy { it.block.wetMass } + val thrust = engines.unitSumOf { it.thrust } + val wetMass = blocks.unitSumOf { it.block.wetMass } val stages: List @@ -44,7 +44,7 @@ class RocketInfo( } } - val dryMass = wetMass - stages.sumBy { it.fuelMass } + val dryMass = wetMass - stages.unitSumOf { it.fuelMass } val info = buildString { val planet = PlanetManager.getByWorld(commandComputer.world) ?: error("Planet not found") @@ -72,7 +72,8 @@ class RocketInfo( fun twr(gravity: Acceleration): Double { if (gravity == Acceleration.ZERO) return Double.POSITIVE_INFINITY - return thrust / (wetMass * gravity) + if (stages.isEmpty()) return 0.0 + return stages.first().engines.unitSumOf { it.first.thrust } / (wetMass * gravity) } inner class Stage( @@ -82,7 +83,7 @@ class RocketInfo( val fuel: Map = fuelBlocks.processSlimefunBlocks(FuelTank::getFuelLevel) .fold(mapOf()) { acc, fuel -> acc.mergeMaps(fuel, Volume::plus) } - val fuelMass = fuel.toList().sumBy { (gas, volume) -> gas.liquidDensity * volume } + val fuelMass = fuel.toList().unitSumOf { (gas, volume) -> gas.liquidDensity * volume } val deltaV = deltaV( engines.map { it.first }, @@ -93,7 +94,7 @@ class RocketInfo( } private fun deltaV(engines: List, wetMass: Mass, dryMass: Mass): Velocity { - val ispNeum = engines.sumBy { it.thrust }.newtons + val ispNeum = engines.unitSumOf { it.thrust }.newtons val ispDenom = engines.sumOf { it.thrust.newtons / it.specificImpulse.doubleSeconds } val isp = if (ispDenom == 0.0) 0.seconds else (ispNeum / ispDenom).seconds diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/GalactifunCategories.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/GalactifunCategories.kt index 86897c3..db21d47 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/GalactifunCategories.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/GalactifunCategories.kt @@ -1,6 +1,6 @@ package io.github.addoncommunity.galactifun.impl -import io.github.addoncommunity.galactifun.util.key +import io.github.addoncommunity.galactifun.util.bukkit.key import io.github.thebusybiscuit.slimefun4.api.items.groups.NestedItemGroup import io.github.thebusybiscuit.slimefun4.api.items.groups.SubItemGroup import io.github.thebusybiscuit.slimefun4.libraries.dough.items.CustomItemStack diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/GalactifunItems.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/GalactifunItems.kt index 09fcf94..2b2e875 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/GalactifunItems.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/GalactifunItems.kt @@ -53,10 +53,10 @@ object GalactifunItems { +"Capacity: 1000 L" } - val ROCKET_ENGINE_I = buildSlimefunItem(3000.seconds, 100.kilonewtons, Gas.HYDROGEN) { + val ROCKET_ENGINE_I = buildSlimefunItem(30000.seconds, 100.kilonewtons, Gas.HYDROGEN) { category = GalactifunCategories.ROCKET_COMPONENTS id = "ROCKET_ENGINE_I" - name = "BMR-50 \"Lil' Boomer\"" + name = """BMR-50 "Lil' Boomer"""" material = MaterialType.Material(Material.FURNACE) recipeType = RecipeType.NULL recipe = emptyArray() @@ -65,7 +65,23 @@ object GalactifunItems { +"" +"Manufacturer: Boomer & Bros. Explosives, Inc." +"Thrust: 100 kN" - +"Specific impulse: 3000 s" + +"Specific impulse: 30,000 s" + +"Fuel: Hydrogen" + } + + val ROCKET_ENGINE_II = buildSlimefunItem(20000.seconds, 300.kilonewtons, Gas.HYDROGEN) { + category = GalactifunCategories.ROCKET_COMPONENTS + id = "ROCKET_ENGINE_II" + name = """BMR-100 "Normal Boomer"""" + material = MaterialType.Material(Material.FURNACE) + recipeType = RecipeType.NULL + recipe = emptyArray() + + +"Your standard junkyard rocket engine" + +"" + +"Manufacturer: Boomer & Bros. Explosives, Inc." + +"Thrust: 300 kN" + +"Specific impulse: 20,000 s" +"Fuel: Hydrogen" } diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/Gf2Command.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/Gf2Command.kt index b1c61e7..0293122 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/Gf2Command.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/Gf2Command.kt @@ -5,11 +5,14 @@ import co.aikar.commands.annotation.* import io.github.addoncommunity.galactifun.api.objects.PlanetaryObject import io.github.addoncommunity.galactifun.api.objects.planet.PlanetaryWorld import io.github.addoncommunity.galactifun.impl.managers.PlanetManager +import io.github.addoncommunity.galactifun.impl.managers.RocketManager import io.github.addoncommunity.galactifun.util.PlanetMenu -import io.github.addoncommunity.galactifun.util.galactifunTeleport +import io.github.addoncommunity.galactifun.util.SerializedBlock +import io.github.addoncommunity.galactifun.util.bukkit.galactifunTeleport import io.github.seggan.sf4k.location.plusAssign import kotlinx.datetime.Clock import org.bukkit.Location +import org.bukkit.entity.BlockDisplay import org.bukkit.entity.Player @Suppress("unused") @@ -51,4 +54,32 @@ object Gf2Command : BaseCommand() { fun selector(player: Player) { PlanetMenu().open(player) } + + @Subcommand("undisplayentityify") + @CommandPermission(Permissions.ADMIN) + @Description("Turns all serialized display entities into blocks") + fun undiplayentityify(player: Player) { + var count = 0 + for (entity in player.world.entities) { + if (entity is BlockDisplay) { + val block = SerializedBlock.loadFromDisplayEntity(entity) + if (block != null) { + block.place(entity.location) + count++ + } + } + } + player.sendMessage("Undisplayentityified $count entities") + } + + @Subcommand("cancellaunches") + @CommandPermission(Permissions.ADMIN) + @Description("Cancels all rocket launches") + fun cancelLaunches(player: Player) { + for (launch in RocketManager.launches) { + launch.cancel() + } + player.sendMessage("Cancelled ${RocketManager.launches.size} rocket launches") + RocketManager.launches.clear() + } } \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/Permissions.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/Permissions.kt index 9298f34..6d0b430 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/Permissions.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/Permissions.kt @@ -1,6 +1,6 @@ package io.github.addoncommunity.galactifun.impl object Permissions { - const val TELEPORT = "galactifun.teleport" + const val ADMIN = "galactifun.admin" } \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/CaptainsChair.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/CaptainsChair.kt index 1402503..f43a1e1 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/CaptainsChair.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/CaptainsChair.kt @@ -1,9 +1,16 @@ package io.github.addoncommunity.galactifun.impl.items +import io.github.addoncommunity.galactifun.api.rockets.RocketInfo import io.github.addoncommunity.galactifun.impl.items.abstract.Seat +import io.github.addoncommunity.galactifun.impl.managers.PlanetManager +import io.github.addoncommunity.galactifun.impl.managers.RocketManager +import io.github.addoncommunity.galactifun.util.bukkit.plus +import io.github.seggan.sf4k.serial.blockstorage.getBlockStorage import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack import io.github.thebusybiscuit.slimefun4.api.recipes.RecipeType +import io.github.thebusybiscuit.slimefun4.libraries.dough.blocks.BlockPosition +import net.kyori.adventure.text.format.NamedTextColor import org.bukkit.block.Block import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack @@ -16,6 +23,41 @@ class CaptainsChair( ) : Seat(itemGroup, item, recipeType, recipe) { override fun onSit(p: Player, b: Block) { - p.sendMessage("You are now sitting in the Captain's Chair") + val rocket = getRocket(b) + if (rocket == null) { + p.sendMessage(NamedTextColor.RED + "This chair hasn't connected to the rocket yet, please make sure the rocket has a command computer and try again in a few seconds.") + return + } + p.sendMessage(NamedTextColor.GOLD + rocket.info) + val currentPlanet = PlanetManager.getByWorld(p.world) ?: return +// object : PlanetMenu() { +// override fun modifyItem(p: Player, obj: CelestialObject, item: ItemStack): ItemStack { +// item.modifyLore { +// if (obj == currentPlanet) { +// it.add(NamedTextColor.GREEN + "You are here") +// } +// if (obj.orbiters.isNotEmpty()) { +// it.add(NamedTextColor.YELLOW + "Left click to view orbiters") +// } +// if (obj is AlienWorld) { +// it.add(NamedTextColor.YELLOW + "Right click to travel to this planet") +// } +// } +// return item +// } +// +// override fun onClick(p: Player, obj: CelestialObject, action: ClickAction): Boolean { +// if (action.isRightClicked && obj is PlanetaryWorld) { +// rocket.travelTo(obj) +// return false +// } +// return true +// } +// } + } + + private fun getRocket(b: Block): RocketInfo? { + val commandComputer = b.getBlockStorage("rocket") ?: return null + return RocketManager.getInfo(commandComputer) } } \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/CommandComputer.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/CommandComputer.kt index 4455b04..4565a4d 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/CommandComputer.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/CommandComputer.kt @@ -1,13 +1,27 @@ package io.github.addoncommunity.galactifun.impl.items +import com.github.shynixn.mccoroutine.bukkit.launch +import io.github.addoncommunity.galactifun.api.betteritem.BetterSlimefunItem +import io.github.addoncommunity.galactifun.api.betteritem.ItemHandler +import io.github.addoncommunity.galactifun.api.betteritem.Ticker import io.github.addoncommunity.galactifun.api.rockets.RocketInfo +import io.github.addoncommunity.galactifun.impl.items.abstract.Seat +import io.github.addoncommunity.galactifun.impl.managers.PlanetManager import io.github.addoncommunity.galactifun.impl.managers.RocketManager -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.addoncommunity.galactifun.pluginInstance +import io.github.addoncommunity.galactifun.units.Velocity.Companion.metersPerSecond +import io.github.addoncommunity.galactifun.units.abs +import io.github.addoncommunity.galactifun.units.div +import io.github.addoncommunity.galactifun.units.times +import io.github.addoncommunity.galactifun.units.unitSumOf +import io.github.addoncommunity.galactifun.util.* +import io.github.addoncommunity.galactifun.util.bukkit.* +import io.github.seggan.sf4k.location.plus import io.github.seggan.sf4k.location.position +import io.github.seggan.sf4k.serial.blockstorage.getBlockStorage +import io.github.seggan.sf4k.serial.blockstorage.setBlockStorage +import io.github.seggan.sf4k.serial.pdc.get +import io.github.seggan.sf4k.serial.pdc.set import io.github.thebusybiscuit.slimefun4.api.events.PlayerRightClickEvent import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack @@ -16,32 +30,41 @@ 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 kotlinx.coroutines.Job +import kotlinx.coroutines.future.await +import kotlinx.coroutines.joinAll +import me.mrCookieSlime.Slimefun.api.BlockStorage import net.kyori.adventure.text.format.NamedTextColor +import org.bukkit.Location +import org.bukkit.Particle import org.bukkit.block.Block +import org.bukkit.block.BlockFace +import org.bukkit.entity.BlockDisplay +import org.bukkit.entity.Entity +import org.bukkit.entity.LivingEntity +import org.bukkit.entity.Player import org.bukkit.event.block.BlockPlaceEvent import org.bukkit.inventory.ItemStack +import org.bukkit.util.BoundingBox import java.util.* +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.collections.ArrayDeque import kotlin.jvm.optionals.getOrNull +import kotlin.math.max +import kotlin.random.Random +import kotlin.time.Duration.Companion.seconds class CommandComputer( itemGroup: ItemGroup, item: SlimefunItemStack, recipeType: RecipeType, recipe: Array -) : TickingBlock(itemGroup, item, recipeType, recipe) { +) : BetterSlimefunItem(itemGroup, item, recipeType, recipe) { private val counters = Object2IntOpenHashMap() - init { - addItemHandler(BlockUseHandler(::onRightClick)) - addItemHandler(object : BlockPlaceHandler(false) { - override fun onPlayerPlace(e: BlockPlaceEvent) { - rescanRocket(e.block.position) - } - }) - } - - override fun tick(b: Block) { + @Ticker + private fun tick(b: Block) { val pos = b.position val counter = counters.mergeInt(pos, 1, Int::plus) if (counter % 8 == 0) { @@ -49,10 +72,18 @@ class CommandComputer( } } + @ItemHandler(BlockPlaceHandler::class) + private fun onPlace(e: BlockPlaceEvent) { + rescanRocket(e.block.position) + } + private fun rescanRocket(pos: BlockPosition) { val rocketBlocks = pos.floodSearch { this.checkBlock() == null } val detected = if (!rocketBlocks.exceededMax) rocketBlocks.found else emptySet() val blocks = detected.map(BlockPosition::getBlock) + blocks.processSlimefunBlocks { + it.setBlockStorage("rocket", pos) + } val engines = blocks.processSlimefunBlocks { b -> val fuel = b.position.floodSearch { it.checkBlock() != null }.found.toMutableSet() fuel.remove(b.position) @@ -61,15 +92,203 @@ class CommandComputer( RocketManager.register(RocketInfo(pos, detected, engines)) } + @ItemHandler(BlockUseHandler::class) private fun onRightClick(e: PlayerRightClickEvent) { + e.cancel() + val p = e.player val pos = e.clickedBlock.getOrNull()?.position ?: return rescanRocket(pos) val info = RocketManager.getInfo(pos)!! if (info.blocks.isEmpty()) { - e.player.sendMessage(NamedTextColor.RED + "No rocket detected") + p.sendMessage(NamedTextColor.RED + "No rocket detected") return } - e.player.sendMessage(NamedTextColor.GOLD + info.info) - e.cancel() + val seat = Seat.getSitting(p) + if (seat != null + && BlockStorage.check(seat) is CaptainsChair + && seat.getBlockStorage("rocket") == pos + ) { + RocketManager.launches += pluginInstance.launch { launchRocket(p, pos, info, seat) } + } else { + e.player.sendMessage(NamedTextColor.GOLD + info.info) + } + } + + private suspend fun launchRocket(p: Player, pos: BlockPosition, rocket: RocketInfo, seat: Location) { + val world = p.world + val currentPlanet = PlanetManager.getByWorld(world) + if (currentPlanet == null) { + p.sendMessage(NamedTextColor.RED + "You are not on a planet") + return + } + val space = currentPlanet.orbitPosition + p.sendMessage("The rocket's current position is ${pos.x}, ${pos.y}, ${pos.z}") + p.sendMessage("The cost to travel to space is %.2s".format(currentPlanet.surfaceToOrbitCost)) + + val firstStage = rocket.stages.first() + val dVMinusSpace = firstStage.deltaV - currentPlanet.surfaceToOrbitCost + if (dVMinusSpace.metersPerSecond <= 0) { + p.sendMessage( + NamedTextColor.RED + + "The rocket needs %.2s more delta-v to reach space".format(abs(dVMinusSpace)) + ) + return + } + p.sendMessage("The rocket will have %.2s delta-v left after reaching space".format(dVMinusSpace)) + + if (rocket.twr(currentPlanet.gravity) < 1) { + p.sendMessage("The rocket doesn't have enough thrust-to-weight ratio to get off the ground") + return + } + + val hasBlocking = rocket.blocks.groupBy { it.x to it.z } + .map { (_, blocks) -> blocks.maxBy { it.y } } + .any { !it.isHighest() } + + if (hasBlocking) { + p.sendMessage("The rocket is blocked by terrain") + return + } + + p.sendMessage("Enter the destination's x, y, z coordinates separated by commas") + val coords = p.awaitChatInput() + if (Seat.getSitting(p) != seat) return + val match = coordinateRegex.matchEntire(coords) + if (match == null) { + p.sendMessage("Invalid coordinates") + return + } + val (x, y, z) = match.destructured + val dest = space.offset(x.toDouble(), y.toDouble(), z.toDouble()) + + val entities = mutableSetOf(p) + rocket.blocks.consumeSpreadOut(200) { b -> + val block = b.block + val foundEntities = world.getNearbyEntities(BoundingBox.of(block)) + foundEntities.addAll( + world.getNearbyEntities(BoundingBox.of(block.getRelative(BlockFace.UP))) + .filter { it.isOnGround } + ) + val serialized = SerializedBlock.serialize(block) + val display = serialized.createDisplayEntity(b.location) + entities.add(display) + entities.addAll(foundEntities.filter { it.vehicle == null }.flatMap(::freezeEntity)) + } + + // Random launch messages + val launched = AtomicBoolean(false) + pluginInstance.launch { + val launchMessages = ArrayDeque(pluginInstance.launchMessages) + launchMessages.shuffle() + while (true) { + delayTicks(Random.nextInt(15, 30)) + if (launched.get()) break + for (player in entities.filterIsInstance()) { + player.sendMessage(launchMessages.removeFirst() + "...") + } + } + } + + // Engine smoke + pluginInstance.launch { + while (!launched.get()) { + for ((_, engine) in firstStage.engines) { + world.spawnParticle( + Particle.CAMPFIRE_SIGNAL_SMOKE, + engine.location.add(0.0, -1.0, 0.0), + Random.nextInt(1, 3), + 0.5, + 0.0, + 0.5 + ) + } + delayTicks(2) + } + } + + // Countdown + repeat(10) { + for (player in entities.filterIsInstance()) { + player.sendMessage(NamedTextColor.GOLD + "Launching in ${10 - it}...") + } + delayTicks(20) + } + launched.set(true) + + val movingEntities = entities.filter { it.vehicle == null } + val offsets = movingEntities.associateWith { it.location.subtract(pos.toLocation()) } + val maxHeight = world.maxHeight + var position = pos.y.toDouble() + + val weight = rocket.wetMass * currentPlanet.gravity + val netForce = firstStage.engines.unitSumOf { it.first.thrust } - weight + val acceleration = netForce / rocket.wetMass + val marginalAcceleration = acceleration * 0.05.seconds + var speed = 0.0.metersPerSecond + while (position < maxHeight) { + for (entity in movingEntities) { + entity.galactifunTeleport(entity.location.add(0.0, (speed / 20).metersPerSecond, 0.0)) + speed += marginalAcceleration + position = max(position, entity.location.y) + } + delayTicks(1) + } + + val jobs = mutableListOf() + for (entity in movingEntities) { + jobs += pluginInstance.launch { + entity.galactifunTeleport( + dest.copy(world = PlanetManager.spaceWorld) + offsets[entity]!! + ).await() + unfreezeEntity(entity) + } + } + jobs.joinAll() + + for (entity in entities) { + if (!entity.isValid || entity in movingEntities) continue + unfreezeEntity(entity) + } + } +} + +private val coordinateRegex = """\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*""".toRegex() +private val GRAVITY_KEY = "has_gravity".key() +private val AI_KEY = "has_ai".key() +private val FLIGHT_KEY = "is_flight".key() + +fun freezeEntity(entity: Entity): List { + val pdc = entity.persistentDataContainer + pdc.set(GRAVITY_KEY, entity.hasGravity()) + entity.setGravity(false) + if (entity is LivingEntity) { + if (entity is Player) { + pdc.set(FLIGHT_KEY, entity.allowFlight) + entity.allowFlight = false + } else { + pdc.set(AI_KEY, entity.hasAI()) + entity.setAI(false) + } + } + return entity.passengers.flatMap(::freezeEntity) + entity +} + +fun unfreezeEntity(entity: Entity) { + val pdc = entity.persistentDataContainer + when (entity) { + is Player -> { + entity.sendMessage("You have arrived at your destination") + entity.allowFlight = pdc.get(FLIGHT_KEY) ?: false + } + + is BlockDisplay -> SerializedBlock.loadFromDisplayEntity(entity, true) + ?.place(entity.location) + + else -> { + entity.setGravity(pdc.get(GRAVITY_KEY) ?: true) + if (entity is LivingEntity) { + entity.setAI(pdc.get(AI_KEY) ?: true) + } + } } } \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/FuelTank.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/FuelTank.kt index e506c4a..8e2a6a8 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/FuelTank.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/FuelTank.kt @@ -2,13 +2,9 @@ package io.github.addoncommunity.galactifun.impl.items import io.github.addoncommunity.galactifun.api.blocks.CustomMass import io.github.addoncommunity.galactifun.api.objects.properties.atmosphere.Gas -import io.github.addoncommunity.galactifun.units.Mass +import io.github.addoncommunity.galactifun.units.* import io.github.addoncommunity.galactifun.units.Mass.Companion.kilograms -import io.github.addoncommunity.galactifun.units.Volume -import io.github.addoncommunity.galactifun.units.Volume.Companion.liters -import io.github.addoncommunity.galactifun.units.sumBy -import io.github.addoncommunity.galactifun.units.times -import io.github.addoncommunity.galactifun.util.adjacentFaces +import io.github.addoncommunity.galactifun.util.bukkit.adjacentFaces import io.github.addoncommunity.galactifun.util.checkBlock import io.github.addoncommunity.galactifun.util.general.enumMapOf import io.github.addoncommunity.galactifun.util.general.mergeMaps @@ -54,7 +50,7 @@ class FuelTank( val consumed = item.amount.coerceAtMost(8) menu.consumeItem(INPUT, consumed) val fuel = getFuelLevel(b).toMutableMap() - fuel.merge(gasItem.gas, consumed.liters, Volume::plus) + fuel.merge(gasItem.gas, consumed.kilograms / gasItem.gas.liquidDensity, Volume::plus) setFuelLevel(b, fuel) } @@ -76,7 +72,7 @@ class FuelTank( distributable-- val stuffed = enumMapOf() for ((gas, amount) in fuels) { - val space = cap - stuffed.values.sumOf { it.liters }.liters + val space = cap - stuffed.values.sum() val toAdd = amount.coerceAtMost(space) stuffed.merge(gas, toAdd, Volume::plus) } @@ -122,7 +118,7 @@ class FuelTank( override fun getWetMass(block: Block): Mass { val fuelMass = getFuelLevel(block).toList() - .sumBy { (gas, amount) -> gas.liquidDensity * amount } + .unitSumOf { (gas, amount) -> gas.liquidDensity * amount } return getMass(block) + fuelMass } diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/abstract/Seat.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/abstract/Seat.kt index 8bb04f3..254a236 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/abstract/Seat.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/items/abstract/Seat.kt @@ -3,9 +3,10 @@ package io.github.addoncommunity.galactifun.impl.items.abstract import io.github.addoncommunity.galactifun.api.betteritem.BetterSlimefunItem import io.github.addoncommunity.galactifun.api.betteritem.ItemHandler import io.github.addoncommunity.galactifun.units.Angle.Companion.radians -import io.github.addoncommunity.galactifun.util.key -import io.github.addoncommunity.galactifun.util.nearbyEntitiesByType -import io.github.addoncommunity.galactifun.util.summon +import io.github.addoncommunity.galactifun.util.bukkit.key +import io.github.addoncommunity.galactifun.util.bukkit.nearbyEntitiesByType +import io.github.addoncommunity.galactifun.util.bukkit.summon +import io.github.seggan.sf4k.serial.pdc.get import io.github.seggan.sf4k.serial.pdc.set import io.github.thebusybiscuit.slimefun4.api.events.PlayerRightClickEvent import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup @@ -30,7 +31,14 @@ open class Seat( recipe: Array ) : BetterSlimefunItem(itemGroup, item, recipeType, recipe) { - private val armorStandKey = "seat".key() + companion object { + private val armorStandKey = "seat".key() + + fun getSitting(player: Player): Location? { + val armorStand = player.vehicle as? ArmorStand ?: return null + return armorStand.persistentDataContainer.get(armorStandKey) + } + } @ItemHandler(BlockPlaceHandler::class) private fun onPlace(e: BlockPlaceEvent) { @@ -42,7 +50,7 @@ open class Seat( armorStand.setGravity(false) armorStand.setAI(false) armorStand.isMarker = true - armorStand.persistentDataContainer.set(armorStandKey, true) + armorStand.persistentDataContainer.set(armorStandKey, b.location) val data = b.blockData if (data is Directional) { val facing = data.facing diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/managers/PlanetManager.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/managers/PlanetManager.kt index 1a227d9..962c37a 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/managers/PlanetManager.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/managers/PlanetManager.kt @@ -1,7 +1,6 @@ package io.github.addoncommunity.galactifun.impl.managers import com.github.shynixn.mccoroutine.bukkit.launch -import com.jeff_media.morepersistentdatatypes.DataType import io.github.addoncommunity.galactifun.api.objects.PlanetaryObject import io.github.addoncommunity.galactifun.api.objects.planet.PlanetaryWorld import io.github.addoncommunity.galactifun.api.objects.properties.OrbitPosition @@ -9,10 +8,12 @@ import io.github.addoncommunity.galactifun.api.objects.properties.atmosphere.Atm import io.github.addoncommunity.galactifun.impl.Permissions import io.github.addoncommunity.galactifun.impl.space.SpaceGenerator import io.github.addoncommunity.galactifun.pluginInstance -import io.github.addoncommunity.galactifun.util.key -import io.github.addoncommunity.galactifun.util.locationZero -import io.github.addoncommunity.galactifun.util.nearbyEntitiesByType -import io.github.addoncommunity.galactifun.util.summon +import io.github.addoncommunity.galactifun.util.bukkit.key +import io.github.addoncommunity.galactifun.util.bukkit.locationZero +import io.github.addoncommunity.galactifun.util.bukkit.nearbyEntitiesByType +import io.github.addoncommunity.galactifun.util.bukkit.summon +import io.github.seggan.sf4k.serial.pdc.get +import io.github.seggan.sf4k.serial.pdc.set import io.papermc.paper.event.entity.EntityMoveEvent import org.bukkit.Bukkit import org.bukkit.GameRule @@ -42,7 +43,6 @@ object PlanetManager : Listener { private val orbits: MutableMap private val orbitsKey = "orbits".key() - private val orbitsPdt = DataType.asMap(DataType.STRING, OrbitPosition.DataType) private val config = YamlConfiguration() @@ -70,7 +70,7 @@ object PlanetManager : Listener { 0.1 ).firstOrNull() ?: spaceWorld.summon(locationZero(spaceWorld)) - orbits = spaceWorldMarker.persistentDataContainer.getOrDefault(orbitsKey, orbitsPdt, mutableMapOf()) + orbits = spaceWorldMarker.persistentDataContainer.get(orbitsKey) ?: mutableMapOf() Bukkit.getPluginManager().registerEvents(this, pluginInstance) } @@ -87,7 +87,7 @@ object PlanetManager : Listener { orbits.size / maxOrbits + offset ) orbits[planet.name] = orbitPos - spaceWorldMarker.persistentDataContainer.set(orbitsKey, orbitsPdt, orbits) + spaceWorldMarker.persistentDataContainer.set(orbitsKey, orbits) } if (planet is PlanetaryWorld) { diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/managers/RocketManager.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/managers/RocketManager.kt index 6944f98..5a9387d 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/managers/RocketManager.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/managers/RocketManager.kt @@ -2,12 +2,15 @@ package io.github.addoncommunity.galactifun.impl.managers import io.github.addoncommunity.galactifun.api.rockets.RocketInfo import io.github.thebusybiscuit.slimefun4.libraries.dough.blocks.BlockPosition +import kotlinx.coroutines.Job object RocketManager { private val rockets = mutableMapOf() val allRockets: Set get() = rockets.values.toSet() + val launches = mutableListOf() + fun register(rocket: RocketInfo) { rockets.remove(rocket.commandComputer) rockets[rocket.commandComputer] = rocket diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/space/SpacePopulator.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/space/SpacePopulator.kt index 3207d0c..f51eb80 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/space/SpacePopulator.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/impl/space/SpacePopulator.kt @@ -1,8 +1,8 @@ package io.github.addoncommunity.galactifun.impl.space -import io.github.addoncommunity.galactifun.util.adjacentFaces import io.github.addoncommunity.galactifun.util.buildRandomizedSet -import io.github.addoncommunity.galactifun.util.set +import io.github.addoncommunity.galactifun.util.bukkit.adjacentFaces +import io.github.addoncommunity.galactifun.util.bukkit.set import org.bukkit.Location import org.bukkit.Material import org.bukkit.generator.BlockPopulator diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Time.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Time.kt index d582ff6..e9e36de 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Time.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Time.kt @@ -13,4 +13,4 @@ inline val Int.years: Duration inline val Duration.doubleSeconds: Double get() = toDouble(DurationUnit.SECONDS) -fun Iterable.sumBy(): Duration = fold(Duration.ZERO) { acc, duration -> acc + duration } \ No newline at end of file +fun Iterable.unitSumOf(): Duration = fold(Duration.ZERO) { acc, duration -> acc + duration } \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/PlanetMenu.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/PlanetMenu.kt index 982365f..bc02852 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/PlanetMenu.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/PlanetMenu.kt @@ -7,6 +7,8 @@ import io.github.addoncommunity.galactifun.api.objects.MilkyWay import io.github.addoncommunity.galactifun.api.objects.PlanetaryObject import io.github.addoncommunity.galactifun.api.objects.Star import io.github.addoncommunity.galactifun.impl.managers.PlanetManager +import io.github.addoncommunity.galactifun.util.bukkit.modifyLore +import io.github.addoncommunity.galactifun.util.bukkit.plus import io.github.thebusybiscuit.slimefun4.utils.ChestMenuUtils import kotlinx.datetime.Clock import me.mrCookieSlime.CSCoreLibPlugin.general.Inventory.ChestMenu @@ -83,6 +85,7 @@ open class PlanetMenu { is Star -> { val dist = from.distanceTo(to, now) val lore = mutableListOf() + lore += Component.empty() lore += NamedTextColor.GRAY + "Distance: %,.2f light years".format(dist.lightYears) lore += NamedTextColor.GRAY + "Planets: ${to.orbiters.size}" lore @@ -91,15 +94,15 @@ open class PlanetMenu { is PlanetaryObject -> { val dist = from.distanceTo(to, now) val lore = mutableListOf() + lore += Component.empty() lore += NamedTextColor.GRAY + "Distance: %.2s".format(dist) - lore += NamedTextColor.GRAY + "Moons: ${to.orbiters.size}" val dV = from.getDeltaVForTransferTo(to, now) lore += NamedTextColor.GRAY + "Delta-V for travel: %.2s".format(dV) lore } } val item = to.item.clone() - item.lore(info) + item.modifyLore { it.addAll(info) } menu[i] = modifyItem(p, to, item) menu[i] = MenuClickHandler { _, _, _, action -> if (onClick(p, to, action) && to.orbiters.isNotEmpty()) { @@ -115,7 +118,7 @@ open class PlanetMenu { * * @param p The player who closed the menu */ - open fun onExit(p: Player) { + protected open fun onExit(p: Player) { p.closeInventory() } @@ -127,7 +130,7 @@ open class PlanetMenu { * @param action The action that was performed * @return `true` if the menu should open the planet's children */ - open fun onClick(p: Player, obj: CelestialObject, action: ClickAction): Boolean { + protected open fun onClick(p: Player, obj: CelestialObject, action: ClickAction): Boolean { return true } @@ -138,7 +141,7 @@ open class PlanetMenu { * @param obj The celestial object that is being displayed * @param item The item that represents the celestial object */ - open fun modifyItem(p: Player, obj: CelestialObject, item: ItemStack): ItemStack { + protected open fun modifyItem(p: Player, obj: CelestialObject, item: ItemStack): ItemStack { return item } } \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/SerializedBlock.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/SerializedBlock.kt new file mode 100644 index 0000000..d4af9ea --- /dev/null +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/SerializedBlock.kt @@ -0,0 +1,103 @@ +package io.github.addoncommunity.galactifun.util + +import io.github.addoncommunity.galactifun.util.bukkit.key +import io.github.addoncommunity.galactifun.util.bukkit.summon +import io.github.seggan.sf4k.serial.pdc.get +import io.github.seggan.sf4k.serial.pdc.set +import me.mrCookieSlime.Slimefun.api.BlockStorage +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.block.Block +import org.bukkit.block.data.BlockData +import org.bukkit.block.structure.Mirror +import org.bukkit.block.structure.StructureRotation +import org.bukkit.entity.BlockDisplay +import org.bukkit.persistence.PersistentDataContainer +import org.bukkit.structure.Structure +import org.bukkit.util.BlockVector +import org.bukkit.util.BoundingBox +import java.io.ByteArrayOutputStream +import java.util.concurrent.ThreadLocalRandom + +class SerializedBlock private constructor( + private val data: BlockData, + private val blockStorage: String, + private val structure: Structure +) { + + companion object { + + private val SAVED_DATA = "saved_block_data".key() + private val SAVED_STRUCTURE = "saved_structure".key() + private val SAVED_BLOCK_STORAGE = "saved_block_storage".key() + + fun serialize( + block: Block, + delete: Boolean = true, + includeEntities: Boolean = false + ): SerializedBlock { + val data = block.blockData.clone() + val blockStorage = BlockStorage.getBlockInfoAsJson(block) + val structure = Bukkit.getStructureManager().createStructure() + structure.fill(block.location, BlockVector(1, 1, 1), includeEntities) + if (delete) { + block.type = Material.AIR + BlockStorage.clearBlockInfo(block) + } + if (includeEntities) { + for (entity in block.world.getNearbyEntities(BoundingBox.of(block))) { + entity.remove() + } + } + return SerializedBlock(data, blockStorage, structure) + } + + fun loadFromPdc(pdc: PersistentDataContainer): SerializedBlock? { + val data = pdc.get(SAVED_DATA)?.let(Bukkit::createBlockData) ?: return null + val blockStorage = pdc.get(SAVED_BLOCK_STORAGE) ?: return null + val bytes = pdc.get(SAVED_STRUCTURE) ?: return null + val structure = Bukkit.getStructureManager().loadStructure(bytes.inputStream()) + return SerializedBlock(data, blockStorage, structure) + } + + fun loadFromDisplayEntity(display: BlockDisplay, delete: Boolean = true): SerializedBlock? { + val serialized = loadFromPdc(display.persistentDataContainer) ?: return null + if (delete) { + display.remove() + } + return serialized + } + } + + fun place(location: Location) { + val block = location.block + structure.place( + location, + true, + StructureRotation.NONE, + Mirror.NONE, + 0, + 1.0F, + ThreadLocalRandom.current() + ) + BlockStorage.setBlockInfo(block, blockStorage, true) + // We don't need to restore the block data because the structure already does that + } + + fun saveToPdc(pdc: PersistentDataContainer) { + pdc.set(SAVED_DATA, data.asString) + pdc.set(SAVED_BLOCK_STORAGE, blockStorage) + val stream = ByteArrayOutputStream() + Bukkit.getStructureManager().saveStructure(stream, structure) + pdc.set(SAVED_STRUCTURE, stream.toByteArray()) + } + + fun createDisplayEntity(location: Location): BlockDisplay { + val block = location.block + val display = block.world.summon(location) + display.block = data + saveToPdc(display.persistentDataContainer) + return display + } +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/BlockPosUtils.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/bukkit/BlockPosUtils.kt similarity index 83% rename from plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/BlockPosUtils.kt rename to plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/bukkit/BlockPosUtils.kt index e8f31be..f0b816a 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/BlockPosUtils.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/bukkit/BlockPosUtils.kt @@ -1,9 +1,11 @@ -package io.github.addoncommunity.galactifun.util +package io.github.addoncommunity.galactifun.util.bukkit import io.github.thebusybiscuit.slimefun4.libraries.dough.blocks.BlockPosition import org.bukkit.block.Block import org.bukkit.block.BlockFace +val BlockPosition.location get() = this.toLocation() + fun BlockPosition.getFace(face: BlockFace): BlockPosition { return BlockPosition(this.world, this.x + face.modX, this.y + face.modY, this.z + face.modZ) } @@ -56,4 +58,11 @@ inline fun BlockPosition.floodSearch( } } -data class FloodSearchResult(val found: Set, val exceededMax: Boolean) \ No newline at end of file +data class FloodSearchResult(val found: Set, val exceededMax: Boolean) + +tailrec fun BlockPosition.isHighest(): Boolean { + val world = this.world + val next = this.getFace(BlockFace.UP) + if (next.y >= world.maxHeight) return true + return if (next.block.type.isAir) next.isHighest() else false +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/DummyMetadataValue.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/bukkit/DummyMetadataValue.kt similarity index 81% rename from plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/DummyMetadataValue.kt rename to plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/bukkit/DummyMetadataValue.kt index d8abefc..fd1c535 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/DummyMetadataValue.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/bukkit/DummyMetadataValue.kt @@ -1,4 +1,4 @@ -package io.github.addoncommunity.galactifun.util +package io.github.addoncommunity.galactifun.util.bukkit import io.github.addoncommunity.galactifun.pluginInstance import org.bukkit.metadata.MetadataValueAdapter diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/McUtils.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/bukkit/McUtils.kt similarity index 55% rename from plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/McUtils.kt rename to plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/bukkit/McUtils.kt index c7054cd..4bc9926 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/McUtils.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/bukkit/McUtils.kt @@ -1,7 +1,6 @@ -package io.github.addoncommunity.galactifun.util +package io.github.addoncommunity.galactifun.util.bukkit import io.github.addoncommunity.galactifun.pluginInstance -import kotlinx.coroutines.suspendCancellableCoroutine import net.kyori.adventure.text.Component import net.kyori.adventure.text.TextComponent import net.kyori.adventure.text.format.TextColor @@ -10,15 +9,21 @@ import net.kyori.adventure.text.minimessage.MiniMessage import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer import org.bukkit.* import org.bukkit.entity.Entity -import org.bukkit.event.* import org.bukkit.event.player.PlayerTeleportEvent +import org.bukkit.inventory.ItemStack import java.util.* import java.util.concurrent.CompletableFuture -import kotlin.coroutines.resume -fun String.key(): NamespacedKey = NamespacedKey(pluginInstance, this) +internal fun String.key(): NamespacedKey = NamespacedKey(pluginInstance, this) -fun Location.withWorld(world: World): Location = Location(world, x, y, z, yaw, pitch) +fun Location.copy( + world: World? = this.world, + x: Double = this.x, + y: Double = this.y, + z: Double = this.z, + yaw: Float = this.yaw, + pitch: Float = this.pitch +): Location = Location(world, x, y, z, yaw, pitch) inline fun World.nearbyEntitiesByType( location: Location, @@ -41,16 +46,37 @@ operator fun RegionAccessor.set(x: Int, y: Int, z: Int, material: Material) = se operator fun RegionAccessor.set(location: Location, material: Material) = setType(location, material) /** - * Teleports while telling Galactifun that the teleport should not be blocked + * Teleports while telling Galactifun that the teleport should not be blocked. + * Can be used to teleport entities with passengers. */ fun Entity.galactifunTeleport( dest: Location, + preservePassengers: Boolean = true, reason: PlayerTeleportEvent.TeleportCause = PlayerTeleportEvent.TeleportCause.PLUGIN ): CompletableFuture { - setMetadata("galactifun.teleporting", DummyMetadataValue) - return teleportAsync(dest, reason).thenApply { - removeMetadata("galactifun.teleporting", pluginInstance) - it + if (preservePassengers && passengers.isNotEmpty()) { + val futures = mutableListOf>() + val passengers = this.passengers + for (passenger in passengers) { + removePassenger(passenger) + futures += passenger.galactifunTeleport(dest, true, reason) + } + var future = galactifunTeleport(dest, false, reason) + for (passengerFuture in futures) { + future = future.thenCombine(passengerFuture) { a, b -> a && b } + } + return future.thenApply { + if (it) { + passengers.forEach(::addPassenger) + } + it + } + } else { + setMetadata("galactifun.teleporting", DummyMetadataValue) + return teleportAsync(dest, reason).thenApply { + removeMetadata("galactifun.teleporting", pluginInstance) + it + } } } @@ -60,31 +86,17 @@ operator fun TextColor.plus(s: String): TextComponent = Component.text() .content(s) .build() +inline fun ItemStack.modifyLore(modifier: (MutableList) -> Unit) { + val meta = itemMeta ?: Bukkit.getItemFactory().getItemMeta(type) + val lore = meta.lore() ?: mutableListOf() + modifier(lore) + meta.lore(lore) + itemMeta = meta +} + operator fun Tag.contains(item: T): Boolean = isTagged(item) fun String.miniMessageToLegacy(): String = LegacyComponentSerializer.legacyAmpersand() .serialize(MiniMessage.miniMessage().deserialize(this)) -fun locationZero(world: World?): Location = Location(world, 0.0, 0.0, 0.0) - -suspend inline fun waitForEvent( - priority: EventPriority = EventPriority.NORMAL, - cancelIfEventCancelled: Boolean = false -): E { - return suspendCancellableCoroutine { cont -> - Bukkit.getPluginManager().registerEvent( - E::class.java, - object : Listener {}, - priority, - { listener, event -> - HandlerList.unregisterAll(listener) - if (cancelIfEventCancelled && event is Cancellable && event.isCancelled) { - cont.cancel() - } else { - cont.resume(event as E) - } - }, - pluginInstance - ) - } -} +fun locationZero(world: World?): Location = Location(world, 0.0, 0.0, 0.0) \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/bukkit/SusUtils.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/bukkit/SusUtils.kt new file mode 100644 index 0000000..84caa5f --- /dev/null +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/bukkit/SusUtils.kt @@ -0,0 +1,69 @@ +package io.github.addoncommunity.galactifun.util.bukkit + +import com.github.shynixn.mccoroutine.bukkit.launch +import com.github.shynixn.mccoroutine.bukkit.ticks +import io.github.addoncommunity.galactifun.launchAsync +import io.github.addoncommunity.galactifun.pluginInstance +import io.github.thebusybiscuit.slimefun4.utils.ChatUtils +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay +import kotlinx.coroutines.suspendCancellableCoroutine +import org.bukkit.Bukkit +import org.bukkit.entity.Player +import org.bukkit.event.* +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +suspend inline fun waitForEvent( + priority: EventPriority = EventPriority.NORMAL, + cancelIfEventCancelled: Boolean = false +): E { + return suspendCancellableCoroutine { cont -> + Bukkit.getPluginManager().registerEvent( + E::class.java, + object : Listener {}, + priority, + { listener, event -> + HandlerList.unregisterAll(listener) + if (cancelIfEventCancelled && event is Cancellable && event.isCancelled) { + cont.cancel() + } else { + cont.resume(event as E) + } + }, + pluginInstance + ) + } +} + +suspend fun Player.awaitChatInput(): String { + return suspendCoroutine { cont -> + ChatUtils.awaitInput(this) { + cont.resume(it) + } + } +} + +suspend fun delayTicks(ticks: Int) { + delay(ticks.ticks) +} + +inline fun Collection.consumeSpreadOut(ticks: Int, crossinline action: suspend (T) -> Unit) { + val channel = Channel() + pluginInstance.launchAsync { + for (item in this@consumeSpreadOut) { + channel.send(item) + } + channel.close() + } + val itemsPerTick = size / ticks + 1 + pluginInstance.launch { + var i = 0 + for (item in channel) { + action(item) + if (++i % itemsPerTick == 0) { + delayTicks(1) + } + } + } +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/items/ItemBuilder.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/items/ItemBuilder.kt index 83caacd..988271b 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/items/ItemBuilder.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/items/ItemBuilder.kt @@ -1,8 +1,8 @@ package io.github.addoncommunity.galactifun.util.items import io.github.addoncommunity.galactifun.pluginInstance +import io.github.addoncommunity.galactifun.util.bukkit.miniMessageToLegacy import io.github.addoncommunity.galactifun.util.general.RequiredProperty -import io.github.addoncommunity.galactifun.util.miniMessageToLegacy import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/items/Mass.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/items/Mass.kt index 02b2536..55572fd 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/items/Mass.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/util/items/Mass.kt @@ -4,7 +4,7 @@ import io.github.addoncommunity.galactifun.api.blocks.CustomMass import io.github.addoncommunity.galactifun.units.Mass import io.github.addoncommunity.galactifun.units.Mass.Companion.kilograms import io.github.addoncommunity.galactifun.units.Mass.Companion.tons -import io.github.addoncommunity.galactifun.util.contains +import io.github.addoncommunity.galactifun.util.bukkit.contains import io.github.thebusybiscuit.slimefun4.utils.tags.SlimefunTag import me.mrCookieSlime.Slimefun.api.BlockStorage import org.bukkit.Material diff --git a/plugin/src/main/resources/config.yml b/plugin/src/main/resources/config.yml index 9181485..6057793 100644 --- a/plugin/src/main/resources/config.yml +++ b/plugin/src/main/resources/config.yml @@ -34,6 +34,6 @@ rockets: - Not going home - Calibrating the 'Are We There Yet?'ometer - Packing extra duct tape - - Applying SPF 5000 sunscreen to the engine - - Pointing the correct end towards space + - Applying SPF 5000 sunscreen + - Pointing correct end towards space - Launching on a wing and a prayer (and a slightly dodgy fuel injector) diff --git a/plugin/src/test/kotlin/io/github/addoncommunity/test/api/objects/properties/OrbitTest.kt b/plugin/src/test/kotlin/io/github/addoncommunity/test/api/objects/properties/OrbitTest.kt index 0821e30..a5102cb 100644 --- a/plugin/src/test/kotlin/io/github/addoncommunity/test/api/objects/properties/OrbitTest.kt +++ b/plugin/src/test/kotlin/io/github/addoncommunity/test/api/objects/properties/OrbitTest.kt @@ -9,11 +9,12 @@ import io.github.addoncommunity.galactifun.units.Angle.Companion.radians import io.github.addoncommunity.galactifun.units.Distance.Companion.au import io.github.addoncommunity.galactifun.units.standardForm import io.github.addoncommunity.test.CommonTest -import io.kotest.matchers.doubles.percent -import io.kotest.matchers.doubles.plusOrMinus -import io.kotest.matchers.shouldBe import kotlinx.datetime.Instant import org.junit.jupiter.api.BeforeEach +import strikt.api.Assertion +import strikt.api.expectThat +import strikt.assertions.isEqualTo +import strikt.assertions.isIn import kotlin.test.Test import kotlin.time.DurationUnit @@ -35,26 +36,28 @@ class OrbitTest : CommonTest() { @Test fun testPeriod() { - orbit.period.toDouble(DurationUnit.DAYS) shouldBeRoughly 365.26 + expectThat(orbit.period.toDouble(DurationUnit.DAYS)).isRoughly(365.26) } @Test fun testPosition() { val time = orbit.timeOfPeriapsis val pos = orbit.position(time) - pos.radius shouldBe orbit.radius(time) + expectThat(pos.radius).isEqualTo(orbit.radius(time)) } @Test fun testVelocity() { fun testVelocityAt(time: Instant, expectedAngle: Angle) { val vel = orbit.velocity(time) - vel.length.meters shouldBeRoughly visVivaEquation( - orbit.parent.gravitationalParameter, - orbit.radius(orbit.timeOfPeriapsis), - orbit.semimajorAxis - ).metersPerSecond - vel.polar.angle.standardForm.degrees shouldBeRoughly expectedAngle.degrees + expectThat(vel.length.meters).isRoughly( + visVivaEquation( + orbit.parent.gravitationalParameter, + orbit.radius(orbit.timeOfPeriapsis), + orbit.semimajorAxis + ).metersPerSecond + ) + expectThat(vel.polar.angle.standardForm.degrees).isRoughly(expectedAngle.degrees) } testVelocityAt(orbit.timeOfPeriapsis, 90.degrees) @@ -70,10 +73,11 @@ class OrbitTest : CommonTest() { 0.radians, orbit.meanAnomaly(time + orbit.period / 4) ) - timeOfFlight.inWholeDays shouldBe 91 + expectThat(timeOfFlight.inWholeDays).isEqualTo(91) } } -infix fun Double.shouldBeRoughly(expected: Double) = this shouldBe expected.plusOrMinus(0.1.percent) +fun Assertion.Builder.isRoughly(expected: Double) = this + .isIn((expected - expected * .1)..(expected + expected * .1)) val EPOCH = Instant.parse("1970-01-01T00:00:00Z") \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index a328a65..279f6b4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } rootProject.name = "Galactifun2" include("uom", "uom-processor", "plugin") diff --git a/uom-processor/src/main/kotlin/io/github/seggan/uom/UomProcessor.kt b/uom-processor/src/main/kotlin/io/github/seggan/uom/UomProcessor.kt index 65d8bca..8dcf15e 100644 --- a/uom-processor/src/main/kotlin/io/github/seggan/uom/UomProcessor.kt +++ b/uom-processor/src/main/kotlin/io/github/seggan/uom/UomProcessor.kt @@ -175,7 +175,7 @@ class UomProcessor( val t = TypeVariableName("T") @OptIn(ExperimentalTypeInference::class) file.addFunction( - FunSpec.builder("sumBy") + FunSpec.builder("unitSumOf") .addAnnotation( AnnotationSpec.builder(ClassName("kotlin", "OptIn")) .addMember("%T::class", ClassName("kotlin.experimental", "ExperimentalTypeInference"))