mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-03-07 06:26:13 +01:00
Merge branch 'main' into release
This commit is contained in:
commit
6b5c1ff965
94 changed files with 3068 additions and 411 deletions
|
|
@ -3,13 +3,15 @@ import * as applications from './module/applications/_module.mjs';
|
|||
import * as data from './module/data/_module.mjs';
|
||||
import * as models from './module/data/_module.mjs';
|
||||
import * as documents from './module/documents/_module.mjs';
|
||||
import * as collections from './module/documents/collections/_module.mjs';
|
||||
import * as dice from './module/dice/_module.mjs';
|
||||
import * as fields from './module/data/fields/_module.mjs';
|
||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
||||
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
||||
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll } from './module/dice/_module.mjs';
|
||||
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll } from './module/dice/_module.mjs';
|
||||
import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs';
|
||||
import { enrichedFateRoll, getFateTypeData } from './module/enrichers/FateRollEnricher.mjs';
|
||||
import {
|
||||
handlebarsRegistration,
|
||||
runMigrations,
|
||||
|
|
@ -24,16 +26,18 @@ import TokenManager from './module/documents/tokenManager.mjs';
|
|||
CONFIG.DH = SYSTEM;
|
||||
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
||||
|
||||
CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll];
|
||||
CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll];
|
||||
CONFIG.Dice.daggerheart = {
|
||||
DHRoll: DHRoll,
|
||||
DualityRoll: DualityRoll,
|
||||
D20Roll: D20Roll,
|
||||
DamageRoll: DamageRoll
|
||||
DamageRoll: DamageRoll,
|
||||
FateRoll: FateRoll
|
||||
};
|
||||
|
||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||
CONFIG.Actor.dataModels = models.actors.config;
|
||||
CONFIG.Actor.collection = collections.DhActorCollection;
|
||||
|
||||
CONFIG.Item.documentClass = documents.DHItem;
|
||||
CONFIG.Item.dataModels = models.items.config;
|
||||
|
|
@ -56,6 +60,9 @@ CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer;
|
|||
|
||||
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
||||
|
||||
CONFIG.RollTable.documentClass = documents.DhRollTable;
|
||||
CONFIG.RollTable.resultTemplate = 'systems/daggerheart/templates/ui/chat/table-result.hbs';
|
||||
|
||||
CONFIG.Scene.documentClass = documents.DhScene;
|
||||
|
||||
CONFIG.Token.documentClass = documents.DhToken;
|
||||
|
|
@ -103,7 +110,7 @@ Hooks.once('init', () => {
|
|||
type: game.i18n.localize(typePath)
|
||||
});
|
||||
|
||||
const { Items, Actors } = foundry.documents.collections;
|
||||
const { Items, Actors, RollTables } = foundry.documents.collections;
|
||||
Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2);
|
||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Ancestry, {
|
||||
types: ['ancestry'],
|
||||
|
|
@ -188,6 +195,12 @@ Hooks.once('init', () => {
|
|||
label: sheetLabel('TYPES.Actor.party')
|
||||
});
|
||||
|
||||
RollTables.unregisterSheet('core', foundry.applications.sheets.RollTableSheet);
|
||||
RollTables.registerSheet(SYSTEM.id, applications.sheets.rollTables.RollTableSheet, {
|
||||
types: ['base'],
|
||||
makeDefault: true
|
||||
});
|
||||
|
||||
DocumentSheetConfig.unregisterSheet(
|
||||
CONFIG.ActiveEffect.documentClass,
|
||||
'core',
|
||||
|
|
@ -296,13 +309,15 @@ Hooks.on('chatMessage', (_, message) => {
|
|||
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
|
||||
: undefined;
|
||||
const difficulty = rollCommand.difficulty;
|
||||
const grantResources = Boolean(rollCommand.grantResources);
|
||||
|
||||
const target = getCommandTarget({ allowNull: true });
|
||||
const title = traitValue
|
||||
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
|
||||
})
|
||||
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
||||
const title =
|
||||
(flavor ?? traitValue)
|
||||
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
|
||||
})
|
||||
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
||||
|
||||
enrichedDualityRoll({
|
||||
reaction,
|
||||
|
|
@ -312,7 +327,36 @@ Hooks.on('chatMessage', (_, message) => {
|
|||
title,
|
||||
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'),
|
||||
actionType: null,
|
||||
advantage
|
||||
advantage,
|
||||
grantResources
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (message.startsWith('/fr')) {
|
||||
const result =
|
||||
message.trim().toLowerCase() === '/fr' ? { result: {} } : rollCommandToJSON(message.replace(/\/fr\s?/, ''));
|
||||
|
||||
if (!result) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateParsing'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const { result: rollCommand, flavor } = result;
|
||||
const fateTypeData = getFateTypeData(rollCommand?.type);
|
||||
|
||||
if (!fateTypeData)
|
||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
||||
|
||||
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
||||
const target = getCommandTarget({ allowNull: true });
|
||||
const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll');
|
||||
|
||||
enrichedFateRoll({
|
||||
target,
|
||||
title,
|
||||
label: fateTypeLabel,
|
||||
fateType
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
|
@ -381,8 +425,8 @@ Hooks.on('targetToken', () => {
|
|||
debouncedRangeEffectCall();
|
||||
});
|
||||
|
||||
Hooks.on('refreshToken', (_, options) => {
|
||||
if (options.refreshPosition) {
|
||||
Hooks.on('refreshToken', (token, options) => {
|
||||
if (options.refreshPosition && !token._original) {
|
||||
debouncedRangeEffectCall();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
76
lang/en.json
76
lang/en.json
|
|
@ -237,10 +237,13 @@
|
|||
"confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)"
|
||||
},
|
||||
"viewLevelups": "View Levelups",
|
||||
"resetCharacter": "Reset Character",
|
||||
"viewParty": "View Party",
|
||||
"InvalidOldCharacterImportTitle": "Old Character Import",
|
||||
"InvalidOldCharacterImportText": "Character data exported prior to system version 1.1 will not generate a complete character. Do you wish to continue?",
|
||||
"cancelBeastform": "Cancel Beastform"
|
||||
"cancelBeastform": "Cancel Beastform",
|
||||
"resetCharacterConfirmationTitle": "Reset Character",
|
||||
"resetCharacterConfirmationContent": "You are reseting all character data except name and portrait. Are you sure?"
|
||||
},
|
||||
"Companion": {
|
||||
"FIELDS": {
|
||||
|
|
@ -314,6 +317,8 @@
|
|||
"selectPrimaryWeapon": "Select Primary Weapon",
|
||||
"selectSecondaryWeapon": "Select Secondary Weapon",
|
||||
"selectSubclass": "Select Subclass",
|
||||
"setupSkipTitle": "Skipping Character Setup",
|
||||
"setupSkipContent": "You are skipping the Character Setup by adding this manually. The character setup is the blinking arrows in the top-right. Are you sure you want to continue?",
|
||||
"startingItems": "Starting Items",
|
||||
"story": "Story",
|
||||
"storyExplanation": "Select which background and connection prompts you want to copy into your character's background.",
|
||||
|
|
@ -325,6 +330,12 @@
|
|||
"title": "{actor} - Character Setup",
|
||||
"traitIncreases": "Trait Increases"
|
||||
},
|
||||
"CharacterReset": {
|
||||
"title": "Reset Character",
|
||||
"alwaysDeleteSection": "Deleted Data",
|
||||
"optionalDeleteSection": "Optional Data",
|
||||
"headerTitle": "Select which data you'd like to keep"
|
||||
},
|
||||
"CombatTracker": {
|
||||
"combatStarted": "Active",
|
||||
"giveSpotlight": "Give The Spotlight",
|
||||
|
|
@ -477,7 +488,9 @@
|
|||
"tokenHUD": {
|
||||
"genericEffects": "Foundry Effects",
|
||||
"depositPartyTokens": "Deposit Party Tokens",
|
||||
"retrievePartyTokens": "Retrieve Party Tokens"
|
||||
"retrievePartyTokens": "Retrieve Party Tokens",
|
||||
"depositCompanionTokens": "Deposit Companion Token",
|
||||
"retrieveCompanionTokens": "Retrieve Companion Token"
|
||||
}
|
||||
},
|
||||
"ImageSelect": {
|
||||
|
|
@ -615,6 +628,13 @@
|
|||
"title": "{name} Resource",
|
||||
"rerollDice": "Reroll Dice"
|
||||
},
|
||||
"RiskItAllDialog": {
|
||||
"title": "{name} - Risk It All",
|
||||
"subtitle": "Clear Stress and Hit Points",
|
||||
"remainingTitle": "Remaining Points",
|
||||
"clearResource": "Clear {resource}",
|
||||
"finalTitle": "Final Character Resources"
|
||||
},
|
||||
"TagTeamSelect": {
|
||||
"title": "Tag Team Roll",
|
||||
"leaderTitle": "Initiating Character",
|
||||
|
|
@ -962,6 +982,10 @@
|
|||
"outsideRange": "Outside Range"
|
||||
},
|
||||
"Condition": {
|
||||
"deathMove": {
|
||||
"name": "Death Move",
|
||||
"description": "The character is about to make a Death Move"
|
||||
},
|
||||
"dead": {
|
||||
"name": "Dead",
|
||||
"description": "The character is dead"
|
||||
|
|
@ -1013,15 +1037,15 @@
|
|||
"DeathMoves": {
|
||||
"avoidDeath": {
|
||||
"name": "Avoid Death",
|
||||
"description": "You drop unconscious temporarily and work with the GM to describe how the situation gets much worse because of it. Then roll your Fear die; if its value is equal to or under your Level, take a Scar."
|
||||
"description": "Your character avoids death and faces the consequences. They temporarily drop unconscious, and then you work with the GM to describe how the situation worsens. While unconscious, your character can't move or act, and they can't be targeted by an attack. They return to consciousness when an ally clears 1 or more of their marked Hit Points or when the party finishes a long rest. After your character falls unconscious, roll your Hope Die. If its value is equal to or less than your character's level, they gain a scar: permanently cross out a Hope slot and work with the GM to determine its lasting narrative impact and how, if possible, it can be restored. If you ever cross out your last Hope slot, your character's journey ends."
|
||||
},
|
||||
"riskItAll": {
|
||||
"name": "Risk It All",
|
||||
"description": "Roll your Duality Dice. If Hope is higher, you stay on your feet and clear an amount of Hit Points and/or Stress equal to the value of the Hope die (divide the Hope die value up between these however you’d prefer). If your Fear die is higher, you cross through the veil of death. If the Duality Dice are tied, you stay on your feet and clear all Hit Points and Stress."
|
||||
"description": "Roll your Duality Dice. If the Hope Die is higher, your character stays on their feet and clears a number of Hit Points or Stress equal to the value of the Hope Die (you can divide the Hope Die value between Hit Points and Stress however you'd prefer). If the Fear Die is higher, your character crosses through the veil of death. If the Duality Dice show matching results, your character stays up and clears all Hit Points and Stress."
|
||||
},
|
||||
"blazeOfGlory": {
|
||||
"name": "Blaze Of Glory",
|
||||
"description": "With Blaze of Glory, the player is accepting death for the character. Take one action (at GM discretion), which becomes an automatic critical success, then cross through the veil of death."
|
||||
"description": "Your character embraces death and goes out in a blaze of glory. Take one final action. It automatically critically succeeds (with GM approval), and then you cross through the veil of death. NOTE: A Blaze of Glory effect has been added to your character. Any Duality Roll will automatically be a critical."
|
||||
}
|
||||
},
|
||||
"DomainCardTypes": {
|
||||
|
|
@ -2062,6 +2086,7 @@
|
|||
"description": "Description",
|
||||
"main": "Data",
|
||||
"information": "Information",
|
||||
"itemFeatures": "Item Features",
|
||||
"notes": "Notes",
|
||||
"inventory": "Inventory",
|
||||
"loadout": "Loadout",
|
||||
|
|
@ -2137,6 +2162,7 @@
|
|||
"dropActorsHere": "Drop Actors here",
|
||||
"dropFeaturesHere": "Drop Features here",
|
||||
"duality": "Duality",
|
||||
"dualityDice": "Duality Dice",
|
||||
"dualityRoll": "Duality Roll",
|
||||
"enabled": "Enabled",
|
||||
"evasion": "Evasion",
|
||||
|
|
@ -2146,11 +2172,14 @@
|
|||
"plural": "Experiences"
|
||||
},
|
||||
"failure": "Failure",
|
||||
"fate": "Fate",
|
||||
"fateRoll": "Fate Roll",
|
||||
"fear": "Fear",
|
||||
"features": "Features",
|
||||
"formula": "Formula",
|
||||
"general": "General",
|
||||
"gm": "GM",
|
||||
"guaranteedCriticalSuccess": "Guaranteed Critical Success",
|
||||
"healing": "Healing",
|
||||
"healingRoll": "Healing Roll",
|
||||
"hit": {
|
||||
|
|
@ -2194,6 +2223,7 @@
|
|||
"single": "Player",
|
||||
"plurial": "Players"
|
||||
},
|
||||
"portrait": "Portrait",
|
||||
"proficiency": "Proficiency",
|
||||
"quantity": "Quantity",
|
||||
"range": "Range",
|
||||
|
|
@ -2210,6 +2240,7 @@
|
|||
"rollWith": "{roll} Roll",
|
||||
"save": "Save",
|
||||
"scalable": "Scalable",
|
||||
"scars": "Scars",
|
||||
"situationalBonus": "Situational Bonus",
|
||||
"spent": "Spent",
|
||||
"step": "Step",
|
||||
|
|
@ -2352,6 +2383,12 @@
|
|||
"secondaryWeapon": "Secondary Weapon"
|
||||
}
|
||||
},
|
||||
"ROLLTABLES": {
|
||||
"FIELDS": {
|
||||
"formulaName": { "label": "Formula Name" }
|
||||
},
|
||||
"formula": "Formula"
|
||||
},
|
||||
"SETTINGS": {
|
||||
"Appearance": {
|
||||
"FIELDS": {
|
||||
|
|
@ -2418,7 +2455,11 @@
|
|||
"overlay": { "label": "Overlay Effect" },
|
||||
"characterDefault": { "label": "Character Default Defeated Status" },
|
||||
"adversaryDefault": { "label": "Adversary Default Defeated Status" },
|
||||
"companionDefault": { "label": "Companion Default Defeated Status" }
|
||||
"companionDefault": { "label": "Companion Default Defeated Status" },
|
||||
"deathMove": { "label": "Death Move" },
|
||||
"dead": { "label": "Dead" },
|
||||
"defeated": { "label": "Defeated" },
|
||||
"unconscious": { "label": "Unconscious" }
|
||||
},
|
||||
"hopeFear": {
|
||||
"label": "Hope & Fear",
|
||||
|
|
@ -2451,10 +2492,6 @@
|
|||
"label": "Show Resource Change Scrolltexts",
|
||||
"hint": "When a character is damaged, uses armor etc, a scrolling text will briefly appear by the token to signify this."
|
||||
},
|
||||
"playerCanEditSheet": {
|
||||
"label": "Players Can Manually Edit Character Settings",
|
||||
"hint": "Players are allowed to access the manual Character Settings and change their statistics beyond the rules."
|
||||
},
|
||||
"roll": {
|
||||
"roll": {
|
||||
"label": "Roll",
|
||||
|
|
@ -2511,6 +2548,7 @@
|
|||
"resetMovesText": "Are you sure you want to reset?",
|
||||
"FIELDS": {
|
||||
"maxFear": { "label": "Max Fear" },
|
||||
"maxHope": { "label": "Max Hope" },
|
||||
"traitArray": { "label": "Initial Trait Modifiers" },
|
||||
"maxLoadout": {
|
||||
"label": "Max Cards in Loadout",
|
||||
|
|
@ -2658,7 +2696,16 @@
|
|||
"currentTarget": "Current"
|
||||
},
|
||||
"deathMove": {
|
||||
"title": "Death Move"
|
||||
"title": "Death Move",
|
||||
"gainScar": "You gained a scar.",
|
||||
"avoidScar": "You have avoided a new scar.",
|
||||
"journeysEnd": "You have {scars} Scars and have crossed out your last Hope slot. Your character's journey ends.",
|
||||
"riskItAllCritical": "Critical Rolled, clearing all marked Stress and Hit Points.",
|
||||
"riskItAllFailure": "The fear die rolled higher. You have crossed through the veil of death.",
|
||||
"blazeOfGlory": "Blaze of Glory Effect Added!",
|
||||
"riskItAllDialogButton": "Clear Stress And Hit Points.",
|
||||
"riskItAllSuccessWithEnoughHope": "The Hope value is more than the marked Stress and Hit Points. Both are cleared fully.",
|
||||
"riskItAllSuccess": "The hope die rolled higher, clear up to {hope} Stress And Hit Points."
|
||||
},
|
||||
"dicePool": {
|
||||
"title": "Dice Pool"
|
||||
|
|
@ -2780,7 +2827,9 @@
|
|||
"noAssignedPlayerCharacter": "You have no assigned character.",
|
||||
"noSelectedToken": "You have no selected token",
|
||||
"onlyUseableByPC": "This can only be used with a PC token",
|
||||
"dualityParsing": "Duality roll not properly formated",
|
||||
"dualityParsing": "Duality roll not properly formatted",
|
||||
"fateParsing": "Fate roll not properly formatted",
|
||||
"fateTypeParsing": "Fate roll not properly formatted, bad fate type. Valid types are 'Hope' and 'Fear'",
|
||||
"attributeFaulty": "The supplied Attribute doesn't exist",
|
||||
"domainCardWrongDomain": "You don't have access to that Domain",
|
||||
"domainCardToHighLevel": "The Domain Card is too high level to be selected",
|
||||
|
|
@ -2844,7 +2893,8 @@
|
|||
"documentIsMissing": "The {documentType} is missing from the world.",
|
||||
"tokenActorMissing": "{name} is missing an Actor",
|
||||
"tokenActorsMissing": "[{names}] missing Actors",
|
||||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used"
|
||||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
||||
"knowTheTide": "Know The Tide gained a token"
|
||||
},
|
||||
"Sidebar": {
|
||||
"actorDirectory": {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
export { default as AttributionDialog } from './attributionDialog.mjs';
|
||||
export { default as BeastformDialog } from './beastformDialog.mjs';
|
||||
export { default as CharacterResetDialog } from './characterResetDialog.mjs';
|
||||
export { default as d20RollDialog } from './d20RollDialog.mjs';
|
||||
export { default as DamageDialog } from './damageDialog.mjs';
|
||||
export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
|
||||
|
|
@ -14,3 +15,4 @@ export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
|||
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
|
||||
export { default as GroupRollDialog } from './group-roll-dialog.mjs';
|
||||
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
|
||||
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
|
||||
|
|
|
|||
105
module/applications/dialogs/characterResetDialog.mjs
Normal file
105
module/applications/dialogs/characterResetDialog.mjs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class CharacterResetDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(actor, options = {}) {
|
||||
super(options);
|
||||
|
||||
this.actor = actor;
|
||||
this.data = {
|
||||
delete: {
|
||||
class: { keep: false, label: 'TYPES.Item.class' },
|
||||
subclass: { keep: false, label: 'TYPES.Item.subclass' },
|
||||
ancestry: { keep: false, label: 'TYPES.Item.ancestry' },
|
||||
community: { keep: false, label: 'TYPES.Item.community' }
|
||||
},
|
||||
optional: {
|
||||
portrait: { keep: true, label: 'DAGGERHEART.GENERAL.portrait' },
|
||||
name: { keep: true, label: 'Name' },
|
||||
biography: { keep: true, label: 'DAGGERHEART.GENERAL.Tabs.biography' },
|
||||
inventory: { keep: true, label: 'DAGGERHEART.GENERAL.inventory' }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'character-reset'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-arrow-rotate-left',
|
||||
title: 'DAGGERHEART.APPLICATIONS.CharacterReset.title'
|
||||
},
|
||||
actions: {
|
||||
finishSelection: this.#finishSelection
|
||||
},
|
||||
form: {
|
||||
handler: this.updateData,
|
||||
submitOnChange: true,
|
||||
submitOnClose: false
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
resourceDice: {
|
||||
id: 'resourceDice',
|
||||
template: 'systems/daggerheart/templates/dialogs/characterReset.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.data = this.data;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async updateData(event, _, formData) {
|
||||
const { data } = foundry.utils.expandObject(formData.object);
|
||||
|
||||
this.data = foundry.utils.mergeObject(this.data, data);
|
||||
this.render();
|
||||
}
|
||||
|
||||
static getUpdateData() {
|
||||
const update = {};
|
||||
if (!this.data.optional.portrait) update.if(!this.data.optional.biography);
|
||||
|
||||
if (!this.data.optional.inventory) return update;
|
||||
}
|
||||
|
||||
static async #finishSelection() {
|
||||
const update = {};
|
||||
if (!this.data.optional.name.keep) {
|
||||
const defaultName = game.system.api.documents.DhpActor.defaultName({ type: 'character' });
|
||||
foundry.utils.setProperty(update, 'name', defaultName);
|
||||
foundry.utils.setProperty(update, 'prototypeToken.name', defaultName);
|
||||
}
|
||||
|
||||
if (!this.data.optional.portrait.keep) {
|
||||
foundry.utils.setProperty(update, 'img', this.actor.schema.fields.img.initial(this.actor));
|
||||
foundry.utils.setProperty(update, 'prototypeToken.==texture', {});
|
||||
foundry.utils.setProperty(update, 'prototypeToken.==ring', {});
|
||||
}
|
||||
|
||||
if (this.data.optional.biography.keep)
|
||||
foundry.utils.setProperty(update, 'system.biography', this.actor.system.biography);
|
||||
|
||||
if (this.data.optional.inventory.keep) foundry.utils.setProperty(update, 'system.gold', this.actor.system.gold);
|
||||
|
||||
const { system, ...rest } = update;
|
||||
await this.actor.update({
|
||||
...rest,
|
||||
'==system': system ?? {}
|
||||
});
|
||||
|
||||
const inventoryItemTypes = ['weapon', 'armor', 'consumable', 'loot'];
|
||||
await this.actor.deleteEmbeddedDocuments(
|
||||
'Item',
|
||||
this.actor.items
|
||||
.filter(x => !inventoryItemTypes.includes(x.type) || !this.data.optional.inventory.keep)
|
||||
.map(x => x.id)
|
||||
);
|
||||
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -109,11 +109,17 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.roll = this.roll;
|
||||
context.rollType = this.roll?.constructor.name;
|
||||
context.rallyDie = this.roll.rallyChoices;
|
||||
const experiences = this.config.data?.system?.experiences || {};
|
||||
|
||||
const actorExperiences = this.config.data?.system?.experiences || {};
|
||||
const companionExperiences = this.config.roll.companionRoll
|
||||
? (this.config.data?.companion?.system.experiences ?? {})
|
||||
: null;
|
||||
const experiences = companionExperiences ?? actorExperiences;
|
||||
context.experiences = Object.keys(experiences).map(id => ({
|
||||
id,
|
||||
...experiences[id]
|
||||
}));
|
||||
|
||||
context.selectedExperiences = this.config.experiences;
|
||||
context.advantage = this.config.roll?.advantage;
|
||||
context.disadvantage = this.config.roll?.disadvantage;
|
||||
|
|
@ -123,7 +129,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.formula = this.roll.constructFormula(this.config);
|
||||
if (this.actor?.system?.traits) context.abilities = this.getTraitModifiers();
|
||||
|
||||
context.showReaction = !this.config.roll?.type || context.rollType === 'DualityRoll';
|
||||
context.showReaction = !this.config.skips?.reaction && context.rollType === 'DualityRoll';
|
||||
context.reactionOverride = this.reactionOverride;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
import { enrichedFateRoll } from '../../enrichers/FateRollEnricher.mjs';
|
||||
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
||||
|
||||
export default class DhpDeathMove extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(actor) {
|
||||
super({});
|
||||
|
||||
this.actor = actor;
|
||||
this.selectedMove = null;
|
||||
this.showRiskItAllButton = false;
|
||||
this.riskItAllButtonLabel = '';
|
||||
this.riskItAllHope = 0;
|
||||
}
|
||||
|
||||
get title() {
|
||||
|
|
@ -38,6 +43,111 @@ export default class DhpDeathMove extends HandlebarsApplicationMixin(Application
|
|||
return context;
|
||||
}
|
||||
|
||||
async handleAvoidDeath() {
|
||||
const target = this.actor.uuid;
|
||||
const config = await enrichedFateRoll({
|
||||
target,
|
||||
title: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.avoidDeath.name'),
|
||||
label: `${game.i18n.localize('DAGGERHEART.GENERAL.hope')} ${game.i18n.localize('DAGGERHEART.GENERAL.fateRoll')}`,
|
||||
fateType: 'Hope'
|
||||
});
|
||||
|
||||
if (!config.roll.fate) return;
|
||||
|
||||
let returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.avoidScar');
|
||||
if (config.roll.fate.value <= this.actor.system.levelData.level.current) {
|
||||
const newScarAmount = this.actor.system.scars + 1;
|
||||
await this.actor.update({
|
||||
system: {
|
||||
scars: newScarAmount
|
||||
}
|
||||
});
|
||||
|
||||
if (newScarAmount >= this.actor.system.resources.hope.max) {
|
||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
||||
return game.i18n.format('DAGGERHEART.UI.Chat.deathMove.journeysEnd', { scars: newScarAmount });
|
||||
}
|
||||
|
||||
returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.gainScar');
|
||||
}
|
||||
|
||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.unconscious.id);
|
||||
return returnMessage;
|
||||
}
|
||||
|
||||
async handleRiskItAll() {
|
||||
const config = await enrichedDualityRoll({
|
||||
reaction: true,
|
||||
traitValue: null,
|
||||
target: this.actor,
|
||||
difficulty: null,
|
||||
title: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.riskItAll.name'),
|
||||
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityDice'),
|
||||
actionType: null,
|
||||
advantage: null,
|
||||
grantResources: false,
|
||||
customConfig: { skips: { resources: true, reaction: true } }
|
||||
});
|
||||
|
||||
if (!config.roll.result) return;
|
||||
|
||||
const clearAllStressAndHitpointsUpdates = [
|
||||
{ key: 'hitPoints', clear: true },
|
||||
{ key: 'stress', clear: true }
|
||||
];
|
||||
|
||||
let chatMessage = '';
|
||||
if (config.roll.isCritical) {
|
||||
config.resourceUpdates.addResources(clearAllStressAndHitpointsUpdates);
|
||||
chatMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.riskItAllCritical');
|
||||
}
|
||||
|
||||
if (config.roll.result.duality == 1) {
|
||||
if (
|
||||
config.roll.hope.value >=
|
||||
this.actor.system.resources.hitPoints.value + this.actor.system.resources.stress.value
|
||||
) {
|
||||
config.resourceUpdates.addResources(clearAllStressAndHitpointsUpdates);
|
||||
chatMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.riskItAllSuccessWithEnoughHope');
|
||||
} else {
|
||||
chatMessage = game.i18n.format('DAGGERHEART.UI.Chat.deathMove.riskItAllSuccess', {
|
||||
hope: config.roll.hope.value
|
||||
});
|
||||
this.showRiskItAllButton = true;
|
||||
this.riskItAllHope = config.roll.hope.value;
|
||||
this.riskItAllButtonLabel = game.i18n.format('DAGGERHEART.UI.Chat.deathMove.riskItAllDialogButton');
|
||||
}
|
||||
}
|
||||
|
||||
if (config.roll.result.duality == -1) {
|
||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
||||
chatMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.riskItAllFailure');
|
||||
}
|
||||
|
||||
await config.resourceUpdates.updateResources();
|
||||
return chatMessage;
|
||||
}
|
||||
|
||||
async handleBlazeOfGlory() {
|
||||
this.actor.createEmbeddedDocuments('ActiveEffect', [
|
||||
{
|
||||
name: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name'),
|
||||
description: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.description'),
|
||||
img: CONFIG.DH.GENERAL.deathMoves.blazeOfGlory.img,
|
||||
changes: [
|
||||
{
|
||||
key: 'system.rules.roll.guaranteedCritical',
|
||||
mode: 2,
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
||||
return game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.blazeOfGlory');
|
||||
}
|
||||
|
||||
static selectMove(_, button) {
|
||||
const move = button.dataset.move;
|
||||
this.selectedMove = CONFIG.DH.GENERAL.deathMoves[move];
|
||||
|
|
@ -46,23 +156,49 @@ export default class DhpDeathMove extends HandlebarsApplicationMixin(Application
|
|||
}
|
||||
|
||||
static async takeMove() {
|
||||
this.close();
|
||||
|
||||
let result = '';
|
||||
|
||||
if (CONFIG.DH.GENERAL.deathMoves.blazeOfGlory === this.selectedMove) {
|
||||
result = await this.handleBlazeOfGlory();
|
||||
}
|
||||
|
||||
if (CONFIG.DH.GENERAL.deathMoves.avoidDeath === this.selectedMove) {
|
||||
result = await this.handleAvoidDeath();
|
||||
}
|
||||
|
||||
if (CONFIG.DH.GENERAL.deathMoves.riskItAll === this.selectedMove) {
|
||||
result = await this.handleRiskItAll();
|
||||
}
|
||||
|
||||
if (!result) return;
|
||||
|
||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
||||
.expandRollMessage?.desc;
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
|
||||
const msg = {
|
||||
user: game.user.id,
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/deathMove.hbs',
|
||||
{
|
||||
player: this.actor.name,
|
||||
actor: { name: this.actor.name, img: this.actor.img },
|
||||
actor: this.actor,
|
||||
actorId: this.actor._id,
|
||||
author: game.users.get(game.user.id),
|
||||
title: game.i18n.localize(this.selectedMove.name),
|
||||
img: this.selectedMove.img,
|
||||
description: game.i18n.localize(this.selectedMove.description)
|
||||
description: game.i18n.localize(this.selectedMove.description),
|
||||
result: result,
|
||||
open: autoExpandDescription ? 'open' : '',
|
||||
chevron: autoExpandDescription ? 'fa-chevron-up' : 'fa-chevron-down',
|
||||
showRiskItAllButton: this.showRiskItAllButton,
|
||||
riskItAllButtonLabel: this.riskItAllButtonLabel,
|
||||
riskItAllHope: this.riskItAllHope
|
||||
}
|
||||
),
|
||||
title: game.i18n.localize(
|
||||
'DAGGERHEART.UI.Chat.deathMove.title'
|
||||
),
|
||||
title: game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.title'),
|
||||
speaker: cls.getSpeaker(),
|
||||
flags: {
|
||||
daggerheart: {
|
||||
|
|
@ -72,7 +208,5 @@ export default class DhpDeathMove extends HandlebarsApplicationMixin(Application
|
|||
};
|
||||
|
||||
cls.create(msg);
|
||||
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
94
module/applications/dialogs/riskItAllDialog.mjs
Normal file
94
module/applications/dialogs/riskItAllDialog.mjs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
export default class RiskItAllDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(actor, resourceValue) {
|
||||
super({});
|
||||
|
||||
this.actor = actor;
|
||||
this.resourceValue = resourceValue;
|
||||
this.choices = {
|
||||
hitPoints: 0,
|
||||
stress: 0
|
||||
};
|
||||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.format('DAGGERHEART.APPLICATIONS.RiskItAllDialog.title', { name: this.actor.name });
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart', 'dh-style', 'dialog', 'views', 'risk-it-all'],
|
||||
position: { width: 280, height: 'auto' },
|
||||
window: { icon: 'fa-solid fa-dice fa-xl' },
|
||||
actions: {
|
||||
finish: RiskItAllDialog.#finish
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
application: {
|
||||
id: 'risk-it-all',
|
||||
template: 'systems/daggerheart/templates/dialogs/riskItAllDialog.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
for (const input of htmlElement.querySelectorAll('.resource-container input'))
|
||||
input.addEventListener('change', this.updateChoice.bind(this));
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.resourceValue = this.resourceValue;
|
||||
context.maxHitPointsValue = Math.min(this.resourceValue, this.actor.system.resources.hitPoints.max);
|
||||
context.maxStressValue = Math.min(this.resourceValue, this.actor.system.resources.stress.max);
|
||||
context.remainingResource = this.resourceValue - this.choices.hitPoints - this.choices.stress;
|
||||
context.unfinished = context.remainingResource !== 0;
|
||||
|
||||
context.choices = this.choices;
|
||||
context.final = {
|
||||
hitPoints: {
|
||||
value: this.actor.system.resources.hitPoints.value - this.choices.hitPoints,
|
||||
max: this.actor.system.resources.hitPoints.max
|
||||
},
|
||||
stress: {
|
||||
value: this.actor.system.resources.stress.value - this.choices.stress,
|
||||
max: this.actor.system.resources.stress.max
|
||||
}
|
||||
};
|
||||
|
||||
context;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
updateChoice(event) {
|
||||
let value = Number.parseInt(event.target.value);
|
||||
const choiceKey = event.target.dataset.choice;
|
||||
const actorValue = this.actor.system.resources[choiceKey].value;
|
||||
const remaining = this.resourceValue - this.choices.hitPoints - this.choices.stress;
|
||||
const changeAmount = value - this.choices[choiceKey];
|
||||
|
||||
/* If trying to increase beyond remaining resource points, just increase to max available */
|
||||
if (remaining - changeAmount < 0) value = this.choices[choiceKey] + remaining;
|
||||
else if (actorValue - value < 0) value = actorValue;
|
||||
|
||||
this.choices[choiceKey] = value;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #finish() {
|
||||
const resourceUpdate = Object.keys(this.choices).reduce((acc, resourceKey) => {
|
||||
const value = this.actor.system.resources[resourceKey].value - this.choices[resourceKey];
|
||||
acc[resourceKey] = { value };
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
await this.actor.update({
|
||||
'system.resources': resourceUpdate
|
||||
});
|
||||
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
classes: ['daggerheart'],
|
||||
actions: {
|
||||
combat: DHTokenHUD.#onToggleCombat,
|
||||
togglePartyTokens: DHTokenHUD.#togglePartyTokens
|
||||
togglePartyTokens: DHTokenHUD.#togglePartyTokens,
|
||||
toggleCompanions: DHTokenHUD.#toggleCompanions
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -26,7 +27,7 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
context.partyOnCanvas =
|
||||
this.actor.type === 'party' &&
|
||||
this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0);
|
||||
context.icons.toggleParty = 'systems/daggerheart/assets/icons/arrow-dunk.png';
|
||||
context.icons.toggleClowncar = 'systems/daggerheart/assets/icons/arrow-dunk.png';
|
||||
context.actorType = this.actor.type;
|
||||
context.usesEffects = this.actor.type !== 'party';
|
||||
context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type)
|
||||
|
|
@ -56,6 +57,9 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
}, {})
|
||||
: null;
|
||||
|
||||
context.hasCompanion = this.actor.system.companion;
|
||||
context.companionOnCanvas = context.hasCompanion && this.actor.system.companion.getActiveTokens().length > 0;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -101,8 +105,24 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
: 'DAGGERHEART.APPLICATIONS.HUD.tokenHUD.depositPartyTokens'
|
||||
);
|
||||
|
||||
await this.toggleClowncar(this.actor.system.partyMembers);
|
||||
}
|
||||
|
||||
static async #toggleCompanions(_, button) {
|
||||
const icon = button.querySelector('img');
|
||||
icon.classList.toggle('flipped');
|
||||
button.dataset.tooltip = game.i18n.localize(
|
||||
icon.classList.contains('flipped')
|
||||
? 'DAGGERHEART.APPLICATIONS.HUD.tokenHUD.retrieveCompanionTokens'
|
||||
: 'DAGGERHEART.APPLICATIONS.HUD.tokenHUD.depositCompanionTokens'
|
||||
);
|
||||
|
||||
await this.toggleClowncar([this.actor.system.companion]);
|
||||
}
|
||||
|
||||
async toggleClowncar(actors) {
|
||||
const animationDuration = 500;
|
||||
const activeTokens = this.actor.system.partyMembers.flatMap(member => member.getActiveTokens());
|
||||
const activeTokens = actors.flatMap(member => member.getActiveTokens());
|
||||
const { x: actorX, y: actorY } = this.document;
|
||||
if (activeTokens.length > 0) {
|
||||
for (let token of activeTokens) {
|
||||
|
|
@ -114,14 +134,15 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
}
|
||||
} else {
|
||||
const activeScene = game.scenes.find(x => x.id === game.user.viewedScene);
|
||||
const partyTokenData = [];
|
||||
for (let member of this.actor.system.partyMembers) {
|
||||
const tokenData = [];
|
||||
for (let member of actors) {
|
||||
const data = await member.getTokenDocument();
|
||||
partyTokenData.push(data.toObject());
|
||||
tokenData.push(data.toObject());
|
||||
}
|
||||
|
||||
const newTokens = await activeScene.createEmbeddedDocuments(
|
||||
'Token',
|
||||
partyTokenData.map(tokenData => ({
|
||||
tokenData.map(tokenData => ({
|
||||
...tokenData,
|
||||
alpha: 0,
|
||||
x: actorX,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import BaseLevelUp from './levelup.mjs';
|
||||
import { defaultCompanionTier, LevelOptionType } from '../../data/levelTier.mjs';
|
||||
import { DhLevelup } from '../../data/levelup.mjs';
|
||||
import { DhCompanionLevelup as DhLevelup } from '../../data/companionLevelup.mjs';
|
||||
import { diceTypes, range } from '../../config/generalConfig.mjs';
|
||||
|
||||
export default class DhCompanionLevelUp extends BaseLevelUp {
|
||||
|
|
@ -9,7 +9,9 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
|
|||
|
||||
this.levelTiers = this.addBonusChoices(defaultCompanionTier);
|
||||
const playerLevelupData = actor.system.levelData;
|
||||
this.levelup = new DhLevelup(DhLevelup.initializeData(this.levelTiers, playerLevelupData));
|
||||
this.levelup = new DhLevelup(
|
||||
DhLevelup.initializeData(this.levelTiers, playerLevelupData, actor.system.levelupChoicesLeft)
|
||||
);
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,10 @@ export default class DhlevelUpViewMode extends HandlebarsApplicationMixin(Applic
|
|||
return checkbox;
|
||||
});
|
||||
|
||||
let label = game.i18n.localize(option.label);
|
||||
let label =
|
||||
optionKey === 'domainCard'
|
||||
? game.i18n.format(option.label, { maxLevel: tier.levels.end })
|
||||
: game.i18n.localize(option.label);
|
||||
return {
|
||||
label: label,
|
||||
checkboxGroups: chunkify(checkboxes, option.minCost, chunkedBoxes => {
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options, 'action');
|
||||
context.source = this.action.toObject(true);
|
||||
context.action = this.action;
|
||||
|
||||
context.summons = [];
|
||||
for (const summon of context.source.summon ?? []) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export * as actors from './actors/_module.mjs';
|
||||
export * as api from './api/_modules.mjs';
|
||||
export * as items from './items/_module.mjs';
|
||||
export * as rollTables from './rollTables/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||
import DhpDeathMove from '../../dialogs/deathMove.mjs';
|
||||
import DhDeathMove from '../../dialogs/deathMove.mjs';
|
||||
import { abilities } from '../../../config/actorConfig.mjs';
|
||||
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
||||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||
|
|
@ -27,6 +27,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
makeDeathMove: CharacterSheet.#makeDeathMove,
|
||||
levelManagement: CharacterSheet.#levelManagement,
|
||||
viewLevelups: CharacterSheet.#viewLevelups,
|
||||
resetCharacter: CharacterSheet.#resetCharacter,
|
||||
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
||||
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||
|
|
@ -42,6 +43,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
icon: 'fa-solid fa-angles-up',
|
||||
label: 'DAGGERHEART.ACTORS.Character.viewLevelups',
|
||||
action: 'viewLevelups'
|
||||
},
|
||||
{
|
||||
icon: 'fa-solid fa-arrow-rotate-left',
|
||||
label: 'DAGGERHEART.ACTORS.Character.resetCharacter',
|
||||
action: 'resetCharacter'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -220,13 +226,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
case 'header':
|
||||
const { playerCanEditSheet, levelupAuto } = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Automation
|
||||
);
|
||||
context.showSettings = game.user.isGM || !levelupAuto || (levelupAuto && playerCanEditSheet);
|
||||
break;
|
||||
case 'loadout':
|
||||
await this._prepareLoadoutContext(context, options);
|
||||
break;
|
||||
|
|
@ -666,12 +665,19 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
new LevelupViewMode(this.document).render({ force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the character data and removes all embedded documents.
|
||||
*/
|
||||
static async #resetCharacter() {
|
||||
new game.system.api.applications.dialogs.CharacterResetDialog(this.document).render({ force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the Death Move interface for the character.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #makeDeathMove() {
|
||||
await new DhpDeathMove(this.document).render({ force: true });
|
||||
await new DhDeathMove(this.document).render({ force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -728,9 +734,9 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
if (!result) return;
|
||||
|
||||
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
|
||||
const costResources = result.costs
|
||||
.filter(x => x.enabled)
|
||||
.map(cost => ({ ...cost, value: -cost.value, total: -cost.total }));
|
||||
const costResources =
|
||||
result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value, total: -cost.total })) ||
|
||||
{};
|
||||
config.resourceUpdates.addResources(costResources);
|
||||
await config.resourceUpdates.updateResources();
|
||||
}
|
||||
|
|
@ -956,6 +962,18 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
}
|
||||
|
||||
async _onDropItem(event, item) {
|
||||
const setupCriticalItemTypes = ['class', 'subclass', 'ancestry', 'community'];
|
||||
if (this.document.system.needsCharacterSetup && setupCriticalItemTypes.includes(item.type)) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.CharacterCreation.setupSkipTitle')
|
||||
},
|
||||
content: game.i18n.localize('DAGGERHEART.APPLICATIONS.CharacterCreation.setupSkipContent')
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
if (this.document.uuid === item.parent?.uuid) {
|
||||
return super._onDropItem(event, item);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,15 +38,6 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
}
|
||||
};
|
||||
|
||||
/** @inheritDoc */
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
|
||||
this.element
|
||||
.querySelector('.level-value')
|
||||
?.addEventListener('change', event => this.document.updateLevel(Number(event.currentTarget.value)));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -71,10 +62,10 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.Roll.action')}: ${this.actor.name}`,
|
||||
headerTitle: `Companion ${game.i18n.localize('DAGGERHEART.GENERAL.Roll.action')}`,
|
||||
roll: {
|
||||
trait: partner.system.spellcastModifierTrait?.key
|
||||
trait: partner.system.spellcastModifierTrait?.key,
|
||||
companionRoll: true
|
||||
},
|
||||
hasRoll: true,
|
||||
data: partner.getRollData()
|
||||
hasRoll: true
|
||||
};
|
||||
|
||||
const result = await partner.diceRoll(config);
|
||||
|
|
|
|||
|
|
@ -600,7 +600,7 @@ export default function DHApplicationMixin(Base) {
|
|||
{
|
||||
relativeTo: isAction ? doc.parent : doc,
|
||||
rollData: doc.getRollData?.(),
|
||||
secrets: isAction ? doc.parent.isOwner : doc.isOwner
|
||||
secrets: isAction ? doc.parent.parent.isOwner : doc.isOwner
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
1
module/applications/sheets/rollTables/_module.mjs
Normal file
1
module/applications/sheets/rollTables/_module.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as RollTableSheet } from './rollTable.mjs';
|
||||
191
module/applications/sheets/rollTables/rollTable.mjs
Normal file
191
module/applications/sheets/rollTables/rollTable.mjs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
export default class DhRollTableSheet extends foundry.applications.sheets.RollTableSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
actions: {
|
||||
changeMode: DhRollTableSheet.#onChangeMode,
|
||||
drawResult: DhRollTableSheet.#onDrawResult,
|
||||
resetResults: DhRollTableSheet.#onResetResults,
|
||||
addFormula: DhRollTableSheet.#addFormula,
|
||||
removeFormula: DhRollTableSheet.#removeFormula
|
||||
}
|
||||
};
|
||||
|
||||
static buildParts() {
|
||||
const { footer, header, sheet, results, ...parts } = super.PARTS;
|
||||
return {
|
||||
sheet: {
|
||||
...sheet,
|
||||
template: 'systems/daggerheart/templates/sheets/rollTable/sheet.hbs'
|
||||
},
|
||||
header: { template: 'systems/daggerheart/templates/sheets/rollTable/header.hbs' },
|
||||
...parts,
|
||||
results: {
|
||||
template: 'systems/daggerheart/templates/sheets/rollTable/results.hbs',
|
||||
templates: ['templates/sheets/roll-table/result-details.hbs'],
|
||||
scrollable: ['table[data-results] tbody']
|
||||
},
|
||||
summary: { template: 'systems/daggerheart/templates/sheets/rollTable/summary.hbs' },
|
||||
footer
|
||||
};
|
||||
}
|
||||
|
||||
static PARTS = DhRollTableSheet.buildParts();
|
||||
|
||||
async _preRender(context, options) {
|
||||
await super._preRender(context, options);
|
||||
|
||||
if (!options.internalRefresh)
|
||||
this.daggerheartFlag = new game.system.api.data.DhRollTable(this.document.flags.daggerheart);
|
||||
}
|
||||
|
||||
/* root PART has a blank element on _attachPartListeners, so it cannot be used to set the eventListeners for the view mode */
|
||||
async _onRender(context, options) {
|
||||
super._onRender(context, options);
|
||||
|
||||
for (const element of this.element.querySelectorAll('.system-update-field'))
|
||||
element.addEventListener('change', this.updateSystemField.bind(this));
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
|
||||
switch (partId) {
|
||||
case 'sheet':
|
||||
context.altFormula = this.daggerheartFlag.altFormula;
|
||||
context.usesAltFormula = Object.keys(this.daggerheartFlag.altFormula).length > 0;
|
||||
context.altFormulaOptions = {
|
||||
'': { name: this.daggerheartFlag.formulaName },
|
||||
...this.daggerheartFlag.altFormula
|
||||
};
|
||||
context.activeAltFormula = this.daggerheartFlag.activeAltFormula;
|
||||
context.selectedFormula = this.daggerheartFlag.getActiveFormula(this.document.formula);
|
||||
context.results = this.getExtendedResults(context.results);
|
||||
break;
|
||||
case 'header':
|
||||
context.altFormula = this.daggerheartFlag.altFormula;
|
||||
context.usesAltFormula = Object.keys(this.daggerheartFlag.altFormula).length > 0;
|
||||
context.altFormulaOptions = {
|
||||
'': { name: this.daggerheartFlag.formulaName },
|
||||
...this.daggerheartFlag.altFormula
|
||||
};
|
||||
context.activeAltFormula = this.daggerheartFlag.activeAltFormula;
|
||||
break;
|
||||
case 'summary':
|
||||
context.systemFields = this.daggerheartFlag.schema.fields;
|
||||
context.altFormula = this.daggerheartFlag.altFormula;
|
||||
context.formulaName = this.daggerheartFlag.formulaName;
|
||||
break;
|
||||
case 'results':
|
||||
context.results = this.getExtendedResults(context.results);
|
||||
break;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
getExtendedResults(results) {
|
||||
const bodyDarkMode = document.body.classList.contains('theme-dark');
|
||||
const elementLightMode = this.element.classList.contains('theme-light');
|
||||
const elementDarkMode = this.element.classList.contains('theme-dark');
|
||||
const isDarkMode = elementDarkMode || (!elementLightMode && bodyDarkMode);
|
||||
|
||||
return results.map(x => ({
|
||||
...x,
|
||||
displayImg: isDarkMode && x.img === 'icons/svg/d20-black.svg' ? 'icons/svg/d20.svg' : x.img
|
||||
}));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Flag SystemData update methods */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async updateSystemField(event) {
|
||||
const { dataset, value } = event.target;
|
||||
await this.daggerheartFlag.updateSource({ [dataset.path]: value });
|
||||
this.render({ internalRefresh: true });
|
||||
}
|
||||
|
||||
getSystemFlagUpdate() {
|
||||
const deleteUpdate = Object.keys(this.document._source.flags.daggerheart?.altFormula ?? {}).reduce(
|
||||
(acc, formulaKey) => {
|
||||
if (!this.daggerheartFlag.altFormula[formulaKey]) acc.altFormula[`-=${formulaKey}`] = null;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ altFormula: {} }
|
||||
);
|
||||
|
||||
return { ['flags.daggerheart']: foundry.utils.mergeObject(this.daggerheartFlag.toObject(), deleteUpdate) };
|
||||
}
|
||||
|
||||
static async #addFormula() {
|
||||
await this.daggerheartFlag.updateSource({
|
||||
[`altFormula.${foundry.utils.randomID()}`]: game.system.api.data.DhRollTable.getDefaultFormula()
|
||||
});
|
||||
this.render({ internalRefresh: true });
|
||||
}
|
||||
|
||||
static async #removeFormula(_event, target) {
|
||||
await this.daggerheartFlag.updateSource({
|
||||
[`altFormula.-=${target.dataset.key}`]: null
|
||||
});
|
||||
this.render({ internalRefresh: true });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Extended RollTable methods */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Alternate between view and edit modes.
|
||||
* @this {RollTableSheet}
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #onChangeMode() {
|
||||
this.mode = this.isEditMode ? 'view' : 'edit';
|
||||
await this.document.update(this.getSystemFlagUpdate());
|
||||
await this.render({ internalRefresh: true });
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
async _processSubmitData(event, form, submitData, options) {
|
||||
/* RollTable sends an empty dummy event when swapping from view/edit first time */
|
||||
if (Object.keys(submitData).length) {
|
||||
if (!submitData.flags) submitData.flags = { daggerheart: {} };
|
||||
submitData.flags.daggerheart = this.getSystemFlagUpdate();
|
||||
}
|
||||
|
||||
super._processSubmitData(event, form, submitData, options);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
static async #onResetResults() {
|
||||
await this.document.update(this.getSystemFlagUpdate());
|
||||
await this.document.resetResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll and draw a TableResult.
|
||||
* @this {RollTableSheet}
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #onDrawResult(_event, button) {
|
||||
if (this.form) await this.submit({ operation: { render: false } });
|
||||
button.disabled = true;
|
||||
const table = this.document;
|
||||
|
||||
await this.document.update(this.getSystemFlagUpdate());
|
||||
|
||||
/* Sending in the currently selectd activeFormula to table.roll to use as the formula */
|
||||
const selectedFormula = this.daggerheartFlag.getActiveFormula(this.document.formula);
|
||||
const tableRoll = await table.roll({ selectedFormula });
|
||||
const draws = table.getResultsForRoll(tableRoll.roll.total);
|
||||
if (draws.length > 0) {
|
||||
if (game.settings.get('core', 'animateRollTable')) await this._animateRoll(draws);
|
||||
await table.draw(tableRoll);
|
||||
}
|
||||
|
||||
// Reenable the button if drawing with replacement since the draw won't trigger a sheet re-render
|
||||
if (table.replacement) button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -81,6 +81,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
|
||||
element.addEventListener('click', this.groupRollExpandSection)
|
||||
);
|
||||
html.querySelectorAll('.risk-it-all-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
||||
);
|
||||
};
|
||||
|
||||
setupHooks() {
|
||||
|
|
@ -94,15 +97,17 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
|
||||
/** Ensure the chat theme inherits the interface theme */
|
||||
_replaceHTML(result, content, options) {
|
||||
const themedElement = result.log?.querySelector(".chat-log");
|
||||
themedElement?.classList.remove("themed", "theme-light", "theme-dark");
|
||||
const themedElement = result.log?.querySelector('.chat-log');
|
||||
themedElement?.classList.remove('themed', 'theme-light', 'theme-dark');
|
||||
super._replaceHTML(result, content, options);
|
||||
}
|
||||
|
||||
/** Remove chat log theme from notifications area */
|
||||
async _onFirstRender(result, content) {
|
||||
await super._onFirstRender(result, content);
|
||||
document.querySelector("#chat-notifications .chat-log")?.classList.remove("themed", "theme-light", "theme-dark")
|
||||
document
|
||||
.querySelector('#chat-notifications .chat-log')
|
||||
?.classList.remove('themed', 'theme-light', 'theme-dark');
|
||||
}
|
||||
|
||||
async onRollSimple(event, message) {
|
||||
|
|
@ -383,4 +388,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
});
|
||||
event.target.closest('.group-roll-section').querySelector('.group-roll-content').classList.toggle('closed');
|
||||
}
|
||||
|
||||
async riskItAllClearStressAndHitPoints(event, data) {
|
||||
const resourceValue = event.target.dataset.resourceValue;
|
||||
const actor = game.actors.get(event.target.dataset.actorId);
|
||||
new game.system.api.applications.dialogs.RiskItAllDialog(actor, resourceValue).render({ force: true });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ export const defeatedConditions = () => {
|
|||
acc[key] = {
|
||||
...choice,
|
||||
img: defeated[`${choice.id}Icon`],
|
||||
description: `DAGGERHEART.CONFIG.Condition.${choice.id}.description`
|
||||
description: game.i18n.localize(`DAGGERHEART.CONFIG.Condition.${choice.id}.description`)
|
||||
};
|
||||
|
||||
return acc;
|
||||
|
|
@ -179,6 +179,10 @@ export const defeatedConditions = () => {
|
|||
};
|
||||
|
||||
export const defeatedConditionChoices = {
|
||||
deathMove: {
|
||||
id: 'deathMove',
|
||||
name: 'DAGGERHEART.CONFIG.Condition.deathMove.name'
|
||||
},
|
||||
defeated: {
|
||||
id: 'defeated',
|
||||
name: 'DAGGERHEART.CONFIG.Condition.defeated.name'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export { default as DhCombat } from './combat.mjs';
|
||||
export { default as DhCombatant } from './combatant.mjs';
|
||||
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
||||
export { default as DhRollTable } from './rollTable.mjs';
|
||||
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
||||
|
||||
export * as countdowns from './countdowns.mjs';
|
||||
|
|
|
|||
|
|
@ -377,14 +377,14 @@ export class ResourceUpdateMap extends Map {
|
|||
if (!resource.key) continue;
|
||||
|
||||
const existing = this.get(resource.key);
|
||||
if (existing) {
|
||||
if (!existing || resource.clear) {
|
||||
this.set(resource.key, resource);
|
||||
} else if (!existing?.clear) {
|
||||
this.set(resource.key, {
|
||||
...existing,
|
||||
value: existing.value + (resource.value ?? 0),
|
||||
total: existing.total + (resource.total ?? 0)
|
||||
});
|
||||
} else {
|
||||
this.set(resource.key, resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
|
|||
});
|
||||
|
||||
/* Common rules applying to Characters and Adversaries */
|
||||
export const commonActorRules = (extendedData = { damageReduction: {} }) => ({
|
||||
export const commonActorRules = (extendedData = { damageReduction: {}, attack: { damage: {} } }) => ({
|
||||
conditionImmunities: new fields.SchemaField({
|
||||
hidden: new fields.BooleanField({ initial: false }),
|
||||
restrained: new fields.BooleanField({ initial: false }),
|
||||
|
|
@ -41,7 +41,23 @@ export const commonActorRules = (extendedData = { damageReduction: {} }) => ({
|
|||
magical: new fields.NumberField({ initial: 0, min: 0 }),
|
||||
physical: new fields.NumberField({ initial: 0, min: 0 })
|
||||
}),
|
||||
...extendedData.damageReduction
|
||||
...(extendedData.damageReduction ?? {})
|
||||
}),
|
||||
attack: new fields.SchemaField({
|
||||
...extendedData.attack,
|
||||
damage: new fields.SchemaField({
|
||||
hpDamageMultiplier: new fields.NumberField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: 1
|
||||
}),
|
||||
hpDamageTakenMultiplier: new fields.NumberField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: 1
|
||||
}),
|
||||
...(extendedData.attack?.damage ?? {})
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,14 @@ export default class DhCharacter extends BaseDataActor {
|
|||
'DAGGERHEART.ACTORS.Character.maxHPBonus'
|
||||
),
|
||||
stress: resourceField(6, 0, 'DAGGERHEART.GENERAL.stress', true),
|
||||
hope: resourceField(6, 2, 'DAGGERHEART.GENERAL.hope')
|
||||
hope: new fields.SchemaField({
|
||||
value: new fields.NumberField({
|
||||
initial: 2,
|
||||
min: 0,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.hope'
|
||||
})
|
||||
})
|
||||
}),
|
||||
traits: new fields.SchemaField({
|
||||
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
|
||||
|
|
@ -78,12 +85,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
bags: new fields.NumberField({ initial: 0, integer: true }),
|
||||
chests: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
scars: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
description: new fields.StringField()
|
||||
})
|
||||
),
|
||||
scars: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.scars' }),
|
||||
biography: new fields.SchemaField({
|
||||
background: new fields.HTMLField(),
|
||||
connections: new fields.HTMLField(),
|
||||
|
|
@ -251,35 +253,35 @@ export default class DhCharacter extends BaseDataActor {
|
|||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
||||
}),
|
||||
disabledArmor: new fields.BooleanField({ intial: false })
|
||||
},
|
||||
attack: {
|
||||
damage: {
|
||||
diceIndex: new fields.NumberField({
|
||||
integer: true,
|
||||
min: 0,
|
||||
max: 5,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.dice.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.attack.damage.dice.hint'
|
||||
}),
|
||||
bonus: new fields.NumberField({
|
||||
required: true,
|
||||
initial: 0,
|
||||
min: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.bonus.label'
|
||||
})
|
||||
},
|
||||
roll: new fields.SchemaField({
|
||||
trait: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.ACTOR.abilities,
|
||||
nullable: true,
|
||||
initial: null,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.roll.trait.label'
|
||||
})
|
||||
})
|
||||
}
|
||||
}),
|
||||
attack: new fields.SchemaField({
|
||||
damage: new fields.SchemaField({
|
||||
diceIndex: new fields.NumberField({
|
||||
integer: true,
|
||||
min: 0,
|
||||
max: 5,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.dice.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.attack.damage.dice.hint'
|
||||
}),
|
||||
bonus: new fields.NumberField({
|
||||
required: true,
|
||||
initial: 0,
|
||||
min: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.bonus.label'
|
||||
})
|
||||
}),
|
||||
roll: new fields.SchemaField({
|
||||
trait: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.ACTOR.abilities,
|
||||
nullable: true,
|
||||
initial: null,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.roll.trait.label'
|
||||
})
|
||||
})
|
||||
}),
|
||||
dualityRoll: new fields.SchemaField({
|
||||
defaultHopeDice: new fields.NumberField({
|
||||
nullable: false,
|
||||
|
|
@ -301,6 +303,9 @@ export default class DhCharacter extends BaseDataActor {
|
|||
runeWard: new fields.BooleanField({ initial: false }),
|
||||
burden: new fields.SchemaField({
|
||||
ignore: new fields.BooleanField()
|
||||
}),
|
||||
roll: new fields.SchemaField({
|
||||
guaranteedCritical: new fields.BooleanField()
|
||||
})
|
||||
})
|
||||
};
|
||||
|
|
@ -363,7 +368,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
const modifiers = subClasses
|
||||
?.map(sc => ({ ...this.traits[sc.system.spellcastingTrait], key: sc.system.spellcastingTrait }))
|
||||
.filter(x => x);
|
||||
return modifiers.sort((a, b) => a.value - b.value)[0];
|
||||
return modifiers.sort((a, b) => (b.value ?? 0) - (a.value ?? 0))[0];
|
||||
}
|
||||
|
||||
get spellcastModifier() {
|
||||
|
|
@ -544,7 +549,18 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
|
||||
get deathMoveViable() {
|
||||
return this.resources.hitPoints.max > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.max;
|
||||
const { characterDefault } = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Automation
|
||||
).defeated;
|
||||
const deathMoveOutcomeStatuses = Object.keys(CONFIG.DH.GENERAL.defeatedConditionChoices).filter(
|
||||
key => key !== characterDefault
|
||||
);
|
||||
const deathMoveNotResolved = this.parent.statuses.every(status => !deathMoveOutcomeStatuses.includes(status));
|
||||
|
||||
const allHitPointsMarked =
|
||||
this.resources.hitPoints.max > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.max;
|
||||
return deathMoveNotResolved && allHitPointsMarked;
|
||||
}
|
||||
|
||||
get armorApplicableDamageTypes() {
|
||||
|
|
@ -642,8 +658,15 @@ export default class DhCharacter extends BaseDataActor {
|
|||
? armor.system.baseThresholds.severe + this.levelData.level.current
|
||||
: this.levelData.level.current * 2
|
||||
};
|
||||
this.resources.hope.max -= Object.keys(this.scars).length;
|
||||
|
||||
const globalHopeMax = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxHope;
|
||||
this.resources.hope.max = globalHopeMax - this.scars;
|
||||
this.resources.hitPoints.max += this.class.value?.system?.hitPoints ?? 0;
|
||||
|
||||
/* Companion Related Data */
|
||||
this.companionData = {
|
||||
levelupChoices: this.levelData.level.current - 1
|
||||
};
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
|
|
@ -659,6 +682,8 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.companion.system.attack.roll.bonus = this.traits.instinct.value;
|
||||
}
|
||||
|
||||
this.resources.hope.value = Math.min(baseHope, this.resources.hope.max);
|
||||
|
|
@ -699,6 +724,30 @@ export default class DhCharacter extends BaseDataActor {
|
|||
changes.system.experiences[experience].core = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scars can alter the amount of current hope */
|
||||
if (changes.system?.scars) {
|
||||
const diff = this.system.scars - changes.system.scars;
|
||||
const newHopeMax = this.system.resources.hope.max + diff;
|
||||
const newHopeValue = Math.min(newHopeMax, this.system.resources.hope.value);
|
||||
if (newHopeValue != this.system.resources.hope.value) {
|
||||
if (!changes.system.resources) changes.system.resources = { hope: { value: 0 } };
|
||||
changes.system.resources.hope = {
|
||||
...changes.system.resources.hope,
|
||||
value: changes.system.resources.hope.value + newHopeValue
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* Force companion data prep */
|
||||
if (this.companion) {
|
||||
if (
|
||||
changes.system?.levelData?.level?.current !== undefined &&
|
||||
changes.system.levelData.level.current !== this._source.levelData.level.current
|
||||
) {
|
||||
this.companion.update(this.companion.toObject(), { diff: false, recursive: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _preDelete() {
|
||||
|
|
@ -714,4 +763,11 @@ export default class DhCharacter extends BaseDataActor {
|
|||
t => !!t
|
||||
);
|
||||
}
|
||||
|
||||
static migrateData(source) {
|
||||
if (typeof source.scars === 'object') source.scars = 0;
|
||||
if (source.resources?.hope?.max) source.scars = Math.max(6 - source.resources.hope.max, 0);
|
||||
|
||||
return super.migrateData(source);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,11 @@ export default class DhCompanion extends BaseDataActor {
|
|||
get proficiency() {
|
||||
return this.partner?.system?.proficiency ?? 1;
|
||||
}
|
||||
|
||||
|
||||
get canLevelUp() {
|
||||
return this.levelupChoicesLeft > 0;
|
||||
}
|
||||
|
||||
isItemValid() {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -147,6 +151,17 @@ export default class DhCompanion extends BaseDataActor {
|
|||
}
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
/* Partner Related Setup */
|
||||
if (this.partner) {
|
||||
this.levelData.level.changed = this.partner.system.levelData.level.current;
|
||||
this.levelupChoicesLeft = Object.values(this.levelData.levelups).reduce((acc, curr) => {
|
||||
acc = Math.max(acc - curr.selections.length, 0);
|
||||
return acc;
|
||||
}, this.partner.system.companionData.levelupChoices);
|
||||
}
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, userId) {
|
||||
const allowed = await super._preUpdate(changes, options, userId);
|
||||
if (allowed === false) return;
|
||||
|
|
@ -162,6 +177,16 @@ export default class DhCompanion extends BaseDataActor {
|
|||
changes.system.experiences[experience].core = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Force partner data prep */
|
||||
if (this.partner) {
|
||||
if (
|
||||
changes.system?.levelData?.level?.current !== undefined &&
|
||||
changes.system.levelData.level.current !== this._source.levelData.level.current
|
||||
) {
|
||||
this.partner.update(this.partner.toObject(), { diff: false, recursive: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _preDelete() {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const config = {
|
|||
adversaryRoll: DHActorRoll,
|
||||
damageRoll: DHActorRoll,
|
||||
dualityRoll: DHActorRoll,
|
||||
fateRoll: DHActorRoll,
|
||||
groupRoll: DHGroupRoll,
|
||||
systemMessage: DHSystemMessage
|
||||
};
|
||||
|
|
|
|||
370
module/data/companionLevelup.mjs
Normal file
370
module/data/companionLevelup.mjs
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
import { abilities } from '../config/actorConfig.mjs';
|
||||
import { chunkify } from '../helpers/utils.mjs';
|
||||
import { LevelOptionType } from './levelTier.mjs';
|
||||
|
||||
export class DhCompanionLevelup extends foundry.abstract.DataModel {
|
||||
static initializeData(levelTierData, pcLevelData, origChoicesLeft) {
|
||||
let choicesLeft = origChoicesLeft;
|
||||
|
||||
const { current, changed } = pcLevelData.level;
|
||||
const bonusChoicesOnly = current === changed;
|
||||
const startLevel = bonusChoicesOnly ? current : current + 1;
|
||||
const endLevel = bonusChoicesOnly ? startLevel : changed;
|
||||
|
||||
const tiers = {};
|
||||
const levels = {};
|
||||
const tierKeys = Object.keys(levelTierData.tiers);
|
||||
tierKeys.forEach(key => {
|
||||
const tier = levelTierData.tiers[key];
|
||||
const belongingLevels = [];
|
||||
for (var i = tier.levels.start; i <= tier.levels.end; i++) {
|
||||
if (i <= endLevel) {
|
||||
const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {};
|
||||
const experiences = initialAchievements.experience
|
||||
? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => {
|
||||
acc[foundry.utils.randomID()] = {
|
||||
name: '',
|
||||
modifier: initialAchievements.experience.modifier
|
||||
};
|
||||
return acc;
|
||||
}, {})
|
||||
: {};
|
||||
|
||||
const currentChoices = pcLevelData.levelups[i]?.selections?.length;
|
||||
const maxSelections =
|
||||
i === endLevel
|
||||
? choicesLeft + (currentChoices ?? 0)
|
||||
: (currentChoices ?? tier.maxSelections[i]);
|
||||
if (!pcLevelData.levelups[i]) choicesLeft -= maxSelections;
|
||||
|
||||
levels[i] = DhLevelupLevel.initializeData(pcLevelData.levelups[i], maxSelections, {
|
||||
...initialAchievements,
|
||||
experiences,
|
||||
domainCards: {}
|
||||
});
|
||||
}
|
||||
|
||||
belongingLevels.push(i);
|
||||
}
|
||||
|
||||
/* Improve. Temporary handling for Companion new experiences */
|
||||
Object.keys(tier.extraAchievements ?? {}).forEach(key => {
|
||||
const level = Number(key);
|
||||
if (level >= startLevel && level <= endLevel) {
|
||||
const levelExtras = tier.extraAchievements[level];
|
||||
if (levelExtras.experience) {
|
||||
levels[level].achievements.experiences[foundry.utils.randomID()] = {
|
||||
name: '',
|
||||
modifier: levelExtras.experience.modifier
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tiers[key] = {
|
||||
name: tier.name,
|
||||
belongingLevels: belongingLevels,
|
||||
options: Object.keys(tier.options).reduce((acc, key) => {
|
||||
acc[key] = tier.options[key].toObject?.() ?? tier.options[key];
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
tiers,
|
||||
levels,
|
||||
startLevel,
|
||||
currentLevel: startLevel,
|
||||
endLevel
|
||||
};
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
tiers: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
belongingLevels: new fields.ArrayField(new fields.NumberField({ required: true, integer: true })),
|
||||
options: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
label: new fields.StringField({ required: true }),
|
||||
checkboxSelections: new fields.NumberField({ required: true, integer: true }),
|
||||
minCost: new fields.NumberField({ required: true, integer: true }),
|
||||
type: new fields.StringField({ required: true, choices: LevelOptionType }),
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
amount: new fields.NumberField({ integer: true })
|
||||
})
|
||||
)
|
||||
})
|
||||
),
|
||||
levels: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupLevel)),
|
||||
startLevel: new fields.NumberField({ required: true, integer: true }),
|
||||
currentLevel: new fields.NumberField({ required: true, integer: true }),
|
||||
endLevel: new fields.NumberField({ required: true, integer: true })
|
||||
};
|
||||
}
|
||||
|
||||
#levelFinished(levelKey) {
|
||||
const allSelectionsMade = this.levels[levelKey].nrSelections.available === 0;
|
||||
const allChoicesMade = Object.keys(this.levels[levelKey].choices).every(choiceKey => {
|
||||
const choice = this.levels[levelKey].choices[choiceKey];
|
||||
return Object.values(choice).every(checkbox => {
|
||||
switch (choiceKey) {
|
||||
case 'trait':
|
||||
case 'experience':
|
||||
case 'domainCard':
|
||||
case 'subclass':
|
||||
case 'vicious':
|
||||
return checkbox.data.length === (checkbox.amount ?? 1);
|
||||
case 'multiclass':
|
||||
const classSelected = checkbox.data.length === 1;
|
||||
const domainSelected = checkbox.secondaryData.domain;
|
||||
const subclassSelected = checkbox.secondaryData.subclass;
|
||||
return classSelected && domainSelected && subclassSelected;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
const experiencesSelected = !this.levels[levelKey].achievements.experiences
|
||||
? true
|
||||
: Object.values(this.levels[levelKey].achievements.experiences).every(exp => exp.name);
|
||||
const domainCardsSelected = Object.values(this.levels[levelKey].achievements.domainCards)
|
||||
.filter(x => x.level <= this.endLevel)
|
||||
.every(card => card.uuid);
|
||||
const allAchievementsSelected = experiencesSelected && domainCardsSelected;
|
||||
|
||||
return allSelectionsMade && allChoicesMade && allAchievementsSelected;
|
||||
}
|
||||
|
||||
get currentLevelFinished() {
|
||||
return this.#levelFinished(this.currentLevel);
|
||||
}
|
||||
|
||||
get allLevelsFinished() {
|
||||
return Object.keys(this.levels)
|
||||
.filter(level => Number(level) >= this.startLevel)
|
||||
.every(this.#levelFinished.bind(this));
|
||||
}
|
||||
|
||||
get unmarkedTraits() {
|
||||
const possibleLevels = Object.values(this.tiers).reduce((acc, tier) => {
|
||||
if (tier.belongingLevels.includes(this.currentLevel)) acc = tier.belongingLevels;
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return Object.keys(this.levels)
|
||||
.filter(key => possibleLevels.some(x => x === Number(key)))
|
||||
.reduce(
|
||||
(acc, levelKey) => {
|
||||
const level = this.levels[levelKey];
|
||||
Object.values(level.choices).forEach(choice =>
|
||||
Object.values(choice).forEach(checkbox => {
|
||||
if (
|
||||
checkbox.type === 'trait' &&
|
||||
checkbox.data.length > 0 &&
|
||||
Number(levelKey) !== this.currentLevel
|
||||
) {
|
||||
checkbox.data.forEach(data => delete acc[data]);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ ...abilities }
|
||||
);
|
||||
}
|
||||
|
||||
get classUpgradeChoices() {
|
||||
let subclasses = [];
|
||||
let multiclass = null;
|
||||
Object.keys(this.levels).forEach(levelKey => {
|
||||
const level = this.levels[levelKey];
|
||||
Object.values(level.choices).forEach(choice => {
|
||||
Object.values(choice).forEach(checkbox => {
|
||||
if (checkbox.type === 'multiclass') {
|
||||
multiclass = {
|
||||
class: checkbox.data.length > 0 ? checkbox.data[0] : null,
|
||||
domain: checkbox.secondaryData.domain ?? null,
|
||||
subclass: checkbox.secondaryData.subclass ?? null,
|
||||
tier: checkbox.tier,
|
||||
level: levelKey
|
||||
};
|
||||
}
|
||||
if (checkbox.type === 'subclass') {
|
||||
subclasses.push({
|
||||
tier: checkbox.tier,
|
||||
level: levelKey
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
return { subclasses, multiclass };
|
||||
}
|
||||
|
||||
get tiersForRendering() {
|
||||
const tierKeys = Object.keys(this.tiers);
|
||||
const selections = Object.keys(this.levels).reduce(
|
||||
(acc, key) => {
|
||||
const level = this.levels[key];
|
||||
Object.keys(level.choices).forEach(optionKey => {
|
||||
const choice = level.choices[optionKey];
|
||||
Object.keys(choice).forEach(checkboxNr => {
|
||||
const checkbox = choice[checkboxNr];
|
||||
if (!acc[checkbox.tier][optionKey]) acc[checkbox.tier][optionKey] = {};
|
||||
Object.keys(choice).forEach(checkboxNr => {
|
||||
acc[checkbox.tier][optionKey][checkboxNr] = { ...checkbox, level: Number(key) };
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
tierKeys.reduce((acc, key) => {
|
||||
acc[key] = {};
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
const { multiclass, subclasses } = this.classUpgradeChoices;
|
||||
return tierKeys.map((tierKey, tierIndex) => {
|
||||
const tier = this.tiers[tierKey];
|
||||
const multiclassInTier = multiclass?.tier === Number(tierKey);
|
||||
const subclassInTier = subclasses.some(x => x.tier === Number(tierKey));
|
||||
|
||||
return {
|
||||
name: game.i18n.localize(tier.name),
|
||||
active: this.currentLevel >= Math.min(...tier.belongingLevels),
|
||||
groups: Object.keys(tier.options).map(optionKey => {
|
||||
const option = tier.options[optionKey];
|
||||
|
||||
const checkboxes = [...Array(option.checkboxSelections).keys()].flatMap(index => {
|
||||
const checkboxNr = index + 1;
|
||||
const checkboxData = selections[tierKey]?.[optionKey]?.[checkboxNr];
|
||||
const checkbox = { ...option, checkboxNr, tier: tierKey };
|
||||
|
||||
if (checkboxData) {
|
||||
checkbox.level = checkboxData.level;
|
||||
checkbox.selected = true;
|
||||
checkbox.disabled = checkbox.level !== this.currentLevel;
|
||||
}
|
||||
|
||||
if (optionKey === 'multiclass') {
|
||||
if ((multiclass && !multiclassInTier) || subclassInTier) {
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (optionKey === 'subclass' && multiclassInTier) {
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
|
||||
return checkbox;
|
||||
});
|
||||
|
||||
let label = game.i18n.localize(option.label);
|
||||
if (optionKey === 'domainCard') {
|
||||
const maxLevel = tier.belongingLevels[tier.belongingLevels.length - 1];
|
||||
label = game.i18n.format(option.label, { maxLevel });
|
||||
}
|
||||
|
||||
return {
|
||||
label: label,
|
||||
checkboxGroups: chunkify(checkboxes, option.minCost, chunkedBoxes => {
|
||||
const anySelected = chunkedBoxes.some(x => x.selected);
|
||||
const anyDisabled = chunkedBoxes.some(x => x.disabled);
|
||||
return {
|
||||
multi: option.minCost > 1,
|
||||
checkboxes: chunkedBoxes.map(x => ({
|
||||
...x,
|
||||
selected: anySelected,
|
||||
disabled: anyDisabled
|
||||
}))
|
||||
};
|
||||
})
|
||||
};
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class DhLevelupLevel extends foundry.abstract.DataModel {
|
||||
static initializeData(levelData = { selections: [] }, maxSelections, achievements) {
|
||||
return {
|
||||
maxSelections: maxSelections,
|
||||
achievements: {
|
||||
experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {},
|
||||
domainCards: levelData.achievements?.domainCards
|
||||
? levelData.achievements.domainCards.reduce((acc, card, index) => {
|
||||
acc[index] = { ...card };
|
||||
return acc;
|
||||
}, {})
|
||||
: (achievements.domainCards ?? {}),
|
||||
proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null
|
||||
},
|
||||
choices: levelData.selections.reduce((acc, data) => {
|
||||
if (!acc[data.optionKey]) acc[data.optionKey] = {};
|
||||
acc[data.optionKey][data.checkboxNr] = { ...data };
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
maxSelections: new fields.NumberField({ required: true, integer: true }),
|
||||
achievements: new fields.SchemaField({
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
modifier: new fields.NumberField({ required: true, integer: true })
|
||||
})
|
||||
),
|
||||
domainCards: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
uuid: new fields.StringField({ required: true, nullable: true, initial: null }),
|
||||
itemUuid: new fields.StringField({ required: true }),
|
||||
level: new fields.NumberField({ required: true, integer: true })
|
||||
})
|
||||
),
|
||||
proficiency: new fields.NumberField({ integer: true })
|
||||
}),
|
||||
choices: new fields.TypedObjectField(
|
||||
new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
tier: new fields.NumberField({ required: true, integer: true }),
|
||||
minCost: new fields.NumberField({ required: true, integer: true }),
|
||||
amount: new fields.NumberField({ integer: true }),
|
||||
value: new fields.StringField(),
|
||||
data: new fields.ArrayField(new fields.StringField()),
|
||||
secondaryData: new fields.TypedObjectField(new fields.StringField()),
|
||||
type: new fields.StringField({ required: true })
|
||||
})
|
||||
)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
get nrSelections() {
|
||||
const selections = Object.keys(this.choices).reduce((acc, choiceKey) => {
|
||||
const choice = this.choices[choiceKey];
|
||||
acc += Object.values(choice).reduce((acc, x) => acc + x.minCost, 0);
|
||||
|
||||
return acc;
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
selections: selections,
|
||||
available: this.maxSelections - selections
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -105,12 +105,22 @@ export default class DamageField extends fields.SchemaField {
|
|||
damagePromises.push(
|
||||
actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates }))
|
||||
);
|
||||
else
|
||||
else {
|
||||
const configDamage = foundry.utils.deepClone(config.damage);
|
||||
const hpDamageMultiplier = config.actionActor?.system.rules.attack.damage.hpDamageMultiplier ?? 1;
|
||||
const hpDamageTakenMultiplier = actor.system.rules.attack.damage.hpDamageTakenMultiplier;
|
||||
if (configDamage.hitPoints) {
|
||||
for (const part of configDamage.hitPoints.parts) {
|
||||
part.total = Math.ceil(part.total * hpDamageMultiplier * hpDamageTakenMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
damagePromises.push(
|
||||
actor
|
||||
.takeDamage(config.damage, config.isDirect)
|
||||
.takeDamage(configDamage, config.isDirect)
|
||||
.then(updates => targetDamage.push({ token, updates }))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(damagePromises).then(async _ => {
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
return await foundry.applications.ux.TextEditor.implementation.enrichHTML(fullDescription, {
|
||||
relativeTo: this,
|
||||
rollData: this.getRollData(),
|
||||
secrets: this.isOwner
|
||||
secrets: this.parent.isOwner
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export default class RegisteredTriggers extends Map {
|
|||
}
|
||||
|
||||
registerItemTriggers(item, registerOverride) {
|
||||
if (!item.actor || !item._stats.createdTime) return;
|
||||
for (const action of item.system.actions ?? []) {
|
||||
if (!action.actor) continue;
|
||||
|
||||
|
|
@ -71,10 +72,21 @@ export default class RegisteredTriggers extends Map {
|
|||
}
|
||||
}
|
||||
|
||||
unregisterSceneEnvironmentTriggers(flagSystemData) {
|
||||
const sceneData = new game.system.api.data.scenes.DHScene(flagSystemData);
|
||||
for (const environment of sceneData.sceneEnvironments) {
|
||||
if (environment.pack) continue;
|
||||
this.unregisterItemTriggers(environment.system.features);
|
||||
}
|
||||
}
|
||||
|
||||
unregisterSceneTriggers(scene) {
|
||||
this.unregisterSceneEnvironmentTriggers(scene.flags.daggerheart);
|
||||
|
||||
for (const triggerKey of Object.keys(CONFIG.DH.TRIGGER.triggers)) {
|
||||
const existingTrigger = this.get(triggerKey);
|
||||
if (!existingTrigger) continue;
|
||||
|
||||
const filtered = new Map();
|
||||
for (const [uuid, data] of existingTrigger.entries()) {
|
||||
if (!uuid.startsWith(scene.uuid)) filtered.set(uuid, data);
|
||||
|
|
@ -83,14 +95,17 @@ export default class RegisteredTriggers extends Map {
|
|||
}
|
||||
}
|
||||
|
||||
registerSceneEnvironmentTriggers(flagSystemData) {
|
||||
const sceneData = new game.system.api.data.scenes.DHScene(flagSystemData);
|
||||
for (const environment of sceneData.sceneEnvironments) {
|
||||
for (const feature of environment.system.features) {
|
||||
if (feature) this.registerItemTriggers(feature, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerSceneTriggers(scene) {
|
||||
/* TODO: Finish sceneEnvironment registration and unreg */
|
||||
// const systemData = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart);
|
||||
// for (const environment of systemData.sceneEnvironments) {
|
||||
// for (const feature of environment.system.features) {
|
||||
// if(feature) this.registerItemTriggers(feature, true);
|
||||
// }
|
||||
// }
|
||||
this.registerSceneEnvironmentTriggers(scene.flags.daggerheart);
|
||||
|
||||
for (const actor of scene.tokens.filter(x => x.actor).map(x => x.actor)) {
|
||||
if (actor.prototypeToken.actorLink) continue;
|
||||
|
|
@ -107,13 +122,11 @@ export default class RegisteredTriggers extends Map {
|
|||
if (!triggerSettings.enabled) return updates;
|
||||
|
||||
const dualityTrigger = this.get(trigger);
|
||||
if (dualityTrigger) {
|
||||
const tokenBoundActors = ['adversary', 'environment'];
|
||||
const triggerActors = ['character', ...tokenBoundActors];
|
||||
if (dualityTrigger?.size) {
|
||||
const triggerActors = ['character', 'adversary', 'environment'];
|
||||
for (let [itemUuid, { actor: actorUuid, triggeringActorType, commands }] of dualityTrigger.entries()) {
|
||||
const actor = await foundry.utils.fromUuid(actorUuid);
|
||||
if (!actor || !triggerActors.includes(actor.type)) continue;
|
||||
if (tokenBoundActors.includes(actor.type) && !actor.getActiveTokens().length) continue;
|
||||
|
||||
const triggerData = CONFIG.DH.TRIGGER.triggers[trigger];
|
||||
if (triggerData.usesActor && triggeringActorType !== 'any') {
|
||||
|
|
|
|||
38
module/data/rollTable.mjs
Normal file
38
module/data/rollTable.mjs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import FormulaField from './fields/formulaField.mjs';
|
||||
|
||||
//Extra definitions for RollTable
|
||||
export default class DhRollTable extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
formulaName: new fields.StringField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: 'Roll Formula',
|
||||
label: 'DAGGERHEART.ROLLTABLES.FIELDS.formulaName.label'
|
||||
}),
|
||||
altFormula: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: 'Roll Formula',
|
||||
label: 'DAGGERHEART.ROLLTABLES.FIELDS.formulaName.label'
|
||||
}),
|
||||
formula: new FormulaField({ label: 'Formula Roll', initial: '1d20' })
|
||||
})
|
||||
),
|
||||
activeAltFormula: new fields.StringField({ nullable: true, initial: null })
|
||||
};
|
||||
}
|
||||
|
||||
getActiveFormula(baseFormula) {
|
||||
return this.activeAltFormula ? (this.altFormula[this.activeAltFormula]?.formula ?? baseFormula) : baseFormula;
|
||||
}
|
||||
|
||||
static getDefaultFormula = () => ({
|
||||
name: game.i18n.localize('Roll Formula'),
|
||||
formula: '1d20'
|
||||
});
|
||||
}
|
||||
|
|
@ -55,15 +55,10 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
initial: true,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.resourceScrollTexts.label'
|
||||
}),
|
||||
playerCanEditSheet: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.playerCanEditSheet.label'
|
||||
}),
|
||||
defeated: new fields.SchemaField({
|
||||
enabled: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.enabled.label'
|
||||
}),
|
||||
overlay: new fields.BooleanField({
|
||||
|
|
@ -74,7 +69,7 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
characterDefault: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.defeatedConditionChoices,
|
||||
initial: CONFIG.DH.GENERAL.defeatedConditionChoices.unconscious.id,
|
||||
initial: CONFIG.DH.GENERAL.defeatedConditionChoices.deathMove.id,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.characterDefault.label'
|
||||
}),
|
||||
adversaryDefault: new fields.StringField({
|
||||
|
|
@ -89,23 +84,29 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
initial: CONFIG.DH.GENERAL.defeatedConditionChoices.defeated.id,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.companionDefault.label'
|
||||
}),
|
||||
deathMoveIcon: new fields.FilePathField({
|
||||
initial: 'icons/magic/life/heart-cross-purple-orange.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.deathMove.label'
|
||||
}),
|
||||
deadIcon: new fields.FilePathField({
|
||||
initial: 'icons/magic/death/grave-tombstone-glow-teal.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false,
|
||||
label: 'Dead'
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.dead.label'
|
||||
}),
|
||||
defeatedIcon: new fields.FilePathField({
|
||||
initial: 'icons/magic/control/fear-fright-mask-orange.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false,
|
||||
label: 'Defeated'
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.defeated.label'
|
||||
}),
|
||||
unconsciousIcon: new fields.FilePathField({
|
||||
initial: 'icons/magic/control/sleep-bubble-purple.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false,
|
||||
label: 'Unconcious'
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.unconscious.label'
|
||||
})
|
||||
}),
|
||||
roll: new fields.SchemaField({
|
||||
|
|
|
|||
|
|
@ -23,6 +23,13 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
initial: 12,
|
||||
label: 'DAGGERHEART.SETTINGS.Homebrew.FIELDS.maxFear.label'
|
||||
}),
|
||||
maxHope: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 0,
|
||||
initial: 6,
|
||||
label: 'DAGGERHEART.SETTINGS.Homebrew.FIELDS.maxHope.label'
|
||||
}),
|
||||
maxLoadout: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@ export { default as D20Roll } from './d20Roll.mjs';
|
|||
export { default as DamageRoll } from './damageRoll.mjs';
|
||||
export { default as DHRoll } from './dhRoll.mjs';
|
||||
export { default as DualityRoll } from './dualityRoll.mjs';
|
||||
export { default as FateRoll } from './fateRoll.mjs';
|
||||
|
|
|
|||
|
|
@ -99,11 +99,14 @@ export default class D20Roll extends DHRoll {
|
|||
|
||||
this.options.roll.modifiers = this.applyBaseBonus();
|
||||
|
||||
const actorExperiences = this.options.roll.companionRoll
|
||||
? (this.options.data?.companion?.system.experiences ?? {})
|
||||
: (this.options.data.system?.experiences ?? {});
|
||||
this.options.experiences?.forEach(m => {
|
||||
if (this.options.data.system?.experiences?.[m])
|
||||
if (actorExperiences[m])
|
||||
this.options.roll.modifiers.push({
|
||||
label: this.options.data.system.experiences[m].name,
|
||||
value: this.options.data.system.experiences[m].value
|
||||
label: actorExperiences[m].name,
|
||||
value: actorExperiences[m].value
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export default class DualityRoll extends D20Roll {
|
|||
constructor(formula, data = {}, options = {}) {
|
||||
super(formula, data, options);
|
||||
this.rallyChoices = this.setRallyChoices();
|
||||
this.guaranteedCritical = options.guaranteedCritical;
|
||||
}
|
||||
|
||||
static messageType = 'dualityRoll';
|
||||
|
|
@ -25,29 +26,23 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
get dHope() {
|
||||
// if ( !(this.terms[0] instanceof foundry.dice.terms.Die) ) return;
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
return this.dice[0];
|
||||
// return this.#hopeDice;
|
||||
}
|
||||
|
||||
set dHope(faces) {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.terms[0].faces = this.getFaces(faces);
|
||||
// this.#hopeDice = `d${face}`;
|
||||
this.dice[0].faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get dFear() {
|
||||
// if ( !(this.terms[1] instanceof foundry.dice.terms.Die) ) return;
|
||||
if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
return this.dice[1];
|
||||
// return this.#fearDice;
|
||||
}
|
||||
|
||||
set dFear(faces) {
|
||||
if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[1].faces = this.getFaces(faces);
|
||||
// this.#fearDice = `d${face}`;
|
||||
}
|
||||
|
||||
get dAdvantage() {
|
||||
|
|
@ -90,26 +85,29 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
get isCritical() {
|
||||
if (this.guaranteedCritical) return true;
|
||||
if (!this.dHope._evaluated || !this.dFear._evaluated) return;
|
||||
return this.dHope.total === this.dFear.total;
|
||||
}
|
||||
|
||||
get withHope() {
|
||||
if (!this._evaluated) return;
|
||||
if (!this._evaluated || this.guaranteedCritical) return;
|
||||
return this.dHope.total > this.dFear.total;
|
||||
}
|
||||
|
||||
get withFear() {
|
||||
if (!this._evaluated) return;
|
||||
if (!this._evaluated || this.guaranteedCritical) return;
|
||||
return this.dHope.total < this.dFear.total;
|
||||
}
|
||||
|
||||
get totalLabel() {
|
||||
const label = this.withHope
|
||||
? 'DAGGERHEART.GENERAL.hope'
|
||||
: this.withFear
|
||||
? 'DAGGERHEART.GENERAL.fear'
|
||||
: 'DAGGERHEART.GENERAL.criticalSuccess';
|
||||
const label = this.guaranteedCritical
|
||||
? 'DAGGERHEART.GENERAL.guaranteedCriticalSuccess'
|
||||
: this.isCritical
|
||||
? 'DAGGERHEART.GENERAL.criticalSuccess'
|
||||
: this.withHope
|
||||
? 'DAGGERHEART.GENERAL.hope'
|
||||
: 'DAGGERHEART.GENERAL.fear';
|
||||
|
||||
return game.i18n.localize(label);
|
||||
}
|
||||
|
|
@ -178,6 +176,21 @@ export default class DualityRoll extends D20Roll {
|
|||
return modifiers;
|
||||
}
|
||||
|
||||
static async buildConfigure(config = {}, message = {}) {
|
||||
config.dialog ??= {};
|
||||
config.guaranteedCritical = config.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||
const change = c.changes.find(ch => ch.key === 'system.rules.roll.guaranteedCritical');
|
||||
if (change) a = true;
|
||||
return a;
|
||||
}, false);
|
||||
|
||||
if (config.guaranteedCritical) {
|
||||
config.dialog.configure = false;
|
||||
}
|
||||
|
||||
return super.buildConfigure(config, message);
|
||||
}
|
||||
|
||||
getActionChangeKeys() {
|
||||
const changeKeys = new Set([`system.bonuses.roll.${this.options.actionType}`]);
|
||||
|
||||
|
|
@ -223,7 +236,7 @@ export default class DualityRoll extends D20Roll {
|
|||
|
||||
data.hope = {
|
||||
dice: roll.dHope.denomination,
|
||||
value: roll.dHope.total,
|
||||
value: this.guaranteedCritical ? 0 : roll.dHope.total,
|
||||
rerolled: {
|
||||
any: roll.dHope.results.some(x => x.rerolled),
|
||||
rerolls: roll.dHope.results.filter(x => x.rerolled)
|
||||
|
|
@ -231,7 +244,7 @@ export default class DualityRoll extends D20Roll {
|
|||
};
|
||||
data.fear = {
|
||||
dice: roll.dFear.denomination,
|
||||
value: roll.dFear.total,
|
||||
value: this.guaranteedCritical ? 0 : roll.dFear.total,
|
||||
rerolled: {
|
||||
any: roll.dFear.results.some(x => x.rerolled),
|
||||
rerolls: roll.dFear.results.filter(x => x.rerolled)
|
||||
|
|
@ -243,7 +256,7 @@ export default class DualityRoll extends D20Roll {
|
|||
};
|
||||
data.result = {
|
||||
duality: roll.withHope ? 1 : roll.withFear ? -1 : 0,
|
||||
total: roll.dHope.total + roll.dFear.total,
|
||||
total: this.guaranteedCritical ? 0 : roll.dHope.total + roll.dFear.total,
|
||||
label: roll.totalLabel
|
||||
};
|
||||
|
||||
|
|
@ -261,7 +274,7 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
static async handleTriggers(roll, config) {
|
||||
if (!config.source?.actor) return;
|
||||
if (!config.source?.actor || config.skips?.triggers) return;
|
||||
|
||||
const updates = [];
|
||||
const dualityUpdates = await game.system.registeredTriggers.runTrigger(
|
||||
|
|
|
|||
85
module/dice/fateRoll.mjs
Normal file
85
module/dice/fateRoll.mjs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||
import D20Roll from './d20Roll.mjs';
|
||||
import { setDiceSoNiceForHopeFateRoll, setDiceSoNiceForFearFateRoll } from '../helpers/utils.mjs';
|
||||
|
||||
export default class FateRoll extends D20Roll {
|
||||
constructor(formula, data = {}, options = {}) {
|
||||
super(formula, data, options);
|
||||
}
|
||||
|
||||
static messageType = 'fateRoll';
|
||||
|
||||
static DefaultDialog = D20RollDialog;
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize(`DAGGERHEART.GENERAL.fateRoll`);
|
||||
}
|
||||
|
||||
get dHope() {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
return this.dice[0];
|
||||
}
|
||||
|
||||
set dHope(faces) {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[0].faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get dFear() {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
return this.dice[0];
|
||||
}
|
||||
|
||||
set dFear(faces) {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[0].faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get isCritical() {
|
||||
return false;
|
||||
}
|
||||
|
||||
get fateDie() {
|
||||
return this.data.fateType;
|
||||
}
|
||||
|
||||
static getHooks(hooks) {
|
||||
return [...(hooks ?? []), 'Fate'];
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
static fromData(data) {
|
||||
data.terms[0].class = foundry.dice.terms.Die.name;
|
||||
return super.fromData(data);
|
||||
}
|
||||
|
||||
createBaseDice() {
|
||||
if (this.dice[0] instanceof foundry.dice.terms.Die) {
|
||||
this.terms = [this.terms[0]];
|
||||
return;
|
||||
}
|
||||
this.terms[0] = new foundry.dice.terms.Die({ faces: 12 });
|
||||
}
|
||||
|
||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||
await super.buildEvaluate(roll, config, message);
|
||||
|
||||
if (roll.fateDie === 'Hope') {
|
||||
await setDiceSoNiceForHopeFateRoll(roll, config.roll.fate.dice);
|
||||
} else {
|
||||
await setDiceSoNiceForFearFateRoll(roll, config.roll.fate.dice);
|
||||
}
|
||||
}
|
||||
|
||||
static postEvaluate(roll, config = {}) {
|
||||
const data = super.postEvaluate(roll, config);
|
||||
|
||||
data.fate = {
|
||||
dice: roll.fateDie === 'Hope' ? roll.dHope.denomination : roll.dFear.denomination,
|
||||
value: roll.fateDie === 'Hope' ? roll.dHope.total : roll.dFear.total,
|
||||
fateDie: roll.fateDie
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ export { default as DhpCombat } from './combat.mjs';
|
|||
export { default as DHCombatant } from './combatant.mjs';
|
||||
export { default as DhActiveEffect } from './activeEffect.mjs';
|
||||
export { default as DhChatMessage } from './chatMessage.mjs';
|
||||
export { default as DhRollTable } from './rollTable.mjs';
|
||||
export { default as DhScene } from './scene.mjs';
|
||||
export { default as DhToken } from './token.mjs';
|
||||
export { default as DhTooltipManager } from './tooltipManager.mjs';
|
||||
|
|
|
|||
|
|
@ -241,6 +241,11 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (this.system.companion) {
|
||||
this.system.companion.updateLevel(usedLevel);
|
||||
}
|
||||
|
||||
this.sheet.render();
|
||||
}
|
||||
}
|
||||
|
|
@ -764,16 +769,24 @@ export default class DhpActor extends Actor {
|
|||
};
|
||||
}
|
||||
} else {
|
||||
const valueFunc = (base, resource, baseMax) => {
|
||||
if (resource.clear) return baseMax && base.inverted ? baseMax : 0;
|
||||
|
||||
return (base.value ?? base) + resource.value;
|
||||
};
|
||||
switch (r.key) {
|
||||
case 'fear':
|
||||
ui.resources.updateFear(
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear) + r.value
|
||||
valueFunc(
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||
r
|
||||
)
|
||||
);
|
||||
break;
|
||||
case 'armor':
|
||||
if (this.system.armor?.system?.marks) {
|
||||
updates.armor.resources['system.marks.value'] = Math.max(
|
||||
Math.min(this.system.armor.system.marks.value + r.value, this.system.armorScore),
|
||||
Math.min(valueFunc(this.system.armor.system.marks, r), this.system.armorScore),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
|
@ -782,7 +795,7 @@ export default class DhpActor extends Actor {
|
|||
if (this.system.resources?.[r.key]) {
|
||||
updates.actor.resources[`system.resources.${r.key}.value`] = Math.max(
|
||||
Math.min(
|
||||
this.system.resources[r.key].value + r.value,
|
||||
valueFunc(this.system.resources[r.key], r, this.system.resources[r.key].max),
|
||||
this.system.resources[r.key].max
|
||||
),
|
||||
0
|
||||
|
|
@ -841,8 +854,8 @@ export default class DhpActor extends Actor {
|
|||
|
||||
async toggleDefeated(defeatedState) {
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).defeated;
|
||||
const { unconscious, defeated, dead } = CONFIG.DH.GENERAL.conditions();
|
||||
const defeatedConditions = new Set([unconscious.id, defeated.id, dead.id]);
|
||||
const { deathMove, unconscious, defeated, dead } = CONFIG.DH.GENERAL.conditions();
|
||||
const defeatedConditions = new Set([deathMove.id, unconscious.id, defeated.id, dead.id]);
|
||||
if (!defeatedState) {
|
||||
for (let defeatedId of defeatedConditions) {
|
||||
await this.toggleStatusEffect(defeatedId, { overlay: settings.overlay, active: defeatedState });
|
||||
|
|
@ -856,6 +869,18 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
}
|
||||
|
||||
async setDeathMoveDefeated(defeatedIconId) {
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).defeated;
|
||||
const actorDefault = settings[`${this.type}Default`];
|
||||
if (!settings.enabled || !settings.enabled || !actorDefault || actorDefault === defeatedIconId) return;
|
||||
|
||||
for (let defeatedId of Object.keys(CONFIG.DH.GENERAL.defeatedConditionChoices)) {
|
||||
await this.toggleStatusEffect(defeatedId, { overlay: settings.overlay, active: false });
|
||||
}
|
||||
|
||||
if (defeatedIconId) await this.toggleStatusEffect(defeatedIconId, { overlay: settings.overlay, active: true });
|
||||
}
|
||||
|
||||
queueScrollText(scrollingTextData) {
|
||||
this.#scrollTextQueue.push(...scrollingTextData.map(data => () => createScrollText(this, data)));
|
||||
if (!this.#scrollTextInterval) {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,15 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (this.type === 'fateRoll') {
|
||||
html.classList.add('fate');
|
||||
if (this.system.roll?.fate.fateDie == 'Hope') {
|
||||
html.classList.add('hope');
|
||||
}
|
||||
if (this.system.roll?.fate.fateDie == 'Fear') {
|
||||
html.classList.add('fear');
|
||||
}
|
||||
}
|
||||
|
||||
const autoExpandRoll = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
|
|
|
|||
1
module/documents/collections/_module.mjs
Normal file
1
module/documents/collections/_module.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as DhActorCollection } from './actorCollection.mjs';
|
||||
14
module/documents/collections/actorCollection.mjs
Normal file
14
module/documents/collections/actorCollection.mjs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
export default class DhActorCollection extends foundry.documents.collections.Actors {
|
||||
/** Ensure companions are initialized after all other subtypes. */
|
||||
_initialize() {
|
||||
super._initialize();
|
||||
const companions = [];
|
||||
for (const actor of this.values()) {
|
||||
if (actor.type === 'companion') companions.push(actor);
|
||||
}
|
||||
for (const actor of companions) {
|
||||
this.delete(actor.id);
|
||||
this.set(actor.id, actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
122
module/documents/rollTable.mjs
Normal file
122
module/documents/rollTable.mjs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
export default class DhRollTable extends foundry.documents.RollTable {
|
||||
async roll({ selectedFormula, roll, recursive = true, _depth = 0 } = {}) {
|
||||
// Prevent excessive recursion
|
||||
if (_depth > 5) {
|
||||
throw new Error(`Maximum recursion depth exceeded when attempting to draw from RollTable ${this.id}`);
|
||||
}
|
||||
|
||||
const formula = selectedFormula ?? this.formula;
|
||||
|
||||
// If there is no formula, automatically calculate an even distribution
|
||||
if (!this.formula) {
|
||||
await this.normalize();
|
||||
}
|
||||
|
||||
// Reference the provided roll formula
|
||||
roll = roll instanceof Roll ? roll : Roll.create(formula);
|
||||
let results = [];
|
||||
|
||||
// Ensure that at least one non-drawn result remains
|
||||
const available = this.results.filter(r => !r.drawn);
|
||||
if (!available.length) {
|
||||
ui.notifications.warn(game.i18n.localize('TABLE.NoAvailableResults'));
|
||||
return { roll, results };
|
||||
}
|
||||
|
||||
// Ensure that results are available within the minimum/maximum range
|
||||
const minRoll = (await roll.reroll({ minimize: true })).total;
|
||||
const maxRoll = (await roll.reroll({ maximize: true })).total;
|
||||
const availableRange = available.reduce(
|
||||
(range, result) => {
|
||||
const r = result.range;
|
||||
if (!range[0] || r[0] < range[0]) range[0] = r[0];
|
||||
if (!range[1] || r[1] > range[1]) range[1] = r[1];
|
||||
return range;
|
||||
},
|
||||
[null, null]
|
||||
);
|
||||
if (availableRange[0] > maxRoll || availableRange[1] < minRoll) {
|
||||
ui.notifications.warn('No results can possibly be drawn from this table and formula.');
|
||||
return { roll, results };
|
||||
}
|
||||
|
||||
// Continue rolling until one or more results are recovered
|
||||
let iter = 0;
|
||||
while (!results.length) {
|
||||
if (iter >= 10000) {
|
||||
ui.notifications.error(
|
||||
`Failed to draw an available entry from Table ${this.name}, maximum iteration reached`
|
||||
);
|
||||
break;
|
||||
}
|
||||
roll = await roll.reroll();
|
||||
results = this.getResultsForRoll(roll.total);
|
||||
iter++;
|
||||
}
|
||||
|
||||
// Draw results recursively from any inner Roll Tables
|
||||
if (recursive) {
|
||||
const inner = [];
|
||||
for (const result of results) {
|
||||
const { type, documentUuid } = result;
|
||||
const documentName = foundry.utils.parseUuid(documentUuid)?.type;
|
||||
if (type === 'document' && documentName === 'RollTable') {
|
||||
const innerTable = await fromUuid(documentUuid);
|
||||
if (innerTable) {
|
||||
const innerRoll = await innerTable.roll({ _depth: _depth + 1 });
|
||||
inner.push(...innerRoll.results);
|
||||
}
|
||||
} else inner.push(result);
|
||||
}
|
||||
results = inner;
|
||||
}
|
||||
|
||||
// Return the Roll and the results
|
||||
return { roll, results };
|
||||
}
|
||||
|
||||
async toMessage(results, { roll, messageData = {}, messageOptions = {} } = {}) {
|
||||
messageOptions.rollMode ??= game.settings.get('core', 'rollMode');
|
||||
|
||||
// Construct chat data
|
||||
messageData = foundry.utils.mergeObject(
|
||||
{
|
||||
author: game.user.id,
|
||||
speaker: foundry.documents.ChatMessage.implementation.getSpeaker(),
|
||||
rolls: [],
|
||||
sound: roll ? CONFIG.sounds.dice : null,
|
||||
flags: { 'core.RollTable': this.id }
|
||||
},
|
||||
messageData
|
||||
);
|
||||
if (roll) messageData.rolls.push(roll);
|
||||
|
||||
// Render the chat card which combines the dice roll with the drawn results
|
||||
const detailsPromises = await Promise.allSettled(results.map(r => r.getHTML()));
|
||||
const flavorKey = `TABLE.DrawFlavor${results.length > 1 ? 'Plural' : ''}`;
|
||||
const flavor = game.i18n.format(flavorKey, {
|
||||
number: results.length,
|
||||
name: foundry.utils.escapeHTML(this.name)
|
||||
});
|
||||
messageData.content = await foundry.applications.handlebars.renderTemplate(CONFIG.RollTable.resultTemplate, {
|
||||
description: await TextEditor.implementation.enrichHTML(this.description, {
|
||||
documents: true,
|
||||
secrets: this.isOwner
|
||||
}),
|
||||
flavor: flavor,
|
||||
results: results.map((result, i) => {
|
||||
const r = result.toObject(false);
|
||||
r.details = detailsPromises[i].value ?? '';
|
||||
const useTableIcon =
|
||||
result.icon === CONFIG.RollTable.resultIcon && this.img !== this.constructor.DEFAULT_ICON;
|
||||
r.icon = useTableIcon ? this.img : result.icon;
|
||||
return r;
|
||||
}),
|
||||
rollHTML: this.displayRoll && roll ? await roll.render() : null,
|
||||
table: this
|
||||
});
|
||||
|
||||
// Create the chat message
|
||||
return foundry.documents.ChatMessage.implementation.create(messageData, messageOptions);
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,27 @@ export default class DhScene extends Scene {
|
|||
}
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, user) {
|
||||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
||||
if (changes.flags?.daggerheart) {
|
||||
if (this._source.flags.daggerheart) {
|
||||
const unregisterTriggerData = this._source.flags.daggerheart.sceneEnvironments.reduce(
|
||||
(acc, env) => {
|
||||
if (!changes.flags.daggerheart.sceneEnvironments.includes(env)) acc.sceneEnvironments.push(env);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ ...this._source.flags.daggerheart, sceneEnvironments: [] }
|
||||
);
|
||||
game.system.registeredTriggers.unregisterSceneEnvironmentTriggers(unregisterTriggerData);
|
||||
}
|
||||
|
||||
game.system.registeredTriggers.registerSceneEnvironmentTriggers(changes.flags.daggerheart);
|
||||
}
|
||||
}
|
||||
|
||||
_onDelete(options, userId) {
|
||||
super._onDelete(options, userId);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { abilities } from '../config/actorConfig.mjs';
|
|||
import { getCommandTarget, rollCommandToJSON } from '../helpers/utils.mjs';
|
||||
|
||||
export default function DhDualityRollEnricher(match, _options) {
|
||||
const roll = rollCommandToJSON(match[1], match[0]);
|
||||
const roll = rollCommandToJSON(match[0]);
|
||||
if (!roll) return match[0];
|
||||
|
||||
return getDualityMessage(roll.result, roll.flavor);
|
||||
|
|
@ -47,6 +47,7 @@ function getDualityMessage(roll, flavor) {
|
|||
${roll?.trait && abilities[roll.trait] ? `data-trait="${roll.trait}"` : ''}
|
||||
${roll?.advantage ? 'data-advantage="true"' : ''}
|
||||
${roll?.disadvantage ? 'data-disadvantage="true"' : ''}
|
||||
${roll?.grantResources ? 'data-grant-resources="true"' : ''}
|
||||
>
|
||||
${roll?.reaction ? '<i class="fa-solid fa-reply"></i>' : '<i class="fa-solid fa-circle-half-stroke"></i>'}
|
||||
${label}
|
||||
|
|
@ -63,7 +64,8 @@ export const renderDualityButton = async event => {
|
|||
traitValue = button.dataset.trait?.toLowerCase(),
|
||||
target = getCommandTarget({ allowNull: true }),
|
||||
difficulty = button.dataset.difficulty,
|
||||
advantage = button.dataset.advantage ? Number(button.dataset.advantage) : undefined;
|
||||
advantage = button.dataset.advantage ? Number(button.dataset.advantage) : undefined,
|
||||
grantResources = Boolean(button.dataset?.grantResources);
|
||||
|
||||
await enrichedDualityRoll(
|
||||
{
|
||||
|
|
@ -73,14 +75,15 @@ export const renderDualityButton = async event => {
|
|||
difficulty,
|
||||
title: button.dataset.title,
|
||||
label: button.dataset.label,
|
||||
advantage
|
||||
advantage,
|
||||
grantResources
|
||||
},
|
||||
event
|
||||
);
|
||||
};
|
||||
|
||||
export const enrichedDualityRoll = async (
|
||||
{ reaction, traitValue, target, difficulty, title, label, advantage },
|
||||
{ reaction, traitValue, target, difficulty, title, label, advantage, grantResources, customConfig },
|
||||
event
|
||||
) => {
|
||||
const config = {
|
||||
|
|
@ -93,16 +96,24 @@ export const enrichedDualityRoll = async (
|
|||
advantage,
|
||||
type: reaction ? 'reaction' : null
|
||||
},
|
||||
skips: {
|
||||
resources: !grantResources,
|
||||
triggers: !grantResources
|
||||
},
|
||||
type: 'trait',
|
||||
hasRoll: true
|
||||
hasRoll: true,
|
||||
...(customConfig ?? {})
|
||||
};
|
||||
|
||||
if (target) {
|
||||
await target.diceRoll(config);
|
||||
const result = await target.diceRoll(config);
|
||||
if (!result) return;
|
||||
result.resourceUpdates.updateResources();
|
||||
} else {
|
||||
// For no target, call DualityRoll directly with basic data
|
||||
config.data = { experiences: {}, traits: {}, rules: {} };
|
||||
config.source = { actor: null };
|
||||
await CONFIG.Dice.daggerheart.DualityRoll.build(config);
|
||||
}
|
||||
return config;
|
||||
};
|
||||
|
|
|
|||
80
module/enrichers/FateRollEnricher.mjs
Normal file
80
module/enrichers/FateRollEnricher.mjs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { getCommandTarget, rollCommandToJSON } from '../helpers/utils.mjs';
|
||||
|
||||
export default function DhFateRollEnricher(match, _options) {
|
||||
const roll = rollCommandToJSON(match[0]);
|
||||
if (!roll) return match[0];
|
||||
|
||||
return getFateMessage(roll.result, roll?.flavor);
|
||||
}
|
||||
|
||||
export function getFateTypeData(fateTypeValue) {
|
||||
const value = fateTypeValue ? fateTypeValue.capitalize() : 'Hope';
|
||||
const lowercased = fateTypeValue?.toLowerCase?.() ?? 'hope';
|
||||
switch (lowercased) {
|
||||
case 'hope':
|
||||
case 'fear':
|
||||
return { value, label: game.i18n.localize(`DAGGERHEART.GENERAL.${lowercased}`) };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getFateMessage(roll, flavor) {
|
||||
const fateTypeData = getFateTypeData(roll?.type);
|
||||
|
||||
if (!fateTypeData)
|
||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
||||
|
||||
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
||||
const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll');
|
||||
|
||||
const fateElement = document.createElement('span');
|
||||
fateElement.innerHTML = `
|
||||
<button type="button" class="fate-roll-button${roll?.inline ? ' inline' : ''}"
|
||||
data-title="${title}"
|
||||
data-label="${fateTypeLabel}"
|
||||
data-fateType="${fateType}"
|
||||
>
|
||||
${title}
|
||||
</button>
|
||||
`;
|
||||
|
||||
return fateElement;
|
||||
}
|
||||
|
||||
export const renderFateButton = async event => {
|
||||
const button = event.currentTarget,
|
||||
target = getCommandTarget({ allowNull: true });
|
||||
|
||||
const fateTypeData = getFateTypeData(button.dataset?.fatetype);
|
||||
|
||||
if (!fateTypeData) ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
||||
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
||||
|
||||
await enrichedFateRoll(
|
||||
{
|
||||
target,
|
||||
title: button.dataset.title,
|
||||
label: button.dataset.label,
|
||||
fateType: fateType
|
||||
},
|
||||
event
|
||||
);
|
||||
};
|
||||
|
||||
export const enrichedFateRoll = async ({ target, title, label, fateType }, event) => {
|
||||
const config = {
|
||||
event: event ?? {},
|
||||
title: title,
|
||||
headerTitle: label,
|
||||
roll: {},
|
||||
hasRoll: true,
|
||||
fateType: fateType,
|
||||
skips: { reaction: true }
|
||||
};
|
||||
|
||||
config.data = { experiences: {}, traits: {}, fateType: fateType };
|
||||
config.source = { actor: target?.uuid };
|
||||
await CONFIG.Dice.daggerheart.FateRoll.build(config);
|
||||
return config;
|
||||
};
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import { default as DhDamageEnricher, renderDamageButton } from './DamageEnricher.mjs';
|
||||
import { default as DhDualityRollEnricher, renderDualityButton } from './DualityRollEnricher.mjs';
|
||||
import { default as DhFateRollEnricher, renderFateButton } from './FateRollEnricher.mjs';
|
||||
import { default as DhEffectEnricher } from './EffectEnricher.mjs';
|
||||
import { default as DhTemplateEnricher, renderMeasuredTemplate } from './TemplateEnricher.mjs';
|
||||
import { default as DhLookupEnricher } from './LookupEnricher.mjs';
|
||||
|
||||
export { DhDamageEnricher, DhDualityRollEnricher, DhEffectEnricher, DhTemplateEnricher };
|
||||
export { DhDamageEnricher, DhDualityRollEnricher, DhEffectEnricher, DhTemplateEnricher, DhFateRollEnricher };
|
||||
|
||||
export const enricherConfig = [
|
||||
{
|
||||
|
|
@ -15,6 +16,10 @@ export const enricherConfig = [
|
|||
pattern: /\[\[\/dr\s?(.*?)\]\]({[^}]*})?/g,
|
||||
enricher: DhDualityRollEnricher
|
||||
},
|
||||
{
|
||||
pattern: /\[\[\/fr\s?(.*?)\]\]({[^}]*})?/g,
|
||||
enricher: DhFateRollEnricher
|
||||
},
|
||||
{
|
||||
pattern: /@Effect\[([^\[\]]*)\]({[^}]*})?/g,
|
||||
enricher: DhEffectEnricher
|
||||
|
|
@ -38,6 +43,10 @@ export const enricherRenderSetup = element => {
|
|||
.querySelectorAll('.duality-roll-button')
|
||||
.forEach(element => element.addEventListener('click', renderDualityButton));
|
||||
|
||||
element
|
||||
.querySelectorAll('.fate-roll-button')
|
||||
.forEach(element => element.addEventListener('click', renderFateButton));
|
||||
|
||||
element
|
||||
.querySelectorAll('.measured-template-button')
|
||||
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { diceTypes, getDiceSoNicePresets, range } from '../config/generalConfig.mjs';
|
||||
import { diceTypes, getDiceSoNicePresets, getDiceSoNicePreset, range } from '../config/generalConfig.mjs';
|
||||
import Tagify from '@yaireo/tagify';
|
||||
|
||||
export const capitalize = string => {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
};
|
||||
|
||||
export function rollCommandToJSON(text, raw) {
|
||||
export function rollCommandToJSON(text) {
|
||||
if (!text) return {};
|
||||
|
||||
const flavorMatch = raw?.match(/{(.*)}$/);
|
||||
const flavorMatch = text?.match(/{(.*)}$/);
|
||||
const flavor = flavorMatch ? flavorMatch[1] : null;
|
||||
|
||||
// Match key="quoted string" OR key=unquotedValue
|
||||
|
|
@ -31,7 +31,7 @@ export function rollCommandToJSON(text, raw) {
|
|||
}
|
||||
result[key] = value;
|
||||
}
|
||||
return Object.keys(result).length > 0 ? { result, flavor } : null;
|
||||
return { result, flavor };
|
||||
}
|
||||
|
||||
export const getCommandTarget = (options = {}) => {
|
||||
|
|
@ -69,6 +69,20 @@ export const setDiceSoNiceForDualityRoll = async (rollResult, advantageState, ho
|
|||
}
|
||||
};
|
||||
|
||||
export const setDiceSoNiceForHopeFateRoll = async (rollResult, hopeFaces) => {
|
||||
if (!game.modules.get('dice-so-nice')?.active) return;
|
||||
const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
|
||||
const diceSoNicePresets = await getDiceSoNicePreset(diceSoNice.hope, hopeFaces);
|
||||
rollResult.dice[0].options = diceSoNicePresets;
|
||||
};
|
||||
|
||||
export const setDiceSoNiceForFearFateRoll = async (rollResult, fearFaces) => {
|
||||
if (!game.modules.get('dice-so-nice')?.active) return;
|
||||
const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
|
||||
const diceSoNicePresets = await getDiceSoNicePreset(diceSoNice.fear, fearFaces);
|
||||
rollResult.dice[0].options = diceSoNicePresets;
|
||||
};
|
||||
|
||||
export const chunkify = (array, chunkSize, mappingFunc) => {
|
||||
var chunkifiedArray = [];
|
||||
for (let i = 0; i < array.length; i += chunkSize) {
|
||||
|
|
|
|||
|
|
@ -212,6 +212,7 @@ export async function runMigrations() {
|
|||
}
|
||||
|
||||
if (foundry.utils.isNewerVersion('1.5.5', lastMigrationVersion)) {
|
||||
/* Clear out Environments that were added directly from compendium */
|
||||
for (const scene of game.scenes) {
|
||||
if (!scene.flags.daggerheart) continue;
|
||||
const systemData = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart);
|
||||
|
|
@ -226,6 +227,25 @@ export async function runMigrations() {
|
|||
|
||||
lastMigrationVersion = '1.5.5';
|
||||
}
|
||||
|
||||
if (foundry.utils.isNewerVersion('1.6.0', lastMigrationVersion)) {
|
||||
/* Delevel any companions that are higher level than their partner character */
|
||||
for (const companion of game.actors.filter(x => x.type === 'companion')) {
|
||||
if (companion.system.levelData.level.current <= 1) continue;
|
||||
|
||||
if (!companion.system.partner) {
|
||||
await companion.updateLevel(1);
|
||||
} else {
|
||||
const endLevel = companion.system.partner.system.levelData.level.current;
|
||||
if (endLevel < companion.system.levelData.level.current) {
|
||||
companion.system.levelData.level.changed = companion.system.levelData.level.current;
|
||||
await companion.updateLevel(endLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastMigrationVersion = '1.6.0';
|
||||
}
|
||||
//#endregion
|
||||
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
||||
|
|
|
|||
|
|
@ -235,7 +235,51 @@
|
|||
},
|
||||
"_id": "2ESeh4tPhr6DI5ty",
|
||||
"img": "icons/magic/death/skull-horned-worn-fire-blue.webp",
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Depths Of Despair",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"_id": "nofxm1vGZ2TmceA2",
|
||||
"img": "icons/magic/death/skull-horned-worn-fire-blue.webp",
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.rules.attack.damage.hpDamageMultiplier",
|
||||
"mode": 5,
|
||||
"value": "2",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">The </span><span style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);font-family:Montserrat, sans-serif;color:rgb(239, 230, 216);font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Demon of Despair</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\"> deals double damage to PCs with 0 Hope.</span></p>",
|
||||
"origin": null,
|
||||
"tint": "#ffffff",
|
||||
"transfer": true,
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null
|
||||
},
|
||||
"_key": "!actors.items.effects!kE4dfhqmIQpNd44e.2ESeh4tPhr6DI5ty.nofxm1vGZ2TmceA2"
|
||||
}
|
||||
],
|
||||
"folder": null,
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
|
|
|
|||
|
|
@ -304,7 +304,51 @@
|
|||
},
|
||||
"_id": "1fE6xo8yIOmZkGNE",
|
||||
"img": "icons/skills/melee/strike-slashes-orange.webp",
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Overwhelm",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"_id": "eGB9G0ljYCcdGbOx",
|
||||
"img": "icons/skills/melee/strike-slashes-orange.webp",
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.rules.attack.damage.hpDamageMultiplier",
|
||||
"mode": 5,
|
||||
"value": "2",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">When a target the </span><span style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);font-family:Montserrat, sans-serif;color:rgb(239, 230, 216);font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Failed Experiment</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\"> attacks has other adversaries within Very Close range, the </span><span style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);font-family:Montserrat, sans-serif;color:rgb(239, 230, 216);font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Failed Experiment</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\"> deals double damage.</span></p>",
|
||||
"origin": null,
|
||||
"tint": "#ffffff",
|
||||
"transfer": true,
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null
|
||||
},
|
||||
"_key": "!actors.items.effects!ChwwVqowFw8hJQwT.1fE6xo8yIOmZkGNE.eGB9G0ljYCcdGbOx"
|
||||
}
|
||||
],
|
||||
"folder": null,
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
|
|
|
|||
|
|
@ -229,7 +229,51 @@
|
|||
},
|
||||
"_id": "FGJTAeL38zTVd4fA",
|
||||
"img": "icons/magic/control/buff-flight-wings-runes-red-yellow.webp",
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Punish the Guilty",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"_id": "ID85zoIa5GfhNMti",
|
||||
"img": "icons/magic/control/buff-flight-wings-runes-red-yellow.webp",
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.rules.attack.damage.hpDamageMultiplier",
|
||||
"mode": 5,
|
||||
"value": "2",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">The </span><span style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);font-family:Montserrat, sans-serif;color:rgb(239, 230, 216);font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Hallowed Archer</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\"> deals double damage to targets marked Guilty by a High Seraph.</span></p>",
|
||||
"origin": null,
|
||||
"tint": "#ffffff",
|
||||
"transfer": true,
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null
|
||||
},
|
||||
"_key": "!actors.items.effects!kabueAo6BALApWqp.FGJTAeL38zTVd4fA.ID85zoIa5GfhNMti"
|
||||
}
|
||||
],
|
||||
"folder": null,
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
|
|
|
|||
|
|
@ -336,7 +336,14 @@
|
|||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"changes": [],
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.rules.attack.damage.hpDamageTakenMultiplier",
|
||||
"mode": 5,
|
||||
"value": "2",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
|
|
@ -350,8 +357,8 @@
|
|||
"description": "",
|
||||
"tint": "#ffffff",
|
||||
"statuses": [
|
||||
"restrained",
|
||||
"vulnerable"
|
||||
"vulnerable",
|
||||
"restrained"
|
||||
],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
|
|
|
|||
|
|
@ -230,7 +230,51 @@
|
|||
"subType": null,
|
||||
"originId": null
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Opportunist",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"_id": "O03vYbyNLO3YPZGo",
|
||||
"img": "icons/skills/targeting/crosshair-triple-strike-orange.webp",
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.rules.attack.damage.hpDamageMultiplier",
|
||||
"mode": 5,
|
||||
"value": "2",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">When two or more adversaries are within Very Close range of a creature, all damage the </span><span style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);font-family:Montserrat, sans-serif;color:rgb(239, 230, 216);font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Skeleton Archer</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\"> deals to that creature is doubled.</span></p>",
|
||||
"origin": null,
|
||||
"tint": "#ffffff",
|
||||
"transfer": true,
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null
|
||||
},
|
||||
"_key": "!actors.items.effects!7X5q7a6ueeHs5oA9.6mL2FQ9pQdfoDNzG.O03vYbyNLO3YPZGo"
|
||||
}
|
||||
],
|
||||
"folder": null,
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
|
|
|
|||
|
|
@ -9,13 +9,47 @@
|
|||
"resource": {
|
||||
"type": "simple",
|
||||
"value": 0,
|
||||
"max": "",
|
||||
"icon": "",
|
||||
"recovery": null,
|
||||
"max": "@system.levelData.level.current",
|
||||
"icon": "fa-solid fa-water",
|
||||
"recovery": "session",
|
||||
"diceStates": {},
|
||||
"dieFaces": "d4"
|
||||
},
|
||||
"actions": {},
|
||||
"actions": {
|
||||
"tFlus34KotJjHfTe": {
|
||||
"type": "effect",
|
||||
"_id": "tFlus34KotJjHfTe",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [
|
||||
{
|
||||
"trigger": "fearRoll",
|
||||
"triggeringActorType": "self",
|
||||
"command": "const { max, value } = this.item.system.resource;\nconst maxValue = actor.system.levelData.level.current;\nconst afterUpdate = value+1;\nif (afterUpdate > maxValue) return;\n\nui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.knowTheTide'));\nreturn { updates: [{\n key: 'resource',\n itemId: this.item.id,\n target: this.item,\n value: 1,\n}]};"
|
||||
}
|
||||
],
|
||||
"cost": [],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Know The Tide",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
"subType": null,
|
||||
"originId": null,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "Consumables",
|
||||
"img": "icons/consumables/potions/bottle-corked-red.webp",
|
||||
"description": "<p>To generate a random consumable, choose a rarity, roll the designated dice, and match the total to the item in the table:</p><ul><li><p>Common: 1d12 or 2d12</p></li><li><p>Uncommon: 2d12 or 3d12</p></li><li><p>Rare: 3d12 or 4d12</p></li><li><p>Legendary: 4d12 or 5d12</p></li></ul>",
|
||||
"description": "",
|
||||
"results": [
|
||||
{
|
||||
"type": "document",
|
||||
|
|
@ -1511,8 +1511,27 @@
|
|||
"default": 0,
|
||||
"Bgvu4A6AMkRFOTGR": 3
|
||||
},
|
||||
"flags": {},
|
||||
"formula": "1d60",
|
||||
"flags": {
|
||||
"daggerheart": {
|
||||
"activeAltFormula": "",
|
||||
"formulaName": "Common",
|
||||
"altFormula": {
|
||||
"uoUn5fRTUkyg6U2G": {
|
||||
"name": "Uncommon",
|
||||
"formula": "3d12"
|
||||
},
|
||||
"FGxM2yoxUUUd9Eov": {
|
||||
"name": "Rare",
|
||||
"formula": "4d12"
|
||||
},
|
||||
"HZ2hRBxu0k8IW0jC": {
|
||||
"name": "Legendary",
|
||||
"formula": "5d12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"formula": "2d12",
|
||||
"_id": "tF04P02yVN1YDVel",
|
||||
"sort": 300000,
|
||||
"_key": "!tables!tF04P02yVN1YDVel"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "Loot",
|
||||
"img": "icons/commodities/treasure/brooch-gold-ruby.webp",
|
||||
"description": "<p>To generate a random item, choose a rarity, roll the designated dice, and match the total to the item in the table: </p><ul><li><p> Common: 1d12 or 2d12 </p></li><li><p>Uncommon: 2d12 or 3d12 </p></li><li><p>Rare: 3d12 or 4d12 </p></li><li><p>Legendary: 4d12 or 5d12</p></li></ul>",
|
||||
"description": "",
|
||||
"results": [
|
||||
{
|
||||
"type": "document",
|
||||
|
|
@ -1511,8 +1511,27 @@
|
|||
"default": 0,
|
||||
"Bgvu4A6AMkRFOTGR": 3
|
||||
},
|
||||
"flags": {},
|
||||
"formula": "1d60",
|
||||
"flags": {
|
||||
"daggerheart": {
|
||||
"activeAltFormula": "",
|
||||
"formulaName": "Common",
|
||||
"altFormula": {
|
||||
"hJJtajaMk14bYM4X": {
|
||||
"name": "Uncommon",
|
||||
"formula": "3d12"
|
||||
},
|
||||
"yDVeXdKpG7LzjHWa": {
|
||||
"name": "Rare",
|
||||
"formula": "4d12"
|
||||
},
|
||||
"qPHNIuUgWAHauI6V": {
|
||||
"name": "Legendary",
|
||||
"formula": "5d12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"formula": "2d12",
|
||||
"_id": "S61Shlt2I5CbLRjz",
|
||||
"sort": 200000,
|
||||
"_key": "!tables!S61Shlt2I5CbLRjz"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "Table of Random Objectives",
|
||||
"name": "Random Objectives",
|
||||
"img": "icons/sundries/documents/document-torn-diagram-tan.webp",
|
||||
"description": "<p>Layering Goals Other than Attrition into Combat</p>",
|
||||
"description": "",
|
||||
"results": [
|
||||
{
|
||||
"type": "text",
|
||||
|
|
@ -311,7 +311,20 @@
|
|||
"default": 0,
|
||||
"Bgvu4A6AMkRFOTGR": 3
|
||||
},
|
||||
"flags": {},
|
||||
"flags": {
|
||||
"daggerheart": {
|
||||
"formulaName": "Roll Formula",
|
||||
"altFormula": {},
|
||||
"activeAltFormula": null,
|
||||
"flags": {
|
||||
"daggerheart": {
|
||||
"formulaName": "Roll Formula",
|
||||
"altFormula": {},
|
||||
"activeAltFormula": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"formula": "1d12",
|
||||
"_id": "I5L1dlgxXTNrCCkL",
|
||||
"sort": 400000,
|
||||
|
|
@ -16,7 +16,51 @@
|
|||
"artist": ""
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Advanced Training",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"_id": "bKOuMxhB2Jth3j2T",
|
||||
"img": "icons/creatures/mammals/wolf-howl-moon-gray.webp",
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.companionData.levelupChoices",
|
||||
"mode": 2,
|
||||
"value": "2",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">Choose two additional level-up options for your companion.</span></p>",
|
||||
"origin": null,
|
||||
"tint": "#ffffff",
|
||||
"transfer": true,
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null
|
||||
},
|
||||
"_key": "!items.effects!uGcs785h94RMtueH.bKOuMxhB2Jth3j2T"
|
||||
}
|
||||
],
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,51 @@
|
|||
"artist": ""
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Expert Training",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"_id": "rknTONvaUDZ2Yz1W",
|
||||
"img": "icons/creatures/mammals/dog-husky-white-blue.webp",
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.companionData.levelupChoices",
|
||||
"mode": 2,
|
||||
"value": "1",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">Choose an additional level-up option for your companion.</span></p>",
|
||||
"origin": null,
|
||||
"tint": "#ffffff",
|
||||
"transfer": true,
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null
|
||||
},
|
||||
"_key": "!items.effects!iCXtOWBKv1FdKdWz.rknTONvaUDZ2Yz1W"
|
||||
}
|
||||
],
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
|
|
|
|||
27
styles/less/dialog/character-reset/sheet.less
Normal file
27
styles/less/dialog/character-reset/sheet.less
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
.daggerheart.dh-style.dialog.views.character-reset {
|
||||
.character-reset-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
legend {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.character-reset-header {
|
||||
font-size: var(--font-size-18);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reset-data-container {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 2fr;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +1,56 @@
|
|||
@import '../../utils/spacing.less';
|
||||
@import '../../utils/colors.less';
|
||||
@import '../../utils/fonts.less';
|
||||
|
||||
.daggerheart.dh-style.dialog.death-move {
|
||||
.death-move-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
|
||||
.moves-list {
|
||||
.move-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
|
||||
&:hover {
|
||||
background-color: light-dark(@soft-shadow, @soft-white-shadow);
|
||||
cursor: pointer;
|
||||
}
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
i {
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='radio'] {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
@import '../../utils/spacing.less';
|
||||
@import '../../utils/colors.less';
|
||||
@import '../../utils/fonts.less';
|
||||
|
||||
.daggerheart.dh-style.dialog.death-move {
|
||||
.death-move-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
|
||||
.moves-list {
|
||||
.move-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
height: 37px;
|
||||
|
||||
&:hover {
|
||||
background-color: light-dark(@soft-shadow, @soft-white-shadow);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
i {
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='radio'] {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,4 +38,8 @@
|
|||
|
||||
@import './item-transfer/sheet.less';
|
||||
|
||||
@import './settings/change-currency-icon.less';
|
||||
@import './settings/change-currency-icon.less';
|
||||
|
||||
@import './risk-it-all/sheet.less';
|
||||
|
||||
@import './character-reset/sheet.less';
|
||||
|
|
|
|||
60
styles/less/dialog/risk-it-all/sheet.less
Normal file
60
styles/less/dialog/risk-it-all/sheet.less
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
.daggerheart.dialog.dh-style.views.risk-it-all {
|
||||
.risk-it-all-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
text-align: center;
|
||||
|
||||
header {
|
||||
font-weight: bold;
|
||||
font-size: var(--font-size-20);
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-size: var(--font-size-18);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.remaining-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.resource-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.final-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
.final-section-values-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
|
||||
.final-section-value-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,14 @@
|
|||
.message-header .message-header-main .message-sub-header-container h4 {
|
||||
color: @dark-blue;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
.table-draw {
|
||||
.table-description {
|
||||
color: @dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,6 +91,7 @@
|
|||
|
||||
.message-content {
|
||||
padding-bottom: 8px;
|
||||
|
||||
.flavor-text {
|
||||
font-size: var(--font-size-12);
|
||||
line-height: 20px;
|
||||
|
|
@ -90,6 +99,33 @@
|
|||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.table-draw {
|
||||
.table-flavor {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 0.5rem;
|
||||
font-size: var(--font-size-12);
|
||||
}
|
||||
|
||||
.table-description {
|
||||
color: @beige;
|
||||
font-style: italic;
|
||||
|
||||
&.flavor-spaced {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.table-results {
|
||||
.description {
|
||||
flex-basis: min-content;
|
||||
|
||||
> p:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
.measured-template-button,
|
||||
.enriched-damage-button,
|
||||
.fate-roll-button,
|
||||
.duality-roll-button {
|
||||
display: inline;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.clown-car img {
|
||||
transition: 0.5s;
|
||||
.clown-car img {
|
||||
transition: 0.5s;
|
||||
|
||||
&.flipped {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
&.flipped {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -195,6 +195,11 @@
|
|||
.hope-value {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
|
||||
&.scar {
|
||||
cursor: initial;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@
|
|||
.input-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,4 +40,5 @@
|
|||
@import './items/heritage.less';
|
||||
@import './items/item-sheet-shared.less';
|
||||
|
||||
@import './rollTables/sheet.less';
|
||||
@import './actions/actions.less';
|
||||
|
|
|
|||
29
styles/less/sheets/rollTables/sheet.less
Normal file
29
styles/less/sheets/rollTables/sheet.less
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
.application.sheet.roll-table-sheet {
|
||||
.formulas-section {
|
||||
legend {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.formulas-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 40px;
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
|
||||
.formula-button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.roll-table-view-formula-container {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
color: @dark;
|
||||
}
|
||||
|
||||
&.fate,
|
||||
&.duality {
|
||||
background-image: url(../assets/parchments/dh-parchment-dark.png);
|
||||
|
||||
|
|
@ -66,6 +67,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.fate,
|
||||
&.critical {
|
||||
--text-color: @chat-purple;
|
||||
--bg-color: @chat-purple-40;
|
||||
|
|
@ -80,7 +82,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:not(.duality) {
|
||||
&:not(.duality .fate) {
|
||||
.font-20 {
|
||||
color: @dark;
|
||||
}
|
||||
|
|
@ -173,6 +175,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.fate {
|
||||
|
||||
&.hope {
|
||||
--text-color: @golden;
|
||||
--bg-color: @golden-40;
|
||||
.message-header,
|
||||
.message-content {
|
||||
background-color: @golden-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&.fear {
|
||||
--text-color: @chat-blue;
|
||||
--bg-color: @chat-blue-40;
|
||||
.message-header,
|
||||
.message-content {
|
||||
background-color: @chat-blue-bg;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.duality {
|
||||
&.hope {
|
||||
--text-color: @golden;
|
||||
|
|
|
|||
152
styles/less/ui/chat/deathmoves.less
Normal file
152
styles/less/ui/chat/deathmoves.less
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
@import '../../utils/colors.less';
|
||||
@import '../../utils/fonts.less';
|
||||
@import '../../utils/spacing.less';
|
||||
|
||||
#interface.theme-light {
|
||||
.daggerheart.chat.death-moves {
|
||||
.death-moves-list .death-move {
|
||||
&:hover {
|
||||
background: @dark-blue-10;
|
||||
}
|
||||
|
||||
.death-label {
|
||||
border-bottom: 1px solid @dark-blue;
|
||||
|
||||
.header-label .title {
|
||||
color: @dark-blue;
|
||||
}
|
||||
.header-label .label {
|
||||
color: @dark;
|
||||
}
|
||||
}
|
||||
|
||||
.fa-chevron-down {
|
||||
color: @dark-blue;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
color: @dark;
|
||||
}
|
||||
|
||||
.result {
|
||||
color: @dark;
|
||||
}
|
||||
|
||||
.risk-it-all-button {
|
||||
color: @dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.daggerheart.chat {
|
||||
&.death-moves {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
details[open] {
|
||||
.fa-chevron-down {
|
||||
transform: rotate(180deg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.death-moves-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
|
||||
.fa-chevron-down {
|
||||
transition: all 0.3s ease;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.death-move {
|
||||
width: 100%;
|
||||
|
||||
.death-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
border-bottom: 1px solid @golden;
|
||||
margin: 0 8px;
|
||||
padding-bottom: 5px;
|
||||
width: -webkit-fill-available;
|
||||
|
||||
&:hover {
|
||||
background: light-dark(@dark-blue-10, @golden-10);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.death-image {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.header-label {
|
||||
padding: 8px;
|
||||
.title {
|
||||
font-size: var(--font-size-16);
|
||||
color: @golden;
|
||||
font-weight: 700;
|
||||
}
|
||||
.label {
|
||||
font-size: var(--font-size-12);
|
||||
color: @beige;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-use-button-parent {
|
||||
width: 100%;
|
||||
|
||||
.action-use-target {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
padding: 4px 8px 10px 40px;
|
||||
font-size: var(--font-size-12);
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
select {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-use-button {
|
||||
width: -webkit-fill-available;
|
||||
margin: 0 8px;
|
||||
font-weight: 600;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.result {
|
||||
padding: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.risk-it-all-button {
|
||||
width: -webkit-fill-available;
|
||||
margin: 0 8px;
|
||||
font-weight: 600;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
@import './chat/effect-summary.less';
|
||||
@import './chat/group-roll.less';
|
||||
@import './chat/refresh-message.less';
|
||||
@import './chat/deathmoves.less';
|
||||
@import './chat/sheet.less';
|
||||
|
||||
@import './combat-sidebar/combat-sidebar.less';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "daggerheart",
|
||||
"title": "Daggerheart",
|
||||
"description": "An unofficial implementation of the Daggerheart system",
|
||||
"version": "1.5.5",
|
||||
"version": "1.6.0",
|
||||
"compatibility": {
|
||||
"minimum": "13.346",
|
||||
"verified": "13.351",
|
||||
|
|
@ -285,6 +285,7 @@
|
|||
},
|
||||
"ChatMessage": {
|
||||
"dualityRoll": {},
|
||||
"fateRoll": {},
|
||||
"adversaryRoll": {},
|
||||
"damageRoll": {},
|
||||
"abilityUse": {},
|
||||
|
|
|
|||
33
templates/dialogs/characterReset.hbs
Normal file
33
templates/dialogs/characterReset.hbs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<div>
|
||||
<div class="character-reset-container">
|
||||
<div class="character-reset-header">{{localize "DAGGERHEART.APPLICATIONS.CharacterReset.headerTitle"}}</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>{{localize "DAGGERHEART.APPLICATIONS.CharacterReset.alwaysDeleteSection"}} <i class="fa-solid fa-lock"></i></legend>
|
||||
|
||||
<div class="reset-data-wrapper two-columns even">
|
||||
{{#each this.data.delete as | data key|}}
|
||||
<div class="reset-data-container">
|
||||
<label>{{localize data.label}}</label>
|
||||
<input type="checkbox" {{checked data.keep}} disabled />
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>{{localize "DAGGERHEART.APPLICATIONS.CharacterReset.optionalDeleteSection"}}</legend>
|
||||
|
||||
<div class="reset-data-wrapper two-columns even">
|
||||
{{#each this.data.optional as | data key|}}
|
||||
<div class="reset-data-container">
|
||||
<label>{{localize data.label}}</label>
|
||||
<input type="checkbox" name="{{concat "data.optional." key ".keep"}}" {{checked data.keep}} />
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<button type="button" data-action="finishSelection">{{localize "Reset"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -68,95 +68,127 @@
|
|||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if (eq @root.rollType 'FateRoll')}}
|
||||
{{#if (eq @root.roll.fateDie 'Hope')}}
|
||||
|
||||
<div class="dice-option">
|
||||
<img class="dice-icon" src="{{concat 'systems/daggerheart/assets/icons/dice/hope/' @root.roll.dHope.denomination '.svg'}}" alt="">
|
||||
<div class="dice-select">
|
||||
<span class="label">{{localize "DAGGERHEART.GENERAL.hope"}}</span>
|
||||
<select name="roll.dice.dHope">
|
||||
{{selectOptions diceOptions selected=@root.roll.dHope.denomination}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq @root.roll.fateDie 'Fear')}}
|
||||
<div class="dice-option">
|
||||
<img class="dice-icon" src="{{concat 'systems/daggerheart/assets/icons/dice/fear/' @root.roll.dFear.denomination '.svg'}}" alt="">
|
||||
<div class="dice-select">
|
||||
<span class="label">{{localize "DAGGERHEART.GENERAL.fear"}}</span>
|
||||
<select name="roll.dice.dFear">
|
||||
{{selectOptions diceOptions selected=@root.roll.dFear.denomination}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if hasSelectedEffects}}
|
||||
<fieldset class="experience-container">
|
||||
<legend>{{localize "DAGGERHEART.GENERAL.Effect.plural"}}</legend>
|
||||
{{#if (ne @root.rollType 'FateRoll')}}
|
||||
{{#if hasSelectedEffects}}
|
||||
<fieldset class="experience-container">
|
||||
<legend>{{localize "DAGGERHEART.GENERAL.Effect.plural"}}</legend>
|
||||
|
||||
{{#each selectedEffects as |effect id|}}
|
||||
<div class="experience-chip {{#if effect.selected}}selected{{/if}}" data-action="toggleSelectedEffect" data-key="{{id}}" data-tooltip="{{this.description}}">
|
||||
<span><i class="{{ifThen effect.selected "fa-solid" "fa-regular"}} fa-circle"></i></span>
|
||||
<span class="label">{{effect.name}}</span>
|
||||
</div>
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
|
||||
{{#if experiences.length}}
|
||||
<fieldset class="experience-container">
|
||||
<legend>{{localize "DAGGERHEART.GENERAL.experience.plural"}}</legend>
|
||||
{{#each experiences}}
|
||||
{{#if name}}
|
||||
<div class="experience-chip {{#if (includes ../selectedExperiences id)}}selected{{/if}}" data-action="selectExperience" data-key="{{id}}" data-tooltip="{{this.description}}">
|
||||
<span><i class="{{ifThen (includes ../selectedExperiences id) "fa-solid" "fa-regular"}} fa-circle"></i></span>
|
||||
<span class="label">{{name}} +{{value}}</span>
|
||||
{{#each selectedEffects as |effect id|}}
|
||||
<div class="experience-chip {{#if effect.selected}}selected{{/if}}" data-action="toggleSelectedEffect" data-key="{{id}}" data-tooltip="{{this.description}}">
|
||||
<span><i class="{{ifThen effect.selected "fa-solid" "fa-regular"}} fa-circle"></i></span>
|
||||
<span class="label">{{effect.name}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
|
||||
<fieldset class="modifier-container {{#if (eq @root.rollType 'DualityRoll')}}two-columns{{else}}one-column{{/if}}">
|
||||
<legend>{{localize "DAGGERHEART.GENERAL.Modifier.plural"}}</legend>
|
||||
<div class="nest-inputs">
|
||||
<button class="advantage-chip flex1 {{#if (eq advantage 1)}}selected{{/if}}" data-action="updateIsAdvantage" data-advantage="1">
|
||||
{{#if (eq advantage 1)}}
|
||||
<span><i class="fa-solid fa-circle"></i></span>
|
||||
{{else}}
|
||||
<span><i class="fa-regular fa-circle"></i></span>
|
||||
{{/if}}
|
||||
<span class="label">{{localize "DAGGERHEART.GENERAL.Advantage.full"}}</span>
|
||||
{{#if @root.rollConfig.data.advantageSources.length}}
|
||||
<span class="advantage-chip-tooltip" data-tooltip="{{concat "#advantage#" @root.rollConfig.source.actor}}"><i class="fa-solid fa-circle-info"></i></span>
|
||||
{{/if}}
|
||||
</button>
|
||||
<button class="disadvantage-chip flex1 {{#if (eq advantage -1)}}selected{{/if}}" data-action="updateIsAdvantage" data-advantage="-1">
|
||||
{{#if (eq advantage -1)}}
|
||||
<span><i class="fa-solid fa-circle"></i></span>
|
||||
{{else}}
|
||||
<span><i class="fa-regular fa-circle"></i></span>
|
||||
{{/if}}
|
||||
<span class="label">{{localize "DAGGERHEART.GENERAL.Disadvantage.full"}}</span>
|
||||
{{#if @root.rollConfig.data.disadvantageSources.length}}
|
||||
<span class="advantage-chip-tooltip" data-tooltip="{{concat "#disadvantage#" @root.rollConfig.source.actor}}"><i class="fa-solid fa-circle-info"></i></span>
|
||||
{{/if}}
|
||||
</button>
|
||||
</div>
|
||||
{{#unless (eq @root.rollType 'D20Roll')}}
|
||||
{{#if experiences.length}}
|
||||
<fieldset class="experience-container">
|
||||
<legend>{{localize "DAGGERHEART.GENERAL.experience.plural"}}</legend>
|
||||
{{#each experiences}}
|
||||
{{#if name}}
|
||||
<div class="experience-chip {{#if (includes ../selectedExperiences id)}}selected{{/if}}" data-action="selectExperience" data-key="{{id}}" data-tooltip="{{this.description}}">
|
||||
<span><i class="{{ifThen (includes ../selectedExperiences id) "fa-solid" "fa-regular"}} fa-circle"></i></span>
|
||||
<span class="label">{{name}} +{{value}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
<fieldset class="modifier-container {{#if (eq @root.rollType 'DualityRoll')}}two-columns{{else}}one-column{{/if}}">
|
||||
{{#if @root.advantage}}
|
||||
<legend>{{localize "DAGGERHEART.GENERAL.Modifier.plural"}}</legend>
|
||||
<div class="nest-inputs">
|
||||
<select name="roll.dice.advantageNumber"{{#unless advantage}} disabled{{/unless}}>
|
||||
{{#times 10}}
|
||||
<option value="{{add this 1}}" {{#if (eq @root.roll.advantageNumber (add this 1))}} selected{{/if}}>{{add this 1}}</option>
|
||||
{{/times}}
|
||||
</select>
|
||||
<select name="roll.dice.advantageFaces"{{#unless advantage}} disabled{{/unless}}>
|
||||
{{selectOptions diceOptions selected=@root.roll.dAdvantage.denomination}}
|
||||
</select>
|
||||
<button class="advantage-chip flex1 {{#if (eq advantage 1)}}selected{{/if}}" data-action="updateIsAdvantage" data-advantage="1">
|
||||
{{#if (eq advantage 1)}}
|
||||
<span><i class="fa-solid fa-circle"></i></span>
|
||||
{{else}}
|
||||
<span><i class="fa-regular fa-circle"></i></span>
|
||||
{{/if}}
|
||||
<span class="label">{{localize "DAGGERHEART.GENERAL.Advantage.full"}}</span>
|
||||
{{#if @root.rollConfig.data.advantageSources.length}}
|
||||
<span class="advantage-chip-tooltip" data-tooltip="{{concat "#advantage#" @root.rollConfig.source.actor}}"><i class="fa-solid fa-circle-info"></i></span>
|
||||
{{/if}}
|
||||
</button>
|
||||
<button class="disadvantage-chip flex1 {{#if (eq advantage -1)}}selected{{/if}}" data-action="updateIsAdvantage" data-advantage="-1">
|
||||
{{#if (eq advantage -1)}}
|
||||
<span><i class="fa-solid fa-circle"></i></span>
|
||||
{{else}}
|
||||
<span><i class="fa-regular fa-circle"></i></span>
|
||||
{{/if}}
|
||||
<span class="label">{{localize "DAGGERHEART.GENERAL.Disadvantage.full"}}</span>
|
||||
{{#if @root.rollConfig.data.disadvantageSources.length}}
|
||||
<span class="advantage-chip-tooltip" data-tooltip="{{concat "#disadvantage#" @root.rollConfig.source.actor}}"><i class="fa-solid fa-circle-info"></i></span>
|
||||
{{/if}}
|
||||
</button>
|
||||
</div>
|
||||
{{#if abilities}}
|
||||
<span>{{localize "DAGGERHEART.GENERAL.traitModifier"}}</span>
|
||||
<select name="trait">
|
||||
{{selectOptions abilities selected=@root.rollConfig.roll.trait valueAttr="id" labelAttr="label" blank="" localize=true}}
|
||||
{{#unless (eq @root.rollType 'D20Roll')}}
|
||||
<div class="nest-inputs">
|
||||
<select name="roll.dice.advantageNumber"{{#unless advantage}} disabled{{/unless}}>
|
||||
{{#times 10}}
|
||||
<option value="{{add this 1}}" {{#if (eq @root.roll.advantageNumber (add this 1))}} selected{{/if}}>{{add this 1}}</option>
|
||||
{{/times}}
|
||||
</select>
|
||||
<select name="roll.dice.advantageFaces"{{#unless advantage}} disabled{{/unless}}>
|
||||
{{selectOptions diceOptions selected=@root.roll.dAdvantage.denomination}}
|
||||
</select>
|
||||
</div>
|
||||
{{#if abilities}}
|
||||
<span>{{localize "DAGGERHEART.GENERAL.traitModifier"}}</span>
|
||||
<select name="trait">
|
||||
{{selectOptions abilities selected=@root.rollConfig.roll.trait valueAttr="id" labelAttr="label" blank="" localize=true}}
|
||||
</select>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
{{#if @root.rallyDie.length}}
|
||||
<span class="formula-label">{{localize "DAGGERHEART.CLASS.Feature.rallyDice"}}</span>
|
||||
<select name="roll.dice._rallyIndex">
|
||||
{{selectOptions @root.rallyDie blank="" selected=@root.roll._rallyIndex}}
|
||||
</select>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{#if @root.rallyDie.length}}
|
||||
<span class="formula-label">{{localize "DAGGERHEART.CLASS.Feature.rallyDice"}}</span>
|
||||
<select name="roll.dice._rallyIndex">
|
||||
{{selectOptions @root.rallyDie blank="" selected=@root.roll._rallyIndex}}
|
||||
</select>
|
||||
{{/if}}
|
||||
{{#if (eq @root.rollType 'DualityRoll')}}<span class="formula-label">{{localize "DAGGERHEART.GENERAL.situationalBonus"}}</span>{{/if}}
|
||||
<input type="text" value="{{extraFormula}}" name="extraFormula" placeholder="{{#if (eq @root.rollType 'DualityRoll')}}Ex: 1d6 + 5{{else}}Situational Bonus{{/if}}">
|
||||
</fieldset>
|
||||
{{#if (eq @root.rollType 'DualityRoll')}}<span class="formula-label">{{localize "DAGGERHEART.GENERAL.situationalBonus"}}</span>{{/if}}
|
||||
<input type="text" value="{{extraFormula}}" name="extraFormula" placeholder="{{#if (eq @root.rollType 'DualityRoll')}}Ex: 1d6 + 5{{else}}Situational Bonus{{/if}}">
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
{{#if (or costs uses)}}
|
||||
{{> 'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs'}}
|
||||
{{/if}}
|
||||
|
||||
<span class="formula-label"><b>{{localize "DAGGERHEART.GENERAL.formula"}}:</b> {{@root.formula}}</span>
|
||||
{{#if (ne @root.rollType 'FateRoll')}}
|
||||
<span class="formula-label"><b>{{localize "DAGGERHEART.GENERAL.formula"}}:</b> {{@root.formula}}</span>
|
||||
{{/if}}
|
||||
|
||||
<div class="roll-dialog-controls">
|
||||
<select class="roll-mode-select" name="selectedRollMode">
|
||||
|
|
|
|||
39
templates/dialogs/riskItAllDialog.hbs
Normal file
39
templates/dialogs/riskItAllDialog.hbs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<div>
|
||||
<div class="risk-it-all-container">
|
||||
<header>{{localize "DAGGERHEART.APPLICATIONS.RiskItAllDialog.subtitle"}}</header>
|
||||
|
||||
<div class="remaining-section">
|
||||
<label class="section-label">{{localize "DAGGERHEART.APPLICATIONS.RiskItAllDialog.remainingTitle"}}</label>
|
||||
<div>{{this.remainingResource}}</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-section">
|
||||
<div class="resource-container">
|
||||
<label>{{localize "DAGGERHEART.APPLICATIONS.RiskItAllDialog.clearResource" resource=(localize "DAGGERHEART.GENERAL.HitPoints.short")}}: {{this.choices.hitPoints}}</label>
|
||||
<input type="range" step="1" min="0" max="{{this.maxHitPointsValue}}" value="{{this.choices.hitPoints}}" name="choices.hitPoints" data-choice="hitPoints" />
|
||||
</div>
|
||||
<div class="resource-container">
|
||||
<label>{{localize "DAGGERHEART.APPLICATIONS.RiskItAllDialog.clearResource" resource=(localize "DAGGERHEART.GENERAL.stress")}}: {{this.choices.stress}}</label>
|
||||
<input type="range" step="1" min="0" max="{{this.maxStressValue}}" value="{{this.choices.stress}}" name="choices.stress" data-choice="stress" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="final-section">
|
||||
<label class="section-label">{{localize "DAGGERHEART.APPLICATIONS.RiskItAllDialog.finalTitle"}}</label>
|
||||
<div class="final-section-values-container">
|
||||
<div class="final-section-value-container">
|
||||
<label>{{localize "DAGGERHEART.GENERAL.HitPoints.plural"}}</label>
|
||||
<span>{{this.final.hitPoints.value}}/{{this.final.hitPoints.max}}</span>
|
||||
</div>
|
||||
<div class="final-section-value-container">
|
||||
<label>{{localize "DAGGERHEART.GENERAL.stress"}}</label>
|
||||
<span>{{this.final.stress.value}}/{{this.final.stress.max}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<button type="button" data-action="finish" {{disabled this.unfinished}}>{{localize "Submit"}}</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -11,6 +11,11 @@
|
|||
<button type="button" class="control-icon" data-action="sort" data-direction="down" data-tooltip="HUD.ToBack">
|
||||
<img src="{{icons.down}}">
|
||||
</button>
|
||||
{{#if hasCompanion}}
|
||||
<button type="button" class="control-icon clown-car" data-action="toggleCompanions" data-tooltip="{{#if companionOnCanvas}}{{localize "DAGGERHEART.APPLICATIONS.HUD.tokenHUD.retrieveCompanionTokens"}}{{else}}{{localize "DAGGERHEART.APPLICATIONS.HUD.tokenHUD.depositCompanionTokens"}}{{/if}}">
|
||||
<img {{#if companionOnCanvas}}class="flipped"{{/if}} src="{{icons.toggleClowncar}}">
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if canConfigure}}
|
||||
<button type="button" class="control-icon" data-action="config" data-tooltip="HUD.OpenConfig">
|
||||
|
|
@ -76,7 +81,7 @@
|
|||
|
||||
{{#if (eq actorType 'party')}}
|
||||
<button type="button" class="control-icon clown-car" data-action="togglePartyTokens" data-tooltip="{{#if partyOnCanvas}}{{localize "DAGGERHEART.APPLICATIONS.HUD.tokenHUD.retrievePartyTokens"}}{{else}}{{localize "DAGGERHEART.APPLICATIONS.HUD.tokenHUD.depositPartyTokens"}}{{/if}}">
|
||||
<img {{#if partyOnCanvas}}class="flipped"{{/if}} src="{{icons.toggleParty}}">
|
||||
<img {{#if partyOnCanvas}}class="flipped"{{/if}} src="{{icons.toggleClowncar}}">
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
{{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}}
|
||||
{{formGroup settingFields.schema.fields.effects.fields.rangeDependent value=settingFields._source.effects.rangeDependent localize=true}}
|
||||
{{formGroup settingFields.schema.fields.levelupAuto value=settingFields._source.levelupAuto localize=true}}
|
||||
{{formGroup settingFields.schema.fields.playerCanEditSheet value=settingFields._source.playerCanEditSheet localize=true}}
|
||||
{{formGroup settingFields.schema.fields.damageReductionRulesDefault value=settingFields._source.damageReductionRulesDefault localize=true}}
|
||||
{{formGroup settingFields.schema.fields.resourceScrollTexts value=settingFields._source.resourceScrollTexts localize=true}}
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@
|
|||
</legend>
|
||||
|
||||
{{formGroup settingFields.schema.fields.defeated.fields.enabled value=settingFields._source.defeated.enabled localize=true}}
|
||||
{{formGroup settingFields.schema.fields.defeated.fields.overlay value=settingFields._source.defeated.overlay localize=true}}
|
||||
{{formGroup settingFields.schema.fields.defeated.fields.overlay value=settingFields._source.defeated.overlay localize=true}}
|
||||
{{formGroup settingFields.schema.fields.defeated.fields.characterDefault value=settingFields._source.defeated.characterDefault labelAttr="name" localize=true}}
|
||||
{{formGroup settingFields.schema.fields.defeated.fields.adversaryDefault value=settingFields._source.defeated.adversaryDefault labelAttr="name" localize=true}}
|
||||
{{formGroup settingFields.schema.fields.defeated.fields.companionDefault value=settingFields._source.defeated.companionDefault labelAttr="name" localize=true}}
|
||||
|
||||
{{formGroup settingFields.schema.fields.defeated.fields.deathMoveIcon value=settingFields._source.defeated.deathMoveIcon localize=true}}
|
||||
{{formGroup settingFields.schema.fields.defeated.fields.deadIcon value=settingFields._source.defeated.deadIcon localize=true}}
|
||||
{{formGroup settingFields.schema.fields.defeated.fields.defeatedIcon value=settingFields._source.defeated.defeatedIcon localize=true}}
|
||||
{{formGroup settingFields.schema.fields.defeated.fields.unconsciousIcon value=settingFields._source.defeated.unconsciousIcon localize=true}}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<h1>{{localize 'DAGGERHEART.SETTINGS.Menu.homebrew.name'}}</h1>
|
||||
</header>
|
||||
{{formGroup settingFields.schema.fields.maxFear value=settingFields._source.maxFear localize=true}}
|
||||
{{formGroup settingFields.schema.fields.maxHope value=settingFields._source.maxHope localize=true}}
|
||||
{{formGroup settingFields.schema.fields.maxDomains value=settingFields._source.maxDomains localize=true}}
|
||||
{{formGroup settingFields.schema.fields.maxLoadout value=settingFields._source.maxLoadout localize=true}}
|
||||
<div class="settings-hint"><label>{{localize "DAGGERHEART.SETTINGS.Homebrew.FIELDS.maxLoadout.hint"}}</label></div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,6 @@
|
|||
</fieldset>
|
||||
<fieldset class="action-category">
|
||||
<legend>{{localize "DAGGERHEART.GENERAL.description"}}</legend>
|
||||
{{formInput fields.description value=source.description enriched=source.description name="description" toggled=true }}
|
||||
{{formInput fields.description value=source.description enriched=action.description name="description" toggled=true }}
|
||||
</fieldset>
|
||||
</section>
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
</div>
|
||||
|
||||
<div class="code-mirror-wrapper {{#if trigger.revealed}}revealed{{/if}}">
|
||||
{{formInput @root.fields.triggers.element.fields.command value=trigger.command elementType="code-mirror" name=(concat "triggers." index ".command") aria=(object label=(localize "Test")) }}
|
||||
{{formInput @root.fields.triggers.element.fields.command value=trigger.command elementType="code-mirror" name=(concat "triggers." index ".command") }}
|
||||
</div>
|
||||
</fieldset>
|
||||
{{/each}}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
{{formGroup systemFields.resources.fields.stress.fields.max value=document._source.system.resources.stress.max localize=true}}
|
||||
|
||||
{{formGroup systemFields.resources.fields.hope.fields.value value=document._source.system.resources.hope.value localize=true}}
|
||||
{{formGroup systemFields.resources.fields.hope.fields.max value=document._source.system.resources.hope.max localize=true}}
|
||||
{{formGroup systemFields.scars value=document._source.system.scars localize=true}}
|
||||
|
||||
{{formGroup systemFields.proficiency value=document._source.system.proficiency localize=true}}
|
||||
<span data-tooltip-text="{{localize "DAGGERHEART.UI.Tooltip.maxEvasionClassBound"}}">
|
||||
|
|
|
|||
|
|
@ -76,6 +76,11 @@
|
|||
{{/if}}
|
||||
</span>
|
||||
{{/times}}
|
||||
{{#times document.system.scars}}
|
||||
<span class='hope-value scar'>
|
||||
<i class='fa-regular fa-ban'></i>
|
||||
</span>
|
||||
{{/times}}
|
||||
</div>
|
||||
{{#if document.system.class.value}}
|
||||
<div class="domains-section">
|
||||
|
|
@ -123,9 +128,7 @@
|
|||
{{/each}}
|
||||
</div>
|
||||
|
||||
{{#> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' showSettings=showSettings }}
|
||||
{{#if ../showSettings}}
|
||||
<button type="button" data-action="openSettings" data-tooltip-text="{{localize "DAGGERHEART.UI.Tooltip.openSheetSettings"}}"><i class="fa-solid fa-wrench"></i></button>
|
||||
{{/if}}
|
||||
{{#> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }}
|
||||
<button type="button" data-action="openSettings" data-tooltip-text="{{localize "DAGGERHEART.UI.Tooltip.openSheetSettings"}}"><i class="fa-solid fa-wrench"></i></button>
|
||||
{{/'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}}
|
||||
</header>
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
<aside class="character-sidebar-sheet">
|
||||
<div class="portrait {{#if isDeath}}death-roll{{/if}}">
|
||||
<img src="{{document.img}}" alt="{{document.name}}" data-action='editImage' data-edit="img">
|
||||
{{#if document.system.class.subclass.system.spellcastingTrait}}
|
||||
{{#if document.system.spellcastModifierTrait.key}}
|
||||
<div class="icons-list">
|
||||
<span class="spellcast-icon {{#if isDeath}}no-label{{/if}}">
|
||||
<span class="spellcast-label">
|
||||
{{localize "DAGGERHEART.ITEMS.Subclass.spellcastingTrait"}}:
|
||||
{{localize (concat 'DAGGERHEART.CONFIG.Traits.' document.system.class.subclass.system.spellcastingTrait '.short')}}
|
||||
{{localize (concat 'DAGGERHEART.CONFIG.Traits.' document.system.spellcastModifierTrait.key '.short')}}
|
||||
</span>
|
||||
<i class="fa-solid fa-wand-sparkles"></i>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
<h3 class='label'>
|
||||
{{localize 'DAGGERHEART.GENERAL.level'}}
|
||||
<div class="input-section">
|
||||
{{#if document.system.levelData.canLevelUp}}
|
||||
{{#if document.system.canLevelUp}}
|
||||
<button
|
||||
type="button"
|
||||
class="level-button glow" data-tooltip="{{localize "DAGGERHEART.ACTORS.Character.levelUp"}}"
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
</button>
|
||||
{{/if}}
|
||||
<span {{#unless document.system.partner}}data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.companionPartnerLevelBlock"}}"{{/unless}}>
|
||||
<input type="text" data-dtype="Number" class="level-value" value={{document.system.levelData.level.changed}} {{#if document.system.needsCharacterSetup}}disabled{{/if}} {{disabled (not document.system.partner)}} />
|
||||
{{document.system.levelData.level.changed}}
|
||||
</span>
|
||||
</div>
|
||||
</h3>
|
||||
|
|
|
|||
20
templates/sheets/rollTable/header.hbs
Normal file
20
templates/sheets/rollTable/header.hbs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<header class="sheet-header img-name">
|
||||
<img src="{{source.img}}" data-action="editImage" data-edit="img" alt="{{localize "DOCUMENT.FIELDS.img.label"}}">
|
||||
|
||||
<input type="text" name="name" value="{{source.name}}" placeholder="{{localize "DOCUMENT.FIELDS.name.label"}}" aria-label="{{localize "DOCUMENT.FIELDS.name.label"}}">
|
||||
{{#if usesAltFormula}}
|
||||
<div class="form-group">
|
||||
<label>{{localize "Formula"}}</label>
|
||||
<div class="form-fields">
|
||||
<select class="system-update-field" data-path="activeAltFormula">
|
||||
{{selectOptions this.altFormulaOptions selected=this.activeAltFormula labelAttr="name"}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<button data-action="changeMode">
|
||||
<i class="fa-solid fa-eye" inert></i>
|
||||
<span>{{localize "TABLE.ACTIONS.ChangeMode.View"}}</span>
|
||||
</button>
|
||||
</header>
|
||||
55
templates/sheets/rollTable/results.hbs
Normal file
55
templates/sheets/rollTable/results.hbs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<section class="tab{{#if tab.active}} active{{/if}}" data-group="{{tab.group}}" data-tab="{{tab.id}}">
|
||||
<table class="flexcol" data-results>
|
||||
<thead>
|
||||
<tr class="flexrow">
|
||||
<th class="image flexrow">
|
||||
<button class="inline-control icon fa-solid fa-plus" data-action="createResult"
|
||||
data-tooltip aria-label="{{localize "TABLE.ACTIONS.CreateResult"}}"></button>
|
||||
</th>
|
||||
<th class="details flexrow">{{localize "TABLE_RESULT.Details"}}</th>
|
||||
<th class="weight flexrow">{{localize "TABLE_RESULT.FIELDS.weight.label"}}</th>
|
||||
<th class="range flexrow">{{localize "TABLE_RESULT.FIELDS.range.label"}}</th>
|
||||
<th class="controls flexrow">
|
||||
<button class="inline-control icon fa-solid fa-scale-balanced" data-action="normalizeResults"
|
||||
data-tooltip aria-label="{{localize "TABLE.ACTIONS.NormalizeResults"}}"></button>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="scrollable">
|
||||
{{#each results as |result i|}}
|
||||
<tr class="flexrow{{#if result.drawn}} drawn{{/if}}" data-result-id="{{result.id}}">
|
||||
<td class="image flexrow">
|
||||
<img src="{{result.displayImg}}" data-action="editImage" data-edit="results.{{i}}.img"
|
||||
alt="{{localize "TABLE_RESULT.FIELDS.img.label"}}" loading="lazy">
|
||||
</td>
|
||||
|
||||
<td class="details">
|
||||
{{> "templates/sheets/roll-table/result-details.hbs" result=result}}
|
||||
</td>
|
||||
|
||||
<td class="weight flexrow">
|
||||
<input type="number" name="results.{{i}}.weight" value="{{result.weight}}" placeholder="1">
|
||||
</td>
|
||||
|
||||
<td class="range flexrow">
|
||||
<input type="number" name="results.{{i}}.range.0" value="{{result.range.[0]}}" placeholder="L">
|
||||
<span class="dash">–</span>
|
||||
<input type="number" name="results.{{i}}.range.1" value="{{result.range.[1]}}" placeholder="H">
|
||||
</td>
|
||||
|
||||
<td class="controls flexrow">
|
||||
<button class="inline-control icon fa-solid fa-file-pen" data-action="openResultSheet"
|
||||
data-tooltip aria-label="{{localize "TABLE.ACTIONS.OpenResultConfig"}}"></button>
|
||||
<button class="inline-control icon fa-solid fa-lock{{#unless result.drawn}}-open{{/unless}}"
|
||||
data-action="lockResult"
|
||||
data-tooltip aria-label="{{localize "TABLE.ACTIONS.ToggleDrawn"}}"></button>
|
||||
<button class="inline-control icon fa-solid fa-trash" data-action="deleteResult"
|
||||
data-tooltip aria-label="{{localize "TABLE.ACTIONS.DeleteResult"}}"></button>
|
||||
</td>
|
||||
|
||||
<input type="hidden" name="results.{{i}}._id" value="{{result.id}}">
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
49
templates/sheets/rollTable/sheet.hbs
Normal file
49
templates/sheets/rollTable/sheet.hbs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<header class="sheet-header flexrow">
|
||||
<img src="{{document.img}}" alt="{{localize "DOCUMENT.FIELDS.img.label"}}">
|
||||
<h1>{{document.name}}</h1>
|
||||
<div class="roll-table-view-formula-container">
|
||||
{{#if usesAltFormula}}
|
||||
<select class="system-update-field" data-path="activeAltFormula">
|
||||
{{selectOptions this.altFormulaOptions selected=this.activeAltFormula labelAttr="name"}}
|
||||
</select>
|
||||
{{/if}}
|
||||
<h4>{{selectedFormula}}</h4>
|
||||
</div>
|
||||
<button data-action="changeMode">
|
||||
<i class="fa-solid fa-pen" inert></i>
|
||||
<span>{{localize "TABLE.ACTIONS.ChangeMode.Edit"}}</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{{{descriptionHTML}}}
|
||||
|
||||
<table class="flexcol" data-results>
|
||||
<thead>
|
||||
<tr class="flexrow">
|
||||
<th class="image flexrow"></th>
|
||||
<th class="range flexrow">{{localize "TABLE_RESULT.FIELDS.range.label"}}</th>
|
||||
<th class="details flexrow">{{localize "TABLE_RESULT.Details"}}</th>
|
||||
<th class="controls flexrow"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="scrollable">
|
||||
{{#each results as |result i|}}
|
||||
<tr class="flexrow{{#if result.drawn}} drawn{{/if}}" data-result-id="{{result.id}}">
|
||||
<td class="image">
|
||||
<img src="{{result.displayImg}}" alt="{{localize "TABLE_RESULT.FIELDS.img.label"}}" loading="lazy">
|
||||
</td>
|
||||
<td class="range">{{result.range}}</td>
|
||||
<td class="details">
|
||||
{{> "templates/sheets/roll-table/result-details.hbs" result=result}}
|
||||
</td>
|
||||
<td class="controls flexrow">
|
||||
<button class="inline-control icon fa-solid fa-lock{{#unless result.drawn}}-open{{/unless}}"
|
||||
data-action="lockResult"
|
||||
data-tooltip aria-label="{{localize "TABLE.ACTIONS.ToggleDrawn"}}"></button>
|
||||
<button class="inline-control icon fa-solid fa-up-from-bracket" data-action="drawSpecificResult"
|
||||
data-tooltip aria-label="{{localize "TABLE.ACTIONS.DrawSpecificResult"}}"></button>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
22
templates/sheets/rollTable/summary.hbs
Normal file
22
templates/sheets/rollTable/summary.hbs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<section class="tab{{#if tab.active}} active{{/if}}" data-group="{{tab.group}}" data-tab="{{tab.id}}">
|
||||
{{formGroup fields.description value=source.description rootId=rootId}}
|
||||
<fieldset class="formulas-section">
|
||||
<legend>{{localize "DAGGERHEART.ROLLTABLES.formula"}}</legend>
|
||||
|
||||
<div class="formulas-container">
|
||||
<span>{{localize "DAGGERHEART.ROLLTABLES.FIELDS.formulaName.label"}}</span>
|
||||
<span>{{localize "Formula Roll"}}</span>
|
||||
<span></span>
|
||||
<input type="text" value="{{@root.formulaName}}" class="system-update-field" data-path="formulaName" />
|
||||
{{formInput fields.formula value=source.formula placeholder=formulaPlaceholder rootId=rootId}}
|
||||
<button class="formula-button" data-action="addFormula"><i class="fa-solid fa-plus"></i></button>
|
||||
{{#each @root.altFormula as | formula key |}}
|
||||
<input type="text" value="{{formula.name}}" class="system-update-field" data-path="{{concat "altFormula." key ".name"}}" />
|
||||
<input type="text" value="{{formula.formula}}" class="system-update-field" data-path="{{concat "altFormula." key ".formula"}}" />
|
||||
<a class="formula-button" data-action="removeFormula" data-key="{{key}}"><i class="fa-solid fa-trash"></i></a>
|
||||
{{/each}}
|
||||
</div>
|
||||
</fieldset>
|
||||
{{formGroup fields.replacement value=source.replacement rootId=rootId}}
|
||||
{{formGroup fields.displayRoll value=source.displayRoll rootId=rootId}}
|
||||
</section>
|
||||
|
|
@ -1,17 +1,32 @@
|
|||
<div class="daggerheart chat downtime">
|
||||
<ul class="downtime-moves-list">
|
||||
<details class="downtime-move">
|
||||
<summary class="downtime-label">
|
||||
<img class="downtime-image" src="{{this.img}}" />
|
||||
<div class="daggerheart chat death-moves">
|
||||
<ul class="death-moves-list">
|
||||
<details class="death-move" {{this.open}}>
|
||||
<summary class="death-label">
|
||||
<img class="death-image" src="{{this.img}}" />
|
||||
<div class="header-label">
|
||||
<h2 class="title">{{this.title}}</h2>
|
||||
<span class="label">{{localize 'DAGGERHEART.UI.Chat.deathMove.title'}}</span>
|
||||
</div>
|
||||
<i class="fa-solid fa-chevron-down"></i>
|
||||
<i class="fa-solid {{this.chevron}}"></i>
|
||||
</summary>
|
||||
<div class="description">
|
||||
{{{this.description}}}
|
||||
</div>
|
||||
</details>
|
||||
</ul>
|
||||
<div class="result">
|
||||
{{{this.result}}}
|
||||
</div>
|
||||
{{#if this.showRiskItAllButton}}
|
||||
<div>
|
||||
<button class="risk-it-all-button" data-resource-value="{{this.riskItAllHope}}" data-actor-id="{{this.actorId}}">
|
||||
<span>
|
||||
{{this.riskItAllButtonLabel}}
|
||||
</span>
|
||||
</button>
|
||||
<div>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -29,64 +29,86 @@
|
|||
<div class="dice-tooltip">
|
||||
<div class="wrapper">
|
||||
<div class="roll-dice">
|
||||
{{#if roll.hope}}
|
||||
<div class="roll-die">
|
||||
<label>{{localize "DAGGERHEART.GENERAL.hope"}}</label>
|
||||
<div class="dice {{roll.hope.dice}} color-hope reroll-button" data-die-index="0" data-type="hope" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerollThing" thing=(localize "DAGGERHEART.GENERAL.hope")}}">
|
||||
{{#if roll.hope.rerolled.any}}<i class="fa-solid fa-dice dice-rerolled" data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.diceIsRerolled" times=roll.hope.rerolled.rerolls.length}}"></i>{{/if}}
|
||||
{{roll.hope.value}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="roll-die has-plus">
|
||||
<label>{{localize "DAGGERHEART.GENERAL.fear"}}</label>
|
||||
<div class="dice {{roll.fear.dice}} color-fear reroll-button" data-die-index="2" data-type="fear" style="--svg-folder: 'fear';" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerollThing" thing=(localize "DAGGERHEART.GENERAL.fear")}}">
|
||||
{{#if roll.fear.rerolled.any}}<i class="fa-solid fa-dice dice-rerolled" data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.diceIsRerolled" times=roll.fear.rerolled.rerolls.length}}"></i>{{/if}}
|
||||
{{roll.fear.value}}
|
||||
</div>
|
||||
</div>
|
||||
{{#if roll.advantage.type}}
|
||||
<div class="roll-die has-plus">
|
||||
{{#if (eq roll.advantage.type 1)}}
|
||||
<label>{{localize "DAGGERHEART.GENERAL.Advantage.short"}}</label>
|
||||
<div class="dice {{roll.advantage.dice}} color-adv">{{roll.advantage.value}}</div>
|
||||
{{else}}
|
||||
<label>{{localize "DAGGERHEART.GENERAL.Disadvantage.short"}}</label>
|
||||
<div class="dice {{roll.advantage.dice}} color-dis">{{roll.advantage.value}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if roll.rally.dice}}
|
||||
<div class="roll-die has-plus">
|
||||
<label>{{localize "DAGGERHEART.CLASS.Feature.short"}}</label>
|
||||
<div class="dice {{roll.rally.dice}}">{{roll.rally.value}}</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#each roll.extra}}
|
||||
{{#each results}}
|
||||
{{#unless discarded}}
|
||||
<div class="roll-die has-plus">
|
||||
<label></label>
|
||||
<div class="dice {{../dice}}">{{result}}</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
{{#each roll.dice}}
|
||||
{{#each results}}
|
||||
<div class="roll-die {{#unless (or @../first discarded)}} has-plus{{/unless}}">
|
||||
<div class="dice {{../dice}}{{#if discarded}} discarded{{else}}{{#if (and @../first ../../roll.advantage.type)}}{{#if (eq ../../roll.advantage.type 1)}} color-adv{{else}} color-dis{{/if}}{{/if}}{{#if success}} color-adv{{/if}}{{/if}}">
|
||||
{{result}}
|
||||
</div>
|
||||
{{#if roll.fate}}
|
||||
{{#if (eq roll.fate.fateDie "Hope")}}
|
||||
<div class="roll-die">
|
||||
<label>{{localize "DAGGERHEART.GENERAL.hope"}}</label>
|
||||
<div class="dice {{roll.fate.dice}} color-hope" data-die-index="0" data-type="hope">
|
||||
{{roll.fate.value}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if (eq roll.fate.fateDie "Fear")}}
|
||||
<div class="roll-die">
|
||||
<label>{{localize "DAGGERHEART.GENERAL.fear"}}</label>
|
||||
<div class="dice {{roll.fate.dice}} color-fear" data-die-index="0" data-type="fear">
|
||||
{{roll.fate.value}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if roll.hope}}
|
||||
<div class="roll-die">
|
||||
<label>{{localize "DAGGERHEART.GENERAL.hope"}}</label>
|
||||
<div class="dice {{roll.hope.dice}} color-hope reroll-button" data-die-index="0" data-type="hope" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerollThing" thing=(localize "DAGGERHEART.GENERAL.hope")}}">
|
||||
{{#if roll.hope.rerolled.any}}<i class="fa-solid fa-dice dice-rerolled" data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.diceIsRerolled" times=roll.hope.rerolled.rerolls.length}}"></i>{{/if}}
|
||||
{{roll.hope.value}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="roll-die has-plus">
|
||||
<label>{{localize "DAGGERHEART.GENERAL.fear"}}</label>
|
||||
<div class="dice {{roll.fear.dice}} color-fear reroll-button" data-die-index="2" data-type="fear" style="--svg-folder: 'fear';" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerollThing" thing=(localize "DAGGERHEART.GENERAL.fear")}}">
|
||||
{{#if roll.fear.rerolled.any}}<i class="fa-solid fa-dice dice-rerolled" data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.diceIsRerolled" times=roll.fear.rerolled.rerolls.length}}"></i>{{/if}}
|
||||
{{roll.fear.value}}
|
||||
</div>
|
||||
</div>
|
||||
{{#if roll.advantage.type}}
|
||||
<div class="roll-die has-plus">
|
||||
{{#if (eq roll.advantage.type 1)}}
|
||||
<label>{{localize "DAGGERHEART.GENERAL.Advantage.short"}}</label>
|
||||
<div class="dice {{roll.advantage.dice}} color-adv">{{roll.advantage.value}}</div>
|
||||
{{else}}
|
||||
<label>{{localize "DAGGERHEART.GENERAL.Disadvantage.short"}}</label>
|
||||
<div class="dice {{roll.advantage.dice}} color-dis">{{roll.advantage.value}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if roll.rally.dice}}
|
||||
<div class="roll-die has-plus">
|
||||
<label>{{localize "DAGGERHEART.CLASS.Feature.short"}}</label>
|
||||
<div class="dice {{roll.rally.dice}}">{{roll.rally.value}}</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#each roll.extra}}
|
||||
{{#each results}}
|
||||
{{#unless discarded}}
|
||||
<div class="roll-die has-plus">
|
||||
<label></label>
|
||||
<div class="dice {{../dice}}">{{result}}</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
{{#each roll.dice}}
|
||||
{{#each results}}
|
||||
<div class="roll-die {{#unless (or @../first discarded)}} has-plus{{/unless}}">
|
||||
<div class="dice {{../dice}}{{#if discarded}} discarded{{else}}{{#if (and @../first ../../roll.advantage.type)}}{{#if (eq ../../roll.advantage.type 1)}} color-adv{{else}} color-dis{{/if}}{{/if}}{{#if success}} color-adv{{/if}}{{/if}}">
|
||||
{{result}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="roll-formula">{{roll.formula}}</div>
|
||||
{{#if roll.fate}}
|
||||
{{else}}
|
||||
<div class="roll-formula">{{roll.formula}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
17
templates/ui/chat/table-result.hbs
Normal file
17
templates/ui/chat/table-result.hbs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<div class="table-draw" data-table-id="{{table.id}}">
|
||||
{{#if flavor}}<div class="table-flavor">{{flavor}}</div>{{/if}}
|
||||
|
||||
{{#if description}}
|
||||
<div class="table-description {{#if flavor}}flavor-spaced{{/if}}">{{{description}}}</div>
|
||||
{{/if}}
|
||||
{{{rollHTML}}}
|
||||
|
||||
<ul class="table-results">
|
||||
{{#each results as |result|}}
|
||||
<li class="flexrow" data-result-id="{{result.id}}">
|
||||
<img src="{{result.icon}}">
|
||||
{{{result.details}}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue