167 lines
9.7 KiB
JavaScript
167 lines
9.7 KiB
JavaScript
|
|
/**
|
|||
|
|
* 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');
|
|||
|
|
|
|||
|
|
if (fs.existsSync(outPath)) {
|
|||
|
|
console.log(` ✓ skip ${name}`);
|
|||
|
|
ok++;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 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);
|