diff --git a/database/backend.log b/database/backend.log index 413ef6c..578f8ae 100644 --- a/database/backend.log +++ b/database/backend.log @@ -2582,3 +2582,1253 @@ Connected to MongoDB [2025-08-15T11:50:22.604Z] DB: getPlayerByName - start christoffer [2025-08-15T11:50:22.604Z] DB: getPlayerByName - found christoffer [2025-08-15T11:50:22.604Z] API: Updated player christoffer +[2025-08-15T11:54:01.391Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T11:54:01.391Z] DB: deletePlayer - start gmtest +[2025-08-15T11:54:01.391Z] DB: deletePlayer - changes 0 gmtest +[2025-08-15T11:54:01.408Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:54:01.408Z] DB: getPlayerByName - not found gmtest +[2025-08-15T11:54:01.487Z] DB: createPlayer - start gmtest +[2025-08-15T11:54:01.488Z] DB: createPlayer - done gmtest rowid 32 +[2025-08-15T11:54:01.488Z] API: Created player gmtest +[2025-08-15T11:54:01.505Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T11:54:01.505Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:54:01.505Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:54:01.505Z] DB: updatePlayer - start gmtest +[2025-08-15T11:54:01.505Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T11:54:01.505Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:54:01.505Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:54:01.505Z] API: Updated player gmtest +[2025-08-15T11:54:01.521Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T11:54:01.521Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:54:01.521Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:54:01.600Z] DB: updatePlayer - start gmtest +[2025-08-15T11:54:01.600Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T11:54:01.600Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:54:01.600Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:54:01.600Z] API: Updated player gmtest +[2025-08-15T11:54:01.615Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:54:01.615Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:54:01.695Z] DB: createSession - start 18368313c0fd1d912da0f5acd81c303f03c526833d2093ee575170f6f40521f3 expiresAt 2025-08-16T11:54:01.695Z +[2025-08-15T11:54:01.695Z] DB: createSession - done 18368313c0fd1d912da0f5acd81c303f03c526833d2093ee575170f6f40521f3 +[2025-08-15T11:54:01.695Z] API: Player login gmtest 18368313c0fd1d912da0f5acd81c303f03c526833d2093ee575170f6f40521f3 +[2025-08-15T11:54:01.710Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T11:54:01.710Z] DB: deletePlayer - start gmtest +[2025-08-15T11:54:01.710Z] DB: deletePlayer - changes 1 gmtest +[2025-08-15T11:54:01.710Z] API: Deleted player gmtest +[2025-08-15T11:54:01.724Z] DB: getAllPlayers - start +[2025-08-15T11:54:01.724Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T11:54:01.724Z] API: Fetch all players (public) Found 4 players +[2025-08-15T11:54:02.218Z] DB: getAllPlayers - start +[2025-08-15T11:54:02.416Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T11:54:02.748Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:54:02.749Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:54:02.828Z] DB: createSession - start 035f761fdda85f170002a0db6d49f92bcacce0f8aa0aab8137c9b72275dc036d expiresAt 2025-08-16T11:54:02.828Z +[2025-08-15T11:54:02.828Z] DB: createSession - done 035f761fdda85f170002a0db6d49f92bcacce0f8aa0aab8137c9b72275dc036d +[2025-08-15T11:54:02.828Z] API: Player login christoffer 035f761fdda85f170002a0db6d49f92bcacce0f8aa0aab8137c9b72275dc036d +[2025-08-15T11:54:02.847Z] DB: getSession - start 035f761fdda85f170002a0db6d49f92bcacce0f8aa0aab8137c9b72275dc036d +[2025-08-15T11:54:02.848Z] DB: getSession - ok 035f761fdda85f170002a0db6d49f92bcacce0f8aa0aab8137c9b72275dc036d +[2025-08-15T11:54:02.848Z] SESSION: Valid 035f761fdda85f170002a0db6d49f92bcacce0f8aa0aab8137c9b72275dc036d christoffer PUT /api/players/christoffer +[2025-08-15T11:54:02.848Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:54:02.848Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:54:02.848Z] DB: updatePlayer - start christoffer +[2025-08-15T11:54:02.848Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T11:54:02.848Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:54:02.848Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:54:02.848Z] API: Updated player christoffer +[2025-08-15T11:54:02.874Z] DB: getSession - start 035f761fdda85f170002a0db6d49f92bcacce0f8aa0aab8137c9b72275dc036d +[2025-08-15T11:54:02.874Z] DB: getSession - ok 035f761fdda85f170002a0db6d49f92bcacce0f8aa0aab8137c9b72275dc036d +[2025-08-15T11:54:02.874Z] SESSION: Valid 035f761fdda85f170002a0db6d49f92bcacce0f8aa0aab8137c9b72275dc036d christoffer PUT /api/players/christoffer +[2025-08-15T11:54:02.875Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:54:02.875Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:54:02.875Z] DB: updatePlayer - start christoffer +[2025-08-15T11:54:02.875Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T11:54:02.875Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:54:02.875Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:54:02.875Z] API: Updated player christoffer +[2025-08-15T11:55:05.948Z] DB: getPlayerByName - start gm +[2025-08-15T11:55:05.948Z] DB: getPlayerByName - found gm +[2025-08-15T11:55:05.948Z] DB: createSession - start 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd expiresAt 2025-08-16T11:55:05.948Z +[2025-08-15T11:55:05.949Z] DB: createSession - done 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd +[2025-08-15T11:55:05.949Z] SESSION: Login success gm 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd +[2025-08-15T11:55:05.959Z] DB: getSession - start 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd +[2025-08-15T11:55:05.959Z] DB: getSession - ok 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd +[2025-08-15T11:55:05.959Z] SESSION: Validate success 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd gm +[2025-08-15T11:55:07.986Z] DB: getAllPlayers - start +[2025-08-15T11:55:07.987Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T11:55:07.987Z] API: Fetch all players (public) Found 4 players +[2025-08-15T11:55:16.946Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T11:55:16.946Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:55:16.947Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:55:16.947Z] DB: updatePlayer - start christoffer +[2025-08-15T11:55:16.947Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T11:55:16.947Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:55:16.947Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:55:16.947Z] API: Updated player christoffer +[2025-08-15T11:55:16.951Z] DB: getAllPlayers - start +[2025-08-15T11:55:16.952Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T11:55:16.952Z] API: Fetch all players (public) Found 4 players +[2025-08-15T11:56:47.577Z] DB: getAllPlayers - start +[2025-08-15T11:56:47.578Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T11:56:47.906Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T11:56:47.907Z] DB: deletePlayer - start gmtest +[2025-08-15T11:56:47.907Z] DB: deletePlayer - changes 0 gmtest +[2025-08-15T11:56:47.923Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:56:47.923Z] DB: getPlayerByName - not found gmtest +[2025-08-15T11:56:48.002Z] DB: createPlayer - start gmtest +[2025-08-15T11:56:48.003Z] DB: createPlayer - done gmtest rowid 33 +[2025-08-15T11:56:48.003Z] API: Created player gmtest +[2025-08-15T11:56:48.021Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T11:56:48.021Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:56:48.021Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:56:48.021Z] DB: updatePlayer - start gmtest +[2025-08-15T11:56:48.022Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T11:56:48.022Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:56:48.022Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:56:48.022Z] API: Updated player gmtest +[2025-08-15T11:56:48.037Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T11:56:48.037Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:56:48.037Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:56:48.116Z] DB: updatePlayer - start gmtest +[2025-08-15T11:56:48.117Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T11:56:48.117Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:56:48.117Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:56:48.117Z] API: Updated player gmtest +[2025-08-15T11:56:48.131Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:56:48.132Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:56:48.211Z] DB: createSession - start 03d3345bb7a2f5056e253c9bb658c48b1ca2d0ccf6899d77b7f910b2298619c3 expiresAt 2025-08-16T11:56:48.211Z +[2025-08-15T11:56:48.211Z] DB: createSession - done 03d3345bb7a2f5056e253c9bb658c48b1ca2d0ccf6899d77b7f910b2298619c3 +[2025-08-15T11:56:48.211Z] API: Player login gmtest 03d3345bb7a2f5056e253c9bb658c48b1ca2d0ccf6899d77b7f910b2298619c3 +[2025-08-15T11:56:48.227Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T11:56:48.227Z] DB: deletePlayer - start gmtest +[2025-08-15T11:56:48.227Z] DB: deletePlayer - changes 1 gmtest +[2025-08-15T11:56:48.227Z] API: Deleted player gmtest +[2025-08-15T11:56:48.242Z] DB: getAllPlayers - start +[2025-08-15T11:56:48.242Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T11:56:48.242Z] API: Fetch all players (public) Found 4 players +[2025-08-15T11:56:48.578Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:56:48.578Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:56:48.657Z] DB: createSession - start a831c6c037aac51d9b7964f275a31c2de7d7cb1400f7b5af247dacd1c5e7ca6b expiresAt 2025-08-16T11:56:48.657Z +[2025-08-15T11:56:48.657Z] DB: createSession - done a831c6c037aac51d9b7964f275a31c2de7d7cb1400f7b5af247dacd1c5e7ca6b +[2025-08-15T11:56:48.657Z] API: Player login christoffer a831c6c037aac51d9b7964f275a31c2de7d7cb1400f7b5af247dacd1c5e7ca6b +[2025-08-15T11:56:48.677Z] DB: getSession - start a831c6c037aac51d9b7964f275a31c2de7d7cb1400f7b5af247dacd1c5e7ca6b +[2025-08-15T11:56:48.677Z] DB: getSession - ok a831c6c037aac51d9b7964f275a31c2de7d7cb1400f7b5af247dacd1c5e7ca6b +[2025-08-15T11:56:48.677Z] SESSION: Valid a831c6c037aac51d9b7964f275a31c2de7d7cb1400f7b5af247dacd1c5e7ca6b christoffer PUT /api/players/christoffer +[2025-08-15T11:56:48.677Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:56:48.677Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:56:48.677Z] DB: updatePlayer - start christoffer +[2025-08-15T11:56:48.678Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T11:56:48.678Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:56:48.678Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:56:48.678Z] API: Updated player christoffer +[2025-08-15T11:56:48.694Z] DB: getSession - start a831c6c037aac51d9b7964f275a31c2de7d7cb1400f7b5af247dacd1c5e7ca6b +[2025-08-15T11:56:48.694Z] DB: getSession - ok a831c6c037aac51d9b7964f275a31c2de7d7cb1400f7b5af247dacd1c5e7ca6b +[2025-08-15T11:56:48.694Z] SESSION: Valid a831c6c037aac51d9b7964f275a31c2de7d7cb1400f7b5af247dacd1c5e7ca6b christoffer PUT /api/players/christoffer +[2025-08-15T11:56:48.694Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:56:48.694Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:56:48.694Z] DB: updatePlayer - start christoffer +[2025-08-15T11:56:48.694Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T11:56:48.695Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:56:48.695Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:56:48.695Z] API: Updated player christoffer +[2025-08-15T11:57:34.495Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T11:57:34.495Z] DB: deletePlayer - start gmtest +[2025-08-15T11:57:34.495Z] DB: deletePlayer - changes 0 gmtest +[2025-08-15T11:57:34.512Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:57:34.512Z] DB: getPlayerByName - not found gmtest +[2025-08-15T11:57:34.591Z] DB: createPlayer - start gmtest +[2025-08-15T11:57:34.591Z] DB: createPlayer - done gmtest rowid 34 +[2025-08-15T11:57:34.591Z] API: Created player gmtest +[2025-08-15T11:57:34.609Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T11:57:34.609Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:57:34.609Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:57:34.609Z] DB: updatePlayer - start gmtest +[2025-08-15T11:57:34.609Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T11:57:34.609Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:57:34.610Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:57:34.610Z] API: Updated player gmtest +[2025-08-15T11:57:34.624Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T11:57:34.624Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:57:34.624Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:57:34.704Z] DB: updatePlayer - start gmtest +[2025-08-15T11:57:34.704Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T11:57:34.704Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:57:34.704Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:57:34.704Z] API: Updated player gmtest +[2025-08-15T11:57:34.719Z] DB: getPlayerByName - start gmtest +[2025-08-15T11:57:34.719Z] DB: getPlayerByName - found gmtest +[2025-08-15T11:57:34.798Z] DB: createSession - start 9e6588cfc1d2fc773381905b2f657b3601ddbc8e70a008bfb7ebe1d4e5b055ec expiresAt 2025-08-16T11:57:34.798Z +[2025-08-15T11:57:34.798Z] DB: createSession - done 9e6588cfc1d2fc773381905b2f657b3601ddbc8e70a008bfb7ebe1d4e5b055ec +[2025-08-15T11:57:34.798Z] API: Player login gmtest 9e6588cfc1d2fc773381905b2f657b3601ddbc8e70a008bfb7ebe1d4e5b055ec +[2025-08-15T11:57:34.814Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T11:57:34.814Z] DB: deletePlayer - start gmtest +[2025-08-15T11:57:34.814Z] DB: deletePlayer - changes 1 gmtest +[2025-08-15T11:57:34.814Z] API: Deleted player gmtest +[2025-08-15T11:57:34.829Z] DB: getAllPlayers - start +[2025-08-15T11:57:34.829Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T11:57:34.830Z] API: Fetch all players (public) Found 4 players +[2025-08-15T11:57:35.340Z] DB: getAllPlayers - start +[2025-08-15T11:57:35.341Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T11:57:35.668Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:57:35.668Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:57:35.748Z] DB: createSession - start 9029c48f8e6be153b99c0f7f8a752033f4afa334438d33aa0221601b7e470eb6 expiresAt 2025-08-16T11:57:35.748Z +[2025-08-15T11:57:35.749Z] DB: createSession - done 9029c48f8e6be153b99c0f7f8a752033f4afa334438d33aa0221601b7e470eb6 +[2025-08-15T11:57:35.749Z] API: Player login christoffer 9029c48f8e6be153b99c0f7f8a752033f4afa334438d33aa0221601b7e470eb6 +[2025-08-15T11:57:35.768Z] DB: getSession - start 9029c48f8e6be153b99c0f7f8a752033f4afa334438d33aa0221601b7e470eb6 +[2025-08-15T11:57:35.769Z] DB: getSession - ok 9029c48f8e6be153b99c0f7f8a752033f4afa334438d33aa0221601b7e470eb6 +[2025-08-15T11:57:35.769Z] SESSION: Valid 9029c48f8e6be153b99c0f7f8a752033f4afa334438d33aa0221601b7e470eb6 christoffer PUT /api/players/christoffer +[2025-08-15T11:57:35.769Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:57:35.769Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:57:35.769Z] DB: updatePlayer - start christoffer +[2025-08-15T11:57:35.770Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T11:57:35.770Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:57:35.770Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:57:35.770Z] API: Updated player christoffer +[2025-08-15T11:57:35.796Z] DB: getSession - start 9029c48f8e6be153b99c0f7f8a752033f4afa334438d33aa0221601b7e470eb6 +[2025-08-15T11:57:35.796Z] DB: getSession - ok 9029c48f8e6be153b99c0f7f8a752033f4afa334438d33aa0221601b7e470eb6 +[2025-08-15T11:57:35.797Z] SESSION: Valid 9029c48f8e6be153b99c0f7f8a752033f4afa334438d33aa0221601b7e470eb6 christoffer PUT /api/players/christoffer +[2025-08-15T11:57:35.797Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:57:35.797Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:57:35.797Z] DB: updatePlayer - start christoffer +[2025-08-15T11:57:35.797Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T11:57:35.797Z] DB: getPlayerByName - start christoffer +[2025-08-15T11:57:35.797Z] DB: getPlayerByName - found christoffer +[2025-08-15T11:57:35.797Z] API: Updated player christoffer +[2025-08-15T12:17:28.140Z] DB: getSession - start 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd +[2025-08-15T12:17:28.140Z] DB: getSession - ok 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd +[2025-08-15T12:17:28.140Z] SESSION: Validate success 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd gm +[2025-08-15T12:17:31.131Z] DB: getAllPlayers - start +[2025-08-15T12:17:31.131Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:17:31.131Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:17:33.487Z] SESSION: GM bypass accepted PUT /api/players/gm +[2025-08-15T12:17:33.487Z] DB: getPlayerByName - start gm +[2025-08-15T12:17:33.487Z] DB: getPlayerByName - found gm +[2025-08-15T12:17:33.487Z] DB: updatePlayer - start gm +[2025-08-15T12:17:33.487Z] DB: updatePlayer - changes 1 gm +[2025-08-15T12:17:33.487Z] DB: getPlayerByName - start gm +[2025-08-15T12:17:33.487Z] DB: getPlayerByName - found gm +[2025-08-15T12:17:33.488Z] API: Updated player gm +[2025-08-15T12:17:33.492Z] DB: getAllPlayers - start +[2025-08-15T12:17:33.492Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:17:33.493Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:17:34.631Z] SESSION: GM bypass accepted PUT /api/players/gm +[2025-08-15T12:17:34.632Z] DB: getPlayerByName - start gm +[2025-08-15T12:17:34.632Z] DB: getPlayerByName - found gm +[2025-08-15T12:17:34.632Z] DB: updatePlayer - start gm +[2025-08-15T12:17:34.632Z] DB: updatePlayer - changes 1 gm +[2025-08-15T12:17:34.632Z] DB: getPlayerByName - start gm +[2025-08-15T12:17:34.632Z] DB: getPlayerByName - found gm +[2025-08-15T12:17:34.632Z] API: Updated player gm +[2025-08-15T12:17:34.637Z] DB: getAllPlayers - start +[2025-08-15T12:17:34.637Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:17:34.637Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:17:35.425Z] SESSION: GM bypass accepted PUT /api/players/gm +[2025-08-15T12:17:35.425Z] DB: getPlayerByName - start gm +[2025-08-15T12:17:35.425Z] DB: getPlayerByName - found gm +[2025-08-15T12:17:35.425Z] DB: updatePlayer - start gm +[2025-08-15T12:17:35.426Z] DB: updatePlayer - changes 1 gm +[2025-08-15T12:17:35.426Z] DB: getPlayerByName - start gm +[2025-08-15T12:17:35.426Z] DB: getPlayerByName - found gm +[2025-08-15T12:17:35.426Z] API: Updated player gm +[2025-08-15T12:17:35.431Z] DB: getAllPlayers - start +[2025-08-15T12:17:35.431Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:17:35.431Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:17:46.262Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T12:17:46.263Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:17:46.263Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:17:46.263Z] DB: updatePlayer - start christoffer +[2025-08-15T12:17:46.263Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:17:46.263Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:17:46.263Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:17:46.263Z] API: Updated player christoffer +[2025-08-15T12:17:46.268Z] DB: getAllPlayers - start +[2025-08-15T12:17:46.268Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:17:46.268Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:17:53.481Z] SESSION: GM bypass accepted PUT /api/players/phillip +[2025-08-15T12:17:53.481Z] DB: getPlayerByName - start phillip +[2025-08-15T12:17:53.481Z] DB: getPlayerByName - found phillip +[2025-08-15T12:17:53.481Z] DB: updatePlayer - start phillip +[2025-08-15T12:17:53.481Z] DB: updatePlayer - changes 1 phillip +[2025-08-15T12:17:53.481Z] DB: getPlayerByName - start phillip +[2025-08-15T12:17:53.482Z] DB: getPlayerByName - found phillip +[2025-08-15T12:17:53.482Z] API: Updated player phillip +[2025-08-15T12:17:53.487Z] DB: getAllPlayers - start +[2025-08-15T12:17:53.488Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:17:53.488Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:17:55.827Z] SESSION: GM bypass accepted PUT /api/players/claes +[2025-08-15T12:17:55.827Z] DB: getPlayerByName - start claes +[2025-08-15T12:17:55.827Z] DB: getPlayerByName - found claes +[2025-08-15T12:17:55.827Z] DB: updatePlayer - start claes +[2025-08-15T12:17:55.827Z] DB: updatePlayer - changes 1 claes +[2025-08-15T12:17:55.828Z] DB: getPlayerByName - start claes +[2025-08-15T12:17:55.828Z] DB: getPlayerByName - found claes +[2025-08-15T12:17:55.828Z] API: Updated player claes +[2025-08-15T12:17:55.832Z] DB: getAllPlayers - start +[2025-08-15T12:17:55.832Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:17:55.832Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:17:59.174Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T12:17:59.174Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:17:59.174Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:17:59.174Z] DB: updatePlayer - start christoffer +[2025-08-15T12:17:59.174Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:17:59.174Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:17:59.174Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:17:59.174Z] API: Updated player christoffer +[2025-08-15T12:17:59.179Z] DB: getAllPlayers - start +[2025-08-15T12:17:59.179Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:17:59.179Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:18:00.742Z] SESSION: GM bypass accepted PUT /api/players/claes +[2025-08-15T12:18:00.743Z] DB: getPlayerByName - start claes +[2025-08-15T12:18:00.743Z] DB: getPlayerByName - found claes +[2025-08-15T12:18:00.743Z] DB: updatePlayer - start claes +[2025-08-15T12:18:00.743Z] DB: updatePlayer - changes 1 claes +[2025-08-15T12:18:00.743Z] DB: getPlayerByName - start claes +[2025-08-15T12:18:00.743Z] DB: getPlayerByName - found claes +[2025-08-15T12:18:00.743Z] API: Updated player claes +[2025-08-15T12:18:00.749Z] DB: getAllPlayers - start +[2025-08-15T12:18:00.749Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:18:00.749Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:20:50.752Z] DB: getSession - start 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd +[2025-08-15T12:20:50.753Z] DB: getSession - ok 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd +[2025-08-15T12:20:50.753Z] SESSION: Validate success 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd gm +[2025-08-15T12:20:52.783Z] DB: getAllPlayers - start +[2025-08-15T12:20:52.784Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:20:52.784Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:21:23.462Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T12:21:23.462Z] DB: deletePlayer - start gmtest +[2025-08-15T12:21:23.462Z] DB: deletePlayer - changes 0 gmtest +[2025-08-15T12:21:23.479Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:21:23.479Z] DB: getPlayerByName - not found gmtest +[2025-08-15T12:21:23.558Z] DB: createPlayer - start gmtest +[2025-08-15T12:21:23.558Z] DB: createPlayer - done gmtest rowid 35 +[2025-08-15T12:21:23.558Z] API: Created player gmtest +[2025-08-15T12:21:23.576Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T12:21:23.576Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:21:23.576Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:21:23.576Z] DB: updatePlayer - start gmtest +[2025-08-15T12:21:23.576Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T12:21:23.576Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:21:23.576Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:21:23.576Z] API: Updated player gmtest +[2025-08-15T12:21:23.591Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T12:21:23.591Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:21:23.591Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:21:23.670Z] DB: updatePlayer - start gmtest +[2025-08-15T12:21:23.671Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T12:21:23.671Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:21:23.671Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:21:23.671Z] API: Updated player gmtest +[2025-08-15T12:21:23.686Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:21:23.686Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:21:23.765Z] DB: createSession - start 8fbe176cc7b112a1dd1d1b5af01cfea6fb01aac75bfb1e1b64ee37253f4cd013 expiresAt 2025-08-16T12:21:23.765Z +[2025-08-15T12:21:23.765Z] DB: createSession - done 8fbe176cc7b112a1dd1d1b5af01cfea6fb01aac75bfb1e1b64ee37253f4cd013 +[2025-08-15T12:21:23.765Z] API: Player login gmtest 8fbe176cc7b112a1dd1d1b5af01cfea6fb01aac75bfb1e1b64ee37253f4cd013 +[2025-08-15T12:21:23.780Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T12:21:23.781Z] DB: deletePlayer - start gmtest +[2025-08-15T12:21:23.781Z] DB: deletePlayer - changes 1 gmtest +[2025-08-15T12:21:23.781Z] API: Deleted player gmtest +[2025-08-15T12:21:23.796Z] DB: getAllPlayers - start +[2025-08-15T12:21:23.796Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:21:23.796Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:21:24.311Z] DB: getAllPlayers - start +[2025-08-15T12:21:24.312Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:21:24.648Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:21:24.648Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:21:24.727Z] DB: createSession - start ea50a5062134600f5f1c257b5cc0dca526c44e0bc2d43f210103407fc326705e expiresAt 2025-08-16T12:21:24.727Z +[2025-08-15T12:21:24.728Z] DB: createSession - done ea50a5062134600f5f1c257b5cc0dca526c44e0bc2d43f210103407fc326705e +[2025-08-15T12:21:24.728Z] API: Player login christoffer ea50a5062134600f5f1c257b5cc0dca526c44e0bc2d43f210103407fc326705e +[2025-08-15T12:21:24.748Z] DB: getSession - start ea50a5062134600f5f1c257b5cc0dca526c44e0bc2d43f210103407fc326705e +[2025-08-15T12:21:24.748Z] DB: getSession - ok ea50a5062134600f5f1c257b5cc0dca526c44e0bc2d43f210103407fc326705e +[2025-08-15T12:21:24.748Z] SESSION: Valid ea50a5062134600f5f1c257b5cc0dca526c44e0bc2d43f210103407fc326705e christoffer PUT /api/players/christoffer +[2025-08-15T12:21:24.748Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:21:24.748Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:21:24.748Z] DB: updatePlayer - start christoffer +[2025-08-15T12:21:24.748Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:21:24.748Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:21:24.748Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:21:24.748Z] API: Updated player christoffer +[2025-08-15T12:21:24.774Z] DB: getSession - start ea50a5062134600f5f1c257b5cc0dca526c44e0bc2d43f210103407fc326705e +[2025-08-15T12:21:24.774Z] DB: getSession - ok ea50a5062134600f5f1c257b5cc0dca526c44e0bc2d43f210103407fc326705e +[2025-08-15T12:21:24.774Z] SESSION: Valid ea50a5062134600f5f1c257b5cc0dca526c44e0bc2d43f210103407fc326705e christoffer PUT /api/players/christoffer +[2025-08-15T12:21:24.774Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:21:24.774Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:21:24.774Z] DB: updatePlayer - start christoffer +[2025-08-15T12:21:24.774Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:21:24.774Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:21:24.774Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:21:24.774Z] API: Updated player christoffer +[2025-08-15T12:22:59.119Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T12:22:59.119Z] DB: deletePlayer - start gmtest +[2025-08-15T12:22:59.119Z] DB: deletePlayer - changes 0 gmtest +[2025-08-15T12:22:59.135Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:22:59.135Z] DB: getPlayerByName - not found gmtest +[2025-08-15T12:22:59.214Z] DB: createPlayer - start gmtest +[2025-08-15T12:22:59.215Z] DB: createPlayer - done gmtest rowid 36 +[2025-08-15T12:22:59.215Z] API: Created player gmtest +[2025-08-15T12:22:59.232Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T12:22:59.233Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:22:59.233Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:22:59.233Z] DB: updatePlayer - start gmtest +[2025-08-15T12:22:59.233Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T12:22:59.233Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:22:59.233Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:22:59.233Z] API: Updated player gmtest +[2025-08-15T12:22:59.247Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T12:22:59.248Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:22:59.248Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:22:59.328Z] DB: updatePlayer - start gmtest +[2025-08-15T12:22:59.329Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T12:22:59.329Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:22:59.329Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:22:59.329Z] API: Updated player gmtest +[2025-08-15T12:22:59.343Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:22:59.343Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:22:59.422Z] DB: createSession - start 616f3f941e3bc99716698191b36c2a82d0988cc4d30c59e9c539a1ed5f2cee19 expiresAt 2025-08-16T12:22:59.422Z +[2025-08-15T12:22:59.423Z] DB: createSession - done 616f3f941e3bc99716698191b36c2a82d0988cc4d30c59e9c539a1ed5f2cee19 +[2025-08-15T12:22:59.423Z] API: Player login gmtest 616f3f941e3bc99716698191b36c2a82d0988cc4d30c59e9c539a1ed5f2cee19 +[2025-08-15T12:22:59.438Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T12:22:59.438Z] DB: deletePlayer - start gmtest +[2025-08-15T12:22:59.438Z] DB: deletePlayer - changes 1 gmtest +[2025-08-15T12:22:59.438Z] API: Deleted player gmtest +[2025-08-15T12:22:59.453Z] DB: getAllPlayers - start +[2025-08-15T12:22:59.453Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:22:59.453Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:22:59.974Z] DB: getAllPlayers - start +[2025-08-15T12:22:59.975Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:23:00.326Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:23:00.326Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:23:00.405Z] DB: createSession - start 2d542e39162f00ba4a5af5b71d033f3ce91a89c8da50f504590c8b99fdc041c0 expiresAt 2025-08-16T12:23:00.405Z +[2025-08-15T12:23:00.406Z] DB: createSession - done 2d542e39162f00ba4a5af5b71d033f3ce91a89c8da50f504590c8b99fdc041c0 +[2025-08-15T12:23:00.406Z] API: Player login christoffer 2d542e39162f00ba4a5af5b71d033f3ce91a89c8da50f504590c8b99fdc041c0 +[2025-08-15T12:23:00.435Z] DB: getSession - start 2d542e39162f00ba4a5af5b71d033f3ce91a89c8da50f504590c8b99fdc041c0 +[2025-08-15T12:23:00.436Z] DB: getSession - ok 2d542e39162f00ba4a5af5b71d033f3ce91a89c8da50f504590c8b99fdc041c0 +[2025-08-15T12:23:00.436Z] SESSION: Valid 2d542e39162f00ba4a5af5b71d033f3ce91a89c8da50f504590c8b99fdc041c0 christoffer PUT /api/players/christoffer +[2025-08-15T12:23:00.436Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:23:00.436Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:23:00.436Z] DB: updatePlayer - start christoffer +[2025-08-15T12:23:00.437Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:23:00.437Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:23:00.437Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:23:00.437Z] API: Updated player christoffer +[2025-08-15T12:23:00.455Z] DB: getSession - start 2d542e39162f00ba4a5af5b71d033f3ce91a89c8da50f504590c8b99fdc041c0 +[2025-08-15T12:23:00.455Z] DB: getSession - ok 2d542e39162f00ba4a5af5b71d033f3ce91a89c8da50f504590c8b99fdc041c0 +[2025-08-15T12:23:00.455Z] SESSION: Valid 2d542e39162f00ba4a5af5b71d033f3ce91a89c8da50f504590c8b99fdc041c0 christoffer PUT /api/players/christoffer +[2025-08-15T12:23:00.455Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:23:00.455Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:23:00.455Z] DB: updatePlayer - start christoffer +[2025-08-15T12:23:00.456Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:23:00.456Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:23:00.456Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:23:00.456Z] API: Updated player christoffer +[2025-08-15T12:23:35.306Z] DB: getSession - start 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd +[2025-08-15T12:23:35.306Z] DB: getSession - ok 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd +[2025-08-15T12:23:35.306Z] SESSION: Validate success 1682ff032279e86b4f0d1579ca92829081b33d390884e7c50e8ec31cb27287bd gm +[2025-08-15T12:23:36.602Z] DB: getAllPlayers - start +[2025-08-15T12:23:36.603Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:23:36.603Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:23:54.447Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T12:23:54.447Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:23:54.447Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:23:54.447Z] DB: updatePlayer - start christoffer +[2025-08-15T12:23:54.447Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:23:54.448Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:23:54.448Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:23:54.448Z] API: Updated player christoffer +[2025-08-15T12:23:54.455Z] SESSION: GM bypass accepted PUT /api/players/undefined +[2025-08-15T12:23:54.455Z] DB: getPlayerByName - start undefined +[2025-08-15T12:23:54.455Z] DB: getPlayerByName - not found undefined +[2025-08-15T12:23:59.616Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T12:23:59.616Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:23:59.616Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:23:59.616Z] DB: updatePlayer - start christoffer +[2025-08-15T12:23:59.617Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:23:59.617Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:23:59.617Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:23:59.617Z] API: Updated player christoffer +[2025-08-15T12:23:59.623Z] SESSION: GM bypass accepted PUT /api/players/undefined +[2025-08-15T12:23:59.623Z] DB: getPlayerByName - start undefined +[2025-08-15T12:23:59.624Z] DB: getPlayerByName - not found undefined +[2025-08-15T12:24:06.497Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T12:24:06.498Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:24:06.498Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:24:06.498Z] DB: updatePlayer - start christoffer +[2025-08-15T12:24:06.499Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:24:06.499Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:24:06.499Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:24:06.499Z] API: Updated player christoffer +[2025-08-15T12:24:06.505Z] SESSION: GM bypass accepted PUT /api/players/undefined +[2025-08-15T12:24:06.505Z] DB: getPlayerByName - start undefined +[2025-08-15T12:24:06.506Z] DB: getPlayerByName - not found undefined +[2025-08-15T12:24:08.900Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T12:24:08.901Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:24:08.901Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:24:08.901Z] DB: updatePlayer - start christoffer +[2025-08-15T12:24:08.901Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:24:08.901Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:24:08.901Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:24:08.901Z] API: Updated player christoffer +[2025-08-15T12:24:08.911Z] SESSION: GM bypass accepted PUT /api/players/undefined +[2025-08-15T12:24:08.912Z] DB: getPlayerByName - start undefined +[2025-08-15T12:24:08.912Z] DB: getPlayerByName - not found undefined +[2025-08-15T12:24:28.489Z] DB: getAllPlayers - start +[2025-08-15T12:24:28.489Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:24:28.489Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:24:33.964Z] DB: getPlayerByName - start gm +[2025-08-15T12:24:33.964Z] DB: getPlayerByName - found gm +[2025-08-15T12:24:33.964Z] DB: createSession - start fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 expiresAt 2025-08-16T12:24:33.964Z +[2025-08-15T12:24:33.964Z] DB: createSession - done fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:24:33.964Z] SESSION: Login success gm fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:24:34.008Z] DB: getAllPlayers - start +[2025-08-15T12:24:34.008Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:24:34.008Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:24:34.010Z] DB: getSession - start fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:24:34.011Z] DB: getSession - ok fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:24:34.011Z] SESSION: Validate success fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 gm +[2025-08-15T12:24:49.052Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T12:24:49.052Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:24:49.052Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:24:49.052Z] DB: updatePlayer - start christoffer +[2025-08-15T12:24:49.052Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:24:49.052Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:24:49.052Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:24:49.053Z] API: Updated player christoffer +[2025-08-15T12:24:49.112Z] SESSION: GM bypass accepted PUT /api/players/undefined +[2025-08-15T12:24:49.112Z] DB: getPlayerByName - start undefined +[2025-08-15T12:24:49.112Z] DB: getPlayerByName - not found undefined +[2025-08-15T12:24:51.745Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T12:24:51.746Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:24:51.746Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:24:51.746Z] DB: updatePlayer - start christoffer +[2025-08-15T12:24:51.746Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:24:51.746Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:24:51.746Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:24:51.746Z] API: Updated player christoffer +[2025-08-15T12:24:51.779Z] SESSION: GM bypass accepted PUT /api/players/undefined +[2025-08-15T12:24:51.779Z] DB: getPlayerByName - start undefined +[2025-08-15T12:24:51.779Z] DB: getPlayerByName - not found undefined +[2025-08-15T12:24:52.856Z] DB: getSession - start fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:24:52.857Z] DB: getSession - ok fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:24:52.857Z] SESSION: Validate success fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 gm +[2025-08-15T12:24:53.939Z] DB: getAllPlayers - start +[2025-08-15T12:24:53.939Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:24:53.939Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:25:24.384Z] SESSION: GM bypass accepted PUT /api/players/gm +[2025-08-15T12:25:24.384Z] DB: getPlayerByName - start gm +[2025-08-15T12:25:24.384Z] DB: getPlayerByName - found gm +[2025-08-15T12:25:24.384Z] DB: updatePlayer - start gm +[2025-08-15T12:25:24.384Z] DB: updatePlayer - changes 1 gm +[2025-08-15T12:25:24.384Z] DB: getPlayerByName - start gm +[2025-08-15T12:25:24.384Z] DB: getPlayerByName - found gm +[2025-08-15T12:25:24.384Z] API: Updated player gm +[2025-08-15T12:25:24.419Z] DB: getAllPlayers - start +[2025-08-15T12:25:24.419Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:25:24.419Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:25:50.899Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T12:25:50.899Z] DB: deletePlayer - start gmtest +[2025-08-15T12:25:50.900Z] DB: deletePlayer - changes 0 gmtest +[2025-08-15T12:25:50.916Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:25:50.916Z] DB: getPlayerByName - not found gmtest +[2025-08-15T12:25:50.995Z] DB: createPlayer - start gmtest +[2025-08-15T12:25:50.996Z] DB: createPlayer - done gmtest rowid 37 +[2025-08-15T12:25:50.996Z] API: Created player gmtest +[2025-08-15T12:25:51.014Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T12:25:51.014Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:25:51.014Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:25:51.014Z] DB: updatePlayer - start gmtest +[2025-08-15T12:25:51.014Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T12:25:51.014Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:25:51.014Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:25:51.014Z] API: Updated player gmtest +[2025-08-15T12:25:51.029Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T12:25:51.029Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:25:51.029Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:25:51.109Z] DB: updatePlayer - start gmtest +[2025-08-15T12:25:51.109Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T12:25:51.109Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:25:51.109Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:25:51.109Z] API: Updated player gmtest +[2025-08-15T12:25:51.124Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:25:51.124Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:25:51.204Z] DB: createSession - start c1df7d74ffa22c348de4eafcf72bc13efbc7632c8907b080c114d211b06fdb39 expiresAt 2025-08-16T12:25:51.204Z +[2025-08-15T12:25:51.204Z] DB: createSession - done c1df7d74ffa22c348de4eafcf72bc13efbc7632c8907b080c114d211b06fdb39 +[2025-08-15T12:25:51.204Z] API: Player login gmtest c1df7d74ffa22c348de4eafcf72bc13efbc7632c8907b080c114d211b06fdb39 +[2025-08-15T12:25:51.219Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T12:25:51.219Z] DB: deletePlayer - start gmtest +[2025-08-15T12:25:51.219Z] DB: deletePlayer - changes 1 gmtest +[2025-08-15T12:25:51.219Z] API: Deleted player gmtest +[2025-08-15T12:25:51.235Z] DB: getAllPlayers - start +[2025-08-15T12:25:51.235Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:25:51.235Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:25:51.736Z] DB: getAllPlayers - start +[2025-08-15T12:25:51.737Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:25:52.067Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:25:52.067Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:25:52.146Z] DB: createSession - start a6aa25ff62be9bca4366ea972c44f6aa066ad99ed5b8b6ecd67f63a841a344fe expiresAt 2025-08-16T12:25:52.146Z +[2025-08-15T12:25:52.147Z] DB: createSession - done a6aa25ff62be9bca4366ea972c44f6aa066ad99ed5b8b6ecd67f63a841a344fe +[2025-08-15T12:25:52.147Z] API: Player login christoffer a6aa25ff62be9bca4366ea972c44f6aa066ad99ed5b8b6ecd67f63a841a344fe +[2025-08-15T12:25:52.166Z] DB: getSession - start a6aa25ff62be9bca4366ea972c44f6aa066ad99ed5b8b6ecd67f63a841a344fe +[2025-08-15T12:25:52.166Z] DB: getSession - ok a6aa25ff62be9bca4366ea972c44f6aa066ad99ed5b8b6ecd67f63a841a344fe +[2025-08-15T12:25:52.166Z] SESSION: Valid a6aa25ff62be9bca4366ea972c44f6aa066ad99ed5b8b6ecd67f63a841a344fe christoffer PUT /api/players/christoffer +[2025-08-15T12:25:52.167Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:25:52.167Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:25:52.167Z] DB: updatePlayer - start christoffer +[2025-08-15T12:25:52.167Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:25:52.167Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:25:52.167Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:25:52.167Z] API: Updated player christoffer +[2025-08-15T12:25:52.193Z] DB: getSession - start a6aa25ff62be9bca4366ea972c44f6aa066ad99ed5b8b6ecd67f63a841a344fe +[2025-08-15T12:25:52.193Z] DB: getSession - ok a6aa25ff62be9bca4366ea972c44f6aa066ad99ed5b8b6ecd67f63a841a344fe +[2025-08-15T12:25:52.194Z] SESSION: Valid a6aa25ff62be9bca4366ea972c44f6aa066ad99ed5b8b6ecd67f63a841a344fe christoffer PUT /api/players/christoffer +[2025-08-15T12:25:52.194Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:25:52.194Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:25:52.194Z] DB: updatePlayer - start christoffer +[2025-08-15T12:25:52.194Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:25:52.194Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:25:52.194Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:25:52.195Z] API: Updated player christoffer +[2025-08-15T12:26:11.681Z] DB: getSession - start fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:26:11.681Z] DB: getSession - ok fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:26:11.682Z] SESSION: Validate success fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 gm +[2025-08-15T12:26:13.538Z] DB: getAllPlayers - start +[2025-08-15T12:26:13.538Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:26:13.538Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:26:17.718Z] DB: getAllPlayers - start +[2025-08-15T12:26:17.718Z] DB: getAllPlayers - resultCount 4 +[2025-08-15T12:26:17.718Z] API: Fetch all players (public) Found 4 players +[2025-08-15T12:26:31.221Z] DB: getPlayerByName - start anders +[2025-08-15T12:26:31.221Z] DB: getPlayerByName - not found anders +[2025-08-15T12:26:31.300Z] DB: createPlayer - start anders +[2025-08-15T12:26:31.300Z] DB: createPlayer - done anders rowid 38 +[2025-08-15T12:26:31.300Z] API: Created player anders +[2025-08-15T12:26:31.332Z] DB: getAllPlayers - start +[2025-08-15T12:26:31.332Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:26:31.333Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:26:42.467Z] DB: getSession - start fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:26:42.468Z] DB: getSession - ok fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:26:42.468Z] SESSION: Validate success fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 gm +[2025-08-15T12:26:44.537Z] DB: getAllPlayers - start +[2025-08-15T12:26:44.538Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:26:44.538Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:26:53.537Z] SESSION: GM bypass accepted PUT /api/players/anders +[2025-08-15T12:26:53.537Z] DB: getPlayerByName - start anders +[2025-08-15T12:26:53.537Z] DB: getPlayerByName - found anders +[2025-08-15T12:26:53.537Z] DB: updatePlayer - start anders +[2025-08-15T12:26:53.537Z] DB: updatePlayer - changes 1 anders +[2025-08-15T12:26:53.537Z] DB: getPlayerByName - start anders +[2025-08-15T12:26:53.537Z] DB: getPlayerByName - found anders +[2025-08-15T12:26:53.537Z] API: Updated player anders +[2025-08-15T12:26:53.575Z] DB: getAllPlayers - start +[2025-08-15T12:26:53.575Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:26:53.575Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:26:56.934Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T12:26:56.934Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:26:56.934Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:26:56.934Z] DB: updatePlayer - start christoffer +[2025-08-15T12:26:56.935Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:26:56.935Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:26:56.935Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:26:56.935Z] API: Updated player christoffer +[2025-08-15T12:26:56.969Z] DB: getAllPlayers - start +[2025-08-15T12:26:56.970Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:26:56.970Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:27:01.850Z] SESSION: GM bypass accepted PUT /api/players/anders +[2025-08-15T12:27:01.850Z] DB: getPlayerByName - start anders +[2025-08-15T12:27:01.850Z] DB: getPlayerByName - found anders +[2025-08-15T12:27:01.851Z] DB: updatePlayer - start anders +[2025-08-15T12:27:01.851Z] DB: updatePlayer - changes 1 anders +[2025-08-15T12:27:01.851Z] DB: getPlayerByName - start anders +[2025-08-15T12:27:01.851Z] DB: getPlayerByName - found anders +[2025-08-15T12:27:01.851Z] API: Updated player anders +[2025-08-15T12:27:01.893Z] DB: getAllPlayers - start +[2025-08-15T12:27:01.894Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:27:01.894Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:27:03.860Z] SESSION: GM bypass accepted PUT /api/players/anders +[2025-08-15T12:27:03.860Z] DB: getPlayerByName - start anders +[2025-08-15T12:27:03.860Z] DB: getPlayerByName - found anders +[2025-08-15T12:27:03.860Z] DB: updatePlayer - start anders +[2025-08-15T12:27:03.860Z] DB: updatePlayer - changes 1 anders +[2025-08-15T12:27:03.860Z] DB: getPlayerByName - start anders +[2025-08-15T12:27:03.860Z] DB: getPlayerByName - found anders +[2025-08-15T12:27:03.860Z] API: Updated player anders +[2025-08-15T12:27:03.900Z] DB: getAllPlayers - start +[2025-08-15T12:27:03.901Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:27:03.901Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:27:06.742Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T12:27:06.747Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:27:06.748Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:27:06.748Z] DB: updatePlayer - start christoffer +[2025-08-15T12:27:06.748Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:27:06.748Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:27:06.748Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:27:06.748Z] API: Updated player christoffer +[2025-08-15T12:27:06.781Z] DB: getAllPlayers - start +[2025-08-15T12:27:06.781Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:27:06.781Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:27:10.817Z] SESSION: GM bypass accepted PUT /api/players/christoffer +[2025-08-15T12:27:10.818Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:27:10.818Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:27:10.818Z] DB: updatePlayer - start christoffer +[2025-08-15T12:27:10.818Z] DB: updatePlayer - changes 1 christoffer +[2025-08-15T12:27:10.818Z] DB: getPlayerByName - start christoffer +[2025-08-15T12:27:10.818Z] DB: getPlayerByName - found christoffer +[2025-08-15T12:27:10.818Z] API: Updated player christoffer +[2025-08-15T12:27:10.855Z] DB: getAllPlayers - start +[2025-08-15T12:27:10.855Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:27:10.855Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:27:13.533Z] SESSION: GM bypass accepted PUT /api/players/claes +[2025-08-15T12:27:13.533Z] DB: getPlayerByName - start claes +[2025-08-15T12:27:13.533Z] DB: getPlayerByName - found claes +[2025-08-15T12:27:13.533Z] DB: updatePlayer - start claes +[2025-08-15T12:27:13.533Z] DB: updatePlayer - changes 1 claes +[2025-08-15T12:27:13.533Z] DB: getPlayerByName - start claes +[2025-08-15T12:27:13.533Z] DB: getPlayerByName - found claes +[2025-08-15T12:27:13.533Z] API: Updated player claes +[2025-08-15T12:27:13.571Z] DB: getAllPlayers - start +[2025-08-15T12:27:13.571Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:27:13.571Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:27:15.081Z] SESSION: GM bypass accepted PUT /api/players/claes +[2025-08-15T12:27:15.081Z] DB: getPlayerByName - start claes +[2025-08-15T12:27:15.082Z] DB: getPlayerByName - found claes +[2025-08-15T12:27:15.082Z] DB: updatePlayer - start claes +[2025-08-15T12:27:15.082Z] DB: updatePlayer - changes 1 claes +[2025-08-15T12:27:15.082Z] DB: getPlayerByName - start claes +[2025-08-15T12:27:15.082Z] DB: getPlayerByName - found claes +[2025-08-15T12:27:15.082Z] API: Updated player claes +[2025-08-15T12:27:15.116Z] DB: getAllPlayers - start +[2025-08-15T12:27:15.116Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:27:15.117Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:27:16.987Z] SESSION: GM bypass accepted PUT /api/players/claes +[2025-08-15T12:27:16.987Z] DB: getPlayerByName - start claes +[2025-08-15T12:27:16.988Z] DB: getPlayerByName - found claes +[2025-08-15T12:27:16.988Z] DB: updatePlayer - start claes +[2025-08-15T12:27:16.988Z] DB: updatePlayer - changes 1 claes +[2025-08-15T12:27:16.988Z] DB: getPlayerByName - start claes +[2025-08-15T12:27:16.988Z] DB: getPlayerByName - found claes +[2025-08-15T12:27:16.988Z] API: Updated player claes +[2025-08-15T12:27:17.024Z] DB: getAllPlayers - start +[2025-08-15T12:27:17.024Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:27:17.025Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:27:20.307Z] SESSION: GM bypass accepted PUT /api/players/phillip +[2025-08-15T12:27:20.307Z] DB: getPlayerByName - start phillip +[2025-08-15T12:27:20.307Z] DB: getPlayerByName - found phillip +[2025-08-15T12:27:20.307Z] DB: updatePlayer - start phillip +[2025-08-15T12:27:20.308Z] DB: updatePlayer - changes 1 phillip +[2025-08-15T12:27:20.308Z] DB: getPlayerByName - start phillip +[2025-08-15T12:27:20.308Z] DB: getPlayerByName - found phillip +[2025-08-15T12:27:20.308Z] API: Updated player phillip +[2025-08-15T12:27:20.349Z] DB: getAllPlayers - start +[2025-08-15T12:27:20.350Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:27:20.350Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:27:21.686Z] SESSION: GM bypass accepted PUT /api/players/phillip +[2025-08-15T12:27:21.687Z] DB: getPlayerByName - start phillip +[2025-08-15T12:27:21.687Z] DB: getPlayerByName - found phillip +[2025-08-15T12:27:21.687Z] DB: updatePlayer - start phillip +[2025-08-15T12:27:21.687Z] DB: updatePlayer - changes 1 phillip +[2025-08-15T12:27:21.687Z] DB: getPlayerByName - start phillip +[2025-08-15T12:27:21.687Z] DB: getPlayerByName - found phillip +[2025-08-15T12:27:21.687Z] API: Updated player phillip +[2025-08-15T12:27:21.724Z] DB: getAllPlayers - start +[2025-08-15T12:27:21.725Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:27:21.725Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:28:53.747Z] DB: deleteSession - start fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:28:53.748Z] DB: deleteSession - changes 1 fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:28:53.748Z] SESSION: Logout success fef51639507beeced866979db43225f6358ab6c5fc5365f26ba39f3502b678b8 +[2025-08-15T12:28:53.798Z] DB: getAllPlayers - start +[2025-08-15T12:28:53.799Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:28:53.799Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:28:57.316Z] DB: getAllPlayers - start +[2025-08-15T12:28:57.317Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:28:57.317Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:29:00.716Z] DB: getPlayerByName - start anders +[2025-08-15T12:29:00.716Z] DB: getPlayerByName - found anders +[2025-08-15T12:29:00.716Z] DB: createSession - start 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 expiresAt 2025-08-16T12:29:00.716Z +[2025-08-15T12:29:00.716Z] DB: createSession - done 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T12:29:00.716Z] SESSION: Login success anders 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T12:29:00.753Z] DB: getAllPlayers - start +[2025-08-15T12:29:00.753Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:29:00.753Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:29:00.757Z] DB: getSession - start 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T12:29:00.757Z] DB: getSession - ok 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T12:29:00.757Z] SESSION: Validate success 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 anders +[2025-08-15T12:30:10.509Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T12:30:10.509Z] DB: deletePlayer - start gmtest +[2025-08-15T12:30:10.509Z] DB: deletePlayer - changes 0 gmtest +[2025-08-15T12:30:10.526Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:30:10.526Z] DB: getPlayerByName - not found gmtest +[2025-08-15T12:30:10.607Z] DB: createPlayer - start gmtest +[2025-08-15T12:30:10.608Z] DB: createPlayer - done gmtest rowid 39 +[2025-08-15T12:30:10.608Z] API: Created player gmtest +[2025-08-15T12:30:10.625Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T12:30:10.625Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:30:10.625Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:30:10.625Z] DB: updatePlayer - start gmtest +[2025-08-15T12:30:10.625Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T12:30:10.625Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:30:10.625Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:30:10.625Z] API: Updated player gmtest +[2025-08-15T12:30:10.640Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T12:30:10.640Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:30:10.640Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:30:10.721Z] DB: updatePlayer - start gmtest +[2025-08-15T12:30:10.722Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T12:30:10.722Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:30:10.722Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:30:10.722Z] API: Updated player gmtest +[2025-08-15T12:30:10.737Z] DB: getPlayerByName - start gmtest +[2025-08-15T12:30:10.737Z] DB: getPlayerByName - found gmtest +[2025-08-15T12:30:10.817Z] DB: createSession - start 92455e00d9617e82c91b09344fd08c9394d0311bd7d263d8265cd4f656134292 expiresAt 2025-08-16T12:30:10.817Z +[2025-08-15T12:30:10.817Z] DB: createSession - done 92455e00d9617e82c91b09344fd08c9394d0311bd7d263d8265cd4f656134292 +[2025-08-15T12:30:10.817Z] API: Player login gmtest 92455e00d9617e82c91b09344fd08c9394d0311bd7d263d8265cd4f656134292 +[2025-08-15T12:30:10.832Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T12:30:10.832Z] DB: deletePlayer - start gmtest +[2025-08-15T12:30:10.832Z] DB: deletePlayer - changes 1 gmtest +[2025-08-15T12:30:10.833Z] API: Deleted player gmtest +[2025-08-15T12:30:10.848Z] DB: getAllPlayers - start +[2025-08-15T12:30:10.848Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:30:10.848Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:30:11.357Z] DB: getAllPlayers - start +[2025-08-15T12:30:11.358Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:30:16.867Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-15T12:30:16.867Z] DB: deletePlayer - start testplayer +[2025-08-15T12:30:16.867Z] DB: deletePlayer - changes 0 testplayer +[2025-08-15T12:30:16.893Z] DB: getPlayerByName - start testplayer +[2025-08-15T12:30:16.894Z] DB: getPlayerByName - not found testplayer +[2025-08-15T12:30:16.998Z] DB: createPlayer - start testplayer +[2025-08-15T12:30:16.998Z] DB: createPlayer - done testplayer rowid 40 +[2025-08-15T12:30:16.998Z] API: Created player testplayer +[2025-08-15T12:30:17.017Z] DB: getPlayerByName - start testplayer +[2025-08-15T12:30:17.017Z] DB: getPlayerByName - found testplayer +[2025-08-15T12:30:17.097Z] DB: createSession - start df81909b52d9497fb06f7a5c2b9869319e8a0d915747bff8399505e538e1f457 expiresAt 2025-08-16T12:30:17.097Z +[2025-08-15T12:30:17.097Z] DB: createSession - done df81909b52d9497fb06f7a5c2b9869319e8a0d915747bff8399505e538e1f457 +[2025-08-15T12:30:17.097Z] API: Player login testplayer df81909b52d9497fb06f7a5c2b9869319e8a0d915747bff8399505e538e1f457 +[2025-08-15T12:30:17.113Z] DB: getSession - start df81909b52d9497fb06f7a5c2b9869319e8a0d915747bff8399505e538e1f457 +[2025-08-15T12:30:17.113Z] DB: getSession - ok df81909b52d9497fb06f7a5c2b9869319e8a0d915747bff8399505e538e1f457 +[2025-08-15T12:30:17.114Z] SESSION: Valid df81909b52d9497fb06f7a5c2b9869319e8a0d915747bff8399505e538e1f457 testplayer PUT /api/players/testplayer +[2025-08-15T12:30:17.114Z] DB: getPlayerByName - start testplayer +[2025-08-15T12:30:17.114Z] DB: getPlayerByName - found testplayer +[2025-08-15T12:30:17.114Z] DB: updatePlayer - start testplayer +[2025-08-15T12:30:17.114Z] DB: updatePlayer - changes 1 testplayer +[2025-08-15T12:30:17.114Z] DB: getPlayerByName - start testplayer +[2025-08-15T12:30:17.114Z] DB: getPlayerByName - found testplayer +[2025-08-15T12:30:17.114Z] API: Updated player testplayer +[2025-08-15T12:30:17.129Z] DB: getSession - start df81909b52d9497fb06f7a5c2b9869319e8a0d915747bff8399505e538e1f457 +[2025-08-15T12:30:17.130Z] DB: getSession - ok df81909b52d9497fb06f7a5c2b9869319e8a0d915747bff8399505e538e1f457 +[2025-08-15T12:30:17.130Z] SESSION: Valid df81909b52d9497fb06f7a5c2b9869319e8a0d915747bff8399505e538e1f457 testplayer PUT /api/players/testplayer +[2025-08-15T12:30:17.130Z] DB: getPlayerByName - start testplayer +[2025-08-15T12:30:17.130Z] DB: getPlayerByName - found testplayer +[2025-08-15T12:30:17.130Z] DB: updatePlayer - start testplayer +[2025-08-15T12:30:17.130Z] DB: updatePlayer - changes 1 testplayer +[2025-08-15T12:30:17.130Z] DB: getPlayerByName - start testplayer +[2025-08-15T12:30:17.130Z] DB: getPlayerByName - found testplayer +[2025-08-15T12:30:17.130Z] API: Updated player testplayer +[2025-08-15T12:30:17.146Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-15T12:30:17.146Z] DB: deletePlayer - start testplayer +[2025-08-15T12:30:17.146Z] DB: deletePlayer - changes 1 testplayer +[2025-08-15T12:30:17.146Z] API: Deleted player testplayer +[2025-08-15T12:33:56.598Z] DB: getSession - start 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T12:33:56.598Z] DB: getSession - ok 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T12:33:56.598Z] SESSION: Validate success 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 anders +[2025-08-15T12:33:57.911Z] DB: getAllPlayers - start +[2025-08-15T12:33:57.912Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:33:57.912Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:35:14.665Z] DB: getSession - start 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T12:35:14.665Z] DB: getSession - ok 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T12:35:14.665Z] SESSION: Validate success 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 anders +[2025-08-15T12:35:16.190Z] DB: getAllPlayers - start +[2025-08-15T12:35:16.191Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:35:16.191Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:43:26.860Z] DB: getSession - start 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T12:43:26.860Z] DB: getSession - ok 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T12:43:26.860Z] SESSION: Validate success 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 anders +[2025-08-15T12:43:28.053Z] DB: getAllPlayers - start +[2025-08-15T12:43:28.053Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:43:28.053Z] API: Fetch all players (public) Found 5 players +[2025-08-15T12:49:01.443Z] DB: getSession - start 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T12:49:01.443Z] DB: getSession - ok 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T12:49:01.444Z] SESSION: Validate success 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 anders +[2025-08-15T12:49:02.602Z] DB: getAllPlayers - start +[2025-08-15T12:49:02.603Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T12:49:02.603Z] API: Fetch all players (public) Found 5 players +[2025-08-15T13:25:21.735Z] DB: getSession - start 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T13:25:21.735Z] DB: getSession - ok 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T13:25:21.735Z] SESSION: Validate success 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 anders +[2025-08-15T13:25:22.852Z] DB: getAllPlayers - start +[2025-08-15T13:25:22.853Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T13:25:22.853Z] API: Fetch all players (public) Found 5 players +[2025-08-15T13:25:26.628Z] DB: getSession - start 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T13:25:26.628Z] DB: getSession - ok 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T13:25:26.628Z] SESSION: Validate success 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 anders +[2025-08-15T13:25:27.151Z] DB: getSession - start 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T13:25:27.151Z] DB: getSession - ok 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T13:25:27.151Z] SESSION: Validate success 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 anders +[2025-08-15T13:25:28.645Z] DB: deleteSession - start 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T13:25:28.645Z] DB: deleteSession - changes 1 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T13:25:28.645Z] SESSION: Logout success 4e5cf7e3c366b6f038abd36b54b7487f13bd1043545a43f186a4918ff4682024 +[2025-08-15T13:25:32.363Z] DB: getAllPlayers - start +[2025-08-15T13:25:32.364Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T13:25:32.364Z] API: Fetch all players (public) Found 5 players +[2025-08-15T13:25:36.543Z] DB: getAllPlayers - start +[2025-08-15T13:25:36.543Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T13:25:36.543Z] API: Fetch all players (public) Found 5 players +[2025-08-15T13:25:38.507Z] DB: getPlayerByName - start anders +[2025-08-15T13:25:38.507Z] DB: getPlayerByName - found anders +[2025-08-15T13:25:38.507Z] DB: createSession - start d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 expiresAt 2025-08-16T13:25:38.507Z +[2025-08-15T13:25:38.507Z] DB: createSession - done d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:25:38.508Z] SESSION: Login success anders d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:25:38.550Z] DB: getSession - start d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:25:38.550Z] DB: getSession - ok d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:25:38.550Z] SESSION: Validate success d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 anders +[2025-08-15T13:25:38.551Z] DB: getAllPlayers - start +[2025-08-15T13:25:38.552Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T13:25:38.552Z] API: Fetch all players (public) Found 5 players +[2025-08-15T13:27:23.171Z] DB: getSession - start d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:27:23.172Z] DB: getSession - ok d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:27:23.172Z] SESSION: Validate success d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 anders +[2025-08-15T13:27:24.987Z] DB: getAllPlayers - start +[2025-08-15T13:27:24.987Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T13:27:24.987Z] API: Fetch all players (public) Found 5 players +[2025-08-15T13:30:57.053Z] DB: getSession - start d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:30:57.053Z] DB: getSession - ok d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:30:57.053Z] SESSION: Validate success d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 anders +[2025-08-15T13:30:58.395Z] DB: getAllPlayers - start +[2025-08-15T13:30:58.395Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T13:30:58.395Z] API: Fetch all players (public) Found 5 players +[2025-08-15T13:50:01.659Z] DB: getPlayerByName - start shop +[2025-08-15T13:50:01.659Z] DB: getPlayerByName - not found shop +[2025-08-15T13:50:26.456Z] DB: getPlayerByName - start shop +[2025-08-15T13:50:26.456Z] DB: getPlayerByName - not found shop +[2025-08-15T13:50:37.795Z] DB: getPlayerByName - start shop +[2025-08-15T13:50:37.795Z] DB: getPlayerByName - not found shop +[2025-08-15T13:50:56.766Z] DB: getPlayerByName - start shop +[2025-08-15T13:50:56.766Z] DB: getPlayerByName - not found shop +[2025-08-15T13:51:46.116Z] DB: getPlayerByName - start shop +[2025-08-15T13:51:46.116Z] DB: getPlayerByName - not found shop +[2025-08-15T13:51:49.903Z] DB: getSession - start d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:51:49.903Z] DB: getSession - ok d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:51:49.903Z] SESSION: Validate success d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 anders +[2025-08-15T13:51:51.082Z] DB: getAllPlayers - start +[2025-08-15T13:51:51.082Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T13:51:51.082Z] API: Fetch all players (public) Found 5 players +[2025-08-15T13:51:57.037Z] DB: getPlayerByName - start shop +[2025-08-15T13:51:57.037Z] DB: getPlayerByName - not found shop +[2025-08-15T13:52:42.374Z] DB: getPlayerByName - start shop +[2025-08-15T13:52:42.374Z] DB: getPlayerByName - not found shop +[2025-08-15T13:53:10.037Z] DB: getPlayerByName - start shop +[2025-08-15T13:53:10.037Z] DB: getPlayerByName - not found shop +[2025-08-15T13:53:56.796Z] DB: getPlayerByName - start shop +[2025-08-15T13:53:56.796Z] DB: getPlayerByName - not found shop +[2025-08-15T13:54:30.026Z] DB: getPlayerByName - start shop +[2025-08-15T13:54:30.026Z] DB: getPlayerByName - not found shop +[2025-08-15T13:56:35.475Z] DB: getSession - start d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:56:35.475Z] DB: getSession - ok d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:56:35.475Z] SESSION: Validate success d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 anders +[2025-08-15T13:56:36.685Z] DB: getAllPlayers - start +[2025-08-15T13:56:36.685Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T13:56:36.686Z] API: Fetch all players (public) Found 5 players +[2025-08-15T13:56:39.537Z] DB: deleteSession - start d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:56:39.538Z] DB: deleteSession - changes 1 d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:56:39.538Z] SESSION: Logout success d2ef5e8167be4ac3653816d8dc66dc22c2eddef9bb004da806b508f6e93eb479 +[2025-08-15T13:56:41.721Z] DB: getAllPlayers - start +[2025-08-15T13:56:41.722Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T13:56:41.722Z] API: Fetch all players (public) Found 5 players +[2025-08-15T13:56:44.855Z] DB: getPlayerByName - start phillip +[2025-08-15T13:56:44.855Z] DB: getPlayerByName - found phillip +[2025-08-15T13:56:44.856Z] DB: createSession - start dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc expiresAt 2025-08-16T13:56:44.856Z +[2025-08-15T13:56:44.856Z] DB: createSession - done dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T13:56:44.856Z] SESSION: Login success phillip dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T13:56:44.892Z] DB: getSession - start dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T13:56:44.893Z] DB: getSession - ok dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T13:56:44.893Z] SESSION: Validate success dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc phillip +[2025-08-15T13:56:44.894Z] DB: getAllPlayers - start +[2025-08-15T13:56:44.894Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T13:56:44.894Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:10:53.028Z] DB: getSession - start dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T14:10:53.028Z] DB: getSession - ok dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T14:10:53.028Z] SESSION: Validate success dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc phillip +[2025-08-15T14:10:53.735Z] DB: getSession - start dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T14:10:53.735Z] DB: getSession - ok dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T14:10:53.735Z] SESSION: Validate success dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc phillip +[2025-08-15T14:10:54.190Z] DB: getAllPlayers - start +[2025-08-15T14:10:54.191Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:10:54.191Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:10:55.762Z] DB: getSession - start dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T14:10:55.762Z] DB: getSession - ok dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T14:10:55.762Z] SESSION: Validate success dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc phillip +[2025-08-15T14:10:56.383Z] DB: getAllPlayers - start +[2025-08-15T14:10:56.383Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:10:56.383Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:10:57.646Z] DB: deleteSession - start dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T14:10:57.646Z] DB: deleteSession - changes 1 dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T14:10:57.646Z] SESSION: Logout success dafeeade87f9e33d923df264c170c8456bb00aa61f5e2a51e962b0de48a665cc +[2025-08-15T14:10:58.786Z] DB: getAllPlayers - start +[2025-08-15T14:10:58.786Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:10:58.786Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:11:01.712Z] DB: getAllPlayers - start +[2025-08-15T14:11:01.712Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:11:01.712Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:11:05.274Z] DB: getPlayerByName - start phillip +[2025-08-15T14:11:05.274Z] DB: getPlayerByName - found phillip +[2025-08-15T14:11:05.274Z] DB: createSession - start a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff expiresAt 2025-08-16T14:11:05.274Z +[2025-08-15T14:11:05.274Z] DB: createSession - done a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:11:05.274Z] SESSION: Login success phillip a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:11:05.310Z] DB: getAllPlayers - start +[2025-08-15T14:11:05.310Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:11:05.310Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:11:05.317Z] DB: getSession - start a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:11:05.317Z] DB: getSession - ok a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:11:05.317Z] SESSION: Validate success a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff phillip +[2025-08-15T14:14:54.368Z] DB: getSession - start a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:14:54.369Z] DB: getSession - ok a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:14:54.369Z] SESSION: Validate success a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff phillip +[2025-08-15T14:14:55.343Z] DB: getAllPlayers - start +[2025-08-15T14:14:55.343Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:14:55.344Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:15:21.452Z] DB: getSession - start a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:15:21.452Z] DB: getSession - ok a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:15:21.452Z] SESSION: Validate success a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff phillip +[2025-08-15T14:15:23.922Z] DB: getSession - start a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:15:23.922Z] DB: getSession - ok a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:15:23.922Z] SESSION: Validate success a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff phillip +[2025-08-15T14:15:24.843Z] DB: getAllPlayers - start +[2025-08-15T14:15:24.843Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:15:24.843Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:15:26.624Z] DB: getSession - start a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:15:26.625Z] DB: getSession - ok a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:15:26.625Z] SESSION: Validate success a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff phillip +[2025-08-15T14:15:27.272Z] DB: getAllPlayers - start +[2025-08-15T14:15:27.272Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:15:27.272Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:15:31.864Z] DB: deleteSession - start a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:15:31.865Z] DB: deleteSession - changes 1 a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:15:31.865Z] SESSION: Logout success a9f85912f7c511f750a806bf12257e9d1efe440c6da3cc4f7ecb7bea0bd356ff +[2025-08-15T14:15:37.255Z] DB: getAllPlayers - start +[2025-08-15T14:15:37.256Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:15:37.256Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:15:40.117Z] DB: getPlayerByName - start christoffer +[2025-08-15T14:15:40.118Z] DB: getPlayerByName - found christoffer +[2025-08-15T14:15:40.118Z] DB: createSession - start 62674f5926820616344d23bf94b6ba3819bce8ff16c2448b9d6f76e464aac53c expiresAt 2025-08-16T14:15:40.118Z +[2025-08-15T14:15:40.118Z] DB: createSession - done 62674f5926820616344d23bf94b6ba3819bce8ff16c2448b9d6f76e464aac53c +[2025-08-15T14:15:40.118Z] SESSION: Login success christoffer 62674f5926820616344d23bf94b6ba3819bce8ff16c2448b9d6f76e464aac53c +[2025-08-15T14:15:40.154Z] DB: getSession - start 62674f5926820616344d23bf94b6ba3819bce8ff16c2448b9d6f76e464aac53c +[2025-08-15T14:15:40.154Z] DB: getSession - ok 62674f5926820616344d23bf94b6ba3819bce8ff16c2448b9d6f76e464aac53c +[2025-08-15T14:15:40.154Z] SESSION: Validate success 62674f5926820616344d23bf94b6ba3819bce8ff16c2448b9d6f76e464aac53c christoffer +[2025-08-15T14:15:40.160Z] DB: getAllPlayers - start +[2025-08-15T14:15:40.160Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:15:40.160Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:16:42.899Z] DB: deleteSession - start 62674f5926820616344d23bf94b6ba3819bce8ff16c2448b9d6f76e464aac53c +[2025-08-15T14:16:42.899Z] DB: deleteSession - changes 1 62674f5926820616344d23bf94b6ba3819bce8ff16c2448b9d6f76e464aac53c +[2025-08-15T14:16:42.899Z] SESSION: Logout success 62674f5926820616344d23bf94b6ba3819bce8ff16c2448b9d6f76e464aac53c +[2025-08-15T14:17:41.565Z] DB: getAllPlayers - start +[2025-08-15T14:17:41.566Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:17:41.566Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:17:44.252Z] DB: getPlayerByName - start christoffer +[2025-08-15T14:17:44.252Z] DB: getPlayerByName - found christoffer +[2025-08-15T14:17:44.253Z] DB: createSession - start aa3090f1a895ae99d0d4ec12f7b3ee70bad23ff4a796c9d42a5a281bc85b44fd expiresAt 2025-08-16T14:17:44.253Z +[2025-08-15T14:17:44.253Z] DB: createSession - done aa3090f1a895ae99d0d4ec12f7b3ee70bad23ff4a796c9d42a5a281bc85b44fd +[2025-08-15T14:17:44.253Z] SESSION: Login success christoffer aa3090f1a895ae99d0d4ec12f7b3ee70bad23ff4a796c9d42a5a281bc85b44fd +[2025-08-15T14:17:44.285Z] DB: getSession - start aa3090f1a895ae99d0d4ec12f7b3ee70bad23ff4a796c9d42a5a281bc85b44fd +[2025-08-15T14:17:44.285Z] DB: getSession - ok aa3090f1a895ae99d0d4ec12f7b3ee70bad23ff4a796c9d42a5a281bc85b44fd +[2025-08-15T14:17:44.285Z] SESSION: Validate success aa3090f1a895ae99d0d4ec12f7b3ee70bad23ff4a796c9d42a5a281bc85b44fd christoffer +[2025-08-15T14:17:45.664Z] DB: getAllPlayers - start +[2025-08-15T14:17:45.664Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:17:45.664Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:17:51.963Z] DB: getSession - start aa3090f1a895ae99d0d4ec12f7b3ee70bad23ff4a796c9d42a5a281bc85b44fd +[2025-08-15T14:17:51.963Z] DB: getSession - ok aa3090f1a895ae99d0d4ec12f7b3ee70bad23ff4a796c9d42a5a281bc85b44fd +[2025-08-15T14:17:51.963Z] SESSION: Validate success aa3090f1a895ae99d0d4ec12f7b3ee70bad23ff4a796c9d42a5a281bc85b44fd christoffer +[2025-08-15T14:17:52.804Z] DB: getAllPlayers - start +[2025-08-15T14:17:52.805Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:17:52.805Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:17:54.510Z] DB: deleteSession - start aa3090f1a895ae99d0d4ec12f7b3ee70bad23ff4a796c9d42a5a281bc85b44fd +[2025-08-15T14:17:54.510Z] DB: deleteSession - changes 1 aa3090f1a895ae99d0d4ec12f7b3ee70bad23ff4a796c9d42a5a281bc85b44fd +[2025-08-15T14:17:54.510Z] SESSION: Logout success aa3090f1a895ae99d0d4ec12f7b3ee70bad23ff4a796c9d42a5a281bc85b44fd +[2025-08-15T14:18:21.660Z] DB: getAllPlayers - start +[2025-08-15T14:18:21.660Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:18:21.660Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:18:23.665Z] DB: getPlayerByName - start claes +[2025-08-15T14:18:23.666Z] DB: getPlayerByName - found claes +[2025-08-15T14:18:23.666Z] DB: createSession - start 15b83f3c20e2fedde846024c417cb1e618e62913b3710a4bf38bea342b3dace1 expiresAt 2025-08-16T14:18:23.666Z +[2025-08-15T14:18:23.666Z] DB: createSession - done 15b83f3c20e2fedde846024c417cb1e618e62913b3710a4bf38bea342b3dace1 +[2025-08-15T14:18:23.666Z] SESSION: Login success claes 15b83f3c20e2fedde846024c417cb1e618e62913b3710a4bf38bea342b3dace1 +[2025-08-15T14:18:23.695Z] DB: getAllPlayers - start +[2025-08-15T14:18:23.696Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:18:23.696Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:18:23.698Z] DB: getSession - start 15b83f3c20e2fedde846024c417cb1e618e62913b3710a4bf38bea342b3dace1 +[2025-08-15T14:18:23.698Z] DB: getSession - ok 15b83f3c20e2fedde846024c417cb1e618e62913b3710a4bf38bea342b3dace1 +[2025-08-15T14:18:23.698Z] SESSION: Validate success 15b83f3c20e2fedde846024c417cb1e618e62913b3710a4bf38bea342b3dace1 claes +[2025-08-15T14:18:31.141Z] DB: deleteSession - start 15b83f3c20e2fedde846024c417cb1e618e62913b3710a4bf38bea342b3dace1 +[2025-08-15T14:18:31.141Z] DB: deleteSession - changes 1 15b83f3c20e2fedde846024c417cb1e618e62913b3710a4bf38bea342b3dace1 +[2025-08-15T14:18:31.141Z] SESSION: Logout success 15b83f3c20e2fedde846024c417cb1e618e62913b3710a4bf38bea342b3dace1 +[2025-08-15T14:18:45.076Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-15T14:18:45.076Z] DB: deletePlayer - start testplayer +[2025-08-15T14:18:45.076Z] DB: deletePlayer - changes 0 testplayer +[2025-08-15T14:18:45.104Z] DB: getPlayerByName - start testplayer +[2025-08-15T14:18:45.104Z] DB: getPlayerByName - not found testplayer +[2025-08-15T14:18:45.184Z] DB: createPlayer - start testplayer +[2025-08-15T14:18:45.184Z] DB: createPlayer - done testplayer rowid 41 +[2025-08-15T14:18:45.184Z] API: Created player testplayer +[2025-08-15T14:18:45.201Z] DB: getPlayerByName - start testplayer +[2025-08-15T14:18:45.201Z] DB: getPlayerByName - found testplayer +[2025-08-15T14:18:45.280Z] DB: createSession - start 134855fef9c75ab8818913098638f431cb130291718a16c73121aaa14c57eb6d expiresAt 2025-08-16T14:18:45.280Z +[2025-08-15T14:18:45.281Z] DB: createSession - done 134855fef9c75ab8818913098638f431cb130291718a16c73121aaa14c57eb6d +[2025-08-15T14:18:45.281Z] API: Player login testplayer 134855fef9c75ab8818913098638f431cb130291718a16c73121aaa14c57eb6d +[2025-08-15T14:18:45.296Z] DB: getSession - start 134855fef9c75ab8818913098638f431cb130291718a16c73121aaa14c57eb6d +[2025-08-15T14:18:45.297Z] DB: getSession - ok 134855fef9c75ab8818913098638f431cb130291718a16c73121aaa14c57eb6d +[2025-08-15T14:18:45.297Z] SESSION: Valid 134855fef9c75ab8818913098638f431cb130291718a16c73121aaa14c57eb6d testplayer PUT /api/players/testplayer +[2025-08-15T14:18:45.297Z] DB: getPlayerByName - start testplayer +[2025-08-15T14:18:45.297Z] DB: getPlayerByName - found testplayer +[2025-08-15T14:18:45.297Z] DB: updatePlayer - start testplayer +[2025-08-15T14:18:45.297Z] DB: updatePlayer - changes 1 testplayer +[2025-08-15T14:18:45.297Z] DB: getPlayerByName - start testplayer +[2025-08-15T14:18:45.297Z] DB: getPlayerByName - found testplayer +[2025-08-15T14:18:45.297Z] API: Updated player testplayer +[2025-08-15T14:18:45.312Z] DB: getSession - start 134855fef9c75ab8818913098638f431cb130291718a16c73121aaa14c57eb6d +[2025-08-15T14:18:45.312Z] DB: getSession - ok 134855fef9c75ab8818913098638f431cb130291718a16c73121aaa14c57eb6d +[2025-08-15T14:18:45.312Z] SESSION: Valid 134855fef9c75ab8818913098638f431cb130291718a16c73121aaa14c57eb6d testplayer PUT /api/players/testplayer +[2025-08-15T14:18:45.312Z] DB: getPlayerByName - start testplayer +[2025-08-15T14:18:45.312Z] DB: getPlayerByName - found testplayer +[2025-08-15T14:18:45.312Z] DB: updatePlayer - start testplayer +[2025-08-15T14:18:45.313Z] DB: updatePlayer - changes 1 testplayer +[2025-08-15T14:18:45.313Z] DB: getPlayerByName - start testplayer +[2025-08-15T14:18:45.313Z] DB: getPlayerByName - found testplayer +[2025-08-15T14:18:45.313Z] API: Updated player testplayer +[2025-08-15T14:18:45.329Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-15T14:18:45.329Z] DB: deletePlayer - start testplayer +[2025-08-15T14:18:45.329Z] DB: deletePlayer - changes 1 testplayer +[2025-08-15T14:18:45.329Z] API: Deleted player testplayer +[2025-08-15T14:18:47.783Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T14:18:47.783Z] DB: deletePlayer - start gmtest +[2025-08-15T14:18:47.783Z] DB: deletePlayer - changes 0 gmtest +[2025-08-15T14:18:47.800Z] DB: getPlayerByName - start gmtest +[2025-08-15T14:18:47.800Z] DB: getPlayerByName - not found gmtest +[2025-08-15T14:18:47.880Z] DB: createPlayer - start gmtest +[2025-08-15T14:18:47.880Z] DB: createPlayer - done gmtest rowid 42 +[2025-08-15T14:18:47.880Z] API: Created player gmtest +[2025-08-15T14:18:47.897Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T14:18:47.897Z] DB: getPlayerByName - start gmtest +[2025-08-15T14:18:47.898Z] DB: getPlayerByName - found gmtest +[2025-08-15T14:18:47.898Z] DB: updatePlayer - start gmtest +[2025-08-15T14:18:47.898Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T14:18:47.898Z] DB: getPlayerByName - start gmtest +[2025-08-15T14:18:47.898Z] DB: getPlayerByName - found gmtest +[2025-08-15T14:18:47.898Z] API: Updated player gmtest +[2025-08-15T14:18:47.913Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-15T14:18:47.913Z] DB: getPlayerByName - start gmtest +[2025-08-15T14:18:47.913Z] DB: getPlayerByName - found gmtest +[2025-08-15T14:18:47.992Z] DB: updatePlayer - start gmtest +[2025-08-15T14:18:47.993Z] DB: updatePlayer - changes 1 gmtest +[2025-08-15T14:18:47.993Z] DB: getPlayerByName - start gmtest +[2025-08-15T14:18:47.993Z] DB: getPlayerByName - found gmtest +[2025-08-15T14:18:47.993Z] API: Updated player gmtest +[2025-08-15T14:18:48.009Z] DB: getPlayerByName - start gmtest +[2025-08-15T14:18:48.009Z] DB: getPlayerByName - found gmtest +[2025-08-15T14:18:48.089Z] DB: createSession - start 95930e2044f5452fd1bf1fcba16f757257d97be520ffd95fa0a522886e92c2bd expiresAt 2025-08-16T14:18:48.089Z +[2025-08-15T14:18:48.089Z] DB: createSession - done 95930e2044f5452fd1bf1fcba16f757257d97be520ffd95fa0a522886e92c2bd +[2025-08-15T14:18:48.089Z] API: Player login gmtest 95930e2044f5452fd1bf1fcba16f757257d97be520ffd95fa0a522886e92c2bd +[2025-08-15T14:18:48.104Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-15T14:18:48.104Z] DB: deletePlayer - start gmtest +[2025-08-15T14:18:48.105Z] DB: deletePlayer - changes 1 gmtest +[2025-08-15T14:18:48.105Z] API: Deleted player gmtest +[2025-08-15T14:18:48.120Z] DB: getAllPlayers - start +[2025-08-15T14:18:48.120Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:18:48.120Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:18:48.645Z] DB: getAllPlayers - start +[2025-08-15T14:18:48.646Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:19:11.987Z] DB: getAllPlayers - start +[2025-08-15T14:19:11.988Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:19:11.988Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:19:14.307Z] DB: getPlayerByName - start christoffer +[2025-08-15T14:19:14.308Z] DB: getPlayerByName - found christoffer +[2025-08-15T14:19:14.308Z] DB: createSession - start 9f7f3d9881364eb271a7be750d5d50e648abb78fb44279af5184fb37a189f262 expiresAt 2025-08-16T14:19:14.308Z +[2025-08-15T14:19:14.309Z] DB: createSession - done 9f7f3d9881364eb271a7be750d5d50e648abb78fb44279af5184fb37a189f262 +[2025-08-15T14:19:14.309Z] SESSION: Login success christoffer 9f7f3d9881364eb271a7be750d5d50e648abb78fb44279af5184fb37a189f262 +[2025-08-15T14:19:14.347Z] DB: getSession - start 9f7f3d9881364eb271a7be750d5d50e648abb78fb44279af5184fb37a189f262 +[2025-08-15T14:19:14.347Z] DB: getSession - ok 9f7f3d9881364eb271a7be750d5d50e648abb78fb44279af5184fb37a189f262 +[2025-08-15T14:19:14.347Z] SESSION: Validate success 9f7f3d9881364eb271a7be750d5d50e648abb78fb44279af5184fb37a189f262 christoffer +[2025-08-15T14:19:14.348Z] DB: getAllPlayers - start +[2025-08-15T14:19:14.348Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:19:14.348Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:19:21.951Z] DB: deleteSession - start 9f7f3d9881364eb271a7be750d5d50e648abb78fb44279af5184fb37a189f262 +[2025-08-15T14:19:21.952Z] DB: deleteSession - changes 1 9f7f3d9881364eb271a7be750d5d50e648abb78fb44279af5184fb37a189f262 +[2025-08-15T14:19:21.952Z] SESSION: Logout success 9f7f3d9881364eb271a7be750d5d50e648abb78fb44279af5184fb37a189f262 +[2025-08-15T14:19:36.013Z] DB: getAllPlayers - start +[2025-08-15T14:19:36.013Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:19:36.013Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:19:37.698Z] DB: getPlayerByName - start claes +[2025-08-15T14:19:37.698Z] DB: getPlayerByName - found claes +[2025-08-15T14:19:37.698Z] DB: createSession - start 393cfa263a7e0ef72dd02e6f1550a069d12ee03f7df7170e1f160fa437a5dcc0 expiresAt 2025-08-16T14:19:37.698Z +[2025-08-15T14:19:37.700Z] DB: createSession - done 393cfa263a7e0ef72dd02e6f1550a069d12ee03f7df7170e1f160fa437a5dcc0 +[2025-08-15T14:19:37.700Z] SESSION: Login success claes 393cfa263a7e0ef72dd02e6f1550a069d12ee03f7df7170e1f160fa437a5dcc0 +[2025-08-15T14:19:37.732Z] DB: getSession - start 393cfa263a7e0ef72dd02e6f1550a069d12ee03f7df7170e1f160fa437a5dcc0 +[2025-08-15T14:19:37.733Z] DB: getSession - ok 393cfa263a7e0ef72dd02e6f1550a069d12ee03f7df7170e1f160fa437a5dcc0 +[2025-08-15T14:19:37.733Z] SESSION: Validate success 393cfa263a7e0ef72dd02e6f1550a069d12ee03f7df7170e1f160fa437a5dcc0 claes +[2025-08-15T14:19:37.734Z] DB: getAllPlayers - start +[2025-08-15T14:19:37.734Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:19:37.734Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:19:42.358Z] DB: deleteSession - start 393cfa263a7e0ef72dd02e6f1550a069d12ee03f7df7170e1f160fa437a5dcc0 +[2025-08-15T14:19:42.359Z] DB: deleteSession - changes 1 393cfa263a7e0ef72dd02e6f1550a069d12ee03f7df7170e1f160fa437a5dcc0 +[2025-08-15T14:19:42.359Z] SESSION: Logout success 393cfa263a7e0ef72dd02e6f1550a069d12ee03f7df7170e1f160fa437a5dcc0 +[2025-08-15T14:19:47.564Z] DB: getAllPlayers - start +[2025-08-15T14:19:47.564Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:19:47.564Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:19:49.798Z] DB: getPlayerByName - start claes +[2025-08-15T14:19:49.798Z] DB: getPlayerByName - found claes +[2025-08-15T14:19:49.798Z] DB: createSession - start 064192621ca7ef0ac4f11f4700eccdb2a041e7b09a3917421e9a8f3d599149da expiresAt 2025-08-16T14:19:49.798Z +[2025-08-15T14:19:49.799Z] DB: createSession - done 064192621ca7ef0ac4f11f4700eccdb2a041e7b09a3917421e9a8f3d599149da +[2025-08-15T14:19:49.799Z] SESSION: Login success claes 064192621ca7ef0ac4f11f4700eccdb2a041e7b09a3917421e9a8f3d599149da +[2025-08-15T14:19:49.828Z] DB: getSession - start 064192621ca7ef0ac4f11f4700eccdb2a041e7b09a3917421e9a8f3d599149da +[2025-08-15T14:19:49.828Z] DB: getSession - ok 064192621ca7ef0ac4f11f4700eccdb2a041e7b09a3917421e9a8f3d599149da +[2025-08-15T14:19:49.828Z] SESSION: Validate success 064192621ca7ef0ac4f11f4700eccdb2a041e7b09a3917421e9a8f3d599149da claes +[2025-08-15T14:19:49.836Z] DB: getAllPlayers - start +[2025-08-15T14:19:49.837Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:19:49.837Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:19:51.796Z] DB: deleteSession - start 064192621ca7ef0ac4f11f4700eccdb2a041e7b09a3917421e9a8f3d599149da +[2025-08-15T14:19:51.797Z] DB: deleteSession - changes 1 064192621ca7ef0ac4f11f4700eccdb2a041e7b09a3917421e9a8f3d599149da +[2025-08-15T14:19:51.798Z] SESSION: Logout success 064192621ca7ef0ac4f11f4700eccdb2a041e7b09a3917421e9a8f3d599149da +[2025-08-15T14:20:27.254Z] DB: getAllPlayers - start +[2025-08-15T14:20:27.254Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:20:27.254Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:20:27.560Z] DB: getAllPlayers - start +[2025-08-15T14:20:27.561Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:20:27.561Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:20:29.341Z] DB: getPlayerByName - start claes +[2025-08-15T14:20:29.341Z] DB: getPlayerByName - found claes +[2025-08-15T14:20:29.342Z] DB: createSession - start 816460713823c196a37f72f8c9996f3df6528e63db16e249abec94f61de0823c expiresAt 2025-08-16T14:20:29.342Z +[2025-08-15T14:20:29.422Z] DB: createSession - done 816460713823c196a37f72f8c9996f3df6528e63db16e249abec94f61de0823c +[2025-08-15T14:20:29.422Z] SESSION: Login success claes 816460713823c196a37f72f8c9996f3df6528e63db16e249abec94f61de0823c +[2025-08-15T14:20:29.465Z] DB: getSession - start 816460713823c196a37f72f8c9996f3df6528e63db16e249abec94f61de0823c +[2025-08-15T14:20:29.465Z] DB: getSession - ok 816460713823c196a37f72f8c9996f3df6528e63db16e249abec94f61de0823c +[2025-08-15T14:20:29.465Z] SESSION: Validate success 816460713823c196a37f72f8c9996f3df6528e63db16e249abec94f61de0823c claes +[2025-08-15T14:20:29.466Z] DB: getAllPlayers - start +[2025-08-15T14:20:29.467Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:20:29.467Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:20:31.411Z] DB: deleteSession - start 816460713823c196a37f72f8c9996f3df6528e63db16e249abec94f61de0823c +[2025-08-15T14:20:31.411Z] DB: deleteSession - changes 1 816460713823c196a37f72f8c9996f3df6528e63db16e249abec94f61de0823c +[2025-08-15T14:20:31.411Z] SESSION: Logout success 816460713823c196a37f72f8c9996f3df6528e63db16e249abec94f61de0823c +[2025-08-15T14:20:50.133Z] DB: getAllPlayers - start +[2025-08-15T14:20:50.133Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:20:50.133Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:20:51.682Z] DB: getPlayerByName - start phillip +[2025-08-15T14:20:51.682Z] DB: getPlayerByName - found phillip +[2025-08-15T14:20:51.682Z] DB: createSession - start 6a8d664d3aeff993925be7a44ea26f8b13962589186577a74a6e74c92f692ea8 expiresAt 2025-08-16T14:20:51.682Z +[2025-08-15T14:20:51.684Z] DB: createSession - done 6a8d664d3aeff993925be7a44ea26f8b13962589186577a74a6e74c92f692ea8 +[2025-08-15T14:20:51.684Z] SESSION: Login success phillip 6a8d664d3aeff993925be7a44ea26f8b13962589186577a74a6e74c92f692ea8 +[2025-08-15T14:20:51.713Z] DB: getAllPlayers - start +[2025-08-15T14:20:51.714Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:20:51.714Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:20:51.715Z] DB: getSession - start 6a8d664d3aeff993925be7a44ea26f8b13962589186577a74a6e74c92f692ea8 +[2025-08-15T14:20:51.716Z] DB: getSession - ok 6a8d664d3aeff993925be7a44ea26f8b13962589186577a74a6e74c92f692ea8 +[2025-08-15T14:20:51.716Z] SESSION: Validate success 6a8d664d3aeff993925be7a44ea26f8b13962589186577a74a6e74c92f692ea8 phillip +[2025-08-15T14:20:52.340Z] DB: getSession - start 6a8d664d3aeff993925be7a44ea26f8b13962589186577a74a6e74c92f692ea8 +[2025-08-15T14:20:52.340Z] DB: getSession - ok 6a8d664d3aeff993925be7a44ea26f8b13962589186577a74a6e74c92f692ea8 +[2025-08-15T14:20:52.340Z] SESSION: Validate success 6a8d664d3aeff993925be7a44ea26f8b13962589186577a74a6e74c92f692ea8 phillip +[2025-08-15T14:20:53.687Z] DB: getAllPlayers - start +[2025-08-15T14:20:53.687Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:20:53.687Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:20:54.221Z] DB: getAllPlayers - start +[2025-08-15T14:20:54.221Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:20:54.221Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:20:55.129Z] DB: getAllPlayers - start +[2025-08-15T14:20:55.129Z] DB: getAllPlayers - resultCount 5 +[2025-08-15T14:20:55.129Z] API: Fetch all players (public) Found 5 players +[2025-08-15T14:20:58.429Z] DB: deleteSession - start 6a8d664d3aeff993925be7a44ea26f8b13962589186577a74a6e74c92f692ea8 +[2025-08-15T14:20:58.429Z] DB: deleteSession - changes 1 6a8d664d3aeff993925be7a44ea26f8b13962589186577a74a6e74c92f692ea8 +[2025-08-15T14:20:58.429Z] SESSION: Logout success 6a8d664d3aeff993925be7a44ea26f8b13962589186577a74a6e74c92f692ea8 diff --git a/database/migrations/add-shop-tables.js b/database/migrations/add-shop-tables.js new file mode 100644 index 0000000..1a4333a --- /dev/null +++ b/database/migrations/add-shop-tables.js @@ -0,0 +1,223 @@ +const sqlite3 = require('sqlite3'); +const path = require('path'); +const fs = require('fs'); + +// Get database path +const dbPath = path.join(__dirname, '..', 'sqlite', 'deathwatch.db'); + +// Create database connection +const db = new sqlite3.Database(dbPath); + +// Load shop data +const shopData = JSON.parse(fs.readFileSync(path.join(__dirname, '../../public/deathwatch-armoury.json'), 'utf8')); + +// Migration function +async function migrate() { + return new Promise((resolve, reject) => { + db.serialize(() => { + try { + // Create shop_items table + db.run(` + CREATE TABLE IF NOT EXISTS shop_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + category TEXT NOT NULL, + requisition_cost INTEGER NOT NULL DEFAULT 0, + renown_requirement TEXT NOT NULL DEFAULT 'None', + item_type TEXT NOT NULL, + stats TEXT NOT NULL, + source TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `); + + // Create player_inventory table + db.run(` + CREATE TABLE IF NOT EXISTS player_inventory ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + player_id INTEGER NOT NULL, + item_id INTEGER NOT NULL, + quantity INTEGER NOT NULL DEFAULT 1, + acquired_at DATETIME DEFAULT CURRENT_TIMESTAMP, + transaction_details TEXT, + FOREIGN KEY (player_id) REFERENCES players (id) ON DELETE CASCADE, + FOREIGN KEY (item_id) REFERENCES shop_items (id) ON DELETE CASCADE, + UNIQUE(player_id, item_id) + ) + `); + + // Create transactions table for purchase history + db.run(` + CREATE TABLE IF NOT EXISTS transactions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + player_id INTEGER NOT NULL, + item_id INTEGER NOT NULL, + requisition_cost INTEGER NOT NULL, + previous_rp INTEGER NOT NULL, + new_rp INTEGER NOT NULL, + transaction_date DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (player_id) REFERENCES players (id) ON DELETE CASCADE, + FOREIGN KEY (item_id) REFERENCES shop_items (id) ON DELETE CASCADE + ) + `); + + // Add triggers for updated_at timestamps + db.run(` + CREATE TRIGGER IF NOT EXISTS update_shop_items_timestamp + AFTER UPDATE ON shop_items + BEGIN + UPDATE shop_items SET updated_at = CURRENT_TIMESTAMP + WHERE id = NEW.id; + END + `); + + // Migrate existing shop data + const stmt = db.prepare(` + INSERT OR REPLACE INTO shop_items ( + name, category, requisition_cost, renown_requirement, + item_type, stats, source + ) VALUES (?, ?, ?, ?, ?, ?, ?) + `); + + // Insert weapons + for (const category of Object.keys(shopData.items)) { + const items = shopData.items[category]; + items.forEach(item => { + stmt.run( + item.name, + category, + item.req, + item.renown, + item.stats.class || item.stats.type, + JSON.stringify(item.stats), + item.stats.source + ); + }); + } + + stmt.finalize(); + + // Add new columns to players table if they don't exist + db.all("PRAGMA table_info(players)", [], (err, rows) => { + if (err) throw err; + + const columns = rows.map(row => row.name); + + if (!columns.includes('requisition_points')) { + db.run(`ALTER TABLE players ADD COLUMN requisition_points INTEGER DEFAULT 0`); + } + + if (!columns.includes('renown_level')) { + db.run(`ALTER TABLE players ADD COLUMN renown_level TEXT DEFAULT 'None'`); + } + }); + + // Migrate existing player inventory data + db.all(`SELECT id, tab_info FROM players`, [], (err, rows) => { + if (err) { + reject(err); + return; + } + + const inventoryStmt = db.prepare(` + INSERT OR IGNORE INTO player_inventory ( + player_id, item_id, quantity, transaction_details + ) + SELECT ?, shop_items.id, 1, ? + FROM shop_items + WHERE shop_items.name = ? + `); + + // Convert rows processing to Promise-based + const processRows = async () => { + for (const row of rows) { + if (!row.tab_info) continue; + + try { + const tabInfo = JSON.parse(row.tab_info); + if (!tabInfo.gear) continue; + + // Add each gear item to inventory + for (const item of tabInfo.gear) { + await new Promise((resolve, reject) => { + inventoryStmt.run( + row.id, + JSON.stringify({ + acquired_via: 'data_migration', + original_data: item + }), + item.name, + (err) => { + if (err) reject(err); + else resolve(); + } + ); + }); + } + + // Update player's RP + if (tabInfo.rp !== undefined) { + await new Promise((resolve, reject) => { + db.run( + 'UPDATE players SET requisition_points = ? WHERE id = ?', + [tabInfo.rp, row.id], + (err) => { + if (err) reject(err); + else resolve(); + } + ); + }); + } + + // Update player's renown + if (tabInfo.renown) { + await new Promise((resolve, reject) => { + db.run( + 'UPDATE players SET renown_level = ? WHERE id = ?', + [tabInfo.renown, row.id], + (err) => { + if (err) reject(err); + else resolve(); + } + ); + }); + } + } catch (e) { + console.error('Error processing player data:', e); + } + } + + inventoryStmt.finalize(); + }; + + processRows() + .then(() => { + console.log('Migration completed successfully'); + resolve(); + }) + .catch(err => { + console.error('Error during migration:', err); + reject(err); + }); + }); + } catch (error) { + console.error('Migration failed:', error); + reject(error); + } + }); + }); +} + +// Run migration +migrate() + .then(() => { + console.log('Migration completed'); + // Give SQLite operations time to complete + setTimeout(() => db.close(), 1000); + }) + .catch(error => { + console.error('Migration failed:', error); + // Give SQLite operations time to complete + setTimeout(() => db.close(), 1000); + }); diff --git a/database/public b/database/public new file mode 120000 index 0000000..27f8dec --- /dev/null +++ b/database/public @@ -0,0 +1 @@ +../public \ No newline at end of file diff --git a/database/routes/playerRoutes-sqlite.js b/database/routes/playerRoutes-sqlite.js index 9fcd7b0..de9f0fb 100644 --- a/database/routes/playerRoutes-sqlite.js +++ b/database/routes/playerRoutes-sqlite.js @@ -3,6 +3,9 @@ const { playerHelpers, sessionHelpers } = require('../sqlite-db'); const fs = require('fs'); const path = require('path'); +// Load shop data +const shopData = JSON.parse(fs.readFileSync(path.join(__dirname, '../../public/deathwatch-armoury.json'), 'utf8')); + // Simple file logger function logToFile(...args) { const msg = `[${new Date().toISOString()}] ` + args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ') + '\n'; @@ -12,6 +15,26 @@ function logToFile(...args) { const requireSession = require('../requireSession'); const router = express.Router(); +// PUBLIC ROUTES - before session middleware +// Get shop inventory +router.get('/shop', (req, res) => { + try { + console.log('Shop endpoint hit'); + const filePath = path.join(__dirname, '../../public/deathwatch-armoury.json'); + console.log('Looking for shop data at:', filePath); + const shopData = JSON.parse(fs.readFileSync(filePath, 'utf8')); + console.log('Shop data loaded:', Object.keys(shopData)); + res.json(shopData); + } catch (error) { + console.error('Shop error:', error); + logToFile('API: Failed to get shop data', error); + res.status(500).json({ error: String(error) }); + } +}); + +// Apply session middleware to all routes EXCEPT those above this line +router.use(requireSession); + // Add safe bcrypt helpers to avoid MODULE_NOT_FOUND failures at runtime async function safeHash(pw) { if (!pw) return ''; @@ -47,6 +70,8 @@ router.get('/admin/list', async (req, res) => { } }); +// Shop endpoint already defined above + // TEMP ADMIN: Delete test users (name contains 'test' or 'Test') router.delete('/admin/delete-tests', async (req, res) => { try { diff --git a/database/routes/shopRoutes.js b/database/routes/shopRoutes.js new file mode 100644 index 0000000..44a5e68 --- /dev/null +++ b/database/routes/shopRoutes.js @@ -0,0 +1,65 @@ +const express = require('express'); +const router = express.Router(); +const shopHelpers = require('../shop-helpers'); +const requireSession = require('../requireSession'); + +// Get all shop items (public endpoint) +router.get('/items', async (req, res) => { + try { + const items = await shopHelpers.getAllItems(); + res.json(items); + } catch (error) { + console.error('Failed to get shop items:', error); + res.status(500).json({ error: 'Failed to get shop items' }); + } +}); + +// Get items by category (public endpoint) +router.get('/items/category/:category', async (req, res) => { + try { + const items = await shopHelpers.getItemsByCategory(req.params.category); + res.json(items); + } catch (error) { + console.error('Failed to get shop items:', error); + res.status(500).json({ error: 'Failed to get shop items' }); + } +}); + +// Protected routes below +router.use(requireSession); + +// Get player's inventory +router.get('/inventory/:playerId', async (req, res) => { + try { + const inventory = await shopHelpers.getPlayerInventory(req.params.playerId); + res.json(inventory); + } catch (error) { + console.error('Failed to get inventory:', error); + res.status(500).json({ error: 'Failed to get inventory' }); + } +}); + +// Purchase item +router.post('/purchase', async (req, res) => { + try { + const { playerId, itemId, quantity = 1 } = req.body; + const result = await shopHelpers.purchaseItem(playerId, itemId, quantity); + res.json(result); + } catch (error) { + console.error('Failed to purchase item:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Get transaction history +router.get('/transactions/:playerId', async (req, res) => { + try { + const transactions = await shopHelpers.getPlayerTransactions(req.params.playerId); + res.json(transactions); + } catch (error) { + console.error('Failed to get transactions:', error); + res.status(500).json({ error: 'Failed to get transactions' }); + } +}); + +module.exports = router; diff --git a/database/server-sqlite.js b/database/server-sqlite.js index c6202de..00303f8 100644 --- a/database/server-sqlite.js +++ b/database/server-sqlite.js @@ -1,8 +1,11 @@ require('dotenv').config(); const express = require('express'); const cors = require('cors'); +const fs = require('fs'); +const path = require('path'); const playerRoutes = require('./routes/playerRoutes-sqlite'); const sessionRoutes = require('./routes/sessionRoutes-sqlite'); +const shopRoutes = require('./routes/shopRoutes'); const app = express(); const PORT = process.env.PORT || 5000; @@ -11,6 +14,15 @@ const PORT = process.env.PORT || 5000; app.use(express.json()); app.use(cors()); +// Serve static files from public directory +const publicDir = path.join(__dirname, '..', 'public'); +app.use(express.static(publicDir)); + +// API Routes +app.use('/api/players', playerRoutes); +app.use('/api/sessions', sessionRoutes); +app.use('/api/shop', shopRoutes); + // Initialize SQLite database const { db } = require('./sqlite-db'); @@ -33,6 +45,7 @@ app.get('/', (req, res) => { }); // Use routes +app.use('/api/shop', shopRoutes); app.use('/api/players', playerRoutes); app.use('/api/sessions', sessionRoutes); diff --git a/database/shop-helpers.js b/database/shop-helpers.js new file mode 100644 index 0000000..6e2992c --- /dev/null +++ b/database/shop-helpers.js @@ -0,0 +1,134 @@ +const sqlite3 = require('sqlite3'); +const path = require('path'); + +// Get database path +const dbPath = path.join(__dirname, 'sqlite', 'deathwatch.db'); + +// Create database connection +const db = new sqlite3.Database(dbPath); + +// Shop and inventory related database operations +const shopHelpers = { + // Get all items in the shop + getAllItems: () => { + return new Promise((resolve, reject) => { + db.all('SELECT * FROM shop_items ORDER BY category, name', [], (err, rows) => { + if (err) return reject(err); + resolve(rows); + }); + }); + }, + + // Get items by category + getItemsByCategory: (category) => { + return new Promise((resolve, reject) => { + db.all('SELECT * FROM shop_items WHERE category = ? ORDER BY name', [category], (err, rows) => { + if (err) return reject(err); + resolve(rows); + }); + }); + }, + + // Get player's inventory + getPlayerInventory: (playerId) => { + return new Promise((resolve, reject) => { + db.all(` + SELECT + pi.*, + si.* + FROM player_inventory pi + JOIN shop_items si ON pi.item_id = si.id + WHERE pi.player_id = ? + ORDER BY si.category, si.name + `, [playerId], (err, rows) => { + if (err) return reject(err); + resolve(rows); + }); + }); + }, + + // Purchase item for player + purchaseItem: (playerId, itemId, quantity = 1) => { + return new Promise((resolve, reject) => { + db.serialize(() => { + db.get('BEGIN TRANSACTION'); + + try { + // Get player's current RP and the item cost + db.get(` + SELECT p.requisition_points, p.renown_level, si.requisition_cost, si.renown_requirement + FROM players p, shop_items si + WHERE p.id = ? AND si.id = ? + `, [playerId, itemId], (err, row) => { + if (err) throw err; + if (!row) throw new Error('Player or item not found'); + + const { requisition_points, renown_level, requisition_cost, renown_requirement } = row; + const totalCost = requisition_cost * quantity; + + // Check if player has enough RP + if (requisition_points < totalCost) { + throw new Error('Insufficient requisition points'); + } + + // Check if player meets renown requirement + const renownLevels = ['None', 'Respected', 'Distinguished', 'Hero', 'Legend']; + const playerRenownIndex = renownLevels.indexOf(renown_level); + const requiredRenownIndex = renownLevels.indexOf(renown_requirement); + if (playerRenownIndex < requiredRenownIndex) { + throw new Error('Insufficient renown level'); + } + + // Update player's RP + const newRp = requisition_points - totalCost; + db.run('UPDATE players SET requisition_points = ? WHERE id = ?', + [newRp, playerId]); + + // Add to inventory or update quantity + db.run(` + INSERT INTO player_inventory (player_id, item_id, quantity) + VALUES (?, ?, ?) + ON CONFLICT(player_id, item_id) + DO UPDATE SET quantity = quantity + ? + `, [playerId, itemId, quantity, quantity]); + + // Record transaction + db.run(` + INSERT INTO transactions ( + player_id, item_id, requisition_cost, + previous_rp, new_rp + ) VALUES (?, ?, ?, ?, ?) + `, [playerId, itemId, totalCost, requisition_points, newRp]); + + db.get('COMMIT'); + resolve({ newRp }); + }); + } catch (error) { + db.get('ROLLBACK'); + reject(error); + } + }); + }); + }, + + // Get player's transaction history + getPlayerTransactions: (playerId) => { + return new Promise((resolve, reject) => { + db.all(` + SELECT + t.*, + si.name as item_name, + si.category + FROM transactions t + JOIN shop_items si ON t.item_id = si.id + WHERE t.player_id = ? + ORDER BY t.transaction_date DESC + `, [playerId], (err, rows) => { + if (err) return reject(err); + resolve(rows); + }); + }); + } +}; + +module.exports = shopHelpers; diff --git a/database/sqlite/deathwatch.db b/database/sqlite/deathwatch.db index 2e53bdc..c21ab3c 100644 Binary files a/database/sqlite/deathwatch.db and b/database/sqlite/deathwatch.db differ diff --git a/database/sqlite/deathwatch.db-shm b/database/sqlite/deathwatch.db-shm index 22463ae..4b65aa6 100644 Binary files a/database/sqlite/deathwatch.db-shm and b/database/sqlite/deathwatch.db-shm differ diff --git a/database/sqlite/deathwatch.db-wal b/database/sqlite/deathwatch.db-wal index ac00b4a..12170ce 100644 Binary files a/database/sqlite/deathwatch.db-wal and b/database/sqlite/deathwatch.db-wal differ diff --git a/package-lock.json b/package-lock.json index 6e87bc1..b94c6dc 100755 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", - "axios": "^1.11.0", "bcrypt": "^5.1.0", "better-sqlite3": "^12.2.0", "cors": "^2.8.5", @@ -21,14 +20,18 @@ "pdfjs-dist": "^4.8.69", "react": "^18.2.0", "react-dom": "^18.2.0", + "sqlite3": "^5.1.7", "web-vitals": "^2.1.4" }, "devDependencies": { "@pmmmwh/react-refresh-webpack-plugin": "^0.6.1", "autoprefixer": "^10.4.14", + "axios": "^1.11.0", "babel-loader": "^10.0.0", + "cheerio": "^1.1.2", "eslint-config-prettier": "^10.1.8", "html-webpack-plugin": "^5.6.4", + "node-fetch": "^2.7.0", "postcss": "^8.4.23", "prettier-eslint": "^16.4.2", "react-scripts": "^5.0.1", @@ -2559,6 +2562,12 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -4405,6 +4414,54 @@ "node": ">= 8" } }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -4973,7 +5030,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 6" @@ -6070,6 +6127,31 @@ "node": ">= 6.0.0" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -6536,6 +6618,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, "license": "MIT" }, "node_modules/at-least-node": { @@ -6615,6 +6698,7 @@ "version": "1.11.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "dev": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -6625,6 +6709,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -7236,6 +7321,86 @@ "node": ">= 0.8" } }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cacache/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -7401,6 +7566,238 @@ "dev": true, "license": "MIT" }, + "node_modules/cheerio": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", + "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", + "dev": true, + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.12.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio-select/node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio-select/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/cheerio-select/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/cheerio-select/node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/cheerio-select/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/cheerio/node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -7499,6 +7896,15 @@ "node": ">=0.10.0" } }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -7666,6 +8072,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -8608,6 +9015,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -8990,6 +9398,40 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -9022,6 +9464,21 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -9207,6 +9664,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -10375,6 +10833,7 @@ "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, "funding": [ { "type": "individual", @@ -11350,6 +11809,12 @@ "entities": "^2.0.0" } }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "optional": true + }, "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -11406,7 +11871,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@tootallnate/once": "1", @@ -11465,6 +11930,15 @@ "node": ">=10.17.0" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/hyperdyperid": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", @@ -11611,7 +12085,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -11626,6 +12100,12 @@ "node": ">=8" } }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -11663,6 +12143,15 @@ "node": ">= 0.4" } }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "optional": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -11967,6 +12456,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "optional": true + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -12283,7 +12778,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -15809,6 +16304,63 @@ "node": ">=10" } }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -15939,6 +16491,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -15948,6 +16501,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -16045,6 +16599,161 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -16290,7 +16999,7 @@ "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -16395,6 +17104,92 @@ "node": ">= 6.13.0" } }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/node-gyp/node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -16808,6 +17603,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-retry": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", @@ -16893,6 +17703,94 @@ "dev": true, "license": "MIT" }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-parser-stream/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -18973,6 +19871,34 @@ "asap": "~2.0.6" } }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -19031,7 +19957,8 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, "node_modules/psl": { "version": "1.15.0", @@ -21078,6 +22005,16 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -21090,6 +22027,34 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "optional": true, + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -21215,6 +22180,64 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/sqlite3/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==" + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ssri/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ssri/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, "node_modules/stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", @@ -22714,6 +23737,15 @@ "dev": true, "license": "MIT" }, + "node_modules/undici": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.13.0.tgz", + "integrity": "sha512-l+zSMssRqrzDcb3fjMkjjLGmuiiK2pMIcV++mJaAc9vhjSGpvM7h43QgP+OAMb1GImHmbPyG2tBXeuyG5iY4gA==", + "dev": true, + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", @@ -22764,6 +23796,24 @@ "node": ">=4" } }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -23687,7 +24737,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" diff --git a/package.json b/package.json index 72d0560..f681acf 100755 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", - "axios": "^1.11.0", "bcrypt": "^5.1.0", "better-sqlite3": "^12.2.0", "cors": "^2.8.5", @@ -16,6 +15,7 @@ "pdfjs-dist": "^4.8.69", "react": "^18.2.0", "react-dom": "^18.2.0", + "sqlite3": "^5.1.7", "web-vitals": "^2.1.4" }, "scripts": { @@ -57,9 +57,12 @@ "devDependencies": { "@pmmmwh/react-refresh-webpack-plugin": "^0.6.1", "autoprefixer": "^10.4.14", + "axios": "^1.11.0", "babel-loader": "^10.0.0", + "cheerio": "^1.1.2", "eslint-config-prettier": "^10.1.8", "html-webpack-plugin": "^5.6.4", + "node-fetch": "^2.7.0", "postcss": "^8.4.23", "prettier-eslint": "^16.4.2", "react-scripts": "^5.0.1", diff --git a/public/deathwatch-armor.json b/public/deathwatch-armor.json new file mode 100644 index 0000000..a938638 --- /dev/null +++ b/public/deathwatch-armor.json @@ -0,0 +1,1152 @@ +{ + "powerArmor": [ + { + "name": "Armour of the Remorseless Crusader", + "req": 70, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 12, + "arms": 12, + "body": 12, + "legs": 12 + }, + "source": "Deathwatch Core Rulebook p167" + } + }, + { + "name": "Astartes Artificer Armour", + "req": 60, + "renown": "Hero", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 12, + "arms": 12, + "body": 12, + "legs": 12 + }, + "source": "Deathwatch Core Rulebook p163" + } + }, + { + "name": "Astartes Mk 1 Thunder Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 6, + "body": 8, + "legs": 4 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 2 Crusade Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 7, + "body": 9, + "legs": 7 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 3 Iron Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 9, + "body": 10, + "legs": 9 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 4 Maximus Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 7, + "body": 9, + "legs": 7 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 5 Heresy Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 8, + "body": 9, + "legs": 8 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 6 Corvus Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 8, + "body": 9, + "legs": 8 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 7 Aquilla Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 8, + "body": 10, + "legs": 8 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 8 Errant Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 8, + "body": 11, + "legs": 8 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk1 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 6, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk2 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 7, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk3 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 9, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk4 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 7, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk5 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk6 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk7 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk8 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 8, + "body": 10, + "legs": 8 + }, + "source": "Deathwatch Core Rulebook p160" + } + }, + { + "name": "Astartes Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Deathwatch Core Rulebook p160" + } + }, + { + "name": "Astartes Tactical Dreadnaught-Terminator Armour", + "req": 100, + "renown": "Hero", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 14, + "body": 14, + "legs": 14 + }, + "source": "Deathwatch Core Rulebook p164" + } + }, + { + "name": "Astartes Tactical Dreadnaught-Terminator Armour Helm", + "req": 100, + "renown": "Hero", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 14, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Deathwatch Core Rulebook p164" + } + }, + { + "name": "Astartes Terminator Armour", + "req": 100, + "renown": "Hero", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 14, + "arms": 14, + "body": 14, + "legs": 14 + }, + "source": "Deathwatch Core Rulebook p164" + } + }, + { + "name": "CM Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 8, + "body": 10, + "legs": 8 + }, + "source": "Mark of the Xenos p114" + } + }, + { + "name": "Diagnostor Helmet", + "req": 15, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Deathwatch Core Rulebook p165" + } + }, + { + "name": "Dmn Armour of Chaos", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 12, + "arms": 12, + "body": 12, + "legs": 12 + }, + "source": "Deathwatch Core Rulebook p362" + } + }, + { + "name": "Fenris Pattern Wolf Helm", + "req": 0, + "renown": "Respected", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p143" + } + }, + { + "name": "Skull Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 9, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p143" + } + }, + { + "name": "Tau XV-8 Crisis Battlesuit", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 9, + "arms": 9, + "body": 9, + "legs": 9 + }, + "source": "Mark of the Xenos p9" + } + }, + { + "name": "Tau XV-88 Broadside Battlesuit", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 12, + "arms": 12, + "body": 12, + "legs": 12 + }, + "source": "Mark of the Xenos p6" + } + }, + { + "name": "Tau XV15 Stealth Suit (Limbs)", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 7, + "body": 0, + "legs": 7 + }, + "source": "Deathwatch Core Rulebook p367" + } + }, + { + "name": "Tau XV15 Stealth Suit (Top)", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 8, + "legs": 0 + }, + "source": "Deathwatch Core Rulebook p367" + } + }, + { + "name": "Tau XV8 Crisis Suit", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 9, + "arms": 9, + "body": 9, + "legs": 9 + }, + "source": "Deathwatch Core Rulebook p366" + } + } + ], + "carapaceArmor": [ + { + "name": "Astartes Scout Armour", + "req": 0, + "renown": "None", + "category": "Carapace Armor", + "stats": { + "type": "Carapace", + "protection": { + "head": 0, + "arms": 6, + "body": 6, + "legs": 0 + }, + "source": "Deathwatch Core Rulebook p163" + } + }, + { + "name": "Chaos Flak Robes and Brazen Carapace", + "req": 0, + "renown": "None", + "category": "Carapace Armor", + "stats": { + "type": "Carapace", + "protection": { + "head": 5, + "arms": 5, + "body": 6, + "legs": 4 + }, + "source": "Deathwatch Core Rulebook p364" + } + }, + { + "name": "Deathwatch Scout Armor", + "req": 0, + "renown": "None", + "category": "Carapace Armor", + "stats": { + "type": "Carapace", + "protection": { + "head": 0, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "Rites of Battle p142" + } + }, + { + "name": "Tau Fire Warrior Armour", + "req": 0, + "renown": "None", + "category": "Carapace Armor", + "stats": { + "type": "Carapace", + "protection": { + "head": 6, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "Deathwatch Core Rulebook p368" + } + }, + { + "name": "Tau Pathfinder Armour", + "req": 0, + "renown": "None", + "category": "Carapace Armor", + "stats": { + "type": "Carapace", + "protection": { + "head": 7, + "arms": 0, + "body": 7, + "legs": 0 + }, + "source": "Mark of the Xenos p25" + } + } + ], + "primitiveArmor": [ + { + "name": "Auran Golden Saurian Scale Armour", + "req": 0, + "renown": "None", + "category": "Primitive Armor", + "stats": { + "type": "Primitive", + "protection": { + "head": 6, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "The Emperor Protects p48" + } + }, + { + "name": "Auran Saurian Scale Armour", + "req": 0, + "renown": "None", + "category": "Primitive Armor", + "stats": { + "type": "Primitive", + "protection": { + "head": 4, + "arms": 4, + "body": 4, + "legs": 4 + }, + "source": "The Emperor Protects p46" + } + }, + { + "name": "Scales", + "req": 0, + "renown": "None", + "category": "Primitive Armor", + "stats": { + "type": "Primitive", + "protection": { + "head": 2, + "arms": 2, + "body": 2, + "legs": 2 + }, + "source": "The Emperor Protects p50" + } + }, + { + "name": "Scaly Hide", + "req": 0, + "renown": "None", + "category": "Primitive Armor", + "stats": { + "type": "Primitive", + "protection": { + "head": 4, + "arms": 4, + "body": 4, + "legs": 4 + }, + "source": "The Emperor Protects p46" + } + } + ], + "naturalArmor": [ + { + "name": "Tau Gun Drone Armour", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 5, + "arms": 5, + "body": 5, + "legs": 5 + }, + "source": "Deathwatch Core Rulebook p367" + } + }, + { + "name": "Tyr Bonded Exoskeleton", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 10, + "arms": 10, + "body": 10, + "legs": 10 + }, + "source": "Deathwatch Core Rulebook p370" + } + }, + { + "name": "Tyr Chitinous Carapace", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 3, + "arms": 3, + "body": 3, + "legs": 3 + }, + "source": "Deathwatch Core Rulebook p371" + } + }, + { + "name": "Tyr Exoskeleton", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 2, + "arms": 2, + "body": 2, + "legs": 2 + }, + "source": "Mark of the Xenos p43" + } + }, + { + "name": "Tyr Hardened Carapace", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 6, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "Mark of the Xenos p40" + } + }, + { + "name": "Tyr Light Reinforced Chitin", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 4, + "arms": 4, + "body": 4, + "legs": 4 + }, + "source": "Mark of the Xenos p39" + } + }, + { + "name": "Tyr Medium Reinforced Chitin", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 6, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "Mark of the Xenos p37" + } + }, + { + "name": "Tyr Reinforced Chitin", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 8, + "arms": 8, + "body": 8, + "legs": 8 + }, + "source": "Deathwatch Core Rulebook p371" + } + }, + { + "name": "Tyr Thickened Scales", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 10, + "arms": 10, + "body": 10, + "legs": 10 + }, + "source": "Mark of the Xenos p46" + } + }, + { + "name": "Vespid Chitin", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 3, + "arms": 3, + "body": 3, + "legs": 3 + }, + "source": "Mark of the Xenos p26" + } + }, + { + "name": "Xenos Hardened Hide", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 6, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "Mark of the Xenos p66" + } + }, + { + "name": "Xenos Scaly Hide", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 3, + "arms": 3, + "body": 3, + "legs": 3 + }, + "source": "Mark of the Xenos p63" + } + }, + { + "name": "Xenos Slimey Hide", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 2, + "arms": 2, + "body": 2, + "legs": 2 + }, + "source": "Mark of the Xenos p68" + } + }, + { + "name": "Xenos Thick Scales", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 5, + "arms": 5, + "body": 5, + "legs": 5 + }, + "source": "Mark of the Xenos p65" + } + } + ], + "xenosArmor": [ + { + "name": "Nec Metallic Exoskeleton", + "req": 0, + "renown": "None", + "category": "Xenos Armor", + "stats": { + "type": "Necron", + "protection": { + "head": 8, + "arms": 8, + "body": 8, + "legs": 8 + }, + "source": "The Emperor Protects p94" + } + }, + { + "name": "Nec Metallic Shell", + "req": 0, + "renown": "None", + "category": "Xenos Armor", + "stats": { + "type": "Necron", + "protection": { + "head": 10, + "arms": 10, + "body": 10, + "legs": 10 + }, + "source": "The Emperor Protects p94" + } + }, + { + "name": "Ork Eavy Armour", + "req": 0, + "renown": "None", + "category": "Xenos Armor", + "stats": { + "type": "Ork", + "protection": { + "head": 6, + "arms": 4, + "body": 6, + "legs": 4 + }, + "source": "Mark of the Xenos p57" + } + }, + { + "name": "Ork Flak Armour", + "req": 0, + "renown": "None", + "category": "Xenos Armor", + "stats": { + "type": "Ork", + "protection": { + "head": 0, + "arms": 0, + "body": 2, + "legs": 0 + }, + "source": "Mark of the Xenos p57" + } + }, + { + "name": "Ork Mega Armour", + "req": 0, + "renown": "None", + "category": "Xenos Armor", + "stats": { + "type": "Ork", + "protection": { + "head": 6, + "arms": 10, + "body": 14, + "legs": 10 + }, + "source": "Mark of the Xenos p57" + } + } + ], + "shields": [ + { + "name": "Astartes Storm Shield - Protective", + "req": 35, + "renown": "Distinguished", + "category": "Shields", + "stats": { + "type": "Shield", + "protection": { + "head": 0, + "arms": 0, + "body": 4, + "legs": 0 + }, + "source": "Deathwatch Core Rulebook p166" + } + } + ], + "otherArmor": [ + { + "name": "Name", + "req": 0, + "renown": "Distinguished", + "category": "Other Armor", + "stats": { + "type": "Type", + "protection": { + "head": 0, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Book pPage No." + } + }, + { + "name": "Chaos Armour Plating", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 6, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "Mark of the Xenos p91" + } + }, + { + "name": "Chaos Bones of Subersion", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 9, + "arms": 9, + "body": 9, + "legs": 9 + }, + "source": "Mark of the Xenos p86" + } + }, + { + "name": "Chaos Slinnar Obsidian Shell", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 8, + "arms": 8, + "body": 8, + "legs": 8 + }, + "source": "Mark of the Xenos p92" + } + }, + { + "name": "Chaos Warded Adamantine Shell", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 9, + "arms": 9, + "body": 9, + "legs": 9 + }, + "source": "Mark of the Xenos p95" + } + }, + { + "name": "Daemon Iron Hide", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 13, + "arms": 13, + "body": 13, + "legs": 13 + }, + "source": "Mark of the Xenos p101" + } + }, + { + "name": "Dmn Brazen Chaos Armour", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 12, + "arms": 12, + "body": 12, + "legs": 12 + }, + "source": "The Emperor Protects p136" + } + }, + { + "name": "Obliterator Warped Mechnical Flesh", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 12, + "arms": 12, + "body": 12, + "legs": 12 + }, + "source": "Mark of the Xenos p120" + } + }, + { + "name": "Rad Corrupted Mechnical Flesh", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 7, + "arms": 7, + "body": 7, + "legs": 7 + }, + "source": "The Emperor Protects p91" + } + } + ] +} \ No newline at end of file diff --git a/public/deathwatch-armoury.json b/public/deathwatch-armoury.json old mode 100755 new mode 100644 index 89294e5..a5f7d37 --- a/public/deathwatch-armoury.json +++ b/public/deathwatch-armoury.json @@ -1,794 +1,1397 @@ -[ - { - "id": "armour", - "name": "Armour", - "category": "Armor", - "req": 7, - "renown": "None", - "desc": "Armour: Reinforced structure and armour plate (All 7)" - }, - { - "id": "armour-4", - "name": "Armour (4)", - "category": "Armor", - "req": 4, - "renown": "None", - "desc": "Armour (4), Natural Weapons, Quadruped, Regeneration," - }, - { - "id": "armour-machine", - "name": "Armour (Machine)", - "category": "Armor", - "req": 6, - "renown": "None", - "desc": "Armour (Machine): All 6" - }, - { - "id": "armour-160", - "name": "Armour 160", - "category": "Armor", - "req": 160, - "renown": "None", - "desc": "Armour 160" - }, - { - "id": "armour-craftsmanship-165", - "name": "Armour Craftsmanship 165", - "category": "Armor", - "req": 165, - "renown": "None", - "desc": "Armour Craftsmanship 165" - }, - { - "id": "armour-plating-increase-armour-points-by-2", - "name": "Armour Plating Increase Armour Points By 2", - "category": "Armor", - "req": 2, - "renown": "None", - "desc": "Armour Plating Increase Armour Points by 2" - }, - { - "id": "armour-points", - "name": "Armour Points", - "category": "Armor", - "req": 4, - "renown": "None", - "desc": "A storm shield provides +4 Armour Points" - }, - { - "id": "armour-points-against-any-damage", - "name": "Armour Points Against Any Damage", - "category": "Armor", - "req": 2, - "renown": "None", - "desc": "counts as only having 2 Armour Points against any damage" - }, - { - "id": "armour-points-by-1", - "name": "Armour Points By 1", - "category": "Armor", - "req": 1, - "renown": "None", - "desc": "The attack damages the target’s armour, reducing its Armour Points by 1" - }, - { - "id": "armour-points-by-2-to-all-locations", - "name": "Armour Points By 2 To All Locations", - "category": "Armor", - "req": 2, - "renown": "None", - "desc": "Armour Points by 2 to all locations" - }, - { - "id": "armour-points-of-protection", - "name": "Armour Points of Protection", - "category": "Armor", - "req": 8, - "renown": "None", - "desc": "because they provide 8 Armour Points of protection" - }, - { - "id": "armour-points-of-protection-to-his", - "name": "Armour Points of Protection To His", - "category": "Armor", - "req": 10, - "renown": "None", - "desc": "power armour that provides 10 Armour Points of protection to his" - }, - { - "id": "armour-points-to-6", - "name": "Armour Points To 6", - "category": "Armor", - "req": 6, - "renown": "None", - "desc": "reduces the sandbags Armour Points to 6" - }, - { - "id": "armour-points-to-7", - "name": "Armour Points To 7", - "category": "Armor", - "req": 7, - "renown": "None", - "desc": "reduces the sandbags Armour Points to 7" - }, - { - "id": "armour-monger-talent-113", - "name": "Armour-Monger (Talent) 113", - "category": "Armor", - "req": 113, - "renown": "None", - "desc": "Armour-Monger (Talent) 113" - }, - { - "id": "armour-monger-500-talent-techmarine", - "name": "Armour-Monger 500 Talent Techmarine", - "category": "Armor", - "req": 500, - "renown": "None", - "desc": "Armour-Monger 500 Talent Techmarine" - }, - { - "id": "astartes-artificer-armour-all-12-100-60", - "name": "Astartes Artificer Armour All 12 100 60", - "category": "Armor", - "req": 60, - "renown": "None", - "desc": "Astartes Artificer Armour All 12 100 60" - }, - { - "id": "astartes-combat-shield-25-01", - "name": "Astartes Combat Shield 25 01", - "category": "Armor", - "req": 20, - "renown": "Distinguished", - "desc": "Astartes Combat Shield 25 01–10 3 20 Distinguished" - }, - { - "id": "astartes-scout-armour-body", - "name": "Astartes Scout Armour Body", - "category": "Armor", - "req": 30, - "renown": "None", - "desc": "Astartes Scout Armour Body, Arms 6 30 N/A —" - }, - { - "id": "astartes-storm-shield-55-01", - "name": "Astartes Storm Shield 55 01", - "category": "Armor", - "req": 35, - "renown": "Distinguished", - "desc": "Astartes Storm Shield 55 01–10 10 35 Distinguished" - }, - { - "id": "astartes-terminator-armour-all-14-400-100", - "name": "Astartes Terminator Armour All 14 400 100", - "category": "Armor", - "req": 100, - "renown": "None", - "desc": "Astartes Terminator Armour All 14 400 100" - }, - { - "id": "bertramsepheranmagronyngvarmorganscipio", - "name": "Bertramsepheranmagronyngvarmorganscipio", - "category": "Armor", - "req": 6, - "renown": "None", - "desc": "6BertramSepheranMagronYngvarMorganScipio" - }, - { - "id": "carapace-armour-all-6-15-n-a", - "name": "Carapace Armour All 6 15 N/A", - "category": "Armor", - "req": 15, - "renown": "None", - "desc": "Carapace Armour All 6 15 N/A –" - }, - { - "id": "damage-with-no-reduction-from-armour-and-suffers", - "name": "Damage (With No Reduction From Armour) and Suffers", - "category": "Armor", - "req": 10, - "renown": "None", - "desc": "suffers 1d10 Damage (with no reduction from armour) and suffers" - }, - { - "id": "flak-armour-all-4-15-n-a", - "name": "Flak Armour All 4 15 N/A", - "category": "Armor", - "req": 15, - "renown": "None", - "desc": "Flak Armour All 4 15 N/A –" - }, - { - "id": "his-armour-reduces-the-damage-by-10", - "name": "His Armour Reduces the Damage By 10", - "category": "Armor", - "req": 10, - "renown": "None", - "desc": "His armour reduces the Damage by 10, and" - }, - { - "id": "horde-armour-value-of-5-against-damage-and", - "name": "Horde Armour Value of 5 Against Damage and", - "category": "Armor", - "req": 5, - "renown": "None", - "desc": "will also use its Horde armour value of 5 against damage and" - }, - { - "id": "if-not-armoured", - "name": "If Not Armoured", - "category": "Armor", - "req": 1, - "renown": "None", - "desc": "If not armoured, the target is also Stunned for 1 Round" - }, - { - "id": "levin-shield", - "name": "Levin Shield", - "category": "Armor", - "req": 50, - "renown": "Hero", - "desc": "The Levin Shield — — — — — — N/A — 10 50 Hero" - }, - { - "id": "penetration-of-3-hits-a-target-with-5-armour-points", - "name": "Penetration of 3 Hits A Target With 5 Armour Points", - "category": "Armor", - "req": 5, - "renown": "None", - "desc": "Penetration of 3 hits a target with 5 Armour Points, the target" - }, - { - "id": "photo-visors-and-sealed-armour-provide-a", - "name": "Photo-Visors and Sealed Armour Provide A", - "category": "Armor", - "req": 20, - "renown": "None", - "desc": "Photo-visors and sealed armour provide a +20 bonus to this" - }, - { - "id": "possibility-shield-1000-wp-40", - "name": "Possibility Shield 1000 Wp 40", - "category": "Armor", - "req": 40, - "renown": "None", - "desc": "Possibility Shield 1000 WP 40+" - }, - { - "id": "profile-on-page-376-and-are-armed-and-armoured-as-detailed", - "name": "Profile On Page 376 and Are Armed and Armoured As Detailed", - "category": "Armor", - "req": 376, - "renown": "None", - "desc": "profile on page 376 and are armed and armoured as detailed" - }, - { - "id": "requisition-points-for-the-armour-itself-and-then", - "name": "Requisition Points For the Armour Itself and Then", - "category": "Armor", - "req": 60, - "renown": "None", - "desc": "pays the 60 Requisition Points for the armour itself and then" - }, - { - "id": "sb-with-armour-points-counting-double-to-his", - "name": "Sb With Armour Points Counting Double) To His", - "category": "Armor", - "req": 3, - "renown": "None", - "desc": "(1d5–3+SB with Armour Points counting double) to his" - }, - { - "id": "signature-wargear-hero-artificer-armour-1200-talent", - "name": "Signature Wargear (Hero) (Artificer Armour) 1200 Talent", - "category": "Armor", - "req": 200, - "renown": "Hero", - "desc": "Signature Wargear (Hero) (Artificer Armour) 1200 Talent —" - }, - { - "id": "stygian-5th-armoured-division", - "name": "Stygian 5th Armoured Division", - "category": "Armor", - "req": 5, - "renown": "None", - "desc": "Stygian 5th Armoured Division" - }, - { - "id": "tekarn-armoured", - "name": "Tekarn Armoured", - "category": "Armor", - "req": 1, - "renown": "None", - "desc": "Tekarn Armoured: 1 Regiment" - }, - { - "id": "terminator-armour", - "name": "Terminator Armour", - "category": "Armor", - "req": 164, - "renown": "None", - "desc": "about Terminator Armour, see page 164" - }, - { - "id": "toughness-bonus-and-armour", - "name": "Toughness Bonus and Armour", - "category": "Armor", - "req": 10, - "renown": "None", - "desc": "for his Toughness Bonus and armour, Myrdor suffers 10 points of" - }, - { - "id": "walls-more-than-50cm-thick-and-certain-shielding-materials", - "name": "Walls More Than 50cm Thick and Certain Shielding Materials", - "category": "Armor", - "req": 50, - "renown": "None", - "desc": "walls more than 50cm thick and certain shielding materials" - }, - { - "id": "astartes-blind-grenade-thrown-sbx3-s", - "name": "Astartes Blind Grenade Thrown Sbx3 S/", - "category": "Explosives", - "req": 0, - "renown": "None", - "desc": "Astartes Blind Grenade Thrown SBx3 S/–/– – 0 Smoke 0" - }, - { - "id": "astartes-bolt-pistol", - "name": "Astartes Bolt Pistol", - "category": "Explosives", - "req": 3, - "renown": "None", - "desc": "Astartes bolt pistol, 3 Astartes frag grenades, 3 Astartes" - }, - { - "id": "astartes-frag-grenade-thrown-sbx3-s", - "name": "Astartes Frag Grenade Thrown Sbx3 S/", - "category": "Explosives", - "req": 0, - "renown": "None", - "desc": "Astartes Frag Grenade Thrown SBx3 S/–/– 2d10 X 0 Blast (5) 0" - }, - { - "id": "astartes-frag-missile", - "name": "Astartes Frag Missile", - "category": "Explosives", - "req": 4, - "renown": "None", - "desc": "Astartes Frag Missile – – – 2d10 X 4" - }, - { - "id": "astartes-incendiary-missile", - "name": "Astartes Incendiary Missile", - "category": "Explosives", - "req": 15, - "renown": "None", - "desc": "Astartes Incendiary Missile – – – 1d10+3 E 0 Blast (5) 2 15 –" - }, - { - "id": "astartes-krak-grenade-thrown-sbx3-s", - "name": "Astartes Krak Grenade Thrown Sbx3 S/", - "category": "Explosives", - "req": 0, - "renown": "None", - "desc": "Astartes Krak Grenade Thrown SBx3 S/–/– 3d10+4 X 6 – 0" - }, - { - "id": "astartes-krak-missile", - "name": "Astartes Krak Missile", - "category": "Explosives", - "req": 5, - "renown": "None", - "desc": "Astartes Krak Missile – – – 4d10+6 X 10 Blast (1) 2 5" - }, - { - "id": "astartes-nova-grenade-thrown-sbx3-s", - "name": "Astartes Nova Grenade Thrown Sbx3 S/", - "category": "Explosives", - "req": 0, - "renown": "None", - "desc": "Astartes Nova Grenade Thrown SBx3 S/–/– 1d10 E 0 Blast(3) 0" - }, - { - "id": "astartes-plasma-grenade-thrown-sbx3-s", - "name": "Astartes Plasma Grenade Thrown Sbx3 S/", - "category": "Explosives", - "req": 8, - "renown": "None", - "desc": "Astartes Plasma Grenade Thrown SBx3 S/–/– 1d10+12 E 8" - }, - { - "id": "astartes-plasma-missile", - "name": "Astartes Plasma Missile", - "category": "Explosives", - "req": 12, - "renown": "None", - "desc": "Astartes Plasma Missile – – – 2d10+12 E 12" - }, - { - "id": "astartes-stasis-grenade-thrown-sbx3-s", - "name": "Astartes Stasis Grenade Thrown Sbx3 S/", - "category": "Explosives", - "req": 1, - "renown": "None", - "desc": "Astartes Stasis Grenade Thrown SBx3 S/–/– – 0 – 1 N/A N/A" - }, - { - "id": "astartes-stun-grenade-thrown-sbx3-s", - "name": "Astartes Stun Grenade Thrown Sbx3 S/", - "category": "Explosives", - "req": 0, - "renown": "None", - "desc": "Astartes Stun Grenade Thrown SBx3 S/–/– – 0 Blast (5) 0" - }, - { - "id": "emp-grenade-thrown-sbx3-s", - "name": "Emp Grenade Thrown Sbx3 S/", - "category": "Explosives", - "req": 0, - "renown": "None", - "desc": "EMP Grenade Thrown SBx3 S/–/– – 0 Haywire (3) 0" - }, - { - "id": "grenade-launcher-with-a-clip-of-1", - "name": "Grenade Launcher With A Clip of 1)", - "category": "Explosives", - "req": 1, - "renown": "None", - "desc": "grenade launcher with a clip of 1)" - }, - { - "id": "grenades-and-missiles-150", - "name": "Grenades and Missiles 150", - "category": "Explosives", - "req": 150, - "renown": "None", - "desc": "Grenades and Missiles 150" - }, - { - "id": "missile-pod-basic-90m-s-2", - "name": "Missile Pod", - "category": "Explosives", - "req": 5, - "renown": "None", - "desc": "Missile Pod Basic 90m S/2/– 2d10+6 X 5 — — Mounted" - }, - { - "id": "photon-flash-grenade-thrown-sbx3-s", - "name": "Photon Flash Grenade Thrown Sbx3 S/", - "category": "Explosives", - "req": 0, - "renown": "None", - "desc": "Photon Flash Grenade Thrown SBx3 S/–/– Special 0 – 0" - }, - { - "id": "so-a-grenade-with-blast-4-will-automatically", - "name": "So A Grenade With Blast (4) Will Automatically", - "category": "Explosives", - "req": 4, - "renown": "None", - "desc": "So a grenade with Blast (4) will automatically" - }, - { - "id": "throwing-grenades-151", - "name": "Throwing Grenades 151", - "category": "Explosives", - "req": 151, - "renown": "None", - "desc": "Throwing Grenades 151" - }, - { - "id": "vortex-grenade-has-a-radius-of-3-metres", - "name": "Vortex Grenade Has A Radius of 3 Metres", - "category": "Explosives", - "req": 3, - "renown": "None", - "desc": "The vortex grenade has a radius of 3 metres" - }, - { - "id": "vortex-grenade-thrown-sbx3-s", - "name": "Vortex Grenade Thrown Sbx3 S/", - "category": "Explosives", - "req": 1, - "renown": "None", - "desc": "Vortex Grenade Thrown SBx3 S/–/– Special 0 – 1 N/A N/A" - }, - { - "id": "astartes-force-fields-overload-on-a-roll-of-01", - "name": "Astartes Force Fields Overload On A Roll of 01", - "category": "Gear", - "req": 10, - "renown": "None", - "desc": "Astartes force fields overload on a roll of 01–10" - }, - { - "id": "astartes-chainfist-mounted-2d10", - "name": "Astartes Chainfist", - "category": "Melee", - "req": 10, - "renown": "None", - "desc": "Astartes Chainfist Mounted 2d10" - }, - { - "id": "astartes-chainsword-melee-1d10", - "name": "Astartes Chainsword Melee 1d10", - "category": "Melee", - "req": 5, - "renown": "None", - "desc": "Astartes Chainsword Melee 1d10+3 R 4 Balanced, Tearing 10 5 –" - }, - { - "id": "astartes-combat-knife-melee-1d10", - "name": "Astartes Combat Knife Melee 1d10", - "category": "Melee", - "req": 3, - "renown": "None", - "desc": "Astartes Combat Knife Melee 1d10+2 R 2 – 2 3" - }, - { - "id": "astartes-force-staff-melee-1d10", - "name": "Astartes Force Staff Melee 1d10", - "category": "Melee", - "req": 25, - "renown": "Respected", - "desc": "Astartes Force Staff Melee 1d10+1 I 0 Balanced, Special 5 25 Respected" - }, - { - "id": "astartes-force-sword-melee-1d10", - "name": "Astartes Force Sword Melee 1d10", - "category": "Melee", - "req": 25, - "renown": "Respected", - "desc": "Astartes Force Sword Melee 1d10+2 R 2 Balanced, Special 5 25 Respected" - }, - { - "id": "astartes-lightning-claw-melee-1d10", - "name": "Astartes Lightning Claw Melee 1d10", - "category": "Melee", - "req": 8, - "renown": "None", - "desc": "Astartes Lightning Claw Melee 1d10+6 E 8" - }, - { - "id": "astartes-thunder-hammer-melee-2d10", - "name": "Astartes Thunder Hammer Melee 2d10", - "category": "Melee", - "req": 8, - "renown": "None", - "desc": "Astartes Thunder Hammer Melee 2d10+5 E 8" - }, - { - "id": "omnissian-axe-astartes-pattern-melee-2d10", - "name": "Omnissian Axe (Astartes-Pattern) Melee 2d10", - "category": "Melee", - "req": 30, - "renown": "None", - "desc": "Omnissian Axe (Astartes-Pattern) Melee 2d10+6 E 6 Power Field, Unbalanced 14 30" - }, - { - "id": "adeptus-astartes-heavy-flamer-see-page-145", - "name": "Adeptus Astartes Heavy Flamer (See Page 145)", - "category": "Ranged", - "req": 145, - "renown": "None", - "desc": "an Adeptus Astartes heavy flamer (see page 145), with all the" - }, - { - "id": "astartes-digital-flamer-pistol-4m-s", - "name": "Astartes Digital Flamer", - "category": "Ranged", - "req": 1, - "renown": "None", - "desc": "Astartes Digital Flamer Pistol 4m S/–/– 2d10+2 E 3 1 Special Flame" - }, - { - "id": "astartes-digital-melta-pistol-4m-s", - "name": "Astartes Digital Melta", - "category": "Ranged", - "req": 0, - "renown": "None", - "desc": "Astartes Digital Melta Pistol 4m S/–/– 2d10+5 E 12 1 Special – 0" - }, - { - "id": "astartes-flamer-basic-20m-s", - "name": "Astartes Flamer", - "category": "Ranged", - "req": 10, - "renown": "None", - "desc": "Astartes Flamer Basic 20m S/–/– 2d10+2 E 3 6 2 Full Flame 15 10 –" - }, - { - "id": "astartes-lascannon-heavy-300m-s", - "name": "Astartes Lascannon", - "category": "Ranged", - "req": 30, - "renown": "Respected", - "desc": "Astartes Lascannon Heavy 300m S/–/– 6d10+10 E 10 6 2 Full – 75 30 Respected" - }, - { - "id": "astartes-plasma-pistol-see-page-148", - "name": "Astartes Plasma", - "category": "Ranged", - "req": 148, - "renown": "None", - "desc": "used as though it were an Astartes Plasma Pistol (see page 148)" - }, - { - "id": "astartes-shotgun-basic-30m-s-2", - "name": "Astartes Shotgun", - "category": "Ranged", - "req": 18, - "renown": "None", - "desc": "Astartes Shotgun Basic 30m S/2/– 2d10+3 I 0 18 Full" - }, - { - "id": "astartes-sniper-rifle-basic-200m-s", - "name": "Astartes Sniper Rifle", - "category": "Ranged", - "req": 2, - "renown": "None", - "desc": "Astartes Sniper Rifle Basic 200m S/–/– 1d10 R 0 6 2 Full" - }, - { - "id": "autogun-basic-90m-s-3-10-1d10", - "name": "Autogun", - "category": "Ranged", - "req": 3, - "renown": "None", - "desc": "Autogun Basic 90m S/3/10 1d10+3 I 0 30 2 Full – 3" - }, - { - "id": "autopistol-pistol-30m-s", - "name": "Autopistol", - "category": "Ranged", - "req": 2, - "renown": "None", - "desc": "Autopistol Pistol 30m S/–/6 1d10+2 I 0 18 Full – 2" - }, - { - "id": "bolter-assault-free-3-no", - "name": "Bolter Assault Free 3 No", - "category": "Ranged", - "req": 3, - "renown": "None", - "desc": "Bolter Assault Free 3 No" - }, - { - "id": "bolter-drill-talent-114", - "name": "Bolter Drill (Talent) 114", - "category": "Ranged", - "req": 114, - "renown": "None", - "desc": "Bolter Drill (Talent) 114" - }, - { - "id": "bolter-drill-1", - "name": "Bolter Drill 1", - "category": "Ranged", - "req": 500, - "renown": "None", - "desc": "Bolter Drill 1,500 Talent Astartes Weapon Training" - }, - { - "id": "bolter-drill-800-talent", - "name": "Bolter Drill 800 Talent", - "category": "Ranged", - "req": 800, - "renown": "None", - "desc": "Bolter Drill 800 Talent —" - }, - { - "id": "boltgun-suffer-an-additional-30-penalty-and-can-only-be", - "name": "Boltgun Suffer An Additional –30 Penalty and Can Only Be", - "category": "Ranged", - "req": 30, - "renown": "None", - "desc": "boltgun suffer an additional –30 penalty and can only be" - }, - { - "id": "gunmetalican-13th-armoured", - "name": "Gunmetalican 13th Armoured", - "category": "Ranged", - "req": 13, - "renown": "None", - "desc": "Gunmetalican 13th Armoured" - }, - { - "id": "he-is-shot-by-a-lascannon-that-deals-21-points", - "name": "He Is Shot By A Lascannon That Deals 21 Points", - "category": "Ranged", - "req": 21, - "renown": "None", - "desc": "He is shot by a lascannon that deals 21 points" - }, - { - "id": "lasgun", - "name": "Lasgun", - "category": "Ranged", - "req": 100, - "renown": "None", - "desc": "Lasgun, (100m" - }, - { - "id": "laspistol-pistol-30m-s", - "name": "Laspistol", - "category": "Ranged", - "req": 1, - "renown": "None", - "desc": "Laspistol Pistol 30m S/–/– 1d10+2 E 0 30 Full Reliable 1" - }, - { - "id": "launchers-152", - "name": "Launchers 152", - "category": "Ranged", - "req": 152, - "renown": "None", - "desc": "Launchers 152" - }, - { - "id": "melta-bomb-12-25", - "name": "Melta Bomb 12 25", - "category": "Ranged", - "req": 25, - "renown": "None", - "desc": "Melta bomb 12 25 –" - }, - { - "id": "mob-armed-with-autopistols-will-cause-3d10-2-damage-with", - "name": "Mob Armed With Autopistols Will Cause 3d10+2 Damage With", - "category": "Ranged", - "req": 2, - "renown": "None", - "desc": "mob armed with autopistols will cause 3d10+2 damage with" - }, - { - "id": "plasma", - "name": "Plasma", - "category": "Ranged", - "req": 100, - "renown": "None", - "desc": "supplies hold 100 rounds of ammunition for Plasma, Melta, and" - }, - { - "id": "plasma-cannon-would-fire-a-blast-3-on-maximal", - "name": "Plasma Cannon Would Fire A Blast (3) On Maximal)", - "category": "Ranged", - "req": 3, - "renown": "None", - "desc": "Plasma Cannon would fire a Blast (3) on Maximal)" - }, - { - "id": "plasma-pistol-30m-s", - "name": "Plasma Pistol (30m, S/—/—", - "category": "Ranged", - "req": 30, - "renown": "None", - "desc": "plasma pistol (30m, S/—/—" - }, - { - "id": "plasma-rifle-basic-90m-s-2", - "name": "Plasma Rifle", - "category": "Ranged", - "req": 8, - "renown": "None", - "desc": "Plasma Rifle Basic 90m S/2/– 2d10+9 E 8 — — Tearing, Mounted" - }, - { - "id": "requisition-a-storm-bolter-20-requisition-to-use-instead", - "name": "Requisition A Storm Bolter (20 Requisition) To Use Instead", - "category": "Ranged", - "req": 20, - "renown": "None", - "desc": "decides to Requisition a storm bolter (20 Requisition) to use instead" - }, - { - "id": "skapulan-bolter-basic-120m-s-2-4-2d10", - "name": "Skapulan Bolter", - "category": "Ranged", - "req": 28, - "renown": "None", - "desc": "Skapulan Bolter Basic 120m S/2/4 2d10+9 X 7 28 Full" +{ + "version": "2.0.0", + "lastUpdated": "2025-08-15T13:31:19.082Z", + "categories": [ + "rangedWeapons", + "meleeWeapons", + "grenades", + "otherWeapons", + "powerArmor", + "carapaceArmor", + "primitiveArmor", + "naturalArmor", + "xenosArmor", + "shields", + "otherArmor" + ], + "items": { + "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": "None", + "category": "Ranged Weapon", + "stats": { + "damage": "Range 200m; S/-/-; 0d0 S; Pen 0; Clip 1; Reload 2 Full; Two handed;", + "class": "Heavy - Launch", + "source": "Living Errata v1.1 p137" + } + }, + { + "name": "Astartes Assault Cannon", + "req": 30, + "renown": "Hero", + "category": "Ranged Weapon", + "stats": { + "damage": "Range 150m; -/-/10; 2d10+12 I; Pen 6; Clip 200; Reload 3 Full; Tearing, Devastating (1)", + "class": "Mounted - Solid Projectile", + "source": "Living Errata v1.1 p149" + } + }, + { + "name": "Astartes Assault Shotgun", + "req": 7, + "renown": "None", + "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": "None", + "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": "None", + "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 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": "Hero", + "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": "Hero", + "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" + } + } + ], + "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 Chainfist", + "req": 40, + "renown": "Hero", + "category": "Melee 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 Chainsword", + "req": 5, + "renown": "None", + "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": "None", + "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" + } + } + ], + "grenades": [ + { + "name": "Astartes Anti-Plant Grenade", + "req": 18, + "renown": "None", + "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": "None", + "category": "Grenade", + "stats": { + "damage": "0d0 S; Pen 0; Smoke (10)", + "class": "Thrown - Grenade", + "source": "Living Errata v1.1 p150" + } + } + ], + "otherWeapons": [ + { + "name": "Argrax Clawed Hooves", + "req": 0, + "renown": "None", + "category": "Other", + "stats": { + "damage": "1d10 I; Pen 0;", + "class": "Natural - Primitive", + "source": "The Emperor Protects p46" + } + } + ], + "powerArmor": [ + { + "name": "Armour of the Remorseless Crusader", + "req": 70, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 12, + "arms": 12, + "body": 12, + "legs": 12 + }, + "source": "Deathwatch Core Rulebook p167" + } + }, + { + "name": "Astartes Artificer Armour", + "req": 60, + "renown": "Hero", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 12, + "arms": 12, + "body": 12, + "legs": 12 + }, + "source": "Deathwatch Core Rulebook p163" + } + }, + { + "name": "Astartes Mk 1 Thunder Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 6, + "body": 8, + "legs": 4 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 2 Crusade Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 7, + "body": 9, + "legs": 7 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 3 Iron Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 9, + "body": 10, + "legs": 9 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 4 Maximus Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 7, + "body": 9, + "legs": 7 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 5 Heresy Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 8, + "body": 9, + "legs": 8 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 6 Corvus Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 8, + "body": 9, + "legs": 8 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 7 Aquilla Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 8, + "body": 10, + "legs": 8 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk 8 Errant Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 8, + "body": 11, + "legs": 8 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk1 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 6, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk2 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 7, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk3 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 9, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk4 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 7, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk5 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk6 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk7 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Mk8 Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p151" + } + }, + { + "name": "Astartes Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 8, + "body": 10, + "legs": 8 + }, + "source": "Deathwatch Core Rulebook p160" + } + }, + { + "name": "Astartes Power Armour Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Deathwatch Core Rulebook p160" + } + }, + { + "name": "Astartes Tactical Dreadnaught-Terminator Armour", + "req": 100, + "renown": "Hero", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 14, + "body": 14, + "legs": 14 + }, + "source": "Deathwatch Core Rulebook p164" + } + }, + { + "name": "Astartes Tactical Dreadnaught-Terminator Armour Helm", + "req": 100, + "renown": "Hero", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 14, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Deathwatch Core Rulebook p164" + } + }, + { + "name": "Astartes Terminator Armour", + "req": 100, + "renown": "Hero", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 14, + "arms": 14, + "body": 14, + "legs": 14 + }, + "source": "Deathwatch Core Rulebook p164" + } + }, + { + "name": "CM Power Armour", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 8, + "body": 10, + "legs": 8 + }, + "source": "Mark of the Xenos p114" + } + }, + { + "name": "Diagnostor Helmet", + "req": 15, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Deathwatch Core Rulebook p165" + } + }, + { + "name": "Dmn Armour of Chaos", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 12, + "arms": 12, + "body": 12, + "legs": 12 + }, + "source": "Deathwatch Core Rulebook p362" + } + }, + { + "name": "Fenris Pattern Wolf Helm", + "req": 0, + "renown": "Respected", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p143" + } + }, + { + "name": "Skull Helm", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 9, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Rites of Battle p143" + } + }, + { + "name": "Tau XV-8 Crisis Battlesuit", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 9, + "arms": 9, + "body": 9, + "legs": 9 + }, + "source": "Mark of the Xenos p9" + } + }, + { + "name": "Tau XV-88 Broadside Battlesuit", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 12, + "arms": 12, + "body": 12, + "legs": 12 + }, + "source": "Mark of the Xenos p6" + } + }, + { + "name": "Tau XV15 Stealth Suit (Limbs)", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 0, + "arms": 7, + "body": 0, + "legs": 7 + }, + "source": "Deathwatch Core Rulebook p367" + } + }, + { + "name": "Tau XV15 Stealth Suit (Top)", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 8, + "arms": 0, + "body": 8, + "legs": 0 + }, + "source": "Deathwatch Core Rulebook p367" + } + }, + { + "name": "Tau XV8 Crisis Suit", + "req": 0, + "renown": "None", + "category": "Power Armor", + "stats": { + "type": "Power", + "protection": { + "head": 9, + "arms": 9, + "body": 9, + "legs": 9 + }, + "source": "Deathwatch Core Rulebook p366" + } + } + ], + "carapaceArmor": [ + { + "name": "Astartes Scout Armour", + "req": 0, + "renown": "None", + "category": "Carapace Armor", + "stats": { + "type": "Carapace", + "protection": { + "head": 0, + "arms": 6, + "body": 6, + "legs": 0 + }, + "source": "Deathwatch Core Rulebook p163" + } + }, + { + "name": "Chaos Flak Robes and Brazen Carapace", + "req": 0, + "renown": "None", + "category": "Carapace Armor", + "stats": { + "type": "Carapace", + "protection": { + "head": 5, + "arms": 5, + "body": 6, + "legs": 4 + }, + "source": "Deathwatch Core Rulebook p364" + } + }, + { + "name": "Deathwatch Scout Armor", + "req": 0, + "renown": "None", + "category": "Carapace Armor", + "stats": { + "type": "Carapace", + "protection": { + "head": 0, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "Rites of Battle p142" + } + }, + { + "name": "Tau Fire Warrior Armour", + "req": 0, + "renown": "None", + "category": "Carapace Armor", + "stats": { + "type": "Carapace", + "protection": { + "head": 6, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "Deathwatch Core Rulebook p368" + } + }, + { + "name": "Tau Pathfinder Armour", + "req": 0, + "renown": "None", + "category": "Carapace Armor", + "stats": { + "type": "Carapace", + "protection": { + "head": 7, + "arms": 0, + "body": 7, + "legs": 0 + }, + "source": "Mark of the Xenos p25" + } + } + ], + "primitiveArmor": [ + { + "name": "Auran Golden Saurian Scale Armour", + "req": 0, + "renown": "None", + "category": "Primitive Armor", + "stats": { + "type": "Primitive", + "protection": { + "head": 6, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "The Emperor Protects p48" + } + }, + { + "name": "Auran Saurian Scale Armour", + "req": 0, + "renown": "None", + "category": "Primitive Armor", + "stats": { + "type": "Primitive", + "protection": { + "head": 4, + "arms": 4, + "body": 4, + "legs": 4 + }, + "source": "The Emperor Protects p46" + } + }, + { + "name": "Scales", + "req": 0, + "renown": "None", + "category": "Primitive Armor", + "stats": { + "type": "Primitive", + "protection": { + "head": 2, + "arms": 2, + "body": 2, + "legs": 2 + }, + "source": "The Emperor Protects p50" + } + }, + { + "name": "Scaly Hide", + "req": 0, + "renown": "None", + "category": "Primitive Armor", + "stats": { + "type": "Primitive", + "protection": { + "head": 4, + "arms": 4, + "body": 4, + "legs": 4 + }, + "source": "The Emperor Protects p46" + } + } + ], + "naturalArmor": [ + { + "name": "Tau Gun Drone Armour", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 5, + "arms": 5, + "body": 5, + "legs": 5 + }, + "source": "Deathwatch Core Rulebook p367" + } + }, + { + "name": "Tyr Bonded Exoskeleton", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 10, + "arms": 10, + "body": 10, + "legs": 10 + }, + "source": "Deathwatch Core Rulebook p370" + } + }, + { + "name": "Tyr Chitinous Carapace", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 3, + "arms": 3, + "body": 3, + "legs": 3 + }, + "source": "Deathwatch Core Rulebook p371" + } + }, + { + "name": "Tyr Exoskeleton", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 2, + "arms": 2, + "body": 2, + "legs": 2 + }, + "source": "Mark of the Xenos p43" + } + }, + { + "name": "Tyr Hardened Carapace", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 6, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "Mark of the Xenos p40" + } + }, + { + "name": "Tyr Light Reinforced Chitin", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 4, + "arms": 4, + "body": 4, + "legs": 4 + }, + "source": "Mark of the Xenos p39" + } + }, + { + "name": "Tyr Medium Reinforced Chitin", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 6, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "Mark of the Xenos p37" + } + }, + { + "name": "Tyr Reinforced Chitin", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 8, + "arms": 8, + "body": 8, + "legs": 8 + }, + "source": "Deathwatch Core Rulebook p371" + } + }, + { + "name": "Tyr Thickened Scales", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 10, + "arms": 10, + "body": 10, + "legs": 10 + }, + "source": "Mark of the Xenos p46" + } + }, + { + "name": "Vespid Chitin", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 3, + "arms": 3, + "body": 3, + "legs": 3 + }, + "source": "Mark of the Xenos p26" + } + }, + { + "name": "Xenos Hardened Hide", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 6, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "Mark of the Xenos p66" + } + }, + { + "name": "Xenos Scaly Hide", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 3, + "arms": 3, + "body": 3, + "legs": 3 + }, + "source": "Mark of the Xenos p63" + } + }, + { + "name": "Xenos Slimey Hide", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 2, + "arms": 2, + "body": 2, + "legs": 2 + }, + "source": "Mark of the Xenos p68" + } + }, + { + "name": "Xenos Thick Scales", + "req": 0, + "renown": "None", + "category": "Natural Armor", + "stats": { + "type": "Natural", + "protection": { + "head": 5, + "arms": 5, + "body": 5, + "legs": 5 + }, + "source": "Mark of the Xenos p65" + } + } + ], + "xenosArmor": [ + { + "name": "Nec Metallic Exoskeleton", + "req": 0, + "renown": "None", + "category": "Xenos Armor", + "stats": { + "type": "Necron", + "protection": { + "head": 8, + "arms": 8, + "body": 8, + "legs": 8 + }, + "source": "The Emperor Protects p94" + } + }, + { + "name": "Nec Metallic Shell", + "req": 0, + "renown": "None", + "category": "Xenos Armor", + "stats": { + "type": "Necron", + "protection": { + "head": 10, + "arms": 10, + "body": 10, + "legs": 10 + }, + "source": "The Emperor Protects p94" + } + }, + { + "name": "Ork Eavy Armour", + "req": 0, + "renown": "None", + "category": "Xenos Armor", + "stats": { + "type": "Ork", + "protection": { + "head": 6, + "arms": 4, + "body": 6, + "legs": 4 + }, + "source": "Mark of the Xenos p57" + } + }, + { + "name": "Ork Flak Armour", + "req": 0, + "renown": "None", + "category": "Xenos Armor", + "stats": { + "type": "Ork", + "protection": { + "head": 0, + "arms": 0, + "body": 2, + "legs": 0 + }, + "source": "Mark of the Xenos p57" + } + }, + { + "name": "Ork Mega Armour", + "req": 0, + "renown": "None", + "category": "Xenos Armor", + "stats": { + "type": "Ork", + "protection": { + "head": 6, + "arms": 10, + "body": 14, + "legs": 10 + }, + "source": "Mark of the Xenos p57" + } + } + ], + "shields": [ + { + "name": "Astartes Storm Shield - Protective", + "req": 35, + "renown": "Distinguished", + "category": "Shields", + "stats": { + "type": "Shield", + "protection": { + "head": 0, + "arms": 0, + "body": 4, + "legs": 0 + }, + "source": "Deathwatch Core Rulebook p166" + } + } + ], + "otherArmor": [ + { + "name": "Name", + "req": 0, + "renown": "Distinguished", + "category": "Other Armor", + "stats": { + "type": "Type", + "protection": { + "head": 0, + "arms": 0, + "body": 0, + "legs": 0 + }, + "source": "Book pPage No." + } + }, + { + "name": "Chaos Armour Plating", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 6, + "arms": 6, + "body": 6, + "legs": 6 + }, + "source": "Mark of the Xenos p91" + } + }, + { + "name": "Chaos Bones of Subersion", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 9, + "arms": 9, + "body": 9, + "legs": 9 + }, + "source": "Mark of the Xenos p86" + } + }, + { + "name": "Chaos Slinnar Obsidian Shell", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 8, + "arms": 8, + "body": 8, + "legs": 8 + }, + "source": "Mark of the Xenos p92" + } + }, + { + "name": "Chaos Warded Adamantine Shell", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 9, + "arms": 9, + "body": 9, + "legs": 9 + }, + "source": "Mark of the Xenos p95" + } + }, + { + "name": "Daemon Iron Hide", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 13, + "arms": 13, + "body": 13, + "legs": 13 + }, + "source": "Mark of the Xenos p101" + } + }, + { + "name": "Dmn Brazen Chaos Armour", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 12, + "arms": 12, + "body": 12, + "legs": 12 + }, + "source": "The Emperor Protects p136" + } + }, + { + "name": "Obliterator Warped Mechnical Flesh", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 12, + "arms": 12, + "body": 12, + "legs": 12 + }, + "source": "Mark of the Xenos p120" + } + }, + { + "name": "Rad Corrupted Mechnical Flesh", + "req": 0, + "renown": "None", + "category": "Other Armor", + "stats": { + "type": "Other", + "protection": { + "head": 7, + "arms": 7, + "body": 7, + "legs": 7 + }, + "source": "The Emperor Protects p91" + } + } + ] } -] \ No newline at end of file +} \ No newline at end of file diff --git a/public/deathwatch-weapons.json b/public/deathwatch-weapons.json new file mode 100644 index 0000000..2c09de1 --- /dev/null +++ b/public/deathwatch-weapons.json @@ -0,0 +1,230 @@ +{ + "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": "None", + "category": "Ranged Weapon", + "stats": { + "damage": "Range 200m; S/-/-; 0d0 S; Pen 0; Clip 1; Reload 2 Full; Two handed;", + "class": "Heavy - Launch", + "source": "Living Errata v1.1 p137" + } + }, + { + "name": "Astartes Assault Cannon", + "req": 30, + "renown": "Hero", + "category": "Ranged Weapon", + "stats": { + "damage": "Range 150m; -/-/10; 2d10+12 I; Pen 6; Clip 200; Reload 3 Full; Tearing, Devastating (1)", + "class": "Mounted - Solid Projectile", + "source": "Living Errata v1.1 p149" + } + }, + { + "name": "Astartes Assault Shotgun", + "req": 7, + "renown": "None", + "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": "None", + "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": "None", + "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 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": "Hero", + "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": "Hero", + "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" + } + } + ], + "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 Chainfist", + "req": 40, + "renown": "Hero", + "category": "Melee 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 Chainsword", + "req": 5, + "renown": "None", + "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": "None", + "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" + } + } + ], + "grenades": [ + { + "name": "Astartes Anti-Plant Grenade", + "req": 18, + "renown": "None", + "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": "None", + "category": "Grenade", + "stats": { + "damage": "0d0 S; Pen 0; Smoke (10)", + "class": "Thrown - Grenade", + "source": "Living Errata v1.1 p150" + } + } + ], + "other": [ + { + "name": "Argrax Clawed Hooves", + "req": 0, + "renown": "None", + "category": "Other", + "stats": { + "damage": "1d10 I; Pen 0;", + "class": "Natural - Primitive", + "source": "The Emperor Protects p46" + } + } + ] +} \ No newline at end of file diff --git a/scripts/build-shop-db.js b/scripts/build-shop-db.js new file mode 100644 index 0000000..8316af3 --- /dev/null +++ b/scripts/build-shop-db.js @@ -0,0 +1,48 @@ +const { categorizedWeapons } = require('./parse-weapons'); +const { categorizedArmor } = require('./parse-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, + + // Armor + powerArmor: categorizedArmor.powerArmor, + carapaceArmor: categorizedArmor.carapaceArmor, + primitiveArmor: categorizedArmor.primitiveArmor, + naturalArmor: categorizedArmor.naturalArmor, + xenosArmor: categorizedArmor.xenosArmor, + shields: categorizedArmor.shields, + otherArmor: categorizedArmor.otherArmor + }; + + // Add metadata + const shopDatabase = { + version: "2.0.0", + lastUpdated: new Date().toISOString(), + categories: Object.keys(shopItems), + items: shopItems + }; + + return shopDatabase; +}; + +// Write the database +if (require.main === module) { + const fs = require('fs'); + const path = require('path'); + + // First ensure our source data is up to date + require('./parse-weapons'); + require('./parse-armor'); + + // Then build and write the combined database + 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); +} diff --git a/scripts/clean-armoury.js b/scripts/clean-armoury.js old mode 100755 new mode 100644 index 84efd0f..41f377f --- a/scripts/clean-armoury.js +++ b/scripts/clean-armoury.js @@ -1,96 +1,243 @@ -/* - Clean and normalize armoury JSON produced from PDF extraction. - Usage: - node scripts/clean-armoury.js public/deathwatch-armoury.json [output.json] -*/ +const rawWeaponData = ` +Angelus Bolter Basic - Bolt Range 50m; S/3/-; 1d10+9 X; Pen 6; Clip 36; Reload 2 Full; Tearing 25 Respected Deathwatch First Founding 97 +Argrax Clawed Hooves Natural - Primitive 1d10 I; Pen 0; 0 Any Deathwatch The Emperor Protects 46 +Armour of the Remorseless Crusader (Forearm Sword) One-Handed Melee - Primitive 1d10+8 R; Pen 6; +10 to hit; Sanctified 70 Hero Deathwatch Deathwatch Core Rulebook 167 +Artificer Omnissian Axe One-Handed Melee - Power 1d10+9 E; Pen 7; Power Field, Unbalanced 25 Distinguished Deathwatch First Founding 99 +Astartes Anti-Plant Grenade Thrown - Grenade 3d10 E; Pen 0; Blast (3) 18 Any Deathwatch Living Errata v1.1 136 +Astartes Armoursbane Missile Launcher Heavy - Launch Range 200m; S/-/-; 0d0 S; Pen 0; Clip 1; Reload 2 Full; Two handed; 15 Any Deathwatch Living Errata v1.1 137 +Astartes Assault Cannon Mounted - Solid Projectile Range 150m; -/-/10; 2d10+12 I; Pen 6; Clip 200; Reload 3 Full; Tearing, Devastating (1) 30 Famed Deathwatch Living Errata v1.1 149 +Astartes Assault Shotgun Basic - Solid Projectile Range 30m; S/3/5; 1d10+10 I; Pen 4; Clip 20; Reload 1 Full; Two handed; Reliable, Scatter 7 Any Deathwatch Living Errata v1.1 136 +Astartes Blind Grenade Thrown - Grenade 0d0 S; Pen 0; Smoke (10) 10 Any Deathwatch Living Errata v1.1 150 +Astartes Bolt Pistol Pistol - Bolt Range 30m; S/2/-; 1d10+9 X; Pen 4; Clip 14; Reload 1 Full; Tearing 5 Any Deathwatch Living Errata v1.1 146 +Astartes Bolter-Godwyn Basic - Bolt Range 100m; S/3/-; 1d10+9 X; Pen 4; Clip 28; Reload 1 Full; Two handed; Tearing 5 Any Deathwatch Living Errata v1.1 146 +Astartes Boltgun-Stalker Basic - Bolt Range 200m; S/-/-; 1d10+9 X; Pen 4; Clip 24; Reload 1 Full; Two handed; Accurate, Tearing 15 Respected Deathwatch Living Errata v1.1 146 +Astartes Chainfist One-Handed Melee - Power 2d10+"2*SB" E; Pen 10; Power Field, Tearing, Unwieldy 40 Famed Deathwatch Living Errata v1.1 154 +Astartes Chainsword One-Handed Melee - Chain 1d10+3 R; Pen 3; Balanced, Tearing 5 Any Deathwatch Living Errata v1.1 153 +Astartes Combat Knife One-Handed Melee - Primitive 1d10 R; Pen 2; 3 Any Deathwatch Living Errata v1.1 155 +Astartes Combat Shield One-Handed Melee - Primitive 1d5+1 I; Pen 0; Balanced 20 Distinguished Deathwatch Living Errata v1.1 166 +Astartes Combi-Weapon Basic - Bolt Range 100m; S/4/-; 1d10+9 X; Pen 4; Clip 28; Reload 1 Full; Two handed; Tearing 15 Respected Deathwatch Living Errata v1.1 146 +Astartes Conversion Beamer Heavy - Exotic Range 100m; S/-/-; 0d0 E; Pen 0; Clip 4; Reload 2 Full; Two handed; 40 Hero Deathwatch Living Errata v1.1 152 +Astartes Cyclone Missile Launcher Mounted - Launch Range 300m; S/2/-; 0d0 S; Pen 0; Clip 12; Reload 3 Full; 25 Famed Deathwatch Living Errata v1.1 152 +Astartes Digital Flamer Pistol - Exotic Range 4m; S/-/-; 1d10+9 E; Pen 4; Clip 1; Reload 1d5 Hours; Flame, Spray 35 Famed Deathwatch Living Errata v1.1 153 +Astartes Digital Laser Pistol - Exotic Range 4m; S/-/-; 1d10+4 E; Pen 7; Clip 1; Reload 1d5 Hours; Reliable 35 Famed Deathwatch Living Errata v1.1 153 +Astartes Digital Melta Pistol - Exotic Range 4m; S/-/-; 2d10+12 E; Pen 10; Clip 1; Reload 1d5 Hours; 35 Famed Deathwatch Living Errata v1.1 153 +Astartes Executioner Axe Two-Handed Melee - Power 1d10+13 E; Pen 8; Two handed; Power Field, Unwieldy, Felling (x1) 25 Famed Deathwatch Living Errata v1.1 139 +Astartes Flamer Basic - Flame Range 20m; S/-/-; 1d10+9 E; Pen 4; Clip 6; Reload 2 Full; Two handed; Flame, Spray 10 Any Deathwatch Living Errata v1.1 148 +Astartes Force Staff One-Handed Melee - Primitive 1d10+"1+PR+SB" S; Pen "PR"; Balanced 25 Respected Deathwatch Living Errata v1.1 155 +Astartes Force Sword One-Handed Melee - Primitive 1d10+"2+PR+SB" S; Pen "2+PR"; Balanced 25 Respected Deathwatch Living Errata v1.1 156 +Astartes Frag Grenade Thrown - Grenade 2d10+2 X; Pen 0; Blast (4) 1 Any Deathwatch Living Errata v1.1 150 +Astartes Hand Flamer Pistol - Flame Range 10m; S/-/-; 1d10+7 E; Pen 4; Clip 4; Reload 2 Full; Flame, Spray 10 Any Deathwatch Living Errata v1.1 149 +Astartes Heavy Flamer Heavy - Flame Range 30m; S/-/-; 1d10+12 E; Pen 6; Clip 10; Reload 2 Full; Two handed; Flame, Spray 15 Any Deathwatch Living Errata v1.1 149 +Astartes Heavy-Bolter Heavy - Bolt Range 150m; -/-/6; 1d10+12 X; Pen 5; Clip 60; Reload 1 Full; Two handed; Tearing 20 Any Deathwatch Living Errata v1.1 147 +Astartes Incendiary Grenade Thrown - Grenade 1d10+4 E; Pen 0; Blast (3) 15 Any Deathwatch Living Errata v1.1 150 +Astartes Infernus Pistol Pistol - Melta Range 10m; S/-/-; 2d10+12 E; Pen 12; Clip 4; Reload 1 Full; Melta 35 Famed Deathwatch Living Errata v1.1 148 +Astartes Krak Grenade Thrown - Grenade 3d10+4 X; Pen 6; 1 Any Deathwatch Living Errata v1.1 150 +Astartes Lascannon Heavy - Las Range 300m; S/-/-; 5d10+10 E; Pen 10; Clip 6; Reload 2 Full; Two handed; Proven (3) 30 Respected Deathwatch Living Errata v1.1 149 +Astartes Lightning Claw One-Handed Melee - Power 1d10+6 E; Pen 8; Power Field, Proven (4) 30 Distinguished Deathwatch Living Errata v1.1 154 +Astartes Meltagun-Vulkan Basic - Melta Range 30m; S/-/-; 2d10+14 E; Pen 12; Clip 6; Reload 2 Full; Two handed; Melta 20 Respected Deathwatch Living Errata v1.1 148 +Astartes Missile Launcher-Soundstrike Heavy - Launch Range 250m; S/-/-; 0d0 S; Pen 0; Clip 8; Reload 1 Full; Two handed; 10 Any Deathwatch Living Errata v1.1 152 +Astartes Multi-Melta - Maxima Heavy - Melta Range 60m; S/-/-; 2d10+16 E; Pen 12; Clip 12; Reload 2 Full; Two handed; Blast (1), Melta 35 Respected Deathwatch Living Errata v1.1 148 +Astartes Nova Grenade Thrown - Grenade 1d10 E; Pen 0; Blast (3) 15 Any Deathwatch Living Errata v1.1 151 +Astartes Photon Flash Grenade Thrown - Grenade 0d0 S; Pen 0; 5 Any Deathwatch Living Errata v1.1 151 +Astartes Plasma Cannon Heavy - Plasma Range 150m; S/-/-; 2d10+12 E; Pen 10; Clip 16; Reload 5 Full; Two handed; Blast (3), Volatile, Maximal 30 Distinguished Deathwatch Living Errata v1.1 147 +Astartes Plasma Grenade Thrown - Grenade 1d10+12 E; Pen 8; Blast (3) 20 Distinguished Deathwatch Living Errata v1.1 151 +Astartes Plasma Gun -Ragefire Basic - Plasma Range 100m; S/2/-; 1d10+12 E; Pen 10; Clip 24; Reload 4 Full; Volatile, Maximal 20 Respected Deathwatch Living Errata v1.1 148 +Astartes Plasma Pistol Pistol - Plasma Range 30m; S/2/-; 1d10+10 E; Pen 8; Clip 12; Reload 3 Full; Volatile, Maximal 25 Respected Deathwatch Living Errata v1.1 148 +Astartes Power Axe One-Handed Melee - Power 1d10+8 E; Pen 7; Power Field, Unbalanced 20 Respected Deathwatch Living Errata v1.1 155 +Astartes Power Claymore Two-Handed Melee - Power 1d10+11 E; Pen 8; Two handed; Power Field, Unwieldy, Devastating (1) 15 Distinguished Deathwatch Living Errata v1.1 139 +Astartes Power Falchion One-Handed Melee - Power 1d10+4 E; Pen 6; Two handed; Power Field, Razor Sharp 25 Distinguished Deathwatch Living Errata v1.1 139 +Astartes Power Fist One-Handed Melee - Power 2d10+"2*SB" E; Pen 9; Power Field, Unwieldy 30 Distinguished Deathwatch Living Errata v1.1 155 +Astartes Power Spear Two-Handed Melee - Power 1d10+6 E; Pen 7; Two handed; Power Field 15 Distinguished Deathwatch Living Errata v1.1 139 +Astartes Power Sword One-Handed Melee - Power 1d10+6 E; Pen 6; Balanced, Power Field 20 Respected Deathwatch Living Errata v1.1 155 +Astartes Servo-Arm Mounted - Primitive 2d10+14 I; Pen 10; 30 Any Deathwatch Living Errata v1.1 177 +`.trim(); -const fs = require('fs') -const path = require('path') - -function loadJson(p){ return JSON.parse(fs.readFileSync(p,'utf8')) } -function saveJson(p, obj){ fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, JSON.stringify(obj,null,2),'utf8') } - -function titleCaseWord(w){ - if (!w) return w - if (w.toUpperCase()==='X' || w.toUpperCase()==='E') return w.toUpperCase() - if (/^\d/.test(w)) return w - if (w.length<=2) return w[0] ? (w[0].toUpperCase()+w.slice(1).toLowerCase()) : w - return w[0].toUpperCase()+w.slice(1).toLowerCase() -} - -function titleCaseName(s){ - return String(s||'') - .split(/([\s\-()/]+)/) - .map(part => /[\s\-()/]+/.test(part) ? part : titleCaseWord(part)) - .join('') - .replace(/\bOf\b|\bAnd\b|\bThe\b/g, m=>m.toLowerCase()) -} - -function cleanName(raw, desc){ - let n = String(raw||'').trim() - const d = String(desc||'') - // Common OCR noise - n = n.replace(/^rtes\b/i,'Astartes ') - n = n.replace(/WeApons/gi,'Weapons') - // If desc contains a canonical prefix use it - const m1 = d.match(/\b(Astartes [A-Za-z][A-Za-z\-\s()]+?)(?:\s+(?:Basic|Pistol|Heavy|Mounted)\b)/) - if (m1) n = m1[1] - // Strip trailing stat blocks starting at class tokens - n = n.replace(/\s+(Basic|Pistol|Heavy|Mounted)\b[\s\S]*$/,'') - // Remove leading filler - n = n.replace(/^[-–—•\s"'`]+/,'') - n = n.replace(/^(a|an|the)\s+/i,'') - // Compact whitespace - n = n.replace(/\s{2,}/g,' ') - // Title case - n = titleCaseName(n) - return n.trim() -} - -const keepWord = /(Astartes|Bolter|Boltgun|Bolt Pistol|Storm Bolter|Plasma|Melta|Flamer|Lascannon|Shotgun|Autogun|Autopistol|Lasgun|Laspistol|Assault Cannon|Sniper Rifle|Combi|Cyclone|Missile|Launcher|Grenade|Power Sword|Power Axe|Power Fist|Thunder Hammer|Lightning Claw|Chainsword|Chainfist|Shield|Armor|Armour)/i -const looksBad = /\b(although|because|while|most|this|these|those|provides|requires|usually|common|mode|effects|counts|burst|power|pack|fuel|rounds|weapon is|weapon’s|weapons can|weapons work)\b/i - -function isLikelyItem(name, cat){ - if (!name) return false - if (name.length < 3 || name.length > 60) return false - if (/^[a-z]/.test(name)) return false - if (looksBad.test(name)) return false - if (/(WeApons|Weapon)/i.test(name) && !/Power|Force|Storm|Shield|Astartes/i.test(name)) return false - if (/(Grenade|Missile)/i.test(name)) return true - if (/(Launcher)/i.test(name)) return true - if (keepWord.test(name)) return true - // Armor pieces - if (/(Shield|Armour|Armor|Helmet|Helm)/i.test(name)) return true - return false -} - -function normalizeEntry(e){ - const name = cleanName(e.name, e.desc) - let req = Number(e.req||0) - if (!Number.isFinite(req) || req<0) req = 0 - const renown = /Respected|Distinguished|Famed|Hero/.test(e.renown||'') ? e.renown : 'None' - const category = e.category || 'Gear' - return { ...e, name, req, renown, category } -} - -function main(){ - const input = process.argv[2] || path.resolve('public/deathwatch-armoury.json') - const output = process.argv[3] || input - const src = loadJson(input) - const cleaned = [] - const seen = new Set() - for (const e of src){ - const n = normalizeEntry(e) - if (!isLikelyItem(n.name, n.category)) continue - const key = n.name.toLowerCase() - if (seen.has(key)) continue - seen.add(key) - cleaned.push(n) +/** Parse raw weapon data */ +const parseWeaponData = (data) => { + // Split into lines and filter out empty ones + const lines = data.split('\n').filter(line => line.trim()); + const weapons = []; + + // Skip header + for (let i = 1; i < lines.length; i++) { + const cols = lines[i].split('\t').map(col => col.trim()); + if (cols.length >= 8) { + weapons.push({ + name: cols[0], + class: cols[1], + damage: cols[2], + cost: parseInt(cols[3]) || 0, + availability: cols[4], + gameSetting: cols[5], + book: cols[6], + pageNo: cols[7] + }); + } } - // Sort by category then name - cleaned.sort((a,b)=> String(a.category).localeCompare(String(b.category)) || a.name.localeCompare(b.name)) - saveJson(output, cleaned) - console.log('Cleaned items:', cleaned.length, 'from', src.length, '->', output) + return weapons; +}; + +/** Core Deathwatch equipment types */ +const weaponData = ` +Name Class Damage Cost Availability Game Setting Book Page No. +${rawWeaponData} +`.trim(); + +const weapons = parseWeaponData(weaponData); + +// Use Map to deduplicate items, preferring Living Errata versions +const uniqueWeapons = new Map(); + +weapons.forEach(weapon => { + const key = weapon.name; + const existing = uniqueWeapons.get(key); + + // Prefer Living Errata over Core Rulebook + if (!existing || weapon.book.includes('Living Errata')) { + uniqueWeapons.set(key, weapon); + } +}); + +const mapAvailabilityToRenown = (availability) => { + const map = { + 'Any': 'None', + 'Common': 'None', + 'Scarce': 'None', + 'Rare': 'Respected', + 'Very Rare': 'Distinguished', + 'Extremely Rare': 'Hero', + 'Near Unique': 'Hero', + 'Unique': 'Legend', + 'Distinguished': 'Distinguished', + 'Famed': 'Hero', + 'Respected': 'Respected', + 'Hero': 'Hero' + }; + return map[availability] || 'Distinguished'; +}; + +const mapClassToCategory = (itemClass) => { + if (itemClass.toLowerCase().includes('melee')) return 'Melee Weapon'; + if (itemClass.toLowerCase().includes('pistol')) return 'Ranged Weapon'; + if (itemClass.toLowerCase().includes('basic') || + itemClass.toLowerCase().includes('heavy') || + itemClass.toLowerCase().includes('mounted')) return 'Ranged Weapon'; + if (itemClass.toLowerCase().includes('thrown')) return 'Grenade'; + return 'Other'; +}; + +// Group items by category +const generateShopItems = () => { + const items = Array.from(uniqueWeapons.values()).map(w => ({ + name: w.name, + req: w.cost, + renown: mapAvailabilityToRenown(w.availability), + category: mapClassToCategory(w.class), + stats: { + damage: w.damage, + class: w.class, + source: `${w.book} p${w.pageNo}` + } + })); + + return { + armor: [ + { name: "Astartes Power Armour", req: 40, renown: "None", category: "Armor" }, + { name: "Astartes Mk6 Corvus Power Armour", req: 45, renown: "Distinguished", category: "Armor" }, + +// Convert the raw weapons into categorized shop items +const generateShopItems = () => { + const items = Array.from(uniqueWeapons.values()).map(w => ({ + name: w.name, + req: w.cost, + renown: mapAvailabilityToRenown(w.availability), + category: mapClassToCategory(w.class), + stats: { + damage: w.damage, + class: w.class, + source: `${w.book} p${w.pageNo}` + } + })); + + // Group items by category + const categorized = { + armor: [ + { name: "Astartes Power Armour", req: 40, renown: "None", category: "Armor" }, + { name: "Astartes Mk6 Corvus Power Armour", req: 45, renown: "Distinguished", category: "Armor" }, + { name: "Astartes Mk7 Aquila Power Armour", req: 45, renown: "Distinguished", category: "Armor" }, + { name: "Astartes Mk8 Errant Power Armour", req: 50, renown: "Distinguished", category: "Armor" }, + { name: "Armour of the Remorseless Crusader", req: 70, renown: "Hero", category: "Armor" }, + { name: "Astartes Scout Armour", req: 30, renown: "None", category: "Armor" }, + { name: "Astartes Terminator Armour", req: 100, renown: "Hero", category: "Armor" }, + { name: "Astartes Artificer Armour", req: 60, renown: "Distinguished", category: "Armor" }, + { name: "Astartes Storm Shield", req: 35, renown: "Distinguished", category: "Armor" }, + { name: "Astartes Combat Shield", req: 20, renown: "Distinguished", category: "Armor" }, + { name: "Astartes Iron Halo", req: 45, renown: "Hero", category: "Armor" }, + { name: "Astartes Sacred Shield", req: 40, renown: "Hero", category: "Armor" } + ], + rangedWeapons: [ + { name: "Astartes Bolt Pistol", req: 15, renown: "None", category: "Ranged Weapon" }, + { name: "Astartes Boltgun", req: 20, renown: "None", category: "Ranged Weapon" }, + { name: "Astartes Heavy Bolter", req: 30, renown: "None", category: "Ranged Weapon" }, + { name: "Astartes Storm Bolter", req: 25, renown: "Distinguished", category: "Ranged Weapon" }, + { name: "Angelus Bolter", req: 25, renown: "Respected", category: "Ranged Weapon" }, + { name: "Stalker Pattern Boltgun", req: 30, renown: "Distinguished", category: "Ranged Weapon" }, + { name: "Hellfire Bolter", req: 35, renown: "Distinguished", category: "Ranged Weapon" }, + { name: "Plasma Pistol", req: 30, renown: "Distinguished", category: "Ranged Weapon" }, + { name: "Plasma Gun", req: 35, renown: "Distinguished", category: "Ranged Weapon" }, + { name: "Plasma Cannon", req: 45, renown: "Hero", category: "Ranged Weapon" }, + { name: "Meltagun", req: 35, renown: "Distinguished", category: "Ranged Weapon" }, + { name: "Multi-Melta", req: 45, renown: "Hero", category: "Ranged Weapon" }, + { name: "Flamer", req: 25, renown: "None", category: "Ranged Weapon" }, + { name: "Heavy Flamer", req: 35, renown: "Distinguished", category: "Ranged Weapon" }, + { name: "Lascannon", req: 40, renown: "Distinguished", category: "Ranged Weapon" }, + { name: "Missile Launcher", req: 40, renown: "Distinguished", category: "Ranged Weapon" }, + { name: "Vengeance Launcher", req: 45, renown: "Hero", category: "Ranged Weapon" }, + { name: "Astartes Grenade Launcher", req: 30, renown: "Distinguished", category: "Ranged Weapon" } + ], + meleeWeapons: [ + { name: "Chainsword", req: 15, renown: "None", category: "Melee Weapon" }, + { name: "Eviscerator", req: 35, renown: "Distinguished", category: "Melee Weapon" }, + { name: "Power Sword", req: 25, renown: "Distinguished", category: "Melee Weapon" }, + { name: "Power Axe", req: 25, renown: "Distinguished", category: "Melee Weapon" }, + { name: "Power Maul", req: 25, renown: "Distinguished", category: "Melee Weapon" }, + { name: "Power Lance", req: 30, renown: "Distinguished", category: "Melee Weapon" }, + { name: "Power Fist", req: 35, renown: "Distinguished", category: "Melee Weapon" }, + { name: "Lightning Claw", req: 35, renown: "Distinguished", category: "Melee Weapon" }, + { name: "Thunder Hammer", req: 40, renown: "Hero", category: "Melee Weapon" }, + { name: "Force Sword", req: 35, renown: "Distinguished", category: "Melee Weapon" }, + { name: "Force Axe", req: 35, renown: "Distinguished", category: "Melee Weapon" }, + { name: "Force Halberd", req: 35, renown: "Distinguished", category: "Melee Weapon" }, + { name: "Force Staff", req: 35, renown: "Distinguished", category: "Melee Weapon" }, + { name: "Combat Knife", req: 10, renown: "None", category: "Melee Weapon" } + ], + ammunition: [ + { name: "Bolt Pistol Magazine", req: 2, renown: "None", category: "Ammunition" }, + { name: "Boltgun Magazine", req: 2, renown: "None", category: "Ammunition" }, + { name: "Storm Bolter Magazine", req: 3, renown: "None", category: "Ammunition" }, + { name: "Heavy Bolter Belt", req: 5, renown: "None", category: "Ammunition" }, + { name: "Hellfire Rounds", req: 8, renown: "Distinguished", category: "Ammunition" }, + { name: "Metal Storm Rounds", req: 8, renown: "Distinguished", category: "Ammunition" }, + { name: "Dragonfire Rounds", req: 8, renown: "Distinguished", category: "Ammunition" }, + { name: "Vengeance Rounds", req: 8, renown: "Distinguished", category: "Ammunition" }, + { name: "Kraken Penetrator Rounds", req: 8, renown: "Distinguished", category: "Ammunition" }, + { name: "Plasma Charge Pack", req: 5, renown: "None", category: "Ammunition" }, + { name: "Melta Charge", req: 5, renown: "None", category: "Ammunition" }, + { name: "Promethium Tank", req: 3, renown: "None", category: "Ammunition" }, + { name: "Missile Pack", req: 5, renown: "None", category: "Ammunition" }, + { name: "Krak Missiles", req: 8, renown: "None", category: "Ammunition" }, + { name: "Frag Missiles", req: 6, renown: "None", category: "Ammunition" }, + { name: "Vengeance Launcher Shells", req: 6, renown: "Distinguished", category: "Ammunition" } + ] +}; + +const path = require('path'); +const fs = require('fs'); + +if (require.main === module) { + const output = path.resolve('public/deathwatch-armoury.json'); + fs.writeFileSync(output, JSON.stringify(coreItems, null, 2), 'utf8'); + console.log('Wrote core items to:', output); } -if (require.main === module) main() +module.exports = coreItems; diff --git a/scripts/fetch-armoury.js b/scripts/fetch-armoury.js new file mode 100644 index 0000000..a7dda92 --- /dev/null +++ b/scripts/fetch-armoury.js @@ -0,0 +1,86 @@ +const axios = require('axios'); +const cheerio = require('cheerio'); + +async function fetchWeaponData() { + try { + const response = await axios.get('http://www.40krpgtools.com/armoury/weapons/'); + const html = response.data; + const $ = cheerio.load(html); + + const weapons = []; + + // Select the table and iterate through rows + $('table tr').each((i, row) => { + if (i === 0) return; // Skip header row + + const cols = $(row).find('td'); + if (cols.length < 8) return; // Skip incomplete rows + + weapons.push({ + name: $(cols[0]).text().trim(), + class: $(cols[1]).text().trim(), + damage: $(cols[2]).text().trim(), + cost: parseInt($(cols[3]).text().trim()) || 0, + availability: $(cols[4]).text().trim(), + gameSetting: $(cols[5]).text().trim(), + book: $(cols[6]).text().trim(), + pageNo: $(cols[7]).text().trim() + }); + }); + + // Filter for Deathwatch items only + const deathwatchItems = weapons.filter(w => + w.gameSetting === 'Deathwatch' && + w.name.startsWith('Astartes') + ); + + // Convert to our shop format + const shopItems = deathwatchItems.map(w => ({ + name: w.name, + req: w.cost, + renown: mapAvailabilityToRenown(w.availability), + category: mapClassToCategory(w.class), + stats: { + damage: w.damage, + class: w.class, + source: `${w.book} p${w.pageNo}` + } + })); + + return shopItems; + } catch (error) { + console.error('Error fetching weapon data:', error); + return []; + } +} + +function mapAvailabilityToRenown(availability) { + const map = { + 'Any': 'None', + 'Common': 'None', + 'Scarce': 'None', + 'Rare': 'Respected', + 'Very Rare': 'Distinguished', + 'Extremely Rare': 'Hero', + 'Near Unique': 'Hero', + 'Unique': 'Legend' + }; + return map[availability] || 'Distinguished'; +} + +function mapClassToCategory(itemClass) { + if (itemClass.includes('Melee')) return 'Melee Weapon'; + if (itemClass.includes('Pistol')) return 'Ranged Weapon'; + if (itemClass.includes('Basic') || itemClass.includes('Heavy')) return 'Ranged Weapon'; + return 'Other'; +} + +if (require.main === module) { + fetchWeaponData() + .then(items => { + console.log(JSON.stringify(items, null, 2)); + }) + .catch(console.error); +} + +module.exports = { fetchWeaponData }; diff --git a/scripts/parse-armor.js b/scripts/parse-armor.js new file mode 100644 index 0000000..0bfae58 --- /dev/null +++ b/scripts/parse-armor.js @@ -0,0 +1,194 @@ +const armorData = ` +Name Type Protection Cost Availability Game Setting Book Page No. +Armour of the Remorseless Crusader Power 12 all 70 Any Deathwatch Deathwatch Core Rulebook 167 +Astartes Artificer Armour Power 12 all 60 Hero Deathwatch Deathwatch Core Rulebook 163 +Astartes Mk 1 Thunder Power Armour Power 0 / 6 / 8 / 4 0 Any Deathwatch Rites of Battle 151 +Astartes Mk 2 Crusade Power Armour Power 0 / 7 / 9 / 7 0 Any Deathwatch Rites of Battle 151 +Astartes Mk 3 Iron Power Armour Power 0 / 9 / 10 / 9 0 Any Deathwatch Rites of Battle 151 +Astartes Mk 4 Maximus Power Armour Power 0 / 7 / 9 / 7 0 Any Deathwatch Rites of Battle 151 +Astartes Mk 5 Heresy Power Armour Power 0 / 8 / 9 / 8 0 Any Deathwatch Rites of Battle 151 +Astartes Mk 6 Corvus Power Armour Power 0 / 8 / 9 / 8 0 Any Deathwatch Rites of Battle 151 +Astartes Mk 7 Aquilla Power Armour Power 0 / 8 / 10 / 8 0 Any Deathwatch Rites of Battle 151 +Astartes Mk 8 Errant Power Armour Power 0 / 8 / 11 / 8 0 Any Deathwatch Rites of Battle 151 +Astartes Mk1 Power Armour Helm Power 6 / 0 / 0 / 0 0 Any Deathwatch Rites of Battle 151 +Astartes Mk2 Power Armour Helm Power 7 / 0 / 0 / 0 0 Any Deathwatch Rites of Battle 151 +Astartes Mk3 Power Armour Helm Power 9 / 0 / 0 / 0 0 Any Deathwatch Rites of Battle 151 +Astartes Mk4 Power Armour Helm Power 7 / 0 / 0 / 0 0 Any Deathwatch Rites of Battle 151 +Astartes Mk5 Power Armour Helm Power 8 / 0 / 0 / 0 0 Any Deathwatch Rites of Battle 151 +Astartes Mk6 Power Armour Helm Power 8 / 0 / 0 / 0 0 Any Deathwatch Rites of Battle 151 +Astartes Mk7 Power Armour Helm Power 8 / 0 / 0 / 0 0 Any Deathwatch Rites of Battle 151 +Astartes Mk8 Power Armour Helm Power 8 / 0 / 0 / 0 0 Any Deathwatch Rites of Battle 151 +Astartes Power Armour Power 0 / 8 / 10 / 8 0 Any Deathwatch Deathwatch Core Rulebook 160 +Astartes Power Armour Helm Power 8 / 0 / 0 / 0 0 Any Deathwatch Deathwatch Core Rulebook 160 +Astartes Scout Armour Carapace 0 / 6 / 6 / 0 0 Any Deathwatch Deathwatch Core Rulebook 163 +Astartes Storm Shield - Protective Shield 0 / 0LA 4RA / 4 / 0 35 Distinguished Deathwatch Deathwatch Core Rulebook 166 +Astartes Tactical Dreadnaught-Terminator Armour Power 0 / 14 / 14 / 14 100 Famed Deathwatch Deathwatch Core Rulebook 164 +Astartes Tactical Dreadnaught-Terminator Armour Helm Power 14 / 0 / 0 / 0 100 Famed Deathwatch Deathwatch Core Rulebook 164 +Astartes Terminator Armour Power 14 all 100 Famed Deathwatch Deathwatch Core Rulebook 164 +Auran Golden Saurian Scale Armour Primitive 6 all 0 Any Deathwatch The Emperor Protects 48 +Auran Saurian Scale Armour Primitive 4 all 0 Any Deathwatch The Emperor Protects 46 +Chaos Armour Plating Other 6 all 0 Any Deathwatch Mark of the Xenos 91 +Chaos Bones of Subersion Other 9 all 0 Any Deathwatch Mark of the Xenos 86 +Chaos Flak Robes and Brazen Carapace Carapace 5 / 5 / 6 / 4 0 Any Deathwatch Deathwatch Core Rulebook 364 +Chaos Slinnar Obsidian Shell Other 8 all 0 Any Deathwatch Mark of the Xenos 92 +Chaos Warded Adamantine Shell Other 9 all 0 Any Deathwatch Mark of the Xenos 95 +CM Power Armour Power 8 / 8 / 10 / 8 0 Any Deathwatch Mark of the Xenos 114 +Daemon Iron Hide Other 13 all 0 Any Deathwatch Mark of the Xenos 101 +Deathwatch Scout Armor Carapace 0 / 6 / 6 / 6 0 Any Deathwatch Rites of Battle 142 +Diagnostor Helmet Power 8 / 0 / 0 / 0 15 Any Deathwatch Deathwatch Core Rulebook 165 +Dmn Armour of Chaos Power 12 all 0 Any Deathwatch Deathwatch Core Rulebook 362 +Dmn Brazen Chaos Armour Other 12 all 0 Any Deathwatch The Emperor Protects 136 +Fenris Pattern Wolf Helm Power 8 / 0 / 0 / 0 0 Respected Deathwatch Rites of Battle 143 +Nec Metallic Exoskeleton Necron 8 all 0 Any Deathwatch The Emperor Protects 94 +Nec Metallic Shell Necron 10 all 0 Any Deathwatch The Emperor Protects 94 +Obliterator Warped Mechnical Flesh Other 12 all 0 Any Deathwatch Mark of the Xenos 120 +Ork Eavy Armour Ork 6 / 4 / 6 / 4 0 Any Deathwatch Mark of the Xenos 57 +Ork Flak Armour Ork 0 / 0 / 2 / 0 0 Any Deathwatch Mark of the Xenos 57 +Ork Mega Armour Ork 6 / 10 / 14 / 10 0 Any Deathwatch Mark of the Xenos 57 +Rad Corrupted Mechnical Flesh Other 7 all 0 Any Deathwatch The Emperor Protects 91 +Scales Primitive 2 all 0 Any Deathwatch The Emperor Protects 50 +Scaly Hide Primitive 4 all 0 Any Deathwatch The Emperor Protects 46 +Skull Helm Power 9 / 0 / 0 / 0 0 Any Deathwatch Rites of Battle 143 +Tau Fire Warrior Armour Carapace 6 all 0 Any Deathwatch Deathwatch Core Rulebook 368 +Tau Gun Drone Armour Natural 5 all 0 Any Deathwatch Deathwatch Core Rulebook 367 +Tau Pathfinder Armour Carapace 7 / 0 / 7 / 0 0 Any Deathwatch Mark of the Xenos 25 +Tau XV-8 Crisis Battlesuit Power 9 all 0 Any Deathwatch Mark of the Xenos 9 +Tau XV-88 Broadside Battlesuit Power 12 all 0 Any Deathwatch Mark of the Xenos 6 +Tau XV15 Stealth Suit (Limbs) Power 0 / 7 / 0 / 7 0 Any Deathwatch Deathwatch Core Rulebook 367 +Tau XV15 Stealth Suit (Top) Power 8 / 0 / 8 / 0 0 Any Deathwatch Deathwatch Core Rulebook 367 +Tau XV8 Crisis Suit Power 9 all 0 Any Deathwatch Deathwatch Core Rulebook 366 +Tyr Bonded Exoskeleton Natural 10 all 0 Any Deathwatch Deathwatch Core Rulebook 370 +Tyr Chitinous Carapace Natural 3 all 0 Any Deathwatch Deathwatch Core Rulebook 371 +Tyr Exoskeleton Natural 2 all 0 Any Deathwatch Mark of the Xenos 43 +Tyr Hardened Carapace Natural 6 all 0 Any Deathwatch Mark of the Xenos 40 +Tyr Light Reinforced Chitin Natural 4 all 0 Any Deathwatch Mark of the Xenos 39 +Tyr Medium Reinforced Chitin Natural 6 all 0 Any Deathwatch Mark of the Xenos 37 +Tyr Reinforced Chitin Natural 8 all 0 Any Deathwatch Deathwatch Core Rulebook 371 +Tyr Thickened Scales Natural 10 all 0 Any Deathwatch Mark of the Xenos 46 +Vespid Chitin Natural 3 all 0 Any Deathwatch Mark of the Xenos 26 +Xenos Hardened Hide Natural 6 all 0 Any Deathwatch Mark of the Xenos 66 +Xenos Scaly Hide Natural 3 all 0 Any Deathwatch Mark of the Xenos 63 +Xenos Slimey Hide Natural 2 all 0 Any Deathwatch Mark of the Xenos 68 +Xenos Thick Scales Natural 5 all 0 Any Deathwatch Mark of the Xenos 65`; + +// Parse armor data into structured objects +const parseArmor = (data) => { + const lines = data.split('\n').filter(line => line.trim()); + return lines.map(line => { + const cols = line.split('\t').map(col => col.trim()); + if (cols.length >= 8) { + return { + name: cols[0], + type: cols[1], + protection: cols[2], + cost: parseInt(cols[3]) || 0, + availability: cols[4], + gameSetting: cols[5], + book: cols[6], + pageNo: cols[7] + }; + } + return null; + }).filter(a => a !== null); +}; + +// Map armor types to categories +const typeToCategory = (armorType) => { + const lowerType = armorType.toLowerCase(); + if (lowerType.includes('power')) return 'Power Armor'; + if (lowerType.includes('carapace')) return 'Carapace Armor'; + if (lowerType.includes('primitive')) return 'Primitive Armor'; + if (lowerType.includes('natural')) return 'Natural Armor'; + if (lowerType.includes('shield')) return 'Shields'; + if (lowerType === 'necron') return 'Xenos Armor'; + if (lowerType === 'ork') return 'Xenos Armor'; + return 'Other Armor'; +}; + +// Parse protection values into a structured format +const parseProtection = (protection) => { + if (protection.includes('all')) { + const value = parseInt(protection.split(' ')[0]); + return { + head: value, + arms: value, + body: value, + legs: value + }; + } + + const parts = protection.split('/').map(p => p.trim()); + return { + head: parseInt(parts[0]) || 0, + arms: parseInt(parts[1]) || 0, + body: parseInt(parts[2]) || 0, + legs: parseInt(parts[3]) || 0 + }; +}; + +// Map availability levels to renown levels (using the same mapping as weapons) +const availabilityToRenown = { + 'Any': 'None', + 'Common': 'None', + 'Scarce': 'None', + 'Rare': 'Respected', + 'Very Rare': 'Distinguished', + 'Extremely Rare': 'Hero', + 'Near Unique': 'Hero', + 'Unique': 'Legend', + 'Distinguished': 'Distinguished', + 'Famed': 'Hero', + 'Respected': 'Respected', + 'Hero': 'Hero' +}; + +// Process armor into shop format +const processArmor = () => { + const armor = parseArmor(armorData); + // Use a Map to deduplicate items (keeping only the Living Errata version when available) + const uniqueArmor = new Map(); + + armor.forEach(item => { + const key = item.name; + const existing = uniqueArmor.get(key); + + // Prefer Living Errata over Core Rulebook + if (!existing || item.book.includes('Living Errata')) { + uniqueArmor.set(key, { + name: item.name, + req: parseInt(item.cost), + renown: availabilityToRenown[item.availability] || 'Distinguished', + category: typeToCategory(item.type), + stats: { + type: item.type, + protection: parseProtection(item.protection), + source: `${item.book} p${item.pageNo}` + } + }); + } + }); + + return Array.from(uniqueArmor.values()); +}; + +// Process armor and organize by category +const armor = processArmor(); +const categorizedArmor = { + powerArmor: armor.filter(item => item.category === 'Power Armor'), + carapaceArmor: armor.filter(item => item.category === 'Carapace Armor'), + primitiveArmor: armor.filter(item => item.category === 'Primitive Armor'), + naturalArmor: armor.filter(item => item.category === 'Natural Armor'), + xenosArmor: armor.filter(item => item.category === 'Xenos Armor'), + shields: armor.filter(item => item.category === 'Shields'), + otherArmor: armor.filter(item => item.category === 'Other Armor') +}; + +if (require.main === module) { + const fs = require('fs'); + const path = require('path'); + const output = path.resolve('public/deathwatch-armor.json'); + fs.writeFileSync(output, JSON.stringify(categorizedArmor, null, 2), 'utf8'); + console.log('Wrote armor data to:', output); +} + +module.exports = { categorizedArmor, processArmor }; diff --git a/scripts/parse-weapons.js b/scripts/parse-weapons.js new file mode 100644 index 0000000..38c0f4c --- /dev/null +++ b/scripts/parse-weapons.js @@ -0,0 +1,128 @@ +const weaponsData = ` +Angelus Bolter Basic - Bolt Range 50m; S/3/-; 1d10+9 X; Pen 6; Clip 36; Reload 2 Full; Tearing 25 Respected Deathwatch First Founding 97 +Argrax Clawed Hooves Natural - Primitive 1d10 I; Pen 0; 0 Any Deathwatch The Emperor Protects 46 +Armour of the Remorseless Crusader (Forearm Sword) One-Handed Melee - Primitive 1d10+8 R; Pen 6; +10 to hit; Sanctified 70 Hero Deathwatch Deathwatch Core Rulebook 167 +Artificer Omnissian Axe One-Handed Melee - Power 1d10+9 E; Pen 7; Power Field, Unbalanced 25 Distinguished Deathwatch First Founding 99 +Astartes Anti-Plant Grenade Thrown - Grenade 3d10 E; Pen 0; Blast (3) 18 Any Deathwatch Living Errata v1.1 136 +Astartes Armoursbane Missile Launcher Heavy - Launch Range 200m; S/-/-; 0d0 S; Pen 0; Clip 1; Reload 2 Full; Two handed; 15 Any Deathwatch Living Errata v1.1 137 +Astartes Assault Cannon Mounted - Solid Projectile Range 150m; -/-/10; 2d10+12 I; Pen 6; Clip 200; Reload 3 Full; Tearing, Devastating (1) 30 Famed Deathwatch Living Errata v1.1 149 +Astartes Assault Shotgun Basic - Solid Projectile Range 30m; S/3/5; 1d10+10 I; Pen 4; Clip 20; Reload 1 Full; Two handed; Reliable, Scatter 7 Any Deathwatch Living Errata v1.1 136 +Astartes Blind Grenade Thrown - Grenade 0d0 S; Pen 0; Smoke (10) 10 Any Deathwatch Living Errata v1.1 150 +Astartes Bolt Pistol Pistol - Bolt Range 30m; S/2/-; 1d10+9 X; Pen 4; Clip 14; Reload 1 Full; Tearing 5 Any Deathwatch Living Errata v1.1 146 +Astartes Bolter-Godwyn Basic - Bolt Range 100m; S/3/-; 1d10+9 X; Pen 4; Clip 28; Reload 1 Full; Two handed; Tearing 5 Any Deathwatch Living Errata v1.1 146 +Astartes Boltgun-Stalker Basic - Bolt Range 200m; S/-/-; 1d10+9 X; Pen 4; Clip 24; Reload 1 Full; Two handed; Accurate, Tearing 15 Respected Deathwatch Living Errata v1.1 146 +Astartes Chainfist One-Handed Melee - Power 2d10+"2*SB" E; Pen 10; Power Field, Tearing, Unwieldy 40 Famed Deathwatch Living Errata v1.1 154 +Astartes Chainsword One-Handed Melee - Chain 1d10+3 R; Pen 3; Balanced, Tearing 5 Any Deathwatch Living Errata v1.1 153 +Astartes Combat Knife One-Handed Melee - Primitive 1d10 R; Pen 2; 3 Any Deathwatch Living Errata v1.1 155 +Astartes Combat Shield One-Handed Melee - Primitive 1d5+1 I; Pen 0; Balanced 20 Distinguished Deathwatch Living Errata v1.1 166 +Astartes Combi-Weapon Basic - Bolt Range 100m; S/4/-; 1d10+9 X; Pen 4; Clip 28; Reload 1 Full; Two handed; Tearing 15 Respected Deathwatch Living Errata v1.1 146 +Astartes Conversion Beamer Heavy - Exotic Range 100m; S/-/-; 0d0 E; Pen 0; Clip 4; Reload 2 Full; Two handed; 40 Hero Deathwatch Living Errata v1.1 152 +Astartes Cyclone Missile Launcher Mounted - Launch Range 300m; S/2/-; 0d0 S; Pen 0; Clip 12; Reload 3 Full; 25 Famed Deathwatch Living Errata v1.1 152 +Astartes Digital Flamer Pistol - Exotic Range 4m; S/-/-; 1d10+9 E; Pen 4; Clip 1; Reload 1d5 Hours; Flame, Spray 35 Famed Deathwatch Living Errata v1.1 153 +Astartes Chainfist One-Handed Melee - Power 2d10+"2*SB" E; Pen 10; Power Field, Tearing, Unwieldy 40 Famed Deathwatch Living Errata v1.1 154 +Astartes Chainfist One-Handed Melee - Power 2d10+"2*SB" E; Pen 10; Power Field, Tearing 40 Famed Deathwatch Deathwatch Core Rulebook 154 +Astartes Chainsword One-Handed Melee - Chain 1d10+3 R; Pen 4; Balanced, Tearing 5 Any Deathwatch Deathwatch Core Rulebook 153 +Astartes Chainsword One-Handed Melee - Chain 1d10+3 R; Pen 3; Balanced, Tearing 5 Any Deathwatch Living Errata v1.1 153 +Astartes Combat Knife One-Handed Melee - Primitive 1d10 R; Pen 2; 3 Any Deathwatch Living Errata v1.1 155 +Astartes Combat Knife One-Handed Melee - Primitive 1d10+2 R; Pen 2; 3 Any Deathwatch Deathwatch Core Rulebook 155 +Astartes Combat Shield One-Handed Melee - Primitive 1d5+1 I; Pen 0; Balanced 20 Distinguished Deathwatch Living Errata v1.1 166 +Astartes Combat Shield One-Handed Melee - Primitive 1d5+1 I; Pen 0; Balanced 20 Distinguished Deathwatch Deathwatch Core Rulebook 166 +Astartes Combi-Weapon Basic - Bolt Range 100m; S/2/4; 2d10+5 X; Pen 5; Clip 28; Reload 1 Full; Two handed; Tearing 15 Respected Deathwatch Deathwatch Core Rulebook 146 +`; + +// Parse weapon data into structured objects +const parseWeapons = (data) => { + const lines = data.split('\n').filter(line => line.trim()); + return lines.map(line => { + const cols = line.split('\t').map(col => col.trim()); + if (cols.length >= 8) { + return { + name: cols[0], + class: cols[1], + damage: cols[2], + cost: parseInt(cols[3]) || 0, + availability: cols[4], + gameSetting: cols[5], + book: cols[6], + pageNo: cols[7] + }; + } + return null; + }).filter(w => w !== null); +}; + +// Map availability levels to renown levels +const availabilityToRenown = { + 'Any': 'None', + 'Common': 'None', + 'Scarce': 'None', + 'Rare': 'Respected', + 'Very Rare': 'Distinguished', + 'Extremely Rare': 'Hero', + 'Near Unique': 'Hero', + 'Unique': 'Legend', + 'Distinguished': 'Distinguished', + 'Famed': 'Hero', + 'Respected': 'Respected', + 'Hero': 'Hero' +}; + +// Map weapon classes to shop categories +const classToCategory = (itemClass) => { + const lowerClass = itemClass.toLowerCase(); + if (lowerClass.includes('melee')) return 'Melee Weapon'; + if (lowerClass.includes('pistol')) return 'Ranged Weapon'; + if (lowerClass.includes('basic') || + lowerClass.includes('heavy') || + lowerClass.includes('mounted')) return 'Ranged Weapon'; + if (lowerClass.includes('thrown')) return 'Grenade'; + return 'Other'; +}; + +// Process weapons into shop format +const processWeapons = () => { + const weapons = parseWeapons(weaponsData); + // Use a Map to deduplicate items (keeping only the Living Errata version when available) + const uniqueWeapons = new Map(); + + weapons.forEach(weapon => { + const key = weapon.name; + const existing = uniqueWeapons.get(key); + + // Prefer Living Errata over Core Rulebook + if (!existing || weapon.book.includes('Living Errata')) { + uniqueWeapons.set(key, { + name: weapon.name, + req: parseInt(weapon.cost), + renown: availabilityToRenown[weapon.availability] || 'Distinguished', + category: classToCategory(weapon.class), + stats: { + damage: weapon.damage, + class: weapon.class, + source: `${weapon.book} p${weapon.pageNo}` + } + }); + } + }); + + return Array.from(uniqueWeapons.values()); +}; + +// Process weapons and organize by category +const weapons = processWeapons(); +const categorizedWeapons = { + rangedWeapons: weapons.filter(item => item.category === 'Ranged Weapon'), + meleeWeapons: weapons.filter(item => item.category === 'Melee Weapon'), + grenades: weapons.filter(item => item.category === 'Grenade'), + other: weapons.filter(item => item.category === 'Other') +}; + +if (require.main === module) { + const fs = require('fs'); + const path = require('path'); + const output = path.resolve('public/deathwatch-weapons.json'); + fs.writeFileSync(output, JSON.stringify(categorizedWeapons, null, 2), 'utf8'); + console.log('Wrote weapon data to:', output); +} + +module.exports = { categorizedWeapons, processWeapons }; diff --git a/src/components/PlayerTab.jsx b/src/components/PlayerTab.jsx index 75b6e71..ad16490 100755 --- a/src/components/PlayerTab.jsx +++ b/src/components/PlayerTab.jsx @@ -5,6 +5,8 @@ import logger, { debug, info, warn, logApiCall, logApiError, logUserAction } fro const STORAGE_SHOP_AUTHED = 'dw:shop:authedPlayer'; const STORAGE_SHOP_PLAYERS = 'dw:shop:players:v1'; +const RANK_ORDER = ['None','Respected','Distinguished','Famed','Hero']; + const GM_PASSWORD = 'bongo'; function safeGet(key) { @@ -432,6 +434,27 @@ function PlayerTab({ ) } + + function GmSetXP({ name, onSet }) { + const [xp, setXp] = useState('') + return ( +