Add Acceleration Segment + Improved Thrust calculation

This commit is contained in:
Neshura 2024-05-24 12:37:31 +02:00
parent 90085dd87a
commit 986cf8453e
Signed by: Neshura
GPG key ID: B6983AAA6B9A7A6C
9 changed files with 288 additions and 138 deletions

View file

@ -0,0 +1,50 @@
<svelte:options runes />
<script>
import {applyUnits, Localization} from "$lib/constants";
import {Separator} from "$lib/components/ui/separator";
import NewInventory from "$lib/components/NewInventory.svelte";
import InventoryList from "$lib/components/InventoryList.svelte";
import {Label} from "$lib/components/ui/label";
import {Input} from "$lib/components/ui/input";
import * as Card from "$lib/components/ui/card";
let { i18n = new Localization("en-GB"), ship, inventoryMultiplier, onAddInventory, onRemoveInventory } = $props();
let valid = $derived.by(() => {
let valids = [];
valids[0] = typeof ship !== "undefined";
valids[1] = typeof inventoryMultiplier !== "undefined";
valids[2] = typeof onAddInventory !== "undefined";
valids[3] = typeof onRemoveInventory !== "undefined";
return !valids.includes(false);
});
</script>
{#if valid}
<Card.Root class="flex-1 flex flex-col flex-grow">
<Card.Header>
<Card.Title>{i18n.localize("inventorySettings")}</Card.Title>
</Card.Header>
<Card.Content class="flex flex-col flex-grow gap-3">
<Separator />
<Label for="inventorySize">{i18n.localize("inventory")} {i18n.localize("multiplier")}</Label>
<Input id="inventorySize" type="number" bind:value={inventoryMultiplier}/>
<Card.Root>
<Card.Content class="pt-6 flex flex-col gap-3">
<NewInventory i18n={i18n} grid={ship.grid} {onAddInventory} />
</Card.Content>
</Card.Root>
<Card.Root class="flex flex-col h-60 flex-grow">
<Card.Header>
<Card.Title>{i18n.localize("inventories")} ({applyUnits(i18n, ship.getTotalVolume(inventoryMultiplier), "l")})</Card.Title>
</Card.Header>
<Card.Content class="flex flex-col gap-3 overflow-hidden">
<Separator />
<InventoryList i18n={i18n} inventories={ship.inventories} {onRemoveInventory} grid={ship.grid} multiplier={inventoryMultiplier} />
</Card.Content>
</Card.Root>
</Card.Content>
</Card.Root>
{/if}

View file

@ -0,0 +1,50 @@
<svelte:options runes />
<script>
import {applyUnits, Localization} from "$lib/constants";
import {Separator} from "$lib/components/ui/separator";
import NewInventory from "$lib/components/NewInventory.svelte";
import InventoryList from "$lib/components/InventoryList.svelte";
import {Label} from "$lib/components/ui/label";
import {Input} from "$lib/components/ui/input";
import * as Card from "$lib/components/ui/card";
import NewThruster from "$lib/components/NewThruster.svelte";
import ThrusterList from "$lib/components/ThrusterList.svelte";
let { i18n = new Localization("en-GB"), ship, atmosphericDensity, onAddThruster, onRemoveThruster } = $props();
let valid = $derived.by(() => {
let valids = [];
valids[0] = typeof ship !== "undefined";
valids[1] = typeof atmosphericDensity !== "undefined";
valids[2] = typeof onAddThruster !== "undefined";
valids[3] = typeof onRemoveThruster !== "undefined";
return !valids.includes(false);
});
</script>
{#if valid}
<Card.Root class="flex-1 flex flex-col flex-grow">
<Card.Header>
<Card.Title>{i18n.localize("thrusterSettings")}</Card.Title>
</Card.Header>
<Card.Content class="flex flex-col flex-grow gap-3">
<Separator />
<Card.Root>
<Card.Content class="flex flex-col gap-3 pt-6">
<NewThruster i18n={i18n} {onAddThruster} />
</Card.Content>
</Card.Root>
<Card.Root class="flex flex-col h-60 flex-grow">
<Card.Header>
<Card.Title>{i18n.localize("thrusters")} ({applyUnits(i18n, ship.getTotalThrust(atmosphericDensity), "N")})</Card.Title>
</Card.Header>
<Card.Content class="flex flex-col gap-3 overflow-hidden">
<Separator />
<ThrusterList i18n={i18n} thrusters={ship.thrusters} {onRemoveThruster} grid={ship.grid} atmosphere={atmosphericDensity}/>
</Card.Content>
</Card.Root>
</Card.Content>
</Card.Root>
{/if}

View file

@ -0,0 +1,35 @@
<svelte:options runes />
<script lang="ts">
import {applyUnits, Localization, weightConversion} from "$lib/constants";
let { i18n = new Localization("en-GB"), ship, atmosphere, inventoryMultiplier, material } = $props();
let valid = $derived.by(() => {
let valids = [];
valids[0] = typeof ship !== "undefined";
valids[1] = typeof atmosphere !== "undefined";
valids[2] = typeof inventoryMultiplier !== "undefined";
valids[3] = typeof material !== "undefined";
return !valids.includes(false);
});
</script>
{#if valid}
<div class="flex gap-2">
<p>{i18n.localize("acceleration")} ({i18n.localize("full")}):</p>
{#if ship.getAcceleration(atmosphere, inventoryMultiplier, material) <= 0}
<p class="text-destructive">{applyUnits(i18n, ship.getAcceleration(atmosphere, inventoryMultiplier, material), "m/s²")}</p>
{:else}
<p>{applyUnits(i18n, ship.getAcceleration(atmosphere, inventoryMultiplier, material), "m/s²")}</p>
{/if}
</div>
<div class="flex gap-2">
<p>{i18n.localize("acceleration")} ({i18n.localize("empty")}):</p>
{#if ship.getMaxAcceleration(atmosphere) < 0}
<p class="text-destructive">{applyUnits(i18n, ship.getMaxAcceleration(atmosphere), "m/s²")}</p>
{:else}
<p>{applyUnits(i18n, ship.getMaxAcceleration(atmosphere), "m/s²")}</p>
{/if}
</div>
{/if}

View file

@ -19,7 +19,7 @@
<Label for="atmosDensity">{i18n.localize("atmosDensity")} ({(value * 100).toFixed(0)}%)</Label> <Label for="atmosDensity">{i18n.localize("atmosDensity")} ({(value * 100).toFixed(0)}%)</Label>
<div class="flex gap-2"> <div class="flex gap-2">
<Label for="atmosDensity">0%</Label> <Label for="atmosDensity">0%</Label>
<Slider id="atmosDensity" value={array} onValueChange={handleChange} min={0} max={100} step={1} /> <Slider id="atmosDensity" class="fill-primary" value={array} onValueChange={handleChange} min={0} max={100} step={1} />
<Label>100%</Label> <Label>100%</Label>
</div> </div>

View file

@ -21,7 +21,7 @@
{#if valid} {#if valid}
{#each thrusters as thruster, index} {#each thrusters as thruster, index}
<p class="gap-2 flex justify-between"> <p class="gap-2 flex justify-between">
- {i18n.localize(thruster.details.size)} {i18n.localize(thruster.details.type.details.name)}: {applyUnits(i18n, thruster.getThrust(grid, atmosphere), "N")} - {i18n.localize(thruster.details.size)} {i18n.localize(thruster.details.type.details.name)}: {applyUnits(i18n, thruster.getThrust(grid, atmosphere), "N")} ({Math.round(thruster.details.type.details.curve.getEffectiveness(atmosphere).toFixed(2) * 100)}%)
<Button variant="destructive" onmousedown={() => onRemoveThruster(index)}>X</Button> <Button variant="destructive" onmousedown={() => onRemoveThruster(index)}>X</Button>
</p> </p>
{/each} {/each}

View file

@ -196,6 +196,22 @@ const localization = new Map([
["deuterium", new Map([ ["deuterium", new Map([
["en-GB", "Deuterium"], ["en-GB", "Deuterium"],
["de-DE", "Deuterium"], ["de-DE", "Deuterium"],
])],
["vehicleWeightInTons", new Map([
["en-GB", "Vehicle Weight (in tons)"],
["de-DE", "Fahrzeug Gewicht (in Tonnen)"],
])],
["full", new Map([
["en-GB", "full"],
["de-DE", "voll"],
])],
["empty", new Map([
["en-GB", "empty"],
["de-DE", "leer"],
])],
["acceleration", new Map([
["en-GB", "Acceleration"],
["de-DE", "Beschleunigung"],
])] ])]
]) ])
@ -265,6 +281,6 @@ export function weightConversion(i18n: Localization, weight: number): string {
return applyUnits(i18n, weight/1000, "t") return applyUnits(i18n, weight/1000, "t")
} }
else { else {
return `${weight.toFixed(2)}kg` return `${(weight * 100).toFixed(2) || (weight * 100) / 100}kg`
} }
} }

View file

@ -2,12 +2,13 @@ import {Thruster, THRUSTER_LIST, THRUSTER_TYPE_LIST} from "$lib/thruster.svelte"
import {Grid} from "$lib/grid"; import {Grid} from "$lib/grid";
import {INVENTORIES, Inventory} from "$lib/containers.svelte"; import {INVENTORIES, Inventory} from "$lib/containers.svelte";
import type {Fuel} from "$lib/fuel"; import type {Fuel} from "$lib/fuel";
import type {CargoMaterial} from "$lib/materials";
export class Ship { export class Ship {
thrusters: Array<Thruster> = $state([]); thrusters: Array<Thruster> = $state([]);
inventories: Array<Inventory> = $state([]); inventories: Array<Inventory> = $state([]);
grid: Grid = $state(Grid.Small); grid: Grid = $state(Grid.Small);
weight: number = $state(0); tons: number = $state(0);
constructor(grid: Grid = Grid.Small) { constructor(grid: Grid = Grid.Small) {
this.grid = grid; this.grid = grid;
@ -22,7 +23,7 @@ export class Ship {
return inventory.details.key; return inventory.details.key;
}), }),
grid: this.grid, grid: this.grid,
weight: this.weight, weight: this.tons,
} }
} }
@ -49,7 +50,7 @@ export class Ship {
} }
if (ship.weight !== undefined) { if (ship.weight !== undefined) {
this.weight = ship.weight; this.tons = ship.weight;
} }
} }
@ -85,14 +86,6 @@ export class Ship {
return thrust; return thrust;
} }
getTotalMaxThrust(): number {
let thrust = 0;
this.thrusters.forEach((thruster) => {
thrust += thruster.getMaxThrust(this.grid);
});
return thrust;
}
getFuelTypes(): Array<Fuel> { getFuelTypes(): Array<Fuel> {
let fuels: Array<Fuel> = []; let fuels: Array<Fuel> = [];
this.thrusters.forEach((thruster) => { this.thrusters.forEach((thruster) => {
@ -102,4 +95,29 @@ export class Ship {
}) })
return fuels; return fuels;
} }
getTotalWeightPossible(gravity: number, atmosphere: number): number {
return this.getTotalThrust(atmosphere) / (gravity * 9.81)
}
getVehicleWeightPossible(gravity: number, atmosphere: number, inventoryMultiplier: number, material: CargoMaterial): number {
return this.getTotalWeightPossible(gravity, atmosphere) - this.getTotalVolume(inventoryMultiplier) * material.density;
}
getVehicleWeight(): number {
return (this.tons * 1000);
}
getTotalWeight(inventoryMultiplier: number, material: CargoMaterial) {
return this.getVehicleWeight() + this.getTotalVolume(inventoryMultiplier) * material.density;
}
getAcceleration(atmosphere: number, inventoryMultiplier: number, material: CargoMaterial) {
console.log(material)
return this.getTotalThrust(atmosphere) / this.getTotalWeight(inventoryMultiplier, material)
}
getMaxAcceleration(atmosphere: number) {
return this.getTotalThrust(atmosphere) / this.getVehicleWeight();
}
} }

View file

@ -8,11 +8,41 @@ export enum ThrusterSize {
Huge = "hugeThruster", Huge = "hugeThruster",
} }
export type ThrustDensity = {
thrust: number;
density: number;
}
export class ThrustCurve {
private min: ThrustDensity;
private max: ThrustDensity;
constructor(min: ThrustDensity, max: ThrustDensity) {
this.min = min;
this.max = max;
}
getEffectiveness(airDensity: number): number {
if (airDensity >= this.max.density) {
return this.max.thrust;
}
else if (airDensity <= this.min.density) {
return this.min.thrust;
}
else {
return (1 - this.getPointOnCurve(airDensity)) * this.min.thrust + this.getPointOnCurve(airDensity) * this.max.thrust
}
}
private getPointOnCurve(airDensity: number): number {
return (airDensity-this.min.density)/(this.max.density - this.min.density);
}
}
type ThrusterTypeDetails = { type ThrusterTypeDetails = {
key: string, key: string,
name: string, name: string,
vacuum: number, curve: ThrustCurve,
atmosphere: number,
fuel: Fuel, fuel: Fuel,
}; };
@ -20,29 +50,25 @@ export const THRUSTER_TYPE_LIST: { [key: string]: ThrusterTypeDetails } = {
Atmospheric: { Atmospheric: {
key: "Atmospheric", key: "Atmospheric",
name: "atmosphericThruster", name: "atmosphericThruster",
vacuum: 0, curve: new ThrustCurve({density: 0.3, thrust: 0}, {density: 1, thrust: 1}),
atmosphere: 1,
fuel: Fuel.Electricity, fuel: Fuel.Electricity,
}, },
Ion: { Ion: {
key: "Ion", key: "Ion",
name: "ionThruster", name: "ionThruster",
vacuum: 1, curve: new ThrustCurve({density: 0, thrust: 1}, {density: 1, thrust: 0.3}),
atmosphere: 0.2,
fuel: Fuel.Electricity, fuel: Fuel.Electricity,
}, },
Hydrogen: { Hydrogen: {
key: "Hydrogen", key: "Hydrogen",
name: "hydrogenThruster", name: "hydrogenThruster",
vacuum: 1, curve: new ThrustCurve({density: 0, thrust: 1}, {density: 1, thrust: 1}),
atmosphere: 1,
fuel: Fuel.Hydrogen, fuel: Fuel.Hydrogen,
}, },
Fusion: { Fusion: {
key: "Fusion", key: "Fusion",
name: "fusionThruster", name: "fusionThruster",
vacuum: 1, curve: new ThrustCurve({density: 0, thrust: 1}, {density: 1, thrust: 0.5}),
atmosphere: 0.5,
fuel: Fuel.Deuterium, fuel: Fuel.Deuterium,
}, },
} }
@ -61,9 +87,8 @@ export class ThrusterType {
let equals: Array<boolean> = []; let equals: Array<boolean> = [];
equals[0] = this.details.key === obj.details.key; equals[0] = this.details.key === obj.details.key;
equals[1] = this.details.name === obj.details.name; equals[1] = this.details.name === obj.details.name;
equals[2] = this.details.vacuum === obj.details.vacuum; equals[2] = this.details.curve === obj.details.curve;
equals[3] = this.details.atmosphere === obj.details.atmosphere; equals[3] = this.details.fuel === obj.details.fuel;
equals[4] = this.details.fuel === obj.details.fuel;
return !equals.includes(false); return !equals.includes(false);
} }
} }
@ -229,7 +254,7 @@ export class Thruster {
} }
getThrust(grid: Grid, atmosphereDensity: number): number { getThrust(grid: Grid, atmosphereDensity: number): number {
let efficiencyCoefficient: number = (this.details.type.details.vacuum * (1 - atmosphereDensity)) + (this.details.type.details.atmosphere * atmosphereDensity); let efficiencyCoefficient: number = this.details.type.details.curve.getEffectiveness(atmosphereDensity);
let thruster: ThrusterDetails | undefined = THRUSTER_LIST[this.details.key]; let thruster: ThrusterDetails | undefined = THRUSTER_LIST[this.details.key];
if (thruster === undefined) { if (thruster === undefined) {
throw(`Thruster ${this.details.key} undefined, cannot retrieve thrust values`); throw(`Thruster ${this.details.key} undefined, cannot retrieve thrust values`);

View file

@ -25,6 +25,9 @@
import {Slider} from "$lib/components/ui/slider"; import {Slider} from "$lib/components/ui/slider";
import Atmosphere from "$lib/components/Atmosphere.svelte"; import Atmosphere from "$lib/components/Atmosphere.svelte";
import WeightOutputSegment from "$lib/components/WeightOutputSegment.svelte"; import WeightOutputSegment from "$lib/components/WeightOutputSegment.svelte";
import Inventories from "$lib/cards/Inventories.svelte";
import Thrusters from "$lib/cards/Thrusters.svelte";
import AccelerationOutputSegment from "$lib/components/AccelerationOutputSegment.svelte";
const STORAGE_VERSION = "2"; const STORAGE_VERSION = "2";
let i18n = $state(new Localization("en-GB")); let i18n = $state(new Localization("en-GB"));
@ -66,10 +69,6 @@
} }
}) })
let maxWeight: number = $derived(ship.getTotalThrust(atmosphericDensity) / (gravity * 9.81))
let maxVehicleWeight: number = $derived(maxWeight - ship.getTotalVolume(inventoryMultiplier) * material.density)
function addInventory(newInventory: Inventory) { function addInventory(newInventory: Inventory) {
console.log("triggered") console.log("triggered")
ship.addInventory(newInventory); ship.addInventory(newInventory);
@ -87,12 +86,6 @@
ship.removeThruster(index); ship.removeThruster(index);
} }
function handleGravityChange() {
if (gravity <= 0) {
gravity = 0;
}
}
onMount(() => { onMount(() => {
if (navigator) { if (navigator) {
i18n.language = navigator.language; i18n.language = navigator.language;
@ -149,6 +142,7 @@
<meta name="robots" content="noindex nofollow" /> <meta name="robots" content="noindex nofollow" />
</svelte:head> </svelte:head>
{#if mounted}
<div class="gap-4 flex flex-row flex-wrap h-full"> <div class="gap-4 flex flex-row flex-wrap h-full">
<Card.Root class="flex-1"> <Card.Root class="flex-1">
<Card.Header> <Card.Header>
@ -173,7 +167,7 @@
{/each} {/each}
</div> </div>
<Label for="gravity">{i18n.localize("gravity")}</Label> <Label for="gravity">{i18n.localize("gravity")}</Label>
<Input type="number" step="0.01" id="gravity" bind:value={gravity} onchange={(change) => {handleGravityChange(change)}}/> <Input type="number" step="0.01" id="gravity" bind:value={gravity} min="0"/>
<Label for="density">{i18n.localize("density")}</Label> <Label for="density">{i18n.localize("density")}</Label>
<div id="density" class="flex gap-2"> <div id="density" class="flex gap-2">
{#each Object.values(CargoMaterial) as value} {#each Object.values(CargoMaterial) as value}
@ -185,6 +179,9 @@
{/each} {/each}
</div> </div>
<Atmosphere bind:value={atmosphericDensity} /> <Atmosphere bind:value={atmosphericDensity} />
<Separator />
<Label for="weight">{i18n.localize("vehicleWeightInTons")}</Label>
<Input type="number" step="0.01" id="weight" bind:value={ship.tons} min="0" />
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
<Card.Root> <Card.Root>
@ -193,63 +190,22 @@
</Card.Header> </Card.Header>
<Card.Content class="flex flex-col gap-3"> <Card.Content class="flex flex-col gap-3">
<Separator /> <Separator />
<WeightOutputSegment {i18n} maxWeight={maxWeight} maxVehicleWeight="{maxVehicleWeight}" /> <WeightOutputSegment {i18n} maxWeight={ship.getTotalWeightPossible(gravity, atmosphericDensity)} maxVehicleWeight="{ship.getVehicleWeightPossible(gravity, atmosphericDensity, inventoryMultiplier, material)}" />
<Separator /> <Separator />
<p>{i18n.localize(ship.getFuelTypes().length > 1 ? "fuels" : "fuel")}: <p>{i18n.localize(ship.getFuelTypes().length > 1 ? "fuels" : "fuel")}:
{ship.getFuelTypes().map((fuel) => i18n.localize(fuel.name)).join(", ") } {ship.getFuelTypes().map((fuel) => i18n.localize(fuel.name)).join(", ") }
</p> </p>
</Card.Content>
</Card.Root>
</Card.Content>
</Card.Root>
<Card.Root class="flex-1 flex flex-col flex-grow">
<Card.Header>
<Card.Title>{i18n.localize("thrusterSettings")}</Card.Title>
</Card.Header>
<Card.Content class="flex flex-col flex-grow gap-3">
<Separator /> <Separator />
<Card.Root> <AccelerationOutputSegment {i18n} {ship} atmosphere={atmosphericDensity} {inventoryMultiplier} {material} />
<Card.Content class="flex flex-col gap-3 pt-6">
<NewThruster i18n={i18n} onAddThruster={addThruster} />
</Card.Content>
</Card.Root>
<Card.Root class="flex flex-col h-60 flex-grow">
<Card.Header>
<Card.Title>{i18n.localize("thrusters")} ({applyUnits(i18n, ship.getTotalThrust(atmosphericDensity), "N")})</Card.Title>
</Card.Header>
<Card.Content class="flex flex-col gap-3 overflow-hidden">
<Separator />
<ThrusterList i18n={i18n} thrusters={ship.thrusters} onRemoveThruster={removeThruster} grid={ship.grid} atmosphere={atmosphericDensity}/>
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
<Card.Root class="flex-1 flex flex-col flex-grow"> <Thrusters {i18n} {ship} {atmosphericDensity} onAddThruster={addThruster} onRemoveThruster={removeThruster} />
<Card.Header> <Inventories {i18n} {ship} {inventoryMultiplier} onAddInventory={addInventory} onRemoveInventory={removeInventory} />
<Card.Title>{i18n.localize("inventorySettings")}</Card.Title>
</Card.Header>
<Card.Content class="flex flex-col flex-grow gap-3">
<Separator />
<Label for="inventorySize">{i18n.localize("inventory")} {i18n.localize("multiplier")}</Label>
<Input id="inventorySize" type="number" bind:value={inventoryMultiplier}/>
<Card.Root>
<Card.Content class="pt-6 flex flex-col gap-3">
<NewInventory i18n={i18n} grid={ship.grid} onAddInventory={addInventory} />
</Card.Content>
</Card.Root>
<Card.Root class="flex flex-col h-60 flex-grow">
<Card.Header>
<Card.Title>{i18n.localize("inventories")} ({applyUnits(i18n, ship.getTotalVolume(inventoryMultiplier), "l")})</Card.Title>
</Card.Header>
<Card.Content class="flex flex-col gap-3 overflow-hidden">
<Separator />
<InventoryList i18n={i18n} inventories={ship.inventories} onRemoveInventory={removeInventory} grid={ship.grid} multiplier={inventoryMultiplier} />
</Card.Content>
</Card.Root>
</Card.Content>
</Card.Root>
</div> </div>
{/if}