Merge branch 'salvo-missile-launcher'

This commit is contained in:
Yanick Champoux 2023-05-15 09:54:11 -04:00
commit bb7a2e1992
25 changed files with 585 additions and 204 deletions

View File

@ -8,6 +8,8 @@ releases:
changes: changes:
- desc: add SMRs - desc: add SMRs
type: feat type: feat
- desc: add SMLs
type: feat
- version: 3.1.0 - version: 3.1.0
changes: changes:
- desc: add version and changelog to the about section - desc: add version and changelog to the about section

View File

@ -1,175 +0,0 @@
<nav>
<button class="button is-danger" type="button" on:click={reset}>reset</button>
<div class="spacer" />
<button class="button is-info about" on:click={toggle_notes}>about</button>
<div class="field has-addons">
<p class="control">
<button class="button" on:click={() => set_output(null)}>editor</button>
</p>
<p class="control">
<button class="button" on:click={() => set_output("json")}>json</button>
</p>
<p class="control">
<button class="button" on:click={() => set_output("print")}>print</button>
</p>
</div>
</nav>
{#if show_notes}
<Notes show={show_notes} on:close={toggle_notes} />
{/if}
{#if output === "json"}
<OutputJson ship={$ship} on:close={() => set_output(null)} />
{:else if output === "print"}
<Print ship={$ship} />
{:else}
<main>
<ShipSpecs />
<Propulsion
ftl={$ship.ftl}
engine={$ship.engine}
on:change_ftl={change_ftl}
on:change_engine={change_engine}
/>
<Hull
ship_mass={$ship.general.mass}
{...$ship.structure.hull}
on:change_hull={change_hull}
screens={$ship.structure.screens}
armour={$ship.structure.armour}
on:set_screens={set_screens}
cargo={$ship.cargo}
streamlining={$ship.streamlining}
on:set_cargo={ship_dispatch}
on:ship_change={ship_dispatch}
/>
<Section label="weaponry">
<Firecons
{...$ship.weaponry.firecons}
on:change_firecons={change_firecons}
/>
<ADFC {...$ship.weaponry.adfc} />
<AddWeapon />
{#each weapons as weapon (weapon.id)}
<Weapon {weapon} id={weapon.id} cost={weapon.cost} mass={weapon.mass} />
{/each}
</Section>
<Carrier {...$ship.carrier} />
</main>
<footer>
Written by <a href="https://twitter.com/yenzie">Yanick Champoux</a>. Code
available on
<a href="https://github.com/aotds/aotds-shipyard">Github</a>
</footer>
{/if}
<script>
import { setContext } from "svelte";
import Ribbon from "./Ribbon.svelte";
import shipStore from "../stores/ship";
import OutputJson from "./Output/Json.svelte";
import Print from "./Output/Print/index.svelte";
import ShipSpecs from "./ShipSpecs/index.svelte";
import Notes from "./Notes.svelte";
import ShipItem from "./ShipItem/index.svelte";
import Field from "./Field/index.svelte";
import Hull from "./Hull/index.svelte";
import Firecons from "./Firecons.svelte";
import Propulsion from "./Propulsion/index.svelte";
import Section from "./Section/index.svelte";
import Weapon from "./Weapon/index.svelte";
import Carrier from "./Carrier/index.svelte";
import ADFC from "./Weaponry/ADFC/index.svelte";
import AddWeapon from "./Weaponry/AddWeapon/index.svelte";
const ship = shipStore();
setContext("ship", ship);
let ship_name = $ship.general.name;
const change_name = (event) =>
ship.dispatch(ship.actions.set_name(event.target.value));
const change_mass = ({ target: { value } }) =>
ship.dispatch(ship.actions.set_ship_mass(parseInt(value)));
const add_weapon = () => ship.dispatch.add_weapon();
const change_ftl = ({ detail }) => ship.dispatch.set_ftl(detail);
const change_engine = ({ detail }) => ship.dispatch.set_engine(detail);
const change_hull = ({ detail }) => ship.dispatch.set_hull(detail);
const change_firecons = ({ detail }) => ship.dispatch.set_firecons(detail);
const change_weapon = ({ detail }) => ship.dispatch.set_weapon(detail);
let weapons = [];
$: console.log(weapons);
$: weapons = $ship.weaponry.weapons;
const reset = ship.dispatch.reset;
const set_screens = ({ detail }) => ship.dispatch.set_screens(detail);
const ship_dispatch = ({ detail }) => ship.dispatch(detail);
let show_notes = false;
const toggle_notes = () => (show_notes = !show_notes);
let output = null;
const set_output = (value) => (output = value);
</script>
<style>
main {
display: grid;
grid-template-columns: 1fr 14em 8em;
width: 60em;
margin-left: auto;
margin-right: auto;
}
nav {
grid-column: 1 / span 3 !important;
display: flex;
width: 60em;
margin-left: auto;
margin-right: auto;
margin-bottom: 1em;
}
nav .spacer {
flex: 1;
}
:global(main > *) {
grid-column: 1;
}
input.reset {
grid-column: 2;
}
footer {
width: var(--main-width);
margin: 0 auto;
text-align: right;
}
.about {
margin-right: 2em;
}
</style>

View File

@ -26,7 +26,6 @@
export let { dispatch } = getContext("api"); export let { dispatch } = getContext("api");
$: console.log(type);
$: dispatch.setSquadronType({ type, id }); $: dispatch.setSquadronType({ type, id });
</script> </script>

View File

@ -0,0 +1,40 @@
{#if magazines.length > 0}
<ShipItem {...reqs}>
<div>missile magazines</div>
<div class="magazines">
{#each magazines as magazine (magazine.id)}
<Magazine {...magazine} unused={magazine.launchers.length === 0} />
{/each}
</div>
</ShipItem>
{/if}
<script>
import { getContext } from "svelte";
import ShipItem from "$lib/components/ShipItem.svelte";
import Magazine from "./MissileMagazines/Magazine.svelte";
export let magazines = [];
let reqs = { cost: 0, mass: 0 };
const api = getContext("api");
$: {
reqs = { cost: 0, mass: 0 };
magazines.forEach(({ reqs: r }) => {
reqs.cost += r.cost;
reqs.mass += r.mass;
});
}
const handleMagazine = () => {};
</script>
<style>
.magazines {
display: flex;
margin-left: 2em;
}
</style>

View File

@ -0,0 +1,42 @@
<div class="magazine">
<div class="field label">
<input
type="number"
min={unused ? 0 : 1}
disabled={unused}
bind:value={maxAmmo}
/>
<label class="active">magazine {id}</label>
</div>
<label class="checkbox">
<input type="checkbox" bind:checked={extended} disabled={unused} />
<span>extended range</span>
</label>
</div>
<script>
import { getContext } from "svelte";
export let id = 1;
export let maxAmmo = 0;
export let extended = false;
export let unused = false;
export let api = getContext("api");
$: api?.dispatch?.setMissileMagazine?.(id, maxAmmo, extended);
</script>
<style>
.magazine {
max-width: 20em;
margin-right: 2em;
}
.magazine input {
max-width: 8em;
text-align: center;
}
.field {
margin-bottom: 1em;
}
</style>

View File

@ -3,15 +3,18 @@
<ADFC {...adfc} /> <ADFC {...adfc} />
<MissileMagazines magazines={missileMagazines} />
<AddWeapon /> <AddWeapon />
{#each weapons as weapon (weapon.id)} {#each weapons as weapon (weapon.id)}
<Weapon {...weapon} /> <Weapon {...weapon} nbrMissileMagazines={missileMagazines.length} />
{/each} {/each}
</Section> </Section>
<script> <script>
import { getContext } from "svelte"; import { getContext } from "svelte";
import u from "@yanick/updeep-remeda";
import Section from "$lib/components/Section.svelte"; import Section from "$lib/components/Section.svelte";
@ -21,11 +24,28 @@
import Firecons from "./Weaponry/Firecons.svelte"; import Firecons from "./Weaponry/Firecons.svelte";
import ADFC from "./Weaponry/ADFC.svelte"; import ADFC from "./Weaponry/ADFC.svelte";
import AddWeapon from "./Weaponry/AddWeapon.svelte"; import AddWeapon from "./Weaponry/AddWeapon.svelte";
import MissileMagazines from "./MissileMagazines.svelte";
import Weapon from "./Weaponry/Weapon/index.svelte";
export let firecons = {}; export let firecons = {};
export let adfc = {}; export let adfc = {};
export let missileMagazines = [];
import Weapon from "./Weaponry/Weapon/index.svelte";
export let weapons = []; export let weapons = [];
$: missileMagazines = addLaunchersToMagazines(missileMagazines, weapons);
function addLaunchersToMagazines(magazines, weapons) {
return u.map(magazines, (mag) =>
u(mag, {
launchers: weapons.filter(
u.matches({
specs: {
type: "sml",
missileMagazineId: mag.id,
},
})
),
})
);
}
</script> </script>

View File

@ -54,7 +54,6 @@
$: if (!arc_options[weaponClass].includes(nbrArcs)) { $: if (!arc_options[weaponClass].includes(nbrArcs)) {
nbrArcs = arc_options[weaponClass][0]; nbrArcs = arc_options[weaponClass][0];
console.log({ nbrArcs, label: "in if" });
} }
const broadside = ["FS", "FP", "AP", "AS"]; const broadside = ["FS", "FP", "AP", "AS"];
@ -72,7 +71,6 @@
); );
} }
$: console.log({ newArcs, arcs });
if ( if (
arcs.length !== newArcs.length || arcs.length !== newArcs.length ||
arcs.length !== R.intersection(arcs, newArcs).length arcs.length !== R.intersection(arcs, newArcs).length
@ -83,8 +81,6 @@
$: if (arcs.length !== nbrArcs) setArcs(arcs[0]); $: if (arcs.length !== nbrArcs) setArcs(arcs[0]);
$: console.log("it changed!", arcs);
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let i = 5; let i = 5;

View File

@ -0,0 +1,18 @@
import SML from "./index.svelte";
export default {
title: "Salvo Missile Launcher",
component: SML,
};
export const Primary = {
render: (args) => ({
Component: SML,
props: args,
}),
args: {
availableMissileMagazineIds: [1, 2, 3],
missileMagazineId: 1,
arcs: ["F", "FS", "FP"],
},
};

View File

@ -0,0 +1,8 @@
<Hst.Story title="ShipEdit/Weaponry/Weapons/SML">
<SML />
</Hst.Story>
<script>
export let Hst;
import SML from "./index.svelte";
</script>

View File

@ -0,0 +1,74 @@
<span>salvo missile launcher</span>
<div class="arcs">
<Arcs
size={48}
selected={arcs}
on:clickArc={({ detail }) => setFirstArc(detail)}
/>
</div>
<div class="field label suffix">
<select bind:value={missileMagazineId}>
{#each availableMissileMagazineIds as id}
<option value={id}>{id}</option>
{/each}
</select>
<label class:active={true}>magazine</label>
</div>
<script lang="ts">
import u from "@yanick/updeep-remeda";
import { createEventDispatcher } from "svelte";
import memoize from "memoize-one";
import Arcs from "../Arcs.svelte";
import {
weaponTypes,
arcs as allArcs,
} from "$lib/store/ship/weaponry/rules.ts";
export let arcs = ["F", "FS", "FP"];
export let missileMagazineId;
export let nbrMissileMagazines = 1;
$: availableMissileMagazineIds = Array.from({ length: nbrMissileMagazines })
.fill(1)
.map((_, i) => i + 1);
const nbrArcs = 3;
let firstArc = allArcs[0];
const setFirstArc = (a) => (firstArc = a);
$: arcs = setArcs(firstArc, nbrArcs);
function setArcs(firstArc, nbrArcs) {
let first_index = allArcs.findIndex((arc) => arc === firstArc);
if (first_index === -1) first_index = 0;
return Array.from({ length: nbrArcs }).map(
(_dummy, i) => allArcs[(first_index + i) % allArcs.length]
);
}
const dispatch = createEventDispatcher();
const memoChange = memoize((missileMagazineId, ...arcs) =>
dispatch("change", {
missileMagazineId,
arcs,
})
);
$: memoChange(missileMagazineId, ...arcs);
</script>
<style>
.arcs {
margin-top: 0.5rem;
}
label span {
padding-left: 1em;
}
</style>

View File

@ -0,0 +1,79 @@
<span>salvo missile launcher</span>
<div class="arcs">
<Arcs
size={48}
selected={arcs}
on:clickArc={({ detail }) => setFirstArc(detail)}
/>
</div>
<div class="field label suffix">
<select bind:value={missileMagazineId}>
{#each availableMissileMagazineIds as id}
<option value={id}>{id}</option>
{/each}
</select>
<label class:active={true}>magazine</label>
</div>
<script lang="ts">
import u from "@yanick/updeep-remeda";
import { createEventDispatcher } from "svelte";
import memoize from "memoize-one";
import Arcs from "../Arcs.svelte";
import {
weaponTypes,
arcs as allArcs,
} from "$lib/store/ship/weaponry/rules.ts";
export let arcs = ["F", "FS", "FP"];
<<<<<<< HEAD
export let missileMagazineId;
=======
export let availableMissileMagazineIds = [1];
export let missileMagazineId = 1;
>>>>>>> a5a09f9 (storybook is back)
export let nbrMissileMagazines = 1;
$: availableMissileMagazineIds = Array.from({ length: nbrMissileMagazines })
.fill(1)
.map((_, i) => i + 1);
const nbrArcs = 3;
let firstArc = allArcs[0];
const setFirstArc = (a) => (firstArc = a);
$: arcs = setArcs(firstArc, nbrArcs);
function setArcs(firstArc, nbrArcs) {
let first_index = allArcs.findIndex((arc) => arc === firstArc);
if (first_index === -1) first_index = 0;
return Array.from({ length: nbrArcs }).map(
(_dummy, i) => allArcs[(first_index + i) % allArcs.length]
);
}
const dispatch = createEventDispatcher();
const memoChange = memoize((missileMagazineId, ...arcs) =>
dispatch("change", {
missileMagazineId,
arcs,
})
);
$: memoChange(missileMagazineId, ...arcs);
</script>
<style>
.arcs {
margin-top: 0.5rem;
}
label span {
padding-left: 1em;
}
</style>

View File

@ -2,7 +2,12 @@
<div class="weapon_row"> <div class="weapon_row">
<a on:click={remove}><i>Delete</i> </a> <a on:click={remove}><i>Delete</i> </a>
<svelte:component this={component[type]} {...specs} on:change={update} /> <svelte:component
this={component[type]}
{...specs}
{nbrMissileMagazines}
on:change={update}
/>
</div> </div>
</ShipItem> </ShipItem>
@ -21,6 +26,7 @@
import Torpedo from "./Torpedo/index.svelte"; import Torpedo from "./Torpedo/index.svelte";
import Missile from "./HeavyMissile/index.svelte"; import Missile from "./HeavyMissile/index.svelte";
import SalvoMissileRack from "./SalvoMissileRack.svelte"; import SalvoMissileRack from "./SalvoMissileRack.svelte";
import SalvoMissileLauncher from "./SML/index.svelte";
const component = { const component = {
beam: Beam, beam: Beam,
@ -32,11 +38,13 @@
torpedo: Torpedo, torpedo: Torpedo,
heavyMissile: Missile, heavyMissile: Missile,
smr: SalvoMissileRack, smr: SalvoMissileRack,
sml: SalvoMissileLauncher,
}; };
export let reqs = {}; export let reqs = {};
export let specs = {}; export let specs = {};
export let id; export let id;
export let nbrMissileMagazines = 0;
const api = getContext("api"); const api = getContext("api");
@ -45,7 +53,6 @@
const remove = () => api?.dispatch?.removeWeapon?.(id); const remove = () => api?.dispatch?.removeWeapon?.(id);
const update = ({ detail }) => { const update = ({ detail }) => {
console.log({ id, type });
api?.dispatch?.setWeapon?.(id, { api?.dispatch?.setWeapon?.(id, {
type, type,
...detail, ...detail,

View File

@ -21,7 +21,6 @@ import { readable, get, derived } from "svelte/store";
const store = weaponryDux.createStore(); const store = weaponryDux.createStore();
const state = readable(store.getState(), (set) => { const state = readable(store.getState(), (set) => {
store.subscribe(() => { store.subscribe(() => {
console.log(store.getState());
set(store.getState()); set(store.getState());
}); });
}); });

View File

@ -46,8 +46,6 @@
let nbr_arcs = 6; let nbr_arcs = 6;
$: nbr_arcs = arc_options[weapon_class][0]; $: nbr_arcs = arc_options[weapon_class][0];
$: console.log({ arcs, nbr_arcs });
$: if (arcs.length !== nbr_arcs) { $: if (arcs.length !== nbr_arcs) {
if (nbr_arcs === "broadside") { if (nbr_arcs === "broadside") {
arcs = all_arcs.filter((arc) => arc.length === 1); arcs = all_arcs.filter((arc) => arc.length === 1);

View File

@ -97,8 +97,6 @@
arcs.forEach((arc) => (new_arcs[arc] = false)); arcs.forEach((arc) => (new_arcs[arc] = false));
_.range(nbr_arcs).forEach((i) => { _.range(nbr_arcs).forEach((i) => {
console.log(first_index);
console.log(selected_arc);
new_arcs[arcs[first_index]] = true; new_arcs[arcs[first_index]] = true;
first_index = (first_index + 1) % arcs.length; first_index = (first_index + 1) % arcs.length;
}); });

View File

@ -101,6 +101,7 @@ exports[`state has the expected shape 1`] = `
}, },
"stations": 0, "stations": 0,
}, },
"missileMagazines": [],
"weapons": [], "weapons": [],
}, },
} }

View File

@ -17,6 +17,7 @@ import { armorDux } from "./ship/structure/armor";
import { fireconsDux } from "./ship/weaponry/firecons"; import { fireconsDux } from "./ship/weaponry/firecons";
import { adfcDux } from "./ship/weaponry/adfc"; import { adfcDux } from "./ship/weaponry/adfc";
import { weaponsDux } from "./ship/weaponry/weapons"; import { weaponsDux } from "./ship/weaponry/weapons";
import { weaponryDux } from "./ship/weaponry";
if (typeof process !== "undefined") { if (typeof process !== "undefined") {
process.env.UPDEEP_MODE = "dangerously_never_freeze"; process.env.UPDEEP_MODE = "dangerously_never_freeze";
@ -42,15 +43,6 @@ const propulsion = new Updux({
}, },
}); });
const weaponry = new Updux({
initialState: {},
subduxes: {
adfc: adfcDux,
firecons: fireconsDux,
weapons: weaponsDux,
},
});
const restore = createPayloadAction<typeof shipDux.initialState>("restore"); const restore = createPayloadAction<typeof shipDux.initialState>("restore");
const importShip = const importShip =
createPayloadAction<typeof shipDux.initialState>("importShip"); createPayloadAction<typeof shipDux.initialState>("importShip");
@ -68,7 +60,7 @@ const shipDux = new Updux({
structure, structure,
propulsion, propulsion,
carrier: carrierDux, carrier: carrierDux,
weaponry, weaponry: weaponryDux,
}, },
}); });

View File

@ -0,0 +1,50 @@
import { weaponryDux } from "./index.ts";
test("sml and magazine", () => {
const store = weaponryDux.createStore();
expect(store.getState().missileMagazines).toHaveLength(0);
store.dispatch.addWeapon("sml");
store.dispatch.addWeapon("sml");
expect(store.getState().missileMagazines).toHaveLength(2);
expect(store.getState().missileMagazines[0]).toMatchObject({
extended: false,
maxAmmo: 1,
});
store.dispatch.setMissileMagazine(1, 3, true);
expect(store.getState().missileMagazines[0]).toMatchObject({
extended: true,
maxAmmo: 3,
});
store.dispatch.addWeapon("sml");
expect(store.getState().missileMagazines).toHaveLength(3);
expect(store.getState().missileMagazines[2]).toHaveProperty("id", 3);
// they all get assigned '1' at birth
expect(store.getState().weapons[2].specs).toHaveProperty(
"missileMagazineId",
1
);
store.dispatch.setWeapon(3, {
missileMagazineId: 3,
arcs: ["F", "FS", "FP"],
});
expect(store.getState().weapons[2].specs).toHaveProperty(
"missileMagazineId",
3
);
store.dispatch.removeWeapon(store.getState().weapons[1].id);
expect(store.getState().missileMagazines).toHaveLength(2);
expect(store.getState().weapons[1].specs.missileMagazineId).toEqual(2);
expect(store.getState().missileMagazines[1].id).toEqual(2);
});

View File

@ -0,0 +1,116 @@
import Updux, { createPayloadAction } from "updux";
import { adfcDux } from "./adfc";
import { fireconsDux } from "./firecons";
import { weaponsDux } from "./weapons";
import type { Reqs } from "$lib/shipDux/reqs";
import u from "@yanick/updeep-remeda";
import * as R from "remeda";
if (typeof process !== "undefined") {
process.env.UPDEEP_MODE = "dangerously_never_freeze";
}
type MissileMagazine = {
id: number;
maxAmmo: number;
extended: boolean;
};
const setMissileMagazine = createPayloadAction(
"setMissileMagazine",
(id, maxAmmo, extended = undefined) => ({ id, maxAmmo, extended })
);
const moveMissileMagazine = createPayloadAction(
"moveMissileMagazine",
(from, to) => ({
from,
to,
})
);
const removeMissileMagazine = createPayloadAction<number>(
"removeMissileMagazine"
);
const magazinesDux = new Updux({
actions: { setMissileMagazine, removeMissileMagazine, moveMissileMagazine },
initialState: [] as MissileMagazine[],
});
magazinesDux.addMutation(weaponsDux.actions.addWeapon, (payload) => (state) => {
if (payload !== "sml") return state;
state.push({
id: state.length + 1,
extended: false,
maxAmmo: 1,
reqs: magazineReqs({ extended: false, maxAmmo: 1 }),
});
});
magazinesDux.addMutation(setMissileMagazine, (payload) =>
u.map(
u.if(u.matches({ id: payload.id }), (state) => {
state = u(state, payload);
return u(state, { reqs: magazineReqs(state) });
})
)
);
magazinesDux.addMutation(moveMissileMagazine, ({ from, to }) =>
u.map(u.if(u.matches({ id: from }), { id: to }))
);
magazinesDux.addMutation(removeMissileMagazine, (id) =>
u.reject(u.matches({ id }))
);
magazinesDux.addReaction((api) => (store) => {
store
.map(R.prop("id"))
.filter(({ id }, i) => id !== i + 1)
.forEach((id, i) => api.dispatch.moveMissileMagazine(id, i + 1));
});
function magazineReqs(magazine: MissileMagazine): Reqs {
let mass = magazine.maxAmmo * (magazine.extended ? 3 : 2);
return { mass, cost: 3 * mass };
}
export const weaponryDux = new Updux({
initialState: {},
subduxes: {
adfc: adfcDux,
firecons: fireconsDux,
weapons: weaponsDux,
missileMagazines: magazinesDux,
},
});
weaponryDux.addMutation(moveMissileMagazine, ({ from, to }) =>
u({
weapons: u.map(
u.if(u.matches({ specs: { missileMagazineId: from } }), {
specs: { missileMagazineId: to },
})
),
})
);
weaponryDux.addReaction((api) => (state) => {
const smls = state.weapons.filter(u.matches({ specs: { type: "sml" } }));
const usedMagazines = smls.map(
({ specs: { missileMagazineId } }) => missileMagazineId
);
const unusedMags = state.missileMagazines
.map(R.prop("id"))
.filter((id) => !usedMagazines.includes(id));
unusedMags.forEach((id) => api.dispatch.setMissileMagazine(id, 0));
if (smls.length >= state.missileMagazines.length) return;
api.dispatch.removeMissileMagazine(unusedMags[0]);
});

View File

@ -40,6 +40,18 @@ type SalvoMissileRack = {
extended: boolean; extended: boolean;
}; };
type MissileMagazine = {
id: number;
maxAmmo: number;
extended: boolean;
};
type SalvoMissileLauncher = {
type: "salvoMissileLauncher";
arcs: Arc[];
missileMagazineId: number;
};
type Graser = { type Graser = {
type: "graser"; type: "graser";
weaponClass: 1 | 2 | 3; weaponClass: 1 | 2 | 3;
@ -60,7 +72,8 @@ export type Weapon =
| Needle | Needle
| Graser | Graser
| Torpedo | Torpedo
| HeavyMissile; | HeavyMissile
| SalvoMissileLauncher;
export const weaponTypes = [ export const weaponTypes = [
{ {
@ -155,6 +168,16 @@ export const weaponTypes = [
type: "smr", type: "smr",
}, },
}, },
{
name: "salvo missile launcher",
type: "sml",
reqs: { cost: 9, mass: 3 },
initial: {
arcs: ["FP", "F", "FS"],
type: "sml",
missileMagazineId: 1,
},
},
]; ];
export function weaponReqs(weapon): Reqs { export function weaponReqs(weapon): Reqs {

View File

@ -0,0 +1,16 @@
import SML from "./index.svelte";
export default {
title: "PrintShip/Weapons/SML",
component: SML,
tags: ["autodocs"],
};
export const Primary = {
args: {
magazine: {
maxAmmo: 3,
},
launchers: [{ arcs: ["F"] }, { arcs: ["FS"] }],
},
};

View File

@ -0,0 +1,59 @@
<div class="sml">
<div class="launchers">
{#each launchers as launcher, i (i)}
<div>
<SMR {...launcher} />
<div class="line">&nbsp;</div>
</div>
{/each}
</div>
<div class="magazine">
{#each Array.from({ length: magazine.maxAmmo }).fill(1) as a, i (i)}
<img src="/icons/missile.svg" width="18" />
{/each}
</div>
<div>
{#if magazine.extended}
<div>extended range</div>
{/if}
</div>
</div>
<script>
import SMR from "../SMR/index.svelte";
export let launchers = [];
/** the missile magazine feeding the launchers */
export let magazine = {};
</script>
<style>
.sml {
display: flex;
flex-direction: column;
align-items: center;
}
.launchers {
display: flex;
}
.launchers > div {
display: flex;
flex-direction: column;
align-items: center;
}
.line {
width: 0.2em;
background-color: black;
top: -0.5em;
}
.magazine {
border: 0.2em black solid;
border-radius: 0.5em;
padding: 0.3em;
text-align: center;
width: 100%;
display: flex;
justify-content: space-evenly;
}
</style>

View File

@ -7,6 +7,7 @@ import Graser from "./Graser/index.svelte";
import Torpedo from "./Torpedo/index.svelte"; import Torpedo from "./Torpedo/index.svelte";
import HeavyMissile from "./HeavyMissile/index.svelte"; import HeavyMissile from "./HeavyMissile/index.svelte";
import SalvoMissileRack from "./SMR/index.svelte"; import SalvoMissileRack from "./SMR/index.svelte";
import SalvoMissileLauncher from "./SML/index.svelte";
export default { export default {
torpedo: Torpedo, torpedo: Torpedo,
@ -18,4 +19,5 @@ export default {
needle: Needlebeam, needle: Needlebeam,
heavyMissile: HeavyMissile, heavyMissile: HeavyMissile,
smr: SalvoMissileRack, smr: SalvoMissileRack,
sml: SalvoMissileLauncher,
}; };

View File

@ -15,6 +15,15 @@
<HeavyMissiles {heavyMissiles} /> <HeavyMissiles {heavyMissiles} />
</div> </div>
<div class="weapon-group">
{#each Object.keys(smls) as magId (magId)}
<SML
launchers={smls[magId].map(R.prop("specs"))}
magazine={magazines.find(({ id }) => id == magId)}
/>
{/each}
</div>
<Beams {beams} /> <Beams {beams} />
<Weapons {weapons} /> <Weapons {weapons} />
@ -61,6 +70,8 @@
import Beams from "./Weapons/Beams.svelte"; import Beams from "./Weapons/Beams.svelte";
import HeavyMissiles from "./Weapons/HeavyMissiles.svelte"; import HeavyMissiles from "./Weapons/HeavyMissiles.svelte";
import SalvoMissileRack from "./Weapons/SMR/index.svelte"; import SalvoMissileRack from "./Weapons/SMR/index.svelte";
import SML from "./Weapons/SML/index.svelte";
import * as R from "remeda";
export let identification = {}; export let identification = {};
export let propulsion = {}; export let propulsion = {};
@ -76,11 +87,13 @@
weapons, weapons,
u.matches({ u.matches({
specs: { specs: {
type: (t) => ["smr", "pds", "beam", "heavyMissile"].includes(t), type: (t) => ["sml", "smr", "pds", "beam", "heavyMissile"].includes(t),
}, },
}) })
); );
$: magazines = weaponry.missileMagazines;
$: pds = (weaponry?.weapons ?? []).filter( $: pds = (weaponry?.weapons ?? []).filter(
u.matches({ specs: { type: "pds" } }) u.matches({ specs: { type: "pds" } })
); );
@ -93,6 +106,10 @@
$: smrs = (weaponry?.weapons ?? []).filter( $: smrs = (weaponry?.weapons ?? []).filter(
u.matches({ specs: { type: "smr" } }) u.matches({ specs: { type: "smr" } })
); );
$: smls = R.groupBy(
(weaponry?.weapons ?? []).filter(u.matches({ specs: { type: "sml" } })),
({ specs: { missileMagazineId } }) => missileMagazineId
);
</script> </script>
<style> <style>

View File

@ -7,7 +7,7 @@ import git from "git-describe";
/** @type {import('vite').UserConfig} */ /** @type {import('vite').UserConfig} */
const config = { const config = {
plugins: [sveltekit()], plugins: [sveltekit()],
// publicDir: "./static", publicDir: "./static",
ssr: {}, ssr: {},
optimizeDeps: {}, optimizeDeps: {},
define: { define: {