Skip to content

Instantly share code, notes, and snippets.

@jnorthrup
Last active February 3, 2023 19:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jnorthrup/5234d62630a6bddc17d556cc83f1de91 to your computer and use it in GitHub Desktop.
Save jnorthrup/5234d62630a6bddc17d556cc83f1de91 to your computer and use it in GitHub Desktop.
package ed.fumes.ed.fumes
import ed.fumes.ed.fumes.FrameShiftDrive.FSDClass.*
import ed.fumes.ed.fumes.FrameShiftDrive.FSDRating.*
import kotlin.math.min
import kotlin.math.pow
typealias Tons = Double
typealias LY = Double
data class FrameShiftDrive(
val rating: FSDRating,
val fsdClass: FSDClass,
var optimalMass: Tons,
var maxFuelPerJump: Tons
) {
enum class FSDRating(val linearConstant: Double) { A(12.0), B(10.0), C(8.0), D(10.0), E(11.0), }
enum class FSDClass(val powerConstant: Double) { C2(2.00), C3(2.15), C4(2.30), C5(2.45), C6(2.60), C7(2.75), C8(2.90) }
var boostFactor = 1.0
val linearConstant: Double get() = rating.linearConstant
val powerConstant: Double get() = fsdClass.powerConstant
}
enum class BaseFSD(val fsd: FrameShiftDrive) {
base2A(FrameShiftDrive(A, C2, 48.0, 0.60)),
base2B(FrameShiftDrive(B, C2, 54.0, 0.60)),
base2C(FrameShiftDrive(C, C2, 60.0, 0.60)),
base2D(FrameShiftDrive(D, C2, 75.0, 0.80)),
base2E(FrameShiftDrive(E, C2, 90.0, 0.90)),
base3A(FrameShiftDrive(A, C3, 80.0, 1.20)),
base3B(FrameShiftDrive(B, C3, 90.0, 1.20)),
base3C(FrameShiftDrive(C, C3, 100.0, 1.20)),
base3D(FrameShiftDrive(D, C3, 125.0, 1.50)),
base3E(FrameShiftDrive(E, C3, 150.0, 1.80)),
base4A(FrameShiftDrive(A, C4, 280.0, 2.00)),
base4B(FrameShiftDrive(B, C4, 315.0, 2.00)),
base4C(FrameShiftDrive(C, C4, 350.0, 2.00)),
base4D(FrameShiftDrive(D, C4, 438.0, 2.50)),
base4E(FrameShiftDrive(E, C4, 525.0, 3.00)),
base5A(FrameShiftDrive(A, C5, 560.0, 3.30)),
base5B(FrameShiftDrive(B, C5, 630.0, 3.30)),
base5C(FrameShiftDrive(C, C5, 700.0, 3.30)),
base5D(FrameShiftDrive(D, C5, 875.0, 4.10)),
base5E(FrameShiftDrive(E, C5, 1050.0, 5.00)),
base6A(FrameShiftDrive(A, C6, 960.0, 5.30)),
base6B(FrameShiftDrive(B, C6, 1080.0, 5.30)),
base6C(FrameShiftDrive(C, C6, 1200.0, 5.30)),
base6D(FrameShiftDrive(D, C6, 1500.0, 6.60)),
base6E(FrameShiftDrive(E, C6, 1800.0, 8.00)),
base7A(FrameShiftDrive(A, C7, 1440.0, 8.50)),
base7B(FrameShiftDrive(B, C7, 1620.0, 8.50)),
base7C(FrameShiftDrive(C, C7, 1800.0, 8.50)),
base7D(FrameShiftDrive(D, C7, 2250.0, 10.60)),
base7E(FrameShiftDrive(E, C7, 2700.0, 12.80)),
}
enum class FSDBooster(
/** jump range increase in LY */
val jumpRangeIncrease: LY
) {
booster1H(4.00),
booster2H(6.00),
booster3H(7.75),
booster4H(9.25),
booster5H(10.50),
}
data class Ship(
val name: String,
val unladedMass: Tons,
val fsd: FrameShiftDrive,
val fuelCapacity: Tons,
val cargoCapacity: Tons,
val fsdBooster: FSDBooster? = null,
var fuelRemaining: Tons = fuelCapacity,
var cargoRemaining: Tons = 0.0,
) {
var boostFactor: Double
get() = fsd.boostFactor
set(value) {
fsd.boostFactor = value
}
val totalMass: Tons = unladedMass + cargoRemaining + fuelRemaining
/* csharp code we want to port
// from EDCD 20/11/22 updated fuel use . Note refill tank is not taken into account in frontiers calc
public double FuelUse(double cargo, double unladenmass, double fuel, double distance, double boost)
{
double mass = unladenmass + cargo + fuel; // weight
double basemaxrange = (OptimalMass / mass) * Math.Pow((MaxFuelPerJump * 1000 / LinearConstant), (1 / PowerConstant));
double boostfactor = Math.Pow((basemaxrange / (basemaxrange + FSDGuardianBoosterRange)), PowerConstant);
return boostfactor * LinearConstant * 0.001 * Math.Pow(((distance / boost) * mass / OptimalMass), PowerConstant);
}
// Eahlstan special, account for small fuel loads
public double JumpRange(int currentCargo, double unladenMassHullModules, double fuel, double boost)
{
double mass = currentCargo + unladenMassHullModules + fuel;
double massf = OptimalMass / mass;
double fuelmultiplier = (LinearConstant * 0.001);
double powerf = Math.Pow(MaxFuelPerJump / fuelmultiplier, 1 / PowerConstant);
double basev = powerf * massf;
if (fuel >= MaxFuelPerJump)
{
return (basev + FSDGuardianBoosterRange) * boost;
}
else
{
double basemaxrange = (OptimalMass / mass) * Math.Pow((MaxFuelPerJump * 1000 / LinearConstant), (1 / PowerConstant));
double boostfactor = Math.Pow((basemaxrange / (basemaxrange + FSDGuardianBoosterRange)), PowerConstant);
return (Math.Pow((fuel / (boostfactor * fuelmultiplier)), (1 / PowerConstant)) * massf) * boost;
}
}
*/
fun jumpRangeForFuel(fuel1: Tons): LY {
val fuel = min(fuel1, fsd.maxFuelPerJump)
val mass = totalMass
val massf = fsd.optimalMass / mass
val fuelmultiplier = fsd.linearConstant * 0.001
val grdnBoost: LY = fsdBooster?.jumpRangeIncrease ?: 0.0
return if (fuel >= fsd.maxFuelPerJump) {
val powerf = (fsd.maxFuelPerJump / fuelmultiplier).pow(1 / fsd.powerConstant)
val basev = powerf * massf
(basev + grdnBoost) * boostFactor
} else {
val basemaxrange: LY =
fsd.optimalMass / mass * (fsd.maxFuelPerJump * 1000 / fsd.linearConstant).pow(1 / fsd.powerConstant)
val boostfactor = basemaxrange.pow(fsd.powerConstant) / (basemaxrange + grdnBoost).pow(fsd.powerConstant)
(fuel / (boostfactor * fuelmultiplier)).pow(1 / fsd.powerConstant) * massf * boostFactor
}
}
fun fuelUse(distance: LY): Tons {
val baseMaxRange =
(fsd.optimalMass / totalMass) * (fsd.maxFuelPerJump * 1000 / fsd.linearConstant).pow(1 / fsd.powerConstant)
val boostFactor = baseMaxRange.pow(fsd.powerConstant) / (baseMaxRange + (fsdBooster?.jumpRangeIncrease
?: 0.0)).pow(fsd.powerConstant)
return boostFactor * fsd.linearConstant * 0.001 * ((distance / boostFactor) * totalMass / fsd.optimalMass).pow(
fsd.powerConstant
)
}
fun jump(distance: LY): Boolean {
val fuel = fuelUse(distance)
if (fuel > fuelRemaining) {
return false
}
fuelRemaining -= fuel
return true
}
fun refuel(): Ship {
fuelRemaining = fuelCapacity
return this
}
fun maxJumpRange(remaining: Tons = Double.MAX_VALUE): LY = copy().run {
fuelRemaining = min(fuelCapacity, remaining)
//loop through jumps until we run out of fuel
var range = 0.0
do {
val jumpRange = jumpRangeForFuel(fuelRemaining)
fuelRemaining -= fuelUse(jumpRange)
range += jumpRange
} while (fuelRemaining > 0.0)
range
}
}
Ship(name=SideWinder, unladedMass=43.2, fsd=FrameShiftDrive(rating=E, fsdClass=C2, optimalMass=90.0, maxFuelPerJump=0.9), fuelCapacity=2.0, cargoCapacity=4.0, fsdBooster=null, fuelRemaining=2.0, cargoRemaining=0.0)
jump 2 LY: uses 0.011097995061728394 tons
jump 10 LY: uses 0.27744987654320985 tons
jump with 2 tons of fuel: 18.0106334150434 LY
jump with full fuel empty cargo: 18.0106334150434 LY
max range: 44.51156098569662 LY
Ship(name=Anaconda, unladedMass=1066.4, fsd=FrameShiftDrive(rating=E, fsdClass=C6, optimalMass=1800.0, maxFuelPerJump=8.0), fuelCapacity=32.0, cargoCapacity=114.0, fsdBooster=booster5H, fuelRemaining=32.0, cargoRemaining=0.0)
jump 2 LY: uses 0.10202684812309537 tons
jump 10 LY: uses 6.6994088347747605 tons
jump with 2 tons of fuel: 18.283476175626898 LY
jump with full fuel empty cargo: 31.16163995087626 LY
package ed.fumes.ed.fumes
import ed.fumes.ed.fumes.Ship
import kotlin.test.*
class TestShip {
@Test
fun testShip1() {
// show me a SideWinder ship with no booster
val sidey = Ship("SideWinder", 43.2, BaseFSD.base2E.fsd.copy(), 2.0, 4.0)
// anaconda with a 5H booster
val anaconda = Ship("Anaconda", 1066.4, BaseFSD.base6E.fsd.copy(), 32.0, 114.0, FSDBooster.booster5H)
listOf(sidey, anaconda).forEach {
println(it)
// jump 2 LY
val jump2 = it.fuelUse(2.0)
println("jump 2 LY: uses $jump2 tons")
// jump 10 LY
val jump10 = it.fuelUse(10.0)
println("jump 10 LY: uses $jump10 tons")
//jump with 2 tons of fuel
val jumpRange2 = it.jumpRangeForFuel(2.0)
println("jump with 2 tons of fuel: $jumpRange2 LY")
//jump with full fuel empty cargo
val jumpRangeFull = it.jumpRangeForFuel(it.fuelCapacity)
println("jump with full fuel empty cargo: $jumpRangeFull LY")
//show maxrange with a copy of the ship
val maxRange = it.copy().maxJumpRange()
println("max range: $maxRange LY")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment