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
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a complete Foundry VTT v13 system implementation for Vagabond RPG (Pulp Fantasy TTRPG).
|
||||
|
||||
## Key Architecture Decisions
|
||||
|
||||
### Data Models (Foundry v13 style)
|
||||
|
||||
- Use TypeDataModel classes in `module/data/` for Actor and Item schemas
|
||||
- Character stats: Might, Dexterity, Awareness, Reason, Presence, Luck (range 2-7)
|
||||
- Derived values calculated in `prepareData()`: HP, Speed, Save difficulties, Skill difficulties
|
||||
|
||||
### 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
|
||||
- Hinder: -d6 penalty die
|
||||
- Crit: Natural 20 by default, but threshold can be modified per-skill by Active Effects
|
||||
- Exploding dice: d6! notation for certain abilities
|
||||
|
||||
### Spell Casting
|
||||
|
||||
- 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)
|
||||
- Duration: Instant (free), Focus (ongoing), Continual (permanent)
|
||||
- Cast dialog must calculate and display total mana cost before casting
|
||||
|
||||
### Class System
|
||||
|
||||
- Classes are Items with progression tables
|
||||
- When dragged to character, creates Active Effects for current level
|
||||
- On level up, update Active Effects to grant new features
|
||||
- Supports future multiclassing by allowing multiple class items
|
||||
|
||||
### Crit Threshold System
|
||||
|
||||
- Each skill/action has a `critThreshold` field (default 20)
|
||||
- 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
|
||||
- Gunslinger's Deadeye dynamically reduces on consecutive hits
|
||||
|
||||
### Resources
|
||||
- HP: max = Might * Level
|
||||
|
||||
- HP: max = Might \* Level
|
||||
- Mana: class-dependent, max from class progression
|
||||
- Luck: equals Luck stat, refreshes on rest
|
||||
- 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
|
||||
|
||||
## File Naming Conventions
|
||||
|
||||
- Main system entry: `vagabond.mjs`
|
||||
- Document classes: `VagabondActor.mjs`, `VagabondItem.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`
|
||||
|
||||
## Testing Commands
|
||||
|
||||
```bash
|
||||
# Start local Foundry
|
||||
docker compose up -d
|
||||
@ -63,7 +72,9 @@ docker compose logs -f foundry
|
||||
```
|
||||
|
||||
## Reference Data Location
|
||||
|
||||
Game rules and content are documented in NoteDiscovery under `gaming/vagabond-rpg/`:
|
||||
|
||||
- `core-mechanics.md` - Stats, checks, dice, HP
|
||||
- `combat.md` - Actions, movement, defending, zones
|
||||
- `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`
|
||||
|
||||
## Project Roadmap
|
||||
|
||||
See `PROJECT_ROADMAP.json` for complete task breakdown with dependencies.
|
||||
|
||||
## Style Guidelines
|
||||
|
||||
- Parchment color scheme with high contrast (WCAG AA compliant)
|
||||
- Match official Hero Record layout where possible
|
||||
- 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
|
||||
|
||||
### From Foundry
|
||||
|
||||
1. Open Foundry VTT Setup
|
||||
2. Navigate to Game Systems
|
||||
3. Click "Install System"
|
||||
@ -24,6 +25,7 @@ A complete Foundry VTT v13 system implementation for **Vagabond RPG** - Pulp Fan
|
||||
```
|
||||
|
||||
### Manual Installation
|
||||
|
||||
1. Download the latest release from GitHub
|
||||
2. Extract to `Data/systems/vagabond/`
|
||||
3. Restart Foundry VTT
|
||||
@ -31,10 +33,12 @@ A complete Foundry VTT v13 system implementation for **Vagabond RPG** - Pulp Fan
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+
|
||||
- Docker & Docker Compose (for local Foundry instance)
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/calcorum/vagabond-rpg-foundryvtt.git
|
||||
@ -53,6 +57,7 @@ docker compose up -d
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
vagabond-rpg-foundryvtt/
|
||||
├── module/ # JavaScript modules
|
||||
@ -76,6 +81,7 @@ vagabond-rpg-foundryvtt/
|
||||
```
|
||||
|
||||
### Building Styles
|
||||
|
||||
```bash
|
||||
# One-time build
|
||||
npm run build
|
||||
@ -85,6 +91,7 @@ npm run watch
|
||||
```
|
||||
|
||||
### Creating a Release
|
||||
|
||||
```bash
|
||||
npm run release
|
||||
```
|
||||
|
||||
@ -14,7 +14,7 @@ VAGABOND.stats = {
|
||||
awareness: "VAGABOND.StatAwareness",
|
||||
reason: "VAGABOND.StatReason",
|
||||
presence: "VAGABOND.StatPresence",
|
||||
luck: "VAGABOND.StatLuck"
|
||||
luck: "VAGABOND.StatLuck",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -26,7 +26,7 @@ VAGABOND.statsAbbr = {
|
||||
awareness: "VAGABOND.StatAwarenessAbbr",
|
||||
reason: "VAGABOND.StatReasonAbbr",
|
||||
presence: "VAGABOND.StatPresenceAbbr",
|
||||
luck: "VAGABOND.StatLuckAbbr"
|
||||
luck: "VAGABOND.StatLuckAbbr",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -44,7 +44,7 @@ VAGABOND.skills = {
|
||||
mysticism: { label: "VAGABOND.SkillMysticism", stat: "awareness" },
|
||||
performance: { label: "VAGABOND.SkillPerformance", stat: "presence" },
|
||||
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" },
|
||||
brawl: { label: "VAGABOND.AttackBrawl", stat: "might" },
|
||||
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 = {
|
||||
reflex: { label: "VAGABOND.SaveReflex", stats: ["dexterity", "awareness"] },
|
||||
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 },
|
||||
glyph: { label: "VAGABOND.DeliveryGlyph", 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 = {
|
||||
instant: { label: "VAGABOND.DurationInstant", focus: false },
|
||||
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",
|
||||
shock: "VAGABOND.DamageShock",
|
||||
poison: "VAGABOND.DamagePoison",
|
||||
acid: "VAGABOND.DamageAcid"
|
||||
acid: "VAGABOND.DamageAcid",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -115,7 +115,7 @@ VAGABOND.weaponProperties = {
|
||||
loading: "VAGABOND.PropertyLoading",
|
||||
brawl: "VAGABOND.PropertyBrawl",
|
||||
crude: "VAGABOND.PropertyCrude",
|
||||
versatile: "VAGABOND.PropertyVersatile"
|
||||
versatile: "VAGABOND.PropertyVersatile",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -125,7 +125,7 @@ VAGABOND.gripTypes = {
|
||||
"1h": "VAGABOND.Grip1H",
|
||||
"2h": "VAGABOND.Grip2H",
|
||||
versatile: "VAGABOND.GripVersatile",
|
||||
fist: "VAGABOND.GripFist"
|
||||
fist: "VAGABOND.GripFist",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -134,7 +134,7 @@ VAGABOND.gripTypes = {
|
||||
VAGABOND.armorTypes = {
|
||||
light: "VAGABOND.ArmorLight",
|
||||
heavy: "VAGABOND.ArmorHeavy",
|
||||
shield: "VAGABOND.ArmorShield"
|
||||
shield: "VAGABOND.ArmorShield",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -146,7 +146,7 @@ VAGABOND.sizes = {
|
||||
large: "VAGABOND.SizeLarge",
|
||||
huge: "VAGABOND.SizeHuge",
|
||||
giant: "VAGABOND.SizeGiant",
|
||||
colossal: "VAGABOND.SizeColossal"
|
||||
colossal: "VAGABOND.SizeColossal",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -160,7 +160,7 @@ VAGABOND.beingTypes = {
|
||||
beast: "VAGABOND.BeingBeast",
|
||||
outer: "VAGABOND.BeingOuter",
|
||||
primordial: "VAGABOND.BeingPrimordial",
|
||||
undead: "VAGABOND.BeingUndead"
|
||||
undead: "VAGABOND.BeingUndead",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -169,7 +169,7 @@ VAGABOND.beingTypes = {
|
||||
VAGABOND.zones = {
|
||||
frontline: "VAGABOND.ZoneFrontline",
|
||||
midline: "VAGABOND.ZoneMidline",
|
||||
backline: "VAGABOND.ZoneBackline"
|
||||
backline: "VAGABOND.ZoneBackline",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -186,7 +186,7 @@ VAGABOND.speedByDex = {
|
||||
4: 30,
|
||||
5: 30,
|
||||
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 { preloadHandlebarsTemplates } from "./helpers/templates.mjs";
|
||||
|
||||
// Import test registration (for Quench)
|
||||
import { registerQuenchTests } from "./tests/quench-init.mjs";
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Foundry VTT Initialization */
|
||||
/* -------------------------------------------- */
|
||||
@ -24,7 +27,8 @@ import { VAGABOND } from "./helpers/config.mjs";
|
||||
/**
|
||||
* 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");
|
||||
|
||||
// Add custom constants for configuration
|
||||
@ -59,7 +63,8 @@ Hooks.once("init", function() {
|
||||
/**
|
||||
* 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");
|
||||
|
||||
// Display welcome message for GMs
|
||||
@ -76,64 +81,68 @@ Hooks.once("ready", function() {
|
||||
/**
|
||||
* Define Handlebars helpers used throughout the system
|
||||
*/
|
||||
Hooks.once("init", function() {
|
||||
Hooks.once("init", () => {
|
||||
// Multiply helper for formulas
|
||||
Handlebars.registerHelper("multiply", function(a, b) {
|
||||
return Number(a) * Number(b);
|
||||
});
|
||||
Handlebars.registerHelper("multiply", (a, b) => Number(a) * Number(b));
|
||||
|
||||
// Subtract helper
|
||||
Handlebars.registerHelper("subtract", function(a, b) {
|
||||
return Number(a) - Number(b);
|
||||
});
|
||||
Handlebars.registerHelper("subtract", (a, b) => Number(a) - Number(b));
|
||||
|
||||
// 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;
|
||||
return trained ? 20 - (statValue * 2) : 20 - statValue;
|
||||
return trained ? 20 - statValue * 2 : 20 - statValue;
|
||||
});
|
||||
|
||||
// Check if value equals comparison
|
||||
Handlebars.registerHelper("eq", function(a, b) {
|
||||
return a === b;
|
||||
});
|
||||
Handlebars.registerHelper("eq", (a, b) => a === b);
|
||||
|
||||
// Check if value is greater than
|
||||
Handlebars.registerHelper("gt", function(a, b) {
|
||||
return Number(a) > Number(b);
|
||||
});
|
||||
Handlebars.registerHelper("gt", (a, b) => Number(a) > Number(b));
|
||||
|
||||
// Check if value is less than
|
||||
Handlebars.registerHelper("lt", function(a, b) {
|
||||
return Number(a) < Number(b);
|
||||
});
|
||||
Handlebars.registerHelper("lt", (a, b) => Number(a) < Number(b));
|
||||
|
||||
// Concatenate strings
|
||||
Handlebars.registerHelper("concat", function(...args) {
|
||||
Handlebars.registerHelper("concat", (...args) => {
|
||||
// Remove the Handlebars options object from args
|
||||
args.pop();
|
||||
return args.join("");
|
||||
});
|
||||
|
||||
// Capitalize first letter
|
||||
Handlebars.registerHelper("capitalize", function(str) {
|
||||
Handlebars.registerHelper("capitalize", (str) => {
|
||||
if (typeof str !== "string") return "";
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
});
|
||||
|
||||
// Format number with sign (+/-)
|
||||
Handlebars.registerHelper("signedNumber", function(num) {
|
||||
Handlebars.registerHelper("signedNumber", (num) => {
|
||||
const n = Number(num) || 0;
|
||||
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) */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
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");
|
||||
});
|
||||
}
|
||||
|
||||
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",
|
||||
"watch": "sass styles/scss/vagabond.scss styles/vagabond.css --watch --style=expanded --source-map",
|
||||
"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"
|
||||
},
|
||||
"repository": {
|
||||
@ -19,8 +23,26 @@
|
||||
},
|
||||
"homepage": "https://github.com/calcorum/vagabond-rpg-foundryvtt#readme",
|
||||
"devDependencies": {
|
||||
"sass": "^1.69.0",
|
||||
"eslint": "^8.56.0"
|
||||
"eslint": "^8.57.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"
|
||||
}
|
||||
|
||||
@ -10,7 +10,9 @@
|
||||
color: $color-text-primary;
|
||||
|
||||
// Box sizing
|
||||
*, *::before, *::after {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@ -29,7 +31,12 @@
|
||||
}
|
||||
|
||||
// Headings
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: $font-family-header;
|
||||
font-weight: $font-weight-bold;
|
||||
line-height: $line-height-tight;
|
||||
@ -37,12 +44,24 @@
|
||||
margin: 0 0 $spacing-2 0;
|
||||
}
|
||||
|
||||
h1 { font-size: $font-size-4xl; }
|
||||
h2 { font-size: $font-size-3xl; }
|
||||
h3 { font-size: $font-size-2xl; }
|
||||
h4 { font-size: $font-size-xl; }
|
||||
h5 { font-size: $font-size-lg; }
|
||||
h6 { font-size: $font-size-base; }
|
||||
h1 {
|
||||
font-size: $font-size-4xl;
|
||||
}
|
||||
h2 {
|
||||
font-size: $font-size-3xl;
|
||||
}
|
||||
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
|
||||
p {
|
||||
@ -54,7 +73,8 @@
|
||||
}
|
||||
|
||||
// Lists
|
||||
ul, ol {
|
||||
ul,
|
||||
ol {
|
||||
margin: 0 0 $spacing-4 0;
|
||||
padding-left: $spacing-6;
|
||||
}
|
||||
@ -76,7 +96,8 @@
|
||||
margin-bottom: $spacing-4;
|
||||
}
|
||||
|
||||
th, td {
|
||||
th,
|
||||
td {
|
||||
padding: $spacing-2 $spacing-3;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid $color-border-light;
|
||||
@ -123,8 +144,7 @@
|
||||
.sheet.vagabond {
|
||||
@include custom-scrollbar;
|
||||
background: $color-parchment;
|
||||
background-image:
|
||||
linear-gradient(
|
||||
background-image: linear-gradient(
|
||||
to bottom,
|
||||
rgba($color-parchment-light, 0.3) 0%,
|
||||
transparent 5%,
|
||||
|
||||
@ -46,9 +46,9 @@ $shadow-medium: rgba(0, 0, 0, 0.2);
|
||||
$shadow-dark: rgba(0, 0, 0, 0.3);
|
||||
|
||||
// Typography
|
||||
$font-family-header: 'Modesto Condensed', 'Roboto Slab', 'Georgia', serif;
|
||||
$font-family-body: 'Signika', 'Roboto', 'Helvetica Neue', sans-serif;
|
||||
$font-family-mono: 'Roboto Mono', 'Consolas', monospace;
|
||||
$font-family-header: "Modesto Condensed", "Roboto Slab", "Georgia", serif;
|
||||
$font-family-body: "Signika", "Roboto", "Helvetica Neue", sans-serif;
|
||||
$font-family-mono: "Roboto Mono", "Consolas", monospace;
|
||||
|
||||
// Font sizes (using rem for accessibility)
|
||||
$font-size-xs: 0.75rem; // 12px
|
||||
|
||||
@ -144,7 +144,8 @@
|
||||
|
||||
// Animation
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
|
||||
@ -97,7 +97,8 @@
|
||||
margin-right: $spacing-2;
|
||||
}
|
||||
|
||||
input, select {
|
||||
input,
|
||||
select {
|
||||
flex: 1;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
@ -2,23 +2,23 @@
|
||||
// ==========================================
|
||||
|
||||
// Configuration
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
@import "variables";
|
||||
@import "mixins";
|
||||
|
||||
// Base styles
|
||||
@import 'base';
|
||||
@import "base";
|
||||
|
||||
// Components
|
||||
@import 'components/buttons';
|
||||
@import 'components/forms';
|
||||
@import 'components/tabs';
|
||||
@import "components/buttons";
|
||||
@import "components/forms";
|
||||
@import "components/tabs";
|
||||
|
||||
// Sheets
|
||||
@import 'sheets/actor-sheet';
|
||||
@import 'sheets/item-sheet';
|
||||
@import "sheets/actor-sheet";
|
||||
@import "sheets/item-sheet";
|
||||
|
||||
// Dialogs
|
||||
@import 'dialogs/roll-dialog';
|
||||
@import "dialogs/roll-dialog";
|
||||
|
||||
// Chat
|
||||
@import 'chat/chat-cards';
|
||||
@import "chat/chat-cards";
|
||||
|
||||
11
system.json
11
system.json
@ -109,7 +109,16 @@
|
||||
{
|
||||
"name": "Vagabond RPG",
|
||||
"sorting": "a",
|
||||
"packs": ["ancestries", "classes", "spells", "perks", "weapons", "armor", "equipment", "bestiary"]
|
||||
"packs": [
|
||||
"ancestries",
|
||||
"classes",
|
||||
"spells",
|
||||
"perks",
|
||||
"weapons",
|
||||
"armor",
|
||||
"equipment",
|
||||
"bestiary"
|
||||
]
|
||||
}
|
||||
],
|
||||
"socket": false,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user