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 @@
Delete - +
@@ -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 });