Major Rewrite of underlying code

This commit is contained in:
Neshura 2024-05-22 22:56:17 +02:00
parent d031e2b7a1
commit c7fa40b454
Signed by: Neshura
GPG key ID: B6983AAA6B9A7A6C
15 changed files with 923 additions and 437 deletions

View file

@ -4,67 +4,48 @@
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 210 40% 98%;
--ring: 222.2 84% 4.9%;
--background: 239 19% 95%;
--foreground: 239 5% 0%;
--card: 239 19% 90%;
--card-foreground: 239 5% 10%;
--popover: 239 19% 95%;
--popover-foreground: 239 95% 0%;
--primary: 239 58% 35%;
--primary-foreground: 0 0% 100%;
--secondary: 239 19% 70%;
--secondary-foreground: 0 0% 0%;
--muted: 201 19% 85%;
--muted-foreground: 239 5% 35%;
--accent: 201 19% 80%;
--accent-foreground: 239 5% 10%;
--destructive: 0 50% 30%;
--destructive-foreground: 239 5% 90%;
--border: 239 20% 50%;
--input: 239 20% 18%;
--ring: 239 58% 35%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--ring: hsl(212.7,26.8%,83.9);
--background: 239 19% 5%;
--foreground: 239 5% 90%;
--card: 239 19% 0%;
--card-foreground: 239 5% 90%;
--popover: 239 19% 5%;
--popover-foreground: 239 5% 90%;
--primary: 239 58% 35%;
--primary-foreground: 0 0% 100%;
--secondary: 239 19% 10%;
--secondary-foreground: 0 0% 100%;
--muted: 201 19% 15%;
--muted-foreground: 239 5% 60%;
--accent: 201 19% 15%;
--accent-foreground: 239 5% 90%;
--destructive: 0 50% 30%;
--destructive-foreground: 239 5% 90%;
--border: 239 20% 18%;
--input: 239 20% 18%;
--ring: 239 58% 35%;
--radius: 0.5rem;
}
}

View file

@ -0,0 +1,34 @@
<svelte:options runes />
<script lang="ts">
import {applyUnits} from "$lib/constants.js";
import {Button} from "$lib/components/ui/button/index.js";
import {Grid} from "$lib/grid";
let { i18n = new Localization("en-GB"), inventories = $bindable(), grid, multiplier } = $props();
let valid = $derived.by(() => {
let valids = [];
valids[0] = typeof inventories !== "undefined";
valids[1] = typeof grid !== "undefined";
valids[2] = typeof multiplier !== "undefined";
return !valids.includes(false);
});
function removeInventory(index: number) {
thrusters.splice(index, 1);
}
$inspect(inventories)
</script>
<div class="max-h-full flex flex-col gap-2 overflow-y-auto">
{#if valid}
{#each inventories as inventory, index}
<p class="flex gap-2 justify-between">
- {i18n.localize(inventory.details.name)} {i18n.localize("volume")}: {applyUnits(i18n, inventory.getVolume(grid, multiplier), "l")}
<Button variant="destructive" onclick={() => removeInventory(index)}>X</Button>
</p>
{/each}
{:else}
<p>Invalid Props</p>
{/if}
</div>

View file

@ -0,0 +1,66 @@
<svelte:options runes />
<script lang="ts">
import {getFromLocalStorage, Localization} from "$lib/constants.js";
import {Button} from "$lib/components/ui/button/index.js";
import {Grid} from "$lib/grid";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import {Inventory, INVENTORIES} from "$lib/containers.svelte";
import {THRUSTER_TYPE_LIST, ThrusterType} from "$lib/thruster.svelte";
import {onMount} from "svelte";
let { i18n = new Localization("en-GB"), onAddInventory, grid } = $props();
let mounted = $state(false);
let valid = $derived.by(() => {
let valids = [];
valids[0] = typeof onAddInventory !== "undefined";
valids[1] = typeof grid !== "undefined";
return !valids.includes(false);
});
let key: string = $state(INVENTORIES.CargoSmall.key)
let name = $derived(INVENTORIES[key].name);
$effect(() => {
if (mounted) {
localStorage.setItem("newInventory", key)
}
})
function updateDetails(newKey: string) {
key = newKey;
}
function construct() {
return new Inventory(INVENTORIES[key]);
}
onMount(() => {
let ret = getFromLocalStorage("newInventory");
if (ret.result) {
key = ret.value;
}
mounted = true;
})
</script>
{#if valid}
<div class="flex flex-col flex-wrap gap-3 max-w-fit place-self-center items-center">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="secondary">{i18n.localize(name)}</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.RadioGroup value={key} onValueChange={(change) => updateDetails(change)}>
{#each Object.values(INVENTORIES) as inventory}
<DropdownMenu.RadioItem value={inventory.key}>{i18n.localize(inventory.name)}</DropdownMenu.RadioItem>
{/each}
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu.Root>
<Button class="bg-primary max-w-fit" onclick={() => onAddInventory(construct())}>{i18n.localize("addInventory")}</Button>
</div>
{:else}
<p>Invalid Props</p>
{/if}

View file

@ -0,0 +1,115 @@
<svelte:options runes />
<script lang="ts">
import {
Thruster, THRUSTER_LIST,
THRUSTER_TYPE_LIST,
ThrusterSize,
ThrusterType
} from "$lib/thruster.svelte";
import {onMount} from "svelte";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import {getFromLocalStorage, Localization} from "$lib/constants";
import {Button} from "$lib/components/ui/button";
import {Grid} from "$lib/grid";
let { i18n = new Localization("en-GB"), onAddThruster, grid = Grid.Small } = $props();
let mounted = $state(false);
let valid = $derived.by(() => {
let valids = [];
valids[0] = typeof onAddThruster !== "undefined";
valids[1] = typeof grid !== "undefined";
return !valids.includes(false);
});
let type: ThrusterType = $state(new ThrusterType(THRUSTER_TYPE_LIST.Atmospheric));
$effect(() => {
if (mounted) {
localStorage.setItem("newThrusterType", type.details.key);
}
})
let size = $state(ThrusterSize.Small);
$effect(() => {
if (mounted) {
localStorage.setItem("newThrusterSize", size)
}
})
let sizes: Array<ThrusterSize> = $derived(Thruster.getByType(type, size).map((thruster) => {return thruster.size}));
$effect(() => {
if (!sizes.includes(size)) {
size = sizes[0]
}
})
function construct() {
let newThruster;
Object.values(THRUSTER_LIST).forEach((thruster) => {
if (thruster.type.equals(type) && thruster.size === size) {
newThruster = new Thruster(thruster);
}
})
return newThruster;
}
function updateDetails(newKey: string) {
type.details = THRUSTER_TYPE_LIST[newKey];
}
onMount(() => {
let ret = getFromLocalStorage("newThrusterType");
if (ret.result) {
type = new ThrusterType(THRUSTER_TYPE_LIST[ret.value]);
}
ret = getFromLocalStorage("newThrusterSize");
if (ret.result) {
size = ret.value;
}
mounted = true;
})
</script>
<div class="flex flex-col gap-3 place-self-center items-center">
{#if valid}
<div class="flex flex-row gap-3 flex-wrap justify-center">
<div>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="secondary">
{i18n.localize(size)}
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.RadioGroup bind:value={size}>
{#each sizes as thrusterSize}
<DropdownMenu.RadioItem value={thrusterSize}>{i18n.localize(thrusterSize)}</DropdownMenu.RadioItem>
{/each}
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="secondary">
{i18n.localize(type.details.name)}
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.RadioGroup value={type.details.key} onValueChange={(change) => {updateDetails(change)}}>
{#each Object.values(THRUSTER_TYPE_LIST) as thrusterType}
<DropdownMenu.RadioItem value={thrusterType.key}>{i18n.localize(thrusterType.name)}</DropdownMenu.RadioItem>
{/each}
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
<Button vclass="bg-primary max-w-fit" onclick={() => onAddThruster(construct())}>{i18n.localize("addThruster")}</Button>
{:else}
<p>Invalid Props</p>
{/if}
</div>

View file

@ -0,0 +1,34 @@
<svelte:options runes />
<script lang="ts">
import {Button} from "$lib/components/ui/button/index";
import {applyUnits, Localization} from "$lib/constants";
import {Grid} from "$lib/grid";
let { i18n = new Localization("en-GB"), thrusters = $bindable(), grid, atmosphere } = $props();
let valid = $derived.by(() => {
let valids = [];
valids[0] = typeof thrusters !== "undefined";
valids[1] = typeof grid !== "undefined";
valids[2] = typeof atmosphere !== "undefined";
return !valids.includes(false);
});
function removeThruster(index: number) {
thrusters.splice(index, 1);
}
</script>
<div class="max-h-full flex flex-col gap-2 overflow-y-auto">
{#if valid}
{#each thrusters as thruster, index}
<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")}
<Button variant="destructive" onclick={() => removeThruster(index)}>X</Button>
</p>
{/each}
{:else}
<p>Invalid Props</p>
{/if}
</div>

View file

@ -0,0 +1 @@
export type ObjectValues<T> = T[keyof T];

View file

@ -1,114 +1,3 @@
export enum Grids {
Small = "smallGrid",
Large = "largeGrid"
}
export enum ThrusterFuel {
Electric = "electric",
Hydrogen = "hydrogen",
Deuterium = "deuterium"
}
export class ThrusterTypeDetails {
atmosphericFactor: number;
vacuumFactor: number;
fuel: ThrusterFuel;
sizes: Map<ThrusterSize, ThrusterSizeDetails>;
constructor(atmos: number, vacuum: number, fuel: ThrusterFuel, sizes: Map<ThrusterSize, ThrusterSizeDetails>) {
this.atmosphericFactor = atmos;
this.vacuumFactor = vacuum;
this.fuel = fuel;
this.sizes = sizes;
}
}
export enum ThrusterSize {
Small = "smallThruster",
Large = "largeThruster",
Huge = "hugeThruster",
}
export enum ThrusterType {
Ion = "ion",
Atmospheric = "atmos",
Hydrogen = "hydrogen",
Fusion = "fusion"
}
export class ThrusterSizeDetails {
thrust: number;
maxFuelConsumption: number;
constructor(thrust: number, maxFuelConsumption = 0) {
this.thrust = thrust;
this.maxFuelConsumption = maxFuelConsumption;
}
}
export const thrusterDetails: Map<Grids, Map<ThrusterType, ThrusterTypeDetails>> = new Map([
[Grids.Large, new Map([
[ThrusterType.Atmospheric, new ThrusterTypeDetails(1, 0, ThrusterFuel.Electric, new Map([
[ThrusterSize.Large, new ThrusterSizeDetails(4500000)],
[ThrusterSize.Small, new ThrusterSizeDetails(350000)]
]))],
[ThrusterType.Ion, new ThrusterTypeDetails(0.2, 1, ThrusterFuel.Electric, new Map([
[ThrusterSize.Large, new ThrusterSizeDetails(4320000)],
[ThrusterSize.Small, new ThrusterSizeDetails(345600)]
]))],
[ThrusterType.Hydrogen, new ThrusterTypeDetails(1, 1, ThrusterFuel.Hydrogen, new Map([
[ThrusterSize.Large, new ThrusterSizeDetails(7200000)],
[ThrusterSize.Small, new ThrusterSizeDetails(1080000)]
]))],
[ThrusterType.Fusion, new ThrusterTypeDetails(0.5, 1, ThrusterFuel.Deuterium, new Map([
[ThrusterSize.Large, new ThrusterSizeDetails(33780000)],
[ThrusterSize.Small, new ThrusterSizeDetails(9040000)]
]))]
])],
[Grids.Small, new Map([
[ThrusterType.Atmospheric, new ThrusterTypeDetails(1, 0, ThrusterFuel.Electric, new Map([
[ThrusterSize.Large, new ThrusterSizeDetails(340000)],
[ThrusterSize.Small, new ThrusterSizeDetails(65000)]
]))],
[ThrusterType.Ion, new ThrusterTypeDetails(0.2, 1, ThrusterFuel.Electric, new Map([
[ThrusterSize.Large, new ThrusterSizeDetails(172800)],
[ThrusterSize.Small, new ThrusterSizeDetails(14400)]
]))],
[ThrusterType.Hydrogen, new ThrusterTypeDetails(1, 1, ThrusterFuel.Hydrogen, new Map([
[ThrusterSize.Large, new ThrusterSizeDetails(480000)],
[ThrusterSize.Small, new ThrusterSizeDetails(98400)]
]))],
[ThrusterType.Fusion, new ThrusterTypeDetails(0.5, 1, ThrusterFuel.Deuterium, new Map([
[ThrusterSize.Huge, new ThrusterSizeDetails(9040000)],
[ThrusterSize.Large, new ThrusterSizeDetails(1030000)],
[ThrusterSize.Small, new ThrusterSizeDetails(210000)]
]))]
])]
])
export enum InventoryType {
Connector = "connector",
CargoSmall = "smallCargo",
CargoMedium = "mediumCargo",
CargoLarge = "largeCargo",
}
export const inventorySizes = new Map([
[Grids.Large, new Map([
[InventoryType.CargoLarge, 421000],
[InventoryType.CargoMedium, 0],
[InventoryType.CargoSmall, 15625],
[InventoryType.Connector, 8000]
])],
[Grids.Small, new Map([
[InventoryType.CargoLarge, 15625],
[InventoryType.CargoMedium, 3375],
[InventoryType.CargoSmall, 125],
[InventoryType.Connector, 1152]
])],
]);
export const metricModifiers: Map<number, string> = new Map([
[1000000000, "G"],
[1000000, "M"],
@ -116,17 +5,7 @@ export const metricModifiers: Map<number, string> = new Map([
[1, ""]
]);
export enum Density {
Ore = "ore",
Component = "component",
}
export const densityValues: Map<Density, number> = new Map([
[Density.Ore, 1/0.37],
[Density.Component, 1/0.047],
])
export const localization = new Map([
const localization = new Map([
["space engineers", new Map([
["en-GB", "Space Engineers"]
])],
@ -210,21 +89,21 @@ export const localization = new Map([
["en-GB", "Large Cargo Container"],
["de-DE", "Großer Frachtcontainer"]
])],
["fusion", new Map([
["en-GB", "Fusion"],
["de-DE", "Fusion"]
["fusionThruster", new Map([
["en-GB", "Fusion Thruster"],
["de-DE", "Fusionstriebwerk"]
])],
["hydrogen", new Map([
["en-GB", "Hydrogen"],
["de-DE", "Wasserstoff"]
["hydrogenThruster", new Map([
["en-GB", "Hydrogen Thruster"],
["de-DE", "Wasserstofftriebwerk"]
])],
["ion", new Map([
["en-GB", "Ion"],
["de-DE", "Ionen"]
["ionThruster", new Map([
["en-GB", "Ion Thruster"],
["de-DE", "Ionentriebwerk"]
])],
["atmos", new Map([
["en-GB", "Atmospheric"],
["de-DE", "Atmosphären"]
["atmosphericThruster", new Map([
["en-GB", "Atmospheric Thruster"],
["de-DE", "Atmosphärentriebwerk"]
])],
["addInventory", new Map([
["en-GB", "Add Inventory"],
@ -278,9 +157,9 @@ export const localization = new Map([
["en-GB", "Ore"],
["de-DE", "Erz"]
])],
["component", new Map([
["en-GB", "Component"],
["de-DE", "Komponente"]
["platinumIngot", new Map([
["en-GB", "Platinum Ingot"],
["de-DE", "Platinbarren"]
])],
["density", new Map([
["en-GB", "Density"],
@ -292,7 +171,72 @@ export const localization = new Map([
])],
])
export type Thruster = {
type: ThrusterType,
size: ThrusterSize
export class Localization {
dictionary: Map<string, Map<string, string>>;
language: string;
constructor(language: string) {
this.language = language;
this.dictionary = localization;
};
localize(key: string): string {
let localizationObject = this.dictionary.get(key)
if (typeof localizationObject === "undefined") {
return `missing text: ${key}`
}
else {
let localizedText = localizationObject.get(this.language)
if (typeof localizedText === "undefined") {
return localizationObject.get("en-GB") || `mising text: ${key}`;
}
else {
return localizedText
}
}
}
}
export function getFromLocalStorage(key: string) {
let val = localStorage.getItem(key);
if (val !== null && typeof val !== "undefined" && val !== '{}') {
return { result: true, value: val };
}
else {
return { result: false, value: undefined };
}
}
export function applyUnits(i18n: Localization, value: number, unit: string): string {
for (const [power, modifier] of [...metricModifiers.entries()]) {
if (value > power) {
if (value / power >= 10000) {
let base = value / power;
let rest = base % 1000;
base = (base - rest) / 1000;
rest = Math.round((rest + Number.EPSILON) * 100) / 100
return `${base}${i18n.localize("separator")}${rest}${modifier}${unit}`
}
else {
let rounded = Math.round(((value / power) + Number.EPSILON) * 100) / 100
return `${rounded}${modifier}${unit}`
}
}
}
if (value < 0) {
return `-${applyUnits(i18n,value * -1, unit)}`
}
let rounded = Math.round(((value + Number.EPSILON) * 100) / 100)
return `${rounded}${unit}`
}
export function weightConversion(i18n: Localization, weight: number): string {
if (weight > 1000 || weight < -1000) {
return applyUnits(i18n, weight/1000, "t")
}
else {
return `${weight.toFixed(2)}kg`
}
}

View file

@ -0,0 +1,62 @@
import {Grid} from "$lib/grid";
import type {ObjectValues} from "$lib/components/object-values";
import {THRUSTER_LIST, THRUSTER_TYPE_LIST} from "$lib/thruster.svelte";
import {applyUnits} from "$lib/constants";
type InventoryDetails = {
key: string,
name: string,
capacity: Map<Grid, number>
}
export const INVENTORIES: { [key: string]: InventoryDetails } = {
CargoLarge: {
key: "CargoLarge",
name: "largeCargo",
capacity: new Map([
[Grid.Small, 15625],
[Grid.Large, 421000],
])
},
CargoMedium: {
key: "CargoMedium",
name: "mediumCargo",
capacity: new Map([
[Grid.Small, 3375],
])
},
CargoSmall: {
key: "CargoSmall",
name: "smallCargo",
capacity: new Map([
[Grid.Small, 125],
[Grid.Large, 15625],
])
},
Connector: {
key: "Connector",
name: "connector",
capacity: new Map([
[Grid.Small, 1152],
[Grid.Large, 8000],
])
}
}
type InventoryList = ObjectValues<typeof INVENTORIES>;
export class Inventory {
details: InventoryDetails;
constructor(details: InventoryDetails) {
if (typeof details === "undefined") {
throw("Inventory undefined, cannot create Object");
}
this.details = details;
}
getVolume(grid: Grid, multiplier: number): number {
console.log(this.details.capacity.get(grid), multiplier);
return (this.details.capacity.get(grid) || 0) * multiplier;
}
}

13
src/lib/fuel.ts Normal file
View file

@ -0,0 +1,13 @@
export class Fuel {
static readonly Electricity: Fuel = new Fuel('electric', 'W');
static readonly Hydrogen: Fuel = new Fuel('hydrogen', 'l');
static readonly Deuterium: Fuel = new Fuel('deuterium', 'l');
readonly unit: string;
readonly name: string;
private constructor(name: string, unit: string) {
this.name = name;
this.unit = unit;
}
}

4
src/lib/grid.ts Normal file
View file

@ -0,0 +1,4 @@
export enum Grid {
Small = "small",
Large = "large",
}

26
src/lib/materials.ts Normal file
View file

@ -0,0 +1,26 @@
export class CargoMaterial {
static readonly Ore: CargoMaterial = new CargoMaterial('ore', 1/0.37);
static readonly PlatinumIngot: CargoMaterial = new CargoMaterial('platinumIngot', 1/0.047);
readonly density: number;
readonly name: string;
private constructor(name: string, density: number) {
this.name = name;
this.density = density;
}
static load(name: string) {
let ret = CargoMaterial.Ore;
Object.values(CargoMaterial).forEach((material) => {
if (material.name === name) {
ret = material;
}
})
return ret;
}
save() {
return this.name;
}
}

90
src/lib/ship.svelte.ts Normal file
View file

@ -0,0 +1,90 @@
import {Thruster, THRUSTER_LIST} from "$lib/thruster.svelte";
import {Grid} from "$lib/grid";
import {INVENTORIES, Inventory} from "$lib/containers.svelte";
export class Ship {
thrusters: Array<Thruster> = $state([]);
inventories: Array<Inventory> = $state([]);
grid: Grid = $state(Grid.Small);
weight: number = $state(0);
constructor(grid: Grid = Grid.Small) {
this.grid = grid;
}
save() {
return {
thrusters: this.thrusters.map((thruster) => {
return thruster.details.key;
}),
inventories: this.inventories.map((inventory) => {
return inventory.details.key;
}),
grid: this.grid,
weight: this.weight,
}
}
load(ship: {
grid: Grid,
thrusters: Array<string>,
inventories: Array<string>,
weight: number,
}) {
if (ship.grid !== undefined) {
this.grid = ship.grid;
}
if (ship.thrusters !== undefined) {
this.thrusters = ship.thrusters.map((thruster) => {
return new Thruster(THRUSTER_LIST[thruster]);
});
}
if (ship.inventories !== undefined) {
this.inventories = ship.inventories.map((inventory) => {
return new Inventory(INVENTORIES[inventory]);
});
}
if (ship.weight !== undefined) {
this.weight = ship.weight;
}
}
addThruster(thruster: Thruster): void {
this.thrusters.push(thruster);
}
addInventory(inventory: Inventory): void {
this.inventories.push(inventory);
}
getTotalVolume(inventoryMultiplier: number) {
let volume = 0;
this.inventories.forEach((inventory) => {
volume += inventory.getVolume(this.grid, inventoryMultiplier)
});
return volume;
}
spliceInventories(index: number, length: number): void {
this.inventories.splice(index, length);
}
getTotalThrust(atmosphere: number): number {
let thrust: number = 0;
this.thrusters.forEach((thruster) => {
thrust += thruster.getThrust(this.grid, atmosphere);
});
return thrust;
}
getTotalMaxThrust(): number {
let thrust = 0;
this.thrusters.forEach((thruster) => {
thrust += thruster.getMaxThrust(this.grid);
});
return thrust;
}
}

267
src/lib/thruster.svelte.ts Normal file
View file

@ -0,0 +1,267 @@
import {Fuel} from "$lib/fuel";
import {Grid} from "$lib/grid";
import type {ObjectValues} from "$lib/components/object-values";
export enum ThrusterSize {
Small = "smallThruster",
Large = "largeThruster",
Huge = "hugeThruster",
}
type ThrusterTypeDetails = {
key: string,
name: string,
vacuum: number,
atmosphere: number,
fuel: Fuel,
};
export const THRUSTER_TYPE_LIST: { [key: string]: ThrusterTypeDetails } = {
Atmospheric: {
key: "Atmospheric",
name: "atmosphericThruster",
vacuum: 0,
atmosphere: 1,
fuel: Fuel.Electricity,
},
Ion: {
key: "Ion",
name: "ionThruster",
vacuum: 1,
atmosphere: 0.2,
fuel: Fuel.Electricity,
},
Hydrogen: {
key: "Hydrogen",
name: "hydrogenThruster",
vacuum: 1,
atmosphere: 1,
fuel: Fuel.Hydrogen,
},
Fusion: {
key: "Fusion",
name: "fusionThruster",
vacuum: 1,
atmosphere: 0.5,
fuel: Fuel.Deuterium,
},
}
export class ThrusterType {
details: ThrusterTypeDetails = $state(THRUSTER_TYPE_LIST.Atmospheric);
constructor(type: ThrusterTypeDetails) {
if (typeof type === "undefined") {
throw("ThrusterType undefined, cannot create Object");
}
this.details = type;
}
equals(obj: ThrusterType) {
let equals: Array<boolean> = [];
equals[0] = this.details.key === obj.details.key;
equals[1] = this.details.name === obj.details.name;
equals[2] = this.details.vacuum === obj.details.vacuum;
equals[3] = this.details.atmosphere === obj.details.atmosphere;
equals[4] = this.details.fuel === obj.details.fuel;
return !equals.includes(false);
}
}
type ThrusterDetails = {
key: string,
name: string,
type: ThrusterType,
size: ThrusterSize,
fuelConsumption: Map<Grid, number>;
thrust: Map<Grid, number>;
}
export const THRUSTER_LIST: {[key: string]: ThrusterDetails } = {
SmallAtmos: {
key: "SmallAtmos",
name: "smallAtmos",
type: new ThrusterType(THRUSTER_TYPE_LIST.Atmospheric),
size: ThrusterSize.Small,
fuelConsumption: new Map([
[Grid.Small, 0],
[Grid.Large, 0],
]),
thrust: new Map([
[Grid.Small, 65000],
[Grid.Large, 350000],
])
},
LargeAtmos: {
key: "LargeAtmos",
name: "largeAtmos",
type: new ThrusterType(THRUSTER_TYPE_LIST.Atmospheric),
size: ThrusterSize.Large,
fuelConsumption: new Map([
[Grid.Small, 0],
[Grid.Large, 0],
]),
thrust: new Map([
[Grid.Small, 340000],
[Grid.Large, 4500000],
])
},
SmallIon: {
key: "SmallIon",
name: "smallIon",
type: new ThrusterType(THRUSTER_TYPE_LIST.Ion),
size: ThrusterSize.Small,
fuelConsumption: new Map([
[Grid.Small, 0],
[Grid.Large, 0],
]),
thrust: new Map([
[Grid.Small, 14400],
[Grid.Large, 345600],
])
},
LargeIon: {
key: "LargeIon",
name: "largeIon",
type: new ThrusterType(THRUSTER_TYPE_LIST.Ion),
size: ThrusterSize.Large,
fuelConsumption: new Map([
[Grid.Small, 0],
[Grid.Large, 0],
]),
thrust: new Map([
[Grid.Small, 172800],
[Grid.Large, 4320000],
])
},
SmallHydrogen: {
key: "SmallHydrogen",
name: "smallHydrogen",
type: new ThrusterType(THRUSTER_TYPE_LIST.Hydrogen),
size: ThrusterSize.Small,
fuelConsumption: new Map([
[Grid.Small, 0],
[Grid.Large, 0],
]),
thrust: new Map([
[Grid.Small, 98400],
[Grid.Large, 1080000],
])
},
LargeHydrogen: {
key: "LargeHydrogen",
name: "largeHydrogen",
type: new ThrusterType(THRUSTER_TYPE_LIST.Hydrogen),
size: ThrusterSize.Large,
fuelConsumption: new Map([
[Grid.Small, 0],
[Grid.Large, 0],
]),
thrust: new Map([
[Grid.Small, 480000],
[Grid.Large, 7200000],
])
},
SmallFusion: {
key: "SmallFusion",
name: "smallFusion",
type: new ThrusterType(THRUSTER_TYPE_LIST.Fusion),
size: ThrusterSize.Small,
fuelConsumption: new Map([
[Grid.Small, 0],
[Grid.Large, 0],
]),
thrust: new Map([
[Grid.Small, 210000],
[Grid.Large, 9040000],
])
},
LargeFusion: {
key: "LargeFusion",
name: "largeFusion",
type: new ThrusterType(THRUSTER_TYPE_LIST.Fusion),
size: ThrusterSize.Large,
fuelConsumption: new Map([
[Grid.Small, 0],
[Grid.Large, 0],
]),
thrust: new Map([
[Grid.Small, 1030000],
[Grid.Large, 33780000],
])
},
HugeFusion: {
key: "HugeFusion",
name: "hugeFusion",
type: new ThrusterType(THRUSTER_TYPE_LIST.Fusion),
size: ThrusterSize.Huge,
fuelConsumption: new Map([
[Grid.Small, 0],
]),
thrust: new Map([
[Grid.Small, 9040000],
])
}
}
export class Thruster {
details: ThrusterDetails;
constructor(thruster: ThrusterDetails) {
if (typeof thruster === "undefined") {
throw("Thruster undefined, cannot create Object");
}
this.details = thruster;
}
equals(obj: Thruster): boolean {
let equals: Array<boolean> = [];
equals[0] = this.details.key === obj.details.key;
equals[1] = this.details.name === obj.details.name;
equals[2] = this.details.type === obj.details.type;
equals[3] = this.details.size === obj.details.size;
equals[4] = this.details.fuelConsumption === obj.details.fuelConsumption;
equals[5] = this.details.thrust === obj.details.thrust;
return !equals.includes(false);
}
getThrust(grid: Grid, atmosphereDensity: number): number {
let efficiencyCoefficient: number = (this.details.type.details.vacuum * (1 - atmosphereDensity)) + (this.details.type.details.atmosphere * atmosphereDensity);
let thruster: ThrusterDetails | undefined = THRUSTER_LIST[this.details.key];
if (thruster === undefined) {
throw(`Thruster ${this.details.key} undefined, cannot retrieve thrust values`);
}
let thrust: number | undefined = thruster.thrust.get(grid);
if (thrust === undefined) {
console.warn(`Thruster ${this.details.key} has no thrust values. Defaulting to 0`);
thrust = 0;
}
return thrust * efficiencyCoefficient;
}
getMaxThrust(grid: Grid): number {
let thruster: ThrusterDetails | undefined = THRUSTER_LIST[this.details.key];
if (thruster === undefined) {
throw("Thruster undefined, cannot retrieve thrust values");
}
let thrust: number | undefined = thruster.thrust.get(grid);
if (thrust === undefined) {
console.warn(`Thruster ${this.details.key} has no thrust values. Defaulting to 0`);
thrust = 0;
}
return thrust;
}
static getByType(type: ThrusterType, grid: Array<Grid> = Object.values(Grid)): Array<ThrusterDetails> {
let thrusters: Array<ThrusterDetails> = [];
Object.values(THRUSTER_LIST).forEach((thruster) => {
if (thruster.type.details.key === type.details.key) {
thrusters.push(thruster);
}
})
return thrusters;
}
}

View file

@ -1,3 +1,7 @@
<script>
import "../app.css";
</script>
<slot></slot>
<slot></slot><script>import "../app.css";</script><style></style>
<style></style>

View file

@ -4,34 +4,32 @@
import * as Card from "$lib/components/ui/card";
import {Button} from "$lib/components/ui/button";
import {
Grids,
inventorySizes,
InventoryType,
localization, metricModifiers, type Thruster,
ThrusterSize,
ThrusterType,
thrusterDetails, Density, densityValues, ThrusterTypeDetails, ThrusterSizeDetails
applyUnits,
getFromLocalStorage,
Localization, weightConversion
} from "$lib/constants";
import {Input} from "$lib/components/ui/input";
import {Label} from "$lib/components/ui/label";
import {Separator} from "$lib/components/ui/separator";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import {onMount} from "svelte";
import {CargoMaterial} from "$lib/materials";
import ThrusterList from "$lib/components/ThrusterList.svelte";
import {Ship} from "$lib/ship.svelte";
import {Grid} from "$lib/grid";
import NewThruster from "$lib/components/NewThruster.svelte";
import NewInventory from "$lib/components/NewInventory.svelte";
import {Inventory} from "$lib/containers.svelte";
import InventoryList from "$lib/components/InventoryList.svelte";
let locale = $state("en-GB");
let i18n = $state(new Localization("en-GB"));
let mounted = $state(false);
let thrusters: Array<Thruster> = $state([])
let ship = $state(new Ship(Grid.Small));
$effect(() => {
ship.thrusters;
if (mounted) {
localStorage.setItem("thrusters", JSON.stringify(thrusters));
}
})
let inventories: Array<InventoryType> = $state([])
$effect(() => {
if (mounted) {
localStorage.setItem("inventories", JSON.stringify(inventories));
localStorage.setItem("ship", JSON.stringify(ship.save()));
}
})
@ -41,12 +39,6 @@
localStorage.setItem("inventoryMultiplier", inventoryMultiplier);
}
})
let gridSize: Grids = $state(Grids.Small);
$effect(() => {
if (mounted) {
localStorage.setItem("gridSize", gridSize);
}
})
let gravity: number = $state(1);
$effect(() => {
@ -55,10 +47,10 @@
}
})
let density: Density = $state(Density.Ore);
let material: CargoMaterial = $state(CargoMaterial.Ore);
$effect(() => {
if (mounted) {
localStorage.setItem("density", density);
localStorage.setItem("density", JSON.stringify(material.save()));
}
})
@ -69,157 +61,53 @@
}
})
let newThruster: Thruster = $state({
type: ThrusterType.Atmospheric,
size: ThrusterSize.Small,
})
$effect(() => {
if (mounted) {
localStorage.setItem("newThruster", JSON.stringify(newThruster))
}
})
let maxWeight: number = $derived(ship.getTotalMaxThrust() / (gravity * 9.81))
let newInventory: InventoryType = $state(InventoryType.CargoMedium)
$effect(() => {
if (mounted) {
localStorage.setItem("newInventory", JSON.stringify(newInventory))
}
})
let maxVehicleWeight: number = $derived(maxWeight - ship.getTotalVolume(inventoryMultiplier) * material.density)
$inspect(maxVehicleWeight);
$inspect(`${maxWeight} - ${ship.getTotalVolume(inventoryMultiplier)} * ${material.density} = ${maxVehicleWeight}`)
let totalThrust: number = $derived.by(() => {
let thrust = 0;
thrusters.forEach((thruster) => {
thrust += getThrust(thruster)
})
return thrust
});
let totalVolume: number = $derived.by(() => {
let volume = 0;
inventories.forEach((inventory) => {
volume += getInventoryVolume(inventory);
})
return volume;
})
let maxWeight: number = $derived(totalThrust / (gravity * 9.81))
let maxVehicleWeight: number = $derived(maxWeight - totalVolume * densityValues.get(density))
function weightConversion(weight: number): string {
if (weight > 1000 || weight < -1000) {
return applyUnits(weight/1000, "t")
}
else {
return `${weight.toFixed(2)}kg`
}
function addInventory(newInventory: Inventory) {
ship.addInventory(newInventory);
}
function getInventoryVolume(inventory: InventoryType): number {
return inventorySizes.get(gridSize).get(inventory) * inventoryMultiplier
}
function getThrusterDetails(thruster: Thruster): ThrusterSizeDetails {
return thrusterDetails.get(gridSize).get(thruster.type).sizes.get(thruster.size)
}
function getThrust(thruster: Thruster): number {
let typeDetails = getThrusterTypeDetails(thruster);
let sizeDetails = getThrusterDetails(thruster);
let thrustEfficiency = (typeDetails.vacuumFactor * (1 - atmosphericDensity)) + (typeDetails.atmosphericFactor * atmosphericDensity);
return sizeDetails.thrust * thrustEfficiency;
}
function getThrusterTypeDetails(thruster: Thruster): ThrusterTypeDetails {
return thrusterDetails.get(gridSize).get(thruster.type)
}
function addThruster() {
thrusters.push({ type: newThruster.type, size: newThruster.size});
}
function addInventory() {
inventories.push(newInventory);
}
function localized(key: string): string {
let localizationObject = localization.get(key)
if (typeof localizationObject === "undefined") {
return `missing text: ${key}`
}
else {
let localizedText = localizationObject.get(locale)
if (typeof localizedText === "undefined") {
return localizationObject.get("en-GB")
}
else {
return localizedText
}
}
}
function applyUnits(value: number, unit: string) {
for (const [power, modifier] of [...metricModifiers.entries()]) {
if (value > power) {
if (value / power >= 10000) {
let base = value / power;
let rest = base % 1000;
base = (base - rest) / 1000;
rest = Math.round((rest + Number.EPSILON) * 100) / 100
return `${base}${localized("separator")}${rest}${modifier}${unit}`
}
else {
let rounded = Math.round(((value / power) + Number.EPSILON) * 100) / 100
return `${rounded}${modifier}${unit}`
}
}
}
if (value < 0) {
return `-${applyUnits(value * -1, unit)}`
}
let rounded = Math.round(((value + Number.EPSILON) * 100) / 100)
return `${rounded}${unit}`
function addThruster(newThruster: Thruster) {
ship.addThruster(newThruster);
}
onMount(() => {
if (navigator) {
locale = navigator.language;
i18n.language = navigator.language;
}
if (localStorage.getItem("thrusters") !== null) {
thrusters = JSON.parse(localStorage.getItem("thrusters"))
let ret = getFromLocalStorage("ship");
if (ret.result) {
ship.load(JSON.parse(ret.value));
}
if (localStorage.getItem("inventories") !== null) {
inventories = JSON.parse(localStorage.getItem("inventories"))
ret = getFromLocalStorage("gravity");
if (ret.result) {
gravity = ret.value;
}
if (localStorage.getItem("gridSize") !== null) {
gridSize = localStorage.getItem("gridSize")
ret = getFromLocalStorage("density");
if (ret.result) {
let parsed = JSON.parse(ret.value);
Object.values(CargoMaterial).forEach((cargoMaterial) => {
if (cargoMaterial.name === parsed) {
material = cargoMaterial
}
})
}
if (localStorage.getItem("gravity") !== null) {
gravity = localStorage.getItem("gravity")
ret = getFromLocalStorage("atmosphericDensity");
if (ret.result) {
atmosphericDensity = ret.value;
}
if (localStorage.getItem("density") !== null) {
density = localStorage.getItem("density")
}
if (localStorage.getItem("atmosphericDensity") !== null) {
atmosphericDensity = localStorage.getItem("atmosphericDensity")
}
if (localStorage.getItem("inventoryMultiplier") !== null) {
inventoryMultiplier = localStorage.getItem("inventoryMultiplier")
}
if (localStorage.getItem("newThruster") !== null) {
newThruster = JSON.parse(localStorage.getItem("newThruster"))
}
if (localStorage.getItem("newInventory") !== null) {
newInventory = JSON.parse(localStorage.getItem("newInventory"))
ret = getFromLocalStorage("inventoryMultiplier");
if (ret.result) {
inventoryMultiplier = ret.value;
}
mounted = true;
@ -227,134 +115,91 @@
</script>
<svelte:head>
<title>{localized("title")}</title>
<title>{i18n.localize("title")}</title>
<meta name="robots" content="noindex nofollow" />
</svelte:head>
<div class="gap-4 grid grid-cols-3 h-full">
<Card.Root class="col-span-1">
<div class="gap-4 flex flex-row flex-wrap h-full">
<Card.Root class="flex-1">
<Card.Header>
<Card.Title>{localized("title")}</Card.Title>
<Card.Title>{i18n.localize("title")}</Card.Title>
</Card.Header>
<Card.Content class="flex flex-col gap-3">
<Separator />
<Label for="gravity">{localized("gravity")}</Label>
<Label for="gravity">{i18n.localize("gravity")}</Label>
<Input type="number" step="0.01" id="gravity" bind:value={gravity}/>
<Label for="atmosDensity">{localized("atmosDensity")}</Label>
<Label for="atmosDensity">{i18n.localize("atmosDensity")}</Label>
<Input type="number" step="0.01" id="atmosDensity" bind:value={atmosphericDensity}/>
<Separator />
<Label for="gridSize">{localized("gridSize")}</Label>
<div id="gridSize" class="flex">
{#each Object.values(Grids) as size}
{#if gridSize === size}
<Button variant="primary">{localized(size)}</Button>
<Label for="gridSize">{i18n.localize("gridSize")}</Label>
<div id="gridSize" class="flex gap-2">
{#each Object.values(Grid) as size}
{#if ship.grid === size}
<Button class="bg-primary">{i18n.localize(size)}</Button>
{:else}
<Button variant="secondary" onclick={() => gridSize = size}>{localized(size)}</Button>
<Button variant="secondary" onclick={() => ship.grid = size}>{i18n.localize(size)}</Button>
{/if}
{/each}
</div>
<Separator />
<Label for="density">{localized("density")}</Label>
<div id="density" class="flex">
{#each Object.values(Density) as value}
{#if density === value}
<Button variant="primary">{localized(value)}</Button>
<Label for="density">{i18n.localize("density")}</Label>
<div id="density" class="flex gap-2">
{#each Object.values(CargoMaterial) as value}
{#if material === value}
<Button class="bg-primary">{i18n.localize(value.name)}</Button>
{:else}
<Button variant="secondary" onclick={() => density = value}>{localized(value)}</Button>
<Button variant="secondary" onclick={() => material = value}>{i18n.localize(value.name)}</Button>
{/if}
{/each}
</div>
<Separator />
<p>{localized("liftableVehcileWeight")}: {weightConversion(maxVehicleWeight)}</p>
<p>{localized("liftableWeight")}: {weightConversion(maxWeight)}</p>
<p>{i18n.localize("liftableVehcileWeight")}: {weightConversion(i18n, maxVehicleWeight)}</p>
<p>{i18n.localize("liftableWeight")}: {weightConversion(i18n, maxWeight)}</p>
</Card.Content>
</Card.Root>
<Card.Root class="col-span-1 flex flex-col flex-grow">
<Card.Root class="flex-1 flex flex-col flex-grow">
<Card.Header>
<Card.Title>{localized("thrusterSettings")}</Card.Title>
<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">
<DropdownMenu.Root>
<DropdownMenu.Trigger>{localized(newThruster.type)}</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.RadioGroup bind:value={newThruster.type}>
{#each Object.values(ThrusterType) as thrusterType}
<DropdownMenu.RadioItem value={thrusterType}>{localized(thrusterType)}</DropdownMenu.RadioItem>
{/each}
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu.Root>
<DropdownMenu.Root>
<DropdownMenu.Trigger>{localized(newThruster.size)}</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.RadioGroup bind:value={newThruster.size}>
{#each getThrusterTypeDetails(newThruster).sizes.keys() as thrusterDetails}
<DropdownMenu.RadioItem value={thrusterDetails}>{localized(thrusterDetails)}</DropdownMenu.RadioItem>
{/each}
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu.Root>
<Button variant="secondary" onclick={() => addThruster()}>{localized("addThruster")}</Button>
<NewThruster i18n={i18n} onAddThruster={addThruster} />
</Card.Content>
</Card.Root>
<Card.Root class="flex flex-col h-0 flex-grow">
<Card.Root class="flex flex-col h-60 flex-grow">
<Card.Header>
<Card.Title>{localized("thrusters")} ({applyUnits(totalThrust, "N")})</Card.Title>
<Card.Title>{i18n.localize("thrusters")} ({applyUnits(i18n, ship.getTotalThrust(atmosphericDensity), "N")})</Card.Title>
</Card.Header>
<Card.Content class="overflow-hidden">
<Card.Content class="flex flex-col gap-3 overflow-hidden">
<Separator />
<div class="max-h-full overflow-y-auto">
{#each thrusters as thruster, index}
<p class="p-1">
- {localized(thruster.size)} {localized(thruster.type)} {localized("thruster")}: {applyUnits(getThrust(thruster), "N")}
<Button variant="destructive" onclick={() => thrusters.splice(index, 1)}>X</Button>
</p>
{/each}
</div>
<ThrusterList i18n={i18n} bind:thrusters={ship.thrusters} grid={ship.grid} atmosphere={atmosphericDensity}/>
</Card.Content>
</Card.Root>
</Card.Content>
</Card.Root>
<Card.Root class="col-span-1 flex flex-col flex-grow">
<Card.Root class="flex-1 flex flex-col flex-grow">
<Card.Header>
<Card.Title>{localized("inventorySettings")}</Card.Title>
<Card.Title>{i18n.localize("inventorySettings")}</Card.Title>
</Card.Header>
<Card.Content class="flex flex-col flex-grow gap-3">
<Separator />
<Label for="inventorySize">{localized("inventory")} {localized("multiplier")}</Label>
<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">
<div class="flex gap-2 flex-wrap">
{#each Object.values(InventoryType) as inventory}
{#if newInventory === inventory}
<Button variant="primary">{localized(inventory)}</Button>
{:else}
<Button variant="secondary" onclick={() => newInventory = inventory}>{localized(inventory)}</Button>
{/if}
{/each}
</div>
<Button variant="secondary" onclick={() => addInventory()}>{localized("addInventory")}</Button>
<NewInventory i18n={i18n} grid={ship.grid} onAddInventory={addInventory} />
</Card.Content>
</Card.Root>
<Card.Root class="flex flex-col h-0 flex-grow">
<Card.Root class="flex flex-col h-60 flex-grow">
<Card.Header>
<Card.Title>{localized("inventories")} ({applyUnits(totalVolume, "l")})</Card.Title>
<Card.Title>{i18n.localize("inventories")} ({applyUnits(i18n, ship.getTotalVolume(inventoryMultiplier), "l")})</Card.Title>
</Card.Header>
<Card.Content class="overflow-hidden">
<Card.Content class="flex flex-col gap-3 overflow-hidden">
<Separator />
<div class="max-h-full overflow-y-auto">
{#each inventories as inventory, index}
<p class="p-1">
- {localized(inventory)} {localized("volume")}: {applyUnits(getInventoryVolume(inventory), "l")}
<Button variant="destructive" onclick={() => inventories.splice(index, 1)}>X</Button>
</p>
{/each}
</div>
<InventoryList i18n={i18n} bind:inventories={ship.inventories} grid={ship.grid} multiplier={inventoryMultiplier} />
</Card.Content>
</Card.Root>
</Card.Content>