diff --git a/CHANGELOG.yml b/CHANGELOG.yml
index 754345c..4c5a36f 100644
--- a/CHANGELOG.yml
+++ b/CHANGELOG.yml
@@ -8,6 +8,8 @@ releases:
changes:
- desc: add SMRs
type: feat
+ - desc: add SMLs
+ type: feat
- version: 3.1.0
changes:
- desc: add version and changelog to the about section
diff --git a/src/lib/components/ShipEdit/MissileMagazines.svelte b/src/lib/components/ShipEdit/MissileMagazines.svelte
new file mode 100644
index 0000000..0a78e7b
--- /dev/null
+++ b/src/lib/components/ShipEdit/MissileMagazines.svelte
@@ -0,0 +1,40 @@
+{#if magazines.length > 0}
+
+ missile magazines
+
+ {#each magazines as magazine (magazine.id)}
+
+ {/each}
+
+
+{/if}
+
+
+
+
diff --git a/src/lib/components/ShipEdit/MissileMagazines/Magazine.svelte b/src/lib/components/ShipEdit/MissileMagazines/Magazine.svelte
new file mode 100644
index 0000000..33cab17
--- /dev/null
+++ b/src/lib/components/ShipEdit/MissileMagazines/Magazine.svelte
@@ -0,0 +1,42 @@
+
+
+
+
+
diff --git a/src/lib/components/ShipEdit/Weaponry.svelte b/src/lib/components/ShipEdit/Weaponry.svelte
index 7e3dec6..5d7431e 100644
--- a/src/lib/components/ShipEdit/Weaponry.svelte
+++ b/src/lib/components/ShipEdit/Weaponry.svelte
@@ -3,15 +3,18 @@
+
+
{#each weapons as weapon (weapon.id)}
-
+
{/each}
diff --git a/src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.stories.js b/src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.stories.js
new file mode 100644
index 0000000..784570d
--- /dev/null
+++ b/src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.stories.js
@@ -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"],
+ },
+};
diff --git a/src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.story.svelte b/src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.story.svelte
new file mode 100644
index 0000000..b5e96b5
--- /dev/null
+++ b/src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.story.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.svelte b/src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.svelte
new file mode 100644
index 0000000..91c2047
--- /dev/null
+++ b/src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.svelte
@@ -0,0 +1,74 @@
+salvo missile launcher
+
+
+
setFirstArc(detail)}
+ />
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.svelte.orig b/src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.svelte.orig
new file mode 100644
index 0000000..afe76ff
--- /dev/null
+++ b/src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.svelte.orig
@@ -0,0 +1,79 @@
+salvo missile launcher
+
+
+
setFirstArc(detail)}
+ />
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/components/ShipEdit/Weaponry/Weapon/index.svelte b/src/lib/components/ShipEdit/Weaponry/Weapon/index.svelte
index c1b2815..f2647b1 100644
--- a/src/lib/components/ShipEdit/Weaponry/Weapon/index.svelte
+++ b/src/lib/components/ShipEdit/Weaponry/Weapon/index.svelte
@@ -2,7 +2,12 @@
@@ -21,6 +26,7 @@
import Torpedo from "./Torpedo/index.svelte";
import Missile from "./HeavyMissile/index.svelte";
import SalvoMissileRack from "./SalvoMissileRack.svelte";
+ import SalvoMissileLauncher from "./SML/index.svelte";
const component = {
beam: Beam,
@@ -32,11 +38,13 @@
torpedo: Torpedo,
heavyMissile: Missile,
smr: SalvoMissileRack,
+ sml: SalvoMissileLauncher,
};
export let reqs = {};
export let specs = {};
export let id;
+ export let nbrMissileMagazines = 0;
const api = getContext("api");
diff --git a/src/lib/store/ship.ts b/src/lib/store/ship.ts
index b7ba86c..70d9980 100644
--- a/src/lib/store/ship.ts
+++ b/src/lib/store/ship.ts
@@ -17,6 +17,7 @@ import { armorDux } from "./ship/structure/armor";
import { fireconsDux } from "./ship/weaponry/firecons";
import { adfcDux } from "./ship/weaponry/adfc";
import { weaponsDux } from "./ship/weaponry/weapons";
+import { weaponryDux } from "./ship/weaponry";
if (typeof process !== "undefined") {
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("restore");
const importShip =
createPayloadAction("importShip");
@@ -68,7 +60,7 @@ const shipDux = new Updux({
structure,
propulsion,
carrier: carrierDux,
- weaponry,
+ weaponry: weaponryDux,
},
});
diff --git a/src/lib/store/ship/weaponry/index.test.ts b/src/lib/store/ship/weaponry/index.test.ts
new file mode 100644
index 0000000..97a8df1
--- /dev/null
+++ b/src/lib/store/ship/weaponry/index.test.ts
@@ -0,0 +1,52 @@
+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);
+
+ console.log(store.getState());
+
+ expect(store.getState().weapons[1].specs.missileMagazineId).toEqual(2);
+ expect(store.getState().missileMagazines[1].id).toEqual(2);
+});
diff --git a/src/lib/store/ship/weaponry/index.ts b/src/lib/store/ship/weaponry/index.ts
new file mode 100644
index 0000000..378f4da
--- /dev/null
+++ b/src/lib/store/ship/weaponry/index.ts
@@ -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(
+ "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]);
+});
diff --git a/src/lib/store/ship/weaponry/rules.ts b/src/lib/store/ship/weaponry/rules.ts
index e4184c4..707c448 100644
--- a/src/lib/store/ship/weaponry/rules.ts
+++ b/src/lib/store/ship/weaponry/rules.ts
@@ -40,6 +40,18 @@ type SalvoMissileRack = {
extended: boolean;
};
+type MissileMagazine = {
+ id: number;
+ maxAmmo: number;
+ extended: boolean;
+};
+
+type SalvoMissileLauncher = {
+ type: "salvoMissileLauncher";
+ arcs: Arc[];
+ missileMagazineId: number;
+};
+
type Graser = {
type: "graser";
weaponClass: 1 | 2 | 3;
@@ -60,7 +72,8 @@ export type Weapon =
| Needle
| Graser
| Torpedo
- | HeavyMissile;
+ | HeavyMissile
+ | SalvoMissileLauncher;
export const weaponTypes = [
{
@@ -155,6 +168,16 @@ export const weaponTypes = [
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 {
diff --git a/src/routes/(editor)/export/print/PrintShip/Weapons/SML/index.stories.js b/src/routes/(editor)/export/print/PrintShip/Weapons/SML/index.stories.js
new file mode 100644
index 0000000..6be7cb1
--- /dev/null
+++ b/src/routes/(editor)/export/print/PrintShip/Weapons/SML/index.stories.js
@@ -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"] }],
+ },
+};
diff --git a/src/routes/(editor)/export/print/PrintShip/Weapons/SML/index.svelte b/src/routes/(editor)/export/print/PrintShip/Weapons/SML/index.svelte
new file mode 100644
index 0000000..869ecac
--- /dev/null
+++ b/src/routes/(editor)/export/print/PrintShip/Weapons/SML/index.svelte
@@ -0,0 +1,59 @@
+
+
+ {#each launchers as launcher, i (i)}
+
+ {/each}
+
+
+ {#each Array.from({ length: magazine.maxAmmo }).fill(1) as a, i (i)}
+
+ {/each}
+
+
+ {#if magazine.extended}
+
extended range
+ {/if}
+
+
+
+
+
+
diff --git a/src/routes/(editor)/export/print/PrintShip/Weapons/printComps.js b/src/routes/(editor)/export/print/PrintShip/Weapons/printComps.js
index 516a706..7b0c2a3 100644
--- a/src/routes/(editor)/export/print/PrintShip/Weapons/printComps.js
+++ b/src/routes/(editor)/export/print/PrintShip/Weapons/printComps.js
@@ -7,6 +7,7 @@ import Graser from "./Graser/index.svelte";
import Torpedo from "./Torpedo/index.svelte";
import HeavyMissile from "./HeavyMissile/index.svelte";
import SalvoMissileRack from "./SMR/index.svelte";
+import SalvoMissileLauncher from "./SML/index.svelte";
export default {
torpedo: Torpedo,
@@ -18,4 +19,5 @@ export default {
needle: Needlebeam,
heavyMissile: HeavyMissile,
smr: SalvoMissileRack,
+ sml: SalvoMissileLauncher,
};
diff --git a/src/routes/(editor)/export/print/PrintShip/index.svelte b/src/routes/(editor)/export/print/PrintShip/index.svelte
index 2562d1a..47abbde 100644
--- a/src/routes/(editor)/export/print/PrintShip/index.svelte
+++ b/src/routes/(editor)/export/print/PrintShip/index.svelte
@@ -15,6 +15,15 @@
+
+ {#each Object.keys(smls) as magId (magId)}
+ id == magId)}
+ />
+ {/each}
+
+
@@ -61,6 +70,8 @@
import Beams from "./Weapons/Beams.svelte";
import HeavyMissiles from "./Weapons/HeavyMissiles.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 propulsion = {};
@@ -76,11 +87,14 @@
weapons,
u.matches({
specs: {
- type: (t) => ["smr", "pds", "beam", "heavyMissile"].includes(t),
+ type: (t) => ["sml", "smr", "pds", "beam", "heavyMissile"].includes(t),
},
})
);
+ $: magazines = weaponry.missileMagazines;
+ $: console.log({ magazines });
+
$: pds = (weaponry?.weapons ?? []).filter(
u.matches({ specs: { type: "pds" } })
);
@@ -93,6 +107,14 @@
$: smrs = (weaponry?.weapons ?? []).filter(
u.matches({ specs: { type: "smr" } })
);
+ $: console.log(
+ (weaponry?.weapons ?? []).filter(u.matches({ specs: { type: "sml" } }))
+ );
+ $: smls = R.groupBy(
+ (weaponry?.weapons ?? []).filter(u.matches({ specs: { type: "sml" } })),
+ ({ specs: { missileMagazineId } }) => missileMagazineId
+ );
+ $: console.log({ smls });