import { useState } from "react"; import factionBackground from "./assets/factionBackground.png"; import adeptusAstartesIcon from "./assets/adeptusAstartesIcon.png"; import rangedIcon from "./assets/rangedIcon.png"; import { Arrow, wavyLine } from "./assets/icons"; import { Weapons } from "./Weapons"; export const Roster = ({ roster, onePerPage }) => { if (!roster) { return null; } const { name, cost, forces } = roster; return (
{name} [{cost.commandPoints} CP]
{forces.map((force, index) => ( ))}
); }; const Force = ({ force, onePerPage }) => { const { units, rules, catalog } = force; return (
{units.map((unit, index) => ( ))}
); }; const Unit = ({ unit, index, catalog, onePerPage }) => { const [hide, setHide] = useState(false); let { name, weapons, abilities, keywords, factions, rules, modelStats, modelList, psykers, spells, cost, woundTracker, } = unit; const meleeWeapons = weapons .filter( (weapon) => (weapon.range === "Melee" || weapon.type === "Melee") && weapon.range !== "" ) .sort((a, b) => a.selectionName.localeCompare(b.selectionName)); if (meleeWeapons.length === 0) { meleeWeapons.push({ name: "Close combat weapon", selectionName: "Close combat weapon", range: "Melee", damage: "1", type: "Melee", str: "+0", ap: "0", }); } const rangedWeapons = weapons .filter( (weapon) => weapon.range !== "Melee" && weapon.range !== "-" && weapon.range !== "" ) .sort((a, b) => a.selectionName.localeCompare(b.selectionName)); const weaponDescriptions = weapons .filter( (weapon) => (weapon.range === "-" || weapon.range === "") && weapon.abilities ) .sort((a, b) => a.selectionName.localeCompare(b.selectionName)); const modelsWithDifferentProfiles = weapons.filter( (weapon) => weapon.selectionName !== weapon.name && !weapon.name.includes("(Shooting)") && !weapon.name.includes("(Melee)") ); const overridePrimary = getOverridePrimary(factions, keywords); return (
{wavyLine}
{name} {cost.points}pts
{modelStats.map((model, index) => ( 1} showWeapons={ modelList.length > 1 || modelList[0].includes("x ") // show Weapons when multiple weapons of same type, for example (2x Atomiser Beam, Reanimator's Claws) } /> ))}
{weaponDescriptions.map((weapon, index) => ( ))} {modelsWithDifferentProfiles.length > 0 && !weaponDescriptions.length && ( )}
{Arrow} {weapon.name} - {weapon.abilities}
{Arrow} Before selecting targets for this weapon, select one of its profiles to make attacks with.
ABILITIES
); }; const ModelStats = ({ modelStats, modelStat, index, showName, showWeapons, modelList, }) => { let { move, toughness, save, wounds, leadership, name, bs, ws, attacks } = modelStat; if (!wounds) { wounds = "/"; } if (!name.includes("Sgt")) { modelList = modelList.filter((model) => !model.includes("Sgt")); } if (!name.includes("Sergeant")) { modelList = modelList.filter((model) => !model.includes("Sergeant")); } let modelListMatches = modelList.filter((model) => model.includes(name + " w") ); if (modelListMatches.length === 0) { modelListMatches = modelList.filter((model) => model.includes(name)); } modelListMatches = modelListMatches.map((model) => model.replaceAll(name, "") ); const degradingStuff = []; const bsChange = Math.abs(parseInt(modelStats[0].bs, 10) - parseInt(bs, 10)); if (bsChange > 0) { degradingStuff.push(`${bsChange} from the balistic skill`); } const wsChange = Math.abs(parseInt(modelStats[0].ws, 10) - parseInt(ws, 10)); if (wsChange > 0) { degradingStuff.push(`${wsChange} from the weapon skill`); } const attacksChange = Math.abs( parseInt(modelStats[0].attacks, 10) - parseInt(attacks, 10) ); if (attacksChange > 0) { degradingStuff.push(`${attacksChange} from the number of attacks`); } return (
{showName && (
{name}
)} {showWeapons && (
{modelListMatches.map((model, index) => (
{model}
))}
)} {degradingStuff.length > 0 && (
{`Each time this model makes an attack, subtract ${degradingStuff.join(" and ")}.`}
)}
); }; const FactionIcon = ({ catalog }) => { return (
); }; const Keywords = ({ keywords }) => { keywords = Array.from(keywords).filter( (keyword) => keyword !== "Configuration" ); return (
KEYWORDS: {[...keywords].join(", ")}
); }; const Factions = ({ factions }) => { return (
FACTION KEYWORDS: {[...factions].join(", ")}
); }; const Rules = ({ rules }) => { if (!rules || rules.size === 0) return null; return (
RULES: {[...rules.keys()].map((rule) => rule).join(", ")}
); }; const Abilities = ({ abilities }) => { if (!abilities) return null; return (
{abilities && [...abilities.keys()].map((ability) => (
{ability}:{" "} {abilities.get(ability)}
))}
); }; const Characteristic = ({ title, characteristic, index }) => { return (
{index === 0 && (
{title}
)} {characteristic}
); }; const FancyBox = ({ children }) => { return (
{children}
); }; const Psykers = ({ title, psykers }) => { return ( {psykers.length > 0 && ( )} {psykers.map((psyker, index) => ( ))} {psykers.length > 0 && ( )}
PSYKER Cast Deny Powers Known
); }; const Psyker = ({ psyker, index }) => { let { name, cast, deny, powers } = psyker; return ( {cast} {deny} {powers} ); }; const WoundTracker = ({ woundTracker }) => { if (!woundTracker || woundTracker.length === 0) return null; const uniqueWoundTracker = woundTracker.filter((woundTrack, index) => { const firstIndex = woundTracker.findIndex( (woundTrack2) => woundTrack2.name === woundTrack.name ); return firstIndex === index; }); return (
{[...uniqueWoundTracker[0].table.keys()].map((key) => ( ))} {uniqueWoundTracker.map((woundTrack, index) => ( {[...woundTrack.table.values()].map((entry) => ( ))} ))}
{key}
{entry}
); }; const Spells = ({ title, spells }) => { return ( {spells.length > 0 && ( )} {spells.map((spell, index) => ( ))} {spells.length > 0 && ( )}
{title} RANGE WARP CHARGE
); }; const Spell = ({ spell, index, className }) => { let { name, range, manifest, details } = spell; return ( <>
{name}
{range} {manifest} {details} ); }; const getSpellClassNames = (spells, index) => { let differentColor = false; for (let i = 1; i <= index; i++) { let { name } = spells[i]; if (name !== spells[i - 1].name) { differentColor = !differentColor; } } const classes = []; if (differentColor) classes.push("rowOtherColor"); if (index === 0) classes.push("noBorderTop"); if (index > 0 && spells[index].name === spells[index - 1].name) classes.push("noBorderTop"); return classes.join(" "); }; const ForceRules = ({ rules, onePerPage }) => { rules.set( "Torrent", "When resolving an attack made with this weapon, do not make a hit roll: it automatically scores a hit." ); rules.set( "Indirect Fire", "This weapon can target units that are not visible to the bearer." ); rules.set( "Melta X", "Each time an attack made with this weapon targets a unit within half range, that attack's Damagae characteristic is increased by the amount denotex by 'X'." ); return (
{[...rules.keys()] .filter((rule) => !rule.startsWith("Explodes")) .map((rule) => (
{rule}: {rules.get(rule)}
))}
); }; const getOverridePrimary = (factions, keywords) => { if ( factions.has("Khorne") && factions.has("Tzeentch") && factions.has("Nurgle") && factions.has("Slaanesh") ) return; if (factions.has("Khorne") || keywords.has("Khorne")) return "#883531"; if (factions.has("Tzeentch") || keywords.has("Tzeentch")) return "#1f3d7a"; if (factions.has("Nurgle") || keywords.has("Nurgle")) return "#5c672b"; if (factions.has("Slaanesh") || keywords.has("Slaanesh")) return "#634c74"; };