Add development tooling: ESLint, Prettier, Husky, and Quench tests
Set up complete development environment with: - ESLint with Foundry VTT globals (game, CONFIG, Actor, etc.) - Prettier for consistent code formatting - Husky + lint-staged for pre-commit hooks - Quench test framework structure with sanity checks Documentation: - DEVELOPMENT.md with tooling decisions and rationale - README.md updated with development setup instructions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
37300ccf90
commit
44dbd00e1b
118
.eslintrc.json
Normal file
118
.eslintrc.json
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2022": true
|
||||||
|
},
|
||||||
|
"extends": ["eslint:recommended"],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"game": "readonly",
|
||||||
|
"canvas": "readonly",
|
||||||
|
"ui": "readonly",
|
||||||
|
"CONFIG": "readonly",
|
||||||
|
"CONST": "readonly",
|
||||||
|
"Hooks": "readonly",
|
||||||
|
"Application": "readonly",
|
||||||
|
"FormApplication": "readonly",
|
||||||
|
"ActorSheet": "readonly",
|
||||||
|
"ItemSheet": "readonly",
|
||||||
|
"Actor": "readonly",
|
||||||
|
"Item": "readonly",
|
||||||
|
"ChatMessage": "readonly",
|
||||||
|
"Roll": "readonly",
|
||||||
|
"Dialog": "readonly",
|
||||||
|
"TextEditor": "readonly",
|
||||||
|
"FilePicker": "readonly",
|
||||||
|
"Handlebars": "readonly",
|
||||||
|
"foundry": "readonly",
|
||||||
|
"renderTemplate": "readonly",
|
||||||
|
"loadTemplates": "readonly",
|
||||||
|
"getTemplate": "readonly",
|
||||||
|
"fromUuid": "readonly",
|
||||||
|
"fromUuidSync": "readonly",
|
||||||
|
"duplicate": "readonly",
|
||||||
|
"mergeObject": "readonly",
|
||||||
|
"setProperty": "readonly",
|
||||||
|
"getProperty": "readonly",
|
||||||
|
"hasProperty": "readonly",
|
||||||
|
"expandObject": "readonly",
|
||||||
|
"flattenObject": "readonly",
|
||||||
|
"isObjectEmpty": "readonly",
|
||||||
|
"invertObject": "readonly",
|
||||||
|
"filterObject": "readonly",
|
||||||
|
"diffObject": "readonly",
|
||||||
|
"randomID": "readonly",
|
||||||
|
"debounce": "readonly",
|
||||||
|
"deepClone": "readonly",
|
||||||
|
"isEmpty": "readonly",
|
||||||
|
"getType": "readonly",
|
||||||
|
"ActiveEffect": "readonly",
|
||||||
|
"Token": "readonly",
|
||||||
|
"TokenDocument": "readonly",
|
||||||
|
"Scene": "readonly",
|
||||||
|
"User": "readonly",
|
||||||
|
"Folder": "readonly",
|
||||||
|
"Compendium": "readonly",
|
||||||
|
"CompendiumCollection": "readonly",
|
||||||
|
"DocumentSheetConfig": "readonly",
|
||||||
|
"ContextMenu": "readonly",
|
||||||
|
"DragDrop": "readonly",
|
||||||
|
"SearchFilter": "readonly",
|
||||||
|
"Tabs": "readonly",
|
||||||
|
"quench": "readonly"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-unused-vars": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"argsIgnorePattern": "^_",
|
||||||
|
"varsIgnorePattern": "^_"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-shadow": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"builtinGlobals": true,
|
||||||
|
"hoist": "all",
|
||||||
|
"allow": ["event", "name", "status", "parent", "top", "close", "open", "print"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-var": "error",
|
||||||
|
"prefer-const": "warn",
|
||||||
|
"eqeqeq": ["error", "smart"],
|
||||||
|
"curly": ["error", "multi-line"],
|
||||||
|
"no-console": ["warn", { "allow": ["warn", "error"] }],
|
||||||
|
"no-debugger": "warn",
|
||||||
|
"no-duplicate-imports": "error",
|
||||||
|
"no-template-curly-in-string": "warn",
|
||||||
|
"no-unreachable-loop": "error",
|
||||||
|
"no-use-before-define": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"functions": false,
|
||||||
|
"classes": true,
|
||||||
|
"variables": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"arrow-body-style": ["warn", "as-needed"],
|
||||||
|
"no-else-return": "warn",
|
||||||
|
"no-lonely-if": "warn",
|
||||||
|
"no-unneeded-ternary": "warn",
|
||||||
|
"prefer-arrow-callback": "warn",
|
||||||
|
"prefer-template": "warn",
|
||||||
|
"object-shorthand": "warn",
|
||||||
|
"spaced-comment": ["warn", "always", { "markers": ["/"] }]
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["**/*.test.mjs", "**/tests/**/*.mjs"],
|
||||||
|
"rules": {
|
||||||
|
"no-unused-expressions": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ignorePatterns": ["node_modules/", "foundrydata/", "styles/*.css", "*.min.js"]
|
||||||
|
}
|
||||||
1
.husky/pre-commit
Executable file
1
.husky/pre-commit
Executable file
@ -0,0 +1 @@
|
|||||||
|
npx lint-staged
|
||||||
27
.prettierignore
Normal file
27
.prettierignore
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Foundry data
|
||||||
|
foundrydata/
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
styles/*.css
|
||||||
|
styles/*.css.map
|
||||||
|
|
||||||
|
# Package lock
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# Compendium packs (LevelDB format)
|
||||||
|
packs/*/*.db
|
||||||
|
packs/*/CURRENT
|
||||||
|
packs/*/LOCK
|
||||||
|
packs/*/LOG*
|
||||||
|
packs/*/MANIFEST*
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Minified files
|
||||||
|
*.min.js
|
||||||
|
*.min.css
|
||||||
34
.prettierrc
Normal file
34
.prettierrc
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 100,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"quoteProps": "as-needed",
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.json",
|
||||||
|
"options": {
|
||||||
|
"tabWidth": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": "*.hbs",
|
||||||
|
"options": {
|
||||||
|
"parser": "html",
|
||||||
|
"printWidth": 120
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": "*.scss",
|
||||||
|
"options": {
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
17
CLAUDE.md
17
CLAUDE.md
@ -1,42 +1,49 @@
|
|||||||
# Vagabond RPG Foundry VTT System - Development Context
|
# Vagabond RPG Foundry VTT System - Development Context
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
This is a complete Foundry VTT v13 system implementation for Vagabond RPG (Pulp Fantasy TTRPG).
|
This is a complete Foundry VTT v13 system implementation for Vagabond RPG (Pulp Fantasy TTRPG).
|
||||||
|
|
||||||
## Key Architecture Decisions
|
## Key Architecture Decisions
|
||||||
|
|
||||||
### Data Models (Foundry v13 style)
|
### Data Models (Foundry v13 style)
|
||||||
|
|
||||||
- Use TypeDataModel classes in `module/data/` for Actor and Item schemas
|
- Use TypeDataModel classes in `module/data/` for Actor and Item schemas
|
||||||
- Character stats: Might, Dexterity, Awareness, Reason, Presence, Luck (range 2-7)
|
- Character stats: Might, Dexterity, Awareness, Reason, Presence, Luck (range 2-7)
|
||||||
- Derived values calculated in `prepareData()`: HP, Speed, Save difficulties, Skill difficulties
|
- Derived values calculated in `prepareData()`: HP, Speed, Save difficulties, Skill difficulties
|
||||||
|
|
||||||
### Roll System
|
### Roll System
|
||||||
- Base formula: d20 >= (20 - Stat) for untrained, d20 >= (20 - Stat*2) for trained
|
|
||||||
|
- Base formula: d20 >= (20 - Stat) for untrained, d20 >= (20 - Stat\*2) for trained
|
||||||
- Favor: +d6 bonus die
|
- Favor: +d6 bonus die
|
||||||
- Hinder: -d6 penalty die
|
- Hinder: -d6 penalty die
|
||||||
- Crit: Natural 20 by default, but threshold can be modified per-skill by Active Effects
|
- Crit: Natural 20 by default, but threshold can be modified per-skill by Active Effects
|
||||||
- Exploding dice: d6! notation for certain abilities
|
- Exploding dice: d6! notation for certain abilities
|
||||||
|
|
||||||
### Spell Casting
|
### Spell Casting
|
||||||
|
|
||||||
- Dynamic mana cost = base + delivery cost + duration cost + extra damage dice
|
- Dynamic mana cost = base + delivery cost + duration cost + extra damage dice
|
||||||
- Delivery types: Touch(0), Remote(0), Imbue(0), Cube(1), Aura(2), Cone(2), Glyph(2), Line(2), Sphere(2)
|
- Delivery types: Touch(0), Remote(0), Imbue(0), Cube(1), Aura(2), Cone(2), Glyph(2), Line(2), Sphere(2)
|
||||||
- Duration: Instant (free), Focus (ongoing), Continual (permanent)
|
- Duration: Instant (free), Focus (ongoing), Continual (permanent)
|
||||||
- Cast dialog must calculate and display total mana cost before casting
|
- Cast dialog must calculate and display total mana cost before casting
|
||||||
|
|
||||||
### Class System
|
### Class System
|
||||||
|
|
||||||
- Classes are Items with progression tables
|
- Classes are Items with progression tables
|
||||||
- When dragged to character, creates Active Effects for current level
|
- When dragged to character, creates Active Effects for current level
|
||||||
- On level up, update Active Effects to grant new features
|
- On level up, update Active Effects to grant new features
|
||||||
- Supports future multiclassing by allowing multiple class items
|
- Supports future multiclassing by allowing multiple class items
|
||||||
|
|
||||||
### Crit Threshold System
|
### Crit Threshold System
|
||||||
|
|
||||||
- Each skill/action has a `critThreshold` field (default 20)
|
- Each skill/action has a `critThreshold` field (default 20)
|
||||||
- Active Effects from classes/perks can modify: `system.skills.melee.critThreshold`
|
- Active Effects from classes/perks can modify: `system.skills.melee.critThreshold`
|
||||||
- Fighter's Valor reduces crit by 1/2/3 at levels 1/4/8
|
- Fighter's Valor reduces crit by 1/2/3 at levels 1/4/8
|
||||||
- Gunslinger's Deadeye dynamically reduces on consecutive hits
|
- Gunslinger's Deadeye dynamically reduces on consecutive hits
|
||||||
|
|
||||||
### Resources
|
### Resources
|
||||||
- HP: max = Might * Level
|
|
||||||
|
- HP: max = Might \* Level
|
||||||
- Mana: class-dependent, max from class progression
|
- Mana: class-dependent, max from class progression
|
||||||
- Luck: equals Luck stat, refreshes on rest
|
- Luck: equals Luck stat, refreshes on rest
|
||||||
- Fatigue: 0-5, death at 5, each reduces item slots by 1
|
- Fatigue: 0-5, death at 5, each reduces item slots by 1
|
||||||
@ -44,6 +51,7 @@ This is a complete Foundry VTT v13 system implementation for Vagabond RPG (Pulp
|
|||||||
- Custom resources can be added dynamically
|
- Custom resources can be added dynamically
|
||||||
|
|
||||||
## File Naming Conventions
|
## File Naming Conventions
|
||||||
|
|
||||||
- Main system entry: `vagabond.mjs`
|
- Main system entry: `vagabond.mjs`
|
||||||
- Document classes: `VagabondActor.mjs`, `VagabondItem.mjs`
|
- Document classes: `VagabondActor.mjs`, `VagabondItem.mjs`
|
||||||
- Sheet classes: `VagabondCharacterSheet.mjs`, `VagabondNPCSheet.mjs`
|
- Sheet classes: `VagabondCharacterSheet.mjs`, `VagabondNPCSheet.mjs`
|
||||||
@ -51,6 +59,7 @@ This is a complete Foundry VTT v13 system implementation for Vagabond RPG (Pulp
|
|||||||
- Templates: `character-sheet.hbs`, `npc-sheet.hbs`, `spell-item.hbs`
|
- Templates: `character-sheet.hbs`, `npc-sheet.hbs`, `spell-item.hbs`
|
||||||
|
|
||||||
## Testing Commands
|
## Testing Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start local Foundry
|
# Start local Foundry
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
@ -63,7 +72,9 @@ docker compose logs -f foundry
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Reference Data Location
|
## Reference Data Location
|
||||||
|
|
||||||
Game rules and content are documented in NoteDiscovery under `gaming/vagabond-rpg/`:
|
Game rules and content are documented in NoteDiscovery under `gaming/vagabond-rpg/`:
|
||||||
|
|
||||||
- `core-mechanics.md` - Stats, checks, dice, HP
|
- `core-mechanics.md` - Stats, checks, dice, HP
|
||||||
- `combat.md` - Actions, movement, defending, zones
|
- `combat.md` - Actions, movement, defending, zones
|
||||||
- `character-creation.md` - Ancestries, classes, leveling
|
- `character-creation.md` - Ancestries, classes, leveling
|
||||||
@ -77,9 +88,11 @@ Original PDF at: `/mnt/NV2/Development/claude-home/gaming/Vagabond_RPG_-_Pulp_Fa
|
|||||||
Character sheet reference: `/mnt/NV2/Development/claude-home/gaming/Vagabond_-_Hero_Record_Interactive_PDF.pdf`
|
Character sheet reference: `/mnt/NV2/Development/claude-home/gaming/Vagabond_-_Hero_Record_Interactive_PDF.pdf`
|
||||||
|
|
||||||
## Project Roadmap
|
## Project Roadmap
|
||||||
|
|
||||||
See `PROJECT_ROADMAP.json` for complete task breakdown with dependencies.
|
See `PROJECT_ROADMAP.json` for complete task breakdown with dependencies.
|
||||||
|
|
||||||
## Style Guidelines
|
## Style Guidelines
|
||||||
|
|
||||||
- Parchment color scheme with high contrast (WCAG AA compliant)
|
- Parchment color scheme with high contrast (WCAG AA compliant)
|
||||||
- Match official Hero Record layout where possible
|
- Match official Hero Record layout where possible
|
||||||
- Use CSS custom properties for theming
|
- Use CSS custom properties for theming
|
||||||
|
|||||||
299
DEVELOPMENT.md
Normal file
299
DEVELOPMENT.md
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
# Development Guide
|
||||||
|
|
||||||
|
This document outlines the development toolchain, conventions, and testing strategy for the Vagabond RPG Foundry VTT system.
|
||||||
|
|
||||||
|
## Tooling Decisions
|
||||||
|
|
||||||
|
### Why These Tools?
|
||||||
|
|
||||||
|
| Tool | Decision | Rationale |
|
||||||
|
| ------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| **ESLint** | ✅ Adopted | Industry standard for JavaScript linting. Catches bugs before runtime, enforces consistent patterns. |
|
||||||
|
| **@typhonjs-fvtt/eslint-config-foundry.js** | ✅ Adopted | Foundry-specific ESLint plugin that knows about Foundry globals (`game`, `CONFIG`, `canvas`, etc.). Prevents accidentally shadowing core APIs. |
|
||||||
|
| **Prettier** | ✅ Adopted | Opinionated formatter eliminates style debates. Consistent code regardless of contributor. |
|
||||||
|
| **Husky + lint-staged** | ✅ Adopted | Pre-commit hooks enforce quality gates. No unlinted code enters the repo. |
|
||||||
|
| **Quench** | ✅ Adopted | In-Foundry testing module powered by Mocha + Chai. Tests run with real Foundry API access - essential for testing sheets, rolls, and Active Effects. |
|
||||||
|
| **Vitest/Jest** | ❌ Deferred | Most system logic requires Foundry context. Pure unit tests have limited value here. May add later for formula calculations. |
|
||||||
|
| **TypeScript** | ❌ Deferred | Adds complexity for a solo/small team project. JSDoc comments provide type hints without build step overhead. May migrate later. |
|
||||||
|
| **Rollup/Vite** | ❌ Deferred | Foundry v13 has excellent ESM support. No bundling needed for our use case. SCSS compilation via sass CLI is sufficient. |
|
||||||
|
|
||||||
|
### Alternatives Considered
|
||||||
|
|
||||||
|
**Testing:**
|
||||||
|
|
||||||
|
- **Vitest with foundry-test-utils**: Provides mocked Foundry environment (~600 lines of mocks). Rejected because mocks drift from real API, and Quench gives us actual Foundry context.
|
||||||
|
- **Cypress/Playwright**: E2E browser testing. Overkill for a game system; Quench is purpose-built for Foundry.
|
||||||
|
|
||||||
|
**Linting:**
|
||||||
|
|
||||||
|
- **Biome**: Faster than ESLint+Prettier combined. Rejected because no Foundry-specific config exists yet.
|
||||||
|
- **Standard JS**: Zero-config linting. Rejected because we need Foundry global awareness.
|
||||||
|
|
||||||
|
**Build:**
|
||||||
|
|
||||||
|
- **Gulp**: Used by SR5-FoundryVTT. Rejected as unnecessary complexity; npm scripts + sass CLI suffice.
|
||||||
|
- **TyphonJS Svelte/Vite**: Modern but opinionated toward Svelte. We're using vanilla Handlebars.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Node.js 18+ (v20 has known issues with some Foundry tooling)
|
||||||
|
- Docker & Docker Compose
|
||||||
|
- Git
|
||||||
|
|
||||||
|
### Initial Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone repository
|
||||||
|
git clone git@github.com:calcorum/vagabond-rpg-foundryvtt.git
|
||||||
|
cd vagabond-rpg-foundryvtt
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Copy environment file and add your Foundry credentials
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env with your FOUNDRY_USERNAME, FOUNDRY_PASSWORD, FOUNDRY_LICENSE_KEY
|
||||||
|
|
||||||
|
# Start local Foundry instance
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Start SCSS watcher (in separate terminal)
|
||||||
|
npm run watch
|
||||||
|
|
||||||
|
# Access Foundry at http://localhost:30000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Scripts
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| ---------------------- | ------------------------------------------- |
|
||||||
|
| `npm run build` | Compile SCSS to CSS (production) |
|
||||||
|
| `npm run watch` | Watch SCSS and recompile on changes |
|
||||||
|
| `npm run lint` | Run ESLint on all JavaScript |
|
||||||
|
| `npm run lint:fix` | Auto-fix ESLint issues where possible |
|
||||||
|
| `npm run format` | Run Prettier on all files |
|
||||||
|
| `npm run format:check` | Check if files are formatted (CI) |
|
||||||
|
| `npm test` | Run Quench tests (requires running Foundry) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
### ESLint Rules
|
||||||
|
|
||||||
|
We use a Foundry-aware ESLint configuration that:
|
||||||
|
|
||||||
|
1. **Knows Foundry globals**: `game`, `CONFIG`, `canvas`, `ui`, `Hooks`, `Actor`, `Item`, etc. are recognized
|
||||||
|
2. **Prevents shadowing**: You can't accidentally create a local `game` variable
|
||||||
|
3. **Enforces ES2022+**: Modern JavaScript features encouraged
|
||||||
|
4. **Warns on common mistakes**: Unused variables, undefined references, etc.
|
||||||
|
|
||||||
|
### Prettier Configuration
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"printWidth": 100,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "always"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why these choices:**
|
||||||
|
|
||||||
|
- `printWidth: 100` - Balances readability with modern wide screens
|
||||||
|
- `semi: true` - Explicit semicolons prevent ASI edge cases
|
||||||
|
- `singleQuote: false` - Matches Foundry core style
|
||||||
|
- `trailingComma: "es5"` - Cleaner diffs, valid ES5+
|
||||||
|
|
||||||
|
### Pre-commit Hooks
|
||||||
|
|
||||||
|
Husky runs on every commit:
|
||||||
|
|
||||||
|
1. **lint-staged**: Runs ESLint and Prettier only on staged files
|
||||||
|
2. **Blocks commits** if linting fails or files aren't formatted
|
||||||
|
|
||||||
|
To bypass (use sparingly):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit --no-verify -m "emergency fix"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Quench Integration Tests
|
||||||
|
|
||||||
|
[Quench](https://github.com/Ethaks/FVTT-Quench) runs tests inside Foundry with full API access.
|
||||||
|
|
||||||
|
**What to test:**
|
||||||
|
|
||||||
|
- ✅ Data model validation (Actor/Item creation)
|
||||||
|
- ✅ Derived value calculations (HP, Speed, difficulties)
|
||||||
|
- ✅ Roll formula generation
|
||||||
|
- ✅ Active Effect application
|
||||||
|
- ✅ Sheet rendering (no JS errors)
|
||||||
|
- ✅ Compendium item import
|
||||||
|
|
||||||
|
**What NOT to test:**
|
||||||
|
|
||||||
|
- ❌ Foundry core functionality
|
||||||
|
- ❌ Third-party module interactions
|
||||||
|
- ❌ UI pixel-perfect rendering
|
||||||
|
|
||||||
|
### Test File Location
|
||||||
|
|
||||||
|
```
|
||||||
|
module/
|
||||||
|
├── tests/
|
||||||
|
│ ├── quench-init.mjs # Registers test batches with Quench
|
||||||
|
│ ├── actor.test.mjs # Actor data model tests
|
||||||
|
│ ├── item.test.mjs # Item data model tests
|
||||||
|
│ ├── rolls.test.mjs # Dice roll tests
|
||||||
|
│ └── effects.test.mjs # Active Effects tests
|
||||||
|
```
|
||||||
|
|
||||||
|
### Writing Tests
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// module/tests/actor.test.mjs
|
||||||
|
export function registerActorTests(quench) {
|
||||||
|
quench.registerBatch(
|
||||||
|
"vagabond.actors",
|
||||||
|
(context) => {
|
||||||
|
const { describe, it, expect } = context;
|
||||||
|
|
||||||
|
describe("Character Actor", () => {
|
||||||
|
it("calculates HP as Might × Level", async () => {
|
||||||
|
const actor = await Actor.create({
|
||||||
|
name: "Test Character",
|
||||||
|
type: "character",
|
||||||
|
system: { stats: { might: { value: 5 } }, level: 3 },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(actor.system.resources.hp.max).to.equal(15);
|
||||||
|
await actor.delete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ displayName: "Vagabond: Actor Tests" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
1. Install the Quench module in Foundry (Module Management)
|
||||||
|
2. Enable Quench in your world
|
||||||
|
3. Click the flask icon in the scene controls
|
||||||
|
4. Select "Vagabond" test batches
|
||||||
|
5. Click "Run"
|
||||||
|
|
||||||
|
Or via macro:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
quench.runBatches("vagabond.*");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
vagabond-rpg-foundryvtt/
|
||||||
|
├── .husky/ # Git hooks
|
||||||
|
├── module/ # JavaScript source
|
||||||
|
│ ├── vagabond.mjs # System entry point
|
||||||
|
│ ├── data/ # Data models (TypeDataModel classes)
|
||||||
|
│ ├── documents/ # Document classes (VagabondActor, VagabondItem)
|
||||||
|
│ ├── sheets/ # Sheet classes
|
||||||
|
│ ├── helpers/ # Utility functions
|
||||||
|
│ ├── dice/ # Roll handling
|
||||||
|
│ └── tests/ # Quench test files
|
||||||
|
├── templates/ # Handlebars templates
|
||||||
|
├── styles/ # SCSS source and compiled CSS
|
||||||
|
├── lang/ # Localization
|
||||||
|
├── packs/ # Compendium data
|
||||||
|
├── assets/ # Images and icons
|
||||||
|
├── system.json # Foundry manifest
|
||||||
|
├── .eslintrc.json # ESLint configuration
|
||||||
|
├── .prettierrc # Prettier configuration
|
||||||
|
└── package.json # npm dependencies and scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Continuous Integration
|
||||||
|
|
||||||
|
### GitHub Actions (Future)
|
||||||
|
|
||||||
|
When we set up CI, it will:
|
||||||
|
|
||||||
|
1. **On Pull Request:**
|
||||||
|
- Run `npm run lint`
|
||||||
|
- Run `npm run format:check`
|
||||||
|
- Verify SCSS compiles without errors
|
||||||
|
|
||||||
|
2. **On Release Tag:**
|
||||||
|
- Build production CSS
|
||||||
|
- Create release zip
|
||||||
|
- Publish to GitHub Releases
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
### Commit Message Format
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>: <short description>
|
||||||
|
|
||||||
|
[optional body]
|
||||||
|
|
||||||
|
[optional footer]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Types:**
|
||||||
|
|
||||||
|
- `feat`: New feature
|
||||||
|
- `fix`: Bug fix
|
||||||
|
- `docs`: Documentation only
|
||||||
|
- `style`: Formatting, no code change
|
||||||
|
- `refactor`: Code change that neither fixes nor adds
|
||||||
|
- `test`: Adding tests
|
||||||
|
- `chore`: Build/tooling changes
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: Add spell casting dialog with mana calculator
|
||||||
|
|
||||||
|
fix: Correct HP calculation for characters with Tough perk
|
||||||
|
|
||||||
|
docs: Update README with installation instructions
|
||||||
|
```
|
||||||
|
|
||||||
|
### Branch Naming
|
||||||
|
|
||||||
|
- `feature/spell-casting-dialog`
|
||||||
|
- `fix/hp-calculation`
|
||||||
|
- `docs/readme-update`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Foundry VTT API Documentation](https://foundryvtt.com/api/)
|
||||||
|
- [Foundry Community Wiki](https://foundryvtt.wiki/)
|
||||||
|
- [Quench Testing Module](https://github.com/Ethaks/FVTT-Quench)
|
||||||
|
- [TyphonJS ESLint Config](https://github.com/typhonjs-fvtt/eslint-config-foundry.js)
|
||||||
|
- [Vagabond RPG Rules (NoteDiscovery)](https://notes.manticorum.com) - gaming/vagabond-rpg/
|
||||||
@ -15,6 +15,7 @@ A complete Foundry VTT v13 system implementation for **Vagabond RPG** - Pulp Fan
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### From Foundry
|
### From Foundry
|
||||||
|
|
||||||
1. Open Foundry VTT Setup
|
1. Open Foundry VTT Setup
|
||||||
2. Navigate to Game Systems
|
2. Navigate to Game Systems
|
||||||
3. Click "Install System"
|
3. Click "Install System"
|
||||||
@ -24,6 +25,7 @@ A complete Foundry VTT v13 system implementation for **Vagabond RPG** - Pulp Fan
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Manual Installation
|
### Manual Installation
|
||||||
|
|
||||||
1. Download the latest release from GitHub
|
1. Download the latest release from GitHub
|
||||||
2. Extract to `Data/systems/vagabond/`
|
2. Extract to `Data/systems/vagabond/`
|
||||||
3. Restart Foundry VTT
|
3. Restart Foundry VTT
|
||||||
@ -31,10 +33,12 @@ A complete Foundry VTT v13 system implementation for **Vagabond RPG** - Pulp Fan
|
|||||||
## Development Setup
|
## Development Setup
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Node.js 18+
|
- Node.js 18+
|
||||||
- Docker & Docker Compose (for local Foundry instance)
|
- Docker & Docker Compose (for local Foundry instance)
|
||||||
|
|
||||||
### Quick Start
|
### Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone the repository
|
# Clone the repository
|
||||||
git clone https://github.com/calcorum/vagabond-rpg-foundryvtt.git
|
git clone https://github.com/calcorum/vagabond-rpg-foundryvtt.git
|
||||||
@ -53,6 +57,7 @@ docker compose up -d
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
vagabond-rpg-foundryvtt/
|
vagabond-rpg-foundryvtt/
|
||||||
├── module/ # JavaScript modules
|
├── module/ # JavaScript modules
|
||||||
@ -76,6 +81,7 @@ vagabond-rpg-foundryvtt/
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Building Styles
|
### Building Styles
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# One-time build
|
# One-time build
|
||||||
npm run build
|
npm run build
|
||||||
@ -85,6 +91,7 @@ npm run watch
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Creating a Release
|
### Creating a Release
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run release
|
npm run release
|
||||||
```
|
```
|
||||||
|
|||||||
@ -14,7 +14,7 @@ VAGABOND.stats = {
|
|||||||
awareness: "VAGABOND.StatAwareness",
|
awareness: "VAGABOND.StatAwareness",
|
||||||
reason: "VAGABOND.StatReason",
|
reason: "VAGABOND.StatReason",
|
||||||
presence: "VAGABOND.StatPresence",
|
presence: "VAGABOND.StatPresence",
|
||||||
luck: "VAGABOND.StatLuck"
|
luck: "VAGABOND.StatLuck",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,7 +26,7 @@ VAGABOND.statsAbbr = {
|
|||||||
awareness: "VAGABOND.StatAwarenessAbbr",
|
awareness: "VAGABOND.StatAwarenessAbbr",
|
||||||
reason: "VAGABOND.StatReasonAbbr",
|
reason: "VAGABOND.StatReasonAbbr",
|
||||||
presence: "VAGABOND.StatPresenceAbbr",
|
presence: "VAGABOND.StatPresenceAbbr",
|
||||||
luck: "VAGABOND.StatLuckAbbr"
|
luck: "VAGABOND.StatLuckAbbr",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,7 +44,7 @@ VAGABOND.skills = {
|
|||||||
mysticism: { label: "VAGABOND.SkillMysticism", stat: "awareness" },
|
mysticism: { label: "VAGABOND.SkillMysticism", stat: "awareness" },
|
||||||
performance: { label: "VAGABOND.SkillPerformance", stat: "presence" },
|
performance: { label: "VAGABOND.SkillPerformance", stat: "presence" },
|
||||||
sneak: { label: "VAGABOND.SkillSneak", stat: "dexterity" },
|
sneak: { label: "VAGABOND.SkillSneak", stat: "dexterity" },
|
||||||
survival: { label: "VAGABOND.SkillSurvival", stat: "awareness" }
|
survival: { label: "VAGABOND.SkillSurvival", stat: "awareness" },
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,7 +54,7 @@ VAGABOND.attackTypes = {
|
|||||||
melee: { label: "VAGABOND.AttackMelee", stat: "might" },
|
melee: { label: "VAGABOND.AttackMelee", stat: "might" },
|
||||||
brawl: { label: "VAGABOND.AttackBrawl", stat: "might" },
|
brawl: { label: "VAGABOND.AttackBrawl", stat: "might" },
|
||||||
ranged: { label: "VAGABOND.AttackRanged", stat: "awareness" },
|
ranged: { label: "VAGABOND.AttackRanged", stat: "awareness" },
|
||||||
finesse: { label: "VAGABOND.AttackFinesse", stat: "dexterity" }
|
finesse: { label: "VAGABOND.AttackFinesse", stat: "dexterity" },
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,7 +63,7 @@ VAGABOND.attackTypes = {
|
|||||||
VAGABOND.saves = {
|
VAGABOND.saves = {
|
||||||
reflex: { label: "VAGABOND.SaveReflex", stats: ["dexterity", "awareness"] },
|
reflex: { label: "VAGABOND.SaveReflex", stats: ["dexterity", "awareness"] },
|
||||||
endure: { label: "VAGABOND.SaveEndure", stats: ["might", "might"] },
|
endure: { label: "VAGABOND.SaveEndure", stats: ["might", "might"] },
|
||||||
will: { label: "VAGABOND.SaveWill", stats: ["reason", "presence"] }
|
will: { label: "VAGABOND.SaveWill", stats: ["reason", "presence"] },
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,7 +78,7 @@ VAGABOND.spellDelivery = {
|
|||||||
cone: { label: "VAGABOND.DeliveryCone", cost: 2 },
|
cone: { label: "VAGABOND.DeliveryCone", cost: 2 },
|
||||||
glyph: { label: "VAGABOND.DeliveryGlyph", cost: 2 },
|
glyph: { label: "VAGABOND.DeliveryGlyph", cost: 2 },
|
||||||
line: { label: "VAGABOND.DeliveryLine", cost: 2 },
|
line: { label: "VAGABOND.DeliveryLine", cost: 2 },
|
||||||
sphere: { label: "VAGABOND.DeliverySphere", cost: 2 }
|
sphere: { label: "VAGABOND.DeliverySphere", cost: 2 },
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,7 +87,7 @@ VAGABOND.spellDelivery = {
|
|||||||
VAGABOND.spellDuration = {
|
VAGABOND.spellDuration = {
|
||||||
instant: { label: "VAGABOND.DurationInstant", focus: false },
|
instant: { label: "VAGABOND.DurationInstant", focus: false },
|
||||||
focus: { label: "VAGABOND.DurationFocus", focus: true },
|
focus: { label: "VAGABOND.DurationFocus", focus: true },
|
||||||
continual: { label: "VAGABOND.DurationContinual", focus: false }
|
continual: { label: "VAGABOND.DurationContinual", focus: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,7 +101,7 @@ VAGABOND.damageTypes = {
|
|||||||
cold: "VAGABOND.DamageCold",
|
cold: "VAGABOND.DamageCold",
|
||||||
shock: "VAGABOND.DamageShock",
|
shock: "VAGABOND.DamageShock",
|
||||||
poison: "VAGABOND.DamagePoison",
|
poison: "VAGABOND.DamagePoison",
|
||||||
acid: "VAGABOND.DamageAcid"
|
acid: "VAGABOND.DamageAcid",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,7 +115,7 @@ VAGABOND.weaponProperties = {
|
|||||||
loading: "VAGABOND.PropertyLoading",
|
loading: "VAGABOND.PropertyLoading",
|
||||||
brawl: "VAGABOND.PropertyBrawl",
|
brawl: "VAGABOND.PropertyBrawl",
|
||||||
crude: "VAGABOND.PropertyCrude",
|
crude: "VAGABOND.PropertyCrude",
|
||||||
versatile: "VAGABOND.PropertyVersatile"
|
versatile: "VAGABOND.PropertyVersatile",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,7 +125,7 @@ VAGABOND.gripTypes = {
|
|||||||
"1h": "VAGABOND.Grip1H",
|
"1h": "VAGABOND.Grip1H",
|
||||||
"2h": "VAGABOND.Grip2H",
|
"2h": "VAGABOND.Grip2H",
|
||||||
versatile: "VAGABOND.GripVersatile",
|
versatile: "VAGABOND.GripVersatile",
|
||||||
fist: "VAGABOND.GripFist"
|
fist: "VAGABOND.GripFist",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,7 +134,7 @@ VAGABOND.gripTypes = {
|
|||||||
VAGABOND.armorTypes = {
|
VAGABOND.armorTypes = {
|
||||||
light: "VAGABOND.ArmorLight",
|
light: "VAGABOND.ArmorLight",
|
||||||
heavy: "VAGABOND.ArmorHeavy",
|
heavy: "VAGABOND.ArmorHeavy",
|
||||||
shield: "VAGABOND.ArmorShield"
|
shield: "VAGABOND.ArmorShield",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,7 +146,7 @@ VAGABOND.sizes = {
|
|||||||
large: "VAGABOND.SizeLarge",
|
large: "VAGABOND.SizeLarge",
|
||||||
huge: "VAGABOND.SizeHuge",
|
huge: "VAGABOND.SizeHuge",
|
||||||
giant: "VAGABOND.SizeGiant",
|
giant: "VAGABOND.SizeGiant",
|
||||||
colossal: "VAGABOND.SizeColossal"
|
colossal: "VAGABOND.SizeColossal",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,7 +160,7 @@ VAGABOND.beingTypes = {
|
|||||||
beast: "VAGABOND.BeingBeast",
|
beast: "VAGABOND.BeingBeast",
|
||||||
outer: "VAGABOND.BeingOuter",
|
outer: "VAGABOND.BeingOuter",
|
||||||
primordial: "VAGABOND.BeingPrimordial",
|
primordial: "VAGABOND.BeingPrimordial",
|
||||||
undead: "VAGABOND.BeingUndead"
|
undead: "VAGABOND.BeingUndead",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -169,7 +169,7 @@ VAGABOND.beingTypes = {
|
|||||||
VAGABOND.zones = {
|
VAGABOND.zones = {
|
||||||
frontline: "VAGABOND.ZoneFrontline",
|
frontline: "VAGABOND.ZoneFrontline",
|
||||||
midline: "VAGABOND.ZoneMidline",
|
midline: "VAGABOND.ZoneMidline",
|
||||||
backline: "VAGABOND.ZoneBackline"
|
backline: "VAGABOND.ZoneBackline",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -186,7 +186,7 @@ VAGABOND.speedByDex = {
|
|||||||
4: 30,
|
4: 30,
|
||||||
5: 30,
|
5: 30,
|
||||||
6: 35,
|
6: 35,
|
||||||
7: 35
|
7: 35,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
165
module/tests/actor.test.mjs
Normal file
165
module/tests/actor.test.mjs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
/**
|
||||||
|
* Actor Data Model Tests
|
||||||
|
*
|
||||||
|
* Tests for VagabondActor class and character/NPC data models.
|
||||||
|
* These tests verify derived value calculations, resource tracking,
|
||||||
|
* and data integrity.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register actor tests with Quench
|
||||||
|
* @param {Quench} quenchRunner - The Quench test runner instance
|
||||||
|
*/
|
||||||
|
export function registerActorTests(quenchRunner) {
|
||||||
|
quenchRunner.registerBatch(
|
||||||
|
"vagabond.actors.character",
|
||||||
|
(context) => {
|
||||||
|
const { describe, it, expect, beforeEach, afterEach } = context;
|
||||||
|
|
||||||
|
let testActor = null;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Create a fresh test actor before each test
|
||||||
|
testActor = await Actor.create({
|
||||||
|
name: "Test Character",
|
||||||
|
type: "character",
|
||||||
|
system: {
|
||||||
|
stats: {
|
||||||
|
might: { value: 5 },
|
||||||
|
dexterity: { value: 4 },
|
||||||
|
awareness: { value: 3 },
|
||||||
|
reason: { value: 4 },
|
||||||
|
presence: { value: 3 },
|
||||||
|
luck: { value: 2 },
|
||||||
|
},
|
||||||
|
level: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
// Clean up test actor after each test
|
||||||
|
if (testActor) {
|
||||||
|
await testActor.delete();
|
||||||
|
testActor = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Derived Values", () => {
|
||||||
|
it("calculates Max HP as Might × Level", async () => {
|
||||||
|
expect(testActor.system.resources.hp.max).to.equal(5); // 5 × 1
|
||||||
|
|
||||||
|
await testActor.update({ "system.level": 3 });
|
||||||
|
expect(testActor.system.resources.hp.max).to.equal(15); // 5 × 3
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calculates Speed based on Dexterity", async () => {
|
||||||
|
// DEX 4 = 30 ft speed
|
||||||
|
expect(testActor.system.speed.value).to.equal(30);
|
||||||
|
|
||||||
|
await testActor.update({ "system.stats.dexterity.value": 6 });
|
||||||
|
expect(testActor.system.speed.value).to.equal(35);
|
||||||
|
|
||||||
|
await testActor.update({ "system.stats.dexterity.value": 2 });
|
||||||
|
expect(testActor.system.speed.value).to.equal(25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calculates Item Slots as 8 + Might", async () => {
|
||||||
|
expect(testActor.system.itemSlots.max).to.equal(13); // 8 + 5
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calculates Save difficulties correctly", async () => {
|
||||||
|
// Reflex = DEX + AWR = 4 + 3 = 7, Difficulty = 20 - 7 = 13
|
||||||
|
expect(testActor.system.saves.reflex.difficulty).to.equal(13);
|
||||||
|
|
||||||
|
// Endure = MIT + MIT = 5 + 5 = 10, Difficulty = 20 - 10 = 10
|
||||||
|
expect(testActor.system.saves.endure.difficulty).to.equal(10);
|
||||||
|
|
||||||
|
// Will = RSN + PRS = 4 + 3 = 7, Difficulty = 20 - 7 = 13
|
||||||
|
expect(testActor.system.saves.will.difficulty).to.equal(13);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calculates Skill difficulties based on training", async () => {
|
||||||
|
// Untrained: 20 - stat
|
||||||
|
// Trained: 20 - (stat × 2)
|
||||||
|
|
||||||
|
// Arcana (RSN 4), untrained: 20 - 4 = 16
|
||||||
|
expect(testActor.system.skills.arcana.difficulty).to.equal(16);
|
||||||
|
|
||||||
|
// Set trained
|
||||||
|
await testActor.update({ "system.skills.arcana.trained": true });
|
||||||
|
// Trained: 20 - (4 × 2) = 12
|
||||||
|
expect(testActor.system.skills.arcana.difficulty).to.equal(12);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Resource Tracking", () => {
|
||||||
|
it("tracks Fatigue from 0 to 5", async () => {
|
||||||
|
expect(testActor.system.resources.fatigue.value).to.equal(0);
|
||||||
|
|
||||||
|
await testActor.update({ "system.resources.fatigue.value": 3 });
|
||||||
|
expect(testActor.system.resources.fatigue.value).to.equal(3);
|
||||||
|
|
||||||
|
// Fatigue reduces item slots
|
||||||
|
expect(testActor.system.itemSlots.max).to.equal(10); // 13 - 3
|
||||||
|
});
|
||||||
|
|
||||||
|
it("tracks Current Luck up to Luck stat", async () => {
|
||||||
|
expect(testActor.system.resources.luck.max).to.equal(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ displayName: "Vagabond: Character Actors" }
|
||||||
|
);
|
||||||
|
|
||||||
|
quenchRunner.registerBatch(
|
||||||
|
"vagabond.actors.npc",
|
||||||
|
(context) => {
|
||||||
|
const { describe, it, expect, beforeEach, afterEach } = context;
|
||||||
|
|
||||||
|
let testNPC = null;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
testNPC = await Actor.create({
|
||||||
|
name: "Test Goblin",
|
||||||
|
type: "npc",
|
||||||
|
system: {
|
||||||
|
hd: 1,
|
||||||
|
hp: { value: 4, max: 4 },
|
||||||
|
tl: 0.8,
|
||||||
|
zone: "frontline",
|
||||||
|
morale: 6,
|
||||||
|
armor: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
if (testNPC) {
|
||||||
|
await testNPC.delete();
|
||||||
|
testNPC = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("NPC Stats", () => {
|
||||||
|
it("stores HD and HP independently", async () => {
|
||||||
|
expect(testNPC.system.hd).to.equal(1);
|
||||||
|
expect(testNPC.system.hp.max).to.equal(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stores Threat Level (TL)", async () => {
|
||||||
|
expect(testNPC.system.tl).to.equal(0.8);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stores Zone behavior", async () => {
|
||||||
|
expect(testNPC.system.zone).to.equal("frontline");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stores Morale score", async () => {
|
||||||
|
expect(testNPC.system.morale).to.equal(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ displayName: "Vagabond: NPC Actors" }
|
||||||
|
);
|
||||||
|
}
|
||||||
74
module/tests/quench-init.mjs
Normal file
74
module/tests/quench-init.mjs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* Quench Test Registration
|
||||||
|
*
|
||||||
|
* This module registers all Vagabond RPG test batches with the Quench testing framework.
|
||||||
|
* Tests are organized by domain: actors, items, rolls, effects.
|
||||||
|
*
|
||||||
|
* @see https://github.com/Ethaks/FVTT-Quench
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Import test modules
|
||||||
|
// import { registerActorTests } from "./actor.test.mjs";
|
||||||
|
// import { registerItemTests } from "./item.test.mjs";
|
||||||
|
// import { registerRollTests } from "./rolls.test.mjs";
|
||||||
|
// import { registerEffectTests } from "./effects.test.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register all Vagabond test batches with Quench
|
||||||
|
* Called from the main system init when Quench is available
|
||||||
|
* @param {Quench} quenchRunner - The Quench test runner instance
|
||||||
|
*/
|
||||||
|
export function registerQuenchTests(quenchRunner) {
|
||||||
|
// Register placeholder test batch to verify Quench integration
|
||||||
|
quenchRunner.registerBatch(
|
||||||
|
"vagabond.sanity",
|
||||||
|
(context) => {
|
||||||
|
const { describe, it, expect } = context;
|
||||||
|
|
||||||
|
describe("Vagabond System Sanity Checks", () => {
|
||||||
|
it("should have CONFIG.VAGABOND defined", () => {
|
||||||
|
expect(CONFIG.VAGABOND).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have all six stats configured", () => {
|
||||||
|
const stats = CONFIG.VAGABOND.stats;
|
||||||
|
expect(stats).to.have.property("might");
|
||||||
|
expect(stats).to.have.property("dexterity");
|
||||||
|
expect(stats).to.have.property("awareness");
|
||||||
|
expect(stats).to.have.property("reason");
|
||||||
|
expect(stats).to.have.property("presence");
|
||||||
|
expect(stats).to.have.property("luck");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have all twelve skills configured", () => {
|
||||||
|
const skills = CONFIG.VAGABOND.skills;
|
||||||
|
expect(Object.keys(skills)).to.have.length(12);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have spell delivery types configured", () => {
|
||||||
|
const delivery = CONFIG.VAGABOND.spellDelivery;
|
||||||
|
expect(delivery.touch.cost).to.equal(0);
|
||||||
|
expect(delivery.aura.cost).to.equal(2);
|
||||||
|
expect(delivery.sphere.cost).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should calculate correct speed by DEX", () => {
|
||||||
|
const speedByDex = CONFIG.VAGABOND.speedByDex;
|
||||||
|
expect(speedByDex[2]).to.equal(25);
|
||||||
|
expect(speedByDex[4]).to.equal(30);
|
||||||
|
expect(speedByDex[6]).to.equal(35);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ displayName: "Vagabond: Sanity Checks" }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register domain-specific test batches (uncomment as implemented)
|
||||||
|
// registerActorTests(quenchRunner);
|
||||||
|
// registerItemTests(quenchRunner);
|
||||||
|
// registerRollTests(quenchRunner);
|
||||||
|
// registerEffectTests(quenchRunner);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log("Vagabond RPG | Quench tests registered");
|
||||||
|
}
|
||||||
@ -17,6 +17,9 @@ import { VAGABOND } from "./helpers/config.mjs";
|
|||||||
// Import helper functions
|
// Import helper functions
|
||||||
// import { preloadHandlebarsTemplates } from "./helpers/templates.mjs";
|
// import { preloadHandlebarsTemplates } from "./helpers/templates.mjs";
|
||||||
|
|
||||||
|
// Import test registration (for Quench)
|
||||||
|
import { registerQuenchTests } from "./tests/quench-init.mjs";
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Foundry VTT Initialization */
|
/* Foundry VTT Initialization */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
@ -24,7 +27,8 @@ import { VAGABOND } from "./helpers/config.mjs";
|
|||||||
/**
|
/**
|
||||||
* Init hook - runs once when Foundry initializes
|
* Init hook - runs once when Foundry initializes
|
||||||
*/
|
*/
|
||||||
Hooks.once("init", function() {
|
Hooks.once("init", () => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log("Vagabond RPG | Initializing Vagabond RPG System");
|
console.log("Vagabond RPG | Initializing Vagabond RPG System");
|
||||||
|
|
||||||
// Add custom constants for configuration
|
// Add custom constants for configuration
|
||||||
@ -59,7 +63,8 @@ Hooks.once("init", function() {
|
|||||||
/**
|
/**
|
||||||
* Ready hook - runs when Foundry is fully loaded
|
* Ready hook - runs when Foundry is fully loaded
|
||||||
*/
|
*/
|
||||||
Hooks.once("ready", function() {
|
Hooks.once("ready", () => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log("Vagabond RPG | System Ready");
|
console.log("Vagabond RPG | System Ready");
|
||||||
|
|
||||||
// Display welcome message for GMs
|
// Display welcome message for GMs
|
||||||
@ -76,64 +81,68 @@ Hooks.once("ready", function() {
|
|||||||
/**
|
/**
|
||||||
* Define Handlebars helpers used throughout the system
|
* Define Handlebars helpers used throughout the system
|
||||||
*/
|
*/
|
||||||
Hooks.once("init", function() {
|
Hooks.once("init", () => {
|
||||||
// Multiply helper for formulas
|
// Multiply helper for formulas
|
||||||
Handlebars.registerHelper("multiply", function(a, b) {
|
Handlebars.registerHelper("multiply", (a, b) => Number(a) * Number(b));
|
||||||
return Number(a) * Number(b);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Subtract helper
|
// Subtract helper
|
||||||
Handlebars.registerHelper("subtract", function(a, b) {
|
Handlebars.registerHelper("subtract", (a, b) => Number(a) - Number(b));
|
||||||
return Number(a) - Number(b);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Calculate difficulty (20 - stat or 20 - stat*2 if trained)
|
// Calculate difficulty (20 - stat or 20 - stat*2 if trained)
|
||||||
Handlebars.registerHelper("difficulty", function(stat, trained) {
|
Handlebars.registerHelper("difficulty", (stat, trained) => {
|
||||||
const statValue = Number(stat) || 0;
|
const statValue = Number(stat) || 0;
|
||||||
return trained ? 20 - (statValue * 2) : 20 - statValue;
|
return trained ? 20 - statValue * 2 : 20 - statValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if value equals comparison
|
// Check if value equals comparison
|
||||||
Handlebars.registerHelper("eq", function(a, b) {
|
Handlebars.registerHelper("eq", (a, b) => a === b);
|
||||||
return a === b;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if value is greater than
|
// Check if value is greater than
|
||||||
Handlebars.registerHelper("gt", function(a, b) {
|
Handlebars.registerHelper("gt", (a, b) => Number(a) > Number(b));
|
||||||
return Number(a) > Number(b);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if value is less than
|
// Check if value is less than
|
||||||
Handlebars.registerHelper("lt", function(a, b) {
|
Handlebars.registerHelper("lt", (a, b) => Number(a) < Number(b));
|
||||||
return Number(a) < Number(b);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Concatenate strings
|
// Concatenate strings
|
||||||
Handlebars.registerHelper("concat", function(...args) {
|
Handlebars.registerHelper("concat", (...args) => {
|
||||||
// Remove the Handlebars options object from args
|
// Remove the Handlebars options object from args
|
||||||
args.pop();
|
args.pop();
|
||||||
return args.join("");
|
return args.join("");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Capitalize first letter
|
// Capitalize first letter
|
||||||
Handlebars.registerHelper("capitalize", function(str) {
|
Handlebars.registerHelper("capitalize", (str) => {
|
||||||
if (typeof str !== "string") return "";
|
if (typeof str !== "string") return "";
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Format number with sign (+/-)
|
// Format number with sign (+/-)
|
||||||
Handlebars.registerHelper("signedNumber", function(num) {
|
Handlebars.registerHelper("signedNumber", (num) => {
|
||||||
const n = Number(num) || 0;
|
const n = Number(num) || 0;
|
||||||
return n >= 0 ? `+${n}` : `${n}`;
|
return n >= 0 ? `+${n}` : `${n}`;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Quench Test Registration */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register tests with the Quench testing framework if available.
|
||||||
|
* Quench provides in-Foundry testing using Mocha + Chai.
|
||||||
|
* @see https://github.com/Ethaks/FVTT-Quench
|
||||||
|
*/
|
||||||
|
Hooks.once("quenchReady", (quenchRunner) => {
|
||||||
|
registerQuenchTests(quenchRunner);
|
||||||
|
});
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Hot Reload Support (Development) */
|
/* Hot Reload Support (Development) */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
if (import.meta.hot) {
|
if (import.meta.hot) {
|
||||||
import.meta.hot.accept((newModule) => {
|
import.meta.hot.accept((_newModule) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log("Vagabond RPG | Hot reload triggered");
|
console.log("Vagabond RPG | Hot reload triggered");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
2421
package-lock.json
generated
Normal file
2421
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@ -6,6 +6,10 @@
|
|||||||
"build": "sass styles/scss/vagabond.scss styles/vagabond.css --style=compressed",
|
"build": "sass styles/scss/vagabond.scss styles/vagabond.css --style=compressed",
|
||||||
"watch": "sass styles/scss/vagabond.scss styles/vagabond.css --watch --style=expanded --source-map",
|
"watch": "sass styles/scss/vagabond.scss styles/vagabond.css --watch --style=expanded --source-map",
|
||||||
"lint": "eslint module/",
|
"lint": "eslint module/",
|
||||||
|
"lint:fix": "eslint module/ --fix",
|
||||||
|
"format": "prettier --write \"module/**/*.mjs\" \"styles/**/*.scss\" \"lang/**/*.json\" \"*.json\" \"*.md\"",
|
||||||
|
"format:check": "prettier --check \"module/**/*.mjs\" \"styles/**/*.scss\" \"lang/**/*.json\" \"*.json\" \"*.md\"",
|
||||||
|
"prepare": "husky",
|
||||||
"release": "npm run build && zip -r vagabond.zip system.json module/ templates/ styles/vagabond.css lang/ packs/ assets/ LICENSE README.md"
|
"release": "npm run build && zip -r vagabond.zip system.json module/ templates/ styles/vagabond.css lang/ packs/ assets/ LICENSE README.md"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -19,8 +23,26 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/calcorum/vagabond-rpg-foundryvtt#readme",
|
"homepage": "https://github.com/calcorum/vagabond-rpg-foundryvtt#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"sass": "^1.69.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint": "^8.56.0"
|
"husky": "^9.1.0",
|
||||||
|
"lint-staged": "^15.2.0",
|
||||||
|
"prettier": "^3.3.0",
|
||||||
|
"sass": "^1.69.0"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"module/**/*.mjs": [
|
||||||
|
"eslint --fix",
|
||||||
|
"prettier --write"
|
||||||
|
],
|
||||||
|
"styles/**/*.scss": [
|
||||||
|
"prettier --write"
|
||||||
|
],
|
||||||
|
"lang/**/*.json": [
|
||||||
|
"prettier --write"
|
||||||
|
],
|
||||||
|
"*.{json,md}": [
|
||||||
|
"prettier --write"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,9 @@
|
|||||||
color: $color-text-primary;
|
color: $color-text-primary;
|
||||||
|
|
||||||
// Box sizing
|
// Box sizing
|
||||||
*, *::before, *::after {
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +31,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Headings
|
// Headings
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
font-family: $font-family-header;
|
font-family: $font-family-header;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
line-height: $line-height-tight;
|
line-height: $line-height-tight;
|
||||||
@ -37,12 +44,24 @@
|
|||||||
margin: 0 0 $spacing-2 0;
|
margin: 0 0 $spacing-2 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 { font-size: $font-size-4xl; }
|
h1 {
|
||||||
h2 { font-size: $font-size-3xl; }
|
font-size: $font-size-4xl;
|
||||||
h3 { font-size: $font-size-2xl; }
|
}
|
||||||
h4 { font-size: $font-size-xl; }
|
h2 {
|
||||||
h5 { font-size: $font-size-lg; }
|
font-size: $font-size-3xl;
|
||||||
h6 { font-size: $font-size-base; }
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: $font-size-2xl;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: $font-size-xl;
|
||||||
|
}
|
||||||
|
h5 {
|
||||||
|
font-size: $font-size-lg;
|
||||||
|
}
|
||||||
|
h6 {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
}
|
||||||
|
|
||||||
// Paragraphs
|
// Paragraphs
|
||||||
p {
|
p {
|
||||||
@ -54,7 +73,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lists
|
// Lists
|
||||||
ul, ol {
|
ul,
|
||||||
|
ol {
|
||||||
margin: 0 0 $spacing-4 0;
|
margin: 0 0 $spacing-4 0;
|
||||||
padding-left: $spacing-6;
|
padding-left: $spacing-6;
|
||||||
}
|
}
|
||||||
@ -76,7 +96,8 @@
|
|||||||
margin-bottom: $spacing-4;
|
margin-bottom: $spacing-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
th, td {
|
th,
|
||||||
|
td {
|
||||||
padding: $spacing-2 $spacing-3;
|
padding: $spacing-2 $spacing-3;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 1px solid $color-border-light;
|
border-bottom: 1px solid $color-border-light;
|
||||||
@ -123,14 +144,13 @@
|
|||||||
.sheet.vagabond {
|
.sheet.vagabond {
|
||||||
@include custom-scrollbar;
|
@include custom-scrollbar;
|
||||||
background: $color-parchment;
|
background: $color-parchment;
|
||||||
background-image:
|
background-image: linear-gradient(
|
||||||
linear-gradient(
|
to bottom,
|
||||||
to bottom,
|
rgba($color-parchment-light, 0.3) 0%,
|
||||||
rgba($color-parchment-light, 0.3) 0%,
|
transparent 5%,
|
||||||
transparent 5%,
|
transparent 95%,
|
||||||
transparent 95%,
|
rgba($color-parchment-dark, 0.3) 100%
|
||||||
rgba($color-parchment-dark, 0.3) 100%
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window header customization
|
// Window header customization
|
||||||
|
|||||||
@ -11,29 +11,29 @@ $color-parchment-dark: #d4c4a8;
|
|||||||
$color-parchment-darker: #c4b08c;
|
$color-parchment-darker: #c4b08c;
|
||||||
|
|
||||||
// Text colors (high contrast)
|
// Text colors (high contrast)
|
||||||
$color-text-primary: #2c2416; // Main text - dark brown/black
|
$color-text-primary: #2c2416; // Main text - dark brown/black
|
||||||
$color-text-secondary: #4a3f2f; // Secondary text
|
$color-text-secondary: #4a3f2f; // Secondary text
|
||||||
$color-text-muted: #6b5d4d; // Muted/disabled text
|
$color-text-muted: #6b5d4d; // Muted/disabled text
|
||||||
$color-text-inverse: #f5f0e1; // Text on dark backgrounds
|
$color-text-inverse: #f5f0e1; // Text on dark backgrounds
|
||||||
|
|
||||||
// Accent colors
|
// Accent colors
|
||||||
$color-accent-primary: #8b4513; // Saddle brown - primary actions
|
$color-accent-primary: #8b4513; // Saddle brown - primary actions
|
||||||
$color-accent-secondary: #654321; // Dark brown - secondary
|
$color-accent-secondary: #654321; // Dark brown - secondary
|
||||||
$color-accent-highlight: #cd853f; // Peru - highlights/hover
|
$color-accent-highlight: #cd853f; // Peru - highlights/hover
|
||||||
|
|
||||||
// Semantic colors
|
// Semantic colors
|
||||||
$color-success: #2d5a27; // Dark green - success/healing
|
$color-success: #2d5a27; // Dark green - success/healing
|
||||||
$color-danger: #8b0000; // Dark red - damage/danger
|
$color-danger: #8b0000; // Dark red - damage/danger
|
||||||
$color-warning: #b8860b; // Dark goldenrod - warnings
|
$color-warning: #b8860b; // Dark goldenrod - warnings
|
||||||
$color-info: #2f4f4f; // Dark slate gray - info
|
$color-info: #2f4f4f; // Dark slate gray - info
|
||||||
|
|
||||||
// Stat colors (for visual distinction)
|
// Stat colors (for visual distinction)
|
||||||
$color-might: #8b0000; // Red - strength/power
|
$color-might: #8b0000; // Red - strength/power
|
||||||
$color-dexterity: #228b22; // Green - agility
|
$color-dexterity: #228b22; // Green - agility
|
||||||
$color-awareness: #4169e1; // Royal blue - perception
|
$color-awareness: #4169e1; // Royal blue - perception
|
||||||
$color-reason: #9932cc; // Purple - intellect
|
$color-reason: #9932cc; // Purple - intellect
|
||||||
$color-presence: #daa520; // Goldenrod - charisma
|
$color-presence: #daa520; // Goldenrod - charisma
|
||||||
$color-luck: #20b2aa; // Light sea green - fortune
|
$color-luck: #20b2aa; // Light sea green - fortune
|
||||||
|
|
||||||
// Borders
|
// Borders
|
||||||
$color-border: #8b7355;
|
$color-border: #8b7355;
|
||||||
@ -46,19 +46,19 @@ $shadow-medium: rgba(0, 0, 0, 0.2);
|
|||||||
$shadow-dark: rgba(0, 0, 0, 0.3);
|
$shadow-dark: rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
// Typography
|
// Typography
|
||||||
$font-family-header: 'Modesto Condensed', 'Roboto Slab', 'Georgia', serif;
|
$font-family-header: "Modesto Condensed", "Roboto Slab", "Georgia", serif;
|
||||||
$font-family-body: 'Signika', 'Roboto', 'Helvetica Neue', sans-serif;
|
$font-family-body: "Signika", "Roboto", "Helvetica Neue", sans-serif;
|
||||||
$font-family-mono: 'Roboto Mono', 'Consolas', monospace;
|
$font-family-mono: "Roboto Mono", "Consolas", monospace;
|
||||||
|
|
||||||
// Font sizes (using rem for accessibility)
|
// Font sizes (using rem for accessibility)
|
||||||
$font-size-xs: 0.75rem; // 12px
|
$font-size-xs: 0.75rem; // 12px
|
||||||
$font-size-sm: 0.875rem; // 14px
|
$font-size-sm: 0.875rem; // 14px
|
||||||
$font-size-base: 1rem; // 16px
|
$font-size-base: 1rem; // 16px
|
||||||
$font-size-lg: 1.125rem; // 18px
|
$font-size-lg: 1.125rem; // 18px
|
||||||
$font-size-xl: 1.25rem; // 20px
|
$font-size-xl: 1.25rem; // 20px
|
||||||
$font-size-2xl: 1.5rem; // 24px
|
$font-size-2xl: 1.5rem; // 24px
|
||||||
$font-size-3xl: 1.875rem; // 30px
|
$font-size-3xl: 1.875rem; // 30px
|
||||||
$font-size-4xl: 2.25rem; // 36px
|
$font-size-4xl: 2.25rem; // 36px
|
||||||
|
|
||||||
// Font weights
|
// Font weights
|
||||||
$font-weight-normal: 400;
|
$font-weight-normal: 400;
|
||||||
@ -73,14 +73,14 @@ $line-height-relaxed: 1.75;
|
|||||||
|
|
||||||
// Spacing scale
|
// Spacing scale
|
||||||
$spacing-0: 0;
|
$spacing-0: 0;
|
||||||
$spacing-1: 0.25rem; // 4px
|
$spacing-1: 0.25rem; // 4px
|
||||||
$spacing-2: 0.5rem; // 8px
|
$spacing-2: 0.5rem; // 8px
|
||||||
$spacing-3: 0.75rem; // 12px
|
$spacing-3: 0.75rem; // 12px
|
||||||
$spacing-4: 1rem; // 16px
|
$spacing-4: 1rem; // 16px
|
||||||
$spacing-5: 1.25rem; // 20px
|
$spacing-5: 1.25rem; // 20px
|
||||||
$spacing-6: 1.5rem; // 24px
|
$spacing-6: 1.5rem; // 24px
|
||||||
$spacing-8: 2rem; // 32px
|
$spacing-8: 2rem; // 32px
|
||||||
$spacing-10: 2.5rem; // 40px
|
$spacing-10: 2.5rem; // 40px
|
||||||
|
|
||||||
// Border radius
|
// Border radius
|
||||||
$radius-sm: 2px;
|
$radius-sm: 2px;
|
||||||
|
|||||||
@ -144,7 +144,8 @@
|
|||||||
|
|
||||||
// Animation
|
// Animation
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% {
|
0%,
|
||||||
|
100% {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
|
|||||||
@ -97,7 +97,8 @@
|
|||||||
margin-right: $spacing-2;
|
margin-right: $spacing-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, select {
|
input,
|
||||||
|
select {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,23 +2,23 @@
|
|||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
@import 'variables';
|
@import "variables";
|
||||||
@import 'mixins';
|
@import "mixins";
|
||||||
|
|
||||||
// Base styles
|
// Base styles
|
||||||
@import 'base';
|
@import "base";
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
@import 'components/buttons';
|
@import "components/buttons";
|
||||||
@import 'components/forms';
|
@import "components/forms";
|
||||||
@import 'components/tabs';
|
@import "components/tabs";
|
||||||
|
|
||||||
// Sheets
|
// Sheets
|
||||||
@import 'sheets/actor-sheet';
|
@import "sheets/actor-sheet";
|
||||||
@import 'sheets/item-sheet';
|
@import "sheets/item-sheet";
|
||||||
|
|
||||||
// Dialogs
|
// Dialogs
|
||||||
@import 'dialogs/roll-dialog';
|
@import "dialogs/roll-dialog";
|
||||||
|
|
||||||
// Chat
|
// Chat
|
||||||
@import 'chat/chat-cards';
|
@import "chat/chat-cards";
|
||||||
|
|||||||
11
system.json
11
system.json
@ -109,7 +109,16 @@
|
|||||||
{
|
{
|
||||||
"name": "Vagabond RPG",
|
"name": "Vagabond RPG",
|
||||||
"sorting": "a",
|
"sorting": "a",
|
||||||
"packs": ["ancestries", "classes", "spells", "perks", "weapons", "armor", "equipment", "bestiary"]
|
"packs": [
|
||||||
|
"ancestries",
|
||||||
|
"classes",
|
||||||
|
"spells",
|
||||||
|
"perks",
|
||||||
|
"weapons",
|
||||||
|
"armor",
|
||||||
|
"equipment",
|
||||||
|
"bestiary"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"socket": false,
|
"socket": false,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user