import { useRef, useState, useEffect, Fragment } from "react"; import factionBackground from "../assets/factionBackground.png"; import adeptusAstartesIcon from "../assets/adeptusAstartesIcon.png"; import { Arrow, wavyLine } from "../assets/icons"; import { Weapons, hasDifferentProfiles } from "./Weapons"; import { useLocalStorage } from "../helpers/useLocalStorage"; export const Roster = ({ roster, onePerPage, colorUserChoice }) => { if (!roster) { return null; } const { name, cost, forces } = roster; return (
{name} [{cost.commandPoints} CP, {cost.points} pts]
{forces.map((force, index) => ( ))}
); }; const Force = ({ force, onePerPage, colorUserChoice }) => { const { units, factionRules, rules, catalog } = force; var mergedRules = new Map([...factionRules, ...rules]); return (
{units.map((unit, index) => ( ))}
); }; const Unit = ({ unit, index, catalog, onePerPage, forceRules, colorUserChoice, }) => { const [hide, setHide] = useState(false); const [hideModelCount, setHideModelCount] = useState(false); const uploadRef = useRef(); let { name, meleeWeapons, rangedWeapons, abilities, keywords, factions, rules, modelStats, modelList, cost, } = unit; const [image, setImage] = useLocalStorage("image" + name); const hasImage = image && image !== "undefined"; const weapons = [...meleeWeapons, ...rangedWeapons]; const weaponDescriptions = weapons .filter( (weapon) => (weapon.range === "-" || weapon.range === "") && weapon.abilities, ) .sort((a, b) => a.selectionName.localeCompare(b.selectionName)); const modelsWithDifferentProfiles = weapons.filter((weapon, index) => { const { selectionName, name } = weapon; const previousWeapon = weapons[index - 1]; const nextWeapon = weapons[index + 1]; return ( hasDifferentProfiles(selectionName, name) && ((previousWeapon && hasDifferentProfiles( previousWeapon.selectionName, previousWeapon.name, ) && selectionName === previousWeapon.selectionName) || (nextWeapon && hasDifferentProfiles(nextWeapon.selectionName, nextWeapon.name) && selectionName === nextWeapon.selectionName)) ); }); const overridePrimary = colorUserChoice ? "" : getOverridePrimary(factions, keywords); useEffect(() => { // Check if the browser is Safari, and if so, remove the accept attribute // from the file input element. This is because Safari doesn't support // extensions on the accept attribute for input type=file // (https://caniuse.com/input-file-accept). If set, they will not allow any // file to be selected. if ( navigator.userAgent.match(/AppleWebKit.*Safari/) && !navigator.userAgent.includes("Chrome") ) { uploadRef.current?.removeAttribute("accept"); } }, []); const areAllModelsTheSame = (modelStats) => { if (modelStats.length === 1) return true; const firstModel = modelStats[0]; return modelStats.every( (model) => model.move === firstModel.move && model.toughness === firstModel.toughness && model.save === firstModel.save && model.wounds === firstModel.wounds && model.leadership === firstModel.leadership && model.oc === firstModel.oc && model.bs === firstModel.bs && model.ws === firstModel.ws && model.attacks === firstModel.attacks, ); }; if (areAllModelsTheSame(modelStats)) { modelStats = [modelStats[0]]; } if (modelList.length === 1) { modelList[0] = modelList[0] .replace(name, "") .replace("(", "") .replace(")", ""); } return (
{wavyLine}
{name} {cost.points}pts
{modelStats.map((model, index) => ( 1} abilities={abilities.Abilities} /> ))}
{!hideModelCount && modelStats?.[0] && (
1 ? "" : "self-center" }`} style={{ fontSize: "0.7em", }} > {modelList.map((model, index) => (
{model}
))}
)}
{hasImage && ( )}
{hasImage && ( )}
{modelsWithDifferentProfiles.length > 0 && !weaponDescriptions.length && ( )}
{Arrow}
Before selecting targets for this weapon, select one of its profiles to make attacks with.
ABILITIES
); }; const checkAbilitiesForInvul = (abilitiesMap, name) => { const abilities = [...abilitiesMap.keys()]; for (let ability of abilities) { if (ability.includes(":")) { // if : then only match if the name is the same if ( !name .trim() .toLowerCase() .includes(ability.split(":")[1].trim().toLowerCase()) ) { continue; } } switch (abilitiesMap.get(ability)?.trim()?.toLowerCase()) { case "2+": if (ability?.toLowerCase().includes("invulnerable save")) return "2+"; break; case "3+": if (ability?.toLowerCase().includes("invulnerable save")) return "3+"; break; case "4+": if (ability?.toLowerCase().includes("invulnerable save")) return "4+"; break; case "5+": if (ability?.toLowerCase().includes("invulnerable save")) return "5+"; break; case "6+": if (ability?.toLowerCase().includes("invulnerable save")) return "6+"; break; case "this model has a 2+ invulnerable save.": return "2+"; case "this model has a 3+ invulnerable save.": return "3+"; case "this model has a 4+ invulnerable save.": return "4+"; case "this model has a 5+ invulnerable save.": return "5+"; case "this model has a 6+ invulnerable save.": return "6+"; case "models in this unit have a 2+ invulnerable save.": return "2+"; case "models in this unit have a 3+ invulnerable save.": return "3+"; case "models in this unit have a 4+ invulnerable save.": return "4+"; case "models in this unit have a 5+ invulnerable save.": return "5+"; case "models in this unit have a 6+ invulnerable save.": return "6+"; case "models in this unit have a 2+ invulnerable save against ranged weapons.": return "2+*"; case "models in this unit have a 3+ invulnerable save against ranged weapons.": return "3+*"; case "models in this unit have a 4+ invulnerable save against ranged weapons.": return "4+*"; case "models in this unit have a 5+ invulnerable save against ranged weapons.": return "5+*"; case "models in this unit have a 6+ invulnerable save against ranged weapons.": return "6+*"; case "while this model is leading a unit, models in that unit have a 2+ invulnerable save.": return "2+*"; case "while this model is leading a unit, models in that unit have a 3+ invulnerable save.": return "3+*"; case "while this model is leading a unit, models in that unit have a 4+ invulnerable save.": return "4+*"; case "while this model is leading a unit, models in that unit have a 5+ invulnerable save.": return "5+*"; case "while this model is leading a unit, models in that unit have a 6+ invulnerable save.": return "6+*"; case "while this model is leading a unit, models in that unit have a 2+ invulnerable save against ranged attacks.": return "2+*"; case "while this model is leading a unit, models in that unit have a 3+ invulnerable save against ranged attacks.": return "3+*"; case "while this model is leading a unit, models in that unit have a 4+ invulnerable save against ranged attacks.": return "4+*"; case "while this model is leading a unit, models in that unit have a 5+ invulnerable save against ranged attacks.": return "5+*"; case "while this model is leading a unit, models in that unit have a 6+ invulnerable save against ranged attacks.": return "6+*"; case "this model has a 2+ invulnerable save. you cannot re-roll invulnerable saving throws made for this model.": return "2+*"; case "this model has a 3+ invulnerable save. you cannot re-roll invulnerable saving throws made for this model.": return "3+*"; case "this model has a 4+ invulnerable save. you cannot re-roll invulnerable saving throws made for this model.": return "4+*"; case "this model has a 5+ invulnerable save. you cannot re-roll invulnerable saving throws made for this model.": return "5+*"; case "this model has a 6+ invulnerable save. you cannot re-roll invulnerable saving throws made for this model.": return "6+*"; } } return false; }; const ModelStats = ({ modelStat, index, showName, abilities }) => { let { move, toughness, save, wounds, leadership, name, oc, bs, ws, attacks } = modelStat; if (!wounds) { wounds = "/"; } const hasInvul = checkAbilitiesForInvul(abilities, name); return (
{showName && (
{name}
)}
); }; const InvulRow = ({ hasInvul }) => { if (!hasInvul) return null; const isSpecialInvul = hasInvul.includes("*"); const invulWithoutSpecial = hasInvul.replace("*", ""); return (
{invulWithoutSpecial}
INVULNERABLE SAVE{isSpecialInvul ? "*" : ""}
); }; const FactionIcon = ({ catalog }) => { return (
); }; const Keywords = ({ keywords }) => { const joinedKeywords = [...keywords].join(", "); return (
KEYWORDS: 70 ? ".8em" : "1em", fontWeight: 800, }} > {joinedKeywords}
); }; const Factions = ({ factions }) => { return (
FACTION KEYWORDS: {[...factions].join(", ")}
); }; const Rules = ({ rules }) => { if (!rules || rules.size === 0) return null; return (
RULES: {[...rules.keys()].map((rule, index) => ( {rule} {index < rules.size - 1 ? ", " : ""} ))}
); }; const Abilities = ({ abilities }) => { if (!abilities) return null; let keys = [...abilities.keys()]; return (
{abilities && keys.map((ability) => (
{ability}:{" "} {abilities.get(ability)}
))}
); }; const Characteristic = ({ title, characteristic, index }) => { return (
{index === 0 && (
{title}
)} {characteristic}
); }; const FancyBox = ({ children, className, style }) => { return (
{children}
); }; const FancyShield = ({ children, className, style }) => { return (
{children}
); }; const OtherAbilities = ({ abilities }) => { let keys = [...Object.keys(abilities)?.filter((key) => key !== "Abilities")]; return keys.map((key) => { return ( {key} {[...abilities[key]].map(([name, value]) => ( {name}: {value} ))} ); }); }; const ForceRules = ({ rules, onePerPage }) => { const [hide, setHide] = useState(false); return (
{[...rules.keys()].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 "#015d68"; if (factions.has("Nurgle") || keywords.has("Nurgle")) return "#5c672b"; if (factions.has("Slaanesh") || keywords.has("Slaanesh")) return "#634c74"; };