Initial commit: WH40K Points Comparator

- React + MUI DataGrid app with faction filter, search, change filter
- Biggest movers cards (drops/rises) scoped to current filter view
- Historical points graph modal (5 MFM versions: 1.14 → current)
- URL state sync (faction, dir, q params — shareable URLs)
- Grimdark favicon + OG embed image (Google Imagen)
- Multi-stage Dockerfile (node build → nginx serve)
- docker-compose.yml with Traefik + Cloudflare TLS
- Data pipeline: build_deduped_data.py merges PDF + live scrape
- Ynnari merged into Aeldari (shared codex)
- Mobile responsive: flex columns, no fixed pixel widths
- Color semantics: green=cheaper, red=costlier (consistent everywhere)
- 1,449 units across 31 factions
This commit is contained in:
root
2026-06-18 02:42:29 +00:00
commit 38bffa491c
66 changed files with 39243 additions and 0 deletions

14
.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
node_modules/
dist/
*.pyc
__pycache__/
.env
.DS_Store
*.log
# Don't commit the large live/pdf data directories — they're source data
live/
pdf/
pdf23/
pdf32/
pdf114/
# Keep the built data.json though (it's the merged output)

43
Dockerfile Normal file
View File

@@ -0,0 +1,43 @@
# ── Build stage: React app ──
FROM node:20-alpine AS build
WORKDIR /app
COPY react-app/package.json react-app/package-lock.json* ./
RUN npm ci --silent || npm install --silent
COPY react-app/ ./
RUN npm run build
# ── Runtime stage: nginx ──
FROM nginx:alpine
# Copy built assets from build stage
COPY --from=build /app/dist/ /usr/share/nginx/html/
# SPA-friendly nginx config with CORS headers for module scripts
RUN printf 'server {\n\
listen 80;\n\
server_name _;\n\
root /usr/share/nginx/html;\n\
index index.html;\n\
\n\
gzip on;\n\
gzip_types text/plain text/css application/javascript application/json image/svg+xml;\n\
gzip_min_length 256;\n\
\n\
# CORS + cache for static assets\n\
location ~* \.(js|css|json|png|jpg|webp|svg|ico|woff2?)$ {\n\
expires 1h;\n\
add_header Cache-Control "public, max-age=3600";\n\
add_header Access-Control-Allow-Origin "*" always;\n\
}\n\
\n\
location / {\n\
add_header Access-Control-Allow-Origin "*" always;\n\
try_files $uri $uri/ /index.html;\n\
}\n\
}\n' > /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

97
README.md Normal file
View File

@@ -0,0 +1,97 @@
# WH40K Points Comparator
Compare Warhammer 40,000 unit points across Munitorum Field Manual versions.
## Features
- **Multi-version comparison** — Track points changes across 5 MFM versions (1.14 → current)
- **Faction filtering** — Browse by faction or view all 1,449 units
- **Biggest movers** — Top 5 price drops and rises for the current view
- **Historical graph** — Click any unit to see a points history chart across all MFM versions
- **Shareable URLs** — Filter state is stored in URL query params (`?faction=...&dir=...&q=...`)
- **Mobile responsive** — Flex-based layout that scales to any device
- **Social embeds** — OG/Twitter meta tags with grimdark favicon and preview image
## Tech Stack
- React + MUI (Material UI DataGrid)
- Vite build
- nginx (Alpine) in Docker
- Traefik reverse proxy with Cloudflare TLS
## Data Sources
- **Current MFM** — Scraped from warhammer-community.com (live data)
- **MFM 4.3** (Jun 2026), **3.2** (Aug 2025), **2.3** (Mar 2025), **1.14** (Dec 2024) — Parsed from PDFs
## Deployment
### Using Docker Compose (recommended)
1. Edit `docker-compose.yml` — change the `Host()` rule to your domain
2. Build and deploy:
```bash
docker compose up -d --build
```
### Without Traefik
Uncomment the `ports` section in `docker-compose.yml`:
```yaml
ports:
- "8080:80"
```
Then access at `http://localhost:8080`.
### Building from scratch
The Dockerfile is multi-stage:
1. **Build stage**`node:20-alpine` installs deps and runs `npm run build`
2. **Runtime stage**`nginx:alpine` serves the static files
No pre-built artifacts needed — just `docker compose build`.
## Data Pipeline
```
PDFs + live scrape → per-faction JSON files
build_deduped_data.py
react-app/public/data.json
React app (fetched at runtime)
```
Run `python3 build_deduped_data.py` to rebuild `data.json` from source data.
## Project Structure
```
wh40k-factions/
├── Dockerfile # Multi-stage: node build → nginx serve
├── docker-compose.yml # Traefik + Cloudflare TLS config
├── build_deduped_data.py # Merges all MFM versions into data.json
├── parse_pdf_per_faction.py # PDF → per-faction JSON (MFM 4.3)
├── react-app/
│ ├── index.html # OG meta tags, favicon
│ ├── package.json
│ ├── vite.config.js
│ ├── public/
│ │ ├── data.json # Merged dataset (5 MFM versions)
│ │ ├── favicon.png # Grimdark Aquila icon
│ │ └── og-image.png # Social embed banner
│ └── src/
│ ├── main.jsx
│ └── App.jsx # Main app: filters, movers, DataGrid, graph modal
├── live/ # Current MFM scrape (per-faction JSON)
├── pdf/ # MFM 4.3 PDF parse (per-faction JSON)
├── pdf32/ # MFM 3.2 PDF parse
├── pdf23/ # MFM 2.3 PDF parse
└── pdf114/ # MFM 1.14 PDF parse
```
## License
MIT

298
build_deduped_data.py Normal file
View File

@@ -0,0 +1,298 @@
#!/usr/bin/env python3
"""
Rebuild react-app/public/data.json with:
1. Filter out weapon-upgrade rows (size starts with "per " or "+ ").
2. Collapse each (faction, name) into ONE row, with a `sizes` array of
{size, original, new, change_pct, change_pts, tier, history} variants.
3. Only keep sizes that the MFM (new/live) actually listed.
4. Fill missing originals by scaling proportionally to model count.
5. Build a `history` array per size with {date, version, pts} from all 3 sources:
- v3.2 PDF (Aug 20, 2025)
- v4.3 PDF (Jun 5, 2026)
- Live MFM (Jun 17, 2026)
The history data does NOT appear in the table — it's only used when the user
clicks a unit name to open the graph modal.
"""
import json
import re
import time
from pathlib import Path
from collections import defaultdict
ROOT = Path("/root/wh40k-factions")
PDF32_DIR = ROOT / "pdf32" # v3.2
PDF_DIR = ROOT / "pdf" # v4.3
LIVE_DIR = ROOT / "live" # current MFM
OUT = ROOT / "react-app" / "public" / "data.json"
OUT.parent.mkdir(parents=True, exist_ok=True)
DP_RE = re.compile(r"\b\d+DP\b", re.IGNORECASE)
DETACHMENT_TIERS = {"ENHANCEMENTS", "DETACHMENT"}
UPGRADE_PREFIXES = ("per ", "+ ")
# Version metadata (oldest → newest)
VERSIONS = [
{"version": "1.14", "date": "2024-12-01", "label": "MFM 1.14", "dir": ROOT / "pdf114"},
{"version": "2.3", "date": "2025-03-01", "label": "MFM 2.3", "dir": ROOT / "pdf23"},
{"version": "3.2", "date": "2025-08-20", "label": "MFM 3.2", "dir": PDF32_DIR},
{"version": "4.3", "date": "2026-06-05", "label": "MFM 4.3", "dir": PDF_DIR},
{"version": "current", "date": "2026-06-17", "label": "MFM (current)", "dir": LIVE_DIR},
]
def norm_name(s: str) -> str:
if not s:
return ""
s = s.lower()
s = re.sub(r"\s+", " ", s).strip()
s = re.sub(r"[^a-z0-9 ]", "", s)
return s
def norm_size(s: str) -> str:
if not s:
return ""
s = s.lower().strip()
s = re.sub(r"\s+", " ", s)
m = re.search(r"(\d+)\s*model", s)
if m:
n = int(m.group(1))
return f"{n} model{'s' if n != 1 else ''}"
return s
def is_upgrade_size(size: str) -> bool:
s = (size or "").lower().strip()
return s.startswith(UPGRADE_PREFIXES)
def is_detachment_name(name: str) -> bool:
return bool(DP_RE.search(name or ""))
def is_detachment_tier(tier) -> bool:
if not tier:
return False
return str(tier).upper() in DETACHMENT_TIERS
def model_count(s):
m = re.search(r"(\d+)", s)
return int(m.group(1)) if m else 1
def load_version(ver_info):
"""Load all rows from a version's directory. Returns dict keyed by (slug, norm_name, norm_size) -> pts."""
rows = {}
slug_dir = ver_info["dir"]
if not slug_dir.exists():
return rows
for path in sorted(slug_dir.glob("*.json")):
if path.stem.startswith("_"):
continue
data = json.load(open(path))
slug = data.get("slug", path.stem)
# Ynnari shares the Aeldari codex — merge into aeldari
if slug == "ynnari":
slug = "aeldari"
for unit, entries in data.get("units", {}).items():
if is_detachment_name(unit):
continue
for e in entries:
if ver_info["version"] == "current" and is_detachment_tier(e.get("tier")):
continue
size_disp = e.get("size", "")
if is_upgrade_size(size_disp):
continue
size = norm_size(size_disp)
if not size:
continue
k = (slug, norm_name(unit), size)
pts = e.get("pts")
if pts is not None:
# Keep lowest pts if duplicates
if k not in rows or pts < rows[k]:
rows[k] = pts
return rows
def main():
# Load each version
version_data = {}
for ver in VERSIONS:
rows = load_version(ver)
version_data[ver["version"]] = rows
print(f"{ver['label']}: {len(rows)} size-rows loaded")
# Use "current" (live) as the primary set of units/sizes
# and "4.3" as the source of "original" (old codex) values
live_rows = version_data.get("current", {})
pdf43_rows = version_data.get("4.3", {})
pdf32_rows = version_data.get("3.2", {})
# Also load faction names from live data
faction_names = {}
for path in sorted(LIVE_DIR.glob("*.json")):
if path.stem.startswith("_"):
continue
data = json.load(open(path))
slug = data.get("slug", path.stem)
faction_names[slug] = data.get("name", slug)
# Group live rows by (slug, norm_name)
groups = defaultdict(list)
for (slug, name_norm, size), pts in live_rows.items():
groups[(slug, name_norm)].append({"size": size, "new": pts})
# Also include PDF-only units (removed from MFM)
for (slug, name_norm, size), pts in pdf43_rows.items():
if (slug, name_norm, size) not in live_rows:
groups[(slug, name_norm)].append({"size": size, "new": None, "original": pts})
out_units = []
for (slug, name_norm), grp in groups.items():
# Sort by numeric size
grp.sort(key=lambda r: model_count(r["size"]))
# Deduplicate sizes (keep first occurrence)
seen_sizes = set()
unique = []
for r in grp:
if r["size"] not in seen_sizes:
seen_sizes.add(r["size"])
unique.append(r)
grp = unique
# Only keep sizes that the MFM (new/live) actually listed
mfm_sizes = [r for r in grp if r["new"] is not None]
if not mfm_sizes:
mfm_sizes = [grp[0]] # removed unit, keep one PDF entry
# Find base original (smallest size with a non-None original in 4.3 PDF)
base_orig = None
base_count = None
for (s, n, sz), pts in pdf43_rows.items():
if s == slug and n == name_norm:
if base_orig is None or model_count(sz) < base_count:
base_orig = pts
base_count = model_count(sz)
# Fill missing originals on MFM sizes by scaling from base original
for r in mfm_sizes:
if r.get("original") is None:
# Try exact match in 4.3 PDF first
key = (slug, name_norm, r["size"])
if key in pdf43_rows:
r["original"] = pdf43_rows[key]
elif base_orig is not None and base_count is not None:
cnt = model_count(r["size"])
if base_count > 0 and cnt > 0:
r["original"] = round(base_orig * cnt / base_count)
else:
r["original"] = None
# Build sizes[] array with history
sizes = []
for r in mfm_sizes:
o, n = r.get("original"), r["new"]
change_pct = round((n - o) / o * 100, 2) if (o is not None and n is not None and o > 0) else None
change_pts = (n - o) if (o is not None and n is not None) else None
# Build history for this size
history = []
for ver in VERSIONS:
key = (slug, name_norm, r["size"])
pts_map = version_data[ver["version"]]
if key in pts_map:
history.append({
"date": ver["date"],
"version": ver["label"],
"pts": pts_map[key],
})
sizes.append({
"size": r["size"],
"original": o,
"new": n,
"tier": None,
"change_pct": change_pct,
"change_pts": change_pts,
"history": history,
})
# default_size = smallest
default = sizes[0]
default_size = default["size"]
# Display name: try to find Title Case from PDF data
display_name = name_norm.title()
for path in sorted(PDF_DIR.glob("*.json")):
if path.stem.startswith("_"):
continue
data = json.load(open(path))
if data.get("slug") == slug:
for unit in data.get("units", {}):
if norm_name(unit) == name_norm:
display_name = unit
break
break
faction_name = faction_names.get(slug, slug)
out_units.append({
"faction": slug,
"faction_name": faction_name,
"name": display_name,
"size": default["size"],
"original": default["original"],
"new": default["new"],
"tier": default.get("tier"),
"change_pct": default["change_pct"],
"change_pts": default["change_pts"],
"sizes": sizes,
"default_size": default_size,
})
# Stats
has_both = sum(1 for u in out_units if u["original"] is not None and u["new"] is not None)
only_pdf = sum(1 for u in out_units if u["original"] is not None and u["new"] is None)
only_live = sum(1 for u in out_units if u["original"] is None and u["new"] is not None)
pct_changes = [u["change_pct"] for u in out_units if u["change_pct"] is not None]
pct_changes_sorted = sorted(pct_changes, key=lambda x: x)
units_with_history = sum(1 for u in out_units if any(len(s.get("history", [])) > 1 for s in u["sizes"]))
payload = {
"generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"versions": [{"date": v["date"], "label": v["label"]} for v in VERSIONS],
"factions": sorted({u["faction"] for u in out_units}),
"faction_names": {u["faction"]: u["faction_name"] for u in out_units},
"stats": {
"total_rows": len(out_units),
"rows_with_both": has_both,
"rows_pdf_only": only_pdf,
"rows_live_only": only_live,
"biggest_drop_pct": pct_changes_sorted[0] if pct_changes_sorted else None,
"biggest_rise_pct": pct_changes_sorted[-1] if pct_changes_sorted else None,
"multi_size": sum(1 for u in out_units if len(u["sizes"]) > 1),
"units_with_history": units_with_history,
},
"units": out_units,
}
OUT.write_text(json.dumps(payload, ensure_ascii=False))
print(f"\nWrote {OUT}")
print(f" total rows: {len(out_units)}")
print(f" with both: {has_both}")
print(f" PDF only: {only_pdf}")
print(f" LIVE only: {only_live}")
print(f" multi-size: {sum(1 for u in out_units if len(u['sizes']) > 1)}")
print(f" with history: {units_with_history}")
if pct_changes:
print(f" biggest drop: {pct_changes_sorted[0]:.2f}%")
print(f" biggest rise: {pct_changes_sorted[-1]:.2f}%")
print(f" size: {OUT.stat().st_size / 1024:.1f} KB")
if __name__ == "__main__":
main()

188
build_pdfs.py Normal file
View File

@@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""Visit each WH40K faction page slowly and print to PDF.
Uses the system chromium binary via Playwright so we don't need to download
the bundled headless shell. Each page gets a polite delay, waits for
content, then prints to PDF with background graphics enabled.
"""
from __future__ import annotations
import json
import re
import sys
import time
from pathlib import Path
from playwright.sync_api import sync_playwright
FACTIONS = [
("adepta-sororitas", "Adepta Sororitas", "https://mfm.warhammer-community.com/en/adepta-sororitas"),
("adeptus-custodes", "Adeptus Custodes", "https://mfm.warhammer-community.com/en/adeptus-custodes"),
("adeptus-mechanicus", "Adeptus Mechanicus", "https://mfm.warhammer-community.com/en/adeptus-mechanicus"),
("aeldari", "Aeldari", "https://mfm.warhammer-community.com/en/aeldari"),
("astra-militarum", "Astra Militarum", "https://mfm.warhammer-community.com/en/astra-militarum"),
("black-templars", "Black Templars", "https://mfm.warhammer-community.com/en/black-templars"),
("blood-angels", "Blood Angels", "https://mfm.warhammer-community.com/en/blood-angels"),
("chaos-daemons", "Chaos Daemons", "https://mfm.warhammer-community.com/en/chaos-daemons"),
("chaos-knights", "Chaos Knights", "https://mfm.warhammer-community.com/en/chaos-knights"),
("chaos-space-marines", "Chaos Space Marines", "https://mfm.warhammer-community.com/en/chaos-space-marines"),
("chaos-titan-legions", "Chaos Titan Legions", "https://mfm.warhammer-community.com/en/chaos-titan-legions"),
("dark-angels", "Dark Angels", "https://mfm.warhammer-community.com/en/dark-angels"),
("death-guard", "Death Guard", "https://mfm.warhammer-community.com/en/death-guard"),
("deathwatch", "Deathwatch", "https://mfm.warhammer-community.com/en/deathwatch"),
("drukhari", "Drukhari", "https://mfm.warhammer-community.com/en/drukhari"),
("emperors-children", "Emperor's Children", "https://mfm.warhammer-community.com/en/emperors-children"),
("genestealer-cults", "Genestealer Cults", "https://mfm.warhammer-community.com/en/genestealer-cults"),
("grey-knights", "Grey Knights", "https://mfm.warhammer-community.com/en/grey-knights"),
("imperial-agents", "Imperial Agents", "https://mfm.warhammer-community.com/en/imperial-agents"),
("imperial-knights", "Imperial Knights", "https://mfm.warhammer-community.com/en/imperial-knights"),
("leagues-of-votann", "Leagues of Votann", "https://mfm.warhammer-community.com/en/leagues-of-votann"),
("necrons", "Necrons", "https://mfm.warhammer-community.com/en/necrons"),
("orks", "Orks", "https://mfm.warhammer-community.com/en/orks"),
("space-marines", "Space Marines", "https://mfm.warhammer-community.com/en/space-marines"),
("space-wolves", "Space Wolves", "https://mfm.warhammer-community.com/en/space-wolves"),
("tau-empire", "T'au Empire", "https://mfm.warhammer-community.com/en/tau-empire"),
("thousand-sons", "Thousand Sons", "https://mfm.warhammer-community.com/en/thousand-sons"),
("titan-legions", "Titan Legions", "https://mfm.warhammer-community.com/en/titan-legions"),
("tyranids", "Tyranids", "https://mfm.warhammer-community.com/en/tyranids"),
("world-eaters", "World Eaters", "https://mfm.warhammer-community.com/en/world-eaters"),
]
OUT_DIR = Path("/root/wh40k-factions/pdfs")
LOG_DIR = Path("/root/wh40k-factions/logs")
OUT_DIR.mkdir(parents=True, exist_ok=True)
LOG_DIR.mkdir(parents=True, exist_ok=True)
CHROMIUM_BIN = "/usr/bin/chromium"
# Polite crawl pacing
PAGE_DELAY_S = 3.0 # settle time after navigation
NETWORK_IDLE_TIMEOUT_MS = 20000
SLOW_LOAD_BUFFER_MS = 4000
def slugify(s: str) -> str:
return re.sub(r"[^a-z0-9]+", "-", s.lower()).strip("-")
def fetch_one(p, slug: str, name: str, url: str, idx: int, total: int) -> dict:
"""Visit a single URL, render it, save a PDF. Return a status dict."""
pdf_path = OUT_DIR / f"{slug}.pdf"
log_path = LOG_DIR / f"{slug}.log"
status = {
"slug": slug,
"name": name,
"url": url,
"pdf": str(pdf_path),
"ok": False,
"size_bytes": 0,
"error": None,
"elapsed_s": 0.0,
}
print(f"[{idx:>2}/{total}] {name} -> {url}", flush=True)
start = time.time()
browser = None
try:
context = p.chromium.launch_persistent_context(
user_data_dir=f"/tmp/wh40k-chrome-{slug}",
executable_path=CHROMIUM_BIN,
headless=True,
viewport={"width": 1280, "height": 1800},
user_agent=(
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"
),
accept_downloads=False,
ignore_https_errors=True,
)
page = context.new_page()
# Quiet the page console
page.on("pageerror", lambda exc: print(f" pageerror: {exc}", flush=True))
page.goto(url, wait_until="domcontentloaded", timeout=45000)
# Give the Next.js client-side hydration time to run, then wait for
# network to settle (images, fonts, etc.).
try:
page.wait_for_load_state("networkidle", timeout=NETWORK_IDLE_TIMEOUT_MS)
except Exception as e:
print(f" networkidle timeout (continuing): {e}", flush=True)
# Scroll to bottom to trigger lazy-loaded images / sections, then
# back to top so the PDF starts at the header.
page.evaluate(
"""
async () => {
const sleep = ms => new Promise(r => setTimeout(r, ms));
const total = document.body.scrollHeight;
for (let y = 0; y <= total; y += 800) {
window.scrollTo(0, y);
await sleep(120);
}
window.scrollTo(0, 0);
await sleep(SLEEP);
}
""".replace("SLEEP", str(SLOW_LOAD_BUFFER_MS))
)
# Final settle
page.wait_for_timeout(int(SLOW_LOAD_BUFFER_MS))
page.pdf(
path=str(pdf_path),
format="A4",
print_background=True,
margin={"top": "10mm", "bottom": "10mm", "left": "10mm", "right": "10mm"},
prefer_css_page_size=False,
)
context.close()
if pdf_path.exists() and pdf_path.stat().st_size > 1024:
status["ok"] = True
status["size_bytes"] = pdf_path.stat().st_size
print(f" OK {pdf_path.name} ({status['size_bytes']/1024:.1f} KiB)", flush=True)
else:
status["error"] = "pdf missing or too small"
print(f" FAIL {status['error']}", flush=True)
except Exception as e:
status["error"] = repr(e)
print(f" FAIL {status['error']}", flush=True)
finally:
if browser:
try:
browser.close()
except Exception:
pass
status["elapsed_s"] = round(time.time() - start, 2)
log_path.write_text(json.dumps(status, indent=2))
return status
def main() -> int:
results = []
with sync_playwright() as p:
for i, (slug, name, url) in enumerate(FACTIONS, 1):
r = fetch_one(p, slug, name, url, i, len(FACTIONS))
results.append(r)
# Inter-page politeness delay (skip after last)
if i < len(FACTIONS):
time.sleep(PAGE_DELAY_S)
# Summary
ok = sum(1 for r in results if r["ok"])
print()
print("=" * 60)
print(f"Done. {ok}/{len(results)} factions converted.")
print("=" * 60)
for r in results:
flag = "OK" if r["ok"] else "FAIL"
size = f"{r['size_bytes']/1024:.1f} KiB" if r["ok"] else r["error"]
print(f" [{flag:>4}] {r['name']:<28} {size}")
(LOG_DIR / "_summary.json").write_text(json.dumps(results, indent=2))
return 0 if ok == len(results) else 1
if __name__ == "__main__":
sys.exit(main())

203
build_site_data.py Normal file
View File

@@ -0,0 +1,203 @@
#!/usr/bin/env python3
"""
Merge per-faction PDF (original) + LIVE (new) data into a single
client-loadable manifest. Skip detachment entries (names with DP suffix
or ENHANCEMENTS tier). Compute % change. Write to:
/root/wh40k-factions/site/data.json
Schema:
{
"generated_at": "...",
"factions": ["adepta-sororitas", ...], # ordered list
"units": [
{
"faction": "astra-militarum",
"faction_name": "Astra Militarum",
"name": "Valkyrie",
"size": "1 model",
"original": 190, # may be null if unit not in PDF
"new": 170, # may be null if unit not in LIVE
"tier": "YOUR 1ST TO 2ND UNITS COST", # may be null
"change_pct": -10.53, # may be null
"change_pts": -20
},
...
]
}
"""
import json
import re
import time
from pathlib import Path
ROOT = Path("/root/wh40k-factions")
PDF_DIR = ROOT / "pdf"
LIVE_DIR = ROOT / "live"
OUT = ROOT / "site" / "data.json"
OUT.parent.mkdir(parents=True, exist_ok=True)
# Detachments: skip any unit name with DP suffix, and skip ENHANCEMENTS-tier rows.
DP_RE = re.compile(r"\b\d+DP\b", re.IGNORECASE)
DETACHMENT_TIERS = {"ENHANCEMENTS", "DETACHMENT"}
def norm_name(s: str) -> str:
"""Normalize unit name for cross-source matching.
PDF has Title Case, LIVE has UPPERCASE — fold them together."""
if not s:
return ""
s = s.lower()
s = re.sub(r"\s+", " ", s).strip()
s = re.sub(r"[^a-z0-9 ]", "", s)
return s
def norm_size(s: str) -> str:
if not s:
return ""
s = s.lower().strip()
s = re.sub(r"\s+", " ", s)
m = re.search(r"(\d+)\s*model", s)
if m:
n = int(m.group(1))
return f"{n} model{'s' if n != 1 else ''}"
return s
def is_detachment_name(name: str) -> bool:
return bool(DP_RE.search(name or ""))
def is_detachment_tier(tier) -> bool:
if not tier:
return False
return str(tier).upper() in DETACHMENT_TIERS
def main():
# Discover factions present in both dirs
pdf_slugs = {p.stem for p in PDF_DIR.glob("*.json")
if not p.stem.startswith("_")}
live_slugs = {p.stem for p in LIVE_DIR.glob("*.json")
if not p.stem.startswith("_")}
slugs = sorted(pdf_slugs | live_slugs)
print(f"PDF factions: {len(pdf_slugs)}")
print(f"LIVE factions: {len(live_slugs)}")
print(f"Total slugs: {len(slugs)}")
# Build a { (slug, name_norm, size_norm): {pdf_pts, live_pts, tier, name_display} }
rows = {}
# PDF
for slug in slugs:
path = PDF_DIR / f"{slug}.json"
if not path.exists():
continue
data = json.load(open(path))
faction_name = data.get("name", slug)
for unit, entries in data.get("units", {}).items():
if is_detachment_name(unit):
continue
for e in entries:
size = norm_size(e.get("size"))
if not size:
continue
k = (slug, norm_name(unit), size)
rec = rows.setdefault(k, {
"faction": slug,
"faction_name": faction_name,
"name": unit,
"size": e.get("size", size), # keep display form
"original": None,
"new": None,
"tier": None,
})
# PDF may have multiple rows per (unit, size) — take the lowest as "base"
pts = e.get("pts")
if pts is not None:
if rec["original"] is None or pts < rec["original"]:
rec["original"] = pts
# LIVE
for slug in slugs:
path = LIVE_DIR / f"{slug}.json"
if not path.exists():
continue
data = json.load(open(path))
faction_name = data.get("name", slug)
for unit, entries in data.get("units", {}).items():
if is_detachment_name(unit):
continue
for e in entries:
if is_detachment_tier(e.get("tier")):
continue
size = norm_size(e.get("size"))
if not size:
continue
k = (slug, norm_name(unit), size)
rec = rows.setdefault(k, {
"faction": slug,
"faction_name": faction_name,
"name": unit,
"size": e.get("size", size),
"original": None,
"new": None,
"tier": None,
})
pts = e.get("pts")
if pts is not None:
if rec["new"] is None or pts < rec["new"]:
rec["new"] = pts
# Use the cheapest tier as the "primary" tier label
tier = e.get("tier")
if tier and not rec["tier"]:
rec["tier"] = tier
# Compute change
out_units = []
for k, r in rows.items():
o, n = r["original"], r["new"]
if o is not None and n is not None and o > 0:
r["change_pct"] = round((n - o) / o * 100, 2)
r["change_pts"] = n - o
else:
r["change_pct"] = None
r["change_pts"] = None
out_units.append(r)
# Stats
has_both = sum(1 for u in out_units if u["original"] is not None and u["new"] is not None)
only_pdf = sum(1 for u in out_units if u["original"] is not None and u["new"] is None)
only_live = sum(1 for u in out_units if u["original"] is None and u["new"] is not None)
pct_changes = [u["change_pct"] for u in out_units if u["change_pct"] is not None]
pct_changes_sorted = sorted(pct_changes, key=lambda x: x)
payload = {
"generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"factions": sorted({u["faction"] for u in out_units}),
"faction_names": {u["faction"]: u["faction_name"] for u in out_units},
"stats": {
"total_rows": len(out_units),
"rows_with_both": has_both,
"rows_pdf_only": only_pdf,
"rows_live_only": only_live,
"biggest_drop_pct": pct_changes_sorted[0] if pct_changes_sorted else None,
"biggest_rise_pct": pct_changes_sorted[-1] if pct_changes_sorted else None,
},
"units": out_units,
}
OUT.write_text(json.dumps(payload, ensure_ascii=False))
print(f"\nWrote {OUT}")
print(f" total rows: {len(out_units)}")
print(f" with both: {has_both}")
print(f" PDF only: {only_pdf}")
print(f" LIVE only: {only_live}")
if pct_changes:
print(f" biggest drop: {pct_changes_sorted[0]:.2f}%")
print(f" biggest rise: {pct_changes_sorted[-1]:.2f}%")
print(f" size: {OUT.stat().st_size / 1024:.1f} KB")
if __name__ == "__main__":
main()

1821
csv/_all_factions.csv Normal file

File diff suppressed because it is too large Load Diff

41
csv/adepta-sororitas.csv Normal file
View File

@@ -0,0 +1,41 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Adepta Sororitas,1 Mortifiers,70,70,"0,00%",,,
Adepta Sororitas,1 Penitent Engines,75,70,"-6,67%",,,
Adepta Sororitas,10 Arco-Flagellants,,140,,,,
Adepta Sororitas,10 Celestian Sacresants,,150,,165,,
Adepta Sororitas,10 Repentia Squad,,160,,,,
Adepta Sororitas,10 Seraphim Squad,,160,,,170,
Adepta Sororitas,10 Zephyrim Squad,,160,,,,
Adepta Sororitas,2 Mortifiers,,130,,,,
Adepta Sororitas,2 Penitent Engines,,140,,,,
Adepta Sororitas,3 Arco-Flagellants,45,50,"11,11%",,,
Adepta Sororitas,5 Celestian Sacresants,70,75,"7,14%",90,,
Adepta Sororitas,5 Repentia Squad,75,75,"0,00%",,,
Adepta Sororitas,5 Seraphim Squad,80,85,"6,25%",,95,
Adepta Sororitas,5 Zephyrim Squad,80,80,"0,00%",,,
Adepta Sororitas,AESTRED THURGA AND AGATHAE DOLAN,70,80,"14,29%",,,
Adepta Sororitas,BATTLE SISTERS SQUAD,105,100,"-4,76%",,,
Adepta Sororitas,CANONESS,60,60,"0,00%",,,
Adepta Sororitas,CANONESS WITH JUMP PACK,75,75,"0,00%",,,
Adepta Sororitas,CASTIGATOR,160,165,"3,12%",,175,
Adepta Sororitas,CELESTIAN INSIDIANTS,120,120,"0,00%",,,
Adepta Sororitas,DAEMONIFUGE,85,85,"0,00%",,,
Adepta Sororitas,DIALOGUS,40,40,"0,00%",,,
Adepta Sororitas,DOGMATA,45,45,"0,00%",,,
Adepta Sororitas,DOMINION SQUAD,120,100,"-16,67%",,110,
Adepta Sororitas,EXORCIST,210,180,"-14,29%",220,,
Adepta Sororitas,HOSPITALLER,60,75,"25,00%",85,,
Adepta Sororitas,IMAGIFIER,65,65,"0,00%",,,
Adepta Sororitas,IMMOLATOR,115,110,"-4,35%",,,120
Adepta Sororitas,INTRANZIA FRAYE,150,150,"0,00%",,,
Adepta Sororitas,JUNITH ERUITA,80,115,"43,75%",,,
Adepta Sororitas,MINISTORUM PRIEST,50,50,"0,00%",,,
Adepta Sororitas,MORVENN VAHL,185,185,"0,00%",,,
Adepta Sororitas,PALATINE,50,50,"0,00%",,,
Adepta Sororitas,PARAGON WARSUITS,210,180,"-14,29%",,190,
Adepta Sororitas,RETRIBUTOR SQUAD,120,105,"-12,50%",,115,
Adepta Sororitas,SAINT CELESTINE,150,150,"0,00%",,,
Adepta Sororitas,SANCTIFIERS,110,110,"0,00%",,,
Adepta Sororitas,SISTERS NOVITIATE SQUAD,100,100,"0,00%",,,
Adepta Sororitas,SORORITAS RHINO,75,75,"0,00%",,,
Adepta Sororitas,TRIUMPH OF SAINT KATHERINE,235,245,"4,26%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Adepta Sororitas 1 Mortifiers 70 70 0,00%
3 Adepta Sororitas 1 Penitent Engines 75 70 -6,67%
4 Adepta Sororitas 10 Arco-Flagellants 140
5 Adepta Sororitas 10 Celestian Sacresants 150 165
6 Adepta Sororitas 10 Repentia Squad 160
7 Adepta Sororitas 10 Seraphim Squad 160 170
8 Adepta Sororitas 10 Zephyrim Squad 160
9 Adepta Sororitas 2 Mortifiers 130
10 Adepta Sororitas 2 Penitent Engines 140
11 Adepta Sororitas 3 Arco-Flagellants 45 50 11,11%
12 Adepta Sororitas 5 Celestian Sacresants 70 75 7,14% 90
13 Adepta Sororitas 5 Repentia Squad 75 75 0,00%
14 Adepta Sororitas 5 Seraphim Squad 80 85 6,25% 95
15 Adepta Sororitas 5 Zephyrim Squad 80 80 0,00%
16 Adepta Sororitas AESTRED THURGA AND AGATHAE DOLAN 70 80 14,29%
17 Adepta Sororitas BATTLE SISTERS SQUAD 105 100 -4,76%
18 Adepta Sororitas CANONESS 60 60 0,00%
19 Adepta Sororitas CANONESS WITH JUMP PACK 75 75 0,00%
20 Adepta Sororitas CASTIGATOR 160 165 3,12% 175
21 Adepta Sororitas CELESTIAN INSIDIANTS 120 120 0,00%
22 Adepta Sororitas DAEMONIFUGE 85 85 0,00%
23 Adepta Sororitas DIALOGUS 40 40 0,00%
24 Adepta Sororitas DOGMATA 45 45 0,00%
25 Adepta Sororitas DOMINION SQUAD 120 100 -16,67% 110
26 Adepta Sororitas EXORCIST 210 180 -14,29% 220
27 Adepta Sororitas HOSPITALLER 60 75 25,00% 85
28 Adepta Sororitas IMAGIFIER 65 65 0,00%
29 Adepta Sororitas IMMOLATOR 115 110 -4,35% 120
30 Adepta Sororitas INTRANZIA FRAYE 150 150 0,00%
31 Adepta Sororitas JUNITH ERUITA 80 115 43,75%
32 Adepta Sororitas MINISTORUM PRIEST 50 50 0,00%
33 Adepta Sororitas MORVENN VAHL 185 185 0,00%
34 Adepta Sororitas PALATINE 50 50 0,00%
35 Adepta Sororitas PARAGON WARSUITS 210 180 -14,29% 190
36 Adepta Sororitas RETRIBUTOR SQUAD 120 105 -12,50% 115
37 Adepta Sororitas SAINT CELESTINE 150 150 0,00%
38 Adepta Sororitas SANCTIFIERS 110 110 0,00%
39 Adepta Sororitas SISTERS NOVITIATE SQUAD 100 100 0,00%
40 Adepta Sororitas SORORITAS RHINO 75 75 0,00%
41 Adepta Sororitas TRIUMPH OF SAINT KATHERINE 235 245 4,26%

50
csv/adeptus-custodes.csv Normal file
View File

@@ -0,0 +1,50 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Adeptus Custodes,10 Prosecutors,,85,,,,
Adeptus Custodes,10 Vigilators,,100,,,,
Adeptus Custodes,10 Witchseekers,,100,,,,
Adeptus Custodes,2 Allarus Custodians,110,110,"0,00%",,,
Adeptus Custodes,2 Vertus Praetors,150,145,"-3,33%",,,
Adeptus Custodes,3 Agamatus Custodians,225,225,"0,00%",,,
Adeptus Custodes,3 Allarus Custodians,,165,,,,
Adeptus Custodes,3 Aquilon Custodians,195,195,"0,00%",,,
Adeptus Custodes,3 Venatari Custodians,165,160,"-3,03%",,,
Adeptus Custodes,3 Vertus Praetors,,215,,,,
Adeptus Custodes,4 Custodian Guard,160,170,"6,25%",,,180
Adeptus Custodes,4 Custodian Wardens,210,210,"0,00%",230,,
Adeptus Custodes,4 Prosecutors,40,45,"12,50%",,,
Adeptus Custodes,4 Vigilators,45,50,"11,11%",,,
Adeptus Custodes,4 Witchseekers,45,50,"11,11%",,,
Adeptus Custodes,5 Allarus Custodians,,275,,,,
Adeptus Custodes,5 Custodian Guard,,215,,,,225
Adeptus Custodes,5 Custodian Wardens,,260,,280,,
Adeptus Custodes,5 Prosecutors,,50,,,,
Adeptus Custodes,5 Vigilators,,55,,,,
Adeptus Custodes,5 Witchseekers,,55,,,,
Adeptus Custodes,6 Agamatus Custodians,,450,,,,
Adeptus Custodes,6 Allarus Custodians,,330,,,,
Adeptus Custodes,6 Aquilon Custodians,,390,,,,
Adeptus Custodes,6 Venatari Custodians,,320,,,,
Adeptus Custodes,9 Prosecutors,,75,,,,
Adeptus Custodes,9 Vigilators,,90,,,,
Adeptus Custodes,9 Witchseekers,,90,,,,
Adeptus Custodes,ALEYA,65,65,"0,00%",,,
Adeptus Custodes,ANATHEMA PSYKANA RHINO,75,75,"0,00%",,,
Adeptus Custodes,ARES GUNSHIP,580,580,"0,00%",610,,
Adeptus Custodes,BLADE CHAMPION,120,110,"-8,33%",125,,
Adeptus Custodes,CALADIUS GRAV-TANK,215,210,"-2,33%",,225,
Adeptus Custodes,CONTEMPTOR-ACHILLUS DREADNOUGHT,155,155,"0,00%",,170,
Adeptus Custodes,CONTEMPTOR-GALATUS DREADNOUGHT,165,165,"0,00%",,180,
Adeptus Custodes,CORONUS GRAV-CARRIER,200,180,"-10,00%",,200,
Adeptus Custodes,CUSTODIAN GUARD WITH ADRASITE AND PYRITHITE SPEARS,,250,,,,260
Adeptus Custodes,KNIGHT-CENTURA,55,55,"0,00%",,,
Adeptus Custodes,ORION ASSAULT DROPSHIP,690,690,"0,00%",740,,
Adeptus Custodes,PALLAS GRAV-ATTACK,105,100,"-4,76%",,,
Adeptus Custodes,SAGITTARUM CUSTODIANS,225,225,"0,00%",,,
Adeptus Custodes,SHIELD-CAPTAIN,120,110,"-8,33%",,,
Adeptus Custodes,SHIELD-CAPTAIN IN ALLARUS TERMINATOR ARMOUR,,130,,,,
Adeptus Custodes,SHIELD-CAPTAIN ON DAWNEAGLE JETBIKE,150,140,"-6,67%",,,
Adeptus Custodes,TELEMON HEAVY DREADNOUGHT,225,225,"0,00%",245,,
Adeptus Custodes,TRAJANN VALORIS,140,135,"-3,57%",,,
Adeptus Custodes,VALERIAN,110,110,"0,00%",,,
Adeptus Custodes,VENERABLE CONTEMPTOR DREADNOUGHT,170,170,"0,00%",,185,
Adeptus Custodes,VENERABLE LAND RAIDER,220,220,"0,00%",240,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Adeptus Custodes 10 Prosecutors 85
3 Adeptus Custodes 10 Vigilators 100
4 Adeptus Custodes 10 Witchseekers 100
5 Adeptus Custodes 2 Allarus Custodians 110 110 0,00%
6 Adeptus Custodes 2 Vertus Praetors 150 145 -3,33%
7 Adeptus Custodes 3 Agamatus Custodians 225 225 0,00%
8 Adeptus Custodes 3 Allarus Custodians 165
9 Adeptus Custodes 3 Aquilon Custodians 195 195 0,00%
10 Adeptus Custodes 3 Venatari Custodians 165 160 -3,03%
11 Adeptus Custodes 3 Vertus Praetors 215
12 Adeptus Custodes 4 Custodian Guard 160 170 6,25% 180
13 Adeptus Custodes 4 Custodian Wardens 210 210 0,00% 230
14 Adeptus Custodes 4 Prosecutors 40 45 12,50%
15 Adeptus Custodes 4 Vigilators 45 50 11,11%
16 Adeptus Custodes 4 Witchseekers 45 50 11,11%
17 Adeptus Custodes 5 Allarus Custodians 275
18 Adeptus Custodes 5 Custodian Guard 215 225
19 Adeptus Custodes 5 Custodian Wardens 260 280
20 Adeptus Custodes 5 Prosecutors 50
21 Adeptus Custodes 5 Vigilators 55
22 Adeptus Custodes 5 Witchseekers 55
23 Adeptus Custodes 6 Agamatus Custodians 450
24 Adeptus Custodes 6 Allarus Custodians 330
25 Adeptus Custodes 6 Aquilon Custodians 390
26 Adeptus Custodes 6 Venatari Custodians 320
27 Adeptus Custodes 9 Prosecutors 75
28 Adeptus Custodes 9 Vigilators 90
29 Adeptus Custodes 9 Witchseekers 90
30 Adeptus Custodes ALEYA 65 65 0,00%
31 Adeptus Custodes ANATHEMA PSYKANA RHINO 75 75 0,00%
32 Adeptus Custodes ARES GUNSHIP 580 580 0,00% 610
33 Adeptus Custodes BLADE CHAMPION 120 110 -8,33% 125
34 Adeptus Custodes CALADIUS GRAV-TANK 215 210 -2,33% 225
35 Adeptus Custodes CONTEMPTOR-ACHILLUS DREADNOUGHT 155 155 0,00% 170
36 Adeptus Custodes CONTEMPTOR-GALATUS DREADNOUGHT 165 165 0,00% 180
37 Adeptus Custodes CORONUS GRAV-CARRIER 200 180 -10,00% 200
38 Adeptus Custodes CUSTODIAN GUARD WITH ADRASITE AND PYRITHITE SPEARS 250 260
39 Adeptus Custodes KNIGHT-CENTURA 55 55 0,00%
40 Adeptus Custodes ORION ASSAULT DROPSHIP 690 690 0,00% 740
41 Adeptus Custodes PALLAS GRAV-ATTACK 105 100 -4,76%
42 Adeptus Custodes SAGITTARUM CUSTODIANS 225 225 0,00%
43 Adeptus Custodes SHIELD-CAPTAIN 120 110 -8,33%
44 Adeptus Custodes SHIELD-CAPTAIN IN ALLARUS TERMINATOR ARMOUR 130
45 Adeptus Custodes SHIELD-CAPTAIN ON DAWNEAGLE JETBIKE 150 140 -6,67%
46 Adeptus Custodes TELEMON HEAVY DREADNOUGHT 225 225 0,00% 245
47 Adeptus Custodes TRAJANN VALORIS 140 135 -3,57%
48 Adeptus Custodes VALERIAN 110 110 0,00%
49 Adeptus Custodes VENERABLE CONTEMPTOR DREADNOUGHT 170 170 0,00% 185
50 Adeptus Custodes VENERABLE LAND RAIDER 220 220 0,00% 240

View File

@@ -0,0 +1,52 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Adeptus Mechanicus,1 Ironstrider Ballistarii,85,80,"-5,88%",,95,
Adeptus Mechanicus,1 Sydonian Dragoons With Radium Jezzails,55,55,"0,00%",,,
Adeptus Mechanicus,1 Sydonian Dragoons With Taser Lances,65,60,"-7,69%",,,
Adeptus Mechanicus,10 Corpuscarii Electro-Priests,,130,,,,
Adeptus Mechanicus,10 Fulgurite Electro-Priests,,140,,,,
Adeptus Mechanicus,10 Pteraxii Skystalkers,,150,,,160,
Adeptus Mechanicus,10 Pteraxii Sterylizors,,160,,,170,
Adeptus Mechanicus,10 Sicarian Infiltrators,,155,,,165,
Adeptus Mechanicus,10 Sicarian Ruststalkers,,160,,,170,
Adeptus Mechanicus,2 Ironstrider Ballistarii,,160,,,175,
Adeptus Mechanicus,2 Kastelan Robots,180,160,"-11,11%",180,,
Adeptus Mechanicus,2 Sydonian Dragoons With Radium Jezzails,,100,,,,
Adeptus Mechanicus,2 Sydonian Dragoons With Taser Lances,,120,,,,
Adeptus Mechanicus,3 Ironstrider Ballistarii,,240,,,255,
Adeptus Mechanicus,3 Kataphron Breachers,160,150,"-6,25%",,,
Adeptus Mechanicus,3 Kataphron Destroyers,105,100,"-4,76%",,,
Adeptus Mechanicus,3 Serberys Raiders,60,60,"0,00%",,,
Adeptus Mechanicus,3 Serberys Sulphurhounds,55,55,"0,00%",,,
Adeptus Mechanicus,3 Sydonian Dragoons With Radium Jezzails,,150,,,,
Adeptus Mechanicus,3 Sydonian Dragoons With Taser Lances,,170,,,,
Adeptus Mechanicus,4 Kastelan Robots,,320,,340,,
Adeptus Mechanicus,5 Corpuscarii Electro-Priests,65,65,"0,00%",,,
Adeptus Mechanicus,5 Fulgurite Electro-Priests,70,70,"0,00%",,,
Adeptus Mechanicus,5 Pteraxii Skystalkers,75,80,"6,67%",,90,
Adeptus Mechanicus,5 Pteraxii Sterylizors,80,80,"0,00%",,90,
Adeptus Mechanicus,5 Sicarian Infiltrators,75,75,"0,00%",,85,
Adeptus Mechanicus,5 Sicarian Ruststalkers,80,75,"-6,25%",,85,
Adeptus Mechanicus,6 Kataphron Breachers,,320,,,,
Adeptus Mechanicus,6 Kataphron Destroyers,,210,,,,
Adeptus Mechanicus,6 Serberys Raiders,,120,,,,
Adeptus Mechanicus,6 Serberys Sulphurhounds,,110,,,,
Adeptus Mechanicus,ARCHAEOPTER FUSILAVE,160,160,"0,00%",,,
Adeptus Mechanicus,ARCHAEOPTER STRATORAPTOR,185,185,"0,00%",,,
Adeptus Mechanicus,ARCHAEOPTER TRANSVECTOR,150,145,"-3,33%",,,
Adeptus Mechanicus,BELISARIUS CAWL,210,220,"4,76%",,,
Adeptus Mechanicus,CYBERNETICA DATASMITH,35,25,"-28,57%",,,
Adeptus Mechanicus,HASTARII EXTERMINATORS,135,115,"-14,81%",,130,
Adeptus Mechanicus,HASTARII FUSILIERS,145,115,"-20,69%",,130,
Adeptus Mechanicus,ONAGER DUNECRAWLER,155,155,"0,00%",,,
Adeptus Mechanicus,SERVITOR BATTLECLADE,60,65,"8,33%",,,
Adeptus Mechanicus,SKITARII MARSHAL,35,35,"0,00%",,,
Adeptus Mechanicus,SKITARII RANGERS,85,85,"0,00%",,,
Adeptus Mechanicus,SKITARII VANGUARD,95,90,"-5,26%",,,
Adeptus Mechanicus,SKORPIUS DISINTEGRATOR,165,160,"-3,03%",,,
Adeptus Mechanicus,SKORPIUS DUNERIDER,85,85,"0,00%",,,
Adeptus Mechanicus,SYDONIAN SKATROS,50,50,"0,00%",,,
Adeptus Mechanicus,TECH-PRIEST DOMINUS,65,65,"0,00%",,,
Adeptus Mechanicus,TECH-PRIEST ENGINSEER,55,55,"0,00%",,,
Adeptus Mechanicus,TECH-PRIEST MANIPULUS,60,60,"0,00%",,,
Adeptus Mechanicus,TECHNOARCHEOLOGIST,45,45,"0,00%",,,
Adeptus Mechanicus,THULIA GHULD,210,180,"-14,29%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Adeptus Mechanicus 1 Ironstrider Ballistarii 85 80 -5,88% 95
3 Adeptus Mechanicus 1 Sydonian Dragoons With Radium Jezzails 55 55 0,00%
4 Adeptus Mechanicus 1 Sydonian Dragoons With Taser Lances 65 60 -7,69%
5 Adeptus Mechanicus 10 Corpuscarii Electro-Priests 130
6 Adeptus Mechanicus 10 Fulgurite Electro-Priests 140
7 Adeptus Mechanicus 10 Pteraxii Skystalkers 150 160
8 Adeptus Mechanicus 10 Pteraxii Sterylizors 160 170
9 Adeptus Mechanicus 10 Sicarian Infiltrators 155 165
10 Adeptus Mechanicus 10 Sicarian Ruststalkers 160 170
11 Adeptus Mechanicus 2 Ironstrider Ballistarii 160 175
12 Adeptus Mechanicus 2 Kastelan Robots 180 160 -11,11% 180
13 Adeptus Mechanicus 2 Sydonian Dragoons With Radium Jezzails 100
14 Adeptus Mechanicus 2 Sydonian Dragoons With Taser Lances 120
15 Adeptus Mechanicus 3 Ironstrider Ballistarii 240 255
16 Adeptus Mechanicus 3 Kataphron Breachers 160 150 -6,25%
17 Adeptus Mechanicus 3 Kataphron Destroyers 105 100 -4,76%
18 Adeptus Mechanicus 3 Serberys Raiders 60 60 0,00%
19 Adeptus Mechanicus 3 Serberys Sulphurhounds 55 55 0,00%
20 Adeptus Mechanicus 3 Sydonian Dragoons With Radium Jezzails 150
21 Adeptus Mechanicus 3 Sydonian Dragoons With Taser Lances 170
22 Adeptus Mechanicus 4 Kastelan Robots 320 340
23 Adeptus Mechanicus 5 Corpuscarii Electro-Priests 65 65 0,00%
24 Adeptus Mechanicus 5 Fulgurite Electro-Priests 70 70 0,00%
25 Adeptus Mechanicus 5 Pteraxii Skystalkers 75 80 6,67% 90
26 Adeptus Mechanicus 5 Pteraxii Sterylizors 80 80 0,00% 90
27 Adeptus Mechanicus 5 Sicarian Infiltrators 75 75 0,00% 85
28 Adeptus Mechanicus 5 Sicarian Ruststalkers 80 75 -6,25% 85
29 Adeptus Mechanicus 6 Kataphron Breachers 320
30 Adeptus Mechanicus 6 Kataphron Destroyers 210
31 Adeptus Mechanicus 6 Serberys Raiders 120
32 Adeptus Mechanicus 6 Serberys Sulphurhounds 110
33 Adeptus Mechanicus ARCHAEOPTER FUSILAVE 160 160 0,00%
34 Adeptus Mechanicus ARCHAEOPTER STRATORAPTOR 185 185 0,00%
35 Adeptus Mechanicus ARCHAEOPTER TRANSVECTOR 150 145 -3,33%
36 Adeptus Mechanicus BELISARIUS CAWL 210 220 4,76%
37 Adeptus Mechanicus CYBERNETICA DATASMITH 35 25 -28,57%
38 Adeptus Mechanicus HASTARII EXTERMINATORS 135 115 -14,81% 130
39 Adeptus Mechanicus HASTARII FUSILIERS 145 115 -20,69% 130
40 Adeptus Mechanicus ONAGER DUNECRAWLER 155 155 0,00%
41 Adeptus Mechanicus SERVITOR BATTLECLADE 60 65 8,33%
42 Adeptus Mechanicus SKITARII MARSHAL 35 35 0,00%
43 Adeptus Mechanicus SKITARII RANGERS 85 85 0,00%
44 Adeptus Mechanicus SKITARII VANGUARD 95 90 -5,26%
45 Adeptus Mechanicus SKORPIUS DISINTEGRATOR 165 160 -3,03%
46 Adeptus Mechanicus SKORPIUS DUNERIDER 85 85 0,00%
47 Adeptus Mechanicus SYDONIAN SKATROS 50 50 0,00%
48 Adeptus Mechanicus TECH-PRIEST DOMINUS 65 65 0,00%
49 Adeptus Mechanicus TECH-PRIEST ENGINSEER 55 55 0,00%
50 Adeptus Mechanicus TECH-PRIEST MANIPULUS 60 60 0,00%
51 Adeptus Mechanicus TECHNOARCHEOLOGIST 45 45 0,00%
52 Adeptus Mechanicus THULIA GHULD 210 180 -14,29%

98
csv/aeldari.csv Normal file
View File

@@ -0,0 +1,98 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Aeldari,1 Starfangs,,70,,,,
Aeldari,1 Vyper,75,75,"0,00%",,,
Aeldari,1 War Walkers,,85,,,,
Aeldari,1 Warlock Skyrunners,45,45,"0,00%",,,
Aeldari,10 Corsair Skyreavers,,150,,,160,
Aeldari,10 Corsair Voidreavers,,110,,,,
Aeldari,10 Corsair Voidscarred,,160,,,,
Aeldari,10 Dark Reapers,,210,,,,
Aeldari,10 Dire Avengers,,150,,,,
Aeldari,10 Fire Dragons,,240,,,250,
Aeldari,10 Howling Banshees,,165,,,,
Aeldari,10 Rangers,,110,,,,
Aeldari,10 Striking Scorpions,,145,,,,
Aeldari,10 Swooping Hawks,,190,,,205,
Aeldari,10 Warp Spiders,,200,,,215,
Aeldari,10 Ynnari Incubi,,160,,,170,
Aeldari,11 Troupe,,190,,,,
Aeldari,12 Troupe,,205,,,,
Aeldari,2 Skyweavers,95,105,"10,53%",,,
Aeldari,2 Starfangs,,140,,,,
Aeldari,2 Vyper,,140,,,,
Aeldari,2 War Walkers,,170,,,,
Aeldari,2 Warlock Conclave,55,55,"0,00%",,,
Aeldari,2 Warlock Skyrunners,,90,,,,
Aeldari,3 Shining Spears,110,105,"-4,55%",,,
Aeldari,3 Shroud Runners,80,95,"18,75%",,,
Aeldari,3 Windriders,80,80,"0,00%",,,
Aeldari,3 Ynnari Reavers,,65,,,,
Aeldari,4 Skyweavers,,210,,,,
Aeldari,4 Warlock Conclave,,120,,,,
Aeldari,5 Corsair Skyreavers,75,80,"6,67%",,90,
Aeldari,5 Corsair Voidreavers,65,65,"0,00%",,,
Aeldari,5 Corsair Voidscarred,80,80,"0,00%",,,
Aeldari,5 Dark Reapers,90,100,"11,11%",,,
Aeldari,5 Dire Avengers,75,75,"0,00%",,,
Aeldari,5 Fire Dragons,120,120,"0,00%",,130,
Aeldari,5 Howling Banshees,95,85,"-10,53%",,,
Aeldari,5 Rangers,55,60,"9,09%",,,
Aeldari,5 Striking Scorpions,85,75,"-11,76%",,,
Aeldari,5 Swooping Hawks,95,100,"5,26%",,115,
Aeldari,5 Troupe,85,85,"0,00%",,,
Aeldari,5 Warp Spiders,105,115,"9,52%",,130,
Aeldari,5 Ynnari Incubi,,80,,,90,
Aeldari,6 Shining Spears,,210,,,,
Aeldari,6 Shroud Runners,,175,,,,
Aeldari,6 Troupe,,100,,,,
Aeldari,6 Windriders,,160,,,,
Aeldari,6 Ynnari Reavers,,120,,,,
Aeldari,ASURMEN,135,135,"0,00%",,,
Aeldari,AUTARCH,85,85,"0,00%",,,
Aeldari,AUTARCH WAYLEAPER,80,80,"0,00%",,,
Aeldari,AVATAR OF KHAINE,280,265,"-5,36%",,,
Aeldari,BAHARROTH,115,125,"8,70%",,,
Aeldari,CRIMSON HUNTER,160,160,"0,00%",,,
Aeldari,D-CANNON PLATFORM,125,110,"-12,00%",125,,
Aeldari,DEATH JESTER,90,80,"-11,11%",,,
Aeldari,ELDRAD ULTHRAN,120,130,"8,33%",,,
Aeldari,FALCON,130,130,"0,00%",,,
Aeldari,FARSEER,70,70,"0,00%",,,
Aeldari,FARSEER SKYRUNNER,80,70,"-12,50%",,,
Aeldari,FIRE PRISM,150,150,"0,00%",,,
Aeldari,FUEGAN,120,130,"8,33%",,,
Aeldari,GUARDIAN DEFENDERS,100,90,"-10,00%",,,
Aeldari,HEMLOCK WRAITHFIGHTER,155,155,"0,00%",,,
Aeldari,JAIN ZAR,120,105,"-12,50%",,,
Aeldari,KHARSETH,95,85,"-10,53%",,,
Aeldari,LHYKHIS,135,135,"0,00%",,,
Aeldari,MAUGAN RA,100,100,"0,00%",,,
Aeldari,NIGHT SPINNER,190,170,"-10,53%",190,,
Aeldari,PHANTOM TITAN,,2100,,,,
Aeldari,PRINCE YRIEL,95,95,"0,00%",,,
Aeldari,REVENANT TITAN,,1100,,,,
Aeldari,SHADOW WEAVER PLATFORM,75,60,"-20,00%",,,
Aeldari,SHADOWSEER,60,60,"0,00%",,,
Aeldari,SOLITAIRE,115,115,"0,00%",,,
Aeldari,SPIRITSEER,65,55,"-15,38%",,,
Aeldari,STARWEAVER,80,80,"0,00%",,,
Aeldari,STORM GUARDIANS,110,110,"0,00%",,,
Aeldari,THE VISARCH,,90,,,,
Aeldari,THE YNCARNE,,245,,,,
Aeldari,TROUPE MASTER,75,75,"0,00%",,,
Aeldari,VIBRO CANNON PLATFORM,60,60,"0,00%",,,
Aeldari,VOIDWEAVER,125,115,"-8,00%",,,
Aeldari,WARLOCK,45,45,"0,00%",,,
Aeldari,WAVE SERPENT,125,125,"0,00%",,,
Aeldari,WRAITHBLADES,150,140,"-6,67%",,,
Aeldari,WRAITHGUARD,160,145,"-9,38%",,,
Aeldari,WRAITHKNIGHT,435,415,"-4,60%",435,,
Aeldari,WRAITHKNIGHT WITH GHOSTGLAIVE,420,405,"-3,57%",425,,
Aeldari,WRAITHLORD,130,125,"-3,85%",,,
Aeldari,YNNARI ARCHON,,85,,,,
Aeldari,YNNARI KABALITE WARRIORS,,110,,,,
Aeldari,YNNARI RAIDER,,80,,,,
Aeldari,YNNARI SUCCUBUS,,45,,,,
Aeldari,YNNARI VENOM,,70,,,,
Aeldari,YNNARI WYCHES,,90,,,,
Aeldari,YVRAINE,,100,,,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Aeldari 1 Starfangs 70
3 Aeldari 1 Vyper 75 75 0,00%
4 Aeldari 1 War Walkers 85
5 Aeldari 1 Warlock Skyrunners 45 45 0,00%
6 Aeldari 10 Corsair Skyreavers 150 160
7 Aeldari 10 Corsair Voidreavers 110
8 Aeldari 10 Corsair Voidscarred 160
9 Aeldari 10 Dark Reapers 210
10 Aeldari 10 Dire Avengers 150
11 Aeldari 10 Fire Dragons 240 250
12 Aeldari 10 Howling Banshees 165
13 Aeldari 10 Rangers 110
14 Aeldari 10 Striking Scorpions 145
15 Aeldari 10 Swooping Hawks 190 205
16 Aeldari 10 Warp Spiders 200 215
17 Aeldari 10 Ynnari Incubi 160 170
18 Aeldari 11 Troupe 190
19 Aeldari 12 Troupe 205
20 Aeldari 2 Skyweavers 95 105 10,53%
21 Aeldari 2 Starfangs 140
22 Aeldari 2 Vyper 140
23 Aeldari 2 War Walkers 170
24 Aeldari 2 Warlock Conclave 55 55 0,00%
25 Aeldari 2 Warlock Skyrunners 90
26 Aeldari 3 Shining Spears 110 105 -4,55%
27 Aeldari 3 Shroud Runners 80 95 18,75%
28 Aeldari 3 Windriders 80 80 0,00%
29 Aeldari 3 Ynnari Reavers 65
30 Aeldari 4 Skyweavers 210
31 Aeldari 4 Warlock Conclave 120
32 Aeldari 5 Corsair Skyreavers 75 80 6,67% 90
33 Aeldari 5 Corsair Voidreavers 65 65 0,00%
34 Aeldari 5 Corsair Voidscarred 80 80 0,00%
35 Aeldari 5 Dark Reapers 90 100 11,11%
36 Aeldari 5 Dire Avengers 75 75 0,00%
37 Aeldari 5 Fire Dragons 120 120 0,00% 130
38 Aeldari 5 Howling Banshees 95 85 -10,53%
39 Aeldari 5 Rangers 55 60 9,09%
40 Aeldari 5 Striking Scorpions 85 75 -11,76%
41 Aeldari 5 Swooping Hawks 95 100 5,26% 115
42 Aeldari 5 Troupe 85 85 0,00%
43 Aeldari 5 Warp Spiders 105 115 9,52% 130
44 Aeldari 5 Ynnari Incubi 80 90
45 Aeldari 6 Shining Spears 210
46 Aeldari 6 Shroud Runners 175
47 Aeldari 6 Troupe 100
48 Aeldari 6 Windriders 160
49 Aeldari 6 Ynnari Reavers 120
50 Aeldari ASURMEN 135 135 0,00%
51 Aeldari AUTARCH 85 85 0,00%
52 Aeldari AUTARCH WAYLEAPER 80 80 0,00%
53 Aeldari AVATAR OF KHAINE 280 265 -5,36%
54 Aeldari BAHARROTH 115 125 8,70%
55 Aeldari CRIMSON HUNTER 160 160 0,00%
56 Aeldari D-CANNON PLATFORM 125 110 -12,00% 125
57 Aeldari DEATH JESTER 90 80 -11,11%
58 Aeldari ELDRAD ULTHRAN 120 130 8,33%
59 Aeldari FALCON 130 130 0,00%
60 Aeldari FARSEER 70 70 0,00%
61 Aeldari FARSEER SKYRUNNER 80 70 -12,50%
62 Aeldari FIRE PRISM 150 150 0,00%
63 Aeldari FUEGAN 120 130 8,33%
64 Aeldari GUARDIAN DEFENDERS 100 90 -10,00%
65 Aeldari HEMLOCK WRAITHFIGHTER 155 155 0,00%
66 Aeldari JAIN ZAR 120 105 -12,50%
67 Aeldari KHARSETH 95 85 -10,53%
68 Aeldari LHYKHIS 135 135 0,00%
69 Aeldari MAUGAN RA 100 100 0,00%
70 Aeldari NIGHT SPINNER 190 170 -10,53% 190
71 Aeldari PHANTOM TITAN 2100
72 Aeldari PRINCE YRIEL 95 95 0,00%
73 Aeldari REVENANT TITAN 1100
74 Aeldari SHADOW WEAVER PLATFORM 75 60 -20,00%
75 Aeldari SHADOWSEER 60 60 0,00%
76 Aeldari SOLITAIRE 115 115 0,00%
77 Aeldari SPIRITSEER 65 55 -15,38%
78 Aeldari STARWEAVER 80 80 0,00%
79 Aeldari STORM GUARDIANS 110 110 0,00%
80 Aeldari THE VISARCH 90
81 Aeldari THE YNCARNE 245
82 Aeldari TROUPE MASTER 75 75 0,00%
83 Aeldari VIBRO CANNON PLATFORM 60 60 0,00%
84 Aeldari VOIDWEAVER 125 115 -8,00%
85 Aeldari WARLOCK 45 45 0,00%
86 Aeldari WAVE SERPENT 125 125 0,00%
87 Aeldari WRAITHBLADES 150 140 -6,67%
88 Aeldari WRAITHGUARD 160 145 -9,38%
89 Aeldari WRAITHKNIGHT 435 415 -4,60% 435
90 Aeldari WRAITHKNIGHT WITH GHOSTGLAIVE 420 405 -3,57% 425
91 Aeldari WRAITHLORD 130 125 -3,85%
92 Aeldari YNNARI ARCHON 85
93 Aeldari YNNARI KABALITE WARRIORS 110
94 Aeldari YNNARI RAIDER 80
95 Aeldari YNNARI SUCCUBUS 45
96 Aeldari YNNARI VENOM 70
97 Aeldari YNNARI WYCHES 90
98 Aeldari YVRAINE 100

86
csv/astra-militarum.csv Normal file
View File

@@ -0,0 +1,86 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Astra Militarum,1 Armoured Sentinels,65,65,"0,00%",,,
Astra Militarum,1 Hippogriff Afv,70,70,"0,00%",,,
Astra Militarum,1 Scout Sentinels,55,55,"0,00%",,,
Astra Militarum,10 Attilan Rough Riders,,120,,,125,
Astra Militarum,10 Cadian Shock Troops,65,75,"15,38%",,,
Astra Militarum,10 Catachan Jungle Fighters,65,75,"15,38%",,,
Astra Militarum,10 Death Korps Of Krieg,65,75,"15,38%",,,
Astra Militarum,10 Death Riders,,110,,,,
Astra Militarum,10 Krieg Combat Engineers,,95,,,105,
Astra Militarum,10 Ratlings,,100,,,,
Astra Militarum,10 Tempestus Scions,,155,,,165,
Astra Militarum,2 Armoured Sentinels,,120,,,,
Astra Militarum,2 Hippogriff Afv,,140,,,,
Astra Militarum,2 Scout Sentinels,,100,,,,
Astra Militarum,20 Cadian Shock Troops,,145,,,,
Astra Militarum,20 Catachan Jungle Fighters,,145,,,,
Astra Militarum,20 Death Korps Of Krieg,,145,,,,
Astra Militarum,3 Bullgryn Squad,100,90,"-10,00%",105,,
Astra Militarum,3 Ogryn Squad,60,60,"0,00%",,,
Astra Militarum,5 Attilan Rough Riders,60,60,"0,00%",,65,
Astra Militarum,5 Death Riders,60,60,"0,00%",,,
Astra Militarum,5 Krieg Combat Engineers,60,65,"8,33%",,75,
Astra Militarum,5 Ratlings,60,60,"0,00%",,,
Astra Militarum,5 Tempestus Scions,70,75,"7,14%",,85,
Astra Militarum,6 Bullgryn Squad,,200,,215,,
Astra Militarum,6 Ogryn Squad,,120,,,,
Astra Militarum,AEGIS DEFENCE LINE,145,145,"0,00%",,,
Astra Militarum,ARTILLERY TEAM,95,95,"0,00%",,,
Astra Militarum,AVENGER STRIKE FIGHTER,,130,,,,
Astra Militarum,BANEBLADE,450,450,"0,00%",470,,
Astra Militarum,BANEHAMMER,420,420,"0,00%",440,,
Astra Militarum,BANESWORD,450,450,"0,00%",470,,
Astra Militarum,BASILISK,140,115,"-17,86%",135,,
Astra Militarum,CADIAN CASTELLAN,55,55,"0,00%",,,
Astra Militarum,CADIAN COMMAND SQUAD,65,65,"0,00%",,,
Astra Militarum,CADIAN HEAVY WEAPONS SQUAD,65,65,"0,00%",,,
Astra Militarum,CADIAN RECON SQUAD,80,80,"0,00%",,,
Astra Militarum,CATACHAN COMMAND SQUAD,65,60,"-7,69%",,,
Astra Militarum,CATACHAN HEAVY WEAPONS SQUAD,65,65,"0,00%",,,
Astra Militarum,CENTAUR RSV,85,75,"-11,76%",,,
Astra Militarum,CHIMERA,85,85,"0,00%",,,
Astra Militarum,COMMISSAR,30,30,"0,00%",,,
Astra Militarum,COMMISSAR GRAVES,110,125,"13,64%",,,
Astra Militarum,COMMISSAR GRAVES ON FOOT,65,65,"0,00%",,,
Astra Militarum,COMMISSAR YARRICK,150,130,"-13,33%",,,
Astra Militarum,CYCLOPS DEMOLITION VEHICLE,,40,,,45,
Astra Militarum,DEATHSTRIKE,145,125,"-13,79%",135,,
Astra Militarum,DOOMHAMMER,415,415,"0,00%",435,,
Astra Militarum,FIELD ORDNANCE BATTERY,110,90,"-18,18%",,,
Astra Militarum,GAUNTS GHOSTS,100,95,"-5,00%",,,
Astra Militarum,HELLHAMMER,420,420,"0,00%",440,,
Astra Militarum,HELLHOUND,125,125,"0,00%",,135,
Astra Militarum,HYDRA,95,90,"-5,26%",,,
Astra Militarum,KASRKIN,110,115,"4,55%",,125,
Astra Militarum,KRIEG COMMAND SQUAD,65,65,"0,00%",,,
Astra Militarum,KRIEG HEAVY WEAPONS SQUAD,75,60,"-20,00%",,,
Astra Militarum,LEMAN RUSS BATTLE TANK,185,185,"0,00%",,195,
Astra Militarum,LEMAN RUSS COMMANDER,235,235,"0,00%",,250,
Astra Militarum,LEMAN RUSS DEMOLISHER,190,190,"0,00%",,200,
Astra Militarum,LEMAN RUSS ERADICATOR,170,170,"0,00%",,180,
Astra Militarum,LEMAN RUSS EXECUTIONER,170,170,"0,00%",,180,
Astra Militarum,LEMAN RUSS EXTERMINATOR,180,180,"0,00%",,190,
Astra Militarum,LEMAN RUSS PUNISHER,150,150,"0,00%",,160,
Astra Militarum,LEMAN RUSS VANQUISHER,145,150,"3,45%",,160,
Astra Militarum,LORD MARSHAL DREIR,100,75,"-25,00%",,,
Astra Militarum,LORD SOLAR LEONTUS,130,130,"0,00%",,,
Astra Militarum,MANTICORE,165,150,"-9,09%",170,,
Astra Militarum,MILITARUM TEMPESTUS COMMAND SQUAD,85,85,"0,00%",95,,
Astra Militarum,MINISTORUM PRIEST,35,35,"0,00%",,,
Astra Militarum,NORK DEDDOG,60,60,"0,00%",,,
Astra Militarum,OGRYN BODYGUARD,40,40,"0,00%",,,
Astra Militarum,PRIMARIS PSYKER,60,60,"0,00%",,,
Astra Militarum,ROGAL DORN BATTLE TANK,260,260,"0,00%",275,,
Astra Militarum,ROGAL DORN COMMANDER,290,290,"0,00%",305,,
Astra Militarum,SHADOWSWORD,410,410,"0,00%",430,,
Astra Militarum,SLY MARBO,55,55,"0,00%",,,
Astra Militarum,STORMLORD,430,430,"0,00%",450,,
Astra Militarum,STORMSWORD,465,465,"0,00%",485,,
Astra Militarum,TAUROX,75,75,"0,00%",,,
Astra Militarum,TAUROX PRIME,90,85,"-5,56%",,,
Astra Militarum,TECH-PRIEST ENGINSEER,45,45,"0,00%",,,
Astra Militarum,TEMPESTUS AQUILONS,100,100,"0,00%",,,
Astra Militarum,URSULA CREED,85,85,"0,00%",,,
Astra Militarum,VALKYRIE,,170,,,180,
Astra Militarum,WYVERN,,95,,115,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Astra Militarum 1 Armoured Sentinels 65 65 0,00%
3 Astra Militarum 1 Hippogriff Afv 70 70 0,00%
4 Astra Militarum 1 Scout Sentinels 55 55 0,00%
5 Astra Militarum 10 Attilan Rough Riders 120 125
6 Astra Militarum 10 Cadian Shock Troops 65 75 15,38%
7 Astra Militarum 10 Catachan Jungle Fighters 65 75 15,38%
8 Astra Militarum 10 Death Korps Of Krieg 65 75 15,38%
9 Astra Militarum 10 Death Riders 110
10 Astra Militarum 10 Krieg Combat Engineers 95 105
11 Astra Militarum 10 Ratlings 100
12 Astra Militarum 10 Tempestus Scions 155 165
13 Astra Militarum 2 Armoured Sentinels 120
14 Astra Militarum 2 Hippogriff Afv 140
15 Astra Militarum 2 Scout Sentinels 100
16 Astra Militarum 20 Cadian Shock Troops 145
17 Astra Militarum 20 Catachan Jungle Fighters 145
18 Astra Militarum 20 Death Korps Of Krieg 145
19 Astra Militarum 3 Bullgryn Squad 100 90 -10,00% 105
20 Astra Militarum 3 Ogryn Squad 60 60 0,00%
21 Astra Militarum 5 Attilan Rough Riders 60 60 0,00% 65
22 Astra Militarum 5 Death Riders 60 60 0,00%
23 Astra Militarum 5 Krieg Combat Engineers 60 65 8,33% 75
24 Astra Militarum 5 Ratlings 60 60 0,00%
25 Astra Militarum 5 Tempestus Scions 70 75 7,14% 85
26 Astra Militarum 6 Bullgryn Squad 200 215
27 Astra Militarum 6 Ogryn Squad 120
28 Astra Militarum AEGIS DEFENCE LINE 145 145 0,00%
29 Astra Militarum ARTILLERY TEAM 95 95 0,00%
30 Astra Militarum AVENGER STRIKE FIGHTER 130
31 Astra Militarum BANEBLADE 450 450 0,00% 470
32 Astra Militarum BANEHAMMER 420 420 0,00% 440
33 Astra Militarum BANESWORD 450 450 0,00% 470
34 Astra Militarum BASILISK 140 115 -17,86% 135
35 Astra Militarum CADIAN CASTELLAN 55 55 0,00%
36 Astra Militarum CADIAN COMMAND SQUAD 65 65 0,00%
37 Astra Militarum CADIAN HEAVY WEAPONS SQUAD 65 65 0,00%
38 Astra Militarum CADIAN RECON SQUAD 80 80 0,00%
39 Astra Militarum CATACHAN COMMAND SQUAD 65 60 -7,69%
40 Astra Militarum CATACHAN HEAVY WEAPONS SQUAD 65 65 0,00%
41 Astra Militarum CENTAUR RSV 85 75 -11,76%
42 Astra Militarum CHIMERA 85 85 0,00%
43 Astra Militarum COMMISSAR 30 30 0,00%
44 Astra Militarum COMMISSAR GRAVES 110 125 13,64%
45 Astra Militarum COMMISSAR GRAVES ON FOOT 65 65 0,00%
46 Astra Militarum COMMISSAR YARRICK 150 130 -13,33%
47 Astra Militarum CYCLOPS DEMOLITION VEHICLE 40 45
48 Astra Militarum DEATHSTRIKE 145 125 -13,79% 135
49 Astra Militarum DOOMHAMMER 415 415 0,00% 435
50 Astra Militarum FIELD ORDNANCE BATTERY 110 90 -18,18%
51 Astra Militarum GAUNT’S GHOSTS 100 95 -5,00%
52 Astra Militarum HELLHAMMER 420 420 0,00% 440
53 Astra Militarum HELLHOUND 125 125 0,00% 135
54 Astra Militarum HYDRA 95 90 -5,26%
55 Astra Militarum KASRKIN 110 115 4,55% 125
56 Astra Militarum KRIEG COMMAND SQUAD 65 65 0,00%
57 Astra Militarum KRIEG HEAVY WEAPONS SQUAD 75 60 -20,00%
58 Astra Militarum LEMAN RUSS BATTLE TANK 185 185 0,00% 195
59 Astra Militarum LEMAN RUSS COMMANDER 235 235 0,00% 250
60 Astra Militarum LEMAN RUSS DEMOLISHER 190 190 0,00% 200
61 Astra Militarum LEMAN RUSS ERADICATOR 170 170 0,00% 180
62 Astra Militarum LEMAN RUSS EXECUTIONER 170 170 0,00% 180
63 Astra Militarum LEMAN RUSS EXTERMINATOR 180 180 0,00% 190
64 Astra Militarum LEMAN RUSS PUNISHER 150 150 0,00% 160
65 Astra Militarum LEMAN RUSS VANQUISHER 145 150 3,45% 160
66 Astra Militarum LORD MARSHAL DREIR 100 75 -25,00%
67 Astra Militarum LORD SOLAR LEONTUS 130 130 0,00%
68 Astra Militarum MANTICORE 165 150 -9,09% 170
69 Astra Militarum MILITARUM TEMPESTUS COMMAND SQUAD 85 85 0,00% 95
70 Astra Militarum MINISTORUM PRIEST 35 35 0,00%
71 Astra Militarum NORK DEDDOG 60 60 0,00%
72 Astra Militarum OGRYN BODYGUARD 40 40 0,00%
73 Astra Militarum PRIMARIS PSYKER 60 60 0,00%
74 Astra Militarum ROGAL DORN BATTLE TANK 260 260 0,00% 275
75 Astra Militarum ROGAL DORN COMMANDER 290 290 0,00% 305
76 Astra Militarum SHADOWSWORD 410 410 0,00% 430
77 Astra Militarum SLY MARBO 55 55 0,00%
78 Astra Militarum STORMLORD 430 430 0,00% 450
79 Astra Militarum STORMSWORD 465 465 0,00% 485
80 Astra Militarum TAUROX 75 75 0,00%
81 Astra Militarum TAUROX PRIME 90 85 -5,56%
82 Astra Militarum TECH-PRIEST ENGINSEER 45 45 0,00%
83 Astra Militarum TEMPESTUS AQUILONS 100 100 0,00%
84 Astra Militarum URSULA CREED 85 85 0,00%
85 Astra Militarum VALKYRIE 170 180
86 Astra Militarum WYVERN 95 115

116
csv/black-templars.csv Normal file
View File

@@ -0,0 +1,116 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Black Templars,1 Firestrike Servo-Turrets,75,75,"0,00%",,,
Black Templars,10 Assault Intercessor Squad,,150,,,,
Black Templars,10 Assault Intercessors With Jump Packs,,160,,,170,
Black Templars,10 Devastator Squad,,200,,,,
Black Templars,10 Heavy Intercessor Squad,,200,,,,
Black Templars,10 Hellblaster Squad,,220,,,,
Black Templars,10 Incursor Squad,,150,,,160,
Black Templars,10 Infernus Squad,,170,,,,
Black Templars,10 Infiltrator Squad,,180,,,190,
Black Templars,10 Intercessor Squad,,150,,,,
Black Templars,10 Reiver Squad,,150,,,,
Black Templars,10 Scout Squad,,120,,,130,
Black Templars,10 Sternguard Veteran Squad,,160,,,,
Black Templars,10 Sword Brethren Squad,,260,,,275,
Black Templars,10 Terminator Assault Squad,,310,,,,
Black Templars,10 Terminator Squad,,320,,,,
Black Templars,10 Vanguard Veteran Squad With Jump Packs,,200,,,210,
Black Templars,2 Firestrike Servo-Turrets,,150,,,,
Black Templars,3 Aggressor Squad,95,90,"-5,26%",,100,
Black Templars,3 Bladeguard Veteran Squad,80,80,"0,00%",,90,
Black Templars,3 Centurion Assault Squad,150,150,"0,00%",,,
Black Templars,3 Centurion Devastator Squad,175,175,"0,00%",,,
Black Templars,3 Eradicator Squad,90,90,"0,00%",,100,
Black Templars,3 Inceptor Squad,120,120,"0,00%",,135,
Black Templars,3 Outrider Squad,80,70,"-12,50%",,,
Black Templars,4 Sword Brethren Squad,,105,,,120,
Black Templars,5 Assault Intercessor Squad,75,75,"0,00%",,,
Black Templars,5 Assault Intercessors With Jump Packs,90,85,"-5,56%",,95,
Black Templars,5 Devastator Squad,120,120,"0,00%",,,
Black Templars,5 Heavy Intercessor Squad,100,100,"0,00%",,,
Black Templars,5 Hellblaster Squad,110,110,"0,00%",,,
Black Templars,5 Incursor Squad,80,85,"6,25%",,95,
Black Templars,5 Infernus Squad,90,85,"-5,56%",,,
Black Templars,5 Infiltrator Squad,100,110,"10,00%",,120,
Black Templars,5 Intercessor Squad,80,80,"0,00%",,,
Black Templars,5 Reiver Squad,80,75,"-6,25%",,,
Black Templars,5 Scout Squad,70,65,"-7,14%",,75,
Black Templars,5 Sternguard Veteran Squad,85,85,"0,00%",,,
Black Templars,5 Sword Brethren Squad,,130,,,145,
Black Templars,5 Terminator Assault Squad,180,155,"-13,89%",,,
Black Templars,5 Terminator Squad,175,160,"-8,57%",,,
Black Templars,5 Vanguard Veteran Squad With Jump Packs,100,100,"0,00%",,110,
Black Templars,6 Aggressor Squad,,180,,,190,
Black Templars,6 Bladeguard Veteran Squad,,160,,,170,
Black Templars,6 Centurion Assault Squad,,300,,,,
Black Templars,6 Centurion Devastator Squad,,350,,,,
Black Templars,6 Eradicator Squad,,180,,,190,
Black Templars,6 Inceptor Squad,,240,,,255,
Black Templars,6 Outrider Squad,,140,,,,
Black Templars,9 Sword Brethren Squad,,235,,,250,
Black Templars,ANCIENT,50,40,"-20,00%",,,
Black Templars,ANCIENT IN TERMINATOR ARMOUR,75,65,"-13,33%",,,
Black Templars,APOTHECARY,50,40,"-20,00%",,,
Black Templars,APOTHECARY BIOLOGIS,70,70,"0,00%",,,
Black Templars,ASTRAEUS,525,525,"0,00%",575,,
Black Templars,BALLISTUS DREADNOUGHT,150,150,"0,00%",,160,
Black Templars,BLADEGUARD ANCIENT,45,40,"-11,11%",,,
Black Templars,BRUTALIS DREADNOUGHT,160,150,"-6,25%",,160,
Black Templars,CAPTAIN,80,80,"0,00%",,,
Black Templars,CAPTAIN IN GRAVIS ARMOUR,80,80,"0,00%",,,
Black Templars,CAPTAIN IN PHOBOS ARMOUR,70,70,"0,00%",,,
Black Templars,CAPTAIN IN TERMINATOR ARMOUR,95,85,"-10,53%",,,
Black Templars,CAPTAIN WITH JUMP PACK,75,75,"0,00%",,,
Black Templars,CASTELLAN,70,70,"0,00%",,,
Black Templars,CHAPLAIN,60,60,"0,00%",,,
Black Templars,CHAPLAIN GRIMALDUS,110,110,"0,00%",,,
Black Templars,CHAPLAIN IN TERMINATOR ARMOUR,75,75,"0,00%",,,
Black Templars,CHAPLAIN ON BIKE,75,70,"-6,67%",,,
Black Templars,CHAPLAIN WITH JUMP PACK,75,75,"0,00%",,,
Black Templars,COMPANY HEROES,105,105,"0,00%",,,
Black Templars,CRUSADE ANCIENT,55,45,"-18,18%",,,
Black Templars,DESOLATION SQUAD,200,180,"-10,00%",210,,
Black Templars,DREADNOUGHT,135,135,"0,00%",,,
Black Templars,DROP POD,70,70,"0,00%",,,
Black Templars,ELIMINATOR SQUAD,85,75,"-11,76%",,,
Black Templars,EMPERORS CHAMPION,100,100,"0,00%",,,
Black Templars,ERADICATOR SQUAD WITH HEAVY BOLTERS,,70,,,80,
Black Templars,EXECRATOR,60,60,"0,00%",,,
Black Templars,GLADIATOR LANCER,160,160,"0,00%",,170,
Black Templars,GLADIATOR REAPER,160,160,"0,00%",,170,
Black Templars,GLADIATOR VALIANT,150,150,"0,00%",,160,
Black Templars,HAMMERFALL BUNKER,175,175,"0,00%",,,
Black Templars,HIGH MARSHAL HELBRECHT,120,120,"0,00%",,,
Black Templars,IMPULSOR,85,85,"0,00%",,,
Black Templars,INVADER ATV,60,60,"0,00%",,,
Black Templars,INVICTOR TACTICAL WARSUIT,125,125,"0,00%",,,
Black Templars,JUDICIAR,70,55,"-21,43%",,,
Black Templars,LAND RAIDER,220,220,"0,00%",,240,
Black Templars,LAND RAIDER CRUSADER,220,220,"0,00%",,240,
Black Templars,LAND RAIDER REDEEMER,270,250,"-7,41%",,270,
Black Templars,LAND SPEEDER,,95,,,105,
Black Templars,LIEUTENANT,55,45,"-18,18%",,,
Black Templars,LIEUTENANT IN PHOBOS ARMOUR,55,45,"-18,18%",,,
Black Templars,LIEUTENANT IN REIVER ARMOUR,55,45,"-18,18%",,,
Black Templars,LIEUTENANT WITH COMBI-WEAPON,85,85,"0,00%",,,
Black Templars,MARSHAL,80,80,"0,00%",90,,
Black Templars,PREDATOR ANNIHILATOR,135,135,"0,00%",,145,
Black Templars,PREDATOR DESTRUCTOR,140,140,"0,00%",,150,
Black Templars,RAZORBACK,95,95,"0,00%",,,
Black Templars,REDEMPTOR DREADNOUGHT,205,195,"-4,88%",,210,
Black Templars,REPULSOR,180,170,"-5,56%",,190,
Black Templars,REPULSOR EXECUTIONER,235,245,"4,26%",,265,
Black Templars,RHINO,75,75,"0,00%",,,
Black Templars,STORM SPEEDER HAILSTRIKE,115,105,"-8,70%",,115,
Black Templars,STORM SPEEDER HAMMERSTRIKE,125,130,"4,00%",,140,
Black Templars,STORM SPEEDER THUNDERSTRIKE,135,135,"0,00%",,145,
Black Templars,STORMHAWK INTERCEPTOR,155,155,"0,00%",,,
Black Templars,STORMRAVEN GUNSHIP,280,280,"0,00%",300,,
Black Templars,STORMTALON GUNSHIP,165,165,"0,00%",,,
Black Templars,SUPPRESSOR SQUAD,75,75,"0,00%",,,
Black Templars,TACTICAL SQUAD,140,140,"0,00%",,,
Black Templars,TECHMARINE,55,55,"0,00%",,,
Black Templars,THUNDERHAWK GUNSHIP,840,840,"0,00%",,,
Black Templars,VINDICATOR,185,185,"0,00%",,200,
Black Templars,WHIRLWIND,190,175,"-7,89%",195,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Black Templars 1 Firestrike Servo-Turrets 75 75 0,00%
3 Black Templars 10 Assault Intercessor Squad 150
4 Black Templars 10 Assault Intercessors With Jump Packs 160 170
5 Black Templars 10 Devastator Squad 200
6 Black Templars 10 Heavy Intercessor Squad 200
7 Black Templars 10 Hellblaster Squad 220
8 Black Templars 10 Incursor Squad 150 160
9 Black Templars 10 Infernus Squad 170
10 Black Templars 10 Infiltrator Squad 180 190
11 Black Templars 10 Intercessor Squad 150
12 Black Templars 10 Reiver Squad 150
13 Black Templars 10 Scout Squad 120 130
14 Black Templars 10 Sternguard Veteran Squad 160
15 Black Templars 10 Sword Brethren Squad 260 275
16 Black Templars 10 Terminator Assault Squad 310
17 Black Templars 10 Terminator Squad 320
18 Black Templars 10 Vanguard Veteran Squad With Jump Packs 200 210
19 Black Templars 2 Firestrike Servo-Turrets 150
20 Black Templars 3 Aggressor Squad 95 90 -5,26% 100
21 Black Templars 3 Bladeguard Veteran Squad 80 80 0,00% 90
22 Black Templars 3 Centurion Assault Squad 150 150 0,00%
23 Black Templars 3 Centurion Devastator Squad 175 175 0,00%
24 Black Templars 3 Eradicator Squad 90 90 0,00% 100
25 Black Templars 3 Inceptor Squad 120 120 0,00% 135
26 Black Templars 3 Outrider Squad 80 70 -12,50%
27 Black Templars 4 Sword Brethren Squad 105 120
28 Black Templars 5 Assault Intercessor Squad 75 75 0,00%
29 Black Templars 5 Assault Intercessors With Jump Packs 90 85 -5,56% 95
30 Black Templars 5 Devastator Squad 120 120 0,00%
31 Black Templars 5 Heavy Intercessor Squad 100 100 0,00%
32 Black Templars 5 Hellblaster Squad 110 110 0,00%
33 Black Templars 5 Incursor Squad 80 85 6,25% 95
34 Black Templars 5 Infernus Squad 90 85 -5,56%
35 Black Templars 5 Infiltrator Squad 100 110 10,00% 120
36 Black Templars 5 Intercessor Squad 80 80 0,00%
37 Black Templars 5 Reiver Squad 80 75 -6,25%
38 Black Templars 5 Scout Squad 70 65 -7,14% 75
39 Black Templars 5 Sternguard Veteran Squad 85 85 0,00%
40 Black Templars 5 Sword Brethren Squad 130 145
41 Black Templars 5 Terminator Assault Squad 180 155 -13,89%
42 Black Templars 5 Terminator Squad 175 160 -8,57%
43 Black Templars 5 Vanguard Veteran Squad With Jump Packs 100 100 0,00% 110
44 Black Templars 6 Aggressor Squad 180 190
45 Black Templars 6 Bladeguard Veteran Squad 160 170
46 Black Templars 6 Centurion Assault Squad 300
47 Black Templars 6 Centurion Devastator Squad 350
48 Black Templars 6 Eradicator Squad 180 190
49 Black Templars 6 Inceptor Squad 240 255
50 Black Templars 6 Outrider Squad 140
51 Black Templars 9 Sword Brethren Squad 235 250
52 Black Templars ANCIENT 50 40 -20,00%
53 Black Templars ANCIENT IN TERMINATOR ARMOUR 75 65 -13,33%
54 Black Templars APOTHECARY 50 40 -20,00%
55 Black Templars APOTHECARY BIOLOGIS 70 70 0,00%
56 Black Templars ASTRAEUS 525 525 0,00% 575
57 Black Templars BALLISTUS DREADNOUGHT 150 150 0,00% 160
58 Black Templars BLADEGUARD ANCIENT 45 40 -11,11%
59 Black Templars BRUTALIS DREADNOUGHT 160 150 -6,25% 160
60 Black Templars CAPTAIN 80 80 0,00%
61 Black Templars CAPTAIN IN GRAVIS ARMOUR 80 80 0,00%
62 Black Templars CAPTAIN IN PHOBOS ARMOUR 70 70 0,00%
63 Black Templars CAPTAIN IN TERMINATOR ARMOUR 95 85 -10,53%
64 Black Templars CAPTAIN WITH JUMP PACK 75 75 0,00%
65 Black Templars CASTELLAN 70 70 0,00%
66 Black Templars CHAPLAIN 60 60 0,00%
67 Black Templars CHAPLAIN GRIMALDUS 110 110 0,00%
68 Black Templars CHAPLAIN IN TERMINATOR ARMOUR 75 75 0,00%
69 Black Templars CHAPLAIN ON BIKE 75 70 -6,67%
70 Black Templars CHAPLAIN WITH JUMP PACK 75 75 0,00%
71 Black Templars COMPANY HEROES 105 105 0,00%
72 Black Templars CRUSADE ANCIENT 55 45 -18,18%
73 Black Templars DESOLATION SQUAD 200 180 -10,00% 210
74 Black Templars DREADNOUGHT 135 135 0,00%
75 Black Templars DROP POD 70 70 0,00%
76 Black Templars ELIMINATOR SQUAD 85 75 -11,76%
77 Black Templars EMPEROR’S CHAMPION 100 100 0,00%
78 Black Templars ERADICATOR SQUAD WITH HEAVY BOLTERS 70 80
79 Black Templars EXECRATOR 60 60 0,00%
80 Black Templars GLADIATOR LANCER 160 160 0,00% 170
81 Black Templars GLADIATOR REAPER 160 160 0,00% 170
82 Black Templars GLADIATOR VALIANT 150 150 0,00% 160
83 Black Templars HAMMERFALL BUNKER 175 175 0,00%
84 Black Templars HIGH MARSHAL HELBRECHT 120 120 0,00%
85 Black Templars IMPULSOR 85 85 0,00%
86 Black Templars INVADER ATV 60 60 0,00%
87 Black Templars INVICTOR TACTICAL WARSUIT 125 125 0,00%
88 Black Templars JUDICIAR 70 55 -21,43%
89 Black Templars LAND RAIDER 220 220 0,00% 240
90 Black Templars LAND RAIDER CRUSADER 220 220 0,00% 240
91 Black Templars LAND RAIDER REDEEMER 270 250 -7,41% 270
92 Black Templars LAND SPEEDER 95 105
93 Black Templars LIEUTENANT 55 45 -18,18%
94 Black Templars LIEUTENANT IN PHOBOS ARMOUR 55 45 -18,18%
95 Black Templars LIEUTENANT IN REIVER ARMOUR 55 45 -18,18%
96 Black Templars LIEUTENANT WITH COMBI-WEAPON 85 85 0,00%
97 Black Templars MARSHAL 80 80 0,00% 90
98 Black Templars PREDATOR ANNIHILATOR 135 135 0,00% 145
99 Black Templars PREDATOR DESTRUCTOR 140 140 0,00% 150
100 Black Templars RAZORBACK 95 95 0,00%
101 Black Templars REDEMPTOR DREADNOUGHT 205 195 -4,88% 210
102 Black Templars REPULSOR 180 170 -5,56% 190
103 Black Templars REPULSOR EXECUTIONER 235 245 4,26% 265
104 Black Templars RHINO 75 75 0,00%
105 Black Templars STORM SPEEDER HAILSTRIKE 115 105 -8,70% 115
106 Black Templars STORM SPEEDER HAMMERSTRIKE 125 130 4,00% 140
107 Black Templars STORM SPEEDER THUNDERSTRIKE 135 135 0,00% 145
108 Black Templars STORMHAWK INTERCEPTOR 155 155 0,00%
109 Black Templars STORMRAVEN GUNSHIP 280 280 0,00% 300
110 Black Templars STORMTALON GUNSHIP 165 165 0,00%
111 Black Templars SUPPRESSOR SQUAD 75 75 0,00%
112 Black Templars TACTICAL SQUAD 140 140 0,00%
113 Black Templars TECHMARINE 55 55 0,00%
114 Black Templars THUNDERHAWK GUNSHIP 840 840 0,00%
115 Black Templars VINDICATOR 185 185 0,00% 200
116 Black Templars WHIRLWIND 190 175 -7,89% 195

127
csv/blood-angels.csv Normal file
View File

@@ -0,0 +1,127 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Blood Angels,1 Firestrike Servo-Turrets,75,75,"0,00%",,,
Blood Angels,10 Assault Intercessor Squad,,150,,,,
Blood Angels,10 Assault Intercessors With Jump Packs,,180,,,190,
Blood Angels,10 Death Company Marines,,160,,,170,
Blood Angels,10 Death Company Marines With Bolt Rifles,,155,,,165,
Blood Angels,10 Death Company Marines With Jump Packs,,230,,,245,
Blood Angels,10 Devastator Squad,,200,,,,
Blood Angels,10 Heavy Intercessor Squad,,200,,,,
Blood Angels,10 Hellblaster Squad,,220,,,,
Blood Angels,10 Incursor Squad,,150,,,160,
Blood Angels,10 Infernus Squad,,170,,,,
Blood Angels,10 Infiltrator Squad,,180,,,190,
Blood Angels,10 Intercessor Squad,,150,,,,
Blood Angels,10 Reiver Squad,,150,,,,
Blood Angels,10 Scout Squad,,120,,,130,
Blood Angels,10 Sternguard Veteran Squad,,190,,,,
Blood Angels,10 Terminator Assault Squad,,310,,,,
Blood Angels,10 Terminator Squad,,320,,,,
Blood Angels,10 Vanguard Veteran Squad With Jump Packs,,210,,,220,
Blood Angels,2 Firestrike Servo-Turrets,,150,,,,
Blood Angels,3 Aggressor Squad,95,90,"-5,26%",,100,
Blood Angels,3 Bladeguard Veteran Squad,80,85,"6,25%",,95,
Blood Angels,3 Centurion Assault Squad,150,150,"0,00%",,,
Blood Angels,3 Centurion Devastator Squad,175,175,"0,00%",,,
Blood Angels,3 Eradicator Squad,90,90,"0,00%",,100,
Blood Angels,3 Inceptor Squad,120,120,"0,00%",,135,
Blood Angels,3 Outrider Squad,80,75,"-6,25%",,,
Blood Angels,3 Sanguinary Guard,125,125,"0,00%",,145,
Blood Angels,5 Assault Intercessor Squad,75,80,"6,67%",,,
Blood Angels,5 Assault Intercessors With Jump Packs,90,95,"5,56%",,105,
Blood Angels,5 Death Company Marines,85,85,"0,00%",,95,
Blood Angels,5 Death Company Marines With Bolt Rifles,85,80,"-5,88%",,90,
Blood Angels,5 Death Company Marines With Jump Packs,120,120,"0,00%",,135,
Blood Angels,5 Devastator Squad,120,120,"0,00%",,,
Blood Angels,5 Heavy Intercessor Squad,100,100,"0,00%",,,
Blood Angels,5 Hellblaster Squad,110,110,"0,00%",,,
Blood Angels,5 Incursor Squad,80,85,"6,25%",,95,
Blood Angels,5 Infernus Squad,90,85,"-5,56%",,,
Blood Angels,5 Infiltrator Squad,100,110,"10,00%",,120,
Blood Angels,5 Intercessor Squad,80,80,"0,00%",,,
Blood Angels,5 Reiver Squad,80,75,"-6,25%",,,
Blood Angels,5 Scout Squad,70,65,"-7,14%",,75,
Blood Angels,5 Sternguard Veteran Squad,100,100,"0,00%",,,
Blood Angels,5 Terminator Assault Squad,180,155,"-13,89%",,,
Blood Angels,5 Terminator Squad,170,160,"-5,88%",,,
Blood Angels,5 Vanguard Veteran Squad With Jump Packs,100,105,"5,00%",,115,
Blood Angels,6 Aggressor Squad,,180,,,190,
Blood Angels,6 Bladeguard Veteran Squad,,170,,,180,
Blood Angels,6 Centurion Assault Squad,,300,,,,
Blood Angels,6 Centurion Devastator Squad,,350,,,,
Blood Angels,6 Eradicator Squad,,180,,,190,
Blood Angels,6 Inceptor Squad,,240,,,255,
Blood Angels,6 Outrider Squad,,140,,,,
Blood Angels,6 Sanguinary Guard,,260,,,280,
Blood Angels,ANCIENT,50,40,"-20,00%",,,
Blood Angels,ANCIENT IN TERMINATOR ARMOUR,75,65,"-13,33%",,,
Blood Angels,APOTHECARY,50,40,"-20,00%",,,
Blood Angels,APOTHECARY BIOLOGIS,70,70,"0,00%",,,
Blood Angels,ASTORATH,85,85,"0,00%",,,
Blood Angels,ASTRAEUS,525,525,"0,00%",575,,
Blood Angels,BAAL PREDATOR,125,125,"0,00%",,135,
Blood Angels,BALLISTUS DREADNOUGHT,150,150,"0,00%",,160,
Blood Angels,BLADEGUARD ANCIENT,45,40,"-11,11%",,,
Blood Angels,BLOOD ANGELS CAPTAIN,80,80,"0,00%",,,
Blood Angels,BRUTALIS DREADNOUGHT,160,150,"-6,25%",,160,
Blood Angels,CAPTAIN,80,80,"0,00%",,,
Blood Angels,CAPTAIN IN GRAVIS ARMOUR,80,80,"0,00%",,,
Blood Angels,CAPTAIN IN PHOBOS ARMOUR,70,70,"0,00%",,,
Blood Angels,CAPTAIN IN TERMINATOR ARMOUR,95,85,"-10,53%",,,
Blood Angels,CAPTAIN WITH JUMP PACK,75,80,"6,67%",,,
Blood Angels,CHAPLAIN,60,60,"0,00%",,,
Blood Angels,CHAPLAIN IN TERMINATOR ARMOUR,75,75,"0,00%",,,
Blood Angels,CHAPLAIN ON BIKE,75,70,"-6,67%",,,
Blood Angels,CHAPLAIN WITH JUMP PACK,75,80,"6,67%",,,
Blood Angels,CHIEF LIBRARIAN MEPHISTON,120,110,"-8,33%",,,
Blood Angels,COMMANDER DANTE,120,125,"4,17%",,,
Blood Angels,COMPANY HEROES,105,105,"0,00%",,,
Blood Angels,DEATH COMPANY CAPTAIN,70,70,"0,00%",,,
Blood Angels,DEATH COMPANY CAPTAIN WITH JUMP PACK,75,75,"0,00%",,,
Blood Angels,DEATH COMPANY DREADNOUGHT,160,150,"-6,25%",,160,
Blood Angels,DESOLATION SQUAD,200,180,"-10,00%",210,,
Blood Angels,DREADNOUGHT,135,135,"0,00%",,,
Blood Angels,DROP POD,70,70,"0,00%",,,
Blood Angels,ELIMINATOR SQUAD,85,75,"-11,76%",,,
Blood Angels,ERADICATOR SQUAD WITH HEAVY BOLTERS,,70,,,80,
Blood Angels,GLADIATOR LANCER,160,160,"0,00%",,170,
Blood Angels,GLADIATOR REAPER,160,160,"0,00%",,170,
Blood Angels,GLADIATOR VALIANT,150,150,"0,00%",,160,
Blood Angels,HAMMERFALL BUNKER,175,175,"0,00%",,,
Blood Angels,IMPULSOR,80,80,"0,00%",,,
Blood Angels,INVADER ATV,60,60,"0,00%",,,
Blood Angels,INVICTOR TACTICAL WARSUIT,125,125,"0,00%",,,
Blood Angels,JUDICIAR,70,55,"-21,43%",,,
Blood Angels,LAND RAIDER,220,220,"0,00%",,240,
Blood Angels,LAND RAIDER CRUSADER,220,220,"0,00%",,240,
Blood Angels,LAND RAIDER REDEEMER,270,250,"-7,41%",,270,
Blood Angels,LAND SPEEDER,,95,,,105,
Blood Angels,LEMARTES,100,100,"0,00%",,,
Blood Angels,LIBRARIAN,65,60,"-7,69%",,,
Blood Angels,LIBRARIAN IN PHOBOS ARMOUR,70,70,"0,00%",,,
Blood Angels,LIBRARIAN IN TERMINATOR ARMOUR,75,75,"0,00%",,,
Blood Angels,LIEUTENANT,55,45,"-18,18%",,,
Blood Angels,LIEUTENANT IN PHOBOS ARMOUR,55,45,"-18,18%",,,
Blood Angels,LIEUTENANT IN REIVER ARMOUR,55,45,"-18,18%",,,
Blood Angels,LIEUTENANT WITH COMBI-WEAPON,85,85,"0,00%",,,
Blood Angels,PREDATOR ANNIHILATOR,135,135,"0,00%",,145,
Blood Angels,PREDATOR DESTRUCTOR,140,140,"0,00%",,150,
Blood Angels,RAZORBACK,95,95,"0,00%",,,
Blood Angels,REDEMPTOR DREADNOUGHT,205,195,"-4,88%",,210,
Blood Angels,REPULSOR,180,170,"-5,56%",,190,
Blood Angels,REPULSOR EXECUTIONER,230,230,"0,00%",,250,
Blood Angels,RHINO,75,75,"0,00%",,,
Blood Angels,SANGUINARY PRIEST,75,75,"0,00%",,85,
Blood Angels,STORM SPEEDER HAILSTRIKE,115,105,"-8,70%",,115,
Blood Angels,STORM SPEEDER HAMMERSTRIKE,125,130,"4,00%",,140,
Blood Angels,STORM SPEEDER THUNDERSTRIKE,135,135,"0,00%",,145,
Blood Angels,STORMHAWK INTERCEPTOR,155,155,"0,00%",,,
Blood Angels,STORMRAVEN GUNSHIP,280,280,"0,00%",300,,
Blood Angels,STORMTALON GUNSHIP,165,165,"0,00%",,,
Blood Angels,SUPPRESSOR SQUAD,75,75,"0,00%",,,
Blood Angels,TACTICAL SQUAD,140,140,"0,00%",,,
Blood Angels,TECHMARINE,55,55,"0,00%",,,
Blood Angels,THE SANGUINOR,130,130,"0,00%",,,
Blood Angels,THUNDERHAWK GUNSHIP,840,840,"0,00%",,,
Blood Angels,VINDICATOR,185,185,"0,00%",,200,
Blood Angels,WHIRLWIND,190,175,"-7,89%",195,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Blood Angels 1 Firestrike Servo-Turrets 75 75 0,00%
3 Blood Angels 10 Assault Intercessor Squad 150
4 Blood Angels 10 Assault Intercessors With Jump Packs 180 190
5 Blood Angels 10 Death Company Marines 160 170
6 Blood Angels 10 Death Company Marines With Bolt Rifles 155 165
7 Blood Angels 10 Death Company Marines With Jump Packs 230 245
8 Blood Angels 10 Devastator Squad 200
9 Blood Angels 10 Heavy Intercessor Squad 200
10 Blood Angels 10 Hellblaster Squad 220
11 Blood Angels 10 Incursor Squad 150 160
12 Blood Angels 10 Infernus Squad 170
13 Blood Angels 10 Infiltrator Squad 180 190
14 Blood Angels 10 Intercessor Squad 150
15 Blood Angels 10 Reiver Squad 150
16 Blood Angels 10 Scout Squad 120 130
17 Blood Angels 10 Sternguard Veteran Squad 190
18 Blood Angels 10 Terminator Assault Squad 310
19 Blood Angels 10 Terminator Squad 320
20 Blood Angels 10 Vanguard Veteran Squad With Jump Packs 210 220
21 Blood Angels 2 Firestrike Servo-Turrets 150
22 Blood Angels 3 Aggressor Squad 95 90 -5,26% 100
23 Blood Angels 3 Bladeguard Veteran Squad 80 85 6,25% 95
24 Blood Angels 3 Centurion Assault Squad 150 150 0,00%
25 Blood Angels 3 Centurion Devastator Squad 175 175 0,00%
26 Blood Angels 3 Eradicator Squad 90 90 0,00% 100
27 Blood Angels 3 Inceptor Squad 120 120 0,00% 135
28 Blood Angels 3 Outrider Squad 80 75 -6,25%
29 Blood Angels 3 Sanguinary Guard 125 125 0,00% 145
30 Blood Angels 5 Assault Intercessor Squad 75 80 6,67%
31 Blood Angels 5 Assault Intercessors With Jump Packs 90 95 5,56% 105
32 Blood Angels 5 Death Company Marines 85 85 0,00% 95
33 Blood Angels 5 Death Company Marines With Bolt Rifles 85 80 -5,88% 90
34 Blood Angels 5 Death Company Marines With Jump Packs 120 120 0,00% 135
35 Blood Angels 5 Devastator Squad 120 120 0,00%
36 Blood Angels 5 Heavy Intercessor Squad 100 100 0,00%
37 Blood Angels 5 Hellblaster Squad 110 110 0,00%
38 Blood Angels 5 Incursor Squad 80 85 6,25% 95
39 Blood Angels 5 Infernus Squad 90 85 -5,56%
40 Blood Angels 5 Infiltrator Squad 100 110 10,00% 120
41 Blood Angels 5 Intercessor Squad 80 80 0,00%
42 Blood Angels 5 Reiver Squad 80 75 -6,25%
43 Blood Angels 5 Scout Squad 70 65 -7,14% 75
44 Blood Angels 5 Sternguard Veteran Squad 100 100 0,00%
45 Blood Angels 5 Terminator Assault Squad 180 155 -13,89%
46 Blood Angels 5 Terminator Squad 170 160 -5,88%
47 Blood Angels 5 Vanguard Veteran Squad With Jump Packs 100 105 5,00% 115
48 Blood Angels 6 Aggressor Squad 180 190
49 Blood Angels 6 Bladeguard Veteran Squad 170 180
50 Blood Angels 6 Centurion Assault Squad 300
51 Blood Angels 6 Centurion Devastator Squad 350
52 Blood Angels 6 Eradicator Squad 180 190
53 Blood Angels 6 Inceptor Squad 240 255
54 Blood Angels 6 Outrider Squad 140
55 Blood Angels 6 Sanguinary Guard 260 280
56 Blood Angels ANCIENT 50 40 -20,00%
57 Blood Angels ANCIENT IN TERMINATOR ARMOUR 75 65 -13,33%
58 Blood Angels APOTHECARY 50 40 -20,00%
59 Blood Angels APOTHECARY BIOLOGIS 70 70 0,00%
60 Blood Angels ASTORATH 85 85 0,00%
61 Blood Angels ASTRAEUS 525 525 0,00% 575
62 Blood Angels BAAL PREDATOR 125 125 0,00% 135
63 Blood Angels BALLISTUS DREADNOUGHT 150 150 0,00% 160
64 Blood Angels BLADEGUARD ANCIENT 45 40 -11,11%
65 Blood Angels BLOOD ANGELS CAPTAIN 80 80 0,00%
66 Blood Angels BRUTALIS DREADNOUGHT 160 150 -6,25% 160
67 Blood Angels CAPTAIN 80 80 0,00%
68 Blood Angels CAPTAIN IN GRAVIS ARMOUR 80 80 0,00%
69 Blood Angels CAPTAIN IN PHOBOS ARMOUR 70 70 0,00%
70 Blood Angels CAPTAIN IN TERMINATOR ARMOUR 95 85 -10,53%
71 Blood Angels CAPTAIN WITH JUMP PACK 75 80 6,67%
72 Blood Angels CHAPLAIN 60 60 0,00%
73 Blood Angels CHAPLAIN IN TERMINATOR ARMOUR 75 75 0,00%
74 Blood Angels CHAPLAIN ON BIKE 75 70 -6,67%
75 Blood Angels CHAPLAIN WITH JUMP PACK 75 80 6,67%
76 Blood Angels CHIEF LIBRARIAN MEPHISTON 120 110 -8,33%
77 Blood Angels COMMANDER DANTE 120 125 4,17%
78 Blood Angels COMPANY HEROES 105 105 0,00%
79 Blood Angels DEATH COMPANY CAPTAIN 70 70 0,00%
80 Blood Angels DEATH COMPANY CAPTAIN WITH JUMP PACK 75 75 0,00%
81 Blood Angels DEATH COMPANY DREADNOUGHT 160 150 -6,25% 160
82 Blood Angels DESOLATION SQUAD 200 180 -10,00% 210
83 Blood Angels DREADNOUGHT 135 135 0,00%
84 Blood Angels DROP POD 70 70 0,00%
85 Blood Angels ELIMINATOR SQUAD 85 75 -11,76%
86 Blood Angels ERADICATOR SQUAD WITH HEAVY BOLTERS 70 80
87 Blood Angels GLADIATOR LANCER 160 160 0,00% 170
88 Blood Angels GLADIATOR REAPER 160 160 0,00% 170
89 Blood Angels GLADIATOR VALIANT 150 150 0,00% 160
90 Blood Angels HAMMERFALL BUNKER 175 175 0,00%
91 Blood Angels IMPULSOR 80 80 0,00%
92 Blood Angels INVADER ATV 60 60 0,00%
93 Blood Angels INVICTOR TACTICAL WARSUIT 125 125 0,00%
94 Blood Angels JUDICIAR 70 55 -21,43%
95 Blood Angels LAND RAIDER 220 220 0,00% 240
96 Blood Angels LAND RAIDER CRUSADER 220 220 0,00% 240
97 Blood Angels LAND RAIDER REDEEMER 270 250 -7,41% 270
98 Blood Angels LAND SPEEDER 95 105
99 Blood Angels LEMARTES 100 100 0,00%
100 Blood Angels LIBRARIAN 65 60 -7,69%
101 Blood Angels LIBRARIAN IN PHOBOS ARMOUR 70 70 0,00%
102 Blood Angels LIBRARIAN IN TERMINATOR ARMOUR 75 75 0,00%
103 Blood Angels LIEUTENANT 55 45 -18,18%
104 Blood Angels LIEUTENANT IN PHOBOS ARMOUR 55 45 -18,18%
105 Blood Angels LIEUTENANT IN REIVER ARMOUR 55 45 -18,18%
106 Blood Angels LIEUTENANT WITH COMBI-WEAPON 85 85 0,00%
107 Blood Angels PREDATOR ANNIHILATOR 135 135 0,00% 145
108 Blood Angels PREDATOR DESTRUCTOR 140 140 0,00% 150
109 Blood Angels RAZORBACK 95 95 0,00%
110 Blood Angels REDEMPTOR DREADNOUGHT 205 195 -4,88% 210
111 Blood Angels REPULSOR 180 170 -5,56% 190
112 Blood Angels REPULSOR EXECUTIONER 230 230 0,00% 250
113 Blood Angels RHINO 75 75 0,00%
114 Blood Angels SANGUINARY PRIEST 75 75 0,00% 85
115 Blood Angels STORM SPEEDER HAILSTRIKE 115 105 -8,70% 115
116 Blood Angels STORM SPEEDER HAMMERSTRIKE 125 130 4,00% 140
117 Blood Angels STORM SPEEDER THUNDERSTRIKE 135 135 0,00% 145
118 Blood Angels STORMHAWK INTERCEPTOR 155 155 0,00%
119 Blood Angels STORMRAVEN GUNSHIP 280 280 0,00% 300
120 Blood Angels STORMTALON GUNSHIP 165 165 0,00%
121 Blood Angels SUPPRESSOR SQUAD 75 75 0,00%
122 Blood Angels TACTICAL SQUAD 140 140 0,00%
123 Blood Angels TECHMARINE 55 55 0,00%
124 Blood Angels THE SANGUINOR 130 130 0,00%
125 Blood Angels THUNDERHAWK GUNSHIP 840 840 0,00%
126 Blood Angels VINDICATOR 185 185 0,00% 200
127 Blood Angels WHIRLWIND 190 175 -7,89% 195

64
csv/chaos-daemons.csv Normal file
View File

@@ -0,0 +1,64 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Chaos Daemons,1 Beasts Of Nurgle,65,70,"7,69%",,,
Chaos Daemons,1 Hellflayers,80,80,"0,00%",,,
Chaos Daemons,10 Flesh Hounds,,150,,,,
Chaos Daemons,10 Seekers,,155,,,,
Chaos Daemons,2 Beasts Of Nurgle,,140,,,,
Chaos Daemons,2 Hellflayers,,160,,,,
Chaos Daemons,3 Bloodcrushers,110,95,"-13,64%",,105,
Chaos Daemons,3 Fiends,95,90,"-5,26%",,,
Chaos Daemons,3 Flamers,65,65,"0,00%",,,
Chaos Daemons,3 Nurglings,40,45,"12,50%",,,
Chaos Daemons,3 Plague Drones,110,110,"0,00%",,,
Chaos Daemons,3 Screamers,80,80,"0,00%",,,
Chaos Daemons,5 Flesh Hounds,75,75,"0,00%",,,
Chaos Daemons,5 Seekers,80,80,"0,00%",,,
Chaos Daemons,6 Bloodcrushers,,180,,,190,
Chaos Daemons,6 Fiends,,180,,,,
Chaos Daemons,6 Flamers,,130,,,,
Chaos Daemons,6 Nurglings,,90,,,,
Chaos Daemons,6 Plague Drones,,220,,,,
Chaos Daemons,6 Screamers,,160,,,,
Chaos Daemons,BELAKOR,375,390,"4,00%",,,
Chaos Daemons,BLOODLETTERS,110,110,"0,00%",,,
Chaos Daemons,BLOODMASTER,65,65,"0,00%",,,
Chaos Daemons,BLOODTHIRSTER,305,320,"4,92%",,335,
Chaos Daemons,BLUE HORRORS,125,125,"0,00%",,,
Chaos Daemons,BURNING CHARIOT,115,115,"0,00%",,,
Chaos Daemons,CHANGECASTER,60,60,"0,00%",,,
Chaos Daemons,CONTORTED EPITOME,100,100,"0,00%",,,
Chaos Daemons,DAEMON PRINCE OF CHAOS,190,165,"-13,16%",,,
Chaos Daemons,DAEMON PRINCE OF CHAOS WITH WINGS,180,190,"5,56%",,,
Chaos Daemons,DAEMONETTES,100,90,"-10,00%",,,
Chaos Daemons,EPIDEMIUS,80,80,"0,00%",,,
Chaos Daemons,EXALTED FLAMER,65,65,"0,00%",,,
Chaos Daemons,FATESKIMMER,95,95,"0,00%",,,
Chaos Daemons,FECULENT GNARLMAW,100,100,"0,00%",,,
Chaos Daemons,FLUXMASTER,60,80,"33,33%",,,
Chaos Daemons,GREAT UNCLEAN ONE,250,265,"6,00%",,280,
Chaos Daemons,HORTICULOUS SLIMUX,120,120,"0,00%",,,
Chaos Daemons,INFERNAL ENRAPTURESS,60,60,"0,00%",,,
Chaos Daemons,KAIROS FATEWEAVER,295,295,"0,00%",,,
Chaos Daemons,KARANAK,75,70,"-6,67%",,,
Chaos Daemons,KEEPER OF SECRETS,240,255,"6,25%",,270,
Chaos Daemons,LORD OF CHANGE,285,300,"5,26%",,315,
Chaos Daemons,PINK HORRORS,140,150,"7,14%",,,
Chaos Daemons,PLAGUEBEARERS,110,115,"4,55%",,,
Chaos Daemons,POXBRINGER,55,55,"0,00%",,,
Chaos Daemons,RENDMASTER ON BLOOD THRONE,165,150,"-9,09%",,170,
Chaos Daemons,ROTIGUS,265,280,"5,66%",,,
Chaos Daemons,SHALAXI HELBANE,340,340,"0,00%",,,
Chaos Daemons,SKARBRAND,305,315,"3,28%",,,
Chaos Daemons,SKULL ALTAR,105,105,"0,00%",,,
Chaos Daemons,SKULL CANNON,95,90,"-5,26%",,,
Chaos Daemons,SKULLMASTER,100,85,"-15,00%",,,
Chaos Daemons,SKULLTAKER,85,85,"0,00%",,,
Chaos Daemons,SLOPPITY BILEPIPER,55,55,"0,00%",,,
Chaos Daemons,SOUL GRINDER,180,180,"0,00%",,195,
Chaos Daemons,SPOILPOX SCRIVENER,60,60,"0,00%",,,
Chaos Daemons,SYLLESSKE,120,120,"0,00%",,,
Chaos Daemons,THE BLUE SCRIBES,75,75,"0,00%",,,
Chaos Daemons,THE CHANGELING,90,105,"16,67%",,,
Chaos Daemons,THE MASQUE OF SLAANESH,95,95,"0,00%",,,
Chaos Daemons,TORMENTBRINGER,140,135,"-3,57%",,,
Chaos Daemons,TRANCEWEAVER,60,60,"0,00%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Chaos Daemons 1 Beasts Of Nurgle 65 70 7,69%
3 Chaos Daemons 1 Hellflayers 80 80 0,00%
4 Chaos Daemons 10 Flesh Hounds 150
5 Chaos Daemons 10 Seekers 155
6 Chaos Daemons 2 Beasts Of Nurgle 140
7 Chaos Daemons 2 Hellflayers 160
8 Chaos Daemons 3 Bloodcrushers 110 95 -13,64% 105
9 Chaos Daemons 3 Fiends 95 90 -5,26%
10 Chaos Daemons 3 Flamers 65 65 0,00%
11 Chaos Daemons 3 Nurglings 40 45 12,50%
12 Chaos Daemons 3 Plague Drones 110 110 0,00%
13 Chaos Daemons 3 Screamers 80 80 0,00%
14 Chaos Daemons 5 Flesh Hounds 75 75 0,00%
15 Chaos Daemons 5 Seekers 80 80 0,00%
16 Chaos Daemons 6 Bloodcrushers 180 190
17 Chaos Daemons 6 Fiends 180
18 Chaos Daemons 6 Flamers 130
19 Chaos Daemons 6 Nurglings 90
20 Chaos Daemons 6 Plague Drones 220
21 Chaos Daemons 6 Screamers 160
22 Chaos Daemons BE’LAKOR 375 390 4,00%
23 Chaos Daemons BLOODLETTERS 110 110 0,00%
24 Chaos Daemons BLOODMASTER 65 65 0,00%
25 Chaos Daemons BLOODTHIRSTER 305 320 4,92% 335
26 Chaos Daemons BLUE HORRORS 125 125 0,00%
27 Chaos Daemons BURNING CHARIOT 115 115 0,00%
28 Chaos Daemons CHANGECASTER 60 60 0,00%
29 Chaos Daemons CONTORTED EPITOME 100 100 0,00%
30 Chaos Daemons DAEMON PRINCE OF CHAOS 190 165 -13,16%
31 Chaos Daemons DAEMON PRINCE OF CHAOS WITH WINGS 180 190 5,56%
32 Chaos Daemons DAEMONETTES 100 90 -10,00%
33 Chaos Daemons EPIDEMIUS 80 80 0,00%
34 Chaos Daemons EXALTED FLAMER 65 65 0,00%
35 Chaos Daemons FATESKIMMER 95 95 0,00%
36 Chaos Daemons FECULENT GNARLMAW 100 100 0,00%
37 Chaos Daemons FLUXMASTER 60 80 33,33%
38 Chaos Daemons GREAT UNCLEAN ONE 250 265 6,00% 280
39 Chaos Daemons HORTICULOUS SLIMUX 120 120 0,00%
40 Chaos Daemons INFERNAL ENRAPTURESS 60 60 0,00%
41 Chaos Daemons KAIROS FATEWEAVER 295 295 0,00%
42 Chaos Daemons KARANAK 75 70 -6,67%
43 Chaos Daemons KEEPER OF SECRETS 240 255 6,25% 270
44 Chaos Daemons LORD OF CHANGE 285 300 5,26% 315
45 Chaos Daemons PINK HORRORS 140 150 7,14%
46 Chaos Daemons PLAGUEBEARERS 110 115 4,55%
47 Chaos Daemons POXBRINGER 55 55 0,00%
48 Chaos Daemons RENDMASTER ON BLOOD THRONE 165 150 -9,09% 170
49 Chaos Daemons ROTIGUS 265 280 5,66%
50 Chaos Daemons SHALAXI HELBANE 340 340 0,00%
51 Chaos Daemons SKARBRAND 305 315 3,28%
52 Chaos Daemons SKULL ALTAR 105 105 0,00%
53 Chaos Daemons SKULL CANNON 95 90 -5,26%
54 Chaos Daemons SKULLMASTER 100 85 -15,00%
55 Chaos Daemons SKULLTAKER 85 85 0,00%
56 Chaos Daemons SLOPPITY BILEPIPER 55 55 0,00%
57 Chaos Daemons SOUL GRINDER 180 180 0,00% 195
58 Chaos Daemons SPOILPOX SCRIVENER 60 60 0,00%
59 Chaos Daemons SYLL’ESSKE 120 120 0,00%
60 Chaos Daemons THE BLUE SCRIBES 75 75 0,00%
61 Chaos Daemons THE CHANGELING 90 105 16,67%
62 Chaos Daemons THE MASQUE OF SLAANESH 95 95 0,00%
63 Chaos Daemons TORMENTBRINGER 140 135 -3,57%
64 Chaos Daemons TRANCEWEAVER 60 60 0,00%

21
csv/chaos-knights.csv Normal file
View File

@@ -0,0 +1,21 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Chaos Knights,CHAOS ACASTUS KNIGHT ASTERIUS,765,785,"2,61%",860,,
Chaos Knights,CHAOS ACASTUS KNIGHT PORPHYRION,700,725,"3,57%",800,,
Chaos Knights,CHAOS CERASTUS KNIGHT ACHERON,385,370,"-3,90%",385,,
Chaos Knights,CHAOS CERASTUS KNIGHT ATRAPOS,395,395,"0,00%",415,,
Chaos Knights,CHAOS CERASTUS KNIGHT CASTIGATOR,385,370,"-3,90%",385,,
Chaos Knights,CHAOS CERASTUS KNIGHT LANCER,385,395,"2,60%",415,,
Chaos Knights,CHAOS QUESTORIS KNIGHT MAGAERA,375,375,"0,00%",390,,
Chaos Knights,CHAOS QUESTORIS KNIGHT STYRIX,375,365,"-2,67%",380,,
Chaos Knights,KNIGHT ABOMINANT,355,355,"0,00%",,370,
Chaos Knights,KNIGHT DESECRATOR,355,355,"0,00%",,370,
Chaos Knights,KNIGHT DESPOILER,390,380,"-2,56%",400,,
Chaos Knights,KNIGHT RAMPAGER,365,365,"0,00%",,380,
Chaos Knights,KNIGHT RUINATOR,355,340,"-4,23%",,355,
Chaos Knights,KNIGHT TYRANT,410,400,"-2,44%",420,,
Chaos Knights,WAR DOG BRIGAND,140,140,"0,00%",,,
Chaos Knights,WAR DOG EXECUTIONER,130,130,"0,00%",,,
Chaos Knights,WAR DOG HUNTSMAN,140,140,"0,00%",,,
Chaos Knights,WAR DOG KARNIVORE,150,155,"3,33%",,,
Chaos Knights,WAR DOG MOIRAX,150,150,"0,00%",,160,
Chaos Knights,WAR DOG STALKER,140,140,"0,00%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Chaos Knights CHAOS ACASTUS KNIGHT ASTERIUS 765 785 2,61% 860
3 Chaos Knights CHAOS ACASTUS KNIGHT PORPHYRION 700 725 3,57% 800
4 Chaos Knights CHAOS CERASTUS KNIGHT ACHERON 385 370 -3,90% 385
5 Chaos Knights CHAOS CERASTUS KNIGHT ATRAPOS 395 395 0,00% 415
6 Chaos Knights CHAOS CERASTUS KNIGHT CASTIGATOR 385 370 -3,90% 385
7 Chaos Knights CHAOS CERASTUS KNIGHT LANCER 385 395 2,60% 415
8 Chaos Knights CHAOS QUESTORIS KNIGHT MAGAERA 375 375 0,00% 390
9 Chaos Knights CHAOS QUESTORIS KNIGHT STYRIX 375 365 -2,67% 380
10 Chaos Knights KNIGHT ABOMINANT 355 355 0,00% 370
11 Chaos Knights KNIGHT DESECRATOR 355 355 0,00% 370
12 Chaos Knights KNIGHT DESPOILER 390 380 -2,56% 400
13 Chaos Knights KNIGHT RAMPAGER 365 365 0,00% 380
14 Chaos Knights KNIGHT RUINATOR 355 340 -4,23% 355
15 Chaos Knights KNIGHT TYRANT 410 400 -2,44% 420
16 Chaos Knights WAR DOG BRIGAND 140 140 0,00%
17 Chaos Knights WAR DOG EXECUTIONER 130 130 0,00%
18 Chaos Knights WAR DOG HUNTSMAN 140 140 0,00%
19 Chaos Knights WAR DOG KARNIVORE 150 155 3,33%
20 Chaos Knights WAR DOG MOIRAX 150 150 0,00% 160
21 Chaos Knights WAR DOG STALKER 140 140 0,00%

View File

@@ -0,0 +1,66 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Chaos Space Marines,10 Chaos Terminator Squad,,360,,,,
Chaos Space Marines,10 Chosen,,250,,260,,
Chaos Space Marines,10 Cultist Mob,50,50,"0,00%",,,
Chaos Space Marines,10 Legionaries,,170,,,,
Chaos Space Marines,10 Nemesis Claw,,190,,,,
Chaos Space Marines,10 Possessed,,250,,,260,
Chaos Space Marines,10 Raptors,,210,,,220,
Chaos Space Marines,10 Red Corsairs Raiders,,210,,,220,
Chaos Space Marines,10 Warp Talons,,280,,,290,
Chaos Space Marines,16 Accursed Cultists,,195,,215,,
Chaos Space Marines,20 Cultist Mob,,90,,,,
Chaos Space Marines,3 Chaos Bikers,70,70,"0,00%",,,
Chaos Space Marines,5 Chaos Terminator Squad,180,180,"0,00%",,,
Chaos Space Marines,5 Chosen,125,125,"0,00%",135,,
Chaos Space Marines,5 Legionaries,90,90,"0,00%",,,
Chaos Space Marines,5 Nemesis Claw,110,110,"0,00%",,,
Chaos Space Marines,5 Possessed,120,120,"0,00%",,130,
Chaos Space Marines,5 Raptors,110,110,"0,00%",,120,
Chaos Space Marines,5 Red Corsairs Raiders,110,110,"0,00%",,120,
Chaos Space Marines,5 Warp Talons,125,125,"0,00%",,135,
Chaos Space Marines,6 Chaos Bikers,,130,,,,
Chaos Space Marines,8 Accursed Cultists,90,90,"0,00%",110,,
Chaos Space Marines,ABADDON THE DESPOILER,270,285,"5,56%",,,
Chaos Space Marines,CHAOS LAND RAIDER,220,220,"0,00%",,240,
Chaos Space Marines,CHAOS LORD,90,90,"0,00%",,,
Chaos Space Marines,CHAOS LORD IN TERMINATOR ARMOUR,85,85,"0,00%",,,
Chaos Space Marines,CHAOS LORD WITH JUMP PACK,80,90,"12,50%",,,
Chaos Space Marines,CHAOS PREDATOR ANNIHILATOR,135,135,"0,00%",,145,
Chaos Space Marines,CHAOS PREDATOR DESTRUCTOR,140,140,"0,00%",,150,
Chaos Space Marines,CHAOS RHINO,75,75,"0,00%",,,
Chaos Space Marines,CHAOS SPAWN,70,60,"-14,29%",,,
Chaos Space Marines,CHAOS VINDICATOR,185,185,"0,00%",,195,
Chaos Space Marines,CULTIST FIREBRAND,45,45,"0,00%",,50,
Chaos Space Marines,CYPHER,90,90,"0,00%",,,
Chaos Space Marines,DARK APOSTLE,65,65,"0,00%",,,
Chaos Space Marines,DARK COMMUNE,90,90,"0,00%",100,,
Chaos Space Marines,DEFILER,250,300,"20,00%",330,,
Chaos Space Marines,FABIUS BILE,100,100,"0,00%",,,
Chaos Space Marines,FELLGOR BEASTMEN,70,60,"-14,29%",,,
Chaos Space Marines,FORGEFIEND,170,160,"-5,88%",,170,
Chaos Space Marines,HAARKEN WORLDCLAIMER,90,90,"0,00%",,,
Chaos Space Marines,HAVOCS,125,125,"0,00%",,135,
Chaos Space Marines,HELBRUTE,130,130,"0,00%",,,
Chaos Space Marines,HELDRAKE,205,175,"-14,63%",,,
Chaos Space Marines,HERETIC ASTARTES DAEMON PRINCE,165,165,"0,00%",,,
Chaos Space Marines,HERETIC ASTARTES DAEMON PRINCE WITH WINGS,,180,,,,
Chaos Space Marines,HURON BLACKHEART,120,120,"0,00%",,,
Chaos Space Marines,KHORNE LORD OF SKULLS,450,450,"0,00%",475,,
Chaos Space Marines,KRAVEK MORNE,,120,,,,
Chaos Space Marines,LORD DISCORDANT ON HELSTALKER,160,160,"0,00%",,,
Chaos Space Marines,MASTER OF EXECUTIONS,80,70,"-12,50%",,,
Chaos Space Marines,MASTER OF POSSESSION,60,60,"0,00%",,,
Chaos Space Marines,MASTERS OF THE MAELSTROM,115,135,"17,39%",,,
Chaos Space Marines,MAULERFIEND,130,130,"0,00%",,,
Chaos Space Marines,MUTILATORS,200,180,"-10,00%",,190,
Chaos Space Marines,NOCTILITH CROWN,125,125,"0,00%",,,
Chaos Space Marines,OBLITERATORS,160,160,"0,00%",,170,
Chaos Space Marines,RED CORSAIRS REAVE-CAPTAIN,75,70,"-6,67%",,,
Chaos Space Marines,SORCERER,60,60,"0,00%",,,
Chaos Space Marines,SORCERER IN TERMINATOR ARMOUR,80,80,"0,00%",,,
Chaos Space Marines,TRAITOR ENFORCER,55,70,"27,27%",,,
Chaos Space Marines,TRAITOR GUARDSMEN SQUAD,70,70,"0,00%",,,
Chaos Space Marines,VASHTORR THE ARKIFANE,175,205,"17,14%",,,
Chaos Space Marines,VENOMCRAWLER,110,110,"0,00%",,120,
Chaos Space Marines,WARPSMITH,70,60,"-14,29%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Chaos Space Marines 10 Chaos Terminator Squad 360
3 Chaos Space Marines 10 Chosen 250 260
4 Chaos Space Marines 10 Cultist Mob 50 50 0,00%
5 Chaos Space Marines 10 Legionaries 170
6 Chaos Space Marines 10 Nemesis Claw 190
7 Chaos Space Marines 10 Possessed 250 260
8 Chaos Space Marines 10 Raptors 210 220
9 Chaos Space Marines 10 Red Corsairs Raiders 210 220
10 Chaos Space Marines 10 Warp Talons 280 290
11 Chaos Space Marines 16 Accursed Cultists 195 215
12 Chaos Space Marines 20 Cultist Mob 90
13 Chaos Space Marines 3 Chaos Bikers 70 70 0,00%
14 Chaos Space Marines 5 Chaos Terminator Squad 180 180 0,00%
15 Chaos Space Marines 5 Chosen 125 125 0,00% 135
16 Chaos Space Marines 5 Legionaries 90 90 0,00%
17 Chaos Space Marines 5 Nemesis Claw 110 110 0,00%
18 Chaos Space Marines 5 Possessed 120 120 0,00% 130
19 Chaos Space Marines 5 Raptors 110 110 0,00% 120
20 Chaos Space Marines 5 Red Corsairs Raiders 110 110 0,00% 120
21 Chaos Space Marines 5 Warp Talons 125 125 0,00% 135
22 Chaos Space Marines 6 Chaos Bikers 130
23 Chaos Space Marines 8 Accursed Cultists 90 90 0,00% 110
24 Chaos Space Marines ABADDON THE DESPOILER 270 285 5,56%
25 Chaos Space Marines CHAOS LAND RAIDER 220 220 0,00% 240
26 Chaos Space Marines CHAOS LORD 90 90 0,00%
27 Chaos Space Marines CHAOS LORD IN TERMINATOR ARMOUR 85 85 0,00%
28 Chaos Space Marines CHAOS LORD WITH JUMP PACK 80 90 12,50%
29 Chaos Space Marines CHAOS PREDATOR ANNIHILATOR 135 135 0,00% 145
30 Chaos Space Marines CHAOS PREDATOR DESTRUCTOR 140 140 0,00% 150
31 Chaos Space Marines CHAOS RHINO 75 75 0,00%
32 Chaos Space Marines CHAOS SPAWN 70 60 -14,29%
33 Chaos Space Marines CHAOS VINDICATOR 185 185 0,00% 195
34 Chaos Space Marines CULTIST FIREBRAND 45 45 0,00% 50
35 Chaos Space Marines CYPHER 90 90 0,00%
36 Chaos Space Marines DARK APOSTLE 65 65 0,00%
37 Chaos Space Marines DARK COMMUNE 90 90 0,00% 100
38 Chaos Space Marines DEFILER 250 300 20,00% 330
39 Chaos Space Marines FABIUS BILE 100 100 0,00%
40 Chaos Space Marines FELLGOR BEASTMEN 70 60 -14,29%
41 Chaos Space Marines FORGEFIEND 170 160 -5,88% 170
42 Chaos Space Marines HAARKEN WORLDCLAIMER 90 90 0,00%
43 Chaos Space Marines HAVOCS 125 125 0,00% 135
44 Chaos Space Marines HELBRUTE 130 130 0,00%
45 Chaos Space Marines HELDRAKE 205 175 -14,63%
46 Chaos Space Marines HERETIC ASTARTES DAEMON PRINCE 165 165 0,00%
47 Chaos Space Marines HERETIC ASTARTES DAEMON PRINCE WITH WINGS 180
48 Chaos Space Marines HURON BLACKHEART 120 120 0,00%
49 Chaos Space Marines KHORNE LORD OF SKULLS 450 450 0,00% 475
50 Chaos Space Marines KRAVEK MORNE 120
51 Chaos Space Marines LORD DISCORDANT ON HELSTALKER 160 160 0,00%
52 Chaos Space Marines MASTER OF EXECUTIONS 80 70 -12,50%
53 Chaos Space Marines MASTER OF POSSESSION 60 60 0,00%
54 Chaos Space Marines MASTERS OF THE MAELSTROM 115 135 17,39%
55 Chaos Space Marines MAULERFIEND 130 130 0,00%
56 Chaos Space Marines MUTILATORS 200 180 -10,00% 190
57 Chaos Space Marines NOCTILITH CROWN 125 125 0,00%
58 Chaos Space Marines OBLITERATORS 160 160 0,00% 170
59 Chaos Space Marines RED CORSAIRS REAVE-CAPTAIN 75 70 -6,67%
60 Chaos Space Marines SORCERER 60 60 0,00%
61 Chaos Space Marines SORCERER IN TERMINATOR ARMOUR 80 80 0,00%
62 Chaos Space Marines TRAITOR ENFORCER 55 70 27,27%
63 Chaos Space Marines TRAITOR GUARDSMEN SQUAD 70 70 0,00%
64 Chaos Space Marines VASHTORR THE ARKIFANE 175 205 17,14%
65 Chaos Space Marines VENOMCRAWLER 110 110 0,00% 120
66 Chaos Space Marines WARPSMITH 70 60 -14,29%

View File

@@ -0,0 +1,5 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Chaos Titan Legions,CHAOS REAVER TITAN,,2200,,,,
Chaos Titan Legions,CHAOS WARBRINGER NEMESIS TITAN,,2600,,,,
Chaos Titan Legions,CHAOS WARHOUND TITAN,,1100,,,,
Chaos Titan Legions,CHAOS WARLORD TITAN,,3500,,,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Chaos Titan Legions CHAOS REAVER TITAN 2200
3 Chaos Titan Legions CHAOS WARBRINGER NEMESIS TITAN 2600
4 Chaos Titan Legions CHAOS WARHOUND TITAN 1100
5 Chaos Titan Legions CHAOS WARLORD TITAN 3500

127
csv/dark-angels.csv Normal file
View File

@@ -0,0 +1,127 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Dark Angels,1 Firestrike Servo-Turrets,75,75,"0,00%",,,
Dark Angels,10 Assault Intercessor Squad,,150,,,,
Dark Angels,10 Assault Intercessors With Jump Packs,,160,,,170,
Dark Angels,10 Deathwing Terminator Squad,,330,,,,
Dark Angels,10 Devastator Squad,,200,,,,
Dark Angels,10 Heavy Intercessor Squad,,200,,,,
Dark Angels,10 Hellblaster Squad,,220,,,,
Dark Angels,10 Incursor Squad,,150,,,160,
Dark Angels,10 Infernus Squad,,170,,,,
Dark Angels,10 Infiltrator Squad,,180,,,190,
Dark Angels,10 Intercessor Squad,,150,,,,
Dark Angels,10 Reiver Squad,,150,,,,
Dark Angels,10 Scout Squad,,120,,,130,
Dark Angels,10 Sternguard Veteran Squad,,190,,,,
Dark Angels,10 Terminator Assault Squad,,310,,,,
Dark Angels,10 Terminator Squad,,320,,,,
Dark Angels,10 Vanguard Veteran Squad With Jump Packs,,200,,,210,
Dark Angels,2 Firestrike Servo-Turrets,,150,,,,
Dark Angels,3 Aggressor Squad,95,90,"-5,26%",,100,
Dark Angels,3 Bladeguard Veteran Squad,80,80,"0,00%",,90,
Dark Angels,3 Centurion Assault Squad,150,150,"0,00%",,,
Dark Angels,3 Centurion Devastator Squad,175,175,"0,00%",,,
Dark Angels,3 Eradicator Squad,90,90,"0,00%",,100,
Dark Angels,3 Inceptor Squad,120,120,"0,00%",,135,
Dark Angels,3 Inner Circle Companions,90,80,"-11,11%",,90,
Dark Angels,3 Outrider Squad,80,70,"-12,50%",,,
Dark Angels,3 Ravenwing Black Knights,80,75,"-6,25%",,85,
Dark Angels,5 Assault Intercessor Squad,75,75,"0,00%",,,
Dark Angels,5 Assault Intercessors With Jump Packs,90,85,"-5,56%",,95,
Dark Angels,5 Deathwing Terminator Squad,180,165,"-8,33%",,,
Dark Angels,5 Devastator Squad,120,120,"0,00%",,,
Dark Angels,5 Heavy Intercessor Squad,100,100,"0,00%",,,
Dark Angels,5 Hellblaster Squad,110,110,"0,00%",,,
Dark Angels,5 Incursor Squad,80,85,"6,25%",,95,
Dark Angels,5 Infernus Squad,90,85,"-5,56%",,,
Dark Angels,5 Infiltrator Squad,100,110,"10,00%",,120,
Dark Angels,5 Intercessor Squad,80,80,"0,00%",,,
Dark Angels,5 Reiver Squad,80,75,"-6,25%",,,
Dark Angels,5 Scout Squad,70,65,"-7,14%",,75,
Dark Angels,5 Sternguard Veteran Squad,100,100,"0,00%",,,
Dark Angels,5 Terminator Assault Squad,180,155,"-13,89%",,,
Dark Angels,5 Terminator Squad,170,160,"-5,88%",,,
Dark Angels,5 Vanguard Veteran Squad With Jump Packs,100,100,"0,00%",,110,
Dark Angels,6 Aggressor Squad,,180,,,190,
Dark Angels,6 Bladeguard Veteran Squad,,160,,,170,
Dark Angels,6 Centurion Assault Squad,,300,,,,
Dark Angels,6 Centurion Devastator Squad,,350,,,,
Dark Angels,6 Eradicator Squad,,180,,,190,
Dark Angels,6 Inceptor Squad,,240,,,255,
Dark Angels,6 Inner Circle Companions,,170,,,180,
Dark Angels,6 Outrider Squad,,140,,,,
Dark Angels,6 Ravenwing Black Knights,,150,,,160,
Dark Angels,ANCIENT,50,40,"-20,00%",,,
Dark Angels,ANCIENT IN TERMINATOR ARMOUR,75,65,"-13,33%",,,
Dark Angels,APOTHECARY,50,40,"-20,00%",,,
Dark Angels,APOTHECARY BIOLOGIS,70,70,"0,00%",,,
Dark Angels,ASMODAI,70,70,"0,00%",,,
Dark Angels,ASTRAEUS,525,525,"0,00%",575,,
Dark Angels,AZRAEL,125,140,"12,00%",,,
Dark Angels,BALLISTUS DREADNOUGHT,150,150,"0,00%",,160,
Dark Angels,BELIAL,85,75,"-11,76%",,,
Dark Angels,BLADEGUARD ANCIENT,45,40,"-11,11%",,,
Dark Angels,BRUTALIS DREADNOUGHT,160,150,"-6,25%",,160,
Dark Angels,CAPTAIN,80,80,"0,00%",,,
Dark Angels,CAPTAIN IN GRAVIS ARMOUR,80,80,"0,00%",,,
Dark Angels,CAPTAIN IN PHOBOS ARMOUR,70,70,"0,00%",,,
Dark Angels,CAPTAIN IN TERMINATOR ARMOUR,95,85,"-10,53%",,,
Dark Angels,CAPTAIN WITH JUMP PACK,75,75,"0,00%",,,
Dark Angels,CHAPLAIN,60,60,"0,00%",,,
Dark Angels,CHAPLAIN IN TERMINATOR ARMOUR,75,75,"0,00%",,,
Dark Angels,CHAPLAIN ON BIKE,75,70,"-6,67%",,,
Dark Angels,CHAPLAIN WITH JUMP PACK,75,75,"0,00%",,,
Dark Angels,COMPANY HEROES,105,105,"0,00%",,,
Dark Angels,DEATHWING KNIGHTS,250,240,"-4,00%",260,,
Dark Angels,DESOLATION SQUAD,200,180,"-10,00%",210,,
Dark Angels,DREADNOUGHT,135,135,"0,00%",,,
Dark Angels,DROP POD,70,70,"0,00%",,,
Dark Angels,ELIMINATOR SQUAD,85,75,"-11,76%",,,
Dark Angels,ERADICATOR SQUAD WITH HEAVY BOLTERS,,70,,,80,
Dark Angels,EZEKIEL,75,75,"0,00%",,,
Dark Angels,GLADIATOR LANCER,160,160,"0,00%",,170,
Dark Angels,GLADIATOR REAPER,160,160,"0,00%",,170,
Dark Angels,GLADIATOR VALIANT,150,150,"0,00%",,160,
Dark Angels,HAMMERFALL BUNKER,175,175,"0,00%",,,
Dark Angels,IMPULSOR,80,80,"0,00%",,,
Dark Angels,INVADER ATV,60,60,"0,00%",,,
Dark Angels,INVICTOR TACTICAL WARSUIT,125,125,"0,00%",,,
Dark Angels,JUDICIAR,70,55,"-21,43%",,,
Dark Angels,LAND RAIDER,220,220,"0,00%",,240,
Dark Angels,LAND RAIDER CRUSADER,220,220,"0,00%",,240,
Dark Angels,LAND RAIDER REDEEMER,270,250,"-7,41%",,270,
Dark Angels,LAND SPEEDER,,95,,,105,
Dark Angels,LAND SPEEDER VENGEANCE,120,120,"0,00%",,130,
Dark Angels,LAZARUS,70,70,"0,00%",,,
Dark Angels,LIBRARIAN,65,60,"-7,69%",,,
Dark Angels,LIBRARIAN IN PHOBOS ARMOUR,70,70,"0,00%",,,
Dark Angels,LIBRARIAN IN TERMINATOR ARMOUR,75,75,"0,00%",,,
Dark Angels,LIEUTENANT,55,45,"-18,18%",,,
Dark Angels,LIEUTENANT IN PHOBOS ARMOUR,55,45,"-18,18%",,,
Dark Angels,LIEUTENANT IN REIVER ARMOUR,55,45,"-18,18%",,,
Dark Angels,LIEUTENANT WITH COMBI-WEAPON,85,85,"0,00%",,,
Dark Angels,LION ELJONSON,315,285,"-9,52%",,,
Dark Angels,NEPHILIM JETFIGHTER,195,195,"0,00%",,,
Dark Angels,PREDATOR ANNIHILATOR,135,135,"0,00%",,145,
Dark Angels,PREDATOR DESTRUCTOR,140,140,"0,00%",,150,
Dark Angels,RAVENWING COMMAND SQUAD,120,115,"-4,17%",,125,
Dark Angels,RAVENWING DARK TALON,210,200,"-4,76%",,,
Dark Angels,RAVENWING DARKSHROUD,100,80,"-20,00%",,,
Dark Angels,RAZORBACK,95,95,"0,00%",,,
Dark Angels,REDEMPTOR DREADNOUGHT,205,195,"-4,88%",,210,
Dark Angels,REPULSOR,180,170,"-5,56%",,190,
Dark Angels,REPULSOR EXECUTIONER,230,230,"0,00%",,250,
Dark Angels,RHINO,75,75,"0,00%",,,
Dark Angels,SAMMAEL,115,105,"-8,70%",,,
Dark Angels,STORM SPEEDER HAILSTRIKE,115,105,"-8,70%",,115,
Dark Angels,STORM SPEEDER HAMMERSTRIKE,125,130,"4,00%",,140,
Dark Angels,STORM SPEEDER THUNDERSTRIKE,135,135,"0,00%",,145,
Dark Angels,STORMHAWK INTERCEPTOR,155,155,"0,00%",,,
Dark Angels,STORMRAVEN GUNSHIP,280,280,"0,00%",300,,
Dark Angels,STORMTALON GUNSHIP,165,165,"0,00%",,,
Dark Angels,SUPPRESSOR SQUAD,75,75,"0,00%",,,
Dark Angels,TACTICAL SQUAD,140,140,"0,00%",,,
Dark Angels,TECHMARINE,55,55,"0,00%",,,
Dark Angels,THUNDERHAWK GUNSHIP,840,840,"0,00%",,,
Dark Angels,VINDICATOR,185,185,"0,00%",,200,
Dark Angels,WHIRLWIND,190,175,"-7,89%",195,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Dark Angels 1 Firestrike Servo-Turrets 75 75 0,00%
3 Dark Angels 10 Assault Intercessor Squad 150
4 Dark Angels 10 Assault Intercessors With Jump Packs 160 170
5 Dark Angels 10 Deathwing Terminator Squad 330
6 Dark Angels 10 Devastator Squad 200
7 Dark Angels 10 Heavy Intercessor Squad 200
8 Dark Angels 10 Hellblaster Squad 220
9 Dark Angels 10 Incursor Squad 150 160
10 Dark Angels 10 Infernus Squad 170
11 Dark Angels 10 Infiltrator Squad 180 190
12 Dark Angels 10 Intercessor Squad 150
13 Dark Angels 10 Reiver Squad 150
14 Dark Angels 10 Scout Squad 120 130
15 Dark Angels 10 Sternguard Veteran Squad 190
16 Dark Angels 10 Terminator Assault Squad 310
17 Dark Angels 10 Terminator Squad 320
18 Dark Angels 10 Vanguard Veteran Squad With Jump Packs 200 210
19 Dark Angels 2 Firestrike Servo-Turrets 150
20 Dark Angels 3 Aggressor Squad 95 90 -5,26% 100
21 Dark Angels 3 Bladeguard Veteran Squad 80 80 0,00% 90
22 Dark Angels 3 Centurion Assault Squad 150 150 0,00%
23 Dark Angels 3 Centurion Devastator Squad 175 175 0,00%
24 Dark Angels 3 Eradicator Squad 90 90 0,00% 100
25 Dark Angels 3 Inceptor Squad 120 120 0,00% 135
26 Dark Angels 3 Inner Circle Companions 90 80 -11,11% 90
27 Dark Angels 3 Outrider Squad 80 70 -12,50%
28 Dark Angels 3 Ravenwing Black Knights 80 75 -6,25% 85
29 Dark Angels 5 Assault Intercessor Squad 75 75 0,00%
30 Dark Angels 5 Assault Intercessors With Jump Packs 90 85 -5,56% 95
31 Dark Angels 5 Deathwing Terminator Squad 180 165 -8,33%
32 Dark Angels 5 Devastator Squad 120 120 0,00%
33 Dark Angels 5 Heavy Intercessor Squad 100 100 0,00%
34 Dark Angels 5 Hellblaster Squad 110 110 0,00%
35 Dark Angels 5 Incursor Squad 80 85 6,25% 95
36 Dark Angels 5 Infernus Squad 90 85 -5,56%
37 Dark Angels 5 Infiltrator Squad 100 110 10,00% 120
38 Dark Angels 5 Intercessor Squad 80 80 0,00%
39 Dark Angels 5 Reiver Squad 80 75 -6,25%
40 Dark Angels 5 Scout Squad 70 65 -7,14% 75
41 Dark Angels 5 Sternguard Veteran Squad 100 100 0,00%
42 Dark Angels 5 Terminator Assault Squad 180 155 -13,89%
43 Dark Angels 5 Terminator Squad 170 160 -5,88%
44 Dark Angels 5 Vanguard Veteran Squad With Jump Packs 100 100 0,00% 110
45 Dark Angels 6 Aggressor Squad 180 190
46 Dark Angels 6 Bladeguard Veteran Squad 160 170
47 Dark Angels 6 Centurion Assault Squad 300
48 Dark Angels 6 Centurion Devastator Squad 350
49 Dark Angels 6 Eradicator Squad 180 190
50 Dark Angels 6 Inceptor Squad 240 255
51 Dark Angels 6 Inner Circle Companions 170 180
52 Dark Angels 6 Outrider Squad 140
53 Dark Angels 6 Ravenwing Black Knights 150 160
54 Dark Angels ANCIENT 50 40 -20,00%
55 Dark Angels ANCIENT IN TERMINATOR ARMOUR 75 65 -13,33%
56 Dark Angels APOTHECARY 50 40 -20,00%
57 Dark Angels APOTHECARY BIOLOGIS 70 70 0,00%
58 Dark Angels ASMODAI 70 70 0,00%
59 Dark Angels ASTRAEUS 525 525 0,00% 575
60 Dark Angels AZRAEL 125 140 12,00%
61 Dark Angels BALLISTUS DREADNOUGHT 150 150 0,00% 160
62 Dark Angels BELIAL 85 75 -11,76%
63 Dark Angels BLADEGUARD ANCIENT 45 40 -11,11%
64 Dark Angels BRUTALIS DREADNOUGHT 160 150 -6,25% 160
65 Dark Angels CAPTAIN 80 80 0,00%
66 Dark Angels CAPTAIN IN GRAVIS ARMOUR 80 80 0,00%
67 Dark Angels CAPTAIN IN PHOBOS ARMOUR 70 70 0,00%
68 Dark Angels CAPTAIN IN TERMINATOR ARMOUR 95 85 -10,53%
69 Dark Angels CAPTAIN WITH JUMP PACK 75 75 0,00%
70 Dark Angels CHAPLAIN 60 60 0,00%
71 Dark Angels CHAPLAIN IN TERMINATOR ARMOUR 75 75 0,00%
72 Dark Angels CHAPLAIN ON BIKE 75 70 -6,67%
73 Dark Angels CHAPLAIN WITH JUMP PACK 75 75 0,00%
74 Dark Angels COMPANY HEROES 105 105 0,00%
75 Dark Angels DEATHWING KNIGHTS 250 240 -4,00% 260
76 Dark Angels DESOLATION SQUAD 200 180 -10,00% 210
77 Dark Angels DREADNOUGHT 135 135 0,00%
78 Dark Angels DROP POD 70 70 0,00%
79 Dark Angels ELIMINATOR SQUAD 85 75 -11,76%
80 Dark Angels ERADICATOR SQUAD WITH HEAVY BOLTERS 70 80
81 Dark Angels EZEKIEL 75 75 0,00%
82 Dark Angels GLADIATOR LANCER 160 160 0,00% 170
83 Dark Angels GLADIATOR REAPER 160 160 0,00% 170
84 Dark Angels GLADIATOR VALIANT 150 150 0,00% 160
85 Dark Angels HAMMERFALL BUNKER 175 175 0,00%
86 Dark Angels IMPULSOR 80 80 0,00%
87 Dark Angels INVADER ATV 60 60 0,00%
88 Dark Angels INVICTOR TACTICAL WARSUIT 125 125 0,00%
89 Dark Angels JUDICIAR 70 55 -21,43%
90 Dark Angels LAND RAIDER 220 220 0,00% 240
91 Dark Angels LAND RAIDER CRUSADER 220 220 0,00% 240
92 Dark Angels LAND RAIDER REDEEMER 270 250 -7,41% 270
93 Dark Angels LAND SPEEDER 95 105
94 Dark Angels LAND SPEEDER VENGEANCE 120 120 0,00% 130
95 Dark Angels LAZARUS 70 70 0,00%
96 Dark Angels LIBRARIAN 65 60 -7,69%
97 Dark Angels LIBRARIAN IN PHOBOS ARMOUR 70 70 0,00%
98 Dark Angels LIBRARIAN IN TERMINATOR ARMOUR 75 75 0,00%
99 Dark Angels LIEUTENANT 55 45 -18,18%
100 Dark Angels LIEUTENANT IN PHOBOS ARMOUR 55 45 -18,18%
101 Dark Angels LIEUTENANT IN REIVER ARMOUR 55 45 -18,18%
102 Dark Angels LIEUTENANT WITH COMBI-WEAPON 85 85 0,00%
103 Dark Angels LION EL’JONSON 315 285 -9,52%
104 Dark Angels NEPHILIM JETFIGHTER 195 195 0,00%
105 Dark Angels PREDATOR ANNIHILATOR 135 135 0,00% 145
106 Dark Angels PREDATOR DESTRUCTOR 140 140 0,00% 150
107 Dark Angels RAVENWING COMMAND SQUAD 120 115 -4,17% 125
108 Dark Angels RAVENWING DARK TALON 210 200 -4,76%
109 Dark Angels RAVENWING DARKSHROUD 100 80 -20,00%
110 Dark Angels RAZORBACK 95 95 0,00%
111 Dark Angels REDEMPTOR DREADNOUGHT 205 195 -4,88% 210
112 Dark Angels REPULSOR 180 170 -5,56% 190
113 Dark Angels REPULSOR EXECUTIONER 230 230 0,00% 250
114 Dark Angels RHINO 75 75 0,00%
115 Dark Angels SAMMAEL 115 105 -8,70%
116 Dark Angels STORM SPEEDER HAILSTRIKE 115 105 -8,70% 115
117 Dark Angels STORM SPEEDER HAMMERSTRIKE 125 130 4,00% 140
118 Dark Angels STORM SPEEDER THUNDERSTRIKE 135 135 0,00% 145
119 Dark Angels STORMHAWK INTERCEPTOR 155 155 0,00%
120 Dark Angels STORMRAVEN GUNSHIP 280 280 0,00% 300
121 Dark Angels STORMTALON GUNSHIP 165 165 0,00%
122 Dark Angels SUPPRESSOR SQUAD 75 75 0,00%
123 Dark Angels TACTICAL SQUAD 140 140 0,00%
124 Dark Angels TECHMARINE 55 55 0,00%
125 Dark Angels THUNDERHAWK GUNSHIP 840 840 0,00%
126 Dark Angels VINDICATOR 185 185 0,00% 200
127 Dark Angels WHIRLWIND 190 175 -7,89% 195

47
csv/death-guard.csv Normal file
View File

@@ -0,0 +1,47 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Death Guard,1 Beasts Of Nurgle,65,70,"7,69%",,,
Death Guard,1 Myphitic Blight-Haulers,100,100,"0,00%",,,
Death Guard,10 Blightlord Terminators,,370,,,,
Death Guard,10 Plague Marines,,190,,,,
Death Guard,10 Poxwalkers,65,65,"0,00%",,,
Death Guard,2 Beasts Of Nurgle,,140,,,,
Death Guard,2 Myphitic Blight-Haulers,,200,,,,
Death Guard,20 Poxwalkers,,130,,,,
Death Guard,3 Blightlord Terminators,115,115,"0,00%",,,
Death Guard,3 Deathshroud Terminators,160,160,"0,00%",,170,
Death Guard,3 Nurglings,40,45,"12,50%",,,
Death Guard,3 Plague Drones,115,110,"-4,35%",,,
Death Guard,5 Blightlord Terminators,,185,,,,
Death Guard,5 Plague Marines,95,90,"-5,26%",,,
Death Guard,6 Deathshroud Terminators,,320,,,330,
Death Guard,6 Nurglings,,90,,,,
Death Guard,6 Plague Drones,,220,,,,
Death Guard,7 Plague Marines,,125,,,,
Death Guard,BIOLOGUS PUTRIFIER,60,60,"0,00%",,,
Death Guard,CHAOS LAND RAIDER,220,220,"0,00%",,240,
Death Guard,CHAOS PREDATOR ANNIHILATOR,135,135,"0,00%",,145,
Death Guard,CHAOS PREDATOR DESTRUCTOR,145,145,"0,00%",,155,
Death Guard,CHAOS RHINO,85,85,"0,00%",,,
Death Guard,CHAOS SPAWN,70,80,"14,29%",,,
Death Guard,DAEMON PRINCE OF NURGLE,195,195,"0,00%",,,
Death Guard,DAEMON PRINCE OF NURGLE WITH WINGS,180,170,"-5,56%",,,
Death Guard,DEFILER,250,290,"16,00%",320,,
Death Guard,FOETID BLOAT-DRONE,100,100,"0,00%",,110,
Death Guard,FOETID BLOAT-DRONE WITH HEAVY BLIGHT LAUNCHER,,125,,,135,
Death Guard,FOUL BLIGHTSPAWN,75,65,"-13,33%",,,
Death Guard,GREAT UNCLEAN ONE,250,265,"6,00%",,280,
Death Guard,HELBRUTE,115,110,"-4,35%",,,
Death Guard,ICON BEARER,45,45,"0,00%",,,
Death Guard,LORD OF CONTAGION,120,120,"0,00%",,,
Death Guard,LORD OF POXES,75,65,"-13,33%",,,
Death Guard,LORD OF VIRULENCE,100,100,"0,00%",,,
Death Guard,MALIGNANT PLAGUECASTER,60,60,"0,00%",,,
Death Guard,MIASMIC MALIGNIFIER,105,105,"0,00%",,,
Death Guard,MORTARION,380,400,"5,26%",,,
Death Guard,NOXIOUS BLIGHTBRINGER,50,60,"20,00%",,,
Death Guard,PLAGUE SURGEON,50,50,"0,00%",,,
Death Guard,PLAGUEBEARERS,110,115,"4,55%",,,
Death Guard,PLAGUEBURST CRAWLER,210,185,"-11,90%",210,,
Death Guard,ROTIGUS,265,280,"5,66%",,,
Death Guard,TALLYMAN,50,60,"20,00%",,,
Death Guard,TYPHUS,100,100,"0,00%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Death Guard 1 Beasts Of Nurgle 65 70 7,69%
3 Death Guard 1 Myphitic Blight-Haulers 100 100 0,00%
4 Death Guard 10 Blightlord Terminators 370
5 Death Guard 10 Plague Marines 190
6 Death Guard 10 Poxwalkers 65 65 0,00%
7 Death Guard 2 Beasts Of Nurgle 140
8 Death Guard 2 Myphitic Blight-Haulers 200
9 Death Guard 20 Poxwalkers 130
10 Death Guard 3 Blightlord Terminators 115 115 0,00%
11 Death Guard 3 Deathshroud Terminators 160 160 0,00% 170
12 Death Guard 3 Nurglings 40 45 12,50%
13 Death Guard 3 Plague Drones 115 110 -4,35%
14 Death Guard 5 Blightlord Terminators 185
15 Death Guard 5 Plague Marines 95 90 -5,26%
16 Death Guard 6 Deathshroud Terminators 320 330
17 Death Guard 6 Nurglings 90
18 Death Guard 6 Plague Drones 220
19 Death Guard 7 Plague Marines 125
20 Death Guard BIOLOGUS PUTRIFIER 60 60 0,00%
21 Death Guard CHAOS LAND RAIDER 220 220 0,00% 240
22 Death Guard CHAOS PREDATOR ANNIHILATOR 135 135 0,00% 145
23 Death Guard CHAOS PREDATOR DESTRUCTOR 145 145 0,00% 155
24 Death Guard CHAOS RHINO 85 85 0,00%
25 Death Guard CHAOS SPAWN 70 80 14,29%
26 Death Guard DAEMON PRINCE OF NURGLE 195 195 0,00%
27 Death Guard DAEMON PRINCE OF NURGLE WITH WINGS 180 170 -5,56%
28 Death Guard DEFILER 250 290 16,00% 320
29 Death Guard FOETID BLOAT-DRONE 100 100 0,00% 110
30 Death Guard FOETID BLOAT-DRONE WITH HEAVY BLIGHT LAUNCHER 125 135
31 Death Guard FOUL BLIGHTSPAWN 75 65 -13,33%
32 Death Guard GREAT UNCLEAN ONE 250 265 6,00% 280
33 Death Guard HELBRUTE 115 110 -4,35%
34 Death Guard ICON BEARER 45 45 0,00%
35 Death Guard LORD OF CONTAGION 120 120 0,00%
36 Death Guard LORD OF POXES 75 65 -13,33%
37 Death Guard LORD OF VIRULENCE 100 100 0,00%
38 Death Guard MALIGNANT PLAGUECASTER 60 60 0,00%
39 Death Guard MIASMIC MALIGNIFIER 105 105 0,00%
40 Death Guard MORTARION 380 400 5,26%
41 Death Guard NOXIOUS BLIGHTBRINGER 50 60 20,00%
42 Death Guard PLAGUE SURGEON 50 50 0,00%
43 Death Guard PLAGUEBEARERS 110 115 4,55%
44 Death Guard PLAGUEBURST CRAWLER 210 185 -11,90% 210
45 Death Guard ROTIGUS 265 280 5,66%
46 Death Guard TALLYMAN 50 60 20,00%
47 Death Guard TYPHUS 100 100 0,00%

112
csv/deathwatch.csv Normal file
View File

@@ -0,0 +1,112 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Deathwatch,1 Firestrike Servo-Turrets,75,75,"0,00%",,,
Deathwatch,10 Assault Intercessor Squad,,150,,,,
Deathwatch,10 Assault Intercessors With Jump Packs,,160,,,170,
Deathwatch,10 Deathwatch Terminator Squad,,330,,,,
Deathwatch,10 Deathwatch Veterans,,190,,,,
Deathwatch,10 Decimus Kill Team,,190,,,,
Deathwatch,10 Heavy Intercessor Squad,,200,,,,
Deathwatch,10 Hellblaster Squad,,220,,,,
Deathwatch,10 Incursor Squad,,150,,,160,
Deathwatch,10 Infernus Squad,,170,,,,
Deathwatch,10 Infiltrator Squad,,180,,,190,
Deathwatch,10 Intercessor Squad,,150,,,,
Deathwatch,10 Reiver Squad,,150,,,,
Deathwatch,10 Sternguard Veteran Squad,,190,,,,
Deathwatch,10 Vanguard Veteran Squad With Jump Packs,,200,,,210,
Deathwatch,2 Firestrike Servo-Turrets,,150,,,,
Deathwatch,3 Aggressor Squad,95,90,"-5,26%",,100,
Deathwatch,3 Bladeguard Veteran Squad,80,80,"0,00%",,90,
Deathwatch,3 Centurion Assault Squad,150,150,"0,00%",,,
Deathwatch,3 Centurion Devastator Squad,175,175,"0,00%",,,
Deathwatch,3 Eradicator Squad,90,90,"0,00%",,100,
Deathwatch,3 Inceptor Squad,120,120,"0,00%",,135,
Deathwatch,3 Outrider Squad,80,70,"-12,50%",,,
Deathwatch,5 Assault Intercessor Squad,75,75,"0,00%",,,
Deathwatch,5 Assault Intercessors With Jump Packs,90,85,"-5,56%",,95,
Deathwatch,5 Deathwatch Terminator Squad,190,180,"-5,26%",,,
Deathwatch,5 Deathwatch Veterans,100,100,"0,00%",,,
Deathwatch,5 Decimus Kill Team,100,100,"0,00%",,,
Deathwatch,5 Heavy Intercessor Squad,100,100,"0,00%",,,
Deathwatch,5 Hellblaster Squad,110,110,"0,00%",,,
Deathwatch,5 Incursor Squad,80,85,"6,25%",,95,
Deathwatch,5 Infernus Squad,90,85,"-5,56%",,,
Deathwatch,5 Infiltrator Squad,100,110,"10,00%",,120,
Deathwatch,5 Intercessor Squad,80,80,"0,00%",,,
Deathwatch,5 Reiver Squad,80,75,"-6,25%",,,
Deathwatch,5 Sternguard Veteran Squad,100,100,"0,00%",,,
Deathwatch,5 Vanguard Veteran Squad With Jump Packs,100,100,"0,00%",,110,
Deathwatch,6 Aggressor Squad,,180,,,190,
Deathwatch,6 Bladeguard Veteran Squad,,160,,,170,
Deathwatch,6 Centurion Assault Squad,,300,,,,
Deathwatch,6 Centurion Devastator Squad,,350,,,,
Deathwatch,6 Eradicator Squad,,180,,,190,
Deathwatch,6 Inceptor Squad,,240,,,255,
Deathwatch,6 Outrider Squad,,140,,,,
Deathwatch,ANCIENT,50,40,"-20,00%",,,
Deathwatch,ANCIENT IN TERMINATOR ARMOUR,75,65,"-13,33%",,,
Deathwatch,APOTHECARY,50,40,"-20,00%",,,
Deathwatch,APOTHECARY BIOLOGIS,70,70,"0,00%",,,
Deathwatch,ASTRAEUS,525,525,"0,00%",575,,
Deathwatch,BALLISTUS DREADNOUGHT,150,150,"0,00%",,160,
Deathwatch,BLADEGUARD ANCIENT,45,40,"-11,11%",,,
Deathwatch,BRUTALIS DREADNOUGHT,160,150,"-6,25%",,160,
Deathwatch,CAPTAIN,80,80,"0,00%",,,
Deathwatch,CAPTAIN IN GRAVIS ARMOUR,80,80,"0,00%",,,
Deathwatch,CAPTAIN IN PHOBOS ARMOUR,70,70,"0,00%",,,
Deathwatch,CAPTAIN IN TERMINATOR ARMOUR,95,85,"-10,53%",,,
Deathwatch,CAPTAIN WITH JUMP PACK,75,75,"0,00%",,,
Deathwatch,CHAPLAIN,60,60,"0,00%",,,
Deathwatch,CHAPLAIN IN TERMINATOR ARMOUR,75,75,"0,00%",,,
Deathwatch,CHAPLAIN ON BIKE,75,70,"-6,67%",,,
Deathwatch,CHAPLAIN WITH JUMP PACK,75,75,"0,00%",,,
Deathwatch,COMPANY HEROES,105,105,"0,00%",,,
Deathwatch,CORVUS BLACKSTAR,180,180,"0,00%",,,
Deathwatch,DESOLATION SQUAD,200,180,"-10,00%",210,,
Deathwatch,DREADNOUGHT,135,135,"0,00%",,,
Deathwatch,DROP POD,70,70,"0,00%",,,
Deathwatch,ELIMINATOR SQUAD,85,75,"-11,76%",,,
Deathwatch,ERADICATOR SQUAD WITH HEAVY BOLTERS,,70,,,80,
Deathwatch,FORTIS KILL TEAM,180,195,"8,33%",,210,
Deathwatch,GLADIATOR LANCER,160,160,"0,00%",,170,
Deathwatch,GLADIATOR REAPER,160,160,"0,00%",,170,
Deathwatch,GLADIATOR VALIANT,150,150,"0,00%",,160,
Deathwatch,HAMMERFALL BUNKER,175,175,"0,00%",,,
Deathwatch,IMPULSOR,80,80,"0,00%",,,
Deathwatch,INDOMITOR KILL TEAM,265,275,"3,77%",,290,
Deathwatch,INVADER ATV,60,60,"0,00%",,,
Deathwatch,INVICTOR TACTICAL WARSUIT,125,125,"0,00%",,,
Deathwatch,JUDICIAR,70,55,"-21,43%",,,
Deathwatch,LAND RAIDER,220,220,"0,00%",,240,
Deathwatch,LAND RAIDER CRUSADER,220,220,"0,00%",,240,
Deathwatch,LAND RAIDER REDEEMER,270,250,"-7,41%",,270,
Deathwatch,LAND SPEEDER,,95,,,105,
Deathwatch,LIBRARIAN,65,60,"-7,69%",,,
Deathwatch,LIBRARIAN IN PHOBOS ARMOUR,70,70,"0,00%",,,
Deathwatch,LIBRARIAN IN TERMINATOR ARMOUR,75,75,"0,00%",,,
Deathwatch,LIEUTENANT,55,45,"-18,18%",,,
Deathwatch,LIEUTENANT IN PHOBOS ARMOUR,55,45,"-18,18%",,,
Deathwatch,LIEUTENANT IN REIVER ARMOUR,55,45,"-18,18%",,,
Deathwatch,LIEUTENANT WITH COMBI-WEAPON,85,85,"0,00%",,,
Deathwatch,PREDATOR ANNIHILATOR,135,135,"0,00%",,145,
Deathwatch,PREDATOR DESTRUCTOR,140,140,"0,00%",,150,
Deathwatch,RAZORBACK,95,95,"0,00%",,,
Deathwatch,REDEMPTOR DREADNOUGHT,205,195,"-4,88%",,210,
Deathwatch,REPULSOR,180,170,"-5,56%",,190,
Deathwatch,REPULSOR EXECUTIONER,230,230,"0,00%",,250,
Deathwatch,RHINO,75,75,"0,00%",,,
Deathwatch,SPECTRUS KILL TEAM,180,170,"-5,56%",,180,
Deathwatch,STORM SPEEDER HAILSTRIKE,115,105,"-8,70%",,115,
Deathwatch,STORM SPEEDER HAMMERSTRIKE,125,130,"4,00%",,140,
Deathwatch,STORM SPEEDER THUNDERSTRIKE,135,135,"0,00%",,145,
Deathwatch,STORMHAWK INTERCEPTOR,155,155,"0,00%",,,
Deathwatch,STORMRAVEN GUNSHIP,280,280,"0,00%",300,,
Deathwatch,STORMTALON GUNSHIP,165,165,"0,00%",,,
Deathwatch,SUPPRESSOR SQUAD,75,75,"0,00%",,,
Deathwatch,TALONSTRIKE KILL TEAM,275,265,"-3,64%",,280,
Deathwatch,TECHMARINE,55,55,"0,00%",,,
Deathwatch,THUNDERHAWK GUNSHIP,840,840,"0,00%",,,
Deathwatch,VINDICATOR,185,185,"0,00%",,200,
Deathwatch,WATCH CAPTAIN ARTEMIS,65,65,"0,00%",,,
Deathwatch,WATCH MASTER,95,95,"0,00%",,,
Deathwatch,WHIRLWIND,190,175,"-7,89%",195,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Deathwatch 1 Firestrike Servo-Turrets 75 75 0,00%
3 Deathwatch 10 Assault Intercessor Squad 150
4 Deathwatch 10 Assault Intercessors With Jump Packs 160 170
5 Deathwatch 10 Deathwatch Terminator Squad 330
6 Deathwatch 10 Deathwatch Veterans 190
7 Deathwatch 10 Decimus Kill Team 190
8 Deathwatch 10 Heavy Intercessor Squad 200
9 Deathwatch 10 Hellblaster Squad 220
10 Deathwatch 10 Incursor Squad 150 160
11 Deathwatch 10 Infernus Squad 170
12 Deathwatch 10 Infiltrator Squad 180 190
13 Deathwatch 10 Intercessor Squad 150
14 Deathwatch 10 Reiver Squad 150
15 Deathwatch 10 Sternguard Veteran Squad 190
16 Deathwatch 10 Vanguard Veteran Squad With Jump Packs 200 210
17 Deathwatch 2 Firestrike Servo-Turrets 150
18 Deathwatch 3 Aggressor Squad 95 90 -5,26% 100
19 Deathwatch 3 Bladeguard Veteran Squad 80 80 0,00% 90
20 Deathwatch 3 Centurion Assault Squad 150 150 0,00%
21 Deathwatch 3 Centurion Devastator Squad 175 175 0,00%
22 Deathwatch 3 Eradicator Squad 90 90 0,00% 100
23 Deathwatch 3 Inceptor Squad 120 120 0,00% 135
24 Deathwatch 3 Outrider Squad 80 70 -12,50%
25 Deathwatch 5 Assault Intercessor Squad 75 75 0,00%
26 Deathwatch 5 Assault Intercessors With Jump Packs 90 85 -5,56% 95
27 Deathwatch 5 Deathwatch Terminator Squad 190 180 -5,26%
28 Deathwatch 5 Deathwatch Veterans 100 100 0,00%
29 Deathwatch 5 Decimus Kill Team 100 100 0,00%
30 Deathwatch 5 Heavy Intercessor Squad 100 100 0,00%
31 Deathwatch 5 Hellblaster Squad 110 110 0,00%
32 Deathwatch 5 Incursor Squad 80 85 6,25% 95
33 Deathwatch 5 Infernus Squad 90 85 -5,56%
34 Deathwatch 5 Infiltrator Squad 100 110 10,00% 120
35 Deathwatch 5 Intercessor Squad 80 80 0,00%
36 Deathwatch 5 Reiver Squad 80 75 -6,25%
37 Deathwatch 5 Sternguard Veteran Squad 100 100 0,00%
38 Deathwatch 5 Vanguard Veteran Squad With Jump Packs 100 100 0,00% 110
39 Deathwatch 6 Aggressor Squad 180 190
40 Deathwatch 6 Bladeguard Veteran Squad 160 170
41 Deathwatch 6 Centurion Assault Squad 300
42 Deathwatch 6 Centurion Devastator Squad 350
43 Deathwatch 6 Eradicator Squad 180 190
44 Deathwatch 6 Inceptor Squad 240 255
45 Deathwatch 6 Outrider Squad 140
46 Deathwatch ANCIENT 50 40 -20,00%
47 Deathwatch ANCIENT IN TERMINATOR ARMOUR 75 65 -13,33%
48 Deathwatch APOTHECARY 50 40 -20,00%
49 Deathwatch APOTHECARY BIOLOGIS 70 70 0,00%
50 Deathwatch ASTRAEUS 525 525 0,00% 575
51 Deathwatch BALLISTUS DREADNOUGHT 150 150 0,00% 160
52 Deathwatch BLADEGUARD ANCIENT 45 40 -11,11%
53 Deathwatch BRUTALIS DREADNOUGHT 160 150 -6,25% 160
54 Deathwatch CAPTAIN 80 80 0,00%
55 Deathwatch CAPTAIN IN GRAVIS ARMOUR 80 80 0,00%
56 Deathwatch CAPTAIN IN PHOBOS ARMOUR 70 70 0,00%
57 Deathwatch CAPTAIN IN TERMINATOR ARMOUR 95 85 -10,53%
58 Deathwatch CAPTAIN WITH JUMP PACK 75 75 0,00%
59 Deathwatch CHAPLAIN 60 60 0,00%
60 Deathwatch CHAPLAIN IN TERMINATOR ARMOUR 75 75 0,00%
61 Deathwatch CHAPLAIN ON BIKE 75 70 -6,67%
62 Deathwatch CHAPLAIN WITH JUMP PACK 75 75 0,00%
63 Deathwatch COMPANY HEROES 105 105 0,00%
64 Deathwatch CORVUS BLACKSTAR 180 180 0,00%
65 Deathwatch DESOLATION SQUAD 200 180 -10,00% 210
66 Deathwatch DREADNOUGHT 135 135 0,00%
67 Deathwatch DROP POD 70 70 0,00%
68 Deathwatch ELIMINATOR SQUAD 85 75 -11,76%
69 Deathwatch ERADICATOR SQUAD WITH HEAVY BOLTERS 70 80
70 Deathwatch FORTIS KILL TEAM 180 195 8,33% 210
71 Deathwatch GLADIATOR LANCER 160 160 0,00% 170
72 Deathwatch GLADIATOR REAPER 160 160 0,00% 170
73 Deathwatch GLADIATOR VALIANT 150 150 0,00% 160
74 Deathwatch HAMMERFALL BUNKER 175 175 0,00%
75 Deathwatch IMPULSOR 80 80 0,00%
76 Deathwatch INDOMITOR KILL TEAM 265 275 3,77% 290
77 Deathwatch INVADER ATV 60 60 0,00%
78 Deathwatch INVICTOR TACTICAL WARSUIT 125 125 0,00%
79 Deathwatch JUDICIAR 70 55 -21,43%
80 Deathwatch LAND RAIDER 220 220 0,00% 240
81 Deathwatch LAND RAIDER CRUSADER 220 220 0,00% 240
82 Deathwatch LAND RAIDER REDEEMER 270 250 -7,41% 270
83 Deathwatch LAND SPEEDER 95 105
84 Deathwatch LIBRARIAN 65 60 -7,69%
85 Deathwatch LIBRARIAN IN PHOBOS ARMOUR 70 70 0,00%
86 Deathwatch LIBRARIAN IN TERMINATOR ARMOUR 75 75 0,00%
87 Deathwatch LIEUTENANT 55 45 -18,18%
88 Deathwatch LIEUTENANT IN PHOBOS ARMOUR 55 45 -18,18%
89 Deathwatch LIEUTENANT IN REIVER ARMOUR 55 45 -18,18%
90 Deathwatch LIEUTENANT WITH COMBI-WEAPON 85 85 0,00%
91 Deathwatch PREDATOR ANNIHILATOR 135 135 0,00% 145
92 Deathwatch PREDATOR DESTRUCTOR 140 140 0,00% 150
93 Deathwatch RAZORBACK 95 95 0,00%
94 Deathwatch REDEMPTOR DREADNOUGHT 205 195 -4,88% 210
95 Deathwatch REPULSOR 180 170 -5,56% 190
96 Deathwatch REPULSOR EXECUTIONER 230 230 0,00% 250
97 Deathwatch RHINO 75 75 0,00%
98 Deathwatch SPECTRUS KILL TEAM 180 170 -5,56% 180
99 Deathwatch STORM SPEEDER HAILSTRIKE 115 105 -8,70% 115
100 Deathwatch STORM SPEEDER HAMMERSTRIKE 125 130 4,00% 140
101 Deathwatch STORM SPEEDER THUNDERSTRIKE 135 135 0,00% 145
102 Deathwatch STORMHAWK INTERCEPTOR 155 155 0,00%
103 Deathwatch STORMRAVEN GUNSHIP 280 280 0,00% 300
104 Deathwatch STORMTALON GUNSHIP 165 165 0,00%
105 Deathwatch SUPPRESSOR SQUAD 75 75 0,00%
106 Deathwatch TALONSTRIKE KILL TEAM 275 265 -3,64% 280
107 Deathwatch TECHMARINE 55 55 0,00%
108 Deathwatch THUNDERHAWK GUNSHIP 840 840 0,00%
109 Deathwatch VINDICATOR 185 185 0,00% 200
110 Deathwatch WATCH CAPTAIN ARTEMIS 65 65 0,00%
111 Deathwatch WATCH MASTER 95 95 0,00%
112 Deathwatch WHIRLWIND 190 175 -7,89% 195

32
csv/drukhari.csv Normal file
View File

@@ -0,0 +1,32 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Drukhari,1 Cronos,55,55,"0,00%",,,
Drukhari,1 Talos,80,75,"-6,25%",,85,
Drukhari,10 Hellions,,180,,,190,
Drukhari,10 Incubi,,180,,,190,
Drukhari,10 Mandrakes,,160,,,170,
Drukhari,10 Wracks,,120,,,,
Drukhari,2 Cronos,,100,,,,
Drukhari,2 Talos,,150,,,160,
Drukhari,3 Reavers,70,75,"7,14%",,85,
Drukhari,5 Hellions,85,90,"5,88%",,100,
Drukhari,5 Incubi,90,90,"0,00%",,100,
Drukhari,5 Mandrakes,75,80,"6,67%",,90,
Drukhari,5 Wracks,60,60,"0,00%",,,
Drukhari,6 Reavers,,150,,,160,
Drukhari,8 Wracks,,100,,,,
Drukhari,ARCHON,80,80,"0,00%",,,
Drukhari,DRAZHAR,85,85,"0,00%",,,
Drukhari,HAEMONCULUS,60,50,"-16,67%",,,
Drukhari,HAND OF THE ARCHON,125,115,"-8,00%",,,
Drukhari,KABALITE WARRIORS,115,110,"-4,35%",,,
Drukhari,LADY MALYS,100,100,"0,00%",,,
Drukhari,LELITH HESPERAX,85,80,"-5,88%",,,
Drukhari,RAIDER,85,85,"0,00%",,,
Drukhari,RAVAGER,110,100,"-9,09%",,110,
Drukhari,RAZORWING JETFIGHTER,170,170,"0,00%",,,
Drukhari,SCOURGES WITH HEAVY WEAPONS,130,110,"-15,38%",,120,
Drukhari,SCOURGES WITH SHARDCARBINES,75,75,"0,00%",,85,
Drukhari,SUCCUBUS,50,50,"0,00%",,,
Drukhari,VENOM,70,70,"0,00%",,,
Drukhari,VOIDRAVEN BOMBER,245,245,"0,00%",,,
Drukhari,WYCHES,90,90,"0,00%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Drukhari 1 Cronos 55 55 0,00%
3 Drukhari 1 Talos 80 75 -6,25% 85
4 Drukhari 10 Hellions 180 190
5 Drukhari 10 Incubi 180 190
6 Drukhari 10 Mandrakes 160 170
7 Drukhari 10 Wracks 120
8 Drukhari 2 Cronos 100
9 Drukhari 2 Talos 150 160
10 Drukhari 3 Reavers 70 75 7,14% 85
11 Drukhari 5 Hellions 85 90 5,88% 100
12 Drukhari 5 Incubi 90 90 0,00% 100
13 Drukhari 5 Mandrakes 75 80 6,67% 90
14 Drukhari 5 Wracks 60 60 0,00%
15 Drukhari 6 Reavers 150 160
16 Drukhari 8 Wracks 100
17 Drukhari ARCHON 80 80 0,00%
18 Drukhari DRAZHAR 85 85 0,00%
19 Drukhari HAEMONCULUS 60 50 -16,67%
20 Drukhari HAND OF THE ARCHON 125 115 -8,00%
21 Drukhari KABALITE WARRIORS 115 110 -4,35%
22 Drukhari LADY MALYS 100 100 0,00%
23 Drukhari LELITH HESPERAX 85 80 -5,88%
24 Drukhari RAIDER 85 85 0,00%
25 Drukhari RAVAGER 110 100 -9,09% 110
26 Drukhari RAZORWING JETFIGHTER 170 170 0,00%
27 Drukhari SCOURGES WITH HEAVY WEAPONS 130 110 -15,38% 120
28 Drukhari SCOURGES WITH SHARDCARBINES 75 75 0,00% 85
29 Drukhari SUCCUBUS 50 50 0,00%
30 Drukhari VENOM 70 70 0,00%
31 Drukhari VOIDRAVEN BOMBER 245 245 0,00%
32 Drukhari WYCHES 90 90 0,00%

29
csv/emperors-children.csv Normal file
View File

@@ -0,0 +1,29 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Emperor's Children,10 Infractors,,160,,,,
Emperor's Children,10 Seekers,,155,,,,
Emperor's Children,10 Tormentors,,160,,,,
Emperor's Children,3 Fiends,95,90,"-5,26%",,,
Emperor's Children,3 Flawless Blades,100,95,"-5,00%",,,
Emperor's Children,5 Infractors,85,85,"0,00%",,,
Emperor's Children,5 Seekers,80,80,"0,00%",,,
Emperor's Children,5 Tormentors,85,80,"-5,88%",,,
Emperor's Children,6 Fiends,,180,,,,
Emperor's Children,6 Flawless Blades,,190,,,,
Emperor's Children,CHAOS LAND RAIDER,220,220,"0,00%",,240,
Emperor's Children,CHAOS RHINO,80,80,"0,00%",,,
Emperor's Children,CHAOS SPAWN,70,70,"0,00%",,,
Emperor's Children,CHAOS TERMINATORS,145,145,"0,00%",,,
Emperor's Children,DAEMON PRINCE OF SLAANESH,180,170,"-5,56%",185,,
Emperor's Children,DAEMON PRINCE OF SLAANESH WITH WINGS,,205,,235,,
Emperor's Children,DAEMONETTES,90,90,"0,00%",,,
Emperor's Children,DEFILER,250,290,"16,00%",320,,
Emperor's Children,FULGRIM,340,350,"2,94%",,,
Emperor's Children,HELDRAKE,195,175,"-10,26%",,,
Emperor's Children,KEEPER OF SECRETS,240,255,"6,25%",,270,
Emperor's Children,LORD EXULTANT,80,80,"0,00%",,90,
Emperor's Children,LORD KAKOPHONIST,70,70,"0,00%",,,
Emperor's Children,LUCIUS THE ETERNAL,150,130,"-13,33%",,,
Emperor's Children,MAULERFIEND,130,130,"0,00%",,140,
Emperor's Children,NOISE MARINES,145,145,"0,00%",,160,
Emperor's Children,SHALAXI HELBANE,340,340,"0,00%",,,
Emperor's Children,SORCERER,60,60,"0,00%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Emperor's Children 10 Infractors 160
3 Emperor's Children 10 Seekers 155
4 Emperor's Children 10 Tormentors 160
5 Emperor's Children 3 Fiends 95 90 -5,26%
6 Emperor's Children 3 Flawless Blades 100 95 -5,00%
7 Emperor's Children 5 Infractors 85 85 0,00%
8 Emperor's Children 5 Seekers 80 80 0,00%
9 Emperor's Children 5 Tormentors 85 80 -5,88%
10 Emperor's Children 6 Fiends 180
11 Emperor's Children 6 Flawless Blades 190
12 Emperor's Children CHAOS LAND RAIDER 220 220 0,00% 240
13 Emperor's Children CHAOS RHINO 80 80 0,00%
14 Emperor's Children CHAOS SPAWN 70 70 0,00%
15 Emperor's Children CHAOS TERMINATORS 145 145 0,00%
16 Emperor's Children DAEMON PRINCE OF SLAANESH 180 170 -5,56% 185
17 Emperor's Children DAEMON PRINCE OF SLAANESH WITH WINGS 205 235
18 Emperor's Children DAEMONETTES 90 90 0,00%
19 Emperor's Children DEFILER 250 290 16,00% 320
20 Emperor's Children FULGRIM 340 350 2,94%
21 Emperor's Children HELDRAKE 195 175 -10,26%
22 Emperor's Children KEEPER OF SECRETS 240 255 6,25% 270
23 Emperor's Children LORD EXULTANT 80 80 0,00% 90
24 Emperor's Children LORD KAKOPHONIST 70 70 0,00%
25 Emperor's Children LUCIUS THE ETERNAL 150 130 -13,33%
26 Emperor's Children MAULERFIEND 130 130 0,00% 140
27 Emperor's Children NOISE MARINES 145 145 0,00% 160
28 Emperor's Children SHALAXI HELBANE 340 340 0,00%
29 Emperor's Children SORCERER 60 60 0,00%

33
csv/genestealer-cults.csv Normal file
View File

@@ -0,0 +1,33 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Genestealer Cults,1 Achilles Ridgerunners,95,95,"0,00%",,105,
Genestealer Cults,10 Aberrants,,300,,,310,
Genestealer Cults,10 Acolyte Hybrids With Autopistols,,130,,,,
Genestealer Cults,10 Acolyte Hybrids With Hand Flamers,,150,,,,
Genestealer Cults,10 Atalan Jackals,,160,,,170,
Genestealer Cults,10 Hybrid Metamorphs,,150,,,160,
Genestealer Cults,10 Neophyte Hybrids,65,70,"7,69%",,,
Genestealer Cults,10 Purestrain Genestealers,,140,,,150,
Genestealer Cults,2 Achilles Ridgerunners,,160,,,170,
Genestealer Cults,20 Neophyte Hybrids,,145,,,,
Genestealer Cults,5 Aberrants,135,150,"11,11%",,160,
Genestealer Cults,5 Acolyte Hybrids With Autopistols,65,70,"7,69%",,,
Genestealer Cults,5 Acolyte Hybrids With Hand Flamers,70,75,"7,14%",,,
Genestealer Cults,5 Atalan Jackals,85,90,"5,88%",,100,
Genestealer Cults,5 Hybrid Metamorphs,70,75,"7,14%",,85,
Genestealer Cults,5 Purestrain Genestealers,75,80,"6,67%",,90,
Genestealer Cults,ABOMINANT,85,85,"0,00%",,95,
Genestealer Cults,ACOLYTE ICONWARD,50,50,"0,00%",,,
Genestealer Cults,BENEFICTUS,70,75,"7,14%",,,
Genestealer Cults,BIOPHAGUS,50,50,"0,00%",,,
Genestealer Cults,CLAMAVUS,50,50,"0,00%",,,
Genestealer Cults,GOLIATH ROCKGRINDER,120,120,"0,00%",,130,
Genestealer Cults,GOLIATH TRUCK,85,85,"0,00%",,,
Genestealer Cults,JACKAL ALPHUS,55,55,"0,00%",,,
Genestealer Cults,KELERMORPH,60,60,"0,00%",,,
Genestealer Cults,LOCUS,45,35,"-22,22%",,,
Genestealer Cults,MAGUS,50,50,"0,00%",,,
Genestealer Cults,NEXOS,60,60,"0,00%",,,
Genestealer Cults,PATRIARCH,75,80,"6,67%",,,
Genestealer Cults,PRIMUS,70,70,"0,00%",,,
Genestealer Cults,REDUCTUS SABOTEUR,65,70,"7,69%",,,
Genestealer Cults,SANCTUS,50,65,"30,00%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Genestealer Cults 1 Achilles Ridgerunners 95 95 0,00% 105
3 Genestealer Cults 10 Aberrants 300 310
4 Genestealer Cults 10 Acolyte Hybrids With Autopistols 130
5 Genestealer Cults 10 Acolyte Hybrids With Hand Flamers 150
6 Genestealer Cults 10 Atalan Jackals 160 170
7 Genestealer Cults 10 Hybrid Metamorphs 150 160
8 Genestealer Cults 10 Neophyte Hybrids 65 70 7,69%
9 Genestealer Cults 10 Purestrain Genestealers 140 150
10 Genestealer Cults 2 Achilles Ridgerunners 160 170
11 Genestealer Cults 20 Neophyte Hybrids 145
12 Genestealer Cults 5 Aberrants 135 150 11,11% 160
13 Genestealer Cults 5 Acolyte Hybrids With Autopistols 65 70 7,69%
14 Genestealer Cults 5 Acolyte Hybrids With Hand Flamers 70 75 7,14%
15 Genestealer Cults 5 Atalan Jackals 85 90 5,88% 100
16 Genestealer Cults 5 Hybrid Metamorphs 70 75 7,14% 85
17 Genestealer Cults 5 Purestrain Genestealers 75 80 6,67% 90
18 Genestealer Cults ABOMINANT 85 85 0,00% 95
19 Genestealer Cults ACOLYTE ICONWARD 50 50 0,00%
20 Genestealer Cults BENEFICTUS 70 75 7,14%
21 Genestealer Cults BIOPHAGUS 50 50 0,00%
22 Genestealer Cults CLAMAVUS 50 50 0,00%
23 Genestealer Cults GOLIATH ROCKGRINDER 120 120 0,00% 130
24 Genestealer Cults GOLIATH TRUCK 85 85 0,00%
25 Genestealer Cults JACKAL ALPHUS 55 55 0,00%
26 Genestealer Cults KELERMORPH 60 60 0,00%
27 Genestealer Cults LOCUS 45 35 -22,22%
28 Genestealer Cults MAGUS 50 50 0,00%
29 Genestealer Cults NEXOS 60 60 0,00%
30 Genestealer Cults PATRIARCH 75 80 6,67%
31 Genestealer Cults PRIMUS 70 70 0,00%
32 Genestealer Cults REDUCTUS SABOTEUR 65 70 7,69%
33 Genestealer Cults SANCTUS 50 65 30,00%

37
csv/grey-knights.csv Normal file
View File

@@ -0,0 +1,37 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Grey Knights,10 Brotherhood Terminator Squad,,375,,,,385
Grey Knights,10 Interceptor Squad,,250,,,260,
Grey Knights,10 Paladin Squad,,450,,,465,
Grey Knights,10 Purgation Squad,,220,,,230,
Grey Knights,10 Purifier Squad,,260,,,270,
Grey Knights,10 Strike Squad,,240,,,,
Grey Knights,4 Brotherhood Terminator Squad,150,140,"-6,67%",,,150
Grey Knights,4 Paladin Squad,180,170,"-5,56%",,185,
Grey Knights,5 Brotherhood Terminator Squad,,175,,,,185
Grey Knights,5 Interceptor Squad,125,125,"0,00%",,135,
Grey Knights,5 Paladin Squad,,215,,,230,
Grey Knights,5 Purgation Squad,115,110,"-4,35%",,120,
Grey Knights,5 Purifier Squad,125,130,"4,00%",,140,
Grey Knights,5 Strike Squad,120,120,"0,00%",,,
Grey Knights,8 Brotherhood Terminator Squad,,300,,,,310
Grey Knights,8 Paladin Squad,,360,,,375,
Grey Knights,BROTHER-CAPTAIN,90,95,"5,56%",,,
Grey Knights,BROTHERHOOD CHAMPION,70,70,"0,00%",,,
Grey Knights,BROTHERHOOD CHAPLAIN,65,65,"0,00%",,,
Grey Knights,BROTHERHOOD LIBRARIAN,80,90,"12,50%",100,,
Grey Knights,BROTHERHOOD TECHMARINE,70,70,"0,00%",,,
Grey Knights,CASTELLAN CROWE,90,100,"11,11%",,,
Grey Knights,GRAND MASTER,95,95,"0,00%",,,
Grey Knights,GRAND MASTER IN NEMESIS DREADKNIGHT,225,200,"-11,11%",,215,
Grey Knights,GRAND MASTER VOLDUS,110,140,"27,27%",,,
Grey Knights,GREY KNIGHTS THUNDERHAWK GUNSHIP,805,805,"0,00%",855,,
Grey Knights,LAND RAIDER,220,220,"0,00%",,240,
Grey Knights,LAND RAIDER CRUSADER,220,220,"0,00%",,240,
Grey Knights,LAND RAIDER REDEEMER,270,250,"-7,41%",,270,
Grey Knights,NEMESIS DREADKNIGHT,210,195,"-7,14%",,210,
Grey Knights,RAZORBACK,85,85,"0,00%",,,
Grey Knights,RHINO,80,80,"0,00%",,,
Grey Knights,STORMHAWK INTERCEPTOR,160,160,"0,00%",,,
Grey Knights,STORMRAVEN GUNSHIP,280,280,"0,00%",300,,
Grey Knights,STORMTALON GUNSHIP,170,170,"0,00%",,,
Grey Knights,VENERABLE DREADNOUGHT,140,130,"-7,14%",,140,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Grey Knights 10 Brotherhood Terminator Squad 375 385
3 Grey Knights 10 Interceptor Squad 250 260
4 Grey Knights 10 Paladin Squad 450 465
5 Grey Knights 10 Purgation Squad 220 230
6 Grey Knights 10 Purifier Squad 260 270
7 Grey Knights 10 Strike Squad 240
8 Grey Knights 4 Brotherhood Terminator Squad 150 140 -6,67% 150
9 Grey Knights 4 Paladin Squad 180 170 -5,56% 185
10 Grey Knights 5 Brotherhood Terminator Squad 175 185
11 Grey Knights 5 Interceptor Squad 125 125 0,00% 135
12 Grey Knights 5 Paladin Squad 215 230
13 Grey Knights 5 Purgation Squad 115 110 -4,35% 120
14 Grey Knights 5 Purifier Squad 125 130 4,00% 140
15 Grey Knights 5 Strike Squad 120 120 0,00%
16 Grey Knights 8 Brotherhood Terminator Squad 300 310
17 Grey Knights 8 Paladin Squad 360 375
18 Grey Knights BROTHER-CAPTAIN 90 95 5,56%
19 Grey Knights BROTHERHOOD CHAMPION 70 70 0,00%
20 Grey Knights BROTHERHOOD CHAPLAIN 65 65 0,00%
21 Grey Knights BROTHERHOOD LIBRARIAN 80 90 12,50% 100
22 Grey Knights BROTHERHOOD TECHMARINE 70 70 0,00%
23 Grey Knights CASTELLAN CROWE 90 100 11,11%
24 Grey Knights GRAND MASTER 95 95 0,00%
25 Grey Knights GRAND MASTER IN NEMESIS DREADKNIGHT 225 200 -11,11% 215
26 Grey Knights GRAND MASTER VOLDUS 110 140 27,27%
27 Grey Knights GREY KNIGHTS THUNDERHAWK GUNSHIP 805 805 0,00% 855
28 Grey Knights LAND RAIDER 220 220 0,00% 240
29 Grey Knights LAND RAIDER CRUSADER 220 220 0,00% 240
30 Grey Knights LAND RAIDER REDEEMER 270 250 -7,41% 270
31 Grey Knights NEMESIS DREADKNIGHT 210 195 -7,14% 210
32 Grey Knights RAZORBACK 85 85 0,00%
33 Grey Knights RHINO 80 80 0,00%
34 Grey Knights STORMHAWK INTERCEPTOR 160 160 0,00%
35 Grey Knights STORMRAVEN GUNSHIP 280 280 0,00% 300
36 Grey Knights STORMTALON GUNSHIP 170 170 0,00%
37 Grey Knights VENERABLE DREADNOUGHT 140 130 -7,14% 140

33
csv/imperial-agents.csv Normal file
View File

@@ -0,0 +1,33 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Imperial Agents,10 Aquila Kill Team,,200,,,,
Imperial Agents,10 Deathwatch Kill Team,,190,,,,
Imperial Agents,12 Inquisitorial Agents,,100,,,,
Imperial Agents,5 Aquila Kill Team,100,100,"0,00%",,,
Imperial Agents,5 Deathwatch Kill Team,100,100,"0,00%",,,
Imperial Agents,6 Inquisitorial Agents,60,50,"-16,67%",,,
Imperial Agents,CALLIDUS ASSASSIN,100,100,"0,00%",,,
Imperial Agents,CORVUS BLACKSTAR,180,180,"0,00%",,,
Imperial Agents,CULEXUS ASSASSIN,85,85,"0,00%",,,
Imperial Agents,EVERSOR ASSASSIN,120,110,"-8,33%",,,
Imperial Agents,EXACTION SQUAD,90,90,"0,00%",,,
Imperial Agents,GREY KNIGHTS TERMINATOR SQUAD,210,190,"-9,52%",,,
Imperial Agents,IMPERIAL NAVY BREACHERS,90,90,"0,00%",,,
Imperial Agents,IMPERIAL RHINO,75,75,"0,00%",,,
Imperial Agents,INQUISITOR,65,55,"-15,38%",,,
Imperial Agents,INQUISITOR COTEAZ,95,75,"-21,05%",,,
Imperial Agents,INQUISITOR DRAXUS,110,75,"-31,82%",,,
Imperial Agents,INQUISITOR GREYFAX,65,65,"0,00%",,,
Imperial Agents,INQUISITOR KROYLE,100,100,"0,00%",,,
Imperial Agents,INQUISITORIAL CHIMERA,70,70,"0,00%",,,
Imperial Agents,MINISTORUM PRIEST,40,40,"0,00%",,,
Imperial Agents,NAVIGATOR,75,60,"-20,00%",,,
Imperial Agents,ROGUE TRADER ENTOURAGE,105,75,"-28,57%",,,
Imperial Agents,SANCTIFIERS,100,100,"0,00%",,,
Imperial Agents,SISTERS OF BATTLE IMMOLATOR,115,100,"-13,04%",,,
Imperial Agents,SISTERS OF BATTLE SQUAD,100,100,"0,00%",,,
Imperial Agents,SUBDUCTOR SQUAD,100,85,"-15,00%",,,
Imperial Agents,VIGILANT SQUAD,85,85,"0,00%",,,
Imperial Agents,VINDICARE ASSASSIN,125,110,"-12,00%",,,
Imperial Agents,VOIDSMEN-AT-ARMS,70,50,"-28,57%",,,
Imperial Agents,WATCH CAPTAIN ARTEMIS,65,65,"0,00%",,,
Imperial Agents,WATCH MASTER,105,95,"-9,52%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Imperial Agents 10 Aquila Kill Team 200
3 Imperial Agents 10 Deathwatch Kill Team 190
4 Imperial Agents 12 Inquisitorial Agents 100
5 Imperial Agents 5 Aquila Kill Team 100 100 0,00%
6 Imperial Agents 5 Deathwatch Kill Team 100 100 0,00%
7 Imperial Agents 6 Inquisitorial Agents 60 50 -16,67%
8 Imperial Agents CALLIDUS ASSASSIN 100 100 0,00%
9 Imperial Agents CORVUS BLACKSTAR 180 180 0,00%
10 Imperial Agents CULEXUS ASSASSIN 85 85 0,00%
11 Imperial Agents EVERSOR ASSASSIN 120 110 -8,33%
12 Imperial Agents EXACTION SQUAD 90 90 0,00%
13 Imperial Agents GREY KNIGHTS TERMINATOR SQUAD 210 190 -9,52%
14 Imperial Agents IMPERIAL NAVY BREACHERS 90 90 0,00%
15 Imperial Agents IMPERIAL RHINO 75 75 0,00%
16 Imperial Agents INQUISITOR 65 55 -15,38%
17 Imperial Agents INQUISITOR COTEAZ 95 75 -21,05%
18 Imperial Agents INQUISITOR DRAXUS 110 75 -31,82%
19 Imperial Agents INQUISITOR GREYFAX 65 65 0,00%
20 Imperial Agents INQUISITOR KROYLE 100 100 0,00%
21 Imperial Agents INQUISITORIAL CHIMERA 70 70 0,00%
22 Imperial Agents MINISTORUM PRIEST 40 40 0,00%
23 Imperial Agents NAVIGATOR 75 60 -20,00%
24 Imperial Agents ROGUE TRADER ENTOURAGE 105 75 -28,57%
25 Imperial Agents SANCTIFIERS 100 100 0,00%
26 Imperial Agents SISTERS OF BATTLE IMMOLATOR 115 100 -13,04%
27 Imperial Agents SISTERS OF BATTLE SQUAD 100 100 0,00%
28 Imperial Agents SUBDUCTOR SQUAD 100 85 -15,00%
29 Imperial Agents VIGILANT SQUAD 85 85 0,00%
30 Imperial Agents VINDICARE ASSASSIN 125 110 -12,00%
31 Imperial Agents VOIDSMEN-AT-ARMS 70 50 -28,57%
32 Imperial Agents WATCH CAPTAIN ARTEMIS 65 65 0,00%
33 Imperial Agents WATCH MASTER 105 95 -9,52%

23
csv/imperial-knights.csv Normal file
View File

@@ -0,0 +1,23 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Imperial Knights,ACASTUS KNIGHT ASTERIUS,765,785,"2,61%",860,,
Imperial Knights,ACASTUS KNIGHT PORPHYRION,700,725,"3,57%",800,,
Imperial Knights,ARMIGER HELVERIN,135,140,"3,70%",,,
Imperial Knights,ARMIGER MOIRAX,150,150,"0,00%",,160,
Imperial Knights,ARMIGER WARGLAIVE,140,140,"0,00%",,,
Imperial Knights,CANIS REX,415,415,"0,00%",,,
Imperial Knights,CERASTUS KNIGHT ACHERON,395,380,"-3,80%",395,,
Imperial Knights,CERASTUS KNIGHT ATRAPOS,405,405,"0,00%",425,,
Imperial Knights,CERASTUS KNIGHT CASTIGATOR,395,380,"-3,80%",395,,
Imperial Knights,CERASTUS KNIGHT LANCER,395,415,"5,06%",435,,
Imperial Knights,KNIGHT CASTELLAN,410,400,"-2,44%",420,,
Imperial Knights,KNIGHT CRUSADER,385,395,"2,60%",415,,
Imperial Knights,KNIGHT DEFENDER,415,400,"-3,61%",420,,
Imperial Knights,KNIGHT DESTRIER,250,265,"6,00%",,280,
Imperial Knights,KNIGHT ERRANT,355,355,"0,00%",,370,
Imperial Knights,KNIGHT GALLANT,355,355,"0,00%",,370,
Imperial Knights,KNIGHT PALADIN,375,375,"0,00%",,390,
Imperial Knights,KNIGHT PRECEPTOR,365,365,"0,00%",,380,
Imperial Knights,KNIGHT VALIANT,410,400,"-2,44%",,415,
Imperial Knights,KNIGHT WARDEN,375,375,"0,00%",,390,
Imperial Knights,QUESTORIS KNIGHT MAGAERA,385,385,"0,00%",400,,
Imperial Knights,QUESTORIS KNIGHT STYRIX,385,375,"-2,60%",390,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Imperial Knights ACASTUS KNIGHT ASTERIUS 765 785 2,61% 860
3 Imperial Knights ACASTUS KNIGHT PORPHYRION 700 725 3,57% 800
4 Imperial Knights ARMIGER HELVERIN 135 140 3,70%
5 Imperial Knights ARMIGER MOIRAX 150 150 0,00% 160
6 Imperial Knights ARMIGER WARGLAIVE 140 140 0,00%
7 Imperial Knights CANIS REX 415 415 0,00%
8 Imperial Knights CERASTUS KNIGHT ACHERON 395 380 -3,80% 395
9 Imperial Knights CERASTUS KNIGHT ATRAPOS 405 405 0,00% 425
10 Imperial Knights CERASTUS KNIGHT CASTIGATOR 395 380 -3,80% 395
11 Imperial Knights CERASTUS KNIGHT LANCER 395 415 5,06% 435
12 Imperial Knights KNIGHT CASTELLAN 410 400 -2,44% 420
13 Imperial Knights KNIGHT CRUSADER 385 395 2,60% 415
14 Imperial Knights KNIGHT DEFENDER 415 400 -3,61% 420
15 Imperial Knights KNIGHT DESTRIER 250 265 6,00% 280
16 Imperial Knights KNIGHT ERRANT 355 355 0,00% 370
17 Imperial Knights KNIGHT GALLANT 355 355 0,00% 370
18 Imperial Knights KNIGHT PALADIN 375 375 0,00% 390
19 Imperial Knights KNIGHT PRECEPTOR 365 365 0,00% 380
20 Imperial Knights KNIGHT VALIANT 410 400 -2,44% 415
21 Imperial Knights KNIGHT WARDEN 375 375 0,00% 390
22 Imperial Knights QUESTORIS KNIGHT MAGAERA 385 385 0,00% 400
23 Imperial Knights QUESTORIS KNIGHT STYRIX 385 375 -2,60% 390

30
csv/leagues-of-votann.csv Normal file
View File

@@ -0,0 +1,30 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Leagues of Votann,1 Kapricus Defenders,70,65,"-7,14%",,,
Leagues of Votann,10 Cthonian Beserks,,200,,,,
Leagues of Votann,10 Einhyr Hearthguard,,270,,,280,
Leagues of Votann,2 Kapricus Defenders,,130,,,,
Leagues of Votann,3 Brôkhyr Thunderkyn,80,80,"0,00%",,90,
Leagues of Votann,3 Hernkyn Pioneers,80,80,"0,00%",,90,
Leagues of Votann,3 Ironkin Steeljacks With Heavy Volkanite Disintegrators,,80,,,90,
Leagues of Votann,3 Ironkin Steeljacks With Melee Weapons,85,80,"-5,88%",,90,
Leagues of Votann,5 Cthonian Beserks,100,100,"0,00%",,,
Leagues of Votann,5 Einhyr Hearthguard,135,130,"-3,70%",,140,
Leagues of Votann,6 Brôkhyr Thunderkyn,,160,,,170,
Leagues of Votann,6 Hernkyn Pioneers,,160,,,170,
Leagues of Votann,6 Ironkin Steeljacks With Heavy Volkanite Disintegrators,,160,,,170,
Leagues of Votann,6 Ironkin Steeljacks With Melee Weapons,,160,,,170,
Leagues of Votann,ARKANYST EVALUATOR,65,70,"7,69%",,,
Leagues of Votann,BEREHK STORNBRÖW,95,95,"0,00%",,,
Leagues of Votann,BRÔKHYR IRON-MASTER,75,75,"0,00%",,,
Leagues of Votann,BURI AEGNIRSSEN,95,95,"0,00%",,,
Leagues of Votann,CTHONIAN EARTHSHAKERS,110,110,"0,00%",,,
Leagues of Votann,EINHYR CHAMPION,70,65,"-7,14%",,,
Leagues of Votann,GRIMNYR,65,65,"0,00%",,,
Leagues of Votann,HEARTHKYN WARRIORS,100,100,"0,00%",,,
Leagues of Votann,HEKATON LAND FORTRESS,240,250,"4,17%",265,,
Leagues of Votann,HERNKYN YAEGIRS,90,90,"0,00%",,,
Leagues of Votann,KAPRICUS CARRIER,75,75,"0,00%",,,
Leagues of Votann,KÂHL,65,65,"0,00%",,,
Leagues of Votann,MEMNYR STRATEGIST,45,45,"0,00%",,,
Leagues of Votann,SAGITAUR,90,85,"-5,56%",,,
Leagues of Votann,ÛTHAR THE DESTINED,95,90,"-5,26%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Leagues of Votann 1 Kapricus Defenders 70 65 -7,14%
3 Leagues of Votann 10 Cthonian Beserks 200
4 Leagues of Votann 10 Einhyr Hearthguard 270 280
5 Leagues of Votann 2 Kapricus Defenders 130
6 Leagues of Votann 3 Brôkhyr Thunderkyn 80 80 0,00% 90
7 Leagues of Votann 3 Hernkyn Pioneers 80 80 0,00% 90
8 Leagues of Votann 3 Ironkin Steeljacks With Heavy Volkanite Disintegrators 80 90
9 Leagues of Votann 3 Ironkin Steeljacks With Melee Weapons 85 80 -5,88% 90
10 Leagues of Votann 5 Cthonian Beserks 100 100 0,00%
11 Leagues of Votann 5 Einhyr Hearthguard 135 130 -3,70% 140
12 Leagues of Votann 6 Brôkhyr Thunderkyn 160 170
13 Leagues of Votann 6 Hernkyn Pioneers 160 170
14 Leagues of Votann 6 Ironkin Steeljacks With Heavy Volkanite Disintegrators 160 170
15 Leagues of Votann 6 Ironkin Steeljacks With Melee Weapons 160 170
16 Leagues of Votann ARKANYST EVALUATOR 65 70 7,69%
17 Leagues of Votann BEREHK STORNBRÖW 95 95 0,00%
18 Leagues of Votann BRÔKHYR IRON-MASTER 75 75 0,00%
19 Leagues of Votann BURI AEGNIRSSEN 95 95 0,00%
20 Leagues of Votann CTHONIAN EARTHSHAKERS 110 110 0,00%
21 Leagues of Votann EINHYR CHAMPION 70 65 -7,14%
22 Leagues of Votann GRIMNYR 65 65 0,00%
23 Leagues of Votann HEARTHKYN WARRIORS 100 100 0,00%
24 Leagues of Votann HEKATON LAND FORTRESS 240 250 4,17% 265
25 Leagues of Votann HERNKYN YAEGIRS 90 90 0,00%
26 Leagues of Votann KAPRICUS CARRIER 75 75 0,00%
27 Leagues of Votann KÂHL 65 65 0,00%
28 Leagues of Votann MEMNYR STRATEGIST 45 45 0,00%
29 Leagues of Votann SAGITAUR 90 85 -5,56%
30 Leagues of Votann ÛTHAR THE DESTINED 95 90 -5,26%

72
csv/necrons.csv Normal file
View File

@@ -0,0 +1,72 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Necrons,1 Canoptek Spyders,75,65,"-13,33%",,,
Necrons,1 Convergence Of Dominion,60,60,"0,00%",,,
Necrons,1 Lokhust Destroyers,40,40,"0,00%",,50,
Necrons,1 Lokhust Heavy Destroyers,55,50,"-9,09%",,60,
Necrons,10 Deathmarks,,120,,,130,
Necrons,10 Flayed Ones,,100,,,,
Necrons,10 Immortals,,140,,,,
Necrons,10 Lychguard,,160,,,,
Necrons,10 Necron Warriors,90,80,"-11,11%",,,
Necrons,10 Triarch Praetorians,,160,,,,
Necrons,2 Canoptek Spyders,,110,,,,
Necrons,2 Convergence Of Dominion,,120,,,,
Necrons,2 Lokhust Destroyers,,55,,,65,
Necrons,2 Lokhust Heavy Destroyers,,100,,,110,
Necrons,20 Necron Warriors,,190,,,,
Necrons,3 Canoptek Scarab Swarms,40,40,"0,00%",,,
Necrons,3 Canoptek Wraiths,110,95,"-13,64%",115,,
Necrons,3 Convergence Of Dominion,,180,,,,
Necrons,3 Lokhust Destroyers,,80,,,90,
Necrons,3 Lokhust Heavy Destroyers,,150,,,160,
Necrons,3 Ophydian Destroyers,80,80,"0,00%",,90,
Necrons,3 Skorpekh Destroyers,90,85,"-5,56%",,95,
Necrons,3 Tomb Blades,75,70,"-6,67%",,80,
Necrons,5 Deathmarks,60,60,"0,00%",,70,
Necrons,5 Flayed Ones,60,55,"-8,33%",,,
Necrons,5 Immortals,70,70,"0,00%",,,
Necrons,5 Lychguard,85,80,"-5,88%",,,
Necrons,5 Triarch Praetorians,90,80,"-11,11%",,,
Necrons,6 Canoptek Scarab Swarms,,80,,,,
Necrons,6 Canoptek Wraiths,,220,,240,,
Necrons,6 Lokhust Destroyers,,160,,,170,
Necrons,6 Ophydian Destroyers,,145,,,155,
Necrons,6 Skorpekh Destroyers,,170,,,180,
Necrons,6 Tomb Blades,,140,,,150,
Necrons,ANNIHILATION BARGE,105,95,"-9,52%",,,
Necrons,CANOPTEK DOOMSTALKER,140,140,"0,00%",,,
Necrons,CANOPTEK MACROCYTES,85,70,"-17,65%",,,
Necrons,CANOPTEK REANIMATOR,75,70,"-6,67%",,,
Necrons,CANOPTEK TOMB CRAWLERS,50,50,"0,00%",,,
Necrons,CATACOMB COMMAND BARGE,120,120,"0,00%",,,
Necrons,CHRONOMANCER,65,80,"23,08%",90,,
Necrons,CRYPTOTHRALLS,60,60,"0,00%",,,
Necrons,CTAN SHARD OF THE DECEIVER,310,330,"6,45%",,,
Necrons,CTAN SHARD OF THE NIGHTBRINGER,340,360,"5,88%",,,
Necrons,CTAN SHARD OF THE VOID DRAGON,330,345,"4,55%",,,
Necrons,DOOM SCYTHE,230,200,"-13,04%",,,
Necrons,DOOMSDAY ARK,200,200,"0,00%",,220,
Necrons,GEOMANCER,75,75,"0,00%",,,
Necrons,GHOST ARK,115,100,"-13,04%",,,
Necrons,HEXMARK DESTROYER,75,75,"0,00%",,,
Necrons,ILLUMINOR SZERAS,165,175,"6,06%",,,
Necrons,IMOTEKH THE STORMLORD,100,100,"0,00%",,,
Necrons,LOKHUST LORD,80,70,"-12,50%",,,
Necrons,MONOLITH,400,420,"5,00%",440,,
Necrons,NEKROSOR AMMENTAR,185,175,"-5,41%",,,
Necrons,NIGHT SCYTHE,145,125,"-13,79%",,,
Necrons,OBELISK,300,280,"-6,67%",310,,
Necrons,ORIKAN THE DIVINER,80,90,"12,50%",,,
Necrons,OVERLORD,85,90,"5,88%",,,
Necrons,OVERLORD WITH TRANSLOCATION SHROUD,85,90,"5,88%",,,
Necrons,PLASMANCER,55,55,"0,00%",,,
Necrons,PSYCHOMANCER,55,55,"0,00%",,,
Necrons,ROYAL WARDEN,50,50,"0,00%",,,
Necrons,SERAPTEK HEAVY CONSTRUCT,540,540,"0,00%",570,,
Necrons,SKORPEKH LORD,90,90,"0,00%",,,
Necrons,TECHNOMANCER,80,80,"0,00%",90,,
Necrons,TESSERACT VAULT,425,445,"4,71%",465,,
Necrons,THE SILENT KING,400,400,"0,00%",,,
Necrons,TRANSCENDENT CTAN,325,340,"4,62%",360,,
Necrons,TRAZYN THE INFINITE,75,65,"-13,33%",,,
Necrons,TRIARCH STALKER,110,110,"0,00%",,120,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Necrons 1 Canoptek Spyders 75 65 -13,33%
3 Necrons 1 Convergence Of Dominion 60 60 0,00%
4 Necrons 1 Lokhust Destroyers 40 40 0,00% 50
5 Necrons 1 Lokhust Heavy Destroyers 55 50 -9,09% 60
6 Necrons 10 Deathmarks 120 130
7 Necrons 10 Flayed Ones 100
8 Necrons 10 Immortals 140
9 Necrons 10 Lychguard 160
10 Necrons 10 Necron Warriors 90 80 -11,11%
11 Necrons 10 Triarch Praetorians 160
12 Necrons 2 Canoptek Spyders 110
13 Necrons 2 Convergence Of Dominion 120
14 Necrons 2 Lokhust Destroyers 55 65
15 Necrons 2 Lokhust Heavy Destroyers 100 110
16 Necrons 20 Necron Warriors 190
17 Necrons 3 Canoptek Scarab Swarms 40 40 0,00%
18 Necrons 3 Canoptek Wraiths 110 95 -13,64% 115
19 Necrons 3 Convergence Of Dominion 180
20 Necrons 3 Lokhust Destroyers 80 90
21 Necrons 3 Lokhust Heavy Destroyers 150 160
22 Necrons 3 Ophydian Destroyers 80 80 0,00% 90
23 Necrons 3 Skorpekh Destroyers 90 85 -5,56% 95
24 Necrons 3 Tomb Blades 75 70 -6,67% 80
25 Necrons 5 Deathmarks 60 60 0,00% 70
26 Necrons 5 Flayed Ones 60 55 -8,33%
27 Necrons 5 Immortals 70 70 0,00%
28 Necrons 5 Lychguard 85 80 -5,88%
29 Necrons 5 Triarch Praetorians 90 80 -11,11%
30 Necrons 6 Canoptek Scarab Swarms 80
31 Necrons 6 Canoptek Wraiths 220 240
32 Necrons 6 Lokhust Destroyers 160 170
33 Necrons 6 Ophydian Destroyers 145 155
34 Necrons 6 Skorpekh Destroyers 170 180
35 Necrons 6 Tomb Blades 140 150
36 Necrons ANNIHILATION BARGE 105 95 -9,52%
37 Necrons CANOPTEK DOOMSTALKER 140 140 0,00%
38 Necrons CANOPTEK MACROCYTES 85 70 -17,65%
39 Necrons CANOPTEK REANIMATOR 75 70 -6,67%
40 Necrons CANOPTEK TOMB CRAWLERS 50 50 0,00%
41 Necrons CATACOMB COMMAND BARGE 120 120 0,00%
42 Necrons CHRONOMANCER 65 80 23,08% 90
43 Necrons CRYPTOTHRALLS 60 60 0,00%
44 Necrons C’TAN SHARD OF THE DECEIVER 310 330 6,45%
45 Necrons C’TAN SHARD OF THE NIGHTBRINGER 340 360 5,88%
46 Necrons C’TAN SHARD OF THE VOID DRAGON 330 345 4,55%
47 Necrons DOOM SCYTHE 230 200 -13,04%
48 Necrons DOOMSDAY ARK 200 200 0,00% 220
49 Necrons GEOMANCER 75 75 0,00%
50 Necrons GHOST ARK 115 100 -13,04%
51 Necrons HEXMARK DESTROYER 75 75 0,00%
52 Necrons ILLUMINOR SZERAS 165 175 6,06%
53 Necrons IMOTEKH THE STORMLORD 100 100 0,00%
54 Necrons LOKHUST LORD 80 70 -12,50%
55 Necrons MONOLITH 400 420 5,00% 440
56 Necrons NEKROSOR AMMENTAR 185 175 -5,41%
57 Necrons NIGHT SCYTHE 145 125 -13,79%
58 Necrons OBELISK 300 280 -6,67% 310
59 Necrons ORIKAN THE DIVINER 80 90 12,50%
60 Necrons OVERLORD 85 90 5,88%
61 Necrons OVERLORD WITH TRANSLOCATION SHROUD 85 90 5,88%
62 Necrons PLASMANCER 55 55 0,00%
63 Necrons PSYCHOMANCER 55 55 0,00%
64 Necrons ROYAL WARDEN 50 50 0,00%
65 Necrons SERAPTEK HEAVY CONSTRUCT 540 540 0,00% 570
66 Necrons SKORPEKH LORD 90 90 0,00%
67 Necrons TECHNOMANCER 80 80 0,00% 90
68 Necrons TESSERACT VAULT 425 445 4,71% 465
69 Necrons THE SILENT KING 400 400 0,00%
70 Necrons TRANSCENDENT C’TAN 325 340 4,62% 360
71 Necrons TRAZYN THE INFINITE 75 65 -13,33%
72 Necrons TRIARCH STALKER 110 110 0,00% 120

76
csv/orks.csv Normal file
View File

@@ -0,0 +1,76 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Orks,1 Mek Gunz,50,45,"-10,00%",,55,
Orks,10 Beast Snagga Boyz,95,90,"-5,26%",,,
Orks,10 Boyz,80,75,"-6,25%",,,
Orks,10 Burna Boyz,,120,,,,
Orks,10 Flash Gitz,,150,,,160,
Orks,10 Lootas,,100,,,110,
Orks,10 Nobz,,210,,,220,
Orks,10 Stormboyz,,130,,,,
Orks,11 Gretchin,40,45,"12,50%",,,
Orks,2 Meganobz,65,60,"-7,69%",,80,
Orks,2 Mek Gunz,,90,,,100,
Orks,20 Beast Snagga Boyz,,170,,,,
Orks,20 Boyz,,150,,,,
Orks,22 Gretchin,,80,,,,
Orks,3 Deffkoptas,80,75,"-6,25%",,,
Orks,3 Killa Kans,125,120,"-4,00%",,130,
Orks,3 Meganobz,,90,,,110,
Orks,3 Mek Gunz,,135,,,145,
Orks,3 Warbikers,65,60,"-7,69%",,,
Orks,4 Squighog Boyz,150,140,"-6,67%",,,
Orks,5 Burna Boyz,,60,,,,
Orks,5 Flash Gitz,80,75,"-6,25%",,85,
Orks,5 Lootas,,50,,,60,
Orks,5 Meganobz,,150,,,170,
Orks,5 Nobz,105,105,"0,00%",,115,
Orks,5 Stormboyz,65,65,"0,00%",,,
Orks,6 Deffkoptas,,140,,,,
Orks,6 Killa Kans,,240,,,250,
Orks,6 Meganobz,,180,,,200,
Orks,6 Warbikers,,120,,,,
Orks,8 Squighog Boyz,,270,,,,
Orks,BANNERNOB,,50,,,,
Orks,BATTLEWAGON,160,145,"-9,38%",,,
Orks,BEASTBOSS,80,80,"0,00%",,,
Orks,BEASTBOSS ON SQUIGOSAUR,110,95,"-13,64%",,,
Orks,BIG MEK,70,70,"0,00%",,,
Orks,BIG MEK DAKKARIG,,100,,,110,
Orks,BIG MEK IN MEGA ARMOUR,90,80,"-11,11%",,,
Orks,BIG MEK WITH SHOKK ATTACK GUN,80,70,"-12,50%",,80,
Orks,BIGBOSS,,55,,,,
Orks,BIGED BOSSBUNKA,135,135,"0,00%",,,
Orks,BLITZA-BOMMER,115,105,"-8,70%",,,
Orks,BOOMDAKKA SNAZZWAGON,70,70,"0,00%",,,
Orks,BOSS SNIKROT,75,75,"0,00%",,,
Orks,BREAKA BOYZ,140,125,"-10,71%",,135,
Orks,BURNA-BOMMER,125,115,"-8,00%",,,
Orks,DAKKAJET,135,125,"-7,41%",,,
Orks,DEFF DREAD,120,110,"-8,33%",,120,
Orks,DEFFKILLA WARTRIKE,80,70,"-12,50%",,,
Orks,GARGANTUAN SQUIGGOTH,440,440,"0,00%",490,,
Orks,GHAZGHKULL THRAKA,235,235,"0,00%",,,
Orks,GORKANAUT,265,255,"-3,77%",,275,
Orks,HUNTA RIG,135,125,"-7,41%",,,
Orks,KILL RIG,155,145,"-6,45%",,,
Orks,KOMMANDOS,120,120,"0,00%",,,
Orks,KUSTOM BOOSTA-BLASTA,70,70,"0,00%",,,
Orks,MEGATRAKK SCRAPJET,75,75,"0,00%",,,
Orks,MEK,45,45,"0,00%",,,
Orks,MORKANAUT,280,270,"-3,57%",,290,
Orks,MOZROG SKRAGBAD,145,125,"-13,79%",,,
Orks,PAINBOSS,70,60,"-14,29%",,,
Orks,PAINBOY,80,80,"0,00%",,,
Orks,RUKKATRUKK SQUIGBUGGY,95,85,"-10,53%",,,
Orks,SHOKKJUMP DRAGSTA,70,70,"0,00%",,,
Orks,STOMPA,800,600,"-25,00%",700,,
Orks,TANKBUSTAS,140,125,"-10,71%",,135,
Orks,TRUKK,70,65,"-7,14%",,,
Orks,WARBOSS,75,75,"0,00%",,,
Orks,WARBOSS IN MEGA ARMOUR,80,80,"0,00%",,,
Orks,WARTRAKK,,60,,,,
Orks,WAZBOM BLASTAJET,175,165,"-5,71%",,,
Orks,WAZDAKKA GUTSMEK,175,175,"0,00%",,,
Orks,WEIRDBOY,65,65,"0,00%",,,
Orks,WURRBOY,60,60,"0,00%",,,
Orks,ZODGROD WORTSNAGGA,90,80,"-11,11%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Orks 1 Mek Gunz 50 45 -10,00% 55
3 Orks 10 Beast Snagga Boyz 95 90 -5,26%
4 Orks 10 Boyz 80 75 -6,25%
5 Orks 10 Burna Boyz 120
6 Orks 10 Flash Gitz 150 160
7 Orks 10 Lootas 100 110
8 Orks 10 Nobz 210 220
9 Orks 10 Stormboyz 130
10 Orks 11 Gretchin 40 45 12,50%
11 Orks 2 Meganobz 65 60 -7,69% 80
12 Orks 2 Mek Gunz 90 100
13 Orks 20 Beast Snagga Boyz 170
14 Orks 20 Boyz 150
15 Orks 22 Gretchin 80
16 Orks 3 Deffkoptas 80 75 -6,25%
17 Orks 3 Killa Kans 125 120 -4,00% 130
18 Orks 3 Meganobz 90 110
19 Orks 3 Mek Gunz 135 145
20 Orks 3 Warbikers 65 60 -7,69%
21 Orks 4 Squighog Boyz 150 140 -6,67%
22 Orks 5 Burna Boyz 60
23 Orks 5 Flash Gitz 80 75 -6,25% 85
24 Orks 5 Lootas 50 60
25 Orks 5 Meganobz 150 170
26 Orks 5 Nobz 105 105 0,00% 115
27 Orks 5 Stormboyz 65 65 0,00%
28 Orks 6 Deffkoptas 140
29 Orks 6 Killa Kans 240 250
30 Orks 6 Meganobz 180 200
31 Orks 6 Warbikers 120
32 Orks 8 Squighog Boyz 270
33 Orks BANNERNOB 50
34 Orks BATTLEWAGON 160 145 -9,38%
35 Orks BEASTBOSS 80 80 0,00%
36 Orks BEASTBOSS ON SQUIGOSAUR 110 95 -13,64%
37 Orks BIG MEK 70 70 0,00%
38 Orks BIG MEK DAKKARIG 100 110
39 Orks BIG MEK IN MEGA ARMOUR 90 80 -11,11%
40 Orks BIG MEK WITH SHOKK ATTACK GUN 80 70 -12,50% 80
41 Orks BIGBOSS 55
42 Orks BIG’ED BOSSBUNKA 135 135 0,00%
43 Orks BLITZA-BOMMER 115 105 -8,70%
44 Orks BOOMDAKKA SNAZZWAGON 70 70 0,00%
45 Orks BOSS SNIKROT 75 75 0,00%
46 Orks BREAKA BOYZ 140 125 -10,71% 135
47 Orks BURNA-BOMMER 125 115 -8,00%
48 Orks DAKKAJET 135 125 -7,41%
49 Orks DEFF DREAD 120 110 -8,33% 120
50 Orks DEFFKILLA WARTRIKE 80 70 -12,50%
51 Orks GARGANTUAN SQUIGGOTH 440 440 0,00% 490
52 Orks GHAZGHKULL THRAKA 235 235 0,00%
53 Orks GORKANAUT 265 255 -3,77% 275
54 Orks HUNTA RIG 135 125 -7,41%
55 Orks KILL RIG 155 145 -6,45%
56 Orks KOMMANDOS 120 120 0,00%
57 Orks KUSTOM BOOSTA-BLASTA 70 70 0,00%
58 Orks MEGATRAKK SCRAPJET 75 75 0,00%
59 Orks MEK 45 45 0,00%
60 Orks MORKANAUT 280 270 -3,57% 290
61 Orks MOZROG SKRAGBAD 145 125 -13,79%
62 Orks PAINBOSS 70 60 -14,29%
63 Orks PAINBOY 80 80 0,00%
64 Orks RUKKATRUKK SQUIGBUGGY 95 85 -10,53%
65 Orks SHOKKJUMP DRAGSTA 70 70 0,00%
66 Orks STOMPA 800 600 -25,00% 700
67 Orks TANKBUSTAS 140 125 -10,71% 135
68 Orks TRUKK 70 65 -7,14%
69 Orks WARBOSS 75 75 0,00%
70 Orks WARBOSS IN MEGA ARMOUR 80 80 0,00%
71 Orks WARTRAKK 60
72 Orks WAZBOM BLASTAJET 175 165 -5,71%
73 Orks WAZDAKKA GUTSMEK 175 175 0,00%
74 Orks WEIRDBOY 65 65 0,00%
75 Orks WURRBOY 60 60 0,00%
76 Orks ZODGROD WORTSNAGGA 90 80 -11,11%

128
csv/space-marines.csv Normal file
View File

@@ -0,0 +1,128 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Space Marines,1 Firestrike Servo-Turrets,75,75,"0,00%",,,
Space Marines,10 Assault Intercessor Squad,,150,,,,
Space Marines,10 Assault Intercessors With Jump Packs,,160,,,170,
Space Marines,10 Devastator Squad,,200,,,,
Space Marines,10 Heavy Intercessor Squad,,200,,,,
Space Marines,10 Hellblaster Squad,,220,,,,
Space Marines,10 Incursor Squad,,150,,,160,
Space Marines,10 Infernus Squad,,170,,,,
Space Marines,10 Infiltrator Squad,,180,,,190,
Space Marines,10 Intercessor Squad,,150,,,,
Space Marines,10 Reiver Squad,,150,,,,
Space Marines,10 Scout Squad,,120,,,130,
Space Marines,10 Sternguard Veteran Squad,,190,,,,
Space Marines,10 Terminator Assault Squad,,310,,,,
Space Marines,10 Terminator Squad,,320,,,,
Space Marines,10 Vanguard Veteran Squad With Jump Packs,,200,,,210,
Space Marines,2 Firestrike Servo-Turrets,,150,,,,
Space Marines,3 Aggressor Squad,95,90,"-5,26%",,100,
Space Marines,3 Bladeguard Veteran Squad,80,80,"0,00%",,90,
Space Marines,3 Centurion Assault Squad,150,150,"0,00%",,,
Space Marines,3 Centurion Devastator Squad,175,175,"0,00%",,,
Space Marines,3 Eradicator Squad,90,90,"0,00%",,100,
Space Marines,3 Inceptor Squad,120,120,"0,00%",,135,
Space Marines,3 Outrider Squad,80,70,"-12,50%",,,
Space Marines,3 Victrix Honour Guard,110,110,"0,00%",130,,
Space Marines,5 Assault Intercessor Squad,75,75,"0,00%",,,
Space Marines,5 Assault Intercessors With Jump Packs,90,85,"-5,56%",,95,
Space Marines,5 Devastator Squad,120,120,"0,00%",,,
Space Marines,5 Heavy Intercessor Squad,100,100,"0,00%",,,
Space Marines,5 Hellblaster Squad,110,110,"0,00%",,,
Space Marines,5 Incursor Squad,80,85,"6,25%",,95,
Space Marines,5 Infernus Squad,90,85,"-5,56%",,,
Space Marines,5 Infiltrator Squad,100,110,"10,00%",,120,
Space Marines,5 Intercessor Squad,80,80,"0,00%",,,
Space Marines,5 Reiver Squad,80,75,"-6,25%",,,
Space Marines,5 Scout Squad,70,65,"-7,14%",,75,
Space Marines,5 Sternguard Veteran Squad,100,100,"0,00%",,,
Space Marines,5 Terminator Assault Squad,180,155,"-13,89%",,,
Space Marines,5 Terminator Squad,170,160,"-5,88%",,,
Space Marines,5 Vanguard Veteran Squad With Jump Packs,100,100,"0,00%",,110,
Space Marines,6 Aggressor Squad,,180,,,190,
Space Marines,6 Bladeguard Veteran Squad,,160,,,170,
Space Marines,6 Centurion Assault Squad,,300,,,,
Space Marines,6 Centurion Devastator Squad,,350,,,,
Space Marines,6 Eradicator Squad,,180,,,190,
Space Marines,6 Inceptor Squad,,240,,,255,
Space Marines,6 Outrider Squad,,140,,,,
Space Marines,6 Victrix Honour Guard,,220,,240,,
Space Marines,ADRAX AGATONE,85,80,"-5,88%",,,
Space Marines,AETHON SHAAN,110,100,"-9,09%",,,
Space Marines,ANCIENT,50,40,"-20,00%",,,
Space Marines,ANCIENT IN TERMINATOR ARMOUR,75,65,"-13,33%",,,
Space Marines,APOTHECARY,50,40,"-20,00%",,,
Space Marines,APOTHECARY BIOLOGIS,70,70,"0,00%",,,
Space Marines,ASTRAEUS,525,525,"0,00%",575,,
Space Marines,BALLISTUS DREADNOUGHT,150,150,"0,00%",,160,
Space Marines,BLADEGUARD ANCIENT,45,40,"-11,11%",,,
Space Marines,BRUTALIS DREADNOUGHT,160,150,"-6,25%",,160,
Space Marines,CAANOK VAR,100,90,"-10,00%",,,
Space Marines,CAPTAIN,80,80,"0,00%",,,
Space Marines,CAPTAIN IN GRAVIS ARMOUR,80,80,"0,00%",,,
Space Marines,CAPTAIN IN PHOBOS ARMOUR,70,70,"0,00%",,,
Space Marines,CAPTAIN IN TERMINATOR ARMOUR,95,85,"-10,53%",,,
Space Marines,CAPTAIN TITUS,90,90,"0,00%",,,
Space Marines,CAPTAIN WITH JUMP PACK,75,75,"0,00%",,,
Space Marines,CATO SICARIUS,95,95,"0,00%",,,
Space Marines,CHAPLAIN,60,60,"0,00%",,,
Space Marines,CHAPLAIN IN TERMINATOR ARMOUR,75,75,"0,00%",,,
Space Marines,CHAPLAIN ON BIKE,75,70,"-6,67%",,,
Space Marines,CHAPLAIN WITH JUMP PACK,75,75,"0,00%",,,
Space Marines,CHIEF LIBRARIAN TIGURIUS,75,95,"26,67%",,,
Space Marines,COMPANY HEROES,105,105,"0,00%",,,
Space Marines,DARNATH LYSANDER,100,100,"0,00%",,,
Space Marines,DESOLATION SQUAD,200,180,"-10,00%",210,,
Space Marines,DREADNOUGHT,135,135,"0,00%",,,
Space Marines,DROP POD,70,70,"0,00%",,,
Space Marines,ELIMINATOR SQUAD,85,75,"-11,76%",,,
Space Marines,ERADICATOR SQUAD WITH HEAVY BOLTERS,,70,,,80,
Space Marines,GLADIATOR LANCER,160,160,"0,00%",,170,
Space Marines,GLADIATOR REAPER,160,160,"0,00%",,170,
Space Marines,GLADIATOR VALIANT,150,150,"0,00%",,160,
Space Marines,HAMMERFALL BUNKER,175,175,"0,00%",,,
Space Marines,IMPULSOR,80,80,"0,00%",,,
Space Marines,INVADER ATV,60,60,"0,00%",,,
Space Marines,INVICTOR TACTICAL WARSUIT,125,125,"0,00%",,,
Space Marines,IRON FATHER FEIRROS,95,85,"-10,53%",,,
Space Marines,JUDICIAR,70,55,"-21,43%",,,
Space Marines,KAYVAAN SHRIKE,100,100,"0,00%",,,
Space Marines,KORSARRO KHAN,60,55,"-8,33%",,,
Space Marines,LAND RAIDER,220,220,"0,00%",,240,
Space Marines,LAND RAIDER CRUSADER,220,220,"0,00%",,240,
Space Marines,LAND RAIDER REDEEMER,270,250,"-7,41%",,270,
Space Marines,LAND SPEEDER,,95,,,105,
Space Marines,LIBRARIAN,65,60,"-7,69%",,,
Space Marines,LIBRARIAN IN PHOBOS ARMOUR,70,70,"0,00%",,,
Space Marines,LIBRARIAN IN TERMINATOR ARMOUR,75,75,"0,00%",,,
Space Marines,LIEUTENANT,55,45,"-18,18%",,,
Space Marines,LIEUTENANT IN PHOBOS ARMOUR,55,45,"-18,18%",,,
Space Marines,LIEUTENANT IN REIVER ARMOUR,55,45,"-18,18%",,,
Space Marines,LIEUTENANT WITH COMBI-WEAPON,85,85,"0,00%",,,
Space Marines,MARNEUS CALGAR IN ARMOUR OF ANTILOCHUS,140,140,"0,00%",,,
Space Marines,PEDRO KANTOR,90,80,"-11,11%",,,
Space Marines,PREDATOR ANNIHILATOR,135,135,"0,00%",,145,
Space Marines,PREDATOR DESTRUCTOR,140,140,"0,00%",,150,
Space Marines,RAZORBACK,95,95,"0,00%",,,
Space Marines,REDEMPTOR DREADNOUGHT,205,195,"-4,88%",,210,
Space Marines,REPULSOR,180,170,"-5,56%",,190,
Space Marines,REPULSOR EXECUTIONER,230,240,"4,35%",,260,
Space Marines,RHINO,75,75,"0,00%",,,
Space Marines,ROBOUTE GUILLIMAN,340,340,"0,00%",,,
Space Marines,STORM SPEEDER HAILSTRIKE,115,105,"-8,70%",,115,
Space Marines,STORM SPEEDER HAMMERSTRIKE,125,130,"4,00%",,140,
Space Marines,STORM SPEEDER THUNDERSTRIKE,135,135,"0,00%",,145,
Space Marines,STORMHAWK INTERCEPTOR,155,155,"0,00%",,,
Space Marines,STORMRAVEN GUNSHIP,280,280,"0,00%",300,,
Space Marines,STORMTALON GUNSHIP,165,165,"0,00%",,,
Space Marines,SUBODEN KHAN,115,100,"-13,04%",,,
Space Marines,SUPPRESSOR SQUAD,75,75,"0,00%",,,
Space Marines,TACTICAL SQUAD,140,140,"0,00%",,,
Space Marines,TECHMARINE,55,55,"0,00%",,,
Space Marines,THUNDERHAWK GUNSHIP,840,840,"0,00%",,,
Space Marines,TOR GARADON,90,80,"-11,11%",,,
Space Marines,URIEL VENTRIS,95,95,"0,00%",,,
Space Marines,VINDICATOR,185,185,"0,00%",,200,
Space Marines,VULKAN HESTAN,100,85,"-15,00%",,,
Space Marines,WARDENS OF ULTRAMAR,105,110,"4,76%",,,
Space Marines,WHIRLWIND,190,175,"-7,89%",195,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Space Marines 1 Firestrike Servo-Turrets 75 75 0,00%
3 Space Marines 10 Assault Intercessor Squad 150
4 Space Marines 10 Assault Intercessors With Jump Packs 160 170
5 Space Marines 10 Devastator Squad 200
6 Space Marines 10 Heavy Intercessor Squad 200
7 Space Marines 10 Hellblaster Squad 220
8 Space Marines 10 Incursor Squad 150 160
9 Space Marines 10 Infernus Squad 170
10 Space Marines 10 Infiltrator Squad 180 190
11 Space Marines 10 Intercessor Squad 150
12 Space Marines 10 Reiver Squad 150
13 Space Marines 10 Scout Squad 120 130
14 Space Marines 10 Sternguard Veteran Squad 190
15 Space Marines 10 Terminator Assault Squad 310
16 Space Marines 10 Terminator Squad 320
17 Space Marines 10 Vanguard Veteran Squad With Jump Packs 200 210
18 Space Marines 2 Firestrike Servo-Turrets 150
19 Space Marines 3 Aggressor Squad 95 90 -5,26% 100
20 Space Marines 3 Bladeguard Veteran Squad 80 80 0,00% 90
21 Space Marines 3 Centurion Assault Squad 150 150 0,00%
22 Space Marines 3 Centurion Devastator Squad 175 175 0,00%
23 Space Marines 3 Eradicator Squad 90 90 0,00% 100
24 Space Marines 3 Inceptor Squad 120 120 0,00% 135
25 Space Marines 3 Outrider Squad 80 70 -12,50%
26 Space Marines 3 Victrix Honour Guard 110 110 0,00% 130
27 Space Marines 5 Assault Intercessor Squad 75 75 0,00%
28 Space Marines 5 Assault Intercessors With Jump Packs 90 85 -5,56% 95
29 Space Marines 5 Devastator Squad 120 120 0,00%
30 Space Marines 5 Heavy Intercessor Squad 100 100 0,00%
31 Space Marines 5 Hellblaster Squad 110 110 0,00%
32 Space Marines 5 Incursor Squad 80 85 6,25% 95
33 Space Marines 5 Infernus Squad 90 85 -5,56%
34 Space Marines 5 Infiltrator Squad 100 110 10,00% 120
35 Space Marines 5 Intercessor Squad 80 80 0,00%
36 Space Marines 5 Reiver Squad 80 75 -6,25%
37 Space Marines 5 Scout Squad 70 65 -7,14% 75
38 Space Marines 5 Sternguard Veteran Squad 100 100 0,00%
39 Space Marines 5 Terminator Assault Squad 180 155 -13,89%
40 Space Marines 5 Terminator Squad 170 160 -5,88%
41 Space Marines 5 Vanguard Veteran Squad With Jump Packs 100 100 0,00% 110
42 Space Marines 6 Aggressor Squad 180 190
43 Space Marines 6 Bladeguard Veteran Squad 160 170
44 Space Marines 6 Centurion Assault Squad 300
45 Space Marines 6 Centurion Devastator Squad 350
46 Space Marines 6 Eradicator Squad 180 190
47 Space Marines 6 Inceptor Squad 240 255
48 Space Marines 6 Outrider Squad 140
49 Space Marines 6 Victrix Honour Guard 220 240
50 Space Marines ADRAX AGATONE 85 80 -5,88%
51 Space Marines AETHON SHAAN 110 100 -9,09%
52 Space Marines ANCIENT 50 40 -20,00%
53 Space Marines ANCIENT IN TERMINATOR ARMOUR 75 65 -13,33%
54 Space Marines APOTHECARY 50 40 -20,00%
55 Space Marines APOTHECARY BIOLOGIS 70 70 0,00%
56 Space Marines ASTRAEUS 525 525 0,00% 575
57 Space Marines BALLISTUS DREADNOUGHT 150 150 0,00% 160
58 Space Marines BLADEGUARD ANCIENT 45 40 -11,11%
59 Space Marines BRUTALIS DREADNOUGHT 160 150 -6,25% 160
60 Space Marines CAANOK VAR 100 90 -10,00%
61 Space Marines CAPTAIN 80 80 0,00%
62 Space Marines CAPTAIN IN GRAVIS ARMOUR 80 80 0,00%
63 Space Marines CAPTAIN IN PHOBOS ARMOUR 70 70 0,00%
64 Space Marines CAPTAIN IN TERMINATOR ARMOUR 95 85 -10,53%
65 Space Marines CAPTAIN TITUS 90 90 0,00%
66 Space Marines CAPTAIN WITH JUMP PACK 75 75 0,00%
67 Space Marines CATO SICARIUS 95 95 0,00%
68 Space Marines CHAPLAIN 60 60 0,00%
69 Space Marines CHAPLAIN IN TERMINATOR ARMOUR 75 75 0,00%
70 Space Marines CHAPLAIN ON BIKE 75 70 -6,67%
71 Space Marines CHAPLAIN WITH JUMP PACK 75 75 0,00%
72 Space Marines CHIEF LIBRARIAN TIGURIUS 75 95 26,67%
73 Space Marines COMPANY HEROES 105 105 0,00%
74 Space Marines DARNATH LYSANDER 100 100 0,00%
75 Space Marines DESOLATION SQUAD 200 180 -10,00% 210
76 Space Marines DREADNOUGHT 135 135 0,00%
77 Space Marines DROP POD 70 70 0,00%
78 Space Marines ELIMINATOR SQUAD 85 75 -11,76%
79 Space Marines ERADICATOR SQUAD WITH HEAVY BOLTERS 70 80
80 Space Marines GLADIATOR LANCER 160 160 0,00% 170
81 Space Marines GLADIATOR REAPER 160 160 0,00% 170
82 Space Marines GLADIATOR VALIANT 150 150 0,00% 160
83 Space Marines HAMMERFALL BUNKER 175 175 0,00%
84 Space Marines IMPULSOR 80 80 0,00%
85 Space Marines INVADER ATV 60 60 0,00%
86 Space Marines INVICTOR TACTICAL WARSUIT 125 125 0,00%
87 Space Marines IRON FATHER FEIRROS 95 85 -10,53%
88 Space Marines JUDICIAR 70 55 -21,43%
89 Space Marines KAYVAAN SHRIKE 100 100 0,00%
90 Space Marines KOR’SARRO KHAN 60 55 -8,33%
91 Space Marines LAND RAIDER 220 220 0,00% 240
92 Space Marines LAND RAIDER CRUSADER 220 220 0,00% 240
93 Space Marines LAND RAIDER REDEEMER 270 250 -7,41% 270
94 Space Marines LAND SPEEDER 95 105
95 Space Marines LIBRARIAN 65 60 -7,69%
96 Space Marines LIBRARIAN IN PHOBOS ARMOUR 70 70 0,00%
97 Space Marines LIBRARIAN IN TERMINATOR ARMOUR 75 75 0,00%
98 Space Marines LIEUTENANT 55 45 -18,18%
99 Space Marines LIEUTENANT IN PHOBOS ARMOUR 55 45 -18,18%
100 Space Marines LIEUTENANT IN REIVER ARMOUR 55 45 -18,18%
101 Space Marines LIEUTENANT WITH COMBI-WEAPON 85 85 0,00%
102 Space Marines MARNEUS CALGAR IN ARMOUR OF ANTILOCHUS 140 140 0,00%
103 Space Marines PEDRO KANTOR 90 80 -11,11%
104 Space Marines PREDATOR ANNIHILATOR 135 135 0,00% 145
105 Space Marines PREDATOR DESTRUCTOR 140 140 0,00% 150
106 Space Marines RAZORBACK 95 95 0,00%
107 Space Marines REDEMPTOR DREADNOUGHT 205 195 -4,88% 210
108 Space Marines REPULSOR 180 170 -5,56% 190
109 Space Marines REPULSOR EXECUTIONER 230 240 4,35% 260
110 Space Marines RHINO 75 75 0,00%
111 Space Marines ROBOUTE GUILLIMAN 340 340 0,00%
112 Space Marines STORM SPEEDER HAILSTRIKE 115 105 -8,70% 115
113 Space Marines STORM SPEEDER HAMMERSTRIKE 125 130 4,00% 140
114 Space Marines STORM SPEEDER THUNDERSTRIKE 135 135 0,00% 145
115 Space Marines STORMHAWK INTERCEPTOR 155 155 0,00%
116 Space Marines STORMRAVEN GUNSHIP 280 280 0,00% 300
117 Space Marines STORMTALON GUNSHIP 165 165 0,00%
118 Space Marines SUBODEN KHAN 115 100 -13,04%
119 Space Marines SUPPRESSOR SQUAD 75 75 0,00%
120 Space Marines TACTICAL SQUAD 140 140 0,00%
121 Space Marines TECHMARINE 55 55 0,00%
122 Space Marines THUNDERHAWK GUNSHIP 840 840 0,00%
123 Space Marines TOR GARADON 90 80 -11,11%
124 Space Marines URIEL VENTRIS 95 95 0,00%
125 Space Marines VINDICATOR 185 185 0,00% 200
126 Space Marines VULKAN HE’STAN 100 85 -15,00%
127 Space Marines WARDENS OF ULTRAMAR 105 110 4,76%
128 Space Marines WHIRLWIND 190 175 -7,89% 195

130
csv/space-wolves.csv Normal file
View File

@@ -0,0 +1,130 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Space Wolves,1 Firestrike Servo-Turrets,75,75,"0,00%",,,
Space Wolves,10 Assault Intercessor Squad,,150,,,,
Space Wolves,10 Assault Intercessors With Jump Packs,,160,,,170,
Space Wolves,10 Blood Claws,135,135,"0,00%",,,
Space Wolves,10 Fenrisian Wolves,,75,,,80,
Space Wolves,10 Heavy Intercessor Squad,,200,,,,
Space Wolves,10 Hellblaster Squad,,220,,,,
Space Wolves,10 Incursor Squad,,150,,,160,
Space Wolves,10 Infernus Squad,,170,,,,
Space Wolves,10 Infiltrator Squad,,180,,,190,
Space Wolves,10 Intercessor Squad,,150,,,,
Space Wolves,10 Reiver Squad,,150,,,,
Space Wolves,10 Scout Squad,,120,,,130,
Space Wolves,10 Sternguard Veteran Squad,,190,,,,
Space Wolves,10 Terminator Assault Squad,,310,,,,
Space Wolves,10 Terminator Squad,,320,,,,
Space Wolves,10 Vanguard Veteran Squad With Jump Packs,,200,,,210,
Space Wolves,10 Wolf Guard Terminators,,300,,310,,
Space Wolves,10 Wulfen,,170,,,180,
Space Wolves,10 Wulfen With Storm Shields,,200,,,210,
Space Wolves,12 Wolf Scouts,,190,,,200,
Space Wolves,2 Firestrike Servo-Turrets,,150,,,,
Space Wolves,20 Blood Claws,,270,,,,
Space Wolves,3 Aggressor Squad,95,90,"-5,26%",,100,
Space Wolves,3 Bladeguard Veteran Squad,80,80,"0,00%",,90,
Space Wolves,3 Centurion Assault Squad,150,150,"0,00%",,,
Space Wolves,3 Centurion Devastator Squad,175,175,"0,00%",,,
Space Wolves,3 Eradicator Squad,90,90,"0,00%",,100,
Space Wolves,3 Inceptor Squad,120,120,"0,00%",,135,
Space Wolves,3 Outrider Squad,80,70,"-12,50%",,,
Space Wolves,3 Thunderwolf Cavalry,115,100,"-13,04%",,110,
Space Wolves,5 Assault Intercessor Squad,75,75,"0,00%",,,
Space Wolves,5 Assault Intercessors With Jump Packs,90,85,"-5,56%",,95,
Space Wolves,5 Fenrisian Wolves,40,45,"12,50%",,50,
Space Wolves,5 Heavy Intercessor Squad,100,100,"0,00%",,,
Space Wolves,5 Hellblaster Squad,110,110,"0,00%",,,
Space Wolves,5 Incursor Squad,80,85,"6,25%",,95,
Space Wolves,5 Infernus Squad,90,85,"-5,56%",,,
Space Wolves,5 Infiltrator Squad,100,110,"10,00%",,120,
Space Wolves,5 Intercessor Squad,80,80,"0,00%",,,
Space Wolves,5 Reiver Squad,80,75,"-6,25%",,,
Space Wolves,5 Scout Squad,70,65,"-7,14%",,75,
Space Wolves,5 Sternguard Veteran Squad,100,100,"0,00%",,,
Space Wolves,5 Terminator Assault Squad,180,155,"-13,89%",,,
Space Wolves,5 Terminator Squad,170,160,"-5,88%",,,
Space Wolves,5 Vanguard Veteran Squad With Jump Packs,100,100,"0,00%",,110,
Space Wolves,5 Wolf Guard Terminators,170,150,"-11,76%",160,,
Space Wolves,5 Wulfen,85,85,"0,00%",,95,
Space Wolves,5 Wulfen With Storm Shields,100,100,"0,00%",,110,
Space Wolves,6 Aggressor Squad,,180,,,190,
Space Wolves,6 Bladeguard Veteran Squad,,160,,,170,
Space Wolves,6 Centurion Assault Squad,,300,,,,
Space Wolves,6 Centurion Devastator Squad,,350,,,,
Space Wolves,6 Eradicator Squad,,180,,,190,
Space Wolves,6 Inceptor Squad,,240,,,255,
Space Wolves,6 Outrider Squad,,140,,,,
Space Wolves,6 Thunderwolf Cavalry,,200,,,210,
Space Wolves,6 Wolf Scouts,105,95,"-9,52%",,105,
Space Wolves,ANCIENT,50,40,"-20,00%",,,
Space Wolves,ANCIENT IN TERMINATOR ARMOUR,75,65,"-13,33%",,,
Space Wolves,ARJAC ROCKFIST,105,105,"0,00%",,,
Space Wolves,ASTRAEUS,525,525,"0,00%",575,,
Space Wolves,BALLISTUS DREADNOUGHT,150,150,"0,00%",,160,
Space Wolves,BJORN THE FELL-HANDED,160,160,"0,00%",,,
Space Wolves,BLADEGUARD ANCIENT,45,40,"-11,11%",,,
Space Wolves,BRUTALIS DREADNOUGHT,160,150,"-6,25%",,160,
Space Wolves,CAPTAIN,80,80,"0,00%",,,
Space Wolves,CAPTAIN IN GRAVIS ARMOUR,80,80,"0,00%",,,
Space Wolves,CAPTAIN IN PHOBOS ARMOUR,70,70,"0,00%",,,
Space Wolves,CAPTAIN IN TERMINATOR ARMOUR,95,85,"-10,53%",,,
Space Wolves,CAPTAIN WITH JUMP PACK,75,75,"0,00%",,,
Space Wolves,CHAPLAIN,60,60,"0,00%",,,
Space Wolves,CHAPLAIN IN TERMINATOR ARMOUR,75,75,"0,00%",,,
Space Wolves,CHAPLAIN ON BIKE,75,70,"-6,67%",,,
Space Wolves,CHAPLAIN WITH JUMP PACK,75,75,"0,00%",,,
Space Wolves,COMPANY HEROES,105,105,"0,00%",,,
Space Wolves,DESOLATION SQUAD,200,180,"-10,00%",210,,
Space Wolves,DREADNOUGHT,135,135,"0,00%",,,
Space Wolves,DROP POD,70,70,"0,00%",,,
Space Wolves,ELIMINATOR SQUAD,85,75,"-11,76%",,,
Space Wolves,ERADICATOR SQUAD WITH HEAVY BOLTERS,,70,,,80,
Space Wolves,GLADIATOR LANCER,160,160,"0,00%",,170,
Space Wolves,GLADIATOR REAPER,160,160,"0,00%",,170,
Space Wolves,GLADIATOR VALIANT,150,150,"0,00%",,160,
Space Wolves,GREY HUNTERS,165,165,"0,00%",,,
Space Wolves,HAMMERFALL BUNKER,175,175,"0,00%",,,
Space Wolves,IMPULSOR,80,80,"0,00%",,,
Space Wolves,INVADER ATV,60,60,"0,00%",,,
Space Wolves,INVICTOR TACTICAL WARSUIT,125,125,"0,00%",,,
Space Wolves,IRON PRIEST,55,55,"0,00%",,,
Space Wolves,JUDICIAR,70,55,"-21,43%",,,
Space Wolves,LAND RAIDER,220,220,"0,00%",,240,
Space Wolves,LAND RAIDER CRUSADER,220,220,"0,00%",,240,
Space Wolves,LAND RAIDER REDEEMER,270,250,"-7,41%",,270,
Space Wolves,LAND SPEEDER,,95,,,105,
Space Wolves,LIBRARIAN,65,60,"-7,69%",,,
Space Wolves,LIBRARIAN IN PHOBOS ARMOUR,70,70,"0,00%",,,
Space Wolves,LIBRARIAN IN TERMINATOR ARMOUR,75,75,"0,00%",,,
Space Wolves,LIEUTENANT,55,45,"-18,18%",,,
Space Wolves,LIEUTENANT IN PHOBOS ARMOUR,55,45,"-18,18%",,,
Space Wolves,LIEUTENANT IN REIVER ARMOUR,55,45,"-18,18%",,,
Space Wolves,LIEUTENANT WITH COMBI-WEAPON,85,85,"0,00%",,,
Space Wolves,LOGAN GRIMNAR,110,110,"0,00%",,,
Space Wolves,MURDERFANG,150,150,"0,00%",,,
Space Wolves,NJAL STORMCALLER,85,85,"0,00%",,,
Space Wolves,PREDATOR ANNIHILATOR,135,135,"0,00%",,145,
Space Wolves,PREDATOR DESTRUCTOR,140,140,"0,00%",,150,
Space Wolves,RAGNAR BLACKMANE,100,100,"0,00%",,,
Space Wolves,RAZORBACK,95,95,"0,00%",,,
Space Wolves,REDEMPTOR DREADNOUGHT,205,195,"-4,88%",,210,
Space Wolves,REPULSOR,180,170,"-5,56%",,190,
Space Wolves,REPULSOR EXECUTIONER,230,230,"0,00%",,250,
Space Wolves,RHINO,75,75,"0,00%",,,
Space Wolves,STORM SPEEDER HAILSTRIKE,115,105,"-8,70%",,115,
Space Wolves,STORM SPEEDER HAMMERSTRIKE,125,130,"4,00%",,140,
Space Wolves,STORM SPEEDER THUNDERSTRIKE,135,135,"0,00%",,145,
Space Wolves,STORMHAWK INTERCEPTOR,155,155,"0,00%",,,
Space Wolves,STORMRAVEN GUNSHIP,280,280,"0,00%",300,,
Space Wolves,STORMTALON GUNSHIP,165,165,"0,00%",,,
Space Wolves,SUPPRESSOR SQUAD,75,75,"0,00%",,,
Space Wolves,TECHMARINE,55,55,"0,00%",,,
Space Wolves,THUNDERHAWK GUNSHIP,840,840,"0,00%",,,
Space Wolves,ULRIK THE SLAYER,70,70,"0,00%",,,
Space Wolves,VENERABLE DREADNOUGHT,130,125,"-3,85%",,135,
Space Wolves,VINDICATOR,185,185,"0,00%",,200,
Space Wolves,WHIRLWIND,190,175,"-7,89%",195,,
Space Wolves,WOLF GUARD BATTLE LEADER,65,65,"0,00%",,,
Space Wolves,WOLF PRIEST,70,70,"0,00%",,,
Space Wolves,WULFEN DREADNOUGHT,135,135,"0,00%",,145,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Space Wolves 1 Firestrike Servo-Turrets 75 75 0,00%
3 Space Wolves 10 Assault Intercessor Squad 150
4 Space Wolves 10 Assault Intercessors With Jump Packs 160 170
5 Space Wolves 10 Blood Claws 135 135 0,00%
6 Space Wolves 10 Fenrisian Wolves 75 80
7 Space Wolves 10 Heavy Intercessor Squad 200
8 Space Wolves 10 Hellblaster Squad 220
9 Space Wolves 10 Incursor Squad 150 160
10 Space Wolves 10 Infernus Squad 170
11 Space Wolves 10 Infiltrator Squad 180 190
12 Space Wolves 10 Intercessor Squad 150
13 Space Wolves 10 Reiver Squad 150
14 Space Wolves 10 Scout Squad 120 130
15 Space Wolves 10 Sternguard Veteran Squad 190
16 Space Wolves 10 Terminator Assault Squad 310
17 Space Wolves 10 Terminator Squad 320
18 Space Wolves 10 Vanguard Veteran Squad With Jump Packs 200 210
19 Space Wolves 10 Wolf Guard Terminators 300 310
20 Space Wolves 10 Wulfen 170 180
21 Space Wolves 10 Wulfen With Storm Shields 200 210
22 Space Wolves 12 Wolf Scouts 190 200
23 Space Wolves 2 Firestrike Servo-Turrets 150
24 Space Wolves 20 Blood Claws 270
25 Space Wolves 3 Aggressor Squad 95 90 -5,26% 100
26 Space Wolves 3 Bladeguard Veteran Squad 80 80 0,00% 90
27 Space Wolves 3 Centurion Assault Squad 150 150 0,00%
28 Space Wolves 3 Centurion Devastator Squad 175 175 0,00%
29 Space Wolves 3 Eradicator Squad 90 90 0,00% 100
30 Space Wolves 3 Inceptor Squad 120 120 0,00% 135
31 Space Wolves 3 Outrider Squad 80 70 -12,50%
32 Space Wolves 3 Thunderwolf Cavalry 115 100 -13,04% 110
33 Space Wolves 5 Assault Intercessor Squad 75 75 0,00%
34 Space Wolves 5 Assault Intercessors With Jump Packs 90 85 -5,56% 95
35 Space Wolves 5 Fenrisian Wolves 40 45 12,50% 50
36 Space Wolves 5 Heavy Intercessor Squad 100 100 0,00%
37 Space Wolves 5 Hellblaster Squad 110 110 0,00%
38 Space Wolves 5 Incursor Squad 80 85 6,25% 95
39 Space Wolves 5 Infernus Squad 90 85 -5,56%
40 Space Wolves 5 Infiltrator Squad 100 110 10,00% 120
41 Space Wolves 5 Intercessor Squad 80 80 0,00%
42 Space Wolves 5 Reiver Squad 80 75 -6,25%
43 Space Wolves 5 Scout Squad 70 65 -7,14% 75
44 Space Wolves 5 Sternguard Veteran Squad 100 100 0,00%
45 Space Wolves 5 Terminator Assault Squad 180 155 -13,89%
46 Space Wolves 5 Terminator Squad 170 160 -5,88%
47 Space Wolves 5 Vanguard Veteran Squad With Jump Packs 100 100 0,00% 110
48 Space Wolves 5 Wolf Guard Terminators 170 150 -11,76% 160
49 Space Wolves 5 Wulfen 85 85 0,00% 95
50 Space Wolves 5 Wulfen With Storm Shields 100 100 0,00% 110
51 Space Wolves 6 Aggressor Squad 180 190
52 Space Wolves 6 Bladeguard Veteran Squad 160 170
53 Space Wolves 6 Centurion Assault Squad 300
54 Space Wolves 6 Centurion Devastator Squad 350
55 Space Wolves 6 Eradicator Squad 180 190
56 Space Wolves 6 Inceptor Squad 240 255
57 Space Wolves 6 Outrider Squad 140
58 Space Wolves 6 Thunderwolf Cavalry 200 210
59 Space Wolves 6 Wolf Scouts 105 95 -9,52% 105
60 Space Wolves ANCIENT 50 40 -20,00%
61 Space Wolves ANCIENT IN TERMINATOR ARMOUR 75 65 -13,33%
62 Space Wolves ARJAC ROCKFIST 105 105 0,00%
63 Space Wolves ASTRAEUS 525 525 0,00% 575
64 Space Wolves BALLISTUS DREADNOUGHT 150 150 0,00% 160
65 Space Wolves BJORN THE FELL-HANDED 160 160 0,00%
66 Space Wolves BLADEGUARD ANCIENT 45 40 -11,11%
67 Space Wolves BRUTALIS DREADNOUGHT 160 150 -6,25% 160
68 Space Wolves CAPTAIN 80 80 0,00%
69 Space Wolves CAPTAIN IN GRAVIS ARMOUR 80 80 0,00%
70 Space Wolves CAPTAIN IN PHOBOS ARMOUR 70 70 0,00%
71 Space Wolves CAPTAIN IN TERMINATOR ARMOUR 95 85 -10,53%
72 Space Wolves CAPTAIN WITH JUMP PACK 75 75 0,00%
73 Space Wolves CHAPLAIN 60 60 0,00%
74 Space Wolves CHAPLAIN IN TERMINATOR ARMOUR 75 75 0,00%
75 Space Wolves CHAPLAIN ON BIKE 75 70 -6,67%
76 Space Wolves CHAPLAIN WITH JUMP PACK 75 75 0,00%
77 Space Wolves COMPANY HEROES 105 105 0,00%
78 Space Wolves DESOLATION SQUAD 200 180 -10,00% 210
79 Space Wolves DREADNOUGHT 135 135 0,00%
80 Space Wolves DROP POD 70 70 0,00%
81 Space Wolves ELIMINATOR SQUAD 85 75 -11,76%
82 Space Wolves ERADICATOR SQUAD WITH HEAVY BOLTERS 70 80
83 Space Wolves GLADIATOR LANCER 160 160 0,00% 170
84 Space Wolves GLADIATOR REAPER 160 160 0,00% 170
85 Space Wolves GLADIATOR VALIANT 150 150 0,00% 160
86 Space Wolves GREY HUNTERS 165 165 0,00%
87 Space Wolves HAMMERFALL BUNKER 175 175 0,00%
88 Space Wolves IMPULSOR 80 80 0,00%
89 Space Wolves INVADER ATV 60 60 0,00%
90 Space Wolves INVICTOR TACTICAL WARSUIT 125 125 0,00%
91 Space Wolves IRON PRIEST 55 55 0,00%
92 Space Wolves JUDICIAR 70 55 -21,43%
93 Space Wolves LAND RAIDER 220 220 0,00% 240
94 Space Wolves LAND RAIDER CRUSADER 220 220 0,00% 240
95 Space Wolves LAND RAIDER REDEEMER 270 250 -7,41% 270
96 Space Wolves LAND SPEEDER 95 105
97 Space Wolves LIBRARIAN 65 60 -7,69%
98 Space Wolves LIBRARIAN IN PHOBOS ARMOUR 70 70 0,00%
99 Space Wolves LIBRARIAN IN TERMINATOR ARMOUR 75 75 0,00%
100 Space Wolves LIEUTENANT 55 45 -18,18%
101 Space Wolves LIEUTENANT IN PHOBOS ARMOUR 55 45 -18,18%
102 Space Wolves LIEUTENANT IN REIVER ARMOUR 55 45 -18,18%
103 Space Wolves LIEUTENANT WITH COMBI-WEAPON 85 85 0,00%
104 Space Wolves LOGAN GRIMNAR 110 110 0,00%
105 Space Wolves MURDERFANG 150 150 0,00%
106 Space Wolves NJAL STORMCALLER 85 85 0,00%
107 Space Wolves PREDATOR ANNIHILATOR 135 135 0,00% 145
108 Space Wolves PREDATOR DESTRUCTOR 140 140 0,00% 150
109 Space Wolves RAGNAR BLACKMANE 100 100 0,00%
110 Space Wolves RAZORBACK 95 95 0,00%
111 Space Wolves REDEMPTOR DREADNOUGHT 205 195 -4,88% 210
112 Space Wolves REPULSOR 180 170 -5,56% 190
113 Space Wolves REPULSOR EXECUTIONER 230 230 0,00% 250
114 Space Wolves RHINO 75 75 0,00%
115 Space Wolves STORM SPEEDER HAILSTRIKE 115 105 -8,70% 115
116 Space Wolves STORM SPEEDER HAMMERSTRIKE 125 130 4,00% 140
117 Space Wolves STORM SPEEDER THUNDERSTRIKE 135 135 0,00% 145
118 Space Wolves STORMHAWK INTERCEPTOR 155 155 0,00%
119 Space Wolves STORMRAVEN GUNSHIP 280 280 0,00% 300
120 Space Wolves STORMTALON GUNSHIP 165 165 0,00%
121 Space Wolves SUPPRESSOR SQUAD 75 75 0,00%
122 Space Wolves TECHMARINE 55 55 0,00%
123 Space Wolves THUNDERHAWK GUNSHIP 840 840 0,00%
124 Space Wolves ULRIK THE SLAYER 70 70 0,00%
125 Space Wolves VENERABLE DREADNOUGHT 130 125 -3,85% 135
126 Space Wolves VINDICATOR 185 185 0,00% 200
127 Space Wolves WHIRLWIND 190 175 -7,89% 195
128 Space Wolves WOLF GUARD BATTLE LEADER 65 65 0,00%
129 Space Wolves WOLF PRIEST 70 70 0,00%
130 Space Wolves WULFEN DREADNOUGHT 135 135 0,00% 145

54
csv/tau-empire.csv Normal file
View File

@@ -0,0 +1,54 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
T'au Empire,1 Broadside Battlesuits,80,75,"-6,25%",,95,
T'au Empire,1 Krootox Riders,40,45,"12,50%",,,
T'au Empire,1 Piranhas,60,65,"8,33%",,75,
T'au Empire,10 Kroot Carnivores,65,65,"0,00%",,,
T'au Empire,10 Kroot Hounds,,65,,,,
T'au Empire,10 Vespid Stingwings,,115,,,,
T'au Empire,2 Broadside Battlesuits,,150,,,170,
T'au Empire,2 Krootox Riders,,60,,,,
T'au Empire,2 Piranhas,,110,,,120,
T'au Empire,20 Kroot Carnivores,,130,,,,
T'au Empire,3 Broadside Battlesuits,,240,,,260,
T'au Empire,3 Krootox Rampagers,85,85,"0,00%",,95,
T'au Empire,3 Krootox Riders,,90,,,,
T'au Empire,3 Piranhas,,165,,,175,
T'au Empire,5 Kroot Hounds,40,45,"12,50%",,,
T'au Empire,5 Vespid Stingwings,65,70,"7,69%",,,
T'au Empire,6 Krootox Rampagers,,170,,,180,
T'au Empire,AX-1-0 TIGER SHARK,315,315,"0,00%",375,,
T'au Empire,BREACHER TEAM,90,90,"0,00%",,,
T'au Empire,CADRE FIREBLADE,50,50,"0,00%",,,
T'au Empire,COMMANDER FARSIGHT,85,80,"-5,88%",,,
T'au Empire,COMMANDER IN COLDSTAR BATTLESUIT,95,95,"0,00%",,,
T'au Empire,COMMANDER IN ENFORCER BATTLESUIT,80,80,"0,00%",,,
T'au Empire,COMMANDER SHADOWSUN,100,100,"0,00%",,,
T'au Empire,CRISIS FIREKNIFE BATTLESUITS,120,100,"-16,67%",,110,
T'au Empire,CRISIS STARSCYTHE BATTLESUITS,110,90,"-18,18%",,100,
T'au Empire,CRISIS SUNFORGE BATTLESUITS,140,125,"-10,71%",,135,
T'au Empire,DARKSTRIDER,60,60,"0,00%",,,
T'au Empire,DEVILFISH,85,85,"0,00%",,,
T'au Empire,ETHEREAL,50,50,"0,00%",,,
T'au Empire,FIRESIGHT TEAM,60,55,"-8,33%",,,
T'au Empire,GHOSTKEEL BATTLESUIT,160,150,"-6,25%",,160,
T'au Empire,HAMMERHEAD GUNSHIP,145,140,"-3,45%",,150,
T'au Empire,KROOT FARSTALKERS,85,75,"-11,76%",,85,
T'au Empire,KROOT FLESH SHAPER,45,45,"0,00%",,,
T'au Empire,KROOT LONE-SPEAR,80,80,"0,00%",,,
T'au Empire,KROOT TRAIL SHAPER,55,50,"-9,09%",,,
T'au Empire,KROOT WAR SHAPER,50,60,"20,00%",,,
T'au Empire,MANTA,2100,2100,"0,00%",,,
T'au Empire,PATHFINDER TEAM,90,80,"-11,11%",,,
T'au Empire,RAZORSHARK STRIKE FIGHTER,170,160,"-5,88%",,,
T'au Empire,RIPTIDE BATTLESUIT,200,180,"-10,00%",,210,
T'au Empire,SKY RAY GUNSHIP,150,140,"-6,67%",,,
T'au Empire,STEALTH BATTLESUITS,100,100,"0,00%",,110,
T'au Empire,STORMSURGE,360,360,"0,00%",385,,
T'au Empire,STRIKE TEAM,70,70,"0,00%",,,
T'au Empire,SUN SHARK BOMBER,160,150,"-6,25%",,,
T'au Empire,TAUNAR SUPREMACY ARMOUR,790,790,"0,00%",890,,
T'au Empire,THE TWIN LANCE,185,205,"10,81%",,,
T'au Empire,TIDEWALL DRONEPORT,85,85,"0,00%",,,
T'au Empire,TIDEWALL GUNRIG,90,90,"0,00%",,,
T'au Empire,TIDEWALL SHIELDLINE,85,85,"0,00%",,,
T'au Empire,TIGER SHARK,325,325,"0,00%",375,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 T'au Empire 1 Broadside Battlesuits 80 75 -6,25% 95
3 T'au Empire 1 Krootox Riders 40 45 12,50%
4 T'au Empire 1 Piranhas 60 65 8,33% 75
5 T'au Empire 10 Kroot Carnivores 65 65 0,00%
6 T'au Empire 10 Kroot Hounds 65
7 T'au Empire 10 Vespid Stingwings 115
8 T'au Empire 2 Broadside Battlesuits 150 170
9 T'au Empire 2 Krootox Riders 60
10 T'au Empire 2 Piranhas 110 120
11 T'au Empire 20 Kroot Carnivores 130
12 T'au Empire 3 Broadside Battlesuits 240 260
13 T'au Empire 3 Krootox Rampagers 85 85 0,00% 95
14 T'au Empire 3 Krootox Riders 90
15 T'au Empire 3 Piranhas 165 175
16 T'au Empire 5 Kroot Hounds 40 45 12,50%
17 T'au Empire 5 Vespid Stingwings 65 70 7,69%
18 T'au Empire 6 Krootox Rampagers 170 180
19 T'au Empire AX-1-0 TIGER SHARK 315 315 0,00% 375
20 T'au Empire BREACHER TEAM 90 90 0,00%
21 T'au Empire CADRE FIREBLADE 50 50 0,00%
22 T'au Empire COMMANDER FARSIGHT 85 80 -5,88%
23 T'au Empire COMMANDER IN COLDSTAR BATTLESUIT 95 95 0,00%
24 T'au Empire COMMANDER IN ENFORCER BATTLESUIT 80 80 0,00%
25 T'au Empire COMMANDER SHADOWSUN 100 100 0,00%
26 T'au Empire CRISIS FIREKNIFE BATTLESUITS 120 100 -16,67% 110
27 T'au Empire CRISIS STARSCYTHE BATTLESUITS 110 90 -18,18% 100
28 T'au Empire CRISIS SUNFORGE BATTLESUITS 140 125 -10,71% 135
29 T'au Empire DARKSTRIDER 60 60 0,00%
30 T'au Empire DEVILFISH 85 85 0,00%
31 T'au Empire ETHEREAL 50 50 0,00%
32 T'au Empire FIRESIGHT TEAM 60 55 -8,33%
33 T'au Empire GHOSTKEEL BATTLESUIT 160 150 -6,25% 160
34 T'au Empire HAMMERHEAD GUNSHIP 145 140 -3,45% 150
35 T'au Empire KROOT FARSTALKERS 85 75 -11,76% 85
36 T'au Empire KROOT FLESH SHAPER 45 45 0,00%
37 T'au Empire KROOT LONE-SPEAR 80 80 0,00%
38 T'au Empire KROOT TRAIL SHAPER 55 50 -9,09%
39 T'au Empire KROOT WAR SHAPER 50 60 20,00%
40 T'au Empire MANTA 2100 2100 0,00%
41 T'au Empire PATHFINDER TEAM 90 80 -11,11%
42 T'au Empire RAZORSHARK STRIKE FIGHTER 170 160 -5,88%
43 T'au Empire RIPTIDE BATTLESUIT 200 180 -10,00% 210
44 T'au Empire SKY RAY GUNSHIP 150 140 -6,67%
45 T'au Empire STEALTH BATTLESUITS 100 100 0,00% 110
46 T'au Empire STORMSURGE 360 360 0,00% 385
47 T'au Empire STRIKE TEAM 70 70 0,00%
48 T'au Empire SUN SHARK BOMBER 160 150 -6,25%
49 T'au Empire TA’UNAR SUPREMACY ARMOUR 790 790 0,00% 890
50 T'au Empire THE TWIN LANCE 185 205 10,81%
51 T'au Empire TIDEWALL DRONEPORT 85 85 0,00%
52 T'au Empire TIDEWALL GUNRIG 90 90 0,00%
53 T'au Empire TIDEWALL SHIELDLINE 85 85 0,00%
54 T'au Empire TIGER SHARK 325 325 0,00% 375

43
csv/thousand-sons.csv Normal file
View File

@@ -0,0 +1,43 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Thousand Sons,10 Rubric Marines,,190,,,,200
Thousand Sons,10 Scarab Occult Terminators,,370,,,385,
Thousand Sons,10 Tzaangors,70,75,"7,14%",,,
Thousand Sons,2 Sekhetar Robots,80,80,"0,00%",,,
Thousand Sons,20 Tzaangors,,145,,,,
Thousand Sons,3 Flamers,65,65,"0,00%",,,
Thousand Sons,3 Screamers,80,80,"0,00%",,,
Thousand Sons,3 Tzaangor Enlightened,45,45,"0,00%",,,
Thousand Sons,3 Tzaangor Enlightened With Fatecaster Greatbows,,55,,,,
Thousand Sons,4 Sekhetar Robots,,160,,,,
Thousand Sons,5 Rubric Marines,100,100,"0,00%",,,110
Thousand Sons,5 Scarab Occult Terminators,180,180,"0,00%",,195,
Thousand Sons,6 Flamers,,130,,,,
Thousand Sons,6 Screamers,,160,,,,
Thousand Sons,6 Tzaangor Enlightened,,90,,,,
Thousand Sons,6 Tzaangor Enlightened With Fatecaster Greatbows,,110,,,,
Thousand Sons,AHRIMAN,100,100,"0,00%",,,
Thousand Sons,BLUE HORRORS,90,90,"0,00%",,,
Thousand Sons,CHAOS LAND RAIDER,220,220,"0,00%",,240,
Thousand Sons,CHAOS PREDATOR ANNIHILATOR,130,130,"0,00%",,140,
Thousand Sons,CHAOS PREDATOR DESTRUCTOR,130,130,"0,00%",,140,
Thousand Sons,CHAOS RHINO,90,90,"0,00%",,,
Thousand Sons,CHAOS SPAWN,65,65,"0,00%",,,
Thousand Sons,CHAOS VINDICATOR,185,185,"0,00%",,195,
Thousand Sons,DAEMON PRINCE OF TZEENTCH,180,170,"-5,56%",,,
Thousand Sons,DAEMON PRINCE OF TZEENTCH WITH WINGS,170,170,"0,00%",180,,
Thousand Sons,DEFILER,250,290,"16,00%",320,,
Thousand Sons,EXALTED SORCERER,80,90,"12,50%",,,
Thousand Sons,EXALTED SORCERER ON DISC OF TZEENTCH,100,90,"-10,00%",,,
Thousand Sons,FORGEFIEND,130,135,"3,85%",,145,
Thousand Sons,HELBRUTE,110,110,"0,00%",,,
Thousand Sons,HELDRAKE,215,175,"-18,60%",,,
Thousand Sons,INFERNAL MASTER,95,95,"0,00%",,,
Thousand Sons,KAIROS FATEWEAVER,295,295,"0,00%",,,
Thousand Sons,LORD OF CHANGE,285,300,"5,26%",,315,
Thousand Sons,MAGNUS THE RED,435,455,"4,60%",,,
Thousand Sons,MAULERFIEND,120,120,"0,00%",,130,
Thousand Sons,MUTALITH VORTEX BEAST,170,170,"0,00%",,190,
Thousand Sons,PINK HORRORS,115,115,"0,00%",,,
Thousand Sons,SORCERER,80,85,"6,25%",,,
Thousand Sons,SORCERER IN TERMINATOR ARMOUR,85,95,"11,76%",,105,
Thousand Sons,TZAANGOR SHAMAN,60,60,"0,00%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Thousand Sons 10 Rubric Marines 190 200
3 Thousand Sons 10 Scarab Occult Terminators 370 385
4 Thousand Sons 10 Tzaangors 70 75 7,14%
5 Thousand Sons 2 Sekhetar Robots 80 80 0,00%
6 Thousand Sons 20 Tzaangors 145
7 Thousand Sons 3 Flamers 65 65 0,00%
8 Thousand Sons 3 Screamers 80 80 0,00%
9 Thousand Sons 3 Tzaangor Enlightened 45 45 0,00%
10 Thousand Sons 3 Tzaangor Enlightened With Fatecaster Greatbows 55
11 Thousand Sons 4 Sekhetar Robots 160
12 Thousand Sons 5 Rubric Marines 100 100 0,00% 110
13 Thousand Sons 5 Scarab Occult Terminators 180 180 0,00% 195
14 Thousand Sons 6 Flamers 130
15 Thousand Sons 6 Screamers 160
16 Thousand Sons 6 Tzaangor Enlightened 90
17 Thousand Sons 6 Tzaangor Enlightened With Fatecaster Greatbows 110
18 Thousand Sons AHRIMAN 100 100 0,00%
19 Thousand Sons BLUE HORRORS 90 90 0,00%
20 Thousand Sons CHAOS LAND RAIDER 220 220 0,00% 240
21 Thousand Sons CHAOS PREDATOR ANNIHILATOR 130 130 0,00% 140
22 Thousand Sons CHAOS PREDATOR DESTRUCTOR 130 130 0,00% 140
23 Thousand Sons CHAOS RHINO 90 90 0,00%
24 Thousand Sons CHAOS SPAWN 65 65 0,00%
25 Thousand Sons CHAOS VINDICATOR 185 185 0,00% 195
26 Thousand Sons DAEMON PRINCE OF TZEENTCH 180 170 -5,56%
27 Thousand Sons DAEMON PRINCE OF TZEENTCH WITH WINGS 170 170 0,00% 180
28 Thousand Sons DEFILER 250 290 16,00% 320
29 Thousand Sons EXALTED SORCERER 80 90 12,50%
30 Thousand Sons EXALTED SORCERER ON DISC OF TZEENTCH 100 90 -10,00%
31 Thousand Sons FORGEFIEND 130 135 3,85% 145
32 Thousand Sons HELBRUTE 110 110 0,00%
33 Thousand Sons HELDRAKE 215 175 -18,60%
34 Thousand Sons INFERNAL MASTER 95 95 0,00%
35 Thousand Sons KAIROS FATEWEAVER 295 295 0,00%
36 Thousand Sons LORD OF CHANGE 285 300 5,26% 315
37 Thousand Sons MAGNUS THE RED 435 455 4,60%
38 Thousand Sons MAULERFIEND 120 120 0,00% 130
39 Thousand Sons MUTALITH VORTEX BEAST 170 170 0,00% 190
40 Thousand Sons PINK HORRORS 115 115 0,00%
41 Thousand Sons SORCERER 80 85 6,25%
42 Thousand Sons SORCERER IN TERMINATOR ARMOUR 85 95 11,76% 105
43 Thousand Sons TZAANGOR SHAMAN 60 60 0,00%

5
csv/titan-legions.csv Normal file
View File

@@ -0,0 +1,5 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Titan Legions,REAVER TITAN,,2200,,,,
Titan Legions,WARBRINGER NEMESIS TITAN,,2600,,,,
Titan Legions,WARHOUND TITAN,,1100,,,,
Titan Legions,WARLORD TITAN,,3500,,,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Titan Legions REAVER TITAN 2200
3 Titan Legions WARBRINGER NEMESIS TITAN 2600
4 Titan Legions WARHOUND TITAN 1100
5 Titan Legions WARLORD TITAN 3500

75
csv/tyranids.csv Normal file
View File

@@ -0,0 +1,75 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
Tyranids,1 Biovores,50,60,"20,00%",,,
Tyranids,1 Carnifexes,90,90,"0,00%",,,
Tyranids,1 Mucolid Spores,30,30,"0,00%",,,
Tyranids,1 Pyrovores,40,45,"12,50%",,55,
Tyranids,1 Ripper Swarms,25,30,"20,00%",,,
Tyranids,10 Barbgaunts,,110,,,,
Tyranids,10 Gargoyles,85,80,"-5,88%",,,
Tyranids,10 Genestealers,,140,,,150,
Tyranids,10 Hormagaunts,65,70,"7,69%",,,
Tyranids,10 Termagants,60,60,"0,00%",,,
Tyranids,11 Neurogaunts,45,45,"0,00%",,,
Tyranids,2 Biovores,,100,,,,
Tyranids,2 Carnifexes,,180,,,,
Tyranids,2 Mucolid Spores,,60,,,,
Tyranids,2 Pyrovores,,65,,,75,
Tyranids,2 Ripper Swarms,,40,,,,
Tyranids,20 Gargoyles,,155,,,,
Tyranids,20 Hormagaunts,,120,,,,
Tyranids,20 Termagants,,110,,,,
Tyranids,22 Neurogaunts,,90,,,,
Tyranids,3 Biovores,,140,,,,
Tyranids,3 Hive Guard,90,80,"-11,11%",,90,
Tyranids,3 Pyrovores,,95,,,105,
Tyranids,3 Ripper Swarms,,50,,,,
Tyranids,3 Spore Mines,55,55,"0,00%",,,
Tyranids,3 Tyranid Warriors With Melee Bio-Weapons,75,75,"0,00%",,,
Tyranids,3 Tyranid Warriors With Ranged Bio-Weapons,65,60,"-7,69%",,,
Tyranids,3 Tyrant Guard,80,80,"0,00%",,,
Tyranids,3 Venomthropes,70,55,"-21,43%",,,
Tyranids,3 Von RyanS Leapers,70,60,"-14,29%",,,
Tyranids,3 Zoanthropes,100,100,"0,00%",,,
Tyranids,5 Barbgaunts,55,55,"0,00%",,,
Tyranids,5 Genestealers,75,75,"0,00%",,85,
Tyranids,6 Hive Guard,,160,,,170,
Tyranids,6 Spore Mines,,110,,,,
Tyranids,6 Tyranid Warriors With Melee Bio-Weapons,,150,,,,
Tyranids,6 Tyranid Warriors With Ranged Bio-Weapons,,120,,,,
Tyranids,6 Tyrant Guard,,160,,,,
Tyranids,6 Venomthropes,,110,,,,
Tyranids,6 Von RyanS Leapers,,120,,,,
Tyranids,6 Zoanthropes,,200,,,,
Tyranids,BROODLORD,80,80,"0,00%",,,
Tyranids,DEATHLEAPER,80,80,"0,00%",,,
Tyranids,EXOCRINE,140,140,"0,00%",,150,
Tyranids,HARPY,215,185,"-13,95%",,,
Tyranids,HARRIDAN,610,610,"0,00%",660,,
Tyranids,HARUSPEX,125,125,"0,00%",,135,
Tyranids,HIEROPHANT,810,810,"0,00%",910,,
Tyranids,HIVE CRONE,200,170,"-15,00%",,,
Tyranids,HIVE TYRANT,195,195,"0,00%",,,
Tyranids,HYPERADAPTED RAVENERS,165,165,"0,00%",,175,
Tyranids,LICTOR,60,60,"0,00%",,,
Tyranids,MALECEPTOR,170,180,"5,88%",,190,
Tyranids,MAWLOC,135,135,"0,00%",,,
Tyranids,NEUROLICTOR,70,80,"14,29%",,90,
Tyranids,NEUROTYRANT,105,115,"9,52%",,,
Tyranids,NORN ASSIMILATOR,275,250,"-9,09%",270,,
Tyranids,NORN EMISSARY,260,250,"-3,85%",270,,
Tyranids,OLD ONE EYE,150,140,"-6,67%",,,
Tyranids,PARASITE OF MORTREX,80,70,"-12,50%",,,
Tyranids,PSYCHOPHAGE,110,110,"0,00%",,,
Tyranids,RAVENERS,125,125,"0,00%",,135,
Tyranids,SCREAMER-KILLER,125,125,"0,00%",,135,
Tyranids,SPOROCYST,145,145,"0,00%",,,
Tyranids,TERVIGON,160,160,"0,00%",,,
Tyranids,THE RED TERROR,130,130,"0,00%",,,
Tyranids,THE SWARMLORD,220,210,"-4,55%",,,
Tyranids,TOXICRENE,150,135,"-10,00%",,,
Tyranids,TRYGON,140,140,"0,00%",,,
Tyranids,TYRANID PRIME WITH LASH WHIP,65,75,"15,38%",,,
Tyranids,TYRANNOCYTE,105,90,"-14,29%",,,
Tyranids,TYRANNOFEX,200,180,"-10,00%",,190,
Tyranids,WINGED HIVE TYRANT,170,185,"8,82%",,,
Tyranids,WINGED TYRANID PRIME,65,65,"0,00%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 Tyranids 1 Biovores 50 60 20,00%
3 Tyranids 1 Carnifexes 90 90 0,00%
4 Tyranids 1 Mucolid Spores 30 30 0,00%
5 Tyranids 1 Pyrovores 40 45 12,50% 55
6 Tyranids 1 Ripper Swarms 25 30 20,00%
7 Tyranids 10 Barbgaunts 110
8 Tyranids 10 Gargoyles 85 80 -5,88%
9 Tyranids 10 Genestealers 140 150
10 Tyranids 10 Hormagaunts 65 70 7,69%
11 Tyranids 10 Termagants 60 60 0,00%
12 Tyranids 11 Neurogaunts 45 45 0,00%
13 Tyranids 2 Biovores 100
14 Tyranids 2 Carnifexes 180
15 Tyranids 2 Mucolid Spores 60
16 Tyranids 2 Pyrovores 65 75
17 Tyranids 2 Ripper Swarms 40
18 Tyranids 20 Gargoyles 155
19 Tyranids 20 Hormagaunts 120
20 Tyranids 20 Termagants 110
21 Tyranids 22 Neurogaunts 90
22 Tyranids 3 Biovores 140
23 Tyranids 3 Hive Guard 90 80 -11,11% 90
24 Tyranids 3 Pyrovores 95 105
25 Tyranids 3 Ripper Swarms 50
26 Tyranids 3 Spore Mines 55 55 0,00%
27 Tyranids 3 Tyranid Warriors With Melee Bio-Weapons 75 75 0,00%
28 Tyranids 3 Tyranid Warriors With Ranged Bio-Weapons 65 60 -7,69%
29 Tyranids 3 Tyrant Guard 80 80 0,00%
30 Tyranids 3 Venomthropes 70 55 -21,43%
31 Tyranids 3 Von Ryan’S Leapers 70 60 -14,29%
32 Tyranids 3 Zoanthropes 100 100 0,00%
33 Tyranids 5 Barbgaunts 55 55 0,00%
34 Tyranids 5 Genestealers 75 75 0,00% 85
35 Tyranids 6 Hive Guard 160 170
36 Tyranids 6 Spore Mines 110
37 Tyranids 6 Tyranid Warriors With Melee Bio-Weapons 150
38 Tyranids 6 Tyranid Warriors With Ranged Bio-Weapons 120
39 Tyranids 6 Tyrant Guard 160
40 Tyranids 6 Venomthropes 110
41 Tyranids 6 Von Ryan’S Leapers 120
42 Tyranids 6 Zoanthropes 200
43 Tyranids BROODLORD 80 80 0,00%
44 Tyranids DEATHLEAPER 80 80 0,00%
45 Tyranids EXOCRINE 140 140 0,00% 150
46 Tyranids HARPY 215 185 -13,95%
47 Tyranids HARRIDAN 610 610 0,00% 660
48 Tyranids HARUSPEX 125 125 0,00% 135
49 Tyranids HIEROPHANT 810 810 0,00% 910
50 Tyranids HIVE CRONE 200 170 -15,00%
51 Tyranids HIVE TYRANT 195 195 0,00%
52 Tyranids HYPERADAPTED RAVENERS 165 165 0,00% 175
53 Tyranids LICTOR 60 60 0,00%
54 Tyranids MALECEPTOR 170 180 5,88% 190
55 Tyranids MAWLOC 135 135 0,00%
56 Tyranids NEUROLICTOR 70 80 14,29% 90
57 Tyranids NEUROTYRANT 105 115 9,52%
58 Tyranids NORN ASSIMILATOR 275 250 -9,09% 270
59 Tyranids NORN EMISSARY 260 250 -3,85% 270
60 Tyranids OLD ONE EYE 150 140 -6,67%
61 Tyranids PARASITE OF MORTREX 80 70 -12,50%
62 Tyranids PSYCHOPHAGE 110 110 0,00%
63 Tyranids RAVENERS 125 125 0,00% 135
64 Tyranids SCREAMER-KILLER 125 125 0,00% 135
65 Tyranids SPOROCYST 145 145 0,00%
66 Tyranids TERVIGON 160 160 0,00%
67 Tyranids THE RED TERROR 130 130 0,00%
68 Tyranids THE SWARMLORD 220 210 -4,55%
69 Tyranids TOXICRENE 150 135 -10,00%
70 Tyranids TRYGON 140 140 0,00%
71 Tyranids TYRANID PRIME WITH LASH WHIP 65 75 15,38%
72 Tyranids TYRANNOCYTE 105 90 -14,29%
73 Tyranids TYRANNOFEX 200 180 -10,00% 190
74 Tyranids WINGED HIVE TYRANT 170 185 8,82%
75 Tyranids WINGED TYRANID PRIME 65 65 0,00%

38
csv/world-eaters.csv Normal file
View File

@@ -0,0 +1,38 @@
Faction,Unidad,Coste original,Coste nuevo,% de cambio,2 o más unidades,3 o más unidades,4 o más unidades
World Eaters,10 Chaos Terminators,,350,,,360,
World Eaters,10 Flesh Hounds,,150,,,,
World Eaters,10 Jakhals,65,65,"0,00%",,,
World Eaters,10 Khorne Berzerkers,180,180,"0,00%",,,
World Eaters,20 Jakhals,,130,,,,
World Eaters,20 Khorne Berzerkers,,345,,,,
World Eaters,3 Bloodcrushers,110,95,"-13,64%",,105,
World Eaters,3 Eightbound,135,135,"0,00%",,145,
World Eaters,3 Exalted Eightbound,140,140,"0,00%",,150,
World Eaters,5 Chaos Terminators,175,175,"0,00%",,185,
World Eaters,5 Flesh Hounds,75,75,"0,00%",,,
World Eaters,6 Bloodcrushers,,180,,,190,
World Eaters,6 Eightbound,,270,,,280,
World Eaters,6 Exalted Eightbound,,280,,,290,
World Eaters,ANGRON,340,350,"2,94%",,,
World Eaters,BLOODLETTERS,90,90,"0,00%",,,
World Eaters,BLOODTHIRSTER,305,320,"4,92%",,335,
World Eaters,CHAOS LAND RAIDER,220,220,"0,00%",,240,
World Eaters,CHAOS PREDATOR ANNIHILATOR,145,135,"-6,90%",,145,
World Eaters,CHAOS PREDATOR DESTRUCTOR,145,135,"-6,90%",,145,
World Eaters,CHAOS RHINO,85,85,"0,00%",,,
World Eaters,CHAOS SPAWN,90,95,"5,56%",,,
World Eaters,DAEMON PRINCE OF KHORNE,200,200,"0,00%",,,
World Eaters,DAEMON PRINCE OF KHORNE WITH WINGS,180,170,"-5,56%",,,
World Eaters,DEFILER,250,270,"8,00%",300,,
World Eaters,FORGEFIEND,165,160,"-3,03%",,175,
World Eaters,GOREMONGERS,75,75,"0,00%",,,
World Eaters,HELBRUTE,120,120,"0,00%",,,
World Eaters,HELDRAKE,200,175,"-12,50%",,,
World Eaters,KHORNE LORD OF SKULLS,505,505,"0,00%",535,,
World Eaters,KHÂRN THE BETRAYER,100,115,"15,00%",,,
World Eaters,LORD INVOCATUS,110,110,"0,00%",,,
World Eaters,LORD ON JUGGERNAUT,105,105,"0,00%",,,
World Eaters,MASTER OF EXECUTIONS,60,60,"0,00%",,,
World Eaters,MAULERFIEND,150,145,"-3,33%",,155,
World Eaters,SKARBRAND,305,315,"3,28%",,,
World Eaters,SLAUGHTERBOUND,100,100,"0,00%",,,
1 Faction Unidad Coste original Coste nuevo % de cambio 2 o más unidades 3 o más unidades 4 o más unidades
2 World Eaters 10 Chaos Terminators 350 360
3 World Eaters 10 Flesh Hounds 150
4 World Eaters 10 Jakhals 65 65 0,00%
5 World Eaters 10 Khorne Berzerkers 180 180 0,00%
6 World Eaters 20 Jakhals 130
7 World Eaters 20 Khorne Berzerkers 345
8 World Eaters 3 Bloodcrushers 110 95 -13,64% 105
9 World Eaters 3 Eightbound 135 135 0,00% 145
10 World Eaters 3 Exalted Eightbound 140 140 0,00% 150
11 World Eaters 5 Chaos Terminators 175 175 0,00% 185
12 World Eaters 5 Flesh Hounds 75 75 0,00%
13 World Eaters 6 Bloodcrushers 180 190
14 World Eaters 6 Eightbound 270 280
15 World Eaters 6 Exalted Eightbound 280 290
16 World Eaters ANGRON 340 350 2,94%
17 World Eaters BLOODLETTERS 90 90 0,00%
18 World Eaters BLOODTHIRSTER 305 320 4,92% 335
19 World Eaters CHAOS LAND RAIDER 220 220 0,00% 240
20 World Eaters CHAOS PREDATOR ANNIHILATOR 145 135 -6,90% 145
21 World Eaters CHAOS PREDATOR DESTRUCTOR 145 135 -6,90% 145
22 World Eaters CHAOS RHINO 85 85 0,00%
23 World Eaters CHAOS SPAWN 90 95 5,56%
24 World Eaters DAEMON PRINCE OF KHORNE 200 200 0,00%
25 World Eaters DAEMON PRINCE OF KHORNE WITH WINGS 180 170 -5,56%
26 World Eaters DEFILER 250 270 8,00% 300
27 World Eaters FORGEFIEND 165 160 -3,03% 175
28 World Eaters GOREMONGERS 75 75 0,00%
29 World Eaters HELBRUTE 120 120 0,00%
30 World Eaters HELDRAKE 200 175 -12,50%
31 World Eaters KHORNE LORD OF SKULLS 505 505 0,00% 535
32 World Eaters KHÂRN THE BETRAYER 100 115 15,00%
33 World Eaters LORD INVOCATUS 110 110 0,00%
34 World Eaters LORD ON JUGGERNAUT 105 105 0,00%
35 World Eaters MASTER OF EXECUTIONS 60 60 0,00%
36 World Eaters MAULERFIEND 150 145 -3,33% 155
37 World Eaters SKARBRAND 305 315 3,28%
38 World Eaters SLAUGHTERBOUND 100 100 0,00%

38
docker-compose.yml Normal file
View File

@@ -0,0 +1,38 @@
# WH40K Points Comparator — Docker Compose
# Multi-stage Dockerfile builds the React app and serves via nginx.
#
# Usage:
# 1. Edit HOST below to match your domain
# 2. Edit CERT_RESOLVER to match your Traefik cert resolver
# 3. docker compose up -d --build
#
# If you don't use Traefik, remove the labels and expose a port directly:
# ports:
# - "8080:80"
services:
wh40k-site:
build:
context: .
dockerfile: Dockerfile
image: wh40k-site:latest
container_name: wh40k-site
restart: unless-stopped
# Direct port access (uncomment if not using Traefik):
# ports:
# - "8080:80"
networks:
- hermes-net
labels:
- "traefik.enable=true"
- "traefik.docker.network=hermes-net"
- "traefik.http.routers.wh40k-site.entrypoints=websecure"
- "traefik.http.routers.wh40k-site.rule=Host(`wh40k.damascusfront.net`)"
- "traefik.http.routers.wh40k-site.tls=true"
- "traefik.http.routers.wh40k-site.tls.certresolver=cloudflare"
- "traefik.http.services.wh40k-site.loadbalancer.server.port=80"
networks:
hermes-net:
external: true
name: hermes-net

58
index.html Normal file
View File

@@ -0,0 +1,58 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>WH40K 10th Edition — Faction Points Compendium</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
max-width: 900px; margin: 40px auto; padding: 0 20px;
background: #0d1117; color: #e6edf3; }
h1 { color: #c9d1d9; border-bottom: 1px solid #30363d; padding-bottom: 10px; }
h2 { color: #8b949e; font-size: 1.1em; margin-top: 30px; }
.card { background: #161b22; border: 1px solid #30363d; border-radius: 8px;
padding: 20px; margin: 15px 0; }
.card h3 { margin-top: 0; color: #c9d1d9; }
a.btn { display: inline-block; background: #238636; color: white; padding: 10px 18px;
border-radius: 6px; text-decoration: none; font-weight: 600; margin: 4px 8px 4px 0; }
a.btn.alt { background: #1f6feb; }
a.btn:hover { opacity: 0.9; }
code { background: #0d1117; padding: 2px 6px; border-radius: 3px; color: #79c0ff; }
.meta { color: #8b949e; font-size: 0.9em; }
.legend { display: flex; gap: 20px; margin: 10px 0; }
.legend span { padding: 4px 12px; border-radius: 4px; font-size: 0.9em; }
.pos { background: #c6efce; color: #006100; }
.neg { background: #ffc7ce; color: #9c0006; }
.neutral { background: #d9e1f2; color: #1f4e78; }
</style>
</head>
<body>
<h1>WH40K 10th Edition — Faction Points Compendium</h1>
<p class="meta">30 factions · 1,820 unit rows · mfm.warhammer-community.com + Full_armies_10th.pdf</p>
<h2>📊 Excel workbook (recommended)</h2>
<div class="card">
<h3>wh40k_factions_all.xlsx</h3>
<p>One workbook, 30 sheets (one per faction) plus a Summary sheet. The <code>% change</code> column is color-coded:</p>
<div class="legend">
<span class="pos">Positive change (green)</span>
<span class="neg">Negative change (red)</span>
<span class="neutral">No change (neutral)</span>
</div>
<a class="btn" href="/wh40k_factions_all.xlsx" download>⬇ Download xlsx (108 KB)</a>
</div>
<h2>📄 CSV downloads</h2>
<div class="card">
<h3>Per-faction + combined</h3>
<a class="btn alt" href="/csv/_all_factions.csv" download>⬇ All factions (single file, 91 KB)</a>
<a class="btn alt" href="/csv/" >📁 Browse 30 per-faction files</a>
</div>
<h2>📚 Source data</h2>
<div class="card meta">
<p><strong>Live MFM (Munitorum Field Manual) prices:</strong> scraped from <code>mfm.warhammer-community.com</code> on 2026-06-17.</p>
<p><strong>Original (pre-MFM) points:</strong> parsed from <code>Full_armies_10th.pdf</code> via pymupdf.</p>
<p><strong>Known gaps:</strong> Titan Legions and Chaos Titan Legions have no live MFM pages. Chapter supplements (BT, BA, DA, SW, DW) inherit from the Space Marines codex.</p>
</div>
</body>
</html>

286
join_and_export.py Normal file
View File

@@ -0,0 +1,286 @@
"""Join PDF (original) vs live MFM (new) and emit per-faction CSVs + combined.
Output schema (matches the user's reference image):
Faction, Unidad, Coste original, Coste nuevo, % de cambio,
2 o más unidades, 3 o más unidades, 4 o más unidades
Key:
- For each unit present in the live site (source of truth for "new"),
find the matching cost row in the PDF by name + size.
- The "base" cost row is the cheapest / smallest tier (the "YOUR UNIT
COSTS" tier from the PDF, or the "YOUR 1ST TO 2ND UNITS COST" /
"YOUR 1ST UNIT COSTS" tier from the live site).
- The "2 o más" / "3 o más" / "4 o más" columns are populated from any
other cost rows for the same unit (looking up by unit size).
- If a unit is in the live site but not the PDF, original is blank.
- If a unit is in the PDF but not the live site, it's omitted (the
PDF is a snapshot — the live site is current).
"""
import csv
import json
import re
from pathlib import Path
ROOT = Path("/root/wh40k-factions")
PDF = ROOT / "pdf_data.json"
LIVE = ROOT / "live_data.json"
CSV_DIR = ROOT / "csv"
CSV_DIR.mkdir(parents=True, exist_ok=True)
# Chapter supplements inherit unit data from the parent faction's PDF section.
# When the live site lists a generic SM unit (Aggressor Squad, Intercessor Squad,
# etc.) and the chapter's PDF supplement doesn't include it, fall back to the
# parent Space Marines PDF entries for "original".
INHERIT_FROM_PARENT = {
"black-templars": "space-marines",
"blood-angels": "space-marines",
"dark-angels": "space-marines",
"space-wolves": "space-marines",
"deathwatch": "space-marines",
"emperors-children":"chaos-space-marines",
"thousand-sons": "chaos-space-marines",
"world-eaters": "chaos-space-marines",
"death-guard": "chaos-space-marines",
}
# Spanish column headers (matching the image)
HEADERS = [
"Faction",
"Unidad",
"Coste original",
"Coste nuevo",
"% de cambio",
"2 o más unidades",
"3 o más unidades",
"4 o más unidades",
]
def normalize_unit_name(s: str) -> str:
"""Normalize a unit name for cross-source matching.
The PDF has 'Title Case' (e.g. 'Broadside Battlesuits'), the live site
has 'UPPERCASE' (e.g. 'BROADSIDE BATTLESUITS'). Normalize:
- uppercase
- strip leading/trailing whitespace
- collapse multiple spaces
- remove apostrophes and smart quotes
"""
s = s.upper()
s = s.replace("\u2019", "").replace("'", "")
s = re.sub(r"\s+", " ", s).strip()
return s
def normalize_size(s: str) -> str:
"""Normalize a size string like '1 model', '1 models', '10 models'."""
s = s.lower().strip()
s = s.replace("\u2019", "").replace("'", "")
# '1 model' and '1 models' both become '1 model'
if "model" in s:
m = re.match(r"(\d+)\s+models?", s)
if m:
return f"{m.group(1)} model"
return s
# Tiers considered "bulk" cost (more units). Live site tier names.
# (These are looked up by exact label inside the join logic.)
BULK_TIERS = { # noqa: F841 — kept for documentation
"YOUR 3RD + UNIT COSTS",
"YOUR 2ND + UNIT COSTS",
}
def pct_change(orig, new):
if not orig or orig == 0:
return ""
delta = (new - orig) / orig * 100
# Match the image's format: 2 decimals, comma decimal separator
s = f"{abs(delta):.2f}"
return f"{'-' if delta < 0 else ''}{s}".replace(".", ",") + "%"
def fmt_pts(p):
if p is None:
return ""
return str(p)
def _size_n(size_str: str) -> int:
m = re.match(r"(\d+)", size_str)
return int(m.group(1)) if m else 1
def find_tier_cost_at_size(model_rows, tier_label: str, size_n: int):
"""Find the row matching a tier label and a specific model count.
Live site tier labels we care about for the bulk columns:
'YOUR 2ND + UNIT COSTS' -> 2 o más
'YOUR 3RD + UNIT COSTS' -> 3 o más
'YOUR 4TH + UNIT COSTS' -> 4 o más (if it ever exists)
"""
for r in model_rows:
tier = (r.get("tier") or "").upper().strip()
if tier == tier_label:
n = _size_n(r["size"])
if n == size_n:
return r["pts"]
return None
def join_faction(faction_slug: str, pdf: dict, live: dict, all_pdf: dict) -> list[dict]:
"""Build the comparison rows for one faction."""
pdf_units = pdf.get("units", {})
live_units = live.get("units", {})
# Build PDF lookup: (unit_normalized, size_normalized) -> row
pdf_idx = {}
for unit, rows in pdf_units.items():
nu = normalize_unit_name(unit)
for r in rows:
if "model" not in r["size"].lower():
continue
ns = normalize_size(r["size"])
pdf_idx[(nu, ns)] = r
# Inherit parent-faction PDF entries for chapter supplements.
# If a unit isn't in the chapter's PDF, look it up in the parent.
parent = INHERIT_FROM_PARENT.get(faction_slug)
if parent and parent in all_pdf:
for unit, rows in all_pdf[parent].get("units", {}).items():
nu = normalize_unit_name(unit)
for r in rows:
if "model" not in r["size"].lower():
continue
ns = normalize_size(r["size"])
# don't overwrite chapter-specific entries
if (nu, ns) not in pdf_idx:
pdf_idx[(nu, ns)] = r
rows = []
# Walk live units (source of truth for "new")
for unit, unit_rows in live_units.items():
nu = normalize_unit_name(unit)
# skip wargear-only units
model_rows = [r for r in unit_rows if "model" in r["size"].lower()]
if not model_rows:
continue
# Pick the base tier: prefer YOUR UNIT COSTS, else first tier.
base_tier = None
for r in model_rows:
tier = (r.get("tier") or "").upper().strip()
if tier == "YOUR UNIT COSTS":
base_tier = tier
break
if base_tier is None:
base_tier = (model_rows[0].get("tier") or "").upper().strip()
# Find all distinct sizes in the base tier -> one row per size
base_size_rows = [r for r in model_rows
if (r.get("tier") or "").upper().strip() == base_tier]
if not base_size_rows:
# fallback: any rows
base_size_rows = model_rows
# Distinct sizes in display order (smallest first)
seen_sizes = set()
base_sizes = []
for r in base_size_rows:
ns_r = normalize_size(r["size"])
n_r = _size_n(r["size"])
key = (n_r, ns_r)
if key in seen_sizes:
continue
seen_sizes.add(key)
base_sizes.append((ns_r, n_r))
if not base_sizes:
continue
for ns, base_n in base_sizes:
# Find the live row matching this size+base tier
live_row = next((r for r in base_size_rows
if normalize_size(r["size"]) == ns), None)
if not live_row:
continue
new_pts = live_row["pts"]
# Find original in PDF
pdf_row = pdf_idx.get((nu, ns))
orig_pts = pdf_row["pts"] if pdf_row else None
# Bulk columns: cost of THIS size in 2ND+ / 3RD+ / 4TH+ tiers
bulk_2 = find_tier_cost_at_size(model_rows, "YOUR 2ND + UNIT COSTS", base_n)
bulk_3 = find_tier_cost_at_size(model_rows, "YOUR 3RD + UNIT COSTS", base_n)
bulk_4 = find_tier_cost_at_size(model_rows, "YOUR 4TH + UNIT COSTS", base_n)
# Display the unit name with the size prefix if there's only one
# size in the base tier, use the unit name alone; if multiple
# sizes, prefix with the count.
if len(base_sizes) == 1:
display_unit = unit
else:
display_unit = f"{base_n} {unit.title()}"
rows.append({
"Faction": live.get("name", pdf.get("name", faction_slug)),
"Unidad": display_unit,
"Coste original": fmt_pts(orig_pts),
"Coste nuevo": fmt_pts(new_pts),
"% de cambio": pct_change(orig_pts, new_pts) if orig_pts else "",
"2 o más unidades": fmt_pts(bulk_2),
"3 o más unidades": fmt_pts(bulk_3),
"4 o más unidades": fmt_pts(bulk_4),
})
# Sort: by Faction then Unidad
rows.sort(key=lambda r: (r["Faction"], r["Unidad"]))
return rows
def main():
pdf = json.loads(PDF.read_text())
live = json.loads(LIVE.read_text())
if not pdf or not live:
print("ERR: pdf or live data missing — run parse_pdf.py and scrape_live.py first")
return 1
all_rows: list[dict] = []
summary = []
# Build per-faction CSVs
for slug in sorted(set(list(pdf.keys()) + list(live.keys()))):
p = pdf.get(slug, {"name": slug, "units": {}})
l = live.get(slug, {"name": slug, "units": {}})
rows = join_faction(slug, p, l, pdf)
if not rows:
print(f" {slug}: no rows (skipped)")
continue
out_path = CSV_DIR / f"{slug}.csv"
with out_path.open("w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=HEADERS)
w.writeheader()
w.writerows(rows)
all_rows.extend(rows)
n_with_orig = sum(1 for r in rows if r["Coste original"])
summary.append((slug, l.get("name", slug), len(rows), n_with_orig))
print(f" {slug:25s} {len(rows):3d} rows ({n_with_orig} with original)")
# Combined
combined = CSV_DIR / "_all_factions.csv"
with combined.open("w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=HEADERS)
w.writeheader()
w.writerows(all_rows)
print()
print(f"per-faction CSVs: {CSV_DIR}/*.csv ({len(summary)} files)")
print(f"combined CSV: {combined} ({len(all_rows)} rows total)")
return 0
if __name__ == "__main__":
raise SystemExit(main())

22916
live_data.json Normal file

File diff suppressed because it is too large Load Diff

214
parse_pdf.py Normal file
View File

@@ -0,0 +1,214 @@
"""Parse the MFM PDF into structured data.
Output: /root/wh40k-factions/pdf_data.json
Each page in the PDF has lines like:
UnitName
<N> models<N dots> <K> pts
<N> models<N dots> <K> pts
NextUnitName
...
A unit is followed by one or more "<n> models / <k> pts" rows. Sometimes
unit names are followed by additional cost lines, sometimes wargear tables
are inline. The trick: skip until we find a header, then a unit name is
the next non-empty, non-numeric line.
Some pages have "DETACHMENT ENHANCEMENTS" sections — skip them.
"""
import json
import re
from pathlib import Path
import pymupdf
PDF = "/root/.hermes/cache/documents/doc_5ee1da27de4e_Full_armies_10th.pdf"
OUT = Path("/root/wh40k-factions/pdf_data.json")
PAGE_TO_FACTION = {
2: "Adepta Sororitas",
3: "Adeptus Custodes",
4: "Adeptus Mechanicus",
5: "Adeptus Titanicus",
6: "Aeldari",
7: "Ynnari",
8: "Astra Militarum",
9: "Black Templars",
10: "Black Templars",
11: "Blood Angels",
12: "Chaos Daemons",
13: "Chaos Daemons",
14: "Chaos Knights",
15: "Chaos Space Marines",
16: "Chaos Space Marines",
17: "Dark Angels",
18: "Death Guard",
19: "Deathwatch",
20: "Drukhari",
21: "Emperor's Children",
22: "Genestealer Cults",
23: "Grey Knights",
24: "Imperial Agents",
25: "Imperial Agents",
26: "Imperial Knights",
27: "Leagues of Votann",
28: "Necrons",
29: "Necrons",
30: "Orks",
31: "Orks",
32: "Space Marines",
33: "Space Marines",
34: "Space Wolves",
35: "Tau Empire", # PDF uses straight apostrophe, normalize
36: "Thousand Sons",
37: "Tyranids",
38: "Tyranids",
39: "World Eaters",
}
# A line that says "<n> models" or "1 model" optionally followed by a pts value
COST_LINE_RE = re.compile(
r"^\s*(\d+)\s+models?\s*\x08*[.\s\x00-\x1f]*?(\d+)\s*pts?\s*$",
re.IGNORECASE,
)
# Just a "1 model" line (singular model count) — pts may be on a separate line
SIZE_ONLY_RE = re.compile(r"^\s*(\d+)\s+models?\s*\x08*\s*$", re.IGNORECASE)
PTS_LINE_RE = re.compile(r"^\s*(\d+)\s*pts?\s*$", re.IGNORECASE)
# Section markers that should be skipped
SKIP_SECTIONS = (
"WARGEAR OPTIONS", "DETACHMENT ENHANCEMENTS", "STRATAGEMS",
"ARMY RULE", "ENHANCEMENTS", "LITANIES", "PSYCHIC", "FACTION PACK",
)
# Unit names start with a capital letter and are not too long.
def is_unit_name(s: str) -> bool:
s = s.strip()
if not s:
return False
if len(s) > 80:
return False
if not s[0].isalpha():
return False
# reject lines that look numeric / header
if any(kw in s.upper() for kw in ("CODEX:", "INDEX:", "FACTION", "WARGEAR",
"DETACHMENT", "ENHANCEMENT", "STRATAGEM")):
return False
return True
def slug(name: str) -> str:
s = name.lower().replace("\u2019", "").replace("'", "")
s = re.sub(r"[^a-z0-9]+", "-", s).strip("-")
return s
def clean_line(s: str) -> str:
"""Strip control chars and trailing dot leaders (PDFium renders them as 0xFFFD)."""
s = s.replace("\x08", "").replace("\x0c", "")
# remove dot leaders (literal dots, 0xFFFD replacement chars, and spaces)
s = re.sub(r"[\.\uFFFD]{2,}", "", s)
s = re.sub(r"\s{2,}", " ", s) # collapse double spaces
return s.strip()
def parse_page(text: str) -> dict[str, list[dict]]:
units: dict[str, list[dict]] = {}
raw_lines = text.split("\n")
current_unit: str | None = None
pending_size: str | None = None
skip = False
for raw in raw_lines:
line = clean_line(raw)
if not line:
continue
upper = line.upper()
# Section break
if any(kw in upper for kw in SKIP_SECTIONS):
skip = True
current_unit = None
pending_size = None
continue
if skip:
# section ends when we see a new CODEX/INDEX/FACTION PACK header
if "CODEX" in upper or "INDEX" in upper or "FACTION PACK" in upper:
skip = False
else:
continue
if upper.startswith("CODEX:") or upper.startswith("INDEX:"):
continue
# Try cost line with both size and pts
m = COST_LINE_RE.match(line)
if m:
if current_unit:
units.setdefault(current_unit, []).append({
"size": f"{m.group(1)} models",
"pts": int(m.group(2)),
})
pending_size = None
# Do NOT reset current_unit — the next line may be another
# size variant for the same unit (e.g. "10 models ... 140 pts"
# right after "3 models ... 45 pts"). current_unit is cleared
# only when we encounter a new unit-name line.
continue
# Try size-only line
m = SIZE_ONLY_RE.match(line)
if m:
pending_size = f"{m.group(1)} models"
continue
# Try pts-only line (completes a pending size)
m = PTS_LINE_RE.match(line)
if m and pending_size and current_unit:
units.setdefault(current_unit, []).append({
"size": pending_size,
"pts": int(m.group(1)),
})
pending_size = None
# Do NOT reset current_unit — same reason as above.
continue
if m:
# pts without a known size — drop pending
pending_size = None
continue
# Otherwise this is a unit name (or noise)
if is_unit_name(line):
current_unit = line
pending_size = None
return units
def main():
doc = pymupdf.open(PDF)
out: dict[str, dict] = {}
for page_idx in range(doc.page_count):
page_num = page_idx + 1
if page_num not in PAGE_TO_FACTION:
continue
faction = PAGE_TO_FACTION[page_num]
s = slug(faction)
if s not in out:
out[s] = {"name": faction.replace("Tau", "T'au"), "pages": [], "units": {}}
out[s]["pages"].append(page_num)
page_units = parse_page(doc[page_idx].get_text())
for unit, costs in page_units.items():
existing = out[s]["units"].get(unit, [])
seen = {(c["size"], c["pts"]) for c in existing}
for c in costs:
if (c["size"], c["pts"]) not in seen:
existing.append(c)
seen.add((c["size"], c["pts"]))
out[s]["units"][unit] = existing
OUT.write_text(json.dumps(out, indent=2, ensure_ascii=False))
print(f"wrote {OUT}")
print(f"factions parsed: {len(out)}")
for s, data in out.items():
print(f" {s:25s}: {len(data['units']):3d} units ({', '.join(map(str, data['pages']))})")
if __name__ == "__main__":
main()

158
parse_pdf114.py Normal file
View File

@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""
Parse MFM_1.14.pdf into per-faction JSON files.
Output: /root/wh40k-factions/pdf114/<slug>.json
"""
import json
import re
import sys
import time
from pathlib import Path
import pymupdf
sys.path.insert(0, str(Path(__file__).parent))
from parse_pdf import parse_page
PDF = "/root/.hermes/cache/documents/doc_7f90137b6664_MFM_1.14.pdf"
OUT_DIR = Path("/root/wh40k-factions/pdf114")
OUT_DIR.mkdir(parents=True, exist_ok=True)
PAGE_TO_SLUG = {
1: None,
2: "adepta-sororitas",
3: "adeptus-custodes",
4: "adeptus-mechanicus",
5: "adeptus-titanicus",
6: "aeldari",
7: "aeldari", # forge world
8: "astra-militarum",
9: "astra-militarum", # forge world (Death Korps, Earthshaker, etc.)
10: "black-templars",
11: "blood-angels",
12: "chaos-daemons",
13: "chaos-knights",
14: "chaos-space-marines",
15: "dark-angels",
16: "death-guard",
17: "deathwatch",
18: "drukhari",
19: "genestealer-cults",
20: "grey-knights",
21: "imperial-agents",
22: "imperial-knights",
23: "leagues-of-votann",
24: "necrons",
25: "orks",
26: "orks", # detachment enhancements (skipped by parser)
27: "space-marines",
28: "space-marines", # forge world (Predator etc.)
29: "space-wolves",
30: "tau-empire",
31: "thousand-sons",
32: "tyranids",
33: "tyranids", # detachment enhancements (skipped)
34: "world-eaters",
}
NAME_BY_SLUG = {
"adepta-sororitas": "Adepta Sororitas",
"adeptus-custodes": "Adeptus Custodes",
"adeptus-mechanicus": "Adeptus Mechanicus",
"adeptus-titanicus": "Adeptus Titanicus",
"aeldari": "Aeldari",
"astra-militarum": "Astra Militarum",
"black-templars": "Black Templars",
"blood-angels": "Blood Angels",
"chaos-daemons": "Chaos Daemons",
"chaos-knights": "Chaos Knights",
"chaos-space-marines": "Chaos Space Marines",
"dark-angels": "Dark Angels",
"death-guard": "Death Guard",
"deathwatch": "Deathwatch",
"drukhari": "Drukhari",
"genestealer-cults": "Genestealer Cults",
"grey-knights": "Grey Knights",
"imperial-agents": "Imperial Agents",
"imperial-knights": "Imperial Knights",
"leagues-of-votann": "Leagues of Votann",
"necrons": "Necrons",
"orks": "Orks",
"space-marines": "Space Marines",
"space-wolves": "Space Wolves",
"tau-empire": "T'au Empire",
"thousand-sons": "Thousand Sons",
"tyranids": "Tyranids",
"world-eaters": "World Eaters",
}
def main():
doc = pymupdf.open(PDF)
print(f"PDF: {PDF} pages={doc.page_count}", flush=True)
accum = {}
for page_idx in range(doc.page_count):
page_num = page_idx + 1
s = PAGE_TO_SLUG.get(page_num)
if not s:
print(f" page {page_num:2d}: skipped (title/blank)", flush=True)
continue
if s not in accum:
accum[s] = {
"slug": s,
"name": NAME_BY_SLUG.get(s, s),
"source": Path(PDF).name,
"version": "1.14",
"extracted_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"pages": [],
"n_units": 0,
"n_rows": 0,
"units": {},
}
accum[s]["pages"].append(page_num)
page_units = parse_page(doc[page_idx].get_text())
n_added = 0
for unit, costs in page_units.items():
existing = accum[s]["units"].setdefault(unit, [])
seen = {(c["size"], c["pts"]) for c in existing}
for c in costs:
if (c["size"], c["pts"]) not in seen:
existing.append(c)
seen.add((c["size"], c["pts"]))
n_added += 1
print(f" page {page_num:2d} -> {s:<22} +{n_added:>3} entries", flush=True)
doc.close()
manifest = {"pdf": Path(PDF).name, "version": "1.14", "factions": []}
total_units = total_rows = 0
for s, data in sorted(accum.items()):
data["n_units"] = len(data["units"])
data["n_rows"] = sum(len(v) for v in data["units"].values())
out_path = OUT_DIR / f"{s}.json"
with open(out_path, "w") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
total_units += data["n_units"]
total_rows += data["n_rows"]
manifest["factions"].append({
"slug": s, "name": data["name"],
"pages": data["pages"],
"n_units": data["n_units"],
"n_rows": data["n_rows"],
"file": out_path.name,
})
print(f" {s:<22} {data['n_units']:>3} units / {data['n_rows']:>3} rows -> {out_path.name}", flush=True)
manifest["generated_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
manifest["n_factions"] = len(manifest["factions"])
manifest["total_units"] = total_units
manifest["total_rows"] = total_rows
with open(OUT_DIR / "_manifest.json", "w") as f:
json.dump(manifest, f, indent=2, ensure_ascii=False)
print(f"\nWrote {len(manifest['factions'])} faction files to {OUT_DIR}/")
print(f"Total: {total_units} units / {total_rows} size-rows")
if __name__ == "__main__":
main()

165
parse_pdf23.py Normal file
View File

@@ -0,0 +1,165 @@
#!/usr/bin/env python3
"""
Parse MFM_2.3_March_2025.pdf into per-faction JSON files.
Output: /root/wh40k-factions/pdf23/<slug>.json
"""
import json
import re
import sys
import time
from pathlib import Path
import pymupdf
sys.path.insert(0, str(Path(__file__).parent))
from parse_pdf import parse_page
PDF = "/root/.hermes/cache/documents/doc_cb9ee828b86b_MFM_2.3_March_2025.pdf"
OUT_DIR = Path("/root/wh40k-factions/pdf23")
OUT_DIR.mkdir(parents=True, exist_ok=True)
# Same page mapping as 3.2 (verified — same 38-page layout)
PAGE_TO_SLUG = {
1: None,
2: "adepta-sororitas",
3: "adeptus-custodes",
4: "adeptus-mechanicus",
5: "adeptus-titanicus",
6: "aeldari",
7: "ynnari",
8: "astra-militarum",
9: "astra-militarum", # detachment enhancements (skipped)
10: "black-templars",
11: "blood-angels",
12: "chaos-daemons",
13: "chaos-daemons", # detachment enhancements
14: "chaos-knights",
15: "chaos-space-marines",
16: "chaos-space-marines", # detachment enhancements
17: "dark-angels",
18: "death-guard",
19: "deathwatch",
20: "drukhari",
21: "emperors-children",
22: "genestealer-cults",
23: "grey-knights",
24: "imperial-agents",
25: "imperial-agents",
26: "imperial-knights",
27: "leagues-of-votann",
28: "necrons",
29: "orks",
30: "orks", # detachment enhancements
31: "space-marines",
32: "space-marines", # forge world
33: "space-wolves",
34: "tau-empire",
35: "thousand-sons",
36: "tyranids",
37: "tyranids", # detachment enhancements
38: "world-eaters",
}
NAME_BY_SLUG = {
"adepta-sororitas": "Adepta Sororitas",
"adeptus-custodes": "Adeptus Custodes",
"adeptus-mechanicus": "Adeptus Mechanicus",
"adeptus-titanicus": "Adeptus Titanicus",
"aeldari": "Aeldari",
"ynnari": "Ynnari",
"astra-militarum": "Astra Militarum",
"black-templars": "Black Templars",
"blood-angels": "Blood Angels",
"chaos-daemons": "Chaos Daemons",
"chaos-knights": "Chaos Knights",
"chaos-space-marines": "Chaos Space Marines",
"dark-angels": "Dark Angels",
"death-guard": "Death Guard",
"deathwatch": "Deathwatch",
"drukhari": "Drukhari",
"emperors-children": "Emperor's Children",
"genestealer-cults": "Genestealer Cults",
"grey-knights": "Grey Knights",
"imperial-agents": "Imperial Agents",
"imperial-knights": "Imperial Knights",
"leagues-of-votann": "Leagues of Votann",
"necrons": "Necrons",
"orks": "Orks",
"space-marines": "Space Marines",
"space-wolves": "Space Wolves",
"tau-empire": "T'au Empire",
"thousand-sons": "Thousand Sons",
"tyranids": "Tyranids",
"world-eaters": "World Eaters",
}
def main():
doc = pymupdf.open(PDF)
print(f"PDF: {PDF} pages={doc.page_count}", flush=True)
accum = {}
for page_idx in range(doc.page_count):
page_num = page_idx + 1
s = PAGE_TO_SLUG.get(page_num)
if not s:
print(f" page {page_num:2d}: skipped (title/blank)", flush=True)
continue
if s not in accum:
accum[s] = {
"slug": s,
"name": NAME_BY_SLUG.get(s, s),
"source": Path(PDF).name,
"version": "2.3",
"extracted_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"pages": [],
"n_units": 0,
"n_rows": 0,
"units": {},
}
accum[s]["pages"].append(page_num)
page_units = parse_page(doc[page_idx].get_text())
n_added = 0
for unit, costs in page_units.items():
existing = accum[s]["units"].setdefault(unit, [])
seen = {(c["size"], c["pts"]) for c in existing}
for c in costs:
if (c["size"], c["pts"]) not in seen:
existing.append(c)
seen.add((c["size"], c["pts"]))
n_added += 1
print(f" page {page_num:2d} -> {s:<22} +{n_added:>3} entries", flush=True)
doc.close()
manifest = {"pdf": Path(PDF).name, "version": "2.3", "factions": []}
total_units = total_rows = 0
for s, data in sorted(accum.items()):
data["n_units"] = len(data["units"])
data["n_rows"] = sum(len(v) for v in data["units"].values())
out_path = OUT_DIR / f"{s}.json"
with open(out_path, "w") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
total_units += data["n_units"]
total_rows += data["n_rows"]
manifest["factions"].append({
"slug": s, "name": data["name"],
"pages": data["pages"],
"n_units": data["n_units"],
"n_rows": data["n_rows"],
"file": out_path.name,
})
print(f" {s:<22} {data['n_units']:>3} units / {data['n_rows']:>3} rows -> {out_path.name}", flush=True)
manifest["generated_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
manifest["n_factions"] = len(manifest["factions"])
manifest["total_units"] = total_units
manifest["total_rows"] = total_rows
with open(OUT_DIR / "_manifest.json", "w") as f:
json.dump(manifest, f, indent=2, ensure_ascii=False)
print(f"\nWrote {len(manifest['factions'])} faction files to {OUT_DIR}/")
print(f"Total: {total_units} units / {total_rows} size-rows")
if __name__ == "__main__":
main()

171
parse_pdf32.py Normal file
View File

@@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""
Parse field_manual_3.2.pdf into per-faction JSON files,
reusing the fixed parser from parse_pdf.py.
Output: /root/wh40k-factions/pdf32/<slug>.json
"""
import json
import re
import sys
import time
from pathlib import Path
import pymupdf
sys.path.insert(0, str(Path(__file__).parent))
from parse_pdf import parse_page, slug, clean_line
PDF = "/root/.hermes/cache/documents/doc_b0e211b5e744_field_manual_3.2.pdf"
OUT_DIR = Path("/root/wh40k-factions/pdf32")
OUT_DIR.mkdir(parents=True, exist_ok=True)
# Same page mapping as 4.3 (verified — same order, same factions)
PAGE_TO_SLUG = {
1: None,
2: "adepta-sororitas",
3: "adeptus-custodes",
4: "adeptus-mechanicus",
5: "adeptus-titanicus",
6: "aeldari",
7: "ynnari",
8: "astra-militarum",
9: "astra-militarum", # detachment enhancements (skipped by parser)
10: "black-templars",
11: "blood-angels",
12: "chaos-daemons",
13: "chaos-daemons", # detachment enhancements
14: "chaos-knights",
15: "chaos-space-marines",
16: "chaos-space-marines", # detachment enhancements
17: "dark-angels",
18: "death-guard",
19: "deathwatch",
20: "drukhari",
21: "emperors-children",
22: "genestealer-cults",
23: "grey-knights",
24: "imperial-agents",
25: "imperial-agents",
26: "imperial-knights",
27: "leagues-of-votann",
28: "necrons",
29: "orks",
30: "orks", # detachment enhancements
31: "space-marines",
32: "space-marines", # forge world units (Pedro Kantor, Predator, etc.)
33: "space-wolves",
34: "tau-empire",
35: "thousand-sons",
36: "tyranids",
37: "tyranids", # detachment enhancements
38: "world-eaters",
}
NAME_BY_SLUG = {
"adepta-sororitas": "Adepta Sororitas",
"adeptus-custodes": "Adeptus Custodes",
"adeptus-mechanicus": "Adeptus Mechanicus",
"adeptus-titanicus": "Adeptus Titanicus",
"aeldari": "Aeldari",
"ynnari": "Ynnari",
"astra-militarum": "Astra Militarum",
"black-templars": "Black Templars",
"blood-angels": "Blood Angels",
"chaos-daemons": "Chaos Daemons",
"chaos-knights": "Chaos Knights",
"chaos-space-marines": "Chaos Space Marines",
"dark-angels": "Dark Angels",
"death-guard": "Death Guard",
"deathwatch": "Deathwatch",
"drukhari": "Drukhari",
"emperors-children": "Emperor's Children",
"genestealer-cults": "Genestealer Cults",
"grey-knights": "Grey Knights",
"imperial-agents": "Imperial Agents",
"imperial-knights": "Imperial Knights",
"leagues-of-votann": "Leagues of Votann",
"necrons": "Necrons",
"orks": "Orks",
"space-marines": "Space Marines",
"space-wolves": "Space Wolves",
"tau-empire": "T'au Empire",
"thousand-sons": "Thousand Sons",
"tyranids": "Tyranids",
"world-eaters": "World Eaters",
}
# World Eaters is page 38 in this PDF (no separate page 39)
# Check if it exists
PAGE_TO_SLUG[38] = "world-eaters"
def main():
doc = pymupdf.open(PDF)
print(f"PDF: {PDF} pages={doc.page_count}", flush=True)
accum = {}
for page_idx in range(doc.page_count):
page_num = page_idx + 1
s = PAGE_TO_SLUG.get(page_num)
if not s:
print(f" page {page_num:2d}: skipped (title/blank)", flush=True)
continue
if s not in accum:
accum[s] = {
"slug": s,
"name": NAME_BY_SLUG.get(s, s),
"source": Path(PDF).name,
"version": "3.2",
"extracted_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"pages": [],
"n_units": 0,
"n_rows": 0,
"units": {},
}
accum[s]["pages"].append(page_num)
page_units = parse_page(doc[page_idx].get_text())
n_added = 0
for unit, costs in page_units.items():
existing = accum[s]["units"].setdefault(unit, [])
seen = {(c["size"], c["pts"]) for c in existing}
for c in costs:
if (c["size"], c["pts"]) not in seen:
existing.append(c)
seen.add((c["size"], c["pts"]))
n_added += 1
print(f" page {page_num:2d} -> {s:<22} +{n_added:>3} entries", flush=True)
doc.close()
manifest = {"pdf": Path(PDF).name, "version": "3.2", "factions": []}
total_units = total_rows = 0
for s, data in sorted(accum.items()):
data["n_units"] = len(data["units"])
data["n_rows"] = sum(len(v) for v in data["units"].values())
out_path = OUT_DIR / f"{s}.json"
with open(out_path, "w") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
total_units += data["n_units"]
total_rows += data["n_rows"]
manifest["factions"].append({
"slug": s, "name": data["name"],
"pages": data["pages"],
"n_units": data["n_units"],
"n_rows": data["n_rows"],
"file": out_path.name,
})
print(f" {s:<22} {data['n_units']:>3} units / {data['n_rows']:>3} rows -> {out_path.name}", flush=True)
manifest["generated_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
manifest["n_factions"] = len(manifest["factions"])
manifest["total_units"] = total_units
manifest["total_rows"] = total_rows
with open(OUT_DIR / "_manifest.json", "w") as f:
json.dump(manifest, f, indent=2, ensure_ascii=False)
print(f"\nWrote {len(manifest['factions'])} faction files to {OUT_DIR}/")
print(f"Total: {total_units} units / {total_rows} size-rows")
if __name__ == "__main__":
main()

215
parse_pdf_per_faction.py Normal file
View File

@@ -0,0 +1,215 @@
#!/usr/bin/env python3
"""
Extract the MFM PDF (Full_armies_10th.pdf) into per-faction JSON files,
mirroring the shape of the live scraper output in /root/wh40k-factions/live/.
Output:
/root/wh40k-factions/pdf/<slug>.json (one per faction)
/root/wh40k-factions/pdf/_manifest.json
Schema per file (matches live scraper):
{
"slug": "astra-militarum",
"name": "Astra Militarum",
"source": "Full_armies_10th.pdf",
"version": "v4.3", # parsed from page 1 header
"extracted_at": "2026-06-17T...",
"pages": [8, 9], # all PDF pages that contribute units
"n_units": 84,
"n_rows": 162,
"units": {
"Valkyrie": [
{"size": "1 model", "pts": 190}
],
"...": [...]
}
}
Page-to-faction mapping is fixed: page 9 is Astra Militarum forge world
(was incorrectly mapped to Black Templars in the original parse_pdf.py).
Detachment-enhancements-only pages are mapped to their parent faction but
contribute zero units (parser skips them).
"""
import json
import re
import sys
import time
from pathlib import Path
import pymupdf
sys.path.insert(0, str(Path(__file__).parent))
# Reuse the parser + regexes + helpers from the original script.
from parse_pdf import parse_page, slug, clean_line
PDF = "/root/.hermes/cache/documents/doc_ed3e1a0bd12e_Full_armies_10th.pdf"
OUT_DIR = Path("/root/wh40k-factions/pdf")
OUT_DIR.mkdir(parents=True, exist_ok=True)
# Slug for each page. Fixed mapping (the original had page 9 wrong:
# page 9 contains Astra Militarum forge-world units, not Black Templars).
PAGE_TO_SLUG = {
1: None, # title page
2: "adepta-sororitas",
3: "adeptus-custodes",
4: "adeptus-mechanicus",
5: "adeptus-titanicus", # Forge World Titans (Adeptus Titanicus)
6: "aeldari",
7: "ynnari", # Ynnari subset of Aeldari
8: "astra-militarum",
9: "astra-militarum", # AM forge world (Valkyrie, Wyvern, etc.)
10: "black-templars",
11: "blood-angels",
12: "chaos-daemons",
13: "chaos-daemons", # detachment enhancements (skipped by parser)
14: "chaos-knights",
15: "chaos-space-marines",
16: "chaos-space-marines", # detachment enhancements
17: "dark-angels",
18: "death-guard",
19: "deathwatch",
20: "drukhari",
21: "emperors-children",
22: "genestealer-cults",
23: "grey-knights",
24: "imperial-agents", # rules page (no units)
25: "imperial-agents", # units
26: "imperial-knights",
27: "leagues-of-votann",
28: "necrons",
29: "necrons", # detachment enhancements
30: "orks",
31: "orks", # detachment enhancements
32: "space-marines",
33: "space-marines", # forge world units (Predator etc.)
34: "space-wolves",
35: "tau-empire",
36: "thousand-sons",
37: "tyranids",
38: "tyranids", # detachment enhancements
39: "world-eaters",
}
# Display name per slug. For names with apostrophes, use curly form for display.
NAME_BY_SLUG = {
"adepta-sororitas": "Adepta Sororitas",
"adeptus-custodes": "Adeptus Custodes",
"adeptus-mechanicus": "Adeptus Mechanicus",
"adeptus-titanicus": "Adeptus Titanicus",
"aeldari": "Aeldari",
"ynnari": "Ynnari",
"astra-militarum": "Astra Militarum",
"black-templars": "Black Templars",
"blood-angels": "Blood Angels",
"chaos-daemons": "Chaos Daemons",
"chaos-knights": "Chaos Knights",
"chaos-space-marines": "Chaos Space Marines",
"dark-angels": "Dark Angels",
"death-guard": "Death Guard",
"deathwatch": "Deathwatch",
"drukhari": "Drukhari",
"emperors-children": "Emperor's Children",
"genestealer-cults": "Genestealer Cults",
"grey-knights": "Grey Knights",
"imperial-agents": "Imperial Agents",
"imperial-knights": "Imperial Knights",
"leagues-of-votann": "Leagues of Votann",
"necrons": "Necrons",
"orks": "Orks",
"space-marines": "Space Marines",
"space-wolves": "Space Wolves",
"tau-empire": "T'au Empire",
"thousand-sons": "Thousand Sons",
"tyranids": "Tyranids",
"world-eaters": "World Eaters",
}
def extract_version(doc) -> str | None:
"""Pull the MFM version from the title page (e.g. 'VERSION 4.3')."""
try:
text = doc[0].get_text()
except Exception:
return None
m = re.search(r"VERSION\s+([\d.]+)", text, re.IGNORECASE)
return m.group(1) if m else None
def main():
if not Path(PDF).exists():
print(f"ERROR: PDF not found at {PDF}", file=sys.stderr)
sys.exit(1)
doc = pymupdf.open(PDF)
version = extract_version(doc)
print(f"PDF: {PDF} pages={doc.page_count} version={version}", flush=True)
# accumulator per faction slug
accum: dict[str, dict] = {}
for page_idx in range(doc.page_count):
page_num = page_idx + 1
s = PAGE_TO_SLUG.get(page_num)
if not s:
print(f" page {page_num:2d}: skipped (title/blank)", flush=True)
continue
if s not in accum:
accum[s] = {
"slug": s,
"name": NAME_BY_SLUG[s],
"source": Path(PDF).name,
"version": version,
"extracted_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"pages": [],
"n_units": 0,
"n_rows": 0,
"units": {},
}
accum[s]["pages"].append(page_num)
page_units = parse_page(doc[page_idx].get_text())
n_added = 0
for unit, costs in page_units.items():
existing = accum[s]["units"].setdefault(unit, [])
seen = {(c["size"], c["pts"]) for c in existing}
for c in costs:
if (c["size"], c["pts"]) not in seen:
existing.append(c)
seen.add((c["size"], c["pts"]))
n_added += 1
print(f" page {page_num:2d} -> {s:<22} +{n_added:>3} entries", flush=True)
doc.close()
# finalize counts + write files
manifest = {"pdf": Path(PDF).name, "version": version, "factions": []}
total_units = total_rows = 0
for s, data in sorted(accum.items()):
data["n_units"] = len(data["units"])
data["n_rows"] = sum(len(v) for v in data["units"].values())
out_path = OUT_DIR / f"{s}.json"
with open(out_path, "w") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
total_units += data["n_units"]
total_rows += data["n_rows"]
manifest["factions"].append({
"slug": s, "name": data["name"],
"pages": data["pages"],
"n_units": data["n_units"],
"n_rows": data["n_rows"],
"file": out_path.name,
})
print(f" {s:<22} {data['n_units']:>3} units / {data['n_rows']:>3} rows "
f"-> {out_path.name}", flush=True)
manifest["generated_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
manifest["n_factions"] = len(manifest["factions"])
manifest["total_units"] = total_units
manifest["total_rows"] = total_rows
with open(OUT_DIR / "_manifest.json", "w") as f:
json.dump(manifest, f, indent=2, ensure_ascii=False)
print(f"\nWrote {len(manifest['factions'])} faction files to {OUT_DIR}/")
print(f"Total: {total_units} units / {total_rows} size-rows")
print(f"Manifest: {OUT_DIR / '_manifest.json'}")
if __name__ == "__main__":
main()

6426
pdf_data.json Normal file

File diff suppressed because it is too large Load Diff

3
react-app/Dockerfile Normal file
View File

@@ -0,0 +1,3 @@
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html/
RUN printf 'server {\n listen 80;\n server_name _;\n root /usr/share/nginx/html;\n index index.html;\n gzip on;\n gzip_types text/plain text/css application/javascript application/json image/svg+xml;\n gzip_min_length 256;\n \n # CORS headers for module scripts\n add_header Access-Control-Allow-Origin "*" always;\n \n location ~* \\.(js|css|json|png|jpg|webp|svg|ico|woff2?)$ {\n expires 1h;\n add_header Cache-Control "public, max-age=3600";\n add_header Access-Control-Allow-Origin "*" always;\n }\n \n location / {\n try_files $uri $uri/ /index.html;\n }\n}\n' > /etc/nginx/conf.d/default.conf

View File

@@ -0,0 +1,23 @@
services:
wh40k-site:
build:
context: .
dockerfile: Dockerfile
image: wh40k-site:latest
container_name: wh40k-site
restart: unless-stopped
networks:
- hermes-net
labels:
- "traefik.enable=true"
- "traefik.docker.network=hermes-net"
- "traefik.http.routers.wh40k-site.entrypoints=websecure"
- "traefik.http.routers.wh40k-site.rule=Host(`wh40k.damascusfront.net`)"
- "traefik.http.routers.wh40k-site.tls=true"
- "traefik.http.routers.wh40k-site.tls.certresolver=cloudflare"
- "traefik.http.services.wh40k-site.loadbalancer.server.port=80"
networks:
hermes-net:
external: true
name: hermes-net

29
react-app/index.html Normal file
View File

@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#0a0e14">
<link rel="icon" type="image/png" href="./favicon.png">
<link rel="apple-touch-icon" href="./favicon.png">
<!-- Open Graph / Discord embed -->
<meta property="og:type" content="website">
<meta property="og:title" content="WH40K Points Comparator">
<meta property="og:description" content="Compare Warhammer 40,000 unit points across Munitorum Field Manual versions. Track price changes, view historical trends, and find the biggest winners and losers.">
<meta property="og:image" content="./og-image.png">
<meta property="og:url" content="https://wh40k.damascusfront.net/">
<!-- Twitter embed -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="WH40K Points Comparator">
<meta name="twitter:description" content="Compare Warhammer 40,000 unit points across Munitorum Field Manual versions. Track price changes, view historical trends, and find the biggest winners and losers.">
<meta name="twitter:image" content="./og-image.png">
<title>WH40K Points Comparator — MFM v4.3</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

2062
react-app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
react-app/package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "react-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/material": "^9.1.1",
"@mui/x-data-grid": "^9.5.0",
"@vitejs/plugin-react": "^6.0.2",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"vite": "^8.0.16"
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 KiB

508
react-app/src/App.jsx Normal file
View File

@@ -0,0 +1,508 @@
import React, { useState, useMemo, useCallback } from 'react'
import {
Box, Container, Typography, TextField, Select, MenuItem, InputLabel,
FormControl, Stack, IconButton, Modal, Paper,
useMediaQuery, useTheme,
} from '@mui/material'
import { DataGrid } from '@mui/x-data-grid'
// ── helpers ──
function sizeShort(size) {
return size.replace(/\s*models?$/, '')
}
function pctLabel(pct) {
if (pct === null || pct === undefined) return '—'
const sign = pct > 0 ? '+' : ''
return `${sign}${pct.toFixed(1)}%`
}
function ptsLabel(pts) {
if (pts === null || pts === undefined) return '—'
return pts > 0 ? `+${pts}` : `${pts}`
}
// Color: red = more expensive (bad for player), green = cheaper (good for player)
function changeColor(val) {
if (val === null || val === undefined) return 'text.secondary'
if (val > 0) return '#f85149' // costlier = red
if (val < 0) return '#3fb950' // cheaper = green
return 'text.secondary'
}
// ── Size dropdown cell ──
function SizeCell({ row, onSelect }) {
const sizes = row.sizes || []
if (sizes.length <= 1) {
return (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%' }}>
<Typography variant="body2" color="text.secondary" sx={{ fontSize: { xs: '0.68rem', sm: '0.8rem' } }}>
{sizeShort(row.size)}
</Typography>
</Box>
)
}
return (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%' }}>
<Select
size="small"
value={row.size}
onChange={(e) => { e.stopPropagation(); onSelect(row, e.target.value) }}
onClick={(e) => e.stopPropagation()}
variant="outlined"
IconComponent={() => null}
sx={{
minWidth: 'auto',
height: 24,
'& .MuiSelect-select': { py: 0.15, px: 0.5, fontSize: { xs: '0.65rem', sm: '0.75rem' }, fontWeight: 600, paddingRight: '0.5px !important' },
'& .MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(59,130,246,0.3)' },
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: 'primary.main' },
'&.Mui-focused .MuiOutlinedInput-notchedOutline': { borderColor: 'primary.main' },
}}
renderValue={(v) => sizeShort(v)}
>
{sizes.map((s) => (
<MenuItem key={s.size} value={s.size} sx={{ fontSize: '0.8rem' }}>
{sizeShort(s.size)}
</MenuItem>
))}
</Select>
</Box>
)
}
// ── Line graph modal ──
function GraphModal({ row, open, onClose }) {
const [graphSize, setGraphSize] = useState(null)
React.useEffect(() => {
if (row) setGraphSize(row.size)
}, [row])
if (!row) return null
const sizes = row.sizes || []
const activeSize = graphSize || row.size
const activeSizeData = sizes.find(s => s.size === activeSize) || sizes[0]
const history = activeSizeData?.history || []
const W = 640, H = 360, padL = 70, padR = 24, padT = 24, padB = 48
const chartW = W - padL - padR
const chartH = H - padT - padB
const pts = history.map(h => h.pts)
const minPts = Math.min(...pts, 0)
const maxPts = Math.max(...pts, 1)
const ptsRange = maxPts - minPts || 1
const padY = ptsRange * 0.15
const yMin = Math.max(0, minPts - padY)
const yMax = maxPts + padY
const yRange = yMax - yMin || 1
const n = history.length
const xFor = (i) => n <= 1 ? chartW / 2 + padL : padL + (i / (n - 1)) * chartW
const yFor = (v) => padT + chartH - ((v - yMin) / yRange) * chartH
const linePath = history.map((h, i) => `${i === 0 ? 'M' : 'L'} ${xFor(i)} ${yFor(h.pts)}`).join(' ')
const areaPath = history.length > 1
? `${linePath} L ${xFor(n - 1)} ${padT + chartH} L ${xFor(0)} ${padT + chartH} Z`
: ''
const yTicks = []
const tickCount = Math.min(4, Math.ceil(yRange / 10))
for (let i = 0; i <= tickCount; i++) {
const v = yMin + (yRange * i / tickCount)
yTicks.push({ v: Math.round(v), y: yFor(v) })
}
const fmtDate = (d) => {
const dt = new Date(d)
return dt.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: '2-digit' })
}
return (
<Modal open={open} onClose={onClose} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', p: { xs: 0, sm: 2 }, width: '100%', height: '100%' }}>
<Paper sx={{
maxWidth: { xs: '100%', sm: 720 },
width: '100%',
height: { xs: '100%', sm: 'auto' },
maxHeight: { xs: '100%', sm: '90vh' },
overflow: 'auto', p: { xs: 1, sm: 3 },
borderRadius: { xs: 0, sm: 2 },
display: 'flex', flexDirection: 'column',
}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
<Box sx={{ minWidth: 0, flex: 1 }}>
<Typography variant="h6" fontWeight={700} sx={{ fontSize: { xs: '1rem', sm: '1.1rem' }, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{row.name}</Typography>
<Typography variant="body2" color="text.secondary" sx={{ fontSize: { xs: '0.72rem', sm: '0.8rem' } }}>{row.faction_name}</Typography>
</Box>
<IconButton onClick={onClose} size="small" sx={{ color: 'text.secondary', flexShrink: 0 }}></IconButton>
</Box>
{sizes.length > 1 && (
<FormControl size="small" sx={{ mb: 2, minWidth: 140 }}>
<InputLabel>Model count</InputLabel>
<Select
value={activeSize}
label="Model count"
onChange={(e) => setGraphSize(e.target.value)}
renderValue={(v) => sizeShort(v) + ' models'}
>
{sizes.map((s) => (
<MenuItem key={s.size} value={s.size}>{sizeShort(s.size)} models</MenuItem>
))}
</Select>
</FormControl>
)}
{history.length > 0 ? (
<Box sx={{ width: '100%', display: 'flex', justifyContent: 'center' }}>
<Box sx={{ width: '100%', maxWidth: { xs: '100%', sm: 680 } }}>
<svg viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', height: 'auto', display: 'block' }}>
{yTicks.map((t, i) => (
<g key={i}>
<line x1={padL} y1={t.y} x2={W - padR} y2={t.y} stroke="#d0d0d0" strokeWidth={0.5} />
<text x={padL - 10} y={t.y + 4} textAnchor="end" fontSize={13} fontWeight={500} fill="wheat">{t.v}</text>
</g>
))}
{areaPath && <path d={areaPath} fill="rgba(59,130,246,0.10)" />}
<path d={linePath} fill="none" stroke="#3b82f6" strokeWidth={2.5} strokeLinejoin="round" strokeLinecap="round" />
{history.map((h, i) => (
<g key={i}>
<circle cx={xFor(i)} cy={yFor(h.pts)} r={5} fill="#3b82f6" stroke="#fff" strokeWidth={2} />
<text x={xFor(i)} y={yFor(h.pts) - 12} textAnchor="middle" fontSize={14} fontWeight={700} fill="wheat">{h.pts}</text>
<text x={xFor(i)} y={H - padB + 20} textAnchor="middle" fontSize={11} fontWeight={500} fill="wheat">{fmtDate(h.date)}</text>
</g>
))}
</svg>
</Box>
</Box>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', py: 4 }}>
No historical data for this unit.
</Typography>
)}
{history.length > 1 && (
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'space-between', gap: 1 }}>
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
{history[0].version}: {history[0].pts}pts
</Typography>
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
{history[history.length - 1].version}: {history[history.length - 1].pts}pts
</Typography>
</Box>
)}
</Paper>
</Modal>
)
}
// ── Main App ──
export default function App() {
const [data, setData] = useState(null)
// Read initial state from URL params
const params = new URLSearchParams(window.location.search)
const [query, setQuery] = useState(params.get('q') || '')
const [faction, setFaction] = useState(params.get('faction') || 'adepta-sororitas')
const [dir, setDir] = useState(params.get('dir') || '')
const [sizeChoice, setSizeChoice] = useState({})
const [modalRow, setModalRow] = useState(null)
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
React.useEffect(() => {
fetch('./data.json').then(r => r.json()).then(setData).catch(console.error)
}, [])
// Sync filter state to URL whenever it changes
React.useEffect(() => {
const p = new URLSearchParams()
if (query) p.set('q', query)
if (faction) p.set('faction', faction)
if (dir) p.set('dir', dir)
const qs = p.toString()
const newUrl = qs ? `?${qs}` : window.location.pathname
window.history.replaceState(null, '', newUrl)
}, [query, faction, dir])
const selectSize = useCallback((row, sizeLabel) => {
const key = `${row.faction}|${row.name}`
setSizeChoice(prev => ({ ...prev, [key]: sizeLabel }))
}, [])
const augmented = useMemo(() => {
if (!data) return []
return data.units.map(u => {
const key = `${u.faction}|${u.name}`
const chosen = sizeChoice[key]
const active = (chosen && u.sizes.find(s => s.size === chosen)) || u.sizes.find(s => s.size === u.default_size) || u.sizes[0]
return {
...u,
size: active.size,
original: active.original,
new: active.new,
change_pct: active.change_pct,
change_pts: active.change_pts,
tier: active.tier,
}
})
}, [data, sizeChoice])
const filtered = useMemo(() => {
if (!augmented.length) return []
let view = augmented
const q = query.trim().toLowerCase()
if (q) {
view = view.filter(u =>
u.name.toLowerCase().includes(q) ||
u.faction_name.toLowerCase().includes(q) ||
u.size.toLowerCase().includes(q)
)
}
if (faction) view = view.filter(u => u.faction === faction)
if (dir === 'up') view = view.filter(u => u.change_pct !== null && u.change_pct > 0)
else if (dir === 'down') view = view.filter(u => u.change_pct !== null && u.change_pct < 0)
else if (dir === 'no-change') view = view.filter(u => u.change_pct === 0)
else if (dir === 'new-only') view = view.filter(u => u.original === null && u.new !== null)
else if (dir === 'old-only') view = view.filter(u => u.original !== null && u.new === null)
return view
}, [augmented, query, faction, dir])
// Movers — based on the currently filtered view
const movers = useMemo(() => {
if (!filtered.length) return { drops: [], rises: [] }
const drops = filtered.filter(u => u.change_pct !== null && u.change_pct < 0)
.sort((a, b) => a.change_pct - b.change_pct).slice(0, 5)
const rises = filtered.filter(u => u.change_pct !== null && u.change_pct > 0)
.sort((a, b) => b.change_pct - a.change_pct).slice(0, 5)
return { drops, rises }
}, [filtered])
const showFactionCol = !faction
const showFactionInMovers = !faction // hide faction name in movers when filtered to a faction
// Columns: all flex-based
const columns = useMemo(() => {
const cols = [
{
field: 'name', headerName: 'Unit', flex: 3, minWidth: 80,
renderCell: (p) => (
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%', height: '100%', overflow: 'hidden' }}>
<Typography
variant="body2"
fontWeight={600}
sx={{
fontSize: { xs: '0.68rem', sm: '0.8rem' },
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
'&:hover': { textDecoration: 'underline', color: 'primary.main' },
cursor: 'pointer',
}}
>
{p.row.name}
</Typography>
</Box>
),
},
{
field: 'size', headerName: '#', flex: 0.6, minWidth: 36,
renderCell: (p) => <SizeCell row={p.row} onSelect={selectSize} />,
},
{
field: 'original', headerName: 'Old', flex: 0.8, minWidth: 36, align: 'right', headerAlign: 'right',
renderCell: (p) => (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', width: '100%', height: '100%' }}>
<Typography sx={{ fontFamily: 'monospace', fontSize: { xs: '0.68rem', sm: '0.8rem' } }}>{p.row.original ?? '—'}</Typography>
</Box>
),
},
{
field: 'new', headerName: 'New', flex: 0.8, minWidth: 36, align: 'right', headerAlign: 'right',
renderCell: (p) => (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', width: '100%', height: '100%' }}>
<Typography sx={{ fontFamily: 'monospace', fontWeight: 600, fontSize: { xs: '0.68rem', sm: '0.8rem' } }}>{p.row.new ?? '—'}</Typography>
</Box>
),
},
{
field: 'change_pct', headerName: 'Δ %', flex: 1, minWidth: 44, align: 'right', headerAlign: 'right',
renderCell: (p) => (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', width: '100%', height: '100%' }}>
<Typography sx={{ fontFamily: 'monospace', fontWeight: 600, fontSize: { xs: '0.68rem', sm: '0.8rem' }, color: changeColor(p.row.change_pct) }}>{pctLabel(p.row.change_pct)}</Typography>
</Box>
),
},
]
// Δ pts column — desktop only
if (!isMobile) {
cols.splice(4, 0, {
field: 'change_pts', headerName: 'Δ pts', flex: 0.8, minWidth: 40, align: 'right', headerAlign: 'right',
renderCell: (p) => (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', width: '100%', height: '100%' }}>
<Typography sx={{ fontFamily: 'monospace', fontSize: { xs: '0.68rem', sm: '0.8rem' }, color: changeColor(p.row.change_pts) }}>{ptsLabel(p.row.change_pts)}</Typography>
</Box>
),
})
}
if (showFactionCol) {
cols.unshift({
field: 'faction_name', headerName: 'Faction', flex: 1.5, minWidth: 80,
renderCell: (p) => (
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%', height: '100%', overflow: 'hidden' }}>
<Typography variant="body2" color="text.secondary" sx={{ fontSize: { xs: '0.68rem', sm: '0.8rem' }, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{p.row.faction_name}</Typography>
</Box>
),
})
}
return cols
}, [selectSize, showFactionCol, isMobile])
if (!data) return <Box sx={{ p: 4, color: 'text.secondary' }}>Loading</Box>
return (
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default' }}>
{/* Sticky filter bar */}
<Box sx={{
borderBottom: 1, borderColor: 'divider', bgcolor: 'background.paper',
position: 'sticky', top: 0, zIndex: 1100,
}}>
<Container maxWidth="xl" sx={{ py: { xs: 0.75, sm: 1 }, px: { xs: 1, sm: 2 } }}>
<Typography variant="h6" fontWeight={700} sx={{ display: { xs: 'none', sm: 'block' }, mb: 0.5, fontSize: '1.1rem' }}>
Points Comparator
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ display: { xs: 'none', sm: 'block' }, mb: 1, fontSize: '0.75rem' }}>
Codex vs. MFM v4.3 · {data.stats.total_rows.toLocaleString()} units · <span style={{ color: '#3fb950' }}>green = cheaper</span> · <span style={{ color: '#f85149' }}>red = costlier</span>
</Typography>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={{ xs: 0.5, sm: 1 }} sx={{ alignItems: { sm: 'flex-end' } }}>
<TextField
size="small"
label="Search"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Unit name…"
sx={{ flex: 2, minWidth: { xs: '100%', sm: 180 } }}
/>
<Stack direction="row" spacing={0.5} sx={{ flex: 1, minWidth: 0 }}>
<FormControl size="small" sx={{ flex: 1, minWidth: 0 }}>
<InputLabel>Faction</InputLabel>
<Select value={faction} label="Faction" onChange={(e) => setFaction(e.target.value)}>
<MenuItem value="">All</MenuItem>
{data.factions.map(slug => (
<MenuItem key={slug} value={slug}>{data.faction_names[slug] || slug}</MenuItem>
))}
</Select>
</FormControl>
<FormControl size="small" sx={{ flex: 1, minWidth: 0 }}>
<InputLabel>{isMobile ? 'Δ' : 'Change'}</InputLabel>
<Select value={dir} label={isMobile ? 'Δ' : 'Change'} onChange={(e) => setDir(e.target.value)}>
<MenuItem value="">All</MenuItem>
<MenuItem value="up"> Costlier</MenuItem>
<MenuItem value="down"> Cheaper</MenuItem>
<MenuItem value="no-change"> No change</MenuItem>
<MenuItem value="new-only">+ New only</MenuItem>
<MenuItem value="old-only"> Removed</MenuItem>
</Select>
</FormControl>
</Stack>
</Stack>
</Container>
</Box>
{/* Movers — based on current filtered view, click opens modal */}
<Container maxWidth="xl" sx={{ py: { xs: 0.5, sm: 2 }, px: { xs: 1, sm: 2 } }}>
<Box sx={{ display: 'flex', gap: { xs: 0.5, sm: 2 }, flexDirection: 'row' }}>
{/* Drops = cheaper = green (good for player) */}
<Paper sx={{ flex: 1, p: { xs: 0.5, sm: 2 }, borderRadius: { xs: 1, sm: 2 }, borderLeft: 3, borderColor: 'success.main', minWidth: 0 }}>
<Typography variant="subtitle2" fontWeight={700} sx={{ mb: 0.25, fontSize: { xs: '0.65rem', sm: '0.8rem' }, color: 'success.main' }}> Cheaper</Typography>
<Stack spacing={0}>
{movers.drops.map((u, i) => (
<Box key={i} sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer', py: 0.15, px: 0.25, borderRadius: 0.5, '&:hover': { bgcolor: 'rgba(63,185,80,0.08)' } }}
onClick={() => setModalRow(u)}>
<Box sx={{ minWidth: 0, overflow: 'hidden', flex: 1 }}>
<Typography variant="body2" fontWeight={600} sx={{ fontSize: { xs: '0.6rem', sm: '0.8rem' }, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', '&:hover': { color: 'primary.main' } }}>{u.name}</Typography>
{showFactionInMovers && (
<Typography variant="caption" color="text.secondary" sx={{ fontSize: { xs: '0.55rem', sm: '0.7rem' }, display: { xs: 'none', sm: 'block' } }}>{u.faction_name}</Typography>
)}
</Box>
<Typography variant="body2" sx={{ fontFamily: 'monospace', color: 'success.main', fontWeight: 700, fontSize: { xs: '0.55rem', sm: '0.8rem' }, whiteSpace: 'nowrap', ml: 0.5 }}>
{u.original}{u.new} ({pctLabel(u.change_pct)})
</Typography>
</Box>
))}
</Stack>
</Paper>
{/* Rises = costlier = red (bad for player) */}
<Paper sx={{ flex: 1, p: { xs: 0.5, sm: 2 }, borderRadius: { xs: 1, sm: 2 }, borderLeft: 3, borderColor: 'error.main', minWidth: 0 }}>
<Typography variant="subtitle2" fontWeight={700} sx={{ mb: 0.25, fontSize: { xs: '0.65rem', sm: '0.8rem' }, color: 'error.main' }}> Costlier</Typography>
<Stack spacing={0}>
{movers.rises.map((u, i) => (
<Box key={i} sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer', py: 0.15, px: 0.25, borderRadius: 0.5, '&:hover': { bgcolor: 'rgba(248,81,73,0.08)' } }}
onClick={() => setModalRow(u)}>
<Box sx={{ minWidth: 0, overflow: 'hidden', flex: 1 }}>
<Typography variant="body2" fontWeight={600} sx={{ fontSize: { xs: '0.6rem', sm: '0.8rem' }, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', '&:hover': { color: 'primary.main' } }}>{u.name}</Typography>
{showFactionInMovers && (
<Typography variant="caption" color="text.secondary" sx={{ fontSize: { xs: '0.55rem', sm: '0.7rem' }, display: { xs: 'none', sm: 'block' } }}>{u.faction_name}</Typography>
)}
</Box>
<Typography variant="body2" sx={{ fontFamily: 'monospace', color: 'error.main', fontWeight: 700, fontSize: { xs: '0.55rem', sm: '0.8rem' }, whiteSpace: 'nowrap', ml: 0.5 }}>
{u.original}{u.new} ({pctLabel(u.change_pct)})
</Typography>
</Box>
))}
</Stack>
</Paper>
</Box>
</Container>
{/* Data Grid */}
<Container maxWidth="xl" sx={{ pb: 4, px: { xs: 1, sm: 2 } }}>
<Paper sx={{ borderRadius: 2, overflow: 'hidden' }}>
{/* Count label — inside the table card, anchored */}
<Box sx={{ px: { xs: 1, sm: 2 }, py: 1, borderBottom: 1, borderColor: 'divider', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="caption" color="text.secondary" sx={{ fontSize: { xs: '0.65rem', sm: '0.7rem' } }}>
<b style={{ color: 'text.primary' }}>{filtered.length.toLocaleString()}</b> units
</Typography>
<Typography variant="caption" color="text.secondary" sx={{ fontSize: { xs: '0.6rem', sm: '0.65rem' }, display: { xs: 'none', sm: 'block' } }}>
Click a unit for points history
</Typography>
</Box>
<DataGrid
rows={filtered}
columns={columns}
getRowId={(row) => `${row.faction}|${row.name}`}
density="compact"
autoHeight
hideFooter
disableColumnMenu
onCellClick={(p) => {
if (p.field === 'size') return
setModalRow(p.row)
}}
sx={{
border: 'none',
width: '100%',
'& .MuiDataGrid-columnHeaders': { bgcolor: 'background.paper', borderBottom: 1, borderColor: 'divider' },
'& .MuiDataGrid-columnHeader': { fontSize: { xs: '0.6rem', sm: '0.75rem' }, textTransform: 'uppercase', fontWeight: 600 },
'& .MuiDataGrid-columnHeaderTitle': { fontSize: { xs: '0.6rem', sm: '0.75rem' } },
'& .MuiDataGrid-columnSeparator': { display: 'none' },
'& .MuiDataGrid-iconButtonContainer': { display: 'none' },
'& .MuiDataGrid-row': { cursor: 'pointer', '&:hover': { bgcolor: 'rgba(59,130,246,0.04)' } },
'& .MuiDataGrid-cell': { borderColor: 'divider', py: { xs: 0.5, sm: 0.5 }, display: 'flex', alignItems: 'center', overflow: 'hidden' },
'& .MuiDataGrid-virtualScroller': { overflowX: 'hidden' },
}}
/>
</Paper>
</Container>
{/* Graph Modal */}
<GraphModal row={modalRow} open={!!modalRow} onClose={() => setModalRow(null)} />
</Box>
)
}

49
react-app/src/main.jsx Normal file
View File

@@ -0,0 +1,49 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { ThemeProvider, createTheme, CssBaseline } from '@mui/material'
import App from './App.jsx'
const theme = createTheme({
palette: {
mode: 'dark',
background: {
default: '#0a0e14',
paper: '#11161e',
},
primary: { main: '#3b82f6' },
secondary: { main: '#60a5fa' },
success: { main: '#3fb950' },
error: { main: '#f85149' },
text: {
primary: '#e6edf3',
secondary: '#7d8590',
},
divider: '#232b38',
},
typography: {
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Helvetica, Arial, sans-serif',
fontSize: 14,
},
shape: { borderRadius: 8 },
components: {
MuiPaper: {
styleOverrides: {
root: { backgroundImage: 'none' },
},
},
MuiTableCell: {
styleOverrides: {
root: { borderBottomColor: '#232b38' },
},
},
},
})
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
</React.StrictMode>
)

20
react-app/vite.config.js Normal file
View File

@@ -0,0 +1,20 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist',
base: './',
modulePreload: { polyfill: true },
rollupOptions: {
output: {
entryFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash][extname]',
},
},
},
server: {
port: 9102,
},
})

259
scrape_live.py Normal file
View File

@@ -0,0 +1,259 @@
"""Scrape live MFM data for all 30 factions.
Output: /root/wh40k-factions/live_data.json
{
"<faction-slug>": {
"name": "T'au Empire",
"version": "v1.0",
"url": "...",
"units": {
"Broadside Battlesuits": [
{
"size": "1 models",
"pts": 75,
"tier": "YOUR 1ST TO 2ND UNITS COST"
},
{"size": "2 models", "pts": 150, "tier": "YOUR 1ST TO 2ND UNITS COST"},
...
{"size": "1 models", "pts": 95, "tier": "YOUR 3RD + UNIT COSTS"},
],
...
}
}
}
"""
import json
import re
import sys
import time
from pathlib import Path
from playwright.sync_api import sync_playwright
FACTIONS = [
("adepta-sororitas", "Adepta Sororitas", "https://mfm.warhammer-community.com/en/adepta-sororitas"),
("adeptus-custodes", "Adeptus Custodes", "https://mfm.warhammer-community.com/en/adeptus-custodes"),
("adeptus-mechanicus", "Adeptus Mechanicus", "https://mfm.warhammer-community.com/en/adeptus-mechanicus"),
("aeldari", "Aeldari", "https://mfm.warhammer-community.com/en/aeldari"),
("astra-militarum", "Astra Militarum", "https://mfm.warhammer-community.com/en/astra-militarum"),
("black-templars", "Black Templars", "https://mfm.warhammer-community.com/en/black-templars"),
("blood-angels", "Blood Angels", "https://mfm.warhammer-community.com/en/blood-angels"),
("chaos-daemons", "Chaos Daemons", "https://mfm.warhammer-community.com/en/chaos-daemons"),
("chaos-knights", "Chaos Knights", "https://mfm.warhammer-community.com/en/chaos-knights"),
("chaos-space-marines", "Chaos Space Marines", "https://mfm.warhammer-community.com/en/chaos-space-marines"),
("chaos-titan-legions", "Chaos Titan Legions", "https://mfm.warhammer-community.com/en/chaos-titan-legions"),
("dark-angels", "Dark Angels", "https://mfm.warhammer-community.com/en/dark-angels"),
("death-guard", "Death Guard", "https://mfm.warhammer-community.com/en/death-guard"),
("deathwatch", "Deathwatch", "https://mfm.warhammer-community.com/en/deathwatch"),
("drukhari", "Drukhari", "https://mfm.warhammer-community.com/en/drukhari"),
("emperors-children", "Emperor's Children", "https://mfm.warhammer-community.com/en/emperors-children"),
("genestealer-cults", "Genestealer Cults", "https://mfm.warhammer-community.com/en/genestealer-cults"),
("grey-knights", "Grey Knights", "https://mfm.warhammer-community.com/en/grey-knights"),
("imperial-agents", "Imperial Agents", "https://mfm.warhammer-community.com/en/imperial-agents"),
("imperial-knights", "Imperial Knights", "https://mfm.warhammer-community.com/en/imperial-knights"),
("leagues-of-votann", "Leagues of Votann", "https://mfm.warhammer-community.com/en/leagues-of-votann"),
("necrons", "Necrons", "https://mfm.warhammer-community.com/en/necrons"),
("orks", "Orks", "https://mfm.warhammer-community.com/en/orks"),
("space-marines", "Space Marines", "https://mfm.warhammer-community.com/en/space-marines"),
("space-wolves", "Space Wolves", "https://mfm.warhammer-community.com/en/space-wolves"),
("tau-empire", "T'au Empire", "https://mfm.warhammer-community.com/en/tau-empire"),
("thousand-sons", "Thousand Sons", "https://mfm.warhammer-community.com/en/thousand-sons"),
("titan-legions", "Titan Legions", "https://mfm.warhammer-community.com/en/titan-legions"),
("tyranids", "Tyranids", "https://mfm.warhammer-community.com/en/tyranids"),
("world-eaters", "World Eaters", "https://mfm.warhammer-community.com/en/world-eaters"),
]
OUT = Path("/root/wh40k-factions/live_data.json")
# JavaScript extractor — runs in the page context. Returns a list of:
# {unit: str, tier: str|null, size: str, pts: int}
EXTRACT_JS = r"""
() => {
// Walk the entire body. The MFM site renders unit cards with class
// "flex flex-col space-y-1 m-1 print:break-inside-avoid-page"
// Each card has:
// - a unit-name heading (h2/h3)
// - one or more tier headers (e.g. "YOUR 1ST TO 2ND UNITS COST")
// - a list of <li> items with "<n> models" and "<k> pts"
const out = [];
const cards = document.querySelectorAll('div.flex.flex-col.space-y-1.m-1');
for (const card of cards) {
// Unit name: first heading child
const heading = card.querySelector('h1, h2, h3, h4, [class*="font-bold"], [class*="uppercase"]');
if (!heading) continue;
const unit = heading.innerText.trim();
if (!unit) continue;
// Now find tier headers and cost lists within the card
// The DOM order is: heading, tier1-label, tier1-list, tier2-label, tier2-list, ...
// Tier labels are short text in CAPS containing "UNIT" or "MODEL"
// Lists are <ul><li>...</li></ul>
const children = Array.from(card.children);
let currentTier = null;
for (const child of children) {
const txt = (child.innerText || '').trim();
if (!txt) continue;
if (/^YOUR\b/i.test(txt) && (txt.includes('UNIT') || txt.includes('COST') || txt.includes('MODEL'))) {
currentTier = txt.replace(/\s+/g, ' ');
continue;
}
if (child.tagName === 'UL' || child.tagName === 'OL' || child.querySelector('li')) {
const items = child.querySelectorAll('li');
for (const li of items) {
const liText = (li.innerText || '').trim();
// Format: "<n> models\n<k> pts" or "<n> model\n<k> pts"
const m = liText.match(/(\d+)\s+models?\s*\n?\s*(\d+)\s*pts?/i);
if (m) {
out.push({
unit: unit,
tier: currentTier,
size: m[1) + ' models',
pts: parseInt(m[2], 10),
});
}
}
continue;
}
}
}
return out;
}
"""
# Wait — the JS had a syntax error above. Rewrite cleanly:
EXTRACT_JS = r"""
() => {
const out = [];
// Card root: <div class="flex flex-col space-y-1 m-1 print:break-inside-avoid-page">
// First child is the unit-name banner. Then come tier blocks:
// <div class="space-y-1">
// <div ...>TIER LABEL</div>
// <ul><li><span>SIZE</span><span>PTS</span></li>...</ul>
// </div>
const cards = document.querySelectorAll('div.flex.flex-col.space-y-1.m-1');
for (const card of cards) {
const header = card.firstElementChild;
if (!header) continue;
const unit = (header.innerText || '').trim();
if (!unit) continue;
// Find all <ul> within the card and walk backwards to find the
// most recent tier label
const uls = card.querySelectorAll('ul');
for (const ul of uls) {
// Find the tier label: walk up to the .space-y-1 wrapper, then
// take its first child (the label div)
let tier = null;
let parent = ul.parentElement; // .space-y-1
if (parent) {
const labelDiv = parent.querySelector(':scope > div');
if (labelDiv) tier = (labelDiv.innerText || '').trim().replace(/\s+/g, ' ');
}
for (const li of ul.querySelectorAll('li')) {
const spans = li.querySelectorAll('span');
if (spans.length < 2) continue;
const size = (spans[0].innerText || '').trim();
const ptsText = (spans[1].innerText || '').trim();
const pts = parseInt(ptsText.replace(/[^\d]/g, ''), 10);
if (!size || isNaN(pts)) continue;
out.push({ unit, tier, size, pts });
}
}
}
return out;
}
"""
def fetch_one(p, slug: str, name: str, url: str, idx: int, total: int) -> dict:
print(f"[{idx:>2}/{total}] {name} -> {url}", flush=True)
start = time.time()
status = {
"slug": slug, "name": name, "url": url,
"ok": False, "n_units": 0, "n_rows": 0, "error": None,
"elapsed_s": 0.0,
}
try:
context = p.chromium.launch_persistent_context(
user_data_dir=f"/tmp/wh40k-live-{slug}",
executable_path="/usr/bin/chromium",
headless=True,
viewport={"width": 1280, "height": 1800},
user_agent=(
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"
),
)
page = context.new_page()
page.goto(url, wait_until="domcontentloaded", timeout=45000)
try:
page.wait_for_load_state("networkidle", timeout=20000)
except Exception as e:
print(f" networkidle timeout (continuing): {e}", flush=True)
page.wait_for_timeout(2500)
# Detect version
version = page.evaluate("""
() => {
const m = (document.body.innerText || '').match(/v\\d+\\.\\d+/);
return m ? m[0] : null;
}
""")
rows = page.evaluate(EXTRACT_JS)
context.close()
# Group rows by unit
units: dict[str, list[dict]] = {}
for r in rows:
units.setdefault(r["unit"], []).append({
"size": r["size"],
"pts": r["pts"],
"tier": r["tier"],
})
status["ok"] = True
status["n_units"] = len(units)
status["n_rows"] = len(rows)
status["version"] = version
print(f" OK units={len(units)} rows={len(rows)} version={version}", flush=True)
return {
"slug": slug,
"name": name,
"url": url,
"version": version,
"units": units,
"_status": status,
}
except Exception as e:
status["error"] = repr(e)
status["elapsed_s"] = round(time.time() - start, 2)
print(f" FAIL {status['error']}", flush=True)
return {
"slug": slug,
"name": name,
"url": url,
"version": None,
"units": {},
"_status": status,
}
def main() -> int:
out: dict = {}
with sync_playwright() as p:
for i, (slug, name, url) in enumerate(FACTIONS, 1):
r = fetch_one(p, slug, name, url, i, len(FACTIONS))
out[slug] = r
if i < len(FACTIONS):
time.sleep(2.0) # politeness between pages
OUT.write_text(json.dumps(out, indent=2, ensure_ascii=False))
print(f"\nwrote {OUT}")
ok = sum(1 for r in out.values() if r["_status"]["ok"])
print(f"summary: {ok}/{len(out)} factions scraped")
return 0 if ok == len(out) else 1
if __name__ == "__main__":
sys.exit(main())

114
scrape_live_per_faction.py Normal file
View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""
Re-scrape live MFM data for all 30 factions, one JSON file per faction.
Output:
/root/wh40k-factions/live/<slug>.json (one per faction)
/root/wh40k-factions/live/_manifest.json (index of all factions + counts)
"""
import json, sys, time
from pathlib import Path
from playwright.sync_api import sync_playwright
sys.path.insert(0, str(Path(__file__).parent))
from scrape_live import FACTIONS, EXTRACT_JS
OUT_DIR = Path("/root/wh40k-factions/live")
OUT_DIR.mkdir(parents=True, exist_ok=True)
def fetch_one(context, slug: str, name: str, url: str) -> dict:
page = context.new_page()
try:
page.goto(url, wait_until="domcontentloaded", timeout=45000)
try:
page.wait_for_load_state("networkidle", timeout=20000)
except Exception as e:
print(f" networkidle timeout (continuing): {e}", flush=True)
page.wait_for_timeout(2500)
# Detect version
version = page.evaluate("""
() => {
const m = (document.body.innerText || '').match(/v\\d+\\.\\d+/);
return m ? m[0] : null;
}
""")
rows = page.evaluate(EXTRACT_JS)
# Group rows by unit
units = {}
for r in rows:
units.setdefault(r["unit"], []).append({
"size": r["size"],
"pts": r["pts"],
"tier": r["tier"],
})
return {
"slug": slug,
"name": name,
"url": url,
"version": version,
"fetched_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"n_units": len(units),
"n_rows": len(rows),
"units": units,
}
finally:
page.close()
def main():
manifest = {"factions": []}
n_total = len(FACTIONS)
print(f"Scraping {n_total} factions to {OUT_DIR}/", flush=True)
with sync_playwright() as p:
browser = p.chromium.launch(executable_path="/usr/bin/chromium", headless=True)
try:
for idx, (slug, name, url) in enumerate(FACTIONS, 1):
t0 = time.time()
print(f"[{idx:>2}/{n_total}] {name} -> {url}", flush=True)
context = browser.new_context(
user_agent=("Mozilla/5.0 (X11; Linux x86_64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/148.0.0.0 Safari/537.36"),
viewport={"width": 1280, "height": 1800},
)
try:
data = fetch_one(context, slug, name, url)
out_path = OUT_DIR / f"{slug}.json"
with open(out_path, "w") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
print(f" {data['n_units']:>3} units / {data['n_rows']:>3} rows "
f"-> {out_path.name} ({time.time()-t0:.1f}s)", flush=True)
manifest["factions"].append({
"slug": slug, "name": name, "url": url,
"version": data["version"],
"n_units": data["n_units"], "n_rows": data["n_rows"],
"file": out_path.name,
"elapsed_s": round(time.time() - t0, 1),
})
except Exception as e:
print(f" ERROR: {e}", flush=True)
manifest["factions"].append({
"slug": slug, "name": name, "url": url,
"error": str(e),
})
finally:
context.close()
finally:
browser.close()
manifest["generated_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
with open(OUT_DIR / "_manifest.json", "w") as f:
json.dump(manifest, f, indent=2, ensure_ascii=False)
n_ok = sum(1 for f in manifest["factions"] if "error" not in f)
n_err = len(manifest["factions"]) - n_ok
print(f"\nDone. {n_ok}/{n_total} factions OK, {n_err} errors.")
print(f"Manifest: {OUT_DIR / '_manifest.json'}")
if __name__ == "__main__":
main()

361
site/app.js Normal file
View File

@@ -0,0 +1,361 @@
// WH40K Points Comparator — clean rewrite
// Event delegation for all interactive elements. No per-render listener attachment.
const $ = (id) => document.getElementById(id);
const state = {
data: null,
view: [],
query: "",
faction: "",
dir: "",
sort: "change_pct_desc",
mode: "table",
sizeChoice: new Map(), // key: "faction|name" → size label
};
// ─── boot ────────────────────────────────────────────
async function boot() {
const res = await fetch("data.json");
if (!res.ok) throw new Error(`Failed to load data.json: ${res.status}`);
state.data = await res.json();
// Populate faction dropdown
const sel = $("faction");
for (const slug of state.data.factions) {
const opt = document.createElement("option");
opt.value = slug;
opt.textContent = state.data.faction_names[slug] || slug;
sel.appendChild(opt);
}
// Header meta
$("meta").textContent =
`${state.data.stats.total_rows} units · ` +
`${state.data.stats.rows_with_both} with both old & new · ` +
`generated ${state.data.generated_at.slice(0, 10)}`;
renderMovers();
applyFilters();
// ── Wire up controls (these never get re-created) ──
$("q").addEventListener("input", (e) => { state.query = e.target.value; applyFilters(); });
$("faction").addEventListener("change", (e) => { state.faction = e.target.value; applyFilters(); });
$("dir").addEventListener("change", (e) => { state.dir = e.target.value; applyFilters(); });
$("sort").addEventListener("change", (e) => { state.sort = e.target.value; applyFilters(); });
$("view").addEventListener("change", (e) => { state.mode = e.target.value; render(); });
// ── Event delegation for #rows (handles all future renders) ──
$("rows").addEventListener("click", (e) => {
// Size toggle button?
const sizeBtn = e.target.closest(".size-btn");
if (sizeBtn) {
e.stopPropagation();
const u = state.data.units.find(x => x.faction === sizeBtn.dataset.fac && x.name === sizeBtn.dataset.name);
if (u) cycleSize(u);
return;
}
// Table header sort?
const th = e.target.closest("th[data-sort]");
if (th) {
const map = { faction: "faction", name: "name", size: "size",
old: "original", new: "new", change_pts: "change_pts", change_pct: "change_pct" };
const k = map[th.dataset.sort];
if (!k) return;
state.sort = state.sort.startsWith(k)
? `${k}_${state.sort.endsWith("_desc") ? "asc" : "desc"}`
: `${k}_desc`;
// Sync the sort dropdown
$("sort").value = state.sort;
applyFilters();
return;
}
// Row click → modal
const row = e.target.closest("[data-fac][data-name]");
if (row && !e.target.closest(".size-btn")) {
openModal(row.dataset.fac, row.dataset.name);
}
});
// ── Event delegation for movers ──
$("movers").addEventListener("click", (e) => {
const mover = e.target.closest(".mover-row");
if (!mover) return;
$("q").value = mover.dataset.name;
state.query = mover.dataset.name;
applyFilters();
$("q").focus();
});
// ── Measure controls height for sticky offset ──
updateStickyTop();
window.addEventListener("resize", updateStickyTop);
}
function updateStickyTop() {
const el = $("controls-wrap");
if (el) {
const h = el.getBoundingClientRect().height;
document.documentElement.style.setProperty("--sticky-top", h + "px");
}
}
// ─── size variant helpers ────────────────────────────
function unitKey(u) { return `${u.faction}|${u.name}`; }
function getActiveSize(u) {
const chosen = state.sizeChoice.get(unitKey(u));
if (chosen) {
const found = u.sizes.find(s => s.size === chosen);
if (found) return found;
}
return u.sizes.find(s => s.size === u.default_size) || u.sizes[0];
}
function cycleSize(u) {
const cur = getActiveSize(u).size;
const idx = u.sizes.findIndex(s => s.size === cur);
const next = u.sizes[(idx + 1) % u.sizes.length];
state.sizeChoice.set(unitKey(u), next.size);
applyFilters();
}
// ─── movers ──────────────────────────────────────────
function renderMovers() {
const aug = state.data.units.map(u => {
const a = getActiveSize(u);
return { ...u, size: a.size, original: a.original, new: a.new, change_pct: a.change_pct, change_pts: a.change_pts };
});
const drops = aug.filter(u => u.change_pct !== null && u.change_pct < 0)
.sort((a, b) => a.change_pct - b.change_pct).slice(0, 5);
const rises = aug.filter(u => u.change_pct !== null && u.change_pct > 0)
.sort((a, b) => b.change_pct - a.change_pct).slice(0, 5);
$("top-drops").innerHTML = drops.map(u => moverRow(u, "down")).join("");
$("top-rises").innerHTML = rises.map(u => moverRow(u, "up")).join("");
$("movers").hidden = false;
}
function moverRow(u, kind) {
const arrow = kind === "up" ? "↑" : "↓";
const cls = kind === "up" ? "rise" : "drop";
const pct = u.change_pct.toFixed(1);
return `
<div class="mover-row ${cls}" data-name="${escapeAttr(u.name)}">
<div class="mover-info">
<div class="mover-name">${escapeHtml(u.name)}</div>
<div class="mover-meta">${escapeHtml(u.faction_name)} · ${escapeHtml(u.size)}</div>
</div>
<div class="mover-costs">${u.original ?? "—"}${u.new ?? "—"}</div>
<div class="mover-delta ${cls}">${arrow} ${pct}%</div>
</div>
`;
}
// ─── filtering / sorting ─────────────────────────────
function applyFilters() {
const q = state.query.trim().toLowerCase();
const fac = state.faction;
const dir = state.dir;
let view = state.data.units.map(u => {
const a = getActiveSize(u);
return { ...u, size: a.size, original: a.original, new: a.new,
change_pct: a.change_pct, change_pts: a.change_pts };
});
if (q) {
view = view.filter(u =>
u.name.toLowerCase().includes(q) ||
u.faction_name.toLowerCase().includes(q) ||
u.size.toLowerCase().includes(q)
);
}
if (fac) view = view.filter(u => u.faction === fac);
if (dir === "up") view = view.filter(u => u.change_pct !== null && u.change_pct > 0);
else if (dir === "down") view = view.filter(u => u.change_pct !== null && u.change_pct < 0);
else if (dir === "no-change") view = view.filter(u => u.change_pct === 0);
else if (dir === "new-only") view = view.filter(u => u.original === null && u.new !== null);
else if (dir === "old-only") view = view.filter(u => u.original !== null && u.new === null);
const [k, d] = state.sort.endsWith("_asc") ? [state.sort.replace("_asc", ""), 1] :
state.sort.endsWith("_desc") ? [state.sort.replace("_desc", ""), -1] :
[state.sort, 1];
view.sort((a, b) => {
let x = a[k], y = b[k];
if (x === null) return 1;
if (y === null) return -1;
if (typeof x === "string") return d * x.localeCompare(y);
return d * (x - y);
});
state.view = view;
render();
}
// ─── rendering ────────────────────────────────────────
function render() {
const view = state.view;
$("count").innerHTML = `Showing <b>${view.length.toLocaleString()}</b> of ${state.data.stats.total_rows.toLocaleString()} units`;
if (view.length === 0) {
$("rows").innerHTML = `<div class="empty">No matches — try clearing filters.</div>`;
return;
}
if (state.mode === "compact") {
$("rows").innerHTML = view.map(compactRow).join("");
} else {
$("rows").innerHTML = `
<table class="row-table">
<thead>
<tr>
<th data-sort="faction">Faction</th>
<th data-sort="name">Unit</th>
<th data-sort="size">Size</th>
<th class="num" data-sort="old">Original</th>
<th class="num" data-sort="new">New</th>
<th class="num" data-sort="change_pts">Δ pts</th>
<th class="num" data-sort="change_pct">Δ %</th>
<th>Status</th>
</tr>
</thead>
<tbody>${view.map(tableRow).join("")}</tbody>
</table>
`;
}
}
function sizeControl(u) {
if (u.sizes.length <= 1) {
return `<span class="size-static">${escapeHtml(u.size)}</span>`;
}
if (u.equal_costs) {
const labels = u.sizes.map(s => s.size.replace(/\s*models?$/, "")).join(" or ");
return `<span class="size-static" title="Same cost at all sizes">${escapeHtml(labels)} models</span>`;
}
const active = u.size;
const sizeShort = active.replace(/\s*models?$/, "");
const count = u.sizes.length;
return `<button class="size-btn" data-fac="${escapeAttr(u.faction)}" data-name="${escapeAttr(u.name)}" title="Click to cycle (${count} sizes)">${escapeHtml(sizeShort)}<span class="size-btn-badge">${count}</span></button>`;
}
function tableRow(u) {
const orig = u.original ?? "—";
const newp = u.new ?? "—";
const dp = u.change_pts === null ? "—" : (u.change_pts > 0 ? `+${u.change_pts}` : u.change_pts);
const pc = u.change_pct === null ? "—" : `${u.change_pct > 0 ? "+" : ""}${u.change_pct.toFixed(1)}%`;
let pill = `<span class="pill none">—</span>`;
if (u.change_pct !== null) {
if (u.change_pct > 0) pill = `<span class="pill up">↑ ${u.change_pct.toFixed(1)}%</span>`;
else if (u.change_pct < 0) pill = `<span class="pill down">↓ ${Math.abs(u.change_pct).toFixed(1)}%</span>`;
else pill = `<span class="pill same">— 0%</span>`;
} else if (u.new !== null && u.original === null) {
pill = `<span class="pill new">NEW</span>`;
} else if (u.new === null && u.original !== null) {
pill = `<span class="pill gone">REMOVED</span>`;
}
return `
<tr data-fac="${escapeAttr(u.faction)}" data-name="${escapeAttr(u.name)}">
<td class="col-faction" data-label="Faction">${escapeHtml(u.faction_name)}</td>
<td class="col-unit" data-label="Unit">${escapeHtml(u.name)}</td>
<td class="col-size" data-label="Size">${sizeControl(u)}</td>
<td class="num" data-label="Original">${orig}</td>
<td class="num" data-label="New">${newp}</td>
<td class="num" data-label="Δ pts">${dp}</td>
<td class="num" data-label="Δ %">${pc}</td>
<td class="col-status" data-label="Status">${pill}</td>
</tr>
`;
}
function compactRow(u) {
const orig = u.original ?? "—";
const newp = u.new ?? "—";
const pc = u.change_pct;
const pcStr = pc === null ? "—" :
pc > 0 ? `<span class="pill up">↑ ${pc.toFixed(1)}%</span>` :
pc < 0 ? `<span class="pill down">↓ ${Math.abs(pc).toFixed(1)}%</span>` :
`<span class="pill same">— 0%</span>`;
return `
<div class="compact-row" data-fac="${escapeAttr(u.faction)}" data-name="${escapeAttr(u.name)}">
<div class="compact-main">
<div class="compact-name">${escapeHtml(u.name)}</div>
<div class="compact-sub">${escapeHtml(u.faction_name)} · ${sizeControl(u)}</div>
</div>
<div class="compact-nums">
<span class="compact-orig">${orig}</span>
<span class="compact-arrow">→</span>
<span class="compact-new">${newp}</span>
<span class="compact-pct">${pcStr}</span>
</div>
</div>
`;
}
// ─── modal ────────────────────────────────────────────
function openModal(fac, name) {
const u = state.data.units.find(x => x.faction === fac && x.name === name);
if (!u) return;
const factionName = state.data.faction_names[fac] || fac;
const activeSize = getActiveSize(u).size;
const backdrop = document.createElement("div");
backdrop.className = "modal-backdrop";
backdrop.innerHTML = `
<div class="modal">
<button class="modal-close" type="button" aria-label="Close">✕</button>
<div class="modal-title">${escapeHtml(name)}</div>
<div class="modal-faction">${escapeHtml(factionName)}</div>
<div class="modal-sizes-label">${u.sizes.length} size variant${u.sizes.length !== 1 ? 's' : ''}</div>
<div class="modal-variants">
${u.sizes.map(s => {
const active = activeSize === s.size;
return `
<div class="variant-row ${active ? 'active' : ''}">
<div class="variant-size">${escapeHtml(s.size)}${active ? ' <span class="variant-current">● current</span>' : ''}</div>
<div class="variant-nums">
<span class="variant-orig">${s.original ?? "—"}</span>
<span class="variant-arrow">→</span>
<span class="variant-new">${s.new ?? "—"}</span>
</div>
<div class="variant-delta">
${s.change_pct === null ? "—" :
s.change_pct > 0 ? `<span class="pill up">↑ ${s.change_pct.toFixed(1)}%</span>` :
s.change_pct < 0 ? `<span class="pill down">↓ ${Math.abs(s.change_pct).toFixed(1)}%</span>` :
`<span class="pill same">— 0%</span>`}
</div>
</div>
`;
}).join("")}
</div>
</div>
`;
document.body.appendChild(backdrop);
const close = () => backdrop.remove();
backdrop.addEventListener("click", (e) => { if (e.target === backdrop) close(); });
backdrop.querySelector(".modal-close").addEventListener("click", close);
document.addEventListener("keydown", function esc(e) {
if (e.key === "Escape") { close(); document.removeEventListener("keydown", esc); }
});
}
// ─── utils ────────────────────────────────────────────
function escapeHtml(s) {
if (s == null) return "";
return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function escapeAttr(s) { return escapeHtml(s).replace(/'/g, "&#39;"); }
boot().catch(err => {
document.body.innerHTML = `<pre style="color:#f85149;padding:20px;">${err.message}</pre>`;
});

1
site/data.json Normal file

File diff suppressed because one or more lines are too long

90
site/index.html Normal file
View File

@@ -0,0 +1,90 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#0a0e14">
<title>WH40K Points Comparator — MFM v4.3</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div class="header-inner">
<div class="header-text">
<h1>Points Comparator</h1>
<p class="subtitle">Codex vs. MFM v4.3 · 30 factions · 1,462 units</p>
</div>
<div id="meta" class="meta"></div>
</div>
</header>
<div class="controls-wrap" id="controls-wrap">
<section class="controls">
<div class="control control-search">
<label for="q">Search</label>
<input id="q" type="search" placeholder="Unit name…" autocomplete="off">
</div>
<div class="control">
<label for="faction">Faction</label>
<select id="faction">
<option value="">All</option>
</select>
</div>
<div class="control">
<label for="dir">Change</label>
<select id="dir">
<option value="">All</option>
<option value="up">↑ Costlier</option>
<option value="down">↓ Cheaper</option>
<option value="no-change">— No change</option>
<option value="new-only">+ New only</option>
<option value="old-only"> Removed</option>
</select>
</div>
<div class="control">
<label for="sort">Sort</label>
<select id="sort">
<option value="change_pct_desc">% change (worst)</option>
<option value="change_pct_asc">% change (best)</option>
<option value="name">Name (A→Z)</option>
<option value="faction">Faction (A→Z)</option>
<option value="new">New cost (high→low)</option>
<option value="old">Original cost (high→low)</option>
</select>
</div>
<div class="control control-view">
<label for="view">View</label>
<select id="view">
<option value="table">Table</option>
<option value="compact">Compact</option>
</select>
</div>
</section>
</div>
<main>
<section class="movers" id="movers" hidden>
<div class="movers-header">
<h2>Biggest movers</h2>
<p>Top 5 drops & rises</p>
</div>
<div class="movers-grid">
<div class="movers-col drops" id="top-drops"></div>
<div class="movers-col rises" id="top-rises"></div>
</div>
</section>
<section class="results">
<div id="count" class="count"></div>
<div id="rows"></div>
</section>
</main>
<footer>
<p>Data: <code>Full_armies_10th.pdf</code> (codex) +
<code>mfm.warhammer-community.com</code> (live MFM). Weapon upgrades excluded.</p>
</footer>
<script src="app.js" type="module"></script>
</body>
</html>

538
site/style.css Normal file
View File

@@ -0,0 +1,538 @@
/* ── WH40K Points Comparator — clean, sharp, effective ── */
:root {
--bg: #0a0e14;
--bg-alt: #0d1117;
--panel: #11161e;
--panel-2: #161c26;
--panel-3: #1c2330;
--border: #232b38;
--border-2: #2d3744;
--text: #e6edf3;
--text-dim: #adbac7;
--muted: #7d8590;
--accent: #3b82f6;
--accent-2: #60a5fa;
--green: #3fb950;
--red: #f85149;
--grey: #6e7681;
--gold: #e3b341;
--green-bg: rgba(63, 185, 80, 0.12);
--red-bg: rgba(248, 81, 73, 0.12);
--gold-bg: rgba(227, 179, 65, 0.10);
--radius: 10px;
--radius-sm: 6px;
--sticky-top: 0px;
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Helvetica, Arial, sans-serif;
--mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
html, body { overflow-x: hidden; }
body {
font-family: var(--font);
background: var(--bg);
color: var(--text);
font-size: 14px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/* ── Header ── */
header {
background: var(--panel);
border-bottom: 1px solid var(--border);
}
.header-inner {
max-width: 1400px;
margin: 0 auto;
padding: 18px 24px;
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
}
h1 {
font-size: 1.3rem;
font-weight: 700;
letter-spacing: -0.02em;
margin: 0;
}
.subtitle {
color: var(--muted);
font-size: 0.8rem;
margin: 2px 0 0;
}
.meta {
font-size: 0.75rem;
color: var(--muted);
white-space: nowrap;
}
/* ── Controls (sticky) ── */
.controls-wrap {
position: sticky;
top: 0;
z-index: 20;
background: rgba(10, 14, 20, 0.92);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
}
.controls {
max-width: 1400px;
margin: 0 auto;
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr 0.7fr;
gap: 10px;
padding: 12px 24px;
}
.control { display: flex; flex-direction: column; gap: 4px; }
.control label {
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
font-weight: 600;
}
.control input, .control select {
background: var(--panel-2);
color: var(--text);
border: 1px solid var(--border-2);
border-radius: var(--radius-sm);
padding: 8px 12px;
font-size: 0.9rem;
font-family: var(--font);
min-height: 38px;
transition: border-color 0.15s, box-shadow 0.15s;
width: 100%;
cursor: pointer;
}
.control input {
cursor: text;
}
.control input:focus, .control select:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
.control select {
appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%237d8590'%3E%3Cpath d='M6 9L1 4h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
padding-right: 30px;
}
/* ── Movers ── */
main {
max-width: 1400px;
margin: 0 auto;
padding: 20px 24px 40px;
}
.movers {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 16px 18px;
margin-bottom: 20px;
}
.movers-header h2 {
font-size: 0.95rem;
font-weight: 700;
letter-spacing: -0.01em;
margin: 0;
}
.movers-header p {
font-size: 0.75rem;
color: var(--muted);
margin: 2px 0 14px;
}
.movers-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.movers-col { display: flex; flex-direction: column; gap: 5px; }
.movers-col.drops .mover-row {
border-left: 3px solid var(--red);
}
.movers-col.rises .mover-row {
border-left: 3px solid var(--green);
}
.mover-row {
display: grid;
grid-template-columns: 1fr auto auto;
gap: 10px;
align-items: center;
padding: 8px 12px;
border-radius: var(--radius-sm);
font-size: 0.82rem;
cursor: pointer;
background: var(--panel-2);
border: 1px solid var(--border);
transition: border-color 0.15s, background 0.15s;
}
.mover-row:hover { border-color: var(--border-2); background: var(--panel-3); }
.mover-name { font-weight: 600; }
.mover-meta { color: var(--muted); font-size: 0.72rem; margin-top: 1px; }
.mover-costs {
font-family: var(--mono);
font-size: 0.8rem;
color: var(--text-dim);
white-space: nowrap;
}
.mover-delta {
font-family: var(--mono);
font-weight: 700;
font-size: 0.85rem;
white-space: nowrap;
}
.mover-delta.drop { color: var(--red); }
.mover-delta.rise { color: var(--green); }
/* ── Count ── */
.count {
color: var(--muted);
font-size: 0.82rem;
margin-bottom: 10px;
}
.count b { color: var(--text); font-weight: 600; }
/* ── Table ── */
.row-table {
width: 100%;
border-collapse: collapse;
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.row-table thead { position: sticky; top: var(--sticky-top); z-index: 5; }
.row-table th {
background: var(--panel-3);
color: var(--muted);
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
padding: 10px 12px;
text-align: left;
border-bottom: 1px solid var(--border-2);
cursor: pointer;
user-select: none;
white-space: nowrap;
}
.row-table th:hover { color: var(--text); }
.row-table th.num { text-align: right; }
.row-table td {
padding: 8px 12px;
border-bottom: 1px solid var(--border);
font-size: 0.85rem;
}
.row-table tr:last-child td { border-bottom: none; }
.row-table tbody tr {
cursor: pointer;
transition: background 0.1s;
}
.row-table tbody tr:hover { background: rgba(59, 130, 246, 0.04); }
.row-table .col-faction { color: var(--text-dim); font-size: 0.8rem; }
.row-table .col-unit { font-weight: 600; }
.row-table .col-size { white-space: nowrap; }
.row-table .col-status { white-space: nowrap; }
.num {
text-align: right;
font-family: var(--mono);
font-variant-numeric: tabular-nums;
}
.muted { color: var(--muted); }
/* ── Pills ── */
.pill {
display: inline-block;
padding: 2px 8px;
border-radius: 20px;
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.02em;
white-space: nowrap;
}
.pill.up { background: var(--green-bg); color: var(--green); }
.pill.down { background: var(--red-bg); color: var(--red); }
.pill.same { background: rgba(110,118,129,0.12); color: var(--grey); }
.pill.new { background: rgba(59,130,246,0.15); color: var(--accent-2); }
.pill.gone { background: rgba(110,118,129,0.12); color: var(--muted); }
.pill.none { background: transparent; color: var(--muted); }
/* ── Size toggle ── */
.size-btn {
background: rgba(59, 130, 246, 0.12);
color: var(--accent-2);
border: 1px solid rgba(59, 130, 246, 0.3);
border-radius: var(--radius-sm);
padding: 4px 10px 4px 8px;
font-size: 0.78rem;
font-weight: 600;
cursor: pointer;
font-family: var(--font);
white-space: nowrap;
display: inline-flex;
align-items: center;
gap: 4px;
transition: all 0.15s;
}
.size-btn:hover {
background: rgba(59, 130, 246, 0.22);
border-color: var(--accent-2);
}
.size-btn:active { transform: scale(0.96); }
.size-btn-badge {
background: rgba(59, 130, 246, 0.3);
color: var(--accent-2);
border-radius: 8px;
padding: 0 5px;
font-size: 0.65rem;
font-weight: 700;
min-width: 16px;
text-align: center;
}
.size-static {
color: var(--muted);
font-size: 0.8rem;
}
/* ── Compact mode ── */
.compact-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
margin-bottom: 6px;
cursor: pointer;
transition: border-color 0.15s;
}
.compact-row:hover { border-color: var(--accent); }
.compact-main { flex: 1; min-width: 0; }
.compact-name { font-weight: 600; font-size: 0.9rem; }
.compact-sub { color: var(--muted); font-size: 0.75rem; margin-top: 2px;
display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.compact-nums {
display: flex;
align-items: center;
gap: 8px;
font-family: var(--mono);
font-size: 0.82rem;
white-space: nowrap;
margin-left: 12px;
}
.compact-orig { color: var(--muted); }
.compact-arrow { color: var(--muted); }
.compact-new { font-weight: 600; }
/* ── Empty ── */
.empty {
text-align: center;
padding: 48px 20px;
color: var(--muted);
font-size: 0.9rem;
}
/* ── Footer ── */
footer {
border-top: 1px solid var(--border);
color: var(--muted);
font-size: 0.78rem;
padding: 20px 24px;
}
footer code {
color: var(--accent-2);
background: var(--panel-2);
padding: 1px 6px;
border-radius: 4px;
font-size: 0.78rem;
}
/* ── Modal ── */
.modal-backdrop {
position: fixed; inset: 0;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px);
display: flex; align-items: center; justify-content: center;
z-index: 100;
padding: 20px;
}
.modal {
background: var(--panel);
border: 1px solid var(--border-2);
border-radius: var(--radius);
max-width: 560px;
width: 100%;
max-height: 80vh;
overflow: auto;
padding: 24px;
position: relative;
}
.modal-title {
font-size: 1.15rem;
font-weight: 700;
letter-spacing: -0.01em;
}
.modal-faction {
color: var(--muted);
font-size: 0.85rem;
margin: 2px 0 16px;
}
.modal-sizes-label {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
font-weight: 600;
margin-bottom: 8px;
}
.modal-close {
position: absolute;
top: 16px; right: 16px;
background: var(--panel-2);
border: 1px solid var(--border-2);
color: var(--muted);
border-radius: var(--radius-sm);
width: 32px; height: 32px;
cursor: pointer;
font-size: 0.85rem;
display: flex; align-items: center; justify-content: center;
transition: all 0.15s;
}
.modal-close:hover { color: var(--text); border-color: var(--text); }
.variant-row {
display: grid;
grid-template-columns: 1fr auto auto;
gap: 12px;
align-items: center;
padding: 10px 12px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
margin-bottom: 6px;
background: var(--panel-2);
}
.variant-row.active {
border-color: var(--accent);
background: rgba(59, 130, 246, 0.06);
}
.variant-size { font-weight: 500; font-size: 0.88rem; }
.variant-current { color: var(--accent-2); font-size: 0.7rem; margin-left: 4px; }
.variant-nums {
font-family: var(--mono);
font-size: 0.82rem;
display: flex;
align-items: center;
gap: 6px;
}
.variant-orig { color: var(--muted); }
.variant-arrow { color: var(--muted); }
.variant-new { font-weight: 600; }
.variant-delta { white-space: nowrap; }
/* ── Responsive: tablet ── */
@media (max-width: 1000px) {
.controls { grid-template-columns: 2fr 1fr 1fr 1fr; }
.control-view { display: none; }
.movers-grid { grid-template-columns: 1fr; }
}
@media (max-width: 768px) {
.controls { grid-template-columns: 1fr 1fr; gap: 8px; padding: 10px 16px; }
.control-search { grid-column: 1 / -1; }
main { padding: 16px 16px 32px; }
.header-inner { padding: 14px 16px; }
h1 { font-size: 1.1rem; }
.meta { display: none; }
.mover-row { grid-template-columns: 1fr auto; }
.mover-costs { display: none; }
}
/* ── Responsive: phone ── */
@media (max-width: 600px) {
body { font-size: 15px; }
/* Controls: single column */
.controls {
grid-template-columns: 1fr;
gap: 6px;
padding: 10px 14px;
}
.control input, .control select {
min-height: 44px;
font-size: 1rem;
padding: 10px 14px;
}
.control select { padding-right: 34px; }
/* Table → card layout */
.row-table thead { display: none; }
.row-table, .row-table tbody, .row-table tr, .row-table td {
display: block;
width: 100%;
}
.row-table { border: none; background: transparent; }
.row-table tbody tr {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
margin-bottom: 8px;
padding: 4px 0;
}
.row-table td {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 14px;
border-bottom: 1px solid var(--border);
font-size: 0.88rem;
text-align: right;
}
.row-table td:last-child { border-bottom: none; }
.row-table td::before {
content: attr(data-label);
color: var(--muted);
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
}
.row-table td.col-unit::before { display: none; }
.row-table td.col-unit {
font-size: 1rem;
font-weight: 700;
padding-top: 12px;
border-bottom: 1px solid var(--border-2);
}
.row-table td.col-faction::before { display: none; }
.row-table td.col-faction {
color: var(--accent-2);
font-size: 0.75rem;
padding-top: 8px;
padding-bottom: 2px;
border-bottom: none;
}
/* Compact mode: stack */
.compact-row { flex-direction: column; align-items: flex-start; gap: 6px; }
.compact-nums { margin-left: 0; flex-wrap: wrap; }
/* Modal: full screen */
.modal-backdrop { padding: 0; }
.modal {
max-width: 100%;
max-height: 100vh;
height: 100vh;
border-radius: 0;
padding: 16px;
}
}

BIN
wh40k-factions-csv.tar.gz Normal file

Binary file not shown.

BIN
wh40k_factions_all.xlsx Normal file

Binary file not shown.