Compare commits

..

78 Commits

Author SHA1 Message Date
uberwald 60b351f50d Fix spell save/defense again
Release Creation / build (release) Successful in 45s
2026-06-13 21:06:18 +02:00
uberwald ace726a1fc fix: use try/finally for spellDefense cleanup instead of delete
Release Creation / build (release) Successful in 47s
delete on game.lethalFantasy.spellDefense was breaking roll flow
on Foundry's proxied game object.  Use try/finally with
assignment to false instead, which is safe on any object type.

Initialize saveSpell local var from one-shot flag so D30
chart lookup correctly uses arcane_spell_defense for spell saves
without requiring user to click the pre-checked checkbox.
2026-06-13 16:51:52 +02:00
uberwald 67499bc199 fix: add missing arcane_spell_defense entry for D30=1
Release Creation / build (release) Successful in 49s
User spec lists D30=1 as 'Possible Spell Calamity or Catastrophe'
for Arcane Spell Defense but it was missing from the table.
2026-06-13 16:29:54 +02:00
uberwald 7eae95cbbd fix: use arcane spell defense D30 chart for spell saves
Release Creation / build (release) Successful in 43s
Bug: saveSpell local var initialized to false (line 142), while
dialog checkbox was pre-checked via dialogContext.saveSpell =
game.lethalFantasy.spellDefense.  If user didn't click the checkbox,
D30 call used SAVING_THROWS chart instead of ARCANE_SPELL_DEFENSE.

Also: game.lethalFantasy.spellDefense was set true before spell
defense rolls but never cleared, leaking to subsequent non-spell saves.

Fix: initialize saveSpell from the one-shot flag and delete it
immediately.  Dialog context now uses the local saveSpell variable
instead of re-reading the deleted flag.
2026-06-13 15:22:34 +02:00
uberwald 1b53bf9152 fix: resolve hud actor from hud.token not render context param
Release Creation / build (release) Successful in 41s
V2 renderTokenHUD passes (hud, html, data) where data is a render
context object — not a TokenDocument.  data.actor was undefined,
and data?.token?.actor was also undefined.  Use hud.token.actor
(or hud.object.actor) instead, which is the real PlaceableObject
with proper actor resolution.

Also fix html.find() → html.querySelector() for V2 HTMLElement.
2026-06-12 19:01:54 +02:00
uberwald 2570bf707e fix: prevent duplicate cross-client defense dialog, clear bleed on heal
- Only send attackBoosted socket when attackerHandledBonus || attackerHasNonGMOwner
  (GM→player: hook handles it, no socket needed; PC→PC: socket needed)
- Clear bleeding wounds when HP restored via token HUD heal buttons
2026-06-12 17:23:39 +02:00
uberwald cbeaaeec99 Final fixes and code review checks
Release Creation / build (release) Successful in 39s
2026-06-12 08:19:42 +02:00
uberwald 37badf2619 fix: attack/defense cross-client reaction flow
- C1: Stop D30 auto-roll on non-primary clients (caused divergence)
- C2: defenderOwner fallback to GM for monster defenders
- C3: Fix tie outcome in handleAttackBoosted (>= not >)
- C5: Convert handleAttackBoosted to while-loop (multi-reaction)
- C4/C6: shouldCreateMessage cross-client guard
- M2: Coordinate main flow defender dialog vs socket handler
- M3: Fresh grit/luck reads each socket handler iteration
- M4: Include defenseD30message in socket payload + re-process
- M5: Communicate attackerHandledBonus in socket payload
- i18n: Add missing COMBAT.* keys, fix weapon.hbs label localize
- d30_results_tables: Fix string typo
2026-06-12 02:51:59 +02:00
uberwald 5839616863 chore: add pushLDBtoYML/pullYMLtoLDB scripts, gitignore packs_src 2026-06-12 01:56:19 +02:00
uberwald 89298490ef fix: pre-check 'Save against spell' checkbox in template when saveSpell is true 2026-06-12 01:56:03 +02:00
uberwald bb42de19bd REmove unused file 2026-06-11 23:05:55 +02:00
uberwald 53f9c33419 chore: gitignore LevelDB internal files, stop tracking auto-generated LDB bookkeeping
Release Creation / build (release) Successful in 46s
2026-06-11 23:00:45 +02:00
uberwald 06eba5f835 fix: show spell tier dialog on character sheet cast; duplicate rollTarget to prevent Item mutation 2026-06-11 22:56:54 +02:00
uberwald 46fa2d15a3 fix: allow defender to react when attacker boosts past defense via cross-client socket 2026-06-11 22:41:54 +02:00
uberwald 8aae7bada0 fix: pre-check 'Save against spell' checkbox when defense originates from spell attack 2026-06-11 22:17:37 +02:00
uberwald ceb62bca3f fix: add missing class lethal-luck-grit-hud to template so JS selector matches 2026-06-11 22:03:56 +02:00
uberwald 110ac65ba5 fix: replace hardcoded French bleeding notifications with i18n keys 2026-06-11 21:50:19 +02:00
uberwald 9b75fd4d96 feat: combat-tracker-driven bleeding (HP loss per wound per round) 2026-06-11 21:49:35 +02:00
uberwald 141d6048e0 Fix triple damage issue 2026-06-11 21:32:26 +02:00
uberwald ea7acf6bf8 Fix hp < 0 and D30 with D20 bonus roll 2026-06-11 20:48:46 +02:00
uberwald c20750caa7 Minor fixes regarding rolls and chat messages
Release Creation / build (release) Successful in 48s
2026-06-10 20:17:45 +02:00
uberwald ce630feb51 feat: D30 combat effects, spell tiers, small damage removal, token HUD luck/grit
- Replace Knockback with Internal Injury on D30 (5, 10, 15); remove Shield Bash from D30 counter-attacks
- Eliminate small weapon damage: keep only medium damage labelled Damage in sheets, rolls, and chat
- D30 bonus dice (20, 27, 30) auto-resolved before grit/luck/shield decisions; choice dialogs for special strikes
- D30 combat effects: bleeding wounds, damage ×2/×3 before DR, DR ×2/×3 with component picker dialog
- Add hp.wounds to monster schema for bleeding support
- Show Save against spell? checkbox for all save rolls (not just magic users)
- Fix mulligan restart: persistent D30 process flags prevent double-application and allow both sides to react
- For Dice So Nice, show main roll animation before explosion dice for correct ordering
- Spell tier selection: force Standard/Overpowered choice at cast time, tier-specific aether cost, only chosen damage button shown
- Add +1/−1 luck and grit controls to Token HUD
- Fix inconsistent indentation, remove duplicate i18n key, remove unused includesShield return
2026-06-10 07:53:51 +02:00
uberwald b35b684d50 NEgative values for HP and weapon bonuses 2026-06-06 16:11:36 +02:00
uberwald f6fb0b68b8 Fix spell rolls again
Release Creation / build (release) Successful in 47s
2026-05-25 20:41:00 +02:00
uberwald e45edd60c4 FIx spell order and dual rollll for spell damages
Release Creation / build (release) Successful in 1m6s
2026-05-25 12:29:39 +02:00
uberwald d389a85a9f Fix ranged attacks again
Release Creation / build (release) Successful in 43s
2026-05-24 09:42:07 +02:00
uberwald c217490a5b Fix ranged attacks again
Release Creation / build (release) Has been cancelled
2026-05-24 09:41:06 +02:00
uberwald 38eb1a8d3d Add ranged actions for monsters
Release Creation / build (release) Successful in 54s
2026-05-23 19:10:10 +02:00
uberwald 4724cdf2bb VArious fixes for rolls and ranged attacks
Release Creation / build (release) Successful in 44s
2026-05-23 09:08:16 +02:00
uberwald 6d06c8ddad Various fixes for spell and ranged attacks 2026-05-23 00:21:05 +02:00
uberwald 2770774aa3 Various fixes for spell and ranged attacks 2026-05-23 00:11:58 +02:00
uberwald e417b61625 Spells fixe
Release Creation / build (release) Successful in 46s
2026-05-20 23:17:07 +02:00
uberwald 9a8d580ef6 Other fixes for damage buttons from chat
Release Creation / build (release) Successful in 53s
2026-05-20 10:53:46 +02:00
uberwald 9ccb0f90f0 Other fixes for damage buttons from chat 2026-05-20 10:53:22 +02:00
uberwald 6cf0880ad3 Enhance spell damage and messages content
Release Creation / build (release) Successful in 44s
2026-05-19 10:52:03 +02:00
uberwald 96306623e5 UPdate and fixes for roll in combats
Release Creation / build (release) Successful in 43s
2026-05-18 20:26:39 +02:00
uberwald 7279cd752d Fix initiative again
Release Creation / build (release) Successful in 43s
2026-05-18 07:58:28 +02:00
uberwald db3e8b5d35 Improve init for monsters and some fixwes around shields
Release Creation / build (release) Successful in 48s
2026-05-17 13:22:29 +02:00
uberwald 54421e4a83 MAnage spell/miracle spending points and favor/disfavor for shield rolls
Release Creation / build (release) Successful in 43s
2026-05-10 17:49:53 +02:00
uberwald ac44419b7a Corredction sur attack ranged 2026-05-03 15:12:25 +02:00
uberwald a3fc0a42b9 Corredction sur attack ranged
Release Creation / build (release) Successful in 1m19s
2026-05-03 10:06:44 +02:00
uberwald c8ce840e98 Fix ranged defense + HTH attacks
Release Creation / build (release) Successful in 1m17s
2026-05-02 08:35:22 +02:00
uberwald 55a040062a Fix ranged defense + HTH attacks
Release Creation / build (release) Successful in 1m19s
2026-05-01 23:55:29 +02:00
uberwald 1818a76499 Fix ranged defense + HTH attacks
Release Creation / build (release) Successful in 1m19s
2026-05-01 23:34:05 +02:00
uberwald 55d1b41ca4 Fix ranged defense + HTH attacks 2026-05-01 23:32:53 +02:00
uberwald 841ed82277 Fix for ranged monsters attack
Release Creation / build (release) Successful in 1m2s
2026-05-01 01:12:56 +02:00
uberwald 968d156d09 Upgrade to r14
Release Creation / build (release) Successful in 48s
2026-04-30 14:38:56 +02:00
uberwald 59ff098fca Add ranged attacks for monsters 2026-04-29 20:27:20 +02:00
uberwald b8174d5e22 Fix E dice in dice Tray
Release Creation / build (release) Successful in 55s
2026-04-17 23:21:49 +02:00
uberwald 7f15450566 Add specific diceTray and enhance message styles
Release Creation / build (release) Successful in 49s
2026-04-17 17:32:46 +02:00
uberwald 28fdaff2ec Add specific diceTray and enhance message styles 2026-04-17 17:32:32 +02:00
uberwald 81584ed5d6 Update D30 descriptions and add simple shield option for NPCs 2026-04-16 23:42:24 +02:00
uberwald 8c9a13faf1 Fixes around D30 managemen 2026-04-16 21:39:51 +02:00
uberwald 6c6c473147 Fixes regarding shields usage and spells
Release Creation / build (release) Successful in 52s
2026-04-14 21:31:17 +02:00
uberwald 2e2a917a45 Fixes regarding shields usage and spells 2026-04-14 21:31:03 +02:00
uberwald 343abc32e2 FIx init à 1 again
Release Creation / build (release) Successful in 50s
2026-04-12 11:08:19 +02:00
uberwald c37d92af25 Various initiative fixes + shield management messages
Release Creation / build (release) Successful in 46s
2026-04-12 01:07:58 +02:00
uberwald 42945d33db ATtempt to fix init rolls
Release Creation / build (release) Successful in 52s
2026-04-07 20:44:13 +02:00
uberwald 1bf88bac06 ATtempt to fix init rolls 2026-04-07 20:43:15 +02:00
uberwald 3ad5681539 Fix inititiative rolls
Release Creation / build (release) Successful in 52s
2026-04-06 00:02:14 +02:00
uberwald df6f8e5710 Add token HUD +HP
Release Creation / build (release) Successful in 1m37s
2026-02-06 22:20:20 +01:00
uberwald 52877e3a68 New combat management and various improvments
Release Creation / build (release) Successful in 48s
2026-01-19 23:22:32 +01:00
uberwald a06dfa0ae9 Update skills/shield DR
Release Creation / build (release) Successful in 1m8s
2026-01-12 14:27:29 +01:00
uberwald 0836cada75 Re-org folders and enhance minor CSS stuff 2026-01-04 09:21:21 +01:00
uberwald 61ed1597e7 Add damage management and DR for monsters also
Release Creation / build (release) Successful in 1m48s
2025-12-19 15:41:27 +01:00
uberwald 96062c6fd9 Roll damages and so on
Release Creation / build (release) Successful in 56s
2025-12-14 21:18:00 +01:00
uberwald f6b35536de Manage DR and damage roll 2025-12-14 20:48:33 +01:00
uberwald 7d27562bb4 Fix DR and defense values
Release Creation / build (release) Successful in 1m7s
2025-12-08 11:54:36 +01:00
uberwald 64f2efdcb9 Fix last HD Roll
Release Creation / build (release) Successful in 1m35s
2025-11-07 07:58:58 +01:00
uberwald 66f7aade25 Fix jog + update compendiums
Release Creation / build (release) Successful in 52s
2025-10-17 20:06:49 +02:00
uberwald 8f682a1458 Auto-publish package on releas 2025-10-01 17:26:29 +02:00
uberwald fa3054f24b Some granted dice/favor fixes
Release Creation / build (release) Successful in 2m48s
2025-10-01 17:17:33 +02:00
uberwald 59a891630e Fix tooltip
Release Creation / build (release) Successful in 38s
2025-09-20 09:34:13 +02:00
uberwald 35b88b3914 Poison/Contagion fixes again
Release Creation / build (release) Successful in 42s
2025-09-19 23:32:15 +02:00
uberwald cb8bcfd9ea Ranged defense fixes again
Release Creation / build (release) Successful in 43s
2025-09-19 22:30:26 +02:00
uberwald eedce1a498 Latest fixes
Release Creation / build (release) Successful in 46s
2025-09-17 07:47:40 +02:00
uberwald 76a99fe33f Various fixes 2025-09-16 23:49:02 +02:00
uberwald 59a39850ce Fix miracle/spell rolls + upadte compendiums
Release Creation / build (release) Successful in 56s
2025-09-09 20:14:25 +02:00
104 changed files with 11492 additions and 1373 deletions
+1
View File
@@ -0,0 +1 @@
packs/** filter=lfs diff=lfs merge=lfs -text
+51 -40
View File
@@ -1,6 +1,6 @@
name: Release Creation
on:
on:
release:
types: [published]
@@ -8,45 +8,56 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "💡 The ${{ gitea.repository }} repository will cloned to the runner."
#- uses: actions/checkout@v3
- uses: RouxAntoine/checkout@v3.5.4
- run: echo "💡 The ${{ gitea.repository }} repository will cloned to the runner."
# get part of the tag after the `v`
- name: Extract tag version number
id: get_version
uses: battila7/get-version-action@v2
#- uses: actions/checkout@v3
- uses: RouxAntoine/checkout@v3.5.4
# Substitute the Manifest and Download URLs in the module.json
- name: Substitute Manifest and Download Links For Versioned Ones
id: sub_manifest_link_version
uses: microsoft/variable-substitution@v1
with:
files: 'system.json'
env:
version: ${{steps.get_version.outputs.version-without-v}}
url: https://www.uberwald.me/gitea/${{gitea.repository}}
manifest: https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-lethal-fantasy-${{github.event.release.tag_name}}.zip
# Create a zip file with all files required by the module to add to the release
- run: |
apt update -y
apt install -y zip
# get part of the tag after the `v`
- name: Extract tag version number
id: get_version
uses: battila7/get-version-action@v2
- run: zip -r ./fvtt-lethal-fantasy-${{github.event.release.tag_name}}.zip system.json README.md LICENSE assets/ css/ lang/ module/ packs-system/ templates/ lethal-fantasy.mjs
- name: setup go
uses: https://github.com/actions/setup-go@v4
with:
go-version: '>=1.20.1'
- name: Use Go Action
id: use-go-action
uses: https://gitea.com/actions/release-action@main
with:
files: |-
./fvtt-lethal-fantasy-${{github.event.release.tag_name}}.zip
system.json
api_key: '${{secrets.ALLOW_PUSH_RELEASE}}'
# Substitute the Manifest and Download URLs in the module.json
- name: Substitute Manifest and Download Links For Versioned Ones
id: sub_manifest_link_version
uses: microsoft/variable-substitution@v1
with:
files: "system.json"
env:
version: ${{steps.get_version.outputs.version-without-v}}
url: https://www.uberwald.me/gitea/${{gitea.repository}}
manifest: https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-lethal-fantasy-${{github.event.release.tag_name}}.zip
# Create a zip file with all files required by the module to add to the release
- run: |
apt update -y
apt install -y zip
- run: zip -r ./fvtt-lethal-fantasy-${{github.event.release.tag_name}}.zip system.json README.md LICENSE assets/ css/ lang/ module/ packs-system/ templates/ lethal-fantasy.mjs
- name: setup go
uses: https://github.com/actions/setup-go@v4
with:
go-version: ">=1.20.1"
- name: Use Go Action
id: use-go-action
uses: https://gitea.com/actions/release-action@main
with:
files: |-
./fvtt-lethal-fantasy-${{github.event.release.tag_name}}.zip
system.json
api_key: "${{secrets.ALLOW_PUSH_RELEASE}}"
- name: Publish to Foundry server
uses: djlechuck/foundryvtt-publish-package-action@v1
with:
token: ${{ secrets.FOUNDRY_PUBLISH_KEY }}
id: "fvtt-lethal-fantasy"
version: ${{github.event.release.tag_name}}
manifest: "https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/system.json"
notes: "https://www.uberwald.me/gitea/public/fvtt-lethal-fantasy/raw/branch/main/changelog.md"
compatibility-minimum: "14"
compatibility-verified: "14"
+12
View File
@@ -7,4 +7,16 @@ styles/*.css
node_modules/
.history
.github/
# LevelDB internals (auto-generated, churn on every open)
packs-system/**/*.log
packs-system/**/LOG
packs-system/**/LOG.old
packs-system/**/CURRENT
packs-system/**/LOCK
packs-system/**/MANIFEST-*
# YAML source for pack round-trip
packs_src/
+6
View File
@@ -0,0 +1,6 @@
{
"cSpell.words": [
"biodata",
"LETHALFANTASY"
]
}
+105
View File
@@ -0,0 +1,105 @@
# Lethal Fantasy FoundryVTT System — Session Context
## Current Goal
Fix Grit/Luck defense reaction dialog UX (stacking dialogs, multiple clicks, revert on close) and cross-client sync of defense bonuses.
## Accomplished
### Pass 1 — Critical Issues
- **Telemetry removed**: `ClassCounter`, `registerWorldCount`, orphaned `worldKey` setting deleted from system.json
- **globalThis side effects**: `globalThis.SYSTEM`, `globalThis.pendingDefenses` moved from top-level to `init` hook
- **console.log → log()**: All runtime console.log replaced with `log()` helper guarded by `lethalFantasy.debug` setting
- **Stale Tenebris refs**: `macros.mjs``TENEBRIS.Label.jet``LETHALFANTASY.Label.jet`, `TENEBRIS.Manager.*``LETHALFANTASY.Label.*`, `tenebris.macro` flag → `lethalFantasy.macro`
### Pass 2 — V1/V2 Mixing, Fire-and-Forget
- **V1 sheet registrations removed**: `foundry.appv1.sheets.*` in system.json
- **V1 `activateListeners`/jQuery**: removed dead `defaultOptions`, V1 tab code from `combat.mjs`
- **V2 API paths**: `FilePicker` → V2, `TextEditor.getDragEventData` → V2, `item.sheet.render(true)``render({force:true})`, `super._onRender()``super._onRender(context, options)`, `token._id``token.id`
- **Fire-and-forget Promises**: All `actor.update()`, `ChatMessage.create()`, `prepareRoll()`, `prepareMonsterRoll()`, socket handler calls now awaited
- **Misnamed class**: `LethalFantasySkill``LethalFantasyWeapon`; added missing `WEAPON_TYPE` import; fixed `weaponCategory`
### Pass 3 — Code Review Fixes
- **Duplicated dialogs**: Per-element `.rollable`/`.wound-data` bindings moved to `_onRender` (V2 destroys/recreates DOM each render); `_activateListeners` reverted
- **renderChatMessage reverted**: V2 hook `renderChatMessage` passes jQuery html, `querySelectorAll` fails; kept `renderChatMessageHTML`
- **Roll actions broken**: Fixed `async` base-actor-sheet methods; `_onRender` bindings for rollable elements restored
- **Token HUD guard**: `html.querySelector()``html.find().length` (html is jQuery object)
- **All review awaits confirmed**: `showDefenseRequest`/`socket` handlers all awaited
## Defense Dialog Investigation — Status
### Symptom (user process)
1. Monster (GM) attacks player — hits
2. Player uses Grit/Luck to boost defense
3. Defense now beats attack — reports new result
4. Dialog **stays open** — Grit/Luck/bonus dice options still visible
5. Closing dialog (Continue or X) causes "rolls vanish" — reverts to original result
### Root Cause Found — Duplicate cross-client processing (FIXED)
When monster (GM) attacks player, the `createChatMessage` hook fires on **both** clients:
```
Player's client: GM's client:
defense msg created defense msg synced
↓ ↓
hook fires (line 557) hook fires (line 557)
isPrimaryController(defender)=true isPrimaryController(defender)=false
↓ ↓
Defense dialog A shows Defense dialog skipped
Player spends Grit Cross-client code (line 1009):
defenseRoll=10→16 isPrimaryController(attacker)=true
While loop exits defenderOwner=player (≠GM)
Comparison: "miss" ↓
**Sends attackBoosted with ORIGINAL
defenseRoll=10 (stale!)**
Player receives socket → handleAttackBoosted
→ Defense dialog B shows with OLD values
→ When closed, comparison: "hit" (overwrites!)
```
Player sees **two** dialogs (A then B). Dialog B uses unboosted values, so closing/ignoring it produces a stale "hit" result that overwrites the correct "miss."
### Fix
`lethal-fantasy.mjs:1016` — only send `attackBoosted` socket when `attackerHandledBonus || attackerHasNonGMOwner`. Guards against stale-socket overwrite for GM→player combat (where hook-based processing works without socket), while preserving socket delegation for PC→PC cross-client (where `attackerIsCrossClient` suppresses the hook-based processing on the defender's client).
Before:
```js
if (defenderOwner && defenderOwner.id !== game.user.id) {
game.socket.emit(`system.${SYSTEM.id}`, { type: "attackBoosted", ... })
return
}
```
After:
```js
if (defenderOwner && defenderOwner.id !== game.user.id) {
if (attackerHandledBonus || attackerHasNonGMOwner) {
game.socket.emit(`system.${SYSTEM.id}`, { type: "attackBoosted", ... })
}
return
}
```
### Same-Client Path
Code pattern is identical between attack and defense dialogs — both use `await DialogV2.wait({rejectClose:false})` in a while loop. Same-client defense works correctly because no duplicate socket messages arrive.
### Other Findings
- `offerGritLuckBonus` (`utils.mjs:1121`) is dead code — never called
- `promptCombatBonusDie` (`utils.mjs:975`) is correct — DialogV2 resolves to callback return value, not `action`
- Cross-client `handleAttackBoosted` (`utils.mjs:291`) still uses `else if` chain without `continue` — functionally correct but differs from same-client pattern
### Code Paths
| Flow | File | Line |
|------|------|------|
| Same-client attack | `lethal-fantasy.mjs` | 918-1004 |
| Same-client defense | `lethal-fantasy.mjs` | 697-870 |
| Cross-client defense | `module/utils.mjs` | 291-445 |
| Cross-client socket guard | `lethal-fantasy.mjs` | 1006-1037 |
| Attack Grit offer | `module/utils.mjs` | 1210-1290 |
### Key Files
- `lethal-fantasy.mjs` — Main system hooks, same-client attack/defense reactions
- `module/utils.mjs` — Cross-client defense flow, bonus dialogs, compareAttackDefense
- `module/documents/actor.mjs``prepareRoll()` entry point
- `module/documents/roll.mjs` — Roll resolution pipeline
+7 -1
View File
@@ -1 +1,7 @@
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><g class="" style="" transform="translate(0,0)"><path d="M373.47 25.5c-33.475-.064-67.614 13.444-94.44 43.156l37.22 145.156-33.437.032 35.343 132.093-116.718-188.375 50.03 5.375L202.5 47.312C120.437-1.43 4.756 40.396 8.5 158.156c4.402 138.44 191.196 184.6 247.406 331.625 59.376-147.035 251.26-184.33 246.656-331.624-2.564-82.042-64.6-132.532-129.093-132.656z" fill="#fff" fill-opacity="1"></path></g></svg>
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<g class="" style="" transform="translate(0,0)">
<path
d="M373.47 25.5c-33.475-.064-67.614 13.444-94.44 43.156l37.22 145.156-33.437.032 35.343 132.093-116.718-188.375 50.03 5.375L202.5 47.312C120.437-1.43 4.756 40.396 8.5 158.156c4.402 138.44 191.196 184.6 247.406 331.625 59.376-147.035 251.26-184.33 246.656-331.624-2.564-82.042-64.6-132.532-129.093-132.656z"
fill="#dc2626" fill-opacity="1"></path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 506 B

After

Width:  |  Height:  |  Size: 533 B

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- Heart shape -->
<path
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
fill="#4ade80" stroke="#22c55e" />
<!-- Plus sign inside heart -->
<line x1="12" y1="8" x2="12" y2="14" stroke="#ffffff" stroke-width="2.5" />
<line x1="9" y1="11" x2="15" y2="11" stroke="#ffffff" stroke-width="2.5" />
</svg>

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
## v13.0.12
- Fix favor/disfavor
- Fix granted dice
- Cosmetic fixes
File diff suppressed because it is too large Load Diff
+266 -21
View File
@@ -1,8 +1,32 @@
{
"COMBAT": {
"Round": "Second {round}",
"Rounds": "Seconds",
"RoundNext": "Next second"
"Begin": "Begin Combat",
"Create": "Create Encounter",
"Delete": "Delete Encounter",
"Encounter": "Encounter",
"EncounterNext": "Next Encounter",
"EncounterPrevious": "Previous Encounter",
"End": "End Combat",
"InitiativeReset": "Reset Initiative",
"InitiativeRoll": "Roll Initiative",
"InitiativeScore": "Initiative Score",
"NavLabel": "Combat Tracker Navigation",
"None": "None",
"NotStarted": "Not Started",
"PanToCombatant": "Pan to Combatant",
"PingCombatant": "Ping Combatant",
"RollAll": "Roll All",
"RollNPC": "Roll NPCs",
"Round": "Second {round}",
"RoundNext": "Next second",
"RoundPrev": "Previous second",
"Rounds": "Seconds",
"Settings": "Combat Settings",
"ToggleDead": "Toggle Dead",
"ToggleVis": "Toggle Visible",
"TurnEnd": "End Turn",
"TurnNext": "Next Turn",
"TurnPrev": "Previous Turn"
},
"LETHALFANTASY": {
"Armor": {
@@ -152,6 +176,57 @@
"wis": {
"label": "Wisdom"
}
},
"agility": {
"label": "Agility"
},
"app": {
"label": "Appearance"
},
"cha": {
"label": "Charisma"
},
"con": {
"label": "Constitution"
},
"contagion": {
"label": "Contagion Save"
},
"dex": {
"label": "Dexterity"
},
"dodge": {
"label": "Dodge Save"
},
"dying": {
"label": "Dying Challenge"
},
"int": {
"label": "Intelligence"
},
"luc": {
"label": "Luck"
},
"pain": {
"label": "Pain Save"
},
"painCourage": {
"label": "Pain/Courage Save"
},
"poison": {
"label": "Poison Save"
},
"str": {
"label": "Strength"
},
"toughness": {
"label": "Toughness Save"
},
"will": {
"label": "Willpower Save"
},
"wis": {
"label": "Wisdom"
}
},
"Monster": {
@@ -250,6 +325,21 @@
"wis": {
"label": "Wisdom"
}
},
"perception": {
"label": "Perception"
},
"resistIntimidation": {
"label": "Resist Intimidation"
},
"resistPerformance": {
"label": "Resist Performance"
},
"resistTorture": {
"label": "Resist Torture"
},
"stealth": {
"label": "Stealth"
}
},
"Delete": "Delete",
@@ -282,6 +372,9 @@
},
"Label": {
"agility": "Dexterity",
"applyDamage": "Apply damage to:",
"selectTarget": "Select target for attack:",
"rollDamage": "Roll Damage",
"gotoToken": "Go to token",
"combatAction": "Combat action",
"currentAction": "Current ongoing action",
@@ -291,31 +384,36 @@
"miracle-power": "Miracle - Power",
"miracle-attack": "Miracle - Attack",
"spell": "Spell",
"will":"Will",
"dodge":"Dodge",
"toughness":"Toughness",
"contagion":"Contagion",
"poison":"Poison",
"pain":"Pain",
"paincourage":"Pain/Courage",
"will": "Will",
"dodge": "Dodge",
"toughness": "Toughness",
"contagion": "Contagion",
"poison": "Poison",
"pain": "Pain",
"paincourage": "Pain/Courage",
"granted": "Granted Dice",
"shields": "Shields",
"armorHitPoints": "Armor hit points",
"grantedAttackDice": "Granted attack",
"grantedDamageDice": "Granted damage",
"grantedDefenseDice": "Granted defense",
"damageResistance": "Damage resistance",
"damageResistance": "DR",
"damageResistanceShort": "DR",
"shieldDamageReduction": "Shield DR",
"shieldDefenseDice": "Shield dice",
"stealth": "Stealth",
"progressionDice": "Progression/Lethargy dice",
"rollProgressionCount": "Roll progression count",
"rollProgressionDice": "Roll progression/Lethargy dice",
"earned": "Earned",
"divinityPoints": "Divinity points",
"divinityPoints": "Grace",
"aetherPoints": "Aether points",
"attacks": "Attacks",
"attackMode": "Attack Mode",
"meleeModeLabel": "Melee (8 attacks)",
"rangedModeLabel": "Ranged (4 attacks)",
"monster": "Monster",
"Resist" :"Resist",
"Resist": "Resist",
"resist": "Resist",
"resistTorture": "Resist torture",
"resistPerformance": "Resist performance",
@@ -388,8 +486,17 @@
"notes": "Notes",
"pc": "PC",
"perception": "Perception",
"pointBlank": "Point blank",
"short": "Short",
"medium": "Medium",
"long": "Long",
"extreme": "Extreme",
"outOfSkill": "Out of skill",
"range": "Range",
"rangeDefenseDialog": "Ranged defense dialog",
"rangeDefenseRoll": "Ranged defense roll",
"rangeAttackDialog": "Ranged attack dialog",
"rangeAttackRoll": "Ranged attack roll",
"rangedAttackDefense": "Ranged attack defense",
"resource": "Resource",
"resources": "Resources",
@@ -421,7 +528,43 @@
"monster-damage": "Monster damage",
"monster-defense": "Monster defense",
"weapons": "Weapons",
"wis": "WIS"
"wis": "WIS",
"combatProgressionStart": "Combat start threshold",
"miracle": "Miracle",
"titleStandard": "Standard Roll",
"privateRoll": "Private Roll",
"current": "Current",
"max": "Max",
"speed": "Speed",
"bonuses": "Bonuses",
"handToHandAttacks": "Hand To Hand Attacks",
"beyondSkill": "Beyond Skill",
"letItFly": "Let It Fly!",
"class": "Class",
"mortal": "Mortal",
"alignment": "Alignment",
"age": "Age",
"height": "Height",
"weight": "Weight",
"eyes": "Eyes",
"hair": "Hair",
"magicUser": "Magic User",
"clericUser": "Cleric User",
"lastHdRoll": "Last HD roll",
"naturalDR": "Natural DR",
"magicalDR": "Magical DR",
"saveBonus": "Save bonus (1/5 levels)",
"spellBonus": "Spell bonus (1/5 levels)",
"miracleBonus": "Miracle bonus (1/5 levels)",
"devPointsTotal": "Dev. Points (Total)",
"devPointsRem": "Dev. Points (Rem.)",
"length": "Length",
"vision": "Vision",
"damageType": "Damage Type",
"components": "Components",
"coverRanged": "Cover vs ranged attacks",
"standing": "Standing",
"crouching": "Crouching"
},
"Miracle": {
"FIELDS": {
@@ -477,6 +620,15 @@
},
"savingThrow": {
"label": "Saving throw"
},
"damageDiceOverpowered": {
"label": "Overpowered Damage Dice"
},
"damageDiceOverpowered2": {
"label": "Overpowered 2 Damage Dice"
},
"damageDice": {
"label": "Damage Dice"
}
}
},
@@ -496,7 +648,9 @@
"messageLethargyKO": "{spellName} : Lethargy still ongoing ... ( dice result : {roll} )",
"messageProgressionKO": "{name} can't attack this second.",
"messageProgressionOKMonster": "{name} can attack this second with {weapon}.",
"messageProgressionKOMonster": "{name} can't attack this second (dice result {roll})."
"messageProgressionKOMonster": "{name} can't attack this second (dice result {roll}).",
"bleedingCombatEnd": "Bleeding active out of combat: {names}",
"bleedingCombatStart": "Bleeding still active on: {names}"
},
"Opponent": {
"FIELDS": {}
@@ -511,7 +665,9 @@
"save": "Save roll {save}",
"success": "Success",
"visibility": "Visibility",
"favorDisfavor": "Favor/Disfavor"
"favorDisfavor": "Favor/Disfavor",
"displayArmor": "Target: {targetName} — Armor DR: {targetArmor} — Damage: {realDamage}",
"resourceLost": "Resource spent"
},
"Save": {
"FIELDS": {
@@ -553,7 +709,7 @@
"label": "Min"
}
},
"damagereduction": {
"damageReduction": {
"label": "Damage reduction"
},
"defense": {
@@ -596,7 +752,8 @@
"label": "Min"
}
}
}
},
"autoDestruction": "Auto-Destruction"
},
"Skill": {
"Category": {
@@ -653,6 +810,9 @@
"weaponClass": {
"label": "Class"
}
},
"error": {
"weaponBonus": "Weapon bonus exceeds the allowed maximum (skill total / 10)"
}
},
"Spell": {
@@ -692,6 +852,12 @@
"cost": {
"label": "Cost"
},
"costOverpowered": {
"label": "Cost (Overpowered)"
},
"costOverpowered2": {
"label": "Cost (Overpowered 2)"
},
"description": {
"label": "Description"
},
@@ -715,14 +881,35 @@
},
"catalyst": {
"label": "Catalyst"
},
"damageDice": {
"label": "Damage dice"
},
"damageDiceOverpowered": {
"label": "Overpowered Damage Dice"
},
"damageDiceOverpowered2": {
"label": "Overpowered 2 Damage Dice"
}
},
"Range": {
"contact": "Contact",
"distant": "Distant",
"loin": "Far",
"na": "N/A",
"proche": "Close"
}
},
"ToggleSheet": "Toggle mode",
"Tooltip": {
"addEquipment": "New equipment",
"addSpell": "New spells",
"skill": "Skills list"
"skill": "Skills list",
"combatProgressionStart": "First attack of combat can succeed on a roll of 1 through this value. Resets to 1 for subsequent attacks.",
"addMiracle": "Add Miracle",
"gifts": "Gifts list",
"skills": "Skills list",
"vulnerabilities": "Vulnerabilities list"
},
"Vulnerability": {
"FIELDS": {
@@ -737,7 +924,9 @@
}
}
},
"Warning": {},
"Warning": {
"defenseShieldOrder": "To avoid a hit without using the shield, roll Grit or Luck first — then roll the shield."
},
"Weapon": {
"FIELDS": {
"isAgile": {
@@ -856,6 +1045,62 @@
"melee": "Melee",
"ranged": "Ranged"
}
},
"Dialog": {
"applyDamageTo": "Apply damage to",
"weapon": "Weapon",
"totalDamage": "Total Damage",
"damageReduction": "Damage Reduction",
"armorDR": "Armor DR",
"shieldDR": "Shield DR",
"totalDR": "Total DR",
"selectOption": "Select damage application option",
"noDR": "No DR",
"withArmor": "With Armor DR only",
"withAll": "With Armor + Shield DR",
"damage": "damage"
},
"DamageApplied": {
"subtitle": "suffered damage",
"damageDealt": "Damage dealt",
"from": "from"
},
"ProgressionMessage": {
"canAct": "ready to act!",
"cannotAct": "cannot act this second",
"diceResult": "Dice result",
"progressionCount": "Progression count:"
},
"Combat": {
"RollMonsters": "Roll Monsters",
"monstersNotRolledTitle": "Monsters Not Rolled",
"monstersNotRolledMsg": "Monsters have not rolled this second. Proceed anyway?",
"proceedYes": "Proceed",
"proceedNo": "Cancel",
"spellDRDialogTitle": "Spell Damage — Apply DR?",
"spellDRDialogMsg": "Enter a damage reduction value to subtract, or click No DR to apply full damage.",
"spellDRLabel": "DR:",
"spellNoDR": "No DR",
"spellApplyDR": "Apply DR"
},
"EquipmentCategories": {
"ClassKit": "Class Kit",
"Clothing": "Clothing",
"EssentialKit": "Essential Kit",
"FoodDrink": "Food & Drink",
"LandTransport": "Land Transport",
"Light": "Light",
"LoadBearing": "Load Bearing",
"Misc": "Miscellaneous",
"Mount": "Mount",
"Music": "Music",
"Sleeping": "Sleeping",
"WaterTransport": "Water Transport"
},
"DiceTray": {
"CountTitle": "Number of dice",
"ExplodeTitle": "Exploding dice — re-roll on maximum value",
"ChatTitle": "Free Roll"
}
},
"TYPES": {
@@ -876,4 +1121,4 @@
"weapon": "Weapon"
}
}
}
}
+1269 -47
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -9,4 +9,5 @@ export { default as LethalFantasySpellSheet } from "./sheets/spell-sheet.mjs"
export { default as LethalFantasyEquipmentSheet } from "./sheets/equipment-sheet.mjs"
export { default as LethalFantasyShieldSheet } from "./sheets/shield-sheet.mjs"
export { default as LethalFantasyMiracleSheet } from "./sheets/miracle-sheet.mjs"
export { injectDiceTray } from "./free-roll.mjs"
+84 -60
View File
@@ -18,62 +18,44 @@ export class LethalFantasyCombatTracker extends foundry.applications.sidebar.tab
actions: {
initiativePlus: LethalFantasyCombatTracker.#initiativePlus,
initiativeMinus: LethalFantasyCombatTracker.#initiativeMinus,
rollMonsterProgression: LethalFantasyCombatTracker.#rollMonsterProgression,
},
});
async _prepareContext(options) {
let data = await super._prepareContext(options);
console?.log("Combat Tracker Data", data);
log("Combat Tracker Data", data);
/*for (let u of data.turns) {
let c = game.combat.combatants.get(u.id);
u.progressionCount = c.system.progressionCount
u.isMonster = c.actor.type === "monster"
}
console.log("Combat Data", data);*/
log("Combat Data", data);*/
return data;
}
static #initiativePlus(ev) {
static async #initiativePlus(ev) {
ev.preventDefault();
let cId = ev.target.closest(".combatant").dataset.combatantId;
let c = game.combat.combatants.get(cId);
c.update({ 'initiative': c.initiative + 1 });
console.log("Initiative Plus");
await c.update({ 'initiative': c.initiative + 1 });
}
static #initiativeMinus(ev) {
static async #initiativeMinus(ev) {
ev.preventDefault();
let cId = ev.target.closest(".combatant").dataset.combatantId;
let c = game.combat.combatants.get(cId);
let newInit = Math.max(c.initiative - 1, 0);
c.update({ 'initiative': newInit });
await c.update({ 'initiative': newInit });
}
activateListeners(html) {
super.activateListeners(html);
// Display Combat settings
html.find(".initiative-plus").click(ev => {
ev.preventDefault();
let cId = ev.currentTarget.closest(".combatant").dataset.combatantId;
let c = game.combat.combatants.get(cId);
c.update({ 'initiative': c.initiative + 1 });
});
html.find(".initiative-minus").click(ev => {
ev.preventDefault();
let cId = ev.currentTarget.closest(".combatant").dataset.combatantId;
let c = game.combat.combatants.get(cId);
c.update({ 'initiative': c.initiative - 1 });
console.log("Initiative Minus");
});
}
/* -------------------------------------------- */
static get defaultOptions() {
let path = "systems/fvtt-lethal-fantasy/templates/combat-tracker.hbs";
return foundry.utils.mergeObject(super.defaultOptions, {
template: path,
});
/**
* Roll progression dice for all monster combatants that are eligible this round.
* @param {Event} ev Click event.
*/
static async #rollMonsterProgression(ev) {
ev.preventDefault();
await game.combat.rollMonsterProgression();
}
}
@@ -84,7 +66,7 @@ export class LethalFantasyCombat extends Combat {
* @returns {Combatant[]}
*/
setupTurns() {
console?.log("Setup Turns....");
log("Setup Turns....");
this.turns ||= [];
// Determine the turn order and the current turn
@@ -100,42 +82,73 @@ export class LethalFantasyCombat extends Combat {
return this.turns = turns;
}
async startCombat() {
this._playCombatSound("startEncounter")
const updateData = { round: 0, turn: 0 }
Hooks.callAll("combatStart", this, updateData)
await this.update(updateData)
return this
}
async rollInitiative(ids, options) {
console.log("%%%%%%%%% Roll Initiative", ids, options);
ids = typeof ids === "string" ? [ids] : ids;
let messages = [];
let rollMode = game.settings.get("core", "rollMode");
let updates = [];
for (let cId of ids) {
const c = this.combatants.get(cId);
let user = game.users.find(u => u.active && u.character && u.character.id === c.actor.id);
if (user?.hasPlayerOwner) {
console.log("Rolling initiative for", c.actor.name);
game.socket.emit(`system.${SYSTEM.id}`, { type: "rollInitiative", actorId: c.actor.id, combatId: this.id, combatantId: c.id });
const playerOwner = game.users.find(u => u.active && !u.isGM && u.character?.id === c.actor.id);
if (game.user.isGM && playerOwner) {
game.socket.emit(`system.${SYSTEM.id}`, { type: "rollInitiative", userId: playerOwner.id, actorId: c.actor.id, combatId: this.id, combatantId: c.id });
} else {
user = game.users.find(u => u.active && u.isGM);
c.actor.system.rollInitiative(this.id, c.id);
await c.actor.system.rollInitiative(this.id, c.id);
}
}
return this;
}
resetProgression(cId) {
let c = this.combatants.get(cId);
c.update({ 'system.progressionCount': 0 });
/** Roll progression dice for all eligible monster combatants this round. Called manually by the GM. */
async rollMonsterProgression() {
const currentRound = this.round;
const monsters = this.combatants.filter(c => c.actor?.type === "monster" && !c.isDefeated);
if (monsters.length === 0) {
ui.notifications.warn("No monsters in combat.");
return;
}
let rolled = 0;
for (let c of monsters) {
if (c.initiative !== null && currentRound >= c.initiative) {
await c.actor.system.rollProgressionDice(this.id, c.id);
rolled++;
}
}
if (rolled === 0) {
const earliest = monsters.reduce((min, c) => (c.initiative !== null && c.initiative < min) ? c.initiative : min, Infinity);
if (earliest === Infinity) {
ui.notifications.warn("Monsters have no initiative set. Roll initiative first.");
} else {
ui.notifications.info(`No monsters act yet — earliest monster initiative is ${earliest} (current round: ${currentRound}).`);
}
} else {
this._monsterProgressionRolledRound = currentRound;
}
}
setCasting(cId) {
async resetProgression(cId) {
let c = this.combatants.get(cId);
c.setFlag(SYSTEM.id, "casting", true);
await c.update({ 'system.progressionCount': 0 });
}
resetCasting(cId) {
async setCasting(cId) {
let c = this.combatants.get(cId);
c.setFlag(SYSTEM.id, "casting", false);
await c.setFlag(SYSTEM.id, "casting", true);
}
async resetCasting(cId) {
let c = this.combatants.get(cId);
await c.setFlag(SYSTEM.id, "casting", false);
}
isCasting(cId) {
@@ -144,15 +157,12 @@ export class LethalFantasyCombat extends Combat {
}
async nextTurn() {
console.log("NEXT TURN");
let turn = this.turn ?? -1;
let skipDefeated = this.settings.skipDefeated;
// Determine the next turn number
let next = null;
for (let [i, t] of this.turns.entries()) {
console.log("Turn", t);
if (i <= turn) continue;
if (skipDefeated && t.isDefeated) continue;
next = i;
@@ -176,7 +186,6 @@ export class LethalFantasyCombat extends Combat {
this.turnsDone = false
let turn = this.turn === null ? null : 0; // Preserve the fact that it's no-one's turn currently.
console.log("ROUND", this);
let advanceTime = Math.max(this.turns.length - this.turn, 0) * CONFIG.time.turnTime;
advanceTime += CONFIG.time.roundTime;
@@ -194,14 +203,29 @@ export class LethalFantasyCombat extends Combat {
return this;
}
// Warn if eligible monsters have not rolled progression dice this round
const eligibleMonsters = this.combatants.filter(
c => c.actor?.type === "monster" && !c.isDefeated && c.initiative !== null && this.round >= c.initiative
);
if (eligibleMonsters.length > 0 && this._monsterProgressionRolledRound !== this.round) {
const proceed = await foundry.applications.api.DialogV2.confirm({
window: { title: game.i18n.localize("LETHALFANTASY.Combat.monstersNotRolledTitle") },
content: `<p>${game.i18n.localize("LETHALFANTASY.Combat.monstersNotRolledMsg")}</p>`,
yes: { label: game.i18n.localize("LETHALFANTASY.Combat.proceedYes") },
no: { label: game.i18n.localize("LETHALFANTASY.Combat.proceedNo") },
rejectClose: false,
});
if (!proceed) return this;
}
for (let c of this.combatants) {
if (nextRound >= c.initiative) {
let user = game.users.find(u => u.active && u.character && u.character.id === c.actor.id);
if (user?.hasPlayerOwner) {
game.socket.emit(`system.${SYSTEM.id}`, { type: "rollProgressionDice", progressionCount: c.system.progressionCount + 1, actorId: c.actor.id, combatId: this.id, combatantId: c.id });
if (c.actor.type === "monster") continue; // Monsters roll manually via the "Roll Monsters" button
const playerOwner = game.users.find(u => u.active && !u.isGM && u.character?.id === c.actor.id);
if (game.user.isGM && playerOwner) {
game.socket.emit(`system.${SYSTEM.id}`, { type: "rollProgressionDice", userId: playerOwner.id, progressionCount: c.system.progressionCount + 1, actorId: c.actor.id, combatId: this.id, combatantId: c.id });
} else {
user = game.users.find(u => u.active && u.isGM);
c.actor.system.rollProgressionDice(this.id, c.id);
await c.actor.system.rollProgressionDice(this.id, c.id);
}
}
}
+155
View File
@@ -0,0 +1,155 @@
/**
* Free Dice Tray — injected into the Foundry chat sidebar.
*
* Provides a compact bar for GM and players to roll any standard die (d4d30)
* or its exploding variant (dXx) without needing an actor sheet.
* Supports selecting how many dice to roll (19).
*/
/** Standard dice available in Lethal Fantasy */
const DICE_TYPES = ["d4", "d6", "d8", "d10", "d12", "d20", "d30"]
/**
* Inject the dice tray bar into the ChatLog HTML.
* Called from `Hooks.on("renderChatLog", ...)`.
*
* @param {Application} _chatLog
* @param {HTMLElement|jQuery} html
*/
export function injectDiceTray(_chatLog, html) {
const el = (html instanceof HTMLElement) ? html : (html[0] ?? html)
if (!el?.querySelector) return
if (el.querySelector(".lf-dice-tray")) return
const bar = document.createElement("div")
bar.className = "lf-dice-tray"
const diceButtons = DICE_TYPES.map(d =>
`<button type="button" class="lf-dt-die-btn" data-die="${d}" title="${d.toUpperCase()}">${d.toUpperCase()}</button>`
).join("")
const countOptions = Array.from({ length: 9 }, (_, i) =>
`<option value="${i + 1}">${i + 1}</option>`
).join("")
bar.innerHTML = `
<div class="lf-dt-row">
<span class="lf-dt-label"><i class="fa-solid fa-dice"></i></span>
<select class="lf-dt-count" title="${game.i18n.localize("LETHALFANTASY.DiceTray.CountTitle")}">
${countOptions}
</select>
<div class="lf-dt-dice">${diceButtons}</div>
<label class="lf-dt-explode-label" title="${game.i18n.localize("LETHALFANTASY.DiceTray.ExplodeTitle")}">
<input type="checkbox" class="lf-dt-explode" />
<i class="fa-solid fa-explosion"></i>
</label>
</div>
`
bar.addEventListener("click", async ev => {
const btn = ev.target.closest(".lf-dt-die-btn")
if (!btn) return
ev.stopPropagation()
const dieType = btn.dataset.die
const count = parseInt(bar.querySelector(".lf-dt-count").value) || 1
const explode = bar.querySelector(".lf-dt-explode").checked
try {
await rollFreeDie(dieType, count, explode)
} catch (err) {
console.error("Lethal Fantasy | Dice Tray error:", err)
ui.notifications?.error("Dice Tray roll failed — see console")
}
})
const anchor = el.querySelector(".chat-form")
?? el.querySelector(".chat-message-form")
?? el.querySelector("form")
if (anchor) {
anchor.parentElement.insertBefore(bar, anchor)
} else {
el.appendChild(bar)
}
}
/**
* Roll one or more dice of the given type and post the result to chat.
* For exploding dice, follows the Lethal Fantasy rule: each exploded reroll
* contributes (result 1) to the total, same as all other system rolls.
*
* @param {string} dieType Die face, e.g. "d20"
* @param {number} count Number of dice to roll (19)
* @param {boolean} explode Whether to use the exploding variant (max triggers reroll at 1)
* @returns {Promise<void>}
*/
export async function rollFreeDie(dieType, count = 1, explode = false) {
const sides = parseInt(dieType.replace("d", "")) || 20
const baseFormula = `1d${sides}`
const label = explode
? `${count}${dieType.toUpperCase()}E`
: `${count}${dieType.toUpperCase()}`
const dieLabel = dieType.toUpperCase()
const dieChips = []
let total = 0
for (let i = 0; i < count; i++) {
const r0 = await new Roll(baseFormula).evaluate()
if (game?.dice3d) await game.dice3d.showForRoll(r0, game.user, true)
let diceResult = r0.dice[0].results[0].result
dieChips.push({ label: dieLabel, value: diceResult, exploded: false })
total += diceResult
if (explode) {
while (diceResult === sides) {
const rx = await new Roll(baseFormula).evaluate()
if (game?.dice3d) await game.dice3d.showForRoll(rx, game.user, true)
diceResult = rx.dice[0].results[0].result
const contrib = diceResult - 1
dieChips.push({ label: `${dieLabel}-1`, value: contrib, exploded: true })
total += contrib
}
}
}
const resultHtml = dieChips.map(chip => {
const isMax = !chip.exploded && chip.value === sides
const isMin = chip.value === 1
const explodeIcon = chip.exploded ? `<i class="fa-solid fa-burst lf-dt-explode-icon"></i>` : ""
const classes = ["lf-frc-die-chip", isMax ? "lf-frc-max" : "", isMin ? "lf-frc-min" : ""].filter(Boolean).join(" ")
return `<div class="${classes}">
<span class="lf-frc-die-type">${chip.label}</span>
<span class="lf-frc-die-sep">→</span>
<span class="lf-frc-die-val">${chip.value}${explodeIcon}</span>
</div>`
}).join("")
const totalLabel = game.i18n.localize("LETHALFANTASY.Label.total").toUpperCase()
const content = `
<div class="lf-free-roll-card">
<div class="lf-frc-header">
<i class="fa-solid fa-dice"></i>
<span class="lf-frc-title-text">${game.i18n.localize("LETHALFANTASY.DiceTray.ChatTitle")}</span>
<span class="lf-frc-badge">${label}</span>
</div>
<div class="lf-frc-dice">${resultHtml}</div>
<div class="lf-frc-total-bar">
<span class="lf-frc-total-label">${totalLabel}</span>
<span class="lf-frc-total-value">${total}</span>
</div>
</div>
`
const rollMode = game.settings.get("core", "rollMode")
// Normalize old-style rollMode keys (v12/v13) to new-style (v14), fallback to "public"
const modeMap = { publicroll: "public", gmroll: "gm", blindroll: "blind", selfroll: "self" }
const mode = modeMap[rollMode] ?? rollMode ?? "public"
const msgData = {
speaker: ChatMessage.getSpeaker(),
content,
sound: CONFIG.sounds.dice,
mode,
}
await ChatMessage.create(msgData)
}
@@ -78,7 +78,6 @@ export default class LethalFantasyActorSheet extends HandlebarsApplicationMixin(
/** @override */
_onRender(context, options) {
this.#dragDrop.forEach((d) => d.bind(this.element))
// Add listeners to rollable elements
const rollables = this.element.querySelectorAll(".rollable")
rollables.forEach((d) => d.addEventListener("click", this._onRoll.bind(this)))
}
@@ -140,7 +139,7 @@ export default class LethalFantasyActorSheet extends HandlebarsApplicationMixin(
if ("link" in event.target.dataset) return
const el = event.currentTarget.closest('[data-drag="true"]')
const dragType = el.dataset.dragType
const dragType = el?.dataset?.dragType
let dragData = {}
@@ -234,12 +233,12 @@ export default class LethalFantasyActorSheet extends HandlebarsApplicationMixin(
const attr = target.dataset.edit
const current = foundry.utils.getProperty(this.document, attr)
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}
const fp = new FilePicker({
const fp = new foundry.applications.ux.FilePicker.implementation({
current,
type: "image",
redirectToRoot: img ? [img] : [],
callback: (path) => {
this.document.update({ [attr]: path })
callback: async (path) => {
await this.document.update({ [attr]: path })
},
top: this.position.top + 40,
left: this.position.left + 10,
@@ -261,7 +260,7 @@ export default class LethalFantasyActorSheet extends HandlebarsApplicationMixin(
item = await fromUuid(uuid)
if (!item) item = this.document.items.get(id)
if (!item) return
item.sheet.render(true)
item.sheet.render({ force: true })
}
/**
@@ -284,8 +283,8 @@ export default class LethalFantasyActorSheet extends HandlebarsApplicationMixin(
* @private
* @static
*/
static #onCreateSpell(event, target) {
const item = this.document.createEmbeddedDocuments("Item", [{ name: "Nouveau sortilège", type: "spell" }])
static async #onCreateSpell(event, target) {
await this.document.createEmbeddedDocuments("Item", [{ name: "Nouveau sortilège", type: "spell" }])
}
// #endregion
@@ -177,12 +177,12 @@ export default class LethalFantasyItemSheet extends HandlebarsApplicationMixin(f
const attr = target.dataset.edit
const current = foundry.utils.getProperty(this.document, attr)
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}
const fp = new FilePicker({
const fp = new foundry.applications.ux.FilePicker.implementation({
current,
type: "image",
redirectToRoot: img ? [img] : [],
callback: (path) => {
this.document.update({ [attr]: path })
callback: async (path) => {
await this.document.update({ [attr]: path })
},
top: this.position.top + 40,
left: this.position.left + 10,
+112 -26
View File
@@ -22,6 +22,7 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
divinityPointsMinus: LethalFantasyCharacterSheet.#onDivinityPointsMinus,
aetherPointsPlus: LethalFantasyCharacterSheet.#onAetherPointsPlus,
aetherPointsMinus: LethalFantasyCharacterSheet.#onAetherPointsMinus,
rollSpellDamage: LethalFantasyCharacterSheet.#onRollSpellDamage,
},
}
@@ -70,10 +71,10 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "LETHALFANTASY.Label.biography" },
}
if (this.actor.system.biodata.magicUser) {
tabs.spells = { id: "spells", group: "sheet", icon: "fa-sharp-duotone fa-solid fa-wand-magic-sparkles", label: "LETHALFANTASY.Label.spells" }
tabs.spells = { id: "spells", group: "sheet", icon: "fa-solid fa-wand-magic-sparkles", label: "LETHALFANTASY.Label.spells" }
}
if (this.actor.system.biodata.clericUser) {
tabs.miracles = { id: "miracles", group: "sheet", icon: "fa-sharp-duotone fa-solid fa-hands-praying", label: "LETHALFANTASY.Label.miracles" }
tabs.miracles = { id: "miracles", group: "sheet", icon: "fa-solid fa-hands-praying", label: "LETHALFANTASY.Label.miracles" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
@@ -95,13 +96,26 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
const doc = this.document
switch (partId) {
case "main":
context.armorDR = this.actor.getArmorDR()
context.damageReduction = this.actor.computeDamageReduction()
context.damageReductionShield = this.actor.getShieldDR()
break
case "skills":
case "skills": {
context.tab = context.tabs.skills
context.skills = doc.itemTypes.skill
// Organiser les skills par catégorie
const categories = ['layperson', 'professional', 'weapon', 'armor', 'resist']
context.skillsByCategory = categories.map(cat => {
return {
category: cat,
label: `LETHALFANTASY.Skill.Category.${cat}`,
skills: context.skills.filter(s => s.system.category === cat)
}
}).filter(catData => catData.skills.length > 0)
context.gifts = doc.itemTypes.gift
context.vulnerabilities = doc.itemTypes.vulnerability
break
}
case "spells":
context.tab = context.tabs.spells
context.spells = doc.itemTypes.spell
@@ -143,7 +157,7 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
// Handle different data types
if (data.type === "Item") {
if (data.type === "Item") {
const item = await fromUuid(data.uuid)
return this._onDropItem(item)
}
@@ -159,61 +173,132 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
await roll.toMessage({}, { messageMode: roll.options.rollMode })
}
static async #onRollInitiative(event, target) {
await this.document.system.rollInitiative()
}
static #onArmorHitPointsPlus(event, target) {
static async #onArmorHitPointsPlus(event, target) {
let armorHP = this.actor.system.combat.armorHitPoints
armorHP += 1
this.actor.update({ "system.combat.armorHitPoints": armorHP })
await this.actor.update({ "system.combat.armorHitPoints": armorHP })
}
static #onArmorHitPointsMinus(event, target) {
static async #onArmorHitPointsMinus(event, target) {
let armorHP = this.actor.system.combat.armorHitPoints
armorHP -= 1
this.actor.update({ "system.combat.armorHitPoints": Math.max(armorHP, 0) })
await this.actor.update({ "system.combat.armorHitPoints": Math.max(armorHP, 0) })
}
static #onDivinityPointsPlus(event, target) {
static async #onDivinityPointsPlus(event, target) {
let points = this.actor.system.divinityPoints.value
points += 1
points = Math.min(points, this.actor.system.divinityPoints.max)
this.actor.update({ "system.divinityPoints.value": points })
await this.actor.update({ "system.divinityPoints.value": points })
}
static #onDivinityPointsMinus(event, target) {
static async #onDivinityPointsMinus(event, target) {
let points = this.actor.system.divinityPoints.value
points -= 1
points = Math.max(points, 0)
this.actor.update({ "system.divinityPoints.value": points })
await this.actor.update({ "system.divinityPoints.value": points })
}
static #onAetherPointsPlus(event, target) {
static async #onAetherPointsPlus(event, target) {
let points = this.actor.system.aetherPoints.value
points += 1
points = Math.min(points, this.actor.system.aetherPoints.max)
this.actor.update({ "system.aetherPoints.value": points })
await this.actor.update({ "system.aetherPoints.value": points })
}
static #onAetherPointsMinus(event, target) {
static async #onAetherPointsMinus(event, target) {
let points = this.actor.system.aetherPoints.value
points -= 1
points = Math.max(points, 0)
this.actor.update({ "system.aetherPoints.value": points })
await this.actor.update({ "system.aetherPoints.value": points })
}
/**
* Handles spell damage roll from the spell sheet tab.
* Shows a DR dialog then rolls the appropriate damage formula.
* @param {PointerEvent} event
* @param {HTMLElement} target
*/
static async #onRollSpellDamage(event, target) {
if (this.isEditMode) return
const itemId = target.dataset.itemId
const tier = target.dataset.damageTier
const spell = this.actor.items.get(itemId)
if (!spell) return
const formulaMap = {
standard: spell.system.damageDice,
overpowered: spell.system.damageDiceOverpowered,
overpowered2: spell.system.damageDiceOverpowered2,
}
const formula = formulaMap[tier]
if (!formula) return
const manualDR = await foundry.applications.api.DialogV2.wait({
window: { title: game.i18n.localize("LETHALFANTASY.Combat.spellDRDialogTitle") },
classes: ["lethalfantasy"],
position: { width: 320 },
content: `<div style="padding:0.5rem 0">
<p style="margin-bottom:0.6rem">${game.i18n.localize("LETHALFANTASY.Combat.spellDRDialogMsg")}</p>
<div style="display:flex;align-items:center;gap:0.5rem">
<label style="font-weight:bold">${game.i18n.localize("LETHALFANTASY.Combat.spellDRLabel")}</label>
<input type="number" name="manualDr" value="0" min="0" style="width:5rem"/>
</div>
</div>`,
buttons: [
{
action: "noDR",
type: "button",
label: game.i18n.localize("LETHALFANTASY.Combat.spellNoDR"),
icon: "fa-solid fa-wand-magic-sparkles",
callback: () => 0
},
{
action: "applyDR",
type: "button",
label: game.i18n.localize("LETHALFANTASY.Combat.spellApplyDR"),
icon: "fa-solid fa-shield",
callback: (event, button) => Number(button.form?.elements?.manualDr?.value) || 0
},
{
action: "cancel",
type: "button",
label: game.i18n.localize("LETHALFANTASY.Combat.proceedNo"),
callback: () => "cancel"
}
],
rejectClose: false
})
if (manualDR === null || manualDR === "cancel") return
const rollOpts = {
type: "spell-damage",
rollType: "spell-damage",
rollName: `${spell.name}${formula}`,
isDamage: true,
rollData: { isDamage: true },
manualDR,
actorId: this.actor.id,
actorName: this.actor.name,
actorImage: this.actor.img
}
await LethalFantasyRoll.rollSpellDamageToMessage(formula, rollOpts)
}
static #onCreateEquipment(event, target) {
}
_onRender(context, options) {
// Inputs with class `item-quantity`
const woundDescription = this.element.querySelectorAll('.wound-data')
for (const input of woundDescription) {
input.addEventListener("change", (e) => {
input.addEventListener("change", async (e) => {
e.preventDefault();
e.stopImmediatePropagation();
const newValue = e.currentTarget.value
@@ -221,11 +306,11 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
const fieldName = e.currentTarget.dataset.name
let tab = foundry.utils.duplicate(this.actor.system.hp.wounds)
tab[index][fieldName] = newValue
console.log(tab, index, fieldName, newValue)
this.actor.update({ "system.hp.wounds": tab });
log(tab, index, fieldName, newValue)
await this.actor.update({ "system.hp.wounds": tab });
})
}
super._onRender();
super._onRender(context, options);
}
@@ -246,10 +331,11 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
async _onRoll(event, target) {
if (this.isEditMode) return
console.log("Roll event", event)
const rollType = event.target.dataset.rollType
let rollKey = event.target.dataset.rollKey;
let rollDice = event.target.dataset?.rollDice;
const el = event.currentTarget
const rollType = el.dataset.rollType
if (!rollType) return
let rollKey = el.dataset.rollKey
let rollDice = el.dataset.rollDice
this.actor.prepareRoll(rollType, rollKey, rollDice)
+10 -6
View File
@@ -91,7 +91,7 @@ export default class LethalFantasyMonsterSheet extends LethalFantasyActorSheet {
*/
async _onDrop(event) {
if (!this.isEditable || !this.isEditMode) return
const data = TextEditor.getDragEventData(event)
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
// Handle different data types
switch (data.type) {
@@ -111,11 +111,13 @@ export default class LethalFantasyMonsterSheet extends LethalFantasyActorSheet {
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
await roll.toMessage({}, { messageMode: roll.options.rollMode })
}
static async #onRollInitiative(event, target) {
await this.document.system.rollInitiative(event, target)
const combat = game.combat
const combatant = combat?.combatants.find(c => c.actorId === this.document.id)
await this.document.system.rollInitiative(combat?.id, combatant?.id)
}
getBestWeaponClassSkill(skills, rollType, multiplier = 1.0) {
@@ -162,9 +164,11 @@ export default class LethalFantasyMonsterSheet extends LethalFantasyActorSheet {
async _onRoll(event, target) {
if (this.isEditMode) return
const rollType = event.target.dataset.rollType
let rollKey = event.target.dataset.rollKey
let rollDice = event.target.dataset?.rollDice || "0"
const rollable = event.target.closest('.rollable')
if (!rollable) return
const rollType = rollable.dataset.rollType
let rollKey = rollable.dataset.rollKey
let rollDice = rollable.dataset?.rollDice || "0"
this.actor.system.prepareMonsterRoll(rollType, rollKey, rollDice)
}
}
+673
View File
@@ -0,0 +1,673 @@
{
"d30_dice_results": {
"30": {
"melee_attack": {
"type": "choice",
"choices": [
{
"type": "special_strike",
"options": [
"lethal",
"vital"
]
},
{
"type": "bonus_dice",
"dice": "D20E",
"target": "attack"
}
],
"description": "Possible Lethal or Vital Strike or Add D20E to Attack"
},
"ranged_attack": {
"type": "choice",
"choices": [
{
"type": "special_strike",
"options": [
"lethal",
"vital"
]
},
{
"type": "bonus_dice",
"dice": "D20E",
"target": "attack"
}
],
"description": "Possible Lethal or Vital Strike or Add D20E to Attack"
},
"melee_defense": {
"type": "choice",
"choices": [
{
"type": "special_defense",
"options": [
"flawless",
"legendary"
]
},
{
"type": "bonus_dice",
"dice": "D20E",
"target": "defense"
}
],
"description": "Possible Flawless or Legendary Defense or Add D20E to Defense"
},
"arcane_spell_attack": {
"type": "choice",
"choices": [
{
"type": "special_strike",
"options": [
"lethal_magical",
"vital_magical"
]
},
{
"type": "bonus_dice",
"dice": "D20E",
"target": "spell_attack"
}
],
"description": "Possible Lethal or Vital Magical Strike or Add D20E to Spell Attack"
},
"arcane_spell_defense": {
"type": "choice",
"choices": [
{
"type": "spell_calamity"
},
{
"type": "bonus_dice",
"dice": "D20E",
"target": "spell_defense"
}
],
"description": "Possible Spell Catastrophe or adds D20E to Spell Defense"
},
"skill_rolls": {
"type": "skill_auto_success",
"description": "Skill Succeeds Regardless of Opposing Roll"
},
"ranged_defense": {
"type": "choice",
"choices": [
{
"type": "special_defense",
"options": [
"flawless",
"legendary"
]
},
{
"type": "bonus_dice",
"dice": "D20E",
"target": "defense"
}
],
"description": "Possible Flawless or Legendary Defense or Add D20E to Defense"
},
"saving_throws": {
"type": "save_auto_success",
"description": "Saving Throw Succeeds Regardless of Opposing Roll"
}
},
"29": {
"melee_attack": {
"type": "gain_grit",
"amount": 1,
"description": "Gain 1 Grit"
},
"ranged_attack": {
"type": "gain_grit",
"amount": 1,
"description": "Gain 1 Grit"
},
"melee_defense": {
"type": "gain_grit",
"amount": 1,
"description": "Gain 1 Grit"
},
"arcane_spell_attack": {
"type": "gain_grit",
"amount": 1,
"description": "Gain 1 Grit"
},
"arcane_spell_defense": {
"type": "gain_grit",
"amount": 1,
"description": "Gain 1 Grit"
},
"skill_rolls": {
"type": "gain_grit",
"amount": 1,
"description": "Gain 1 Grit"
},
"ranged_defense": {
"type": "gain_grit",
"amount": 1,
"description": "Gain 1 Grit"
},
"saving_throws": {
"type": "gain_grit",
"amount": 1,
"description": "Gain 1 Grit"
}
},
"28": {
"melee_attack": {
"type": "shield_destruction",
"description": "Shield Destruction"
}
},
"27": {
"melee_attack": {
"type": "bonus_dice",
"dice": "D6",
"target": "attack",
"description": "Granted D6 (1-6) Attack Modifier for This Melee Attack"
},
"ranged_attack": {
"type": "bonus_dice",
"dice": "D6",
"target": "attack",
"description": "Granted D6 (1-6) Attack Modifier for This Ranged Attack"
},
"melee_defense": {
"type": "luck_die",
"scope": "combat",
"description": "Granted 1 Luck dice for Use in This Combat Only"
},
"arcane_spell_attack": {
"type": "no_lethargy",
"description": "No Spell Lethargy the Aether Approves of Characters Efforts"
},
"arcane_spell_defense": {
"type": "flash_of_pain",
"duration_dice": "1D6E",
"target": "caster",
"description": "Caster Suffers Severe pain and will be under a flash of pain for 1D6E seconds"
},
"ranged_defense": {
"type": "luck_die",
"scope": "combat",
"description": "Granted 1 Luck dice for Use in This Combat Only"
}
},
"26": {
"melee_attack": {
"type": "shield_destruction",
"description": "Shield Destruction"
}
},
"25": {
"skill_rolls": {
"type": "bonus_flat",
"amount": 1,
"target": "skill",
"description": "Add 1 to Skill Roll"
},
"saving_throws": {
"type": "bonus_flat",
"amount": 1,
"target": "save",
"description": "Add 1 to Saving Throw"
}
},
"21": {
"melee_attack": {
"type": "flash_of_pain",
"duration_dice": "1D6E",
"target": "defender",
"description": "Hit Inflicts Flash of Pain 1D6E seconds"
},
"ranged_attack": {
"type": "flash_of_pain",
"duration_dice": "1D6E",
"target": "defender",
"description": "Hit Inflicts Flash of Pain 1D6E seconds"
},
"melee_defense": {
"type": "recover_pain",
"description": "Defender Recovers or ignores any flash of pain"
},
"arcane_spell_attack": {
"type": "flash_of_pain",
"duration_dice": "1D6E",
"target": "defender",
"description": "Magical Damage inflicts Flash of pain 1D6E seconds"
},
"arcane_spell_defense": {
"type": "flash_of_pain",
"duration_dice": "1D6E",
"target": "caster",
"description": "Caster Suffers Severe pain and will be under a flash of pain for 1D6E seconds"
},
"skill_rolls": {
"type": "bonus_dice",
"dice": "D6",
"target": "skill",
"description": "Granted D6 (1-6) Skill Modifier for this Skill Attempt"
},
"ranged_defense": {
"type": "recover_pain",
"description": "Defender Recovers or ignores any flash of pain"
},
"saving_throws": {
"type": "bonus_dice",
"dice": "D6",
"target": "save",
"description": "Granted D6 (1-6) Saving Throw Modifier for this Saving Throw Attempt"
}
},
"20": {
"melee_attack": {
"type": "choice",
"choices": [
{
"type": "special_strike",
"options": [
"vicious"
]
},
{
"type": "bonus_dice",
"dice": "D12",
"target": "attack"
}
],
"description": "Possible Vicious Strike or Add D12 to attack"
},
"ranged_attack": {
"type": "choice",
"choices": [
{
"type": "special_strike",
"options": [
"vicious"
]
},
{
"type": "bonus_dice",
"dice": "D12",
"target": "attack"
}
],
"description": "Possible Vicious Strike or add D12 to attack"
},
"melee_defense": {
"type": "choice",
"choices": [
{
"type": "special_defense",
"options": [
"perfect"
]
},
{
"type": "bonus_dice",
"dice": "D12",
"target": "defense"
}
],
"description": "Possible 20/20 defense that avoids Any Attack Except a Lethal Strike or adds D12 to defense"
},
"arcane_spell_attack": {
"type": "choice",
"choices": [
{
"type": "special_strike",
"options": [
"vicious_magical"
]
},
{
"type": "bonus_dice",
"dice": "D12",
"target": "spell_attack"
}
],
"description": "Possible Vicious Application of a Magical Attack or add D12 to attack"
},
"arcane_spell_defense": {
"type": "choice",
"choices": [
{
"type": "special_defense",
"options": [
"perfect_spell"
]
},
{
"type": "bonus_dice",
"dice": "D12",
"target": "spell_defense"
}
],
"description": "Possible 20/20 Spell defense that Saves Against Any Magical Attack Except a Lethal Magical Strike or add D12 to spell defense"
},
"skill_rolls": {
"type": "bonus_flat",
"amount": 20,
"target": "skill",
"description": "20 Added to Skill Roll"
},
"ranged_defense": {
"type": "choice",
"choices": [
{
"type": "special_defense",
"options": [
"perfect"
]
},
{
"type": "bonus_dice",
"dice": "D12",
"target": "defense"
}
],
"description": "Possible 20/20 defense that avoids Any Attack Except a Lethal Strike or adds D12 to defense"
},
"saving_throws": {
"type": "bonus_flat",
"amount": 20,
"target": "save",
"description": "20 Added to Saving Throw"
}
},
"15": {
"melee_attack": {
"type": "combo",
"effects": [
{
"type": "bleed"
},
{
"type": "internal_injury"
}
],
"description": "Bleed, Internal Injury on Hit"
},
"ranged_attack": {
"type": "bleed",
"description": "Bleed"
},
"melee_defense": {
"type": "counter_attack",
"options": [
"kick",
"punch"
],
"description": "Kick or Punch"
},
"skill_rolls": {
"type": "bonus_flat",
"amount": 1,
"target": "skill",
"description": "Add 1 to Skill Roll"
},
"ranged_defense": {
"type": "counter_attack",
"options": [
"kick",
"punch"
],
"description": "Kick or Punch"
},
"saving_throws": {
"type": "bonus_flat",
"amount": 1,
"target": "save",
"description": "Add 1 to Saving Throw"
}
},
"13": {},
"11": {
"melee_attack": {
"type": "flurry",
"condition": "hit_or_miss",
"description": "Flurry Attack on Hit or Miss"
},
"ranged_attack": {
"type": "double_damage_dice",
"description": "Roll 2x Damage Dice"
}
},
"10": {
"melee_attack": {
"type": "combo",
"effects": [
{
"type": "bleed"
},
{
"type": "internal_injury"
}
],
"description": "Bleed, Internal Injury on Hit"
},
"ranged_attack": {
"type": "bleed",
"description": "Bleed"
},
"melee_defense": {
"type": "counter_attack",
"options": [
"kick",
"punch"
],
"description": "Kick or Punch"
},
"skill_rolls": {
"type": "bonus_flat",
"amount": 1,
"target": "skill",
"description": "Add 1 to Skill Roll"
},
"ranged_defense": {
"type": "counter_attack",
"options": [
"kick",
"punch"
],
"description": "Kick or Punch"
},
"saving_throws": {
"type": "bonus_flat",
"amount": 1,
"target": "save",
"description": "Add 1 to Saving Throw"
}
},
"8": {
"melee_attack": {
"type": "mulligan",
"description": "Mulligan, Can Choose to Re-roll This Attack"
},
"ranged_attack": {
"type": "mulligan",
"description": "Mulligan, Can Choose to Re-Roll This Attack"
},
"melee_defense": {
"type": "mulligan",
"description": "Mulligan, Can Choose to Re-Roll This Defense"
},
"arcane_spell_attack": {
"type": "mulligan",
"description": "Mulligan, Can Re-Roll This Spell Attack"
},
"arcane_spell_defense": {
"type": "mulligan",
"description": "Mulligan, Can Re-Roll This Spell Defense"
},
"skill_rolls": {
"type": "mulligan",
"description": "Mulligan, Can Re-Roll This Skill roll"
},
"ranged_defense": {
"type": "mulligan",
"description": "Mulligan, Can Choose to Re-Roll This Defense"
},
"saving_throws": {
"type": "mulligan",
"description": "Mulligan, Can Re-Roll This Saving Throw"
}
},
"7": {
"melee_attack": {
"type": "flurry",
"condition": "hit_or_miss",
"description": "Flurry Attack on Hit or Miss"
},
"ranged_attack": {
"type": "double_damage_dice",
"description": "Roll 2x Damage Dice"
}
},
"5": {
"melee_attack": {
"type": "combo",
"effects": [
{
"type": "bleed"
},
{
"type": "internal_injury"
}
],
"description": "Bleed, Internal Injury on Hit"
},
"ranged_attack": {
"type": "bleed",
"description": "Bleed"
},
"melee_defense": {
"type": "counter_attack",
"options": [
"kick",
"punch"
],
"description": "Kick or Punch"
},
"skill_rolls": {
"type": "bonus_flat",
"amount": 1,
"target": "skill",
"description": "Add 1 to Skill Roll"
},
"ranged_defense": {
"type": "counter_attack",
"options": [
"kick",
"punch"
],
"description": "Kick or Punch"
},
"saving_throws": {
"type": "bonus_flat",
"amount": 1,
"target": "save",
"description": "Add 1 to Saving Throw"
}
},
"3": {
"melee_attack": {
"type": "damage_multiplier",
"multiplier": 3,
"description": "Triple Damage"
},
"ranged_attack": {
"type": "damage_multiplier",
"multiplier": 3,
"description": "Triple Damage"
},
"melee_defense": {
"type": "dr_multiplier",
"multiplier": 3,
"includes_shield": true,
"description": "DR Tripled including Shield"
},
"arcane_spell_attack": {
"type": "damage_multiplier",
"multiplier": 3,
"description": "Triple Damage on Spell Damage"
},
"arcane_spell_defense": {
"type": "bonus_dice",
"dice": "D12",
"target": "spell_defense",
"description": "D12 Added to Spell Defense Modifier"
},
"ranged_defense": {
"type": "dr_multiplier",
"multiplier": 3,
"includes_shield": true,
"description": "DR Tripled including Shield"
}
},
"2": {
"melee_attack": {
"type": "damage_multiplier",
"multiplier": 2,
"description": "Double Damage"
},
"ranged_attack": {
"type": "damage_multiplier",
"multiplier": 2,
"description": "Double Damage"
},
"melee_defense": {
"type": "dr_multiplier",
"multiplier": 2,
"includes_shield": true,
"description": "DR Doubled including Shield"
},
"arcane_spell_attack": {
"type": "damage_multiplier",
"multiplier": 2,
"description": "Double Damage on Spell Damage"
},
"arcane_spell_defense": {
"type": "bonus_dice",
"dice": "D6",
"target": "spell_defense",
"description": "D6 Added to Spell Defense Modifier"
},
"ranged_defense": {
"type": "dr_multiplier",
"multiplier": 2,
"includes_shield": true,
"description": "DR Doubled including Shield"
}
},
"1": {
"ranged_attack": {
"type": "fumble",
"detail": "ranged_ammo_broken",
"description": "Possible Fumble Ranged ammo is broken unrecoverable"
},
"arcane_spell_attack": {
"type": "spell_calamity",
"description": "A possible spell calamity has occurred"
},
"arcane_spell_defense": {
"type": "spell_calamity",
"description": "Possible Spell Calamity or Catastrophe"
},
"melee_attack": {
"type": "fumble",
"detail": "melee_fumble",
"description": "Possible Fumble"
}
}
},
"definitions": {
"flash_of_pain": "Causes the victim to defend against melee and spell attacks with disfavor. They can only walk and cannot attack, cast spells, call miracles or perform skills.",
"shield_destruction_condition": "Shield destruction occurs only if damage exceeds the shields DR.",
"matching_30s": "Matching 30s on skill rolls cancel each other out and is resolved by the skill roll.",
"skill_roll_30": "A 30 on a skill roll indicates success at highest level of the skill involved."
}
}
+26 -3
View File
@@ -85,11 +85,18 @@ export const RANGE_CHOICES = {
"pointblank": { label: "Point Blank (Special)", value: "pointblank" },
"short": { label: "Short (+0)", value: "0" },
"medium": { label: "Medium (Red +5)", value: "+5" },
"long": { label: "Long (Purle +7)", value: "+7" },
"long": { label: "Long (Purple +7)", value: "+7" },
"extreme": { label: "Extreme (Grey +9)", value: "+9" },
"beyondskill": { label: "Beyond Skill (Blue +11)", value: "beyondskill" }
}
export const ATTACKER_MOVEMENT_CHOICES = {
"none": { label: "None / Stationary (D20E Favor)", favor: true, value: "2D20kh" },
"walk": { label: "Walk (D20E)", value: "D20" },
"incombat": { label: "In Combat (D20E)", value: "D20" },
"run": { label: "Jog/Run/Sprint (D20E Disfavor)", disfavor: true, value: "2D20kl" }
}
export const ATTACKER_AIM_CHOICES = {
"simple": { label: "Simple (+0)", value: "0" },
"careful": { label: "Careful (Red +5)", value: "+4" },
@@ -104,6 +111,18 @@ export const SPELL_LETHARGY_DICE = [
{ dice: "D20", value: "20", level: "21-25", maxLevel: 25 }
]
export const GRANTED_DICE_CHOICES = {
"0": { label: "None", value: "0" },
"D2": { label: "D2", value: "D2" },
"D3": { label: "D3", value: "D3" },
"D4": { label: "D4", value: "D4" },
"D6": { label: "D6", value: "D6" },
"D8": { label: "D8", value: "D8" },
"D10": { label: "D10", value: "D10" },
"D12": { label: "D12", value: "D12" },
"D20": { label: "D20", value: "D20" }
}
export const INITIATIVE_DICE_CHOICES_PER_CLASS = {
"untrained": [
{ "name": "Asleep or totally distracted (2D12)", "value": "2D12" },
@@ -136,7 +155,7 @@ export const INITIATIVE_DICE_CHOICES_PER_CLASS = {
"cleric": [
{ "name": "Asleep or totally distracted (1D12)", "value": "1D12" },
{ "name": "Awake but unsuspecting (1D10)", "value": "1D10" },
{ "name": "Declared Ready on Alert (1D)", "value": "1D" },
{ "name": "Declared Ready on Alert (1)", "value": "1" },
/*{ "name": "Aware of the enemy, can hear them but not see (1D6)", "value": "1D6" },
{ "name": "Aware and know exactly where the enemy is (1D4)", "value": "1D4" }*/
],
@@ -181,6 +200,7 @@ export const DICE_VALUES = {
export const CHARACTERISTIC_ATTACK = ["str", "int", "wis", "dex"]
export const CHARACTERISTIC_RANGED_ATTACK = ["int", "wis", "dex"]
export const CHARACTERISTIC_DEFENSE = ["int", "wis", "dex"]
export const CHARACTERISTIC_RANGED_DEFENSE = ["int", "wis", "dex"]
export const CHARACTERISTIC_DAMAGE = ["str"]
export const DEFENSE_DICE_VALUES = {
@@ -292,6 +312,7 @@ export const SYSTEM = {
CHARACTERISTIC_ATTACK,
CHARACTERISTIC_RANGED_ATTACK,
CHARACTERISTIC_DEFENSE,
CHARACTERISTIC_RANGED_DEFENSE,
CHARACTERISTIC_DAMAGE,
INITIATIVE_DICE_CHOICES_PER_CLASS,
CHAR_CLASSES,
@@ -307,8 +328,10 @@ export const SYSTEM = {
RANGE_CHOICES,
FAVOR_CHOICES,
ATTACKER_AIM_CHOICES,
ATTACKER_MOVEMENT_CHOICES,
MORTAL_CHOICES,
SPELL_CRITICAL,
MIRACLE_TYPES,
SPELL_LETHARGY_DICE
SPELL_LETHARGY_DICE,
GRANTED_DICE_CHOICES
}
+1
View File
@@ -2,3 +2,4 @@ export { default as LethalFantasyActor } from "./actor.mjs"
export { default as LethalFantasyItem } from "./item.mjs"
export { default as LethalFantasyRoll } from "./roll.mjs"
export { default as LethalFantasyChatMessage } from "./chat-message.mjs"
export { default as D30Roll } from "./d30-roll.mjs"
+185 -51
View File
@@ -65,22 +65,105 @@ export default class LethalFantasyActor extends Actor {
}
}
}
goodSkill.weaponSkillModifier = maxValue * multiplier
goodSkill.weaponSkillModifier = Math.ceil(maxValue * multiplier)
return goodSkill
}
/* *************************************************/
async applyDamage(hpLoss) {
let hp = this.system.hp.value + hpLoss
if (hp < 0) {
hp = 0
}
this.update({ "system.hp.value": hp })
await this.update({ "system.hp.value": hp })
}
/* *************************************************/
async prepareRoll(rollType, rollKey, rollDice ) {
console.log("Preparing roll", rollType, rollKey, rollDice)
getNaturalDR() {
if (this.type === "monster") {
return Number(this.system.hp?.damageResistance) || 0
}
return Number(this.system.biodata?.naturalDR) || 0
}
/* *************************************************/
getMagicDR() {
if (this.type === "monster") return 0
return Number(this.system.biodata?.magicDR) || 0
}
/* *************************************************/
computeDamageReduction() {
if (this.type === "monster") {
let hpDR = this.getNaturalDR()
let combatDR = Number(this.system.combat?.damageReduction) || 0
return hpDR + combatDR
}
let naturalDR = this.getNaturalDR()
let magicDR = this.getMagicDR()
let armorDR = this.getArmorDR()
return naturalDR + magicDR + armorDR
}
/* *************************************************/
getShieldDR() {
// Pour les monstres, utiliser combat.shieldDamageReduction
if (this.type === "monster") {
return Number(this.system.combat?.shieldDamageReduction) || 0
}
// Pour les personnages, utiliser les items de type shield
let dr = 0
for (let item of this.items) {
if (item.type === "shield" && item.system.equipped) {
dr += Number(item.system.damageReduction)
}
}
return dr
}
/* *************************************************/
getArmorDR() {
let dr = 0
for (let item of this.items) {
if (item.type === "armor" && item.system.equipped) {
dr += Number(item.system.damageReduction)
}
}
return dr
}
/* *************************************************/
getArmorDefenseValue() {
let defenseValue = 0
for (let item of this.items) {
if (item.type === "armor" && item.system.equipped) {
defenseValue += Number(item.system.defense)
}
}
return defenseValue
}
/* *************************************************/
fuzzyNameSearchWeaponSkills(weaponName, weaponClass = null) {
// Get all weapon skills without the " skill" suffix
let skills = this.items.filter((i) => i.type === "skill" && i.system.weaponClass === weaponClass && i.system.category === "weapon")
// Remove parenthesis in the weapon name for better matching
weaponName = weaponName.replace(/\(.*?\)/g, "").trim()
// Now search if we find all the words of the weapon name in the skill name
skills = skills.filter((s) => {
let skillName = s.name.toLowerCase().replace(" skill", "").trim()
let wName = weaponName.toLowerCase().trim()
let wWords = wName.split(" ")
for (let w of wWords) {
if (!skillName.includes(w)) {
return false
}
}
return true
})
return skills
}
/* *************************************************/
async prepareRoll(rollType, rollKey, rollDice, defenderId, defenderTokenId, extraShieldDr = 0, d30Effects = {}) {
log("Preparing roll", rollType, rollKey, rollDice, defenderId)
let rollTarget
switch (rollType) {
case "granted":
@@ -89,7 +172,7 @@ export default class LethalFantasyActor extends Actor {
formula: foundry.utils.duplicate(this.system.granted[rollKey]),
rollKey: rollKey
}
if ( rollTarget.formula === "" || rollTarget.formula === undefined) {
if (rollTarget.formula === "" || rollTarget.formula === undefined) {
rollTarget.formula = 0
}
break;
@@ -122,75 +205,126 @@ export default class LethalFantasyActor extends Actor {
case "spell-power":
case "miracle-attack":
case "miracle-power":
rollTarget = this.items.find((i) => (i.type === "miracle" || i.type == "spell") && i.id === rollKey)
rollTarget = foundry.utils.duplicate(this.items.find((i) => (i.type === "miracle" || i.type == "spell") && i.id === rollKey))
rollTarget.rollKey = rollKey
// Read damage tier from combatant currentAction if available
const activeCombatant = game.combat?.combatants?.find(c => c.actorId === this.id)
const currentAction = activeCombatant?.getFlag(SYSTEM.id, "currentAction")
let damageTier = currentAction?.damageTier
// No tier from combat action — prompt the user if multiple tiers exist
if (!damageTier) {
const tierMap = { standard: "damageDice", overpowered: "damageDiceOverpowered", overpowered2: "damageDiceOverpowered2" }
const available = Object.entries(tierMap).filter(([k, v]) => rollTarget.system?.[v])
if (available.length > 1) {
const buttons = available.map(([id]) => ({
action: id,
type: "button",
label: id.charAt(0).toUpperCase() + id.slice(1),
callback: () => id,
}))
damageTier = await foundry.applications.api.DialogV2.wait({
window: { title: "Choose spell tier" },
classes: ["lethalfantasy"],
content: `<p>Select the power level for <strong>${rollTarget.name}</strong>:</p>`,
buttons,
rejectClose: false,
}) || "standard"
} else {
damageTier = "standard"
}
}
rollTarget.damageTier = damageTier
if (rollType === "spell-attack" || rollType === "spell-power") {
const tierCostMap = { standard: "cost", overpowered: "costOverpowered", overpowered2: "costOverpowered2" }
const costField = tierCostMap[damageTier] || "cost"
const cost = Number(rollTarget.system?.[costField]) || 0
const currentAether = Number(this.system.aetherPoints?.value) || 0
if (cost > currentAether) {
ui.notifications.warn(`${this.name} cannot cast ${rollTarget.name}: insufficient Aether (needs ${cost}, has ${currentAether}).`)
return
}
}
if (rollType === "miracle-attack" || rollType === "miracle-power") {
const cost = Number(rollTarget.system?.level) || 0
const currentGrace = Number(this.system.divinityPoints?.value) || 0
if (cost > currentGrace) {
ui.notifications.warn(`${this.name} cannot invoke ${rollTarget.name}: insufficient Grace (needs ${cost}, has ${currentGrace}).`)
return
}
}
break
case "shield-roll": {
rollTarget = this.items.find((i) => i.type === "shield" && i.id === rollKey)
let shieldSkill = this.items.find((i) => i.type === "skill" && i.name.toLowerCase() === rollTarget.name.toLowerCase())
rollTarget.skill = shieldSkill
rollTarget.rollKey = rollKey
}
rollTarget = this.items.find((i) => i.type === "shield" && i.id === rollKey)
let shieldSkill = this.items.find((i) => i.type === "skill" && i.name.toLowerCase() === rollTarget.name.toLowerCase())
rollTarget.skill = shieldSkill
rollTarget.rollKey = rollKey
}
break;
case "weapon-damage-small":
case "weapon-damage-medium":
case "weapon-damage":
case "weapon-attack":
case "weapon-defense": {
let weapon = this.items.find((i) => i.type === "weapon" && i.id === rollKey)
let skill
let skills = this.items.filter((i) => i.type === "skill" && i.name.toLowerCase() === weapon.name.toLowerCase())
let weapon = this.items.find((i) => i.type === "weapon" && i.id === rollKey)
let skill
let skills = this.items.filter((i) => i.type === "skill" && i.system.category === "weapon" && i.name.toLowerCase() === weapon.name.toLowerCase())
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
} else {
skills = this.fuzzyNameSearchWeaponSkills(weapon.name, weapon.system.weaponClass)
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
} else {
skills = this.items.filter((i) => i.type === "skill" && i.name.toLowerCase().replace(" skill", "") === weapon.name.toLowerCase())
skills = this.items.filter((i) => i.type === "skill" && i.system.category === "weapon" && i.system.weaponClass === weapon.system.weaponClass)
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
skill = this.getBestWeaponClassSkill(skills, rollType, 0.5)
} else {
skills = this.items.filter((i) => i.type === "skill" && i.system.weaponClass === weapon.system.weaponClass)
skills = this.items.filter((i) => i.type === "skill" && i.system.category === "weapon" && i.system.weaponClass.includes(SYSTEM.WEAPON_CATEGORIES[weapon.system.weaponClass]))
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 0.5)
skill = this.getBestWeaponClassSkill(skills, rollType, 0.25)
} else {
skills = this.items.filter((i) => i.type === "skill" && i.system.weaponClass.includes(SYSTEM.WEAPON_CATEGORIES[weapon.system.weaponClass]))
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 0.25)
} else {
ui.notifications.warn(game.i18n.localize("LETHALFANTASY.Notifications.skillNotFound"))
return
}
ui.notifications.warn(game.i18n.localize("LETHALFANTASY.Notifications.skillNotFound"))
return
}
}
}
if (!weapon || !skill) {
console.error("Weapon or skill not found", weapon, skill)
ui.notifications.warn(game.i18n.localize("LETHALFANTASY.Notifications.skillNotFound"))
return
}
rollTarget = skill
rollTarget.weapon = weapon
rollTarget.weaponSkillModifier = skill.weaponSkillModifier
rollTarget.rollKey = rollKey
rollTarget.combat = foundry.utils.duplicate(this.system.combat)
if ( rollType === "weapon-damage-small" || rollType === "weapon-damage-medium") {
rollTarget.grantedDice = this.system.granted.damageDice
}
if ( rollType === "weapon-attack") {
rollTarget.grantedDice = this.system.granted.attackDice
}
if ( rollType === "weapon-defense") {
rollTarget.grantedDice = this.system.granted.defenseDice
}
}
if (!weapon || !skill) {
console.error("Weapon or skill not found", weapon, skill)
ui.notifications.warn(game.i18n.localize("LETHALFANTASY.Notifications.skillNotFound"))
return
}
// Créer un objet plain au lieu de modifier directement le skill
rollTarget = {
...skill,
weapon: weapon,
weaponSkillModifier: skill.weaponSkillModifier,
rollKey: rollKey,
combat: foundry.utils.duplicate(this.system.combat),
isRangedAttack: weapon.system.weaponType === "ranged"
}
if (rollType === "weapon-damage") {
rollTarget.grantedDice = this.system.granted.damageDice
}
if (rollType === "weapon-attack") {
rollTarget.grantedDice = this.system.granted.attackDice
}
if (rollType === "weapon-defense") {
rollTarget.armorDefense = this.getArmorDefenseValue()
rollTarget.grantedDice = this.system.granted.defenseDice
// Check if this is a ranged defense
rollTarget.isRangedDefense = game.lethalFantasy?.nextDefenseData?.isRanged ?? false
}
}
break
default:
ui.notifications.error(game.i18n.localize("LETHALFANTASY.Notifications.rollTypeNotFound") + String(rollType))
break
return
}
// In all cases
rollTarget.magicUser = this.system.biodata.magicUser
rollTarget.actorModifiers = foundry.utils.duplicate(this.system.modifiers)
rollTarget.actorLevel = this.system.biodata.level
await this.system.roll(rollType, rollTarget)
await this.system.roll(rollType, rollTarget, defenderId, defenderTokenId, extraShieldDr, d30Effects)
}
}
+228
View File
@@ -0,0 +1,228 @@
import { log } from "../utils.mjs"
/**
* Classe pour gérer les résultats du D30 dans Lethal Fantasy
*/
export default class D30Roll {
/**
* Table des résultats D30 chargée depuis le fichier JSON
* @type {Object}
*/
static resultsTable = null
/**
* Définitions des conditions spéciales
* @type {Object}
*/
static definitions = null
/**
* Types de jets supportés
* @type {Object}
*/
static ROLL_TYPES = {
MELEE_ATTACK: "melee_attack",
RANGED_ATTACK: "ranged_attack",
MELEE_DEFENSE: "melee_defense",
RANGED_DEFENSE: "ranged_defense",
ARCANE_SPELL_ATTACK: "arcane_spell_attack",
ARCANE_SPELL_DEFENSE: "arcane_spell_defense",
SKILL_ROLLS: "skill_rolls",
SAVING_THROWS: "saving_throws"
}
/**
* Initialise la classe en chargeant la table des résultats
* @returns {Promise<void>}
*/
static async initialize() {
try {
const response = await fetch("systems/fvtt-lethal-fantasy/module/config/d30_results_tables.json")
const data = await response.json()
this.resultsTable = data.d30_dice_results
this.definitions = data.definitions
log("D30Roll | D30 results table loaded successfully")
} catch (error) {
console.error("D30Roll | Error loading D30 table:", error)
ui.notifications.error("Unable to load D30 results table")
}
}
/**
* Récupère le résultat d'un jet de D30 sous forme d'objet structuré.
* @param {number} diceValue La valeur du dé (1-30)
* @param {string} rollType Le type de jet externe (ex: "weapon-attack", "spell-attack", etc.)
* @param {Object} weapon L'arme ou l'objet utilisé (optionnel, nécessaire pour certains types)
* @param {Object} options Options supplémentaires (optionnel)
* @param {boolean} options.isRanged Si true, utilise ranged_defense au lieu de melee_defense
* @returns {Object|null} L'objet effet `{ type, description, ...fields }` ou null si aucun effet
*/
static getResult(diceValue, rollType, weapon = null, options = {}) {
if (!this.resultsTable) {
console.warn("D30Roll | Results table is not initialized. Call D30Roll.initialize() first.")
return null
}
if (diceValue < 1 || diceValue > 30) {
console.warn(`D30Roll | Invalid dice value: ${diceValue}. Must be between 1 and 30.`)
return null
}
const internalType = this.convertToInternalType(rollType, weapon, options)
if (!internalType) {
console.warn(`D30Roll | Could not convert roll type: ${rollType}`)
return null
}
if (!Object.values(this.ROLL_TYPES).includes(internalType)) {
console.warn(`D30Roll | Invalid internal roll type: ${internalType}`)
return null
}
const resultEntry = this.resultsTable[diceValue]
if (!resultEntry) {
console.warn(`D30Roll | No entry found for value ${diceValue}`)
return null
}
const result = resultEntry[internalType]
return result ?? null
}
/**
* Retourne le type d'effet d'un résultat D30.
* @param {Object|null} result L'objet retourné par getResult()
* @returns {string|null} Le type d'effet ou null
*/
static getEffectType(result) {
return result?.type ?? null
}
/**
* Convertit un rollType externe en rollType interne
* @param {string} externalType Le type de jet externe (ex: "weapon-attack")
* @param {Object} weapon L'arme ou l'objet utilisé (optionnel)
* @param {Object} options Options supplémentaires (optionnel)
* @param {boolean} options.isRanged Si true, utilise ranged_defense au lieu de melee_defense
* @returns {string|null} Le type interne correspondant ou null
*/
static convertToInternalType(externalType, weapon = null, options = {}) {
// Attack types - need weapon to determine if melee or ranged
if (externalType === "weapon-attack") {
if (!weapon) {
console.warn("D30Roll | Weapon object required for weapon-attack type")
// Fall through to use options.isRanged if available, otherwise default melee
}
return (options.isRanged || weapon?.system?.weaponType === "ranged")
? this.ROLL_TYPES.RANGED_ATTACK
: this.ROLL_TYPES.MELEE_ATTACK
}
// Monster attacks - check options.isRanged (set from rollTarget.attackMode) or weapon type
if (externalType === "monster-attack") {
if (options.isRanged || weapon?.system?.weaponType === "ranged") {
return this.ROLL_TYPES.RANGED_ATTACK
}
return this.ROLL_TYPES.MELEE_ATTACK
}
// Defense types
if (externalType === "weapon-defense" || externalType === "monster-defense") {
return options.isRanged ? this.ROLL_TYPES.RANGED_DEFENSE : this.ROLL_TYPES.MELEE_DEFENSE
}
// Spell types
if (externalType === "spell-attack" || externalType === "spell" || externalType === "spell-power") {
return this.ROLL_TYPES.ARCANE_SPELL_ATTACK
}
// Skill types
if (externalType === "skill" || externalType === "monster-skill" || externalType === "challenge") {
return this.ROLL_TYPES.SKILL_ROLLS
}
// Saving throw types
if (externalType === "save") {
return options.isSpellSave ? this.ROLL_TYPES.ARCANE_SPELL_DEFENSE : this.ROLL_TYPES.SAVING_THROWS
}
// If no match, return null
console.warn(`D30Roll | Unknown external roll type: ${externalType}`)
return null
}
/**
* Récupère toutes les informations pour une valeur de dé donnée
* @param {number} diceValue La valeur du dé (1-30)
* @returns {Object|null} Tous les résultats pour cette valeur ou null
*/
static getAllResultsForValue(diceValue) {
if (!this.resultsTable) {
console.warn("D30Roll | Results table is not initialized.")
return null
}
if (diceValue < 1 || diceValue > 30) {
console.warn(`D30Roll | Invalid dice value: ${diceValue}`)
return null
}
return this.resultsTable[diceValue]
}
/**
* Récupère la définition d'une condition spéciale
* @param {string} definitionKey La clé de la définition (ex: "flash_of_pain")
* @returns {string|null} La définition ou null
*/
static getDefinition(definitionKey) {
if (!this.definitions) {
console.warn("D30Roll | Definitions are not initialized.")
return null
}
return this.definitions[definitionKey] || null
}
/**
* Vérifie si un résultat est vide
* @param {Object|null} result Le résultat à vérifier
* @returns {boolean} True si le résultat est vide
*/
static isEmptyResult(result) {
return !result || !result.type
}
/**
* Récupère un résultat formaté pour l'affichage
* @param {number} diceValue La valeur du dé (1-30)
* @param {string} rollType Le type de jet externe
* @param {Object} weapon L'arme ou l'objet utilisé (optionnel)
* @param {Object} options Options supplémentaires (optionnel)
* @returns {Object} Un objet avec le résultat et des informations de formatage
*/
static getFormattedResult(diceValue, rollType, weapon = null, options = {}) {
const result = this.getResult(diceValue, rollType, weapon, options)
const internalType = this.convertToInternalType(rollType, weapon, options)
return {
value: diceValue,
rollType: rollType,
internalType: internalType,
result: result,
isEmpty: this.isEmptyResult(result),
hasResult: !this.isEmptyResult(result)
}
}
/**
* Vérifie si la table est chargée
* @returns {boolean} True si la table est chargée
*/
static isInitialized() {
return this.resultsTable !== null && this.definitions !== null
}
}
+574 -132
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -32,20 +32,20 @@ export class Macros {
dropData.rollType === "save"
? `game.actors.get('${dropData.actorId}').system.roll('${dropData.rollType}', '${dropData.rollTarget}', '=');`
: `game.actors.get('${dropData.actorId}').system.roll('${dropData.rollType}', '${dropData.rollTarget}');`
const rollName = `${game.i18n.localize("TENEBRIS.Label.jet")} ${game.i18n.localize(`TENEBRIS.Manager.${dropData.rollTarget}`)}`
const rollName = `${game.i18n.localize("LETHALFANTASY.Label.jet")} ${game.i18n.localize(`LETHALFANTASY.Label.${dropData.rollTarget}`)}`
this.createMacro(slot, rollName, rollCommand, "icons/svg/d20-grey.svg")
break
case "rollDamage":
const weapon = game.actors.get(dropData.actorId).items.get(dropData.rollTarget)
const rollDamageCommand = `game.actors.get('${dropData.actorId}').system.roll('${dropData.rollType}', '${dropData.rollTarget}');`
const rollDamageName = `${game.i18n.localize("TENEBRIS.Label.jet")} ${weapon.name}`
const rollDamageName = `${game.i18n.localize("LETHALFANTASY.Label.jet")} ${weapon.name}`
this.createMacro(slot, rollDamageName, rollDamageCommand, weapon.img)
break
case "rollAttack":
const rollAttackCommand = `game.actors.get('${dropData.actorId}').system.roll('${dropData.rollValue}', '${dropData.rollTarget}');`
const rollAttackName = `${game.i18n.localize("TENEBRIS.Label.jet")} ${dropData.rollTarget}`
const rollAttackName = `${game.i18n.localize("LETHALFANTASY.Label.jet")} ${dropData.rollTarget}`
this.createMacro(slot, rollAttackName, rollAttackCommand, "icons/svg/d20-grey.svg")
break
@@ -57,7 +57,7 @@ export class Macros {
/**
* Create a macro
* All macros are flaged with a tenebris.macro flag at true
* All macros are flaged with a lethalFantasy.macro flag at true
* @param {*} slot
* @param {*} name
* @param {*} command
@@ -72,7 +72,7 @@ export class Macros {
type: "script",
img: img,
command: command,
flags: { "tenebris.macro": true },
flags: { "lethalFantasy.macro": true },
},
{ displaySheet: false },
)
+42 -14
View File
@@ -65,7 +65,7 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
}
schema.hp = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 1 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
painDamage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
wounds: new fields.ArrayField(new fields.SchemaField(woundFieldSchema), {
@@ -90,9 +90,9 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
current: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.granted = new fields.SchemaField({
attackDice: new fields.StringField({ required: true, nullable: false, initial: "" }),
defenseDice: new fields.StringField({ required: true, nullable: false, initial: "" }),
damageDice: new fields.StringField({ required: true, nullable: false, initial: "" })
attackDice: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.GRANTED_DICE_CHOICES }),
defenseDice: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.GRANTED_DICE_CHOICES }),
damageDice: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.GRANTED_DICE_CHOICES })
})
schema.movement = new fields.SchemaField({
@@ -120,6 +120,8 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
magicUser: new fields.BooleanField({ initial: false }),
clericUser: new fields.BooleanField({ initial: false }),
hpPerLevel: new fields.StringField({ required: true, nullable: false, initial: "" }),
naturalDR: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
magicDR: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
})
schema.modifiers = new fields.SchemaField({
@@ -150,9 +152,11 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
attackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
rangedAttackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
defenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
rangedDefenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
defenseBonus: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
damageModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
armorHitPoints: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
combatProgressionStart: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }),
})
const moneyField = (label) => {
@@ -179,13 +183,13 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
if (!SYSTEM.MORTAL_CHOICES[data.biodata.mortal]) {
for (let key in SYSTEM.MORTAL_CHOICES) {
let mortal = SYSTEM.MORTAL_CHOICES[key]
if ( mortal.label.toLowerCase() === data.biodata.mortal.toLowerCase()) {
if (mortal.label.toLowerCase() === data.biodata.mortal.toLowerCase()) {
data.biodata.mortal = mortal.id
}
if ( data.biodata.mortal.toLowerCase().includes("shire")) {
if (data.biodata.mortal.toLowerCase().includes("shire")) {
data.biodata.mortal = "halflings"
}
if ( data.biodata.mortal.toLowerCase().includes("human")) {
if (data.biodata.mortal.toLowerCase().includes("human")) {
data.biodata.mortal = "mankind"
}
}
@@ -235,8 +239,8 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
this.saves.toughness.value = conDef.toughness_save + this.modifiers.saveModifier
this.challenges.dying.value = conDef.stabilization_dice
this.saves.contagion.value = this.characteristics.con.value + this.modifiers.saveModifier
this.saves.poison.value = this.characteristics.con.value + this.modifiers.saveModifier
this.saves.contagion.value = this.characteristics.con.value;// + this.modifiers.saveModifier
this.saves.poison.value = this.characteristics.con.value; // + this.modifiers.saveModifier
this.combat.attackModifier = 0
for (let chaKey of SYSTEM.CHARACTERISTIC_ATTACK) {
@@ -256,6 +260,12 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
this.combat.defenseModifier += chaDef.defense
}
this.combat.rangedDefenseModifier = this.combat.defenseBonus
for (let chaKey of SYSTEM.CHARACTERISTIC_RANGED_DEFENSE) {
let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
this.combat.rangedDefenseModifier += chaDef.defense
}
this.combat.damageModifier = 0
for (let chaKey of SYSTEM.CHARACTERISTIC_DAMAGE) {
let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
@@ -271,7 +281,7 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
* @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=).
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
*/
async roll(rollType, rollTarget) {
async roll(rollType, rollTarget, defenderId, defenderTokenId, extraShieldDr = 0, d30Effects = {}) {
const hasTarget = false
let roll = await LethalFantasyRoll.prompt({
rollType,
@@ -280,11 +290,18 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
actorName: this.parent.name,
actorImage: this.parent.img,
hasTarget,
target: false
target: false,
defenderId,
defenderTokenId,
extraShieldDr,
damageTier: rollTarget.damageTier || "standard",
d30Bleed: d30Effects.d30Bleed || false,
d30DamageMultiplier: d30Effects.d30DamageMultiplier || 1,
d30DrMultiplier: d30Effects.d30DrMultiplier || 1
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
await roll.toMessage({}, { messageMode: roll.options.rollMode })
}
async rollInitiative(combatId = undefined, combatantId = undefined) {
@@ -293,7 +310,6 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
let wisDef = SYSTEM.CHARACTERISTICS_TABLES.wis.find((c) => c.value === this.characteristics.wis.value)
let maxInit = Number(wisDef.init_cap) || 1000
console.log("Rolling initiative for", this)
let roll = await LethalFantasyRoll.promptInitiative({
actorId: this.parent.id,
@@ -306,10 +322,17 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
await roll.toMessage({}, { messageMode: roll.options.rollMode })
}
async rollProgressionDice(combatId, combatantId, rollProgressionCount) {
let combatant = game.combats.get(combatId)?.combatants?.get(combatantId)
// Don't roll if the combatant is defeated
if (combatant?.isDefeated) {
ui.notifications.warn(`${this.parent.name} is defeated and cannot attack.`)
return
}
// Get all weapons from the actor
let weapons = this.parent.items.filter(i => i.type === "weapon" && i.system.weaponType === "melee")
@@ -337,6 +360,11 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
}
}
if (weaponsChoices.length === 0) {
ui.notifications.warn(`${this.parent.name} has no weapons or spells available for combat. Add a weapon to the character sheet first.`)
return
}
let roll = await LethalFantasyRoll.promptCombatAction({
actorId: this.parent.id,
actorName: this.parent.name,
+3
View File
@@ -35,6 +35,9 @@ export default class LethalFantasyMiracle extends foundry.abstract.TypeDataModel
schema.attackRoll = new fields.StringField({ required: true, initial: "" })
schema.powerRoll = new fields.StringField({ required: true, initial: "" })
schema.damageDice = new fields.StringField({ required: false, initial: "" })
schema.damageDiceOverpowered = new fields.StringField({ required: false, initial: "" })
schema.damageDiceOverpowered2 = new fields.StringField({ required: false, initial: "" })
return schema
}
+177 -24
View File
@@ -56,15 +56,33 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
}, {}),
)
const woundFieldSchema = {
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
duration: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
description: new fields.StringField({ initial: "", required: false, nullable: true }),
}
schema.hp = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 1 }),
average: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
wounds: new fields.ArrayField(new fields.SchemaField(woundFieldSchema), {
initial: [
{ description: "", value: 0, duration: 0 },
{ description: "", value: 0, duration: 0 },
{ description: "", value: 0, duration: 0 },
{ description: "", value: 0, duration: 0 },
{ description: "", value: 0, duration: 0 },
{ description: "", value: 0, duration: 0 },
{ description: "", value: 0, duration: 0 },
{ description: "", value: 0, duration: 0 }
], min: 8
}),
damageResistance: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
painDamage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
const attackField = (label) => {
const attackField = (label, initialNoExplode = false) => {
const schema = {
key: new fields.StringField({ required: true, nullable: false, initial: `attack${label}` }),
name: new fields.StringField({ required: true, nullable: false, initial: `Attack ${label}` }),
@@ -74,6 +92,7 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
damageDice: new fields.StringField({ required: true, nullable: false, initial: "1D6" }),
damageModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
enabled: new fields.BooleanField({ initial: true, required: true, nullable: false }),
noExplode: new fields.BooleanField({ initial: initialNoExplode, required: true, nullable: false }),
}
return new fields.SchemaField(schema, { label })
}
@@ -118,8 +137,37 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
defenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
damageModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
armorHitPoints: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
damageReduction: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
shieldDamageReduction: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
shieldDefenseDice: new fields.StringField({ required: true, nullable: false, initial: "d4" })
})
schema.combatHTH = new fields.SchemaField({
attack1: attackField("1", true),
attack2: attackField("2", true)
})
schema.attackMode = new fields.StringField({
required: true,
nullable: false,
initial: "melee",
choices: { melee: "Melee", ranged: "Ranged" }
})
schema.rangedAttacks = new fields.SchemaField({
attack1: attackField("1"),
attack2: attackField("2"),
attack3: attackField("3"),
attack4: attackField("4")
})
schema.rangedWeaponRange = new fields.SchemaField({
pointBlank: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
short: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
medium: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
long: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
extreme: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
outOfSkill: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
})
return schema
}
@@ -134,8 +182,24 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
* @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=).
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
*/
async roll(rollType, rollTarget) {
async roll(rollType, rollTarget, defenderId = undefined, defenderTokenId = undefined, extraShieldDr = 0, d30Effects = {}) {
const hasTarget = false
// Ranged monster defense uses the ranged defense dialog (movement, range, size modifiers)
if (rollType === "monster-defense" && rollTarget?.isRangedDefense === true) {
let roll = await LethalFantasyRoll.promptRangedDefense({
actorId: this.parent.id,
actorName: this.parent.name,
actorImage: this.parent.img,
rollTarget,
defenderId,
defenderTokenId,
})
if (!roll) return null
await roll.toMessage({}, { messageMode: roll.options.rollMode })
return
}
let roll = await LethalFantasyRoll.prompt({
rollType,
rollTarget,
@@ -143,21 +207,50 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
actorName: this.parent.name,
actorImage: this.parent.img,
hasTarget,
target: false
target: false,
defenderId,
defenderTokenId,
extraShieldDr,
damageTier: rollTarget.damageTier || "standard",
d30Bleed: d30Effects.d30Bleed || false,
d30DamageMultiplier: d30Effects.d30DamageMultiplier || 1,
d30DrMultiplier: d30Effects.d30DrMultiplier || 1
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
await roll.toMessage({}, { messageMode: roll.options.rollMode })
}
async prepareMonsterRoll(rollType, rollKey, rollDice = undefined, tokenId = undefined) {
async prepareMonsterRoll(rollType, rollKey, rollDice = undefined, tokenId = undefined, damageModifier = undefined, defenderId = undefined, defenderTokenId = undefined, extraShieldDr = 0, d30Effects = {}) {
let rollTarget
switch (rollType) {
case "monster-attack":
case "monster-defense":
case "monster-damage":
rollTarget = foundry.utils.duplicate(this.attacks[rollKey])
case "monster-damage": {
const attacksSet = this.attackMode === "ranged" ? this.rangedAttacks : this.attacks
rollTarget = foundry.utils.duplicate(attacksSet[rollKey])
rollTarget.rollKey = rollKey
rollTarget.attackMode = this.attackMode
if (rollType === "monster-defense") {
rollTarget.isRangedDefense = game.lethalFantasy?.nextDefenseData?.isRanged ?? false
}
// Si damageModifier est fourni (depuis le chat), l'utiliser au lieu de celui de la fiche
if (damageModifier !== undefined && rollType === "monster-damage") {
rollTarget.damageModifier = damageModifier
}
break
}
case "monster-attack-hth":
case "monster-defense-hth":
case "monster-damage-hth":
rollTarget = foundry.utils.duplicate(this.combatHTH[rollKey])
rollTarget.rollKey = rollKey
// Si damageModifier est fourni (depuis le chat), l'utiliser au lieu de celui de la fiche
if (damageModifier !== undefined && rollType === "monster-damage-hth") {
rollTarget.damageModifier = damageModifier
}
// Convertir le type de roll pour utiliser les mêmes handlers que les attaques normales
rollType = rollType.replace("-hth", "")
break
case "monster-skill":
rollTarget = foundry.utils.duplicate(this.resists[rollKey])
@@ -168,8 +261,20 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
rollTarget.rollKey = rollKey
rollTarget.rollDice = rollDice
break
case "weapon-damage-small":
case "weapon-damage-medium":
case "shield-defense": {
// Lance directement le dé de défense du bouclier
const formula = rollDice || this.combat.shieldDefenseDice
const roll = new Roll(formula)
await roll.evaluate()
const flavor = game.i18n.localize("LETHALFANTASY.Label.shieldDefenseDice")
await roll.toMessage({
flavor,
speaker: ChatMessage.getSpeaker({ actor: this.parent })
}, { messageMode: roll.options.rollMode ?? game.settings.get("core", "rollMode") })
return
}
case "weapon-damage":
case "weapon-attack":
case "weapon-defense": {
let weapon = this.actor.items.find((i) => i.type === "weapon" && i.id === rollKey)
@@ -202,7 +307,7 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
return
}
rollTarget = skill
rollTarget.weapon = weapon
rollTarget.weapon = foundry.utils.duplicate(weapon)
rollTarget.weaponSkillModifier = skill.weaponSkillModifier
rollTarget.rollKey = rollKey
rollTarget.combat = foundry.utils.duplicate(this.combat)
@@ -210,13 +315,14 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
break
default:
ui.notifications.error(game.i18n.localize("LETHALFANTASY.Notifications.rollTypeNotFound") + String(rollType))
break
return
}
// In all cases
rollTarget.tokenId = tokenId
console.log(rollTarget)
await this.roll(rollType, rollTarget)
if (rollTarget) {
rollTarget.tokenId = tokenId
await this.roll(rollType, rollTarget, defenderId, defenderTokenId, extraShieldDr, d30Effects)
}
}
async rollInitiative(combatId = undefined, combatantId = undefined) {
@@ -235,12 +341,19 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
await roll.toMessage({}, { messageMode: roll.options.rollMode })
}
async rollProgressionDice(combatId, combatantId) {
let combatant = game.combats.get(combatId)?.combatants?.get(combatantId)
const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes)
// Don't roll if the combatant is defeated
if (combatant?.isDefeated) {
ui.notifications.warn(`${this.parent.name} is defeated and cannot attack.`)
return
}
const rollModes = foundry.utils.duplicate(CONFIG.ChatMessage.modes)
const fieldRollMode = new foundry.data.fields.StringField({
choices: rollModes,
blank: false,
@@ -249,7 +362,6 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
let roll = new Roll("1D12")
await roll.evaluate()
let combatant = game.combats.get(combatId)?.combatants?.get(combatantId)
let msg = await roll.toMessage({ flavor: `Progression Roll for ${this.parent.name}` })
if (game?.dice3d) {
@@ -257,12 +369,21 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
}
let hasAttack = false
for (let key in this.attacks) {
let attack = this.attacks[key]
const attacksSet = this.attackMode === "ranged" ? this.rangedAttacks : this.attacks
for (let key in attacksSet) {
let attack = attacksSet[key]
if (attack.enabled && attack.attackScore > 0 && attack.attackScore === roll.total) {
hasAttack = true
let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionOKMonster", { isMonster: true, name: this.parent.name, weapon: attack.name, roll: roll.total })
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
const messageContent = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
{
success: true,
actorName: this.parent.name,
weaponName: attack.name,
rollResult: roll.total
}
)
await ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
let token = combatant?.token
this.prepareMonsterRoll("monster-attack", key, undefined, token?.id)
if (token?.object) {
@@ -271,9 +392,41 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
}
}
}
// Check Hand To Hand attacks as well
if (!hasAttack) {
let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionKOMonster", { isMonster: true, name: this.parent.name, roll: roll.total })
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
for (let key in this.combatHTH) {
let attack = this.combatHTH[key]
if (attack.enabled && attack.attackScore > 0 && attack.attackScore === roll.total) {
hasAttack = true
const messageContent = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
{
success: true,
actorName: this.parent.name,
weaponName: `${attack.name} (HTH)`,
rollResult: roll.total
}
)
await ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
let token = combatant?.token
this.prepareMonsterRoll("monster-attack-hth", key, undefined, token?.id)
if (token?.object) {
token.object?.control({ releaseOthers: true });
return canvas.animatePan(token.object.center);
}
}
}
}
if (!hasAttack) {
const messageContent = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
{
success: false,
actorName: this.parent.name,
rollResult: roll.total
}
)
await ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
}
}
+2 -1
View File
@@ -6,9 +6,10 @@ export default class LethalFantasyShield extends foundry.abstract.TypeDataModel
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.defense = new fields.StringField({required: true, initial: "d4", choices: SYSTEM.SHIELD_DEFENSE_DICE})
schema.defense = new fields.StringField({ required: true, initial: "d4", choices: SYSTEM.SHIELD_DEFENSE_DICE })
schema.movementreduction = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.hascover = new fields.BooleanField({ required: true, initial: false })
schema.damageReduction = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.standing = new fields.SchemaField({
min: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
+5
View File
@@ -19,6 +19,8 @@ export default class LethalFantasySpell extends foundry.abstract.TypeDataModel {
})
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.costOverpowered = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.costOverpowered2 = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.memorized = new fields.BooleanField({ required: true, initial: false })
schema.components = new fields.SchemaField({
@@ -39,6 +41,9 @@ export default class LethalFantasySpell extends foundry.abstract.TypeDataModel {
schema.attackRoll = new fields.StringField({ required: true, initial: "" })
schema.powerRoll = new fields.StringField({ required: true, initial: "" })
schema.damageDice = new fields.StringField({ required: false, initial: "" })
schema.damageDiceOverpowered = new fields.StringField({ required: false, initial: "" })
schema.damageDiceOverpowered2 = new fields.StringField({ required: false, initial: "" })
return schema
}
+6 -5
View File
@@ -1,6 +1,7 @@
import { SYSTEM } from "../config/system.mjs"
import { WEAPON_TYPE } from "../config/weapon.mjs"
export default class LethalFantasySkill extends foundry.abstract.TypeDataModel {
export default class LethalFantasyWeapon extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const schema = {}
@@ -45,9 +46,9 @@ export default class LethalFantasySkill extends foundry.abstract.TypeDataModel {
})
schema.bonuses = new fields.SchemaField({
attackBonus: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
damageBonus: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
defenseBonus: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
attackBonus: new fields.NumberField({ ...requiredInteger, required: true, initial: 0 }),
damageBonus: new fields.NumberField({ ...requiredInteger, required: true, initial: 0 }),
defenseBonus: new fields.NumberField({ ...requiredInteger, required: true, initial: 0 })
})
schema.encLoad = new fields.NumberField({ required: true, initial: 0, min: 0 })
@@ -62,7 +63,7 @@ export default class LethalFantasySkill extends foundry.abstract.TypeDataModel {
static LOCALIZATION_PREFIXES = ["LETHALFANTASY.Weapon"]
get weaponCategory() {
return game.i18n.localize(CATEGORY[this.weaponType].label)
return game.i18n.localize(WEAPON_TYPE[this.weaponType] || this.weaponType)
}
}
+1497 -41
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -1 +0,0 @@
MANIFEST-000400
View File
-8
View File
@@ -1,8 +0,0 @@
2025/09/05-23:10:01.571742 7f0131ffb6c0 Recovering log #398
2025/09/05-23:10:01.582082 7f0131ffb6c0 Delete type=3 #396
2025/09/05-23:10:01.582249 7f0131ffb6c0 Delete type=0 #398
2025/09/05-23:25:34.796479 7f012fff76c0 Level-0 table #403: started
2025/09/05-23:25:34.796530 7f012fff76c0 Level-0 table #403: 0 bytes OK
2025/09/05-23:25:34.802896 7f012fff76c0 Delete type=0 #401
2025/09/05-23:25:34.803270 7f012fff76c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
2025/09/05-23:25:34.803324 7f012fff76c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
-8
View File
@@ -1,8 +0,0 @@
2025/09/04-09:03:59.907857 7f66cffff6c0 Recovering log #394
2025/09/04-09:03:59.918032 7f66cffff6c0 Delete type=3 #392
2025/09/04-09:03:59.918082 7f66cffff6c0 Delete type=0 #394
2025/09/04-09:04:51.777411 7f66cdbff6c0 Level-0 table #399: started
2025/09/04-09:04:51.777431 7f66cdbff6c0 Level-0 table #399: 0 bytes OK
2025/09/04-09:04:51.783440 7f66cdbff6c0 Delete type=0 #397
2025/09/04-09:04:51.790184 7f66cdbff6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
2025/09/04-09:04:51.790212 7f66cdbff6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
View File
Binary file not shown.
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
MANIFEST-000399
View File
-8
View File
@@ -1,8 +0,0 @@
2025/09/05-23:10:01.588433 7f01317fa6c0 Recovering log #397
2025/09/05-23:10:01.598791 7f01317fa6c0 Delete type=3 #395
2025/09/05-23:10:01.598975 7f01317fa6c0 Delete type=0 #397
2025/09/05-23:25:34.782369 7f012fff76c0 Level-0 table #402: started
2025/09/05-23:25:34.782405 7f012fff76c0 Level-0 table #402: 0 bytes OK
2025/09/05-23:25:34.789488 7f012fff76c0 Delete type=0 #400
2025/09/05-23:25:34.803215 7f012fff76c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!zjvGljrLk5SshC9D' @ 0 : 0; will stop at (end)
2025/09/05-23:25:34.803289 7f012fff76c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!zjvGljrLk5SshC9D' @ 0 : 0; will stop at (end)
-8
View File
@@ -1,8 +0,0 @@
2025/09/04-09:03:59.926991 7f66ce7fc6c0 Recovering log #393
2025/09/04-09:03:59.938021 7f66ce7fc6c0 Delete type=3 #391
2025/09/04-09:03:59.938111 7f66ce7fc6c0 Delete type=0 #393
2025/09/04-09:04:51.783500 7f66cdbff6c0 Level-0 table #398: started
2025/09/04-09:04:51.783516 7f66cdbff6c0 Level-0 table #398: 0 bytes OK
2025/09/04-09:04:51.790018 7f66cdbff6c0 Delete type=0 #396
2025/09/04-09:04:51.790191 7f66cdbff6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!zjvGljrLk5SshC9D' @ 0 : 0; will stop at (end)
2025/09/04-09:04:51.790207 7f66cdbff6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!zjvGljrLk5SshC9D' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
View File
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
MANIFEST-000399
View File
-8
View File
@@ -1,8 +0,0 @@
2025/09/05-23:10:01.556390 7f0130ff96c0 Recovering log #397
2025/09/05-23:10:01.567326 7f0130ff96c0 Delete type=3 #395
2025/09/05-23:10:01.567391 7f0130ff96c0 Delete type=0 #397
2025/09/05-23:25:34.789652 7f012fff76c0 Level-0 table #402: started
2025/09/05-23:25:34.789701 7f012fff76c0 Level-0 table #402: 0 bytes OK
2025/09/05-23:25:34.796271 7f012fff76c0 Delete type=0 #400
2025/09/05-23:25:34.803244 7f012fff76c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
2025/09/05-23:25:34.803346 7f012fff76c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
-8
View File
@@ -1,8 +0,0 @@
2025/09/04-09:03:59.893009 7f66ceffd6c0 Recovering log #393
2025/09/04-09:03:59.902791 7f66ceffd6c0 Delete type=3 #391
2025/09/04-09:03:59.902925 7f66ceffd6c0 Delete type=0 #393
2025/09/04-09:04:51.769661 7f66cdbff6c0 Level-0 table #398: started
2025/09/04-09:04:51.769698 7f66cdbff6c0 Level-0 table #398: 0 bytes OK
2025/09/04-09:04:51.777317 7f66cdbff6c0 Delete type=0 #396
2025/09/04-09:04:51.790177 7f66cdbff6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
2025/09/04-09:04:51.790202 7f66cdbff6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
MANIFEST-000099
-8
View File
@@ -1,8 +0,0 @@
2025/09/05-23:10:01.616975 7f0130ff96c0 Recovering log #97
2025/09/05-23:10:01.626690 7f0130ff96c0 Delete type=3 #95
2025/09/05-23:10:01.626777 7f0130ff96c0 Delete type=0 #97
2025/09/05-23:25:34.803522 7f012fff76c0 Level-0 table #102: started
2025/09/05-23:25:34.803594 7f012fff76c0 Level-0 table #102: 0 bytes OK
2025/09/05-23:25:34.810169 7f012fff76c0 Delete type=0 #100
2025/09/05-23:25:34.834274 7f012fff76c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
2025/09/05-23:25:34.834341 7f012fff76c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
-8
View File
@@ -1,8 +0,0 @@
2025/09/04-09:03:59.954570 7f66cffff6c0 Recovering log #93
2025/09/04-09:03:59.964662 7f66cffff6c0 Delete type=3 #91
2025/09/04-09:03:59.964757 7f66cffff6c0 Delete type=0 #93
2025/09/04-09:04:51.797370 7f66cdbff6c0 Level-0 table #98: started
2025/09/04-09:04:51.797420 7f66cdbff6c0 Level-0 table #98: 0 bytes OK
2025/09/04-09:04:51.804045 7f66cdbff6c0 Delete type=0 #96
2025/09/04-09:04:51.817491 7f66cdbff6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
2025/09/04-09:04:51.817519 7f66cdbff6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
MANIFEST-000399
-8
View File
@@ -1,8 +0,0 @@
2025/09/05-23:10:01.602511 7f01307f86c0 Recovering log #397
2025/09/05-23:10:01.613927 7f01307f86c0 Delete type=3 #395
2025/09/05-23:10:01.614000 7f01307f86c0 Delete type=0 #397
2025/09/05-23:25:34.775352 7f012fff76c0 Level-0 table #402: started
2025/09/05-23:25:34.775494 7f012fff76c0 Level-0 table #402: 0 bytes OK
2025/09/05-23:25:34.782238 7f012fff76c0 Delete type=0 #400
2025/09/05-23:25:34.803177 7f012fff76c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
2025/09/05-23:25:34.803306 7f012fff76c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
-8
View File
@@ -1,8 +0,0 @@
2025/09/04-09:03:59.941117 7f66cf7fe6c0 Recovering log #393
2025/09/04-09:03:59.951066 7f66cf7fe6c0 Delete type=3 #391
2025/09/04-09:03:59.951178 7f66cf7fe6c0 Delete type=0 #393
2025/09/04-09:04:51.762591 7f66cdbff6c0 Level-0 table #398: started
2025/09/04-09:04:51.762647 7f66cdbff6c0 Level-0 table #398: 0 bytes OK
2025/09/04-09:04:51.769513 7f66cdbff6c0 Delete type=0 #396
2025/09/04-09:04:51.790167 7f66cdbff6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
2025/09/04-09:04:51.790196 7f66cdbff6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
Binary file not shown.
+7 -4
View File
@@ -375,6 +375,9 @@
min-width: 2.5rem;
max-width: 2.5rem;
}
.ranged-attack-button {
font-size: 0.8rem;
}
button {
min-width: 9rem;
}
@@ -384,11 +387,11 @@
min-width: 6rem;
max-width: 6rem;
}
min-width: 10rem;
max-width: 10rem;
min-width: 11rem;
max-width: 11rem;
.input {
min-width: 2.5rem;
max-width: 2.5rem;
min-width: 3.5rem;
max-width: 3.5rem;
}
}
.granted {
+361 -28
View File
@@ -1,33 +1,34 @@
&.fortune {
img {
border: 0px;
}
.intro-chat {
border-radius: 20px;
display: flex;
flex-direction: row;
.intro-img {
padding: 5px;
width: 80px;
align-self: center;
}
.intro-right {
display: flex;
flex-direction: column;
.introText {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.2);
width: 210px;
text-align: center;
}
}
}
.button.control, .fortune-accepted {
display: flex;
justify-content: center;
align-items: center;
font-size: calc(var(--font-size-standard) * 1.3);
img {
border: 0px;
}
.intro-chat {
border-radius: 20px;
display: flex;
flex-direction: row;
.intro-img {
padding: 5px;
width: 80px;
align-self: center;
}
.intro-right {
display: flex;
flex-direction: column;
.introText {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.2);
width: 210px;
text-align: center;
}
}
}
.button.control,
.fortune-accepted {
display: flex;
justify-content: center;
align-items: center;
font-size: calc(var(--font-size-standard) * 1.3);
}
}
&.ask-roll {
@@ -37,4 +38,336 @@
justify-content: center;
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.2);
}
.defense-request {
padding: 12px;
background: linear-gradient(to bottom, #3a3930 0%, #2a2920 100%);
border: 2px solid #d4af37;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
h3 {
margin: 0 0 10px 0;
color: #d4af37;
font-size: calc(var(--font-size-standard) * 1.1);
font-weight: 700;
text-align: center;
text-transform: uppercase;
letter-spacing: 1px;
i {
margin-right: 6px;
}
}
p {
margin: 8px 0;
color: #f0e6d2;
font-size: calc(var(--font-size-standard) * 0.95);
line-height: 1.4;
strong {
color: #d4af37;
font-weight: 600;
}
}
.defense-prompt {
margin-top: 12px;
padding: 8px;
background: rgba(212, 175, 55, 0.1);
border-left: 3px solid #d4af37;
border-radius: 4px;
font-weight: 600;
color: #d4af37;
text-align: center;
}
}
.defense-request-dialog {
.attack-info {
padding: 12px;
background: linear-gradient(to bottom, rgba(42, 41, 32, 0.8) 0%, rgba(26, 25, 16, 0.9) 100%);
border: 1px solid rgba(212, 175, 55, 0.5);
border-radius: 6px;
margin-bottom: 16px;
p {
margin: 6px 0;
color: #f0e6d2;
font-size: calc(var(--font-size-standard) * 0.95);
strong {
color: #d4af37;
font-weight: 600;
}
}
}
.weapon-selection {
label {
display: block;
margin-bottom: 8px;
color: #d4af37;
font-weight: 600;
font-size: calc(var(--font-size-standard) * 0.95);
}
select {
width: 100%;
padding: 8px 12px;
background: #3a3930 !important;
border: 1px solid #d4af37;
border-radius: 4px;
color: #ffffff !important;
font-size: calc(var(--font-size-standard) * 0.95);
cursor: pointer;
&:focus {
outline: none;
border-color: #f0e6d2;
box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.3);
}
option {
background: #3a3930 !important;
color: #ffffff !important;
padding: 6px;
&:checked,
&:hover {
background: #4a4940 !important;
color: #ffffff !important;
}
}
}
}
}
.grit-luck-dialog {
color: var(--color-text-dark-primary, #191813);
.combat-status {
padding: 12px;
background: linear-gradient(to bottom, rgba(42, 41, 32, 0.88) 0%, rgba(26, 25, 16, 0.95) 100%);
border: 1px solid rgba(212, 175, 55, 0.5);
border-radius: 6px;
margin-bottom: 16px;
p {
margin: 6px 0;
color: #f0e6d2;
font-size: calc(var(--font-size-standard) * 0.95);
strong {
color: #d4af37;
font-weight: 600;
}
}
.bonus-info {
color: #90EE90;
font-style: italic;
margin-top: 8px;
}
}
.offer-text {
color: var(--color-text-dark-primary, #191813);
font-size: calc(var(--font-size-standard) * 1);
text-align: center;
font-weight: 600;
margin: 0 0 8px 0;
}
.shield-warning {
color: #7a4000;
background: rgba(255, 160, 0, 0.12);
border: 1px solid rgba(255, 160, 0, 0.4);
border-radius: 5px;
font-size: calc(var(--font-size-standard) * 0.88);
padding: 6px 10px;
margin: 0;
text-align: center;
i {
color: #c07000;
margin-right: 5px;
}
}
}
.attack-result {
padding: 16px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
margin: 8px 0;
&.attack-success {
background: linear-gradient(to bottom, rgba(139, 0, 0, 0.2) 0%, rgba(100, 0, 0, 0.3) 100%);
border: 2px solid rgba(220, 20, 60, 0.6);
}
&.attack-failure {
background: linear-gradient(to bottom, rgba(0, 100, 139, 0.2) 0%, rgba(0, 70, 100, 0.3) 100%);
border: 2px solid rgba(70, 130, 180, 0.6);
}
h3 {
margin: 0 0 16px 0;
color: #d4af37;
font-size: calc(var(--font-size-standard) * 1.2);
font-weight: 700;
text-align: center;
text-transform: uppercase;
letter-spacing: 1px;
i {
margin-right: 8px;
}
}
.combat-comparison {
display: flex;
align-items: center;
justify-content: space-around;
margin-bottom: 16px;
padding: 12px;
background: rgba(0, 0, 0, 0.3);
border-radius: 6px;
.combat-side {
flex: 1;
text-align: center;
padding: 12px;
border-radius: 6px;
&.winner {
background: rgba(0, 255, 0, 0.1);
border: 2px solid rgba(0, 255, 0, 0.4);
}
&.loser {
background: rgba(255, 0, 0, 0.1);
border: 2px solid rgba(255, 0, 0, 0.3);
}
.side-label {
font-size: calc(var(--font-size-standard) * 0.8);
color: #aaa;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 4px;
}
.side-name {
font-size: calc(var(--font-size-standard) * 1);
color: #f0e6d2;
font-weight: 600;
margin-bottom: 8px;
}
.side-roll {
font-size: calc(var(--font-size-standard) * 1.5);
color: #d4af37;
font-weight: 700;
}
}
.combat-vs {
font-size: calc(var(--font-size-standard) * 1.2);
color: #d4af37;
font-weight: 700;
padding: 0 16px;
}
}
.combat-result-text {
text-align: center;
font-size: calc(var(--font-size-standard) * 1.1);
color: #f0e6d2;
margin-bottom: 16px;
padding: 12px;
background: rgba(0, 0, 0, 0.2);
border-radius: 6px;
i {
margin-right: 8px;
}
strong {
color: #d4af37;
}
}
.attack-result-damage {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
&.single-btn {
grid-template-columns: 1fr;
max-width: 280px;
margin: 0 auto;
}
&.spell-damage {
grid-template-columns: 1fr;
width: 100%;
}
.roll-damage-btn {
padding: 10px 14px;
background: linear-gradient(to bottom, #8b0000 0%, #660000 100%);
border: 1px solid #4b0000;
border-radius: 6px;
color: #f0e6d2;
font-weight: 600;
font-size: calc(var(--font-size-standard) * 0.9);
text-align: center;
white-space: nowrap;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.15), transparent);
transition: left 0.5s;
}
&:hover::before {
left: 100%;
}
i {
font-size: calc(var(--font-size-standard) * 1.1);
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
flex-shrink: 0;
}
&:hover {
background: linear-gradient(to bottom, #a00000 0%, #7b0000 100%);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.15);
transform: translateY(-2px);
border-color: #5b0000;
}
&:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4), inset 0 1px 3px rgba(0, 0, 0, 0.3);
}
}
}
}
+4 -4
View File
@@ -47,7 +47,7 @@ i.lethalfantasy {
.application.dialog.lethalfantasy {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1.0);
font-size: calc(var(--font-size-standard) * 1);
background-image: var(--background-image-base);
button:hover {
background: var(--color-dark-6);
@@ -70,13 +70,13 @@ i.lethalfantasy {
background-size: 100% 100%;
}
.combat-sidebar li.combatant .token-initiative .initiative{
.combat-sidebar li.combatant .token-initiative .initiative {
margin-right: 16px;
}
.combat-sidebar li.combatant .token-initiative {
flex:none;
flex: none;
}
.initiative-minus {
margin-right: 8px;
}
}
+354 -2
View File
@@ -35,10 +35,362 @@
padding-bottom: 0;
width: max-content;
margin: 0;
color:#252424;
color: #252424;
}
#token-hud .hp-loss-wrap .hud-loss-hp-button-select {
#token-hud .hp-loss-wrap .hud-loss-hp-button-select {
padding-left: 8px;
font-size: 0.9rem;
}
/* HP Gain Styles */
#token-hud .hp-gain-wrap {
position: absolute;
left: 75px;
display: none;
top: 50%;
width: 48px;
text-align: start;
overflow-y: auto;
}
#token-hud .hp-gain-wrap-col1 {
transform: translate(-200%, -50%);
}
#token-hud .hp-gain-wrap-col2 {
transform: translate(-300%, -50%);
}
#token-hud .hp-gain-wrap-col3 {
transform: translate(-400%, -50%);
}
#token-hud .hp-gain-hud-active {
display: block;
}
#token-hud .hp-gain-hud-disabled {
display: none;
}
#token-hud .hud-gain-hp-button-select {
max-width: 40px;
background-image: var(--background-image-base);
padding-top: 0;
padding-bottom: 0;
width: max-content;
margin: 0;
color: #252424;
}
#token-hud .hp-gain-wrap .hud-gain-hp-button-select {
padding-left: 8px;
font-size: 0.9rem;
}
/* Luck/Grit Styles */
#token-hud .luck-grit-wrap {
position: absolute;
left: 75px;
display: none;
top: 50%;
width: 80px;
text-align: start;
background: rgba(0, 0, 0, 0.8);
border: 1px solid rgba(139, 69, 19, 0.5);
border-radius: 4px;
padding: 4px 6px;
transform: translate(-100%, -50%);
}
#token-hud .luck-grit-hud-active {
display: block;
}
#token-hud .luck-grit-hud-disabled {
display: none;
}
#token-hud .luck-grit-row {
display: flex;
align-items: center;
gap: 4px;
margin: 2px 0;
}
#token-hud .luck-grit-label {
flex: 1;
color: #c9b896;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
}
#token-hud .luck-grit-btn {
width: 28px;
height: 22px;
padding: 0;
background: rgba(139, 69, 19, 0.25);
border: 1px solid rgba(139, 69, 19, 0.4);
border-radius: 3px;
color: #d4c5a9;
font-size: 12px;
font-weight: 700;
cursor: pointer;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
&:hover {
background: rgba(139, 69, 19, 0.5);
border-color: rgba(139, 69, 19, 0.7);
}
}
/* -------------------------------------------------- */
/* Dice Tray — injected into the Foundry chat sidebar */
/* -------------------------------------------------- */
.lf-dice-tray {
padding: 6px 8px;
background:
linear-gradient(135deg, rgba(245, 232, 200, 0.97) 0%, rgba(238, 222, 185, 0.97) 100%),
url("/systems/fvtt-lethal-fantasy/assets/ui/lethal_fantasy_background.webp") center / cover;
border-top: 2px solid rgba(139, 69, 19, 0.5);
border-bottom: 1px solid rgba(139, 69, 19, 0.25);
width: 100%;
box-sizing: border-box;
pointer-events: all;
.lf-dt-row {
display: flex;
align-items: center;
gap: 5px;
flex-wrap: wrap;
}
.lf-dt-label {
color: #3a2a10;
font-size: 15px;
flex-shrink: 0;
opacity: 0.7;
}
.lf-dt-count {
width: 44px;
flex-shrink: 0;
padding: 3px 4px;
background: rgba(139, 69, 19, 0.15);
border: 1px solid rgba(139, 69, 19, 0.45);
border-radius: 4px;
color: #2a1a08;
font-size: 11px;
font-weight: 700;
cursor: pointer;
text-align: center;
option { background: #f5ead0; color: #2a1a08; }
&:focus {
outline: none;
border-color: rgba(139, 69, 19, 0.7);
box-shadow: 0 0 4px rgba(139, 69, 19, 0.25);
}
}
.lf-dt-dice {
display: flex;
flex-wrap: wrap;
gap: 3px;
flex: 1;
}
.lf-dt-die-btn {
padding: 3px 7px;
background: rgba(139, 69, 19, 0.15);
border: 1px solid rgba(139, 69, 19, 0.4);
border-radius: 4px;
color: #2a1a08;
font-size: 10px;
font-weight: 700;
cursor: pointer;
transition: background 0.15s, border-color 0.15s, box-shadow 0.15s, transform 0.1s;
line-height: 1.5;
letter-spacing: 0.3px;
&:hover {
background: rgba(139, 69, 19, 0.35);
border-color: rgba(139, 69, 19, 0.7);
color: #5a2a00;
box-shadow: 0 0 5px rgba(139, 69, 19, 0.3);
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
box-shadow: none;
background: rgba(139, 69, 19, 0.5);
}
}
.lf-dt-explode-label {
display: flex;
align-items: center;
gap: 3px;
cursor: pointer;
color: rgba(160, 80, 20, 0.7);
font-size: 14px;
flex-shrink: 0;
padding: 3px 6px;
border: 1px solid transparent;
border-radius: 4px;
transition: color 0.15s, border-color 0.15s, background 0.15s;
&:hover {
color: rgba(200, 100, 30, 0.9);
border-color: rgba(139, 69, 19, 0.45);
background: rgba(139, 69, 19, 0.12);
}
input[type="checkbox"] {
appearance: none;
width: 0;
height: 0;
position: absolute;
opacity: 0;
&:checked ~ i {
color: #cc4400;
filter: drop-shadow(0 0 4px rgba(200, 80, 0, 0.6));
}
}
&:has(input:checked) {
color: #cc4400;
border-color: rgba(139, 69, 19, 0.55);
background: rgba(139, 60, 10, 0.2);
}
}
}
/* Free roll chat card — styled to match regular system roll cards */
.lf-free-roll-card {
border-radius: 6px;
overflow: hidden;
.lf-frc-header {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
background: linear-gradient(135deg, rgba(40, 30, 20, 0.7) 0%, rgba(30, 22, 15, 0.9) 100%);
border-bottom: 2px solid rgba(139, 69, 19, 0.4);
i { color: #c9b896; font-size: calc(var(--font-size-standard, 14px) * 1.1); }
.lf-frc-title-text {
font-size: calc(var(--font-size-standard, 14px) * 0.85);
color: #c9b896;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.lf-frc-badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
background: rgba(139, 69, 19, 0.3);
border: 1px solid rgba(139, 69, 19, 0.5);
border-radius: 10px;
font-size: calc(var(--font-size-standard, 14px) * 0.85);
font-weight: 600;
color: #d4c5a9;
}
}
.lf-frc-dice {
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 6px 8px;
background: rgba(0, 0, 0, 0.15);
border: 1px solid rgba(139, 69, 19, 0.3);
border-top: none;
.lf-frc-die-chip {
display: flex;
align-items: center;
gap: 4px;
padding: 3px 8px;
background: rgba(139, 69, 19, 0.2);
border: 1px solid rgba(139, 69, 19, 0.3);
border-radius: 4px;
.lf-frc-die-type {
font-size: calc(var(--font-size-standard, 14px) * 0.85);
font-weight: 600;
color: #2a2a1a;
text-transform: uppercase;
}
.lf-frc-die-sep {
color: rgba(0, 0, 0, 0.35);
font-weight: 300;
font-size: calc(var(--font-size-standard, 14px) * 0.8);
}
.lf-frc-die-val {
font-weight: bold;
color: #ffd700;
font-size: calc(var(--font-size-standard, 14px) * 0.95);
.lf-dt-explode-icon {
font-size: 8px;
color: #ffcc00;
margin-left: 2px;
vertical-align: super;
text-shadow: 0 0 4px rgba(255, 200, 0, 0.8);
}
}
&.lf-frc-max {
background: rgba(139, 90, 19, 0.35);
border-color: rgba(200, 116, 42, 0.6);
.lf-frc-die-val { color: #ff9a40; text-shadow: 0 0 6px rgba(200, 116, 42, 0.6); }
}
&.lf-frc-min {
background: rgba(139, 20, 20, 0.25);
border-color: rgba(139, 34, 34, 0.5);
.lf-frc-die-val { color: #ff6b6b; }
}
}
}
.lf-frc-total-bar {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 6px 10px;
background: linear-gradient(135deg, rgba(40, 30, 20, 0.6) 0%, rgba(20, 15, 10, 0.8) 100%);
border: 2px solid rgba(139, 69, 19, 0.5);
.lf-frc-total-label {
font-size: calc(var(--font-size-standard, 14px) * 0.85);
text-transform: uppercase;
letter-spacing: 0.5px;
color: #c9b896;
}
.lf-frc-total-value {
font-family: var(--font-primary, serif);
font-size: calc(var(--font-size-standard, 14px) * 1.6);
font-weight: bold;
color: #e8d5a0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
}
}
}
+52 -11
View File
@@ -25,7 +25,6 @@
gap: 10px;
flex: 1;
.monster-hp {
display: flex;
gap: 4px;
@@ -46,7 +45,7 @@
min-width: 2.2rem;
max-width: 2.2rem;
margin-left: 4px;
font-size: calc(var(--font-size-standard) * 1.0);
font-size: calc(var(--font-size-standard) * 1);
}
.character-hp-max {
clear: both;
@@ -58,7 +57,7 @@
input {
width: 3.2rem;
text-align: center;
font-size: calc(var(--font-size-standard) * 1.0);
font-size: calc(var(--font-size-standard) * 1);
}
}
}
@@ -121,9 +120,11 @@
flex: 1;
.monster-skill {
display: flex;
display: grid;
grid-template-columns: repeat(3, auto 2.5rem);
gap: 0.5rem;
align-items: center;
margin-right: 0.5rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
@@ -134,12 +135,9 @@
max-width: 2.5rem;
}
.name {
flex: 1;
min-width: 3.2rem;
margin-left: 4px;
}
.form-group {
flex: 1;
padding-left: 4px;
.form-fields {
flex: none;
@@ -157,8 +155,9 @@
.monster-movement {
display: flex;
align-items: center;
margin-right: 0.5rem;
flex-direction: column;
gap: 4px;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
@@ -252,7 +251,7 @@
}
}
.tab.monster-skills .main-div{
.tab.monster-skills .main-div {
display: grid;
grid-template-columns: 1fr;
legend {
@@ -411,6 +410,48 @@
}
}
.ranged-attacks {
grid-template-columns: 1fr;
.attack {
.name {
min-width: 10rem;
max-width: 10rem;
}
.damage-dice {
width: 5rem;
max-width: 5rem;
}
}
.ranged-weapon-range {
margin-top: 8px;
border-top: 1px solid var(--color-border-light-tertiary, #ccc);
padding-top: 6px;
> label {
font-weight: bold;
margin-bottom: 4px;
display: block;
}
.range-fields {
display: flex;
flex-wrap: wrap;
gap: 6px;
.range-field {
display: flex;
flex-direction: column;
align-items: center;
label {
font-size: 0.7rem;
white-space: nowrap;
}
input {
width: 3.5rem;
text-align: center;
}
}
}
}
}
.armors {
display: grid;
grid-template-columns: repeat(3, 1fr);
+1234 -32
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -6,7 +6,7 @@
"download": "#{DOWNLOAD}#",
"url": "#{URL}#",
"license": "LICENSE",
"version": "13.0.0",
"version": "14.0.0",
"authors": [
{
"name": "Uberwald",
@@ -15,7 +15,7 @@
],
"compatibility": {
"minimum": "13",
"verified": "13"
"verified": "14"
},
"esmodules": ["lethal-fantasy.mjs"],
"styles": ["css/fvtt-lethal-fantasy.css"],
+37
View File
@@ -0,0 +1,37 @@
<div class="apply-damage-dialog">
<div class="dialog-content">
<div class="header">
<strong>{{targetName}}</strong>
-
{{weaponName}}
</div>
<div class="damage-summary">
<span class="label">{{localize
"LETHALFANTASY.Dialog.totalDamage"
}}:</span>
<span class="damage-value">{{damageTotal}}</span>
</div>
<div class="dr-summary">
<span>{{localize "LETHALFANTASY.Dialog.armorDR"}}: {{armorDR}}</span>
<span>{{localize "LETHALFANTASY.Dialog.shieldDR"}}: {{shieldDR}}</span>
<span class="total">DR: <strong>{{totalDR}}</strong></span>
</div>
<div class="damage-options">
<div class="option-line">
<span>{{localize "LETHALFANTASY.Dialog.noDR"}}</span>
<strong>{{damageNoDR}}</strong>
</div>
<div class="option-line">
<span>{{localize "LETHALFANTASY.Dialog.withArmor"}}</span>
<strong>{{damageWithArmor}}</strong>
</div>
<div class="option-line">
<span>{{localize "LETHALFANTASY.Dialog.withAll"}}</span>
<strong>{{damageWithAll}}</strong>
</div>
</div>
</div>
</div>
+170 -87
View File
@@ -1,97 +1,180 @@
<section class="tab character-{{tab.id}} {{tab.cssClass}}" data-tab="{{tab.id}}" data-group="{{tab.group}}">
<section
class="tab character-{{tab.id}} {{tab.cssClass}}"
data-tab="{{tab.id}}"
data-group="{{tab.group}}"
>
<div class="main-div">
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.biodata"}}</legend>
<div class="biodata">
<div class="biodata-elem">
<span class="name">Class</span>
{{formInput systemFields.biodata.fields.class value=system.biodata.class }}
</div>
<div class="biodata-elem">
<span class="name">Level</span>
{{formInput systemFields.biodata.fields.level value=system.biodata.level }}
</div>
<div class="biodata-elem">
<span class="name">Mortal</span>
{{formInput systemFields.biodata.fields.mortal value=system.biodata.mortal }}
</div>
<div class="biodata-elem">
<span class="name">Alignment</span>
{{formInput systemFields.biodata.fields.alignment value=system.biodata.alignment }}
</div>
<div class="biodata-elem">
<span class="name">Age</span>
{{formInput systemFields.biodata.fields.age value=system.biodata.age }}
</div>
<div class="biodata-elem">
<span class="name">Height</span>
{{formInput systemFields.biodata.fields.height value=system.biodata.height }}
</div>
<div class="biodata-elem">
<span class="name">Weight</span>
{{formInput systemFields.biodata.fields.weight value=system.biodata.weight }}
</div>
<div class="biodata-elem">
<span class="name">Eyes</span>
{{formInput systemFields.biodata.fields.eyes value=system.biodata.eyes }}
</div>
<div class="biodata-elem">
<span class="name">Hair</span>
{{formInput systemFields.biodata.fields.hair value=system.biodata.hair }}
</div>
<div class="biodata-elem">
<span class="name">Dev. Points (Total)</span>
{{formInput systemFields.developmentPoints.fields.total value=system.developmentPoints.total }}
</div>
<div class="biodata-elem">
<span class="name">Dev. Points (Rem.)</span>
{{formInput systemFields.developmentPoints.fields.remaining value=system.developmentPoints.remaining }}
</div>
<div class="biodata-elem">
<span class="name">Magic User</span>
{{formInput systemFields.biodata.fields.magicUser value=system.biodata.magicUser }}
</div>
<div class="biodata-elem">
<span class="name">Cleric User</span>
{{formInput systemFields.biodata.fields.clericUser value=system.biodata.clericUser }}
</div>
<div class="biodata-elem">
<span class="name">Save bonus (1/5levels)</span>
{{formInput systemFields.modifiers.fields.saveModifier value=system.modifiers.saveModifier disabled=true}}
</div>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.biodata"}}</legend>
<div class="biodata">
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.class"}}</span>
{{formInput
systemFields.biodata.fields.class
value=system.biodata.class
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.level"}}</span>
{{formInput
systemFields.biodata.fields.level
value=system.biodata.level
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.mortal"}}</span>
{{formInput
systemFields.biodata.fields.mortal
value=system.biodata.mortal
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.alignment"}}</span>
{{formInput
systemFields.biodata.fields.alignment
value=system.biodata.alignment
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.age"}}</span>
{{formInput systemFields.biodata.fields.age value=system.biodata.age}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.height"}}</span>
{{formInput
systemFields.biodata.fields.height
value=system.biodata.height
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.weight"}}</span>
{{formInput
systemFields.biodata.fields.weight
value=system.biodata.weight
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.eyes"}}</span>
{{formInput
systemFields.biodata.fields.eyes
value=system.biodata.eyes
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.hair"}}</span>
{{formInput
systemFields.biodata.fields.hair
value=system.biodata.hair
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.devPointsTotal"}}</span>
{{formInput
systemFields.developmentPoints.fields.total
value=system.developmentPoints.total
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.devPointsRem"}}</span>
{{formInput
systemFields.developmentPoints.fields.remaining
value=system.developmentPoints.remaining
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.magicUser"}}</span>
{{formInput
systemFields.biodata.fields.magicUser
value=system.biodata.magicUser
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.clericUser"}}</span>
{{formInput
systemFields.biodata.fields.clericUser
value=system.biodata.clericUser
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.saveBonus"}}</span>
{{formInput
systemFields.modifiers.fields.saveModifier
value=system.modifiers.saveModifier
disabled=true
}}
</div>
{{#if system.biodata.magicUser}}
<div class="biodata-elem">
<span class="name">Spell bonus (1/5levels)</span>
{{formInput systemFields.modifiers.fields.levelSpellModifier value=system.modifiers.levelSpellModifier disabled=true}}
</div>
{{/if}}
{{#if system.biodata.clericUser}}
<div class="biodata-elem">
<span class="name">Miracle bonus (1/5levels)</span>
{{formInput systemFields.modifiers.fields.levelMiracleModifier value=system.modifiers.levelMiracleModifier disabled=true}}
</div>
{{/if}}
{{#if system.biodata.magicUser}}
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.spellBonus"}}</span>
{{formInput
systemFields.modifiers.fields.levelSpellModifier
value=system.modifiers.levelSpellModifier
disabled=true
}}
</div>
{{/if}}
{{#if system.biodata.clericUser}}
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.miracleBonus"}}</span>
{{formInput
systemFields.modifiers.fields.levelMiracleModifier
value=system.modifiers.levelMiracleModifier
disabled=true
}}
</div>
{{/if}}
<div class="biodata-elem">
<span class="name">Last HD roll</span>
{{formInput systemFields.biodata.fields.hpPerLevel value=system.biodata.hpPerLevel disabled=true}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.lastHdRoll"}}</span>
{{formInput
systemFields.biodata.fields.hpPerLevel
value=system.biodata.hpPerLevel
disabled=isPlayMode
}}
</div>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.naturalDR"}}</span>
{{formInput
systemFields.biodata.fields.naturalDR
value=system.biodata.naturalDR
disabled=isPlayMode
}}
</div>
</div>
</fieldset>
<div class="biodata-elem">
<span class="name">{{localize "LETHALFANTASY.Label.magicalDR"}}</span>
{{formInput
systemFields.biodata.fields.magicDR
value=system.biodata.magicDR
disabled=isPlayMode
}}
</div>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description"
toggled=true}}
</fieldset>
</fieldset>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.notes"}}</legend>
{{formInput systemFields.notes enriched=enrichedNotes value=system.notes name="system.notes" toggled=true}}
</fieldset>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.description"}}</legend>
{{formInput
systemFields.description
enriched=enrichedDescription
value=system.description
name="system.description"
toggled=true
}}
</fieldset>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.notes"}}</legend>
{{formInput
systemFields.notes
enriched=enrichedNotes
value=system.notes
name="system.notes"
toggled=true
}}
</fieldset>
</div>
</section>
+142 -139
View File
@@ -1,161 +1,164 @@
<section class="tab character-{{tab.id}} {{tab.cssClass}}" data-tab="combat" data-group="sheet">
<div class="main-div">
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.combatDetails"}}</legend>
<div class="combat-details">
<div class="combat-detail">
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.combatDetails"}}</legend>
<div class="combat-details">
<div class="combat-detail">
<button class="action" data-action="rangedAttackDefense">
{{localize "LETHALFANTASY.Label.rangedAttackDefense"}}
</button>
<button class="action ranged-attack-button" data-action="rangedAttackDefense">
{{localize "LETHALFANTASY.Label.rangedAttackDefense"}}
</button>
<button class="action" data-action="rollInitiative">
{{localize "LETHALFANTASY.Label.rollInitiative"}}
</button>
<button class="action ranged-attack-button" data-action="rollInitiative">
{{localize "LETHALFANTASY.Label.rollInitiative"}}
</button>
<div class="flexrow armor-hp">
<span class="name">{{localize "LETHALFANTASY.Label.armorHitPoints"}}</span>
{{formInput systemFields.combat.fields.armorHitPoints value=system.combat.armorHitPoints localize=true }}
<a data-action="armorHitPointsPlus"><i class="fa-solid fa-hexagon-plus"></i></a>
<a data-action="armorHitPointsMinus"><i class="fa-solid fa-hexagon-minus"></i></a>
</div>
<div class="flexrow armor-hp">
<span class="name" data-tooltip="{{localize 'LETHALFANTASY.Tooltip.combatProgressionStart'}}">{{localize "LETHALFANTASY.Label.combatProgressionStart"}}</span>
{{formInput systemFields.combat.fields.combatProgressionStart value=system.combat.combatProgressionStart disabled=isPlayMode }}
</div>
<div class="flexrow granted">
<span class="">{{localize
"LETHALFANTASY.Label.grantedAttackDice"}}</a></span>
{{formInput systemFields.granted.fields.attackDice value=system.granted.attackDice disabled=isPlayMode }}
</div>
<div class="flexrow granted ">
<span class="">{{localize
"LETHALFANTASY.Label.grantedDefenseDice"}}</a></span>
{{formInput systemFields.granted.fields.defenseDice value=system.granted.defenseDice disabled=isPlayMode }}
</div>
<div class="flexrow granted">
<span class="">{{localize
"LETHALFANTASY.Label.grantedDamageDice"}}</a></span>
{{formInput systemFields.granted.fields.damageDice value=system.granted.damageDice disabled=isPlayMode }}
</div>
<div class="flexrow armor-hp">
<span class="name">{{localize "LETHALFANTASY.Label.armorHitPoints"}}</span>
{{formInput systemFields.combat.fields.armorHitPoints value=system.combat.armorHitPoints localize=true }}
<a data-action="armorHitPointsPlus"><i class="fa-solid fa-hexagon-plus"></i></a>
<a data-action="armorHitPointsMinus"><i class="fa-solid fa-hexagon-minus"></i></a>
</div>
<div class="flexrow granted">
<span class=""><a class="rollable" data-roll-type="granted" data-roll-key="attackDice"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize "LETHALFANTASY.Label.grantedAttackDice"}}</a></span>
{{formInput systemFields.granted.fields.attackDice value=system.granted.attackDice disabled=isPlayMode }}
</div>
<div class="flexrow granted ">
<span class=""><a class="rollable" data-roll-type="granted" data-roll-key="defenseDice"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize "LETHALFANTASY.Label.grantedDefenseDice"}}</a></span>
{{formInput systemFields.granted.fields.defenseDice value=system.granted.defenseDice disabled=isPlayMode }}
</div>
<div class="flexrow granted">
<span class=""><a class="rollable" data-roll-type="granted" data-roll-key="damageDice"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize "LETHALFANTASY.Label.grantedDamageDice"}}</a></span>
{{formInput systemFields.granted.fields.damageDice value=system.granted.damageDice disabled=isPlayMode }}
</div>
</div>
</div>
</fieldset>
</fieldset>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.wounds"}}</legend>
<div class="wounds">
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.wounds"}}</legend>
<div class="wounds">
{{#each system.hp.wounds as |wound idx|}}
<div class="wound">
Name:<input class="wound-description wound-data" type="text" data-type="String" data-index="{{@index}}" value="{{wound.description}}" data-name="description" >
Duration:<input class="wound-duration wound-data" type="text" data-type="Number" data-index="{{@index}}" value="{{wound.duration}}" data-name="duration" >
HP:<input class="wound-value wound-data" type="text" data-type="Number" data-index="{{@index}}" value="{{wound.value}}" data-name="value" >
Name:<input class="wound-description wound-data" type="text" data-type="String" data-index="{{@index}}"
value="{{wound.description}}" data-name="description">
Duration:<input class="wound-duration wound-data" type="text" data-type="Number" data-index="{{@index}}"
value="{{wound.duration}}" data-name="duration">
HP:<input class="wound-value wound-data" type="text" data-type="Number" data-index="{{@index}}"
value="{{wound.value}}" data-name="value">
</div>
{{/each}}
</div>
</fieldset>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.weapons"}}</legend>
<div class="weapons">
{{#each weapons as |item|}}
<div class="weapon" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-drag="true"
data-drag-type="damage">
{{#if (ne item.img "icons/svg/item-bag.svg")}}
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
{{/if}}
<div class="name">
{{item.name}}
</div>
<div class="attack-icons">
<a class="rollable" data-roll-type="weapon-attack" data-roll-key="{{item.id}}" data-tooltip="Roll Attack">
<i class="lf-roll-small fa-solid fa-swords" data-roll-type="weapon-attack" data-roll-key="{{item.id}}"></i>
</a>
<a class="rollable" data-roll-type="weapon-defense" data-roll-key="{{item.id}}" data-tooltip="Roll Defense">
<i class="fa-solid fa-shield-halved" data-roll-type="weapon-defense" data-roll-key="{{item.id}}"></i>
</a>
<a class="rollable" data-roll-type="weapon-damage-small" data-roll-key="{{item.id}}"
data-tooltip="Roll Damage (Small)">
<i class="fa-regular fa-face-head-bandage" data-roll-type="weapon-damage-small"
data-roll-key="{{item.id}}"></i>S
</a>
<a class="rollable" data-roll-type="weapon-damage-medium" data-roll-key="{{item.id}}"
data-tooltip="Roll Damage (Medium)">
<i class="fa-regular fa-face-head-bandage" data-roll-type="weapon-damage-medium"
data-roll-key="{{item.id}}"></i>M
</a>
</div>
<div class="controls">
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'LETHALFANTASY.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</fieldset>
</fieldset>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.armors"}}</legend>
<div class="armors">
{{#each armors as |item|}}
<div class="armor" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="name" data-tooltip="{{{item.system.description}}}">
{{item.name}}
</div>
<div class="item-detail" data-tooltip="Defense">{{item.system.defense}}</div>
<div class="item-detail" data-tooltip="Maximum movement">{{item.system.maximumMovement}}</div>
<div class="item-detail" data-tooltip="HP">{{item.system.hp}}</div>
<div class="item-detail" data-tooltip="Damage Reduction">{{item.system.damageReduction}}</div>
<div class="controls">
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'LETHALFANTASY.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.weapons"}}</legend>
<div class="weapons">
{{#each weapons as |item|}}
<div class="weapon" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-drag="true"
data-drag-type="damage">
{{#if (ne item.img "icons/svg/item-bag.svg")}}
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
{{/if}}
<div class="name" data-tooltip="{{item.system.description}}">
{{item.name}}
</div>
<div class="attack-icons">
<a class="rollable" data-roll-type="weapon-attack" data-roll-key="{{item.id}}" data-tooltip="Roll Attack">
<i class="lf-roll-small fa-solid fa-swords" data-roll-type="weapon-attack"
data-roll-key="{{item.id}}"></i>
</a>
<a class="rollable" data-roll-type="weapon-defense" data-roll-key="{{item.id}}" data-tooltip="Roll Defense">
<i class="fa-solid fa-shield-halved" data-roll-type="weapon-defense" data-roll-key="{{item.id}}"></i>
</a>
<a class="rollable" data-roll-type="weapon-damage" data-roll-key="{{item.id}}"
data-tooltip="Roll Damage">
<i class="fa-regular fa-face-head-bandage" data-roll-type="weapon-damage"
data-roll-key="{{item.id}}"></i>
</a>
</div>
<div class="controls">
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'LETHALFANTASY.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
{{/each}}
</div>
</fieldset>
</fieldset>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.shields"}}</legend>
<div class="shields">
{{#each shields as |item|}}
<div class="shield" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="name" data-tooltip="{{{item.system.description}}}">
{{item.name}}
</div>
<div class="item-detail" data-tooltip="Defense">
<a class="rollable" data-roll-type="shield-roll" data-roll-key="{{item.id}}" data-tooltip="Shield Defense">
<i class="lf-roll-small fa-solid fa-shield" data-roll-type="shield-roll" data-roll-key="{{item.id}}"></i>
{{upperFirst item.system.defense}}
</a>
</div>
<div class="item-detail" data-tooltip="Movement reduction">{{item.system.movementreduction}}</div>
<div class="item-detail" data-tooltip="Has cover">{{#if item.system.hascover}}Cover{{/if}}</div>
<div class="controls">
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'LETHALFANTASY.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.armors"}}</legend>
<div class="armors">
{{#each armors as |item|}}
<div class="armor" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="name" data-tooltip="{{item.system.description}}">
{{item.name}}
</div>
<div class="item-detail" data-tooltip="Defense">{{item.system.defense}}</div>
<div class="item-detail" data-tooltip="Maximum movement">{{item.system.maximumMovement}}</div>
<div class="item-detail" data-tooltip="HP">{{item.system.hp}}</div>
<div class="item-detail" data-tooltip="Damage Reduction">{{item.system.damageReduction}}</div>
<div class="item-detail" data-tooltip={{#if item.system.equipped}}"Equipped"{{else}}"Not Equipped"{{/if}}>{{#if item.system.equipped}}<i class="fas fa-check"></i>{{else}}<i class="fas fa-times"></i>{{/if}}</div>
<div class="controls">
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'LETHALFANTASY.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
{{/each}}
</div>
</fieldset>
<div>
</fieldset>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.shields"}}</legend>
<div class="shields">
{{#each shields as |item|}}
<div class="shield" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="name" data-tooltip="{{item.system.description}}">
{{item.name}}
</div>
<div class="item-detail" data-tooltip="Defense">
<a class="rollable" data-roll-type="shield-roll" data-roll-key="{{item.id}}" data-tooltip="Shield Defense">
<i class="lf-roll-small fa-solid fa-shield" data-roll-type="shield-roll" data-roll-key="{{item.id}}"></i>
{{upperFirst item.system.defense}}
</a>
</div>
<div class="item-detail" data-tooltip="Movement reduction">{{item.system.movementreduction}}</div>
<div class="item-detail" data-tooltip="Has cover">{{#if item.system.hascover}}Cover{{/if}}</div>
<div class="item-detail" data-tooltip={{#if item.system.equipped}}"Equipped"{{else}}"Not Equipped"{{/if}}>{{#if item.system.equipped}}<i class="fas fa-check"></i>{{else}}<i class="fas fa-times"></i>{{/if}}</div>
<div class="controls">
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'LETHALFANTASY.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</fieldset>
</div>
</section>
+330 -103
View File
@@ -1,4 +1,6 @@
<section class="character-main character-main-{{ifThen isPlayMode 'play' 'edit'}}">
<section
class="character-main character-main-{{ifThen isPlayMode 'play' 'edit'}}"
>
{{log "character-main" this}}
<fieldset>
@@ -6,99 +8,189 @@
<div class="character-pc character-pc-{{ifThen isPlayMode 'play' 'edit'}}">
<div class="character-left">
<div class="character-left-image">
<img class="character-img" src="{{actor.img}}" data-edit="img" data-action="editImage"
data-tooltip="{{actor.name}}" />
<img
class="character-img"
src="{{actor.img}}"
data-edit="img"
data-action="editImage"
data-tooltip="{{actor.name}}"
/>
</div>
<fieldset class="">
<div class="flexrow character-hp">
<span class="name">{{localize "LETHALFANTASY.Label.HP"}}</span>
{{formInput systemFields.hp.fields.value value=system.hp.value disabled=isPlayMode
classes="character-hp-value"}}
{{formInput
systemFields.hp.fields.value
value=system.hp.value
disabled=isPlayMode
classes="character-hp-value"
}}
&nbsp;/&nbsp;
{{formInput systemFields.hp.fields.max value=system.hp.max disabled=isPlayMode
classes="character-hp-value"}}
{{formInput
systemFields.hp.fields.max
value=system.hp.max
disabled=isPlayMode
classes="character-hp-value"
}}
</div>
<div class="flexrow character-hp">
<span class="name">{{localize "LETHALFANTASY.Label.grit"}}</span>
{{formInput systemFields.grit.fields.current value=system.grit.current disabled=isPlayMode
classes="character-hp"}}
{{formInput
systemFields.grit.fields.current
value=system.grit.current
disabled=isPlayMode
classes="character-hp"
}}
<span class="name">{{localize "LETHALFANTASY.Label.earned"}}</span>
{{formInput systemFields.grit.fields.earned value=system.grit.earned disabled=isPlayMode
classes="character-hp"}}
{{formInput
systemFields.grit.fields.earned
value=system.grit.earned
disabled=isPlayMode
classes="character-hp"
}}
</div>
<div class="flexrow character-hp">
<span class="name">{{localize "LETHALFANTASY.Label.luck"}}</span>
{{formInput systemFields.luck.fields.current value=system.luck.current disabled=isPlayMode
classes="character-hp"}}
{{formInput
systemFields.luck.fields.current
value=system.luck.current
disabled=isPlayMode
classes="character-hp"
}}
<span class="name">{{localize "LETHALFANTASY.Label.earned"}}</span>
{{formInput systemFields.luck.fields.earned value=system.luck.earned disabled=isPlayMode
classes="character-hp"}}
{{formInput
systemFields.luck.fields.earned
value=system.luck.earned
disabled=isPlayMode
classes="character-hp"
}}
</div>
<div class="flexrow ">
<span class="">{{localize "LETHALFANTASY.Label.damageResistanceShort"}}</span>
{{formInput systemFields.hp.fields.damageResistance value=system.hp.fields.damageResistance
disabled=isPlayMode classes="character-hp"}}
</div>
<div class="flexrow">
<span class="" data-tooltip="Damage reduction">{{localize
"LETHALFANTASY.Label.damageResistanceShort"
}}
</span>
<input
type="text"
class="character-damage-reduction"
data-tooltip="DR (armor+natural+magical)"
disabled
value={{damageReduction}}
/>
<input
type="text"
class="character-damage-reduction"
data-tooltip="DR (shield)"
disabled
value={{damageReductionShield}}
/>
</div>
</fieldset>
</div>
<div class="character-right">
<div class="character-name">
{{formInput fields.name value=source.name rootId=partId disabled=isPlayMode}}
<a class="control" data-action="toggleSheet" data-tooltip="LETHALFANTASY.ToggleSheet"
data-tooltip-direction="UP">
{{formInput
fields.name
value=source.name
rootId=partId
disabled=isPlayMode
}}
<a
class="control"
data-action="toggleSheet"
data-tooltip="LETHALFANTASY.ToggleSheet"
data-tooltip-direction="UP"
>
<i class="fa-solid fa-user-{{ifThen isPlayMode 'lock' 'pen'}}"></i>
</a>
</div>
<fieldset class="character-characteristics character-characteristics-{{ifThen isPlayMode 'play' 'edit'}}">
<fieldset
class="character-characteristics character-characteristics-{{ifThen
isPlayMode
'play'
'edit'
}}"
>
<legend>{{localize "LETHALFANTASY.Label.Saves"}}</legend>
<div class="character-saves">
<div class="character-save">
<span class="name"><a class="rollable" data-roll-type="save" data-roll-key="will"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>
<span class="name"><a
class="rollable"
data-roll-type="save"
data-roll-key="will"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.saves.will"}}
</a></span>
{{formField systemFields.saves.fields.will.fields.value value=system.saves.will.value disabled=true}}
{{formField
systemFields.saves.fields.will.fields.value
value=system.saves.will.value
disabled=true
}}
<span class="name">
<a class="rollable" data-roll-type="save" data-roll-key="dodge"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>
<a
class="rollable"
data-roll-type="save"
data-roll-key="dodge"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.saves.dodge"}}
</a>
</span>
{{formField systemFields.saves.fields.dodge.fields.value value=system.saves.dodge.value
disabled=true}}
{{formField
systemFields.saves.fields.dodge.fields.value
value=system.saves.dodge.value
disabled=true
}}
<span class="name">
<a class="rollable" data-roll-type="save" data-roll-key="toughness"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>
<a
class="rollable"
data-roll-type="save"
data-roll-key="toughness"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.saves.toughness"}}
</a>
</span>
{{formField systemFields.saves.fields.toughness.fields.value value=system.saves.toughness.value
disabled=true}}
{{formField
systemFields.saves.fields.toughness.fields.value
value=system.saves.toughness.value
disabled=true
}}
</div>
<div class="character-save">
<span class="name">
<a class="rollable" data-roll-type="save" data-roll-key="contagion"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>
<a
class="rollable"
data-roll-type="save"
data-roll-key="contagion"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.saves.contagion"}}
</a>
</span>
{{formField systemFields.saves.fields.contagion.fields.value value=system.saves.contagion.value
disabled=true}}
{{formField
systemFields.saves.fields.contagion.fields.value
value=system.saves.contagion.value
disabled=true
}}
<span class="name">
<a class="rollable" data-roll-type="save" data-roll-key="poison"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>
<a
class="rollable"
data-roll-type="save"
data-roll-key="poison"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.saves.poison"}}
</a>
</span>
{{formField systemFields.saves.fields.poison.fields.value value=system.saves.poison.value
disabled=true }}
{{formField
systemFields.saves.fields.poison.fields.value
value=system.saves.poison.value
disabled=true
}}
<!--
<span class="name-pain">
@@ -125,51 +217,124 @@
</div>
</fieldset>
<fieldset class="character-characteristics character-characteristics-{{ifThen isPlayMode 'play' 'edit'}}">
<fieldset
class="character-characteristics character-characteristics-{{ifThen
isPlayMode
'play'
'edit'
}}"
>
<legend>{{localize "LETHALFANTASY.Label.Challenges"}}</legend>
<div class="character-challenges">
<div class="character-challenge">
<span class="name"><a class="rollable" data-roll-type="challenge" data-roll-key="str"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.challenges.strength"}}</a></span>
{{formField systemFields.challenges.fields.str.fields.value value=system.challenges.str.value
disabled=true
<span class="name"><a
class="rollable"
data-roll-type="challenge"
data-roll-key="str"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.challenges.strength"
}}</a></span>
{{formField
systemFields.challenges.fields.str.fields.value
value=system.challenges.str.value
disabled=true
}}
<span class="name"><a class="rollable" data-roll-type="challenge" data-roll-key="agility"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.challenges.agility"}}</a></span>
{{formField systemFields.challenges.fields.agility.fields.value value=system.challenges.agility.value
disabled=true
<span class="name"><a
class="rollable"
data-roll-type="challenge"
data-roll-key="agility"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.challenges.agility"
}}</a></span>
{{formField
systemFields.challenges.fields.agility.fields.value
value=system.challenges.agility.value
disabled=true
}}
<span class="name"><a
class="rollable"
data-roll-type="challenge"
data-roll-key="dying"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.challenges.dying"
}}</a></span>
{{formField
systemFields.challenges.fields.dying.fields.value
value=system.challenges.dying.value
disabled=true
}}
<span class="name"><a class="rollable" data-roll-type="challenge" data-roll-key="dying"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.challenges.dying"}}</a></span>
{{formField systemFields.challenges.fields.dying.fields.value value=system.challenges.dying.value
disabled=true }}
</div>
</div>
</fieldset>
<fieldset class="character-characteristics character-characteristics-{{ifThen isPlayMode 'play' 'edit'}}">
<fieldset
class="character-characteristics character-characteristics-{{ifThen
isPlayMode
'play'
'edit'
}}"
>
<legend>{{localize "LETHALFANTASY.Label.Movement"}}</legend>
<div class="character-movements">
<div class="character-movement">
<span class="name">{{localize "LETHALFANTASY.Label.movement.walk"}}</span>
{{formField systemFields.movement.fields.walk value=system.movement.walk disabled=isPlayMode}}
<span class="name">{{localize "LETHALFANTASY.Label.movement.jog"}}</span>
{{formField systemFields.movement.fields.jog value=system.movement.jog disabled=isPlayMode}}
<span class="name">{{localize "LETHALFANTASY.Label.movement.run"}}</span>
{{formField systemFields.movement.fields.run value=system.movement.run disabled=isPlayMode}}
<span class="name">{{localize "LETHALFANTASY.Label.movement.sprint"}}</span>
{{formField systemFields.movement.fields.sprint value=system.movement.sprint disabled=isPlayMode}}
<span class="name">{{localize
"LETHALFANTASY.Label.movement.walk"
}}</span>
{{formField
systemFields.movement.fields.walk
value=system.movement.walk
disabled=isPlayMode
}}
<span class="name">{{localize
"LETHALFANTASY.Label.movement.jog"
}}</span>
{{formField
systemFields.movement.fields.jog
value=system.movement.jog
disabled=isPlayMode
}}
<span class="name">{{localize
"LETHALFANTASY.Label.movement.run"
}}</span>
{{formField
systemFields.movement.fields.run
value=system.movement.run
disabled=isPlayMode
}}
<span class="name">{{localize
"LETHALFANTASY.Label.movement.sprint"
}}</span>
{{formField
systemFields.movement.fields.sprint
value=system.movement.sprint
disabled=isPlayMode
}}
</div>
<div class="character-movement">
<span class="name">{{localize "LETHALFANTASY.Label.movement.jumpBroad"}}</span>
{{formField systemFields.jump.fields.broad value=system.jump.broad disabled=isPlayMode}}
<span class="name">{{localize "LETHALFANTASY.Label.movement.jumpRunning"}}</span>
{{formField systemFields.jump.fields.running value=system.jump.running disabled=isPlayMode}}
<span class="name">{{localize "LETHALFANTASY.Label.movement.jumpVertical"}}</span>
{{formField systemFields.jump.fields.vertical value=system.jump.vertical disabled=isPlayMode}}
<span class="name">{{localize
"LETHALFANTASY.Label.movement.jumpBroad"
}}</span>
{{formField
systemFields.jump.fields.broad
value=system.jump.broad
disabled=isPlayMode
}}
<span class="name">{{localize
"LETHALFANTASY.Label.movement.jumpRunning"
}}</span>
{{formField
systemFields.jump.fields.running
value=system.jump.running
disabled=isPlayMode
}}
<span class="name">{{localize
"LETHALFANTASY.Label.movement.jumpVertical"
}}</span>
{{formField
systemFields.jump.fields.vertical
value=system.jump.vertical
disabled=isPlayMode
}}
</div>
</div>
</fieldset>
@@ -178,62 +343,124 @@
</div>
</fieldset>
<fieldset class="character-characteristics character-characteristics-{{ifThen isPlayMode 'play' 'edit'}}">
<fieldset
class="character-characteristics character-characteristics-{{ifThen
isPlayMode
'play'
'edit'
}}"
>
<legend>{{localize "LETHALFANTASY.Label.characteristics"}}</legend>
<div class="character-characteristic">
<span>{{localize "LETHALFANTASY.Label.str"}}</span>
{{formField systemFields.characteristics.fields.str.fields.value value=system.characteristics.str.value
disabled=isPlayMode data-char-id="str" }}
{{formField systemFields.characteristics.fields.str.fields.percent value=system.characteristics.str.percent
disabled=isPlayMode type="number"}}
{{formField
systemFields.characteristics.fields.str.fields.value
value=system.characteristics.str.value
disabled=isPlayMode
data-char-id="str"
}}
{{formField
systemFields.characteristics.fields.str.fields.percent
value=system.characteristics.str.percent
disabled=isPlayMode
type="number"
}}
</div>
<div class="character-characteristic">
<span>{{localize "LETHALFANTASY.Label.int"}}</span>
{{formField systemFields.characteristics.fields.int.fields.value value=system.characteristics.int.value
disabled=isPlayMode data-char-id="int" }}
{{formField
systemFields.characteristics.fields.int.fields.value
value=system.characteristics.int.value
disabled=isPlayMode
data-char-id="int"
}}
{{formField systemFields.characteristics.fields.int.fields.percent value=system.characteristics.int.percent
disabled=isPlayMode type="number" }}
{{formField
systemFields.characteristics.fields.int.fields.percent
value=system.characteristics.int.percent
disabled=isPlayMode
type="number"
}}
</div>
<div class="character-characteristic">
<span>{{localize "LETHALFANTASY.Label.wis"}}</span>
{{formField systemFields.characteristics.fields.wis.fields.value value=system.characteristics.wis.value
disabled=isPlayMode data-char-id="wis" }}
{{formField
systemFields.characteristics.fields.wis.fields.value
value=system.characteristics.wis.value
disabled=isPlayMode
data-char-id="wis"
}}
{{formField systemFields.characteristics.fields.wis.fields.percent value=system.characteristics.wis.percent
disabled=isPlayMode type="number"}}
{{formField
systemFields.characteristics.fields.wis.fields.percent
value=system.characteristics.wis.percent
disabled=isPlayMode
type="number"
}}
</div>
<div class="character-characteristic">
<span>{{localize "LETHALFANTASY.Label.dex"}}</span>
{{formField systemFields.characteristics.fields.dex.fields.value value=system.characteristics.dex.value
disabled=isPlayMode data-char-id="wis" }}
{{formField
systemFields.characteristics.fields.dex.fields.value
value=system.characteristics.dex.value
disabled=isPlayMode
data-char-id="wis"
}}
{{formField systemFields.characteristics.fields.dex.fields.percent value=system.characteristics.dex.percent
disabled=isPlayMode type="number" }}
{{formField
systemFields.characteristics.fields.dex.fields.percent
value=system.characteristics.dex.percent
disabled=isPlayMode
type="number"
}}
</div>
<div class="character-characteristic">
<span>{{localize "LETHALFANTASY.Label.con"}}</span>
{{formField systemFields.characteristics.fields.con.fields.value value=system.characteristics.con.value
disabled=isPlayMode data-char-id="con" }}
{{formField
systemFields.characteristics.fields.con.fields.value
value=system.characteristics.con.value
disabled=isPlayMode
data-char-id="con"
}}
{{formField systemFields.characteristics.fields.con.fields.percent value=system.characteristics.con.percent
disabled=isPlayMode type="number"}}
{{formField
systemFields.characteristics.fields.con.fields.percent
value=system.characteristics.con.percent
disabled=isPlayMode
type="number"
}}
</div>
<div class="character-characteristic">
<span>{{localize "LETHALFANTASY.Label.cha"}}</span>
{{formField systemFields.characteristics.fields.cha.fields.value value=system.characteristics.cha.value
disabled=isPlayMode data-char-id="cha" }}
{{formField
systemFields.characteristics.fields.cha.fields.value
value=system.characteristics.cha.value
disabled=isPlayMode
data-char-id="cha"
}}
{{formField systemFields.characteristics.fields.cha.fields.percent value=system.characteristics.cha.percent
disabled=isPlayMode type="number"}}
{{formField
systemFields.characteristics.fields.cha.fields.percent
value=system.characteristics.cha.percent
disabled=isPlayMode
type="number"
}}
</div>
<div class="character-characteristic">
<span>{{localize "LETHALFANTASY.Label.luc"}}</span>
{{formField systemFields.characteristics.fields.luc.fields.value value=system.characteristics.luc.value
disabled=isPlayMode data-char-id="luc" }}
{{formField
systemFields.characteristics.fields.luc.fields.value
value=system.characteristics.luc.value
disabled=isPlayMode
data-char-id="luc"
}}
{{formField systemFields.characteristics.fields.luc.fields.percent value=system.characteristics.luc.percent
disabled=isPlayMode type="number"}}
{{formField
systemFields.characteristics.fields.luc.fields.percent
value=system.characteristics.luc.percent
disabled=isPlayMode
type="number"
}}
</div>
</fieldset>
+2 -2
View File
@@ -5,12 +5,12 @@
<legend>{{localize "LETHALFANTASY.Label.divinityPoints"}}</legend>
<div class="miracle-details">
<div class="miracle-detail">
<span>Current</span>
<span>{{localize "LETHALFANTASY.Label.current"}}</span>
{{formField systemFields.divinityPoints.fields.value value=system.divinityPoints.value localize=true}}
<a data-action="divinityPointsPlus"><i class="fa-solid fa-hexagon-plus"></i></a>
<a data-action="divinityPointsMinus"><i class="fa-solid fa-hexagon-minus"></i></a>
<span>Max</span>
<span>{{localize "LETHALFANTASY.Label.max"}}</span>
{{formField systemFields.divinityPoints.fields.max value=system.divinityPoints.max localize=true
disabled=isPlayMode}}
</div>
+137 -65
View File
@@ -1,73 +1,145 @@
<section class="tab character-{{tab.id}} {{tab.cssClass}}" data-tab="skills" data-group="sheet">
<section
class="tab character-{{tab.id}} {{tab.cssClass}}"
data-tab="skills"
data-group="sheet"
>
<div class="main-div">
<fieldset>
<legend data-tooltip="{{localize " LETHALFANTASY.Tooltip.skills"}}" data-tooltip-direction="UP">{{localize
"LETHALFANTASY.Label.skills"}}</legend>
<div class="skills">
{{#each skills as |item|}}
<div class="skill " data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="name">
<a class="rollable" data-roll-type="skill" data-roll-key="{{item.id}}">
<i class="lf-roll-small fa-duotone fa-solid fa-dice-d10"></i>
{{item.name}}
</a>
{{#each skillsByCategory as |categoryData|}}
<fieldset>
<legend
data-tooltip="{{localize ' LETHALFANTASY.Tooltip.skills'}}"
data-tooltip-direction="UP"
>{{localize categoryData.label}}</legend>
<div class="skills">
{{#each categoryData.skills as |item|}}
<div
class="skill"
data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"
>
<img
class="item-img"
src="{{item.img}}"
data-tooltip="{{item.name}}"
/>
<div class="name">
<a
class="rollable"
data-roll-type="skill"
data-roll-key="{{item.id}}"
>
<i class="lf-roll-small fa-duotone fa-solid fa-dice-d10"></i>
{{item.name}}
</a>
</div>
<div class="score">
+{{item.system.skillTotal}}
</div>
<div class="controls">
<a
data-tooltip="{{localize 'LETHALFANTASY.Edit'}}"
data-action="edit"
data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"
><i class="fas fa-edit"></i></a>
<a
data-tooltip="{{localize 'LETHALFANTASY.Delete'}}"
data-action="delete"
data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"
><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
<div class="score">
+{{item.system.skillTotal}}
</div>
<div class="controls">
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'LETHALFANTASY.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</fieldset>
</fieldset>
{{/each}}
<fieldset>
<legend data-tooltip="{{localize " LETHALFANTASY.Tooltip.gifts"}}" data-tooltip-direction="UP">{{localize
"LETHALFANTASY.Label.gifts"}}</legend>
<div class="gifts">
{{#each gifts as |item|}}
<div class="gift " data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="name" data-tooltip="{{{item.description}}}<br><br>{{item.path}}" data-tooltip-direction="UP">
{{item.name}}
</div>
<div class="controls">
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'LETHALFANTASY.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
<fieldset>
<legend
data-tooltip="{{localize ' LETHALFANTASY.Tooltip.gifts'}}"
data-tooltip-direction="UP"
>{{localize "LETHALFANTASY.Label.gifts"}}</legend>
<div class="gifts">
{{#each gifts as |item|}}
<div
class="gift"
data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"
>
<img
class="item-img"
src="{{item.img}}"
data-tooltip="{{item.name}}"
/>
<div
class="name"
data-tooltip="{{{item.description}}}<br><br>{{item.path}}"
data-tooltip-direction="UP"
>
{{item.name}}
</div>
<div class="controls">
<a
data-tooltip="{{localize 'LETHALFANTASY.Edit'}}"
data-action="edit"
data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"
><i class="fas fa-edit"></i></a>
<a
data-tooltip="{{localize 'LETHALFANTASY.Delete'}}"
data-action="delete"
data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"
><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
{{/each}}
</div>
</fieldset>
</fieldset>
<fieldset>
<legend data-tooltip="{{localize " LETHALFANTASY.Tooltip.vulnerabilities"}}" data-tooltip-direction="UP">{{localize
"LETHALFANTASY.Label.vulnerabilities"}}</legend>
<div class="vulnerabilities">
{{#each vulnerabilities as |item|}}
<div class="vulnerability " data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="name" data-tooltip="{{{item.description}}}<br><br>{{item.path}}" data-tooltip-direction="UP">
{{item.name}}
</div>
<div class="controls">
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'LETHALFANTASY.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
<fieldset>
<legend
data-tooltip="{{localize ' LETHALFANTASY.Tooltip.vulnerabilities'}}"
data-tooltip-direction="UP"
>{{localize "LETHALFANTASY.Label.vulnerabilities"}}</legend>
<div class="vulnerabilities">
{{#each vulnerabilities as |item|}}
<div
class="vulnerability"
data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"
>
<img
class="item-img"
src="{{item.img}}"
data-tooltip="{{item.name}}"
/>
<div
class="name"
data-tooltip="{{{item.description}}}<br><br>{{item.path}}"
data-tooltip-direction="UP"
>
{{item.name}}
</div>
<div class="controls">
<a
data-tooltip="{{localize 'LETHALFANTASY.Edit'}}"
data-action="edit"
data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"
><i class="fas fa-edit"></i></a>
<a
data-tooltip="{{localize 'LETHALFANTASY.Delete'}}"
data-action="delete"
data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"
><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
{{/each}}
</div>
</fieldset>
</div>
</fieldset>
</div>
</section>
+21 -2
View File
@@ -5,12 +5,12 @@
<legend>{{localize "LETHALFANTASY.Label.aetherPoints"}}</legend>
<div class="spell-details">
<div class="spell-detail">
<span>Current</span>
<span>{{localize "LETHALFANTASY.Label.current"}}</span>
{{formField systemFields.aetherPoints.fields.value value=system.aetherPoints.value localize=true}}
<a data-action="aetherPointsPlus"><i class="fa-solid fa-hexagon-plus"></i></a>
<a data-action="aetherPointsMinus"><i class="fa-solid fa-hexagon-minus"></i></a>
<span>Max</span>
<span>{{localize "LETHALFANTASY.Label.max"}}</span>
{{formField systemFields.aetherPoints.fields.max value=system.aetherPoints.max localize=true
disabled=isPlayMode}}
</div>
@@ -37,6 +37,25 @@
<i class="fa-duotone fa-solid fa-stars" data-roll-type="spell-power" data-roll-key="{{item.id}}"></i>
</a>
{{#if item.system.damageDice}}
<a data-action="rollSpellDamage" data-item-id="{{item.id}}" data-damage-tier="standard"
data-tooltip="Spell Damage (Standard)">
<i class="fa-solid fa-wand-magic-sparkles"></i>S
</a>
{{/if}}
{{#if item.system.damageDiceOverpowered}}
<a data-action="rollSpellDamage" data-item-id="{{item.id}}" data-damage-tier="overpowered"
data-tooltip="Spell Damage (Overpowered)">
<i class="fa-solid fa-wand-magic-sparkles"></i>O
</a>
{{/if}}
{{#if item.system.damageDiceOverpowered2}}
<a data-action="rollSpellDamage" data-item-id="{{item.id}}" data-damage-tier="overpowered2"
data-tooltip="Spell Damage (Overpowered 2)">
<i class="fa-solid fa-wand-magic-sparkles"></i>O2
</a>
{{/if}}
<div class="controls">
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
+264 -74
View File
@@ -1,82 +1,272 @@
{{!log 'chat-message' this}}
{{!log 'weaponDamageOptions' weaponDamageOptions}}
<div class="{{cssClass}}">
<div class="intro-chat">
<div class="intro-img">
<img src="{{actingCharImg}}" data-tooltip="{{actingCharName}}" />
</div>
<div class="intro-right">
<span>{{actingCharName}} - {{upperFirst rollName}}</span>
{{#if (match rollType "attack")}}
<span>Attack roll !</span>
{{/if}}
{{#if (match rollType "defense")}}
<span>Defense roll !</span>
{{/if}}
{{#if badResult}}
<span>{{localize "LETHALFANTASY.Label.otherResult"}} : {{badResult}}</span>
{{/if}}
{{#if rollTarget.weapon}}
<span>{{rollTarget.weapon.name}}</span>
{{/if}}
{{#if rollData.letItFly}}
<span>Let It Fly attack ! </span>
{{/if}}
{{#if rollData.pointBlank}}
<span>Point Blank Range Attack !</span>
{{/if}}
{{#if rollData.beyondSkill}}
<span>Beyond Skill Range Attack !</span>
{{/if}}
<span>Formula : {{titleFormula}}</span>
{{#each diceResults as |result|}}
<span>{{result.dice}} : {{result.value}}</span>
{{/each}}
<!-- Header with character info -->
<div class="chat-header">
<div class="character-info">
<div class="character-avatar">
<img src="{{actingCharImg}}" data-tooltip="{{actingCharName}}" />
</div>
<div class="character-details">
<div class="character-name">{{actingCharName}}</div>
<div class="roll-type-badge">
{{#if (match rollType "attack")}}
<i class="fa-solid fa-swords"></i>
{{/if}}
{{#if (match rollType "defense")}}
<i class="fa-solid fa-shield"></i>
{{/if}}
{{#if rollData.isDamage}}
<i class="fa-solid fa-burst"></i>
{{/if}}
{{#if (eq rollType "skill")}}
<i class="fa-solid fa-hand-sparkles"></i>
{{/if}}
{{upperFirst rollName}}
</div>
</div>
</div>
</div>
{{#if isSave}}
<div class="result">
{{#if (eq resultType "success")}}
{{#if isPrivate}}?{{else}}{{localize "LETHALFANTASY.Roll.success"}}{{/if}}
{{else}}
{{#if isPrivate}}?{{else}}{{localize "LETHALFANTASY.Roll.failure"}}{{/if}}
{{/if}}
</div>
{{/if}}
{{#if isResource}}
<div class="result">
{{#if (eq resultType "success")}}
{{#if isPrivate}}?{{else}}{{localize "LETHALFANTASY.Roll.success"}}{{/if}}
{{else}}
{{#if isPrivate}}?{{else}}{{localize "LETHALFANTASY.Roll.failure"}}{{#if isFailure}} ({{localize
"LETHALFANTASY.Roll.resourceLost"}}){{/if}}{{/if}}
{{/if}}
</div>
{{/if}}
{{#if isDamage}}
<div>
{{#if (and isGM hasTarget)}}
{{{localize "LETHALFANTASY.Roll.displayArmor" targetName=targetName targetArmor=targetArmor realDamage=realDamage}}}
{{/if}}
</div>
{{/if}}
<!-- Roll details and modifiers -->
<div class="roll-details">
{{#if rollTarget.weapon}}
<div class="detail-item weapon-name">
<i class="fa-solid fa-sword"></i>
<span>{{rollTarget.weapon.name}}</span>
</div>
{{/if}}
{{#if (eq rollData.favor "favor")}}
<div class="detail-badge favor-badge">
<i class="fa-solid fa-sparkles"></i>
<span>{{localize "LETHALFANTASY.Label.favor"}}</span>
</div>
{{/if}}
{{#if (eq rollData.favor "disfavor")}}
<div class="detail-badge disfavor-badge">
<i class="fa-solid fa-skull"></i>
<span>{{localize "LETHALFANTASY.Label.disfavor"}}</span>
</div>
{{/if}}
{{#if rollData.letItFly}}
<div class="detail-badge special-badge">
<i class="fa-solid fa-bow-arrow"></i>
<span>{{localize "LETHALFANTASY.Label.letItFly"}}</span>
</div>
{{/if}}
{{#if rollData.pointBlank}}
<div class="detail-badge special-badge">
<i class="fa-solid fa-bullseye-arrow"></i>
<span>{{localize "LETHALFANTASY.Label.pointBlank"}}</span>
</div>
{{/if}}
{{#if rollData.beyondSkill}}
<div class="detail-badge special-badge">
<i class="fa-solid fa-target-lock"></i>
<span>{{localize "LETHALFANTASY.Label.beyondSkill"}}</span>
</div>
{{/if}}
{{#if rollData.isDamage}}
<div class="detail-badge damage-badge">
<i class="fa-solid fa-dice-d20"></i>
<span>{{localize "LETHALFANTASY.Label.weapon-damage"}}</span>
</div>
{{/if}}
{{#if badResult}}
<div class="detail-item other-result">
<strong>{{localize "LETHALFANTASY.Label.otherResult"}}</strong>: {{badResult}}
</div>
{{/if}}
</div>
<!-- Formula and dice results -->
<div class="formula-section">
<div class="formula-display">
<i class="fa-solid fa-dice-d20"></i>
<span class="formula-text">{{titleFormula}}</span>
</div>
{{#if diceResults}}
<div class="dice-breakdown">
{{#each diceResults as |result|}}
<div class="dice-item">
<span class="dice-type">{{result.dice}}</span>
<span class="dice-separator">→</span>
<span class="dice-value">{{result.value}}</span>
</div>
{{/each}}
</div>
{{/if}}
</div>
<!-- Result section -->
{{#unless isPrivate}}
<div class="dice-result">
<h4 class="dice-total">{{total}}</h4>
</div>
{{#if D30result}}
<div class="dice-result">
<h4 class="dice-total">D30 result: {{D30result}}</h4>
</div>
<div class="result-section">
<div class="main-result">
<div class="result-label">{{localize "LETHALFANTASY.Label.total"}}</div>
<div class="result-value {{#if (eq resultType 'success')}}success{{else}}failure{{/if}}">
{{total}}
</div>
</div>
{{#if D30result}}
<div class="d30-result">
<div class="d30-header">
<i class="fa-solid fa-dice-d20"></i>
<span class="d30-label">D30 Special</span>
<span class="d30-value">{{D30result}}</span>
</div>
{{#if D30message}}
<div class="d30-message">
<i class="fa-solid fa-wand-magic-sparkles"></i>
<span>{{D30message.description}}</span>
</div>
{{/if}}
</div>
{{/if}}
</div>
{{#if isSave}}
<div class="outcome-badge {{#if (eq resultType 'success')}}success-outcome{{else}}failure-outcome{{/if}}">
{{#if (eq resultType "success")}}
<i class="fa-solid fa-circle-check"></i>
{{localize "LETHALFANTASY.Roll.success"}}
{{else}}
<i class="fa-solid fa-circle-xmark"></i>
{{localize "LETHALFANTASY.Roll.failure"}}
{{/if}}
</div>
{{/if}}
{{#if isResource}}
<div class="outcome-badge {{#if (eq resultType 'success')}}success-outcome{{else}}failure-outcome{{/if}}">
{{#if (eq resultType "success")}}
<i class="fa-solid fa-circle-check"></i>
{{localize "LETHALFANTASY.Roll.success"}}
{{else}}
<i class="fa-solid fa-circle-xmark"></i>
{{localize "LETHALFANTASY.Roll.failure"}}
{{#if isFailure}}
<span class="resource-lost">({{localize "LETHALFANTASY.Roll.resourceLost"}})</span>
{{/if}}
{{/if}}
</div>
{{/if}}
{{#if isDamage}}
{{#if (and isGM hasTarget)}}
<div class="target-info">
<i class="fa-solid fa-crosshairs"></i>
{{{localize
"LETHALFANTASY.Roll.displayArmor"
targetName=targetName
targetArmor=targetArmor
realDamage=realDamage
}}}
</div>
{{/if}}
{{/if}}
{{else}}
<div class="private-result">
<i class="fa-solid fa-eye-slash"></i>
<span>{{localize "LETHALFANTASY.Label.privateRoll"}}</span>
</div>
{{/unless}}
{{#if weaponDamageOptions}}
{{#if canSelectTarget}}
<div class="damage-buttons">
<div class="damage-buttons-grid {{#if weaponDamageOptions.isMonster}}monster-damage{{/if}}">
{{#if weaponDamageOptions.isMonster}}
<button
class="damage-roll-btn"
data-weapon-id="{{weaponDamageOptions.weaponId}}"
data-damage-type="monster"
data-damage-formula="{{weaponDamageOptions.damageFormula}}"
data-damage-modifier="{{weaponDamageOptions.damageModifier}}"
data-is-monster="true"
title="{{localize 'LETHALFANTASY.Label.rollDamage'}} - {{weaponDamageOptions.weaponName}}"
>
<i class="fa-solid fa-dice"></i>
Damage:
{{weaponDamageOptions.damageFormula}}{{#if
weaponDamageOptions.damageModifier
}}+{{weaponDamageOptions.damageModifier}}{{/if}}
</button>
{{else}}
{{#if weaponDamageOptions.damageM}}
<button
class="damage-roll-btn"
data-weapon-id="{{weaponDamageOptions.weaponId}}"
data-damage-type="medium"
data-damage-formula="{{weaponDamageOptions.damageM}}"
data-is-monster="false"
title="{{localize 'LETHALFANTASY.Label.rollDamage'}}"
>
<i class="fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.damage"}}
</button>
{{/if}}
{{/if}}
</div>
</div>
{{/if}}
{{/if}}
{{/unless}}
</div>
{{#if rollData.isDamage}}
{{#if defenderId}}
<div class="damage-result auto-applied">
<div class="auto-damage-notice">
<i class="fa-solid fa-check-circle"></i>
<span>Damage automatically applied to defender (with damage reduction)</span>
</div>
</div>
{{else}}
<div class="damage-result">
<ul>
<li class="li-apply-wounds">
<div>{{localize "LETHALFANTASY.Label.applyDamage"}}</div>
<div class="combatants-grid">
{{#each combatants}}
<button
class="apply-wounds-btn"
data-combatant-id="{{this.id}}"
title="{{this.name}}"
>
{{this.name}}
</button>
{{/each}}
</div>
</li>
</ul>
</div>
{{/if}}
{{/if}}
{{#if isAttack}}
{{#if canSelectTarget}}
<div class="attack-targets">
<ul>
<li class="li-select-target">
<div>{{localize "LETHALFANTASY.Label.selectTarget"}}</div>
<div class="combatants-grid">
{{#each combatants}}
<button
class="request-defense-btn"
data-combatant-id="{{this.id}}"
data-token-id="{{this.tokenId}}"
title="{{this.name}}"
>
{{this.name}}
</button>
{{/each}}
</div>
</li>
</ul>
</div>
{{/if}}
{{/if}}
</div>
+9 -1
View File
@@ -3,7 +3,7 @@
{{!-- GM Controls --}}
{{#if user.isGM}}
{{#if combat.round}}
{{#if (or combat.round (eq combat.turn 0))}}
<!-- <button type="button" class="inline-control combat-control icon fa-solid fa-backward-step"
data-action="previousRound" data-tooltip aria-label="{{ localize "COMBAT.RoundPrev" }}"></button>
<button type="button" class="inline-control combat-control icon fa-solid fa-arrow-left" data-action="previousTurn"
@@ -13,6 +13,14 @@
<span>{{ localize "COMBAT.End" }}</span>
</button>
{{#if combat.round}}
<button type="button" class="combat-control combat-control-lg" data-action="rollMonsterProgression"
data-tooltip="{{ localize 'LETHALFANTASY.Combat.RollMonsters' }}">
<i class="fa-solid fa-dragon" inert></i>
<span>{{ localize "LETHALFANTASY.Combat.RollMonsters" }}</span>
</button>
{{/if}}
<!-- <button type="button" class="inline-control combat-control icon fa-solid fa-arrow-right" data-action="nextTurn"
data-tooltip aria-label="{{ localize "COMBAT.TurnNext" }}"></button> -->
<button type="button" class="inline-control combat-control icon fa-solid fa-forward-step" data-action="nextRound"
+1 -1
View File
@@ -70,7 +70,7 @@
{{!-- Combat Status --}}
<strong class="encounter-title">
{{#if combats.length}}
{{#if combat.round}}
{{#if (or combat.round (eq combat.turn 0))}}
{{ localize "COMBAT.Round" round=combat.round }}
{{else}}
{{ localize "COMBAT.NotStarted" }}
+36
View File
@@ -0,0 +1,36 @@
<div class="fvtt-lethal-fantasy damage-applied-message">
<div class="damage-header">
<div class="damage-icon">
<i class="fa-solid fa-heart-crack"></i>
</div>
<div class="damage-info">
<div class="target-name">{{targetName}}</div>
<div class="damage-subtitle">{{localize "LETHALFANTASY.DamageApplied.subtitle"}}</div>
</div>
</div>
<div class="damage-details">
<div class="damage-amount">
<span class="damage-label">{{localize "LETHALFANTASY.DamageApplied.damageDealt"}}</span>
<span class="damage-value">{{damage}}</span>
{{#if rawDamage}}
<span class="damage-raw">({{rawDamage}} raw)</span>
{{/if}}
</div>
{{#if drText}}
<div class="damage-reduction">
<i class="fa-solid fa-shield"></i>
<span>{{drText}}</span>
</div>
{{/if}}
<div class="damage-source">
<i class="fa-solid fa-crosshairs"></i>
<span>{{localize "LETHALFANTASY.DamageApplied.from"}}
{{#if attackerName}}<strong>{{attackerName}}</strong> with {{/if}}
<strong>{{weaponName}}</strong>
</span>
</div>
</div>
</div>
+43
View File
@@ -0,0 +1,43 @@
<div class="control-icon" data-action="lethal-gain-hp-hud">
<img
class="lethal-hp-gain-hud"
src="systems/fvtt-lethal-fantasy/assets/icons/health-increase.svg"
width="36"
height="36"
title="Restore HP"
/>
<div class="hp-gain-wrap hp-gain-wrap-col1 hp-gain-hud-disabled">
{{#for 1 11 1}}
<button
class="hud-gain-hp-button-select gain-hp-hud-click"
data-hp-value="{{this}}"
>
<span class="">+{{this}}</span>
</button>
{{/for}}
</div>
<div class="hp-gain-wrap hp-gain-wrap-col2 hp-gain-hud-disabled">
{{#for 11 21 1}}
<button
class="hud-gain-hp-button-select gain-hp-hud-click"
data-hp-value="{{this}}"
>
<span class="">+{{this}}</span>
</button>
{{/for}}
</div>
<div class="hp-gain-wrap hp-gain-wrap-col3 hp-gain-hud-disabled">
{{#for 21 31 1}}
<button
class="hud-gain-hp-button-select gain-hp-hud-click"
data-hp-value="{{this}}"
>
<span class="">+{{this}}</span>
</button>
{{/for}}
</div>
</div>
+16
View File
@@ -0,0 +1,16 @@
<div class="control-icon lethal-luck-grit-hud" data-action="lethal-luck-grit-hud">
<i class="fa-solid fa-dice" title="Adjust Luck/Grit" style="width:36px;height:36px;display:flex;align-items:center;justify-content:center;font-size:18px;cursor:pointer"></i>
<div class="luck-grit-wrap luck-grit-hud-disabled">
<div class="luck-grit-row">
<span class="luck-grit-label">Luck</span>
<button class="luck-grit-btn" data-resource="luck" data-amount="-1">1</button>
<button class="luck-grit-btn" data-resource="luck" data-amount="1">+1</button>
</div>
<div class="luck-grit-row">
<span class="luck-grit-label">Grit</span>
<button class="luck-grit-btn" data-resource="grit" data-amount="-1">1</button>
<button class="luck-grit-btn" data-resource="grit" data-amount="1">+1</button>
</div>
</div>
</div>
+4 -1
View File
@@ -7,7 +7,7 @@
{{formField systemFields.miracleType value=system.miracleType}}
{{formField systemFields.level value=system.level}}
<label>Components</label>
<label>{{localize "LETHALFANTASY.Label.components"}}</label>
<div class="shift-right">
{{formField systemFields.components.fields.verbal value=system.components.verbal}}
{{formField systemFields.components.fields.somatic value=system.components.somatic}}
@@ -27,6 +27,9 @@
{{formField systemFields.areaAffected value=system.areaAffected}}
{{formField systemFields.duration value=system.duration}}
{{formField systemFields.savingThrow value=system.savingThrow}}
{{formField systemFields.damageDice value=system.damageDice}}
{{formField systemFields.damageDiceOverpowered value=system.damageDiceOverpowered}}
{{formField systemFields.damageDiceOverpowered2 value=system.damageDiceOverpowered2}}
+5 -5
View File
@@ -6,23 +6,23 @@
<div class="biodata">
<div class="biodata-elem">
<span class="name">Alignment</span>
<span class="name">{{localize "LETHALFANTASY.Label.alignment"}}</span>
{{formInput systemFields.biodata.fields.alignment value=system.biodata.alignment }}
</div>
<div class="biodata-elem">
<span class="name">Height</span>
<span class="name">{{localize "LETHALFANTASY.Label.height"}}</span>
{{formInput systemFields.biodata.fields.height value=system.biodata.height }}
</div>
<div class="biodata-elem">
<span class="name">Weight</span>
<span class="name">{{localize "LETHALFANTASY.Label.weight"}}</span>
{{formInput systemFields.biodata.fields.weight value=system.biodata.weight }}
</div>
<div class="biodata-elem">
<span class="name">Length</span>
<span class="name">{{localize "LETHALFANTASY.Label.length"}}</span>
{{formInput systemFields.biodata.fields.length value=system.biodata.length }}
</div>
<div class="biodata-elem">
<span class="name">Vision</span>
<span class="name">{{localize "LETHALFANTASY.Label.vision"}}</span>
{{formInput systemFields.biodata.fields.vision value=system.biodata.vision }}
</div>
+148 -2
View File
@@ -18,11 +18,20 @@
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.attacks"}}</legend>
<div class="attacks">
<div class="attack-mode-selector">
<label>{{localize "LETHALFANTASY.Label.attackMode"}}</label>
<select name="system.attackMode">
<option value="melee" {{#if (eq system.attackMode "melee")}}selected{{/if}}>{{localize "LETHALFANTASY.Label.meleeModeLabel"}}</option>
<option value="ranged" {{#if (eq system.attackMode "ranged")}}selected{{/if}}>{{localize "LETHALFANTASY.Label.rangedModeLabel"}}</option>
</select>
</div>
{{#if (eq system.attackMode "melee")}}
<div class="attacks melee-attacks">
{{#each system.attacks as |item key|}}
<div class="attack" data-attack-key="{{key}}" >
<div class="">
<input type="checkbox" name="system.attacks.{{item.key}}.enabled" value="{{item.enabled}}" data-tooltip="Attack enabled/disabled" />
<input type="checkbox" name="system.attacks.{{item.key}}.enabled" {{checked item.enabled}} data-tooltip="Attack enabled/disabled" />
</div>
<div class="name">
<input type="text" name="system.attacks.{{item.key}}.name" value="{{item.name}}" data-tooltip="Attack name" />
@@ -42,6 +51,9 @@
<div class="numeric">
<input type="number" name="system.attacks.{{item.key}}.damageModifier" value="{{item.damageModifier}}" data-tooltip="Damage modifier"/>
</div>
<div class="">
<input type="checkbox" name="system.attacks.{{item.key}}.noExplode" {{checked item.noExplode}} data-tooltip="No exploding dice on damage" />
</div>
<div class="attack-icons">
@@ -64,5 +76,139 @@
</div>
{{/each}}
</div>
{{else}}
<div class="attacks ranged-attacks">
{{#each system.rangedAttacks as |item key|}}
<div class="attack" data-attack-key="{{key}}" >
<div class="">
<input type="checkbox" name="system.rangedAttacks.{{item.key}}.enabled" {{checked item.enabled}} data-tooltip="Attack enabled/disabled" />
</div>
<div class="name">
<input type="text" name="system.rangedAttacks.{{item.key}}.name" value="{{item.name}}" data-tooltip="Attack name" />
</div>
<div class="numeric">
<input type="number" name="system.rangedAttacks.{{item.key}}.attackScore" value="{{item.attackScore}}" data-tooltip="Progression number" />
</div>
<div class="numeric">
<input type="number" name="system.rangedAttacks.{{item.key}}.attackModifier" value="{{item.attackModifier}}" data-tooltip="Attack modifier" />
</div>
<div class="numeric">
<input type="number" name="system.rangedAttacks.{{item.key}}.defenseModifier" value="{{item.defenseModifier}}" data-tooltip="Defense modifier"/>
</div>
<div class="damage-dice">
<input type="text" name="system.rangedAttacks.{{item.key}}.damageDice" value="{{item.damageDice}}" data-tooltip="Damage dice"/>
</div>
<div class="numeric">
<input type="number" name="system.rangedAttacks.{{item.key}}.damageModifier" value="{{item.damageModifier}}" data-tooltip="Damage modifier"/>
</div>
<div class="">
<input type="checkbox" name="system.rangedAttacks.{{item.key}}.noExplode" {{checked item.noExplode}} data-tooltip="No exploding dice on damage" />
</div>
<div class="attack-icons">
<a class="rollable" data-roll-type="monster-attack" data-roll-key="{{item.key}}" data-tooltip="Roll Attack">
<i class="lf-roll-small fa-solid fa-bow-arrow" data-roll-type="monster-attack" data-roll-key="{{item.key}}"></i>
</a>
<a class="rollable" data-roll-type="monster-defense" data-roll-key="{{item.key}}" data-tooltip="Roll Defense">
<i class="fa-solid fa-shield-halved" data-roll-type="monster-defense" data-roll-key="{{item.key}}"></i>
</a>
<a class="rollable" data-roll-type="monster-damage" data-roll-key="{{item.key}}"
data-tooltip="Roll Damage">
<i class="fa-regular fa-face-head-bandage" data-roll-type="monster-damage"
data-roll-key="{{item.key}}"></i>
</a>
</div>
</div>
{{/each}}
<div class="ranged-weapon-range">
<label>{{localize "LETHALFANTASY.Label.range"}}</label>
<div class="range-fields">
<div class="range-field">
<label>{{localize "LETHALFANTASY.Label.pointBlank"}}</label>
<input type="number" name="system.rangedWeaponRange.pointBlank" value="{{system.rangedWeaponRange.pointBlank}}" />
</div>
<div class="range-field">
<label>{{localize "LETHALFANTASY.Label.short"}}</label>
<input type="number" name="system.rangedWeaponRange.short" value="{{system.rangedWeaponRange.short}}" />
</div>
<div class="range-field">
<label>{{localize "LETHALFANTASY.Label.medium"}}</label>
<input type="number" name="system.rangedWeaponRange.medium" value="{{system.rangedWeaponRange.medium}}" />
</div>
<div class="range-field">
<label>{{localize "LETHALFANTASY.Label.long"}}</label>
<input type="number" name="system.rangedWeaponRange.long" value="{{system.rangedWeaponRange.long}}" />
</div>
<div class="range-field">
<label>{{localize "LETHALFANTASY.Label.extreme"}}</label>
<input type="number" name="system.rangedWeaponRange.extreme" value="{{system.rangedWeaponRange.extreme}}" />
</div>
<div class="range-field">
<label>{{localize "LETHALFANTASY.Label.outOfSkill"}}</label>
<input type="number" name="system.rangedWeaponRange.outOfSkill" value="{{system.rangedWeaponRange.outOfSkill}}" />
</div>
</div>
</div>
</div>
{{/if}}
</fieldset>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.handToHandAttacks"}}</legend>
<div class="attacks">
{{#each system.combatHTH as |item key|}}
<div class="attack" data-attack-key="{{key}}" >
<div class="">
<input type="checkbox" name="system.combatHTH.{{item.key}}.enabled" {{checked item.enabled}} data-tooltip="HTH Attack enabled/disabled" />
</div>
<div class="name">
<input type="text" name="system.combatHTH.{{item.key}}.name" value="{{item.name}}" data-tooltip="HTH Attack name" />
</div>
<div class="numeric">
<input type="number" name="system.combatHTH.{{item.key}}.attackScore" value="{{item.attackScore}}" data-tooltip="Progression number" />
</div>
<div class="numeric">
<input type="number" name="system.combatHTH.{{item.key}}.attackModifier" value="{{item.attackModifier}}" data-tooltip="Attack modifier" />
</div>
<div class="numeric">
<input type="number" name="system.combatHTH.{{item.key}}.defenseModifier" value="{{item.defenseModifier}}" data-tooltip="Defense modifier"/>
</div>
<div class="damage-dice">
<input type="text" name="system.combatHTH.{{item.key}}.damageDice" value="{{item.damageDice}}" data-tooltip="Damage dice"/>
</div>
<div class="numeric">
<input type="number" name="system.combatHTH.{{item.key}}.damageModifier" value="{{item.damageModifier}}" data-tooltip="Damage modifier"/>
</div>
<div class="">
<input type="checkbox" name="system.combatHTH.{{item.key}}.noExplode" {{checked item.noExplode}} data-tooltip="No exploding dice on damage" />
</div>
<div class="attack-icons">
<a class="rollable" data-roll-type="monster-attack-hth" data-roll-key="{{item.key}}" data-tooltip="Roll HTH Attack">
<i class="lf-roll-small fa-solid fa-hand-fist" data-roll-type="monster-attack-hth" data-roll-key="{{item.key}}"></i>
</a>
<a class="rollable" data-roll-type="monster-defense-hth" data-roll-key="{{item.key}}" data-tooltip="Roll HTH Defense">
<i class="fa-solid fa-shield-halved" data-roll-type="monster-defense-hth" data-roll-key="{{item.key}}"></i>
</a>
<a class="rollable" data-roll-type="monster-damage-hth" data-roll-key="{{item.key}}"
data-tooltip="Roll HTH Damage">
<i class="fa-regular fa-face-head-bandage" data-roll-type="monster-damage-hth"
data-roll-key="{{item.key}}"></i>
</a>
</div>
</div>
{{/each}}
</div>
</fieldset>
</section>
+265 -90
View File
@@ -6,79 +6,177 @@
<div class="monster-pc monster-pc-{{ifThen isPlayMode 'play' 'edit'}}">
<div class="monster-left">
<div class="monster-left-image">
<img class="monster-img" src="{{actor.img}}" data-edit="img" data-action="editImage"
data-tooltip="{{actor.name}}" />
<img
class="monster-img"
src="{{actor.img}}"
data-edit="img"
data-action="editImage"
data-tooltip="{{actor.name}}"
/>
</div>
<fieldset class="monster-characteristics monster-characteristics-{{ifThen isPlayMode 'play' 'edit'}}">
<fieldset
class="monster-characteristics monster-characteristics-{{ifThen
isPlayMode
'play'
'edit'
}}"
>
<div class="flexrow monster-hp">
<span class="name">{{localize "LETHALFANTASY.Label.HP"}}</span>
{{formInput systemFields.hp.fields.value value=system.hp.value disabled=isPlayMode classes="monster-hp-value"}}
{{formInput
systemFields.hp.fields.value
value=system.hp.value
disabled=isPlayMode
classes="monster-hp-value"
}}
&nbsp;/&nbsp;
{{formInput systemFields.hp.fields.max value=system.hp.max disabled=isPlayMode classes="monster-hp-value"}}
{{formInput
systemFields.hp.fields.max
value=system.hp.max
disabled=isPlayMode
classes="monster-hp-value"
}}
</div>
<div class="flexrow monster-hp">
<span class="damage-resistance">{{localize "LETHALFANTASY.Label.damageResistance"}}</span>
{{formInput systemFields.hp.fields.damageResistance value=system.hp.damageResistance disabled=isPlayMode classes="monster-hp-value"}}
<span class="name">{{localize
"LETHALFANTASY.Label.damageResistance"
}}</span>
{{formInput
systemFields.hp.fields.damageResistance
value=system.hp.damageResistance
disabled=isPlayMode
classes="monster-hp-value"
}}
<span class="name">{{localize
"LETHALFANTASY.Label.shieldDamageReduction"
}}</span>
{{formInput
systemFields.combat.fields.shieldDamageReduction
value=system.combat.shieldDamageReduction
disabled=isPlayMode
classes="monster-hp-value"
}}
</div>
<div class="flexrow monster-hp">
<span class="name"><a
class="rollable"
data-roll-type="shield-defense"
data-roll-dice={{system.combat.shieldDefenseDice}}
><i class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.shieldDefenseDice"
}}</a></span>
{{formInput
systemFields.combat.fields.shieldDefenseDice
value=system.combat.shieldDefenseDice
disabled=isPlayMode
classes="monster-hp-value"
}}
</div>
</fieldset>
</div>
<div class="monster-right">
<div class="monster-name">
{{formInput fields.name value=source.name rootId=partId disabled=isPlayMode}}
<a class="control" data-action="toggleSheet" data-tooltip="LETHALFANTASY.ToggleSheet"
data-tooltip-direction="UP">
{{formInput
fields.name
value=source.name
rootId=partId
disabled=isPlayMode
}}
<a
class="control"
data-action="toggleSheet"
data-tooltip="LETHALFANTASY.ToggleSheet"
data-tooltip-direction="UP"
>
<i class="fa-solid fa-user-{{ifThen isPlayMode 'lock' 'pen'}}"></i>
</a>
</div>
<fieldset class="monster-characteristics monster-characteristics-{{ifThen isPlayMode 'play' 'edit'}}">
<fieldset
class="monster-characteristics monster-characteristics-{{ifThen
isPlayMode
'play'
'edit'
}}"
>
<legend>{{localize "LETHALFANTASY.Label.Saves"}}</legend>
<div class="monster-saves">
<div class="monster-save">
<span class="name"><a class="rollable" data-roll-type="save" data-roll-key="will"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>
<span class="name"><a
class="rollable"
data-roll-type="save"
data-roll-key="will"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.saves.will"}}
</a></span>
{{formField systemFields.saves.fields.will.fields.value value=system.saves.will.value disabled=isPlayMode }}
{{formField
systemFields.saves.fields.will.fields.value
value=system.saves.will.value
disabled=isPlayMode
}}
<span class="name">
<a class="rollable" data-roll-type="save" data-roll-key="dodge"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>
<a
class="rollable"
data-roll-type="save"
data-roll-key="dodge"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.saves.dodge"}}
</a>
</span>
{{formField systemFields.saves.fields.dodge.fields.value value=system.saves.dodge.value
disabled=isPlayMode}}
{{formField
systemFields.saves.fields.dodge.fields.value
value=system.saves.dodge.value
disabled=isPlayMode
}}
<span class="name">
<a class="rollable" data-roll-type="save" data-roll-key="toughness"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>
<a
class="rollable"
data-roll-type="save"
data-roll-key="toughness"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.saves.toughness"}}
</a>
</span>
{{formField systemFields.saves.fields.toughness.fields.value value=system.saves.toughness.value
disabled=isPlayMode}}
{{formField
systemFields.saves.fields.toughness.fields.value
value=system.saves.toughness.value
disabled=isPlayMode
}}
</div>
<div class="monster-save">
<span class="name">
<a class="rollable" data-roll-type="save" data-roll-key="contagion"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>
<a
class="rollable"
data-roll-type="save"
data-roll-key="contagion"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.saves.contagion"}}
</a>
</span>
{{formField systemFields.saves.fields.contagion.fields.value value=system.saves.contagion.value
disabled=isPlayMode}}
</span>
{{formField
systemFields.saves.fields.contagion.fields.value
value=system.saves.contagion.value
disabled=isPlayMode
}}
<span class="name">
<a class="rollable" data-roll-type="save" data-roll-key="poison"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.saves.poison"}}
<a
class="rollable"
data-roll-type="save"
data-roll-key="poison"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.saves.poison"}}
</a>
</span>
{{formField systemFields.saves.fields.poison.fields.value value=system.saves.poison.value
disabled=isPlayMode }}
</span>
{{formField
systemFields.saves.fields.poison.fields.value
value=system.saves.poison.value
disabled=isPlayMode
}}
<span class="name">
<!-- <span class="name">
<a class="rollable" data-roll-type="save" data-roll-key="paincourage" data-roll-dice="D20" data-tooltip="Pain/Courage check on wound of..."><i
class="lf-roll-small fa-solid fa-dice-d20"></i>
{{localize "LETHALFANTASY.Label.saves.paincourage"}}
@@ -88,65 +186,88 @@
<span data-tooltip="Pain save if wound exceeds">
{{formField systemFields.hp.fields.painDamage value=system.hp.painDamage disabled=isPlayMode tooltip="Pain Damage"}}
</span>
</span>-->
</div>
</div>
</fieldset>
<fieldset class="monster-characteristics monster-characteristics-{{ifThen isPlayMode 'play' 'edit'}}">
<fieldset
class="monster-characteristics monster-characteristics-{{ifThen
isPlayMode
'play'
'edit'
}}"
>
<legend>{{localize "LETHALFANTASY.Label.skills"}}</legend>
<div class="monster-skills">
<div class="monster-skill">
<span class="name"><a class="rollable" data-roll-type="monster-skill" data-roll-key="resistTorture"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.resistTorture"}}</a></span>
{{formField systemFields.resists.fields.resistTorture.fields.value value=system.resists.resistTorture.value
disabled=isPlayMode
<span class="name"><a
class="rollable"
data-roll-type="monster-skill"
data-roll-key="resistTorture"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.resistTorture"
}}</a></span>
{{formField
systemFields.resists.fields.resistTorture.fields.value
value=system.resists.resistTorture.value
disabled=isPlayMode
}}
<span class="name"><a class="rollable" data-roll-type="monster-skill" data-roll-key="resistPerformance"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.resistPerformance"}}</a></span>
{{formField systemFields.resists.fields.resistPerformance.fields.value value=system.resists.resistPerformance.value
disabled=isPlayMode
<span class="name"><a
class="rollable"
data-roll-type="monster-skill"
data-roll-key="resistPerformance"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.resistPerformance"
}}</a></span>
{{formField
systemFields.resists.fields.resistPerformance.fields.value
value=system.resists.resistPerformance.value
disabled=isPlayMode
}}
<span class="name"><a
class="rollable"
data-roll-type="monster-skill"
data-roll-key="resistIntimidation"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.resistIntimidation"
}}</a></span>
{{formField
systemFields.resists.fields.resistIntimidation.fields.value
value=system.resists.resistIntimidation.value
disabled=isPlayMode
}}
<span class="name"><a class="rollable" data-roll-type="monster-skill" data-roll-key="resistIntimidation"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.resistIntimidation"}}</a></span>
{{formField systemFields.resists.fields.resistIntimidation.fields.value value=system.resists.resistIntimidation.value
disabled=isPlayMode }}
<span class="name"><a class="rollable" data-roll-type="monster-skill" data-roll-key="perception"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.perception"}}</a></span>
{{formField systemFields.resists.fields.perception.fields.value value=system.resists.perception.value
disabled=isPlayMode }}
<span class="name"><a class="rollable" data-roll-type="monster-skill" data-roll-key="stealth"><i
class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.stealth"}}</a></span>
{{formField systemFields.resists.fields.stealth.fields.value value=system.resists.stealth.value
disabled=isPlayMode }}
</div>
</div>
</fieldset>
<div class="monster-skill">
<span class="name"><a
class="rollable"
data-roll-type="monster-skill"
data-roll-key="perception"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.perception"
}}</a></span>
{{formField
systemFields.resists.fields.perception.fields.value
value=system.resists.perception.value
disabled=isPlayMode
}}
<fieldset class="monster-characteristics monster-characteristics-{{ifThen isPlayMode 'play' 'edit'}}">
<legend>{{localize "LETHALFANTASY.Label.Movement"}}</legend>
<div class="monster-movements">
<div class="monster-movement">
<span class="name">{{localize "LETHALFANTASY.Label.movement.walk"}}</span>
{{formField systemFields.movement.fields.walk value=system.movement.walk disabled=isPlayMode}}
<span class="name">{{localize "LETHALFANTASY.Label.movement.jog"}}</span>
{{formField systemFields.movement.fields.jog value=system.challenges.movement.jog disabled=isPlayMode}}
<span class="name">{{localize "LETHALFANTASY.Label.movement.run"}}</span>
{{formField systemFields.movement.fields.run value=system.movement.run disabled=isPlayMode}}
<span class="name">{{localize "LETHALFANTASY.Label.movement.sprint"}}</span>
{{formField systemFields.movement.fields.sprint value=system.movement.sprint disabled=isPlayMode}}
<span class="name"><a
class="rollable"
data-roll-type="monster-skill"
data-roll-key="stealth"
><i class="lf-roll-small fa-solid fa-dice-d20"></i>{{localize
"LETHALFANTASY.Label.stealth"
}}</a></span>
{{formField
systemFields.resists.fields.stealth.fields.value
value=system.resists.stealth.value
disabled=isPlayMode
}}
</div>
</div>
</fieldset>
@@ -154,23 +275,77 @@
</div>
</fieldset>
<fieldset class="monster-characteristics monster-characteristics-{{ifThen isPlayMode 'play' 'edit'}}">
<fieldset
class="monster-characteristics monster-characteristics-{{ifThen
isPlayMode
'play'
'edit'
}}"
>
<legend>{{localize "LETHALFANTASY.Label.characteristics"}}</legend>
<div class="monster-characteristic">
<span>x{{localize "LETHALFANTASY.Label.int"}}</span>
{{formField systemFields.characteristics.fields.int.fields.value value=system.characteristics.int.value
disabled=isPlayMode data-char-id="int" }}
{{formField systemFields.characteristics.fields.int.fields.percent value=system.characteristics.int.percent
disabled=isPlayMode type="number" }}
<span>{{localize "LETHALFANTASY.Label.int"}}</span>
{{formField
systemFields.characteristics.fields.int.fields.value
value=system.characteristics.int.value
disabled=isPlayMode
data-char-id="int"
}}
</div>
<div class="monster-characteristic">
<span>{{localize "LETHALFANTASY.Label.dex"}}</span>
{{formField systemFields.characteristics.fields.dex.fields.value value=system.characteristics.dex.value
disabled=isPlayMode data-char-id="wis" }}
{{formField
systemFields.characteristics.fields.dex.fields.value
value=system.characteristics.dex.value
disabled=isPlayMode
data-char-id="wis"
}}
</div>
{{formField systemFields.characteristics.fields.dex.fields.percent value=system.characteristics.dex.percent
disabled=isPlayMode type="number" }}
<legend>{{localize "LETHALFANTASY.Label.Movement"}}</legend>
<div class="monster-movements">
<div class="monster-movement">
<div class="flexrow monster-hp">
<span class="name">{{localize
"LETHALFANTASY.Label.movement.walk"
}}</span>
{{formField
systemFields.movement.fields.walk
value=system.movement.walk
disabled=isPlayMode
}}
</div>
<div class="flexrow monster-hp">
<span class="name">{{localize
"LETHALFANTASY.Label.movement.jog"
}}</span>
{{formField
systemFields.movement.fields.jog
value=system.movement.jog
disabled=isPlayMode
}}
</div>
<div class="flexrow monster-hp">
<span class="name">{{localize
"LETHALFANTASY.Label.movement.run"
}}</span>
{{formField
systemFields.movement.fields.run
value=system.movement.run
disabled=isPlayMode
}}
</div>
<div class="flexrow monster-hp">
<span class="name">{{localize
"LETHALFANTASY.Label.movement.sprint"
}}</span>
{{formField
systemFields.movement.fields.sprint
value=system.movement.sprint
disabled=isPlayMode
}}
</div>
</div>
</div>
</fieldset>
+44
View File
@@ -0,0 +1,44 @@
<div class="fvtt-lethal-fantasy progression-message {{#if success}}progression-success{{else}}progression-failure{{/if}}">
<div class="progression-header">
<div class="progression-icon">
{{#if success}}
<i class="fa-solid fa-check-circle"></i>
{{else}}
<i class="fa-solid fa-hourglass-half"></i>
{{/if}}
</div>
<div class="progression-info">
<div class="actor-name">{{actorName}}</div>
<div class="progression-subtitle">
{{#if success}}
{{localize "LETHALFANTASY.ProgressionMessage.canAct"}}
{{else}}
{{localize "LETHALFANTASY.ProgressionMessage.cannotAct"}}
{{/if}}
</div>
</div>
</div>
<div class="progression-details">
{{#if weaponName}}
<div class="progression-weapon">
<i class="fa-solid fa-sword"></i>
<span><strong>{{weaponName}}</strong></span>
</div>
{{/if}}
{{#if rollResult}}
<div class="progression-roll">
<span class="roll-label">{{localize "LETHALFANTASY.ProgressionMessage.diceResult"}}</span>
<span class="roll-value">{{rollResult}}</span>
</div>
{{/if}}
{{#if progressionCount}}
<div class="progression-count">
<i class="fa-solid fa-dice-d20"></i>
<span>{{localize "LETHALFANTASY.ProgressionMessage.progressionCount"}} <strong>{{progressionCount}}</strong></span>
</div>
{{/if}}
</div>
</div>
+45
View File
@@ -0,0 +1,45 @@
<div class="lethalfantasy-range-defense-dialog">
<fieldSet class="">
<legend>{{localize "LETHALFANTASY.Label.rangeAttackDialog"}}</legend>
<div class="field-section">
<span class="field-name">Attacker Movement :</span>
<select name="movement" data-tooltip-direction="UP">
{{selectOptions attackerMovementChoices selected=movement}}
</select>
</div>
<div class="field-section">
<span class="field-name">Range :</span>
<select name="range" data-tooltip-direction="UP">
{{selectOptions rangeChoices selected=range}}
</select>
</div>
<div class="field-section">
<span class="field-name">Target Size :</span>
<select name="size" data-tooltip-direction="UP">
{{selectOptions sizeChoices selected=size}}
</select>
</div>
<div class="field-section">
<span class="field-name">Aim :</span>
<select name="attackerAim" data-tooltip-direction="UP">
{{selectOptions attackerAimChoices selected=attackerAim}}
</select>
</div>
</fieldSet>
<fieldSet>
<legend>{{localize "LETHALFANTASY.Roll.visibility"}}</legend>
<span class="fieldset-centered">
<select name="visibility">
{{selectOptions rollModes selected=visibility localize=true}}
</select>
</span>
</fieldSet>
</div>
+5 -3
View File
@@ -36,9 +36,11 @@
<fieldSet>
<legend>{{localize "LETHALFANTASY.Roll.visibility"}}</legend>
<select name="visibility">
{{selectOptions rollModes selected=visibility localize=true}}
</select>
<span class="fieldset-centered">
<select name="visibility">
{{selectOptions rollModes selected=visibility localize=true}}
</select>
</span>
</fieldSet>
</div>
+107 -69
View File
@@ -1,110 +1,149 @@
<div class="lethalfantasy-roll-dialog">
<fieldSet class="">
<legend>{{localize (concat "LETHALFANTASY.Label." rollType)}} - {{actorName}}</legend>
<legend>{{localize (concat "LETHALFANTASY.Label." rollType)}}
-
{{actorName}}</legend>
{{#if rollTarget.tokenId}}
<div class="dialog-save">
<a class="goto-token-button" data-action="gotoToken" data-token-id="{{rollTarget.tokenId}}">{{localize
"LETHALFANTASY.Label.gotoToken"}} </a>
</div>
<div class="dialog-save">
<a
class="goto-token-button"
data-action="gotoToken"
data-token-id="{{rollTarget.tokenId}}"
>{{localize "LETHALFANTASY.Label.gotoToken"}} </a>
</div>
{{/if}}
{{#if (match rollType "attack")}}
<div class="dialog-save">Attack roll ! - {{rollTarget.name}}</div>
<div class="dialog-save">Attack roll ! - {{rollTarget.name}}</div>
{{/if}}
{{#if (match rollType "defense")}}
<div class="dialog-save">Defense roll ! - {{rollTarget.name}}</div>
<div class="dialog-save">Defense roll ! - {{rollTarget.name}}</div>
{{/if}}
{{#if hasModifier}}
<div class="dialog-save">{{upperFirst rollName}} : {{baseFormula}} + {{baseValue}}</div>
<div class="dialog-save">{{upperFirst rollName}}
:
{{baseFormula}}
+
{{baseValue}}</div>
{{else}}
<div class="dialog-save">{{upperFirst rollName}} : {{baseFormula}}</div>
<div class="dialog-save">{{upperFirst rollName}} : {{baseFormula}}</div>
{{/if}}
{{#if rollTarget.weapon}}
<div class="dialog-save">{{localize "LETHALFANTASY.Label.baseModifier"}} : {{rollTarget.charModifier}}</div>
<div class="dialog-save">{{localize "LETHALFANTASY.Label.weapon"}} : {{rollTarget.weapon.name}}</div>
<div class="dialog-save">{{localize "LETHALFANTASY.Label.skill"}} : {{rollTarget.name}}</div>
<div class="dialog-save">{{localize "LETHALFANTASY.Label.skillBonus"}} : {{rollTarget.weaponSkillModifier}}</div>
<div class="dialog-save">{{localize "LETHALFANTASY.Label.baseModifier"}}
:
{{rollTarget.charModifier}}</div>
{{#if (match rollType "defense")}}
<div class="dialog-save">Armor defense modifier :
{{rollTarget.armorDefense}}</div>
{{/if}}
<div class="dialog-save">{{localize "LETHALFANTASY.Label.weapon"}}
:
{{rollTarget.weapon.name}}</div>
<div class="dialog-save">{{localize "LETHALFANTASY.Label.skill"}}
:
{{rollTarget.name}}</div>
<div class="dialog-save">{{localize "LETHALFANTASY.Label.skillBonus"}}
:
{{rollTarget.weaponSkillModifier}}</div>
{{/if}}
{{#if (match rollType "attack")}}
<div class="dialog-save">Add Granted Attack Dice
<input type="checkbox" data-action="selectGranted" name="granted">
</div>
{{#if (eq rollTarget.weapon.system.weaponType "melee")}}
{{else}}
<div class="dialog-save">Point Blank Range Attack
<input type="checkbox" data-action="selectPointBlank" name="pointBlankV">
</div>
<div class="dialog-save">Beyond Skill Range Attack
<input type="checkbox" data-action="selectBeyondSkill" name="beyondSkillV">
</div>
<div class="dialog-save">Let it Fly (Pure D20E)
<input type="checkbox" data-action="selectLetItFly" name="letItFlyV">
</div>
<div class="dialog-save">Aiming
<select name="attackerAim" data-tooltip-direction="UP">
{{selectOptions attackerAimChoices selected=attackerAim}}
</select>
</div>
{{/if}}
<div class="dialog-save">Add Granted Attack Dice
<input type="checkbox" data-action="selectGranted" name="granted" />
</div>
{{#if isRangedAttack}}
<div class="dialog-save">Point Blank Range Attack
<input
type="checkbox"
data-action="selectPointBlank"
name="pointBlankV"
/>
</div>
<div class="dialog-save">Beyond Skill Range Attack
<input
type="checkbox"
data-action="selectBeyondSkill"
name="beyondSkillV"
/>
</div>
<div class="dialog-save">Let it Fly (Pure D20E)
<input
type="checkbox"
data-action="selectLetItFly"
name="letItFlyV"
/>
</div>
<div class="dialog-save">Aiming
<select name="attackerAim" data-tooltip-direction="UP">
{{selectOptions attackerAimChoices selected=attackerAim}}
</select>
</div>
{{/if}}
{{/if}}
{{#if (match rollType "defense")}}
<div class="dialog-save">Add Granted Defense Dice
<input type="checkbox" data-action="selectGranted" name="granted">
</div>
<div class="dialog-save">Add Granted Defense Dice
<input type="checkbox" data-action="selectGranted" name="granted" />
</div>
<div class="dialog-warning">
<i class="fa-solid fa-triangle-exclamation"></i>
{{localize "LETHALFANTASY.Warning.defenseShieldOrder"}}
</div>
{{/if}}
{{#if (match rollType "damage")}}
<div class="dialog-save">Add Granted Damage Dice
<input type="checkbox" data-action="selectGranted" name="granted">
</div>
<div class="dialog-save">Add Granted Damage Dice
<input type="checkbox" data-action="selectGranted" name="granted" />
</div>
{{/if}}
{{#if rollTarget.staticModifier}}
<div class="dialog-save">Static modifier : +{{rollTarget.staticModifier}}</div>
<div class="dialog-save">Static modifier : +{{rollTarget.staticModifier}}</div>
{{/if}}
</fieldSet>
{{#if hasFavor}}
<fieldSet class="dialog-favor">
<legend>{{localize "LETHALFANTASY.Roll.favorDisfavor"}}</legend>
<select name="favor" class="favor-choice" data-tooltip-direction="UP">
{{selectOptions choiceFavor selected=favor}}
</select>
</fieldSet>
<fieldSet class="dialog-favor">
<legend>{{localize "LETHALFANTASY.Roll.favorDisfavor"}}</legend>
<select name="favor" class="favor-choice" data-tooltip-direction="UP">
{{selectOptions choiceFavor selected=favor}}
</select>
</fieldSet>
{{/if}}
{{#if hasModifier}}
<fieldSet class="dialog-modifier">
<legend>{{localize "LETHALFANTASY.Roll.modifierBonusMalus"}}</legend>
<select name="modifier" data-tooltip-direction="UP">
{{selectOptions choiceModifier selected=modifier}}
</select>
<fieldSet class="dialog-modifier">
<legend>{{localize "LETHALFANTASY.Roll.modifierBonusMalus"}}</legend>
<select name="modifier" data-tooltip-direction="UP">
{{selectOptions choiceModifier selected=modifier}}
</select>
{{#if (eq rollType "save")}}
{{#if rollTarget.magicUser}}
<div>
<span>Save against spell (+{{rollTarget.actorModifiers.saveModifier}}) ?</span>
<input type="checkbox" name="saveSpell" value="saveSpell">
</div>
{{/if}}
{{/if}}
</fieldSet>
{{#if (eq rollType "save")}}
<div>
<span>Save against spell (+{{rollTarget.actorModifiers.saveModifier}})
?</span>
<input
type="checkbox"
name="saveSpellCheck"
data-action="saveSpellCheck"
{{#if saveSpell}}checked{{/if}}
/>
</div>
{{/if}}
</fieldSet>
{{/if}}
{{#if hasChangeDice}}
<fieldSet class="dialog-modifier">
<legend>{{localize "LETHALFANTASY.Roll.changeDice"}}</legend>
<select name="changeDice" data-tooltip-direction="UP">
{{selectOptions choiceDice selected=changeDice}}
</select>
</fieldSet>
<fieldSet class="dialog-modifier">
<legend>{{localize "LETHALFANTASY.Roll.changeDice"}}</legend>
<select name="changeDice" data-tooltip-direction="UP">
{{selectOptions choiceDice selected=changeDice}}
</select>
</fieldSet>
{{/if}}
<fieldSet>
@@ -114,5 +153,4 @@
</select>
</fieldSet>
</div>
+31 -17
View File
@@ -1,6 +1,12 @@
<section>
<div class="header">
<img class="item-img" src="{{item.img}}" data-edit="img" data-action="editImage" data-tooltip="{{item.name}}" />
<img
class="item-img"
src="{{item.img}}"
data-edit="img"
data-action="editImage"
data-tooltip="{{item.name}}"
/>
{{formInput fields.name value=source.name}}
</div>
@@ -8,23 +14,32 @@
<div class="align-top">
{{formField systemFields.defense value=system.defense}}
{{formField systemFields.damageReduction value=system.damageReduction}}
<div class="shift-right">&nbsp;
</div>
{{formField systemFields.hascover value=system.hascover}}
{{#if system.hascover}}
<label>Cover vs ranged attacks</label>
<div class="shift-right">
<label>Standing </label>
<div class="flexrow">{{formField systemFields.standing.fields.min value=system.standing.min}} &nbsp;...&nbsp;30
<label>{{localize "LETHALFANTASY.Label.coverRanged"}}</label>
<div class="shift-right">
<label>{{localize "LETHALFANTASY.Label.standing"}}</label>
<div class="flexrow">{{formField
systemFields.standing.fields.min
value=system.standing.min
}}
&nbsp;...&nbsp;30
</div>
</div>
<div class="shift-right">
<label>{{localize "LETHALFANTASY.Label.crouching"}}</label>
<div class="flexrow">{{formField
systemFields.crouching.fields.min
value=system.crouching.min
}}
&nbsp;...&nbsp;30</div>
</div>
</div>
<div class="shift-right">
<label>Crouching</label>
<div class="flexrow">{{formField systemFields.crouching.fields.min value=system.crouching.min}}
&nbsp;...&nbsp;30</div>
</div>
{{/if}}
</div>
@@ -53,15 +68,14 @@
</div>
</div>
<fieldset>
<legend>{{localize "LETHALFANTASY.Label.description"}}</legend>
{{formInput
systemFields.description
enriched=enrichedDescription
value=system.description
name="system.description"
toggled="false"
systemFields.description
enriched=enrichedDescription
value=system.description
name="system.description"
toggled="false"
}}
</fieldset>
</section>

Some files were not shown because too many files have changed in this diff Show More