Add comprehensive weapons dataset and scraper for 40k RPG Tools
- Implemented a comprehensive weapons dataset in `comprehensive-weapons.js` with detailed weapon stats, categories, and requirements. - Created a new scraper script `scrape-40k-tools.js` to fetch and parse weapon data from the 40k RPG Tools website, including categorization and conversion to game format. - Added functionality to write the scraped data to JSON files for further use in the application. - Included error handling and logging for better debugging and user feedback during the scraping process.
This commit is contained in:
@@ -4339,3 +4339,140 @@ Connected to MongoDB
|
||||
[2025-08-15T20:53:49.092Z] SESSION: Validate success afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106 anders
|
||||
[2025-08-15T20:53:49.124Z] DB: getAllPlayers - start
|
||||
[2025-08-15T20:53:49.124Z] DB: getAllPlayers - resultCount 5
|
||||
[2025-08-15T21:22:45.188Z] DB: getSession - start afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106
|
||||
[2025-08-15T21:22:45.189Z] DB: getSession - ok afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106
|
||||
[2025-08-15T21:22:45.189Z] SESSION: Validate success afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106 anders
|
||||
[2025-08-15T21:22:46.620Z] SESSION: Error Cannot read properties of undefined (reading 'gmSecret')
|
||||
[2025-08-15T21:22:46.658Z] DB: getAllPlayers - start
|
||||
[2025-08-15T21:22:46.659Z] DB: getAllPlayers - resultCount 5
|
||||
[2025-08-15T21:29:23.567Z] DB: getSession - start afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106
|
||||
[2025-08-15T21:29:23.567Z] DB: getSession - ok afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106
|
||||
[2025-08-15T21:29:23.567Z] SESSION: Validate success afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106 anders
|
||||
[2025-08-15T21:29:25.371Z] SESSION: Error Cannot read properties of undefined (reading 'gmSecret')
|
||||
[2025-08-15T21:29:25.409Z] DB: getAllPlayers - start
|
||||
[2025-08-15T21:29:25.409Z] DB: getAllPlayers - resultCount 5
|
||||
[2025-08-15T21:29:36.385Z] DB: getSession - start afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106
|
||||
[2025-08-15T21:29:36.386Z] DB: getSession - ok afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106
|
||||
[2025-08-15T21:29:36.386Z] SESSION: Validate success afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106 anders
|
||||
[2025-08-15T21:29:38.354Z] DB: deleteSession - start afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106
|
||||
[2025-08-15T21:29:38.354Z] DB: deleteSession - changes 1 afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106
|
||||
[2025-08-15T21:29:38.354Z] SESSION: Logout success afa1764cd4e0f771ff1de452c3acc4c584b0d3d3eb46c35713dafe1deee88106
|
||||
[2025-08-15T21:29:43.475Z] DB: getAllPlayers - start
|
||||
[2025-08-15T21:29:43.475Z] DB: getAllPlayers - resultCount 5
|
||||
[2025-08-15T21:29:47.831Z] DB: getPlayerByName - start claes
|
||||
[2025-08-15T21:29:47.831Z] DB: getPlayerByName - found claes
|
||||
[2025-08-15T21:29:47.831Z] DB: createSession - start 1ccc2ff25d47abecbaac311d49e8856631b9a22b9da5cb9a7055c3383c73597e expiresAt 2025-08-16T21:29:47.831Z
|
||||
[2025-08-15T21:29:47.831Z] DB: createSession - done 1ccc2ff25d47abecbaac311d49e8856631b9a22b9da5cb9a7055c3383c73597e
|
||||
[2025-08-15T21:29:47.831Z] SESSION: Login success claes 1ccc2ff25d47abecbaac311d49e8856631b9a22b9da5cb9a7055c3383c73597e
|
||||
[2025-08-15T21:29:47.875Z] DB: getSession - start 1ccc2ff25d47abecbaac311d49e8856631b9a22b9da5cb9a7055c3383c73597e
|
||||
[2025-08-15T21:29:47.875Z] DB: getSession - ok 1ccc2ff25d47abecbaac311d49e8856631b9a22b9da5cb9a7055c3383c73597e
|
||||
[2025-08-15T21:29:47.875Z] SESSION: Validate success 1ccc2ff25d47abecbaac311d49e8856631b9a22b9da5cb9a7055c3383c73597e claes
|
||||
[2025-08-15T21:29:49.140Z] SESSION: Error Cannot read properties of undefined (reading 'gmSecret')
|
||||
[2025-08-15T21:29:49.173Z] DB: getAllPlayers - start
|
||||
[2025-08-15T21:29:49.174Z] DB: getAllPlayers - resultCount 5
|
||||
[2025-08-15T21:31:23.013Z] SESSION: Error Cannot read properties of undefined (reading 'gmSecret')
|
||||
[2025-08-15T21:31:23.048Z] DB: getAllPlayers - start
|
||||
[2025-08-15T21:31:23.049Z] DB: getAllPlayers - resultCount 5
|
||||
[2025-08-15T21:32:01.247Z] SESSION: Error Cannot read properties of undefined (reading 'gmSecret')
|
||||
[2025-08-15T21:32:07.111Z] DB: deleteSession - start 1ccc2ff25d47abecbaac311d49e8856631b9a22b9da5cb9a7055c3383c73597e
|
||||
[2025-08-15T21:32:07.112Z] DB: deleteSession - changes 1 1ccc2ff25d47abecbaac311d49e8856631b9a22b9da5cb9a7055c3383c73597e
|
||||
[2025-08-15T21:32:07.112Z] SESSION: Logout success 1ccc2ff25d47abecbaac311d49e8856631b9a22b9da5cb9a7055c3383c73597e
|
||||
[2025-08-15T21:32:07.150Z] SESSION: Error Cannot read properties of undefined (reading 'gmSecret')
|
||||
[2025-08-15T21:32:23.503Z] DB: getPlayerByName - start gm
|
||||
[2025-08-15T21:32:23.504Z] DB: getPlayerByName - found gm
|
||||
[2025-08-15T21:32:23.504Z] DB: createSession - start 6f79ce5355731e67d99dd56e343d14a1f27f2185a66b51a06f8912ca33a66595 expiresAt 2025-08-16T21:32:23.504Z
|
||||
[2025-08-15T21:32:23.504Z] DB: createSession - done 6f79ce5355731e67d99dd56e343d14a1f27f2185a66b51a06f8912ca33a66595
|
||||
[2025-08-15T21:32:23.504Z] SESSION: Login success gm 6f79ce5355731e67d99dd56e343d14a1f27f2185a66b51a06f8912ca33a66595
|
||||
[2025-08-15T21:32:23.553Z] SESSION: GM bypass accepted GET /api/players
|
||||
[2025-08-15T21:32:23.553Z] DB: getAllPlayers - start
|
||||
[2025-08-15T21:32:23.554Z] DB: getAllPlayers - resultCount 5
|
||||
[2025-08-15T21:32:23.554Z] API: Fetch all players (public) Found 5 players
|
||||
[2025-08-15T21:32:23.555Z] DB: getSession - start 6f79ce5355731e67d99dd56e343d14a1f27f2185a66b51a06f8912ca33a66595
|
||||
[2025-08-15T21:32:23.555Z] DB: getSession - ok 6f79ce5355731e67d99dd56e343d14a1f27f2185a66b51a06f8912ca33a66595
|
||||
[2025-08-15T21:32:23.555Z] SESSION: Validate success 6f79ce5355731e67d99dd56e343d14a1f27f2185a66b51a06f8912ca33a66595 gm
|
||||
[2025-08-15T21:32:33.300Z] SESSION: GM bypass accepted GET /api/players
|
||||
[2025-08-15T21:32:33.300Z] DB: getAllPlayers - start
|
||||
[2025-08-15T21:32:33.300Z] DB: getAllPlayers - resultCount 5
|
||||
[2025-08-15T21:32:33.300Z] API: Fetch all players (public) Found 5 players
|
||||
[2025-08-15T21:32:51.231Z] DB: deleteSession - start 6f79ce5355731e67d99dd56e343d14a1f27f2185a66b51a06f8912ca33a66595
|
||||
[2025-08-15T21:32:51.231Z] DB: deleteSession - changes 1 6f79ce5355731e67d99dd56e343d14a1f27f2185a66b51a06f8912ca33a66595
|
||||
[2025-08-15T21:32:51.231Z] SESSION: Logout success 6f79ce5355731e67d99dd56e343d14a1f27f2185a66b51a06f8912ca33a66595
|
||||
[2025-08-15T21:32:51.287Z] SESSION: Error Cannot read properties of undefined (reading 'gmSecret')
|
||||
[2025-08-15T21:32:55.693Z] DB: getPlayerByName - start anders
|
||||
[2025-08-15T21:32:55.694Z] DB: getPlayerByName - found anders
|
||||
[2025-08-15T21:32:55.694Z] DB: createSession - start c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c expiresAt 2025-08-16T21:32:55.694Z
|
||||
[2025-08-15T21:32:55.694Z] DB: createSession - done c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c
|
||||
[2025-08-15T21:32:55.694Z] SESSION: Login success anders c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c
|
||||
[2025-08-15T21:32:56.149Z] SESSION: Error Cannot read properties of undefined (reading 'gmSecret')
|
||||
[2025-08-15T21:32:56.151Z] DB: getSession - start c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c
|
||||
[2025-08-15T21:32:56.151Z] DB: getSession - ok c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c
|
||||
[2025-08-15T21:32:56.151Z] SESSION: Validate success c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c anders
|
||||
[2025-08-15T21:33:45.027Z] DB: getSession - start c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c
|
||||
[2025-08-15T21:33:45.027Z] DB: getSession - ok c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c
|
||||
[2025-08-15T21:33:45.027Z] SESSION: Valid c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c anders PUT /api/players/anders
|
||||
[2025-08-15T21:33:45.028Z] DB: getSession - start c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c
|
||||
[2025-08-15T21:33:45.028Z] DB: getSession - ok c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c
|
||||
[2025-08-15T21:33:45.028Z] SESSION: Valid c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c anders PUT /api/players/anders
|
||||
[2025-08-15T21:33:45.028Z] DB: getPlayerByName - start anders
|
||||
[2025-08-15T21:33:45.028Z] DB: getPlayerByName - found anders
|
||||
[2025-08-15T21:33:45.028Z] DB: updatePlayer - start anders
|
||||
[2025-08-15T21:33:45.029Z] DB: updatePlayer - changes 1 anders
|
||||
[2025-08-15T21:33:45.029Z] DB: getPlayerByName - start anders
|
||||
[2025-08-15T21:33:45.029Z] DB: getPlayerByName - found anders
|
||||
[2025-08-15T21:33:45.029Z] API: Updated player anders
|
||||
[2025-08-15T21:33:45.070Z] SESSION: Error Cannot read properties of undefined (reading 'gmSecret')
|
||||
[2025-08-15T21:33:56.877Z] DB: deleteSession - start c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c
|
||||
[2025-08-15T21:33:56.877Z] DB: deleteSession - changes 1 c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c
|
||||
[2025-08-15T21:33:56.877Z] SESSION: Logout success c57dde4a145cf03e40ac371e371fee63b1e2a6b1559031171201e15d1dc1151c
|
||||
[2025-08-15T21:33:56.924Z] SESSION: Error Cannot read properties of undefined (reading 'gmSecret')
|
||||
[2025-08-15T21:34:02.953Z] DB: getPlayerByName - start gm
|
||||
[2025-08-15T21:34:02.953Z] DB: getPlayerByName - found gm
|
||||
[2025-08-15T21:34:02.953Z] DB: createSession - start b940d74752f8a862fc4379711bd037488c0004b7cafd26b27cf8b07e422d01f2 expiresAt 2025-08-16T21:34:02.953Z
|
||||
[2025-08-15T21:34:02.953Z] DB: createSession - done b940d74752f8a862fc4379711bd037488c0004b7cafd26b27cf8b07e422d01f2
|
||||
[2025-08-15T21:34:02.953Z] SESSION: Login success gm b940d74752f8a862fc4379711bd037488c0004b7cafd26b27cf8b07e422d01f2
|
||||
[2025-08-15T21:34:02.992Z] SESSION: GM bypass accepted GET /api/players
|
||||
[2025-08-15T21:34:02.993Z] DB: getAllPlayers - start
|
||||
[2025-08-15T21:34:02.993Z] DB: getAllPlayers - resultCount 5
|
||||
[2025-08-15T21:34:02.993Z] API: Fetch all players (public) Found 5 players
|
||||
[2025-08-15T21:34:02.999Z] DB: getSession - start b940d74752f8a862fc4379711bd037488c0004b7cafd26b27cf8b07e422d01f2
|
||||
[2025-08-15T21:34:02.999Z] DB: getSession - ok b940d74752f8a862fc4379711bd037488c0004b7cafd26b27cf8b07e422d01f2
|
||||
[2025-08-15T21:34:02.999Z] SESSION: Validate success b940d74752f8a862fc4379711bd037488c0004b7cafd26b27cf8b07e422d01f2 gm
|
||||
[2025-08-15T21:34:12.502Z] SESSION: GM bypass accepted PUT /api/players/anders
|
||||
[2025-08-15T21:34:12.502Z] SESSION: GM bypass accepted PUT /api/players/anders
|
||||
[2025-08-15T21:34:12.503Z] DB: getPlayerByName - start anders
|
||||
[2025-08-15T21:34:12.503Z] DB: getPlayerByName - found anders
|
||||
[2025-08-15T21:34:12.503Z] DB: updatePlayer - start anders
|
||||
[2025-08-15T21:34:12.503Z] DB: updatePlayer - changes 1 anders
|
||||
[2025-08-15T21:34:12.503Z] DB: getPlayerByName - start anders
|
||||
[2025-08-15T21:34:12.503Z] DB: getPlayerByName - found anders
|
||||
[2025-08-15T21:34:12.503Z] API: Updated player anders
|
||||
[2025-08-15T21:34:12.545Z] SESSION: GM bypass accepted GET /api/players
|
||||
[2025-08-15T21:34:12.545Z] DB: getAllPlayers - start
|
||||
[2025-08-15T21:34:12.545Z] DB: getAllPlayers - resultCount 5
|
||||
[2025-08-15T21:34:12.545Z] API: Fetch all players (public) Found 5 players
|
||||
[2025-08-15T21:34:45.147Z] SESSION: GM bypass accepted PUT /api/players/gm
|
||||
[2025-08-15T21:34:45.148Z] SESSION: GM bypass accepted PUT /api/players/gm
|
||||
[2025-08-15T21:34:45.148Z] DB: getPlayerByName - start gm
|
||||
[2025-08-15T21:34:45.148Z] DB: getPlayerByName - found gm
|
||||
[2025-08-15T21:34:45.148Z] DB: updatePlayer - start gm
|
||||
[2025-08-15T21:34:45.148Z] DB: updatePlayer - changes 1 gm
|
||||
[2025-08-15T21:34:45.148Z] DB: getPlayerByName - start gm
|
||||
[2025-08-15T21:34:45.148Z] DB: getPlayerByName - found gm
|
||||
[2025-08-15T21:34:45.148Z] API: Updated player gm
|
||||
[2025-08-15T21:34:45.194Z] SESSION: GM bypass accepted GET /api/players
|
||||
[2025-08-15T21:34:45.195Z] DB: getAllPlayers - start
|
||||
[2025-08-15T21:34:45.195Z] DB: getAllPlayers - resultCount 5
|
||||
[2025-08-15T21:34:45.195Z] API: Fetch all players (public) Found 5 players
|
||||
[2025-08-15T21:35:43.856Z] SESSION: GM bypass accepted PUT /api/players/gm
|
||||
[2025-08-15T21:35:43.856Z] SESSION: GM bypass accepted PUT /api/players/gm
|
||||
[2025-08-15T21:35:43.856Z] DB: getPlayerByName - start gm
|
||||
[2025-08-15T21:35:43.857Z] DB: getPlayerByName - found gm
|
||||
[2025-08-15T21:35:43.857Z] DB: updatePlayer - start gm
|
||||
[2025-08-15T21:35:43.857Z] DB: updatePlayer - changes 1 gm
|
||||
[2025-08-15T21:35:43.857Z] DB: getPlayerByName - start gm
|
||||
[2025-08-15T21:35:43.858Z] DB: getPlayerByName - found gm
|
||||
[2025-08-15T21:35:43.858Z] API: Updated player gm
|
||||
[2025-08-15T21:35:43.910Z] SESSION: GM bypass accepted GET /api/players
|
||||
[2025-08-15T21:35:43.911Z] DB: getAllPlayers - start
|
||||
[2025-08-15T21:35:43.911Z] DB: getAllPlayers - resultCount 5
|
||||
[2025-08-15T21:35:43.911Z] API: Fetch all players (public) Found 5 players
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const path = require('path');
|
||||
const playerRoutes = require('./routes/playerRoutes-sqlite');
|
||||
const sessionRoutes = require('./routes/sessionRoutes-sqlite');
|
||||
const shopRoutes = require('./routes/shopRoutes');
|
||||
@@ -12,6 +13,9 @@ const PORT = process.env.PORT || 5000;
|
||||
app.use(express.json());
|
||||
app.use(cors());
|
||||
|
||||
// Serve static files from the React app build directory
|
||||
app.use(express.static(path.join(__dirname, '../build')));
|
||||
|
||||
// Initialize SQLite database
|
||||
const { db } = require('./sqlite-db');
|
||||
|
||||
@@ -50,6 +54,11 @@ app.use('/api/players', playerRoutes);
|
||||
app.use('/api/sessions', sessionRoutes);
|
||||
app.use('/api/shop', shopRoutes);
|
||||
|
||||
// Catch all handler: send back React's index.html file for any non-API routes
|
||||
app.get(/^(?!\/api).*/, (req, res) => {
|
||||
res.sendFile(path.join(__dirname, '../build/index.html'));
|
||||
});
|
||||
|
||||
// Start Server
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`Server running on http://0.0.0.0:${PORT}`);
|
||||
|
||||
0
database/sqlite-db.db
Normal file
0
database/sqlite-db.db
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
1138
public/deathwatch-armor-comprehensive.json
Normal file
1138
public/deathwatch-armor-comprehensive.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
582
public/deathwatch-weapons-comprehensive.json
Normal file
582
public/deathwatch-weapons-comprehensive.json
Normal file
@@ -0,0 +1,582 @@
|
||||
{
|
||||
"rangedWeapons": [
|
||||
{
|
||||
"name": "Angelus Bolter",
|
||||
"req": 25,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 50m; S/3/-; 1d10+9 X; Pen 6; Clip 36; Reload 2 Full; Tearing",
|
||||
"class": "Basic - Bolt",
|
||||
"source": "First Founding p97"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Armoursbane Missile Launcher",
|
||||
"req": 15,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 200m; S/-/-; 0d0 S; Pen 0; Clip 1; Reload 2 Full; Two handed",
|
||||
"class": "Heavy - Launch",
|
||||
"source": "Rites of Battle p137"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Assault Cannon",
|
||||
"req": 30,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 150m; -/-/10; 3d10+6 I; Pen 6; Clip 200; Reload 3 Full; Tearing",
|
||||
"class": "Mounted - Solid Projectile",
|
||||
"source": "Deathwatch Core Rulebook p149"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Assault Shotgun",
|
||||
"req": 7,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 30m; S/3/5; 1d10+10 I; Pen 4; Clip 20; Reload 1 Full; Two handed; Reliable, Scatter",
|
||||
"class": "Basic - Solid Projectile",
|
||||
"source": "Living Errata v1.1 p136"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Bolt Pistol",
|
||||
"req": 5,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 30m; S/2/-; 1d10+9 X; Pen 4; Clip 14; Reload 1 Full; Tearing",
|
||||
"class": "Pistol - Bolt",
|
||||
"source": "Living Errata v1.1 p146"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Bolter-Godwyn",
|
||||
"req": 5,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 100m; S/3/-; 1d10+9 X; Pen 4; Clip 28; Reload 1 Full; Two handed; Tearing",
|
||||
"class": "Basic - Bolt",
|
||||
"source": "Living Errata v1.1 p146"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Boltgun-Stalker",
|
||||
"req": 15,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 200m; S/-/-; 1d10+9 X; Pen 4; Clip 24; Reload 1 Full; Two handed; Accurate, Tearing",
|
||||
"class": "Basic - Bolt",
|
||||
"source": "Living Errata v1.1 p146"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Chainfist",
|
||||
"req": 40,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "2d10+2*SB E; Pen 10; Power Field, Tearing, Unwieldy",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p154"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Combi-Weapon",
|
||||
"req": 15,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 100m; S/4/-; 1d10+9 X; Pen 4; Clip 28; Reload 1 Full; Two handed; Tearing",
|
||||
"class": "Basic - Bolt",
|
||||
"source": "Living Errata v1.1 p146"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Conversion Beamer",
|
||||
"req": 40,
|
||||
"renown": "Hero",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 100m; S/-/-; 0d0 E; Pen 0; Clip 4; Reload 2 Full; Two handed",
|
||||
"class": "Heavy - Exotic",
|
||||
"source": "Living Errata v1.1 p152"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Cyclone Missile Launcher",
|
||||
"req": 25,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 300m; S/2/-; 0d0 S; Pen 0; Clip 12; Reload 3 Full",
|
||||
"class": "Mounted - Launch",
|
||||
"source": "Living Errata v1.1 p152"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Digital Flamer",
|
||||
"req": 35,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 4m; S/-/-; 1d10+9 E; Pen 4; Clip 1; Reload 1d5 Hours; Flame, Spray",
|
||||
"class": "Pistol - Exotic",
|
||||
"source": "Living Errata v1.1 p153"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Digital Laser",
|
||||
"req": 35,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 4m; S/-/-; 1d10+4 E; Pen 7; Clip 1; Reload 1d5 Hours; Reliable",
|
||||
"class": "Pistol - Exotic",
|
||||
"source": "Living Errata v1.1 p153"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Digital Melta",
|
||||
"req": 35,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 4m; S/-/-; 2d10+12 E; Pen 10; Clip 1; Reload 1d5 Hours",
|
||||
"class": "Pistol - Exotic",
|
||||
"source": "Living Errata v1.1 p153"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Executioner Axe",
|
||||
"req": 25,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+13 E; Pen 8; Two handed; Power Field, Unwieldy, Felling (x1)",
|
||||
"class": "Two-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p139"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Flamer",
|
||||
"req": 10,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 20m; S/-/-; 1d10+9 E; Pen 4; Clip 6; Reload 2 Full; Two handed; Flame, Spray",
|
||||
"class": "Basic - Flame",
|
||||
"source": "Living Errata v1.1 p148"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Heavy Flamer",
|
||||
"req": 15,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 30m; S/-/-; 1d10+12 E; Pen 6; Clip 10; Reload 2 Full; Two handed; Flame, Spray",
|
||||
"class": "Heavy - Flame",
|
||||
"source": "Living Errata v1.1 p149"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Heavy-Bolter",
|
||||
"req": 20,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 150m; -/-/6; 1d10+12 X; Pen 5; Clip 60; Reload 1 Full; Two handed; Tearing",
|
||||
"class": "Heavy - Bolt",
|
||||
"source": "Living Errata v1.1 p147"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Infernus Pistol",
|
||||
"req": 35,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 10m; S/-/-; 2d10+12 E; Pen 12; Clip 4; Reload 1 Full; Melta",
|
||||
"class": "Pistol - Melta",
|
||||
"source": "Living Errata v1.1 p148"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Lascannon",
|
||||
"req": 30,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 300m; S/-/-; 5d10+10 E; Pen 10; Clip 6; Reload 2 Full; Two handed; Proven (3)",
|
||||
"class": "Heavy - Las",
|
||||
"source": "Living Errata v1.1 p149"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Lightning Claw",
|
||||
"req": 30,
|
||||
"renown": "Distinguished",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+6 E; Pen 8; Power Field, Proven (4)",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p154"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Meltagun-Vulkan",
|
||||
"req": 20,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 30m; S/-/-; 2d10+14 E; Pen 12; Clip 6; Reload 2 Full; Two handed; Melta",
|
||||
"class": "Basic - Melta",
|
||||
"source": "Living Errata v1.1 p148"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Missile Launcher-Soundstrike",
|
||||
"req": 10,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 250m; S/-/-; 0d0 S; Pen 0; Clip 8; Reload 1 Full; Two handed",
|
||||
"class": "Heavy - Launch",
|
||||
"source": "Living Errata v1.1 p152"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Multi-Melta - Maxima",
|
||||
"req": 35,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 60m; S/-/-; 2d10+16 E; Pen 12; Clip 12; Reload 2 Full; Two handed; Blast (1), Melta",
|
||||
"class": "Heavy - Melta",
|
||||
"source": "Living Errata v1.1 p148"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Plasma Cannon",
|
||||
"req": 30,
|
||||
"renown": "Distinguished",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 150m; S/-/-; 2d10+12 E; Pen 10; Clip 16; Reload 5 Full; Two handed; Blast (3), Volatile, Maximal",
|
||||
"class": "Heavy - Plasma",
|
||||
"source": "Living Errata v1.1 p147"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Plasma Gun -Ragefire",
|
||||
"req": 20,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 100m; S/2/-; 1d10+12 E; Pen 10; Clip 24; Reload 4 Full; Volatile, Maximal",
|
||||
"class": "Basic - Plasma",
|
||||
"source": "Living Errata v1.1 p148"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Plasma Pistol",
|
||||
"req": 25,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 30m; S/2/-; 1d10+10 E; Pen 8; Clip 12; Reload 3 Full; Volatile, Maximal",
|
||||
"class": "Pistol - Plasma",
|
||||
"source": "Living Errata v1.1 p148"
|
||||
}
|
||||
}
|
||||
],
|
||||
"meleeWeapons": [
|
||||
{
|
||||
"name": "Armour of the Remorseless Crusader (Forearm Sword)",
|
||||
"req": 70,
|
||||
"renown": "Hero",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+8 R; Pen 6; +10 to hit; Sanctified",
|
||||
"class": "One-Handed Melee - Primitive",
|
||||
"source": "Deathwatch Core Rulebook p167"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Artificer Omnissian Axe",
|
||||
"req": 25,
|
||||
"renown": "Distinguished",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+9 E; Pen 7; Power Field, Unbalanced",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "First Founding p99"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Chainsword",
|
||||
"req": 5,
|
||||
"renown": "Any",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+3 R; Pen 3; Balanced, Tearing",
|
||||
"class": "One-Handed Melee - Chain",
|
||||
"source": "Living Errata v1.1 p153"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Combat Knife",
|
||||
"req": 3,
|
||||
"renown": "Any",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10 R; Pen 2",
|
||||
"class": "One-Handed Melee - Primitive",
|
||||
"source": "Living Errata v1.1 p155"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Combat Shield",
|
||||
"req": 20,
|
||||
"renown": "Distinguished",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d5+1 I; Pen 0; Balanced",
|
||||
"class": "One-Handed Melee - Primitive",
|
||||
"source": "Living Errata v1.1 p166"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Power Axe",
|
||||
"req": 20,
|
||||
"renown": "Respected",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+8 E; Pen 7; Power Field, Unbalanced",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p155"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Power Claymore",
|
||||
"req": 15,
|
||||
"renown": "Distinguished",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+11 E; Pen 8; Two handed; Power Field, Unwieldy, Devastating (1)",
|
||||
"class": "Two-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p139"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Power Falchion",
|
||||
"req": 25,
|
||||
"renown": "Distinguished",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+4 E; Pen 6; Two handed; Power Field, Razor Sharp",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p139"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Power Fist",
|
||||
"req": 30,
|
||||
"renown": "Distinguished",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "2d10+2*SB E; Pen 9; Power Field, Unwieldy",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p155"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Power Spear",
|
||||
"req": 15,
|
||||
"renown": "Distinguished",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+6 E; Pen 7; Two handed; Power Field",
|
||||
"class": "Two-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p139"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Power Sword",
|
||||
"req": 20,
|
||||
"renown": "Respected",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+6 E; Pen 6; Balanced, Power Field",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p155"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Force Staff",
|
||||
"req": 25,
|
||||
"renown": "Respected",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+1+PR+SB S; Pen PR; Balanced",
|
||||
"class": "One-Handed Melee - Primitive",
|
||||
"source": "Living Errata v1.1 p155"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Force Sword",
|
||||
"req": 25,
|
||||
"renown": "Respected",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+2+PR+SB S; Pen 2+PR; Balanced",
|
||||
"class": "One-Handed Melee - Primitive",
|
||||
"source": "Living Errata v1.1 p156"
|
||||
}
|
||||
}
|
||||
],
|
||||
"grenades": [
|
||||
{
|
||||
"name": "Astartes Anti-Plant Grenade",
|
||||
"req": 18,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "3d10 E; Pen 0; Blast (3)",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p136"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Blind Grenade",
|
||||
"req": 10,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "0d0 S; Pen 0; Smoke (10)",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p150"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Frag Grenade",
|
||||
"req": 1,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "2d10+2 X; Pen 0; Blast (4)",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p150"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Incendiary Grenade",
|
||||
"req": 15,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "1d10+4 E; Pen 0; Blast (3)",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p150"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Krak Grenade",
|
||||
"req": 1,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "3d10+4 X; Pen 6",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p150"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Nova Grenade",
|
||||
"req": 15,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "1d10 E; Pen 0; Blast (3)",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p151"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Photon Flash Grenade",
|
||||
"req": 5,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "0d0 S; Pen 0",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p151"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Plasma Grenade",
|
||||
"req": 20,
|
||||
"renown": "Distinguished",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "1d10+12 E; Pen 8; Blast (3)",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p151"
|
||||
}
|
||||
}
|
||||
],
|
||||
"other": [
|
||||
{
|
||||
"name": "Argrax Clawed Hooves",
|
||||
"req": 0,
|
||||
"renown": "Any",
|
||||
"category": "Other",
|
||||
"stats": {
|
||||
"damage": "1d10 I; Pen 0",
|
||||
"class": "Natural - Primitive",
|
||||
"source": "The Emperor Protects p46"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Assault Cannon-Samech",
|
||||
"req": 0,
|
||||
"renown": "Any",
|
||||
"category": "Other",
|
||||
"stats": {
|
||||
"damage": "Range 120m; -/-/10; 3d10+6 I; Pen 5; Clip 200; Reload 2 Full; Tearing",
|
||||
"class": "Mounted - Solid Projectile",
|
||||
"source": "The Emperor Protects p120"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Servo-Arm",
|
||||
"req": 30,
|
||||
"renown": "Any",
|
||||
"category": "Other",
|
||||
"stats": {
|
||||
"damage": "2d10+14 I; Pen 10",
|
||||
"class": "Mounted - Primitive",
|
||||
"source": "Living Errata v1.1 p177"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Servo-Arm Exceptional",
|
||||
"req": 30,
|
||||
"renown": "Any",
|
||||
"category": "Other",
|
||||
"stats": {
|
||||
"damage": "2d10+16 I; Pen 10",
|
||||
"class": "Mounted - Primitive",
|
||||
"source": "Deathwatch Core Rulebook p177"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
57
public/deathwatch-weapons-scraped.json
Normal file
57
public/deathwatch-weapons-scraped.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"pistols": [],
|
||||
"basicWeapons": [
|
||||
{
|
||||
"name": "Angelus Bolter",
|
||||
"req": 25,
|
||||
"renown": "Respected",
|
||||
"category": "basic Weapons",
|
||||
"stats": {
|
||||
"damage": "Range 50m; S/3/-; 1d10+9 X; Pen 6; Clip 36; Reload 2 Full; Tearing",
|
||||
"class": "Basic - Bolt",
|
||||
"source": "First Founding p97"
|
||||
}
|
||||
}
|
||||
],
|
||||
"heavyWeapons": [],
|
||||
"mountedWeapons": [],
|
||||
"meleeWeapons": [
|
||||
{
|
||||
"name": "Armour of the Remorseless Crusader (Forearm Sword)",
|
||||
"req": 70,
|
||||
"renown": "Hero",
|
||||
"category": "melee Weapons",
|
||||
"stats": {
|
||||
"damage": "1d10+8 R; Pen 6; +10 to hit; Sanctified",
|
||||
"class": "One-Handed Melee - Primitive",
|
||||
"source": "Deathwatch Core Rulebook p167"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Artificer Omnissian Axe",
|
||||
"req": 25,
|
||||
"renown": "Distinguished",
|
||||
"category": "melee Weapons",
|
||||
"stats": {
|
||||
"damage": "1d10+9 E; Pen 7; Power Field, Unbalanced",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "First Founding p99"
|
||||
}
|
||||
}
|
||||
],
|
||||
"grenades": [],
|
||||
"naturalWeapons": [
|
||||
{
|
||||
"name": "Argrax Clawed Hooves",
|
||||
"req": 0,
|
||||
"renown": "None",
|
||||
"category": "natural Weapons",
|
||||
"stats": {
|
||||
"damage": "1d10 I; Pen 0;",
|
||||
"class": "Natural - Primitive",
|
||||
"source": "The Emperor Protects p46"
|
||||
}
|
||||
}
|
||||
],
|
||||
"otherWeapons": []
|
||||
}
|
||||
@@ -1,28 +1,31 @@
|
||||
const { categorizedWeapons } = require('./parse-weapons');
|
||||
const { categorizedArmor } = require('./parse-armor');
|
||||
const comprehensiveWeapons = require('./comprehensive-weapons');
|
||||
const comprehensiveArmor = require('./comprehensive-armor');
|
||||
|
||||
// Combine all shop items into a single database
|
||||
const buildShopDatabase = () => {
|
||||
const shopItems = {
|
||||
// Weapons
|
||||
rangedWeapons: categorizedWeapons.rangedWeapons,
|
||||
meleeWeapons: categorizedWeapons.meleeWeapons,
|
||||
grenades: categorizedWeapons.grenades,
|
||||
otherWeapons: categorizedWeapons.other,
|
||||
// Use comprehensive weapons dataset
|
||||
rangedWeapons: comprehensiveWeapons.rangedWeapons,
|
||||
meleeWeapons: comprehensiveWeapons.meleeWeapons,
|
||||
grenades: comprehensiveWeapons.grenades,
|
||||
otherWeapons: comprehensiveWeapons.other,
|
||||
|
||||
// Armor
|
||||
powerArmor: categorizedArmor.powerArmor,
|
||||
carapaceArmor: categorizedArmor.carapaceArmor,
|
||||
primitiveArmor: categorizedArmor.primitiveArmor,
|
||||
naturalArmor: categorizedArmor.naturalArmor,
|
||||
xenosArmor: categorizedArmor.xenosArmor,
|
||||
shields: categorizedArmor.shields,
|
||||
otherArmor: categorizedArmor.otherArmor
|
||||
// Use comprehensive armor dataset
|
||||
powerArmor: comprehensiveArmor.powerArmor,
|
||||
powerArmorHelms: comprehensiveArmor.powerArmorHelms,
|
||||
carapaceArmor: comprehensiveArmor.carapaceArmor,
|
||||
naturalArmor: comprehensiveArmor.naturalArmor,
|
||||
primitiveArmor: comprehensiveArmor.primitiveArmor,
|
||||
xenosArmor: comprehensiveArmor.xenosArmor,
|
||||
shields: comprehensiveArmor.shields,
|
||||
otherArmor: comprehensiveArmor.otherArmor
|
||||
};
|
||||
|
||||
// Add metadata
|
||||
const shopDatabase = {
|
||||
version: "2.0.0",
|
||||
version: "3.0.0",
|
||||
lastUpdated: new Date().toISOString(),
|
||||
categories: Object.keys(shopItems),
|
||||
items: shopItems
|
||||
@@ -41,7 +44,7 @@ if (require.main === module) {
|
||||
require('./parse-armor');
|
||||
|
||||
// Then build and write the combined database
|
||||
const output = path.resolve('public/deathwatch-armoury.json');
|
||||
const output = path.resolve('../public/deathwatch-armoury.json');
|
||||
const database = buildShopDatabase();
|
||||
fs.writeFileSync(output, JSON.stringify(database, null, 2), 'utf8');
|
||||
console.log('Wrote combined shop database to:', output);
|
||||
|
||||
1184
scripts/comprehensive-armor.js
Normal file
1184
scripts/comprehensive-armor.js
Normal file
File diff suppressed because it is too large
Load Diff
629
scripts/comprehensive-weapons.js
Normal file
629
scripts/comprehensive-weapons.js
Normal file
@@ -0,0 +1,629 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Enhanced scraper that uses a comprehensive weapons dataset
|
||||
* This includes a manually curated list of weapons from the 40k RPG Tools website
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Comprehensive weapon dataset based on the 40k RPG Tools website
|
||||
const comprehensiveWeaponsData = {
|
||||
"rangedWeapons": [
|
||||
{
|
||||
"name": "Angelus Bolter",
|
||||
"req": 25,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 50m; S/3/-; 1d10+9 X; Pen 6; Clip 36; Reload 2 Full; Tearing",
|
||||
"class": "Basic - Bolt",
|
||||
"source": "First Founding p97"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Armoursbane Missile Launcher",
|
||||
"req": 15,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 200m; S/-/-; 0d0 S; Pen 0; Clip 1; Reload 2 Full; Two handed",
|
||||
"class": "Heavy - Launch",
|
||||
"source": "Rites of Battle p137"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Assault Cannon",
|
||||
"req": 30,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 150m; -/-/10; 3d10+6 I; Pen 6; Clip 200; Reload 3 Full; Tearing",
|
||||
"class": "Mounted - Solid Projectile",
|
||||
"source": "Deathwatch Core Rulebook p149"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Assault Shotgun",
|
||||
"req": 7,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 30m; S/3/5; 1d10+10 I; Pen 4; Clip 20; Reload 1 Full; Two handed; Reliable, Scatter",
|
||||
"class": "Basic - Solid Projectile",
|
||||
"source": "Living Errata v1.1 p136"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Bolt Pistol",
|
||||
"req": 5,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 30m; S/2/-; 1d10+9 X; Pen 4; Clip 14; Reload 1 Full; Tearing",
|
||||
"class": "Pistol - Bolt",
|
||||
"source": "Living Errata v1.1 p146"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Bolter-Godwyn",
|
||||
"req": 5,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 100m; S/3/-; 1d10+9 X; Pen 4; Clip 28; Reload 1 Full; Two handed; Tearing",
|
||||
"class": "Basic - Bolt",
|
||||
"source": "Living Errata v1.1 p146"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Boltgun-Stalker",
|
||||
"req": 15,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 200m; S/-/-; 1d10+9 X; Pen 4; Clip 24; Reload 1 Full; Two handed; Accurate, Tearing",
|
||||
"class": "Basic - Bolt",
|
||||
"source": "Living Errata v1.1 p146"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Chainfist",
|
||||
"req": 40,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "2d10+2*SB E; Pen 10; Power Field, Tearing, Unwieldy",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p154"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Combi-Weapon",
|
||||
"req": 15,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 100m; S/4/-; 1d10+9 X; Pen 4; Clip 28; Reload 1 Full; Two handed; Tearing",
|
||||
"class": "Basic - Bolt",
|
||||
"source": "Living Errata v1.1 p146"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Conversion Beamer",
|
||||
"req": 40,
|
||||
"renown": "Hero",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 100m; S/-/-; 0d0 E; Pen 0; Clip 4; Reload 2 Full; Two handed",
|
||||
"class": "Heavy - Exotic",
|
||||
"source": "Living Errata v1.1 p152"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Cyclone Missile Launcher",
|
||||
"req": 25,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 300m; S/2/-; 0d0 S; Pen 0; Clip 12; Reload 3 Full",
|
||||
"class": "Mounted - Launch",
|
||||
"source": "Living Errata v1.1 p152"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Digital Flamer",
|
||||
"req": 35,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 4m; S/-/-; 1d10+9 E; Pen 4; Clip 1; Reload 1d5 Hours; Flame, Spray",
|
||||
"class": "Pistol - Exotic",
|
||||
"source": "Living Errata v1.1 p153"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Digital Laser",
|
||||
"req": 35,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 4m; S/-/-; 1d10+4 E; Pen 7; Clip 1; Reload 1d5 Hours; Reliable",
|
||||
"class": "Pistol - Exotic",
|
||||
"source": "Living Errata v1.1 p153"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Digital Melta",
|
||||
"req": 35,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 4m; S/-/-; 2d10+12 E; Pen 10; Clip 1; Reload 1d5 Hours",
|
||||
"class": "Pistol - Exotic",
|
||||
"source": "Living Errata v1.1 p153"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Executioner Axe",
|
||||
"req": 25,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+13 E; Pen 8; Two handed; Power Field, Unwieldy, Felling (x1)",
|
||||
"class": "Two-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p139"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Flamer",
|
||||
"req": 10,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 20m; S/-/-; 1d10+9 E; Pen 4; Clip 6; Reload 2 Full; Two handed; Flame, Spray",
|
||||
"class": "Basic - Flame",
|
||||
"source": "Living Errata v1.1 p148"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Heavy Flamer",
|
||||
"req": 15,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 30m; S/-/-; 1d10+12 E; Pen 6; Clip 10; Reload 2 Full; Two handed; Flame, Spray",
|
||||
"class": "Heavy - Flame",
|
||||
"source": "Living Errata v1.1 p149"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Heavy-Bolter",
|
||||
"req": 20,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 150m; -/-/6; 1d10+12 X; Pen 5; Clip 60; Reload 1 Full; Two handed; Tearing",
|
||||
"class": "Heavy - Bolt",
|
||||
"source": "Living Errata v1.1 p147"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Infernus Pistol",
|
||||
"req": 35,
|
||||
"renown": "Famed",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 10m; S/-/-; 2d10+12 E; Pen 12; Clip 4; Reload 1 Full; Melta",
|
||||
"class": "Pistol - Melta",
|
||||
"source": "Living Errata v1.1 p148"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Lascannon",
|
||||
"req": 30,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 300m; S/-/-; 5d10+10 E; Pen 10; Clip 6; Reload 2 Full; Two handed; Proven (3)",
|
||||
"class": "Heavy - Las",
|
||||
"source": "Living Errata v1.1 p149"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Lightning Claw",
|
||||
"req": 30,
|
||||
"renown": "Distinguished",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+6 E; Pen 8; Power Field, Proven (4)",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p154"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Meltagun-Vulkan",
|
||||
"req": 20,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 30m; S/-/-; 2d10+14 E; Pen 12; Clip 6; Reload 2 Full; Two handed; Melta",
|
||||
"class": "Basic - Melta",
|
||||
"source": "Living Errata v1.1 p148"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Missile Launcher-Soundstrike",
|
||||
"req": 10,
|
||||
"renown": "Any",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 250m; S/-/-; 0d0 S; Pen 0; Clip 8; Reload 1 Full; Two handed",
|
||||
"class": "Heavy - Launch",
|
||||
"source": "Living Errata v1.1 p152"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Multi-Melta - Maxima",
|
||||
"req": 35,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 60m; S/-/-; 2d10+16 E; Pen 12; Clip 12; Reload 2 Full; Two handed; Blast (1), Melta",
|
||||
"class": "Heavy - Melta",
|
||||
"source": "Living Errata v1.1 p148"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Plasma Cannon",
|
||||
"req": 30,
|
||||
"renown": "Distinguished",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 150m; S/-/-; 2d10+12 E; Pen 10; Clip 16; Reload 5 Full; Two handed; Blast (3), Volatile, Maximal",
|
||||
"class": "Heavy - Plasma",
|
||||
"source": "Living Errata v1.1 p147"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Plasma Gun -Ragefire",
|
||||
"req": 20,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 100m; S/2/-; 1d10+12 E; Pen 10; Clip 24; Reload 4 Full; Volatile, Maximal",
|
||||
"class": "Basic - Plasma",
|
||||
"source": "Living Errata v1.1 p148"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Plasma Pistol",
|
||||
"req": 25,
|
||||
"renown": "Respected",
|
||||
"category": "Ranged Weapon",
|
||||
"stats": {
|
||||
"damage": "Range 30m; S/2/-; 1d10+10 E; Pen 8; Clip 12; Reload 3 Full; Volatile, Maximal",
|
||||
"class": "Pistol - Plasma",
|
||||
"source": "Living Errata v1.1 p148"
|
||||
}
|
||||
}
|
||||
],
|
||||
"meleeWeapons": [
|
||||
{
|
||||
"name": "Armour of the Remorseless Crusader (Forearm Sword)",
|
||||
"req": 70,
|
||||
"renown": "Hero",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+8 R; Pen 6; +10 to hit; Sanctified",
|
||||
"class": "One-Handed Melee - Primitive",
|
||||
"source": "Deathwatch Core Rulebook p167"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Artificer Omnissian Axe",
|
||||
"req": 25,
|
||||
"renown": "Distinguished",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+9 E; Pen 7; Power Field, Unbalanced",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "First Founding p99"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Chainsword",
|
||||
"req": 5,
|
||||
"renown": "Any",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+3 R; Pen 3; Balanced, Tearing",
|
||||
"class": "One-Handed Melee - Chain",
|
||||
"source": "Living Errata v1.1 p153"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Combat Knife",
|
||||
"req": 3,
|
||||
"renown": "Any",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10 R; Pen 2",
|
||||
"class": "One-Handed Melee - Primitive",
|
||||
"source": "Living Errata v1.1 p155"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Combat Shield",
|
||||
"req": 20,
|
||||
"renown": "Distinguished",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d5+1 I; Pen 0; Balanced",
|
||||
"class": "One-Handed Melee - Primitive",
|
||||
"source": "Living Errata v1.1 p166"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Power Axe",
|
||||
"req": 20,
|
||||
"renown": "Respected",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+8 E; Pen 7; Power Field, Unbalanced",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p155"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Power Claymore",
|
||||
"req": 15,
|
||||
"renown": "Distinguished",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+11 E; Pen 8; Two handed; Power Field, Unwieldy, Devastating (1)",
|
||||
"class": "Two-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p139"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Power Falchion",
|
||||
"req": 25,
|
||||
"renown": "Distinguished",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+4 E; Pen 6; Two handed; Power Field, Razor Sharp",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p139"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Power Fist",
|
||||
"req": 30,
|
||||
"renown": "Distinguished",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "2d10+2*SB E; Pen 9; Power Field, Unwieldy",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p155"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Power Spear",
|
||||
"req": 15,
|
||||
"renown": "Distinguished",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+6 E; Pen 7; Two handed; Power Field",
|
||||
"class": "Two-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p139"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Power Sword",
|
||||
"req": 20,
|
||||
"renown": "Respected",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+6 E; Pen 6; Balanced, Power Field",
|
||||
"class": "One-Handed Melee - Power",
|
||||
"source": "Living Errata v1.1 p155"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Force Staff",
|
||||
"req": 25,
|
||||
"renown": "Respected",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+1+PR+SB S; Pen PR; Balanced",
|
||||
"class": "One-Handed Melee - Primitive",
|
||||
"source": "Living Errata v1.1 p155"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Force Sword",
|
||||
"req": 25,
|
||||
"renown": "Respected",
|
||||
"category": "Melee Weapon",
|
||||
"stats": {
|
||||
"damage": "1d10+2+PR+SB S; Pen 2+PR; Balanced",
|
||||
"class": "One-Handed Melee - Primitive",
|
||||
"source": "Living Errata v1.1 p156"
|
||||
}
|
||||
}
|
||||
],
|
||||
"grenades": [
|
||||
{
|
||||
"name": "Astartes Anti-Plant Grenade",
|
||||
"req": 18,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "3d10 E; Pen 0; Blast (3)",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p136"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Blind Grenade",
|
||||
"req": 10,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "0d0 S; Pen 0; Smoke (10)",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p150"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Frag Grenade",
|
||||
"req": 1,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "2d10+2 X; Pen 0; Blast (4)",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p150"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Incendiary Grenade",
|
||||
"req": 15,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "1d10+4 E; Pen 0; Blast (3)",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p150"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Krak Grenade",
|
||||
"req": 1,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "3d10+4 X; Pen 6",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p150"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Nova Grenade",
|
||||
"req": 15,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "1d10 E; Pen 0; Blast (3)",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p151"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Photon Flash Grenade",
|
||||
"req": 5,
|
||||
"renown": "Any",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "0d0 S; Pen 0",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p151"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Plasma Grenade",
|
||||
"req": 20,
|
||||
"renown": "Distinguished",
|
||||
"category": "Grenade",
|
||||
"stats": {
|
||||
"damage": "1d10+12 E; Pen 8; Blast (3)",
|
||||
"class": "Thrown - Grenade",
|
||||
"source": "Living Errata v1.1 p151"
|
||||
}
|
||||
}
|
||||
],
|
||||
"other": [
|
||||
{
|
||||
"name": "Argrax Clawed Hooves",
|
||||
"req": 0,
|
||||
"renown": "Any",
|
||||
"category": "Other",
|
||||
"stats": {
|
||||
"damage": "1d10 I; Pen 0",
|
||||
"class": "Natural - Primitive",
|
||||
"source": "The Emperor Protects p46"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Assault Cannon-Samech",
|
||||
"req": 0,
|
||||
"renown": "Any",
|
||||
"category": "Other",
|
||||
"stats": {
|
||||
"damage": "Range 120m; -/-/10; 3d10+6 I; Pen 5; Clip 200; Reload 2 Full; Tearing",
|
||||
"class": "Mounted - Solid Projectile",
|
||||
"source": "The Emperor Protects p120"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Servo-Arm",
|
||||
"req": 30,
|
||||
"renown": "Any",
|
||||
"category": "Other",
|
||||
"stats": {
|
||||
"damage": "2d10+14 I; Pen 10",
|
||||
"class": "Mounted - Primitive",
|
||||
"source": "Living Errata v1.1 p177"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Astartes Servo-Arm Exceptional",
|
||||
"req": 30,
|
||||
"renown": "Any",
|
||||
"category": "Other",
|
||||
"stats": {
|
||||
"damage": "2d10+16 I; Pen 10",
|
||||
"class": "Mounted - Primitive",
|
||||
"source": "Deathwatch Core Rulebook p177"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
function main() {
|
||||
try {
|
||||
console.log('Using comprehensive weapons dataset...');
|
||||
|
||||
// Count items
|
||||
const totalItems = Object.values(comprehensiveWeaponsData).reduce((sum, category) => sum + category.length, 0);
|
||||
console.log(`Total weapons: ${totalItems}`);
|
||||
|
||||
// Show breakdown by category
|
||||
Object.keys(comprehensiveWeaponsData).forEach(category => {
|
||||
console.log(`${category}: ${comprehensiveWeaponsData[category].length} items`);
|
||||
});
|
||||
|
||||
// Write to file
|
||||
const outputPath = path.join(__dirname, '..', 'public', 'deathwatch-weapons-comprehensive.json');
|
||||
fs.writeFileSync(outputPath, JSON.stringify(comprehensiveWeaponsData, null, 2));
|
||||
console.log(`Comprehensive weapon data written to: ${outputPath}`);
|
||||
|
||||
console.log('\nNext steps:');
|
||||
console.log('1. Review the data in deathwatch-weapons-comprehensive.json');
|
||||
console.log('2. Run: node scripts/build-shop-db.js to rebuild the shop database');
|
||||
console.log('3. Run: cd database && node migrations/add-shop-tables.js to update SQLite');
|
||||
console.log('4. Restart your server to see the updated shop');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = comprehensiveWeaponsData;
|
||||
224
scripts/scrape-40k-tools.js
Normal file
224
scripts/scrape-40k-tools.js
Normal file
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Scrape weapon data from 40k RPG Tools website
|
||||
*/
|
||||
|
||||
function fetchHTML(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
https.get(url, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => data += chunk);
|
||||
res.on('end', () => resolve(data));
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
function parseWeaponTable(html) {
|
||||
const weapons = [];
|
||||
|
||||
// The data appears to be in the HTML but formatted differently
|
||||
// Let's look for all table cell patterns and reconstruct the table
|
||||
const linkRegex = /<a href='\/armoury\/weapon\/[^']*'>([^<]+)<\/a>/g;
|
||||
const weaponNames = [];
|
||||
|
||||
let match;
|
||||
while ((match = linkRegex.exec(html)) !== null) {
|
||||
weaponNames.push(match[1]);
|
||||
}
|
||||
|
||||
console.log(`Found ${weaponNames.length} weapon names`);
|
||||
|
||||
// For now, let's try a different approach - look for the table rows in the HTML you provided
|
||||
// Parse the specific HTML structure from the user's table
|
||||
const tableRowRegex = /<tr class="(?:odd|even)">\s*<td class="sorting_1"><a href="[^"]*">([^<]+)<\/a><\/td>\s*<td>([^<]*)<\/td>\s*<td>\s*([^<]*?)\s*<\/td>\s*<td>(\d+)<\/td>\s*<td>([^<]*)<\/td>\s*<td><a href="[^"]*">([^<]*)<\/a><\/td>\s*<td><a href="[^"]*">([^<]*)<\/a><\/td>\s*<td>(\d+)<\/td>\s*<\/tr>/gs;
|
||||
|
||||
let rowMatch;
|
||||
while ((rowMatch = tableRowRegex.exec(html)) !== null) {
|
||||
const weapon = {
|
||||
name: rowMatch[1].trim(),
|
||||
class: rowMatch[2].trim(),
|
||||
damage: rowMatch[3].replace(/<[^>]*>/g, '').trim(),
|
||||
cost: parseInt(rowMatch[4]) || 0,
|
||||
availability: rowMatch[5].trim(),
|
||||
gameSetting: rowMatch[6].trim(),
|
||||
book: rowMatch[7].trim(),
|
||||
page: rowMatch[8].trim()
|
||||
};
|
||||
|
||||
weapons.push(weapon);
|
||||
}
|
||||
|
||||
// If that doesn't work, let's try extracting from the data you provided manually
|
||||
if (weapons.length === 0) {
|
||||
console.log('Trying manual extraction from known weapon data...');
|
||||
|
||||
// Extract weapons from the provided HTML table data
|
||||
const knownWeapons = [
|
||||
{
|
||||
name: "Angelus Bolter",
|
||||
class: "Basic - Bolt",
|
||||
damage: "Range 50m; S/3/-; 1d10+9 X; Pen 6; Clip 36; Reload 2 Full; Tearing",
|
||||
cost: 25,
|
||||
availability: "Respected",
|
||||
gameSetting: "Deathwatch",
|
||||
book: "First Founding",
|
||||
page: "97"
|
||||
},
|
||||
{
|
||||
name: "Argrax Clawed Hooves",
|
||||
class: "Natural - Primitive",
|
||||
damage: "1d10 I; Pen 0;",
|
||||
cost: 0,
|
||||
availability: "Any",
|
||||
gameSetting: "Deathwatch",
|
||||
book: "The Emperor Protects",
|
||||
page: "46"
|
||||
},
|
||||
{
|
||||
name: "Armour of the Remorseless Crusader (Forearm Sword)",
|
||||
class: "One-Handed Melee - Primitive",
|
||||
damage: "1d10+8 R; Pen 6; +10 to hit; Sanctified",
|
||||
cost: 70,
|
||||
availability: "Hero",
|
||||
gameSetting: "Deathwatch",
|
||||
book: "Deathwatch Core Rulebook",
|
||||
page: "167"
|
||||
},
|
||||
{
|
||||
name: "Artificer Omnissian Axe",
|
||||
class: "One-Handed Melee - Power",
|
||||
damage: "1d10+9 E; Pen 7; Power Field, Unbalanced",
|
||||
cost: 25,
|
||||
availability: "Distinguished",
|
||||
gameSetting: "Deathwatch",
|
||||
book: "First Founding",
|
||||
page: "99"
|
||||
}
|
||||
// Add more weapons from the HTML table data as needed
|
||||
];
|
||||
|
||||
weapons.push(...knownWeapons);
|
||||
}
|
||||
|
||||
return weapons;
|
||||
}
|
||||
|
||||
function categorizeWeapon(weapon) {
|
||||
const className = weapon.class.toLowerCase();
|
||||
|
||||
if (className.includes('pistol')) {
|
||||
return 'pistols';
|
||||
} else if (className.includes('basic')) {
|
||||
return 'basicWeapons';
|
||||
} else if (className.includes('heavy')) {
|
||||
return 'heavyWeapons';
|
||||
} else if (className.includes('mounted')) {
|
||||
return 'mountedWeapons';
|
||||
} else if (className.includes('melee')) {
|
||||
return 'meleeWeapons';
|
||||
} else if (className.includes('thrown') || className.includes('grenade')) {
|
||||
return 'grenades';
|
||||
} else if (className.includes('natural')) {
|
||||
return 'naturalWeapons';
|
||||
} else {
|
||||
return 'otherWeapons';
|
||||
}
|
||||
}
|
||||
|
||||
function mapAvailabilityToRenown(availability) {
|
||||
const availabilityMap = {
|
||||
'Any': 'None',
|
||||
'Abundant': 'None',
|
||||
'Plentiful': 'None',
|
||||
'Common': 'None',
|
||||
'Average': 'None',
|
||||
'Uncommon': 'None',
|
||||
'Scarce': 'Respected',
|
||||
'Rare': 'Respected',
|
||||
'Very Rare': 'Distinguished',
|
||||
'Extremely Rare': 'Distinguished',
|
||||
'Near Unique': 'Famed',
|
||||
'Unique': 'Hero',
|
||||
'Respected': 'Respected',
|
||||
'Distinguished': 'Distinguished',
|
||||
'Famed': 'Famed',
|
||||
'Hero': 'Hero'
|
||||
};
|
||||
|
||||
return availabilityMap[availability] || 'None';
|
||||
}
|
||||
|
||||
function convertToGameFormat(weapons) {
|
||||
const categorized = {
|
||||
pistols: [],
|
||||
basicWeapons: [],
|
||||
heavyWeapons: [],
|
||||
mountedWeapons: [],
|
||||
meleeWeapons: [],
|
||||
grenades: [],
|
||||
naturalWeapons: [],
|
||||
otherWeapons: []
|
||||
};
|
||||
|
||||
weapons.forEach(weapon => {
|
||||
const category = categorizeWeapon(weapon);
|
||||
const gameWeapon = {
|
||||
name: weapon.name,
|
||||
req: weapon.cost,
|
||||
renown: mapAvailabilityToRenown(weapon.availability),
|
||||
category: category.replace(/([A-Z])/g, ' $1').trim(),
|
||||
stats: {
|
||||
damage: weapon.damage,
|
||||
class: weapon.class,
|
||||
source: `${weapon.book} p${weapon.page}`
|
||||
}
|
||||
};
|
||||
|
||||
categorized[category].push(gameWeapon);
|
||||
});
|
||||
|
||||
return categorized;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
console.log('Fetching weapon data from 40k RPG Tools...');
|
||||
const html = await fetchHTML('https://www.40krpgtools.com/armoury/weapons/');
|
||||
|
||||
console.log('Parsing weapon table...');
|
||||
const weapons = parseWeaponTable(html);
|
||||
console.log(`Found ${weapons.length} weapons`);
|
||||
|
||||
console.log('Converting to game format...');
|
||||
const categorizedWeapons = convertToGameFormat(weapons);
|
||||
|
||||
// Calculate totals per category
|
||||
Object.keys(categorizedWeapons).forEach(category => {
|
||||
console.log(`${category}: ${categorizedWeapons[category].length} items`);
|
||||
});
|
||||
|
||||
// Write to file
|
||||
const outputPath = path.join(__dirname, '..', 'public', 'deathwatch-weapons-scraped.json');
|
||||
fs.writeFileSync(outputPath, JSON.stringify(categorizedWeapons, null, 2));
|
||||
console.log(`Weapon data written to: ${outputPath}`);
|
||||
|
||||
// Create summary
|
||||
const totalWeapons = Object.values(categorizedWeapons).reduce((sum, cat) => sum + cat.length, 0);
|
||||
console.log(`\nSummary: ${totalWeapons} total weapons imported`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error scraping weapon data:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { fetchHTML, parseWeaponTable, convertToGameFormat };
|
||||
Reference in New Issue
Block a user