19 Commits

Author SHA1 Message Date
uberwald 0d3cc5bbe0 Enable auto-release
Release Creation / build (release) Successful in 1m28s
2026-05-22 15:07:34 +02:00
uberwald ae65c93026 Enable auto-release
Release Creation / build (release) Successful in 1m52s
2026-05-22 15:01:26 +02:00
uberwald de2175f31c Enable auto-release
Release Creation / build (release) Failing after 53s
2026-05-22 14:56:06 +02:00
uberwald 220891c93f Enable auto-release
Release Creation / build (release) Failing after 47s
2026-05-22 14:51:16 +02:00
uberwald e7bb7b4456 Enable auto-release
Release Creation / build (release) Failing after 1m40s
2026-05-22 14:45:26 +02:00
uberwald 984dfcf507 Enable auto-release 2026-05-22 14:06:58 +02:00
uberwald 7947ceaff1 Enable auto-release 2026-05-22 14:05:25 +02:00
uberwald 9717bc7b77 Enable auto-release 2026-05-22 14:02:28 +02:00
uberwald dd3fe0e38e Implements inventory system, wip 2026-05-22 11:03:17 +02:00
uberwald 4ff46865c2 Implements inventory system, wip 2026-05-19 22:23:31 +02:00
uberwald 68e3d35af1 Add abilities sectioon on sheet 2026-03-18 17:12:32 +01:00
uberwald 58fdc3482a Add HP TEMP 2026-03-10 19:54:36 +01:00
uberwald 236d752406 Update gitignore 2026-03-10 18:02:02 +01:00
uberwald bf9ad37d24 Add mana+AP reset buttons and conditions 2026-03-10 18:02:02 +01:00
uberwald 7944345c82 Actualiser README.md 2026-02-10 08:09:02 +01:00
uberwald 7abea8e9d4 Add class adancement 2026-02-09 22:46:44 +01:00
uberwald 31573bd522 Fix initiative 2026-01-26 17:47:53 +01:00
uberwald abea77906d Add spells rolls and enhance CSS styling
- Add spell roll functionality to character sheets
- Enhance CSS and LESS styling for better visual presentation
- Update character templates and models
- Remove old backup files (roll-old.mjs, roll.mjs.backup)
- Improve character combat and equipment templates
- Update utility functions and actor documents

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-01-21 13:56:09 +01:00
uberwald 7283f5f15b Update skill sheet 2026-01-14 14:16:31 +01:00
68 changed files with 3839 additions and 2878 deletions
+71
View File
@@ -0,0 +1,71 @@
name: Release Creation
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "💡 The ${{ gitea.repository }} repository will be cloned to the runner."
- uses: RouxAntoine/checkout@v3.5.4
# Extrait la version depuis le tag (ex: v1.2.3 → 1.2.3)
- name: Extract tag version number
id: get_version
uses: battila7/get-version-action@v2
# Met à jour version, manifest et download dans system.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/${{gitea.repository}}/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-prism-rpg-${{github.event.release.tag_name}}.zip
# Compile le CSS depuis les sources LESS (gitignored)
- name: Setup Node.js
uses: https://github.com/actions/setup-node@v4
with:
node-version: "22"
- name: Build CSS
run: npm install && npx gulp css
# Crée le zip de release avec tous les fichiers nécessaires au système
- run: |
apt update -y
apt install -y zip
- run: zip -r ./fvtt-prism-rpg-${{github.event.release.tag_name}}.zip system.json prism-rpg.mjs assets/ css/ lang/ module/ styles/ templates/
- 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-prism-rpg-${{github.event.release.tag_name}}.zip
system.json
api_key: "${{secrets.RELEASE_PRISM }}"
#- name: Publish to Foundry server
# uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
# with:
# token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
# id: "fvtt-prism-rpg"
# version: ${{github.event.release.tag_name}}
# manifest: "https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/latest/system.json"
# notes: "https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-prism-rpg-${{github.event.release.tag_name}}.zip"
# compatibility-minimum: "14"
# compatibility-verified: "14"
+3
View File
@@ -8,3 +8,6 @@ node_modules/
.history
# GitHub/Copilot config
.github/
+1 -13
View File
@@ -1,20 +1,8 @@
## Prism RPG RPG for Foundry Virtual TableTop
The Official game system for playing Prism RPG TTRPG: The Role Playing Game on FoundryVTT. This fully functional system is the foundational framework to build your game.
This product's format, programming code, and presentation is copyrighted by Prism RPG Games LLC.
This system & product are used with permission granted as part of the partnership agreement between Foundry Gaming LLC and Prism RPG Games LLC. It uses the following trademarks and/or copyrights:
This system & product are used with permission granted as part of the partnership agreement between Foundry Gaming LLC and Prism RPG Games LLC.
© 2025 Prism RPG Games. Content copyright Ted McClintock, Prism RPG Games LLC. All Rights Reserved. Prism RPG® is a Registered Trademark of Prism RPG Games LLC. All Rights Reserved.
Prism RPG Games is ©2025 Prism RPG Games, LLC. All rights reserved. Prism RPG, Prism RPG Games, and their associated logos are trademarks of Prism RPG Games, LLC. https://lethalfantasy.com/
For inquiries on developing content for this ruleset please contact Lethalted@lethalfantasy.com
## Community
Please join our Discord server Prism RPG games https://discord.gg/UDvnnyvreV
It's the place to ask questions on how to use the system, make feature request and follow the development of the system.
+19
View File
@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
<!-- Background circle -->
<circle cx="50" cy="50" r="48" fill="#f0f0f0" stroke="#333" stroke-width="2" />
<!-- Star for achievement/advancement -->
<path d="M 50 20 L 55 38 L 74 38 L 59 49 L 64 67 L 50 56 L 36 67 L 41 49 L 26 38 L 45 38 Z"
fill="#ffd700" stroke="#333" stroke-width="1.5" />
<!-- Upward arrow for progression -->
<path d="M 50 75 L 50 85" stroke="#333" stroke-width="3" stroke-linecap="round" />
<path d="M 45 78 L 50 73 L 55 78" fill="none" stroke="#333" stroke-width="3"
stroke-linecap="round" stroke-linejoin="round" />
<!-- Small accent dots for decoration -->
<circle cx="20" cy="30" r="2" fill="#666" />
<circle cx="80" cy="30" r="2" fill="#666" />
<circle cx="20" cy="70" r="2" fill="#666" />
<circle cx="80" cy="70" r="2" fill="#666" />
</svg>

After

Width:  |  Height:  |  Size: 891 B

+823 -15
View File
@@ -54,6 +54,13 @@ i.prismrpg {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1);
background-image: var(--background-image-base);
background-size: 100% 100%;
background-repeat: no-repeat;
}
.application.dialog.prismrpg .window-content {
background-image: var(--background-image-base);
background-size: 100% 100%;
background-repeat: no-repeat;
}
.application.dialog.prismrpg button:hover {
background: var(--color-dark-6);
@@ -681,6 +688,166 @@ i.prismrpg {
.prismrpg .tab.character-equipment .main-div .equipments .name {
min-width: 12rem;
}
.prismrpg .tab.character-equipment .main-div .inv-section {
margin-bottom: 6px;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-items {
display: flex;
flex-direction: column;
gap: 3px;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-item {
display: flex;
align-items: center;
gap: 6px;
padding: 2px 4px;
border-radius: 3px;
background: rgba(255, 255, 255, 0.15);
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-item:hover {
background: rgba(255, 255, 255, 0.25);
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-item .item-img {
width: 24px;
height: 24px;
cursor: pointer;
flex-shrink: 0;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-item .inv-name {
flex: 1;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-item .inv-enc,
.prismrpg .tab.character-equipment .main-div .inv-section .inv-item .inv-uses,
.prismrpg .tab.character-equipment .main-div .inv-section .inv-item .inv-capacity {
font-size: 11px;
color: #555;
min-width: 30px;
text-align: center;
white-space: nowrap;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-item .controls {
display: flex;
gap: 4px;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-item .controls a {
font-size: 12px;
cursor: pointer;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-empty {
font-style: italic;
color: rgba(0, 0, 0, 0.4);
text-align: center;
font-size: 11px;
padding: 4px;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-container[data-container-id] {
border: 1px dashed transparent;
transition: border-color 0.15s, background 0.15s;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-container[data-container-id].drag-over {
border-color: rgba(100, 150, 255, 0.7);
background: rgba(100, 150, 255, 0.12);
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-container-items {
margin: 2px 0 6px 28px;
display: flex;
flex-direction: column;
gap: 2px;
border-left: 2px solid rgba(0, 0, 0, 0.15);
padding-left: 8px;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-container-item {
display: flex;
align-items: center;
gap: 6px;
padding: 2px 4px;
border-radius: 3px;
background: rgba(0, 0, 0, 0.06);
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-container-item:hover {
background: rgba(0, 0, 0, 0.12);
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-container-item .item-img {
width: 20px;
height: 20px;
cursor: pointer;
flex-shrink: 0;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-container-item .inv-name {
flex: 1;
font-size: 11px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-container-item .inv-enc {
font-size: 10px;
color: #555;
min-width: 24px;
text-align: center;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-container-item .inv-container-type-badge {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: rgba(0, 0, 0, 0.45);
background: rgba(0, 0, 0, 0.07);
border-radius: 3px;
padding: 1px 4px;
flex-shrink: 0;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-container-item .controls {
display: flex;
gap: 4px;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-container-item .controls a {
font-size: 11px;
cursor: pointer;
}
.prismrpg .tab.character-equipment .main-div .inv-section .inv-container-empty {
margin: 2px 0 4px 36px;
font-size: 10px;
font-style: italic;
color: rgba(0, 0, 0, 0.35);
}
.prismrpg .tab.character-equipment .main-div .pack-burden-fieldset .pack-burden-display {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
font-size: 13px;
}
.prismrpg .tab.character-equipment .main-div .pack-burden-fieldset .pack-burden-display .pack-burden-label {
font-weight: bold;
}
.prismrpg .tab.character-equipment .main-div .pack-burden-fieldset .pack-burden-display .pack-burden-value {
font-weight: bold;
}
.prismrpg .tab.character-equipment .main-div .pack-burden-fieldset .pack-burden-display .pack-burden-value .pack-burden-used {
color: #e6a817;
}
.prismrpg .tab.character-equipment .main-div .pack-burden-fieldset .pack-burden-display .pack-burden-value .pack-burden-sep {
color: rgba(0, 0, 0, 0.4);
}
.prismrpg .tab.character-equipment .main-div .pack-burden-fieldset .pack-burden-display .pack-burden-value .pack-burden-max {
color: rgba(0, 0, 0, 0.7);
}
.prismrpg .tab.character-equipment .main-div .pack-burden-fieldset .pack-burden-used {
color: #e6a817;
font-weight: bold;
}
.prismrpg .tab.character-equipment .main-div .pack-burden-fieldset .pack-burden-sep {
color: rgba(0, 0, 0, 0.4);
margin: 0 2px;
}
.prismrpg .tab.character-equipment .main-div .pack-burden-fieldset .pack-burden-max {
color: rgba(0, 0, 0, 0.7);
font-weight: bold;
}
.prismrpg .tab.character-combat .main-div {
background-image: url("../assets/ui/prism_rpg_background.webp");
background-size: cover;
@@ -951,6 +1118,27 @@ i.prismrpg {
.prismrpg .tab.character-miracles .main-div prose-mirror.active {
min-height: 150px;
}
.prismrpg .inv-item.is-equipped {
background: rgba(100, 180, 100, 0.12);
border-left: 2px solid rgba(80, 160, 80, 0.6);
}
.prismrpg .inv-item .equipped-icon {
color: #5a9e6a;
}
.prismrpg .inv-item .unequipped-icon {
color: rgba(0, 0, 0, 0.25);
}
.prismrpg .burden-excess {
font-size: 0.75em;
color: #ff5c5c;
margin-left: 4px;
white-space: nowrap;
}
.prismrpg .controls a.disabled {
opacity: 0.35;
pointer-events: none;
cursor: default;
}
.prismrpg .character-main-v2 {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1);
@@ -1052,17 +1240,21 @@ i.prismrpg {
align-items: start;
}
.prismrpg .character-main-v2 .character-left-column {
display: flex;
flex-direction: row;
gap: 12px;
align-items: flex-start;
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: auto auto;
column-gap: 12px;
row-gap: 8px;
align-items: start;
min-width: 0;
}
.prismrpg .character-main-v2 .character-left-column .portrait-hp-column {
display: flex;
flex-direction: column;
gap: 12px;
width: 200px;
flex-shrink: 0;
grid-column: 1;
grid-row: 1 / 3;
}
.prismrpg .character-main-v2 .character-left-column .character-portrait {
width: 200px;
@@ -1126,13 +1318,17 @@ i.prismrpg {
border: 2px solid #6b6b6b;
border-radius: 4px;
}
.prismrpg .character-main-v2 .character-left-column .hp-shields-section .hp-shields .hp-item.hp-temp-item .hp-value input {
background: rgba(255, 230, 160, 0.85);
border-color: #b8860b;
}
.prismrpg .character-main-v2 .character-left-column .character-attributes {
display: flex;
flex-direction: column;
gap: 6px;
flex: 1;
grid-column: 2;
grid-row: 1;
min-width: 0;
max-width: 280px;
}
.prismrpg .character-main-v2 .character-left-column .character-attributes .attribute-shield {
position: relative;
@@ -1404,6 +1600,103 @@ i.prismrpg {
line-height: 1.6;
resize: vertical;
}
.prismrpg .burden-mr-section {
display: flex;
flex-direction: row;
gap: 16px;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.35);
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 4px;
grid-column: 2;
grid-row: 2;
}
.prismrpg .burden-mr-section .burden-mr-item {
display: flex;
flex-direction: column;
gap: 3px;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-display-row {
display: flex;
align-items: center;
gap: 8px;
min-height: 22px;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-edit-row {
display: flex;
align-items: center;
gap: 3px;
flex-wrap: wrap;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-label {
font-weight: bold;
font-size: 11px;
letter-spacing: 0.05em;
min-width: 52px;
color: #fff;
cursor: default;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-sub-attr .form-group {
display: contents;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-sub-attr .form-group label {
display: none;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-sub-attr .form-fields {
display: contents;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-sub-attr select {
font-size: 11px;
padding: 1px 2px;
height: 22px;
max-width: 80px;
background: rgba(0, 0, 0, 0.3);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 3px;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-op {
font-size: 11px;
color: rgba(255, 255, 255, 0.9);
flex-shrink: 0;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-other input[type="number"] {
width: 32px;
text-align: center;
font-size: 11px;
height: 22px;
background: rgba(0, 0, 0, 0.3);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 3px;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-total {
font-size: 18px;
font-weight: bold;
color: #ffe566;
min-width: 24px;
text-align: center;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-used-max {
display: flex;
align-items: center;
gap: 2px;
font-size: 14px;
font-weight: bold;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-used-max .burden-used {
color: #ffe566;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-used-max .burden-separator {
color: rgba(255, 255, 255, 0.8);
}
.prismrpg .burden-mr-section .burden-mr-item .burden-used-max .burden-max {
color: #e0e0e0;
}
.prismrpg .burden-mr-section .burden-mr-item .burden-used-max.burden-overloaded .burden-used {
color: #ff5c5c;
font-weight: bold;
}
.prismrpg .character-subattributes.tab .subattributes-content {
padding: 1rem;
}
@@ -2163,6 +2456,116 @@ i.prismrpg {
.prismrpg .racial-ability-content input[type="checkbox"]:checked::after {
color: rgba(0, 0, 0, 0.1);
}
.prismrpg .ability-content {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1);
color: var(--color-dark-1);
background-image: var(--background-image-base);
background-repeat: no-repeat;
background-size: 100% 100%;
overflow: auto;
}
.prismrpg .ability-content nav.tabs [data-tab] {
color: #636060;
}
.prismrpg .ability-content nav.tabs [data-tab].active {
color: #252424;
}
.prismrpg .ability-content input:disabled,
.prismrpg .ability-content select:disabled {
background-color: rgba(0, 0, 0, 0.2);
border-color: transparent;
color: var(--color-dark-3);
}
.prismrpg .ability-content input,
.prismrpg .ability-content select {
height: 1.5rem;
background-color: rgba(0, 0, 0, 0.1);
border-color: var(--color-dark-6);
color: var(--color-dark-2);
}
.prismrpg .ability-content input[name="name"] {
height: 2.5rem;
margin-right: 4px;
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.2);
font-weight: bold;
border: none;
}
.prismrpg .ability-content fieldset {
margin-bottom: 4px;
border-radius: 4px;
}
.prismrpg .ability-content .form-fields input,
.prismrpg .ability-content .form-fields select {
text-align: center;
font-size: calc(var(--font-size-standard) * 1);
}
.prismrpg .ability-content .form-fields select {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1);
}
.prismrpg .ability-content legend {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.2);
font-weight: bold;
letter-spacing: 1px;
}
.prismrpg .ability-content .form-fields {
padding-top: 4px;
}
.prismrpg .ability-content .form-group {
display: flex;
flex: 1;
flex-direction: row;
}
.prismrpg .ability-content .form-group label {
align-content: center;
min-width: 10rem;
max-width: 10rem;
}
.prismrpg .ability-content .form-group select,
.prismrpg .ability-content .form-group input {
text-align: left;
min-width: 12rem;
max-width: 12rem;
}
.prismrpg .ability-content .form-group input[type="checkbox"] {
min-width: 1.2rem;
max-width: 1.2rem;
margin-right: 0.5rem;
}
.prismrpg .ability-content label {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1);
flex: 50%;
}
.prismrpg .ability-content .align-top {
align-self: flex-start;
padding: 0.1rem;
margin-right: 0.2rem;
}
.prismrpg .ability-content .shift-right {
margin-left: 2rem;
}
.prismrpg .ability-content .header {
display: flex;
}
.prismrpg .ability-content .header img {
width: 50px;
height: 50px;
}
.prismrpg .ability-content input[type="checkbox"] {
font-size: var(--font-size-14);
width: 20px;
padding-top: 0;
}
.prismrpg .ability-content input[type="checkbox"]:checked {
background-color: rgba(0, 0, 0, 0.1);
}
.prismrpg .ability-content input[type="checkbox"]:checked::after {
color: rgba(0, 0, 0, 0.1);
}
.prismrpg .weapon-content {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1);
@@ -3227,6 +3630,118 @@ i.prismrpg {
.prismrpg .class-content label {
flex: 10%;
}
.prismrpg .class-content .advancement-level {
margin-bottom: 1.5rem;
padding: 0.5rem;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;
}
.prismrpg .class-content .advancement-level h3 {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 0 0.5rem 0;
padding-bottom: 0.5rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.prismrpg .class-content .advancement-level h3 .level-title {
color: #000000;
font-weight: bold;
font-size: 80%;
}
.prismrpg .class-content .advancement-level h3 .add-advancement {
padding: 0.25rem 0.5rem;
background: var(--color-control-bg);
border: 1px solid var(--color-border-dark);
border-radius: 3px;
cursor: pointer;
}
.prismrpg .class-content .advancement-level h3 .add-advancement:hover {
background: var(--color-control-bg-hover);
}
.prismrpg .class-content .advancement-level h3 .add-advancement i {
margin: 0;
}
.prismrpg .class-content .advancement-level .empty-advancements {
font-style: italic;
color: rgba(0, 0, 0, 0.5);
margin: 0.5rem 0;
}
.prismrpg .class-content .advancement-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.prismrpg .class-content .advancement-item {
padding: 0.75rem;
background: rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.prismrpg .class-content .advancement-item .advancement-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.prismrpg .class-content .advancement-item .advancement-header .advancement-icon {
width: 40px;
height: 40px;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 3px;
cursor: pointer;
flex-shrink: 0;
}
.prismrpg .class-content .advancement-item .advancement-header .advancement-icon:hover {
border-color: var(--color-border-highlight);
}
.prismrpg .class-content .advancement-item .advancement-header input[type="text"] {
flex: 1;
font-weight: bold;
}
.prismrpg .class-content .advancement-item .advancement-header .toggle-advancement-description {
padding: 0.25rem 0.5rem;
background: var(--color-control-bg);
border: 1px solid var(--color-border-dark);
border-radius: 3px;
cursor: pointer;
flex-shrink: 0;
margin-left: 0.25rem;
}
.prismrpg .class-content .advancement-item .advancement-header .toggle-advancement-description:hover {
background: var(--color-control-bg-hover);
}
.prismrpg .class-content .advancement-item .advancement-header .toggle-advancement-description i {
margin: 0;
color: rgba(0, 0, 0, 0.7);
}
.prismrpg .class-content .advancement-item .advancement-header .delete-advancement {
padding: 0.25rem 0.5rem;
background: var(--color-control-bg);
border: 1px solid var(--color-border-dark);
border-radius: 3px;
cursor: pointer;
flex-shrink: 0;
}
.prismrpg .class-content .advancement-item .advancement-header .delete-advancement:hover {
background: rgba(255, 0, 0, 0.1);
border-color: rgba(255, 0, 0, 0.5);
}
.prismrpg .class-content .advancement-item .advancement-header .delete-advancement i {
margin: 0;
color: rgba(255, 0, 0, 0.7);
}
.prismrpg .class-content .advancement-item .advancement-description {
margin-top: 0.5rem;
overflow: hidden;
transition: max-height 0.3s ease-out, opacity 0.3s ease-out;
max-height: 500px;
opacity: 1;
}
.prismrpg .class-content .advancement-item .advancement-description.collapsed {
max-height: 0;
opacity: 0;
margin-top: 0;
}
.prismrpg .character-path-content {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1);
@@ -3337,6 +3852,126 @@ i.prismrpg {
.prismrpg .character-path-content input[type="checkbox"]:checked::after {
color: rgba(0, 0, 0, 0.1);
}
.prismrpg .container-content {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1);
color: var(--color-dark-1);
background-image: var(--background-image-base);
background-repeat: no-repeat;
background-size: 100% 100%;
overflow: auto;
}
.prismrpg .container-content nav.tabs [data-tab] {
color: #636060;
}
.prismrpg .container-content nav.tabs [data-tab].active {
color: #252424;
}
.prismrpg .container-content input:disabled,
.prismrpg .container-content select:disabled {
background-color: rgba(0, 0, 0, 0.2);
border-color: transparent;
color: var(--color-dark-3);
}
.prismrpg .container-content input,
.prismrpg .container-content select {
height: 1.5rem;
background-color: rgba(0, 0, 0, 0.1);
border-color: var(--color-dark-6);
color: var(--color-dark-2);
}
.prismrpg .container-content input[name="name"] {
height: 2.5rem;
margin-right: 4px;
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.2);
font-weight: bold;
border: none;
}
.prismrpg .container-content fieldset {
margin-bottom: 4px;
border-radius: 4px;
}
.prismrpg .container-content .form-fields input,
.prismrpg .container-content .form-fields select {
text-align: center;
font-size: calc(var(--font-size-standard) * 1);
}
.prismrpg .container-content .form-fields select {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1);
}
.prismrpg .container-content legend {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.2);
font-weight: bold;
letter-spacing: 1px;
}
.prismrpg .container-content .form-fields {
padding-top: 4px;
}
.prismrpg .container-content .form-group {
display: flex;
flex: 1;
flex-direction: row;
}
.prismrpg .container-content .form-group label {
align-content: center;
min-width: 10rem;
max-width: 10rem;
}
.prismrpg .container-content .form-group select,
.prismrpg .container-content .form-group input {
text-align: left;
min-width: 12rem;
max-width: 12rem;
}
.prismrpg .container-content .form-group input[type="checkbox"] {
min-width: 1.2rem;
max-width: 1.2rem;
margin-right: 0.5rem;
}
.prismrpg .container-content label {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1);
flex: 50%;
}
.prismrpg .container-content .align-top {
align-self: flex-start;
padding: 0.1rem;
margin-right: 0.2rem;
}
.prismrpg .container-content .shift-right {
margin-left: 2rem;
}
.prismrpg .container-content .header {
display: flex;
}
.prismrpg .container-content .header img {
width: 50px;
height: 50px;
}
.prismrpg .container-content .item-img {
width: 64px;
height: 64px;
object-fit: contain;
border: 1px solid var(--color-border-light-tertiary);
border-radius: 4px;
}
.prismrpg .consumable-content .item-img {
width: 64px;
height: 64px;
object-fit: contain;
border: 1px solid var(--color-border-light-tertiary);
border-radius: 4px;
}
.prismrpg .loot-content .item-img {
width: 64px;
height: 64px;
object-fit: contain;
border: 1px solid var(--color-border-light-tertiary);
border-radius: 4px;
}
.prismrpg .effects-container {
padding: 0.5rem;
}
@@ -4102,6 +4737,179 @@ i.prismrpg {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.2);
}
.chat-log .message-content .prismrpg-item-chat-card {
font-family: var(--font-primary);
border-radius: 4px;
overflow: hidden;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.05) 0%, rgba(0, 0, 0, 0.02) 100%);
border: 1px solid rgba(0, 0, 0, 0.2);
margin: 2px 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.chat-log .message-content .prismrpg-item-chat-card .item-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 8px;
background: linear-gradient(135deg, #2c2c2c 0%, #1a1a1a 100%);
border-bottom: 1px solid #444;
}
.chat-log .message-content .prismrpg-item-chat-card .item-header h3 {
margin: 0;
font-size: 0.95em;
font-weight: bold;
color: #d4af37;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
font-family: var(--font-secondary);
}
.chat-log .message-content .prismrpg-item-chat-card .item-header .item-type {
padding: 1px 6px;
border-radius: 10px;
font-size: 0.65em;
font-weight: bold;
text-transform: uppercase;
background: rgba(255, 255, 255, 0.15);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.2);
letter-spacing: 0.3px;
}
.chat-log .message-content .prismrpg-item-chat-card .item-image {
display: flex;
justify-content: center;
align-items: center;
padding: 6px;
background: rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.chat-log .message-content .prismrpg-item-chat-card .item-image img {
max-width: 50px;
max-height: 50px;
border-radius: 3px;
border: 1px solid rgba(212, 175, 55, 0.3);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
transition: transform 0.2s ease;
}
.chat-log .message-content .prismrpg-item-chat-card .item-image img:hover {
transform: scale(1.05);
}
.chat-log .message-content .prismrpg-item-chat-card .item-description {
padding: 6px 8px;
color: #000;
font-size: 0.8em;
line-height: 1.3;
background: rgba(255, 255, 255, 0.8);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
font-style: italic;
}
.chat-log .message-content .prismrpg-item-chat-card .item-details {
padding: 6px 8px;
display: flex;
flex-direction: column;
gap: 3px;
}
.chat-log .message-content .prismrpg-item-chat-card .item-details .item-detail {
display: flex;
align-items: center;
padding: 2px 6px;
background: rgba(255, 255, 255, 0.03);
border-left: 2px solid rgba(212, 175, 55, 0.5);
border-radius: 2px;
font-size: 0.8em;
}
.chat-log .message-content .prismrpg-item-chat-card .item-details .item-detail strong {
color: #d4af37;
margin-right: 6px;
min-width: 90px;
font-weight: bold;
}
.chat-log .message-content .prismrpg-item-chat-card .item-details .item-detail:nth-child(even) {
background: rgba(0, 0, 0, 0.05);
}
.chat-log .message-content .prismrpg-item-chat-card.weapon-item .item-header {
background: linear-gradient(135deg, #c41e3a 0%, #8b0000 100%);
}
.chat-log .message-content .prismrpg-item-chat-card.armor-item .item-header {
background: linear-gradient(135deg, #4a5cf7 0%, #2c3e9e 100%);
}
.chat-log .message-content .prismrpg-item-chat-card.spell-item .item-header {
background: linear-gradient(135deg, #9b59b6 0%, #6c3483 100%);
}
.chat-log .message-content .prismrpg-item-chat-card.skill-item .item-header {
background: linear-gradient(135deg, #16a085 0%, #0e6655 100%);
}
.chat-log .message-content .prismrpg-item-chat-card.equipment-item .item-header {
background: linear-gradient(135deg, #f39c12 0%, #b8730f 100%);
}
.new-round-message .chat-title .new-round-label {
font-size: var(--font-size-11);
font-style: italic;
color: #8a7a5a;
margin-top: 1px;
}
.new-round-message .new-round-actors {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
padding: 6px 0 2px;
}
.new-round-message .new-round-actors .new-round-restore-btn {
display: flex;
align-items: center;
gap: 6px;
width: calc(50% - 3px);
padding: 4px 8px 4px 6px;
border: 1px solid #7a6a45;
border-radius: 4px;
background: linear-gradient(135deg, #f5e6c8 0%, #e8d5a0 100%);
cursor: pointer;
font-size: var(--font-size-13);
color: #3a2e1a;
overflow: hidden;
}
.new-round-message .new-round-actors .new-round-restore-btn span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.new-round-message .new-round-actors .new-round-restore-btn img {
flex-shrink: 0;
width: 24px;
height: 24px;
border: none;
border-radius: 3px;
object-fit: cover;
}
.new-round-message .new-round-actors .new-round-restore-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #fdf3dc 0%, #f0e0b0 100%);
border-color: #a08040;
}
.new-round-message .new-round-actors .new-round-restore-btn:disabled,
.new-round-message .new-round-actors .new-round-restore-btn.restored {
opacity: 0.45;
cursor: default;
}
.new-round-message .new-round-actors .new-round-all-btn {
width: 100%;
background: linear-gradient(135deg, #c8dff5 0%, #a0c0e8 100%);
border-color: #4a6a8a;
color: #1a2e3a;
font-weight: bold;
}
.new-round-message .new-round-actors .new-round-all-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #dcedfc 0%, #b0d0f0 100%);
border-color: #3a5a7a;
}
.palette.status-effects .status-separator {
grid-column: 1 / -1;
width: 100%;
height: 2px;
border: none;
border-top: 2px solid rgba(255, 255, 255, 0.35);
margin: 5px 0;
position: relative;
}
.application.dialog.prismrpg {
color: var(--color-dark-1);
}
@@ -4515,15 +5323,15 @@ i.prismrpg {
.prismrpg-roll-dialog-modern .checkbox-group .checkbox-label input[type="checkbox"]:checked ~ .checkbox-text i {
color: #d4af37;
}
.application.dialog.prismrpg .window-content {
.application.dialog.prismrpg:has(.prismrpg-roll-dialog-modern) .window-content {
background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
padding: 8px;
}
.application.dialog.prismrpg .dialog-buttons {
.application.dialog.prismrpg:has(.prismrpg-roll-dialog-modern) .dialog-buttons {
padding: 6px 8px;
gap: 6px;
}
.application.dialog.prismrpg .dialog-buttons button {
.application.dialog.prismrpg:has(.prismrpg-roll-dialog-modern) .dialog-buttons button {
background: linear-gradient(135deg, #4a4a4a 0%, #6a6a6a 100%);
border: 1px solid #3a3a3a;
color: white;
@@ -4532,19 +5340,19 @@ i.prismrpg {
border-radius: 4px;
transition: all 0.2s ease;
}
.application.dialog.prismrpg .dialog-buttons button:hover {
.application.dialog.prismrpg:has(.prismrpg-roll-dialog-modern) .dialog-buttons button:hover {
background: linear-gradient(135deg, #5a5a5a 0%, #7a7a7a 100%);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
transform: translateY(-1px);
}
.application.dialog.prismrpg .dialog-buttons button.default,
.application.dialog.prismrpg .dialog-buttons button[data-button="roll"] {
.application.dialog.prismrpg:has(.prismrpg-roll-dialog-modern) .dialog-buttons button.default,
.application.dialog.prismrpg:has(.prismrpg-roll-dialog-modern) .dialog-buttons button[data-button="roll"] {
background: linear-gradient(135deg, #d4af37 0%, #f4cf67 100%);
border-color: #b49030;
color: #2a2a2a;
}
.application.dialog.prismrpg .dialog-buttons button.default:hover,
.application.dialog.prismrpg .dialog-buttons button[data-button="roll"]:hover {
.application.dialog.prismrpg:has(.prismrpg-roll-dialog-modern) .dialog-buttons button.default:hover,
.application.dialog.prismrpg:has(.prismrpg-roll-dialog-modern) .dialog-buttons button[data-button="roll"]:hover {
background: linear-gradient(135deg, #e4bf47 0%, #ffdf77 100%);
}
#token-hud .hp-loss-wrap {
+222 -4
View File
@@ -244,6 +244,17 @@
},
"wis": {
"label": "Wisdom"
},
"movementRating": {
"label": "Movement Rating",
"subAttribute": { "label": "Sub-Attribute" },
"other": { "label": "Other" },
"reduction": { "label": "Reduction" }
},
"burden": {
"label": "Burden",
"subAttribute": { "label": "Sub-Attribute" },
"other": { "label": "Other" }
}
}
},
@@ -361,6 +372,9 @@
"money": {
"label": "Money unit"
},
"equipped": {
"label": "Equipped"
},
"isKit": {
"label": "Is Kit?"
},
@@ -382,6 +396,88 @@
}
}
},
"Ability": {
"FIELDS": {
"description": {
"label": "Description"
}
}
},
"Combat": {
"newRound": "New Round —",
"restoreAP": "Reset AP & +1 Mana",
"allPC": "All PC"
},
"Status": {
"Aided": "Aided",
"Alert": "Alert",
"Alkalized": "Alkalized",
"Anchored": "Anchored",
"Banished": "Banished",
"Bestowed": "Bestowed",
"Blessed": "Blessed",
"Bleed": "Bleed",
"Blind": "Blind",
"Burning": "Burning",
"Chilled": "Chilled",
"Comatose": "Comatose",
"Compulsed": "Compulsed",
"Concealed": "Concealed",
"Corroded": "Corroded",
"Cursed": "Cursed",
"Dazed": "Dazed",
"Deaf": "Deaf",
"Diseased": "Diseased",
"Distracted": "Distracted",
"Enchanted": "Enchanted",
"Enhance": "Enhance",
"Exhaustion": "Exhaustion",
"Fatigue": "Fatigue",
"Frightened": "Frightened",
"Fury": "Fury",
"Haste": "Haste",
"Heroism": "Heroism",
"Horror": "Horror",
"Inspired": "Inspired",
"Invisible": "Invisible",
"Keen": "Keen",
"LifeDrain": "Life Drain",
"Locked": "Locked",
"Madness": "Madness",
"ManaDrain": "Mana Drain",
"Marked": "Marked",
"Mute": "Mute",
"Necrosis": "Necrosis (Elemental)",
"Numbed": "Numbed",
"Paralyzed": "Paralyzed",
"Petrified": "Petrified",
"Plagued": "Plagued",
"Poison": "Poison",
"Prepared": "Prepared",
"Prone": "Prone",
"Radiated": "Radiated (Elemental)",
"Rage": "Rage",
"Regeneration": "Regeneration",
"Reinforced": "Reinforced",
"Renewed": "Renewed",
"Saturated": "Saturated",
"Sealed": "Sealed",
"Seep": "Seep",
"Shattered": "Shattered",
"Shocked": "Shocked (Elemental)",
"Sightless": "Sightless",
"Silenced": "Silenced",
"Soundless": "Soundless",
"Staggered": "Staggered",
"Stunned": "Stunned",
"Supplied": "Supplied",
"Surged": "Surged",
"Taunt": "Taunt",
"Trance": "Trance",
"Unconscious": "Unconscious",
"Warded": "Warded",
"Wounded": "Wounded"
},
"Label": {
"agility": "Dexterity",
"gotoToken": "Go to token",
@@ -440,6 +536,18 @@
"combatDetails": "Combat details",
"Challenges": "Challenges",
"HP": "HP",
"HPTemp": "Temporary Hit Points",
"movementRating": "Movement Rating",
"burdenCharacter": "Burden",
"packBurden": "Pack Burden",
"container": "Container",
"containers": "Containers",
"consumables": "Consumables",
"loot": "Loot",
"kits": "Kits",
"weapons": "Weapons",
"encLoad": "Enc. Load",
"reduction": "Reduction",
"Movement": "Movement",
"Saves": "Saves",
"app": "APP",
@@ -471,6 +579,7 @@
"equipment": "Equipment",
"experience": "Experience",
"racialAbilities": "Racial Abilities",
"abilities": "Abilities",
"grit": "Grit",
"gritEarned": "Grit earned",
"int": "INT",
@@ -516,6 +625,8 @@
"skills": "Skills",
"sub-attribute": "Sub-Attribute",
"subattributes": "Sub-Attributes",
"subAttribute1": "Sub-Attribute 1",
"subAttribute2": "Sub-Attribute 2",
"spells": "Spells",
"str": "STR",
"titleChallenge": "Challenge",
@@ -686,7 +797,13 @@
"spellcastingTypeMana": "Mana",
"spellcastingTypeFaith": "Faith",
"attributeBonuses": "Attribute Bonuses",
"classFeatures": "Class Features"
"classFeatures": "Class Features",
"advancement": "Advancement",
"addAdvancement": "Add Advancement",
"deleteAdvancement": "Delete Advancement",
"advancementName": "Advancement Name",
"noAdvancements": "No advancements defined for this level",
"toggleDescription": "Toggle Description"
},
"CoreSkill": {
"acrobatics": "Acrobatics",
@@ -805,13 +922,47 @@
"addMiracle": "Add new miracle",
"skill": "Skills list",
"skills": "Skills - Your character's skills and abilities",
"racialAbilities": "Racial Abilities from your character's race and sub-race"
"racialAbilities": "Racial Abilities from your character's race and sub-race",
"abilities": "Abilities acquired through class, paths, or other sources",
"movementRating": "Movement Rating (MR): 3 + Sub-Attribute + Other - Reduction",
"burdenCharacter": "Burden: Base Burden + Sub-Attribute + Other (max); Used = sum of equipped item load",
"packBurden": "Pack Burden capacity of this container",
"uses": "Remaining uses / Max uses",
"useConsumable": "Use this consumable",
"toggleEquipped": "Toggle equipped (affects Pack Burden)",
"addWeapon": "Add weapon",
"addArmor": "Add armor",
"addShield": "Add shield",
"addConsumable": "Add consumable",
"addKit": "Add kit",
"addEquipment": "Add equipment",
"addLoot": "Add loot",
"addContainer": "Add container",
"addRacialAbility": "Add racial ability",
"addAbility": "Add ability",
"excessBurden": "Equipped burden exceeds max — excess reduces Movement Rating",
"assignToContainer": "Assign to container",
"removeFromContainer": "Remove from container",
"packBurden": "Pack Burden"
},
"RollSavingThrow": "Roll Saving Throw",
"Dialog": {
"useConsumable": "Use Consumable",
"useConsumableContent": "Use one charge of <strong>{name}</strong>? ({uses} remaining)",
"assignToContainer": "Assign to Container"
},
"Message": {
"selectCoreSkill": "You must select a Core Skill for your character. Each character chooses one Core Skill at creation.",
"dropRace": "Drag and drop a Race item here",
"dropClass": "Drag and drop a Class item here"
"dropClass": "Drag and drop a Class item here",
"noContainers": "No containers in inventory",
"noConsumables": "No consumables in inventory",
"noLoot": "No loot in inventory",
"noWeapons": "No weapons",
"noArmor": "No armor or shields",
"noKits": "No kits",
"noEquipment": "No equipment",
"noStoredItems": "Nothing stored"
},
"Miracle": {
"FIELDS": {
@@ -1288,6 +1439,69 @@
"subraceAbilityDescription": {
"label": "Sub-race Ability Description"
},
"notes": {
"label": "Notes"
},
"baseBurden": {
"label": "Base Burden"
}
}
},
"Container": {
"FIELDS": {
"packBurden": {
"label": "Pack Burden (Capacity)"
},
"encLoad": {
"label": "Enc. Load"
},
"cost": {
"label": "Cost"
},
"equipped": {
"label": "Equipped"
},
"description": {
"label": "Description"
},
"notes": {
"label": "Notes"
}
}
},
"Consumable": {
"FIELDS": {
"description": {
"label": "Description"
},
"usesMax": {
"label": "Max Uses"
},
"uses": {
"label": "Uses"
},
"encLoad": {
"label": "Enc. Load"
},
"cost": {
"label": "Cost"
},
"notes": {
"label": "Notes"
}
}
},
"Loot": {
"FIELDS": {
"description": {
"label": "Description"
},
"encLoad": {
"label": "Enc. Load"
},
"cost": {
"label": "Cost"
},
"notes": {
"label": "Notes"
}
@@ -1404,6 +1618,7 @@
"armor": "Armor",
"equipment": "Equipment",
"racial-ability": "Racial Ability",
"ability": "Ability",
"miracle": "Miracle",
"save": "Save",
"shield": "Shield",
@@ -1413,7 +1628,10 @@
"weapon": "Weapon",
"race": "Race",
"class": "Class",
"character-path": "Character Path"
"character-path": "Character Path",
"container": "Container",
"consumable": "Consumable",
"loot": "Loot"
}
}
}
+4
View File
@@ -3,6 +3,7 @@ export { default as PrismRPGMonsterSheet } from "./sheets/monster-sheet.mjs"
export { default as PrismRPGWeaponSheet } from "./sheets/weapon-sheet.mjs"
export { default as PrismRPGSkillSheet } from "./sheets/skill-sheet.mjs"
export { default as PrismRPGRacialAbilitySheet } from "./sheets/racial-ability-sheet.mjs"
export { default as PrismRPGAbilitySheet } from "./sheets/ability-sheet.mjs"
export { default as PrismRPGVulnerabilitySheet } from "./sheets/vulnerability-sheet.mjs"
export { default as PrismRPGArmorSheet } from "./sheets/armor-sheet.mjs"
export { default as PrismRPGSpellSheet } from "./sheets/spell-sheet.mjs"
@@ -12,4 +13,7 @@ export { default as PrismRPGMiracleSheet } from "./sheets/miracle-sheet.mjs"
export { default as PrismRPGRaceSheet } from "./sheets/race-sheet.mjs"
export { default as PrismRPGClassSheet } from "./sheets/class-sheet.mjs"
export { default as PrismRPGCharacterPathSheet } from "./sheets/character-path-sheet.mjs"
export { default as PrismRPGContainerSheet } from "./sheets/container-sheet.mjs"
export { default as PrismRPGConsumableSheet } from "./sheets/consumable-sheet.mjs"
export { default as PrismRPGLootSheet } from "./sheets/loot-sheet.mjs"
export { WeaponTypesConfig } from "./weapon-types-config.mjs"
-191
View File
@@ -1,104 +1,6 @@
/* -------------------------------------------- */
export class PrismRPGCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
static PARTS = {
"header": {
"template": "systems/fvtt-prism-rpg/templates/combat-tracker-header-v2.hbs"
},
"tracker": {
"template": "systems/fvtt-prism-rpg/templates/combat-tracker-v2.hbs"
},
"footer": {
"template": "systems/fvtt-prism-rpg/templates/combat-tracker-footer-v2.hbs"
}
}
static DEFAULT_OPTIONS = foundry.utils.mergeObject(super.DEFAULT_OPTIONS, {
actions: {
initiativePlus: PrismRPGCombatTracker.#initiativePlus,
initiativeMinus: PrismRPGCombatTracker.#initiativeMinus,
},
});
async _prepareContext(options) {
let data = await super._prepareContext(options);
console?.log("Combat Tracker Data", data);
/*for (let u of data.turns) {
let c = game.combat.combatants.get(u.id);
u.progressionCount = c.system.progressionCount
u.isMonster = c.actor.type === "monster"
}
console.log("Combat Data", data);*/
return data;
}
static #initiativePlus(ev) {
ev.preventDefault();
let cId = ev.target.closest(".combatant").dataset.combatantId;
let c = game.combat.combatants.get(cId);
c.update({ 'initiative': c.initiative + 1 });
console.log("Initiative Plus");
}
static #initiativeMinus(ev) {
ev.preventDefault();
let cId = ev.target.closest(".combatant").dataset.combatantId;
let c = game.combat.combatants.get(cId);
let newInit = Math.max(c.initiative - 1, 0);
c.update({ 'initiative': newInit });
}
activateListeners(html) {
super.activateListeners(html);
// Display Combat settings
html.find(".initiative-plus").click(ev => {
ev.preventDefault();
let cId = ev.currentTarget.closest(".combatant").dataset.combatantId;
let c = game.combat.combatants.get(cId);
c.update({ 'initiative': c.initiative + 1 });
});
html.find(".initiative-minus").click(ev => {
ev.preventDefault();
let cId = ev.currentTarget.closest(".combatant").dataset.combatantId;
let c = game.combat.combatants.get(cId);
c.update({ 'initiative': c.initiative - 1 });
console.log("Initiative Minus");
});
}
/* -------------------------------------------- */
static get defaultOptions() {
let path = "systems/fvtt-prism-rpg/templates/combat-tracker.hbs";
return foundry.utils.mergeObject(super.defaultOptions, {
template: path,
});
}
}
export class PrismRPGCombat extends Combat {
/**
* Return the Array of combatants sorted into initiative order, breaking ties alphabetically by name.
* @returns {Combatant[]}
*/
setupTurns() {
console?.log("Setup Turns....");
this.turns ||= [];
// Determine the turn order and the current turn
const turns = this.combatants.contents.sort(this.sortCombatantsLF);
if (this.turn !== null) this.turn = Math.clamp(this.turn, 0, turns.length - 1);
// Update state tracking
let c = turns[this.turn];
this.current = this._getCurrentState(c);
if (!this.previous) this.previous = this.current;
// Return the array of prepared turns
return this.turns = turns;
}
async rollInitiative(ids, options) {
console.log("%%%%%%%%% Roll Initiative", ids, options);
@@ -123,98 +25,5 @@ export class PrismRPGCombat extends Combat {
return this;
}
resetProgression(cId) {
let c = this.combatants.get(cId);
c.update({ 'system.progressionCount': 0 });
}
setCasting(cId) {
let c = this.combatants.get(cId);
c.setFlag(SYSTEM.id, "casting", true);
}
resetCasting(cId) {
let c = this.combatants.get(cId);
c.setFlag(SYSTEM.id, "casting", false);
}
isCasting(cId) {
let c = this.combatants.get(cId);
return c.getFlag(SYSTEM.id, "casting");
}
async nextTurn() {
console.log("NEXT TURN");
let turn = this.turn ?? -1;
let skipDefeated = this.settings.skipDefeated;
// Determine the next turn number
let next = null;
for (let [i, t] of this.turns.entries()) {
console.log("Turn", t);
if (i <= turn) continue;
if (skipDefeated && t.isDefeated) continue;
next = i;
break;
}
// Maybe advance to the next round
let round = this.round;
if ((this.round === 0) || (next === null) || (next >= this.turns.length)) {
return this.nextRound();
}
// Update the document, passing data through a hook first
const updateData = { round, turn: next };
const updateOptions = { advanceTime: CONFIG.time.turnTime, direction: 1 };
Hooks.callAll("combatTurn", this, updateData, updateOptions);
return this.update(updateData, updateOptions);
}
async nextRound() {
this.turnsDone = false
let turn = this.turn === null ? null : 0; // Preserve the fact that it's no-one's turn currently.
console.log("ROUND", this);
let advanceTime = Math.max(this.turns.length - this.turn, 0) * CONFIG.time.turnTime;
advanceTime += CONFIG.time.roundTime;
let nextRound = this.round + 1;
let initOK = true;
for (let c of this.combatants) {
if (c.initiative === null) {
initOK = false;
break;
}
}
if (!initOK) {
ui.notifications.error("All combatants must have initiative rolled before the round can advance.");
return this;
}
for (let c of this.combatants) {
if (nextRound >= c.initiative) {
let user = game.users.find(u => u.active && u.character && u.character.id === c.actor.id);
if (user?.hasPlayerOwner) {
game.socket.emit(`system.${SYSTEM.id}`, { type: "rollProgressionDice", progressionCount: c.system.progressionCount + 1, actorId: c.actor.id, combatId: this.id, combatantId: c.id });
} else {
user = game.users.find(u => u.active && u.isGM);
c.actor.system.rollProgressionDice(this.id, c.id);
}
}
}
// Update the document, passing data through a hook first
const updateData = { round: nextRound, turn };
const updateOptions = { advanceTime, direction: 1 };
Hooks.callAll("combatRound", this, updateData, updateOptions);
return this.update(updateData, updateOptions);
}
sortCombatantsLF(a, b) {
return a.initiative - b.initiative;
}
}
@@ -0,0 +1,50 @@
import PrismRPGItemSheet from "./base-item-sheet.mjs"
export default class PrismRPGAbilitySheet extends PrismRPGItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["ability"],
position: {
width: 600,
},
window: {
contentClasses: ["ability-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-prism-rpg/templates/ability.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* Prepare an array of form header tabs.
* @returns {Record<string, Partial<ApplicationTab>>}
*/
#getTabs() {
const tabs = {
description: { id: "description", group: "primary", label: "PRISMRPG.Label.description" },
effects: { id: "effects", group: "primary", label: "PRISMRPG.Label.effects" },
}
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()
return context
}
}
+378 -9
View File
@@ -18,10 +18,22 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
rollInitiative: PrismRPGCharacterSheet.#onRollInitiative,
armorHitPointsPlus: PrismRPGCharacterSheet.#onArmorHitPointsPlus,
armorHitPointsMinus: PrismRPGCharacterSheet.#onArmorHitPointsMinus,
armorPointsPlus: PrismRPGCharacterSheet.#onArmorPointsPlus,
armorPointsMinus: PrismRPGCharacterSheet.#onArmorPointsMinus,
actionPointsPlus: PrismRPGCharacterSheet.#onActionPointsPlus,
actionPointsMinus: PrismRPGCharacterSheet.#onActionPointsMinus,
manaPointsPlus: PrismRPGCharacterSheet.#onManaPointsPlus,
manaPointsMinus: PrismRPGCharacterSheet.#onManaPointsMinus,
hpPlus: PrismRPGCharacterSheet.#onHpPlus,
hpMinus: PrismRPGCharacterSheet.#onHpMinus,
hpTempPlus: PrismRPGCharacterSheet.#onHpTempPlus,
hpTempMinus: PrismRPGCharacterSheet.#onHpTempMinus,
postItemToChat: PrismRPGCharacterSheet.#onPostItemToChat,
useConsumable: PrismRPGCharacterSheet.#onUseConsumable,
toggleContainerEquipped: PrismRPGCharacterSheet.#onToggleContainerEquipped,
toggleEquipped: PrismRPGCharacterSheet.#onToggleEquipped,
assignToContainer: PrismRPGCharacterSheet.#onAssignToContainer,
removeFromContainer: PrismRPGCharacterSheet.#onRemoveFromContainer,
},
}
@@ -102,12 +114,39 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
classes[1] || null,
classes[2] || null
]
// Burden computed values
const bSubAttr = doc.system.burden.subAttribute
const bSubVal = doc.system.subAttributes[bSubAttr]?.value ?? 0
const baseBurden = doc.itemTypes.race?.[0]?.system.baseBurden ?? 0
context.burdenMax = Math.max(0, baseBurden + bSubVal + doc.system.burden.other)
// Equipped burden: only items with equipped=true count toward burden
const equippableTypes = [
...doc.itemTypes.weapon,
...doc.itemTypes.armor,
...doc.itemTypes.shield,
...doc.itemTypes.equipment,
...doc.itemTypes.container,
]
const burdenEquipped = equippableTypes
.filter(i => i.system.equipped)
.reduce((sum, i) => sum + (i.system.encLoad ?? 0), 0)
context.burdenUsed = burdenEquipped
// Excess equipped burden reduces Movement Rating
const excessBurden = Math.max(0, burdenEquipped - context.burdenMax)
// Movement Rating computed value (excess burden adds to reduction)
const mrSubAttr = doc.system.movementRating.subAttribute
const mrSubVal = doc.system.subAttributes[mrSubAttr]?.value ?? 0
context.movementRatingValue = Math.max(0,
3 + mrSubVal + doc.system.movementRating.other - doc.system.movementRating.reduction - excessBurden
)
context.excessBurden = excessBurden
break
case "skills":
context.tab = context.tabs.skills
context.skills = doc.itemTypes.skill
context.racialAbilities = doc.itemTypes["racial-ability"]
context.vulnerabilities = doc.itemTypes.vulnerability
context.abilities = doc.itemTypes.ability
context.vulnerabilities = doc.itemTypes.vulnerability ?? []
break
case "subattributes":
context.tab = context.tabs.subattributes
@@ -125,7 +164,45 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
break
case "equipment":
context.tab = context.tabs.equipment
context.equipments = doc.itemTypes.equipment
// All items that can be stored in containers
const allStorable = [
...doc.itemTypes.weapon,
...doc.itemTypes.armor,
...doc.itemTypes.shield,
...doc.itemTypes.equipment,
...doc.itemTypes.consumable,
...doc.itemTypes.loot,
]
// Build a map: containerId → items[]
const containerGroups = {}
for (const container of doc.itemTypes.container) {
containerGroups[container.id] = { container, items: [] }
}
for (const item of allStorable) {
const cid = item.system.containerId
if (cid && containerGroups[cid]) {
containerGroups[cid].items.push(item)
}
}
context.containerGroups = Object.values(containerGroups)
// Items are "uncontained" if they have no containerId, or if their container was deleted
const isUncontained = i => !i.system.containerId || !containerGroups[i.system.containerId]
context.weapons = doc.itemTypes.weapon.filter(isUncontained)
context.armors = [...doc.itemTypes.armor, ...doc.itemTypes.shield].filter(isUncontained)
context.consumables = doc.itemTypes.consumable.filter(isUncontained)
context.kits = doc.itemTypes.equipment.filter(i => i.system.isKit && isUncontained(i))
context.equipmentItems = doc.itemTypes.equipment.filter(i => !i.system.isKit && isUncontained(i))
context.loots = doc.itemTypes.loot.filter(isUncontained)
context.containers = doc.itemTypes.container
context.packBurdenMax = doc.itemTypes.container
.filter(c => c.system.equipped)
.reduce((sum, c) => sum + (c.system.packBurden ?? 0), 0)
// Pack burden = items stored in an existing container
context.packBurdenUsed = allStorable
.filter(i => i.system.containerId && containerGroups[i.system.containerId])
.reduce((sum, i) => sum + (i.system.encLoad ?? 0), 0)
break
case "biography":
context.tab = context.tabs.biography
@@ -150,6 +227,18 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
// Handle different data types
if (data.type === "Item") {
const item = await fromUuid(data.uuid)
// Check if dropped onto a container row
const containerEl = event.target.closest("[data-container-id]")
if (containerEl && item?.parent === this.document) {
const containerId = containerEl.dataset.containerId
// Don't store containers inside containers
if (item.type !== "container") {
await item.update({ "system.containerId": containerId })
return
}
}
return this._onDropItem(item)
}
}
@@ -158,43 +247,312 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
await this.document.system.rollInitiative()
}
static #onArmorHitPointsPlus(event, target) {
static async #onArmorHitPointsPlus(event, target) {
let armorHP = this.actor.system.combat.armorHitPoints
armorHP += 1
this.actor.update({ "system.combat.armorHitPoints": armorHP })
}
static #onArmorHitPointsMinus(event, target) {
static async #onArmorHitPointsMinus(event, target) {
let armorHP = this.actor.system.combat.armorHitPoints
armorHP -= 1
this.actor.update({ "system.combat.armorHitPoints": Math.max(armorHP, 0) })
}
static #onManaPointsPlus(event, target) {
static async #onManaPointsPlus(event, target) {
let mana = this.actor.system.manaPoints.value
mana += 1
this.actor.update({ "system.manaPoints.value": Math.min(mana, this.actor.system.manaPoints.max) })
}
static #onManaPointsMinus(event, target) {
static async #onManaPointsMinus(event, target) {
let mana = this.actor.system.manaPoints.value
mana -= 1
this.actor.update({ "system.manaPoints.value": Math.max(mana, 0) })
}
static #onHpPlus(event, target) {
static async #onArmorPointsPlus(event, target) {
let armor = this.actor.system.armorPoints.value
armor += 1
this.actor.update({ "system.armorPoints.value": Math.min(armor, this.actor.system.armorPoints.max) })
}
static async #onArmorPointsMinus(event, target) {
let armor = this.actor.system.armorPoints.value
armor -= 1
this.actor.update({ "system.armorPoints.value": Math.max(armor, 0) })
}
static async#onActionPointsPlus(event, target) {
let actionPoints = this.actor.system.actionPoints.value
actionPoints += 1
this.actor.update({ "system.actionPoints.value": Math.min(actionPoints, this.actor.system.actionPoints.max) })
}
static async#onActionPointsMinus(event, target) {
let actionPoints = this.actor.system.actionPoints.value
actionPoints -= 1
this.actor.update({ "system.actionPoints.value": Math.max(actionPoints, 0) })
}
static async#onHpPlus(event, target) {
let hp = this.actor.system.hp.value
hp += 1
this.actor.update({ "system.hp.value": Math.min(hp, this.actor.system.hp.max) })
}
static #onHpMinus(event, target) {
static async#onHpMinus(event, target) {
let hp = this.actor.system.hp.value
hp -= 1
this.actor.update({ "system.hp.value": Math.max(hp, 0) })
}
static #onCreateEquipment(event, target) {
static async#onHpTempPlus(event, target) {
const temp = this.actor.system.hp.temp
this.actor.update({ "system.hp.temp": temp + 1 })
}
static async#onHpTempMinus(event, target) {
const temp = this.actor.system.hp.temp
this.actor.update({ "system.hp.temp": Math.max(temp - 1, 0) })
}
static async #onCreateEquipment(event, target) {
const itemType = target.dataset.itemType ?? "equipment"
const isKit = target.dataset.itemKit === "true"
const typeLabel = game.i18n.localize(`TYPES.Item.${itemType}`) || itemType
const itemData = {
name: game.i18n.format("DOCUMENT.New", { type: typeLabel }),
type: itemType,
}
if (isKit) itemData["system.isKit"] = true
await this.document.createEmbeddedDocuments("Item", [itemData])
}
static async #onUseConsumable(event, target) {
const itemElement = target.closest("[data-item-id]")
if (!itemElement) return
const item = this.document.items.get(itemElement.dataset.itemId)
if (!item) return
if (item.system.uses <= 0) return
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { title: game.i18n.localize("PRISMRPG.Dialog.useConsumable") },
content: `<p>${game.i18n.format("PRISMRPG.Dialog.useConsumableContent", { name: item.name, uses: item.system.uses })}</p>`,
})
if (!confirmed) return
if (item.system.uses <= 1) {
await item.delete()
} else {
await item.update({ "system.uses": item.system.uses - 1 })
}
}
static async #onToggleContainerEquipped(event, target) {
const itemElement = target.closest("[data-item-id]")
if (!itemElement) return
const item = this.document.items.get(itemElement.dataset.itemId)
if (!item) return
await item.update({ "system.equipped": !item.system.equipped })
}
static async #onToggleEquipped(event, target) {
const itemElement = target.closest("[data-item-id]")
if (!itemElement) return
const item = this.document.items.get(itemElement.dataset.itemId)
if (!item) return
await item.update({ "system.equipped": !item.system.equipped })
}
static async #onAssignToContainer(event, target) {
const itemElement = target.closest("[data-item-id]")
if (!itemElement) return
const item = this.document.items.get(itemElement.dataset.itemId)
if (!item || item.type === "container") return
const containers = this.document.itemTypes.container
if (!containers.length) {
ui.notifications.warn(game.i18n.localize("PRISMRPG.Message.noContainers"))
return
}
const options = containers.map(c => {
const escapedName = foundry.utils.escapeHTML(c.name)
return `<option value="${c.id}">${escapedName}</option>`
}).join("")
const content = `<div class="form-group">
<label>${game.i18n.localize("PRISMRPG.Label.container")}</label>
<div class="form-fields">
<select name="containerId">${options}</select>
</div>
</div>`
const containerId = await foundry.applications.api.DialogV2.prompt({
window: { title: game.i18n.localize("PRISMRPG.Dialog.assignToContainer") },
classes: ["prismrpg"],
content,
ok: {
callback: (event, button) => button.form.elements.containerId.value,
},
})
if (containerId) await item.update({ "system.containerId": containerId })
}
static async #onRemoveFromContainer(event, target) {
const itemElement = target.closest("[data-item-id]")
if (!itemElement) return
const item = this.document.items.get(itemElement.dataset.itemId)
if (!item) return
await item.update({ "system.containerId": "" })
}
static async #onPostItemToChat(event, target) {
console.log("PRISM RPG | PostItemToChat action triggered", { event: event, target: target })
// Try to find the item element from the clicked target or its parents
let itemElement = null
// First try with the target (the actual clicked element)
if (event.target) {
itemElement = event.target.closest('[data-item-id]')
}
// If not found, try with currentTarget (the element with the action)
if (!itemElement && event.currentTarget) {
itemElement = event.currentTarget.closest('[data-item-id]')
}
// If still not found, try with the target parameter
if (!itemElement && target) {
itemElement = target.closest('[data-item-id]')
}
console.log("PRISM RPG | Found item element", { itemElement: itemElement })
if (!itemElement) {
console.warn("PRISM RPG | Could not find item element for posting to chat")
return
}
const itemId = itemElement.dataset.itemId
if (!itemId) {
console.warn("PRISM RPG | Item ID not found for posting to chat")
return
}
const item = this.actor.items.get(itemId)
if (!item) {
console.warn("PRISM RPG | Item not found for posting to chat", { itemId: itemId })
return
}
// Create a chat message with the item data
const speaker = ChatMessage.getSpeaker({ actor: this.actor })
const content = await this.formatItemForChat(item)
await ChatMessage.create({
content: content,
speaker: speaker,
})
}
async formatItemForChat(item) {
// Format the item data for chat display
const itemTypeClass = `${item.type}-item`
let htmlContent = `
<div class="prismrpg-item-chat-card ${itemTypeClass}">
<div class="item-header">
<h3>${item.name}</h3>
<span class="item-type">${game.i18n.localize(`TYPES.Item.${item.type}`) || item.type}</span>
</div>
`
// Add item image if available
if (item.img && !item.img.includes('icons/svg/mystery-man.svg')) {
htmlContent += `<div class="item-image"><img src="${item.img}" alt="${item.name}"></div>`
}
// Add item description if available
if (item.system.description && item.system.description.trim() !== '') {
const enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.system.description, { async: true })
htmlContent += `<div class="item-description">${enrichedDescription}</div>`
}
// Add specific item data based on item type
htmlContent += `<div class="item-details">`
switch (item.type) {
case 'weapon':
htmlContent += `
<div class="item-detail"><strong>Type:</strong> ${item.system.weaponType || 'Unknown'}</div>
<div class="item-detail"><strong>Damage:</strong> ${item.system.damage || 'N/A'}</div>
<div class="item-detail"><strong>APC:</strong> ${item.system.apc || 0}</div>
`
if (item.system.damageType) {
const damageTypes = []
if (item.system.damageType.piercing) damageTypes.push('Piercing')
if (item.system.damageType.bludgeoning) damageTypes.push('Bludgeoning')
if (item.system.damageType.slashing) damageTypes.push('Slashing')
if (damageTypes.length > 0) {
htmlContent += `<div class="item-detail"><strong>Damage Type:</strong> ${damageTypes.join('/')}</div>`
}
}
break
case 'armor':
htmlContent += `
<div class="item-detail"><strong>Armor Type:</strong> ${item.system.armorType || 'Unknown'}</div>
<div class="item-detail"><strong>Armor Points:</strong> ${item.system.armorPoints || 0}</div>
<div class="item-detail"><strong>APC:</strong> ${item.system.apc || 0}</div>
`
break
case 'shield':
htmlContent += `
<div class="item-detail"><strong>Shield Type:</strong> ${item.system.shieldType || 'Unknown'}</div>
<div class="item-detail"><strong>Armor Points:</strong> ${item.system.armorPoints || 0}</div>
<div class="item-detail"><strong>APC:</strong> ${item.system.apc || 0}</div>
`
break
case 'skill':
htmlContent += `
<div class="item-detail"><strong>Modifier:</strong> ${item.system.modifier || 0}</div>
<div class="item-detail"><strong>Core Skill:</strong> ${item.system.isCoreSkill ? 'Yes' : 'No'}</div>
`
break
case 'spell':
htmlContent += `
<div class="item-detail"><strong>Level:</strong> ${item.system.level || 'Unknown'}</div>
<div class="item-detail"><strong>Mana Cost:</strong> ${item.system.manaCost || 0}</div>
<div class="item-detail"><strong>Casting Time:</strong> ${item.system.castingTime || 'N/A'}</div>
`
break
case 'miracle':
htmlContent += `
<div class="item-detail"><strong>Prayer Time:</strong> ${item.system.prayerTime || 'N/A'}</div>
`
break
case 'equipment':
htmlContent += `
<div class="item-detail"><strong>Weight:</strong> ${item.system.weight || 'N/A'}</div>
`
break
default:
// For other item types, just show basic info
htmlContent += `<div class="item-detail"><strong>Item Type:</strong> ${item.type}</div>`
}
htmlContent += `</div></div>`
return htmlContent
}
_onRender(context, options) {
@@ -213,6 +571,17 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
this.actor.update({ "system.hp.wounds": tab });
})
}
// Container drag-over highlight
this.element.querySelectorAll("[data-container-id]").forEach(el => {
el.addEventListener("dragover", (e) => {
e.preventDefault()
el.classList.add("drag-over")
})
el.addEventListener("dragleave", () => el.classList.remove("drag-over"))
el.addEventListener("drop", () => el.classList.remove("drag-over"))
})
super._onRender();
}
+137
View File
@@ -32,6 +32,7 @@ export default class PrismRPGClassSheet extends PrismRPGItemSheet {
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "PRISMRPG.Label.details" },
advancements: { id: "advancements", group: "primary", label: "PRISMRPG.Label.advancement" },
description: { id: "description", group: "primary", label: "PRISMRPG.Label.description" },
}
for (const v of Object.values(tabs)) {
@@ -56,6 +57,142 @@ export default class PrismRPGClassSheet extends PrismRPGItemSheet {
context.enrichedFeatures[key] = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.features[key], { async: true })
}
// Enrich all advancement descriptions
context.enrichedAdvancements = {}
context.advancementsByLevel = []
for (let i = 1; i <= 10; i++) {
const key = `level${i}`
const advancements = this.document.system.advancements[key] || []
context.enrichedAdvancements[key] = []
const enrichedAdvancementsList = []
for (let j = 0; j < advancements.length; j++) {
const enrichedDesc = await foundry.applications.ux.TextEditor.implementation.enrichHTML(advancements[j].description, { async: true })
const enrichedAdv = {
...advancements[j],
enrichedDescription: enrichedDesc,
index: j,
levelKey: key
}
context.enrichedAdvancements[key].push(enrichedAdv)
enrichedAdvancementsList.push(enrichedAdv)
}
context.advancementsByLevel.push({
level: i,
levelKey: key,
advancements: enrichedAdvancementsList
})
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
// Add advancement button listeners
this.element.querySelectorAll(".add-advancement").forEach(btn => {
btn.addEventListener("click", this._onAddAdvancement.bind(this))
})
// Delete advancement button listeners
this.element.querySelectorAll(".delete-advancement").forEach(btn => {
btn.addEventListener("click", this._onDeleteAdvancement.bind(this))
})
// Edit advancement icon listeners
this.element.querySelectorAll(".advancement-icon").forEach(img => {
img.addEventListener("click", this._onEditAdvancementIcon.bind(this))
})
// Toggle advancement description listeners
this.element.querySelectorAll(".toggle-advancement-description").forEach(btn => {
btn.addEventListener("click", this._onToggleAdvancementDescription.bind(this))
})
}
/**
* Handle toggling advancement description visibility
* @param {Event} event
*/
_onToggleAdvancementDescription(event) {
event.preventDefault()
const button = event.currentTarget
const item = button.closest(".advancement-item")
const description = item.querySelector(".advancement-description")
const icon = button.querySelector("i")
description.classList.toggle("collapsed")
if (description.classList.contains("collapsed")) {
icon.classList.remove("fa-chevron-up")
icon.classList.add("fa-chevron-down")
} else {
icon.classList.remove("fa-chevron-down")
icon.classList.add("fa-chevron-up")
}
}
/**
* Handle adding a new advancement to a level
* @param {Event} event
*/
async _onAddAdvancement(event) {
event.preventDefault()
const level = event.currentTarget.dataset.level
const advancements = foundry.utils.deepClone(this.document.system.advancements[level] || [])
advancements.push({
icon: "systems/fvtt-prism-rpg/assets/icons/advancement.svg",
name: "",
description: ""
})
await this.document.update({
[`system.advancements.${level}`]: advancements
})
}
/**
* Handle deleting an advancement from a level
* @param {Event} event
*/
async _onDeleteAdvancement(event) {
event.preventDefault()
const level = event.currentTarget.dataset.level
const index = parseInt(event.currentTarget.dataset.index)
const advancements = foundry.utils.deepClone(this.document.system.advancements[level] || [])
advancements.splice(index, 1)
await this.document.update({
[`system.advancements.${level}`]: advancements
})
}
/**
* Handle editing an advancement icon
* @param {Event} event
*/
async _onEditAdvancementIcon(event) {
event.preventDefault()
const level = event.currentTarget.dataset.level
const index = parseInt(event.currentTarget.dataset.index)
const fp = new FilePicker({
type: "image",
current: this.document.system.advancements[level][index].icon,
callback: async (path) => {
const advancements = foundry.utils.deepClone(this.document.system.advancements[level] || [])
advancements[index].icon = path
await this.document.update({
[`system.advancements.${level}`]: advancements
})
}
})
fp.render(true)
}
}
@@ -0,0 +1,44 @@
import PrismRPGItemSheet from "./base-item-sheet.mjs"
export default class PrismRPGConsumableSheet extends PrismRPGItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["consumable"],
position: { width: 550 },
window: { contentClasses: ["consumable-content"] },
}
/** @override */
static PARTS = {
main: { template: "systems/fvtt-prism-rpg/templates/consumable.hbs" },
}
/** @override */
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "PRISMRPG.Label.details" },
description: { id: "description", group: "primary", label: "PRISMRPG.Label.description" },
effects: { id: "effects", group: "primary", label: "PRISMRPG.Label.effects" },
}
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
}
}
@@ -0,0 +1,52 @@
import PrismRPGItemSheet from "./base-item-sheet.mjs"
export default class PrismRPGContainerSheet extends PrismRPGItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["container"],
position: {
width: 600,
},
window: {
contentClasses: ["container-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-prism-rpg/templates/container.hbs",
},
}
/** @override */
tabGroups = {
primary: "details",
}
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "PRISMRPG.Label.details" },
description: { id: "description", group: "primary", label: "PRISMRPG.Label.description" },
effects: { id: "effects", group: "primary", label: "PRISMRPG.Label.effects" },
}
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
}
}
+44
View File
@@ -0,0 +1,44 @@
import PrismRPGItemSheet from "./base-item-sheet.mjs"
export default class PrismRPGLootSheet extends PrismRPGItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["loot"],
position: { width: 500 },
window: { contentClasses: ["loot-content"] },
}
/** @override */
static PARTS = {
main: { template: "systems/fvtt-prism-rpg/templates/loot.hbs" },
}
/** @override */
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "PRISMRPG.Label.details" },
description: { id: "description", group: "primary", label: "PRISMRPG.Label.description" },
effects: { id: "effects", group: "primary", label: "PRISMRPG.Label.effects" },
}
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
}
}
+89
View File
@@ -0,0 +1,89 @@
/**
* Afflictions — negative status effects (Mundane & Magic)
* @type {StatusEffectConfig[]}
*/
export const AFFLICTIONS = [
// ── Mundane ────────────────────────────────────────────────────────────────
{ id: "aff-alkalized", name: "PRISMRPG.Status.Alkalized", icon: "icons/svg/acid.svg", category: "affliction", typing: "mundane" },
{ id: "aff-bleed", name: "PRISMRPG.Status.Bleed", icon: "icons/svg/blood.svg", category: "affliction", typing: "mundane" },
{ id: "aff-blind", name: "PRISMRPG.Status.Blind", icon: "icons/svg/blind.svg", category: "affliction", typing: "mundane" },
{ id: "aff-deaf", name: "PRISMRPG.Status.Deaf", icon: "icons/svg/deaf.svg", category: "affliction", typing: "mundane" },
{ id: "aff-diseased", name: "PRISMRPG.Status.Diseased", icon: "icons/svg/biohazard.svg", category: "affliction", typing: "mundane" },
{ id: "aff-distracted", name: "PRISMRPG.Status.Distracted", icon: "icons/svg/daze.svg", category: "affliction", typing: "mundane" },
{ id: "aff-exhaustion", name: "PRISMRPG.Status.Exhaustion", icon: "icons/svg/sleep.svg", category: "affliction", typing: "mundane" },
{ id: "aff-frightened", name: "PRISMRPG.Status.Frightened", icon: "icons/svg/terror.svg", category: "affliction", typing: "mundane" },
{ id: "aff-marked", name: "PRISMRPG.Status.Marked", icon: "icons/svg/target.svg", category: "affliction", typing: "both" },
{ id: "aff-mute", name: "PRISMRPG.Status.Mute", icon: "icons/svg/silenced.svg", category: "affliction", typing: "mundane" },
{ id: "aff-paralyzed", name: "PRISMRPG.Status.Paralyzed", icon: "icons/svg/paralysis.svg", category: "affliction", typing: "mundane" },
{ id: "aff-petrified", name: "PRISMRPG.Status.Petrified", icon: "icons/svg/frozen.svg", category: "affliction", typing: "mundane" },
{ id: "aff-poison", name: "PRISMRPG.Status.Poison", icon: "icons/svg/poison.svg", category: "affliction", typing: "mundane" },
{ id: "aff-prone", name: "PRISMRPG.Status.Prone", icon: "icons/svg/falling.svg", category: "affliction", typing: "mundane" },
{ id: "aff-rage", name: "PRISMRPG.Status.Rage", icon: "icons/svg/fire.svg", category: "affliction", typing: "mundane" },
{ id: "aff-sealed", name: "PRISMRPG.Status.Sealed", icon: "icons/svg/net.svg", category: "affliction", typing: "mundane" },
{ id: "aff-staggered", name: "PRISMRPG.Status.Staggered", icon: "icons/svg/daze.svg", category: "affliction", typing: "mundane" },
{ id: "aff-stunned", name: "PRISMRPG.Status.Stunned", icon: "icons/svg/stun.svg", category: "affliction", typing: "mundane" },
{ id: "aff-taunt", name: "PRISMRPG.Status.Taunt", icon: "icons/svg/eye.svg", category: "affliction", typing: "mundane" },
{ id: "aff-unconscious",name: "PRISMRPG.Status.Unconscious",icon: "icons/svg/unconscious.svg", category: "affliction", typing: "mundane" },
{ id: "aff-wounded", name: "PRISMRPG.Status.Wounded", icon: "icons/svg/degen.svg", category: "affliction", typing: "mundane" },
// ── Magic ──────────────────────────────────────────────────────────────────
{ id: "aff-banished", name: "PRISMRPG.Status.Banished", icon: "icons/svg/wing.svg", category: "affliction", typing: "magic" },
{ id: "aff-seep", name: "PRISMRPG.Status.Seep", icon: "icons/svg/acid.svg", category: "affliction", typing: "magic" },
{ id: "aff-sightless", name: "PRISMRPG.Status.Sightless", icon: "icons/svg/blind.svg", category: "affliction", typing: "magic" },
{ id: "aff-cursed", name: "PRISMRPG.Status.Cursed", icon: "icons/svg/sun.svg", category: "affliction", typing: "magic" },
{ id: "aff-soundless", name: "PRISMRPG.Status.Soundless", icon: "icons/svg/deaf.svg", category: "affliction", typing: "magic" },
{ id: "aff-plagued", name: "PRISMRPG.Status.Plagued", icon: "icons/svg/biohazard.svg", category: "affliction", typing: "magic" },
{ id: "aff-compulsed", name: "PRISMRPG.Status.Compulsed", icon: "icons/svg/eye.svg", category: "affliction", typing: "magic" },
{ id: "aff-fatigue", name: "PRISMRPG.Status.Fatigue", icon: "icons/svg/sleep.svg", category: "affliction", typing: "magic" },
{ id: "aff-horror", name: "PRISMRPG.Status.Horror", icon: "icons/svg/terror.svg", category: "affliction", typing: "magic" },
{ id: "aff-madness", name: "PRISMRPG.Status.Madness", icon: "icons/svg/daze.svg", category: "affliction", typing: "magic" },
{ id: "aff-silenced", name: "PRISMRPG.Status.Silenced", icon: "icons/svg/silenced.svg", category: "affliction", typing: "magic" },
{ id: "aff-locked", name: "PRISMRPG.Status.Locked", icon: "icons/svg/net.svg", category: "affliction", typing: "magic" },
{ id: "aff-dazed", name: "PRISMRPG.Status.Dazed", icon: "icons/svg/daze.svg", category: "affliction", typing: "magic" },
{ id: "aff-numbed", name: "PRISMRPG.Status.Numbed", icon: "icons/svg/frozen.svg", category: "affliction", typing: "magic" },
{ id: "aff-comatose", name: "PRISMRPG.Status.Comatose", icon: "icons/svg/unconscious.svg", category: "affliction", typing: "magic" },
{ id: "aff-shattered", name: "PRISMRPG.Status.Shattered", icon: "icons/svg/blood.svg", category: "affliction", typing: "magic" },
// ── Elemental (Magic) ──────────────────────────────────────────────────────
{ id: "aff-burning", name: "PRISMRPG.Status.Burning", icon: "icons/svg/fire.svg", category: "affliction", typing: "magic" },
{ id: "aff-chilled", name: "PRISMRPG.Status.Chilled", icon: "icons/svg/frozen.svg", category: "affliction", typing: "magic" },
{ id: "aff-corroded", name: "PRISMRPG.Status.Corroded", icon: "icons/svg/acid.svg", category: "affliction", typing: "magic" },
{ id: "aff-necrosis", name: "PRISMRPG.Status.Necrosis", icon: "icons/svg/degen.svg", category: "affliction", typing: "magic" },
{ id: "aff-radiated", name: "PRISMRPG.Status.Radiated", icon: "icons/svg/lightning.svg", category: "affliction", typing: "magic" },
{ id: "aff-shocked", name: "PRISMRPG.Status.Shocked", icon: "icons/svg/lightning.svg", category: "affliction", typing: "magic" },
]
/**
* Imbuements — positive status effects (Mundane & Magic)
* @type {StatusEffectConfig[]}
*/
export const IMBUEMENTS = [
// ── Mundane ────────────────────────────────────────────────────────────────
{ id: "imb-aided", name: "PRISMRPG.Status.Aided", icon: "icons/svg/upgrade.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-alert", name: "PRISMRPG.Status.Alert", icon: "icons/svg/eye.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-alkalized", name: "PRISMRPG.Status.Alkalized", icon: "icons/svg/acid.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-bestowed", name: "PRISMRPG.Status.Bestowed", icon: "icons/svg/angel.svg", category: "imbuement", typing: "both" },
{ id: "imb-concealed", name: "PRISMRPG.Status.Concealed", icon: "icons/svg/invisible.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-enhance", name: "PRISMRPG.Status.Enhance", icon: "icons/svg/upgrade.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-inspired", name: "PRISMRPG.Status.Inspired", icon: "icons/svg/regen.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-keen", name: "PRISMRPG.Status.Keen", icon: "icons/svg/eye.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-life-drain", name: "PRISMRPG.Status.LifeDrain", icon: "icons/svg/regen.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-madness", name: "PRISMRPG.Status.Madness", icon: "icons/svg/daze.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-prepared", name: "PRISMRPG.Status.Prepared", icon: "icons/svg/upgrade.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-rage", name: "PRISMRPG.Status.Rage", icon: "icons/svg/fire.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-reinforced", name: "PRISMRPG.Status.Reinforced", icon: "icons/svg/mage-shield.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-renewed", name: "PRISMRPG.Status.Renewed", icon: "icons/svg/regen.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-supplied", name: "PRISMRPG.Status.Supplied", icon: "icons/svg/upgrade.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-surged", name: "PRISMRPG.Status.Surged", icon: "icons/svg/lightning.svg", category: "imbuement", typing: "mundane" },
{ id: "imb-trance", name: "PRISMRPG.Status.Trance", icon: "icons/svg/sleep.svg", category: "imbuement", typing: "mundane" },
// ── Magic ──────────────────────────────────────────────────────────────────
{ id: "imb-blessed", name: "PRISMRPG.Status.Blessed", icon: "icons/svg/angel.svg", category: "imbuement", typing: "magic" },
{ id: "imb-anchored", name: "PRISMRPG.Status.Anchored", icon: "icons/svg/net.svg", category: "imbuement", typing: "magic" },
{ id: "imb-saturated", name: "PRISMRPG.Status.Saturated", icon: "icons/svg/regen.svg", category: "imbuement", typing: "magic" },
{ id: "imb-invisible", name: "PRISMRPG.Status.Invisible", icon: "icons/svg/invisible.svg", category: "imbuement", typing: "magic" },
{ id: "imb-enchanted", name: "PRISMRPG.Status.Enchanted", icon: "icons/svg/mage-shield.svg", category: "imbuement", typing: "magic" },
{ id: "imb-heroism", name: "PRISMRPG.Status.Heroism", icon: "icons/svg/upgrade.svg", category: "imbuement", typing: "magic" },
{ id: "imb-mana-drain", name: "PRISMRPG.Status.ManaDrain", icon: "icons/svg/regen.svg", category: "imbuement", typing: "magic" },
{ id: "imb-fury", name: "PRISMRPG.Status.Fury", icon: "icons/svg/fire.svg", category: "imbuement", typing: "magic" },
{ id: "imb-warded", name: "PRISMRPG.Status.Warded", icon: "icons/svg/holy-shield.svg", category: "imbuement", typing: "magic" },
{ id: "imb-regeneration", name: "PRISMRPG.Status.Regeneration", icon: "icons/svg/regen.svg", category: "imbuement", typing: "magic" },
{ id: "imb-haste", name: "PRISMRPG.Status.Haste", icon: "icons/svg/wingfoot.svg", category: "imbuement", typing: "magic" },
]
+23 -9
View File
@@ -261,15 +261,29 @@ export const CHOICE_MODIFIERS = {
}
export const ASCII = `
······················································································································
: :
:@@@ @@@@@@@@ @@@@@@@ @@@ @@@ @@@@@@ @@@ @@@@@@@@ @@@@@@ @@@ @@@ @@@@@@@ @@@@@@ @@@@@@ @@@ @@@ :
:@@! @@! @!! @@! @@@ @@! @@@ @@! @@! @@! @@@ @@!@!@@@ @!! @@! @@@ !@@ @@! !@@ :
:@!! @!!!:! @!! @!@!@!@! @!@!@!@! @!! @!!!:! @!@!@!@! @!@@!!@! @!! @!@!@!@! !@@!! !@!@! :
:!!: !!: !!: !!: !!! !!: !!! !!: !!: !!: !!! !!: !!! !!: !!: !!! !:! !!: :
:: ::.: : : :: :: : : : : : : : : ::.: : : : : : :: : : : : : ::.: : .: :
: :
······················································································································
┌─────────────────────────────────────────────────────┐
│8888888b. 8888888b. 8888888 .d8888b. 888b d888│
│888 Y88b 888 Y88b 888 d88P Y88b 8888b d8888│
│888 888 888 888 888 Y88b. 88888b.d88888│
│888 d88P 888 d88P 888 "Y888b. 888Y88888P888│
│8888888P" 8888888P" 888 "Y88b. 888 Y888P 888│
│888 888 T88b 888 "888 888 Y8P 888│
│888 888 T88b 888 Y88b d88P 888 " 888│
│888 888 T88b 8888888 "Y8888P" 888 888│
│ │
│ │
│ │
│8888888b. 8888888b. .d8888b. │
│888 Y88b 888 Y88b d88P Y88b │
│888 888 888 888 888 888 │
│888 d88P 888 d88P 888 │
│8888888P" 8888888P" 888 88888 │
│888 T88b 888 888 888 │
│888 T88b 888 Y88b d88P │
│888 T88b 888 "Y8888P88 │
└─────────────────────────────────────────────────────┘
`
/**
+16 -32
View File
@@ -36,8 +36,7 @@ export default class PrismRPGActor extends Actor {
if (this.type === "character") {
Object.assign(prototypeToken, {
sight: { enabled: true },
actorLink: true,
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY,
actorLink: false
})
this.updateSource({ prototypeToken })
}
@@ -119,18 +118,27 @@ export default class PrismRPGActor extends Actor {
rollTarget = this.items.find((i) => i.type === "miracle" && i.id === rollKey)
rollTarget.rollKey = rollKey
break
case "skill":
case "skill": {
rollTarget = this.items.find((i) => i.type === "skill" && i.id === rollKey)
rollTarget.rollKey = rollKey
if (rollTarget.system.category === "weapon") {
ui.notifications.warn(game.i18n.localize("PRISMRPG.Notifications.rollFromWeapon"))
return
}
// Get the primary attribute for D&D 5e style rolls
const attrKey = rollTarget.system.primaryAttribute || "dex"
rollTarget.characteristicValue = this.system.characteristics[attrKey].value
// Get the two sub-attributes for this skill
const subAttr1 = rollTarget.system.subAttribute1 || "prowess"
const subAttr2 = rollTarget.system.subAttribute2 || "initiative"
// Store both sub-attribute values for the dialog to choose from
rollTarget.subAttribute1 = subAttr1
rollTarget.subAttribute2 = subAttr2
rollTarget.subAttribute1Value = this.system.subAttributes?.[subAttr1]?.value || 0
rollTarget.subAttribute2Value = this.system.subAttributes?.[subAttr2]?.value || 0
rollTarget.subAttribute1Label = game.i18n.localize(SYSTEM.SUB_ATTRIBUTES?.[subAttr1]?.label || subAttr1)
rollTarget.subAttribute2Label = game.i18n.localize(SYSTEM.SUB_ATTRIBUTES?.[subAttr2]?.label || subAttr2)
rollTarget.proficiencyBonus = rollTarget.system.modifier
break
}
case "spell-attack":
case "spell-power":
case "spell-cast":
@@ -150,38 +158,14 @@ export default class PrismRPGActor extends Actor {
case "weapon-damage-medium":
case "weapon-attack": {
let weapon = this.items.find((i) => i.type === "weapon" && i.id === rollKey)
let skill
let skills = this.items.filter((i) => i.type === "skill" && i.name.toLowerCase() === weapon.name.toLowerCase())
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
} else {
skills = this.items.filter((i) => i.type === "skill" && i.name.toLowerCase().replace(" skill", "") === weapon.name.toLowerCase())
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
} else {
skills = this.items.filter((i) => i.type === "skill" && i.system.weaponClass === weapon.system.weaponClass)
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 0.5)
} else {
skills = this.items.filter((i) => i.type === "skill" && i.system.weaponClass.includes(SYSTEM.WEAPON_CATEGORIES[weapon.system.weaponClass]))
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 0.25)
} else {
ui.notifications.warn(game.i18n.localize("PRISMRPG.Notifications.skillNotFound"))
return
}
}
}
}
if (!weapon || !skill) {
console.error("Weapon or skill not found", weapon, skill)
if (!weapon) {
console.error("Weapon not found", weapon, skill)
ui.notifications.warn(game.i18n.localize("PRISMRPG.Notifications.skillNotFound"))
return
}
// Create a plain object for rollTarget to ensure weapon data is preserved
rollTarget = {
...skill.toObject(),
weapon: weapon.toObject(),
rollKey: rollKey,
combat: foundry.utils.duplicate(this.system.combat),
+1
View File
@@ -9,6 +9,7 @@ export const defaultItemImg = {
race: "systems/fvtt-prism-rpg/assets/icons/icon_race.webp",
class: "systems/fvtt-prism-rpg/assets/icons/icon_class.webp",
"character-path": "systems/fvtt-prism-rpg/assets/icons/icon_character_path.webp",
container: "icons/containers/bags/pack-leather-brown.webp",
}
export default class PrismRPGItem extends Item {
File diff suppressed because it is too large Load Diff
+32 -5
View File
@@ -127,11 +127,10 @@ export default class PrismRPGRoll extends Roll {
case "skill":
options.rollName = options.rollTarget.name
// D&D 5e style: ability modifier + proficiency bonus
const skillCharValue = options.rollTarget.characteristicValue
const skillAbilityMod = this.getAbilityModifier(skillCharValue)
// D&D 5e style: sub-attribute modifier + proficiency bonus
// Default to first sub-attribute, will be recalculated if player chooses different one
const proficiency = options.rollTarget.proficiencyBonus || 0
options.rollTarget.value = skillAbilityMod + proficiency
options.rollTarget.value = options.rollTarget.subAttribute1Value + proficiency
break
case "weapon-attack":
@@ -284,7 +283,8 @@ export default class PrismRPGRoll extends Roll {
attackerAimChoices,
hasTarget: options.hasTarget,
modifier: "+0",
advantage: "none"
advantage: "none",
config: SYSTEM
}
const content = await foundry.applications.handlebars.renderTemplate(
@@ -342,6 +342,15 @@ export default class PrismRPGRoll extends Roll {
}
}
// Recalculate bonus if player chose different sub-attribute for skill
if (rollContext.skillSubAttribute && options.rollType === "skill") {
const chosenSubAttrValue = rollContext.skillSubAttribute === options.rollTarget.subAttribute1 ?
options.rollTarget.subAttribute1Value :
options.rollTarget.subAttribute2Value
const proficiencyBonus = options.rollTarget.proficiencyBonus || 0
bonus = chosenSubAttrValue + proficiencyBonus
}
let extraModifier = rollContext.modifier === "" ? 0 : Number.parseInt(rollContext.modifier, 10)
totalModifier = bonus + extraModifier
@@ -394,6 +403,21 @@ export default class PrismRPGRoll extends Roll {
mentalCharValue = options.rollTarget.mentalCharValue
}
// Store skill sub-attribute information
let skillSubAttribute = null
let skillSubAttributeLabel = null
let skillSubAttributeValue = null
if (options.rollType === "skill" && rollContext.skillSubAttribute) {
skillSubAttribute = rollContext.skillSubAttribute
const subAttrConfig = SYSTEM.SUB_ATTRIBUTES?.[skillSubAttribute]
if (subAttrConfig) {
skillSubAttributeLabel = game.i18n.localize(subAttrConfig.label)
}
skillSubAttributeValue = rollContext.skillSubAttribute === options.rollTarget.subAttribute1 ?
options.rollTarget.subAttribute1Value :
options.rollTarget.subAttribute2Value
}
const rollData = {
type: options.rollType,
rollType: options.rollType,
@@ -411,6 +435,9 @@ export default class PrismRPGRoll extends Roll {
manaUpkeep,
mentalCharacteristic,
mentalCharValue,
skillSubAttribute,
skillSubAttributeLabel,
skillSubAttributeValue,
...rollContext,
}
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -6,9 +6,11 @@ export { default as PrismRPGSkill } from "./skill.mjs"
export { default as PrismRPGArmor } from "./armor.mjs"
export { default as PrismRPGShield } from "./shield.mjs"
export { default as PrismRPGRacialAbility } from "./racial-ability.mjs"
export { default as PrismRPGVulnerability } from "./vulnerability.mjs"
export { default as PrismRPGAbility } from "./ability.mjs"
export { default as PrismRPGEquipment } from "./equipment.mjs"
export { default as PrismRPGMiracle } from "./miracle.mjs"
export { default as PrismRPGRace } from "./race.mjs"
export { default as PrismRPGClass } from "./class.mjs"
export { default as PrismRPGCharacterPath } from "./character-path.mjs"
export { default as PrismRPGContainer } from "./container.mjs"
export { default as PrismRPGConsumable } from "./consumable.mjs"
export { default as PrismRPGLoot } from "./loot.mjs"
+14
View File
@@ -0,0 +1,14 @@
export default class PrismRPGAbility extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["PRISMRPG.Ability"]
}
+26
View File
@@ -25,6 +25,7 @@ export default class PrismRPGArmor extends foundry.abstract.TypeDataModel {
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.money = new fields.StringField({ required: true, initial: "coppercoin", choices: SYSTEM.MONEY })
schema.containerId = new fields.StringField({ required: false, initial: "", nullable: false })
return schema
}
@@ -32,4 +33,29 @@ export default class PrismRPGArmor extends foundry.abstract.TypeDataModel {
/** @override */
static LOCALIZATION_PREFIXES = ["PRISMRPG.Armor"]
static migrateData(data) {
// Migrate old money types to new ones
if (data?.money) {
const moneyMigration = {
"tinbit": "coppercoin",
"copper": "coppercoin",
"silver": "silvercoin",
"gold": "goldcoin",
"platinum": "note"
}
if (moneyMigration[data.money]) {
data.money = moneyMigration[data.money]
}
// If still invalid, default to coppercoin
if (!SYSTEM.MONEY[data.money]) {
console.warn(`Prism RPG | Migrate armor: Invalid money type "${data.money}", defaulting to coppercoin`)
data.money = "coppercoin"
}
}
return super.migrateData(data)
}
}
+65 -50
View File
@@ -89,6 +89,11 @@ export default class PrismRPGCharacter extends foundry.abstract.TypeDataModel {
max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.actionPoints = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.biodata = new fields.SchemaField({
level: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }),
alignment: new fields.StringField({ required: true, nullable: false, initial: "" }),
@@ -131,6 +136,21 @@ export default class PrismRPGCharacter extends foundry.abstract.TypeDataModel {
}, {}),
)
// Sub-attribute choices for movement rating and burden selectors
const subAttributeChoices = () =>
Object.values(SYSTEM.SUB_ATTRIBUTES).reduce((obj, s) => { obj[s.id] = s.label; return obj }, {})
schema.movementRating = new fields.SchemaField({
subAttribute: new fields.StringField({ required: true, initial: "stamina", choices: subAttributeChoices }),
other: new fields.NumberField({ ...requiredInteger, initial: 0 }),
reduction: new fields.NumberField({ ...requiredInteger, initial: 0 })
})
schema.burden = new fields.SchemaField({
subAttribute: new fields.StringField({ required: true, initial: "vigor", choices: subAttributeChoices }),
other: new fields.NumberField({ ...requiredInteger, initial: 0 })
})
return schema
}
@@ -165,6 +185,21 @@ export default class PrismRPGCharacter extends foundry.abstract.TypeDataModel {
prepareDerivedData() {
super.prepareDerivedData();
// Calculate action points max based on level
const level = this.biodata.level
let actionPointsMax = 4
if (level >= 3 && level <= 5) {
actionPointsMax = 5
} else if (level >= 6 && level <= 8) {
actionPointsMax = 6
} else if (level >= 9 && level <= 10) {
actionPointsMax = 7
}
// Set max action points (but don't override if already set to a higher value)
if (this.actionPoints.max < actionPointsMax) {
this.actionPoints.max = actionPointsMax
}
// Calculate sub-attributes from parent characteristics
// Sub-attribute = lowest ability modifier between the two parent characteristics
for (let subAttrKey in SYSTEM.SUB_ATTRIBUTES) {
@@ -241,66 +276,46 @@ export default class PrismRPGCharacter extends foundry.abstract.TypeDataModel {
await roll.toMessage({}, { rollMode: roll.options.rollMode })
}
/**
* Rolls initiative for the character: 1d20 + initiative modifier
* @param {string} combatId - Optional combat ID to update
* @param {string} combatantId - Optional combatant ID to update
* @returns {Promise<Roll|null>} The initiative roll or null if cancelled
*/
async rollInitiative(combatId = undefined, combatantId = undefined) {
const hasTarget = false
let actorClass = this.biodata.class;
// Get the initiative sub-attribute modifier
const initiativeModifier = this.subAttributes.initiative.value
let wisDef = SYSTEM.CHARACTERISTICS_TABLES.wis.find((c) => c.value === this.characteristics.wis.value)
let maxInit = Number(wisDef.init_cap) || 1000
// Create the roll formula: 1d20 + initiative modifier
const formula = `1d20 + ${initiativeModifier}`
let roll = await PrismRPGRoll.promptInitiative({
actorId: this.parent.id,
actorName: this.parent.name,
actorImage: this.parent.img,
combatId,
combatantId,
actorClass,
maxInit,
// Roll the initiative
let initRoll = new Roll(formula)
await initRoll.evaluate()
// Create the chat message
let msg = await initRoll.toMessage({
flavor: `${game.i18n.localize("PRISMRPG.Label.initiative")} - ${this.parent.name}`,
speaker: ChatMessage.getSpeaker({ actor: this.parent })
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
// Wait for 3D dice animation if enabled
if (game?.dice3d) {
await game.dice3d.waitFor3DAnimationByMessageID(msg.id)
}
async rollProgressionDice(combatId, combatantId, rollProgressionCount) {
// Get all weapons from the actor
let weapons = this.parent.items.filter(i => i.type === "weapon" && i.system.weaponType === "melee")
let weaponsChoices = weapons.map(w => { return { id: w.id, name: `${w.name} (${w.system.combatProgressionDice.toUpperCase()})`, combatProgressionDice: w.system.combatProgressionDice.toUpperCase() } })
let rangeWeapons = this.parent.items.filter(i => i.type === "weapon" && i.system.weaponType === "ranged")
for (let w of rangeWeapons) {
weaponsChoices.push({ id: `${w.id}simpleAim`, name: `${w.name} (Simple Aim: ${w.system.speed.simpleAim.toUpperCase()})`, combatProgressionDice: w.system.speed.simpleAim.toUpperCase() })
weaponsChoices.push({ id: `${w.id}carefulAim`, name: `${w.name} (Careful Aim: ${w.system.speed.carefulAim.toUpperCase()})`, combatProgressionDice: w.system.speed.carefulAim.toUpperCase() })
weaponsChoices.push({ id: `${w.id}focusedAim`, name: `${w.name} (Focused Aim: ${w.system.speed.focusedAim.toUpperCase()})`, combatProgressionDice: w.system.speed.focusedAim.toUpperCase() })
}
if (this.biodata.magicUser || this.biodata.clericUser) {
let spells = this.parent.items.filter(i => i.type === "spell" || i.type === "miracle")
for (let s of spells) {
let title = ""
let formula = ""
if (s.type === "spell") {
let dice = PrismRPGUtils.getLethargyDice(s.system.level)
title = `${s.name} (Casting time: ${s.system.castingTime}, Lethargy: ${dice})`
formula = `${s.system.castingTime}+${dice}`
} else {
title = `${s.name} (Prayer time: ${s.system.prayerTime})`
formula = `${s.system.prayerTime}`
}
weaponsChoices.push({ id: s.id, name: title, combatProgressionDice: formula })
// Update the combatant's initiative if in combat
if (combatId && combatantId) {
let combat = game.combats.get(combatId)
if (combat) {
await combat.updateEmbeddedDocuments("Combatant", [{
_id: combatantId,
initiative: initRoll.total
}])
}
}
let roll = await PrismRPGRoll.promptCombatAction({
actorId: this.parent.id,
actorName: this.parent.name,
actorImage: this.parent.img,
weaponsChoices,
combatId,
combatantId,
rollProgressionCount,
type: "progression",
})
return initRoll
}
}
+67
View File
@@ -68,6 +68,41 @@ export default class PrismRPGClass extends foundry.abstract.TypeDataModel {
level10: new fields.HTMLField({ initial: "" })
})
// Advancements (list of advancements per level with icon, name and description)
const advancementSchema = () => new fields.ArrayField(
new fields.SchemaField({
icon: new fields.StringField({
required: true,
initial: "",
label: "Icon"
}),
name: new fields.StringField({
required: true,
initial: "",
label: "Name"
}),
description: new fields.HTMLField({
required: true,
initial: "",
label: "Description"
})
}),
{ initial: [] }
)
schema.advancements = new fields.SchemaField({
level1: advancementSchema(),
level2: advancementSchema(),
level3: advancementSchema(),
level4: advancementSchema(),
level5: advancementSchema(),
level6: advancementSchema(),
level7: advancementSchema(),
level8: advancementSchema(),
level9: advancementSchema(),
level10: advancementSchema()
})
// Proficiencies granted by this class
schema.weaponProficiencies = new fields.StringField({
required: true,
@@ -156,4 +191,36 @@ export default class PrismRPGClass extends foundry.abstract.TypeDataModel {
}
return features
}
/**
* Get the current level's advancements
*/
get currentLevelAdvancements() {
return this.advancements[`level${this.level}`] || []
}
/**
* Get all advancements up to current level
*/
get allAdvancementsUpToLevel() {
const advancements = []
for (let i = 1; i <= this.level; i++) {
const levelAdvancements = this.advancements[`level${i}`]
if (levelAdvancements && levelAdvancements.length > 0) {
advancements.push({
level: i,
advancements: levelAdvancements
})
}
}
return advancements
}
/**
* Get advancements for a specific level
*/
getAdvancementsForLevel(level) {
if (level < 1 || level > 10) return []
return this.advancements[`level${level}`] || []
}
}
+20
View File
@@ -0,0 +1,20 @@
export default class PrismRPGConsumable extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.usesMax = new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 })
schema.uses = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 })
schema.encLoad = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.notes = new fields.HTMLField({ required: true })
schema.containerId = new fields.StringField({ required: false, initial: "", nullable: false })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["PRISMRPG.Consumable"]
}
+19
View File
@@ -0,0 +1,19 @@
export default class PrismRPGContainer extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.packBurden = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.encLoad = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.equipped = new fields.BooleanField({ required: true, initial: false })
schema.notes = new fields.HTMLField({ required: true })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["PRISMRPG.Container"]
}
+2
View File
@@ -12,6 +12,8 @@ export default class PrismRPGEquipment extends foundry.abstract.TypeDataModel {
schema.encLoad = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.money = new fields.StringField({ required: true, initial: "coppercoin", choices: SYSTEM.MONEY })
schema.equipped = new fields.BooleanField({ required: true, initial: false })
schema.containerId = new fields.StringField({ required: false, initial: "", nullable: false })
// Kit properties
schema.isKit = new fields.BooleanField({
+18
View File
@@ -0,0 +1,18 @@
export default class PrismRPGLoot extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.encLoad = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.notes = new fields.HTMLField({ required: true })
schema.containerId = new fields.StringField({ required: false, initial: "", nullable: false })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["PRISMRPG.Loot"]
}
-124
View File
@@ -1,124 +0,0 @@
import { SYSTEM } from "../config/system.mjs"
export default class PrismRPGMiracle extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.description = new fields.HTMLField({
required: false,
blank: true,
initial: "",
textSearch: true,
})
// Miracle level (1-7+)
schema.level = new fields.NumberField({
...requiredInteger,
initial: 1,
min: 1,
max: 25,
})
// Miracle type
schema.miracleType = new fields.StringField({
required: true,
initial: "combat",
choices: SYSTEM.MIRACLE_TYPES
})
// APC to pray
schema.apc = new fields.NumberField({
...requiredInteger,
required: true,
initial: 1,
min: 0,
label: "Action Point Cost"
})
// Faith cost (if applicable in Prism RPG)
schema.faithCost = new fields.NumberField({
...requiredInteger,
required: true,
initial: 0,
min: 0,
label: "Faith Cost"
})
// Divine favor required
schema.divineFavor = new fields.StringField({
required: true,
initial: "",
label: "Divine Favor"
})
// Components (Miracles have 'religious' component)
schema.components = new fields.SchemaField({
verbal: new fields.BooleanField({ initial: false }),
somatic: new fields.BooleanField({ initial: false }),
material: new fields.BooleanField({ initial: false }),
catalyst: new fields.BooleanField({ initial: false }),
religious: new fields.BooleanField({ initial: true })
})
schema.materialComponent = new fields.StringField({
required: true,
initial: ""
})
schema.catalyst = new fields.StringField({
required: true,
initial: ""
})
// Prayer parameters
schema.prayerTime = new fields.StringField({
required: true,
initial: "1 action"
})
schema.miracleRange = new fields.StringField({
required: true,
initial: "Touch"
})
schema.areaAffected = new fields.StringField({
required: true,
initial: "Single target"
})
schema.duration = new fields.StringField({
required: true,
initial: "Instantaneous"
})
schema.savingThrow = new fields.StringField({
required: true,
initial: ""
})
// Keywords
schema.keywords = new fields.ArrayField(
new fields.StringField()
)
// Miracle augment (if applicable)
schema.augment = new fields.StringField({
required: true,
initial: "",
label: "Miracle Augment"
})
schema.augmentDescription = new fields.HTMLField({
required: true,
initial: "",
label: "Augment Description"
})
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["PRISMRPG.Miracle"]
}
+9
View File
@@ -46,6 +46,15 @@ export default class PrismRPGRace extends foundry.abstract.TypeDataModel {
label: "Language"
})
schema.baseBurden = new fields.NumberField({
required: true,
nullable: false,
integer: true,
initial: 0,
min: 0,
label: "Base Burden"
})
// Racial Passive
schema.racialPassive = new fields.StringField({
required: true,
+26
View File
@@ -63,10 +63,36 @@ export default class PrismRPGShield extends foundry.abstract.TypeDataModel {
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.money = new fields.StringField({ required: true, initial: "coppercoin", choices: SYSTEM.MONEY })
schema.equipped = new fields.BooleanField({ required: true, initial: false })
schema.containerId = new fields.StringField({ required: false, initial: "", nullable: false })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["PRISMRPG.Shield"]
static migrateData(data) {
// Migrate old money types to new ones
if (data?.money) {
const moneyMigration = {
"tinbit": "coppercoin",
"copper": "coppercoin",
"silver": "silvercoin",
"gold": "goldcoin",
"platinum": "note"
}
if (moneyMigration[data.money]) {
data.money = moneyMigration[data.money]
}
// If still invalid, default to coppercoin
if (!SYSTEM.MONEY[data.money]) {
console.warn(`Prism RPG | Migrate shield: Invalid money type "${data.money}", defaulting to coppercoin`)
data.money = "coppercoin"
}
}
return super.migrateData(data)
}
}
+30 -23
View File
@@ -1,4 +1,5 @@
import { CORE_SKILLS_CHOICES, CORE_SKILL_BONUS, CORE_SKILLS } from "../config/skill.mjs"
import { SUB_ATTRIBUTES } from "../config/character.mjs"
/**
* Core Skill data model for Prism RPG
@@ -21,14 +22,6 @@ export default class PrismRPGSkill extends foundry.abstract.TypeDataModel {
initial: ""
})
// Core Skill type (from the 18 available Core Skills)
schema.coreSkill = new fields.StringField({
required: true,
initial: "acrobatics",
choices: CORE_SKILLS_CHOICES,
label: "Core Skill"
})
// Is this the character's chosen Core Skill?
schema.isCoreSkill = new fields.BooleanField({
required: true,
@@ -36,18 +29,18 @@ export default class PrismRPGSkill extends foundry.abstract.TypeDataModel {
label: "Is Core Skill"
})
// Primary attribute for this skill (str, dex, con, int, wis, cha)
schema.primaryAttribute = new fields.StringField({
// First sub-attribute for this skill
schema.subAttribute1 = new fields.StringField({
required: true,
initial: "dex",
label: "Primary Attribute"
initial: "prowess",
label: "Sub-Attribute 1"
})
// If Core Skill, which attribute receives the +2 bonus?
schema.attributeBonus = new fields.StringField({
// Second sub-attribute for this skill
schema.subAttribute2 = new fields.StringField({
required: true,
initial: "",
label: "Attribute Bonus"
initial: "initiative",
label: "Sub-Attribute 2"
})
// Skill modifier (includes Core Skill bonus if applicable)
@@ -114,9 +107,9 @@ export default class PrismRPGSkill extends foundry.abstract.TypeDataModel {
prepareDerivedData() {
super.prepareDerivedData()
// D&D 5e style: Core Skill gives +2 proficiency bonus
// Core Skill gives +5 proficiency bonus
if (this.isCoreSkill) {
this.modifier = 2
this.modifier = 5
this.canAdvancedCheck = true
} else {
this.modifier = 0
@@ -126,16 +119,30 @@ export default class PrismRPGSkill extends foundry.abstract.TypeDataModel {
/**
* Calculate skill check bonus
* @param {string} attributeKey The attribute to use (str, dex, con, int, wis, cha)
* @param {string} subAttributeKey The sub-attribute to use (prowess, vigor, etc.)
* @returns {number} Total skill check bonus
*/
getSkillCheckBonus(attributeKey) {
getSkillCheckBonus(subAttributeKey) {
let actor = this.parent?.actor
if (!actor) return this.modifier
const attribute = actor.system.characteristics?.[attributeKey]
const attributeMod = attribute?.mod || 0
const subAttribute = actor.system.subAttributes?.[subAttributeKey]
const subAttributeMod = subAttribute?.value || 0
return attributeMod + this.modifier
return subAttributeMod + this.modifier
}
/**
* Get the available sub-attribute choices for this skill
*/
get subAttributeChoices() {
const choices = {}
if (this.subAttribute1) {
choices[this.subAttribute1] = SUB_ATTRIBUTES[this.subAttribute1]?.label || this.subAttribute1
}
if (this.subAttribute2) {
choices[this.subAttribute2] = SUB_ATTRIBUTES[this.subAttribute2]?.label || this.subAttribute2
}
return choices
}
}
-17
View File
@@ -1,17 +0,0 @@
export default class PrismRPGVulnerability extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.gainedPoints = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["PRISMRPG.Vulnerability"]
}
+26
View File
@@ -127,10 +127,36 @@ export default class PrismRPGWeapon extends foundry.abstract.TypeDataModel {
schema.money = new fields.StringField({ required: true, initial: "coppercoin", choices: SYSTEM.MONEY })
schema.equipped = new fields.BooleanField({ required: true, initial: false })
schema.isImplement = new fields.BooleanField({ required: true, initial: false })
schema.containerId = new fields.StringField({ required: false, initial: "", nullable: false })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["PRISMRPG.Weapon"]
static migrateData(data) {
// Migrate old money types to new ones
if (data?.money) {
const moneyMigration = {
"tinbit": "coppercoin",
"copper": "coppercoin",
"silver": "silvercoin",
"gold": "goldcoin",
"platinum": "note"
}
if (moneyMigration[data.money]) {
data.money = moneyMigration[data.money]
}
// If still invalid, default to coppercoin
if (!SYSTEM.MONEY[data.money]) {
console.warn(`Prism RPG | Migrate weapon: Invalid money type "${data.money}", defaulting to coppercoin`)
data.money = "coppercoin"
}
}
return super.migrateData(data)
}
}
+6 -5
View File
@@ -13,11 +13,6 @@ export default class PrismRPGUtils {
return compendiumData.filter(filter)
}
/* -------------------------------------------- */
static pushCombatOptions(html, options) {
options.push({ name: "Reset Progression", condition: true, icon: '<i class="fas fa-rotate-right"></i>', callback: target => { game.combat.resetProgression(target.data('combatant-id')); } })
}
/* -------------------------------------------- */
static setHookListeners() {
@@ -177,6 +172,11 @@ export default class PrismRPGUtils {
return str ? str.toUpperCase() : '';
})
Handlebars.registerHelper('replace', function (str, search, replacement) {
if (!str) return '';
return str.replace(search, replacement);
})
Handlebars.registerHelper('isEnabled', function (configKey) {
return game.settings.get("bol", configKey);
})
@@ -265,6 +265,7 @@ export default class PrismRPGUtils {
const templatePaths = [
'systems/fvtt-prism-rpg/templates/partial-item-effects.hbs',
'systems/fvtt-prism-rpg/templates/weapon-types-config.hbs',
'systems/fvtt-prism-rpg/templates/chat-new-round.hbs',
]
return foundry.applications.handlebars.loadTemplates(templatePaths)
}
+88 -11
View File
@@ -4,6 +4,7 @@
*/
import { SYSTEM } from "./module/config/system.mjs"
import { AFFLICTIONS, IMBUEMENTS } from "./module/config/effects.mjs"
globalThis.SYSTEM = SYSTEM // Expose the SYSTEM object to the global scope
// Import modules
@@ -11,7 +12,7 @@ import * as models from "./module/models/_module.mjs"
import * as documents from "./module/documents/_module.mjs"
import * as applications from "./module/applications/_module.mjs"
import { PrismRPGCombatTracker, PrismRPGCombat } from "./module/applications/combat.mjs"
import { PrismRPGCombat } from "./module/applications/combat.mjs"
import { Macros } from "./module/macros.mjs"
import { setupTextEnrichers } from "./module/enrichers.mjs"
import { default as PrismRPGUtils } from "./module/utils.mjs"
@@ -36,8 +37,11 @@ Hooks.once("init", function () {
documents,
}
CONFIG.ui.combat = PrismRPGCombatTracker
CONFIG.Combat.documentClass = PrismRPGCombat;
CONFIG.Combat.documentClass = PrismRPGCombat
CONFIG.Combat.initiative = {
formula: "1d20 + @subAttributes.initiative.value",
decimals: 2
}
CONFIG.Actor.documentClass = documents.PrismRPGActor
CONFIG.Actor.dataModels = {
@@ -49,16 +53,18 @@ Hooks.once("init", function () {
CONFIG.Item.dataModels = {
skill: models.PrismRPGSkill,
"racial-ability": models.PrismRPGRacialAbility,
ability: models.PrismRPGAbility,
weapon: models.PrismRPGWeapon,
armor: models.PrismRPGArmor,
shield: models.PrismRPGShield,
spell: models.PrismRPGSpell,
// Vulnerability: models.PrismRPGVulnerability, // Disabled - Legacy from Lethal Fantasy
equipment: models.PrismRPGEquipment,
// Miracle: models.PrismRPGMiracle // Disabled - Legacy from Lethal Fantasy, PRISM uses Divine class features instead
race: models.PrismRPGRace,
class: models.PrismRPGClass,
"character-path": models.PrismRPGCharacterPath,
container: models.PrismRPGContainer,
consumable: models.PrismRPGConsumable,
loot: models.PrismRPGLoot,
}
// Register sheet application classes
@@ -69,7 +75,7 @@ Hooks.once("init", function () {
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ActorSheet)
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGSkillSheet, { types: ["skill"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGRacialAbilitySheet, { types: ["racial-ability"], makeDefault: true })
// Foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGVulnerabilitySheet, { types: ["vulnerability"], makeDefault: true }) // Disabled - Legacy from Lethal Fantasy
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGAbilitySheet, { types: ["ability"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGWeaponSheet, { types: ["weapon"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGSpellSheet, { types: ["spell"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGArmorSheet, { types: ["armor"], makeDefault: true })
@@ -78,7 +84,16 @@ Hooks.once("init", function () {
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGRaceSheet, { types: ["race"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGClassSheet, { types: ["class"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGCharacterPathSheet, { types: ["character-path"], makeDefault: true })
// Foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGMiracleSheet, { types: ["miracle"], makeDefault: true }) // Disabled - Legacy from Lethal Fantasy
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGContainerSheet, { types: ["container"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGConsumableSheet, { types: ["consumable"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGLootSheet, { types: ["loot"], makeDefault: true })
// Status Effects — Afflictions & Imbuements
CONFIG.statusEffects = [
{ id: "dead", name: "EFFECT.StatusDead", icon: "icons/svg/skull.svg" },
...AFFLICTIONS,
...IMBUEMENTS,
]
// Other Document Configuration
CONFIG.ChatMessage.documentClass = documents.PrismRPGChatMessage
@@ -133,9 +148,11 @@ function preLocalizeConfig() {
for (const choice of choicesToLocalize) {
if (CONFIG.PRISMRPG[choice]) {
const localized = {}
for (const [key, label] of Object.entries(CONFIG.PRISMRPG[choice])) {
CONFIG.PRISMRPG[choice][key] = game.i18n.localize(label)
localized[key] = game.i18n.localize(label)
}
CONFIG.PRISMRPG[choice] = localized
}
}
}
@@ -195,6 +212,32 @@ Hooks.on(hookName, (message, html, data) => {
}
}
// Handle new round AP/mana restore buttons (GM only)
if (typeMessage === "newRound") {
$html.find(".new-round-restore-btn").click(async (event) => {
const btn = event.currentTarget
const actorId = btn.dataset.actorId
const targets = actorId === "all"
? $html.find(".new-round-restore-btn[data-actor-id!='all']").toArray().map(b => game.actors.get(b.dataset.actorId)).filter(Boolean)
: [game.actors.get(actorId)].filter(Boolean)
for (const actor of targets) {
await actor.update({
"system.actionPoints.value": actor.system.actionPoints.max,
"system.manaPoints.value": Math.min(actor.system.manaPoints.value + 1, actor.system.manaPoints.max)
})
}
if (actorId === "all") {
$html.find(".new-round-restore-btn").prop("disabled", true).addClass("restored")
} else {
btn.disabled = true
btn.classList.add("restored")
}
})
}
// Handle Roll Damage button click in weapon attack messages
$html.find(".roll-damage-button").click(async (event) => {
const btn = event.currentTarget
@@ -210,10 +253,44 @@ Hooks.on(hookName, (message, html, data) => {
await actor.prepareRoll("weapon-damage-medium", weaponId)
})
})
/**
* Send a GM-only chat message with restore buttons at the start of each new round
*/
Hooks.on("updateCombat", async (combat, change, _options, _userId) => {
if (!game.user.isGM) return
if (change.round === undefined || change.round <= 1) return
Hooks.on("getCombatTrackerEntryContext", (html, options) => {
PrismRPGUtils.pushCombatOptions(html, options);
});
// Deduplicated character-type actors from the active combat
const seen = new Set()
const playerActors = combat.combatants.contents
.filter(c => c.actor?.type === "character" && !seen.has(c.actor.id) && seen.add(c.actor.id))
.map(c => ({ id: c.actor.id, name: c.actor.name, img: c.actor.img }))
if (playerActors.length === 0) return
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-prism-rpg/templates/chat-new-round.hbs",
{ actors: playerActors, round: change.round }
)
await ChatMessage.create({
content,
whisper: ChatMessage.getWhisperRecipients("GM"),
flags: { prismRPG: { typeMessage: "newRound" } }
})
})
/**
* Inject a visual separator between Afflictions and Imbuements in the Token HUD status tray
*/
Hooks.on("renderTokenHUD", (_app, html) => {
const tray = html.querySelector(".status-effects")
if (!tray) return
const firstImb = tray.querySelector("[data-status-id^='imb-']")
if (!firstImb) return
const sep = document.createElement("div")
sep.className = "status-separator"
firstImb.before(sep)
})
/**
* Create a macro when dropping an entity on the hotbar
+26
View File
@@ -0,0 +1,26 @@
.ability-content {
.sheet-common();
.item-sheet-common();
.header {
display: flex;
img {
width: 50px;
height: 50px;
}
}
input[type="checkbox"] {
font-size: var(--font-size-14);
width: 20px;
padding-top: 0;
}
input[type="checkbox"]:checked {
background-color: rgba(0, 0, 0, 0.1);
}
input[type="checkbox"]:checked::after {
color: rgba(0, 0, 0, 0.1);
}
}
+125 -7
View File
@@ -58,17 +58,21 @@
// Left Column - Portrait, Attributes, HP
.character-left-column {
display: flex;
flex-direction: row;
gap: 12px;
align-items: flex-start;
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: auto auto;
column-gap: 12px;
row-gap: 8px;
align-items: start;
min-width: 0;
.portrait-hp-column {
display: flex;
flex-direction: column;
gap: 12px;
width: 200px;
flex-shrink: 0;
grid-column: 1;
grid-row: 1 / 3;
}
.character-portrait {
@@ -143,6 +147,13 @@
border-radius: 4px;
}
}
&.hp-temp-item {
.hp-value input {
background: rgba(255, 230, 160, 0.85);
border-color: #b8860b;
}
}
}
}
}
@@ -151,9 +162,9 @@
display: flex;
flex-direction: column;
gap: 6px;
flex: 1;
grid-column: 2;
grid-row: 1;
min-width: 0;
max-width: 280px;
.attribute-shield {
position: relative;
@@ -477,3 +488,110 @@
}
}
}
// Movement Rating + Burden section
.burden-mr-section {
display: flex;
flex-direction: row;
gap: 16px;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.35);
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 4px;
grid-column: 2;
grid-row: 2;
.burden-mr-item {
display: flex;
flex-direction: column;
gap: 3px;
.burden-display-row {
display: flex;
align-items: center;
gap: 8px;
min-height: 22px;
}
.burden-edit-row {
display: flex;
align-items: center;
gap: 3px;
flex-wrap: wrap;
}
.burden-label {
font-weight: bold;
font-size: 11px;
letter-spacing: 0.05em;
min-width: 52px;
color: #fff;
cursor: default;
}
.burden-sub-attr {
// formField helper renders a .form-group with label stacked above select.
// Flatten it so the select sits inline in the flex row.
.form-group {
display: contents;
label { display: none; }
}
.form-fields {
display: contents;
}
select {
font-size: 11px;
padding: 1px 2px;
height: 22px;
max-width: 80px;
background: rgba(0, 0, 0, 0.3);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 3px;
}
}
.burden-op {
font-size: 11px;
color: rgba(255, 255, 255, 0.9);
flex-shrink: 0;
}
.burden-other {
input[type="number"] {
width: 32px;
text-align: center;
font-size: 11px;
height: 22px;
background: rgba(0, 0, 0, 0.3);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 3px;
}
}
.burden-total {
font-size: 18px;
font-weight: bold;
color: #ffe566;
min-width: 24px;
text-align: center;
}
.burden-used-max {
display: flex;
align-items: center;
gap: 2px;
font-size: 14px;
font-weight: bold;
.burden-used { color: #ffe566; }
.burden-separator { color: rgba(255, 255, 255, 0.8); }
.burden-max { color: #e0e0e0; }
&.burden-overloaded {
.burden-used { color: #ff5c5c; font-weight: bold; }
}
}
}
}
+173
View File
@@ -619,6 +619,150 @@
min-width: 12rem;
}
}
.inv-section {
margin-bottom: 6px;
.inv-items {
display: flex;
flex-direction: column;
gap: 3px;
}
.inv-item {
display: flex;
align-items: center;
gap: 6px;
padding: 2px 4px;
border-radius: 3px;
background: rgba(255,255,255,0.15);
&:hover { background: rgba(255,255,255,0.25); }
.item-img {
width: 24px;
height: 24px;
cursor: pointer;
flex-shrink: 0;
}
.inv-name {
flex: 1;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.inv-enc, .inv-uses, .inv-capacity {
font-size: 11px;
color: #555;
min-width: 30px;
text-align: center;
white-space: nowrap;
}
.controls {
display: flex;
gap: 4px;
a { font-size: 12px; cursor: pointer; }
}
}
.inv-empty {
font-style: italic;
color: rgba(0,0,0,0.4);
text-align: center;
font-size: 11px;
padding: 4px;
}
// Container drag-drop highlight
.inv-container[data-container-id] {
border: 1px dashed transparent;
transition: border-color 0.15s, background 0.15s;
&.drag-over {
border-color: rgba(100, 150, 255, 0.7);
background: rgba(100, 150, 255, 0.12);
}
}
// Items nested inside a container
.inv-container-items {
margin: 2px 0 6px 28px;
display: flex;
flex-direction: column;
gap: 2px;
border-left: 2px solid rgba(0,0,0,0.15);
padding-left: 8px;
}
.inv-container-item {
display: flex;
align-items: center;
gap: 6px;
padding: 2px 4px;
border-radius: 3px;
background: rgba(0,0,0,0.06);
&:hover { background: rgba(0,0,0,0.12); }
.item-img {
width: 20px;
height: 20px;
cursor: pointer;
flex-shrink: 0;
}
.inv-name {
flex: 1;
font-size: 11px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.inv-enc {
font-size: 10px;
color: #555;
min-width: 24px;
text-align: center;
}
.inv-container-type-badge {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: rgba(0,0,0,0.45);
background: rgba(0,0,0,0.07);
border-radius: 3px;
padding: 1px 4px;
flex-shrink: 0;
}
.controls {
display: flex;
gap: 4px;
a { font-size: 11px; cursor: pointer; }
}
}
.inv-container-empty {
margin: 2px 0 4px 36px;
font-size: 10px;
font-style: italic;
color: rgba(0,0,0,0.35);
}
}
.pack-burden-fieldset {
.pack-burden-display {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
font-size: 13px;
.pack-burden-label {
font-weight: bold;
}
.pack-burden-value {
font-weight: bold;
.pack-burden-used { color: #e6a817; }
.pack-burden-sep { color: rgba(0,0,0,0.4); }
.pack-burden-max { color: rgba(0,0,0,0.7); }
}
}
.pack-burden-used { color: #e6a817; font-weight: bold; }
.pack-burden-sep { color: rgba(0,0,0,0.4); margin: 0 2px; }
.pack-burden-max { color: rgba(0,0,0,0.7); font-weight: bold; }
}
}
.tab.character-combat .main-div {
@@ -911,3 +1055,32 @@
min-height: 150px;
}
}
// Equipped item visual feedback
.inv-item {
&.is-equipped {
background: rgba(100, 180, 100, 0.12);
border-left: 2px solid rgba(80, 160, 80, 0.6);
}
.equipped-icon {
color: #5a9e6a;
}
.unequipped-icon {
color: rgba(0, 0, 0, 0.25);
}
}
// Burden overload warning
.burden-excess {
font-size: 0.75em;
color: #ff5c5c;
margin-left: 4px;
white-space: nowrap;
}
// Depleted consumable button
.controls a.disabled {
opacity: 0.35;
pointer-events: none;
cursor: default;
}
+223
View File
@@ -411,4 +411,227 @@
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.2);
}
// Item Chat Card Styles
.prismrpg-item-chat-card {
font-family: var(--font-primary);
border-radius: 4px;
overflow: hidden;
background: linear-gradient(135deg, rgba(0,0,0,0.05) 0%, rgba(0,0,0,0.02) 100%);
border: 1px solid rgba(0,0,0,0.2);
margin: 2px 0;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
.item-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 8px;
background: linear-gradient(135deg, #2c2c2c 0%, #1a1a1a 100%);
border-bottom: 1px solid #444;
h3 {
margin: 0;
font-size: 0.95em;
font-weight: bold;
color: #d4af37;
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
font-family: var(--font-secondary);
}
.item-type {
padding: 1px 6px;
border-radius: 10px;
font-size: 0.65em;
font-weight: bold;
text-transform: uppercase;
background: rgba(255, 255, 255, 0.15);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.2);
letter-spacing: 0.3px;
}
}
.item-image {
display: flex;
justify-content: center;
align-items: center;
padding: 6px;
background: rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
img {
max-width: 50px;
max-height: 50px;
border-radius: 3px;
border: 1px solid rgba(212, 175, 55, 0.3);
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
transition: transform 0.2s ease;
&:hover {
transform: scale(1.05);
}
}
}
.item-description {
padding: 6px 8px;
color: #000;
font-size: 0.8em;
line-height: 1.3;
background: rgba(255, 255, 255, 0.8);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
font-style: italic;
}
.item-details {
padding: 6px 8px;
display: flex;
flex-direction: column;
gap: 3px;
.item-detail {
display: flex;
align-items: center;
padding: 2px 6px;
background: rgba(255, 255, 255, 0.03);
border-left: 2px solid rgba(212, 175, 55, 0.5);
border-radius: 2px;
font-size: 0.8em;
strong {
color: #d4af37;
margin-right: 6px;
min-width: 90px;
font-weight: bold;
}
&:nth-child(even) {
background: rgba(0, 0, 0, 0.05);
}
}
}
// Special styling for weapon items
&.weapon-item {
.item-header {
background: linear-gradient(135deg, #c41e3a 0%, #8b0000 100%);
}
}
// Special styling for armor items
&.armor-item {
.item-header {
background: linear-gradient(135deg, #4a5cf7 0%, #2c3e9e 100%);
}
}
// Special styling for spell items
&.spell-item {
.item-header {
background: linear-gradient(135deg, #9b59b6 0%, #6c3483 100%);
}
}
// Special styling for skill items
&.skill-item {
.item-header {
background: linear-gradient(135deg, #16a085 0%, #0e6655 100%);
}
}
// Special styling for equipment items
&.equipment-item {
.item-header {
background: linear-gradient(135deg, #f39c12 0%, #b8730f 100%);
}
}
}
}
// New round message
.new-round-message {
.chat-title .new-round-label {
font-size: var(--font-size-11);
font-style: italic;
color: #8a7a5a;
margin-top: 1px;
}
.new-round-actors {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
padding: 6px 0 2px;
.new-round-restore-btn {
display: flex;
align-items: center;
gap: 6px;
width: calc(50% - 3px);
padding: 4px 8px 4px 6px;
border: 1px solid #7a6a45;
border-radius: 4px;
background: linear-gradient(135deg, #f5e6c8 0%, #e8d5a0 100%);
cursor: pointer;
font-size: var(--font-size-13);
color: #3a2e1a;
overflow: hidden;
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
img {
flex-shrink: 0;
width: 24px;
height: 24px;
border: none;
border-radius: 3px;
object-fit: cover;
}
&:hover:not(:disabled) {
background: linear-gradient(135deg, #fdf3dc 0%, #f0e0b0 100%);
border-color: #a08040;
}
&:disabled,
&.restored {
opacity: 0.45;
cursor: default;
}
}
.new-round-all-btn {
width: 100%;
background: linear-gradient(135deg, #c8dff5 0%, #a0c0e8 100%);
border-color: #4a6a8a;
color: #1a2e3a;
font-weight: bold;
&:hover:not(:disabled) {
background: linear-gradient(135deg, #dcedfc 0%, #b0d0f0 100%);
border-color: #3a5a7a;
}
}
}
}
// Token HUD — separator between Afflictions and Imbuements
.palette.status-effects {
.status-separator {
grid-column: 1 / -1;
width: 100%;
height: 2px;
border: none;
border-top: 2px solid rgba(255, 255, 255, 0.35);
margin: 5px 0;
position: relative;
}
}
+135
View File
@@ -13,4 +13,139 @@
label {
flex: 10%;
}
.advancement-level {
margin-bottom: 1.5rem;
padding: 0.5rem;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;
h3 {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 0 0.5rem 0;
padding-bottom: 0.5rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
.level-title {
color: #000000;
font-weight: bold;
font-size: 80%;
}
.add-advancement {
padding: 0.25rem 0.5rem;
background: var(--color-control-bg);
border: 1px solid var(--color-border-dark);
border-radius: 3px;
cursor: pointer;
&:hover {
background: var(--color-control-bg-hover);
}
i {
margin: 0;
}
}
}
.empty-advancements {
font-style: italic;
color: rgba(0, 0, 0, 0.5);
margin: 0.5rem 0;
}
}
.advancement-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.advancement-item {
padding: 0.75rem;
background: rgba(0, 0, 0, 0.05);
border-radius: 4px;
.advancement-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
.advancement-icon {
width: 40px;
height: 40px;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 3px;
cursor: pointer;
flex-shrink: 0;
&:hover {
border-color: var(--color-border-highlight);
}
}
input[type="text"] {
flex: 1;
font-weight: bold;
}
.toggle-advancement-description {
padding: 0.25rem 0.5rem;
background: var(--color-control-bg);
border: 1px solid var(--color-border-dark);
border-radius: 3px;
cursor: pointer;
flex-shrink: 0;
margin-left: 0.25rem;
&:hover {
background: var(--color-control-bg-hover);
}
i {
margin: 0;
color: rgba(0, 0, 0, 0.7);
}
}
.delete-advancement {
padding: 0.25rem 0.5rem;
background: var(--color-control-bg);
border: 1px solid var(--color-border-dark);
border-radius: 3px;
cursor: pointer;
flex-shrink: 0;
&:hover {
background: rgba(255, 0, 0, 0.1);
border-color: rgba(255, 0, 0, 0.5);
}
i {
margin: 0;
color: rgba(255, 0, 0, 0.7);
}
}
}
.advancement-description {
margin-top: 0.5rem;
overflow: hidden;
transition:
max-height 0.3s ease-out,
opacity 0.3s ease-out;
max-height: 500px;
opacity: 1;
&.collapsed {
max-height: 0;
opacity: 0;
margin-top: 0;
}
}
}
}
+9
View File
@@ -0,0 +1,9 @@
.consumable-content {
.item-img {
width: 64px;
height: 64px;
object-fit: contain;
border: 1px solid var(--color-border-light-tertiary);
border-radius: 4px;
}
}
+20
View File
@@ -0,0 +1,20 @@
.container-content {
.sheet-common();
.item-sheet-common();
.header {
display: flex;
img {
width: 50px;
height: 50px;
}
}
.item-img {
width: 64px;
height: 64px;
object-fit: contain;
border: 1px solid var(--color-border-light-tertiary);
border-radius: 4px;
}
}
+4
View File
@@ -9,6 +9,7 @@
@import "monster.less";
@import "skill.less";
@import "racial-ability.less";
@import "ability.less";
@import "weapon.less";
@import "armor.less";
@import "spell.less";
@@ -19,6 +20,9 @@
@import "race.less";
@import "class.less";
@import "character-path.less";
@import "container.less";
@import "consumable.less";
@import "loot.less";
@import "effects.less";
@import "weapon-types-config.less";
}
+9
View File
@@ -53,6 +53,15 @@ i.prismrpg {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1);
background-image: var(--background-image-base);
background-size: 100% 100%;
background-repeat: no-repeat;
.window-content {
background-image: var(--background-image-base);
background-size: 100% 100%;
background-repeat: no-repeat;
}
button:hover {
background: var(--color-dark-6);
}
+9
View File
@@ -0,0 +1,9 @@
.loot-content {
.item-img {
width: 64px;
height: 64px;
object-fit: contain;
border: 1px solid var(--color-border-light-tertiary);
border-radius: 4px;
}
}
+2 -2
View File
@@ -263,8 +263,8 @@
}
}
// Dialog application styling
.application.dialog.prismrpg {
// Dialog application styling — only apply grey gradient when roll dialog content is present
.application.dialog.prismrpg:has(.prismrpg-roll-dialog-modern) {
.window-content {
background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
padding: 8px;
+8 -4
View File
@@ -6,7 +6,7 @@
"download": "#{DOWNLOAD}#",
"url": "#{URL}#",
"license": "LICENSE",
"version": "13.0.0",
"version": "14.0.0",
"authors": [
{
"name": "Uberwald",
@@ -14,8 +14,8 @@
}
],
"compatibility": {
"minimum": "13",
"verified": "13"
"minimum": "14",
"verified": "14"
},
"esmodules": ["prism-rpg.mjs"],
"styles": ["css/fvtt-prism-rpg.css"],
@@ -34,6 +34,7 @@
"Item": {
"skill": { "htmlFields": ["description"] },
"racial-ability": { "htmlFields": ["description"] },
"ability": { "htmlFields": ["description"] },
"weapon": { "htmlFields": ["description"] },
"armor": { "htmlFields": ["description"] },
"shield": { "htmlFields": ["description"] },
@@ -41,7 +42,10 @@
"equipment": { "htmlFields": ["description"] },
"race": { "htmlFields": ["description", "racialPassiveDescription", "subraceAbilityDescription", "notes"] },
"class": { "htmlFields": ["description", "attributeBonuses", "notes", "features.level1", "features.level2", "features.level3", "features.level4", "features.level5", "features.level6", "features.level7", "features.level8", "features.level9", "features.level10"] },
"character-path": { "htmlFields": ["description"] }
"character-path": { "htmlFields": ["description"] },
"container": { "htmlFields": ["description", "notes"] },
"consumable": { "htmlFields": ["description", "notes"] },
"loot": { "htmlFields": ["description", "notes"] }
}
},
"grid": {
+38
View File
@@ -0,0 +1,38 @@
<section>
<div class="header">
<img
class="item-img"
src="{{item.img}}"
data-edit="img"
data-action="editImage"
data-tooltip="{{item.name}}"
/>
{{formInput fields.name value=source.name}}
</div>
{{! Navigation des onglets }}
<nav class="sheet-tabs tabs" data-group="primary">
<a class="item {{tabs.description.cssClass}}" data-tab="description">{{localize "PRISMRPG.Label.description"}}</a>
<a class="item {{tabs.effects.cssClass}}" data-tab="effects">{{localize "PRISMRPG.Label.effects"}}</a>
</nav>
{{! Onglet Description }}
<div class="tab {{tabs.description.cssClass}}" data-group="primary" data-tab="description">
<fieldset>
<legend>{{localize "PRISMRPG.Label.description"}}</legend>
{{formInput
systemFields.description
enriched=enrichedDescription
value=system.description
name="system.description"
toggled=true
}}
</fieldset>
</div>
{{! Onglet Effects }}
<div class="tab {{tabs.effects.cssClass}}" data-group="primary" data-tab="effects">
{{> systems/fvtt-prism-rpg/templates/partial-item-effects.hbs}}
</div>
</section>
+2 -59
View File
@@ -8,13 +8,7 @@
<fieldset>
<legend>{{localize "PRISMRPG.Label.biodata"}}</legend>
<div class="biodata">
<div class="biodata-elem">
<span class="name">Class</span>
{{formInput
systemFields.biodata.fields.class
value=system.biodata.class
}}
</div>
{{!-- Class and Mortal fields removed - don't exist in DataModel --}}
<div class="biodata-elem">
<span class="name">Level</span>
{{formInput
@@ -22,13 +16,6 @@
value=system.biodata.level
}}
</div>
<div class="biodata-elem">
<span class="name">Mortal</span>
{{formInput
systemFields.biodata.fields.mortal
value=system.biodata.mortal
}}
</div>
<div class="biodata-elem">
<span class="name">Age</span>
{{formInput systemFields.biodata.fields.age value=system.biodata.age}}
@@ -82,51 +69,7 @@
value=system.biodata.magicUser
}}
</div>
<!-- <div class="biodata-elem">
<span class="name">Cleric User</span>
{{formInput
systemFields.biodata.fields.clericUser
value=system.biodata.clericUser
}}
</div> -->
<div class="biodata-elem">
<span class="name">Save bonus (1/5levels)</span>
{{formInput
systemFields.modifiers.fields.saveModifier
value=system.modifiers.saveModifier
disabled=true
}}
</div>
{{#if system.biodata.magicUser}}
<div class="biodata-elem">
<span class="name">Spell bonus (1/5levels)</span>
{{formInput
systemFields.modifiers.fields.levelSpellModifier
value=system.modifiers.levelSpellModifier
disabled=true
}}
</div>
{{/if}}
{{#if system.biodata.clericUser}}
<div class="biodata-elem">
<span class="name">Miracle bonus (1/5levels)</span>
{{formInput
systemFields.modifiers.fields.levelMiracleModifier
value=system.modifiers.levelMiracleModifier
disabled=true
}}
</div>
{{/if}}
<div class="biodata-elem">
<span class="name">Last HD roll</span>
{{formInput
systemFields.biodata.fields.hpPerLevel
value=system.biodata.hpPerLevel
disabled=true
}}
</div>
{{!-- Cleric User, modifiers, and hpPerLevel fields removed - don't exist in DataModel --}}
</div>
</fieldset>
+3
View File
@@ -21,6 +21,7 @@
class="item-img"
src="{{item.img}}"
data-tooltip="{{item.name}}"
data-action="postItemToChat"
/>
{{/if}}
<div class="name" data-tooltip="{{item.system.description}}">
@@ -88,6 +89,7 @@
class="item-img"
src="{{item.img}}"
data-tooltip="{{item.name}}"
data-action="postItemToChat"
/>
<div class="name" data-tooltip="{{item.system.description}}">
{{item.name}}
@@ -137,6 +139,7 @@
class="item-img"
src="{{item.img}}"
data-tooltip="{{item.name}}"
data-action="postItemToChat"
/>
<div class="name" data-tooltip="{{item.system.description}}">
{{item.name}}
+201 -13
View File
@@ -1,6 +1,16 @@
<section class="tab character-{{tab.id}} {{tab.cssClass}}" data-tab="{{tab.id}}" data-group="{{tab.group}}">
<div class="main-div">
{{! Pack Burden Summary }}
<fieldset class="pack-burden-fieldset">
<legend>{{localize "PRISMRPG.Label.packBurden"}}</legend>
<div class="pack-burden-display">
<span class="pack-burden-used">{{packBurdenUsed}}</span>
<span class="pack-burden-sep">/</span>
<span class="pack-burden-max">{{packBurdenMax}}</span>
</div>
</fieldset>
{{! Money }}
<fieldset>
<legend>{{localize "PRISMRPG.Label.money"}}</legend>
<div class="moneys">
@@ -12,23 +22,201 @@
</div>
</fieldset>
<fieldset>
<legend>{{localize "PRISMRPG.Label.equipment"}}</legend>
<div class="equipments">
{{#each equipments as |item|}}
<div class="equipment" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="name" data-tooltip="{{{item.system.description}}}">
{{item.name}}
</div>
{{! Weapons }}
<fieldset class="inv-section">
<legend>
{{localize "PRISMRPG.Label.weapons"}}
<a data-action="createEquipment" data-item-type="weapon" data-tooltip="{{localize 'PRISMRPG.Tooltip.addWeapon'}}"><i class="fas fa-plus"></i></a>
</legend>
<div class="inv-items">
{{#each weapons as |item|}}
<div class="inv-item {{#if item.system.equipped}}is-equipped{{/if}}" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" data-action="postItemToChat" />
<div class="inv-name">{{item.name}}</div>
<div class="inv-enc" data-tooltip="{{localize 'PRISMRPG.Label.encLoad'}}">{{item.system.encLoad}}</div>
<div class="controls">
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.assignToContainer'}}" data-action="assignToContainer" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-bag-shopping"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.toggleEquipped'}}" data-action="toggleEquipped" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
{{#if item.system.equipped}}<i class="fas fa-shield-check equipped-icon"></i>{{else}}<i class="far fa-circle unequipped-icon"></i>{{/if}}
</a>
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Delete'}}" data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
{{#unless weapons.length}}<p class="inv-empty">{{localize "PRISMRPG.Message.noWeapons"}}</p>{{/unless}}
</div>
</fieldset>
{{! Armor & Shields }}
<fieldset class="inv-section">
<legend>
{{localize "PRISMRPG.Label.armors"}}
<a data-action="createEquipment" data-item-type="armor" data-tooltip="{{localize 'PRISMRPG.Tooltip.addArmor'}}"><i class="fas fa-plus"></i></a>
<a data-action="createEquipment" data-item-type="shield" data-tooltip="{{localize 'PRISMRPG.Tooltip.addShield'}}"><i class="fas fa-shield-halved"></i></a>
</legend>
<div class="inv-items">
{{#each armors as |item|}}
<div class="inv-item {{#if item.system.equipped}}is-equipped{{/if}}" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" data-action="postItemToChat" />
<div class="inv-name">{{item.name}}</div>
<div class="inv-enc" data-tooltip="{{localize 'PRISMRPG.Label.encLoad'}}">{{item.system.encLoad}}</div>
<div class="controls">
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.assignToContainer'}}" data-action="assignToContainer" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-bag-shopping"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.toggleEquipped'}}" data-action="toggleEquipped" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
{{#if item.system.equipped}}<i class="fas fa-shield-check equipped-icon"></i>{{else}}<i class="far fa-circle unequipped-icon"></i>{{/if}}
</a>
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Delete'}}" data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
{{#unless armors.length}}<p class="inv-empty">{{localize "PRISMRPG.Message.noArmor"}}</p>{{/unless}}
</div>
</fieldset>
{{! Consumables }}
<fieldset class="inv-section">
<legend>
{{localize "PRISMRPG.Label.consumables"}}
<a data-action="createEquipment" data-item-type="consumable" data-tooltip="{{localize 'PRISMRPG.Tooltip.addConsumable'}}"><i class="fas fa-plus"></i></a>
</legend>
<div class="inv-items">
{{#each consumables as |item|}}
<div class="inv-item inv-consumable" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" data-action="postItemToChat" />
<div class="inv-name">{{item.name}}</div>
<div class="inv-uses" data-tooltip="{{localize 'PRISMRPG.Tooltip.uses'}}">{{item.system.uses}}/{{item.system.usesMax}}</div>
<div class="inv-enc" data-tooltip="{{localize 'PRISMRPG.Label.encLoad'}}">{{item.system.encLoad}}</div>
<div class="controls">
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.assignToContainer'}}" data-action="assignToContainer" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-bag-shopping"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.useConsumable'}}" data-action="useConsumable" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" {{#unless item.system.uses}}aria-disabled="true" class="disabled"{{/unless}}><i class="fas fa-flask"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Delete'}}" data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
{{#unless consumables.length}}<p class="inv-empty">{{localize "PRISMRPG.Message.noConsumables"}}</p>{{/unless}}
</div>
</fieldset>
{{! Kits }}
<fieldset class="inv-section">
<legend>
{{localize "PRISMRPG.Label.kits"}}
<a data-action="createEquipment" data-item-type="equipment" data-item-kit="true" data-tooltip="{{localize 'PRISMRPG.Tooltip.addKit'}}"><i class="fas fa-plus"></i></a>
</legend>
<div class="inv-items">
{{#each kits as |item|}}
<div class="inv-item {{#if item.system.equipped}}is-equipped{{/if}}" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" data-action="postItemToChat" />
<div class="inv-name">{{item.name}}</div>
<div class="inv-enc" data-tooltip="{{localize 'PRISMRPG.Label.encLoad'}}">{{item.system.encLoad}}</div>
<div class="controls">
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.assignToContainer'}}" data-action="assignToContainer" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-bag-shopping"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.toggleEquipped'}}" data-action="toggleEquipped" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
{{#if item.system.equipped}}<i class="fas fa-shield-check equipped-icon"></i>{{else}}<i class="far fa-circle unequipped-icon"></i>{{/if}}
</a>
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Delete'}}" data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
{{#unless kits.length}}<p class="inv-empty">{{localize "PRISMRPG.Message.noKits"}}</p>{{/unless}}
</div>
</fieldset>
{{! General Equipment (non-kit) }}
<fieldset class="inv-section">
<legend>
{{localize "PRISMRPG.Label.equipment"}}
<a data-action="createEquipment" data-item-type="equipment" data-tooltip="{{localize 'PRISMRPG.Tooltip.addEquipment'}}"><i class="fas fa-plus"></i></a>
</legend>
<div class="inv-items">
{{#each equipmentItems as |item|}}
<div class="inv-item {{#if item.system.equipped}}is-equipped{{/if}}" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" data-action="postItemToChat" />
<div class="inv-name" data-tooltip="{{{item.system.description}}}">{{item.name}}</div>
<div class="inv-enc" data-tooltip="{{localize 'PRISMRPG.Label.encLoad'}}">{{item.system.encLoad}}</div>
<div class="controls">
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.assignToContainer'}}" data-action="assignToContainer" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-bag-shopping"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.toggleEquipped'}}" data-action="toggleEquipped" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
{{#if item.system.equipped}}<i class="fas fa-shield-check equipped-icon"></i>{{else}}<i class="far fa-circle unequipped-icon"></i>{{/if}}
</a>
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Delete'}}" data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
{{#unless equipmentItems.length}}<p class="inv-empty">{{localize "PRISMRPG.Message.noEquipment"}}</p>{{/unless}}
</div>
</fieldset>
{{! Loot }}
<fieldset class="inv-section">
<legend>
{{localize "PRISMRPG.Label.loot"}}
<a data-action="createEquipment" data-item-type="loot" data-tooltip="{{localize 'PRISMRPG.Tooltip.addLoot'}}"><i class="fas fa-plus"></i></a>
</legend>
<div class="inv-items">
{{#each loots as |item|}}
<div class="inv-item" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" data-action="postItemToChat" />
<div class="inv-name">{{item.name}}</div>
<div class="inv-enc" data-tooltip="{{localize 'PRISMRPG.Label.encLoad'}}">{{item.system.encLoad}}</div>
<div class="controls">
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.assignToContainer'}}" data-action="assignToContainer" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-bag-shopping"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Delete'}}" data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
{{#unless loots.length}}<p class="inv-empty">{{localize "PRISMRPG.Message.noLoot"}}</p>{{/unless}}
</div>
</fieldset>
{{! Containers }}
<fieldset class="inv-section">
<legend>
{{localize "PRISMRPG.Label.containers"}}
<a data-action="createEquipment" data-item-type="container" data-tooltip="{{localize 'PRISMRPG.Tooltip.addContainer'}}"><i class="fas fa-plus"></i></a>
</legend>
<div class="inv-items">
{{#each containerGroups as |group|}}
<div class="inv-item inv-container" data-item-id="{{group.container.id}}" data-item-uuid="{{group.container.uuid}}" data-container-id="{{group.container.id}}">
<img class="item-img" src="{{group.container.img}}" data-tooltip="{{group.container.name}}" data-action="postItemToChat" />
<div class="inv-name">{{group.container.name}}</div>
<div class="inv-capacity" data-tooltip="{{localize 'PRISMRPG.Tooltip.packBurden'}}">{{group.container.system.packBurden}}</div>
<div class="inv-enc" data-tooltip="{{localize 'PRISMRPG.Label.encLoad'}}">{{group.container.system.encLoad}}</div>
<div class="controls">
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.toggleEquipped'}}" data-action="toggleContainerEquipped" data-item-id="{{group.container.id}}" data-item-uuid="{{group.container.uuid}}">
{{#if group.container.system.equipped}}<i class="fas fa-backpack"></i>{{else}}<i class="fas fa-box"></i>{{/if}}
</a>
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{group.container.id}}" data-item-uuid="{{group.container.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Delete'}}" data-action="delete" data-item-id="{{group.container.id}}" data-item-uuid="{{group.container.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{#if group.items.length}}
<div class="inv-container-items">
{{#each group.items as |item|}}
<div class="inv-item inv-container-item" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<span class="inv-container-type-badge">{{item.type}}</span>
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" data-action="postItemToChat" />
<div class="inv-name">{{item.name}}</div>
<div class="inv-enc" data-tooltip="{{localize 'PRISMRPG.Label.encLoad'}}">{{item.system.encLoad}}</div>
<div class="controls">
<a data-tooltip="{{localize 'PRISMRPG.Tooltip.removeFromContainer'}}" data-action="removeFromContainer" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-arrow-up-from-bracket"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Delete'}}" data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
{{else}}
<div class="inv-container-empty">{{localize "PRISMRPG.Message.noStoredItems"}}</div>
{{/if}}
{{/each}}
{{#unless containerGroups.length}}<p class="inv-empty">{{localize "PRISMRPG.Message.noContainers"}}</p>{{/unless}}
</div>
</fieldset>
+93 -2
View File
@@ -64,6 +64,18 @@
}}
</div>
</div>
<div class="hp-item hp-temp-item">
<div class="hp-label" data-tooltip="{{localize 'PRISMRPG.Label.HPTemp'}}">HP Tmp</div>
<a data-action="hpTempMinus"><i class="fa-solid fa-minus"></i></a>
<div class="hp-value">
{{formInput
systemFields.hp.fields.temp
value=system.hp.temp
disabled=isPlayMode
}}
</div>
<a data-action="hpTempPlus"><i class="fa-solid fa-plus"></i></a>
</div>
<div class="hp-item">
<div class="hp-label">MANA</div>
<a data-action="manaPointsMinus"><i class="fa-solid fa-minus"></i></a>
@@ -86,6 +98,7 @@
</div>
<div class="hp-item">
<div class="hp-label">ARMOR</div>
<a data-action="armorPointsMinus"><i class="fa-solid fa-minus"></i></a>
<div class="hp-value">
{{formInput
systemFields.armorPoints.fields.value
@@ -93,6 +106,7 @@
disabled=isPlayMode
}}
</div>
<a data-action="armorPointsPlus"><i class="fa-solid fa-plus"></i></a>
<div class="hp-separator">/</div>
<div class="hp-max">
{{formInput
@@ -102,6 +116,26 @@
}}
</div>
</div>
<div class="hp-item">
<div class="hp-label" data-tooltip="Action Points">AP</div>
<a data-action="actionPointsMinus"><i class="fa-solid fa-minus"></i></a>
<div class="hp-value">
{{formInput
systemFields.actionPoints.fields.value
value=system.actionPoints.value
disabled=isPlayMode
}}
</div>
<a data-action="actionPointsPlus"><i class="fa-solid fa-plus"></i></a>
<div class="hp-separator">/</div>
<div class="hp-max">
{{formInput
systemFields.actionPoints.fields.max
value=system.actionPoints.max
disabled=isPlayMode
}}
</div>
</div>
</div>
</div>
</div>
@@ -259,6 +293,63 @@
</div>
</div>
</div>
{{! Movement Rating + Burden Section - below attributes }}
<div class="burden-mr-section">
<div class="burden-mr-item">
<div class="burden-display-row">
<div class="burden-label" data-tooltip="{{localize 'PRISMRPG.Tooltip.movementRating'}}">MR</div>
<div class="burden-total">{{movementRatingValue}}</div>
</div>
{{#if isEditMode}}
<div class="burden-edit-row">
<div class="burden-sub-attr">
{{formField
systemFields.movementRating.fields.subAttribute
value=system.movementRating.subAttribute
localize=true
}}
</div>
<span class="burden-op">+</span>
<div class="burden-other">
{{formInput systemFields.movementRating.fields.other value=system.movementRating.other}}
</div>
<span class="burden-op">-</span>
<div class="burden-other">
{{formInput systemFields.movementRating.fields.reduction value=system.movementRating.reduction}}
</div>
</div>
{{/if}}
</div>
<div class="burden-mr-item">
<div class="burden-display-row">
<div class="burden-label" data-tooltip="{{localize 'PRISMRPG.Tooltip.burdenCharacter'}}">BURDEN</div>
<div class="burden-used-max {{#if excessBurden}}burden-overloaded{{/if}}">
<span class="burden-used">{{burdenUsed}}</span>
<span class="burden-separator">/</span>
<span class="burden-max">{{burdenMax}}</span>
{{#if excessBurden}}
<span class="burden-excess" data-tooltip="{{localize 'PRISMRPG.Tooltip.excessBurden'}}">(-{{excessBurden}} MR)</span>
{{/if}}
</div>
</div>
{{#if isEditMode}}
<div class="burden-edit-row">
<div class="burden-sub-attr">
{{formField
systemFields.burden.fields.subAttribute
value=system.burden.subAttribute
localize=true
}}
</div>
<span class="burden-op">+</span>
<div class="burden-other">
{{formInput systemFields.burden.fields.other value=system.burden.other}}
</div>
</div>
{{/if}}
</div>
</div>
</div>
{{! Right Column - Race, Classes }}
@@ -270,7 +361,7 @@
<div class="race-content">
{{#if race}}
<div class="race-item" data-item-id="{{race.id}}" data-item-uuid="{{race.uuid}}">
<img class="item-img" src="{{race.img}}" data-tooltip="{{race.name}}" />
<img class="item-img" src="{{race.img}}" data-tooltip="{{race.name}}" data-action="postItemToChat" />
<div class="race-name">{{race.name}}</div>
<div class="controls">
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{race.id}}"
@@ -296,7 +387,7 @@
<div class="class-content">
{{#if classItem}}
<div class="class-item" data-item-id="{{classItem.id}}" data-item-uuid="{{classItem.uuid}}">
<img class="item-img" src="{{classItem.img}}" data-tooltip="{{classItem.name}}" />
<img class="item-img" src="{{classItem.img}}" data-tooltip="{{classItem.name}}" data-action="postItemToChat" />
<div class="class-name">{{classItem.name}}</div>
<div class="controls">
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{classItem.id}}"
-52
View File
@@ -1,52 +0,0 @@
<section class="tab character-{{tab.id}} {{tab.cssClass}}" data-tab="{{tab.id}}" data-group="{{tab.group}}">
<div class="main-div">
<fieldset>
<legend>{{localize "PRISMRPG.Label.manaPoints"}}</legend>
<div class="miracle-details">
<div class="miracle-detail">
<span>Current</span>
{{formField systemFields.manaPoints.fields.value value=system.manaPoints.value localize=true}}
<a data-action="manaPointsPlus"><i class="fa-solid fa-hexagon-plus"></i></a>
<a data-action="manaPointsMinus"><i class="fa-solid fa-hexagon-minus"></i></a>
<span>Max</span>
{{formField systemFields.manaPoints.fields.max value=system.manaPoints.max localize=true
disabled=isPlayMode}}
</div>
</div>
</fieldset>
<fieldset>
<legend>{{localize "PRISMRPG.Label.miracles"}}{{#if isEditMode}}<a class="action" data-tooltip="{{localize "
PRISMRPG.Tooltip.addMiracle"}}" data-tooltip-direction="UP"><i class="fas fa-plus"
data-action="createMiracle"></i></a>{{/if}}</legend>
<div class="miracles">
{{#each miracles as |item|}}
<div class="miracle" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-drag="true">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="name">
{{item.name}}
</div>
<a class="rollable" data-roll-type="miracle-attack" data-roll-key="{{item.id}}" data-tooltip="Miracle Attack">
<i class="lf-roll-small fa-solid fa-swords" data-roll-type="miracle-attack" data-roll-key="{{item.id}}"></i>
</a>
<a class="rollable" data-roll-type="miracle-power" data-roll-key="{{item.id}}" data-tooltip="Miracle Power">
<i class="fa-duotone fa-solid fa-stars" data-roll-type="miracle-power" data-roll-key="{{item.id}}"></i>
</a>
<div class="controls">
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</fieldset>
</div>
</section>
+27 -3
View File
@@ -11,8 +11,8 @@
<div class="skill {{#if item.system.isCoreSkill}}is-core-skill{{/if}}"
data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="name">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}\nClick to post to chat" data-action="postItemToChat" />
<div class="name" data-tooltip="{{item.system.description}}">
<a class="rollable" data-roll-type="skill" data-roll-key="{{item.id}}">
<i class="lf-roll-small fa-duotone fa-solid fa-dice-d10"></i>
{{item.name}}
@@ -42,11 +42,12 @@
<fieldset>
<legend data-tooltip="{{localize 'PRISMRPG.Tooltip.racialAbilities'}}" data-tooltip-direction="UP">
{{localize "PRISMRPG.Label.racialAbilities"}}
<a data-action="createEquipment" data-item-type="racial-ability" data-tooltip="{{localize 'PRISMRPG.Tooltip.addRacialAbility'}}"><i class="fas fa-plus"></i></a>
</legend>
<div class="racial-abilities">
{{#each racialAbilities as |item|}}
<div class="racial-ability " data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}\nClick to post to chat" data-action="postItemToChat" />
<div class="name" data-tooltip="{{{item.description}}}<br><br>{{item.path}}" data-tooltip-direction="UP">
{{item.name}}
</div>
@@ -60,5 +61,28 @@
{{/each}}
</div>
</fieldset>
<fieldset>
<legend data-tooltip="{{localize 'PRISMRPG.Tooltip.abilities'}}" data-tooltip-direction="UP">
{{localize "PRISMRPG.Label.abilities"}}
<a data-action="createEquipment" data-item-type="ability" data-tooltip="{{localize 'PRISMRPG.Tooltip.addAbility'}}"><i class="fas fa-plus"></i></a>
</legend>
<div class="racial-abilities">
{{#each abilities as |item|}}
<div class="racial-ability" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}\nClick to post to chat" data-action="postItemToChat" />
<div class="name" data-tooltip="{{{item.description}}}" data-tooltip-direction="UP">
{{item.name}}
</div>
<div class="controls">
<a data-tooltip="{{localize 'PRISMRPG.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'PRISMRPG.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</fieldset>
</div>
</section>
+1 -1
View File
@@ -8,7 +8,7 @@
<div class="spells">
{{#each spells as |item|}}
<div class="spell" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-drag="true">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" data-action="postItemToChat" />
<div class="name">
{{item.name}}
</div>
+16
View File
@@ -111,6 +111,22 @@
</div>
{{/if}}
{{#if (eq rollType "skill")}}
{{#if rollData.skillSubAttributeLabel}}
<div class="skill-info-card">
<div class="skill-header">
<strong class="skill-name">Skill Check</strong>
</div>
<div class="skill-stats">
<div class="stat-item subattribute">
<i class="fas fa-diagram-project"></i>
<span>{{rollData.skillSubAttributeLabel}} (+{{rollData.skillSubAttributeValue}})</span>
</div>
</div>
</div>
{{/if}}
{{/if}}
{{#if rollData.letItFly}}
<div class="special-badge">Let It Fly!</div>
{{/if}}
+20
View File
@@ -0,0 +1,20 @@
<div class="new-round-message prismrpg-chat-card">
<div class="chat-header">
<div class="chat-title">
<div class="roll-name">{{localize "PRISMRPG.Combat.newRound"}} {{round}}</div>
<div class="new-round-label">{{localize "PRISMRPG.Combat.restoreAP"}}</div>
</div>
</div>
<div class="new-round-actors">
<button class="new-round-restore-btn new-round-all-btn" type="button" data-actor-id="all" data-tooltip="{{localize "PRISMRPG.Combat.allPC"}}">
<i class="fas fa-users"></i>
<span>{{localize "PRISMRPG.Combat.allPC"}}</span>
</button>
{{#each actors}}
<button class="new-round-restore-btn" type="button" data-actor-id="{{id}}" data-tooltip="{{name}}">
<img src="{{img}}" width="24" height="24" />
<span>{{name}}</span>
</button>
{{/each}}
</div>
</div>
+55 -1
View File
@@ -7,6 +7,7 @@
{{! Navigation des onglets }}
<nav class="sheet-tabs tabs" data-group="primary">
<a class="item {{tabs.details.cssClass}}" data-tab="details">{{localize "PRISMRPG.Label.details"}}</a>
<a class="item {{tabs.advancements.cssClass}}" data-tab="advancements">{{localize "PRISMRPG.Label.advancement"}}</a>
<a class="item {{tabs.description.cssClass}}" data-tab="description">{{localize "PRISMRPG.Label.description"}}</a>
</nav>
@@ -61,7 +62,7 @@
<legend>{{localize "PRISMRPG.Label.classFeatures"}}</legend>
{{#each system.features as |feature level|}}
<label>{{localize "PRISMRPG.Label.level"}} {{inc @key}}</label>
<label>{{localize "PRISMRPG.Label.level"}} {{replace @key "level" ""}}</label>
{{formInput
(lookup ../systemFields.features.fields @key)
enriched=(lookup ../enrichedFeatures @key)
@@ -73,6 +74,59 @@
</fieldset>
</div>
{{! Onglet Advancements }}
<div class="tab {{tabs.advancements.cssClass}}" data-group="primary" data-tab="advancements">
<fieldset>
<legend>{{localize "PRISMRPG.Label.advancement"}}</legend>
{{#each advancementsByLevel as |levelData|}}
<div class="advancement-level">
<h3>
<span class="level-title">{{localize "PRISMRPG.Label.level"}} {{levelData.level}}</span>
<button type="button" class="add-advancement" data-level="{{levelData.levelKey}}" data-tooltip="{{localize 'PRISMRPG.Label.addAdvancement'}}">
<i class="fas fa-plus"></i>
</button>
</h3>
{{#if levelData.advancements.length}}
<div class="advancement-list">
{{#each levelData.advancements as |advancement|}}
<div class="advancement-item">
<div class="advancement-header">
<img class="advancement-icon" src="{{advancement.icon}}" data-level="{{advancement.levelKey}}" data-index="{{advancement.index}}" />
{{formInput
../../systemFields.advancements.fields.level1.element.fields.name
value=advancement.name
name=(concat "system.advancements." advancement.levelKey "." advancement.index ".name")
placeholder=(localize "PRISMRPG.Label.advancementName")
}}
<button type="button" class="toggle-advancement-description" data-tooltip="{{localize 'PRISMRPG.Label.toggleDescription'}}">
<i class="fas fa-chevron-down"></i>
</button>
<button type="button" class="delete-advancement" data-level="{{advancement.levelKey}}" data-index="{{advancement.index}}" data-tooltip="{{localize 'PRISMRPG.Label.deleteAdvancement'}}">
<i class="fas fa-trash"></i>
</button>
</div>
<div class="advancement-description collapsed">
{{formInput
../../systemFields.advancements.fields.level1.element.fields.description
enriched=advancement.enrichedDescription
value=advancement.description
name=(concat "system.advancements." advancement.levelKey "." advancement.index ".description")
toggled=true
}}
</div>
</div>
{{/each}}
</div>
{{else}}
<p class="empty-advancements">{{localize "PRISMRPG.Label.noAdvancements"}}</p>
{{/if}}
</div>
{{/each}}
</fieldset>
</div>
{{! Onglet Description }}
<div class="tab {{tabs.description.cssClass}}" data-group="primary" data-tab="description">
<fieldset>
+40
View File
@@ -0,0 +1,40 @@
<section>
<div class="header">
<img class="item-img" src="{{item.img}}" data-edit="img" data-action="editImage" data-tooltip="{{item.name}}" />
{{formInput fields.name value=source.name}}
</div>
<nav class="sheet-tabs tabs" data-group="primary">
<a class="item {{tabs.details.cssClass}}" data-tab="details">{{localize "PRISMRPG.Label.details"}}</a>
<a class="item {{tabs.description.cssClass}}" data-tab="description">{{localize "PRISMRPG.Label.description"}}</a>
<a class="item {{tabs.effects.cssClass}}" data-tab="effects">{{localize "PRISMRPG.Label.effects"}}</a>
</nav>
{{! Details Tab }}
<div class="tab {{tabs.details.cssClass}}" data-group="primary" data-tab="details">
<fieldset>
<legend>{{localize "PRISMRPG.Label.consumable"}}</legend>
{{formField systemFields.usesMax value=system.usesMax localize=true}}
{{formField systemFields.uses value=system.uses localize=true}}
{{formField systemFields.encLoad value=system.encLoad localize=true}}
{{formField systemFields.cost value=system.cost localize=true}}
</fieldset>
</div>
{{! Description Tab }}
<div class="tab {{tabs.description.cssClass}}" data-group="primary" data-tab="description">
<fieldset>
<legend>{{localize "PRISMRPG.Label.description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
<fieldset>
<legend>{{localize "PRISMRPG.Label.notes"}}</legend>
{{formInput systemFields.notes enriched=enrichedNotes value=system.notes name="system.notes" toggled=true}}
</fieldset>
</div>
{{! Effects Tab }}
<div class="tab {{tabs.effects.cssClass}}" data-group="primary" data-tab="effects">
{{> systems/fvtt-prism-rpg/templates/partial-item-effects.hbs}}
</div>
</section>
+58
View File
@@ -0,0 +1,58 @@
<section>
<div class="header">
<img
class="item-img"
src="{{item.img}}"
data-edit="img"
data-action="editImage"
data-tooltip="{{item.name}}"
/>
{{formInput fields.name value=source.name}}
</div>
<nav class="sheet-tabs tabs" data-group="primary">
<a class="item {{tabs.details.cssClass}}" data-tab="details">{{localize "PRISMRPG.Label.details"}}</a>
<a class="item {{tabs.description.cssClass}}" data-tab="description">{{localize "PRISMRPG.Label.description"}}</a>
<a class="item {{tabs.effects.cssClass}}" data-tab="effects">{{localize "PRISMRPG.Label.effects"}}</a>
</nav>
{{! Details Tab }}
<div class="tab {{tabs.details.cssClass}}" data-group="primary" data-tab="details">
<fieldset>
<legend>{{localize "PRISMRPG.Label.container"}}</legend>
{{formField systemFields.packBurden value=system.packBurden localize=true}}
{{formField systemFields.encLoad value=system.encLoad localize=true}}
{{formField systemFields.cost value=system.cost localize=true}}
</fieldset>
</div>
{{! Description Tab }}
<div class="tab {{tabs.description.cssClass}}" data-group="primary" data-tab="description">
<fieldset>
<legend>{{localize "PRISMRPG.Label.description"}}</legend>
{{formInput
systemFields.description
enriched=enrichedDescription
value=system.description
name="system.description"
toggled=true
}}
</fieldset>
<fieldset>
<legend>{{localize "PRISMRPG.Label.notes"}}</legend>
{{formInput
systemFields.notes
enriched=enrichedNotes
value=system.notes
name="system.notes"
toggled=true
}}
</fieldset>
</div>
{{! Effects Tab }}
<div class="tab {{tabs.effects.cssClass}}" data-group="primary" data-tab="effects">
{{> systems/fvtt-prism-rpg/templates/partial-item-effects.hbs}}
</div>
</section>
+38
View File
@@ -0,0 +1,38 @@
<section>
<div class="header">
<img class="item-img" src="{{item.img}}" data-edit="img" data-action="editImage" data-tooltip="{{item.name}}" />
{{formInput fields.name value=source.name}}
</div>
<nav class="sheet-tabs tabs" data-group="primary">
<a class="item {{tabs.details.cssClass}}" data-tab="details">{{localize "PRISMRPG.Label.details"}}</a>
<a class="item {{tabs.description.cssClass}}" data-tab="description">{{localize "PRISMRPG.Label.description"}}</a>
<a class="item {{tabs.effects.cssClass}}" data-tab="effects">{{localize "PRISMRPG.Label.effects"}}</a>
</nav>
{{! Details Tab }}
<div class="tab {{tabs.details.cssClass}}" data-group="primary" data-tab="details">
<fieldset>
<legend>{{localize "PRISMRPG.Label.loot"}}</legend>
{{formField systemFields.encLoad value=system.encLoad localize=true}}
{{formField systemFields.cost value=system.cost localize=true}}
</fieldset>
</div>
{{! Description Tab }}
<div class="tab {{tabs.description.cssClass}}" data-group="primary" data-tab="description">
<fieldset>
<legend>{{localize "PRISMRPG.Label.description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
<fieldset>
<legend>{{localize "PRISMRPG.Label.notes"}}</legend>
{{formInput systemFields.notes enriched=enrichedNotes value=system.notes name="system.notes" toggled=true}}
</fieldset>
</div>
{{! Effects Tab }}
<div class="tab {{tabs.effects.cssClass}}" data-group="primary" data-tab="effects">
{{> systems/fvtt-prism-rpg/templates/partial-item-effects.hbs}}
</div>
</section>
+2
View File
@@ -43,6 +43,8 @@
{{formField systemFields.language value=system.language}}
{{formField systemFields.baseBurden value=system.baseBurden}}
</div>
<div class="align-top">
+23
View File
@@ -149,6 +149,29 @@
{{/if}}
{{/if}}
{{! Skill Options }}
{{#if (eq rollType "skill")}}
<div class="option-section skill-section">
<div class="section-title">
<i class="fas fa-dice-d20"></i>
<span>Skill Options</span>
</div>
{{! Choose which sub-attribute to use }}
<div class="option-row">
<label>Use Sub-Attribute:</label>
<select name="skillSubAttribute" class="styled-select">
<option value="{{rollTarget.subAttribute1}}" selected>
{{rollTarget.subAttribute1Label}} (+{{rollTarget.subAttribute1Value}})
</option>
<option value="{{rollTarget.subAttribute2}}">
{{rollTarget.subAttribute2Label}} (+{{rollTarget.subAttribute2Value}})
</option>
</select>
</div>
</div>
{{/if}}
{{! Advantage/Disadvantage }}
{{#if hasAdvantage}}
<div class="option-section">
+20
View File
@@ -117,6 +117,26 @@
{{/if}}
{{/if}}
{{! Skill Options }}
{{#if (eq rollType "skill")}}
<fieldSet class="dialog-skill-options">
<legend>Skill Options</legend>
{{! Choose which sub-attribute to use }}
<div class="dialog-save">
<label>Use Sub-Attribute:</label>
<select name="skillSubAttribute" data-tooltip-direction="UP">
<option value="{{rollTarget.subAttribute1}}" selected>
{{rollTarget.subAttribute1Label}} (+{{rollTarget.subAttribute1Value}})
</option>
<option value="{{rollTarget.subAttribute2}}">
{{rollTarget.subAttribute2Label}} (+{{rollTarget.subAttribute2Value}})
</option>
</select>
</div>
</fieldSet>
{{/if}}
{{#if hasAdvantage}}
<fieldSet class="dialog-advantage">
<legend>{{localize "PRISMRPG.Roll.advantageDisadvantage"}}</legend>
+12 -16
View File
@@ -11,36 +11,32 @@
<p class="hint">{{localize "PRISMRPG.Hint.isCoreSkill"}}</p>
</div>
{{!-- Primary Attribute for Skill Checks --}}
{{!-- First Sub-Attribute for Skill Checks --}}
<div class="form-group">
<label>{{localize "PRISMRPG.Label.primaryAttribute"}}</label>
<select name="system.primaryAttribute">
{{#each config.CHARACTERISTICS}}
<option value="{{@key}}" {{#if (eq ../system.primaryAttribute @key)}}selected{{/if}}>
<label>{{localize "PRISMRPG.Label.subAttribute1"}}</label>
<select name="system.subAttribute1">
{{#each config.SUB_ATTRIBUTES}}
<option value="{{@key}}" {{#if (eq ../system.subAttribute1 @key)}}selected{{/if}}>
{{localize this.label}}
</option>
{{/each}}
</select>
<p class="hint">Primary attribute used for skill checks (D&D 5e style: ability modifier + proficiency)</p>
<p class="hint">First sub-attribute used for skill checks</p>
</div>
{{#if system.isCoreSkill}}
{{!-- Attribute Bonus Selection --}}
{{!-- Second Sub-Attribute for Skill Checks --}}
<div class="form-group">
<label>{{localize "PRISMRPG.Label.attributeBonusChoice"}}</label>
<select name="system.attributeBonus">
<option value="">{{localize "PRISMRPG.Label.selectAttribute"}}</option>
{{#each config.CHARACTERISTICS}}
<option value="{{@key}}" {{#if (eq ../system.attributeBonus @key)}}selected{{/if}}>
<label>{{localize "PRISMRPG.Label.subAttribute2"}}</label>
<select name="system.subAttribute2">
{{#each config.SUB_ATTRIBUTES}}
<option value="{{@key}}" {{#if (eq ../system.subAttribute2 @key)}}selected{{/if}}>
{{localize this.label}}
</option>
{{/each}}
</select>
<p class="hint">{{localize "PRISMRPG.Hint.attributeBonus"}}</p>
<p class="hint">Second sub-attribute used for skill checks</p>
</div>
{{/if}}
{{!-- Notes --}}
<fieldset>
<legend>{{localize "PRISMRPG.Label.notes"}}</legend>