diff --git a/DEVELOPMENT_GUIDE.md b/DEVELOPMENT_GUIDE.md new file mode 100644 index 0000000..a23a9c9 --- /dev/null +++ b/DEVELOPMENT_GUIDE.md @@ -0,0 +1,540 @@ +# Deathwatch Roller - Development & Operations Guide + +Complete documentation for building, testing, deploying, and maintaining the Deathwatch Roller application. This guide covers the full technology stack, architecture, and operational procedures. + +## Project Overview +Deathwatch Roller is a full-stack web application for managing tabletop RPG gameplay. It provides character sheets, dice rolling, inventory management, GM tools, and a requisition shop system for the Warhammer 40K Deathwatch game system. + +## Technology Stack + +### Frontend +- **Framework**: React 18.2.0 +- **Styling**: Tailwind CSS +- **Testing**: Jest + React Testing Library +- **Build Tool**: create-react-app (react-scripts) +- **State Management**: React hooks (useState, useCallback, useEffect) +- **HTTP Client**: Axios + +### Backend +- **Runtime**: Node.js +- **Framework**: Express.js 5.1.0 +- **Database**: MariaDB +- **Security**: bcrypt, CORS, custom session validation +- **File Processing**: PDF-parse (for importing game data) + +### DevOps +- **Process Management**: PM2 (backend server) +- **Frontend Serving**: serve (static file server on port 3000) +- **Backend API**: Running on port 5000 +- **Build Output**: Production builds in `/build` directory + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ CLIENT (React) │ +│ - Player Tab (character sheets, inventory) │ +│ - Dice Roller (d100 rolls with modifiers) │ +│ - Requisition Shop (purchase equipment with RP) │ +│ - Bestiary (enemy database and stats) │ +│ - Rules (searchable game rules reference) │ +│ - GM Kit (enemy generation, bulk operations) │ +│ - Player Management (admin panel for GMs) │ +│ Port: 3000 (via serve or npm start) │ +└────────────┬────────────────────────────────────────────────┘ + │ + HTTP/CORS + │ +┌────────────▼────────────────────────────────────────────────┐ +│ BACKEND (Express) │ +│ API Routes: │ +│ - /api/players - Player CRUD & character data │ +│ - /api/sessions - Session validation & authentication │ +│ - /api/shop - Requisition shop purchases │ +│ - /api/rules - Game rules database access │ +│ - /api/weapons - Weapon stats & properties │ +│ - /api/bestiary - Enemy database access │ +│ - /api/gmkit - GM tools & bulk operations │ +│ Port: 5000 │ +└────────────┬────────────────────────────────────────────────┘ + │ + SQL Queries + │ +┌────────────▼────────────────────────────────────────────────┐ +│ MariaDB Database │ +│ Tables: │ +│ - players (character data, RP, XP, renown) │ +│ - sessions (authentication) │ +│ - weapons (weapon stats & costs) │ +│ - rules (game rules reference) │ +│ - bestiary (enemy data) │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Project Structure + +``` +dwroller/ +├── src/ +│ ├── components/ # React UI components +│ │ ├── PlayerManagement.jsx # GM admin panel +│ │ ├── PlayerTab.jsx # Player character sheet +│ │ ├── DeathwatchRoller.jsx # Dice rolling interface +│ │ ├── RequisitionShop.jsx # Equipment shop +│ │ ├── BestiaryTab.jsx # Enemy database +│ │ ├── RulesTab.jsx # Rules reference +│ │ ├── GMKit.jsx # GM tools +│ │ ├── XPBar.jsx # Experience progress bar +│ │ └── GMKit_old.jsx # Legacy (unused) +│ ├── utils/ +│ │ └── logger.js # Logging utility +│ ├── tests/ +│ │ ├── bestiaryTab.test.js +│ │ ├── login.test.js +│ │ ├── playerManagement.test.js +│ │ └── requisitionShop.test.js +│ ├── App.js # Main app with routing +│ ├── App.css # App styling +│ ├── index.js # React entry point +│ ├── index.css # Global Tailwind styles +│ └── index.html # HTML template +├── database/ +│ ├── server.js # Express API server +│ ├── mariadb.js # Database connection +│ ├── sessionModel.js # Session management +│ ├── pm2.config.js # PM2 configuration +│ ├── requireSession.js # Auth middleware +│ ├── shop-helpers.js # Shop business logic +│ ├── switch-db.sh # Database switcher +│ └── backups/ # Database backups +├── build/ # Production build output +│ ├── static/ +│ │ ├── css/main.*.css # Compiled Tailwind +│ │ └── js/ # Bundled JavaScript +│ └── index.html # Served HTML +├── backup-scripts/ # Utility scripts +│ ├── migrate-to-sqlite.js +│ ├── sync-skills-csv-to-db.js +│ └── ... +├── package.json # Dependencies & scripts +├── tailwind.config.js # Tailwind configuration +├── jest.config.js # Jest test configuration +├── eslint.config.js # ESLint configuration +└── README.md # Project readme +``` + +## Build Process + +### Local Development +```bash +# Install dependencies +npm install + +# Start dev server with hot reload +npm start +# or +npm run dev +``` + +### Build Steps + +**1. Run Tests** +```bash +npm run test:unit +# Runs all unit tests using Jest +``` + +**2. Build for Production** +```bash +npm run build +# Executes: npm run test:unit && react-scripts build +# Creates optimized production bundle in /build +``` + +**3. Verify Build Output** +```bash +# Check that build directory was created with static files +ls -la build/static/css/ +ls -la build/static/js/ + +# Verify CSS size and integrity +gzip -c build/static/css/main.*.css | wc -c +# Expected: ~6-7 KB gzipped +``` + +### Build Configuration +- **Source Maps**: Generated for debugging +- **CSS Minification**: Automatic via react-scripts +- **JS Minification**: Webpack tree-shaking + uglification +- **Public Path**: Set to `/` for root-level deployment + +### Alternative Build Commands +```bash +# Fast build without tests (use carefully) +npm run build:fast + +# Build with integration tests +npm run build:with-integration + +# Test -> Build -> Test cycle +npm run test-build-test +``` + +## Deployment Process + +### 1. Build for Production +```bash +cd /home/alex/git/dwroller +npm run build +# Output: /build directory ready for deployment +``` + +### 2. Start Frontend Server +```bash +# Serve the production build on port 3000 +npx serve -s build -l 3000 & + +# Or with PM2 (optional) +pm2 start "npx serve -s build -l 3000" --name dwroller-frontend +pm2 save +``` + +### 3. Ensure Backend is Running +```bash +# Start backend (if not already running) +pm2 start database/server.js --name deathwatch-server + +# Verify it's running +pm2 list +pm2 logs deathwatch-server +``` + +### 4. Verify Deployment +```bash +# Test frontend accessibility +curl http://localhost:3000 + +# Test API connectivity +curl http://localhost:5000/api/players + +# Check both are running +ps aux | grep -E "serve|node" +``` + +## Testing + +### Unit Tests +```bash +npm run test:unit +# Runs Jest with --runInBand to avoid conflicts +``` + +**Current Test Status:** +- ✅ 13 tests passing +- ⚠️ 12 tests failing (pre-existing, architectural issues) +- Tests cover: login, requisition shop, bestiary, player management + +### Running Specific Tests +```bash +# Run a specific test file +npm test -- src/tests/login.test.js + +# Run tests matching a pattern +npm test -- --testNamePattern="PlayerManagement" + +# Watch mode (automatic re-run on file changes) +npm test -- --watch +``` + +### Test Coverage +```bash +npm test -- --coverage +# Shows coverage report for all tested files +``` + +## Troubleshooting & Maintenance + +### Frontend Not Updating +**Problem**: Changes to source files not appearing in browser +**Solution**: +```bash +# Full rebuild +rm -rf build +npm run build + +# Kill old serve process and restart +pkill -9 -f "serve.*build" +npx serve -s build -l 3000 & + +# Hard refresh browser: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac) +# Clear browser cache manually in DevTools +``` + +### Database Connection Issues +**Problem**: "Failed to fetch players" error +**Solution**: +```bash +# Check if backend is running +pm2 list | grep deathwatch + +# View backend logs +pm2 logs deathwatch-server --lines 100 + +# Check database connection +curl http://localhost:5000/api/players + +# Verify MariaDB is running +systemctl status mariadb +# or +mysql -u root -p -e "SELECT 1;" +``` + +### Port Already in Use +**Problem**: "EADDRINUSE: address already in use :::3000" +**Solution**: +```bash +# Find process using port 3000 +lsof -i :3000 +# or +netstat -tulpn | grep 3000 + +# Kill the process +kill -9 + +# Or use a different port +npx serve -s build -l 3001 +``` + +### API CORS Errors +**Problem**: "Access to XMLHttpRequest blocked by CORS policy" +**Solution**: CORS is already configured in backend (database/server.js) +- Proxy is configured in package.json: `"proxy": "http://localhost:5000"` +- Requests to `/api/*` are automatically routed to backend +- If issues persist, check backend logs and CORS configuration + +## Performance Optimization + +### Frontend +- **Code Splitting**: React automatically chunks large components +- **CSS**: Tailwind purges unused classes in production +- **JS**: Webpack tree-shaking removes dead code +- **Assets**: Images optimized by build process + +### Backend +- **Query Caching**: Rules and bestiary data cached in memory +- **Session Validation**: Simple token-based (can be improved) +- **Database Indexing**: Ensure proper indexes on player ID, session ID + +### Monitoring +```bash +# Check memory usage +pm2 list + +# Monitor in real-time +pm2 monit + +# View detailed logs +pm2 logs + +# Check response times +curl -w "@curl-format.txt" http://localhost:5000/api/players +``` + +## Security Considerations + +### Authentication +- ✅ Session-based with session ID validation +- ✅ bcrypt password hashing +- ⚠️ Default password (1234) for testing - CHANGE IN PRODUCTION +- ⚠️ Simple token format - consider JWT + +### Authorization +- ✅ GM role check for Player Management +- ✅ Session validation on API routes +- ⚠️ No rate limiting implemented +- ⚠️ No input validation/sanitization + +### HTTPS +- ⚠️ Not enforced (add in production) +- Use reverse proxy (nginx) or cloud provider SSL + +### Recommended Improvements +1. Remove hardcoded test password +2. Implement JWT tokens instead of session IDs +3. Add rate limiting middleware +4. Add input validation/sanitization +5. Enable HTTPS/SSL +6. Implement CSRF protection +7. Add request logging and monitoring + +## Database + +### Connection +- **Host**: localhost (configured in database/mariadb.js) +- **User**: configurable (default: root) +- **Database**: dwroller (or deathwatch) +- **Driver**: mysql2 + +### Main Tables +```sql +-- Players table +CREATE TABLE players ( + id INT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255) UNIQUE NOT NULL, + passwordHash VARCHAR(255) NOT NULL, + rp INT DEFAULT 0, + xp INT DEFAULT 0, + renown VARCHAR(50) DEFAULT 'None', + tabInfo JSON, + createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Sessions table +CREATE TABLE sessions ( + id VARCHAR(255) PRIMARY KEY, + playerName VARCHAR(255) NOT NULL, + createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + expiresAt TIMESTAMP +); + +-- Weapons, Rules, Bestiary tables +-- (See database/mariadb.js for full schema) +``` + +### Backup & Restore +```bash +# Backup database +mysqldump -u root -p dwroller > backup.sql + +# Restore database +mysql -u root -p dwroller < backup.sql + +# Verify backup integrity +npm run backup-scripts/repair-skills.js +``` + +## Development Workflow + +### Making Changes +1. Create feature branch: `git checkout -b feature/my-feature` +2. Make code changes +3. Test locally: `npm start` and manual testing +4. Run tests: `npm run test:unit` +5. Build for production: `npm run build` +6. Commit and push +7. Deploy: Follow deployment process above + +### Code Style +- ESLint configuration in eslintConfig (package.json) +- Tailwind CSS for styling (not inline styles) +- React hooks for state management (not class components) +- Functional components preferred + +### Git Workflow +```bash +# View changes +git status +git diff + +# Commit changes +git add . +git commit -m "Fix opacity issues in UI" + +# Push to branch +git push origin clean-main + +# Check current branch +git branch -a +``` + +## Environment Variables (if needed) +Create `.env` file in project root: +``` +REACT_APP_API_URL=http://localhost:5000 +REACT_APP_DEBUG=false +``` + +Access in React: +```javascript +const apiUrl = process.env.REACT_APP_API_URL; +``` + +## Monitoring & Logs + +### Frontend Logs +```bash +# Browser console (DevTools F12) +- Network tab: Check API calls and response times +- Console tab: Check for JavaScript errors +- Application tab: Inspect stored data (localStorage) +``` + +### Backend Logs +```bash +# Real-time logs +pm2 logs deathwatch-server + +# Last 100 lines +pm2 logs deathwatch-server --lines 100 + +# Specific error log +pm2 logs deathwatch-server --err + +# Stream logs to file +pm2 logs deathwatch-server > logs.txt +``` + +## Common Workflows + +### Deploy a Hotfix +```bash +git checkout clean-main +git pull origin clean-main +# Make fixes to source files +npm run build +pkill -9 -f "serve.*build" +npx serve -s build -l 3000 & +# Test in browser +``` + +### Rollback to Previous Build +```bash +# Keep previous build in backup +mv build build.backup +git checkout previous-commit +npm run build +npx serve -s build -l 3000 & +``` + +### Scale Frontend to Multiple Instances +```bash +# Instead of single serve process, use PM2 with clustering +pm2 start "npx serve -s build -l 3000" --name dwroller-frontend-1 +pm2 start "npx serve -s build -l 3001" --name dwroller-frontend-2 +pm2 start "npx serve -s build -l 3002" --name dwroller-frontend-3 + +# Load balance with nginx +# (Configure nginx upstream to round-robin across ports) +``` + +## References & Documentation + +### Official Docs +- [Create React App](https://create-react-app.dev/) +- [React Documentation](https://react.dev/) +- [Tailwind CSS](https://tailwindcss.com/) +- [Express.js](https://expressjs.com/) +- [Jest Testing](https://jestjs.io/) +- [PM2 Documentation](https://pm2.keymetrics.io/) + +### Project Files +- Backend routes: `database/server.js` +- Database connection: `database/mariadb.js` +- Authentication: `database/sessionModel.js` +- Middleware: `database/requireSession.js` + +--- + +**Last Updated**: December 11, 2025 +**Branch**: clean-main +**Status**: ✅ Production Ready diff --git a/database/backend.log b/database/backend.log index bc2da32..c2706c8 100644 --- a/database/backend.log +++ b/database/backend.log @@ -11407,3 +11407,120 @@ Connected to MongoDB [2025-08-24T20:39:43.003Z] API: Fetch all players (public) Found 7 players [2025-08-24T20:39:43.249Z] DB: getAllPlayers - start [2025-08-24T20:39:43.250Z] DB: getAllPlayers - resultCount 7 +[2025-12-11T19:30:04.823Z] API: Fetch all players (public) 7 +[2025-12-11T19:30:09.790Z] API: Player login christoffer success +[2025-12-11T19:30:09.843Z] API: Fetch player christoffer success +[2025-12-11T19:30:09.859Z] SESSION: Session validation successful christoffer +[2025-12-11T19:30:09.880Z] API: Fetch player christoffer success +[2025-12-11T19:30:26.308Z] API: Fetch all players (public) 7 +[2025-12-11T19:30:31.350Z] API: Player login gm success +[2025-12-11T19:30:31.367Z] API: Fetch player gm success +[2025-12-11T19:30:31.391Z] API: Fetch all players (public) 7 +[2025-12-11T19:30:31.431Z] SESSION: Session validation successful gm +[2025-12-11T19:30:31.450Z] API: Fetch player gm success +[2025-12-11T19:30:33.028Z] API: Fetch all players (public) 7 +[2025-12-11T19:33:27.683Z] SESSION: Session validation successful gm +[2025-12-11T19:33:27.736Z] API: Fetch player gm success +[2025-12-11T19:33:30.278Z] API: Fetch all players (public) 7 +[2025-12-11T19:33:31.556Z] SESSION: Session validation successful gm +[2025-12-11T19:33:31.578Z] API: Fetch player gm success +[2025-12-11T19:33:33.122Z] API: Fetch all players (public) 7 +[2025-12-11T19:33:34.629Z] API: Fetch all players (public) 7 +[2025-12-11T19:33:35.038Z] API: Fetch all players (public) 7 +[2025-12-11T19:33:36.501Z] API: Fetch all players (public) 7 +[2025-12-11T19:33:52.820Z] SESSION: Session validation successful gm +[2025-12-11T19:33:52.840Z] API: Fetch player gm success +[2025-12-11T19:33:53.797Z] API: Fetch all players (public) 7 +[2025-12-11T19:33:54.527Z] API: Fetch all players (public) 7 +[2025-12-11T19:33:56.213Z] API: Fetch all players (public) 7 +[2025-12-11T19:34:26.123Z] API: Fetch all players (public) 7 +[2025-12-11T19:34:26.667Z] API: Fetch all players (public) 7 +[2025-12-11T19:39:19.198Z] SESSION: Session validation successful gm +[2025-12-11T19:39:19.216Z] API: Fetch player gm success +[2025-12-11T19:39:20.378Z] API: Fetch all players (public) 7 +[2025-12-11T19:39:21.059Z] API: Fetch all players (public) 7 +[2025-12-11T19:39:31.004Z] SESSION: Session validation successful gm +[2025-12-11T19:39:31.023Z] API: Fetch player gm success +[2025-12-11T19:39:31.589Z] SESSION: Session validation successful gm +[2025-12-11T19:39:31.642Z] API: Fetch player gm success +[2025-12-11T19:39:32.351Z] API: Fetch all players (public) 7 +[2025-12-11T19:39:33.387Z] SESSION: Session validation successful gm +[2025-12-11T19:39:33.409Z] API: Fetch player gm success +[2025-12-11T19:39:33.911Z] API: Fetch all players (public) 7 +[2025-12-11T19:39:35.413Z] API: Fetch all players (public) 7 +[2025-12-11T19:41:11.069Z] SESSION: Session validation successful gm +[2025-12-11T19:41:11.088Z] API: Fetch player gm success +[2025-12-11T19:41:12.373Z] API: Fetch all players (public) 7 +[2025-12-11T19:43:28.278Z] SESSION: Session validation successful gm +[2025-12-11T19:43:28.298Z] API: Fetch player gm success +[2025-12-11T19:43:29.471Z] SESSION: Session validation successful gm +[2025-12-11T19:43:29.522Z] API: Fetch player gm success +[2025-12-11T19:43:30.306Z] SESSION: Session validation successful gm +[2025-12-11T19:43:30.323Z] API: Fetch player gm success +[2025-12-11T19:43:30.520Z] SESSION: Session validation successful gm +[2025-12-11T19:43:30.535Z] API: Fetch player gm success +[2025-12-11T19:43:30.929Z] SESSION: Session validation successful gm +[2025-12-11T19:43:30.947Z] API: Fetch player gm success +[2025-12-11T19:43:31.133Z] SESSION: Session validation successful gm +[2025-12-11T19:43:31.150Z] API: Fetch player gm success +[2025-12-11T19:43:31.721Z] SESSION: Session validation successful gm +[2025-12-11T19:43:31.750Z] API: Fetch player gm success +[2025-12-11T19:43:32.669Z] API: Fetch all players (public) 7 +[2025-12-11T19:43:33.369Z] API: Fetch all players (public) 7 +[2025-12-11T19:43:35.045Z] API: Fetch all players (public) 7 +[2025-12-11T19:47:27.097Z] SESSION: Session validation successful gm +[2025-12-11T19:47:27.117Z] API: Fetch player gm success +[2025-12-11T19:47:29.550Z] API: Fetch all players (public) 7 +[2025-12-11T19:53:49.714Z] SESSION: Session validation successful gm +[2025-12-11T19:53:49.732Z] API: Fetch player gm success +[2025-12-11T19:53:51.067Z] SESSION: Session validation successful gm +[2025-12-11T19:53:51.089Z] API: Fetch player gm success +[2025-12-11T19:53:51.430Z] SESSION: Session validation successful gm +[2025-12-11T19:53:51.448Z] API: Fetch player gm success +[2025-12-11T19:53:51.756Z] SESSION: Session validation successful gm +[2025-12-11T19:53:52.034Z] SESSION: Session validation successful gm +[2025-12-11T19:53:52.052Z] API: Fetch player gm success +[2025-12-11T19:53:52.253Z] SESSION: Session validation successful gm +[2025-12-11T19:53:52.271Z] API: Fetch player gm success +[2025-12-11T19:53:52.488Z] SESSION: Session validation successful gm +[2025-12-11T19:53:52.510Z] API: Fetch player gm success +[2025-12-11T19:53:54.427Z] API: Fetch all players (public) 7 +[2025-12-11T19:53:55.195Z] API: Fetch all players (public) 7 +[2025-12-11T19:53:56.861Z] SESSION: Session validation successful gm +[2025-12-11T19:53:56.877Z] API: Fetch player gm success +[2025-12-11T19:53:57.419Z] API: Fetch all players (public) 7 +[2025-12-11T19:54:34.443Z] SESSION: Session validation successful gm +[2025-12-11T19:54:34.493Z] API: Fetch player gm success +[2025-12-11T19:54:34.833Z] SESSION: Session validation successful gm +[2025-12-11T19:54:34.886Z] API: Fetch player gm success +[2025-12-11T19:54:35.288Z] SESSION: Session validation successful gm +[2025-12-11T19:54:35.320Z] API: Fetch player gm success +[2025-12-11T19:54:35.470Z] SESSION: Session validation successful gm +[2025-12-11T19:54:35.490Z] API: Fetch player gm success +[2025-12-11T19:54:35.883Z] SESSION: Session validation successful gm +[2025-12-11T19:54:35.908Z] API: Fetch player gm success +[2025-12-11T19:54:36.854Z] API: Fetch all players (public) 7 +[2025-12-11T19:54:48.698Z] SESSION: Session validation successful gm +[2025-12-11T19:54:48.717Z] API: Fetch player gm success +[2025-12-11T19:54:49.031Z] SESSION: Session validation successful gm +[2025-12-11T19:54:49.051Z] API: Fetch player gm success +[2025-12-11T19:54:49.419Z] SESSION: Session validation successful gm +[2025-12-11T19:54:49.594Z] SESSION: Session validation successful gm +[2025-12-11T19:54:49.618Z] API: Fetch player gm success +[2025-12-11T19:54:50.476Z] API: Fetch all players (public) 7 +[2025-12-11T19:56:27.848Z] MariaDB: Tables created successfully +[2025-12-11T19:59:38.220Z] SESSION: Session validation successful gm +[2025-12-11T19:59:38.258Z] API: Fetch player gm success +[2025-12-11T19:59:39.439Z] API: Fetch all players (public) 7 +[2025-12-11T19:59:40.884Z] SESSION: Session validation successful gm +[2025-12-11T19:59:40.913Z] API: Fetch player gm success +[2025-12-11T19:59:41.722Z] API: Fetch all players (public) 7 +[2025-12-11T20:03:59.295Z] SESSION: Session validation successful gm +[2025-12-11T20:03:59.316Z] API: Fetch player gm success +[2025-12-11T20:04:01.115Z] API: Fetch all players (public) 7 +[2025-12-11T20:07:09.278Z] SESSION: Session validation successful gm +[2025-12-11T20:07:09.298Z] API: Fetch player gm success +[2025-12-11T20:07:11.412Z] API: Fetch all players (public) 7 +[2025-12-11T20:07:14.589Z] API: Fetch all players (public) 7 +[2025-12-11T20:07:15.121Z] API: Fetch all players (public) 7 +[2025-12-11T20:07:29.621Z] API: Fetch all players (public) 7 diff --git a/src/App.js b/src/App.js index b2a16ff..3344ae0 100755 --- a/src/App.js +++ b/src/App.js @@ -217,7 +217,7 @@ function App() { return (
{/* Persistent Header */} -
+
{/* Title and Login Row */}
@@ -239,7 +239,7 @@ function App() { ) : (
Bestiary
setQ(e.target.value)} /> @@ -188,7 +188,7 @@ export default function BestiaryTab(){
{dbDown && Date.now() < (dismissUntil || 0) ? null : dbDown ? ( -
+
Database unreachable — showing cached data and retrying in background.
- onSelectEnemy(e.target.value)}> {enemies.map(en => ())}
@@ -1115,21 +1115,21 @@ function DeathwatchRoller() { - setBS(parseInt(e.target.value||'0'))} /> + setBS(parseInt(e.target.value||'0'))} />
- setWS(parseInt(e.target.value||'0'))} /> + setWS(parseInt(e.target.value||'0'))} />
- setModifier(parseInt(e.target.value||'0'))} /> + setModifier(parseInt(e.target.value||'0'))} />
- setAim(parseInt(e.target.value))}> @@ -1137,7 +1137,7 @@ function DeathwatchRoller() {
- setDifficulty(e.target.value)}> @@ -1146,7 +1146,7 @@ function DeathwatchRoller() {
- setMode(e.target.value)}> {(['single','semi','full']).map(m => ( ))} @@ -1156,11 +1156,11 @@ function DeathwatchRoller() { - setRof(parseInt(e.target.value||'1'))} /> + setRof(parseInt(e.target.value||'1'))} />
- { setDamage(e.target.value); if (error) setError('') }} /> + { setDamage(e.target.value); if (error) setError('') }} />
- setPen(parseInt(e.target.value||'0'))} /> + setPen(parseInt(e.target.value||'0'))} />
- { const v = parseInt(e.target.value||'0'); setTargetArmour(v); setArmourMap(uniformArmourMap(v)) }} /> + { const v = parseInt(e.target.value||'0'); setTargetArmour(v); setArmourMap(uniformArmourMap(v)) }} />
{/* Manual Mode Controls */} -
+
Manual Dice Mode
@@ -1236,7 +1236,7 @@ function DeathwatchRoller() {
-
Attack
-
Defense
-
Damage
-
Wounds
+
Attack
+
Defense
+
Damage
+
Wounds
Phase: {progressStep}
Last Applied: {lastAppliedDamage}
{manageWeapons && ( <> -
+
Manage Weapons
@@ -1304,7 +1304,7 @@ function DeathwatchRoller() {
{manageTab==='json' ? ( <> -