Files
helldivers/scripts/download-icons.js
2026-04-03 11:59:24 +02:00

161 lines
9.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Download Helldivers 2 stratagem SVG icons from:
* github.com/nvigneux/Helldivers-2-Stratagems-icons-svg
*
* Run once: node scripts/download-icons.js
*/
import https from 'https';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ICONS_DIR = path.join(__dirname, '..', 'public', 'icons');
const BASE_URL = 'https://raw.githubusercontent.com/nvigneux/Helldivers-2-Stratagems-icons-svg/master';
// Map: our stratagem name → { repo category folder, repo filename (without .svg) }
const ICON_MAP = [
// ── General / PAC ────────────────────────────────────────────────────────
['Reinforce', 'General Stratagems', 'Reinforce'],
['Resupply', 'General Stratagems', 'Resupply'],
['SOS Beacon', 'General Stratagems', 'SOS Beacon'],
['Hellbomb', 'General Stratagems', 'Hellbomb'],
['SEAF Artillery', 'General Stratagems', 'SEAF Artillery'],
['Upload Data', 'General Stratagems', 'Upload Data'],
['Prospecting Drill', 'General Stratagems', 'Prospecting Drill'],
['Orbital Illumination Flare', 'General Stratagems', 'Orbital Illumination Flare'],
// ── Orbital Cannons ───────────────────────────────────────────────────────
['Orbital Gatling Barrage', 'Orbital Cannons', 'Orbital Gatling Barrage'],
['Orbital Airburst Strike', 'Orbital Cannons', 'Orbital Airburst Strike'],
['Orbital 120MM HE Barrage', 'Orbital Cannons', 'Orbital 120MM HE Barrage'],
['Orbital 380MM HE Barrage', 'Orbital Cannons', 'Orbital 380MM HE Barrage'],
['Orbital Walking Barrage', 'Orbital Cannons', 'Orbital Walking Barrage'],
['Orbital Laser', 'Orbital Cannons', 'Orbital Laser'],
['Orbital Railcannon Strike', 'Orbital Cannons', 'Orbital Railcannon Strike'],
// ── Bridge ────────────────────────────────────────────────────────────────
['Orbital Precision Strike', 'Bridge', 'Orbital Precision Strike'],
['Orbital Gas Strike', 'Bridge', 'Orbital Gas Strike'],
['Orbital EMS Strike', 'Bridge', 'Orbital EMS Strike'],
['Orbital Smoke Strike', 'Bridge', 'Orbital Smoke Strike'],
['Tesla Tower', 'Bridge', 'Tesla Tower'],
['Shield Generator Relay', 'Bridge', 'Shield Generator Relay'],
['HMG Emplacement', 'Bridge', 'HMG Emplacement'],
// ── Hangar ────────────────────────────────────────────────────────────────
['Eagle Strafing Run', 'Hangar', 'Eagle Strafing Run'],
['Eagle Airstrike', 'Hangar', 'Eagle Airstrike'],
['Eagle Cluster Bomb', 'Hangar', 'Eagle Cluster Bomb'],
['Eagle Napalm Airstrike', 'Hangar', 'Eagle Napalm Airstrike'],
['LIFT-850 Jump Pack', 'Hangar', 'Jump Pack'],
['Eagle Smoke Strike', 'Hangar', 'Eagle Smoke Strike'],
['Eagle 110MM Rocket Pods', 'Hangar', 'Eagle 110MM Rocket Pods'],
['Eagle 500KG Bomb', 'Hangar', 'Eagle 500KG Bomb'],
['Eagle Rearm', 'Hangar', 'Eagle Rearm'],
// ── PAC Support Weapons ─────────────────────────────────────────────────
['Machine Gun', 'Patriotic Administration Center', 'Machine Gun'],
['Anti-Materiel Rifle', 'Patriotic Administration Center', 'Anti-Materiel Rifle'],
['Stalwart', 'Patriotic Administration Center', 'Stalwart'],
['Expendable Anti-Tank', 'Patriotic Administration Center', 'Expendable Anti-Tank'],
['Recoilless Rifle', 'Patriotic Administration Center', 'Recoilless Rifle'],
['Flamethrower', 'Patriotic Administration Center', 'Flamethrower'],
['Autocannon', 'Patriotic Administration Center', 'Autocannon'],
['Heavy Machine Gun', 'Patriotic Administration Center', 'Heavy Machine Gun'],
['Airburst Rocket Launcher', 'Patriotic Administration Center', 'Airburst Rocket Launcher'],
['Commando', 'Patriotic Administration Center', 'Commando'],
['Railgun', 'Patriotic Administration Center', 'Railgun'],
['Spear', 'Patriotic Administration Center', 'Spear'],
// ── Engineering Bay ───────────────────────────────────────────────────────
['Quasar Cannon', 'Engineering Bay', 'Quasar Cannon'],
['Arc Thrower', 'Engineering Bay', 'Arc Thrower'],
['Laser Cannon', 'Engineering Bay', 'Laser Cannon'],
['Grenade Launcher', 'Engineering Bay', 'Grenade Launcher'],
['Supply Pack', 'Engineering Bay', 'Supply Pack'],
['Guard Dog Rover', 'Engineering Bay', 'Guard Dog Rover'],
['Ballistic Shield Backpack', 'Engineering Bay', 'Ballistic Shield Backpack'],
['Shield Generator Pack', 'Engineering Bay', 'Shield Generator Pack'],
['Anti-Personnel Minefield', 'Engineering Bay', 'Anti-Personnel Minefield'],
['Incendiary Mines', 'Engineering Bay', 'Incendiary Mines'],
['Anti-Tank Mines', 'Engineering Bay', 'Anti-Tank Mines'],
// ── Robotics Workshop ─────────────────────────────────────────────────────
['Machine Gun Sentry', 'Robotics Workshop', 'Machine Gun Sentry'],
['Gatling Sentry', 'Robotics Workshop', 'Gatling Sentry'],
['Mortar Sentry', 'Robotics Workshop', 'Mortar Sentry'],
['Guard Dog', 'Robotics Workshop', 'Guard Dog'],
['Autocannon Sentry', 'Robotics Workshop', 'Autocannon Sentry'],
['Rocket Sentry', 'Robotics Workshop', 'Rocket Sentry'],
['EMS Mortar Sentry', 'Robotics Workshop', 'EMS Mortar Sentry'],
['Patriot Exosuit', 'Robotics Workshop', 'Patriot Exosuit'],
['Emancipator Exosuit', 'Robotics Workshop', 'Emancipator Exosuit'],
// ── Urban Legends / Defensive ─────────────────────────────────────────────
['Directional Shield', 'Urban Legends', 'Directional Shield'],
['Anti-Tank Emplacement', 'Urban Legends', 'Anti-Tank Emplacement'],
];
function fetchURL(url) {
return new Promise((resolve, reject) => {
const req = https.get(url, (res) => {
if (res.statusCode === 301 || res.statusCode === 302) {
return fetchURL(res.headers.location).then(resolve).catch(reject);
}
if (res.statusCode !== 200) {
reject(new Error(`HTTP ${res.statusCode} for ${url}`));
res.resume();
return;
}
const chunks = [];
res.on('data', d => chunks.push(d));
res.on('end', () => resolve(Buffer.concat(chunks)));
});
req.on('error', reject);
req.setTimeout(10000, () => { req.destroy(); reject(new Error('Timeout: ' + url)); });
});
}
async function downloadAll() {
let ok = 0, fail = 0;
const failed = [];
for (const [name, folder, file] of ICON_MAP) {
// Local output: public/icons/<slug>.svg (flat directory, slug = name)
const slug = name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
const outPath = path.join(ICONS_DIR, slug + '.svg');
// Build GitHub raw URL (spaces → %20)
const encoded = encodeURIComponent(folder) + '/' + encodeURIComponent(file + '.svg');
const url = `${BASE_URL}/${encoded}`;
try {
const buf = await fetchURL(url);
fs.writeFileSync(outPath, buf);
console.log(` ↓ ok ${name}`);
ok++;
} catch (err) {
console.log(` ✗ FAIL ${name} (${err.message})`);
fail++;
failed.push({ name, url });
}
// Be polite to GitHub CDN
await new Promise(r => setTimeout(r, 80));
}
console.log(`\nDone: ${ok} ok, ${fail} failed`);
if (failed.length) {
console.log('Failed:');
failed.forEach(f => console.log(` ${f.name}${f.url}`));
}
// Output slug map for server.js
const slugMap = {};
for (const [name] of ICON_MAP) {
const slug = name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
if (fs.existsSync(path.join(ICONS_DIR, slug + '.svg'))) {
slugMap[name] = '/icons/' + slug + '.svg';
}
}
const mapPath = path.join(ICONS_DIR, '_map.json');
fs.writeFileSync(mapPath, JSON.stringify(slugMap, null, 2));
console.log(`\nIcon map written to ${mapPath}`);
}
downloadAll().catch(console.error);