Merge branch 'salvo-missile-launcher'
This commit is contained in:
commit
bb7a2e1992
@ -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
|
||||
|
@ -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>
|
@ -26,7 +26,6 @@
|
||||
|
||||
export let { dispatch } = getContext("api");
|
||||
|
||||
$: console.log(type);
|
||||
$: dispatch.setSquadronType({ type, id });
|
||||
</script>
|
||||
|
||||
|
40
src/lib/components/ShipEdit/MissileMagazines.svelte
Normal file
40
src/lib/components/ShipEdit/MissileMagazines.svelte
Normal 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>
|
42
src/lib/components/ShipEdit/MissileMagazines/Magazine.svelte
Normal file
42
src/lib/components/ShipEdit/MissileMagazines/Magazine.svelte
Normal 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>
|
@ -3,15 +3,18 @@
|
||||
|
||||
<ADFC {...adfc} />
|
||||
|
||||
<MissileMagazines magazines={missileMagazines} />
|
||||
|
||||
<AddWeapon />
|
||||
|
||||
{#each weapons as weapon (weapon.id)}
|
||||
<Weapon {...weapon} />
|
||||
<Weapon {...weapon} nbrMissileMagazines={missileMagazines.length} />
|
||||
{/each}
|
||||
</Section>
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
import u from "@yanick/updeep-remeda";
|
||||
|
||||
import Section from "$lib/components/Section.svelte";
|
||||
|
||||
@ -21,11 +24,28 @@
|
||||
import Firecons from "./Weaponry/Firecons.svelte";
|
||||
import ADFC from "./Weaponry/ADFC.svelte";
|
||||
import AddWeapon from "./Weaponry/AddWeapon.svelte";
|
||||
import MissileMagazines from "./MissileMagazines.svelte";
|
||||
import Weapon from "./Weaponry/Weapon/index.svelte";
|
||||
|
||||
export let firecons = {};
|
||||
export let adfc = {};
|
||||
|
||||
import Weapon from "./Weaponry/Weapon/index.svelte";
|
||||
|
||||
export let missileMagazines = [];
|
||||
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>
|
||||
|
@ -54,7 +54,6 @@
|
||||
|
||||
$: if (!arc_options[weaponClass].includes(nbrArcs)) {
|
||||
nbrArcs = arc_options[weaponClass][0];
|
||||
console.log({ nbrArcs, label: "in if" });
|
||||
}
|
||||
|
||||
const broadside = ["FS", "FP", "AP", "AS"];
|
||||
@ -72,7 +71,6 @@
|
||||
);
|
||||
}
|
||||
|
||||
$: console.log({ newArcs, arcs });
|
||||
if (
|
||||
arcs.length !== newArcs.length ||
|
||||
arcs.length !== R.intersection(arcs, newArcs).length
|
||||
@ -83,8 +81,6 @@
|
||||
|
||||
$: if (arcs.length !== nbrArcs) setArcs(arcs[0]);
|
||||
|
||||
$: console.log("it changed!", arcs);
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let i = 5;
|
||||
|
@ -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"],
|
||||
},
|
||||
};
|
@ -0,0 +1,8 @@
|
||||
<Hst.Story title="ShipEdit/Weaponry/Weapons/SML">
|
||||
<SML />
|
||||
</Hst.Story>
|
||||
|
||||
<script>
|
||||
export let Hst;
|
||||
import SML from "./index.svelte";
|
||||
</script>
|
74
src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.svelte
Normal file
74
src/lib/components/ShipEdit/Weaponry/Weapon/SML/index.svelte
Normal 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>
|
@ -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>
|
@ -2,7 +2,12 @@
|
||||
<div class="weapon_row">
|
||||
<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>
|
||||
</ShipItem>
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -45,7 +53,6 @@
|
||||
const remove = () => api?.dispatch?.removeWeapon?.(id);
|
||||
|
||||
const update = ({ detail }) => {
|
||||
console.log({ id, type });
|
||||
api?.dispatch?.setWeapon?.(id, {
|
||||
type,
|
||||
...detail,
|
||||
|
@ -21,7 +21,6 @@ import { readable, get, derived } from "svelte/store";
|
||||
const store = weaponryDux.createStore();
|
||||
const state = readable(store.getState(), (set) => {
|
||||
store.subscribe(() => {
|
||||
console.log(store.getState());
|
||||
set(store.getState());
|
||||
});
|
||||
});
|
||||
|
@ -46,8 +46,6 @@
|
||||
let nbr_arcs = 6;
|
||||
$: nbr_arcs = arc_options[weapon_class][0];
|
||||
|
||||
$: console.log({ arcs, nbr_arcs });
|
||||
|
||||
$: if (arcs.length !== nbr_arcs) {
|
||||
if (nbr_arcs === "broadside") {
|
||||
arcs = all_arcs.filter((arc) => arc.length === 1);
|
||||
|
@ -97,8 +97,6 @@
|
||||
arcs.forEach((arc) => (new_arcs[arc] = false));
|
||||
|
||||
_.range(nbr_arcs).forEach((i) => {
|
||||
console.log(first_index);
|
||||
console.log(selected_arc);
|
||||
new_arcs[arcs[first_index]] = true;
|
||||
first_index = (first_index + 1) % arcs.length;
|
||||
});
|
||||
|
@ -101,6 +101,7 @@ exports[`state has the expected shape 1`] = `
|
||||
},
|
||||
"stations": 0,
|
||||
},
|
||||
"missileMagazines": [],
|
||||
"weapons": [],
|
||||
},
|
||||
}
|
||||
|
@ -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<typeof shipDux.initialState>("restore");
|
||||
const importShip =
|
||||
createPayloadAction<typeof shipDux.initialState>("importShip");
|
||||
@ -68,7 +60,7 @@ const shipDux = new Updux({
|
||||
structure,
|
||||
propulsion,
|
||||
carrier: carrierDux,
|
||||
weaponry,
|
||||
weaponry: weaponryDux,
|
||||
},
|
||||
});
|
||||
|
||||
|
50
src/lib/store/ship/weaponry/index.test.ts
Normal file
50
src/lib/store/ship/weaponry/index.test.ts
Normal 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);
|
||||
});
|
116
src/lib/store/ship/weaponry/index.ts
Normal file
116
src/lib/store/ship/weaponry/index.ts
Normal 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]);
|
||||
});
|
@ -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 {
|
||||
|
@ -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"] }],
|
||||
},
|
||||
};
|
@ -0,0 +1,59 @@
|
||||
<div class="sml">
|
||||
<div class="launchers">
|
||||
{#each launchers as launcher, i (i)}
|
||||
<div>
|
||||
<SMR {...launcher} />
|
||||
<div class="line"> </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>
|
@ -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,
|
||||
};
|
||||
|
@ -15,6 +15,15 @@
|
||||
<HeavyMissiles {heavyMissiles} />
|
||||
</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} />
|
||||
|
||||
<Weapons {weapons} />
|
||||
@ -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,13 @@
|
||||
weapons,
|
||||
u.matches({
|
||||
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(
|
||||
u.matches({ specs: { type: "pds" } })
|
||||
);
|
||||
@ -93,6 +106,10 @@
|
||||
$: smrs = (weaponry?.weapons ?? []).filter(
|
||||
u.matches({ specs: { type: "smr" } })
|
||||
);
|
||||
$: smls = R.groupBy(
|
||||
(weaponry?.weapons ?? []).filter(u.matches({ specs: { type: "sml" } })),
|
||||
({ specs: { missileMagazineId } }) => missileMagazineId
|
||||
);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -7,7 +7,7 @@ import git from "git-describe";
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const config = {
|
||||
plugins: [sveltekit()],
|
||||
// publicDir: "./static",
|
||||
publicDir: "./static",
|
||||
ssr: {},
|
||||
optimizeDeps: {},
|
||||
define: {
|
||||
|
Loading…
Reference in New Issue
Block a user