71 Commits

Author SHA1 Message Date
e0400793ff Fix CI/CD
All checks were successful
Release Creation / build (release) Successful in 52s
2025-06-12 23:12:49 +02:00
1210358c33 Fix CI/CD 2025-06-12 23:12:09 +02:00
c08a8c38e9 Enhance progression rolls, damage rolls and other fixes
Some checks failed
Release Creation / build (release) Failing after 56s
2025-06-12 20:53:54 +02:00
17be9df64b Add FR translation 2025-06-11 11:35:39 +02:00
ecb7fc2757 Update for pure v13 release
Some checks failed
Release Creation / build (release) Failing after 48s
2025-05-20 22:25:35 +02:00
1fde7e4d2c Fix CSS for sheets
Some checks failed
Release Creation / build (release) Failing after 47s
2025-05-14 23:53:49 +02:00
b5a0405403 Foundry v13 migration 2025-05-09 22:44:55 +02:00
36279aa6eb Foundry v13 migration
Some checks failed
Release Creation / build (release) Failing after 53s
2025-05-09 22:41:08 +02:00
d5eba47ab6 Enhance appeareance 2025-05-09 22:17:00 +02:00
b21c5ac9bb Add rituals !
Some checks failed
Release Creation / build (release) Failing after 44s
2025-04-25 00:26:28 +02:00
a43bebc63b Add rituals !
Some checks failed
Release Creation / build (release) Failing after 21s
2025-04-24 23:54:52 +02:00
2b418695c4 Actualiser README.md 2025-03-31 16:34:00 +02:00
5dbb168891 Improve release management 2025-03-31 14:31:23 +02:00
009024296c Improve release management
Some checks failed
Release Creation / build (release) Failing after 58s
2025-03-31 14:27:55 +02:00
968c7da5c5 Improve release management 2025-03-31 14:26:08 +02:00
67ff06f953 Add missing eras 2025-03-31 14:19:19 +02:00
24c5c685d0 Sync version 2025-03-30 20:39:03 +02:00
d62b5472fe Sync version 2025-03-30 20:36:41 +02:00
a1ef74a3ad Sync compendiums 2025-03-30 20:35:44 +02:00
ff89b62133 Fix LethalFantasy stuff 2025-02-13 23:23:32 +01:00
b3eb908f05 Add rituals and tomes and creatures
All checks were successful
Release Creation / build (release) Successful in 48s
2025-02-07 18:06:08 +01:00
2ac0f53c4f Add rituals and tomes 2025-02-07 08:34:57 +01:00
270471f137 Ready for next release
All checks were successful
Release Creation / build (release) Successful in 48s
2025-02-06 22:37:57 +01:00
da9d0e41a5 Fix #45 - Auto addk unarmed fight on new actor 2025-02-06 22:35:07 +01:00
42f25aa186 Fix #46 - Fix gear icon 2025-02-06 22:23:26 +01:00
5f9057db37 Fix #44 - Auto use damage bonus for melee/unarmed 2025-02-06 22:21:56 +01:00
82731c2d40 Fix #43 - Auto allocate current era 2025-02-06 21:54:59 +01:00
a75a3bf157 Fix #39 - Display cured/uncured for mental disorder 2025-02-06 21:35:22 +01:00
e6e6de35df Fix #39 - Display cured/uncured for mental disorder 2025-02-06 21:10:15 +01:00
7341580a7b Fix #38 - Auto set bond values 2025-02-06 20:55:14 +01:00
75a81e589b Sync with wfrp 4, v8.4.0
All checks were successful
Release Creation / build (release) Successful in 50s
2025-02-01 21:17:58 +01:00
74f237c30f Sync with wfrp 4, v8.4.0 2025-02-01 21:17:42 +01:00
440e5c0b66 Sync with wfrp 4, v8.4.0 2025-02-01 21:17:27 +01:00
554220a812 Sync with wfrp 4, v8.4.0
All checks were successful
Release Creation / build (release) Successful in 51s
2025-02-01 21:10:28 +01:00
e7ea8138c9 Enhance fonts/sizing/colors 2025-01-28 14:22:11 +01:00
6a18f7fa29 Additionnal cleanup 2025-01-28 07:20:29 +01:00
7070d4c1da Sync bol for module releas 2025-01-25 22:28:16 +01:00
3b79a19ca5 Sync bol for module releas
All checks were successful
Release Creation / build (release) Successful in 53s
2025-01-25 20:19:37 +01:00
f882a04c0b Auto release script
All checks were successful
Release Creation / build (release) Successful in 48s
2025-01-25 18:55:34 +01:00
eda24442e8 Auto release script 2025-01-25 18:49:18 +01:00
0ec2d53415 Auto release script
All checks were successful
Release Creation / build (release) Successful in 45s
2025-01-25 18:34:18 +01:00
401a94da40 Auto release script
Some checks failed
Release Creation / build (release) Failing after 22s
2025-01-25 18:31:03 +01:00
7758085bf8 Fix various minot stuff + add resources roll 2025-01-25 18:24:20 +01:00
70c4fd5a74 Fix #30 - Disable dual skills dop 2025-01-10 00:11:25 +01:00
03a54d86e6 Fix #28 : switch to manual BP management 2025-01-09 16:18:08 +01:00
3adb34d721 Reduce default icon size, Fix #27 2025-01-09 15:54:41 +01:00
034a60bf19 Reduce default icon size, fix #32 2025-01-09 15:48:08 +01:00
522bb08d35 Allow packs 2025-01-09 15:36:41 +01:00
c144488d1d Move packs to avoid LFS 2025-01-09 15:35:58 +01:00
704dd83b1b Move packs to avoid LFS 2025-01-09 15:35:31 +01:00
3aab98b4b4 Allow packs 2025-01-09 15:18:41 +01:00
2c05594a7e Allow packs 2025-01-09 15:18:20 +01:00
ce1844a070 Add vehicle type 2025-01-08 17:26:57 +01:00
fa7d3ecfca Add resources management 2025-01-08 12:46:36 +01:00
adb4923d00 Enhance roll result colors 2025-01-07 22:22:15 +01:00
44842ff655 Enhance roll result colors 2025-01-07 21:51:38 +01:00
4671f6f9fe Enhance roll result colors 2025-01-07 21:51:13 +01:00
50b7bafb43 Enhance roll result colors 2025-01-07 21:49:50 +01:00
2c3a92994a Fix #24 : Add SAN rolls 2025-01-07 21:38:50 +01:00
dfe4aa0c56 Add packs ! 2025-01-07 20:00:28 +01:00
efc23f466b Fix #20 : Track cured disorder 2025-01-07 19:51:49 +01:00
1b1c2b0cfd Fix #5 : Edit features + display as tooltip when hovering stats 2025-01-07 18:22:17 +01:00
e0f02ecacb Fix #9 : Visual rollable field 2025-01-07 17:57:43 +01:00
4e39c5d0dc Fix #15: Notes/Description for protagonist sheet 2025-01-07 17:44:30 +01:00
9f13961eaf Fix #17 : Motivation description 2025-01-07 17:39:12 +01:00
3d6cd35fa4 Fix #18 : Move motivations to status 2025-01-07 17:38:04 +01:00
33c11e2c05 Fix #19 : Mental disorder description 2025-01-07 17:36:31 +01:00
b600148247 Fix #22 : Bond display 2025-01-07 17:26:08 +01:00
c0d240c5d0 Fix #25 : Change color of WP warning displays 2025-01-07 17:23:25 +01:00
d6a1b2c62a Fix #1 : Drag data on stats 2025-01-07 17:17:22 +01:00
82139eb634 Fix BP computation #3 2025-01-07 17:15:45 +01:00
129 changed files with 7992 additions and 1342 deletions

View File

@ -0,0 +1,63 @@
name: Release Creation
on:
release:
types: [published]
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
# get part of the tag after the `v`
- name: Extract tag version number
id: get_version
uses: battila7/get-version-action@v2
# 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/fvtt-cthulhu-eternal/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-cthulhu-eternal.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-cthulhu-eternal.zip system.json README.md LICENSE assets/ css/ fonts/ lang/ module/ packs-system/ templates/ cthulhu-eternal.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-cthulhu-eternal.zip
system.json
api_key: '${{secrets.ALLOW_PUSH_RELEASE}}'
- name: Publish to Foundry server
uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
with:
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
id: 'fvtt-cthulhu-eternal'
version: ${{github.event.release.tag_name}}
manifest: 'https://www.uberwald.me/gitea/public/fvtt-cthulhu-eternal/releases/download/latest/system.json'
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-cthulhu-eternal.zip'
compatibility-minimum: '13'
compatibility-verified: '13'

3
.gitignore vendored
View File

@ -6,6 +6,3 @@ styles/*.css
# Node Modules
node_modules/
# Foundry VTT
packs/*

View File

@ -1,6 +1,15 @@
<h2><em>Cthulhu Eternal RPG</em> for Foundry Virtual TableTop</h2>
# Cthulhu Eternal RPG for FoundryVTT
<div align="center">
Cthulhu Eternal : https://cthulhueternal.com/
</div>
The system contains :
- Protagonist, Creature and Vehicle sheet
- Arcane, Archetype, Armor, Bond, Gear, Injury, Mental Disorder, Motivation, Ritual, Skill, Tome, Weapon items
- Support for all available eras
- Pre-filled compendium for each era
- Specific look&fell for each era
- And much more !
Discord Contact : LeRatierBretonnien (at Official Foundry Discord of French Foundry Discord)

Binary file not shown.

BIN
assets/fonts/FeFCrm2.ttf Normal file

Binary file not shown.

BIN
assets/fonts/FeGPrm2.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

258
assets/fonts/Skranji.g2n Normal file
View File

@ -0,0 +1,258 @@
GLYPHID 0 PSNAME .notdef
GLYPHID 146 PSNAME Eth UNICODE 00D0
GLYPHID 178 PSNAME eth UNICODE 00F0
GLYPHID 206 PSNAME Lslash UNICODE 0141
GLYPHID 207 PSNAME lslash UNICODE 0142
GLYPHID 217 PSNAME Scaron UNICODE 0160
GLYPHID 218 PSNAME scaron UNICODE 0161
GLYPHID 159 PSNAME Yacute UNICODE 00DD
GLYPHID 191 PSNAME yacute UNICODE 00FD
GLYPHID 160 PSNAME Thorn UNICODE 00DE
GLYPHID 192 PSNAME thorn UNICODE 00FE
GLYPHID 220 PSNAME Zcaron UNICODE 017D
GLYPHID 221 PSNAME zcaron UNICODE 017E
GLYPHID 127 PSNAME onehalf UNICODE 00BD
GLYPHID 126 PSNAME onequarter UNICODE 00BC
GLYPHID 123 PSNAME onesuperior UNICODE 00B9
GLYPHID 128 PSNAME threequarters UNICODE 00BE
GLYPHID 117 PSNAME threesuperior UNICODE 00B3
GLYPHID 116 PSNAME twosuperior UNICODE 00B2
GLYPHID 104 PSNAME brokenbar UNICODE 00A6
GLYPHID 251 PSNAME minus UNICODE 2212
GLYPHID 153 PSNAME multiply UNICODE 00D7
GLYPHID 3 PSNAME space UNICODE 0020
GLYPHID 4 PSNAME exclam UNICODE 0021
GLYPHID 5 PSNAME quotedbl UNICODE 0022
GLYPHID 6 PSNAME numbersign UNICODE 0023
GLYPHID 7 PSNAME dollar UNICODE 0024
GLYPHID 8 PSNAME percent UNICODE 0025
GLYPHID 9 PSNAME ampersand UNICODE 0026
GLYPHID 10 PSNAME quotesingle UNICODE 0027
GLYPHID 11 PSNAME parenleft UNICODE 0028
GLYPHID 12 PSNAME parenright UNICODE 0029
GLYPHID 13 PSNAME asterisk UNICODE 002A
GLYPHID 14 PSNAME plus UNICODE 002B
GLYPHID 15 PSNAME comma UNICODE 002C
GLYPHID 16 PSNAME hyphen UNICODE 002D
GLYPHID 17 PSNAME period UNICODE 002E
GLYPHID 18 PSNAME slash UNICODE 002F
GLYPHID 19 PSNAME zero UNICODE 0030
GLYPHID 20 PSNAME one UNICODE 0031
GLYPHID 21 PSNAME two UNICODE 0032
GLYPHID 22 PSNAME three UNICODE 0033
GLYPHID 23 PSNAME four UNICODE 0034
GLYPHID 24 PSNAME five UNICODE 0035
GLYPHID 25 PSNAME six UNICODE 0036
GLYPHID 26 PSNAME seven UNICODE 0037
GLYPHID 27 PSNAME eight UNICODE 0038
GLYPHID 28 PSNAME nine UNICODE 0039
GLYPHID 29 PSNAME colon UNICODE 003A
GLYPHID 30 PSNAME semicolon UNICODE 003B
GLYPHID 31 PSNAME less UNICODE 003C
GLYPHID 32 PSNAME equal UNICODE 003D
GLYPHID 33 PSNAME greater UNICODE 003E
GLYPHID 34 PSNAME question UNICODE 003F
GLYPHID 35 PSNAME at UNICODE 0040
GLYPHID 36 PSNAME A UNICODE 0041
GLYPHID 37 PSNAME B UNICODE 0042
GLYPHID 38 PSNAME C UNICODE 0043
GLYPHID 39 PSNAME D UNICODE 0044
GLYPHID 40 PSNAME E UNICODE 0045
GLYPHID 41 PSNAME F UNICODE 0046
GLYPHID 42 PSNAME G UNICODE 0047
GLYPHID 43 PSNAME H UNICODE 0048
GLYPHID 44 PSNAME I UNICODE 0049
GLYPHID 45 PSNAME J UNICODE 004A
GLYPHID 46 PSNAME K UNICODE 004B
GLYPHID 47 PSNAME L UNICODE 004C
GLYPHID 48 PSNAME M UNICODE 004D
GLYPHID 49 PSNAME N UNICODE 004E
GLYPHID 50 PSNAME O UNICODE 004F
GLYPHID 51 PSNAME P UNICODE 0050
GLYPHID 52 PSNAME Q UNICODE 0051
GLYPHID 53 PSNAME R UNICODE 0052
GLYPHID 54 PSNAME S UNICODE 0053
GLYPHID 55 PSNAME T UNICODE 0054
GLYPHID 56 PSNAME U UNICODE 0055
GLYPHID 57 PSNAME V UNICODE 0056
GLYPHID 58 PSNAME W UNICODE 0057
GLYPHID 59 PSNAME X UNICODE 0058
GLYPHID 60 PSNAME Y UNICODE 0059
GLYPHID 61 PSNAME Z UNICODE 005A
GLYPHID 62 PSNAME bracketleft UNICODE 005B
GLYPHID 63 PSNAME backslash UNICODE 005C
GLYPHID 64 PSNAME bracketright UNICODE 005D
GLYPHID 65 PSNAME asciicircum UNICODE 005E
GLYPHID 66 PSNAME underscore UNICODE 005F
GLYPHID 67 PSNAME grave UNICODE 0060
GLYPHID 68 PSNAME a UNICODE 0061
GLYPHID 69 PSNAME b UNICODE 0062
GLYPHID 70 PSNAME c UNICODE 0063
GLYPHID 71 PSNAME d UNICODE 0064
GLYPHID 72 PSNAME e UNICODE 0065
GLYPHID 73 PSNAME f UNICODE 0066
GLYPHID 74 PSNAME g UNICODE 0067
GLYPHID 75 PSNAME h UNICODE 0068
GLYPHID 76 PSNAME i UNICODE 0069
GLYPHID 77 PSNAME j UNICODE 006A
GLYPHID 78 PSNAME k UNICODE 006B
GLYPHID 79 PSNAME l UNICODE 006C
GLYPHID 80 PSNAME m UNICODE 006D
GLYPHID 81 PSNAME n UNICODE 006E
GLYPHID 82 PSNAME o UNICODE 006F
GLYPHID 83 PSNAME p UNICODE 0070
GLYPHID 84 PSNAME q UNICODE 0071
GLYPHID 85 PSNAME r UNICODE 0072
GLYPHID 86 PSNAME s UNICODE 0073
GLYPHID 87 PSNAME t UNICODE 0074
GLYPHID 88 PSNAME u UNICODE 0075
GLYPHID 89 PSNAME v UNICODE 0076
GLYPHID 90 PSNAME w UNICODE 0077
GLYPHID 91 PSNAME x UNICODE 0078
GLYPHID 92 PSNAME y UNICODE 0079
GLYPHID 93 PSNAME z UNICODE 007A
GLYPHID 94 PSNAME braceleft UNICODE 007B
GLYPHID 95 PSNAME bar UNICODE 007C
GLYPHID 96 PSNAME braceright UNICODE 007D
GLYPHID 97 PSNAME asciitilde UNICODE 007E
GLYPHID 134 PSNAME Adieresis UNICODE 00C4
GLYPHID 135 PSNAME Aring UNICODE 00C5
GLYPHID 137 PSNAME Ccedilla UNICODE 00C7
GLYPHID 139 PSNAME Eacute UNICODE 00C9
GLYPHID 147 PSNAME Ntilde UNICODE 00D1
GLYPHID 152 PSNAME Odieresis UNICODE 00D6
GLYPHID 158 PSNAME Udieresis UNICODE 00DC
GLYPHID 163 PSNAME aacute UNICODE 00E1
GLYPHID 162 PSNAME agrave UNICODE 00E0
GLYPHID 164 PSNAME acircumflex UNICODE 00E2
GLYPHID 166 PSNAME adieresis UNICODE 00E4
GLYPHID 165 PSNAME atilde UNICODE 00E3
GLYPHID 167 PSNAME aring UNICODE 00E5
GLYPHID 169 PSNAME ccedilla UNICODE 00E7
GLYPHID 171 PSNAME eacute UNICODE 00E9
GLYPHID 170 PSNAME egrave UNICODE 00E8
GLYPHID 172 PSNAME ecircumflex UNICODE 00EA
GLYPHID 173 PSNAME edieresis UNICODE 00EB
GLYPHID 175 PSNAME iacute UNICODE 00ED
GLYPHID 174 PSNAME igrave UNICODE 00EC
GLYPHID 176 PSNAME icircumflex UNICODE 00EE
GLYPHID 177 PSNAME idieresis UNICODE 00EF
GLYPHID 179 PSNAME ntilde UNICODE 00F1
GLYPHID 181 PSNAME oacute UNICODE 00F3
GLYPHID 180 PSNAME ograve UNICODE 00F2
GLYPHID 182 PSNAME ocircumflex UNICODE 00F4
GLYPHID 184 PSNAME odieresis UNICODE 00F6
GLYPHID 183 PSNAME otilde UNICODE 00F5
GLYPHID 188 PSNAME uacute UNICODE 00FA
GLYPHID 187 PSNAME ugrave UNICODE 00F9
GLYPHID 189 PSNAME ucircumflex UNICODE 00FB
GLYPHID 190 PSNAME udieresis UNICODE 00FC
GLYPHID 240 PSNAME dagger UNICODE 2020
GLYPHID 114 PSNAME degree UNICODE 00B0
GLYPHID 100 PSNAME cent UNICODE 00A2
GLYPHID 101 PSNAME sterling UNICODE 00A3
GLYPHID 105 PSNAME section UNICODE 00A7
GLYPHID 242 PSNAME bullet UNICODE 2022
GLYPHID 120 PSNAME paragraph UNICODE 00B6
GLYPHID 161 PSNAME germandbls UNICODE 00DF
GLYPHID 112 PSNAME registered UNICODE 00AE
GLYPHID 107 PSNAME copyright UNICODE 00A9
GLYPHID 249 PSNAME trademark UNICODE 2122
GLYPHID 118 PSNAME acute UNICODE 00B4
GLYPHID 106 PSNAME dieresis UNICODE 00A8
GLYPHID 253 PSNAME notequal UNICODE 2260
GLYPHID 136 PSNAME AE UNICODE 00C6
GLYPHID 154 PSNAME Oslash UNICODE 00D8
GLYPHID 115 PSNAME plusminus UNICODE 00B1
GLYPHID 254 PSNAME lessequal UNICODE 2264
GLYPHID 255 PSNAME greaterequal UNICODE 2265
GLYPHID 103 PSNAME yen UNICODE 00A5
GLYPHID 119 PSNAME mu UNICODE 00B5
GLYPHID 250 PSNAME partialdiff UNICODE 2202
GLYPHID 108 PSNAME ordfeminine UNICODE 00AA
GLYPHID 124 PSNAME ordmasculine UNICODE 00BA
GLYPHID 168 PSNAME ae UNICODE 00E6
GLYPHID 186 PSNAME oslash UNICODE 00F8
GLYPHID 129 PSNAME questiondown UNICODE 00BF
GLYPHID 99 PSNAME exclamdown UNICODE 00A1
GLYPHID 110 PSNAME logicalnot UNICODE 00AC
GLYPHID 222 PSNAME florin UNICODE 0192
GLYPHID 252 PSNAME approxequal UNICODE 2248
GLYPHID 109 PSNAME guillemotleft UNICODE 00AB
GLYPHID 125 PSNAME guillemotright UNICODE 00BB
GLYPHID 243 PSNAME ellipsis UNICODE 2026
GLYPHID 98 PSNAME uni00A0 UNICODE 00A0
GLYPHID 130 PSNAME Agrave UNICODE 00C0
GLYPHID 133 PSNAME Atilde UNICODE 00C3
GLYPHID 151 PSNAME Otilde UNICODE 00D5
GLYPHID 210 PSNAME OE UNICODE 0152
GLYPHID 211 PSNAME oe UNICODE 0153
GLYPHID 232 PSNAME endash UNICODE 2013
GLYPHID 233 PSNAME emdash UNICODE 2014
GLYPHID 237 PSNAME quotedblleft UNICODE 201C
GLYPHID 238 PSNAME quotedblright UNICODE 201D
GLYPHID 234 PSNAME quoteleft UNICODE 2018
GLYPHID 235 PSNAME quoteright UNICODE 2019
GLYPHID 185 PSNAME divide UNICODE 00F7
GLYPHID 256 PSNAME lozenge UNICODE 25CA
GLYPHID 193 PSNAME ydieresis UNICODE 00FF
GLYPHID 219 PSNAME Ydieresis UNICODE 0178
GLYPHID 247 PSNAME fraction UNICODE 2044
GLYPHID 248 PSNAME Euro UNICODE 20AC
GLYPHID 245 PSNAME guilsinglleft UNICODE 2039
GLYPHID 246 PSNAME guilsinglright UNICODE 203A
GLYPHID 258 PSNAME fi UNICODE FB01
GLYPHID 259 PSNAME fl UNICODE FB02
GLYPHID 241 PSNAME daggerdbl UNICODE 2021
GLYPHID 121 PSNAME periodcentered UNICODE 00B7
GLYPHID 236 PSNAME quotesinglbase UNICODE 201A
GLYPHID 239 PSNAME quotedblbase UNICODE 201E
GLYPHID 244 PSNAME perthousand UNICODE 2030
GLYPHID 132 PSNAME Acircumflex UNICODE 00C2
GLYPHID 140 PSNAME Ecircumflex UNICODE 00CA
GLYPHID 131 PSNAME Aacute UNICODE 00C1
GLYPHID 141 PSNAME Edieresis UNICODE 00CB
GLYPHID 138 PSNAME Egrave UNICODE 00C8
GLYPHID 143 PSNAME Iacute UNICODE 00CD
GLYPHID 144 PSNAME Icircumflex UNICODE 00CE
GLYPHID 145 PSNAME Idieresis UNICODE 00CF
GLYPHID 142 PSNAME Igrave UNICODE 00CC
GLYPHID 149 PSNAME Oacute UNICODE 00D3
GLYPHID 150 PSNAME Ocircumflex UNICODE 00D4
GLYPHID 148 PSNAME Ograve UNICODE 00D2
GLYPHID 156 PSNAME Uacute UNICODE 00DA
GLYPHID 157 PSNAME Ucircumflex UNICODE 00DB
GLYPHID 155 PSNAME Ugrave UNICODE 00D9
GLYPHID 197 PSNAME dotlessi UNICODE 0131
GLYPHID 224 PSNAME circumflex UNICODE 02C6
GLYPHID 230 PSNAME tilde UNICODE 02DC
GLYPHID 113 PSNAME macron UNICODE 00AF
GLYPHID 226 PSNAME breve UNICODE 02D8
GLYPHID 227 PSNAME dotaccent UNICODE 02D9
GLYPHID 228 PSNAME ring UNICODE 02DA
GLYPHID 122 PSNAME cedilla UNICODE 00B8
GLYPHID 231 PSNAME hungarumlaut UNICODE 02DD
GLYPHID 229 PSNAME ogonek UNICODE 02DB
GLYPHID 225 PSNAME caron UNICODE 02C7
GLYPHID 102 PSNAME currency UNICODE 00A4
GLYPHID 194 PSNAME hbar UNICODE 0127
GLYPHID 223 PSNAME dotlessj UNICODE 0237
GLYPHID 195 PSNAME Itilde UNICODE 0128
GLYPHID 196 PSNAME itilde UNICODE 0129
GLYPHID 198 PSNAME IJ UNICODE 0132
GLYPHID 199 PSNAME ij UNICODE 0133
GLYPHID 200 PSNAME Jcircumflex UNICODE 0134
GLYPHID 201 PSNAME jcircumflex UNICODE 0135
GLYPHID 204 PSNAME Ldot UNICODE 013F
GLYPHID 203 PSNAME kgreenlandic UNICODE 0138
GLYPHID 205 PSNAME ldotaccent UNICODE 0140
GLYPHID 202 PSNAME kcommaaccent UNICODE 0137
GLYPHID 208 PSNAME Nacute UNICODE 0143
GLYPHID 209 PSNAME nacute UNICODE 0144
GLYPHID 212 PSNAME Racute UNICODE 0154
GLYPHID 213 PSNAME Rcommaaccent UNICODE 0156
GLYPHID 214 PSNAME rcommaaccent UNICODE 0157
GLYPHID 215 PSNAME Rcaron UNICODE 0158
GLYPHID 216 PSNAME rcaron UNICODE 0159
GLYPHID 257 PSNAME commaaccent UNICODE F6C3
GLYPHID 111 PSNAME sfthyphen UNICODE 00AD

BIN
assets/fonts/Skranji.ttf Normal file

Binary file not shown.

BIN
assets/fonts/Skranji.woff Normal file

Binary file not shown.

BIN
assets/fonts/Skranji.woff2 Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><filter id="shadow-1" height="300%" width="300%" x="-100%" y="-100%"><feFlood flood-color="rgba(255, 255, 255, 1)" result="flood"></feFlood><feComposite in="flood" in2="SourceGraphic" operator="atop" result="composite"></feComposite><feGaussianBlur in="composite" stdDeviation="15" result="blur"></feGaussianBlur><feOffset dx="0" dy="0" result="offset"></feOffset><feComposite in="SourceGraphic" in2="offset" operator="over"></feComposite></filter></defs><g class="" style="" transform="translate(0,0)"><path d="M227.227 21.777c-1.845 0-3.704.05-5.567.157-15.314.875-30.76 5.305-39.494 10.863l-.008 73.15c2.884-.094 5.777-.147 8.676-.142 23.382.036 47.104 3.286 68.47 9.513l.01-87.507c-7.034-3.518-19.178-6.03-32.087-6.033zm80.74 9.16c-11.925.15-23.077 2.364-29.967 5.596l-.008 77.602v7.658c38.486 15.67 64.814 42.48 58.735 78.764l-.96 5.73-5.562 1.674c-17.45 5.253-34.872 9.703-52.225 13.335V246.53c25.562-.704 51.327-2.687 77.145-6.098l.02-197.928c-8.284-5.563-23.508-10.243-38.842-11.328-2.792-.198-5.584-.273-8.336-.238zM143.223 46.294c-1.176-.015-2.374-.01-3.588.02-4.175.1-8.533.468-12.903 1.152-15.67 2.454-31.477 8.565-40.406 15.402l-.01 72.955c18.808-15.81 46.704-25.143 77.15-28.54l.007-57.966c-4.82-1.752-12.018-2.916-20.25-3.023zm258.394 3.46c-10.804.117-20.722 1.93-27.043 4.655l-.02 183.182c25.074-4.02 50.16-9.412 75.122-16.358l1.99-158.447c-8.352-5.9-23.648-11.025-39.05-12.553-3.698-.366-7.398-.517-11-.478zm-222.775 74.202c-53.72.702-101.407 20.365-97.887 66.6 15.836-3.918 30.84-5.893 44.94-6.1 34.84-.51 64.213 9.704 87.318 27.613 34.608-3.11 69.852-10 105.412-20.314.14-41.287-74.098-68.657-139.783-67.8zm-48.877 78.65c-1.296-.003-2.603.012-3.92.045-17.256.436-36.45 4.03-57.566 11.037 5.79 53.808 26.325 106.41 58.5 143.346 6.226 7.15 12.856 13.712 19.875 19.615 29.303 9.282 69.26 12.917 110.534 12.14 3.777-55.805-8.717-108.357-36.193-142.74-21.265-26.61-51.064-43.39-91.232-43.444zm129.326 22.282c-9.358 1.637-18.69 3.016-27.995 4.15 1.54 1.74 3.043 3.52 4.502 5.346 3.146 3.937 6.094 8.062 8.873 12.334 9.916.144 19.868.125 29.857-.106H259.29v-21.723zm191.817 15.343c-65.406 17.826-131.462 25.41-195.85 25.315 16.998 35.144 23.828 78.093 21.013 122.6 42.482-2.08 85.03-8.23 118.187-15.983 26.693-32.78 47.37-77.118 56.65-131.932zM400.51 389.9c-38.334 9.145-87.95 16.056-136.873 17.454-47.67 1.36-94.336-2.228-129.448-15.262l-.01 78.93c27.187 12.568 76.414 20.205 127.318 20.298 51.224.094 104.214-7.173 139-20.773l.012-80.647z" fill="#a7de9a" fill-opacity="1" filter="url(#shadow-1)"></path></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1 @@
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><filter id="shadow-1" height="300%" width="300%" x="-100%" y="-100%"><feFlood flood-color="rgba(255, 255, 255, 1)" result="flood"></feFlood><feComposite in="flood" in2="SourceGraphic" operator="atop" result="composite"></feComposite><feGaussianBlur in="composite" stdDeviation="15" result="blur"></feGaussianBlur><feOffset dx="0" dy="0" result="offset"></feOffset><feComposite in="SourceGraphic" in2="offset" operator="over"></feComposite></filter></defs><g class="" style="" transform="translate(0,0)"><path d="M245.813 23.188c-1.228-.006-2.455.027-3.657.093-10.103.56-19.646 3.682-30.156 11.25l20.72 196.782c-8.394 2.127-16.676 4.47-24.814 7.094L137.72 57.812c-7.032-1.706-17.442-.3-27.126 4.626-10.248 5.213-19.034 13.84-22.813 22.937L155.03 261.5c-7.414 4.345-14.59 9.137-21.5 14.47l-74.343-94.25c-16.34.698-34.965 14.455-37.562 32.655C28.89 222.693 93.978 297.77 126 357.405c10.3 19.184 29.543 50.725 39.188 70.064 5.83 11.693 16.004 24.238 27.843 32.342 11.84 8.104 24.7 11.82 37.907 8.282l112.907-30.22c5.493-1.47 9.196-5.39 13.22-11.937 4.02-6.545 7.535-15.137 12.905-23 20.61-30.185 50.432-76.085 115.186-112.062-2.696-15.053-7.405-24.57-12.72-29.563-6.03-5.667-13.198-7.372-23.686-5.843-18.062 2.63-43.498 17.063-69.594 36.874-1.68 1.39-3.318 2.802-4.937 4.22l-7-61.252 42.5-155.718c-4.478-7.355-13.806-13.258-24.845-15.97-10.874-2.67-22.506-1.698-30.28 1.595l-38.75 149.874c-9.365 1.58-18.732 3.17-28.064 4.812L273.69 27.5c-10.057-2.52-19.284-4.272-27.875-4.313zM234.343 255l30.157 56.625 54.406-33.906-33.78 54.186L341.562 362l-64.157-2.188 2.188 64.032-30.03-56.344-54.283 33.813 33.97-54.438-56.53-30.125 63.78 2.156L234.344 255z" fill="#a7de9a" fill-opacity="1" filter="url(#shadow-1)"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1 @@
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><filter id="shadow-1" height="300%" width="300%" x="-100%" y="-100%"><feFlood flood-color="rgba(255, 255, 255, 1)" result="flood"></feFlood><feComposite in="flood" in2="SourceGraphic" operator="atop" result="composite"></feComposite><feGaussianBlur in="composite" stdDeviation="15" result="blur"></feGaussianBlur><feOffset dx="0" dy="0" result="offset"></feOffset><feComposite in="SourceGraphic" in2="offset" operator="over"></feComposite></filter></defs><g class="" style="" transform="translate(0,0)"><path d="M102.5 26.03l90.03 345.75 289.22 23.25-90.063-345.75L102.5 26.03zm-18.906 1.564c-30.466 11.873-55.68 53.098-49.75 75.312l3.25 11.78c.667-1.76 1.36-3.522 2.093-5.28C49.097 85.7 65.748 62.64 89.564 50.5l-5.97-22.906zm10.844 41.593c-16.657 10.012-29.92 28.077-38 47.407-5.247 12.55-8.038 25.63-8.75 36.53L112.5 388.407c.294-.55.572-1.106.875-1.656 10.603-19.252 27.823-37.695 51.125-48.47L94.437 69.19zm74.874 287.594c-17.677 9.078-31.145 23.717-39.562 39-4.464 8.107-7.27 16.364-8.688 23.75l11.688 42.408 1.625.125c-3.84-27.548 11.352-60.504 41.25-81.094l-6.313-24.19zm26.344 34c-32.567 17.27-46.51 52.44-41.844 72.94l289.844 24.5c-5.34-7.79-8.673-17.947-8.594-28.5l-22.406-9L459 443.436l-13.5-12.875c5.604-6.917 13.707-13.05 24.813-17.687L195.656 390.78z" fill="#a7de9a" fill-opacity="1" filter="url(#shadow-1)"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

47
assets/ui/d100.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

13
changelog.md Normal file
View File

@ -0,0 +1,13 @@
# 13.0.0
- Foundry v13 support
- New font for medieval era
# 12.0.12
- Add missing skills for WW1, WW2, future and post-apocalyptic
- Fix packaging
# 12.0.11
- Initial release

View File

@ -0,0 +1,446 @@
{
"label": "Compétences",
"mapping": {
"description": "system.description"
},
"folders": {
"Skills Post-Apo": "Post-Apo",
"Skills Jazz Age": "Jazz Age",
"Skills Modern": "Moderne",
"Skills WW2": "Seconde Guerre Mondiale",
"Skills WW1": "Première Guerre Mondiale",
"Skills Medieval": "Médiéval",
"Skills Revolutions": "Age des",
"Skills Victorian": "Victorien",
"Skills Age of Sail": "Age de la voile",
"Skills Coldwar": "Guerre froide",
"Skills Classical": "Classique",
"Skills Future": "Futur"
},
"entries": {
"Pilot (Type)": {
"name": "Pilotage (Type)",
"description": "<pPiloter, naviguer et diriger des véhicules nautiques ou aériens. Utilisez cette compétence pour assurer la sécurité d'un navire en cas de danger, par exemple lors d'une tempête ou d'une poursuite dangereuse. Chaque type de véhicule requiert une compétence distincte : Avion, Drone, Hélicoptère, Dirigeable, Petite embarcation, Navire, etc.</p>"
},
"Anthropology": {
"name": "Anthropologie",
"description": "<p>L'étude des cultures humaines vivantes. Elle permet de comprendre la morale, les croyances religieuses, les coutumes et les mœurs, et d'identifier (mais pas de traduire) des langues obscures. Alors que l'Histoire s'intéresse au passé lointain et que l'Archéologie étudie les artefacts physiques, l'Anthropologie s'intéresse aux comportements des cultures vivantes et à la manière dont elles se rattachent les unes aux autres et au passé.</p>"
},
"Appraise": {
"name": "Evaluation",
"description": "<p>La capacité à déterminer rapidement la valeur réelle (intrinsèque) d'un objet. Il s'agit d'une compétence importante pour s'assurer que les échanges et le troc vous sont favorables. Elle est également utile pour savoir quels objets ou ressources les autres seraient prêts à échanger pour les obtenir.</p>"
},
"Search": {
"name": "Recherche",
"description": "<p>Trouver des objets dissimulés. La fouille d'une habitation abandonnée peut ne pas nécessiter la compétence Fouille, mais seulement du temps et des efforts, ou une INT suffisamment élevée. Utilisez la compétence Fouille pour trouver un objet qui a été caché avec la compétence Furtivité ou qui est si bien caché ou camouflé qu'il nécessite l'intervention d'un expert. Le Modérateur de Jeu peut faire le jet de Fouille, de sorte que vous ne sachiez pas si votre Protagoniste a réussi ou échoué.</p>"
},
"PostApocalypse Lore (Type)": {
"name": "PostApocalypse Lore (Type)",
"description": "<p>When the Apocalypse came, thousands of years of human culture and society was swept away in an instant. Civilizations shattered; religions were snuffed out. What replaced them was a patchwork of different local cultures, organizations, and belief systems created by the few who survived.</p><p>This skill represents knowledge of one such culture, organization, or belief system. It doesnt cover the indepth customs and regulations of such groups that is the province of the Law skill.</p><p>A Survivor may have knowledge of several different facets of the PostApocalypse world each is represented by a separate PostApocalypse Lore skill. Types might be:</p><ul><li><p>regionbased (e.g., “Desert of Blasted Pillars”),</p></li><li><p>supernaturalcreaturebased (e.g., “Sentient Shadows”),</p></li><li><p>organizationbased (e.g., “New Beginnings Community Network”), or</p></li><li><p>newreligionbased (e.g., “Church of Melqart”).</p></li></ul>"
},
"Reassure": {
"name": "Reassure",
"description": "<p>Use this skill to understand the mental illness afflicting a person, help an afflicted person along the journey back to sanity, or talk someone down when the mental illness threatens to take hold.</p><p>You can also use this skill to assist in social interactions with someone, to calm them from an agitated state in order to extract information from them.</p><p>You cannot use Reassure on yourself.</p><p>Using Reassure to aid someone who suffered exposure to Unnatural forces might cost the reassuring Survivor SAN; see THREATS TO SAN in the SRD.</p>"
},
"Repair/Devise": {
"name": "Repair/Devise",
"description": "<p>With many settlements isolated by a day or more journey from each other, self-reliance is key. The ability to repair broken equipment or create impromptu fixes for unexpected break-downs is vital.</p><p>Repair/Devise is not a craft skilled. Its designed to make some work just long enough to get the job done (hopefully). Many other Cthulhu Eternal settings use Craft (Jury-Rig). Repair/Devise takes the place of that in this setting.</p>"
},
"Trivium": {
"name": "Trivium",
"description": "<p>Trivium is the “lesser” of the two liberal arts. It comprises rhetoric, grammar, and logic. It can be useful for player characters trying to make sense of all the information theyve been given.</p>"
},
"Law (Type)": {
"name": "Droit (Au choix)",
"description": "<p>Utilisez cette compétence pour comprendre et manipuler les structures de pouvoir qui existent au sein de la société, afin d'influencer des résultats juridiques ou de tirer votre Protagoniste d'un mauvais pas.</p><p>Chaque société a son propre système juridique - la compréhension de chacun d'entre eux est une compétence distincte. Parce que toutes les sociétés sont basées sur un comportement humain commun, vous pouvez tenter d'utiliser votre compétence Droit à la *moitié* de sa valeur pour naviguer dans les méandres d'un ordre social entièrement nouveau et inconnu.</p>"
},
"Alertness": {
"name": "Vigilance",
"description": "<p>La Vigilance permet de détecter le danger. Vous pouvez l'utiliser pour entendre l'armement d'un revolver, pour comprendre ce qui est murmuré de l'autre côté d'une paroi, pour repérer un objet caché sous une veste ou pour surprendre quelqu'un qui tente d'échapper aux regards en utilisant la discrétion.</p>"
},
"Swim": {
"name": "Natation",
"description": "<p>La plupart des Protagonistes savent nager correctement dans des eaux calmes. Utilisez la compétence Natation en cas de danger : parcourir une longue distance dans des eaux agitées, empêcher un ami de se noyer ou rejoindre un bateau avant que la chose à tentacules qui se trouve en dessous ne vous attrape.</p>"
},
"Ranged Weapons": {
"name": "Ranged Weapons",
"description": "<p>The ability to accurately hit targets with ranged non-firearm weapons such as Bows, Crossbows, and Slings.</p><p>This skill isnt used for thrown weapons like Spears those make use of the Athletics skill instead.</p>"
},
"Unnatural": {
"name": "Inconcevable",
"description": "<p>La connaissance des secrets fondamentaux de l'univers, qui bouleversent l'esprit. Les choses qui se glissent dans les coins sombres du monde et du cosmos. Utilisez cette compétence pour vous souvenir, reconnaître ou rechercher des faits sur les choses que les humains considèrent comme surnaturel. Cela va bien au-delà de l'Occultismeisme, car la compétence Inconcevable représente des choses réelles. Utilisez-la pour passer au crible les parties les plus sombres des mythes et du folklore et reconnaître en leur sein ce qui est vrai.</p>"
},
"Stealth": {
"name": "Discrétion",
"description": "<p>Dissimuler sa présence ou ses activités. Utilisez-la pour cacher une arme, camoufler une position, dissimuler une amulette Occultisme, faire les poches, vous déplacer silencieusement, suivre sans être vu ou vous fondre dans la foule. Un Protagoniste qui tente un jet de Furtivité ne peut être détecté que par une compétence opposée de Vigilance ou de Fouille (voir TESTS OPPOSES).</p>"
},
"Ride": {
"name": "Equitation",
"description": "<p>Manipuler, dresser et monter un animal - cheval, âne, chameau, etc. Utilisez cette compétence pour assurer la sécurité d'un animal en cas de danger et pour assurer la sécurité, le calme et la santé des animaux de monte.</p>"
},
"Heavy Machinery": {
"name": "Engins Lourds",
"description": "<p>Conduite de tracteurs, de grues, de batteuses, de trains à vapeur ou de tout autre grosse machine.</p>"
},
"Melee Weapons": {
"name": "Armes de mêlée",
"description": "<p>Utilisation létale d'armes de mêlée au combat. Utiliser cette compétence pour blesser ou tuer un adversaire à l'aide d'un couteau, d'une hache, d'un gourdin ou d'une autre arme de corps à corps.</p>"
},
"Disguise": {
"name": "Déguisement",
"description": "<p>Modifiez l'apparence, la voix, la posture, le langage corporel et les manières de votre Protagoniste pour éviter d'être reconnu sans attirer l'attention.</p>"
},
"Technology Use": {
"name": "Technology Use",
"description": "<p>Nobody needs to make a skill roll to use devices and technologies that are in common use in the era and setting of the game. However, making effective use of highly-specialized or technical apparatus is a different question. In the hands on the untrained, such devices are at best useless, and at worst dangerous. Technology Use is an umbrella skill allowing your Protagonist to successfully use all manner of common technical devices, as well as interface with the limited intelligent systems used to control them.</p><p>Note that the detailed design or adaptation of AI-based technology is covered by the Artificial Intelligence skill instead, and technologies specifically designed to defeat electronic security is covered by the Hacking skill.</p>"
},
"Drive": {
"name": "Conduite",
"description": "<p>Piloter tout véhicule ou moyen de transport terrestre en toute sécurité. Sauf avis contraire du Modérateur de Jeu, chaque Protagoniste peut conduire en toute sécurité dans des conditions de circulation normales. Utilisez cette compétence pour assurer la conduite d'un véhicule lors d'une poursuite tendue ou sur un terrain dangereux.</p>"
},
"Administration": {
"name": "Administration",
"description": "<p>Une connaissance du fonctionnement des bureaucraties et des types de formulaires, papiers, journaux, etc. qu'elles génèrent invariablement. Utilisez-le pour passer au crible des dossiers financiers détaillés ou des comptes rendus de réunions de clubs afin de trouver le point particulier qui ne semble pas correspondre au schéma habituel.</p><p>Utilisez cette compétence pour vous rechercher rapidement dans des dossiers administratifs afin de trouver ce qui est important. Notez que cette compétence concerne principalement l'interprétation des informations dont vous disposez ; si vous devez activement rechercher des livres, des dossiers, etc. cela relèverait de la compétence Étude.</p>"
},
"Unarmed Combat": {
"name": "Combat à mains nues",
"description": "<p>L'autodéfense. Un combat entre adversaires non entraînés implique souvent plus de bousculades et de cris que de véritable violence. Utilisez Combat à mains nues pour blesser ou tuer un adversaire avec pour seule arme vos mains (ou les pieds, les coudes, les dents et la tête) de votre Protagoniste.</p>"
},
"Carouse": {
"name": "Carouse",
"description": "<p>People have a variety of ways of reveling and relaxing when the work-day is through. This skill combines a knowledge of such social distractions and indulgences, and a practiced skill in partaking and perhaps excelling in their practice.</p><p>Use it to know the particular forms of entertainments that certain classes of society are likely to crave, and the people and places who are able to satisfy them whether legally or otherwise.</p><p>When participating in a leisure activity, this skill can be called upon when endurance or skillful practice of the revelry would achieve an advantage. One such use might be retaining ones faculties even when imbibing excessive intoxicating drinks. Another might be winning at games of chance commonly used for wagering (in this case, the skill is likely to b part of an OPPOSED TEST against other players Carouse, with sly attempts at cheating covered by the Stealth skill).</p>"
},
"Insight": {
"name": "Psychologie",
"description": "<p>La Psychologie permet d'obtenir des informations sur une personne - en particulier des informations que le sujet préférerait dissimuler - par l'observation, la conversation ou l'examen de modèles de comportement et de relations. Utilisez Psychologie pour reconnaître les signes de malhonnêteté à partir d'indices verbaux et du langage corporel, jauger l'attitude et les intentions, cultiver des sources d'information sur un sujet, déterminez ce qu'il faudrait faire pour obtenir la coopération d'un sujet, ou reconnaître les indices de ce qu'un sujet veut dissimuler.</p><p>La Psychologie permet de déceler les signes d'une maladie mentale, mais la Psychanalyse serait nécessaire pour aider à diagnostiquer une maladie spécifique. Un sujet qui tente délibérément de tromper votre Protagoniste peut tenter un test opposé en comparant sa Persuasion à la Psychologie de votre Protagoniste (voir TESTS OPPOSES).</p>"
},
"Craft (Type)": {
"name": "Artisanat (Au choix)",
"description": "<p>Fabrication et réparation d'outils et de produits sophistiqués. Un travail que la plupart des gens pourraient réaliser ne nécessite pas la compétence Artisanat, mais seulement un test d'INT ou de DEX. Utilisez la compétence Artisanat pour les travaux spécialisés nécessitant une formation : Artisanat (Mécanique) pour remettre en état un appareil cassé - ou pour le saboter au delà du réparable ; Artisanat (Serrurerie) pour ouvrir une serrure sans clé, et ainsi de suite.</p><p>Une spécialisation courante est **Artisanat (Bricolage)** qui représente une capacité à créer des assemblages d'équipements pour remplir une fonction lorsque l'outil approprié pour le travail n'est pas disponible.</p><p>Le Modérateur de Jeu décide si une tâche nécessite de l'artisanat. Chaque type d'artisanat est une compétence distincte : Forge, Charpente, Ferme, Mac Gyver, Serrurerie, Mécanique, etc.</p>"
},
"Archeology": {
"name": "Archéologie",
"description": "<p>L'étude des vestiges physiques des cultures humaines. Elle permet d'analyser une structure en ruine afin d'obtenir des informations utiles sur son fonctionnement il y a longtemps ou sur les circonstances de sa destruction. Alors que l'Anthropologie s'intéresse aux cultures vivantes et que l'Histoire est une vaste étude du passé, l'Archéologie traite de la signification des artefacts laissés sur place.</p>"
},
"History": {
"name": "Histoire",
"description": "<p>Découvrir des faits et des théories sur le passé de l'humanité. Utilisez cette compétence pour vous rappeler ou trouver un fait essentiel dans un passé lointain, reconnaître une référence obscure ou passer au peigne fin des documents que personne, sans votre formation approfondie, ne pourrait trouver. Alors que l'Anthropologie s'intéresse aux cultures vivantes et que l'Archéologie étudie la signification des vestiges anciens, l'histoire est une vaste étude de l'humanité.</p>"
},
"Dodge": {
"name": "Esquive",
"description": "<p>Éviter le danger grâce à l'instinct et aux réflexes. Utilisez l'esquive pour éviter une attaque. Contre les armes à feu et les explosifs, l'esquive n'est utile que pour se mettre à couvert (voir ESQUIVER LES ATTAQUES À DISTANCE).</p>"
},
"Sailing (Type)": {
"name": "Sailing (Type)",
"description": "<p>Piloting, navigating, and captaining waterborne vehicles. Use it to keep a boat or ship safe in a crisis, such as through a storm or in a dangerous pursuit. Each vessel type is a separate skill: Small Boat, Ship, Canoe, etc.</p>"
},
"Military Training (Type)": {
"name": "Entrainement militaire (Au choix)",
"description": "<p>De nombreuses personnes consacrent une partie de leur carrière au service militaire d'une couronne ou d'un empire, que ce soit au sein d'une armée de terre ou d'une marine, ou même sur des bateaux dans les eaux côtières. Un grand nombre de personnes sont également employées dans la gigantesque bureaucratie qui assure l'approvisionnement et le fonctionnement de l'armée. Cette compétence représente l'entraînement et l'expérience de première main obtenus en passant du temps dans l'un de ces services militaires. Elle permet d'appliquer des tactiques ou des stratégies militaires à une situation donnée, de se souvenir de détails spécifiques concernant une unité ou un corps militaire particulier, ou de se familiariser avec l'équipement militaire courant.</p>"
},
"Religion (Type)": {
"name": "Religion (Type)",
"description": "<p>A detailed knowledge of one of the several common belief systems that are known to exist, each providing its own account of the divine and explanations for the ways of the observed natural world.</p><p>Use it to know the differences between the beliefs of varying denominations of the particular religion, or to be able to recite passages from common religious texts. It doesnt include knowledge of obscure or fringe beliefs associated with the religion; details of such hidden wisdom are covered by the relevant Occult skill instead.</p>"
},
"Charm": {
"name": "Charme",
"description": "<p>Changer le point de vue de quelqu'un d'autre ou le convaincre de faire quelque chose pour vous par la ruse, l'astuce, l'amabilité ou d'autres techniques subtiles. Il ne s'agit pas de la menacer (ce qui est couvert par la compétence Intimidation ) ou d'utiliser la logique pour les convaincre (c'est la compétence Persuasion), mais de toutes les autres méthodes douces. Avec le Charme, vous pouvez entrer en contact avec un individu à un niveau plus personnel - peut-être même le séduire - et de tels changements peuvent devenir des attitudes ou des croyances de longue date. Si la victime du charme a un point de vue totalement antagoniste, un test opposé sera probablement nécessaire pour gérer la différence de points de vue.</p>"
},
"Regional Lore (Type)": {
"name": "Connaissance régionale (Au choix)",
"description": "<p>La plupart des personnes cultivées connaissent un peu tous les lieux, mais si vous avez vécu longtemps dans une région spécifique (ou si vous l'avez étudiée), vous en apprenez beaucoup plus. Cette compétence représente une compréhension approfondie des personnes, des lieux et des pratiques courantes propres à une région particulière. Utilisez-la pour vous souvenir des contes populaires racontés par les villageois, des mots locaux uniques, ou des ruines inhabituelles et autres curiosités géographiques trouvés dans la région. Elle peut également renseigner sur les superstitions et les rites bizarres propres à la région.</p>"
},
"Augury": {
"name": "Augury",
"description": "<p>This skill measures knowledge of one or more of the “sciences” used to predict the future, whether it be through watching the flight patterns of birds, reading entrails, or watching the movements of the planets amongst the stars.</p><p>Usually these techniques require multiple observation over several days before yielding some prediction and it is often relatively vague. It is the Game Moderators decision as to whether the prediction is accurate or not, but in either case many (superstitious) people around the Protagonist will firmly believe that the foretold future will come to pass. They might even generously interpret the real events in a way that makes the prediction at least partially correct. For accurate and specific visions of the future, some form of ritual magic must be involved (which is well beyond the remit of this skill).</p>"
},
"Herb Lore": {
"name": "Herb Lore",
"description": "<p>Herb Lore is an understanding of herbs, the proper method to collect them, where to find them, when to find them, and what can be done with them.</p><p>The Alchemy skill is used to turn herbs into poultices, salves, and other useful products including medical preparations.</p>"
},
"Research": {
"name": "Etude",
"description": "<p>Capacité à trouver des informations spécifiques dans des archives ou enfouies dans des dossiers, des registres ou des livres de comptes. Utilisez cette compétence pour naviguer dans le système de catalogue d'une bibliothèque, pour extraire des détails enfouis dans des registres de comptes ou pour localiser toute information pertinente enfouie.</p>"
},
"Athletics": {
"name": "Athlétisme",
"description": "<p>Votre Protagoniste s'entraîne pour tirer le meilleur parti de sa force et de son agilité. La Force et la Dextérité couvrent la puissance physique brute et l'habileté manuelle ; la compétence Athlétisme représente une longue pratique de la course, du saut, de l'escalade et du lancer.</p><p>Utiliser l'Athlétisme pour : </p><ul><li><p>Rattraper quelqu'un à la course (voir TESTS OPPOSÉS). </p></li><li><p>Sauter au dessus dun vide intimidant.</p></li><li><p>Grimper en situation d'urgence.</p></li><li><p>Se receptionner sans dommages après une chute de trois mètres maximum.</p></li><li><p>Atteindre une cible en lançant un couteau ou lancer une grenade exactement sur la cible.</p></li><li><p>Attraper quelque chose qui arrive sans prévenir, comme intercepter une grenade.</p></li></ul>"
},
"Quadrivium": {
"name": "Quadrivium",
"description": "<p>The Quadrivium represents part, along with the Trivium, of a medieval scholars areas of study. The quadrivium encompasses the disciplines of the liberal arts: music, astronomy, arithmetic and geometry.</p>"
},
"Social Etiquette": {
"name": "Etiquette",
"description": "<p>Les bonnes manières ne coûtent rien, dit-on, et à cette époque, elles sont une composante essentielle de la société « civilisée ». Une simple faute de goût lors dun dîner peut faire de vous la risée de lassemblée, ou vous faire exclure dun club prestigieux.</p><p>Utilisez cette compétence pour savoir quelles règles détiquette sappliquent dans une situation donnée, et pour être capable de les suivre instinctivement lorsque cest nécessaire. En complement, cette compétence peut également servir à repérer les moments où quelquun dautre enfreint lune de ces nombreuses règles sociales.</p>"
},
"Surgery": {
"name": "Chirurgie",
"description": "<p>Traitement d'une blessure ou d'une anomalie physique par des moyens invasifs. Par comparaison, les premiers soins permettent de maintenir un patient en vie jusqu'à ce qu'une intervention chirurgicale soit possible, tandis que la médecine assure un rétablissement à long terme. (Voir GUÉRISON)</p>"
},
"Natural World": {
"name": "Naturalisme",
"description": "<p>La compréhension commune du monde naturel telle qu'elle est pratiquée par les agriculteurs, les pêcheurs et d'autres personnes qui gagnent leur vie en fonction de l'évolution des conditions météorologiques, des marées, etc. Elle couvre également la compréhension générale des habitudes naturelles des animaux, ainsi que les différentes utilisations d'espèces végétales spécifiques. Utilisez cette compétence pour déterminer si le comportement observé des forces ou des créatures naturelles est conforme ou non aux modèles \"normaux\" ou courants.</p>"
},
"Persuade": {
"name": "Persuasion",
"description": "<p>Changer la décision ou le désir profond d'une autre personne. Utilisez Persuasion pour obtenir ce que veut votre Protagoniste lorsque le sujet est si têtu, que ce que veut votre Protagoniste est si précieux, ou que la tromperie est si flagrante que le Charisme ne suffit pas.</p><p>Grâce à la Persuasion, votre Protagoniste peut convaincre un témoin que ce qu'il a vu est inoffensif et n'est pas contre nature, convaincre un chef de groupe de vous aider à dissimuler une épidémie afin d'éviter une panique générale, ou tirer des informations utiles d'un sujet récalcitrant. Cette compétence permet également à votre Protagoniste de résister à la persuasion et à l'interrogatoire lors de jets de Persuasion opposés (voir TESTS OPPOSÉS).</p>"
},
"Pilot": {
"name": "Pilotage (Au choix)",
"description": "<p>Piloter, naviguer et diriger des véhicules nautiques ou aériens. Utilisez cette compétence pour assurer la sécurité d'un navire en cas de danger, par exemple lors d'une tempête ou d'une poursuite dangereuse. Chaque type de véhicule requiert une compétence distincte : Avion, Drone, Hélicoptère, Dirigeable, Petite embarcation, Navire, etc.</p>"
},
"Occult": {
"name": "Occultisme",
"description": "<p>L'étude du surnaturel tel qu'il est véhiculé par les traditions humaines, y compris des thèmes comme la magie, le folklore et les sociétés secrètes. Utilisez Occultismeisme pour examiner et déduire l'objectif d'un rituel, ou pour identifier des traditions Occultismees, des groupes, des grimoires, des outils, des symboles ou des légendes. Occultismeisme ne peut jamais indiquer à un Protagoniste ce qui est véritablement surnaturel et ce qui n'est que superstition ou mythologie. C'est le domaine de la compétence Inconcevable.</p>"
},
"Scavenge": {
"name": "Scavenge",
"description": "<p>The world is full of useful items, although most people dont know where to go looking for the good stuff. Use this skill to find and salvage helpful pieces of equipment or locate valuable resources like food and water. Anyone can find items in places where they are likely to be found (e.g., food in a larder); use this skill to find things in places where nobody else would think to look.</p><p>Depending on the game setting and the scarcity of the item being sought, the difficulty of Scavenge tests and the quality of the items unearthed by successful rolls may vary. See SCROUNGING in the SRD. </p><p>Note that efforts to rummage for information rather than items is handled by social or knowledge skills instead.</p>"
},
"First Aid": {
"name": "Premiers secours",
"description": "<p>Le traitement initial et la stabilisation des blessures. Utilisez cette compétence pour aider un personnage à récupérer les points de vie perdus. En comparaison, la chirurgie soigne une blessure grave et la médecine assure un rétablissement à long terme. (Voir GUÉRISON ).</p>"
},
"Psychoanalyze": {
"name": "Psychanalyse",
"description": "<p>Le diagnostic et le traitement des maladies mentales. Elle permet d'identifier un trouble mental, d'aider un patient à se rétablir, de calmer quelqu'un lorsqu'un trouble commence à prendre le dessus et de traiter la maladie mentale à long terme. Vous ne pouvez pas utiliser la psychanalyse sur vous-même. Utiliser la Psychanalyse pour aider quelqu'un qui a été exposé aux forces du Mythe de Cthulhu peut coûter de la SAN au thérapeute ; voir MENACES SUR LA SAN.</p>"
},
"Pharmacy": {
"name": "Pharmacy",
"description": "<p>Knowledge of drugs and herbal remedies, from their ingredients and creation, to their effects, uses, and misuses. Use it to identify and produce medicines and antidotes — as well as poisons.</p><p>Identifying a drug or medicinal herb requires at least 20% skill. Preparing a particularly powerful treatment safely, such as one with psychoactive effects, requires at least 40% skill or a successful roll. Misusing Pharmacy is a quick way to kill a patient (see POISON AND DISEASE in the SRD).</p>"
},
"Theology": {
"name": "Theology",
"description": "<p>Religion is an important part of life in the Victorian era, and this skill represents a knowledge of the common religious practices observed by the mainstream population. Use it to know the differences between the beliefs of varying denominations of Christians, or to be able to recite passages from common religious texts. This skill isnt particular to any one belief system but focusses on those that are widely-observed if you want to know about fringe beliefs, use the Occult skill instead.</p>"
},
"Survival (Type)": {
"name": "Survie (Au choix)",
"description": "<p>Connaissance du monde naturel. Cette compétence permet de planifier une expédition, de prévoir le temps, de reconnaître les particularités de la faune et de la flore, d'utiliser l'environnement pour recueillir d'autres informations ou de trouver de la nourriture, de l'eau et un abri. Chaque type de survie possède sa propre compétence. Les types les plus courants sont Désert, Jungle, Toundra et Ville.</p>"
},
"Ciphers": {
"name": "Ciphers",
"description": "<p>The knowledge of methods whereby the meaning of a message (verbal, written, or other) can be hidden from prying eyes. Use it to encode a message you want to safely send, or to decode a message written by someone else.</p><p>Note that when working with written messages, your effective rating in this skill is capped by your Literacy skill tests will usually be against the lower of the two.</p>"
},
"Forensics": {
"name": "Forensics",
"description": "<p>Gathering detailed information and evidence based on an assessment of human remains. Use it to hypothesize about the cause of a fire, trace the trajectory that a bullet followed, or determine whether a pool of blood is consistent with a major or minor injury.</p>"
},
"Organizational Lore (Type)": {
"name": "Organizational Lore (Type)",
"description": "<p>The world is full of large and complicated organizations, whether they be corporations, sprawling military-industrial groups, or byzantine government agencies. Most people know just the surface details about these entities usually what they want the world to know. But you have spent enough time close to one of these organizations (perhaps even inside it) that you know much more. This skill represents your deep understanding of the power structures, common practices, and key individuals that drive one organization. Use it to recall details of organizational culture, hierarchies, or locations important to the organization. The skill can also be used to determine quirks of the organization that might be exploited to your benefit, as well as offering guidance about who within the ranks might be enticed, coerced, or bribed into helping in some way.</p>"
},
"Navigate": {
"name": "Orientation",
"description": "<p>Trouver rapidement son chemin à l'aide de plan, de cartes et de tableaux, de votre sens de l'orientation, d'une boussole ou de la navigation à l'estime.</p>"
},
"Foreign Language (Type)": {
"name": "Langue étrangère (Type)",
"description": "<p>Maîtrise d'une autre langue. Chaque langue étrangère est une compétence distincte. Une compétence à 20 % permet des conversations hachées ; à 50 %, votre Protagoniste parle et lit comme un natif. Plus la compétence est élevée, plus les informations comprises par votre Protagoniste sont complexes et moins cela prend de temps. Il n'est pas nécessaire d'effectuer un jet de langue étrangère à moins que le Modérateur de Jeu n'indique que la situation est exceptionnellement difficile.</p>"
},
"Rhetoric": {
"name": "Rhetoric",
"description": "<p>The ability to give a captivating and rousing speech in public, capturing a well-structured argument and delivering it verbally in a compelling fashion. Whether or not the content is truthful or not is irrelevant, a well-spoken oration is more</p><p>likely to win the hearts and minds of listeners which, in some cases, is critical to influencing an outcome. In many cases (e.g., two litigants squaring off against one another in court) Rhetoric can be opposed by someone elses Rhetoric skill. In other cases, it can be opposed by an audiences Insight skill (representing their ability to spot the logical flaws or unconvincing body language used while the oration is being delivered).</p>"
},
"Beguile": {
"name": "Beguile",
"description": "<p>In the feudal world, oftentimes the best way to convince someone to do what you want them to do, is by appealing to their sense of power or self-importance. This delicate art combines guile, cunning, “buttering-up”, or other such subtle techniques to curry favor and obtain leverage.</p><p>Beguiling is not about open threats, teasing, or aggressive bluster that is covered by the Harangue/Taunt skill. Nor is it about using cold logic to convince them (thats Persuade). Rather, this skill is about using wile and cunning, coupled with a detailed understanding of the authority and power wielded by the person, to gain advantage.</p><p>If the target of the skill starts from an antagonistic viewpoint, some form of OPPOSED TEST will probably be needed to overcome their negative impression.</p>"
},
"Firearms": {
"name": "Armes à feu",
"description": "<p>Tir sûr et précis avec des armes à feu courantes en combat. Utilisez cette compétence pour atteindre une cible malgré l'adrénaline, la panique et les chocs qui interfèrent avec la coordination main-œil.</p>"
},
"Demolitions": {
"name": "Demolitions",
"description": "<p>Safe handling of explosives in a crisis. Use it to disarm a bomb, set a charge to destroy a target remotely, create homemade explosives from supplies at the hardware store, or analyze a blast to determine exactly what caused it. Failure when handling a bomb means your Survivor needs more time. If its a crisis that requires a roll, a fumble means an accidental explosion.</p>"
},
"Harangue": {
"name": "Intimidation",
"description": "<p>L'art d'intimider quelqu'un pour qu'il soit d'accord avec votre point de vue ou qu'il accepte de faire quelque chose que vous voulez qu'il fasse. Cette technique n'est pas une tentative subtile de faire appel à sa bonne nature (ce qui est couvert par la compétence Charme) ou à sa raison logique (c'est la compétence Persuasion) - au lieu de cela, il s'agit de fanfaronnades, de cris et de menaces. Mais ces techniques sont parfois plus efficaces que d'autres, même si les personnes qui les subissent n'apprécieront probablement pas autant l'interaction.</p>"
},
"Xenoarcheology": {
"name": "Xenoarcheology",
"description": "<p>The study of physical remains of cultures of intelligent species. Use it to analyze a ruined structure to determine useful information about its purpose long ago or the circumstances of its destruction. Where Xenology is about living cultures and History is a broad study of the past, Archeology discerns meaning from the artifacts left behind.</p>"
},
"Natural Philosophy": {
"name": "Natural Philosophy",
"description": "<p>The study of the natural world, covering disciplines that will later come to be called Physics, Biology, Geology, Botany and Zoology. It does not include the study of medicinal substances (that is covered by Apothecary), astronomical motion (thats Mathematics), or the study of chemicals (thats Alchemy). However most other fields of knowledge which pertain to Nature are within the remit of Natural Philosophy.</p>"
},
"Medicine": {
"name": "Médecine",
"description": "<p>L'étude et le traitement des blessures et des maladies. Cette compétence permet de diagnostiquer la cause d'une blessure, d'une maladie ou d'un empoisonnement, d'identifier des anomalies telles que des toxines ou des maladies, d'identifier la cause et l'heure approximative d'un décès, d'identifier le dernier repas d'une personne décédée ou de prescrire des soins de longue durée appropriés. En comparaison, les premiers soins permettent de maintenir un patient en vie jusqu'à ce qu'une intervention chirurgicale soit possible, et la chirurgie permet de soigner une blessure grave. (Voir GUÉRISON).</p>"
},
"Siege Weapons": {
"name": "Siege Weapons",
"description": "<p>A large part of medieval warfare are sieges. Towns, castles, monasteries, cities, even islands can be besieged.</p><p>This skill provides knowledge on how to use, maintain, and defend against the various siege weapons on the battlefield.</p><p>It isnt just a skill for warrior classes, peasants are often press-ganged into serving on siege weapon crews.</p>"
},
"Art (Type)": {
"name": "Art (Au choix)",
"description": "<p>Expertise dans la création ou l'exécution d'une œuvre qui suscite des émotions et des opinions. Cette compétence englobe également la connaissance des techniques et des tendances dans votre domaine, ainsi que la capacité à distinguer une œuvre réelle d'un créateur particulier d'une œuvre falsifiée. N'importe qui peut dessiner une esquisse ; la compétence Art reflète la connaissance, la pratique et le talent. Chaque type d'Art est une compétence distincte : Comédie, Danse, Contrefaçon, Musique, Poésie, Sculpture, Chant, Conte, etc.</p>"
},
"Foreign Court/Kingdom (Type)": {
"name": "Foreign Court/Kingdom (Type)",
"description": "<p>The feudal world is a vast and sprawling place, and while many people know some very basic details of each far-flung power a place name or two and where it is detailed knowledge is limited to those who have spent substantial time living, serving, or studying there.</p><p>This skill represents such deep understanding of the people, places, and common practices that are unique to one colonial outpost. Use it to recall details of the local climate, local peoples, history, and local government arrangement. It can also cover quirky beliefs or discoveries that have come to be common knowledge among residents.</p>"
},
"Empire Lore (Type)": {
"name": "Empire Lore (Type)",
"description": "<p>The Victorian Era world is made up of a patchwork of massive globe-spanning Empires. First among these is the British Empire, but all the other European powers and even upstarts like the United States have their own colonial territories scattered all across the map. This skill represents a detailed knowledge of the inner workings, places, and key figures associated with one such Empire. Use it to recall details about far-flung regions, the titles of important officials,</p><p>or obscure details about how the Empire is governed.</p>"
},
"Streetwise (Type)": {
"name": "Connaissance de la rue (Au choix)",
"description": "<p>Dans les villes et les cités, il existe une « société » parallèle, composée des classes criminelles, des pauvres, et de tous ceux dont les puissants préfèrent nier lexistence. En réalité, ce sous-prolétariat (largement invisible) est constitué de nombreuses sous-cultures et groupes distincts.</p><p>Cette compétence représente une connaissance approfondie de lun de ces milieux. Elle inclut des informations détaillées sur les contacts qui fait quoi, et où se rendre pour obtenir toutes sortes de biens ou services illicites propres à ce groupe. Certains de ces réseaux peuvent être géographiquement localisés (par exemple, « les docks »), tandis que dautres représentent des catégories plus larges de personnes ou dactivités (comme les contrebandiers et les trafiquants dalcool).</p>"
},
"Science (Type)": {
"name": "Science (Au choix)",
"description": "<p>L'étude approfondie des processus du monde. Cette compétence représente bien plus que l'enseignement scientifique ordinaire ; n'importe qui peut tenter un test dINT pour se souvenir de l'enseignement (rudimentaire) qu'il a reçu. La compétence Science est utilisée pour comprendre ou retrouver un principe clé sur la façon dont l'univers fonctionne - ou du moins, sur la façon dont il est censé fonctionner. Chaque science est une compétence distincte : Astronomie, Biologie, Botanique, Chimie, Ingénierie, Génétique, Géologie, Mathématiques, Météorologie, Physique, Planétologie, Zoologie, etc.</p>"
},
"Militaria (Type)": {
"name": "Militaria (Type)",
"description": "<p>Many people spend a part of their careers in some form of military service to a crown or empire whether as part of an army or navy, or even on boats in coastal waters. A great many people are also employed in the giant bureaucracy that keeps the military supplied and functioning. This skill represents first-hand training and experience obtained through time spent in one such military service. Use it to apply military tactics or strategies to a situation, to recall specific details about a particular military unit or corps, or to know your way around common military-issue equipment.</p>"
},
"Cosmology": {
"name": "Cosmology",
"description": "<p>The common understanding of the natural forces, ecology and laws which shape all aspects of the dynamics of a planet, star, or other astronomical object. It covers everything from the basic understanding of how objects orbit one another, how life-giving resources like atmosphere and water come into existence, or are sustained, through to the changing patterns of activity on planets or celestial bodies. It also covers the broad understanding of weather patterns and natural forms of life that are known to exist, common habitats, and which may constitute a threat. Use this skill to determine whether observed behavior of natural forces affecting a celestial body is consistent with “normal” or common patterns, or not.</p><p>If the technology level of the game covers techniques for terraforming planets, this skill confers a detailed understanding of the mechanisms, requirements, and time-frames involved with such methods.</p>"
},
"Literacy": {
"name": "Literacy",
"description": "<p>The ability to read and write your own native language and any other language you have learned. In this era not everyone is taught to be literate, despite it being important (perhaps even necessary) to performing certain jobs. If you want your Protagonist to be able to interpret or create documents, he or she will need to have at least a basic rating in this skill. As a shorthand, anyone with a Literacy skill below 10 is considered functionally illiterate; a skill rating between 11 and 40 represents semi-literacy; a rating above 40 denotes a fully literate individual.</p><p>The skill does not need to be purchased separately for each of the languages known; it is assumed that the basics of reading and writing common alphabets does not need to be re-learned. Note that in many cases where a document written in a foreign language needs to be comprehended, this will likely involve skill tests of BOTH Literacy and therequisite Foreign Language skill. It is the GMs discretion as to whether tests should involve a single die roll compared to both skills, or separate rolls against each.</p>"
},
"Espionage Training (Type)": {
"name": "Espionage Training (Type)",
"description": "<p>Knowledge and practice in the arts of “tradecraft”, the secretive techniques employed by intelligence anciencies to carry out their shadowy business. Each specialization covers the unique methods relating to one aspect of spying, whether technical snooping on communications, developing networks of contacts, or discreetly deploying purpose-built gadgets to silently assassinate opposition spies. Use this skill to perform special “spy stuff” related to your specialty, or use it to recall detailed knowledge about the common techniques that various espionage groups employ in that area.</p>"
},
"Track": {
"name": "Pistage",
"description": "<p>Permet de localiser les traces d'une créature ou d'une personne spécifique et de les suivre. Un test doit être effectué pour localiser les traces, puis un autre toutes les dix minutes pendant la traque. Des pénalités ou des bonus peuvent s'appliquer en fonction du temps écoulé depuis que les traces ont été faites, de la nature du sol, des conditions météorologiques depuis que les traces ont été faites, etc.</p>"
},
"Animal Handling": {
"name": "Animal Handling",
"description": "<p>Animals are part and parcel of medieval life. Everyone has a basic understanding of caring for and trying to control animals. Animal handling can be used to calm a spooked horse, quiet a barking dog, or even train a squirrel to perform tricks.</p><p>Animal handling cant be by a rider to control a mount. Thats the Ride skill.</p>"
},
"Hacking": {
"name": "Hacking",
"description": "<p>Access to places physically or virtually is tightly controlled in high-tech societies, with a range of electronic measures designed to allow authorized individuals and deny everyone else. But no system is perfect, and for those who know their weaknesses the security protecting doors, data repositories, and communication systems is more a temporary complication than a barrier.</p><p>This skill represents practical knowledge of techniques for hacking well-known electronic security systems or controls, as well as the real-world skills needed to successfully employ that book-learning in practice. It also represents abilities to use (or even create) special anti-security devices or “active content” (viruses, worms, etc.).</p>"
},
"Apothecary": {
"name": "Apothecary",
"description": "<p>Knowledge of medicinal preparations, which in this era are a combination of herbs, roots, other naturally-occurring folk remedies, and preparations manufactured through chemical separation and compounding. Use this skill to identify a common medicinal preparation, know what preparation is likely to improve a particular complaint, and safely administer a preparation.</p><p>Identifying a pharmaceutical preparation or medicinal herb requires at least 20% skill. Preparing a particularly powerful treatment safely, such as one with psychoactive effects, requires at least 40% skill or a successful roll. Misusing this skill is a quick way to kill a patient (see POISON AND DISEASE in the SRD).</p>"
},
"Mysticism (Type)": {
"name": "Mysticism (Type)",
"description": "<p>The study of the supernatural as understood by human traditions, including things like magic, initiation into mystery cults, and secret societies. Use Mysticism to examine and deduce the intent of a ritual, or to identify occult traditions, groups, scrolls, tools, symbols, or legends. Mysticism can never tell a Protagonist whats genuinely unnatural and whats just superstition or mythology. Thats the province of the Unnatural skill.</p><p>Mysticism is the more academic counterpart to Folklore. Successful Folklore and Mysticism rolls could provide the same information, viewed through different filters.</p>"
},
"Firearms / Beam Weapons": {
"name": "Firearms / Beam Weapons",
"description": "<p>Safe and accurate shooting with common firearms (projectile or laser/plasma beam) in combat. Use it to hit a target despite the adrenaline, panic, and shock of violence interfering with hand-eye coordination.</p>"
},
"Military Science": {
"name": "Military Science",
"description": "<p>Knowledge of military culture, techniques, and regulations. Use it to identify threats in a battlefield, find accurate ranges, recognize weaknesses in a fortification, deduce the training level of a soldier or unit, reconstruct the events of a battle, or deploy forces advantageously in combat.</p>"
},
"Ordnance": {
"name": "Ordnance",
"description": "<p>Knowledge and practical abilities relating to the use of heavy weapons, typically reserved for open warfare. Use it to recall the common types of armaments used on a particular type of ship, the procedures for maintaining a field gun, or the tactics of a particular nations heavy combatants. It also represents the ability to coordinate a team charged with preparing, aiming, and firing such a weapon. Successful use of the skill is needed for such a firing to be safe and land its projective in the correct location.</p><p>This skill also encompasses the use of explosives for non-military purposes such as mining and demolition.</p>"
},
"Alchemy": {
"name": "Alchemy",
"description": "<p>Alchemy is pseudo-chemistry wrapped in mysticism designed to prolong life, find a universal cure for disease, turn lead into gold, or any fantastical goal. It draws heavily on ancient sources, wraps them in religious trappings, and bottles it. Alchemy as a skill is by no means limited to those educated in the classics. Many wise women and cunning men know as much about alchemy as a Palatinate scholar who claims to have studied with the Arabs.</p>"
},
"Use Gadgets": {
"name": "Gadgets",
"description": "<pNul besoin de faire un jet de compétence pour utiliser les appareils et technologies couramment employés à lépoque et dans le cadre du jeu. En revanche, lusage efficace dun équipement technique ou spécialisé est une toute autre affaire.</p><p>Entre des mains inexpérimentées, de tels dispositifs sont au mieux inutiles… et au pire, dangereux. \"Gadgets\" est une compétence générique qui permet à votre protagoniste dutiliser avec succès toute une gamme dappareils techniques répandus, mais exigeant une certaine expertise.</p>"
},
"Astronomy": {
"name": "Astronomy",
"description": "<p>Knowledge of the movements of the celestial bodies. Use it to know the lunar cycles, or when Venus will be brightest in the sky and therefore closest to the Earth.</p><p>In the Classical Era the scientific knowledge of Astronomy and the predictive art of Astrology are heavily intertwined.</p><p>This skill might afford a limited ability to cast someones horoscope, but detailed prognostications and interpretations are the remit of the Augury skill.</p>"
},
"Mathematics": {
"name": "Mathematics",
"description": "<p>This skill encompasses the knowledge of mathematics, limited to simple theorems such as those described by Euclid, basic algebra and rudimentary trigonometry. It also covers simple cosmography and the motions of the heavenly bodies.</p>"
},
"Folklore": {
"name": "Folklore",
"description": "<p>Folklore is the knowledge of the customs and lore of the common people of region. It is used to deduce the proper way to enter a house, whether to walk around the field widdershins prior to planting winter wheat, or in what order to propitiate the saints. Its also useful when trying to understand the myths and legends of region, such as what creature is said to live in the lake or why the eastern face of the mountain has no trees.</p><p>At the Game Masters discretion, separate folklore skills for different regions may be used or the Game Master can apply a penalty the farther a protagonist travels from their home.</p>"
},
"Homeland": {
"name": "Homeland",
"description": "<p>Homeland represents a protagonists general knowledge about their homeland. Whos in charge, basic geography, known power struggles, which lord owns a certain village, etc.</p><p>History, Folklore, and other more specific skills should be used to gain in-depth knowledge.</p>"
},
"Mechanical Knack": {
"name": "Mechanical Knack",
"description": "<p>The tradesmans ability to fix or build devices which operate according to some mechanical principle. This includes domestic appliances, industrial machines, and vehicles. This includes large-scale mechanical apparatus. Fixes that require specialized training or apparatus are likely to fall under specific Craft skills (e.g., Locksmith), however this skill might still provide some basic assistance with those tasks.</p>"
},
"Bootlick": {
"name": "Bootlick",
"description": "<p>In the hierarchical and bureaucratic colonial world, oftentimes the best way to convince someone to do what you want them to do, is by appealing to their sense of power or self-importance. The delicate art of bootlicking combines guile, cunning, “buttering-up”, or other such subtle techniques to curry favor and obtain leverage.</p><p>Bootlicking is not about open threats, teasing, or aggressive bluster that is covered by the Harangue/Taunt skill. Nor is it about using cold logic to convince them (thats Persuade). Rather, this skill is about using wile and cunning, coupled with a detailed understanding of the authority and power wielded by the person, to gain advantage.</p><p>If the target of the skill starts from an antagonistic viewpoint, some form of OPPOSED TEST will probably be needed to overcome their negative impression.</p>"
},
"Planet / Station Lore (Type)": {
"name": "Planet / Station Lore (Type)",
"description": "<p>Most educated people know a little bit about every place, but if youve spent a long time living in (or studying) on a specific planet, asteroid, or space station you learn so much more. This skill represents a deep understanding of the people, places, and common practices that are unique to one particular locale. Use it to recall urban legends that locals tell, unique local words, or identify unusual artifacts found in the region. It can also cover quirky superstitions and rites that are particular to the place.</p>"
},
"Drive (Type)": {
"name": "Conduite (Au choix)",
"description": "<p>Piloter tout véhicule ou moyen de transport terrestre en toute sécurité. Sauf avis contraire du Modérateur de Jeu, chaque Protagoniste peut conduire en toute sécurité dans des conditions de circulation normales. Utilisez cette compétence pour assurer la conduite d'un véhicule lors d'une poursuite tendue ou sur un terrain dangereux.</p>"
},
"Procuratio": {
"name": "Procuratio",
"description": "<p>A knowledge of how bureaucracies work and what kinds of forms, paperwork, journals, etc. it invariably generates. Use it to sift through detailed charters or pipe rolls to find that one quirky point that just doesnt seem to fit the usual pattern.</p><p>Use this skill to rapidly navigate your way adeptly through written records to find whats important. It can also be useful in determining forgeries. Forged charters are a common scheme that can catch someone just skinning documents off guard.</p><p>Note that this skill is primarily about interpreting information you have; if you need to actively go out and track down books, records, etc. that would be covered by either the Search skill, some combination of social skills, or perhaps Carouse (depending on the mode of searching).</p>"
},
"Poisons": {
"name": "Poisons",
"description": "<p>The use of toxic preparations is common practice in the Classical world whether it be to silence a political rival, commit a murder, or rid a woman of an unwanted pregnancy. In most cases the use of poisons is appealing because it is much harder to trace, and may even appear to be the results of natural causes. Not all poisonings are intended to kill.</p><p>This skill includes the knowledge to prepare poisons from commonly available ingredients, the ability to detect the tell-tale signs that something bears the taint of a known poison, and the knowledge of the distinctive signs that poisons leave on their victims (often on their corpses).</p><p>Note that most ingredients for Classical Era poisons are plants which grow naturally. This skill doesnt help with knowing where to find such plants in the wild (that would be Forage/Hunt) or whether other types of similar plants could be substituted if they are unavailable (that would be Herb Lore).</p>"
},
"Forage/Hunt": {
"name": "Forage/Hunt",
"description": "<p>Forage/Hunt is used to find edible food in the wilderness. The appropriate tools are required, such a bow, spear, or other weapon to hunt animals. It also covers dressing the prey and preparing it for storage.</p><p>A successful roll provides enough food for a group of four to eat for a week.</p>"
},
"Sea Lore (Type)": {
"name": "Sea Lore (Type)",
"description": "<p>The worlds oceans are vast and sprawling, and while everyone knows some very basic details of different regions a port name or two and where each is located detailed knowledge is limited to those who have spent substantial time living, serving, or studying in the colony.</p><p>This skill represents such deep understanding of the people, places, and common practices that are unique to either a region of ocean, a nations shipping fleets, a group of islands/settlements, or a set of common maritime traditions or superstitions. Use it to recall details of the local climate, safe routes, names of famous ships and their captains, and which ports ask few questions about the origins of wealth. It can also cover quirky beliefs or discoveries that have come to be common knowledge among seafarers.</p>"
},
"Artillery": {
"name": "Artillery",
"description": "<p>Safe and accurate use of mortars, missiles, howitzers, tank cannons, and other heavy gunnery. Use it to destroy troop or a hard target in battle.</p>"
},
"Reassure (Copy)": {
"name": "Reassure (Copy)",
"description": "<p>Use this skill to understand the mental illness afflicting a person, help an afflicted person along the journey back to sanity, or talk someone down when the mental illness threatens to take hold.</p><p>You can also use this skill to assist in social interactions with someone, to calm them from an agitated state in order to extract information from them.</p><p>You cannot use Reassure on yourself.</p><p>Using Reassure to aid someone who suffered exposure to Unnatural forces might cost the reassuring Survivor SAN; see THREATS TO SAN.</p>"
},
"Physician": {
"name": "Physician",
"description": "<p>The treatment of an injury or abnormality, either by invasive means or the application of special medical salves and preparations. By comparison, First Aid keeps a patient alive until treatment is possible, and Herb Lore allows for the collection of herbs which, with some guidance from the Physician, might help with long term recovery. (See HEALING in the SRD).</p><p>Note that the Physician skill does not include the craft of brewing deliberately harmful toxins (see the Poisons skill, below), although it may assist with the identification of a death caused by such poisons.</p>"
},
"Colonial Lore (Type)": {
"name": "Colonial Lore (Type)",
"description": "<p>The colonial world is a vast and sprawling place, and while everyone knows some very basic details of each different colony a place name or two and where it is detailed knowledge is limited to those who have spent substantial time living, serving, or studying in the colony.</p><p>This skill represents such deep understanding of the people, places, and common practices that are unique to one colonial outpost. Use it to recall details of the local climate, indigenous peoples, colonial history, and local government arrangement. It can also cover quirky beliefs or discoveries that have come to be common knowledge among colonists.</p>"
},
"Antiquary": {
"name": "Antiquary",
"description": "<p>The study of things from the ancient past, whether they be artifacts, ruined structures, or faded inscriptions. Use it to analyze remnants from the ancient world to determine useful information about their purpose, the civilization that created, or the circumstances of its destruction. Where the History skill is a broad study of past events for which there is sound written accounts or documentation, Antiquary instead focuses on remnants from those ancient epochs about which only sketchy information or supposition exists.</p>"
},
"Heavy Weapons": {
"name": "Heavy Weapons",
"description": "<p>Safe and accurate operation of heavy but portable direct-fire ordnance such as tripod- or vehicle-mounted machine guns or rocket launchers. Use Heavy Weapons to suppress enemies or destroy a vehicle in combat. For even heavier weapons, see Artillery.</p>"
},
"Indigenous Lore (Type)": {
"name": "Indigenous Lore (Type)",
"description": "<p>Wherever European colonies have been established, there have been pre-existing settlements of indigenous peoples. This skill represents a knowledge of one cultural group of such peoples who have a connection to the territory claimed by your Protagonists colony. It represents a deep understanding of the people, places, and practices that are unique to that group. Use it to recall religious beliefs or folklore common to the group, local place names and languages, or unusual ruins and earthworks found in the region settled by the group.</p><p>The skill does not cover an ability to speak indigenous languages those must be purchased separately as Foreign Language skill specializations.</p>"
},
"Occult (Type)": {
"name": "Occultisme (Au choix)",
"description": "<p>L'étude du surnaturel tel qu'il est véhiculé par les traditions humaines, y compris des thèmes comme la magie, le folklore et les sociétés secrètes. Utilisez Occultisme pour examiner et déduire l'objectif d'un rituel, ou pour identifier des traditions Occultismees, des groupes, des grimoires, des outils, des symboles ou des légendes. Occultisme ne peut jamais indiquer à un Protagoniste ce qui est véritablement surnaturel et ce qui n'est que superstition ou mythologie. C'est le domaine de la compétence Inconcevable.</p>"
},
"Zero-G Maneuvering": {
"name": "Zero-G Maneuvering",
"description": "<p>The ability to gracefully move in low or zero gravity environments, and not float around as an uncoordinated tangle of flailing limbs. In a crisis situation or during a space-based pursuit this coordination can be the difference between escaping an enemy, crashing into the superstructure during a spacewalk, or embarrassingly stumbling into an asteroid crater.</p>"
},
"Artificial Intelligence": {
"name": "Artificial Intelligence",
"description": "<p>Most high-tech devices employ a degree of artificial intelligence, even if the average user has no real understanding of how these apparently “smart” gadgets perform their reasoning. This skill represents the deeper knowledge required to:</p><ul><li><p>Build an AI-powered device or system, either from first principles or using pre-written tools.</p></li><li><p>Tell whether a fragment of text, image, video, or sound was generated by an AI.</p></li><li><p>Manipulate the functioning of an AI device or system to cause it to act in a particular way.</p></li><li><p>Confound an AI with logical or ethical conundrums to cause it to freeze or halt or malfunction.</p></li><li><p>Bypass any security or guardrails placed by the makers or the AI to prohibit certain forms of use.</p></li></ul>"
},
"Chirurgury": {
"name": "Chirurgury",
"description": "<p>The treatment of an injury or abnormality, by invasive means. By comparison, First Aid keeps a patient alive until surgery is possible, and Herb Lore allows for medicinal preparations to be administered which might help with long term recovery. (See HEALING in the SRD)</p>"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -12,11 +12,9 @@ import * as documents from "./module/documents/_module.mjs"
import * as applications from "./module/applications/_module.mjs"
import { handleSocketEvent } from "./module/socket.mjs"
import { Macros } from "./module/macros.mjs"
import { setupTextEnrichers } from "./module/enrichers.mjs"
import CthulhuEternalUtils from "./module/utils.mjs"
export class ClassCounter{static printHello(){console.log("Hello")}static sendJsonPostRequest(e,s){const t={method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(s)};return fetch(e,t).then((e=>{if(!e.ok)throw new Error("La requête a échoué avec le statut "+e.status);return e.json()})).catch((e=>{throw console.error("Erreur envoi de la requête:",e),e}))}static registerUsageCount(e=game.system.id,s={}){if(game.user.isGM){game.settings.register(e,"world-key",{name:"Unique world key",scope:"world",config:!1,default:"",type:String});let t=game.settings.get(e,"world-key");null!=t&&""!=t&&"NONE"!=t&&"none"!=t.toLowerCase()||(t=foundry.utils.randomID(32),game.settings.set(e,"world-key",t));let a={name:e,system:game.system.id,worldKey:t,version:game.system.version,language:game.settings.get("core","language"),remoteAddr:game.data.addresses.remote,nbInstalledModules:game.modules.size,nbActiveModules:game.modules.filter((e=>e.active)).length,nbPacks:game.world.packs.size,nbUsers:game.users.size,nbScenes:game.scenes.size,nbActors:game.actors.size,nbPlaylist:game.playlists.size,nbTables:game.tables.size,nbCards:game.cards.size,optionsData:s,foundryVersion:`${game.release.generation}.${game.release.build}`};this.sendJsonPostRequest("https://www.uberwald.me/fvtt_appcount/count_post.php",a)}}}
export class ClassCounter { static printHello() { console.log("Hello") } static sendJsonPostRequest(e, s) { const t = { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json" }, body: JSON.stringify(s) }; return fetch(e, t).then((e => { if (!e.ok) throw new Error("La requête a échoué avec le statut " + e.status); return e.json() })).catch((e => { throw console.error("Erreur envoi de la requête:", e), e })) } static registerUsageCount(e = game.system.id, s = {}) { if (game.user.isGM) { game.settings.register(e, "world-key", { name: "Unique world key", scope: "world", config: !1, default: "", type: String }); let t = game.settings.get(e, "world-key"); null != t && "" != t && "NONE" != t && "none" != t.toLowerCase() || (t = foundry.utils.randomID(32), game.settings.set(e, "world-key", t)); let a = { name: e, system: game.system.id, worldKey: t, version: game.system.version, language: game.settings.get("core", "language"), remoteAddr: game.data.addresses.remote, nbInstalledModules: game.modules.size, nbActiveModules: game.modules.filter((e => e.active)).length, nbPacks: game.world.packs.size, nbUsers: game.users.size, nbScenes: game.scenes.size, nbActors: game.actors.size, nbPlaylist: game.playlists.size, nbTables: game.tables.size, nbCards: game.cards.size, optionsData: s, foundryVersion: `${game.release.generation}.${game.release.build}` }; this.sendJsonPostRequest("https://www.uberwald.me/fvtt_appcount/count_post.php", a) } } }
Hooks.once("init", function () {
console.info("Cthulhu Eternal RPG | Initializing System")
@ -34,7 +32,9 @@ Hooks.once("init", function () {
CONFIG.Actor.documentClass = documents.CthulhuEternalActor
CONFIG.Actor.dataModels = {
protagonist: models.CthulhuEternalProtagonist
protagonist: models.CthulhuEternalProtagonist,
vehicle: models.CthulhuEternalVehicle,
creature: models.CthulhuEternalCreature
}
CONFIG.Item.documentClass = documents.CthulhuEternalItem
@ -48,24 +48,30 @@ Hooks.once("init", function () {
bond: models.CthulhuEternalBond,
arcane: models.CthulhuEternalArcane,
gear: models.CthulhuEternalGear,
archetype: models.CthulhuEternalArchetype
archetype: models.CthulhuEternalArchetype,
ritual: models.CthulhuEternalRitual,
tome: models.CthulhuEternalTome
}
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet)
Actors.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalProtagonistSheet, { types: ["protagonist"], makeDefault: true })
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet)
foundry.documents.collections.Actors.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalProtagonistSheet, { types: ["protagonist"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalVehicleSheet, { types: ["vehicle"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalCreatureSheet, { types: ["creature"], makeDefault: true })
Items.unregisterSheet("core", ItemSheet)
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalSkillSheet, { types: ["skill"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalInjurySheet, { types: ["injury"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalMotivationSheet, { types: ["motivation"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalMentalDisorderSheet, { types: ["mentaldisorder"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalWeaponSheet, { types: ["weapon"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalArcaneSheet, { types: ["arcane"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalArmorSheet, { types: ["armor"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalBondSheet, { types: ["bond"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalGearSheet, { types: ["gear"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalArchetypeSheet, { types: ["archetype"], makeDefault: true })
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet)
foundry.documents.collections.Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalSkillSheet, { types: ["skill"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalInjurySheet, { types: ["injury"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalMotivationSheet, { types: ["motivation"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalMentalDisorderSheet, { types: ["mentaldisorder"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalWeaponSheet, { types: ["weapon"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalArcaneSheet, { types: ["arcane"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalArmorSheet, { types: ["armor"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalBondSheet, { types: ["bond"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalGearSheet, { types: ["gear"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalArchetypeSheet, { types: ["archetype"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalRitualSheet, { types: ["ritual"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalTomeSheet, { types: ["tome"], makeDefault: true })
// Other Document Configuration
CONFIG.ChatMessage.documentClass = documents.CthulhuEternalChatMessage
@ -84,25 +90,14 @@ Hooks.once("init", function () {
// Activate socket handler
game.socket.on(`system.${SYSTEM.id}`, handleSocketEvent)
setupTextEnrichers()
CthulhuEternalUtils.registerSettings()
CthulhuEternalUtils.registerHandlebarsHelpers()
CthulhuEternalUtils.setupCSSRootVariables()
// Gestion des jets de dés depuis les journaux
document.addEventListener("click", (event) => {
const anchor = event.target.closest("a.ask-roll-journal")
if (!anchor) return
event.preventDefault()
event.stopPropagation()
const type = anchor.dataset.rollType
const target = anchor.dataset.rollTarget
const title = anchor.dataset.rollTitle
})
console.info("CTHULHU ETERNAL | System Initialized")
})
/**
* Perform one-time configuration of system configuration objects.
*/
@ -118,46 +113,49 @@ function preLocalizeConfig() {
Hooks.once("ready", function () {
console.info("CTHULHU ETERNAL | Ready")
ClassCounter.registerUsageCount("fvtt-cthulhu-eternal", {})
_showUserGuide()
/* Display the user guide */
async function _showUserGuide() {
if (game.user.isGM) {
const newVer = game.system.version
}
if (typeof Babele !== 'undefined') {
console.info("CTHULHU ETERNAL | Babele detected, setting up translations")
Babele.get().setSystemTranslationsDir("compendiums");
}
if (game.user.isGM) {
ClassCounter.registerUsageCount("fvtt-cthulhu-eternal", {})
}
preLocalizeConfig()
if (game.user.isGM && game.i18n.lang === 'fr' && !game.modules.find(m => m.id == "babele")) {
ChatMessage.create({
user: game.user.id,
whisper: [game.user.id],
content: `<div ><span class="">
<strong>ATTENTION ! Français détecte, mais le module Babele n'est pas installé !<br>Installez Babele pour bénéficier de la traduction des compendiums`
})
}
})
Hooks.on("renderChatMessage", (message, html, data) => {
const typeMessage = data.message.flags.CthulhuEternal?.typeMessage
// Message de demande de jet de dés
if (typeMessage === "askRoll") {
// Affichage des boutons de jet de dés uniquement pour les joueurs
if (game.user.isGM) {
html.find(".ask-roll-dice").each((i, btn) => {
btn.style.display = "none"
})
} else {
html.find(".ask-roll-dice").click((event) => {
const btn = $(event.currentTarget)
const type = btn.data("type")
const value = btn.data("value")
const avantage = btn.data("avantage") ?? "="
const character = game.user.character
if (type === SYSTEM.ROLL_TYPE.RESOURCE) character.rollResource(value)
else if (type === SYSTEM.ROLL_TYPE.SAVE) character.rollSave(value, avantage)
})
}
Hooks.on("renderChatMessageHTML", (message, html, data) => {
// Affichage des boutons de jet de dés uniquement pour les joueurs
if (message.author.id === game.user.id) {
$(html).find(".nudge-roll").each((i, btn) => {
btn.style.display = "inline"
})
$(html).find(".damage-roll").each((i, btn) => {
btn.style.display = "inline"
})
$(html).find(".nudge-roll").click((event) => {
CthulhuEternalUtils.nudgeRoll(message)
})
$(html).find(".damage-roll").click((event) => {
CthulhuEternalUtils.damageRoll(message)
})
}
})
Hooks.on("updateSetting", async (setting, update, options, id) => {
})
// Dice-so-nice Ready
Hooks.once("diceSoNiceReady", (dice3d) => {
configureDiceSoNice(dice3d)
//configureDiceSoNice(dice3d)
})
/**
@ -167,18 +165,8 @@ Hooks.once("diceSoNiceReady", (dice3d) => {
* Journal - open journal sheet
*/
Hooks.on("hotbarDrop", (bar, data, slot) => {
if (["Actor", "Item", "JournalEntry", "roll", "rollDamage", "rollAttack"].includes(data.type)) {
Macros.createCthulhuEternalMacro(data, slot);
if (["Actor", "Item", "JournalEntry", "skill", "weapon"].includes(data.type)) {
// TODO -> Manage this
return false
}
})
/**
* Register world usage statistics
* @param {string} registerKey
*/
function registerWorldCount(registerKey) {
if (game.user.isGM) {
ClassCounter.registerUsageCount(game.system.id, {})
}
}

View File

@ -1,12 +1,17 @@
const gulp = require('gulp');
const less = require('gulp-less');
function onError(err) {
util.log(util.colors.red.bold('[ERROR LESS]:'),util.colors.bgRed(err.message));
this.emit('end');
};
/* ----------------------------------------- */
/* Compile LESS
/* ----------------------------------------- */
function compileLESS() {
return gulp.src("styles/fvtt-cthulhu-eternal.less")
.pipe(less())
.pipe(less()).on('error',console.log.bind(console))
.pipe(gulp.dest("./css"))
}
const css = gulp.series(compileLESS);

View File

@ -1,7 +1,9 @@
{
"TYPES": {
"Actor": {
"protagonist": "Protagonist"
"protagonist": "Protagonist",
"vehicle": "Vehicle",
"creature": "Creature"
},
"Item": {
"skill": "Skill",
@ -13,11 +15,15 @@
"mentaldisorder": "Mental Disorder",
"bond": "Bond" ,
"arcane": "Arcane",
"archetype": "Archetype"
"archetype": "Archetype",
"ritual": "Ritual",
"tome": "Tome"
}
},
"CTHULHUETERNAL": {
"Settings": {
"era": "Select the era of your game",
"eraHint": "Select the era of your game",
"Common": "Common",
"Classical": "Classical",
"Medieval": "Medieval",
@ -34,7 +40,33 @@
},
"Protagonist": {
"FIELDS": {
"damageBonus": {
"label": "Dmg.Bonus"
},
"resources": {
"permanentRating": {
"label": "Permanent Rating"
},
"hand": {
"label": "Hand"
},
"stowed": {
"label": "Stowed"
},
"storage": {
"label": "Storage"
}
},
"biodata": {
"feature": {
"label": "Feature"
},
"adaptedToViolence": {
"label": "Adapted to violence"
},
"adaptedToHelplessness": {
"label": "Adapted to helplessness"
},
"harshness": {
"label": "Harshness"
},
@ -83,6 +115,89 @@
}
}
},
"Creature": {
"FIELDS": {
"damageBonus": {
"label": "Dmg.Bonus"
},
"resources": {
"permanentRating": {
"label": "Permanent Rating"
},
"hand": {
"label": "Hand"
},
"stowed": {
"label": "Stowed"
},
"storage": {
"label": "Storage"
}
},
"biodata": {
"feature": {
"label": "Feature"
},
"adaptedToViolence": {
"label": "Adapted to violence"
},
"adaptedToHelplessness": {
"label": "Adapted to helplessness"
},
"harshness": {
"label": "Harshness"
},
"age": {
"label": "Age"
},
"gender": {
"label": "Gender"
},
"hair": {
"label": "Hair"
},
"eyes": {
"label": "Eyes"
},
"height": {
"label": "Height"
},
"home": {
"label": "Home"
},
"birthplace": {
"label": "Birthplace"
},
"label": "Biodata"
},
"characteristics:": {
"str": {
"label": "Strength"
},
"dex": {
"label": "Dexterity"
},
"int": {
"label": "Intelligence"
},
"pow": {
"label": "Power"
},
"con": {
"label": "Constitution"
},
"char": {
"label": "Charisma"
}
}
}
},
"Insanity": {
"None": "None",
"Flee": "Flee",
"Submit": "Submit",
"Struggle": "Struggle"
},
"Skill": {
"Unnatural": "Unnatural",
"Melee": "Melee Weapons",
@ -97,7 +212,7 @@
},
"settings": {
"label": "Settings era"
},
},
"diceEvolved": {
"label": "Can increase on failure"
},
@ -120,6 +235,9 @@
"resourceLevel": {
"label": "Resource level"
},
"state": {
"label": "State"
},
"settings": {
"label": "Settings era"
}
@ -147,7 +265,22 @@
"submachinegun": "Submachinegun",
"riflecarabine": "Rifle/Carabine"
},
"SelectiveFire": {
"shortburst": "Short Burst",
"longburst": "Long Burst",
"shortspray": "Short Spray",
"longspray": "Long Spray"
},
"FIELDS": {
"hasDirectSkill": {
"label": "Has direct skill"
},
"directSkillValue": {
"label": "Direct skill value"
},
"state": {
"label": "State"
},
"settings": {
"label": "Settings era"
},
@ -157,6 +290,12 @@
"weaponSubtype": {
"label": "Firearm Subtype"
},
"hasSelectiveFire": {
"label": "Has selective fire"
},
"applyDamageBonus": {
"label": "Add damage bonus"
},
"damage": {
"label": "Damage"
},
@ -203,10 +342,41 @@
}
}
},
"Vehicle": {
"FIELDS": {
"description": {
"label": "Description"
},
"notes": {
"label": "Notes"
},
"surfaceSpeed": {
"label": "Surface Speed"
},
"airSpeed": {
"label": "Air Speed"
},
"armor": {
"label": "Armor"
},
"settings": {
"label": "Settings era"
},
"crew": {
"label": "Crew"
},
"state": {
"label": "State"
}
}
},
"MentalDisorder": {
"FIELDS": {
"description": {
"label": "Description"
},
"cured": {
"label": "Cured"
}
}
},
@ -237,7 +407,7 @@
"FIELDS": {
"settings": {
"label": "Settings era"
},
},
"value": {
"label": "Value"
},
@ -255,9 +425,120 @@
"harsh": "Harsh",
"veryHarsh": "Very Harsh"
},
"Tome": {
"FIELDS": {
"language": {
"label": "Language"
},
"settings": {
"label": "Settings"
},
"studyTime": {
"label": "Study Time"
},
"sanLoss": {
"label": "SAN Loss"
},
"unnaturalSkill": {
"label": "Unnatural Skill"
},
"rituals": {
"label": "Rituals"
},
"minimumEra": {
"label": "Minimum Era"
},
"otherBenefits": {
"label": "Other Benefits"
},
"creationDate": {
"label": "Creation Date"
},
"description": {
"label": "Description"
}
},
"Label": {
"tomeDetails": "Tome Details"
},
"Button": {
"addRitual": "Add Ritual"
}
},
"Ritual": {
"Simple": "Simple",
"Complex": "Complex",
"Elaborate": "Elaborate",
"Difficult": "Difficult",
"FIELDS": {
"ritualType": {
"label": "Type"
},
"studyTime": {
"label": "Study time"
},
"studySAN": {
"label": "Study SAN"
},
"activationTime": {
"label": "Activation time"
},
"activationSAN": {
"label": "Activation SAN"
},
"activationWP": {
"label": "Activation WP"
},
"description": {
"label": "Description"
}
}
},
"Label": {
"titleSkill": "Skill",
"titleWeapon": "Weapon",
"lethalityRoll": "Lethality Roll",
"lethalityWounded": "The target is lethally wounded",
"lethalityNotWounded": "The target is not lethally wounded",
"damageRoll": "Damage Roll",
"vehicle":"Vehicle",
"Weapon": "Weapon",
"ZeroWP": "Zero WP : Automatic failure (ie 0%)",
"LowWP": "Low WP",
"Exhausted": "Exhausted",
"creature": "Creature",
"Rituals": "Rituals",
"Tomes": "Tomes",
"otherBenefits": "Other Benefits",
"Unarmed": "Unarmed",
"Cured": "Cured",
"Uncured": "Uncured",
"nudgedRoll": "Nudged Roll",
"selectNewValue": "Select the new value",
"wpCost": "WP Cost",
"Hand": "Hand",
"Stowed": "Stowed",
"Storage": "Storage",
"resourceRating": "Resource rating",
"Resources": "Resources",
"multiplier": "Multiplier",
"setBP": "Set BP",
"Vehicle": "Vehicle",
"Speed": "Speed",
"Slow": "Slow",
"Fast": "Fast",
"Average": "Average",
"None": "None",
"Pristine": "Pristine",
"Worn": "Worn",
"Junk": "Junk",
"resources": "Resources",
"resourceChecks": "Resource Checks",
"sanBPShort": "BP",
"tempInsanity": "Temp. Insanity",
"distinguishingFeatures": "Distinguishing Features",
"titleSkill": "Skill Roll",
"titleWeapon": "Weapon Roll",
"titleCharacteristic": "Characteristic Roll",
"titleSAN": "SAN Roll",
"biodata": "Biodata",
"skill": "Skill",
"modifier": "Modifier",
@ -270,7 +551,7 @@
"intShort": "INT",
"powShort": "POW",
"conShort": "CON",
"chaShort": "CHA",
"chaShort": "CHA",
"strLong": "Strength",
"dexLong": "Dexterity",
"intLong": "Intelligence",
@ -309,6 +590,7 @@
"criticalSuccess": "Critical Success",
"criticalFailure": "Critical Failure",
"Characteristic": "Characteristic",
"characteristic": "Characteristic",
"targetScore": "Target Score",
"gears": "Gears",
"armors": "Armors",
@ -331,7 +613,19 @@
"newGear": "New Gear",
"newArcane": "New Arcane",
"newArchetype": "New Archetype",
"newSkill": "New Skill"
"newSkill": "New Skill",
"newTome": "New Tome",
"newRitual": "New Ritual",
"titleResource": "Resource Roll",
"titleStandard": "Standard Roll",
"wpCostLabel": "Willpower points cost",
"noActorFound": "No actor found",
"skillFailed": "Skill roll failed : the skill has been ticked for progression",
"rollProgress": "Roll Progress",
"skillProgress": "Skill Progress"
},
"ChatMessage": {
"exhausted": "Your protagonist is exhausted. He loses [[/r 1d6]] Willpower Points."
},
"Edit": "Edit",
"Delete": "Delete",
@ -341,14 +635,23 @@
},
"Roll": {
"skill": "Skill",
"roll": "Roll"
"roll": "Roll",
"applyNudge": "Apply",
"cancel": "Cancel",
"nudgeRoll": "Nudge Roll"
},
"Tooltip": {
"sanBP": ">5 SAN lost in one roll, temporary insanity. If SAN less reaches BP = a Disorder unconscious Breaking and AND reset BP."
},
"Setting": {
"sanBP": ">5 SAN lost in one roll, temporary insanity. If SAN less reaches BP = a Disorder unconscious Breaking and AND reset BP.",
"setBP": "Set the current Breaking Point based on the current SAN value",
"addBond": "Add a new Bond"
},
"Chat": {
},
"Notifications": {
"NoWeaponSkill": "No weapon skill found for this weapon. Check Weapon definition or available skills/era",
"NoWeaponType": "No weapon type found for this weapon subtype. Check Weapon definition or available skills/era",
"skillAlreadyExists": "Skill already exists",
"WrongEra": "The era of the item does not match the ear of the system"
}
}
}

657
lang/fr.json Normal file
View File

@ -0,0 +1,657 @@
{
"TYPES": {
"Actor": {
"protagonist": "Protagoniste",
"vehicle": "Véhicule",
"creature": "Créature"
},
"Item": {
"skill": "Compétence",
"weapon": "Arme",
"armor": "Armure",
"injury": "Blessure",
"gear": "Matériel",
"motivation": "Motivation",
"mentaldisorder": "Trouble mental",
"bond": "Attache" ,
"arcane": "Arcane",
"archetype": "Archétype",
"ritual": "Rituel",
"tome": "Ouvrage"
}
},
"CTHULHUETERNAL": {
"Settings": {
"era": "Sélectionnez l'époque de votre jeu",
"eraHint": "L'époque détermine les compétences, les armes et les équipements disponibles pour votre protagoniste.",
"Common": "Commun",
"Classical": "Classique",
"Medieval": "Médiéval",
"Revolution": "Révolution",
"Modern": "Moderne",
"Future": "Futur",
"Jazz": "Jazz",
"WW1": "Première Guerre Mondiale",
"WW2": "Seconde Guerre Mondiale",
"ColdWar": "Guerre Froide",
"Victorian": "Victorienne",
"AgeOfSail": "Âge de la voile",
"PostApo": "Post-Apocalyptique"
},
"Protagonist": {
"FIELDS": {
"damageBonus": {
"label": "Bonus D."
},
"resources": {
"permanentRating": {
"label": "Degré Permanent"
},
"hand": {
"label": "A portée de main"
},
"stowed": {
"label": "Rangé"
},
"storage": {
"label": "Stocké"
}
},
"biodata": {
"feature": {
"label": "Feature"
},
"adaptedToViolence": {
"label": "Habitué à la violence"
},
"adaptedToHelplessness": {
"label": "Habitué à l'impuissance"
},
"harshness": {
"label": "Enfance"
},
"age": {
"label": "Age"
},
"gender": {
"label": "Genre"
},
"hair": {
"label": "Cheveux"
},
"eyes": {
"label": "Yeux"
},
"height": {
"label": "Taille"
},
"home": {
"label": "Maison"
},
"birthplace": {
"label": "Lieu de naissance"
},
"label": "Biodata"
},
"characteristics:": {
"str": {
"label": "Force"
},
"dex": {
"label": "Dextérité"
},
"int": {
"label": "Intelligence"
},
"pow": {
"label": "Pouvoir"
},
"con": {
"label": "Constitution"
},
"char": {
"label": "Charisme"
}
}
}
},
"Creature": {
"FIELDS": {
"damageBonus": {
"label": "Bonus D."
},
"resources": {
"permanentRating": {
"label": "Valeur permanente"
},
"hand": {
"label": "A portée de main"
},
"stowed": {
"label": "Rangé"
},
"storage": {
"label": "Stocké"
}
},
"biodata": {
"feature": {
"label": "Feature"
},
"adaptedToViolence": {
"label": "Habitué à la violence"
},
"adaptedToHelplessness": {
"label": "Habitué à l'impuissance"
},
"harshness": {
"label": "Dureté"
},
"age": {
"label": "Age"
},
"gender": {
"label": "Genre"
},
"hair": {
"label": "Cheveux"
},
"eyes": {
"label": "Yeux"
},
"height": {
"label": "Taille"
},
"home": {
"label": "Maison"
},
"birthplace": {
"label": "Lieu de naissance"
},
"label": "Biodata"
},
"characteristics:": {
"str": {
"label": "Force"
},
"dex": {
"label": "Dextérité"
},
"int": {
"label": "Intelligence"
},
"pow": {
"label": "Pouvoir"
},
"con": {
"label": "Constitution"
},
"char": {
"label": "Charisme"
}
}
}
},
"Insanity": {
"None": "Aucune",
"Flee": "Fuir",
"Submit": "Se Soumettre",
"Struggle": "Lutter"
},
"Skill": {
"Unnatural": "Inconcevable",
"Melee": "Armes de mêlée",
"Firearms": "Armes à feu",
"Athletics": "Athlétisme",
"UnarmedCombat": "Combat à mains nues",
"RangedWeapons": "Armes de tir",
"FirearmsBeams": "Armes à feu / à rayons",
"FIELDS": {
"isAdversary": {
"label": "Adversaire"
},
"settings": {
"label": "Epoque"
},
"diceEvolved": {
"label": "Peut progresser sur un échec"
},
"bonus" :{
"label": "Bonus"
},
"base": {
"label": "Base"
},
"rollFailed": {
"label": "Jet échoué"
},
"description": {
"label": "Description"
}
}
},
"Gear": {
"FIELDS": {
"resourceLevel": {
"label": "Niveau de ressource"
},
"state": {
"label": "Etat"
},
"settings": {
"label": "Epoque"
}
}
},
"Injury": {
"FIELDS": {
"description": {
"label": "Description"
}
}
},
"Weapon": {
"WeaponType": {
"melee": "Mêlée",
"rangedprimitive": "A distance - Primitive",
"rangedthrown": "A distance - Lancer",
"rangedfirearm": "A distance - Arme à feu",
"unarmed": "Non armé"
},
"WeaponSubtype": {
"basicfirearm": "Arme à feu de base",
"pistol": "Pistolet",
"shotgun": "Shotgun",
"submachinegun": "Mitrailleuse",
"riflecarabine": "Fusil/Carabine"
},
"SelectiveFire": {
"shortburst": "Rafale courte",
"longburst": "Rafale longue",
"shortspray": "Barrage court",
"longspray": "Barrage long"
},
"FIELDS": {
"hasDirectSkill": {
"label": "Compétence intégrée"
},
"directSkillValue": {
"label": "Valeur de la compétence intégrée"
},
"state": {
"label": "Etat"
},
"settings": {
"label": "Epoque"
},
"weaponType": {
"label": "Type"
},
"weaponSubtype": {
"label": "Sous-type d'arme à feu"
},
"hasSelectiveFire": {
"label": "Tir sélectif ?"
},
"applyDamageBonus": {
"label": "Ajouer le bonus de dégâts ?"
},
"damage": {
"label": "Dégats"
},
"description": {
"label": "Description"
},
"baseRange": {
"label": "Portée de base"
},
"rangeUnit": {
"label": "Untité de portée"
},
"killRadius": {
"label": "Rayon de destruction"
},
"lethality": {
"label": "Lethalité"
},
"resourceLevel": {
"label": "Niveau de ressource"
},
"armorPiercing": {
"label": "Pénétration d'armure"
}
}
},
"Armor": {
"FIELDS": {
"settings": {
"label": "Epoque"
},
"protection": {
"label": "Protection"
},
"resourceLevel": {
"label": "Niveau de ressource"
}
}
},
"Motivation": {
"FIELDS": {
"description": {
"label": "Description"
}
}
},
"Vehicle": {
"FIELDS": {
"description": {
"label": "Description"
},
"notes": {
"label": "Notes"
},
"surfaceSpeed": {
"label": "Vitesse de surface"
},
"airSpeed": {
"label": "Vitesse aérienne"
},
"armor": {
"label": "Armure"
},
"settings": {
"label": "Epoque"
},
"crew": {
"label": "Équipage"
},
"state": {
"label": "Etat"
}
}
},
"MentalDisorder": {
"FIELDS": {
"description": {
"label": "Description"
},
"cured": {
"label": "Soigné"
}
}
},
"Bond": {
"FIELDS": {
"bondType": {
"label": "Type"
},
"description": {
"label": "Description"
},
"value": {
"label": "Valeur"
}
}
},
"Arcane": {
"FIELDS": {
"value": {
"label": "Valeur"
},
"description": {
"label": "Description"
}
}
},
"Archetype": {
"FIELDS": {
"settings": {
"label": "Epoque"
},
"value": {
"label": "Valeur"
},
"description": {
"label": "Description"
}
}
},
"BondType": {
"individual": "Individu",
"community": "Groupe"
},
"Harshness": {
"normal": "Ordinaire",
"harsh": "Difficile",
"veryHarsh": "Très difficile"
},
"Tome": {
"FIELDS": {
"language": {
"label": "Language"
},
"settings": {
"label": "Epoque"
},
"studyTime": {
"label": "Temps d'étude"
},
"sanLoss": {
"label": "Perte de SAN"
},
"unnaturalSkill": {
"label": "Compétence Inconcevable"
},
"rituals": {
"label": "Rituels"
},
"minimumEra": {
"label": "Epoque minimum"
},
"otherBenefits": {
"label": "Autres avantages"
},
"creationDate": {
"label": "Date de création"
},
"description": {
"label": "Description"
}
},
"Label": {
"tomeDetails": "Détails de l'ouvrage"
},
"Button": {
"addRitual": "Ajouter un Rituel"
}
},
"Ritual": {
"Simple": "Simple",
"Complex": "Complexe",
"Elaborate": "Elaboré",
"Difficult": "Difficile",
"FIELDS": {
"ritualType": {
"label": "Type"
},
"studyTime": {
"label": "Temps d'étude"
},
"studySAN": {
"label": "Perte de SAN à l'étude"
},
"activationTime": {
"label": "Durée d'activation"
},
"activationSAN": {
"label": "Perte de SAN à l'activation"
},
"activationWP": {
"label": "Perte de PVO à l'activation"
},
"description": {
"label": "Description"
}
}
},
"Label": {
"lethalityRoll": "Jet de Létalité",
"lethalityWounded": "La cible est mortellement blessée",
"lethalityNotWounded": "La cible n'est PAS mortellement blessée",
"damageRoll": "Jet de dégâts",
"vehicle":"Véhicule",
"Weapon": "Arme",
"ZeroWP": "PVO à 0 : Echec automatique (ie 0%)",
"LowWP": "PVO faibles",
"Exhausted": "Epuisé",
"creature": "Créature",
"Rituals": "Rituels",
"Tomes": "Ouvrages",
"otherBenefits": "Autres avantages",
"Unarmed": "Désarmé",
"Cured": "Soigné",
"Uncured": "Non soigné",
"nudgedRoll": "Modifier le jeu",
"selectNewValue": "Sélectionner une nouvelle valeur",
"wpCost": "Cout en PVO",
"Hand": "A portée de main",
"Stowed": "Rangé",
"Storage": "Stocké",
"resourceRating": "Niveau de ressource",
"Resources": "Ressources",
"multiplier": "Multiplicateur",
"setBP": "Positionner le PR",
"Vehicle": "Véhicule",
"Speed": "Vitesse",
"Slow": "Lent",
"Fast": "Rapide",
"Average": "Moyen",
"None": "Aucun",
"Pristine": "Neuf",
"Worn": "Usé",
"Junk": "Défectueux",
"resources": "Ressources",
"resourceChecks": "Jet de ressources",
"sanBPShort": "PR",
"tempInsanity": "Folie Temporaire",
"distinguishingFeatures": "Eléments distinctifs",
"titleSkill": "Jet de compétence",
"titleWeapon": "Jet d'arme",
"titleCharacteristic": "Jet de caractéristique",
"titleSAN": "Jet de SAN",
"biodata": "Biodata",
"skill": "Compétence",
"modifier": "Modificateur",
"rollView": "Vue du jet",
"protagonist": "Protagoniste",
"characteristics": "Caractéristiques",
"description": "Description",
"strShort": "FOR",
"dexShort": "DEX",
"intShort": "INT",
"powShort": "POU",
"conShort": "CON",
"chaShort": "CHA",
"strLong": "Force",
"dexLong": "Dextérité",
"intLong": "Intelligence",
"powLong": "Pouvoir",
"conLong": "Constitution",
"chaLong": "Charisme",
"total": "Total",
"skills": "Compétence",
"gear": "Matériel",
"damage": "Dégâts",
"resource": "Ressource",
"armor": "Armure",
"malus": "Malus",
"experience": "Expérience",
"maximum": "Maximum",
"equipment": "Equipement",
"biography": "Biographie",
"notes": "Notes",
"weapons": "Armes",
"HP": "PV",
"SAN": "SAN",
"current": "Actuel",
"max": "Max",
"recovery": "Récup.",
"violence" : "Violence",
"helplessness": "Impuissance",
"breakingPoint": "Point de rupture",
"willpower": "Volonté",
"totalScore": "Score total",
"exhausted": "Epuisé",
"skillRoll": "Jet de compétence",
"charRoll": "Jet de caractéristique",
"finalScore": "Score final",
"failure": "Echec",
"success": "Succès",
"criticalSuccess": "Succès critique",
"criticalFailure": "Echec critique",
"Characteristic": "Caractéristique",
"characteristic": "Caractéristique",
"targetScore": "Score cible",
"gears": "Matériels",
"armors": "Armures",
"motivations": "Motivations",
"mentalDisorders": "Troubles mentaux",
"bonds": "Attaches",
"arcane": "Arcane",
"archetypes": "Archétypes",
"bondType": "Type d'attache",
"injuries": "Blessures",
"damageShort": "Dg",
"status": "Status",
"mentaldisorders": "Troubles mentaux",
"newBond": "Nouvelle Attache",
"newMotivation": "Nouvelle Motivation",
"newMentalDisorder": "Nouveau Trouble mental",
"newWeapon": "Nouvelle Arme",
"newArmor": "Nouvelle Armure",
"newInjury": "Nouvelle Blessure",
"newGear": "Nouvel Equipement",
"newArcane": "Nouvel Arcane",
"newArchetype": "Nouvel Archétype",
"newSkill": "Nouvelle Compétence",
"newTome": "Nouvel Ouvrage",
"newRitual": "Nouveau Rituel",
"titleResource": "Jet de Ressource",
"titleStandard": "Jet standard",
"wpCostLabel": "Coût en PVO",
"noActorFound": "Aucun protagoniste trouvé",
"skillFailed": "Jet de compétence échoué : la compétence a été marquée comme pouvant progresser.",
"rollProgress": "Jet de progression",
"skillProgress": "Progression de compétence"
},
"ChatMessage": {
"exhausted": "Votre protagoniste est épuisé. Il perd [[/r 1d6]] Points de Volonté."
},
"Edit": "Editer",
"Delete": "Supprimer",
"ToggleSheet": "Baculer la fiche",
"Warning": { },
"Dialog": {
},
"Roll": {
"skill": "Compétence",
"roll": "Jet",
"applyNudge": "Lancer",
"cancel": "Annuler",
"nudgeRoll": "Modifier le jet"
},
"Tooltip": {
"sanBP": "Perte de 5+ SAN en 1 jet : folie temporaire. SI la SAN atteint le PR : trouble mental, perte de conscience et reset du PR.",
"setBP": "Positionner le Point de Rupture à la valeur courant de la SAN",
"addBond": "Ajouter une Attache"
},
"Chat": {
},
"Notifications": {
"NoWeaponSkill": "Aucune compétence associée n'a été trouvé pour cette arme. Vérifier la définition de l'arme ainsi que l'époque configurée.",
"NoWeaponType": "Aucun type d'arme trouvé pour ce sous-type. Vérifier la définition de l'arme ainsi que l'époque configurée.",
"skillAlreadyExists": "La compétence existe déja",
"WrongEra": "L'époque de l'item ne correspond pas à celle du jeu en cours."
}
}
}

View File

@ -9,3 +9,7 @@ export { default as CthulhuEternalMentalDisorderSheet } from "./sheets/mentaldis
export { default as CthulhuEternalGearSheet } from "./sheets/gear-sheet.mjs"
export { default as CthulhuEternalMotivationSheet } from "./sheets/motivation-sheet.mjs"
export { default as CthulhuEternalArchetypeSheet } from "./sheets/archetype-sheet.mjs"
export { default as CthulhuEternalRitualSheet } from "./sheets/ritual-sheet.mjs"
export { default as CthulhuEternalVehicleSheet } from "./sheets/vehicle-sheet.mjs"
export { default as CthulhuEternalCreatureSheet } from "./sheets/creature-sheet.mjs"
export { default as CthulhuEternalTomeSheet } from "./sheets/tome-sheet.mjs"

View File

@ -0,0 +1,294 @@
// System Module Imports
import { Utils } from './utils.js'
import { SYSTEM } from "../../config/system.mjs"
export let ActionHandler = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
/**
* Extends Token Action HUD Core's ActionHandler class and builds system-defined actions for the HUD
*/
ActionHandler = class ActionHandler extends coreModule.api.ActionHandler {
/**
* Build system actions
* Called by Token Action HUD Core
* @override
* @param {array} groupIds
*/
async buildSystemActions(groupIds) {
// Set actor and token variables
this.actors = (!this.actor) ? this._getActors() : [this.actor]
this.actorType = this.actor?.type
// Set items variable
if (this.actor) {
let items = this.actor.items
items = coreModule.api.Utils.sortItemsByName(items)
this.items = items
}
if (this.actorType !== 'vehicle') {
this.#buildCharacterActions()
} else if (!this.actor) {
this.#buildMultipleTokenActions()
}
}
/**
* Build character actions
* @private
*/
#buildCharacterActions() {
this.buildAttributes()
this.buildOther()
this.buildLuck()
this.buildSkills()
this.buildEquipment()
}
#showValue() {
return game.settings.get('token-action-hud-core', 'tooltips') === 'none'
}
async buildAttributes() {
const actions = []
for (const key in this.actor.system.characteristics) {
const encodedValue = [coreModule.api.Utils.i18n('attributes'), key].join(this.delimiter)
const tooltip = {
content: String(this.actor.system.characteristics[key].value * 5),
class: 'tah-system-tooltip',
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.' + key),
id: key,
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue
})
}
await this.addActions(actions, {
id: 'attributes',
type: 'system'
})
}
async buildLuck() {
const actions = []
const tooltip = {
content: '50',
class: 'tah-system-tooltip',
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Luck'),
id: 'luck',
info1: this.#showValue() ? { text: '50' } : null,
tooltip,
encodedValue: ['attributes', 'luck'].join(this.delimiter)
})
await this.addActions(actions, { id: 'luck', type: 'system' })
}
async buildOther() {
if (typeof this.actor.system.sanity.value !== 'undefined') {
const actions = []
const groupData = {
id: 'other_sanity',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.SAN'),
type: 'system'
}
this.addGroup(groupData, { id: 'other', type: 'system' }, true)
const tooltip = {
content: String(this.actor.system.san.value + '/' + this.actor.system.san.max),
class: 'tah-system-tooltip',
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.SAN'),
id: 'sanity',
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue: ['attributes', 'sanity'].join(this.delimiter)
},
{
name: '+',
id: 'sanity_add',
encodedValue: ['attributes', 'sanity_add'].join(this.delimiter)
},
{
name: '-',
id: 'sanity_subtract',
encodedValue: ['attributes', 'sanity_subtract'].join(this.delimiter)
})
await this.addActions(actions, { id: 'other_sanity', type: 'system' })
}
if (typeof this.actor.system.hp.value !== 'undefined') {
const actions = []
const groupData = {
id: 'other_health',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.HP'),
type: 'system'
}
this.addGroup(groupData, { id: 'other', type: 'system' }, true)
const tooltip = {
content: String(this.actor.system.hp.value + '/' + this.actor.system.hp.max),
class: 'tah-system-tooltip',
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.HP'),
id: 'health',
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue: ['attributes', 'health'].join(this.delimiter)
},
{
name: '+',
id: 'health_add',
encodedValue: ['attributes', 'health_add'].join(this.delimiter)
},
{
name: '-',
id: 'health_subtract',
encodedValue: ['attributes', 'health_subtract'].join(this.delimiter)
})
await this.addActions(actions, { id: 'other_health', type: 'system' })
}
if (typeof this.actor.system.wp.value !== 'undefined') {
const actions = []
const groupData = {
id: 'other_wp',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.WP'),
type: 'system'
}
this.addGroup(groupData, { id: 'other', type: 'system' }, true)
const tooltip = {
content: String(this.actor.system.wp.value + '/' + this.actor.system.wp.max),
class: 'tah-system-tooltip',
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.WP'),
id: 'wp',
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue: ['attributes', 'wp'].join(this.delimiter)
},
{
name: '+',
id: 'wp_add',
encodedValue: ['attributes', 'wp_add'].join(this.delimiter)
},
{
name: '-',
id: 'wp_subtract',
encodedValue: ['attributes', 'wp_subtract'].join(this.delimiter)
})
await this.addActions(actions, { id: 'other_wp', type: 'system' })
}
}
async buildSkills() {
const actions = []
let actorSkills = this.actor.items.filter(item => item.type === 'skill')
for (const skill in actorSkills) {
if (skill.system.computeScore() > 0) {
const tooltip = {
content: String(skill.skill.system.computeScore()),
direction: 'LEFT'
}
actions.push({
name: skill.name,
id: skill.id,
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue: ['skills', s].join(this.delimiter)
})
}
}
await this.addActions(actions, { id: 'skills', type: 'system' })
}
async buildEquipment() {
let weapons = this.actor.items.filter(item => item.type === 'weapon')
let skills = this.actor.items.filter(item => item.type === 'skill')
for (const item of weapons) {
// Push the weapon name as a new group
const groupData = {
id: 'weapons_' + item._id,
name: item.name,
type: 'system'
}
if (!SYSTEM.WEAPON_SKILL_MAPPING[era] || !SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType]) {
continue
}
let skillName = game.i18n.localize(SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType])
let skill = skills.find(skill => skill.name.toLowerCase() === skillName.toLowerCase())
this.addGroup(groupData, { id: 'weapons', type: 'system' }, true)
if (item.type === 'weapon') {
const weapons = []
const tooltip = {
content: String(skill.system.computeScore()),
direction: 'LEFT'
}
weapons.push({
name: skill.name,
id: skill._id,
info1: this.#showValue() ? { text: tooltip.content } : null,
encodedValue: ['weapons', item._id].join(this.delimiter),
tooltip
})
const damageTooltip = {
content: String(item.system.damage),
direction: 'LEFT'
}
if (item.system.damage !== '') {
weapons.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Damage'),
id: item._id,
info1: this.#showValue() ? { text: damageTooltip.content } : null,
encodedValue: ['damage', item._id].join(this.delimiter),
tooltip: damageTooltip
})
}
if (item.system.isLethal) {
const lethalityTooltip = {
content: String(item.system.lethality),
direction: 'LEFT'
}
weapons.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Lethality'),
id: item._id,
info1: this.#showValue() ? { text: lethalityTooltip.content } : null,
encodedValue: ['lethality', item._id].join(this.delimiter),
tooltip: lethalityTooltip
})
}
await this.addActions(weapons, {
id: 'weapons_' + item._id,
type: 'system'
})
}/* else if (item.type === 'ritual') {
rituals.push({
name: item.name,
id: item._id,
encodedValue: ['rituals', item.name].join(this.delimiter)
})
} */
/* await this.addActions(rituals, {
id: 'rituals',
type: 'system'
}) */
}
}
/**
* Build multiple token actions
* @private
* @returns {object}
*/
#buildMultipleTokenActions() {
}
}
})

View File

@ -0,0 +1,38 @@
/**
* Module-based constants
*/
export const SYSTEM = {
ID: 'fvtt-cthulhu-eternal'
}
/**
* Core module
*/
export const CORE_MODULE = {
ID: 'token-action-hud-core'
}
/**
* Core module version required by the system module
*/
export const REQUIRED_CORE_MODULE_VERSION = '2.0'
/**
* Action types
*/
export const ACTION_TYPE = {
attributes: 'CTHULHUETERNAL.Label.Characteristics',
skills: 'CTHULHUETERNAL.Label.Skill',
equipment: 'CTHULHUETERNAL.Label.Gear'
}
/**
* Groups
*/
export const GROUP = {
attributes: { id: 'attributes', name: 'CTHULHUETERNAL.Label.Characteristics', type: 'system' },
luck: { id: 'luck', name: 'CTHULHUETERNAL.Label.Luck', type: 'system'},
skills: { id: 'skills', name: 'CTHULHUETERNAL.Label.Skills', type: 'system' },
weapons: { id: 'weapons', name: 'CTHULHUETERNAL.Label.Weapons', type: 'system' },
rituals: { id: 'rituals', name: 'CTHULHUETERNAL.Label.Rituals', type: 'system' }
}

View File

@ -0,0 +1,49 @@
import { GROUP } from './constants.js'
/**
* Default layout and groups
*/
export let DEFAULTS = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
const groups = GROUP
Object.values(groups).forEach(group => {
group.name = coreModule.api.Utils.i18n(group.name)
group.listName = `Group: ${coreModule.api.Utils.i18n(group.listName ?? group.name)}`
})
const groupsArray = Object.values(groups)
DEFAULTS = {
layout: [
{
nestId: 'statistics',
id: 'statistics',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Characteristics'),
groups: [
{ ...groups.attributes, nestId: 'statistics_attributes' },
{ ...groups.other, nestId: 'statistics_other' },
{ ...groups.luck, nestId: 'statistics_luck' }
]
},
{
nestId: 'skills',
id: 'skills',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Skills'),
groups: [
{ ...groups.skills, nestId: 'skills_skills' },
{ ...groups.typedSkills, nestId: 'skills_typed' },
{ ...groups.specialTraining, nestId: 'skills_special' }
]
},
{
nestId: 'equipment',
id: 'equipment',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Gear'),
groups: [
{ ...groups.weapons, nestId: 'equipment_weapons' },
{ ...groups.rituals, nestId: 'equipment_rituals' }
]
}
],
groups: groupsArray
}
})

View File

@ -0,0 +1,304 @@
import { SYSTEM } from "../../config/system.mjs"
import CthulhuEternalRoll from '../../documents/roll.mjs'
export let RollHandler = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
/**
* Extends Token Action HUD Core's RollHandler class and handles action events triggered when an action is clicked
*/
RollHandler = class RollHandler extends coreModule.api.RollHandler {
/**
* Handle action click
* Called by Token Action HUD Core when an action is left or right-clicked
* @override
* @param {object} event The event
* @param {string} encodedValue The encoded value
*/
async handleActionClick (event, encodedValue) {
const [actionTypeId, actionId] = encodedValue.split('|')
const knownCharacters = ['character']
// If single actor is selected
if (this.actor) {
await this.#handleAction(event, this.actor, this.token, actionTypeId, actionId)
return
}
const controlledTokens = canvas.tokens.controlled
.filter((token) => knownCharacters.includes(token.actor?.type))
// If multiple actors are selected
for (const token of controlledTokens) {
const actor = token.actor
await this.#handleAction(event, actor, token, actionTypeId, actionId)
}
}
/**
* Handle action hover
* Called by Token Action HUD Core when an action is hovered on or off
* @override
* @param {object} event The event
* @param {string} encodedValue The encoded value
*/
async handleActionHover (event, encodedValue) {
}
/**
* Handle group click
* Called by Token Action HUD Core when a group is right-clicked while the HUD is locked
* @override
* @param {object} event The event
* @param {object} group The group
*/
async handleGroupClick (event, group) {
}
/**
* Handle action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {object} token The token
* @param {string} actionTypeId The action type id
* @param {string} actionId The actionId
*/
async #handleAction (event, actor, token, actionTypeId, actionId) {
switch (actionTypeId) {
case 'attributes':
await this.#handleAttributesAction(event, actor, actionId)
break
case 'skills':
await this.#handleSkillsAction(event, actor, actionId)
break
case 'weapons':
await this.#handleWeaponsAction(event, actor, actionId)
break
case 'damage':
await this.#handleDamageAction(event, actor, actionId)
break
case 'lethality':
await this.#handleLethalityAction(event, actor, actionId)
break
case 'specialTraining':
await this.#handleSpecialTrainingAction(event, actor, actionId)
break
case 'typedSkills':
await this.#handleCustomTypedAction(event, actor, actionId)
break
/* case 'rituals':
await this.#handleRitualsAction(event, actor, actionId)
break */
case 'utility':
await this.#handleUtilityAction(token, actionId)
break
}
}
/**
* Handle Attribute action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleAttributesAction (event, actor, actionId) {
let rollType
if (actionId === 'wp' || actionId === 'health') return
if (actionId.includes('_add') || actionId.includes('_subtract')) {
const attr = actionId.split('_')[0]
const action = actionId.split('_')[1]
const update = {}
update.system = {}
update.system[attr] = {}
update.system[attr].value = action === 'add' ? this.actor.system[attr].value + 1 : this.actor.system[attr].value - 1
if (update.system[attr].value > this.actor.system[attr].max || update.system[attr].value < this.actor.system[attr].min) return
return await this.actor.update(update)
}
if (actionId === 'sanity') {
rollType = actionId
} else if (actionId === 'luck') {
rollType = actionId
} else {
rollType = 'stat'
}
const options = {
actor: this.actor,
rollType,
key: actionId
}
const roll = new DGPercentileRoll('1D100', {}, options)
return await this.actor.sheet.processRoll(event, roll)
}
/**
* Handle Skill action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleSkillsAction (event, actor, actionId) {
const options = {
actor: this.actor,
rollType: 'skill',
key: actionId
}
const skill = this.actor.system.skills[actionId]
if (!skill) return ui.notifications.warn('Bad skill name in HUD.')
const roll = new DGPercentileRoll('1D100', {}, options)
await this.actor.sheet.processRoll(event, roll)
}
/**
* Handle Typed/Custom skills action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleCustomTypedAction (event, actor, actionId) {
const options = {
actor: this.actor,
rollType: 'skill',
key: actionId
}
const roll = new DGPercentileRoll('1D100', {}, options)
await this.actor.sheet.processRoll(event, roll)
}
/**
* Handle SoecialTraining action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleSpecialTrainingAction (event, actor, actionId) {
const attr = this.actor.system.specialTraining.find(a => a.name === actionId).attribute
let target = 0
if (DG.statistics.includes(attr)) {
target = this.actor.system.statistics[attr].x5
} else if (DG.skills.includes(attr)) {
target = this.actor.system.skills[attr].proficiency
} else {
target = this.actor.system.typedSkills[attr].proficiency
}
const options = {
actor: this.actor,
rollType: 'special-training',
key: attr,
specialTrainingName: actionId,
target
}
const roll = new DGPercentileRoll('1D100', {}, options)
await this.actor.sheet.processRoll(event, roll)
}
/**
* Handle Weapon action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleWeaponsAction (event, actor, actionId) {
const item = this.actor.items.get(actionId)
const options = {
actor: this.actor,
rollType: 'weapon',
key: item.system.skill,
item
}
const roll = new DGPercentileRoll('1D100', {}, options)
await this.actor.sheet.processRoll(event, roll)
}
/**
* Handle Damage action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleDamageAction (event, actor, actionId) {
const item = this.actor.items.get(actionId)
if (item.system.lethality > 0 && event.ctrlKey) {
// Toggle on/off lethality
const isLethal = !item.system.isLethal
await item.update({ 'system.isLethal': isLethal })
} else {
const options = {
actor: this.actor,
rollType: 'damage',
key: item.system.damage,
item
}
const roll = new DGDamageRoll(item.system.damage, {}, options)
await this.actor.sheet.processRoll(event, roll)
}
}
/**
* Handle Lethality action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleLethalityAction (event, actor, actionId) {
const item = await this.actor.items.get(actionId)
if (item.system.damage !== '' && event.ctrlKey) {
const isLethal = !item.system.isLethal
await item.update({ 'system.isLethal': isLethal })
} else {
const options = {
actor: this.actor,
rollType: 'lethality',
key: item.system.lethality,
item
}
const roll = new DGLethalityRoll(item.system.damage, {}, options)
await this.actor.sheet.processRoll(event, roll)
}
}
/**
* Handle Ritual action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleRitualsAction (event, actor, actionId) {
const options = {
actor: this.actor,
rollType: 'ritual',
key: actionId
}
const roll = new DGPercentileRoll('1D100', {}, options)
await this.actor.sheet.processRoll(event, roll)
}
/**
* Handle utility action
* @private
* @param {object} token The token
* @param {string} actionId The action id
*/
async #handleUtilityAction (token, actionId) {
switch (actionId) {
case 'endTurn':
if (game.combat?.current?.tokenId === token.id) {
await game.combat?.nextTurn()
}
break
}
}
}
})

View File

@ -0,0 +1,91 @@
// System Module Imports
import { ActionHandler } from './action-handler.js'
import { RollHandler as Core } from './roll-handler.js'
import { SYSTEM } from './constants.js'
import { DEFAULTS } from './defaults.js'
export let SystemManager = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
/**
* Extends Token Action HUD Core's SystemManager class
*/
SystemManager = class SystemManager extends coreModule.api.SystemManager {
/**
* Returns an instance of the ActionHandler to Token Action HUD Core
* Called by Token Action HUD Core
* @override
* @returns {class} The ActionHandler instance
*/
getActionHandler () {
return new ActionHandler()
}
/**
* Returns a list of roll handlers to Token Action HUD Core
* Used to populate the Roll Handler module setting choices
* Called by Token Action HUD Core
* @override
* @returns {object} The available roll handlers
*/
getAvailableRollHandlers () {
const coreTitle = 'Core Template'
const choices = { core: coreTitle }
return choices
}
/**
* Returns an instance of the RollHandler to Token Action HUD Core
* Called by Token Action HUD Core
* @override
* @param {string} rollHandlerId The roll handler ID
* @returns {class} The RollHandler instance
*/
getRollHandler (rollHandlerId) {
let rollHandler
switch (rollHandlerId) {
case 'core':
default:
rollHandler = new Core()
break
}
return rollHandler
}
/**
* Returns the default layout and groups to Token Action HUD Core
* Called by Token Action HUD Core
* @returns {object} The default layout and groups
*/
async registerDefaults () {
return DEFAULTS
}
/**
* Register Token Action HUD system module settings
* Called by Token Action HUD Core
* @override
* @param {function} coreUpdate The Token Action HUD Core update function
*/
registerSettings (coreUpdate) {
/*systemSettings.register(coreUpdate)*/
}
/**
* Returns styles to Token Action HUD Core
* Called by Token Action HUD Core
* @override
* @returns {object} The TAH system styles
*/
registerStyles () {
return {
template: {
class: 'tah-style-template-style', // The class to add to first DIV element
file: 'tah-template-style', // The file without the css extension
moduleId: SYSTEM.ID, // The module ID
name: 'Template Style' // The name to display in the Token Action HUD Core 'Style' module setting
}
}
}
}
})

View File

@ -0,0 +1,55 @@
import { SYSTEM } from './constants.js'
export let Utils = null
function registerHUD() {
Hooks.on('tokenActionHudCoreApiReady', async () => {
/**
* Return the SystemManager and requiredCoreModuleVersion to Token Action HUD Core
*/
const module = game.system
module.api = {
requiredCoreModuleVersion: "2.0",
SystemManager
}
Hooks.call('tokenActionHudSystemReady', module)
})
}
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
/**
* Utility functions
*/
Utils = class Utils {
/**
* Get setting
* @param {string} key The key
* @param {string=null} defaultValue The default value
* @returns {string} The setting value
*/
static getSetting(key, defaultValue = null) {
let value = defaultValue ?? null
try {
value = game.settings.get(SYSTEM.ID, key)
} catch {
coreModule.api.Logger.debug(`Setting '${key}' not found`)
}
return value
}
/**
* Set setting
* @param {string} key The key
* @param {string} value The value
*/
static async setSetting(key, value) {
try {
value = await game.settings.set(MODULE.ID, key, value)
coreModule.api.Logger.debug(`Setting '${key}' set to '${value}'`)
} catch {
coreModule.api.Logger.debug(`Setting '${key}' not found`)
}
}
}
})

View File

@ -22,7 +22,7 @@ export default class CthulhuEternalArcaneSheet extends CthulhuEternalItemSheet {
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await TextEditor.enrichHTML(this.document.system.description, { async: true })
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}

View File

@ -22,7 +22,7 @@ export default class CthulhuEternalArchetypeSheet extends CthulhuEternalItemShee
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await TextEditor.enrichHTML(this.document.system.description, { async: true })
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}

View File

@ -67,7 +67,7 @@ export default class CthulhuEternalActorSheet extends HandlebarsApplicationMixin
actor: this.document,
system: this.document.system,
source: this.document.toObject(),
enrichedDescription: await TextEditor.enrichHTML(this.document.system.description, { async: true }),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true }),
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isEditable: this.isEditable,
@ -96,11 +96,10 @@ export default class CthulhuEternalActorSheet extends HandlebarsApplicationMixin
drop: this._canDragDrop.bind(this),
}
d.callbacks = {
dragstart: this._onDragStart.bind(this),
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this),
}
return new DragDrop(d)
return new foundry.applications.ux.DragDrop.implementation(d)
})
}
@ -133,70 +132,6 @@ export default class CthulhuEternalActorSheet extends HandlebarsApplicationMixin
return true //this.isEditable && this.document.isOwner
}
/**
* Callback actions which occur at the beginning of a drag start workflow.
* @param {DragEvent} event The originating DragEvent
* @protected
*/
_onDragStart(event) {
if ("link" in event.target.dataset) return
const el = event.currentTarget.closest('[data-drag="true"]')
const dragType = el.dataset.dragType
let dragData = {}
let target
switch (dragType) {
case "save":
target = event.currentTarget.querySelector("input")
dragData = {
actorId: this.document.id,
type: "roll",
rollType: target.dataset.rollType,
rollTarget: target.dataset.rollTarget,
value: target.value,
}
break
case "resource":
target = event.currentTarget.querySelector("select")
dragData = {
actorId: this.document.id,
type: "roll",
rollType: target.dataset.rollType,
rollTarget: target.dataset.rollTarget,
value: target.value,
}
break
case "damage":
dragData = {
actorId: this.document.id,
type: "rollDamage",
rollType: el.dataset.dragType,
rollTarget: el.dataset.itemId,
}
break
case "attack":
dragData = {
actorId: this.document.id,
type: "rollAttack",
rollValue: el.dataset.rollValue,
rollTarget: el.dataset.rollTarget,
}
break
default:
// Handle other cases or do nothing
break
}
// Extract the data you need
if (!dragData) return
// Set data transfer
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Callback actions which occur when a dragged element is over a drop target.
* @param {DragEvent} event The originating DragEvent
@ -205,7 +140,7 @@ export default class CthulhuEternalActorSheet extends HandlebarsApplicationMixin
_onDragOver(event) {}
async _onDropItem(item) {
console.log("Dropped item", item)
console.log("Dropped item", item)
let itemData = item.toObject()
await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
}

View File

@ -64,7 +64,7 @@ export default class CthulhuEternalItemSheet extends HandlebarsApplicationMixin(
item: this.document,
system: this.document.system,
source: this.document.toObject(),
enrichedDescription: await TextEditor.enrichHTML(this.document.system.description, { async: true }),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true }),
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isEditable: this.isEditable,

View File

@ -22,7 +22,7 @@ export default class CthulhuEternalGiftSheet extends CthulhuEternalItemSheet {
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await TextEditor.enrichHTML(this.document.system.description, { async: true })
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}

View File

@ -0,0 +1,175 @@
import CthulhuEternalActorSheet from "./base-actor-sheet.mjs"
export default class CthulhuEternalCreatureSheet extends CthulhuEternalActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["creature"],
position: {
width: 860,
height: 620,
},
window: {
contentClasses: ["creature-content"],
},
actions: {
createArmor: CthulhuEternalCreatureSheet.#onCreateArmor,
createWeapon: CthulhuEternalCreatureSheet.#onCreateWeapon,
createSkill: CthulhuEternalCreatureSheet.#onCreateSkill,
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-cthulhu-eternal/templates/creature-main.hbs",
},
tabs: {
template: "templates/generic/tab-navigation.hbs",
},
skills: {
template: "systems/fvtt-cthulhu-eternal/templates/creature-skills.hbs",
},
biography: {
template: "systems/fvtt-cthulhu-eternal/templates/creature-biography.hbs",
},
}
/** @override */
tabGroups = {
sheet: "skills",
}
/**
* Prepare an array of form header tabs.
* @returns {Record<string, Partial<ApplicationTab>>}
*/
#getTabs() {
const tabs = {
skills: { id: "skills", group: "sheet", icon: "fa-solid fa-shapes", label: "CTHULHUETERNAL.Label.skills" },
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "CTHULHUETERNAL.Label.biography" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
context.enrichedDescription = await TextEditor.enrichHTML(this.document.system.description, { async: true })
context.enrichedNotes = await TextEditor.enrichHTML(this.document.system.notes, { async: true })
context.tooltipsCharacteristic = {
str: game.i18n.localize("CTHULHUETERNAL.Characteristic.Str"),
dex: game.i18n.localize("CTHULHUETERNAL.Characteristic.Dex"),
con: game.i18n.localize("CTHULHUETERNAL.Characteristic.Con"),
int: game.i18n.localize("CTHULHUETERNAL.Characteristic.Int"),
pow: game.i18n.localize("CTHULHUETERNAL.Characteristic.Pow"),
cha: game.i18n.localize("CTHULHUETERNAL.Characteristic.Cha")
}
return context
}
/** @override */
async _preparePartContext(partId, context) {
const doc = this.document
switch (partId) {
case "main":
break
case "skills":
context.tab = context.tabs.skills
context.skills = doc.itemTypes.skill
context.skills.sort((a, b) => a.name.localeCompare(b.name))
context.weapons = doc.itemTypes.weapon
context.weapons.sort((a, b) => a.name.localeCompare(b.name))
context.armors = doc.itemTypes.armor
context.armors.sort((a, b) => a.name.localeCompare(b.name))
break
case "biography":
context.tab = context.tabs.biography
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description, { async: true })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes, { async: true })
break
}
return context
}
/**
* Creates a new attack item directly from the sheet and embeds it into the document.
* @param {Event} event The initiating click event.
* @param {HTMLElement} target The current target of the event listener.
*/
static #onCreateWeapon(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newWeapon"), type: "weapon" }])
}
static #onCreateArmor(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newArmor"), type: "armor" }])
}
static #onCreateSkill(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newSkill"), type: "skill" }])
}
/**
* Handles the roll action triggered by user interaction.
*
* @param {PointerEvent} event The event object representing the user interaction.
* @param {HTMLElement} target The target element that triggered the roll.
*
* @returns {Promise<void>} A promise that resolves when the roll action is complete.
*
* @throws {Error} Throws an error if the roll type is not recognized.
*
* @description This method checks the current mode (edit or not) and determines the type of roll
* (save, resource, or damage) based on the target element's data attributes. It retrieves the
* corresponding value from the document's system and performs the roll.
*/
async _onRoll(event, target) {
const rollType = $(event.currentTarget).data("roll-type")
let item
let li
// Debug : console.log(">>>>", event, target, rollType)
// Deprecated : if (this.isEditMode) return
switch (rollType) {
case "char":
let charId = $(event.currentTarget).data("char-id")
item = foundry.utils.duplicate(this.actor.system.characteristics[charId])
item.name = game.i18n.localize(`CTHULHUETERNAL.Label.${charId}Long`)
item.targetScore = item.value * 5
break
case "skill":
li = $(event.currentTarget).parents(".item");
item = this.actor.items.get(li.data("item-id"));
break
case "weapon":
case "damage":
li = $(event.currentTarget).parents(".item");
item = this.actor.items.get(li.data("item-id"));
item.damageBonus = this.actor.system.damageBonus
break
default:
throw new Error(`Unknown roll type ${rollType}`)
}
await this.document.system.roll(rollType, item)
}
async _onDrop(event) {
if (!this.isEditable || !this.isEditMode) return
const data = TextEditor.getDragEventData(event)
// Handle different data types
switch (data.type) {
case "Item":
const item = await fromUuid(data.uuid)
return super._onDropItem(item)
}
}
}

View File

@ -22,7 +22,7 @@ export default class CthulhuEternalInjurySheet extends CthulhuEternalItemSheet {
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await TextEditor.enrichHTML(this.document.system.description, { async: true })
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}

View File

@ -18,4 +18,12 @@ export default class CthulhuEternalMentalDisorderSheet extends CthulhuEternalIte
template: "systems/fvtt-cthulhu-eternal/templates/mentaldisorder.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}

View File

@ -22,6 +22,8 @@ export default class CthulhuEternalMotivationSheet extends CthulhuEternalItemShe
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}

View File

@ -5,13 +5,14 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
static DEFAULT_OPTIONS = {
classes: ["protagonist"],
position: {
width: 848,
width: 860,
height: 620,
},
window: {
contentClasses: ["protagonist-content"],
},
actions: {
setBP: CthulhuEternalProtagonistSheet.#onSetBP,
createGear: CthulhuEternalProtagonistSheet.#onCreateGear,
createArmor: CthulhuEternalProtagonistSheet.#onCreateArmor,
createWeapon: CthulhuEternalProtagonistSheet.#onCreateWeapon,
@ -19,7 +20,9 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
createInjury: CthulhuEternalProtagonistSheet.#onCreateInjury,
createMentalDisorder: CthulhuEternalProtagonistSheet.#onCreateMentalDisorder,
createMotivation: CthulhuEternalProtagonistSheet.#onCreateMotivation,
createSkill: CthulhuEternalProtagonistSheet.#onCreateSkill
createSkill: CthulhuEternalProtagonistSheet.#onCreateSkill,
createRitual: CthulhuEternalProtagonistSheet.#onCreateRitual,
createTome: CthulhuEternalProtagonistSheet.#onCreateTome,
},
}
@ -73,6 +76,9 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
const context = await super._prepareContext()
context.tabs = this.#getTabs()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.notes, { async: true })
context.tooltipsCharacteristic = {
str: game.i18n.localize("CTHULHUETERNAL.Characteristic.Str"),
dex: game.i18n.localize("CTHULHUETERNAL.Characteristic.Dex"),
@ -82,23 +88,9 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
cha: game.i18n.localize("CTHULHUETERNAL.Characteristic.Cha")
}
context.tooltipsRessources = {
}
context.rollType = {
str: "characteristic",
dex: "characteristic",
con: "characteristic",
int: "characteristic",
pow: "characteristic",
cha: "characteristic"
}
return context
}
_generateTooltip(type, target) {
}
/** @override */
async _preparePartContext(partId, context) {
const doc = this.document
@ -108,24 +100,36 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
case "skills":
context.tab = context.tabs.skills
context.skills = doc.itemTypes.skill
context.skills.sort((a, b) => a.name.localeCompare(b.name))
break
case "equipment":
context.tab = context.tabs.equipment
context.weapons = doc.itemTypes.weapon
context.weapons.sort((a, b) => a.name.localeCompare(b.name))
context.armors = doc.itemTypes.armor
context.armors.sort((a, b) => a.name.localeCompare(b.name))
context.gears = doc.itemTypes.gear
context.gears.sort((a, b) => a.name.localeCompare(b.name))
context.rituals = doc.itemTypes.ritual
context.rituals.sort((a, b) => a.name.localeCompare(b.name))
context.tomes = doc.itemTypes.tome
context.tomes.sort((a, b) => a.name.localeCompare(b.name))
break
case "status":
context.tab = context.tabs.status
context.injuries = doc.itemTypes.injury
context.injuries.sort((a, b) => a.name.localeCompare(b.name))
context.mentaldisorders = doc.itemTypes.mentaldisorder
context.mentaldisorders.sort((a, b) => a.name.localeCompare(b.name))
context.motivations = doc.itemTypes.motivation
context.motivations.sort((a, b) => a.name.localeCompare(b.name))
context.bonds = doc.itemTypes.bond
context.bonds.sort((a, b) => a.name.localeCompare(b.name))
break
case "biography":
context.tab = context.tabs.biography
context.motivations = doc.itemTypes.motivation
context.enrichedDescription = await TextEditor.enrichHTML(doc.system.description, { async: true })
context.enrichedNotes = await TextEditor.enrichHTML(doc.system.notes, { async: true })
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description, { async: true })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes, { async: true })
break
}
return context
@ -136,6 +140,10 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
* @param {Event} event The initiating click event.
* @param {HTMLElement} target The current target of the event listener.
*/
static #onSetBP(event, target) {
this.document.system.setBP()
}
static #onCreateGear(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newGear"), type: "gear" }])
}
@ -168,6 +176,14 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newSkill"), type: "skill" }])
}
static #onCreateRitual(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newRitual"), type: "ritual" }])
}
static #onCreateTome(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newTome"), type: "tome" }])
}
/**
* Handles the roll action triggered by user interaction.
*
@ -189,10 +205,15 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
// Debug : console.log(">>>>", event, target, rollType)
// Deprecated : if (this.isEditMode) return
switch (rollType) {
case "resource":
item = foundry.utils.duplicate(this.actor.system.resources)
item.name = game.i18n.localize(`CTHULHUETERNAL.Label.Resources`)
item.targetScore = item.permanentRating
break
case "char":
let charId = $(event.currentTarget).data("char-id")
item = foundry.utils.duplicate(this.actor.system.characteristics[charId])
item.name = game.i18n.localize("CTHULHUETERNAL.Label." + charId + "Long")
item.name = game.i18n.localize(`CTHULHUETERNAL.Label.${charId}Long`)
item.targetScore = item.value * 5
break
case "skill":
@ -203,7 +224,13 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
case "damage":
li = $(event.currentTarget).parents(".item");
item = this.actor.items.get(li.data("item-id"));
item.damageBonus = this.actor.system.damageBonus
break
case "san":
item = foundry.utils.duplicate(this.actor.system.san)
item.name = game.i18n.localize("CTHULHUETERNAL.Label.SAN")
item.targetScore = item.value
break;
default:
throw new Error(`Unknown roll type ${rollType}`)
}
@ -222,5 +249,4 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
}
}
// #endregion
}

View File

@ -0,0 +1,28 @@
import CthulhuEternalItemSheet from "./base-item-sheet.mjs"
export default class CthulhuEternalRitualSheet extends CthulhuEternalItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["ritual"],
position: {
width: 600,
},
window: {
contentClasses: ["ritual-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-cthulhu-eternal/templates/ritual.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}

View File

@ -10,6 +10,10 @@ export default class CthulhuEternalSkillSheet extends CthulhuEternalItemSheet {
window: {
contentClasses: ["skill-content"],
},
actions: {
rollProgress: CthulhuEternalSkillSheet.#onRollProgress,
},
}
/** @override */
@ -22,7 +26,28 @@ export default class CthulhuEternalSkillSheet extends CthulhuEternalItemSheet {
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await TextEditor.enrichHTML(this.document.system.description, { async: true })
context.isGM = game.user.isGM
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
static async #onRollProgress(event, target) {
console.log("Rolling progress for skill", this, event, target)
if (this.actor) {
const roll = await new Roll("1d4").evaluate()
if (roll) {
// Create a chat message with the roll result
const chatData = {
user: game.user.id,
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
content: `<div class="progress-roll">${game.i18n.localize("CTHULHUETERNAL.Label.skillProgress")} - ${this.document.name} +${roll.total}</div>`,
type: CONST.CHAT_MESSAGE_TYPES.ROLL,
roll: roll,
};
await ChatMessage.create(chatData);
this.document.update( {"system.bonus" : this.document.system.bonus + roll.total, "system.rollFailed": false} )
}
}
}
}

View File

@ -0,0 +1,28 @@
import CthulhuEternalItemSheet from "./base-item-sheet.mjs"
export default class CthulhuEternalTomeSheet extends CthulhuEternalItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["tome"],
position: {
width: 600,
},
window: {
contentClasses: ["tome-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-cthulhu-eternal/templates/tome.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}

View File

@ -0,0 +1,117 @@
import CthulhuEternalActorSheet from "./base-actor-sheet.mjs"
export default class CthulhuEternalVehicleSheet extends CthulhuEternalActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["vehicle"],
position: {
width: 680,
height: 540,
},
window: {
contentClasses: ["vehicle-content"],
},
actions: {
createGear: CthulhuEternalVehicleSheet.#onCreateGear,
createWeapon: CthulhuEternalVehicleSheet.#onCreateWeapon,
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-cthulhu-eternal/templates/vehicle-main.hbs",
},
tabs: {
template: "templates/generic/tab-navigation.hbs",
},
equipment: {
template: "systems/fvtt-cthulhu-eternal/templates/vehicle-equipment.hbs",
},
description: {
template: "systems/fvtt-cthulhu-eternal/templates/vehicle-description.hbs",
},
}
/** @override */
tabGroups = {
sheet: "equipment",
}
/**
* Prepare an array of form header tabs.
* @returns {Record<string, Partial<ApplicationTab>>}
*/
#getTabs() {
const tabs = {
equipment: { id: "equipment", group: "sheet", icon: "fa-solid fa-shapes", label: "CTHULHUETERNAL.Label.equipment" },
description: { id: "description", group: "sheet", icon: "fa-solid fa-book", label: "CTHULHUETERNAL.Label.description" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.notes, { async: true })
return context
}
_generateTooltip(type, target) {
}
/** @override */
async _preparePartContext(partId, context) {
const doc = this.document
switch (partId) {
case "main":
break
case "equipment":
context.tab = context.tabs.equipment
context.weapons = doc.itemTypes.weapon
context.gears = doc.itemTypes.gear
break
case "description":
context.tab = context.tabs.description
context.enrichedDescription = await TextEditor.enrichHTML(doc.system.description, { async: true })
context.enrichedNotes = await TextEditor.enrichHTML(doc.system.notes, { async: true })
break
}
return context
}
/**
* Creates a new attack item directly from the sheet and embeds it into the document.
* @param {Event} event The initiating click event.
* @param {HTMLElement} target The current target of the event listener.
*/
static #onCreateGear(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newGear"), type: "gear" }])
}
static #onCreateWeapon(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newWeapon"), type: "weapon" }])
}
async _onDrop(event) {
if (!this.isEditable || !this.isEditMode) return
const data = TextEditor.getDragEventData(event)
// Handle different data types
switch (data.type) {
case "Item":
const item = await fromUuid(data.uuid)
return super._onDropItem(item)
}
}
}

View File

@ -1,6 +1,6 @@
import LethalFantasyItemSheet from "./base-item-sheet.mjs"
import CthulhuEternalItemSheet from "./base-item-sheet.mjs"
export default class LethalFantasyWeaponSheet extends LethalFantasyItemSheet {
export default class CthulhuEternalWeaponSheet extends CthulhuEternalItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["weapon"],

View File

@ -6,16 +6,16 @@ import * as BOND from "./bond.mjs"
export const SYSTEM_ID = "fvtt-cthulhu-eternal"
export const ASCII = `
▄████▄ ▄▄▄█████▓ ██░ ██ █ ██ ██▓ ██░ ██ █ ██ ▓█████▄▄▄█████▓▓█████ ██▀███ ███▄ █ ▄▄▄ ██▓
▒██▀ ▀█ ▓ ██▒ ▓▒▓██░ ██▒ ██ ▓██▒▓██▒ ▓██░ ██▒ ██ ▓██▒ ▓█ ▀▓ ██▒ ▓▒▓█ ▀ ▓██ ▒ ██▒ ██ ▀█ █ ▒████▄ ▓██▒
▒▓█ ▄ ▒ ▓██░ ▒░▒██▀▀██░▓██ ▒██░▒██░ ▒██▀▀██░▓██ ▒██░ ▒███ ▒ ▓██░ ▒░▒███ ▓██ ░▄█ ▒▓██ ▀█ ██▒▒██ ▀█▄ ▒██░
▒▓▓▄ ▄██▒░ ▓██▓ ░ ░▓█ ░██ ▓▓█ ░██░▒██░ ░▓█ ░██ ▓▓█ ░██░ ▒▓█ ▄░ ▓██▓ ░ ▒▓█ ▄ ▒██▀▀█▄ ▓██▒ ▐▌██▒░██▄▄▄▄██ ▒██░
▄████▄ ▄▄▄█████▓ ██░ ██ █ ██ ██▓ ██░ ██ █ ██ ▓█████▄▄▄█████▓▓█████ ██▀███ ███▄ █ ▄▄▄ ██▓
▒██▀ ▀█ ▓ ██▒ ▓▒▓██░ ██▒ ██ ▓██▒▓██▒ ▓██░ ██▒ ██ ▓██▒ ▓█ ▀▓ ██▒ ▓▒▓█ ▀ ▓██ ▒ ██▒ ██ ▀█ █ ▒████▄ ▓██▒
▒▓█ ▄ ▒ ▓██░ ▒░▒██▀▀██░▓██ ▒██░▒██░ ▒██▀▀██░▓██ ▒██░ ▒███ ▒ ▓██░ ▒░▒███ ▓██ ░▄█ ▒▓██ ▀█ ██▒▒██ ▀█▄ ▒██░
▒▓▓▄ ▄██▒░ ▓██▓ ░ ░▓█ ░██ ▓▓█ ░██░▒██░ ░▓█ ░██ ▓▓█ ░██░ ▒▓█ ▄░ ▓██▓ ░ ▒▓█ ▄ ▒██▀▀█▄ ▓██▒ ▐▌██▒░██▄▄▄▄██ ▒██░
▒ ▓███▀ ░ ▒██▒ ░ ░▓█▒░██▓▒▒█████▓ ░██████▒░▓█▒░██▓▒▒█████▓ ░▒████▒ ▒██▒ ░ ░▒████▒░██▓ ▒██▒▒██░ ▓██░ ▓█ ▓██▒░██████▒
░ ░▒ ▒ ░ ▒ ░░ ▒ ░░▒░▒░▒▓▒ ▒ ▒ ░ ▒░▓ ░ ▒ ░░▒░▒░▒▓▒ ▒ ▒ ░░ ▒░ ░ ▒ ░░ ░░ ▒░ ░░ ▒▓ ░▒▓░░ ▒░ ▒ ▒ ▒▒ ▓▒█░░ ▒░▓ ░
░ ▒ ░ ▒ ░▒░ ░░░▒░ ░ ░ ░ ░ ▒ ░ ▒ ░▒░ ░░░▒░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░▒ ░ ▒░░ ░░ ░ ▒░ ▒ ▒▒ ░░ ░ ▒ ░
░ ░ ░ ░░ ░ ░░░ ░ ░ ░ ░ ░ ░░ ░ ░░░ ░ ░ ░ ░ ░ ░░ ░ ░ ░ ░ ░ ▒ ░ ░
░ ░ ░ ░░ ░ ░░░ ░ ░ ░ ░ ░ ░░ ░ ░░░ ░ ░ ░ ░ ░ ░░ ░ ░ ░ ░ ░ ▒ ░ ░
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
`
@ -34,19 +34,26 @@ export const AVAILABLE_SETTINGS = {
postapo: "CTHULHUETERNAL.Settings.PostApo"
}
export const INSANITY = {
"none": "CTHULHUETERNAL.Insanity.None",
"flee": "CTHULHUETERNAL.Insanity.Flee",
"struggle": "CTHULHUETERNAL.Insanity.Struggle",
"submit": "CTHULHUETERNAL.Insanity.Submit"
}
export const ERA_CSS = {
jazz: { primaryFont: "RozhaOne", secondaryFont: "RozhaOne", titleFont: "Broadway", imgFilter: "brightness(0) saturate(100%) invert(52%) sepia(9%) saturate(2368%) hue-rotate(360deg) brightness(86%) contrast(84%)" },
modern: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "Georama", imgFilter: "brightness(0) saturate(100%) invert(92%) sepia(11%) saturate(1214%) hue-rotate(51deg) brightness(93%) contrast(86%)" },
future: { primaryFont: "Megrim", secondaryFont: "Megrim", titleFont: "Seabreed", imgFilter: "brightness(0) saturate(100%) invert(83%) sepia(30%) saturate(588%) hue-rotate(168deg) brightness(105%) contrast(103%)" },
victorian: { primaryFont: "Volkhov", secondaryFont: "Volkhov", titleFont: "Excelsior", imgFilter: "brightness(0) saturate(100%) invert(100%) sepia(59%) saturate(1894%) hue-rotate(337deg) brightness(88%) contrast(98%)" },
coldwar: { primaryFont: "BebasNeue", secondaryFont: "BebasNeue", titleFont: "TopSecret", imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(14%) saturate(2508%) hue-rotate(202deg) brightness(99%) contrast(105%)"},
revolution: { primaryFont: "IMFell", secondaryFont: "IMFell", titleFont: "Dominican", imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(25%) saturate(386%) hue-rotate(7deg) brightness(101%) contrast(84%)" },
medieval: { primaryFont: "UncialAntiqua", secondaryFont: "UncialAntiqua", titleFont: "Luminari", imgFilter: "brightness(0) saturate(100%) invert(93%) sepia(46%) saturate(354%) hue-rotate(321deg) brightness(93%) contrast(87%)"},
ww2: { primaryFont: "SairaStencilOne", secondaryFont: "SairaStencilOne", titleFont: "Armalite", imgFilter: "brightness(0) saturate(100%) invert(95%) sepia(9%) saturate(1471%) hue-rotate(342deg) brightness(103%) contrast(107%)"},
ww1: { primaryFont: "CarterOne", secondaryFont: "CarterOne", titleFont: "SigmarOne", imgFilter: "brightness(0) saturate(100%) invert(90%) sepia(38%) saturate(341%) hue-rotate(21deg) brightness(105%) contrast(105%)"},
ageofsail: { primaryFont: "Tangerine", secondaryFont: "Tangerine", titleFont: "P22Operina", imgFilter: "brightness(0) saturate(100%) invert(43%) sepia(74%) saturate(3154%) hue-rotate(336deg) brightness(95%) contrast(83%)" },
classical: { primaryFont: "SpectralSC", secondaryFont: "SpectralSC", titleFont: "TrajanPro", imgFilter: "brightness(0) saturate(100%) invert(52%) sepia(32%) saturate(7492%) hue-rotate(265deg) brightness(89%) contrast(95%)" },
postapo: { primaryFont: "Teko", secondaryFont: "Teko", titleFont: "Teko", imgFilter: "brightness(0) saturate(100%) invert(44%) sepia(55%) saturate(2341%) hue-rotate(329deg) brightness(122%) contrast(103%))" }
jazz: { primaryFont: "RozhaOne", secondaryFont: "RozhaOne", titleFont: "Broadway", baseFontSize: "0.95rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(52%) sepia(9%) saturate(2368%) hue-rotate(360deg) brightness(86%) contrast(84%)" },
modern: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "Georama", baseFontSize: "1.0rem", titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(92%) sepia(11%) saturate(1214%) hue-rotate(51deg) brightness(93%) contrast(86%)" },
future: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "Seabreed", baseFontSize: "1.0rem", titleFontSize: "2.0rem",imgFilter: "invert(90%) sepia(6%) saturate(1818%) hue-rotate(152deg) brightness(91%) contrast(91%)" },
victorian: { primaryFont: "Volkhov", secondaryFont: "Volkhov", titleFont: "Excelsior", baseFontSize: "1.0rem", titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(100%) sepia(59%) saturate(1894%) hue-rotate(337deg) brightness(88%) contrast(98%)" },
coldwar: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "TopSecret", baseFontSize: "1.0rem", titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(14%) saturate(2508%) hue-rotate(202deg) brightness(99%) contrast(105%)"},
revolution: { primaryFont: "IMFell", secondaryFont: "IMFell", titleFont: "Dominican", baseFontSize: "1.0rem",titleFontSize: "1.3rem",imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(25%) saturate(386%) hue-rotate(7deg) brightness(101%) contrast(84%)" },
medieval: { primaryFont: "Skranji", secondaryFont: "UncialAntiqua", titleFont: "Luminari", baseFontSize: "0.9rem",titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(93%) sepia(46%) saturate(354%) hue-rotate(321deg) brightness(93%) contrast(87%)"},
ww2: { primaryFont: "SairaStencilOne", secondaryFont: "SairaStencilOne", titleFont: "Armalite", baseFontSize: "0.9rem",titleFontSize: "1.2rem",imgFilter: "filter: invert(44%) sepia(8%) saturate(2657%) hue-rotate(40deg) brightness(96%) contrast(75%)"},
ww1: { primaryFont: "CarterOne", secondaryFont: "CarterOne", titleFont: "SigmarOne", baseFontSize: "0.9rem",titleFontSize: "1.1rem",imgFilter: "invert(28%) sepia(27%) saturate(475%) hue-rotate(76deg) brightness(95%) contrast(93%)"},
ageofsail: { primaryFont: "SailRegular", secondaryFont: "SailRegular", titleFont: "P22Operina", baseFontSize: "1.1rem",titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(43%) sepia(74%) saturate(3154%) hue-rotate(336deg) brightness(95%) contrast(83%)" },
classical: { primaryFont: "ChantelliAntiqua", secondaryFont: "ChantelliAntiqua", titleFont: "TrajanPro", baseFontSize: "0.9rem",titleFontSize: "1.1rem",imgFilter: "brightness(0) saturate(100%) invert(52%) sepia(32%) saturate(7492%) hue-rotate(265deg) brightness(89%) contrast(95%)" },
postapo: { primaryFont: "Teko", secondaryFont: "Teko", titleFont: "Teko", baseFontSize: "1.35rem",titleFontSize: "1.5rem",imgFilter: "brightness(0) saturate(100%) invert(44%) sepia(55%) saturate(2341%) hue-rotate(329deg) brightness(122%) contrast(103%))" }
}
export const RESOURCE_RATING = {
@ -82,6 +89,45 @@ export const RESOURCE_RATING = {
}
}
export const RESOURCE_BREAKDOWN = [
{ value: 0, hand: 0, stowed: 0, storage: 0, checks: 0},
{ value: 1, hand: 1, stowed: 0, storage: 0, checks: 1},
{ value: 2, hand: 2, stowed: 0, storage: 0, checks: 1},
{ value: 3, hand: 3, stowed: 0, storage: 0, checks: 1},
{ value: 4, hand: 4, stowed: 0, storage: 0, checks: 1},
{ value: 5, hand: 5, stowed: 0, storage: 0, checks: 1},
{ value: 6, hand: 6, stowed: 0, storage: 0, checks: 1},
{ value: 7, hand: 6, stowed: 1, storage: 0, checks: 2},
{ value: 8, hand: 6, stowed: 2, storage: 0, checks: 2},
{ value: 9, hand: 6, stowed: 3, storage: 0, checks: 2},
{ value: 10, hand: 6, stowed: 4, storage: 0, checks: 2},
{ value: 11, hand: 6, stowed: 5, storage: 0, checks: 2},
{ value: 12, hand: 6, stowed: 6, storage: 0, checks: 2},
{ value: 13, hand: 6, stowed: 6, storage: 1, checks: 3},
{ value: 14, hand: 6, stowed: 6, storage: 2, checks: 3},
{ value: 15, hand: 6, stowed: 6, storage: 3, checks: 3},
{ value: 16, hand: 6, stowed: 6, storage: 4, checks: 3},
{ value: 17, hand: 6, stowed: 6, storage: 5, checks: 3},
{ value: 18, hand: 6, stowed: 6, storage: 6, checks: 3},
{ value: 19, hand: 6, stowed: 6, storage: 7, checks: 3},
{ value: 20, hand: 6, stowed: 6, storage: 8, checks: 3}
]
export const DAMAGE_BONUS = [ -2, -2, -2, -2, -2, -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]
export const VEHICLE_SPEED = {
"none": "CTHULHUETERNAL.Label.None",
"slow": "CTHULHUETERNAL.Label.Slow",
"average": "CTHULHUETERNAL.Label.Average",
"fast": "CTHULHUETERNAL.Label.Fast"
}
export const EQUIPMENT_STATES = {
"pristine": "CTHULHUETERNAL.Label.Pristine",
"worn": "CTHULHUETERNAL.Label.Worn",
"junk": "CTHULHUETERNAL.Label.Junk"
}
export const MENTAL_ILLNESS_CURE_SKILL = {
jazz: "CTHULHUETERNAL.Skill.Psychoanalyze",
modern: "CTHULHUETERNAL.Skill.Psychoanalyze",
@ -96,6 +142,13 @@ export const WEAPON_SKILL_MAPPING = {
"rangedfirearm": "CTHULHUETERNAL.Skill.Firearms",
"unarmed": "CTHULHUETERNAL.Skill.UnarmedCombat"
},
postapo: {
"melee": "CTHULHUETERNAL.Skill.Melee",
"rangedprimitive": "CTHULHUETERNAL.Skill.Firearms",
"rangedthrown": "CTHULHUETERNAL.Skill.Athletics",
"rangedfirearm": "CTHULHUETERNAL.Skill.Firearms",
"unarmed": "CTHULHUETERNAL.Skill.UnarmedCombat"
},
jazz: {
"melee": "CTHULHUETERNAL.Skill.Melee",
"rangedprimitive": "CTHULHUETERNAL.Skill.Firearms",
@ -165,6 +218,40 @@ export const WEAPON_SKILL_MAPPING = {
"unarmed": "CTHULHUETERNAL.Skill.UnarmedCombat"
}
}
export const MODIFIER_CHOICES = {
"-40": "-40",
"-20": "-20",
"-10": "-10",
"+0": "+0",
"+10": "+10",
"+20": "+20",
"+40": "+40",
}
export const MULTIPLIER_CHOICES = {
"0.25": "0.25",
"0.5": "0.5",
"1": "1",
"2": "2",
"4": "4",
"5": "5"
}
export const WEAPON_SELECTIVE_FIRE_CHOICES = {
"shortburst": { id: "shortburst", label: "CTHULHUETERNAL.Weapon.SelectiveFire.shortburst", ammoUsed: 3, lethality: 10, killRadius: 0},
"longburst": { id: "longburst", label: "CTHULHUETERNAL.Weapon.SelectiveFire.longburst", ammoUsed: 5, lethality: 10, killRadius: 1},
"shortspray": { id: "shortspray", label: "CTHULHUETERNAL.Weapon.SelectiveFire.shortspray", ammoUsed: 10, lethality: 10, killRadius: 2},
"longspray": { id: "longspray", label: "CTHULHUETERNAL.Weapon.SelectiveFire.longspray", ammoUsed: 20, lethality: 10, killRadius: 3},
}
export const RITUAL_TYPES = {
"simple": "CTHULHUETERNAL.Ritual.Simple",
"difficult": "CTHULHUETERNAL.Ritual.Difficult",
"complex": "CTHULHUETERNAL.Ritual.Complex",
"elaborate": "CTHULHUETERNAL.Ritual.Elaborate"
}
/**
* Include all constant definitions within the SYSTEM global export
* @type {Object}
@ -175,11 +262,20 @@ export const SYSTEM = {
HARSHNESS: PROTAGONIST.HARSHNESS,
WEAPON_TYPE: WEAPON.WEAPON_TYPE,
WEAPON_SUBTYPE: WEAPON.WEAPON_SUBTYPE,
WEAPON_SELECTIVE_FIRE_CHOICES,
WEAPON_SKILL_MAPPING,
BOND_TYPE: BOND.BOND_TYPE,
AVAILABLE_SETTINGS,
RESOURCE_RATING,
MENTAL_ILLNESS_CURE_SKILL,
ERA_CSS,
ASCII
INSANITY,
EQUIPMENT_STATES,
RESOURCE_BREAKDOWN,
VEHICLE_SPEED,
MODIFIER_CHOICES,
MULTIPLIER_CHOICES,
ASCII,
DAMAGE_BONUS,
RITUAL_TYPES
}

View File

@ -17,4 +17,4 @@ export const WEAPON_SUBTYPE = {
export const WEAPON_RANGE_UNIT = {
"yard": "CTHULHUETERNAL.Weapon.RangeUnit.yard",
"meter": "CTHULHUETERNAL.Weapon.RangeUnit.meter"
}
}

View File

@ -1,7 +1,7 @@
import CthulhuEternalUtils from "../utils.mjs"
export default class CthulhuEternalActor extends Actor {
static async create(data, options) {
// Case of compendium global import
@ -19,29 +19,71 @@ export default class CthulhuEternalActor extends Actor {
const skills = await CthulhuEternalUtils.loadCompendium("fvtt-cthulhu-eternal.skills")
data.items = data.items || []
for (let skill of skills) {
if (skill.system.settings === era ) {
if (skill.system.settings === era) {
data.items.push(skill.toObject())
}
}
data.items.push({ type:"weapon", img: "systems/fvtt-cthulhu-eternal/assets/icons/icon_fist.svg",
name: game.i18n.localize("CTHULHUETERNAL.Label.Unarmed"), system: { damage: "1d4-1", weaponType: "unarmed" } })
}
return super.create(data, options);
}
async _preCreate(data, options, user) {
await super._preCreate(data, options, user)
// Configure prototype token settings
const prototypeToken = {}
if (this.type === "protagonist") {
Object.assign(prototypeToken, {
sight: { enabled: true },
actorLink: true,
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY,
_onUpdate(changed, options, userId) {
// DEBUG : console.log("CthulhuEternalActor.update", changed, options, userId)
if (changed?.system?.wp?.exhausted) {
ChatMessage.create({
user: userId,
speaker: { alias: this.name },
rollMode: "selfroll",
content: game.i18n.localize("CTHULHUETERNAL.ChatMessage.exhausted"),
type: CONST.CHAT_MESSAGE_STYLES.OTHER
})
this.updateSource({ prototypeToken })
}
return super._onUpdate(changed, options, userId)
}
async createEmbeddedDocuments(embeddedName, data, operation) {
let newData = []
if (embeddedName === "Item") {
for (let i of data) {
if (i.type === "skill") {
if (this.items.find(item => item.name.toLowerCase() === i.name.toLowerCase())) {
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.skillAlreadyExists"))
continue
}
}
if (i.type === "bond") {
if (!i.system?.bondType) {
return super.createEmbeddedDocuments(embeddedName, data, operation)
}
if (i.system.bondType === "individual") {
i.system.value = this.system.characteristics.cha.value
} else {
i.system.value = Math.floor(this.system.resources.permanentRating / 2)
}
}
newData.push(i)
}
return super.createEmbeddedDocuments(embeddedName, newData, operation)
}
return super.createEmbeddedDocuments(embeddedName, data, operation)
}
async _preCreate(data, options, user) {
await super._preCreate(data, options, user)
// Configure prototype token settings
const prototypeToken = {}
if (this.type === "protagonist") {
Object.assign(prototypeToken, {
sight: { enabled: true },
actorLink: true,
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY,
})
this.updateSource({ prototypeToken })
}
}
}

View File

@ -1,7 +1,7 @@
export const defaultItemImg = {
weapon: "systems/fvtt-cthulhu-eternal/assets/icons/icon_weapon.svg",
armor: "systems/fvtt-cthulhu-eternal/assets/icons/icon_armor.svg",
gear: "systems/fvtt-cthulhu-eternal/assets/icons/icon_gear.svg",
gear: "systems/fvtt-cthulhu-eternal/assets/icons/icon_equipment.svg",
skill: "systems/fvtt-cthulhu-eternal/assets/icons/icon_skill.svg",
archetype: "systems/fvtt-cthulhu-eternal/assets/icons/icon_archetype.svg",
bond: "systems/fvtt-cthulhu-eternal/assets/icons/icon_bond.svg",
@ -9,6 +9,8 @@ export const defaultItemImg = {
arcane: "systems/fvtt-cthulhu-eternal/assets/icons/icon_arcane.svg",
injury: "systems/fvtt-cthulhu-eternal/assets/icons/icon_injury.svg",
motivation: "systems/fvtt-cthulhu-eternal/assets/icons/icon_motivation.svg",
ritual: "systems/fvtt-cthulhu-eternal/assets/icons/icon_ritual.svg",
tome: "systems/fvtt-cthulhu-eternal/assets/icons/icon_tome.svg",
}
export default class CthulhuEternalItem extends Item {

View File

@ -1,5 +1,6 @@
import { SYSTEM } from "../config/system.mjs"
export default class CthulhuEternalRoll extends Roll {
/**
* The HTML template path used to render dice checks of this type
@ -59,18 +60,6 @@ export default class CthulhuEternalRoll extends Roll {
return this.options.hasTarget
}
get targetName() {
return this.options.targetName
}
get targetArmor() {
return this.options.targetArmor
}
get targetMalus() {
return this.options.targetMalus
}
get realDamage() {
return this.options.realDamage
}
@ -79,6 +68,43 @@ export default class CthulhuEternalRoll extends Roll {
return this.options.weapon
}
get isLowWP() {
return this.options.isLowWP
}
get isZeroWP() {
return this.options.isZeroWP
}
get isExhausted() {
return this.options.isExhausted
}
get isNudgedRoll() {
return this.options.isNudgedRoll
}
get wpCost() {
return this.options.wpCost
}
static updateResourceDialog(options) {
let rating = 0
if (options.rollItem.enableHand) {
rating += options.rollItem.hand
}
if (options.rollItem.enableStowed) {
rating += options.rollItem.stowed
}
if (options.rollItem.enableStorage) {
rating += options.rollItem.storage
}
let multiplier = Number($(`.roll-skill-multiplier`).val())
options.initialScore = rating
options.percentScore = rating * multiplier
$(".resource-score").text(`${rating} (${options.percentScore}%)`)
}
/**
* Prompt the user with a dialog to configure and execute a roll.
*
@ -95,85 +121,141 @@ export default class CthulhuEternalRoll extends Roll {
*/
static async prompt(options = {}) {
let formula = "1d100"
let hasModifier = true
let hasMultiplier = false
options.isNudge = true
switch (options.rollType) {
case "skill":
console.log(options.rollItem)
options.initialScore = options.rollItem.system.computeScore()
break
case "san":
case "char":
options.initialScore = options.rollItem.targetScore
options.isNudge = (options.rollType !== "san")
break
case "damage":
let formula = options.rollItem.system.damage
case "resource":
hasModifier = false
hasMultiplier = true
options.initialScore = options.rollItem.targetScore
options.totalRating = options.rollItem.targetScore
options.percentScore = options.rollItem.targetScore * 5
options.rollItem.enableHand = true
options.rollItem.enableStowed = true
options.rollItem.enableStorage = true
options.isNudge = false
break
case "damage": {
let isLethal = false
options.isNudge = false
if (options.rollItem.system.lethality > 0) {
let lethalityRoll = new Roll("1d100")
await lethalityRoll.evaluate()
let lethalScore = (options?.previousResultType === "successCritical") ? options.rollItem.system.lethality * 2 : options.rollItem.system.lethality
isLethal = (lethalityRoll.total <= lethalScore)
let flavor = `${options.rollItem.name} - <strong> ${game.i18n.localize("CTHULHUETERNAL.Label.lethalityRoll")} </strong> : ${lethalityRoll.total} <= ${lethalScore} => ${isLethal}`
if (isLethal) {
flavor += `<br> ${game.i18n.localize("CTHULHUETERNAL.Label.lethalityWounded")} => HP = 0`
} else {
let wounds = Math.floor(lethalityRoll.total / 10) + (lethalityRoll.total % 10)
flavor += `<br> ${game.i18n.localize("CTHULHUETERNAL.Label.lethalityNotWounded")} => HP loss = ${wounds}`
}
await lethalityRoll.toMessage({
flavor: flavor
});
return
}
let formula = options.rollItem.system.damage
if (options.rollItem.system.weaponType === "melee" || options.rollItem.system.weaponType === "unarmed") {
formula += ` + ${options.rollItem.damageBonus}`
}
if (options?.previousResultType === "successCritical") {
formula = `( ${formula} ) * 2`
}
let damageRoll = new Roll(formula)
await damageRoll.evaluate()
await damageRoll.toMessage({
flavor: `${options.rollItem.name} - Damage Roll`
flavor: `${options.rollItem.name} - ${game.i18n.localize("CTHULHUETERNAL.Label.damageRoll")}`
});
let isLethal = false
if (options.rollItem.system.lethality > 0 ) {
let lethalityRoll = new Roll("1d100")
await lethalityRoll.evaluate()
isLethal = (lethalityRoll.total <= options.rollItem.system.lethality)
await lethalityRoll.toMessage({
flavor: `${options.rollItem.name} - Lethality Roll : ${lethalityRoll.total} <= ${options.rollItem.system.lethality} => ${isLethal}`
});
}
}
return
case "weapon":
case "weapon": {
let era = game.settings.get("fvtt-cthulhu-eternal", "settings-era")
let skillName = game.i18n.localize(SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType])
let actor = game.actors.get(options.actorId)
if (era !== options.rollItem.system.settings) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.WrongEra"))
console.log("WP Wrong Era", era, options.rollItem.system.weaponType)
return
}
if (!SYSTEM.WEAPON_SKILL_MAPPING[era]?.[options.rollItem.system.weaponType]) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponType"))
console.log("WP Not found", era, options.rollItem.system.weaponType)
return
}
options.weapon = options.rollItem
options.rollItem = actor.items.find(i => i.type === "skill" && i.name.toLowerCase() === skillName.toLowerCase())
options.initialScore = options.rollItem.system.computeScore()
console.log("WEAPON", skillName, era, options.rollItem)
if (options.rollItem.system.hasDirectSkill) {
let skillName = options.rollItem.name
options.rollItem = { type: "skill", name: skillName, system: { base: 0, bonus: options.weapon.system.directSkillValue } }
options.initialScore = options.weapon.system.directSkillValue
} else {
let skillName = game.i18n.localize(SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType])
let actor = game.actors.get(options.actorId)
options.rollItem = actor.items.find(i => i.type === "skill" && i.name.toLowerCase() === skillName.toLowerCase())
if (!options.rollItem) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponSkill"))
return
}
options.initialScore = options.rollItem.system.computeScore()
console.log("WEAPON", skillName, era, options.rollItem)
}
}
break
default:
options.initialScore = 50
break
}
const rollModes = Object.fromEntries(Object.entries(CONFIG.Dice.rollModes).map(([key, value]) => [key, game.i18n.localize(value)]))
console.log("Roll options", CONFIG.Dice.rollModes);
const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes); //Object.fromEntries(Object.entries(CONFIG.Dice.rollModes).map(([key, value]) => [key, game.i18n.localize(value)]))
const fieldRollMode = new foundry.data.fields.StringField({
choices: rollModes,
blank: false,
default: "public",
})
const choiceModifier = {
"-10": "-10",
"-20": "-20",
"-40": "-40",
"0": "0",
"+10": "+10",
"+20": "+20",
"+40": "+40",
}
const choiceModifier = SYSTEM.MODIFIER_CHOICES
const choiceMultiplier = SYSTEM.MULTIPLIER_CHOICES
const choiceSelectiveFire = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES
let modifier = "0"
let targetMalus = "0"
let targetName
let targetArmor
let modifier = "+0"
let multiplier = "5"
let dialogContext = {
rollType: options.rollType,
rollItem: foundry.utils.duplicate(options.rollItem), // Object only, no class
weapon: options?.weapon,
weapon: options?.weapon,
initialScore: options.initialScore,
targetScore: options.initialScore,
isLowWP: options.isLowWP,
isZeroWP: options.isZeroWP,
isExhausted: options.isExhausted,
enableHand: options.rollItem.enableHand,
enableStowed: options.rollItem.enableStowed,
enableStorage: options.rollItem.enableStorage,
rollModes,
fieldRollMode,
choiceModifier,
choiceMultiplier,
choiceSelectiveFire,
formula,
hasTarget: options.hasTarget,
hasModifier,
hasMultiplier,
modifier,
targetName,
targetArmor
selectiveFireChoice: "shortburst",
multiplier
}
const content = await renderTemplate("systems/fvtt-cthulhu-eternal/templates/roll-dialog.hbs", dialogContext)
const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/roll-dialog.hbs", dialogContext)
const title = CthulhuEternalRoll.createTitle(options.rollType, options.rollTarget)
const label = game.i18n.localize("CTHULHUETERNAL.Roll.roll")
@ -193,69 +275,112 @@ export default class CthulhuEternalRoll extends Roll {
},
},
],
actions: {
"selectHand": (event, button, dialog) => {
options.rollItem.enableHand = !options.rollItem.enableHand
this.updateResourceDialog(options)
},
"selectStowed": (event, button, dialog) => {
options.rollItem.enableStowed = !options.rollItem.enableStowed
this.updateResourceDialog(options)
},
"selectStorage": (event, button, dialog) => {
options.rollItem.enableStorage = !options.rollItem.enableStorage
this.updateResourceDialog(options)
}
},
rejectClose: false, // Click on Close button will not launch an error
render: (event, dialog) => {
},
$(".roll-skill-multiplier").change(event => {
options.multiplier = Number(event.target.value)
this.updateResourceDialog(options)
})
}
})
// If the user cancels the dialog, exit
if (rollContext === null) return
let rollData = foundry.utils.mergeObject(foundry.utils.duplicate(options), rollContext)
rollData.rollMode = rollContext.visibility
rollData.targetName = targetName
rollData.targetArmor = targetArmor
rollData.targetMalus = targetMalus
rollData.rollMode = rollContext.visibility
// Update target score
console.log(rollData)
rollData.targetScore = Math.min( Math.max(options.initialScore + Number(rollData.modifier), 0), 100)
if ( rollData.isLowWP ) {
rollData.targetScore -= 20
console.log("Rolldata", rollData, options)
if (options.rollType === "resource") {
rollData.targetScore = options.initialScore * Number(rollContext.multiplier)
} else {
rollData.targetScore = Math.min(Math.max(options.initialScore + Number(rollData.modifier), 0), 100)
if (rollData.isLowWP || rollData.isExhausted) {
rollData.targetScore -= 20
}
if (rollData.isZeroWP) {
rollData.targetScore = 0
}
rollData.targetScore = Math.min(Math.max(rollData.targetScore, 0), 100)
}
if ( rollData.isZeroWP ) {
rollData.targetScore = 0
}
rollData.targetScore = Math.min( Math.max(rollData.targetScore, 0), 100)
/**
* A hook event that fires before the roll is made.
*/
if (Hooks.call("fvtt-cthulhu-eternal.preRoll", options, rollData) === false) return
const roll = new this(formula, options.data, rollData)
await roll.evaluate()
// Compute the result quality
let resultType = "failure"
let dec = Math.floor(roll.total/10)
let unit = roll.total - (dec*10)
if (roll.total <= rollData.targetScore) {
resultType = "success"
// Detect if decimal == unit in the dire total result
if (dec === unit || roll.total === 1) {
resultType = "successCritical"
}
} else {
// Detect if decimal == unit in the dire total result
if (dec === unit || roll.total === 100) {
resultType = "failureCritical"
}
}
roll.displayRollResult(roll, options, rollData)
roll.options.resultType = resultType
roll.options.isSuccess = resultType === "success" || resultType === "successCritical"
roll.options.isFailure = resultType === "failure" || resultType === "failureCritical"
roll.options.isCritical = resultType === "successCritical" || resultType === "failureCritical"
/**
* A hook event that fires after the roll has been made.
*/
if (Hooks.call("fvtt-cthulhu-eternal.Roll", options, rollData, roll) === false) return
return roll
}
displayRollResult(formula, options, rollData) {
// Compute the result quality
let resultType = "failure"
let dec = Math.floor(this.total / 10)
let unit = this.total - (dec * 10)
if (this.total <= rollData.targetScore) {
resultType = "success"
// Detect if decimal == unit in the dire total result
if (dec === unit || this.total === 1) {
resultType = "successCritical"
}
} else {
// Detect if decimal == unit in the dire total result
if (dec === unit || this.total === 100) {
resultType = "failureCritical"
}
}
// As per the rules, a roll of 100 is always a failure, even if the target is above 100
if (this.total === 100) {
resultType = "failureCritical"
}
// A roll of 1 is always a critical success, even if the target is 1
if (this.total === 1) {
resultType = "successCritical"
}
if (rollData.targetScore <= 0) {
resultType = "failure"
if (this.total === 1) {
resultType = "success"
}
}
this.options.resultType = resultType
if (this.options.isNudgedRoll) {
this.options.isSuccess = resultType === "success" || resultType === "successCritical"
this.options.isFailure = resultType === "failure" || resultType === "failureCritical"
this.options.isCritical = false
} else {
this.options.isSuccess = resultType === "success" || resultType === "successCritical"
this.options.isFailure = resultType === "failure" || resultType === "failureCritical"
this.options.isCritical = resultType === "successCritical" || resultType === "failureCritical"
}
rollData.resultType = resultType
this.options.isLowWP = rollData.isLowWP
this.options.isZeroWP = rollData.isZeroWP
this.options.isExhausted = rollData.isExhausted
this.options.rollData = foundry.utils.duplicate(rollData)
}
/**
* Creates a title based on the given type.
*
@ -269,6 +394,12 @@ export default class CthulhuEternalRoll extends Roll {
return `${game.i18n.localize("CTHULHUETERNAL.Label.titleSkill")}`
case "weapon":
return `${game.i18n.localize("CTHULHUETERNAL.Label.titleWeapon")}`
case "char":
return `${game.i18n.localize("CTHULHUETERNAL.Label.titleCharacteristic")}`
case "san":
return `${game.i18n.localize("CTHULHUETERNAL.Label.titleSAN")}`
case "resource":
return `${game.i18n.localize("CTHULHUETERNAL.Label.titleResource")}`
default:
return game.i18n.localize("CTHULHUETERNAL.Label.titleStandard")
}
@ -277,7 +408,7 @@ export default class CthulhuEternalRoll extends Roll {
/** @override */
async render(chatOptions = {}) {
let chatData = await this._getChatCardData(chatOptions.isPrivate)
return await renderTemplate(this.constructor.CHAT_TEMPLATE, chatData)
return await foundry.applications.handlebars.renderTemplate(this.constructor.CHAT_TEMPLATE, chatData)
}
/**
@ -306,10 +437,10 @@ export default class CthulhuEternalRoll extends Roll {
*/
async _getChatCardData(isPrivate) {
let cardData = foundry.utils.duplicate(this.options)
cardData.css = [SYSTEM.id, "dice-roll"]
cardData.data = this.data
cardData.diceTotal = this.dice.reduce((t, d) => t + d.total, 0)
cardData.isGM = game.user.isGM
cardData.css = [SYSTEM.id, "dice-roll"]
cardData.data = this.data
cardData.diceTotal = this.dice.reduce((t, d) => t + d.total, 0)
cardData.isGM = game.user.isGM
cardData.formula = this.formula
cardData.total = this.total
cardData.actorId = this.actorId
@ -322,8 +453,11 @@ export default class CthulhuEternalRoll extends Roll {
cardData.realDamage = this.realDamage
cardData.isPrivate = isPrivate
cardData.weapon = this.weapon
console.log(cardData)
cardData.isLowWP = this.isLowWP
cardData.isZeroWP = this.isZeroWP
cardData.isExhausted = this.isExhausted
cardData.isNudgedRoll = this.isNudgedRoll
cardData.wpCost = this.wpCost
cardData.cssClass = cardData.css.join(" ")
cardData.tooltip = isPrivate ? "" : await this.getTooltip()
@ -346,14 +480,38 @@ export default class CthulhuEternalRoll extends Roll {
actingCharName: this.actorName,
actingCharImg: this.actorImage,
hasTarget: this.hasTarget,
targetName: this.targetName,
targetArmor: this.targetArmor,
targetMalus: this.targetMalus,
realDamage: this.realDamage,
...messageData,
},
{ rollMode: rollMode },
)
console.log("Roll to message", this.options, this.options.rollData, this.options.rollItem)
let rollData = this.options.rollData || this.options
let rollItem = this.options.rollItem
if (rollData.resultType.includes("failure") && rollItem.type === "skill") {
// Is the skill able to progress
if (rollItem.system.diceEvolved && !rollItem.system.rollFailed) {
// If the skill is not adversary, we can evolve it
if (!rollItem.system.isAdversary) {
rollItem.system.rollFailed = true
// Get the actor and update the skill
const actor = game.actors.get(rollData.actorId)
await actor.updateEmbeddedDocuments("Item", [{
_id: rollItem._id,
"system.rollFailed": true
}])
// Create a chat message to inform the user
const flavor = `${rollItem.name} - ${game.i18n.localize("CTHULHUETERNAL.Label.skillFailed")}`
await ChatMessage.create({
user: game.user.id,
content: `<div class="cthulhu-eternal-roll"><p>${flavor}</p></div>`,
speaker: ChatMessage.getSpeaker({ actor: rollData.actor }),
}, { rollMode: rollData.rollMode, create: true })
}
}
}
}
}

View File

@ -1,80 +0,0 @@
/**
* Enricher qui permet de transformer un texte en un lien de lancer de dés
* Pour une syntaxe de type @jet[x]{y}(z) avec x la caractéristique, y le titre et z l'avantage
* x de type rob, dex, int, per, vol pour les caractéristiques
* et de type oeil, verbe, san, bourse, magie pour les ressources
* y est le titre du jet et permet de décrire l'action
* z est l'avantage du jet, avec pour valeurs possibles : --, -, +, ++
*/
export function setupTextEnrichers() {
CONFIG.TextEditor.enrichers = CONFIG.TextEditor.enrichers.concat([
{
// eslint-disable-next-line no-useless-escape
pattern: /\@jet\[(.+?)\]{(.*?)}\((.*?)\)/gm,
enricher: async (match, options) => {
const a = document.createElement("a")
a.classList.add("ask-roll-journal")
const target = match[1]
const title = match[2]
const avantage = match[3]
let type = "resource"
if (["rob", "dex", "int", "per", "vol"].includes(target)) {
type = "save"
}
let rollAvantage = "normal"
if (avantage) {
switch (avantage) {
case "++":
rollAvantage = "++"
break
case "+":
rollAvantage = "+"
break
case "-":
rollAvantage = "-"
break
case "--":
rollAvantage = "--"
break
default:
break
}
}
a.dataset.rollType = type
a.dataset.rollTarget = target
a.dataset.rollTitle = title
a.dataset.rollAvantage = rollAvantage
a.innerHTML = `
<i class="fas fa-dice-d20"></i> ${getLibelle(target)}${rollAvantage !== "normal" ? rollAvantage : ""}
`
return a
},
},
])
}
const mapLibelles = {
rob: "ROB",
dex: "DEX",
int: "INT",
per: "PER",
vol: "VOL",
oeil: "OEIL",
verbe: "VERBE",
san: "SANTE MENTALE",
bourse: "BOURSE",
magie: "MAGIE",
}
/**
* Retourne le libellé associé à la valeur qui sera affiché dans le journal
* @param {string} value
*/
function getLibelle(value) {
if (mapLibelles[value]) {
return mapLibelles[value]
}
return null
}

View File

@ -1,82 +0,0 @@
export class Macros {
/**
* Creates a macro based on the type of data dropped onto the hotbar.
*
* @param {Object} dropData The data object representing the item dropped.
* @param {string} dropData.type The type of the dropped item (e.g., "Actor", "JournalEntry", "roll").
* @param {string} dropData.uuid The UUID of the dropped item.
* @param {string} [dropData.actorId] The ID of the actor (required if type is "roll").
* @param {string} [dropData.rollType] The type of roll (required if type is "roll").
* @param {string} [dropData.rollTarget] The target of the roll (required if type is "roll").
* @param {string} [dropData.value] The value of the roll (required if type is "roll").
* @param {number} slot The hotbar slot where the macro will be created.
*
* @returns {Promise<void>} A promise that resolves when the macro is created.
*/
static createCthulhuEternalMacro = async function (dropData, slot) {
switch (dropData.type) {
case "Actor":
const actor = await fromUuid(dropData.uuid)
const actorCommand = `game.actors.get("${actor.id}").sheet.render(true)`
this.createMacro(slot, actor.name, actorCommand, actor.img)
break
case "JournalEntry":
const journal = await fromUuid(dropData.uuid)
const journalCommand = `game.journal.get("${journal.id}").sheet.render(true)`
this.createMacro(slot, journal.name, journalCommand, journal.img ? journal.img : "icons/svg/book.svg")
break
case "roll":
const rollCommand =
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}`)}`
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}`
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}`
this.createMacro(slot, rollAttackName, rollAttackCommand, "icons/svg/d20-grey.svg")
break
default:
// Handle other cases or do nothing
break
}
}
/**
* Create a macro
* All macros are flaged with a tenebris.macro flag at true
* @param {*} slot
* @param {*} name
* @param {*} command
* @param {*} img
*/
static createMacro = async function (slot, name, command, img) {
let macro = game.macros.contents.find((m) => m.name === name && m.command === command)
if (!macro) {
macro = await Macro.create(
{
name: name,
type: "script",
img: img,
command: command,
flags: { "tenebris.macro": true },
},
{ displaySheet: false },
)
game.user.assignHotbarMacro(macro, slot)
}
}
}

View File

@ -9,3 +9,8 @@ export { default as CthulhuEternalBond } from "./bond.mjs"
export { default as CthulhuEternalGear } from "./gear.mjs"
export { default as CthulhuEternalMotivation } from "./motivation.mjs"
export { default as CthulhuEternalArchetype } from "./archetype.mjs"
export { default as CthulhuEternalRitual } from "./ritual.mjs"
export { default as CthulhuEternalVehicle } from "./vehicle.mjs"
export { default as CthulhuEternalCreature } from "./creature.mjs"
export { default as CthulhuEternalTome } from "./tome.mjs"

View File

@ -5,7 +5,8 @@ export default class CthulhuEternalArchetype extends foundry.abstract.TypeDataMo
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.settings = new fields.StringField({ required: true, initial: "modern", choices: SYSTEM.AVAILABLE_SETTINGS })
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.settings = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.description = new fields.HTMLField({
required: false,
@ -19,4 +20,5 @@ export default class CthulhuEternalArchetype extends foundry.abstract.TypeDataMo
/** @override */
static LOCALIZATION_PREFIXES = ["CTHULHUETERNAL.Archetype"]
}

View File

@ -6,7 +6,10 @@ export default class CthulhuEternalArmor extends foundry.abstract.TypeDataModel
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.settings = new fields.StringField({ required: true, initial: "modern", choices: SYSTEM.AVAILABLE_SETTINGS })
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.settings = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.protection = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.resourceLevel = new fields.NumberField({ required: true, initial: 0, min: 0 })

View File

@ -0,0 +1,80 @@
import { SYSTEM } from "../config/system.mjs"
import CthulhuEternalRoll from "../documents/roll.mjs"
export default class CthulhuEternalCreature extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
// Carac
const characteristicField = (label) => {
const schema = {
value: new fields.NumberField({ ...requiredInteger, initial: 3, min: 0 }),
feature: new fields.StringField({ required: true, nullable: false, initial: "" })
}
return new fields.SchemaField(schema, { label })
}
schema.characteristics = new fields.SchemaField(
Object.values(SYSTEM.CHARACTERISTICS).reduce((obj, characteristic) => {
obj[characteristic.id] = characteristicField(characteristic.label)
return obj
}, {}),
)
schema.wp = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 3, min: 0 }),
exhausted: new fields.BooleanField({ required: true, initial: false })
})
schema.hp = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 })
})
schema.movement = new fields.StringField({ required: true, initial: "" })
schema.sanLoss = new fields.StringField({ required: true, initial: "1/1D6" })
schema.armor = new fields.StringField({ required: true, initial: "" })
schema.size = new fields.StringField({ required: true, initial: "medium", choices: SYSTEM.CREATURE_SIZE })
schema.damageBonus = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["CTHULHUETERNAL.Creature"]
/** */
/**
* Rolls a dice for a character.
* @param {("save"|"resource|damage")} rollType The type of the roll.
* @param {number} rollItem The target value for the roll. Which caracteristic or resource. If the roll is a damage roll, this is the id of the item.
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
*/
async roll(rollType, rollItem) {
let opponentTarget
const hasTarget = opponentTarget !== undefined
let roll = await CthulhuEternalRoll.prompt({
rollType,
rollItem,
isLowWP: false,
isZeroWP: false,
isExhausted: false,
actorId: this.parent.id,
actorName: this.parent.name,
actorImage: this.parent.img,
hasTarget,
target: opponentTarget
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
}
}

View File

@ -7,8 +7,12 @@ export default class CthulhuEternalGHear extends foundry.abstract.TypeDataModel
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.settings = new fields.StringField({ required: true, initial: "modern", choices: SYSTEM.AVAILABLE_SETTINGS })
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.settings = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.resourceLevel = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.state = new fields.StringField({ required: true, initial: "pristine", choices: SYSTEM.EQUIPMENT_STATES })
return schema
}

View File

@ -5,7 +5,8 @@ export default class CthulhuEternalMentalDisorder extends foundry.abstract.TypeD
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.cured = new fields.BooleanField({ required: true, initial: false })
return schema
}

View File

@ -33,8 +33,8 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
})
schema.hp = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
stunned: new fields.BooleanField({ required: true, initial: false })
})
@ -42,18 +42,25 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
recovery: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
violence: new fields.ArrayField(new fields.BooleanField(), { required: true, initial: [false, false, false], min:3, max:3}),
helplessness: new fields.ArrayField(new fields.BooleanField(), { required: true, initial: [false, false, false], min:3, max:3 }),
breakingPoint: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
violence: new fields.ArrayField(new fields.BooleanField(), { required: true, initial: [false, false, false], min: 3, max: 3 }),
helplessness: new fields.ArrayField(new fields.BooleanField(), { required: true, initial: [false, false, false], min: 3, max: 3 }),
breakingPoint: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
insanity: new fields.StringField({ required: true, nullable: false, initial: "none", choices: SYSTEM.INSANITY }),
})
schema.damageBonus = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.resources = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }), // Unused but kept for compatibility
permanentRating: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
hand: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
currentHand: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
stowed: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
storage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
currentStowed: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
storage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
currentStorage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
checks: new fields.ArrayField(new fields.BooleanField(), { required: true, initial: [false, false, false], min: 3, max: 3 }),
nbValidChecks: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.biodata = new fields.SchemaField({
@ -65,7 +72,9 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
birthplace: new fields.StringField({ required: true, nullable: false, initial: "" }),
eyes: new fields.StringField({ required: true, nullable: false, initial: "" }),
hair: new fields.StringField({ required: true, nullable: false, initial: "" }),
harshness: new fields.StringField({ required: true, nullable: false, initial: "normal", choices:SYSTEM.HARSHNESS }),
harshness: new fields.StringField({ required: true, nullable: false, initial: "normal", choices: SYSTEM.HARSHNESS }),
adaptedToViolence: new fields.BooleanField({ required: true, initial: false }),
adaptedToHelplessness: new fields.BooleanField({ required: true, initial: false })
})
return schema
@ -78,36 +87,31 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
super.prepareDerivedData();
let updates = {}
if ( this.wp.max !== this.characteristics.pow.value) {
if (this.wp.max !== this.characteristics.pow.value) {
updates[`system.wp.max`] = this.characteristics.pow.value
}
let hpMax = Math.round((this.characteristics.con.value + this.characteristics.str.value) / 2)
if ( this.hp.max !== hpMax) {
if (this.hp.max !== hpMax) {
updates[`system.hp.max`] = hpMax
}
// Get Unnatural skill for MAX SAN
let unnatural = this.parent.items.find(i => i.type === "skill" && i.name.toLowerCase() === game.i18n.localize("CTHULHUETERNAL.Skill.Unnatural").toLowerCase())
let minus = 0
if (unnatural) {
minus = unnatural.data.skillTotal
minus = unnatural.system.skillTotal
}
let maxSan = Math.max(99 - minus, 0)
if ( this.san.max !== maxSan) {
if (this.san.max !== maxSan) {
updates[`system.san.max`] = maxSan
}
let bp = Math.max(this.san.value - this.characteristics.pow.value, 0)
if ( this.san.breakingPoint !== bp) {
updates[`system.san.breakingPoint`] = bp
}
let recoverySan = this.characteristics.pow.value * 5
if (recoverySan > this.san.max) {
recoverySan = this.san.max
}
if ( this.san.recovery !== recoverySan) {
updates[`system.san.recovery`] = recoverySan
if (this.san.recovery !== recoverySan) {
updates[`system.san.recovery`] = recoverySan
}
let dmgBonus = 0
@ -122,7 +126,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
} else if (this.characteristics.str.value <= 20) {
dmgBonus = 2
}
if ( this.damageBonus !== dmgBonus) {
if (this.damageBonus !== dmgBonus) {
updates[`system.damageBonus`] = dmgBonus
}
@ -137,12 +141,30 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
updates[`system.hp.value`] = this.hp.max
}
if (this.resources.permanentRating < 0) {
updates[`system.resources.permanentRating`] = 0
}
let resourceIndex = Math.max(Math.min(this.resources.permanentRating, 20), 0)
let breakdown = SYSTEM.RESOURCE_BREAKDOWN[resourceIndex]
if (this.resources.hand !== breakdown.hand) {
updates[`system.resources.hand`] = breakdown.hand
}
if (this.resources.stowed !== breakdown.stowed) {
updates[`system.resources.stowed`] = breakdown.stowed
}
if (this.resources.storage !== breakdown.storage) {
updates[`system.resources.storage`] = breakdown.storage + (this.resources.permanentRating - resourceIndex)
}
if (this.resources.nbValidChecks !== breakdown.checks) {
updates[`system.resources.nbValidChecks`] = breakdown.checks
}
if (Object.keys(updates).length > 0) {
this.parent.update(updates)
}
}
isLowWP() {
return this.wp.value <= 2
}
@ -151,6 +173,32 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
return this.wp.value === 0
}
isExhausted() {
return this.wp.exhausted
}
modifyWP(value) {
let updates = {}
let wp = Math.max(Math.min(this.wp.value + value, this.wp.max), 0)
if (this.wp.value !== wp) {
updates[`system.wp.value`] = wp
}
if (Object.keys(updates).length > 0) {
this.parent.update(updates)
}
}
setBP() {
let updates = {}
let bp = Math.max(this.san.value - this.characteristics.pow.value, 0)
if (this.san.breakingPoint !== bp) {
updates[`system.san.breakingPoint`] = bp
}
if (Object.keys(updates).length > 0) {
this.parent.update(updates)
}
}
/** */
/**
* Rolls a dice for a character.
@ -167,14 +215,17 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
rollItem,
isLowWP: this.isLowWP(),
isZeroWP: this.isZeroWP(),
isExhausted: this.isExhausted(),
actorId: this.parent.id,
actorName: this.parent.name,
actorImage: this.parent.img,
hasTarget,
previousResultType: rollItem.resultType,
target: opponentTarget
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
}
}

24
module/models/ritual.mjs Normal file
View File

@ -0,0 +1,24 @@
import { SYSTEM } from "../config/system.mjs"
export default class CthulhuEternalRitual extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.ritualType = new fields.StringField({ required: true, initial: "simple", choices: SYSTEM.RITUAL_TYPES })
schema.studyTime = new fields.StringField({ required: true, initial: "X days", textSearch: true })
schema.studySAN = new fields.StringField({ required: true, initial: "1d4", textSearch: true })
schema.activationTime = new fields.StringField({ required: true, initial: "X turns", textSearch: true })
schema.activationWP = new fields.StringField({ required: true, initial: "1d4", textSearch: true })
schema.activationSAN = new fields.StringField({ required: true, initial: "1d6", textSearch: true })
schema.description = new fields.HTMLField({ required: true, textSearch: true })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["CTHULHUETERNAL.Ritual"]
}

View File

@ -6,7 +6,9 @@ export default class CthulhuEternalSkill extends foundry.abstract.TypeDataModel
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.settings = new fields.StringField({ required: true, initial: "modern", choices: SYSTEM.AVAILABLE_SETTINGS })
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.settings = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.base = new fields.StringField({ required: true, initial: "0" })
schema.bonus = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
@ -20,7 +22,6 @@ export default class CthulhuEternalSkill extends foundry.abstract.TypeDataModel
/** @override */
static LOCALIZATION_PREFIXES = ["CTHULHUETERNAL.Skill"]
prepareDerivedData() {
super.prepareDerivedData();
this.skillTotal = this.computeScore();

64
module/models/tome.mjs Normal file
View File

@ -0,0 +1,64 @@
import { SYSTEM } from "../config/system.mjs";
export default class CthulhuEternalTome extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const schema = {};
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.minimumEra = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.creationDate = new fields.StringField({
required: true,
initial: "",
textSearch: true
});
// Language field
schema.language = new fields.StringField({
required: true,
initial: "Latin",
textSearch: true
});
// studyTime field
schema.studyTime = new fields.StringField({
required: true,
initial: "X days",
textSearch: true
});
// SAN loss field
schema.sanLoss = new fields.StringField({
required: true,
initial: "1d4",
textSearch: true
});
// Unnatural skill field
schema.unnaturalSkill = new fields.StringField({
required: true,
initial: "1d4",
textSearch: true
});
schema.rituals = new fields.StringField({
required: true,
initial: "",
textSearch: true
});
schema.otherBenefits = new fields.StringField({
required: true,
initial: "",
textSearch: true
});
schema.description = new fields.HTMLField({ required: true, textSearch: true })
return schema;
}
/** @override */
static LOCALIZATION_PREFIXES = ["CTHULHUETERNAL.Tome"];
}

35
module/models/vehicle.mjs Normal file
View File

@ -0,0 +1,35 @@
import { SYSTEM } from "../config/system.mjs"
import CthulhuEternalRoll from "../documents/roll.mjs"
export default class CthulhuEternalVehicle extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.settings = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.hp = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 })
})
schema.armor = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.surfaceSpeed = new fields.StringField({ required: true, initial: "slow", choices: SYSTEM.VEHICLE_SPEED })
schema.airSpeed = new fields.StringField({ required: true, initial: "none", choices: SYSTEM.VEHICLE_SPEED })
schema.state = new fields.StringField({ required: true, initial: "pristine", choices: SYSTEM.EQUIPMENT_STATES })
schema.crew = new fields.ArrayField(new fields.StringField(), { required: false, initial: [], min:0 })
schema.resourceLevel = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["CTHULHUETERNAL.Vehicle"]
}

View File

@ -1,25 +1,33 @@
import { SYSTEM } from "../config/system.mjs"
export default class LethalFantasySkill extends foundry.abstract.TypeDataModel {
export default class CthulhuEternalWeapon extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const schema = {}
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.settings = new fields.StringField({ required: true, initial: "modern", choices: SYSTEM.AVAILABLE_SETTINGS })
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.settings = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.weaponType = new fields.StringField({ required: true, initial: "melee", choices: SYSTEM.WEAPON_TYPE })
schema.damage = new fields.StringField({required: true, initial: "1d6"})
schema.hasDirectSkill = new fields.BooleanField({ required: true, initial: false })
schema.directSkillValue = new fields.NumberField({ required: true, initial: 0, min: 0, max:99 })
schema.hasSelectiveFire = new fields.BooleanField({ required: true, initial: false })
schema.damage = new fields.StringField({required: true, initial: "1d6"})
schema.applyDamageBonus = new fields.BooleanField({ required: true, initial: false })
schema.baseRange = new fields.StringField({required: true, initial: ""})
schema.rangeUnit = new fields.StringField({ required: true, initial: "yard", choices: SYSTEM.WEAPON_RANGE_UNIT })
schema.lethality = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.killRadius = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.armorPiercing = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.weaponSubtype = new fields.StringField({ required: true, initial: "basicfirearm", choices: SYSTEM.WEAPON_SUBTYPE })
schema.weaponSubtype = new fields.StringField({ required: true, initial: "basicfirearm", choices: SYSTEM.WEAPON_SUBTYPE })
schema.state = new fields.StringField({ required: true, initial: "pristine", choices: SYSTEM.EQUIPMENT_STATES })
schema.resourceLevel = new fields.NumberField({ required: true, initial: 0, min: 0 })
return schema
}

View File

@ -9,24 +9,5 @@
*/
export function handleSocketEvent({ action = null, data = {} } = {}) {
console.debug("handleSocketEvent", action, data)
switch (action) {
case "fortune":
return CthulhuEternalFortune.handleSocketEvent(data)
case "askRoll":
return _askRoll(data)
}
}
/**
* Handles the socket event to ask for a roll.
*
* @param {Object} [options={}] The options object.
* @param {string} [options.userId] The ID of the user who initiated the roll.
*/
export function _askRoll({ userId } = {}) {
console.debug(`handleSocketEvent _askRoll from ${userId} !`)
const currentUser = game.user._id
if (userId === currentUser) {
foundry.audio.AudioHelper.play({ src: "/systems/fvtt-cthulhu-eternal/sounds/drums.wav", volume: 0.8, autoplay: true, loop: false }, false)
}
}

View File

@ -1,11 +1,15 @@
import CthulhuEternalRoll from "./documents/roll.mjs"
import { SystemManager } from './applications/hud/system-manager.js'
import { SYSTEM } from "./config/system.mjs"
export default class CthulhuEternalUtils {
static registerSettings() {
game.settings.register("fvtt-cthulhu-eternal", "settings-era", {
name: game.i18n.localize("CHTUHLUETERNAL.Settings.era"),
hint: game.i18n.localize("CHTUHLUETERNAL.Settings.eraHint"),
default: true,
name: game.i18n.localize("CTHULHUETERNAL.Settings.era"),
hint: game.i18n.localize("CTHULHUETERNAL.Settings.eraHint"),
default: "jazz",
scope: "world",
type: String,
choices: SYSTEM.AVAILABLE_SETTINGS,
@ -174,7 +178,84 @@ export default class CthulhuEternalUtils {
const html = options.fn(this);
return html.replace(rgx, "$& selected");
});
}
static async damageRoll(rollMessage) {
let rollData = rollMessage.rolls[0]?.options?.rollData
let actor = game.actors.get(rollData.actorId)
if (!actor) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Label.noActorFound"))
return
}
rollData.weapon.resultType = rollData.resultType // Keep the result type from the roll message
actor.system.roll("damage", rollData.weapon)
}
static async nudgeRoll(rollMessage) {
let dialogContext = rollMessage.rolls[0]?.options
let actor = game.actors.get(dialogContext.actorId)
dialogContext.wpValue = actor.system.wp.value
dialogContext.rollResultIndex = rollMessage.rolls[0].total - 1
dialogContext.minValue = Math.max(rollMessage.rolls[0].total - (dialogContext.wpValue * 5), 1)
dialogContext.maxValue = Math.min(rollMessage.rolls[0].total + (dialogContext.wpValue * 5), 100)
dialogContext.wpCost = 0
// Build options table for the select operator between minValue and maxValue
dialogContext.nudgeOptions = Array.from({ length: dialogContext.maxValue - dialogContext.minValue + 1 }, (_, i) => dialogContext.minValue + i)
console.log(dialogContext)
const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/nudge-dialog.hbs", dialogContext)
const title = game.i18n.localize("CTHULHUETERNAL.Roll.nudgeRoll")
const rollContext = await foundry.applications.api.DialogV2.wait({
window: { title: title },
classes: ["fvtt-cthulhu-eternal"],
content,
buttons: [
{
action: "apply",
label: game.i18n.localize("CTHULHUETERNAL.Roll.applyNudge"),
callback: (event, button, dialog) => {
const output = Array.from(button.form.elements).reduce((obj, input) => {
if (input.name) obj[input.name] = input.value
return obj
}, {})
return output
},
},
{
action: "cancel",
label: game.i18n.localize("CTHULHUETERNAL.Roll.cancel"),
callback: (event, button, dialog) => { }
}
],
actions: {
},
rejectClose: false, // Click on Close button will not launch an error
render: (event, dialog) => {
$(".nudged-score-select").change(event => {
dialogContext.nudgedValue = Number(event.target.value)+1
dialogContext.wpCost = Math.ceil(Math.abs(rollMessage.rolls[0].total - dialogContext.nudgedValue) / 5)
$("#nudged-wp-cost").val(dialogContext.wpCost)
})
}
})
// If the user cancels the dialog, exit
if (rollContext === null || dialogContext.wpCost === 0) {
return
}
const roll = new CthulhuEternalRoll(String(dialogContext.nudgedValue))
await roll.evaluate()
roll.options = dialogContext
roll.options.isNudgedRoll = true
roll.options.isNudge = false
roll.displayRollResult(roll, dialogContext, dialogContext.rollData)
roll.toMessage()
actor.system.modifyWP(-dialogContext.wpCost)
}
static setupCSSRootVariables() {
@ -183,6 +264,9 @@ export default class CthulhuEternalUtils {
let eraCSS = SYSTEM.ERA_CSS[era];
if (!eraCSS) eraCSS = SYSTEM.ERA_CSS["jazz"];
document.documentElement.style.setProperty('--font-size-standard', eraCSS.baseFontSize);
document.documentElement.style.setProperty('--font-size-title', eraCSS.titleFontSize);
document.documentElement.style.setProperty('--font-size-result', eraCSS.titleFontSize);
document.documentElement.style.setProperty('--font-primary', eraCSS.primaryFont);
document.documentElement.style.setProperty('--font-secondary', eraCSS.secondaryFont);
document.documentElement.style.setProperty('--font-title', eraCSS.titleFont);

Binary file not shown.

View File

View File

@ -0,0 +1 @@
MANIFEST-000081

View File

7
packs-system/rituals/LOG Normal file
View File

@ -0,0 +1,7 @@
2025/06/12-21:40:36.466688 7f13a0ff96c0 Recovering log #79
2025/06/12-21:40:36.523036 7f13a0ff96c0 Delete type=3 #77
2025/06/12-21:40:36.523080 7f13a0ff96c0 Delete type=0 #79
2025/06/12-22:19:34.910570 7f139fbff6c0 Level-0 table #84: started
2025/06/12-22:19:34.910598 7f139fbff6c0 Level-0 table #84: 0 bytes OK
2025/06/12-22:19:34.968716 7f139fbff6c0 Delete type=0 #82
2025/06/12-22:19:35.027464 7f139fbff6c0 Manual compaction at level-0 from '!items!4oyPRBWPBWAChrJP' @ 72057594037927935 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at (end)

View File

@ -0,0 +1,7 @@
2025/06/12-20:37:22.833913 7f13a0ff96c0 Recovering log #75
2025/06/12-20:37:22.843981 7f13a0ff96c0 Delete type=3 #73
2025/06/12-20:37:22.844063 7f13a0ff96c0 Delete type=0 #75
2025/06/12-20:52:48.559109 7f139fbff6c0 Level-0 table #80: started
2025/06/12-20:52:48.559156 7f139fbff6c0 Level-0 table #80: 0 bytes OK
2025/06/12-20:52:48.723881 7f139fbff6c0 Delete type=0 #78
2025/06/12-20:52:48.724064 7f139fbff6c0 Manual compaction at level-0 from '!items!4oyPRBWPBWAChrJP' @ 72057594037927935 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

View File

View File

Binary file not shown.

Binary file not shown.

View File

View File

@ -0,0 +1 @@
MANIFEST-000249

0
packs-system/skills/LOCK Normal file
View File

7
packs-system/skills/LOG Normal file
View File

@ -0,0 +1,7 @@
2025/06/12-21:40:36.410156 7f13a17fa6c0 Recovering log #247
2025/06/12-21:40:36.461712 7f13a17fa6c0 Delete type=3 #245
2025/06/12-21:40:36.461765 7f13a17fa6c0 Delete type=0 #247
2025/06/12-22:19:34.795093 7f139fbff6c0 Level-0 table #252: started
2025/06/12-22:19:34.795129 7f139fbff6c0 Level-0 table #252: 0 bytes OK
2025/06/12-22:19:34.852214 7f139fbff6c0 Delete type=0 #250
2025/06/12-22:19:35.027439 7f139fbff6c0 Manual compaction at level-0 from '!folders!5PrT9QmN1cFPzDFP' @ 72057594037927935 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at (end)

View File

@ -0,0 +1,7 @@
2025/06/12-20:37:22.815373 7f13a1ffb6c0 Recovering log #242
2025/06/12-20:37:22.826434 7f13a1ffb6c0 Delete type=3 #240
2025/06/12-20:37:22.826479 7f13a1ffb6c0 Delete type=0 #242
2025/06/12-20:52:48.502299 7f139fbff6c0 Level-0 table #248: started
2025/06/12-20:52:48.502341 7f139fbff6c0 Level-0 table #248: 0 bytes OK
2025/06/12-20:52:48.558859 7f139fbff6c0 Delete type=0 #246
2025/06/12-20:52:48.724054 7f139fbff6c0 Manual compaction at level-0 from '!folders!5PrT9QmN1cFPzDFP' @ 72057594037927935 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

View File

View File

View File

Binary file not shown.

644
styles/creature.less Normal file
View File

@ -0,0 +1,644 @@
.creature-content {
.sheet-common();
.creature-sheet-common();
overflow: scroll;
}
.sheet-tabs {
background-color: var(--color-light-1);
}
.creature-main {
background-color: var(--color-light-1);
display: flex;
.creature-pc {
display: flex;
gap: 4px;
flex: 1;
.creature-left {
min-width: 180px;
display: flex;
flex-direction: column;
.creature-left-image {
display: flex;
justify-content: center;
align-items: center;
padding-bottom: 8px;
.creature-img {
height: 140px;
width: auto;
border: none;
}
}
.creature-hp {
gap: 2px;
align-items: center;
input {
flex: none;
width: 2rem;
margin-left: 4px;
}
.damage-bonus {
font-size: calc(var(--font-size-standard) * 0.8);
}
.hp-separator {
font-size: calc(var(--font-size-standard) * 1.2);
display: flex;
align-items: center;
justify-content: center;
}
}
.creature-dv,
.creature-dmax {
.form-fields {
flex: none;
}
}
.creature-dmax-edit {
input {
display: flex;
width: 60px;
font-size: calc(var(--font-size-standard) * 1.4);
align-items: center;
justify-content: center;
padding: 0 5px 0 5px;
text-align: center;
}
}
}
.creature-right {
display: flex;
flex-direction: column;
gap: 5px;
.creature-name {
display: flex;
input {
font-family: var(--font-title);
font-size: var(--font-size-title);
width: 400px;
}
}
.san {
align-content: flex-start;
input {
min-width: 2.2rem;
max-width: 2.2rem;
margin-bottom: 4px;
}
select {
min-width: 6rem;
max-width: 6rem;
}
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
font-size: 0.9rem;
}
.button {
min-width: 4rem;
max-width: 4rem;
}
.san-checkbox {
min-width: 1rem;
max-width: 1rem;
}
.label-short-field {
font-size: 0.9rem;
max-width: 3rem;
min-width: 3rem;
flex-grow: 1;
}
.label-recovery {
margin-left: 4px;
}
.label-field {
font-size: 0.9rem;
max-width: 6rem;
min-width: 6rem;
flex-grow: 1;
}
.label-bp {
flex-grow: 1;
max-width: 3rem;
min-width: 3rem;
margin-left: 4px;
}
.label-insanity {
flex-grow: 1;
margin-left: 4px;
max-width: 8rem;
min-width: 8rem;
}
.spacing {
margin-left: 4px;
}
.d100 {
flex: 0;
}
}
.willpower {
input {
min-width: 2.4rem;
max-width: 2.4rem;
}
input[type="checkbox"] {
min-width: 1rem;
max-width: 1rem;
}
.label-field {
flex-grow: 1;
margin-left: 4px;
max-width: 5rem;
min-width: 5rem;
font-size: 0.9rem;
}
.checkbox {
flex-grow: 0;
min-width: 1rem;
max-width: 1rem;
}
}
label {
min-width: 120px;
}
}
}
.creature-pc-play {
min-width: 500px;
}
.creature-pc-edit {
min-width: 650px;
}
.creature-characteristics {
background-color: var(--color-light-1);
display: flex;
flex-direction: column;
gap: 5px;
flex: 1;
.creature-characteristic {
display: flex;
align-items: center;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.rollable {
min-width: 3rem;
max-width: 3rem;
}
.char-text {
margin-left: 0.5rem;
}
.d100 {
flex: 0;
max-width: 0.6rem;
}
.form-group {
flex: 0;
padding-left: 5px;
.form-fields {
font-size: 1.1rem;
flex: none;
width: 40px;
}
}
}
}
.creature-characteristic-play {
min-width: 225px;
}
.creature-characteristic-edit {
min-width: 400px;
}
}
.creature-biography {
background-color: var(--color-light-1);
prose-mirror.inactive {
min-height: 40px;
}
prose-mirror.active {
min-height: 150px;
}
.field-label {
margin-left: 8px;
}
.adapted {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
label {
min-width: 20rem;
}
}
.resources {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
label {
min-width: 8rem;
}
}
.features,
.biodata {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
label {
min-width: 3rem;
}
.feature {
display: flex;
align-items: center;
gap: 4px;
min-width: 18rem;
max-width: 18rem;
}
}
}
.tab.creature-skills .main-div {
background-color: var(--color-light-1);
display: grid;
grid-template-columns: 1fr;
legend {
a {
font-size: calc(var(--font-size-standard) * 1.4);
padding-left: 5px;
}
}
.armors {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
.armor {
display: flex;
align-items: center;
gap: 4px;
min-width: 13rem;
max-width: 13rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.protection {
min-width: 5rem;
max-width: 5rem;
}
.name {
min-width: 8rem;
max-width: 8rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.weapons {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.weapon {
display: flex;
align-items: center;
gap: 4px;
min-width: 13rem;
max-width: 13rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.damage {
min-width: 6rem;
max-width: 6rem;
}
.name {
min-width: 10rem;
max-width: 10rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.skills {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
.skill {
display: flex;
align-items: center;
gap: 4px;
margin-left: 4px;
min-width: 12.3rem;
max-width: 12.3rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.score {
min-width: 1.2rem;
max-width: 1.2rem;
}
.name {
min-width: 10rem;
max-width: 10rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
}
.tab.creature-status .main-div {
background-color: var(--color-light-1);
display: grid;
grid-template-columns: 1fr;
legend {
a {
font-size: calc(var(--font-size-standard) * 1.4);
padding-left: 5px;
}
}
.bonds {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.bond {
display: flex;
align-items: center;
gap: 4px;
min-width: 18rem;
max-width: 18rem;
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 12rem;
max-width: 12rem;
}
.type {
min-width: 6rem;
max-width: 6rem;
}
.level {
min-width: 2rem;
max-width: 2rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.motivations {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.motivation {
display: flex;
align-items: center;
gap: 4px;
min-width: 14rem;
max-width: 14rem;
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 12rem;
max-width: 12rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.mentaldisorders {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.mentaldisorder {
display: flex;
align-items: center;
gap: 4px;
min-width: 18rem;
max-width: 18rem;
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 14rem;
max-width: 14rem;
}
.cured {
min-width: 5rem;
max-width: 5rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.injuries {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.injury {
display: flex;
align-items: center;
gap: 4px;
min-width: 16rem;
max-width: 16rem;
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 14rem;
max-width: 14rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
}
.tab.creature-equipment .main-div {
background-color: var(--color-light-1);
display: grid;
grid-template-columns: 1fr;
legend {
a {
font-size: calc(var(--font-size-standard) * 1.4);
padding-left: 5px;
}
}
.gears {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
.gear {
display: flex;
align-items: center;
gap: 4px;
min-width: 13rem;
max-width: 13rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 10rem;
max-width: 10rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.rituals {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.ritual {
display: flex;
align-items: center;
gap: 4px;
min-width: 20rem;
max-width: 20rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 17rem;
max-width: 17rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.tomes {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.tome {
display: flex;
align-items: center;
gap: 4px;
min-width: 20rem;
max-width: 20rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 17rem;
max-width: 17rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
prose-mirror.inactive {
min-height: 40px;
}
prose-mirror.active {
min-height: 150px;
}
}

View File

@ -44,9 +44,19 @@
src: url("../assets/fonts/Volkhov-Regular.ttf") format("truetype");
}
@font-face {
font-family: "ChantelliAntiqua";
src: url("../assets/fonts/Chantelli_Antiqua.ttf") format("truetype");
}
@font-face {
font-family: "IMFell";
src: url("../assets/fonts/IMFell.ttf") format("truetype");
src: url("../assets/fonts/IMFeDPrm28P.ttf") format("truetype");
}
@font-face {
font-family: "SailRegular";
src: url("../assets/fonts/Sail-Regular.ttf") format("truetype");
}
@font-face {
@ -110,21 +120,6 @@
}
@font-face {
font-family: "Caslon";
src: url("../fonts/caslonpro-regular.otf") format("truetype");
}
@font-face {
font-family: "Caslon Bold";
src: url("../fonts/caslonpro-bold.otf") format("truetype");
}
@font-face {
font-family: "Caslon Bold Italic";
src: url("../fonts/caslonpro-bolditalic.otf") format("truetype");
}
@font-face {
font-family: "Caslon Italic";
src: url("../fonts/caslonpro-italic.otf") format("truetype");
font-family: "Skranji";
src: url("../assets/fonts/Skranji.woff") format("woff");
}

View File

@ -4,6 +4,8 @@
.fvtt-cthulhu-eternal {
@import "mixins.less";
@import "protagonist.less";
@import "vehicle.less";
@import "creature.less";
@import "skill.less";
@import "injury.less";
@import "weapon.less";
@ -15,6 +17,8 @@
@import "gear.less";
@import "arcane.less";
@import "archetype.less";
@import "ritual.less";
@import "tome.less";
}
@import "roll.less";
@import "roll.less";

View File

@ -1,17 +1,33 @@
:root {
--font-size-standard: 0.9rem;
--background-image-base: linear-gradient(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8)),
url("../assets/ui/jazz_background_main.webp");
/*--background-image-base: url("../assets/ui/jazzage_background_main.webp");*/
--font-size-result: 1.4rem;
--background-image-base: linear-gradient(
rgba(255, 255, 255, 0.8),
rgba(255, 255, 255, 0.8)
),
url("../assets/ui/jazz_background_main.webp");
--font-primary: "RozhaOne";
--font-secondary: "RozhaOne";
--font-title: "Broadway";
--logo-standard: url("../assets/logos/reanimated-ce-logo.webp");
--color-success: darkgreen;
--color-success: rgb(15, 122, 15);
--color-failure: darkred;
--color-critical-success: lightgreen;
--color-critical-failure: lightcoral;
--img-icon-color-filter: brightness(0) saturate(100%) invert(52%) sepia(9%) saturate(2368%) hue-rotate(360deg) brightness(86%) contrast(84%);
--color-warning: darkorange;
--color-critical-success: rgb(21, 39, 204);
--color-critical-failure: rgb(141, 32, 231);
--img-icon-color-filter: brightness(0) saturate(100%) invert(52%) sepia(9%)
saturate(2368%) hue-rotate(360deg) brightness(86%) contrast(84%);
}
body {
}
.d100 {
width: 18px;
height: 18px;
color: black;
border-width: 0px;
filter: var(--img-icon-color-filter);
}
.item .thumbnail,
@ -51,7 +67,7 @@ i.fvtt-cthulhu-eternal {
.application.dialog.fvtt-cthulhu-eternal {
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);
@ -65,7 +81,7 @@ i.fvtt-cthulhu-eternal {
.chat-message.whisper {
font-family: var(--font-primary);
background-image: var(--background-image-base);
background-repeat:repeat-y;
background-repeat: repeat-y;
background-position: 0%;
background-size: 100% 100%;
}

View File

@ -1,11 +1,17 @@
.sheet-common() {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1);
font-size: calc(var(--font-size-standard) * 1.05);
color: var(--color-dark-1);
background-image: var(--background-image-base);
background-repeat: no-repeat;
background-size: 100% 100%;
.sheet-tabs {
a {
color: rgba(32, 31, 31, 0.8);
}
}
input:disabled,
select:disabled {
background-color: rgba(0, 0, 0, 0.2);
@ -21,7 +27,7 @@
}
input[name="name"] {
height: 50px;
height: 40px;
margin-right: 10px;
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1);
@ -30,19 +36,27 @@
}
fieldset {
margin-bottom: 5px;
border-radius: 5px;
margin-bottom: 4px;
border-radius: 4px;
}
.form-group {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 4px;
}
.form-fields {
input,
select {
text-align: center;
font-size: calc(var(--font-size-standard) * 1.0);
font-size: calc(var(--font-size-standard) * 1);
}
select {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.0);
font-size: calc(var(--font-size-standard) * 1);
}
}
@ -57,32 +71,41 @@
.protagonist-sheet-common {
label {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.2);
font-size: calc(var(--font-size-standard) * 1);
}
}
.vehicle-sheet-common {
label {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1);
}
}
.creature-sheet-common {
label {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1);
}
}
.item-sheet-common {
.form-fields {
padding-top: 5px;
padding-top: 4px;
}
label {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.0);
font-size: calc(var(--font-size-standard) * 1);
flex: 50%;
}
.align-top {
align-self: flex-start;
padding: 0.1rem;
margin-right: 0.2rem;
/*border-color: black;
border-width: 1px;
border-style: solid;
border-radius: 2%;*/
input[type="checkbox"] {
--checkbox-checkmark-color: var(--color-dark-1);
}
.shift-right {
margin-left: 2rem;
}
}
}

View File

@ -34,6 +34,25 @@
}
}
.protagonist-hp {
gap: 2px;
align-items: center;
input {
flex: none;
width: 2rem;
margin-left: 4px;
}
.damage-bonus {
font-size: calc(var(--font-size-standard) * 0.8);
}
.hp-separator {
font-size: calc(var(--font-size-standard) * 1.2);
display: flex;
align-items: center;
justify-content: center;
}
}
.protagonist-dv,
.protagonist-dmax {
.form-fields {
@ -62,81 +81,94 @@
display: flex;
input {
font-family: var(--font-title);
font-size: calc(var(--font-size-standard) * 1.4);
font-size: var(--font-size-title);
width: 400px;
}
}
.san {
align-content: flex-start;
input {
min-width: 2.2rem;
max-width: 2.2rem;
margin-bottom: 4px;
}
select {
min-width: 6rem;
max-width: 6rem;
}
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
font-size: 0.9rem;
}
.button {
min-width: 4rem;
max-width: 4rem;
}
.san-checkbox {
min-width: 1rem;
max-width: 1rem;
}
.label-field {
flex-grow: 0;
.label-short-field {
font-size: 0.9rem;
max-width: 3rem;
min-width: 3rem;
flex-grow: 1;
}
.label-recovery {
margin-left: 4px;
}
.label-field {
display: flex;
font-size: 1.05rem;
max-width: 6rem;
min-width: 6rem;
flex-grow: 1;
}
.label-bp {
flex-grow: 1;
max-width: 3rem;
min-width: 3rem;
margin-left: 4px;
}
.label-insanity {
flex-grow: 1;
margin-left: 4px;
max-width: 8rem;
min-width: 8rem;
}
.spacing {
margin-left: 4px;
}
.d100 {
flex: 0;
}
}
.willpower {
input {
min-width: 2.2rem;
min-width: 2.4rem;
max-width: 2.4rem;
}
.label-field {
flex-grow: 0;
margin-left: 4px;
}
.checkbox {
input[type="checkbox"] {
min-width: 1rem;
max-width: 1rem;
}
.label-field {
flex-grow: 1;
margin-left: 4px;
max-width: 5rem;
min-width: 5rem;
font-size: 0.9rem;
}
.checkbox {
flex-grow: 0;
min-width: 1rem;
max-width: 1rem;
}
}
.protagonist-infos {
display: flex;
flex-direction: column;
gap: 4px;
label {
min-width: 120px;
}
.protagonist-hp {
display: flex;
gap: 2px;
align-items: center;
.protagonist-hp-value {
.form-fields input {
flex: none;
width: 50px;
margin-left: 4px;
font-size: calc(var(--font-size-standard) * 1.4);
}
}
.protagonist-hp-max {
clear: both;
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin: 3px 0;
align-items: center;
input {
width: 50px;
text-align: center;
font-size: calc(var(--font-size-standard) * 1.4);
}
}
.hp-separator {
font-size: calc(var(--font-size-standard) * 1.2);
display: flex;
align-items: center;
justify-content: center;
}
}
label {
min-width: 120px;
}
}
}
@ -164,12 +196,24 @@
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.rollable {
min-width: 3rem;
max-width: 3rem;
}
.char-text {
margin-left: 0.5rem;
}
.d100 {
flex: 0;
max-width: 0.6rem;
}
.form-group {
flex: 1;
flex: 0;
padding-left: 5px;
.form-fields {
font-size: 1.1rem;
flex: none;
width: 70px;
width: 40px;
}
}
}
@ -195,39 +239,44 @@
.field-label {
margin-left: 8px;
}
.biodata {
.adapted {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
label {
min-width: 20rem;
}
}
.resources {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
label {
min-width: 8rem;
}
}
.motivations {
.features,
.biodata {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.motivation {
gap: 8px;
label {
min-width: 3rem;
}
.feature {
display: flex;
align-items: center;
gap: 4px;
min-width: 14rem;
max-width: 14rem;
.controls {
min-width: 2rem;
max-width: 2rem;
}
.name {
min-width: 8rem;
max-width: 8rem;
}
.item-img {
width: 32px;
height: 32px;
margin: 4px 0 0 0;
}
min-width: 18rem;
max-width: 18rem;
}
}
}
.tab.protagonist-skills {
.tab.protagonist-skills .main-div {
background-color: var(--color-light-1);
display: grid;
grid-template-columns: 1fr;
@ -245,31 +294,42 @@
display: flex;
align-items: center;
gap: 4px;
min-width: 10rem;
max-width: 10rem;
margin-left: 4px;
min-width: 12.3rem;
max-width: 12.3rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
min-width: 2rem;
max-width: 2rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.score {
min-width: 1.2rem;
max-width: 1.2rem;
}
.skill-failed-progress {
text-decoration: dashed underline;
text-underline-offset: 4px;
text-decoration-color: rgb(70, 90, 72);
}
.name {
min-width: 8rem;
max-width: 8rem;
min-width: 10rem;
max-width: 10rem;
}
.item-img {
width: 32px;
height: 32px;
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
}
.tab.protagonist-status {
.tab.protagonist-status .main-div {
background-color: var(--color-light-1);
display: grid;
grid-template-columns: 1fr;
@ -279,6 +339,7 @@
padding-left: 5px;
}
}
.bonds {
display: grid;
grid-template-columns: repeat(2, 1fr);
@ -287,31 +348,60 @@
display: flex;
align-items: center;
gap: 4px;
min-width: 16rem;
max-width: 16rem;
min-width: 18rem;
max-width: 18rem;
.controls {
min-width: 2rem;
max-width: 2rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 12rem;
max-width: 12rem;
}
.type {
min-width: 4rem;
max-width: 4rem;
min-width: 6rem;
max-width: 6rem;
}
.level {
min-width: 2rem;
max-width: 2rem;
}
.item-img {
width: 32px;
height: 32px;
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.motivations {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.motivation {
display: flex;
align-items: center;
gap: 4px;
min-width: 14rem;
max-width: 14rem;
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 12rem;
max-width: 12rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.mentaldisorders {
display: grid;
grid-template-columns: repeat(2, 1fr);
@ -320,23 +410,29 @@
display: flex;
align-items: center;
gap: 4px;
min-width: 16rem;
max-width: 16rem;
min-width: 18rem;
max-width: 18rem;
.controls {
min-width: 2rem;
max-width: 2rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 14rem;
max-width: 14rem;
}
.cured {
min-width: 5rem;
max-width: 5rem;
}
.item-img {
width: 32px;
height: 32px;
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.injuries {
display: grid;
grid-template-columns: repeat(2, 1fr);
@ -348,23 +444,24 @@
min-width: 16rem;
max-width: 16rem;
.controls {
min-width: 2rem;
max-width: 2rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 14rem;
max-width: 14rem;
}
.item-img {
width: 32px;
height: 32px;
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
}
.tab.protagonist-equipment {
.tab.protagonist-equipment .main-div {
background-color: var(--color-light-1);
display: grid;
grid-template-columns: 1fr;
@ -383,27 +480,28 @@
align-items: center;
gap: 4px;
min-width: 13rem;
max-width: 13srem;
max-width: 13rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
min-width: 2rem;
max-width: 2rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.damage {
min-width: 5rem;
max-width: 5rem;
min-width: 6rem;
max-width: 6rem;
}
.name {
min-width: 8rem;
max-width: 8rem;
min-width: 10rem;
max-width: 10rem;
}
.item-img {
width: 32px;
height: 32px;
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
@ -417,15 +515,16 @@
align-items: center;
gap: 4px;
min-width: 13rem;
max-width: 13srem;
max-width: 13rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
min-width: 2rem;
max-width: 2rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.protection {
min-width: 5rem;
@ -436,12 +535,13 @@
max-width: 8rem;
}
.item-img {
width: 32px;
height: 32px;
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.gears {
display: grid;
grid-template-columns: repeat(3, 1fr);
@ -451,27 +551,88 @@
align-items: center;
gap: 4px;
min-width: 13rem;
max-width: 13srem;
max-width: 13rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
min-width: 2rem;
max-width: 2rem;
}
.damage {
min-width: 5rem;
max-width: 5rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 8rem;
max-width: 8rem;
min-width: 10rem;
max-width: 10rem;
}
.item-img {
width: 32px;
height: 32px;
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.rituals {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.ritual {
display: flex;
align-items: center;
gap: 4px;
min-width: 20rem;
max-width: 20rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 17rem;
max-width: 17rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.tomes {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.tome {
display: flex;
align-items: center;
gap: 4px;
min-width: 20rem;
max-width: 20rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 17rem;
max-width: 17rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}

30
styles/ritual.less Normal file
View File

@ -0,0 +1,30 @@
.ritual-content {
.sheet-common();
.item-sheet-common();
fieldset {
margin-top: 8px;
background-color: var(--color-light-1);
.editor-content {
max-height: 400px;
overflow-y: auto;
}
.editor-container {
max-height: 400px;
overflow-y: auto;
}
}
.header {
background-color: var(--color-light-1);
display: flex;
img {
width: 50px;
height: 50px;
}
}
label {
flex: 10%;
}
}

View File

@ -31,11 +31,18 @@
border: none;
background-color: rgba(0, 0, 0, 0.1);
color: var(--color-dark-2);
width: 60px;
width: 4rem;
text-align: center;
}
}
.red-warning {
color: var(--color-failure);
}
.orange-warning {
color: var(--color-warning);
}
.dialog-damage {
display: flex;
@ -86,25 +93,35 @@
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1.0);
}
.nudge-roll {
font-size: calc(var(--font-size-standard) * 1.0);
margin-left: 2rem;
display: none;
}
.roll-damage {
font-size: calc(var(--font-size-standard) * 1.0);
margin-left: 2rem;
display: none;
}
.result-success {
color: var(--color-success);
font-family: var(--font-title);
font-size: calc(var(--font-size-standard) * 1.2);
font-size: var(--font-size-result);
}
.result-critical-success {
color: var(--color-critical-success);
font-family: var(--font-title);
font-size: calc(var(--font-size-standard) * 1.2);
font-size: var(--font-size-result);
}
.result-failure {
color: var(--color-failure);
font-family: var(--font-title);
font-size: calc(var(--font-size-standard) * 1.2);
font-size: var(--font-size-result);
}
.result-critical-failure {
color: var(--color-critical-failure);
font-family: var(--font-title);
font-size: calc(var(--font-size-standard) * 1.2);
font-size: var(--font-size-result);
}
}
.introText {

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