5 Commits

Author SHA1 Message Date
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
22 changed files with 734 additions and 16 deletions
+3
View File
@@ -8,3 +8,6 @@ node_modules/
.history .history
# GitHub/Copilot config
.github/
+1 -13
View File
@@ -1,20 +1,8 @@
## Prism RPG RPG for Foundry Virtual TableTop ## 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. 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 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.
+184
View File
@@ -1126,6 +1126,10 @@ i.prismrpg {
border: 2px solid #6b6b6b; border: 2px solid #6b6b6b;
border-radius: 4px; 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 { .prismrpg .character-main-v2 .character-left-column .character-attributes {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -2163,6 +2167,116 @@ i.prismrpg {
.prismrpg .racial-ability-content input[type="checkbox"]:checked::after { .prismrpg .racial-ability-content input[type="checkbox"]:checked::after {
color: rgba(0, 0, 0, 0.1); 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 { .prismrpg .weapon-content {
font-family: var(--font-primary); font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1); font-size: calc(var(--font-size-standard) * 1);
@@ -4317,6 +4431,76 @@ i.prismrpg {
.chat-log .message-content .prismrpg-item-chat-card.equipment-item .item-header { .chat-log .message-content .prismrpg-item-chat-card.equipment-item .item-header {
background: linear-gradient(135deg, #f39c12 0%, #b8730f 100%); 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 { .application.dialog.prismrpg {
color: var(--color-dark-1); color: var(--color-dark-1);
} }
+87 -1
View File
@@ -382,6 +382,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": { "Label": {
"agility": "Dexterity", "agility": "Dexterity",
"gotoToken": "Go to token", "gotoToken": "Go to token",
@@ -440,6 +522,7 @@
"combatDetails": "Combat details", "combatDetails": "Combat details",
"Challenges": "Challenges", "Challenges": "Challenges",
"HP": "HP", "HP": "HP",
"HPTemp": "Temporary Hit Points",
"Movement": "Movement", "Movement": "Movement",
"Saves": "Saves", "Saves": "Saves",
"app": "APP", "app": "APP",
@@ -471,6 +554,7 @@
"equipment": "Equipment", "equipment": "Equipment",
"experience": "Experience", "experience": "Experience",
"racialAbilities": "Racial Abilities", "racialAbilities": "Racial Abilities",
"abilities": "Abilities",
"grit": "Grit", "grit": "Grit",
"gritEarned": "Grit earned", "gritEarned": "Grit earned",
"int": "INT", "int": "INT",
@@ -813,7 +897,8 @@
"addMiracle": "Add new miracle", "addMiracle": "Add new miracle",
"skill": "Skills list", "skill": "Skills list",
"skills": "Skills - Your character's skills and abilities", "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"
}, },
"RollSavingThrow": "Roll Saving Throw", "RollSavingThrow": "Roll Saving Throw",
"Message": { "Message": {
@@ -1412,6 +1497,7 @@
"armor": "Armor", "armor": "Armor",
"equipment": "Equipment", "equipment": "Equipment",
"racial-ability": "Racial Ability", "racial-ability": "Racial Ability",
"ability": "Ability",
"miracle": "Miracle", "miracle": "Miracle",
"save": "Save", "save": "Save",
"shield": "Shield", "shield": "Shield",
+1
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 PrismRPGWeaponSheet } from "./sheets/weapon-sheet.mjs"
export { default as PrismRPGSkillSheet } from "./sheets/skill-sheet.mjs" export { default as PrismRPGSkillSheet } from "./sheets/skill-sheet.mjs"
export { default as PrismRPGRacialAbilitySheet } from "./sheets/racial-ability-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 PrismRPGVulnerabilitySheet } from "./sheets/vulnerability-sheet.mjs"
export { default as PrismRPGArmorSheet } from "./sheets/armor-sheet.mjs" export { default as PrismRPGArmorSheet } from "./sheets/armor-sheet.mjs"
export { default as PrismRPGSpellSheet } from "./sheets/spell-sheet.mjs" export { default as PrismRPGSpellSheet } from "./sheets/spell-sheet.mjs"
@@ -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
}
}
@@ -26,6 +26,8 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
manaPointsMinus: PrismRPGCharacterSheet.#onManaPointsMinus, manaPointsMinus: PrismRPGCharacterSheet.#onManaPointsMinus,
hpPlus: PrismRPGCharacterSheet.#onHpPlus, hpPlus: PrismRPGCharacterSheet.#onHpPlus,
hpMinus: PrismRPGCharacterSheet.#onHpMinus, hpMinus: PrismRPGCharacterSheet.#onHpMinus,
hpTempPlus: PrismRPGCharacterSheet.#onHpTempPlus,
hpTempMinus: PrismRPGCharacterSheet.#onHpTempMinus,
postItemToChat: PrismRPGCharacterSheet.#onPostItemToChat, postItemToChat: PrismRPGCharacterSheet.#onPostItemToChat,
}, },
} }
@@ -112,6 +114,7 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
context.tab = context.tabs.skills context.tab = context.tabs.skills
context.skills = doc.itemTypes.skill context.skills = doc.itemTypes.skill
context.racialAbilities = doc.itemTypes["racial-ability"] context.racialAbilities = doc.itemTypes["racial-ability"]
context.abilities = doc.itemTypes["ability"]
context.vulnerabilities = doc.itemTypes.vulnerability context.vulnerabilities = doc.itemTypes.vulnerability
break break
case "subattributes": case "subattributes":
@@ -223,6 +226,16 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
this.actor.update({ "system.hp.value": Math.max(hp, 0) }) this.actor.update({ "system.hp.value": Math.max(hp, 0) })
} }
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) { static async #onCreateEquipment(event, target) {
} }
+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" },
]
+1 -2
View File
@@ -36,8 +36,7 @@ export default class PrismRPGActor extends Actor {
if (this.type === "character") { if (this.type === "character") {
Object.assign(prototypeToken, { Object.assign(prototypeToken, {
sight: { enabled: true }, sight: { enabled: true },
actorLink: true, actorLink: false
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY,
}) })
this.updateSource({ prototypeToken }) this.updateSource({ prototypeToken })
} }
+1
View File
@@ -6,6 +6,7 @@ export { default as PrismRPGSkill } from "./skill.mjs"
export { default as PrismRPGArmor } from "./armor.mjs" export { default as PrismRPGArmor } from "./armor.mjs"
export { default as PrismRPGShield } from "./shield.mjs" export { default as PrismRPGShield } from "./shield.mjs"
export { default as PrismRPGRacialAbility } from "./racial-ability.mjs" export { default as PrismRPGRacialAbility } from "./racial-ability.mjs"
export { default as PrismRPGAbility } from "./ability.mjs"
export { default as PrismRPGEquipment } from "./equipment.mjs" export { default as PrismRPGEquipment } from "./equipment.mjs"
export { default as PrismRPGRace } from "./race.mjs" export { default as PrismRPGRace } from "./race.mjs"
export { default as PrismRPGClass } from "./class.mjs" export { default as PrismRPGClass } from "./class.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"]
}
+1
View File
@@ -265,6 +265,7 @@ export default class PrismRPGUtils {
const templatePaths = [ const templatePaths = [
'systems/fvtt-prism-rpg/templates/partial-item-effects.hbs', 'systems/fvtt-prism-rpg/templates/partial-item-effects.hbs',
'systems/fvtt-prism-rpg/templates/weapon-types-config.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) return foundry.applications.handlebars.loadTemplates(templatePaths)
} }
+75
View File
@@ -4,6 +4,7 @@
*/ */
import { SYSTEM } from "./module/config/system.mjs" 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 globalThis.SYSTEM = SYSTEM // Expose the SYSTEM object to the global scope
// Import modules // Import modules
@@ -52,6 +53,7 @@ Hooks.once("init", function () {
CONFIG.Item.dataModels = { CONFIG.Item.dataModels = {
skill: models.PrismRPGSkill, skill: models.PrismRPGSkill,
"racial-ability": models.PrismRPGRacialAbility, "racial-ability": models.PrismRPGRacialAbility,
ability: models.PrismRPGAbility,
weapon: models.PrismRPGWeapon, weapon: models.PrismRPGWeapon,
armor: models.PrismRPGArmor, armor: models.PrismRPGArmor,
shield: models.PrismRPGShield, shield: models.PrismRPGShield,
@@ -70,6 +72,7 @@ Hooks.once("init", function () {
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ActorSheet) 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.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.PrismRPGRacialAbilitySheet, { types: ["racial-ability"], makeDefault: true })
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.PrismRPGWeaponSheet, { types: ["weapon"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGSpellSheet, { types: ["spell"], 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 }) foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGArmorSheet, { types: ["armor"], makeDefault: true })
@@ -79,6 +82,13 @@ Hooks.once("init", function () {
foundry.documents.collections.Items.registerSheet("prismRPG", applications.PrismRPGClassSheet, { types: ["class"], 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.PrismRPGCharacterPathSheet, { types: ["character-path"], makeDefault: true })
// Status Effects — Afflictions & Imbuements
CONFIG.statusEffects = [
{ id: "dead", name: "EFFECT.StatusDead", icon: "icons/svg/skull.svg" },
...AFFLICTIONS,
...IMBUEMENTS,
]
// Other Document Configuration // Other Document Configuration
CONFIG.ChatMessage.documentClass = documents.PrismRPGChatMessage CONFIG.ChatMessage.documentClass = documents.PrismRPGChatMessage
@@ -196,6 +206,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 // Handle Roll Damage button click in weapon attack messages
$html.find(".roll-damage-button").click(async (event) => { $html.find(".roll-damage-button").click(async (event) => {
const btn = event.currentTarget const btn = event.currentTarget
@@ -211,6 +247,45 @@ Hooks.on(hookName, (message, html, data) => {
await actor.prepareRoll("weapon-damage-medium", weaponId) 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
// 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 * Create a macro when dropping an entity on the hotbar
* Item - open roll dialog * Item - open roll dialog
+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);
}
}
+7
View File
@@ -143,6 +143,13 @@
border-radius: 4px; border-radius: 4px;
} }
} }
&.hp-temp-item {
.hp-value input {
background: rgba(255, 230, 160, 0.85);
border-color: #b8860b;
}
}
} }
} }
} }
+87
View File
@@ -548,3 +548,90 @@
} }
} }
} }
// 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;
}
}
+1
View File
@@ -9,6 +9,7 @@
@import "monster.less"; @import "monster.less";
@import "skill.less"; @import "skill.less";
@import "racial-ability.less"; @import "racial-ability.less";
@import "ability.less";
@import "weapon.less"; @import "weapon.less";
@import "armor.less"; @import "armor.less";
@import "spell.less"; @import "spell.less";
+1
View File
@@ -34,6 +34,7 @@
"Item": { "Item": {
"skill": { "htmlFields": ["description"] }, "skill": { "htmlFields": ["description"] },
"racial-ability": { "htmlFields": ["description"] }, "racial-ability": { "htmlFields": ["description"] },
"ability": { "htmlFields": ["description"] },
"weapon": { "htmlFields": ["description"] }, "weapon": { "htmlFields": ["description"] },
"armor": { "htmlFields": ["description"] }, "armor": { "htmlFields": ["description"] },
"shield": { "htmlFields": ["description"] }, "shield": { "htmlFields": ["description"] },
+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>
+12
View File
@@ -64,6 +64,18 @@
}} }}
</div> </div>
</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-item">
<div class="hp-label">MANA</div> <div class="hp-label">MANA</div>
<a data-action="manaPointsMinus"><i class="fa-solid fa-minus"></i></a> <a data-action="manaPointsMinus"><i class="fa-solid fa-minus"></i></a>
+22
View File
@@ -60,5 +60,27 @@
{{/each}} {{/each}}
</div> </div>
</fieldset> </fieldset>
<fieldset>
<legend data-tooltip="{{localize 'PRISMRPG.Tooltip.abilities'}}" data-tooltip-direction="UP">
{{localize "PRISMRPG.Label.abilities"}}
</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> </div>
</section> </section>
+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>