default melee profile, abilties, prepare for "launch"
This commit is contained in:
+55
-4
@@ -2,6 +2,8 @@ import JSZip from "jszip";
|
|||||||
import { Create40kRoster } from "./roster40k";
|
import { Create40kRoster } from "./roster40k";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Roster } from "./Roster";
|
import { Roster } from "./Roster";
|
||||||
|
import Demo0 from "./assets/Demo0.png";
|
||||||
|
import Demo1 from "./assets/Demo1.png";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [error, setError] = useState();
|
const [error, setError] = useState();
|
||||||
@@ -55,7 +57,7 @@ function App() {
|
|||||||
console.log(doc);
|
console.log(doc);
|
||||||
const roster = Create40kRoster(doc);
|
const roster = Create40kRoster(doc);
|
||||||
console.log(roster);
|
console.log(roster);
|
||||||
if (roster && roster._forces.length > 0) {
|
if (roster && roster.forces.length > 0) {
|
||||||
setRoster(roster);
|
setRoster(roster);
|
||||||
/* const renderer = new Renderer40k(roster);
|
/* const renderer = new Renderer40k(roster);
|
||||||
renderer.render(rosterTitle, rosterList, forceUnits); */
|
renderer.render(rosterTitle, rosterList, forceUnits); */
|
||||||
@@ -67,13 +69,62 @@ function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<label>
|
<div className="header print-display-none">
|
||||||
<input type="file" accept=".ros, .rosz" onChange={handleFileSelect} />
|
FancyScribe
|
||||||
Select roster file (.ros, .rosz)
|
<div className="subheader">
|
||||||
|
A fancy way to view your Warhammer 40k BattleScribe rosters.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="body">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
name="rosterUpload"
|
||||||
|
id="rosterUpload"
|
||||||
|
onChange={handleFileSelect}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
for="rosterUpload"
|
||||||
|
className={
|
||||||
|
"print-display-none rosterUpload" +
|
||||||
|
" " +
|
||||||
|
(roster ? "rosterUploaded" : "")
|
||||||
|
}
|
||||||
|
id="rosterUploadContainer"
|
||||||
|
>
|
||||||
|
<div id="preloadContainer">
|
||||||
|
<span>Upload roster file (.ros, .rosz)</span>
|
||||||
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<div style={{ color: "red" }}>{error}</div>
|
<div style={{ color: "red" }}>{error}</div>
|
||||||
|
|
||||||
<Roster roster={roster} />
|
<Roster roster={roster} />
|
||||||
|
<div
|
||||||
|
className="print-display-none"
|
||||||
|
style={{ display: roster ? "none" : "" }}
|
||||||
|
>
|
||||||
|
<div style={{ padding: "8px 0", fontSize: "1.7rem" }}>About</div>
|
||||||
|
<div style={{ fontSize: "1.2rem" }}>
|
||||||
|
PrettyScribe is a website that renders{" "}
|
||||||
|
<a href="https://www.battlescribe.net/" target="_blank">
|
||||||
|
BattleScribe
|
||||||
|
</a>{" "}
|
||||||
|
roster files in an opinionated format inspired by the new 10th
|
||||||
|
edition datacards. Inspiration and large parts of the parsing logic
|
||||||
|
come from the{" "}
|
||||||
|
<a href="https://rweyrauch.github.io/PrettyScribe" target="_blank">
|
||||||
|
PrettyScribe
|
||||||
|
</a>{" "}
|
||||||
|
application.
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: "8px 0", fontSize: "1.7rem" }}>
|
||||||
|
Output Examples
|
||||||
|
</div>
|
||||||
|
<img src={Demo0} />
|
||||||
|
<img src={Demo1} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-38
@@ -1,14 +1,14 @@
|
|||||||
import factionBackground from "./assets/factionBackground.png";
|
import factionBackground from "./assets/factionBackground.png";
|
||||||
import adeptusAstartesIcon from "./assets/adeptusAstartesIcon.png";
|
import adeptusAstartesIcon from "./assets/adeptusAstartesIcon.png";
|
||||||
import rangedIcon from "./assets/rangedIcon.png";
|
import rangedIcon from "./assets/rangedIcon.png";
|
||||||
import { Arrow } from "./assets/icons";
|
import { Arrow, wavyLine } from "./assets/icons";
|
||||||
import { Weapons } from "./Weapons";
|
import { Weapons } from "./Weapons";
|
||||||
|
|
||||||
export const Roster = ({ roster }) => {
|
export const Roster = ({ roster }) => {
|
||||||
if (!roster) {
|
if (!roster) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { _name, _cost, _forces } = roster;
|
const { name, cost, forces } = roster;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -19,17 +19,18 @@ export const Roster = ({ roster }) => {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "var(--primary-color)",
|
backgroundColor: "#536766",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
padding: "4px 16px",
|
padding: "4px 16px",
|
||||||
fontSize: " 1.7em",
|
fontSize: " 1.7em",
|
||||||
fontWeight: "800",
|
fontWeight: "800",
|
||||||
textTransform: "uppercase",
|
textTransform: "uppercase",
|
||||||
|
marginBottom: -16,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{_name} [{_cost._commandPoints} CP]
|
{name} [{cost.commandPoints} CP]
|
||||||
</div>
|
</div>
|
||||||
{_forces.map((force, index) => (
|
{forces.map((force, index) => (
|
||||||
<Force key={index} force={force} />
|
<Force key={index} force={force} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -84,9 +85,9 @@ const getPrimaryColor = (catalog) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Force = ({ force }) => {
|
const Force = ({ force }) => {
|
||||||
const { _units, rules, _catalog } = force;
|
const { units, rules, catalog } = force;
|
||||||
|
|
||||||
const primaryColor = getPrimaryColor(_catalog);
|
const primaryColor = getPrimaryColor(catalog);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -95,7 +96,7 @@ const Force = ({ force }) => {
|
|||||||
"--primary-color-transparent": "#53676660",
|
"--primary-color-transparent": "#53676660",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{_units.map((unit, index) => (
|
{units.map((unit, index) => (
|
||||||
<Unit key={index} unit={unit} />
|
<Unit key={index} unit={unit} />
|
||||||
))}
|
))}
|
||||||
<ForceRules rules={rules} />
|
<ForceRules rules={rules} />
|
||||||
@@ -105,35 +106,47 @@ const Force = ({ force }) => {
|
|||||||
|
|
||||||
const Unit = ({ unit }) => {
|
const Unit = ({ unit }) => {
|
||||||
let {
|
let {
|
||||||
_name,
|
name,
|
||||||
weapons,
|
weapons,
|
||||||
abilities,
|
abilities,
|
||||||
keywords,
|
keywords,
|
||||||
factions,
|
factions,
|
||||||
rules,
|
rules,
|
||||||
modelStats,
|
modelStats,
|
||||||
_modelList,
|
modelList,
|
||||||
_psykers,
|
psykers,
|
||||||
} = unit;
|
} = unit;
|
||||||
|
|
||||||
const hasDifferentProfiles = weapons.some(
|
const hasDifferentProfiles = weapons.some(
|
||||||
(weapon) =>
|
(weapon) =>
|
||||||
weapon._selectionName !== weapon._name &&
|
weapon.selectionName !== weapon.name &&
|
||||||
!weapon._name.includes("(Shooting)") &&
|
!weapon.name.includes("(Shooting)") &&
|
||||||
!weapon._name.includes("(Melee)")
|
!weapon.name.includes("(Melee)")
|
||||||
);
|
);
|
||||||
const meleeWeapons = weapons
|
const meleeWeapons = weapons
|
||||||
.filter((weapon) => weapon._range === "Melee" && weapon._range !== "-")
|
.filter((weapon) => weapon.range === "Melee" && weapon.range !== "-")
|
||||||
.sort((a, b) => a._selectionName.localeCompare(b._selectionName));
|
.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
|
const rangedWeapons = weapons
|
||||||
.filter((weapon) => weapon._range !== "Melee" && weapon._range !== "-")
|
.filter((weapon) => weapon.range !== "Melee" && weapon.range !== "-")
|
||||||
.sort((a, b) => a._selectionName.localeCompare(b._selectionName));
|
.sort((a, b) => a.selectionName.localeCompare(b.selectionName));
|
||||||
modelStats = modelStats.sort((a, b) => b._name.length - a._name.length); // Sort by length of name, so that for example "Primaris Intercessor Sergeant" is before "Primaris Intercessor"
|
if (!modelStats.some((model) => model.name.includes("wounds remaining)"))) {
|
||||||
|
modelStats = modelStats.sort((a, b) => b.name.length - a.name.length); // Sort by length of name, so that for example "Primaris Intercessor Sergeant" is before "Primaris Intercessor"
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="avoid-page-break"
|
className="avoid-page-break"
|
||||||
style={{
|
style={{
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
border: "2px solid var(--primary-color)",
|
border: "2px solid var(--primary-color)",
|
||||||
backgroundColor: "#DFE0E2",
|
backgroundColor: "#DFE0E2",
|
||||||
@@ -141,8 +154,7 @@ const Unit = ({ unit }) => {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: "24px 16px",
|
padding: "24px 0",
|
||||||
paddingRight: 0,
|
|
||||||
paddingBottom: 24,
|
paddingBottom: 24,
|
||||||
background:
|
background:
|
||||||
"linear-gradient(90deg, rgba(20,21,25,1) 0%, rgba(48,57,62,1) 45%, rgba(73,74,79,1) 100%)",
|
"linear-gradient(90deg, rgba(20,21,25,1) 0%, rgba(48,57,62,1) 45%, rgba(73,74,79,1) 100%)",
|
||||||
@@ -153,7 +165,6 @@ const Unit = ({ unit }) => {
|
|||||||
style={{
|
style={{
|
||||||
padding: "4px 16px",
|
padding: "4px 16px",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
minHeight: 85,
|
|
||||||
position: "relative",
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -162,11 +173,40 @@ const Unit = ({ unit }) => {
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
backgroundColor: "var(--primary-color-transparent)",
|
|
||||||
minHeight: 85,
|
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: "var(--primary-color-transparent)",
|
||||||
|
height: 80,
|
||||||
|
minWidth: 340,
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: 80,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: "var(--primary-color-transparent)",
|
||||||
|
height: 40,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
{wavyLine}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: "10",
|
||||||
|
backgroundColor: "var(--primary-color-transparent)",
|
||||||
|
height: 40,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
fontSize: "1.7em",
|
fontSize: "1.7em",
|
||||||
@@ -176,7 +216,7 @@ const Unit = ({ unit }) => {
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{_name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -192,7 +232,7 @@ const Unit = ({ unit }) => {
|
|||||||
<ModelStats
|
<ModelStats
|
||||||
key={index}
|
key={index}
|
||||||
modelStats={model}
|
modelStats={model}
|
||||||
modelList={_modelList}
|
modelList={modelList}
|
||||||
index={index}
|
index={index}
|
||||||
showName={modelStats.length > 1}
|
showName={modelStats.length > 1}
|
||||||
/>
|
/>
|
||||||
@@ -225,7 +265,7 @@ const Unit = ({ unit }) => {
|
|||||||
weapons={meleeWeapons}
|
weapons={meleeWeapons}
|
||||||
modelStats={modelStats}
|
modelStats={modelStats}
|
||||||
/>
|
/>
|
||||||
<Psykers title="PSYKER" psykers={_psykers} />
|
<Psykers title="PSYKER" psykers={psykers} />
|
||||||
</table>
|
</table>
|
||||||
<div style={{ flex: "1" }}></div>
|
<div style={{ flex: "1" }}></div>
|
||||||
{hasDifferentProfiles && (
|
{hasDifferentProfiles && (
|
||||||
@@ -282,12 +322,12 @@ const Unit = ({ unit }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ModelStats = ({ modelStats, index, showName, modelList }) => {
|
const ModelStats = ({ modelStats, index, showName, modelList }) => {
|
||||||
let { move, toughness, save, wounds, leadership, _name } = modelStats;
|
let { move, toughness, save, wounds, leadership, name } = modelStats;
|
||||||
if (!wounds) {
|
if (!wounds) {
|
||||||
wounds = "/";
|
wounds = "/";
|
||||||
}
|
}
|
||||||
const modelListMatches = modelList
|
const modelListMatches = modelList
|
||||||
.filter((model) => model.includes(_name))
|
.filter((model) => model.includes(name))
|
||||||
.map((model) => "(" + model.split("(")[1]);
|
.map((model) => "(" + model.split("(")[1]);
|
||||||
return (
|
return (
|
||||||
<div style={{ display: "flex", gap: 16, alignItems: "center" }}>
|
<div style={{ display: "flex", gap: 16, alignItems: "center" }}>
|
||||||
@@ -305,7 +345,7 @@ const ModelStats = ({ modelStats, index, showName, modelList }) => {
|
|||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{_name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -529,7 +569,7 @@ const Psykers = ({ title, psykers }) => {
|
|||||||
)}
|
)}
|
||||||
<tbody>
|
<tbody>
|
||||||
{psykers.map((psyker, index) => (
|
{psykers.map((psyker, index) => (
|
||||||
<Psyker key={psyker._name} psyker={psyker} index={index} />
|
<Psyker key={psyker.name} psyker={psyker} index={index} />
|
||||||
))}
|
))}
|
||||||
{psykers.length > 0 && (
|
{psykers.length > 0 && (
|
||||||
<tr className="emptyRow">
|
<tr className="emptyRow">
|
||||||
@@ -543,7 +583,7 @@ const Psykers = ({ title, psykers }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Psyker = ({ psyker, index }) => {
|
const Psyker = ({ psyker, index }) => {
|
||||||
let { _name, _cast, _deny, _powers } = psyker;
|
let { name, cast, deny, powers } = psyker;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className={index % 2 ? "rowOtherColor" : ""}>
|
<tr className={index % 2 ? "rowOtherColor" : ""}>
|
||||||
@@ -557,13 +597,13 @@ const Psyker = ({ psyker, index }) => {
|
|||||||
gap: "0 4px",
|
gap: "0 4px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{_name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{_cast}</td>
|
<td>{cast}</td>
|
||||||
<td>{_deny}</td>
|
<td>{deny}</td>
|
||||||
<td style={{ textAlign: "left" }} colSpan="4">
|
<td style={{ textAlign: "left" }} colSpan="4">
|
||||||
{_powers}
|
{powers}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
@@ -588,7 +628,6 @@ const ForceRules = ({ rules }) => {
|
|||||||
<div
|
<div
|
||||||
key={rule}
|
key={rule}
|
||||||
style={{
|
style={{
|
||||||
fontSize: ".8em",
|
|
||||||
lineHeight: 1.4,
|
lineHeight: 1.4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
+53
-29
@@ -32,12 +32,11 @@ export const Weapons = ({ title, weapons, modelStats }) => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{weapons.map((weapon, index) => (
|
{weapons.map((weapon, index) => (
|
||||||
<Weapon
|
<Weapon
|
||||||
key={weapon._name}
|
key={weapon.name}
|
||||||
weapon={weapon}
|
weapon={weapon}
|
||||||
modelStats={modelStats}
|
modelStats={modelStats}
|
||||||
isMelee={isMelee}
|
isMelee={isMelee}
|
||||||
className={getWeaponClassNames(weapons, index)}
|
className={getWeaponClassNames(weapons, index)}
|
||||||
index={index}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{weapons.length > 0 && (
|
{weapons.length > 0 && (
|
||||||
@@ -51,34 +50,41 @@ export const Weapons = ({ title, weapons, modelStats }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Weapon = ({ weapon, modelStats, isMelee, className, index }) => {
|
const Weapon = ({ weapon, modelStats, isMelee, className }) => {
|
||||||
let { _name, _selectionName, _range, _type, str, _ap, _damage } = weapon;
|
let { name, selectionName, range, type, str, ap, damage, abilities } = weapon;
|
||||||
var lastWhiteSpace = _type.lastIndexOf(" ");
|
var lastWhiteSpace = type.lastIndexOf(" ");
|
||||||
const type = _type.substring(0, lastWhiteSpace);
|
type = type.substring(0, lastWhiteSpace);
|
||||||
const attacks = _type.substring(lastWhiteSpace + 1);
|
const attacks = type.substring(lastWhiteSpace + 1);
|
||||||
const bs = modelStats[0]._bs;
|
const bs = modelStats[0].bs;
|
||||||
const ws = modelStats[0]._ws;
|
const ws = modelStats[0].ws;
|
||||||
const strModel = modelStats[0].str;
|
const strModel = modelStats[0].str;
|
||||||
const meleeAttacks = modelStats[0]._attacks;
|
const meleeAttacks = modelStats[0].attacks;
|
||||||
|
|
||||||
if (_name === "Krak grenades") {
|
if (name === "Krak grenades") {
|
||||||
_name = "Krak grenade";
|
name = "Krak grenade";
|
||||||
}
|
}
|
||||||
const differentProfiles =
|
const differentProfiles =
|
||||||
_selectionName !== _name &&
|
selectionName !== name &&
|
||||||
!_name.includes("(Shooting)") &&
|
!name.includes("(Shooting)") &&
|
||||||
!_name.includes("(Melee)");
|
!name.includes("(Melee)");
|
||||||
const interestingType = type && type !== "Melee";
|
const interestingType = type && type !== "Melee";
|
||||||
if (differentProfiles && _name.endsWith(" grenades")) {
|
if (differentProfiles && name.endsWith(" grenades")) {
|
||||||
_name = _name.replace(" grenades", "");
|
name = name.replace(" grenades", "");
|
||||||
}
|
}
|
||||||
if (differentProfiles && _name.endsWith(" grenade")) {
|
if (differentProfiles && name.endsWith(" grenade")) {
|
||||||
_name = _name.replace(" grenade", "");
|
name = name.replace(" grenade", "");
|
||||||
}
|
}
|
||||||
if (differentProfiles && _name.includes(" - ")) {
|
if (differentProfiles && name.includes(" - ")) {
|
||||||
_name = _name.split(" - ")[1];
|
name = name.split(" - ")[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (abilities === "Blast") {
|
||||||
|
abilities = abilities.replaceAll("Blast", "");
|
||||||
|
type += ", Blast";
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<tr className={className}>
|
<tr className={className}>
|
||||||
<td style={{ borderTop: "none", backgroundColor: "#dfe0e2" }}>
|
<td style={{ borderTop: "none", backgroundColor: "#dfe0e2" }}>
|
||||||
{differentProfiles && Arrow}
|
{differentProfiles && Arrow}
|
||||||
@@ -92,8 +98,8 @@ const Weapon = ({ weapon, modelStats, isMelee, className, index }) => {
|
|||||||
gap: "0 4px",
|
gap: "0 4px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{differentProfiles && _selectionName + " - "}
|
{differentProfiles && selectionName + " - "}
|
||||||
{_name}
|
{name}
|
||||||
{interestingType && (
|
{interestingType && (
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
@@ -107,21 +113,39 @@ const Weapon = ({ weapon, modelStats, isMelee, className, index }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{_range}</td>
|
<td>{range}</td>
|
||||||
<td>{isMelee ? meleeAttacks : attacks}</td>
|
<td>{isMelee ? meleeAttacks : attacks}</td>
|
||||||
<td>{isMelee ? ws : bs}</td>
|
<td>{isMelee ? ws : bs}</td>
|
||||||
<td>{isMelee ? calculateWeaponStrength(strModel, str) : str}</td>
|
<td>{isMelee ? calculateWeaponStrength(strModel, str) : str}</td>
|
||||||
<td>{_ap}</td>
|
<td>{ap}</td>
|
||||||
<td>{_damage}</td>
|
<td>{damage}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{abilities && abilities !== "-" && (
|
||||||
|
<tr className={className + " " + "noBorderTop"}>
|
||||||
|
<td style={{ backgroundColor: "#dfe0e2" }}></td>
|
||||||
|
<td
|
||||||
|
colSpan="7"
|
||||||
|
style={{
|
||||||
|
textAlign: "left",
|
||||||
|
fontSize: "0.9em",
|
||||||
|
paddingTop: 0,
|
||||||
|
paddingBottom: 1,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{abilities}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getWeaponClassNames = (weapons, index) => {
|
const getWeaponClassNames = (weapons, index) => {
|
||||||
let differentColor = false;
|
let differentColor = false;
|
||||||
for (let i = 1; i <= index; i++) {
|
for (let i = 1; i <= index; i++) {
|
||||||
let { _selectionName } = weapons[i];
|
let { selectionName } = weapons[i];
|
||||||
if (_selectionName !== weapons[i - 1]._selectionName) {
|
if (selectionName !== weapons[i - 1].selectionName) {
|
||||||
differentColor = !differentColor;
|
differentColor = !differentColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +154,7 @@ const getWeaponClassNames = (weapons, index) => {
|
|||||||
if (index === 0) classes.push("noBorderTop");
|
if (index === 0) classes.push("noBorderTop");
|
||||||
if (
|
if (
|
||||||
index > 0 &&
|
index > 0 &&
|
||||||
weapons[index]._selectionName === weapons[index - 1]._selectionName
|
weapons[index].selectionName === weapons[index - 1].selectionName
|
||||||
)
|
)
|
||||||
classes.push("noBorderTop");
|
classes.push("noBorderTop");
|
||||||
return classes.join(" ");
|
return classes.join(" ");
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 276 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 210 KiB |
@@ -3,3 +3,13 @@ export const Arrow = (
|
|||||||
<path d="m0 0h10l6 4-6 4h-10z" />
|
<path d="m0 0h10l6 4-6 4h-10z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
export const wavyLine = (
|
||||||
|
<svg
|
||||||
|
height={40}
|
||||||
|
width={100}
|
||||||
|
viewBox="0 0 100 40"
|
||||||
|
style={{ fill: "var(--primary-color-transparent)" }}
|
||||||
|
>
|
||||||
|
<path d="m0 0h100c-32 0-68 40-100 40z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg version="1.1" viewBox="0 0 80 40" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<path d="m0 0h80c-16 0-64 40-80 40z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 186 B |
+53
-6
@@ -3,6 +3,7 @@
|
|||||||
Arial, sans-serif;
|
Arial, sans-serif;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
color: rgba(255, 255, 255, 0.87);
|
color: rgba(255, 255, 255, 0.87);
|
||||||
@@ -32,8 +33,6 @@ a:hover {
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
@@ -76,19 +75,67 @@ button:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
max-width: 1100px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
color: #111113;
|
color: #111113;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App {
|
.body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 24px 32px;
|
padding: 24px 32px;
|
||||||
|
max-width: 1100px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #536766;
|
||||||
|
width: 100%;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.7em;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheader {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rosterUpload {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 24px;
|
||||||
|
width: 100%;
|
||||||
|
height: 450px;
|
||||||
|
border: 10px solid #999;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.25s, border 0.25s, font-size 0.25s, background-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rosterUpload:hover {
|
||||||
|
border-color: #536766;
|
||||||
|
background-color: #53676660;
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rosterUploaded,
|
||||||
|
.rosterUploaded:hover {
|
||||||
|
height: auto;
|
||||||
|
border: 2px solid #999;
|
||||||
|
font-size: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.weapons-table {
|
.weapons-table {
|
||||||
|
|||||||
+206
-232
@@ -1,48 +1,39 @@
|
|||||||
export class BaseNotes {
|
export class BaseNotes {
|
||||||
_name = "";
|
name = "";
|
||||||
_customNotes = "";
|
|
||||||
|
|
||||||
name() {
|
|
||||||
return this._name;
|
|
||||||
}
|
|
||||||
|
|
||||||
notes() {
|
|
||||||
return this._customNotes;
|
|
||||||
}
|
|
||||||
|
|
||||||
equal(other) {
|
equal(other) {
|
||||||
if (other == null) return false;
|
if (other == null) return false;
|
||||||
// Weapons in 40k have unique names
|
// Weapons in 40k have unique names
|
||||||
return this._name === other._name;
|
return this.name === other.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A `selection` attached to a unit or model. */
|
/** A `selection` attached to a unit or model. */
|
||||||
export class Upgrade extends BaseNotes {
|
export class Upgrade extends BaseNotes {
|
||||||
_cost = new Costs();
|
cost = new Costs();
|
||||||
_count = 1;
|
count = 1;
|
||||||
|
|
||||||
selectionName() {
|
getSelectionName() {
|
||||||
return this.name();
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
let string = this.selectionName();
|
let string = this.getSelectionName();
|
||||||
if (this._count > 1) string = `${this._count}x ${string}`;
|
if (this.count > 1) string = `${this.count}x ${string}`;
|
||||||
if (this._cost.hasValues()) string += ` ${this._cost.toString()}`;
|
if (this.cost.hasValues()) string += ` ${this.cost.toString()}`;
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A weapon `profile` that is under a `selection`. */
|
/** A weapon `profile` that is under a `selection`. */
|
||||||
export class Weapon extends Upgrade {
|
export class Weapon extends Upgrade {
|
||||||
_selectionName = "";
|
selectionName = "";
|
||||||
|
|
||||||
_range = "Melee";
|
range = "Melee";
|
||||||
_type = "Melee";
|
type = "Melee";
|
||||||
str = "user";
|
str = "user";
|
||||||
_ap = "";
|
ap = "";
|
||||||
_damage = "";
|
damage = "";
|
||||||
|
|
||||||
abilities = "";
|
abilities = "";
|
||||||
|
|
||||||
@@ -50,35 +41,35 @@ export class Weapon extends Upgrade {
|
|||||||
* Name of this weapon's `selection`. This is different from name() because
|
* Name of this weapon's `selection`. This is different from name() because
|
||||||
* name() is used for sorting and deduping weapon profiles.
|
* name() is used for sorting and deduping weapon profiles.
|
||||||
*/
|
*/
|
||||||
selectionName() {
|
getSelectionName() {
|
||||||
return this._selectionName || this.name();
|
return this.selectionName || this.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WoundTracker extends BaseNotes {
|
export class WoundTracker extends BaseNotes {
|
||||||
_name = "";
|
name = "";
|
||||||
_table = new Map();
|
table = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Explosion extends BaseNotes {
|
export class Explosion extends BaseNotes {
|
||||||
_name = "";
|
name = "";
|
||||||
_diceRoll = "";
|
diceRoll = "";
|
||||||
_distance = "";
|
distance = "";
|
||||||
_mortalWounds = "";
|
mortalWounds = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Psyker extends BaseNotes {
|
export class Psyker extends BaseNotes {
|
||||||
_cast = "";
|
cast = "";
|
||||||
_deny = "";
|
deny = "";
|
||||||
_powers = "";
|
powers = "";
|
||||||
_other = "";
|
other = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PsychicPower extends BaseNotes {
|
export class PsychicPower extends BaseNotes {
|
||||||
_name = "";
|
name = "";
|
||||||
_manifest = 0;
|
manifest = 0;
|
||||||
_range = "";
|
range = "";
|
||||||
_details = "";
|
details = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UnitRole = {
|
export const UnitRole = {
|
||||||
@@ -118,48 +109,48 @@ export const UnitRoleToString = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export class Model extends BaseNotes {
|
export class Model extends BaseNotes {
|
||||||
_count = 0;
|
count = 0;
|
||||||
|
|
||||||
// Characteristics
|
// Characteristics
|
||||||
move = '0"';
|
move = '0"';
|
||||||
_ws = "";
|
ws = "";
|
||||||
_bs = "";
|
bs = "";
|
||||||
str = 4;
|
str = 4;
|
||||||
toughness = 4;
|
toughness = 4;
|
||||||
wounds = 1;
|
wounds = 1;
|
||||||
_attacks = "";
|
attacks = "";
|
||||||
leadership = 7;
|
leadership = 7;
|
||||||
save = "";
|
save = "";
|
||||||
|
|
||||||
weapons = [];
|
weapons = [];
|
||||||
_upgrades = [];
|
upgrades = [];
|
||||||
// TODO model upgrades (i.e. tau support systems)
|
// TODO model upgrades (i.e. tau support systems)
|
||||||
_psyker = null;
|
psyker = null;
|
||||||
_psychicPowers = [];
|
psychicPowers = [];
|
||||||
_explosions = [];
|
explosions = [];
|
||||||
|
|
||||||
equal(model) {
|
equal(model) {
|
||||||
if (model == null) return false;
|
if (model == null) return false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this._name === model._name &&
|
this.name === model.name &&
|
||||||
this._count === model._count &&
|
this.count === model.count &&
|
||||||
this.weapons.length === model.weapons.length &&
|
this.weapons.length === model.weapons.length &&
|
||||||
this._upgrades.length === model._upgrades.length
|
this.upgrades.length === model.upgrades.length
|
||||||
) {
|
) {
|
||||||
for (let wi = 0; wi < this.weapons.length; wi++) {
|
for (let wi = 0; wi < this.weapons.length; wi++) {
|
||||||
if (!this.weapons[wi].equal(model.weapons[wi])) {
|
if (!this.weapons[wi].equal(model.weapons[wi])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let wi = 0; wi < this._upgrades.length; wi++) {
|
for (let wi = 0; wi < this.upgrades.length; wi++) {
|
||||||
if (!this._upgrades[wi].equal(model._upgrades[wi])) {
|
if (!this.upgrades[wi].equal(model.upgrades[wi])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check for the same psychic powers
|
// TODO: check for the same psychic powers
|
||||||
if (this._psyker != null || model._psyker != null) return false;
|
if (this.psyker != null || model.psyker != null) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -167,9 +158,9 @@ export class Model extends BaseNotes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nameAndGear() {
|
nameAndGear() {
|
||||||
let name = super.name();
|
let name = super.name;
|
||||||
|
|
||||||
if (this.weapons.length > 0 || this._upgrades.length > 0) {
|
if (this.weapons.length > 0 || this.upgrades.length > 0) {
|
||||||
const gear = this.getDedupedWeaponsAndUpgrades();
|
const gear = this.getDedupedWeaponsAndUpgrades();
|
||||||
name += ` (${gear.map((u) => u.toString()).join(", ")})`;
|
name += ` (${gear.map((u) => u.toString()).join(", ")})`;
|
||||||
}
|
}
|
||||||
@@ -178,8 +169,12 @@ export class Model extends BaseNotes {
|
|||||||
|
|
||||||
getDedupedWeaponsAndUpgrades() {
|
getDedupedWeaponsAndUpgrades() {
|
||||||
const deduped = [];
|
const deduped = [];
|
||||||
for (const upgrade of [...this.weapons, ...this._upgrades]) {
|
for (const upgrade of [...this.weapons, ...this.upgrades]) {
|
||||||
if (!deduped.some((e) => upgrade.selectionName() === e.selectionName())) {
|
if (
|
||||||
|
!deduped.some(
|
||||||
|
(e) => upgrade.getSelectionName() === e.getSelectionName()
|
||||||
|
)
|
||||||
|
) {
|
||||||
deduped.push(upgrade);
|
deduped.push(upgrade);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,75 +183,75 @@ export class Model extends BaseNotes {
|
|||||||
|
|
||||||
normalize() {
|
normalize() {
|
||||||
this.weapons.sort(CompareWeapon);
|
this.weapons.sort(CompareWeapon);
|
||||||
this._upgrades.sort(CompareObj);
|
this.upgrades.sort(CompareObj);
|
||||||
|
|
||||||
this.normalizeUpgrades(this.weapons);
|
this.normalizeUpgrades(this.weapons);
|
||||||
this.normalizeUpgrades(this._upgrades);
|
this.normalizeUpgrades(this.upgrades);
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizeUpgrades(upgrades) {
|
normalizeUpgrades(upgrades) {
|
||||||
for (let i = 0; i < upgrades.length - 1; i++) {
|
for (let i = 0; i < upgrades.length - 1; i++) {
|
||||||
const upgrade = upgrades[i];
|
const upgrade = upgrades[i];
|
||||||
if (upgrade._name === upgrades[i + 1]._name) {
|
if (upgrade.name === upgrades[i + 1].name) {
|
||||||
upgrade._count += upgrades[i + 1]._count;
|
upgrade.count += upgrades[i + 1].count;
|
||||||
upgrade._cost.add(upgrades[i + 1]._cost);
|
upgrade.cost.add(upgrades[i + 1].cost);
|
||||||
upgrades.splice(i + 1, 1);
|
upgrades.splice(i + 1, 1);
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let upgrade of upgrades) {
|
for (let upgrade of upgrades) {
|
||||||
if (upgrade._count % this._count == 0) {
|
if (upgrade.count % this.count == 0) {
|
||||||
upgrade._count /= this._count;
|
upgrade.count /= this.count;
|
||||||
upgrade._cost._points /= this._count;
|
upgrade.cost.points /= this.count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Unit extends BaseNotes {
|
export class Unit extends BaseNotes {
|
||||||
_roleRole = UnitRole.NONE;
|
roleRole = UnitRole.NONE;
|
||||||
factions = new Set();
|
factions = new Set();
|
||||||
keywords = new Set();
|
keywords = new Set();
|
||||||
|
|
||||||
abilities = {};
|
abilities = {};
|
||||||
rules = new Map();
|
rules = new Map();
|
||||||
|
|
||||||
_models = [];
|
models = [];
|
||||||
modelStats = [];
|
modelStats = [];
|
||||||
_modelList = [];
|
modelList = [];
|
||||||
weapons = [];
|
weapons = [];
|
||||||
_spells = [];
|
spells = [];
|
||||||
_psykers = [];
|
psykers = [];
|
||||||
_explosions = [];
|
explosions = [];
|
||||||
|
|
||||||
_cost = new Costs();
|
cost = new Costs();
|
||||||
|
|
||||||
_woundTracker = [];
|
woundTracker = [];
|
||||||
|
|
||||||
nameWithExtraCosts() {
|
nameWithExtraCosts() {
|
||||||
const extraCosts = []; // Track extra costs like cabal points.
|
const extraCosts = []; // Track extra costs like cabal points.
|
||||||
for (const freeformCostType in this._cost._freeformValues) {
|
for (const freeformCostType in this.cost.freeformValues) {
|
||||||
if (this._cost._freeformValues[freeformCostType] === 0) continue;
|
if (this.cost.freeformValues[freeformCostType] === 0) continue;
|
||||||
extraCosts.push(
|
extraCosts.push(
|
||||||
`${this._cost._freeformValues[freeformCostType]}${freeformCostType}`
|
`${this.cost.freeformValues[freeformCostType]}${freeformCostType}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return extraCosts.length
|
return extraCosts.length
|
||||||
? `${this.name()} [${extraCosts.join(", ")}]`
|
? `${this.name} [${extraCosts.join(", ")}]`
|
||||||
: this.name();
|
: this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
equal(unit) {
|
equal(unit) {
|
||||||
if (unit == null) return false;
|
if (unit == null) return false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
unit._name === this._name &&
|
unit.name === this.name &&
|
||||||
unit._role === this._role &&
|
unit.role === this.role &&
|
||||||
unit._models.length === this._models.length &&
|
unit.models.length === this.models.length &&
|
||||||
unit.modelStats.length === this.modelStats.length
|
unit.modelStats.length === this.modelStats.length
|
||||||
) {
|
) {
|
||||||
for (let mi = 0; mi < this._models.length; mi++) {
|
for (let mi = 0; mi < this.models.length; mi++) {
|
||||||
if (!this._models[mi].equal(unit._models[mi])) {
|
if (!this.models[mi].equal(unit.models[mi])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,9 +263,9 @@ export class Unit extends BaseNotes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check how to replace without lodash
|
// Check how to replace without lodash
|
||||||
/* if (!_.isEqual(this.abilities, unit.abilities)) {
|
/* if (!.isEqual(this.abilities, unit.abilities)) {
|
||||||
return false;
|
return false;
|
||||||
} else if (!_.isEqual(this.rules, unit.rules)) {
|
} else if (!.isEqual(this.rules, unit.rules)) {
|
||||||
return false;
|
return false;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
@@ -281,19 +276,19 @@ export class Unit extends BaseNotes {
|
|||||||
|
|
||||||
normalize() {
|
normalize() {
|
||||||
// Sort force units by role and name
|
// Sort force units by role and name
|
||||||
this._models.sort(CompareModel);
|
this.models.sort(CompareModel);
|
||||||
this.modelStats.sort(CompareObj);
|
this.modelStats.sort(CompareObj);
|
||||||
|
|
||||||
for (let model of this._models) {
|
for (let model of this.models) {
|
||||||
model.normalize();
|
model.normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < this._models.length - 1; i++) {
|
for (let i = 0; i < this.models.length - 1; i++) {
|
||||||
const model = this._models[i];
|
const model = this.models[i];
|
||||||
|
|
||||||
if (model.nameAndGear() === this._models[i + 1].nameAndGear()) {
|
if (model.nameAndGear() === this.models[i + 1].nameAndGear()) {
|
||||||
model._count++;
|
model.count++;
|
||||||
this._models.splice(i + 1, 1);
|
this.models.splice(i + 1, 1);
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,77 +302,72 @@ export class Unit extends BaseNotes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._modelList = this._models.map(
|
this.modelList = this.models.map(
|
||||||
(model) =>
|
(model) =>
|
||||||
(model._count > 1 ? `${model._count}x ` : "") + model.nameAndGear()
|
(model.count > 1 ? `${model.count}x ` : "") + model.nameAndGear()
|
||||||
);
|
);
|
||||||
this.weapons = this._models
|
this.weapons = this.models
|
||||||
.map((m) => m.weapons)
|
.map((m) => m.weapons)
|
||||||
.reduce((acc, val) => acc.concat(val), [])
|
.reduce((acc, val) => acc.concat(val), [])
|
||||||
.sort(CompareWeapon)
|
.sort(CompareWeapon)
|
||||||
.filter((weap, i, array) => weap.name() !== array[i - 1]?.name());
|
.filter((weap, i, array) => weap.name !== array[i - 1]?.name);
|
||||||
|
|
||||||
this._spells.push(
|
this.spells.push(
|
||||||
...this._models
|
...this.models
|
||||||
.map((m) => m._psychicPowers)
|
.map((m) => m.psychicPowers)
|
||||||
.reduce((acc, val) => acc.concat(val), [])
|
.reduce((acc, val) => acc.concat(val), [])
|
||||||
);
|
);
|
||||||
this._psykers.push(...this._models.map((m) => m._psyker).filter((p) => p));
|
this.psykers.push(...this.models.map((m) => m.psyker).filter((p) => p));
|
||||||
this._explosions.push(
|
this.explosions.push(
|
||||||
...this._models
|
...this.models
|
||||||
.map((m) => m._explosions)
|
.map((m) => m.explosions)
|
||||||
.reduce((acc, val) => acc.concat(val), [])
|
.reduce((acc, val) => acc.concat(val), [])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Force extends BaseNotes {
|
export class Force extends BaseNotes {
|
||||||
_catalog = "";
|
catalog = "";
|
||||||
_faction = "Unknown";
|
faction = "Unknown";
|
||||||
_factionRules = new Map();
|
factionRules = new Map();
|
||||||
_configurations = [];
|
configurations = [];
|
||||||
rules = new Map();
|
rules = new Map();
|
||||||
_units = [];
|
units = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Roster40k extends BaseNotes {
|
export class Roster40k extends BaseNotes {
|
||||||
_cost = new Costs();
|
cost = new Costs();
|
||||||
_forces = [];
|
forces = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Costs {
|
export class Costs {
|
||||||
_powerLevel = 0;
|
commandPoints = 0;
|
||||||
_commandPoints = 0;
|
points = 0;
|
||||||
_points = 0;
|
freeformValues;
|
||||||
_freeformValues;
|
|
||||||
|
|
||||||
hasValues() {
|
hasValues() {
|
||||||
return (
|
return this.commandPoints !== 0 || this.points !== 0;
|
||||||
this._powerLevel !== 0 || this._commandPoints !== 0 || this._points !== 0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
const values = [];
|
const values = [];
|
||||||
if (this._points !== 0) values.push(`${this._points} pts`);
|
if (this.points !== 0) values.push(`${this.points} pts`);
|
||||||
if (this._powerLevel !== 0) values.push(`${this._powerLevel} PL`);
|
if (this.commandPoints !== 0) values.push(`${this.commandPoints} CP`);
|
||||||
if (this._commandPoints !== 0) values.push(`${this._commandPoints} CP`);
|
|
||||||
return `[${values.join(" / ")}]`;
|
return `[${values.join(" / ")}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(other) {
|
add(other) {
|
||||||
this._powerLevel += other._powerLevel;
|
this.commandPoints += other.commandPoints;
|
||||||
this._commandPoints += other._commandPoints;
|
this.points += other.points;
|
||||||
this._points += other._points;
|
for (const name in other.freeformValues) {
|
||||||
for (const name in other._freeformValues) {
|
this.addFreeformValue(name, other.freeformValues[name]);
|
||||||
this.addFreeformValue(name, other._freeformValues[name]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addFreeformValue(name, value) {
|
addFreeformValue(name, value) {
|
||||||
if (!this._freeformValues) this._freeformValues = {};
|
if (!this.freeformValues) this.freeformValues = {};
|
||||||
const oldValue = this._freeformValues[name] || 0;
|
const oldValue = this.freeformValues[name] || 0;
|
||||||
this._freeformValues[name] = oldValue + value;
|
this.freeformValues[name] = oldValue + value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,9 +379,9 @@ export function Create40kRoster(doc) {
|
|||||||
|
|
||||||
const name = info.getAttributeNode("name")?.nodeValue;
|
const name = info.getAttributeNode("name")?.nodeValue;
|
||||||
if (name) {
|
if (name) {
|
||||||
roster._name = name;
|
roster.name = name;
|
||||||
} else {
|
} else {
|
||||||
roster._name = "40k Army Roster";
|
roster.name = "40k Army Roster";
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseRosterPoints(doc, roster);
|
ParseRosterPoints(doc, roster);
|
||||||
@@ -404,7 +394,7 @@ export function Create40kRoster(doc) {
|
|||||||
function ParseRosterPoints(doc, roster) {
|
function ParseRosterPoints(doc, roster) {
|
||||||
let costs = doc.querySelectorAll("roster>costs>cost");
|
let costs = doc.querySelectorAll("roster>costs>cost");
|
||||||
for (let cost of costs) {
|
for (let cost of costs) {
|
||||||
roster._cost.add(ParseCost(cost));
|
roster.cost.add(ParseCost(cost));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,10 +408,10 @@ function ParseForces(doc, roster) {
|
|||||||
let value = root.getAttributeNode("catalogueName")?.nodeValue;
|
let value = root.getAttributeNode("catalogueName")?.nodeValue;
|
||||||
|
|
||||||
if (which) {
|
if (which) {
|
||||||
f._name = which;
|
f.name = which;
|
||||||
}
|
}
|
||||||
if (value) {
|
if (value) {
|
||||||
f._catalog = value;
|
f.catalog = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Determine force faction and faction specific rules.
|
// TODO: Determine force faction and faction specific rules.
|
||||||
@@ -436,7 +426,7 @@ function ParseForces(doc, roster) {
|
|||||||
|
|
||||||
ParseSelections(root, f);
|
ParseSelections(root, f);
|
||||||
|
|
||||||
roster._forces.push(f);
|
roster.forces.push(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -458,7 +448,7 @@ function ParseSelections(root, force) {
|
|||||||
ParseConfiguration(selection, force);
|
ParseConfiguration(selection, force);
|
||||||
} else if (selection.querySelector('profile[typeName="Unit"]')) {
|
} else if (selection.querySelector('profile[typeName="Unit"]')) {
|
||||||
const unit = ParseUnit(selection);
|
const unit = ParseUnit(selection);
|
||||||
force._units.push(unit);
|
force.units.push(unit);
|
||||||
for (const entry of unit.rules.entries()) {
|
for (const entry of unit.rules.entries()) {
|
||||||
force.rules.set(entry[0], entry[1]);
|
force.rules.set(entry[0], entry[1]);
|
||||||
}
|
}
|
||||||
@@ -470,11 +460,11 @@ function ParseSelections(root, force) {
|
|||||||
// sub-faction
|
// sub-faction
|
||||||
const name = prop.getAttribute("name");
|
const name = prop.getAttribute("name");
|
||||||
if (name && prop.getAttribute("type") === "upgrade") {
|
if (name && prop.getAttribute("type") === "upgrade") {
|
||||||
if (force._faction === "Unknown") {
|
if (force.faction === "Unknown") {
|
||||||
// pick the first upgrade we see
|
// pick the first upgrade we see
|
||||||
force._faction = name;
|
force.faction = name;
|
||||||
}
|
}
|
||||||
ExtractRuleFromSelection(prop, force._factionRules);
|
ExtractRuleFromSelection(prop, force.factionRules);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -482,16 +472,16 @@ function ParseSelections(root, force) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key of force._factionRules.keys()) {
|
for (const key of force.factionRules.keys()) {
|
||||||
force.rules.delete(key);
|
force.rules.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort force units by role and name
|
// Sort force units by role and name
|
||||||
force._units.sort((a, b) => {
|
force.units.sort((a, b) => {
|
||||||
if (a._role > b._role) return 1;
|
if (a.role > b.role) return 1;
|
||||||
else if (a._role == b._role) {
|
else if (a.role == b.role) {
|
||||||
if (a._name > b._name) return 1;
|
if (a.name > b.name) return 1;
|
||||||
else if (a._name == b._name) return 0;
|
else if (a.name == b.name) return 0;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
@@ -517,14 +507,14 @@ function ParseConfiguration(selection, force) {
|
|||||||
if (details.length > 0) configuration += `: ${details.join(", ")}`;
|
if (details.length > 0) configuration += `: ${details.join(", ")}`;
|
||||||
if (costs.hasValues()) configuration += ` ${costs.toString()}`;
|
if (costs.hasValues()) configuration += ` ${costs.toString()}`;
|
||||||
|
|
||||||
force._configurations.push(configuration);
|
force.configurations.push(configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DuplicateForce(force, roster) {
|
function DuplicateForce(force, roster) {
|
||||||
if (!roster || !force) return false;
|
if (!roster || !force) return false;
|
||||||
|
|
||||||
for (let f of roster._forces) {
|
for (let f of roster.forces) {
|
||||||
if (f._catalog === force._catalog) return true;
|
if (f.catalog === force.catalog) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -591,22 +581,8 @@ function LookupRole(roleText) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ExpandBaseNotes(root, obj) {
|
function ExpandBaseNotes(root, obj) {
|
||||||
obj._name = root.getAttributeNode("name")?.nodeValue;
|
obj.name = root.getAttributeNode("name")?.nodeValue;
|
||||||
|
return obj.name;
|
||||||
let element = root;
|
|
||||||
if (
|
|
||||||
root.tagName === "profile" &&
|
|
||||||
root.parentElement &&
|
|
||||||
root.parentElement.parentElement
|
|
||||||
) {
|
|
||||||
element = root.parentElement.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
let child = element.firstElementChild;
|
|
||||||
if (child && child.tagName === "customNotes") {
|
|
||||||
obj._customNotes = child.textContent;
|
|
||||||
}
|
|
||||||
return obj._name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExtractNumberFromParent(root) {
|
function ExtractNumberFromParent(root) {
|
||||||
@@ -676,12 +652,10 @@ function ParseCost(cost) {
|
|||||||
const which = cost.getAttribute("name");
|
const which = cost.getAttribute("name");
|
||||||
const value = cost.getAttribute("value");
|
const value = cost.getAttribute("value");
|
||||||
if (which && value) {
|
if (which && value) {
|
||||||
if (which === " PL") {
|
if (which === "pts") {
|
||||||
costs._powerLevel += +value;
|
costs.points += +value;
|
||||||
} else if (which === "pts") {
|
|
||||||
costs._points += +value;
|
|
||||||
} else if (which === "CP") {
|
} else if (which === "CP") {
|
||||||
costs._commandPoints += +value;
|
costs.commandPoints += +value;
|
||||||
} else {
|
} else {
|
||||||
costs.addFreeformValue(which, +value);
|
costs.addFreeformValue(which, +value);
|
||||||
}
|
}
|
||||||
@@ -706,7 +680,7 @@ function ParseUnit(root) {
|
|||||||
const roleText = catName.trim();
|
const roleText = catName.trim();
|
||||||
let unitRole = LookupRole(roleText);
|
let unitRole = LookupRole(roleText);
|
||||||
if (unitRole != UnitRole.NONE) {
|
if (unitRole != UnitRole.NONE) {
|
||||||
unit._role = unitRole;
|
unit.role = unitRole;
|
||||||
} else {
|
} else {
|
||||||
// Keyword
|
// Keyword
|
||||||
unit.keywords.add(catName);
|
unit.keywords.add(catName);
|
||||||
@@ -763,9 +737,9 @@ function ParseUnit(root) {
|
|||||||
seenProfiles.push(...unseenProfiles);
|
seenProfiles.push(...unseenProfiles);
|
||||||
|
|
||||||
const model = new Model();
|
const model = new Model();
|
||||||
model._name = modelSelection.getAttribute("name") || "Unknown Model";
|
model.name = modelSelection.getAttribute("name") || "Unknown Model";
|
||||||
model._count = Number(modelSelection.getAttribute("number") || 1);
|
model.count = Number(modelSelection.getAttribute("number") || 1);
|
||||||
unit._models.push(model);
|
unit.models.push(model);
|
||||||
|
|
||||||
// Find stats for all profiles (weapons, powers, abilities, etc).
|
// Find stats for all profiles (weapons, powers, abilities, etc).
|
||||||
ParseModelProfiles(profiles, model, unit);
|
ParseModelProfiles(profiles, model, unit);
|
||||||
@@ -788,10 +762,10 @@ function ParseUnit(root) {
|
|||||||
let upgradeName = upgradeSelection.getAttribute("name");
|
let upgradeName = upgradeSelection.getAttribute("name");
|
||||||
if (upgradeName) {
|
if (upgradeName) {
|
||||||
const upgrade = new Upgrade();
|
const upgrade = new Upgrade();
|
||||||
upgrade._name = upgradeName;
|
upgrade.name = upgradeName;
|
||||||
upgrade._cost = GetSelectionCosts(upgradeSelection);
|
upgrade.cost = GetSelectionCosts(upgradeSelection);
|
||||||
upgrade._count = Number(upgradeSelection.getAttribute("number"));
|
upgrade.count = Number(upgradeSelection.getAttribute("number"));
|
||||||
model._upgrades.push(upgrade);
|
model.upgrades.push(upgrade);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -805,28 +779,28 @@ function ParseUnit(root) {
|
|||||||
seenProfiles.push(...unseenProfiles);
|
seenProfiles.push(...unseenProfiles);
|
||||||
if (unseenProfiles.length > 0) {
|
if (unseenProfiles.length > 0) {
|
||||||
const unitUpgradesModel = new Model();
|
const unitUpgradesModel = new Model();
|
||||||
unitUpgradesModel._name = "Unit Upgrades";
|
unitUpgradesModel.name = "Unit Upgrades";
|
||||||
ParseModelProfiles(unseenProfiles, unitUpgradesModel, unit);
|
ParseModelProfiles(unseenProfiles, unitUpgradesModel, unit);
|
||||||
if (unitUpgradesModel.weapons.length > 0 && unit._models.length > 0) {
|
if (unitUpgradesModel.weapons.length > 0 && unit.models.length > 0) {
|
||||||
// Apply weapons at the unit level to all models in the unit.
|
// Apply weapons at the unit level to all models in the unit.
|
||||||
for (const model of unit._models) {
|
for (const model of unit.models) {
|
||||||
model.weapons.push(...unitUpgradesModel.weapons);
|
model.weapons.push(...unitUpgradesModel.weapons);
|
||||||
}
|
}
|
||||||
unitUpgradesModel.weapons.length = 0; // Clear the array.
|
unitUpgradesModel.weapons.length = 0; // Clear the array.
|
||||||
}
|
}
|
||||||
if (unitUpgradesModel._psychicPowers.length > 0) {
|
if (unitUpgradesModel.psychicPowers.length > 0) {
|
||||||
// Add spells to the unit's spell list. However, we'll still need
|
// Add spells to the unit's spell list. However, we'll still need
|
||||||
// to add spell upgrade selections to the upgrade list, below.
|
// to add spell upgrade selections to the upgrade list, below.
|
||||||
unit._spells.push(...unitUpgradesModel._psychicPowers);
|
unit.spells.push(...unitUpgradesModel.psychicPowers);
|
||||||
unitUpgradesModel._psychicPowers.length = 0;
|
unitUpgradesModel.psychicPowers.length = 0;
|
||||||
}
|
}
|
||||||
if (unitUpgradesModel._psyker) {
|
if (unitUpgradesModel.psyker) {
|
||||||
unit._psykers.push(unitUpgradesModel._psyker);
|
unit.psykers.push(unitUpgradesModel.psyker);
|
||||||
unitUpgradesModel._psyker = null;
|
unitUpgradesModel.psyker = null;
|
||||||
}
|
}
|
||||||
if (unitUpgradesModel._explosions.length > 0) {
|
if (unitUpgradesModel.explosions.length > 0) {
|
||||||
unit._explosions.push(...unitUpgradesModel._explosions);
|
unit.explosions.push(...unitUpgradesModel.explosions);
|
||||||
unitUpgradesModel._explosions.length = 0;
|
unitUpgradesModel.explosions.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for any unit-level upgrade selections that we didn't catch
|
// Look for any unit-level upgrade selections that we didn't catch
|
||||||
@@ -843,24 +817,24 @@ function ParseUnit(root) {
|
|||||||
if (!name) continue;
|
if (!name) continue;
|
||||||
|
|
||||||
const upgrade = new Upgrade();
|
const upgrade = new Upgrade();
|
||||||
upgrade._name = name;
|
upgrade.name = name;
|
||||||
upgrade._cost = GetSelectionCosts(selection);
|
upgrade.cost = GetSelectionCosts(selection);
|
||||||
upgrade._count = Number(selection.getAttribute("number"));
|
upgrade.count = Number(selection.getAttribute("number"));
|
||||||
unitUpgradesModel._upgrades.push(upgrade);
|
unitUpgradesModel.upgrades.push(upgrade);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
unitUpgradesModel.weapons.length > 0 ||
|
unitUpgradesModel.weapons.length > 0 ||
|
||||||
unitUpgradesModel._upgrades.length > 0
|
unitUpgradesModel.upgrades.length > 0
|
||||||
) {
|
) {
|
||||||
unit._models.push(unitUpgradesModel);
|
unit.models.push(unitUpgradesModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only match costs->costs associated with the unit and not its children (model and weapon) costs.
|
// Only match costs->costs associated with the unit and not its children (model and weapon) costs.
|
||||||
let costs = root.querySelectorAll("costs>cost");
|
let costs = root.querySelectorAll("costs>cost");
|
||||||
for (let cost of costs) {
|
for (let cost of costs) {
|
||||||
unit._cost.add(ParseCost(cost));
|
unit.cost.add(ParseCost(cost));
|
||||||
}
|
}
|
||||||
|
|
||||||
let rules = root.querySelectorAll("rules > rule");
|
let rules = root.querySelectorAll("rules > rule");
|
||||||
@@ -879,7 +853,7 @@ function ParseModelStatsProfiles(profiles, unit, unitName) {
|
|||||||
if (!profileName || !profileType) return;
|
if (!profileName || !profileType) return;
|
||||||
|
|
||||||
const model = new Model();
|
const model = new Model();
|
||||||
model._name = profileName;
|
model.name = profileName;
|
||||||
unit.modelStats.push(model);
|
unit.modelStats.push(model);
|
||||||
|
|
||||||
ExpandBaseNotes(profile, model);
|
ExpandBaseNotes(profile, model);
|
||||||
@@ -895,10 +869,10 @@ function ParseModelStatsProfiles(profiles, unit, unitName) {
|
|||||||
model.move = char.textContent;
|
model.move = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "WS":
|
case "WS":
|
||||||
model._ws = char.textContent;
|
model.ws = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "BS":
|
case "BS":
|
||||||
model._bs = char.textContent;
|
model.bs = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "S":
|
case "S":
|
||||||
model.str = +char.textContent;
|
model.str = +char.textContent;
|
||||||
@@ -910,7 +884,7 @@ function ParseModelStatsProfiles(profiles, unit, unitName) {
|
|||||||
model.wounds = +char.textContent;
|
model.wounds = +char.textContent;
|
||||||
break;
|
break;
|
||||||
case "A":
|
case "A":
|
||||||
model._attacks = char.textContent;
|
model.attacks = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "Ld":
|
case "Ld":
|
||||||
model.leadership = +char.textContent;
|
model.leadership = +char.textContent;
|
||||||
@@ -945,15 +919,15 @@ function ParseModelProfiles(profiles, model, unit) {
|
|||||||
typeName.includes(" Wounds")
|
typeName.includes(" Wounds")
|
||||||
) {
|
) {
|
||||||
const tracker = ParseWoundTrackerProfile(profile);
|
const tracker = ParseWoundTrackerProfile(profile);
|
||||||
unit._woundTracker.push(tracker);
|
unit.woundTracker.push(tracker);
|
||||||
} else if (typeName == "Psychic Power") {
|
} else if (typeName == "Psychic Power") {
|
||||||
const power = ParsePsychicPowerProfile(profile);
|
const power = ParsePsychicPowerProfile(profile);
|
||||||
model._psychicPowers.push(power);
|
model.psychicPowers.push(power);
|
||||||
} else if (typeName.includes("Explosion")) {
|
} else if (typeName.includes("Explosion")) {
|
||||||
const explosion = ParseExplosionProfile(profile);
|
const explosion = ParseExplosionProfile(profile);
|
||||||
model._explosions.push(explosion);
|
model.explosions.push(explosion);
|
||||||
} else if (typeName == "Psyker") {
|
} else if (typeName == "Psyker") {
|
||||||
model._psyker = ParsePsykerProfile(profile);
|
model.psyker = ParsePsykerProfile(profile);
|
||||||
} else {
|
} else {
|
||||||
// Everything else, like Prayers and Warlord Traits.
|
// Everything else, like Prayers and Warlord Traits.
|
||||||
if (!unit.abilities[typeName]) unit.abilities[typeName] = new Map();
|
if (!unit.abilities[typeName]) unit.abilities[typeName] = new Map();
|
||||||
@@ -986,7 +960,7 @@ function ParseProfileCharacteristics(profile, profileName, typeName, map) {
|
|||||||
function ParseWeaponProfile(profile) {
|
function ParseWeaponProfile(profile) {
|
||||||
const weapon = new Weapon();
|
const weapon = new Weapon();
|
||||||
ExpandBaseNotes(profile, weapon);
|
ExpandBaseNotes(profile, weapon);
|
||||||
weapon._count = ExtractNumberFromParent(profile);
|
weapon.count = ExtractNumberFromParent(profile);
|
||||||
|
|
||||||
let chars = profile.querySelectorAll("characteristics>characteristic");
|
let chars = profile.querySelectorAll("characteristics>characteristic");
|
||||||
for (let char of chars) {
|
for (let char of chars) {
|
||||||
@@ -995,19 +969,19 @@ function ParseWeaponProfile(profile) {
|
|||||||
if (char.textContent) {
|
if (char.textContent) {
|
||||||
switch (charName) {
|
switch (charName) {
|
||||||
case "Range":
|
case "Range":
|
||||||
weapon._range = char.textContent;
|
weapon.range = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "Type":
|
case "Type":
|
||||||
weapon._type = char.textContent;
|
weapon.type = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "S":
|
case "S":
|
||||||
weapon.str = char.textContent;
|
weapon.str = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "AP":
|
case "AP":
|
||||||
weapon._ap = char.textContent;
|
weapon.ap = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "D":
|
case "D":
|
||||||
weapon._damage = char.textContent;
|
weapon.damage = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "Abilities":
|
case "Abilities":
|
||||||
weapon.abilities = char.textContent;
|
weapon.abilities = char.textContent;
|
||||||
@@ -1021,8 +995,8 @@ function ParseWeaponProfile(profile) {
|
|||||||
const selection = profile.parentElement?.parentElement;
|
const selection = profile.parentElement?.parentElement;
|
||||||
const selectionName = selection?.getAttribute("name");
|
const selectionName = selection?.getAttribute("name");
|
||||||
if (selection?.getAttribute("type") === "upgrade" && selectionName) {
|
if (selection?.getAttribute("type") === "upgrade" && selectionName) {
|
||||||
weapon._selectionName = selectionName;
|
weapon.selectionName = selectionName;
|
||||||
weapon._cost = GetSelectionCosts(selection);
|
weapon.cost = GetSelectionCosts(selection);
|
||||||
}
|
}
|
||||||
return weapon;
|
return weapon;
|
||||||
}
|
}
|
||||||
@@ -1035,9 +1009,9 @@ function ParseWoundTrackerProfile(profile) {
|
|||||||
const charName = char.getAttribute("name");
|
const charName = char.getAttribute("name");
|
||||||
if (charName) {
|
if (charName) {
|
||||||
if (char.textContent) {
|
if (char.textContent) {
|
||||||
tracker._table.set(charName, char.textContent);
|
tracker.table.set(charName, char.textContent);
|
||||||
} else {
|
} else {
|
||||||
tracker._table.set(charName, "-");
|
tracker.table.set(charName, "-");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1054,13 +1028,13 @@ function ParsePsychicPowerProfile(profile) {
|
|||||||
if (charName && char.textContent) {
|
if (charName && char.textContent) {
|
||||||
switch (charName) {
|
switch (charName) {
|
||||||
case "Range":
|
case "Range":
|
||||||
power._range = char.textContent;
|
power.range = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "Warp Charge":
|
case "Warp Charge":
|
||||||
power._manifest = +char.textContent;
|
power.manifest = +char.textContent;
|
||||||
break;
|
break;
|
||||||
case "Details":
|
case "Details":
|
||||||
power._details = char.textContent;
|
power.details = char.textContent;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1078,13 +1052,13 @@ function ParseExplosionProfile(profile) {
|
|||||||
if (charName && char.textContent) {
|
if (charName && char.textContent) {
|
||||||
switch (charName) {
|
switch (charName) {
|
||||||
case "Dice Roll":
|
case "Dice Roll":
|
||||||
explosion._diceRoll = char.textContent;
|
explosion.diceRoll = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "Distance":
|
case "Distance":
|
||||||
explosion._distance = char.textContent;
|
explosion.distance = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "Mortal Wounds":
|
case "Mortal Wounds":
|
||||||
explosion._mortalWounds = char.textContent;
|
explosion.mortalWounds = char.textContent;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1102,16 +1076,16 @@ function ParsePsykerProfile(profile) {
|
|||||||
if (charName && char.textContent) {
|
if (charName && char.textContent) {
|
||||||
switch (charName) {
|
switch (charName) {
|
||||||
case "Cast":
|
case "Cast":
|
||||||
psyker._cast = char.textContent;
|
psyker.cast = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "Deny":
|
case "Deny":
|
||||||
psyker._deny = char.textContent;
|
psyker.deny = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "Powers Known":
|
case "Powers Known":
|
||||||
psyker._powers = char.textContent;
|
psyker.powers = char.textContent;
|
||||||
break;
|
break;
|
||||||
case "Other":
|
case "Other":
|
||||||
psyker._other = char.textContent;
|
psyker.other = char.textContent;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1120,35 +1094,35 @@ function ParsePsykerProfile(profile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CompareObj(a, b) {
|
function CompareObj(a, b) {
|
||||||
return Compare(a._name, b._name);
|
return Compare(a.name, b.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CompareModel(a, b) {
|
function CompareModel(a, b) {
|
||||||
if (a._name === b._name) {
|
if (a.name === b.name) {
|
||||||
return Compare(a.nameAndGear(), b.nameAndGear());
|
return Compare(a.nameAndGear(), b.nameAndGear());
|
||||||
} else if (a._name === "Unit Upgrades") {
|
} else if (a.name === "Unit Upgrades") {
|
||||||
// "Unit Upgrades", a special model name, is always sorted last.
|
// "Unit Upgrades", a special model name, is always sorted last.
|
||||||
return 1;
|
return 1;
|
||||||
} else if (b._name === "Unit Upgrades") {
|
} else if (b.name === "Unit Upgrades") {
|
||||||
// "Unit Upgrades", a special model name, is always sorted last.
|
// "Unit Upgrades", a special model name, is always sorted last.
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return Compare(a._name, b._name);
|
return Compare(a.name, b.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CompareWeapon(a, b) {
|
export function CompareWeapon(a, b) {
|
||||||
const aType = a._type.startsWith("Grenade")
|
const aType = a.type.startsWith("Grenade")
|
||||||
? 2
|
? 2
|
||||||
: a._type.startsWith("Melee")
|
: a.type.startsWith("Melee")
|
||||||
? 1
|
? 1
|
||||||
: 0;
|
: 0;
|
||||||
const bType = b._type.startsWith("Grenade")
|
const bType = b.type.startsWith("Grenade")
|
||||||
? 2
|
? 2
|
||||||
: b._type.startsWith("Melee")
|
: b.type.startsWith("Melee")
|
||||||
? 1
|
? 1
|
||||||
: 0;
|
: 0;
|
||||||
return aType - bType || a.name().localeCompare(b.name());
|
return aType - bType || a.name.localeCompare(b.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Compare(a, b) {
|
export function Compare(a, b) {
|
||||||
|
|||||||
Reference in New Issue
Block a user