mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-03-07 14:36:13 +01:00
Merged with development
This commit is contained in:
commit
4944d1ef49
161 changed files with 2733 additions and 694 deletions
|
|
@ -16,9 +16,10 @@ import {
|
||||||
settingsRegistration,
|
settingsRegistration,
|
||||||
socketRegistration
|
socketRegistration
|
||||||
} from './module/systemRegistration/_module.mjs';
|
} from './module/systemRegistration/_module.mjs';
|
||||||
import { placeables } from './module/canvas/_module.mjs';
|
import { placeables, DhTokenLayer } from './module/canvas/_module.mjs';
|
||||||
import './node_modules/@yaireo/tagify/dist/tagify.css';
|
import './node_modules/@yaireo/tagify/dist/tagify.css';
|
||||||
import TemplateManager from './module/documents/templateManager.mjs';
|
import TemplateManager from './module/documents/templateManager.mjs';
|
||||||
|
import TokenManager from './module/documents/tokenManager.mjs';
|
||||||
|
|
||||||
CONFIG.DH = SYSTEM;
|
CONFIG.DH = SYSTEM;
|
||||||
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
||||||
|
|
@ -51,6 +52,8 @@ CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-messag
|
||||||
|
|
||||||
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
||||||
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
||||||
|
CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer;
|
||||||
|
|
||||||
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
||||||
|
|
||||||
CONFIG.RollTable.documentClass = documents.DhRollTable;
|
CONFIG.RollTable.documentClass = documents.DhRollTable;
|
||||||
|
|
@ -64,6 +67,7 @@ CONFIG.Token.rulerClass = placeables.DhTokenRuler;
|
||||||
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
|
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
|
||||||
|
|
||||||
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
||||||
|
CONFIG.ui.nav = applications.ui.DhSceneNavigation;
|
||||||
CONFIG.ui.chat = applications.ui.DhChatLog;
|
CONFIG.ui.chat = applications.ui.DhChatLog;
|
||||||
CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay;
|
CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay;
|
||||||
CONFIG.ui.hotbar = applications.ui.DhHotbar;
|
CONFIG.ui.hotbar = applications.ui.DhHotbar;
|
||||||
|
|
@ -75,6 +79,8 @@ CONFIG.ui.countdowns = applications.ui.DhCountdowns;
|
||||||
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
|
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
|
||||||
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
|
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
|
||||||
CONFIG.ux.TemplateManager = new TemplateManager();
|
CONFIG.ux.TemplateManager = new TemplateManager();
|
||||||
|
CONFIG.ux.TokenManager = new TokenManager();
|
||||||
|
CONFIG.debug.triggers = false;
|
||||||
|
|
||||||
Hooks.once('init', () => {
|
Hooks.once('init', () => {
|
||||||
game.system.api = {
|
game.system.api = {
|
||||||
|
|
@ -86,6 +92,8 @@ Hooks.once('init', () => {
|
||||||
fields
|
fields
|
||||||
};
|
};
|
||||||
|
|
||||||
|
game.system.registeredTriggers = new game.system.api.data.RegisteredTriggers();
|
||||||
|
|
||||||
const { DocumentSheetConfig } = foundry.applications.apps;
|
const { DocumentSheetConfig } = foundry.applications.apps;
|
||||||
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
|
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
|
||||||
DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, {
|
DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, {
|
||||||
|
|
@ -310,7 +318,7 @@ Hooks.on('chatMessage', (_, message) => {
|
||||||
target,
|
target,
|
||||||
difficulty,
|
difficulty,
|
||||||
title,
|
title,
|
||||||
label: 'test',
|
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'),
|
||||||
actionType: null,
|
actionType: null,
|
||||||
advantage
|
advantage
|
||||||
});
|
});
|
||||||
|
|
@ -359,7 +367,9 @@ const updateAllRangeDependentEffects = async () => {
|
||||||
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
|
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
|
||||||
if (!effectsAutomation.rangeDependent) return;
|
if (!effectsAutomation.rangeDependent) return;
|
||||||
|
|
||||||
const tokens = canvas.scene.tokens;
|
const tokens = canvas.scene?.tokens;
|
||||||
|
if (!tokens) return;
|
||||||
|
|
||||||
if (game.user.character) {
|
if (game.user.character) {
|
||||||
// The character updates their character's token. There can be only one token.
|
// The character updates their character's token. There can be only one token.
|
||||||
const characterToken = tokens.find(x => x.actor === game.user.character);
|
const characterToken = tokens.find(x => x.actor === game.user.character);
|
||||||
|
|
@ -387,3 +397,13 @@ Hooks.on('refreshToken', (_, options) => {
|
||||||
|
|
||||||
Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
|
Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
|
||||||
Hooks.on('renderDocumentDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
|
Hooks.on('renderDocumentDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
|
||||||
|
|
||||||
|
/* Non actor-linked Actors should unregister the triggers of their tokens if a scene's token layer is torn down */
|
||||||
|
Hooks.on('canvasTearDown', canvas => {
|
||||||
|
game.system.registeredTriggers.unregisterSceneTriggers(canvas.scene);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Non actor-linked Actors should register the triggers of their tokens on a readied scene */
|
||||||
|
Hooks.on('canvasReady', canas => {
|
||||||
|
game.system.registeredTriggers.registerSceneTriggers(canvas.scene);
|
||||||
|
});
|
||||||
|
|
|
||||||
89
lang/en.json
89
lang/en.json
|
|
@ -69,7 +69,11 @@
|
||||||
},
|
},
|
||||||
"summon": {
|
"summon": {
|
||||||
"name": "Summon",
|
"name": "Summon",
|
||||||
"tooltip": "Create tokens in the scene."
|
"tooltip": "Create tokens in the scene.",
|
||||||
|
"error": "You do not have permission to summon tokens or there is no active scene.",
|
||||||
|
"invalidDrop": "You can only drop Actor entities to summon.",
|
||||||
|
"chatMessageTitle": "Test2",
|
||||||
|
"chatMessageHeaderTitle": "Summoning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Config": {
|
"Config": {
|
||||||
|
|
@ -90,7 +94,9 @@
|
||||||
"customFormula": "Custom Formula",
|
"customFormula": "Custom Formula",
|
||||||
"formula": "Formula"
|
"formula": "Formula"
|
||||||
},
|
},
|
||||||
"displayInChat": "Display in chat"
|
"displayInChat": "Display in chat",
|
||||||
|
"deleteTriggerTitle": "Delete Trigger",
|
||||||
|
"deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?"
|
||||||
},
|
},
|
||||||
"RollField": {
|
"RollField": {
|
||||||
"diceRolling": {
|
"diceRolling": {
|
||||||
|
|
@ -120,6 +126,9 @@
|
||||||
},
|
},
|
||||||
"cost": {
|
"cost": {
|
||||||
"stepTooltip": "+{step} per step"
|
"stepTooltip": "+{step} per step"
|
||||||
|
},
|
||||||
|
"summon": {
|
||||||
|
"dropSummonsHere": "Drop Summons Here"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -194,6 +203,8 @@
|
||||||
"unequip": "Unequip",
|
"unequip": "Unequip",
|
||||||
"useItem": "Use Item"
|
"useItem": "Use Item"
|
||||||
},
|
},
|
||||||
|
"defaultHopeDice": "Default Hope Dice",
|
||||||
|
"defaultFearDice": "Default Fear Dice",
|
||||||
"disadvantageSources": {
|
"disadvantageSources": {
|
||||||
"label": "Disadvantage Sources",
|
"label": "Disadvantage Sources",
|
||||||
"hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
|
"hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
|
||||||
|
|
@ -226,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)"
|
"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",
|
"viewLevelups": "View Levelups",
|
||||||
|
"resetCharacter": "Reset Character",
|
||||||
"viewParty": "View Party",
|
"viewParty": "View Party",
|
||||||
"InvalidOldCharacterImportTitle": "Old Character Import",
|
"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?",
|
"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": {
|
"Companion": {
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
|
|
@ -303,6 +317,8 @@
|
||||||
"selectPrimaryWeapon": "Select Primary Weapon",
|
"selectPrimaryWeapon": "Select Primary Weapon",
|
||||||
"selectSecondaryWeapon": "Select Secondary Weapon",
|
"selectSecondaryWeapon": "Select Secondary Weapon",
|
||||||
"selectSubclass": "Select Subclass",
|
"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",
|
"startingItems": "Starting Items",
|
||||||
"story": "Story",
|
"story": "Story",
|
||||||
"storyExplanation": "Select which background and connection prompts you want to copy into your character's background.",
|
"storyExplanation": "Select which background and connection prompts you want to copy into your character's background.",
|
||||||
|
|
@ -1144,7 +1160,8 @@
|
||||||
"any": "Any",
|
"any": "Any",
|
||||||
"friendly": "Friendly",
|
"friendly": "Friendly",
|
||||||
"hostile": "Hostile",
|
"hostile": "Hostile",
|
||||||
"self": "Self"
|
"self": "Self",
|
||||||
|
"other": "Other"
|
||||||
},
|
},
|
||||||
"TemplateTypes": {
|
"TemplateTypes": {
|
||||||
"circle": "Circle",
|
"circle": "Circle",
|
||||||
|
|
@ -1218,6 +1235,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Triggers": {
|
||||||
|
"postDamageReduction": {
|
||||||
|
"label": "After Damage Reduction"
|
||||||
|
},
|
||||||
|
"preDamageReduction": {
|
||||||
|
"label": "Before Damage Reduction"
|
||||||
|
},
|
||||||
|
"dualityRoll": {
|
||||||
|
"label": "Duality Roll"
|
||||||
|
},
|
||||||
|
"fearRoll": {
|
||||||
|
"label": "Fear Roll"
|
||||||
|
},
|
||||||
|
"triggerTexts": {
|
||||||
|
"strangePatternsContentTitle": "Matched {nr} times.",
|
||||||
|
"strangePatternsContentSubTitle": "Increase hope and stress to a total of {nr}.",
|
||||||
|
"ferocityContent": "Spend 2 Hope to gain {bonus} bonus Evasion until after the next attack against you?",
|
||||||
|
"ferocityEffectDescription": "Your evasion is increased by {bonus}. This bonus lasts until after the next attack made against you."
|
||||||
|
},
|
||||||
|
"triggerType": "Trigger Type",
|
||||||
|
"triggeringActorType": "Triggering Actor Type",
|
||||||
|
"triggerError": "{trigger} trigger failed for {actor}. It's probably configured wrong."
|
||||||
|
},
|
||||||
"WeaponFeature": {
|
"WeaponFeature": {
|
||||||
"barrier": {
|
"barrier": {
|
||||||
"name": "Barrier",
|
"name": "Barrier",
|
||||||
|
|
@ -2050,15 +2090,16 @@
|
||||||
"tier4": "tier 4",
|
"tier4": "tier 4",
|
||||||
"domains": "Domains",
|
"domains": "Domains",
|
||||||
"downtime": "Downtime",
|
"downtime": "Downtime",
|
||||||
|
"itemFeatures": "Item Features",
|
||||||
"roll": "Roll",
|
"roll": "Roll",
|
||||||
"rules": "Rules",
|
"rules": "Rules",
|
||||||
"partyMembers": "Party Members",
|
"partyMembers": "Party Members",
|
||||||
"projects": "Projects",
|
"projects": "Projects",
|
||||||
"types": "Types",
|
"types": "Types",
|
||||||
"itemFeatures": "Item Features",
|
|
||||||
"questions": "Questions",
|
"questions": "Questions",
|
||||||
"configuration": "Configuration",
|
"configuration": "Configuration",
|
||||||
"base": "Base"
|
"base": "Base",
|
||||||
|
"triggers": "Triggers"
|
||||||
},
|
},
|
||||||
"Tiers": {
|
"Tiers": {
|
||||||
"singular": "Tier",
|
"singular": "Tier",
|
||||||
|
|
@ -2179,6 +2220,10 @@
|
||||||
"stress": "Stress",
|
"stress": "Stress",
|
||||||
"subclasses": "Subclasses",
|
"subclasses": "Subclasses",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
|
"summon": {
|
||||||
|
"single": "Summon",
|
||||||
|
"plural": "Summons"
|
||||||
|
},
|
||||||
"take": "Take",
|
"take": "Take",
|
||||||
"Target": {
|
"Target": {
|
||||||
"single": "Target",
|
"single": "Target",
|
||||||
|
|
@ -2245,7 +2290,8 @@
|
||||||
"placeholder": "Using character dimensions",
|
"placeholder": "Using character dimensions",
|
||||||
"disabledPlaceholder": "Set by character size",
|
"disabledPlaceholder": "Set by character size",
|
||||||
"height": { "label": "Height" },
|
"height": { "label": "Height" },
|
||||||
"width": { "label": "Width" }
|
"width": { "label": "Width" },
|
||||||
|
"scale": { "label": "Token Scale" }
|
||||||
},
|
},
|
||||||
"evolved": {
|
"evolved": {
|
||||||
"maximumTier": { "label": "Maximum Tier" },
|
"maximumTier": { "label": "Maximum Tier" },
|
||||||
|
|
@ -2294,6 +2340,9 @@
|
||||||
"DomainCard": {
|
"DomainCard": {
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"recallCost": "Recall Cost",
|
"recallCost": "Recall Cost",
|
||||||
|
"vaultActive": "Active In Vault",
|
||||||
|
"loadoutIgnore": "Ignores Loadout Limits",
|
||||||
|
"domainTouched": "Domain Touched",
|
||||||
"foundationTitle": "Foundation",
|
"foundationTitle": "Foundation",
|
||||||
"specializationTitle": "Specialization",
|
"specializationTitle": "Specialization",
|
||||||
"masteryTitle": "Mastery"
|
"masteryTitle": "Mastery"
|
||||||
|
|
@ -2412,10 +2461,6 @@
|
||||||
"label": "Show Resource Change Scrolltexts",
|
"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."
|
"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": {
|
||||||
"roll": {
|
"roll": {
|
||||||
"label": "Roll",
|
"label": "Roll",
|
||||||
|
|
@ -2438,6 +2483,12 @@
|
||||||
"hint": "Automatically apply effects. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions."
|
"hint": "Automatically apply effects. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"triggers": {
|
||||||
|
"enabled": {
|
||||||
|
"label": "Enabled",
|
||||||
|
"hint": "Advanced automation such as triggering a popup for a wizard's Strange Patterns"
|
||||||
|
}
|
||||||
|
},
|
||||||
"summaryMessages": {
|
"summaryMessages": {
|
||||||
"label": "Summary Messages"
|
"label": "Summary Messages"
|
||||||
}
|
}
|
||||||
|
|
@ -2447,6 +2498,9 @@
|
||||||
},
|
},
|
||||||
"roll": {
|
"roll": {
|
||||||
"title": "Actions"
|
"title": "Actions"
|
||||||
|
},
|
||||||
|
"trigger": {
|
||||||
|
"title": "Triggers"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Homebrew": {
|
"Homebrew": {
|
||||||
|
|
@ -2574,7 +2628,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"disabledText": "Daggerheart Measurements are disabled in System Settings - Variant Rules",
|
"disabledText": "Daggerheart Measurements are disabled in System Settings - Variant Rules",
|
||||||
"rangeMeasurement": "Range Measurement"
|
"rangeMeasurement": "Range Measurement",
|
||||||
|
"sceneEnvironments": "Scene Environments",
|
||||||
|
"dragEnvironmentHere": "Drag environments here"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"UI": {
|
"UI": {
|
||||||
|
|
@ -2661,6 +2717,9 @@
|
||||||
"rerollDamage": "Reroll Damage",
|
"rerollDamage": "Reroll Damage",
|
||||||
"assignTagRoll": "Assign as Tag Roll"
|
"assignTagRoll": "Assign as Tag Roll"
|
||||||
},
|
},
|
||||||
|
"ConsoleLogs": {
|
||||||
|
"triggerRun": "DH TRIGGER | Item '{item}' on actor '{actor}' ran a '{trigger}' trigger."
|
||||||
|
},
|
||||||
"Countdowns": {
|
"Countdowns": {
|
||||||
"title": "Countdowns",
|
"title": "Countdowns",
|
||||||
"toggleIconMode": "Toggle Icon Only",
|
"toggleIconMode": "Toggle Icon Only",
|
||||||
|
|
@ -2789,7 +2848,8 @@
|
||||||
"noActorOwnership": "You do not have permissions for this character",
|
"noActorOwnership": "You do not have permissions for this character",
|
||||||
"documentIsMissing": "The {documentType} is missing from the world.",
|
"documentIsMissing": "The {documentType} is missing from the world.",
|
||||||
"tokenActorMissing": "{name} is missing an Actor",
|
"tokenActorMissing": "{name} is missing an Actor",
|
||||||
"tokenActorsMissing": "[{names}] missing Actors"
|
"tokenActorsMissing": "[{names}] missing Actors",
|
||||||
|
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used"
|
||||||
},
|
},
|
||||||
"Sidebar": {
|
"Sidebar": {
|
||||||
"actorDirectory": {
|
"actorDirectory": {
|
||||||
|
|
@ -2834,7 +2894,8 @@
|
||||||
"deleteItem": "Delete Item",
|
"deleteItem": "Delete Item",
|
||||||
"immune": "Immune",
|
"immune": "Immune",
|
||||||
"middleClick": "[Middle Click] Keep tooltip view",
|
"middleClick": "[Middle Click] Keep tooltip view",
|
||||||
"tokenSize": "The token size used on the canvas"
|
"tokenSize": "The token size used on the canvas",
|
||||||
|
"previewTokenHelp": "Left-click to place, right-click to cancel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.config.experiences = [];
|
this.config.experiences = [];
|
||||||
this.reactionOverride = config.actionType === 'reaction';
|
this.reactionOverride = config.actionType === 'reaction';
|
||||||
|
this.selectedEffects = this.config.bonusEffects;
|
||||||
|
|
||||||
if (config.source?.action) {
|
if (config.source?.action) {
|
||||||
this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent;
|
this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent;
|
||||||
|
|
@ -35,6 +36,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
selectExperience: this.selectExperience,
|
selectExperience: this.selectExperience,
|
||||||
toggleReaction: this.toggleReaction,
|
toggleReaction: this.toggleReaction,
|
||||||
toggleTagTeamRoll: this.toggleTagTeamRoll,
|
toggleTagTeamRoll: this.toggleTagTeamRoll,
|
||||||
|
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||||
submitRoll: this.submitRoll
|
submitRoll: this.submitRoll
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
|
@ -76,6 +78,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
icon
|
icon
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
context.hasSelectedEffects = Boolean(this.selectedEffects && Object.keys(this.selectedEffects).length);
|
||||||
|
context.selectedEffects = this.selectedEffects;
|
||||||
|
|
||||||
this.config.costs ??= [];
|
this.config.costs ??= [];
|
||||||
if (this.config.costs?.length) {
|
if (this.config.costs?.length) {
|
||||||
const updatedCosts = game.system.api.fields.ActionFields.CostField.calcCosts.call(
|
const updatedCosts = game.system.api.fields.ActionFields.CostField.calcCosts.call(
|
||||||
|
|
@ -118,7 +123,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
context.formula = this.roll.constructFormula(this.config);
|
||||||
if (this.actor?.system?.traits) context.abilities = this.getTraitModifiers();
|
if (this.actor?.system?.traits) context.abilities = this.getTraitModifiers();
|
||||||
|
|
||||||
context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll';
|
context.showReaction = !this.config.roll?.type || context.rollType === 'DualityRoll';
|
||||||
context.reactionOverride = this.reactionOverride;
|
context.reactionOverride = this.reactionOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,6 +213,11 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static toggleSelectedEffect(_event, button) {
|
||||||
|
this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
static async submitRoll() {
|
static async submitRoll() {
|
||||||
await this.close({ submitted: true });
|
await this.close({ submitted: true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
|
|
||||||
this.roll = roll;
|
this.roll = roll;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
this.selectedEffects = this.config.bonusEffects;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
|
|
@ -20,6 +21,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
icon: 'fa-solid fa-dice'
|
icon: 'fa-solid fa-dice'
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||||
submitRoll: this.submitRoll
|
submitRoll: this.submitRoll
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
|
@ -57,6 +59,9 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
icon
|
icon
|
||||||
}));
|
}));
|
||||||
context.modifiers = this.config.modifiers;
|
context.modifiers = this.config.modifiers;
|
||||||
|
context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length);
|
||||||
|
context.selectedEffects = this.selectedEffects;
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,6 +74,11 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static toggleSelectedEffect(_event, button) {
|
||||||
|
this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
static async submitRoll() {
|
static async submitRoll() {
|
||||||
await this.close({ submitted: true });
|
await this.close({ submitted: true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,27 +93,29 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
}
|
}
|
||||||
|
|
||||||
getRefreshables() {
|
getRefreshables() {
|
||||||
const actionItems = this.actor.items.filter(x => this.actor.system.isItemAvailable(x)).reduce((acc, x) => {
|
const actionItems = this.actor.items
|
||||||
if (x.system.actions) {
|
.filter(x => this.actor.system.isItemAvailable(x))
|
||||||
const recoverable = x.system.actions.reduce((acc, action) => {
|
.reduce((acc, x) => {
|
||||||
if (refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], action.uses.recovery)) {
|
if (x.system.actions) {
|
||||||
acc.push({
|
const recoverable = x.system.actions.reduce((acc, action) => {
|
||||||
title: x.name,
|
if (refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], action.uses.recovery)) {
|
||||||
name: action.name,
|
acc.push({
|
||||||
uuid: action.uuid
|
title: x.name,
|
||||||
});
|
name: action.name,
|
||||||
|
uuid: action.uuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (recoverable) {
|
||||||
|
acc.push(...recoverable);
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (recoverable) {
|
|
||||||
acc.push(...recoverable);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
const resourceItems = this.actor.items.reduce((acc, x) => {
|
const resourceItems = this.actor.items.reduce((acc, x) => {
|
||||||
if (
|
if (
|
||||||
x.system.resource &&
|
x.system.resource &&
|
||||||
|
|
@ -189,7 +191,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const characters = game.actors.filter(x => x.type === 'character')
|
const characters = game.actors
|
||||||
|
.filter(x => x.type === 'character')
|
||||||
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
|
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
|
||||||
.filter(x => x.uuid !== this.actor.uuid);
|
.filter(x => x.uuid !== this.actor.uuid);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,25 @@
|
||||||
|
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
export default class DhSceneConfigSettings extends foundry.applications.sheets.SceneConfig {
|
export default class DhSceneConfigSettings extends foundry.applications.sheets.SceneConfig {
|
||||||
// static DEFAULT_OPTIONS = {
|
constructor(options) {
|
||||||
// ...super.DEFAULT_OPTIONS,
|
super(options);
|
||||||
// form: {
|
|
||||||
// handler: this.updateData,
|
Hooks.on(socketEvent.Refresh, ({ refreshType }) => {
|
||||||
// closeOnSubmit: true
|
if (refreshType === RefreshType.Scene) this.render();
|
||||||
// }
|
});
|
||||||
// };
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
...super.DEFAULT_OPTIONS,
|
||||||
|
actions: {
|
||||||
|
...super.DEFAULT_OPTIONS.actions,
|
||||||
|
removeSceneEnvironment: DhSceneConfigSettings.#removeSceneEnvironment
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static buildParts() {
|
static buildParts() {
|
||||||
const { footer, tabs, ...parts } = super.PARTS;
|
const { footer, tabs, ...parts } = super.PARTS;
|
||||||
const tmpParts = {
|
const tmpParts = {
|
||||||
// tabs,
|
|
||||||
tabs: { template: 'systems/daggerheart/templates/scene/tabs.hbs' },
|
tabs: { template: 'systems/daggerheart/templates/scene/tabs.hbs' },
|
||||||
...parts,
|
...parts,
|
||||||
dh: { template: 'systems/daggerheart/templates/scene/dh-config.hbs' },
|
dh: { template: 'systems/daggerheart/templates/scene/dh-config.hbs' },
|
||||||
|
|
@ -28,27 +37,47 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
|
||||||
|
|
||||||
static TABS = DhSceneConfigSettings.buildTabs();
|
static TABS = DhSceneConfigSettings.buildTabs();
|
||||||
|
|
||||||
|
async _preRender(context, options) {
|
||||||
|
await super._preFirstRender(context, options);
|
||||||
|
|
||||||
|
if (!options.internalRefresh)
|
||||||
|
this.daggerheartFlag = new game.system.api.data.scenes.DHScene(this.document.flags.daggerheart);
|
||||||
|
}
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'dh':
|
case 'dh':
|
||||||
htmlElement.querySelector('#rangeMeasurementSetting')?.addEventListener('change', async event => {
|
htmlElement.querySelector('#rangeMeasurementSetting')?.addEventListener('change', async event => {
|
||||||
const flagData = foundry.utils.mergeObject(this.document.flags.daggerheart, {
|
this.daggerheartFlag.updateSource({ rangeMeasurement: { setting: event.target.value } });
|
||||||
rangeMeasurement: { setting: event.target.value }
|
this.render({ internalRefresh: true });
|
||||||
});
|
|
||||||
this.document.flags.daggerheart = flagData;
|
|
||||||
this.render();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dragArea = htmlElement.querySelector('.scene-environments');
|
||||||
|
if (dragArea) dragArea.ondrop = this._onDrop.bind(this);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _onDrop(event) {
|
||||||
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
const item = await foundry.utils.fromUuid(data.uuid);
|
||||||
|
if (item instanceof game.system.api.documents.DhpActor && item.type === 'environment') {
|
||||||
|
await this.daggerheartFlag.updateSource({
|
||||||
|
sceneEnvironments: [...this.daggerheartFlag.sceneEnvironments, data.uuid]
|
||||||
|
});
|
||||||
|
this.render({ internalRefresh: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _preparePartContext(partId, context, options) {
|
async _preparePartContext(partId, context, options) {
|
||||||
context = await super._preparePartContext(partId, context, options);
|
context = await super._preparePartContext(partId, context, options);
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'dh':
|
case 'dh':
|
||||||
context.data = new game.system.api.data.scenes.DHScene(canvas.scene.flags.daggerheart);
|
context.data = this.daggerheartFlag;
|
||||||
context.variantRules = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules);
|
context.variantRules = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -56,8 +85,24 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
// static async updateData(event, _, formData) {
|
static async #removeSceneEnvironment(_event, button) {
|
||||||
// const data = foundry.utils.expandObject(formData.object);
|
await this.daggerheartFlag.updateSource({
|
||||||
// this.close(data);
|
sceneEnvironments: this.daggerheartFlag.sceneEnvironments.filter(
|
||||||
// }
|
(_, index) => index !== Number.parseInt(button.dataset.index)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
this.render({ internalRefresh: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _processSubmitData(event, form, submitData, options) {
|
||||||
|
submitData.flags.daggerheart = this.daggerheartFlag.toObject();
|
||||||
|
for (const key of Object.keys(this.document._source.flags.daggerheart?.sceneEnvironments ?? {})) {
|
||||||
|
if (!submitData.flags.daggerheart.sceneEnvironments[key]) {
|
||||||
|
submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super._processSubmitData(event, form, submitData, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
|
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.openSection = null;
|
this.openSection = null;
|
||||||
|
this.openTrigger = this.action.triggers.length > 0 ? 0 : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
|
|
@ -15,7 +16,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
classes: ['daggerheart', 'dh-style', 'dialog', 'max-800'],
|
classes: ['daggerheart', 'dh-style', 'action-config', 'dialog', 'max-800'],
|
||||||
window: {
|
window: {
|
||||||
icon: 'fa-solid fa-wrench',
|
icon: 'fa-solid fa-wrench',
|
||||||
resizable: false
|
resizable: false
|
||||||
|
|
@ -29,13 +30,18 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
removeElement: this.removeElement,
|
removeElement: this.removeElement,
|
||||||
editEffect: this.editEffect,
|
editEffect: this.editEffect,
|
||||||
addDamage: this.addDamage,
|
addDamage: this.addDamage,
|
||||||
removeDamage: this.removeDamage
|
removeDamage: this.removeDamage,
|
||||||
|
editDoc: this.editDoc,
|
||||||
|
addTrigger: this.addTrigger,
|
||||||
|
removeTrigger: this.removeTrigger,
|
||||||
|
expandTrigger: this.expandTrigger
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
handler: this.updateForm,
|
handler: this.updateForm,
|
||||||
submitOnChange: true,
|
submitOnChange: true,
|
||||||
closeOnSubmit: false
|
closeOnSubmit: false
|
||||||
}
|
},
|
||||||
|
dragDrop: [{ dragSelector: null, dropSelector: '#summon-drop-zone', handlers: ['_onDrop'] }]
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
|
|
@ -55,6 +61,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
effect: {
|
effect: {
|
||||||
id: 'effect',
|
id: 'effect',
|
||||||
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
|
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
|
||||||
|
},
|
||||||
|
trigger: {
|
||||||
|
id: 'trigger',
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/action-settings/trigger.hbs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -82,10 +92,18 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
id: 'effect',
|
id: 'effect',
|
||||||
icon: null,
|
icon: null,
|
||||||
label: 'DAGGERHEART.GENERAL.Tabs.effects'
|
label: 'DAGGERHEART.GENERAL.Tabs.effects'
|
||||||
|
},
|
||||||
|
trigger: {
|
||||||
|
active: false,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'primary',
|
||||||
|
id: 'trigger',
|
||||||
|
icon: null,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tabs.triggers'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects'];
|
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects', 'summon'];
|
||||||
|
|
||||||
_getTabs(tabs) {
|
_getTabs(tabs) {
|
||||||
for (const v of Object.values(tabs)) {
|
for (const v of Object.values(tabs)) {
|
||||||
|
|
@ -96,9 +114,24 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
|
htmlElement.querySelectorAll('.summon-count-wrapper input').forEach(element => {
|
||||||
|
element.addEventListener('change', this.updateSummonCount.bind(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options, 'action');
|
const context = await super._prepareContext(_options, 'action');
|
||||||
context.source = this.action.toObject(true);
|
context.source = this.action.toObject(true);
|
||||||
|
|
||||||
|
context.summons = [];
|
||||||
|
for (const summon of context.source.summon ?? []) {
|
||||||
|
const actor = await foundry.utils.fromUuid(summon.actorUUID);
|
||||||
|
context.summons.push({ actor, count: summon.count });
|
||||||
|
}
|
||||||
|
|
||||||
context.openSection = this.openSection;
|
context.openSection = this.openSection;
|
||||||
context.tabs = this._getTabs(this.constructor.TABS);
|
context.tabs = this._getTabs(this.constructor.TABS);
|
||||||
context.config = CONFIG.DH;
|
context.config = CONFIG.DH;
|
||||||
|
|
@ -111,6 +144,16 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
context.baseSaveDifficulty = this.action.actor?.baseSaveDifficulty;
|
context.baseSaveDifficulty = this.action.actor?.baseSaveDifficulty;
|
||||||
context.baseAttackBonus = this.action.actor?.system.attack?.roll.bonus;
|
context.baseAttackBonus = this.action.actor?.system.attack?.roll.bonus;
|
||||||
context.hasRoll = this.action.hasRoll;
|
context.hasRoll = this.action.hasRoll;
|
||||||
|
context.triggers = context.source.triggers.map((trigger, index) => {
|
||||||
|
const { hint, returns, usesActor } = CONFIG.DH.TRIGGER.triggers[trigger.trigger];
|
||||||
|
return {
|
||||||
|
...trigger,
|
||||||
|
hint,
|
||||||
|
returns,
|
||||||
|
usesActor,
|
||||||
|
revealed: this.openTrigger === index
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
|
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
|
||||||
context.tierOptions = [
|
context.tierOptions = [
|
||||||
|
|
@ -181,8 +224,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateForm(event, _, formData) {
|
static async updateForm(event, _, formData) {
|
||||||
const submitData = this._prepareSubmitData(event, formData),
|
const submitData = this._prepareSubmitData(event, formData);
|
||||||
data = foundry.utils.mergeObject(this.action.toObject(), submitData);
|
|
||||||
|
const data = foundry.utils.mergeObject(this.action.toObject(), submitData);
|
||||||
this.action = await this.action.update(data);
|
this.action = await this.action.update(data);
|
||||||
|
|
||||||
this.sheetUpdate?.(this.action);
|
this.sheetUpdate?.(this.action);
|
||||||
|
|
@ -201,12 +245,26 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
static removeElement(event, button) {
|
static removeElement(event, button) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const data = this.action.toObject(),
|
const data = this.action.toObject(),
|
||||||
key = event.target.closest('[data-key]').dataset.key,
|
key = event.target.closest('[data-key]').dataset.key;
|
||||||
index = button.dataset.index;
|
|
||||||
|
// Prefer explicit index, otherwise find by uuid
|
||||||
|
let index = button?.dataset.index;
|
||||||
|
if (index === undefined || index === null || index === '') {
|
||||||
|
const uuid = button?.dataset.uuid ?? button?.dataset.itemUuid;
|
||||||
|
index = data[key].findIndex(e => (e?.actorUUID ?? e?.uuid) === uuid);
|
||||||
|
if (index === -1) return;
|
||||||
|
} else index = Number(index);
|
||||||
|
|
||||||
data[key].splice(index, 1);
|
data[key].splice(index, 1);
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async editDoc(_event, target) {
|
||||||
|
const element = target.closest('[data-item-uuid]');
|
||||||
|
const doc = (await foundry.utils.fromUuid(element.dataset.itemUuid)) ?? null;
|
||||||
|
if (doc) return doc.sheet.render({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
static addDamage(_event) {
|
static addDamage(_event) {
|
||||||
if (!this.action.damage.parts) return;
|
if (!this.action.damage.parts) return;
|
||||||
const data = this.action.toObject(),
|
const data = this.action.toObject(),
|
||||||
|
|
@ -224,6 +282,69 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static addTrigger() {
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.triggers.push({
|
||||||
|
trigger: CONFIG.DH.TRIGGER.triggers.dualityRoll.id,
|
||||||
|
triggeringActor: CONFIG.DH.TRIGGER.triggerActorTargetType.any.id
|
||||||
|
});
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static async removeTrigger(_event, button) {
|
||||||
|
const trigger = CONFIG.DH.TRIGGER.triggers[this.action.triggers[button.dataset.index].trigger];
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.deleteTriggerTitle')
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.ACTIONS.Config.deleteTriggerContent', {
|
||||||
|
trigger: game.i18n.localize(trigger.label)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.triggers = data.triggers.filter((_, index) => index !== Number.parseInt(button.dataset.index));
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static async expandTrigger(_event, button) {
|
||||||
|
const index = Number.parseInt(button.dataset.index);
|
||||||
|
const toggle = (element, codeMirror) => {
|
||||||
|
codeMirror.classList.toggle('revealed');
|
||||||
|
const button = element.querySelector('a > i');
|
||||||
|
button.classList.toggle('fa-angle-up');
|
||||||
|
button.classList.toggle('fa-angle-down');
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldset = button.closest('fieldset');
|
||||||
|
const codeMirror = fieldset.querySelector('.code-mirror-wrapper');
|
||||||
|
toggle(fieldset, codeMirror);
|
||||||
|
|
||||||
|
if (this.openTrigger !== null && this.openTrigger !== index) {
|
||||||
|
const previouslyExpanded = fieldset
|
||||||
|
.closest(`section`)
|
||||||
|
.querySelector(`fieldset[data-index="${this.openTrigger}"]`);
|
||||||
|
const codeMirror = previouslyExpanded.querySelector('.code-mirror-wrapper');
|
||||||
|
toggle(previouslyExpanded, codeMirror);
|
||||||
|
this.openTrigger = index;
|
||||||
|
} else if (this.openTrigger === index) {
|
||||||
|
this.openTrigger = null;
|
||||||
|
} else {
|
||||||
|
this.openTrigger = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSummonCount(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const wrapper = event.target.closest('.summon-count-wrapper');
|
||||||
|
const index = wrapper.dataset.index;
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.summon[index].count = event.target.value;
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
/** Specific implementation in extending classes **/
|
/** Specific implementation in extending classes **/
|
||||||
static async addEffect(_event) {}
|
static async addEffect(_event) {}
|
||||||
static removeEffect(_event, _button) {}
|
static removeEffect(_event, _button) {}
|
||||||
|
|
@ -233,4 +354,29 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
this.tabGroups.primary = 'base';
|
this.tabGroups.primary = 'base';
|
||||||
await super.close(options);
|
await super.close(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _onDrop(event) {
|
||||||
|
const data = foundry.applications.ux.TextEditor.getDragEventData(event);
|
||||||
|
const item = await foundry.utils.fromUuid(data.uuid);
|
||||||
|
if (!(item instanceof game.system.api.documents.DhpActor)) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.summon.invalidDrop'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionData = this.action.toObject();
|
||||||
|
let countvalue = 1;
|
||||||
|
for (const entry of actionData.summon) {
|
||||||
|
if (entry.actorUUID === data.uuid) {
|
||||||
|
entry.count += 1;
|
||||||
|
countvalue = entry.count;
|
||||||
|
await this.constructor.updateForm.bind(this)(null, null, {
|
||||||
|
object: foundry.utils.flattenObject(actionData)
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actionData.summon.push({ actorUUID: data.uuid, count: countvalue });
|
||||||
|
await this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(actionData) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||||
dropSelector: null
|
dropSelector: null
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
|
|
@ -185,7 +185,6 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
super._onDragStart(event);
|
super._onDragStart(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Application Clicks Actions */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,14 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
makeDeathMove: CharacterSheet.#makeDeathMove,
|
makeDeathMove: CharacterSheet.#makeDeathMove,
|
||||||
levelManagement: CharacterSheet.#levelManagement,
|
levelManagement: CharacterSheet.#levelManagement,
|
||||||
viewLevelups: CharacterSheet.#viewLevelups,
|
viewLevelups: CharacterSheet.#viewLevelups,
|
||||||
|
resetCharacter: CharacterSheet.#resetCharacter,
|
||||||
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
||||||
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||||
advanceResourceDie: CharacterSheet.#advanceResourceDie,
|
advanceResourceDie: CharacterSheet.#advanceResourceDie,
|
||||||
cancelBeastform: CharacterSheet.#cancelBeastform,
|
cancelBeastform: CharacterSheet.#cancelBeastform,
|
||||||
useDowntime: this.useDowntime,
|
useDowntime: this.useDowntime,
|
||||||
viewParty: CharacterSheet.#viewParty,
|
viewParty: CharacterSheet.#viewParty
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
|
@ -42,6 +43,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
icon: 'fa-solid fa-angles-up',
|
icon: 'fa-solid fa-angles-up',
|
||||||
label: 'DAGGERHEART.ACTORS.Character.viewLevelups',
|
label: 'DAGGERHEART.ACTORS.Character.viewLevelups',
|
||||||
action: '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) {
|
async _preparePartContext(partId, context, options) {
|
||||||
context = await super._preparePartContext(partId, context, options);
|
context = await super._preparePartContext(partId, context, options);
|
||||||
switch (partId) {
|
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':
|
case 'loadout':
|
||||||
await this._prepareLoadoutContext(context, options);
|
await this._prepareLoadoutContext(context, options);
|
||||||
break;
|
break;
|
||||||
|
|
@ -338,15 +337,20 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
const type = 'effect';
|
const type = 'effect';
|
||||||
const cls = game.system.api.models.actions.actionsTypes[type];
|
const cls = game.system.api.models.actions.actionsTypes[type];
|
||||||
const action = new cls({
|
const action = new cls(
|
||||||
...cls.getSourceConfig(doc.system),
|
{
|
||||||
type: type,
|
...cls.getSourceConfig(doc.system),
|
||||||
chatDisplay: false,
|
type: type,
|
||||||
cost: [{
|
chatDisplay: false,
|
||||||
key: 'stress',
|
cost: [
|
||||||
value: doc.system.recallCost
|
{
|
||||||
}]
|
key: 'stress',
|
||||||
}, { parent: doc.system });
|
value: doc.system.recallCost
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ parent: doc.system }
|
||||||
|
);
|
||||||
const config = await action.use(event);
|
const config = await action.use(event);
|
||||||
if (config) {
|
if (config) {
|
||||||
return doc.update({ 'system.inVault': false });
|
return doc.update({ 'system.inVault': false });
|
||||||
|
|
@ -661,6 +665,32 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
new LevelupViewMode(this.document).render({ force: true });
|
new LevelupViewMode(this.document).render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the character data and removes all embedded documents.
|
||||||
|
*/
|
||||||
|
static async #resetCharacter() {
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.localize('DAGGERHEART.ACTORS.Character.resetCharacterConfirmationTitle')
|
||||||
|
},
|
||||||
|
content: game.i18n.localize('DAGGERHEART.ACTORS.Character.resetCharacterConfirmationContent')
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
await this.document.update({
|
||||||
|
'==system': {}
|
||||||
|
});
|
||||||
|
await this.document.deleteEmbeddedDocuments(
|
||||||
|
'Item',
|
||||||
|
this.document.items.map(x => x.id)
|
||||||
|
);
|
||||||
|
await this.document.deleteEmbeddedDocuments(
|
||||||
|
'ActiveEffect',
|
||||||
|
this.document.effects.map(x => x.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the Death Move interface for the character.
|
* Opens the Death Move interface for the character.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
@ -707,8 +737,10 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||||
ability: abilityLabel
|
ability: abilityLabel
|
||||||
}),
|
}),
|
||||||
|
effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this.document),
|
||||||
roll: {
|
roll: {
|
||||||
trait: button.dataset.attribute
|
trait: button.dataset.attribute,
|
||||||
|
type: 'trait'
|
||||||
},
|
},
|
||||||
hasRoll: true,
|
hasRoll: true,
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
|
|
@ -718,6 +750,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
const result = await this.document.diceRoll(config);
|
const result = await this.document.diceRoll(config);
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
|
/* 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
|
const costResources = result.costs
|
||||||
|
|
@ -822,7 +855,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
static async #toggleVault(_event, button) {
|
static async #toggleVault(_event, button) {
|
||||||
const doc = await getDocFromElement(button);
|
const doc = await getDocFromElement(button);
|
||||||
const { available } = this.document.system.loadoutSlot;
|
const { available } = this.document.system.loadoutSlot;
|
||||||
if (doc.system.inVault && !available) {
|
if (doc.system.inVault && !available && !doc.system.loadoutIgnore) {
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
|
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -900,32 +933,32 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttons = parties.map((p) => {
|
const buttons = parties.map(p => {
|
||||||
const button = document.createElement("button");
|
const button = document.createElement('button');
|
||||||
button.type = "button";
|
button.type = 'button';
|
||||||
button.classList.add("plain");
|
button.classList.add('plain');
|
||||||
const img = document.createElement("img");
|
const img = document.createElement('img');
|
||||||
img.src = p.img;
|
img.src = p.img;
|
||||||
button.append(img);
|
button.append(img);
|
||||||
const name = document.createElement("span");
|
const name = document.createElement('span');
|
||||||
name.textContent = p.name;
|
name.textContent = p.name;
|
||||||
button.append(name);
|
button.append(name);
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener('click', () => {
|
||||||
p.sheet?.render({ force: true });
|
p.sheet?.render({ force: true });
|
||||||
game.tooltip.dismissLockedTooltips();
|
game.tooltip.dismissLockedTooltips();
|
||||||
});
|
});
|
||||||
return button;
|
return button;
|
||||||
});
|
});
|
||||||
|
|
||||||
const html = document.createElement("div");
|
const html = document.createElement('div');
|
||||||
html.classList.add("party-list");
|
html.classList.add('party-list');
|
||||||
html.append(...buttons);
|
html.append(...buttons);
|
||||||
|
|
||||||
game.tooltip.dismissLockedTooltips();
|
game.tooltip.dismissLockedTooltips();
|
||||||
game.tooltip.activate(target, {
|
game.tooltip.activate(target, {
|
||||||
html,
|
html,
|
||||||
locked: true,
|
locked: true
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -948,6 +981,18 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDropItem(event, item) {
|
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) {
|
if (this.document.uuid === item.parent?.uuid) {
|
||||||
return super._onDropItem(event, item);
|
return super._onDropItem(event, item);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
const step = event.key === 'ArrowUp' ? 1 : event.key === 'ArrowDown' ? -1 : 0;
|
const step = event.key === 'ArrowUp' ? 1 : event.key === 'ArrowDown' ? -1 : 0;
|
||||||
if (step !== 0) {
|
if (step !== 0) {
|
||||||
handleUpdate(step);
|
handleUpdate(step);
|
||||||
deltaInput.dispatchEvent(new Event("change", { bubbles: true }));
|
deltaInput.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -222,7 +222,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
if (deltaInput === document.activeElement) {
|
if (deltaInput === document.activeElement) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
handleUpdate(Math.sign(-1 * event.deltaY));
|
handleUpdate(Math.sign(-1 * event.deltaY));
|
||||||
deltaInput.dispatchEvent(new Event("change", { bubbles: true }));
|
deltaInput.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ passive: false }
|
{ passive: false }
|
||||||
|
|
@ -236,7 +236,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
// Handle contenteditable
|
// Handle contenteditable
|
||||||
for (const input of htmlElement.querySelectorAll('[contenteditable][data-property]')) {
|
for (const input of htmlElement.querySelectorAll('[contenteditable][data-property]')) {
|
||||||
const property = input.dataset.property;
|
const property = input.dataset.property;
|
||||||
input.addEventListener("blur", () => {
|
input.addEventListener('blur', () => {
|
||||||
const selection = document.getSelection();
|
const selection = document.getSelection();
|
||||||
if (input.contains(selection.anchorNode)) {
|
if (input.contains(selection.anchorNode)) {
|
||||||
selection.empty();
|
selection.empty();
|
||||||
|
|
@ -244,12 +244,12 @@ export default function DHApplicationMixin(Base) {
|
||||||
this.document.update({ [property]: input.textContent });
|
this.document.update({ [property]: input.textContent });
|
||||||
});
|
});
|
||||||
|
|
||||||
input.addEventListener("keydown", event => {
|
input.addEventListener('keydown', event => {
|
||||||
if (event.key === "Enter") input.blur();
|
if (event.key === 'Enter') input.blur();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Chrome sometimes add <br>, which aren't a problem for the value but are for the placeholder
|
// Chrome sometimes add <br>, which aren't a problem for the value but are for the placeholder
|
||||||
input.addEventListener("input", () => input.querySelectorAll("br").forEach((i) => i.remove()));
|
input.addEventListener('input', () => input.querySelectorAll('br').forEach(i => i.remove()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -505,6 +505,10 @@ export default function DHApplicationMixin(Base) {
|
||||||
const doc = await getDocFromElement(target),
|
const doc = await getDocFromElement(target),
|
||||||
action = doc?.system?.attack ?? doc;
|
action = doc?.system?.attack ?? doc;
|
||||||
const config = action.prepareConfig(event);
|
const config = action.prepareConfig(event);
|
||||||
|
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(
|
||||||
|
this.document,
|
||||||
|
doc
|
||||||
|
);
|
||||||
config.hasRoll = false;
|
config.hasRoll = false;
|
||||||
return action && action.workflow.get('damage').execute(config, null, true);
|
return action && action.workflow.get('damage').execute(config, null, true);
|
||||||
}
|
}
|
||||||
|
|
@ -585,7 +589,9 @@ export default function DHApplicationMixin(Base) {
|
||||||
if (!doc || !descriptionElement) continue;
|
if (!doc || !descriptionElement) continue;
|
||||||
|
|
||||||
// localize the description (idk if it's still necessary)
|
// localize the description (idk if it's still necessary)
|
||||||
const description = game.i18n.localize(doc.system?.description ?? doc.description);
|
const description = doc.system?.getEnrichedDescription
|
||||||
|
? await doc.system.getEnrichedDescription()
|
||||||
|
: game.i18n.localize(doc.system?.description ?? doc.description);
|
||||||
|
|
||||||
// Enrich the description and attach it;
|
// Enrich the description and attach it;
|
||||||
const isAction = doc.documentName === 'Action';
|
const isAction = doc.documentName === 'Action';
|
||||||
|
|
@ -736,7 +742,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
};
|
};
|
||||||
if (inVault) data['system.inVault'] = true;
|
if (inVault) data['system.inVault'] = true;
|
||||||
if (disabled) data.disabled = true;
|
if (disabled) data.disabled = true;
|
||||||
if (type === "domainCard" && parent?.system.domains?.length) {
|
if (type === 'domainCard' && parent?.system.domains?.length) {
|
||||||
data.system.domain = parent.system.domains[0];
|
data.system.domain = parent.system.domains[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,16 +76,10 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _preparePartContext(partId, context, options) {
|
async _preparePartContext(partId, context, options) {
|
||||||
await super._preparePartContext(partId, context, options);
|
await super._preparePartContext(partId, context, options);
|
||||||
const { TextEditor } = foundry.applications.ux;
|
|
||||||
|
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'description':
|
case 'description':
|
||||||
const value = foundry.utils.getProperty(this.document, 'system.description') ?? '';
|
context.enrichedDescription = await this.document.system.getEnrichedDescription();
|
||||||
context.enrichedDescription = await TextEditor.enrichHTML(value, {
|
|
||||||
relativeTo: this.item,
|
|
||||||
rollData: this.item.getRollData(),
|
|
||||||
secrets: this.item.isOwner
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case 'effects':
|
case 'effects':
|
||||||
await this._prepareEffectsContext(context, options);
|
await this._prepareEffectsContext(context, options);
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['dh-style'],
|
classes: ['dh-style', 'directory'],
|
||||||
window: {
|
window: {
|
||||||
title: 'SIDEBAR.TabSettings'
|
title: 'SIDEBAR.TabSettings'
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,5 @@ export { default as DhCombatTracker } from './combatTracker.mjs';
|
||||||
export { default as DhEffectsDisplay } from './effectsDisplay.mjs';
|
export { default as DhEffectsDisplay } from './effectsDisplay.mjs';
|
||||||
export { default as DhFearTracker } from './fearTracker.mjs';
|
export { default as DhFearTracker } from './fearTracker.mjs';
|
||||||
export { default as DhHotbar } from './hotbar.mjs';
|
export { default as DhHotbar } from './hotbar.mjs';
|
||||||
|
export { default as DhSceneNavigation } from './sceneNavigation.mjs';
|
||||||
export { ItemBrowser } from './itemBrowser.mjs';
|
export { ItemBrowser } from './itemBrowser.mjs';
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,21 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
super.close(options);
|
super.close(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 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');
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
async onRollSimple(event, message) {
|
async onRollSimple(event, message) {
|
||||||
const buttonType = event.target.dataset.type ?? 'damage',
|
const buttonType = event.target.dataset.type ?? 'damage',
|
||||||
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
|
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
|
||||||
|
|
@ -135,7 +150,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
async actionUseButton(event, message) {
|
async actionUseButton(event, message) {
|
||||||
const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset;
|
const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset;
|
||||||
const targetUuid = event.currentTarget.closest('.action-use-button-parent').querySelector('select')?.value;
|
const targetUuid = event.currentTarget.closest('.action-use-button-parent').querySelector('select')?.value;
|
||||||
const parent = await foundry.utils.fromUuid(targetUuid || message.system.actor)
|
const parent = await foundry.utils.fromUuid(targetUuid || message.system.actor);
|
||||||
|
|
||||||
const actionType = message.system.moves[moveIndex].actions[actionIndex];
|
const actionType = message.system.moves[moveIndex].actions[actionIndex];
|
||||||
const cls = game.system.api.models.actions.actionsTypes[actionType.type];
|
const cls = game.system.api.models.actions.actionsTypes[actionType.type];
|
||||||
|
|
|
||||||
|
|
@ -230,6 +230,14 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
result.flatMap(r => r),
|
result.flatMap(r => r),
|
||||||
'name'
|
'name'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* If any noticeable slowdown occurs, consider replacing with enriching description on clicking to expand descriptions */
|
||||||
|
for (const item of this.items) {
|
||||||
|
item.system.enrichedDescription =
|
||||||
|
(await item.system.getEnrichedDescription?.()) ??
|
||||||
|
(await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.description));
|
||||||
|
}
|
||||||
|
|
||||||
this.fieldFilter = this._createFieldFilter();
|
this.fieldFilter = this._createFieldFilter();
|
||||||
|
|
||||||
if (this.presets?.filter) {
|
if (this.presets?.filter) {
|
||||||
|
|
|
||||||
89
module/applications/ui/sceneNavigation.mjs
Normal file
89
module/applications/ui/sceneNavigation.mjs
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
|
export default class DhSceneNavigation extends foundry.applications.ui.SceneNavigation {
|
||||||
|
/** @inheritdoc */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
...super.DEFAULT_OPTIONS,
|
||||||
|
classes: ['faded-ui', 'flexcol', 'scene-navigation'],
|
||||||
|
actions: {
|
||||||
|
openSceneEnvironment: DhSceneNavigation.#openSceneEnvironment
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
static PARTS = {
|
||||||
|
scenes: {
|
||||||
|
root: true,
|
||||||
|
template: 'systems/daggerheart/templates/ui/sceneNavigation/scene-navigation.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
|
||||||
|
const extendScenes = scenes =>
|
||||||
|
scenes.map(x => {
|
||||||
|
const scene = game.scenes.get(x.id);
|
||||||
|
if (!scene.flags.daggerheart) return x;
|
||||||
|
|
||||||
|
const daggerheartInfo = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart);
|
||||||
|
const environments = daggerheartInfo.sceneEnvironments.filter(
|
||||||
|
x => x && x.testUserPermission(game.user, 'LIMITED')
|
||||||
|
);
|
||||||
|
const hasEnvironments = environments.length > 0 && x.isView;
|
||||||
|
return {
|
||||||
|
...x,
|
||||||
|
hasEnvironments,
|
||||||
|
environmentImage: hasEnvironments ? environments[0].img : null,
|
||||||
|
environments: environments
|
||||||
|
};
|
||||||
|
});
|
||||||
|
context.scenes.active = extendScenes(context.scenes.active);
|
||||||
|
context.scenes.inactive = extendScenes(context.scenes.inactive);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #openSceneEnvironment(event, button) {
|
||||||
|
const scene = game.scenes.get(button.dataset.sceneId);
|
||||||
|
const sceneEnvironments = new game.system.api.data.scenes.DHScene(
|
||||||
|
scene.flags.daggerheart
|
||||||
|
).sceneEnvironments.filter(x => x.testUserPermission(game.user, 'LIMITED'));
|
||||||
|
|
||||||
|
if (sceneEnvironments.length === 1 || event.shiftKey) {
|
||||||
|
sceneEnvironments[0].sheet.render(true);
|
||||||
|
} else {
|
||||||
|
new foundry.applications.ux.ContextMenu.implementation(
|
||||||
|
button,
|
||||||
|
'.scene-environment',
|
||||||
|
sceneEnvironments.map(environment => ({
|
||||||
|
name: environment.name,
|
||||||
|
callback: () => {
|
||||||
|
if (scene.flags.daggerheart.sceneEnvironments[0] !== environment.uuid) {
|
||||||
|
const newEnvironments = scene.flags.daggerheart.sceneEnvironments;
|
||||||
|
const newFirst = newEnvironments.splice(
|
||||||
|
newEnvironments.findIndex(x => x === environment.uuid)
|
||||||
|
)[0];
|
||||||
|
newEnvironments.unshift(newFirst);
|
||||||
|
emitAsGM(
|
||||||
|
GMUpdateEvent.UpdateDocument,
|
||||||
|
scene.update.bind(scene),
|
||||||
|
{ 'flags.daggerheart.sceneEnvironments': newEnvironments },
|
||||||
|
scene.uuid
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
environment.sheet.render({ force: true });
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
jQuery: false,
|
||||||
|
fixed: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
CONFIG.ux.ContextMenu.triggerContextMenu(event, '.scene-environment');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -96,11 +96,11 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
||||||
* Trigger a context menu event in response to a normal click on a additional options button.
|
* Trigger a context menu event in response to a normal click on a additional options button.
|
||||||
* @param {PointerEvent} event
|
* @param {PointerEvent} event
|
||||||
*/
|
*/
|
||||||
static triggerContextMenu(event) {
|
static triggerContextMenu(event, altSelector) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const { clientX, clientY } = event;
|
const { clientX, clientY } = event;
|
||||||
const selector = '[data-item-uuid]';
|
const selector = altSelector ?? '[data-item-uuid]';
|
||||||
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
|
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
|
||||||
target?.dispatchEvent(
|
target?.dispatchEvent(
|
||||||
new PointerEvent('contextmenu', {
|
new PointerEvent('contextmenu', {
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
export * as placeables from './placeables/_module.mjs';
|
export * as placeables from './placeables/_module.mjs';
|
||||||
|
export { default as DhTokenLayer } from './tokens.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,12 @@
|
||||||
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
|
/** @inheritdoc */
|
||||||
|
async _draw(options) {
|
||||||
|
await super._draw(options);
|
||||||
|
|
||||||
|
if (this.document.flags.daggerheart?.createPlacement)
|
||||||
|
this.previewHelp ||= this.addChild(this.#drawPreviewHelp());
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _drawEffects() {
|
async _drawEffects() {
|
||||||
this.effects.renderable = false;
|
this.effects.renderable = false;
|
||||||
|
|
@ -50,8 +58,8 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
// so that tokens that are touching return 5.
|
// so that tokens that are touching return 5.
|
||||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
const boundsCorrection = canvas.grid.distance / canvas.grid.size;
|
const boundsCorrection = canvas.grid.distance / canvas.grid.size;
|
||||||
const originRadius = this.bounds.width * boundsCorrection / 2;
|
const originRadius = (this.bounds.width * boundsCorrection) / 2;
|
||||||
const targetRadius = target.bounds.width * boundsCorrection / 2;
|
const targetRadius = (target.bounds.width * boundsCorrection) / 2;
|
||||||
const distance = canvas.grid.measurePath([originPoint, destinationPoint]).distance;
|
const distance = canvas.grid.measurePath([originPoint, destinationPoint]).distance;
|
||||||
return distance - originRadius - targetRadius + canvas.grid.distance;
|
return distance - originRadius - targetRadius + canvas.grid.distance;
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +102,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
|
|
||||||
/** Tests if the token is at least adjacent with another, with some leeway for diagonals */
|
/** Tests if the token is at least adjacent with another, with some leeway for diagonals */
|
||||||
isAdjacentWith(token) {
|
isAdjacentWith(token) {
|
||||||
return this.distanceTo(token) <= (canvas.grid.distance * 1.5);
|
return this.distanceTo(token) <= canvas.grid.distance * 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -132,4 +140,25 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
bar.position.set(0, posY);
|
bar.position.set(0, posY);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a helptext for previews as a text object
|
||||||
|
* @returns {PreciseText} The Text object for the preview helper
|
||||||
|
*/
|
||||||
|
#drawPreviewHelp() {
|
||||||
|
const { uiScale } = canvas.dimensions;
|
||||||
|
|
||||||
|
const textStyle = CONFIG.canvasTextStyle.clone();
|
||||||
|
textStyle.fontSize = 18;
|
||||||
|
textStyle.wordWrapWidth = this.w * 2.5;
|
||||||
|
textStyle.fontStyle = 'italic';
|
||||||
|
|
||||||
|
const helpText = new foundry.canvas.containers.PreciseText(
|
||||||
|
`(${game.i18n.localize('DAGGERHEART.UI.Tooltip.previewTokenHelp')})`,
|
||||||
|
textStyle
|
||||||
|
);
|
||||||
|
helpText.anchor.set(helpText.width / 900, 1);
|
||||||
|
helpText.scale.set(uiScale, uiScale);
|
||||||
|
return helpText;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
module/canvas/tokens.mjs
Normal file
16
module/canvas/tokens.mjs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {
|
||||||
|
async _createPreview(createData, options) {
|
||||||
|
if (options.actor) {
|
||||||
|
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||||
|
if (options.actor?.system.metadata.usesSize) {
|
||||||
|
const tokenSize = tokenSizes[options.actor.system.size];
|
||||||
|
if (tokenSize && options.actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
|
||||||
|
createData.width = tokenSize;
|
||||||
|
createData.height = tokenSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super._createPreview(createData, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,3 +10,4 @@ export * as itemConfig from './itemConfig.mjs';
|
||||||
export * as settingsConfig from './settingsConfig.mjs';
|
export * as settingsConfig from './settingsConfig.mjs';
|
||||||
export * as systemConfig from './system.mjs';
|
export * as systemConfig from './system.mjs';
|
||||||
export * as itemBrowserConfig from './itemBrowserConfig.mjs';
|
export * as itemBrowserConfig from './itemBrowserConfig.mjs';
|
||||||
|
export * as triggerConfig from './triggerConfig.mjs';
|
||||||
|
|
|
||||||
|
|
@ -496,6 +496,8 @@ export const diceTypes = {
|
||||||
d20: 'd20'
|
d20: 'd20'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const dieFaces = [4, 6, 8, 10, 12, 20];
|
||||||
|
|
||||||
export const multiplierTypes = {
|
export const multiplierTypes = {
|
||||||
prof: 'Proficiency',
|
prof: 'Proficiency',
|
||||||
cast: 'Spellcast',
|
cast: 'Spellcast',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
const hooksConfig = {
|
export const hooksConfig = {
|
||||||
effectDisplayToggle: 'DHEffectDisplayToggle'
|
effectDisplayToggle: 'DHEffectDisplayToggle'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default hooksConfig;
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ import * as SETTINGS from './settingsConfig.mjs';
|
||||||
import * as EFFECTS from './effectConfig.mjs';
|
import * as EFFECTS from './effectConfig.mjs';
|
||||||
import * as ACTIONS from './actionConfig.mjs';
|
import * as ACTIONS from './actionConfig.mjs';
|
||||||
import * as FLAGS from './flagsConfig.mjs';
|
import * as FLAGS from './flagsConfig.mjs';
|
||||||
import HOOKS from './hooksConfig.mjs';
|
import * as HOOKS from './hooksConfig.mjs';
|
||||||
|
import * as TRIGGER from './triggerConfig.mjs';
|
||||||
import * as ITEMBROWSER from './itemBrowserConfig.mjs';
|
import * as ITEMBROWSER from './itemBrowserConfig.mjs';
|
||||||
|
|
||||||
export const SYSTEM_ID = 'daggerheart';
|
export const SYSTEM_ID = 'daggerheart';
|
||||||
|
|
@ -24,5 +25,6 @@ export const SYSTEM = {
|
||||||
ACTIONS,
|
ACTIONS,
|
||||||
FLAGS,
|
FLAGS,
|
||||||
HOOKS,
|
HOOKS,
|
||||||
|
TRIGGER,
|
||||||
ITEMBROWSER
|
ITEMBROWSER
|
||||||
};
|
};
|
||||||
|
|
|
||||||
42
module/config/triggerConfig.mjs
Normal file
42
module/config/triggerConfig.mjs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
/* hints and returns are intentionally not translated. They are programatical terms and best understood in english */
|
||||||
|
export const triggers = {
|
||||||
|
dualityRoll: {
|
||||||
|
id: 'dualityRoll',
|
||||||
|
usesActor: true,
|
||||||
|
args: ['roll', 'actor'],
|
||||||
|
label: 'DAGGERHEART.CONFIG.Triggers.dualityRoll.label',
|
||||||
|
hint: 'this: Action, roll: DhRoll, actor: DhActor',
|
||||||
|
returns: '{ updates: [{ key, value, total }] }'
|
||||||
|
},
|
||||||
|
fearRoll: {
|
||||||
|
id: 'fearRoll',
|
||||||
|
usesActor: true,
|
||||||
|
args: ['roll', 'actor'],
|
||||||
|
label: 'DAGGERHEART.CONFIG.Triggers.fearRoll.label',
|
||||||
|
hint: 'this: Action, roll: DhRoll, actor: DhActor',
|
||||||
|
returns: '{ updates: [{ key, value, total }] }'
|
||||||
|
},
|
||||||
|
postDamageReduction: {
|
||||||
|
id: 'postDamageReduction',
|
||||||
|
usesActor: true,
|
||||||
|
args: ['damageUpdates', 'actor'],
|
||||||
|
label: 'DAGGERHEART.CONFIG.Triggers.postDamageReduction.label',
|
||||||
|
hint: 'damageUpdates: ResourceUpdates, actor: DhActor',
|
||||||
|
returns: '{ updates: [{ originActor: this.actor, updates: [{ key, value, total }] }] }'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const triggerActorTargetType = {
|
||||||
|
any: {
|
||||||
|
id: 'any',
|
||||||
|
label: 'DAGGERHEART.CONFIG.TargetTypes.any'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
id: 'self',
|
||||||
|
label: 'DAGGERHEART.CONFIG.TargetTypes.self'
|
||||||
|
},
|
||||||
|
other: {
|
||||||
|
id: 'other',
|
||||||
|
label: 'DAGGERHEART.CONFIG.TargetTypes.other'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -2,6 +2,7 @@ export { default as DhCombat } from './combat.mjs';
|
||||||
export { default as DhCombatant } from './combatant.mjs';
|
export { default as DhCombatant } from './combatant.mjs';
|
||||||
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
||||||
export { default as DhRollTable } from './rollTable.mjs';
|
export { default as DhRollTable } from './rollTable.mjs';
|
||||||
|
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
||||||
|
|
||||||
export * as countdowns from './countdowns.mjs';
|
export * as countdowns from './countdowns.mjs';
|
||||||
export * as actions from './action/_module.mjs';
|
export * as actions from './action/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
|
|
||||||
async use(event, options) {
|
async use(event, options) {
|
||||||
const result = await super.use(event, options);
|
const result = await super.use(event, options);
|
||||||
|
if (!result.message) return;
|
||||||
|
|
||||||
if (result.message.system.action.roll?.type === 'attack') {
|
if (result.message.system.action.roll?.type === 'attack') {
|
||||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import DhpActor from '../../documents/actor.mjs';
|
||||||
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
||||||
import { ActionMixin } from '../fields/actionField.mjs';
|
import { ActionMixin } from '../fields/actionField.mjs';
|
||||||
import { originItemField } from '../chat-message/actorRoll.mjs';
|
import { originItemField } from '../chat-message/actorRoll.mjs';
|
||||||
|
import TriggerField from '../fields/triggerField.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
|
@ -34,7 +35,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
nullable: false,
|
nullable: false,
|
||||||
required: true
|
required: true
|
||||||
}),
|
}),
|
||||||
targetUuid: new fields.StringField({ initial: undefined })
|
targetUuid: new fields.StringField({ initial: undefined }),
|
||||||
|
triggers: new fields.ArrayField(new TriggerField())
|
||||||
};
|
};
|
||||||
|
|
||||||
this.extraSchemas.forEach(s => {
|
this.extraSchemas.forEach(s => {
|
||||||
|
|
@ -164,7 +166,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
*/
|
*/
|
||||||
getRollData(data = {}) {
|
getRollData(data = {}) {
|
||||||
const actorData = this.actor ? this.actor.getRollData(false) : {};
|
const actorData = this.actor ? this.actor.getRollData(false) : {};
|
||||||
|
|
||||||
actorData.result = data.roll?.total ?? 1;
|
actorData.result = data.roll?.total ?? 1;
|
||||||
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
||||||
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
||||||
|
|
@ -197,6 +198,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
let config = this.prepareConfig(event);
|
let config = this.prepareConfig(event);
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
||||||
|
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item);
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
// Display configuration window if necessary
|
// Display configuration window if necessary
|
||||||
|
|
@ -263,6 +266,28 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the all potentially applicable effects on the actor
|
||||||
|
* @param {DHActor} actor The actor performing the action
|
||||||
|
* @param {DHItem|DhActor} effectParent The parent of the effect
|
||||||
|
* @returns {DhActiveEffect[]}
|
||||||
|
*/
|
||||||
|
static async getEffects(actor, effectParent) {
|
||||||
|
if (!actor) return [];
|
||||||
|
|
||||||
|
return Array.from(await actor.allApplicableEffects()).filter(effect => {
|
||||||
|
/* Effects on weapons only ever apply for the weapon itself */
|
||||||
|
if (effect.parent.type === 'weapon') {
|
||||||
|
/* Unless they're secondary - then they apply only to other primary weapons */
|
||||||
|
if (effect.parent.system.secondary) {
|
||||||
|
if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) return false;
|
||||||
|
} else if (effectParent?.id !== effect.parent.id) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !effect.isSuppressed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method used to know if a configuration dialog must be shown or not when there is no roll.
|
* Method used to know if a configuration dialog must be shown or not when there is no roll.
|
||||||
* @param {*} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
* @param {*} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||||
|
|
@ -343,6 +368,10 @@ export class ResourceUpdateMap extends Map {
|
||||||
}
|
}
|
||||||
|
|
||||||
addResources(resources) {
|
addResources(resources) {
|
||||||
|
if (!resources?.length) return;
|
||||||
|
const invalidResources = resources.some(resource => !resource.key);
|
||||||
|
if (invalidResources) return;
|
||||||
|
|
||||||
for (const resource of resources) {
|
for (const resource of resources) {
|
||||||
if (!resource.key) continue;
|
if (!resource.key) continue;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,5 @@
|
||||||
import DHBaseAction from './baseAction.mjs';
|
import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DHSummonAction extends DHBaseAction {
|
export default class DHSummonAction extends DHBaseAction {
|
||||||
static defineSchema() {
|
static extraSchemas = [...super.extraSchemas, 'summon'];
|
||||||
const fields = foundry.data.fields;
|
|
||||||
return {
|
|
||||||
...super.defineSchema(),
|
|
||||||
documentUUID: new fields.DocumentUUIDField({ type: 'Actor' })
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async trigger(event, ...args) {
|
|
||||||
if (!this.canSummon || !canvas.scene) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canSummon() {
|
|
||||||
return game.user.can('TOKEN_CREATE');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export default class BeastformEffect extends BaseEffect {
|
||||||
base64: false
|
base64: false
|
||||||
}),
|
}),
|
||||||
tokenSize: new fields.SchemaField({
|
tokenSize: new fields.SchemaField({
|
||||||
|
scale: new fields.NumberField({ nullable: false, initial: 1 }),
|
||||||
height: new fields.NumberField({ integer: false, nullable: true }),
|
height: new fields.NumberField({ integer: false, nullable: true }),
|
||||||
width: new fields.NumberField({ integer: false, nullable: true })
|
width: new fields.NumberField({ integer: false, nullable: true })
|
||||||
})
|
})
|
||||||
|
|
@ -55,7 +56,9 @@ export default class BeastformEffect extends BaseEffect {
|
||||||
const update = {
|
const update = {
|
||||||
...baseUpdate,
|
...baseUpdate,
|
||||||
texture: {
|
texture: {
|
||||||
src: this.characterTokenData.tokenImg
|
src: this.characterTokenData.tokenImg,
|
||||||
|
scaleX: this.characterTokenData.tokenSize.scale,
|
||||||
|
scaleY: this.characterTokenData.tokenSize.scale
|
||||||
},
|
},
|
||||||
ring: {
|
ring: {
|
||||||
enabled: this.characterTokenData.usesDynamicToken,
|
enabled: this.characterTokenData.usesDynamicToken,
|
||||||
|
|
@ -86,7 +89,9 @@ export default class BeastformEffect extends BaseEffect {
|
||||||
y,
|
y,
|
||||||
'texture': {
|
'texture': {
|
||||||
enabled: this.characterTokenData.usesDynamicToken,
|
enabled: this.characterTokenData.usesDynamicToken,
|
||||||
src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg
|
src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg,
|
||||||
|
scaleX: this.characterTokenData.tokenSize.scale,
|
||||||
|
scaleY: this.characterTokenData.tokenSize.scale
|
||||||
},
|
},
|
||||||
'ring': {
|
'ring': {
|
||||||
subject: {
|
subject: {
|
||||||
|
|
|
||||||
|
|
@ -280,6 +280,24 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
dualityRoll: new fields.SchemaField({
|
||||||
|
defaultHopeDice: new fields.NumberField({
|
||||||
|
nullable: false,
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.dieFaces,
|
||||||
|
initial: 12,
|
||||||
|
label: 'DAGGERHEART.ACTORS.Character.defaultHopeDice'
|
||||||
|
}),
|
||||||
|
defaultFearDice: new fields.NumberField({
|
||||||
|
nullable: false,
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.dieFaces,
|
||||||
|
initial: 12,
|
||||||
|
label: 'DAGGERHEART.ACTORS.Character.defaultFearDice'
|
||||||
|
})
|
||||||
|
}),
|
||||||
runeWard: new fields.BooleanField({ initial: false }),
|
runeWard: new fields.BooleanField({ initial: false }),
|
||||||
burden: new fields.SchemaField({
|
burden: new fields.SchemaField({
|
||||||
ignore: new fields.BooleanField()
|
ignore: new fields.BooleanField()
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor from './base.mjs';
|
||||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||||
import DHEnvironmentSettings from '../../applications/sheets-configs/environment-settings.mjs';
|
import DHEnvironmentSettings from '../../applications/sheets-configs/environment-settings.mjs';
|
||||||
|
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
export default class DhEnvironment extends BaseDataActor {
|
export default class DhEnvironment extends BaseDataActor {
|
||||||
|
scenes = new Set();
|
||||||
|
|
||||||
/**@override */
|
/**@override */
|
||||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Environment'];
|
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Environment'];
|
||||||
|
|
||||||
|
|
@ -53,6 +56,31 @@ export default class DhEnvironment extends BaseDataActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
isItemValid(source) {
|
isItemValid(source) {
|
||||||
return source.type === "feature";
|
return source.type === 'feature';
|
||||||
|
}
|
||||||
|
|
||||||
|
_onUpdate(changes, options, userId) {
|
||||||
|
super._onUpdate(changes, options, userId);
|
||||||
|
for (const scene of this.scenes) {
|
||||||
|
scene.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDelete(options, userId) {
|
||||||
|
super._onDelete(options, userId);
|
||||||
|
for (const scene of this.scenes) {
|
||||||
|
if (game.user.isActiveGM) {
|
||||||
|
const newSceneEnvironments = scene.flags.daggerheart.sceneEnvironments.filter(
|
||||||
|
x => x !== this.parent.uuid
|
||||||
|
);
|
||||||
|
scene.update({ 'flags.daggerheart.sceneEnvironments': newSceneEnvironments }).then(() => {
|
||||||
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Scene });
|
||||||
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
|
data: { refreshType: RefreshType.TagTeamRoll }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,6 @@ export { ActionCollection } from './actionField.mjs';
|
||||||
export { default as FormulaField } from './formulaField.mjs';
|
export { default as FormulaField } from './formulaField.mjs';
|
||||||
export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs';
|
export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs';
|
||||||
export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs';
|
export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs';
|
||||||
|
export { default as TriggerField } from './triggerField.mjs';
|
||||||
export { default as MappingField } from './mappingField.mjs';
|
export { default as MappingField } from './mappingField.mjs';
|
||||||
export * as ActionFields from './action/_module.mjs';
|
export * as ActionFields from './action/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,4 @@ export { default as BeastformField } from './beastformField.mjs';
|
||||||
export { default as DamageField } from './damageField.mjs';
|
export { default as DamageField } from './damageField.mjs';
|
||||||
export { default as RollField } from './rollField.mjs';
|
export { default as RollField } from './rollField.mjs';
|
||||||
export { default as MacroField } from './macroField.mjs';
|
export { default as MacroField } from './macroField.mjs';
|
||||||
|
export { default as SummonField } from './summonField.mjs';
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ export class DHActionRollData extends foundry.abstract.DataModel {
|
||||||
if (this.type === CONFIG.DH.GENERAL.rollTypes.attack.id)
|
if (this.type === CONFIG.DH.GENERAL.rollTypes.attack.id)
|
||||||
modifiers.push({
|
modifiers.push({
|
||||||
label: 'Bonus to Hit',
|
label: 'Bonus to Hit',
|
||||||
value: this.bonus ?? this.parent.actor.system.attack.roll.bonus
|
value: this.bonus ?? this.parent.actor.system.attack.roll.bonus ?? 0
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
89
module/data/fields/action/summonField.mjs
Normal file
89
module/data/fields/action/summonField.mjs
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
import FormulaField from '../formulaField.mjs';
|
||||||
|
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class DHSummonField extends fields.ArrayField {
|
||||||
|
/**
|
||||||
|
* Action Workflow order
|
||||||
|
*/
|
||||||
|
static order = 120;
|
||||||
|
|
||||||
|
constructor(options = {}, context = {}) {
|
||||||
|
const summonFields = new fields.SchemaField({
|
||||||
|
actorUUID: new fields.DocumentUUIDField({
|
||||||
|
type: 'Actor',
|
||||||
|
required: true
|
||||||
|
}),
|
||||||
|
count: new FormulaField({
|
||||||
|
required: true,
|
||||||
|
default: '1'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
super(summonFields, options, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async execute() {
|
||||||
|
if (!canvas.scene) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.summon.error'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.summon.length === 0) {
|
||||||
|
ui.notifications.warn('No actors configured for this Summon action.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rolls = [];
|
||||||
|
const summonData = [];
|
||||||
|
for (const summon of this.summon) {
|
||||||
|
let count = summon.count;
|
||||||
|
const roll = new Roll(summon.count);
|
||||||
|
if (!roll.isDeterministic) {
|
||||||
|
await roll.evaluate();
|
||||||
|
if (game.modules.get('dice-so-nice')?.active) rolls.push(roll);
|
||||||
|
count = roll.total;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actor = DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
|
||||||
|
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */
|
||||||
|
summon.rolledCount = count;
|
||||||
|
summon.actor = actor.toObject();
|
||||||
|
|
||||||
|
summonData.push({ actor, count: count });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true)));
|
||||||
|
|
||||||
|
this.actor.sheet?.minimize();
|
||||||
|
DHSummonField.handleSummon(summonData, this.actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for any available instances of the actor present in the world if we're missing artwork in the compendium */
|
||||||
|
static getWorldActor(baseActor) {
|
||||||
|
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
|
||||||
|
if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) {
|
||||||
|
const worldActorCopy = game.actors.find(x => x.name === baseActor.name);
|
||||||
|
return worldActorCopy ?? baseActor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseActor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async handleSummon(summonData, actionActor, summonIndex = 0) {
|
||||||
|
const summon = summonData[summonIndex];
|
||||||
|
const result = await CONFIG.ux.TokenManager.createPreviewAsync(summon.actor, {
|
||||||
|
name: `${summon.actor.prototypeToken.name}${summon.count > 1 ? ` (${summon.count}x)` : ''}`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) return actionActor.sheet?.maximize();
|
||||||
|
summon.actor = result.actor;
|
||||||
|
|
||||||
|
summon.count--;
|
||||||
|
if (summon.count <= 0) {
|
||||||
|
summonIndex++;
|
||||||
|
if (summonIndex === summonData.length) return actionActor.sheet?.maximize();
|
||||||
|
}
|
||||||
|
|
||||||
|
DHSummonField.handleSummon(summonData, actionActor, summonIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -267,7 +267,8 @@ export function ActionMixin(Base) {
|
||||||
action: {
|
action: {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
img: this.baseAction ? this.parent.parent.img : this.img,
|
img: this.baseAction ? this.parent.parent.img : this.img,
|
||||||
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10']
|
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'],
|
||||||
|
summon: this.summon
|
||||||
},
|
},
|
||||||
itemOrigin: this.item,
|
itemOrigin: this.item,
|
||||||
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
||||||
|
|
|
||||||
24
module/data/fields/triggerField.mjs
Normal file
24
module/data/fields/triggerField.mjs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
export default class TriggerField extends foundry.data.fields.SchemaField {
|
||||||
|
constructor(context) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
trigger: new foundry.data.fields.StringField({
|
||||||
|
nullable: false,
|
||||||
|
blank: false,
|
||||||
|
initial: CONFIG.DH.TRIGGER.triggers.dualityRoll.id,
|
||||||
|
choices: CONFIG.DH.TRIGGER.triggers,
|
||||||
|
label: 'DAGGERHEART.CONFIG.Triggers.triggerType'
|
||||||
|
}),
|
||||||
|
triggeringActorType: new foundry.data.fields.StringField({
|
||||||
|
nullable: false,
|
||||||
|
blank: false,
|
||||||
|
initial: CONFIG.DH.TRIGGER.triggerActorTargetType.any.id,
|
||||||
|
choices: CONFIG.DH.TRIGGER.triggerActorTargetType,
|
||||||
|
label: 'DAGGERHEART.CONFIG.Triggers.triggeringActorType'
|
||||||
|
}),
|
||||||
|
command: new foundry.data.fields.JavaScriptField({ async: true })
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -54,6 +54,21 @@ export default class DHArmor extends AttachableItem {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
async getDescriptionData() {
|
||||||
|
const baseDescription = this.description;
|
||||||
|
const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
|
||||||
|
const features = this.armorFeatures.map(x => allFeatures[x.value]);
|
||||||
|
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||||
|
|
||||||
|
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/sheets/items/armor/description.hbs',
|
||||||
|
{ features }
|
||||||
|
);
|
||||||
|
|
||||||
|
return { prefix, value: baseDescription, suffix: null };
|
||||||
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _preUpdate(changes, options, user) {
|
async _preUpdate(changes, options, user) {
|
||||||
const allowed = await super._preUpdate(changes, options, user);
|
const allowed = await super._preUpdate(changes, options, user);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
* @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item
|
* @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addLinkedItemsDiff, createScrollText, getScrollTextData, updateLinkedItemApps } from '../../helpers/utils.mjs';
|
import { addLinkedItemsDiff, getScrollTextData, updateLinkedItemApps } from '../../helpers/utils.mjs';
|
||||||
import { ActionsField } from '../fields/actionField.mjs';
|
import { ActionsField } from '../fields/actionField.mjs';
|
||||||
import FormulaField from '../fields/formulaField.mjs';
|
import FormulaField from '../fields/formulaField.mjs';
|
||||||
|
|
||||||
|
|
@ -124,6 +124,33 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
return [source, page ? `pg ${page}.` : null].filter(x => x).join('. ');
|
return [source, page ? `pg ${page}.` : null].filter(x => x).join('. ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Augments the description for the item with type specific info to display. Implemented in applicable item subtypes.
|
||||||
|
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
async getDescriptionData(_options) {
|
||||||
|
return { prefix: null, value: this.description, suffix: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the enriched and augmented description for the item.
|
||||||
|
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
async getEnrichedDescription() {
|
||||||
|
if (!this.metadata.hasDescription) return '';
|
||||||
|
|
||||||
|
const { prefix, value, suffix } = await this.getDescriptionData();
|
||||||
|
const fullDescription = [prefix, value, suffix].filter(p => !!p).join('\n<hr>\n');
|
||||||
|
|
||||||
|
return await foundry.applications.ux.TextEditor.implementation.enrichHTML(fullDescription, {
|
||||||
|
relativeTo: this,
|
||||||
|
rollData: this.getRollData(),
|
||||||
|
secrets: this.isOwner
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain a data object used to evaluate any dice rolls associated with this Item Type
|
* Obtain a data object used to evaluate any dice rolls associated with this Item Type
|
||||||
* @param {object} [options] - Options which modify the getRollData method.
|
* @param {object} [options] - Options which modify the getRollData method.
|
||||||
|
|
@ -135,6 +162,11 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepareBaseData() {
|
||||||
|
super.prepareBaseData();
|
||||||
|
game.system.registeredTriggers.registerItemTriggers(this.parent);
|
||||||
|
}
|
||||||
|
|
||||||
async _preCreate(data, options, user) {
|
async _preCreate(data, options, user) {
|
||||||
// Skip if no initial action is required or actions already exist
|
// Skip if no initial action is required or actions already exist
|
||||||
if (this.metadata.hasInitialAction && foundry.utils.isEmpty(this.actions)) {
|
if (this.metadata.hasInitialAction && foundry.utils.isEmpty(this.actions)) {
|
||||||
|
|
@ -195,6 +227,28 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
const armorData = getScrollTextData(this.parent.parent.system.resources, changed.system.marks, 'armor');
|
const armorData = getScrollTextData(this.parent.parent.system.resources, changed.system.marks, 'armor');
|
||||||
options.scrollingTextData = [armorData];
|
options.scrollingTextData = [armorData];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changed.system?.actions) {
|
||||||
|
const triggersToRemove = Object.keys(changed.system.actions).reduce((acc, key) => {
|
||||||
|
if (!changed.system.actions[key]) {
|
||||||
|
const strippedKey = key.replace('-=', '');
|
||||||
|
acc.push(...this.actions.get(strippedKey).triggers.map(x => x.trigger));
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
game.system.registeredTriggers.unregisterTriggers(triggersToRemove, this.parent.uuid);
|
||||||
|
|
||||||
|
if (!(this.parent.parent.token instanceof game.system.api.documents.DhToken)) {
|
||||||
|
for (const token of this.parent.parent.getActiveTokens()) {
|
||||||
|
game.system.registeredTriggers.unregisterTriggers(
|
||||||
|
triggersToRemove,
|
||||||
|
`${token.document.uuid}.${this.parent.uuid}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUpdate(changed, options, userId) {
|
_onUpdate(changed, options, userId) {
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
choices: CONFIG.DH.ACTOR.tokenSize,
|
choices: CONFIG.DH.ACTOR.tokenSize,
|
||||||
initial: CONFIG.DH.ACTOR.tokenSize.custom.id
|
initial: CONFIG.DH.ACTOR.tokenSize.custom.id
|
||||||
}),
|
}),
|
||||||
|
scale: new fields.NumberField({ nullable: false, min: 0.2, max: 3, step: 0.05, initial: 1 }),
|
||||||
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
|
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
|
||||||
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
|
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
|
||||||
}),
|
}),
|
||||||
|
|
@ -184,6 +185,7 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
||||||
tokenRingImg: this.parent.parent.prototypeToken.ring.subject.texture,
|
tokenRingImg: this.parent.parent.prototypeToken.ring.subject.texture,
|
||||||
tokenSize: {
|
tokenSize: {
|
||||||
|
scale: this.parent.parent.prototypeToken.texture.scaleX,
|
||||||
height: this.parent.parent.prototypeToken.height,
|
height: this.parent.parent.prototypeToken.height,
|
||||||
width: this.parent.parent.prototypeToken.width
|
width: this.parent.parent.prototypeToken.width
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +211,9 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
texture: {
|
texture: {
|
||||||
src: this.tokenImg
|
src: this.tokenImg,
|
||||||
|
scaleX: this.tokenSize.scale,
|
||||||
|
scaleY: this.tokenSize.scale
|
||||||
},
|
},
|
||||||
ring: {
|
ring: {
|
||||||
subject: {
|
subject: {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,21 @@ export default class DHDomainCard extends BaseDataItem {
|
||||||
required: true,
|
required: true,
|
||||||
initial: CONFIG.DH.DOMAIN.cardTypes.ability.id
|
initial: CONFIG.DH.DOMAIN.cardTypes.ability.id
|
||||||
}),
|
}),
|
||||||
inVault: new fields.BooleanField({ initial: false })
|
inVault: new fields.BooleanField({ initial: false }),
|
||||||
|
vaultActive: new fields.BooleanField({
|
||||||
|
required: true,
|
||||||
|
nullable: false,
|
||||||
|
initial: false
|
||||||
|
}),
|
||||||
|
loadoutIgnore: new fields.BooleanField({
|
||||||
|
required: true,
|
||||||
|
nullable: false,
|
||||||
|
initial: false
|
||||||
|
}),
|
||||||
|
domainTouched: new fields.NumberField({
|
||||||
|
nullable: true,
|
||||||
|
initial: null
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,6 +52,19 @@ export default class DHDomainCard extends BaseDataItem {
|
||||||
return game.i18n.localize(allDomainData[this.domain].label);
|
return game.i18n.localize(allDomainData[this.domain].label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isVaultSupressed() {
|
||||||
|
return this.inVault && !this.vaultActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDomainTouchedSuppressed() {
|
||||||
|
if (!this.parent.system.domainTouched || this.parent.parent?.type !== 'character') return false;
|
||||||
|
|
||||||
|
const matchingDomainCards = this.parent.parent.items.filter(
|
||||||
|
item => !item.system.inVault && item.system.domain === this.parent.system.domain
|
||||||
|
).length;
|
||||||
|
return matchingDomainCards < this.parent.system.domainTouched;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**@override */
|
/**@override */
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,21 @@ export default class DHWeapon extends AttachableItem {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
async getDescriptionData() {
|
||||||
|
const baseDescription = this.description;
|
||||||
|
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
|
||||||
|
const features = this.weaponFeatures.map(x => allFeatures[x.value]);
|
||||||
|
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||||
|
|
||||||
|
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/sheets/items/weapon/description.hbs',
|
||||||
|
{ features }
|
||||||
|
);
|
||||||
|
|
||||||
|
return { prefix, value: baseDescription, suffix: null };
|
||||||
|
}
|
||||||
|
|
||||||
prepareDerivedData() {
|
prepareDerivedData() {
|
||||||
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;
|
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
154
module/data/registeredTriggers.mjs
Normal file
154
module/data/registeredTriggers.mjs
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
export default class RegisteredTriggers extends Map {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerTriggers(triggers, actor, uuid) {
|
||||||
|
for (const triggerKey of Object.keys(CONFIG.DH.TRIGGER.triggers)) {
|
||||||
|
const match = triggers[triggerKey];
|
||||||
|
const existingTrigger = this.get(triggerKey);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
if (existingTrigger?.get(uuid)) this.get(triggerKey).delete(uuid);
|
||||||
|
} else {
|
||||||
|
const { trigger, triggeringActorType, commands } = match;
|
||||||
|
|
||||||
|
if (!existingTrigger) this.set(trigger, new Map());
|
||||||
|
this.get(trigger).set(uuid, { actor, triggeringActorType, commands });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerItemTriggers(item, registerOverride) {
|
||||||
|
for (const action of item.system.actions ?? []) {
|
||||||
|
if (!action.actor) continue;
|
||||||
|
|
||||||
|
/* Non actor-linked should only prep synthetic actors so they're not registering triggers unless they're on the canvas */
|
||||||
|
if (
|
||||||
|
!registerOverride &&
|
||||||
|
!action.actor.prototypeToken.actorLink &&
|
||||||
|
(!(action.actor.parent instanceof game.system.api.documents.DhToken) || !action.actor.parent?.uuid)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const triggers = {};
|
||||||
|
for (const trigger of action.triggers) {
|
||||||
|
const { args } = CONFIG.DH.TRIGGER.triggers[trigger.trigger];
|
||||||
|
const fn = new foundry.utils.AsyncFunction(...args, `{${trigger.command}\n}`);
|
||||||
|
|
||||||
|
if (!triggers[trigger.trigger])
|
||||||
|
triggers[trigger.trigger] = {
|
||||||
|
trigger: trigger.trigger,
|
||||||
|
triggeringActorType: trigger.triggeringActorType,
|
||||||
|
commands: []
|
||||||
|
};
|
||||||
|
triggers[trigger.trigger].commands.push(fn.bind(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.registerTriggers(triggers, action.actor?.uuid, item.uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterTriggers(triggerKeys, uuid) {
|
||||||
|
for (const triggerKey of triggerKeys) {
|
||||||
|
const existingTrigger = this.get(triggerKey);
|
||||||
|
if (!existingTrigger) return;
|
||||||
|
|
||||||
|
existingTrigger.delete(uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterItemTriggers(items) {
|
||||||
|
for (const item of items) {
|
||||||
|
if (!item.system.actions.size) continue;
|
||||||
|
|
||||||
|
const triggers = (item.system.actions ?? []).reduce((acc, action) => {
|
||||||
|
acc.push(...action.triggers.map(x => x.trigger));
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
this.unregisterTriggers(triggers, item.uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterSceneTriggers(scene) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
this.set(triggerKey, filtered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (const actor of scene.tokens.filter(x => x.actor).map(x => x.actor)) {
|
||||||
|
if (actor.prototypeToken.actorLink) continue;
|
||||||
|
|
||||||
|
for (const item of actor.items) {
|
||||||
|
this.registerItemTriggers(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async runTrigger(trigger, currentActor, ...args) {
|
||||||
|
const updates = [];
|
||||||
|
const triggerSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).triggers;
|
||||||
|
if (!triggerSettings.enabled) return updates;
|
||||||
|
|
||||||
|
const dualityTrigger = this.get(trigger);
|
||||||
|
if (dualityTrigger) {
|
||||||
|
const tokenBoundActors = ['adversary', 'environment'];
|
||||||
|
const triggerActors = ['character', ...tokenBoundActors];
|
||||||
|
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') {
|
||||||
|
if (triggeringActorType === 'self' && currentActor?.uuid !== actorUuid) continue;
|
||||||
|
else if (triggeringActorType === 'other' && currentActor?.uuid === actorUuid) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const command of commands) {
|
||||||
|
try {
|
||||||
|
if (CONFIG.debug.triggers) {
|
||||||
|
const item = await foundry.utils.fromUuid(itemUuid);
|
||||||
|
console.log(
|
||||||
|
game.i18n.format('DAGGERHEART.UI.ConsoleLogs.triggerRun', {
|
||||||
|
actor: actor.name ?? '<Missing Actor>',
|
||||||
|
item: item?.name ?? '<Missing Item>',
|
||||||
|
trigger: game.i18n.localize(triggerData.label)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await command(...args);
|
||||||
|
if (result?.updates?.length) updates.push(...result.updates);
|
||||||
|
} catch (_) {
|
||||||
|
const triggerName = game.i18n.localize(triggerData.label);
|
||||||
|
ui.notifications.error(
|
||||||
|
game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerError', {
|
||||||
|
trigger: triggerName,
|
||||||
|
actor: currentActor?.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||||
|
|
||||||
|
/* Foundry does not add any system data for subtyped Scenes. The data model is therefore used by instantiating a new instance of it for sceneConfigSettings.mjs.
|
||||||
|
Needed dataprep and lifetime hooks are handled in documents/scene.
|
||||||
|
*/
|
||||||
export default class DHScene extends foundry.abstract.DataModel {
|
export default class DHScene extends foundry.abstract.DataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
@ -13,7 +18,8 @@ export default class DHScene extends foundry.abstract.DataModel {
|
||||||
veryClose: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.veryClose.name' }),
|
veryClose: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.veryClose.name' }),
|
||||||
close: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.close.name' }),
|
close: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.close.name' }),
|
||||||
far: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.far.name' })
|
far: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.far.name' })
|
||||||
})
|
}),
|
||||||
|
sceneEnvironments: new ForeignDocumentUUIDArrayField({ type: 'Actor', prune: true })
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,6 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
||||||
initial: true,
|
initial: true,
|
||||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.resourceScrollTexts.label'
|
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({
|
defeated: new fields.SchemaField({
|
||||||
enabled: new fields.BooleanField({
|
enabled: new fields.BooleanField({
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -173,6 +168,13 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
||||||
label: 'DAGGERHEART.GENERAL.player.plurial'
|
label: 'DAGGERHEART.GENERAL.player.plurial'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
}),
|
||||||
|
triggers: new fields.SchemaField({
|
||||||
|
enabled: new fields.BooleanField({
|
||||||
|
nullable: false,
|
||||||
|
initial: true,
|
||||||
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.triggers.enabled.label'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,9 @@ export default class D20Roll extends DHRoll {
|
||||||
|
|
||||||
get isCritical() {
|
get isCritical() {
|
||||||
if (!this.d20._evaluated) return;
|
if (!this.d20._evaluated) return;
|
||||||
return this.d20.total >= this.data.system.criticalThreshold;
|
|
||||||
|
const criticalThreshold = this.options.actionType === 'reaction' ? 20 : this.data.system.criticalThreshold;
|
||||||
|
return this.d20.total >= criticalThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasAdvantage() {
|
get hasAdvantage() {
|
||||||
|
|
@ -127,15 +129,55 @@ export default class D20Roll extends DHRoll {
|
||||||
const modifiers = foundry.utils.deepClone(this.options.roll.baseModifiers) ?? [];
|
const modifiers = foundry.utils.deepClone(this.options.roll.baseModifiers) ?? [];
|
||||||
|
|
||||||
modifiers.push(
|
modifiers.push(
|
||||||
...this.getBonus(`roll.${this.options.actionType}`, `${this.options.actionType?.capitalize()} Bonus`)
|
...this.getBonus(
|
||||||
);
|
`system.bonuses.roll.${this.options.actionType}`,
|
||||||
modifiers.push(
|
`${this.options.actionType?.capitalize()} Bonus`
|
||||||
...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type?.capitalize()} Bonus`)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.attack.id) {
|
||||||
|
modifiers.push(
|
||||||
|
...this.getBonus(
|
||||||
|
`system.bonuses.roll.${this.options.roll.type}`,
|
||||||
|
`${this.options.roll.type?.capitalize()} Bonus`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.attack.id ||
|
||||||
|
(this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id && this.options.hasDamage)
|
||||||
|
) {
|
||||||
|
modifiers.push(
|
||||||
|
...this.getBonus(`system.bonuses.roll.attack`, `${this.options.roll.type?.capitalize()} Bonus`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return modifiers;
|
return modifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getActionChangeKeys() {
|
||||||
|
const changeKeys = new Set([`system.bonuses.roll.${this.options.actionType}`]);
|
||||||
|
|
||||||
|
if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.attack.id) {
|
||||||
|
changeKeys.add(`system.bonuses.roll.${this.options.roll.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.attack.id ||
|
||||||
|
(this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id && this.options.hasDamage)
|
||||||
|
) {
|
||||||
|
changeKeys.add(`system.bonuses.roll.attack`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.roll.trait && this.data.traits?.[this.options.roll.trait]) {
|
||||||
|
if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.spellcast.id)
|
||||||
|
changeKeys.add('system.bonuses.roll.trait');
|
||||||
|
}
|
||||||
|
|
||||||
|
return changeKeys;
|
||||||
|
}
|
||||||
|
|
||||||
static postEvaluate(roll, config = {}) {
|
static postEvaluate(roll, config = {}) {
|
||||||
const data = super.postEvaluate(roll, config);
|
const data = super.postEvaluate(roll, config);
|
||||||
data.type = config.actionType;
|
data.type = config.actionType;
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,6 @@ export default class DamageRoll extends DHRoll {
|
||||||
type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage'),
|
type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage'),
|
||||||
options = part ?? this.options;
|
options = part ?? this.options;
|
||||||
|
|
||||||
modifiers.push(...this.getBonus(`${type}`, `${type.capitalize()} Bonus`));
|
|
||||||
if (!this.options.hasHealing) {
|
if (!this.options.hasHealing) {
|
||||||
options.damageTypes?.forEach(t => {
|
options.damageTypes?.forEach(t => {
|
||||||
modifiers.push(...this.getBonus(`${type}.${t}`, `${t.capitalize()} ${type.capitalize()} Bonus`));
|
modifiers.push(...this.getBonus(`${type}.${t}`, `${t.capitalize()} ${type.capitalize()} Bonus`));
|
||||||
|
|
@ -108,6 +107,31 @@ export default class DamageRoll extends DHRoll {
|
||||||
return modifiers;
|
return modifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getActionChangeKeys() {
|
||||||
|
const type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage');
|
||||||
|
const changeKeys = [];
|
||||||
|
|
||||||
|
for (const roll of this.options.roll) {
|
||||||
|
for (const damageType of roll.damageTypes?.values?.() ?? []) {
|
||||||
|
changeKeys.push(`system.bonuses.${type}.${damageType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = this.data.parent?.items?.get(this.options.source.item);
|
||||||
|
if (item) {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'weapon':
|
||||||
|
if (!this.options.hasHealing)
|
||||||
|
['primaryWeapon', 'secondaryWeapon'].forEach(w =>
|
||||||
|
changeKeys.push(`system.bonuses.damage.${w}`)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changeKeys;
|
||||||
|
}
|
||||||
|
|
||||||
constructFormula(config) {
|
constructFormula(config) {
|
||||||
this.options.roll.forEach((part, index) => {
|
this.options.roll.forEach((part, index) => {
|
||||||
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ export default class DHRoll extends Roll {
|
||||||
baseTerms = [];
|
baseTerms = [];
|
||||||
constructor(formula, data = {}, options = {}) {
|
constructor(formula, data = {}, options = {}) {
|
||||||
super(formula, data, options);
|
super(formula, data, options);
|
||||||
|
options.bonusEffects = this.bonusEffectBuilder();
|
||||||
if (!this.data || !Object.keys(this.data).length) this.data = options.data;
|
if (!this.data || !Object.keys(this.data).length) this.data = options.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,12 +165,18 @@ export default class DHRoll extends Roll {
|
||||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
...this.constructor.parse(modifier.join(' + '), this.options.data)
|
...this.constructor.parse(modifier.join(' + '), this.options.data)
|
||||||
];
|
];
|
||||||
} else {
|
} else if (Number.isNumeric(modifier)) {
|
||||||
const numTerm = modifier < 0 ? '-' : '+';
|
const numTerm = modifier < 0 ? '-' : '+';
|
||||||
return [
|
return [
|
||||||
new foundry.dice.terms.OperatorTerm({ operator: numTerm }),
|
new foundry.dice.terms.OperatorTerm({ operator: numTerm }),
|
||||||
new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) })
|
new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) })
|
||||||
];
|
];
|
||||||
|
} else {
|
||||||
|
const numTerm = modifier < 0 ? '-' : '+';
|
||||||
|
return [
|
||||||
|
new foundry.dice.terms.OperatorTerm({ operator: numTerm }),
|
||||||
|
...this.constructor.parse(modifier, this.options.data)
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,18 +192,20 @@ export default class DHRoll extends Roll {
|
||||||
}
|
}
|
||||||
|
|
||||||
getBonus(path, label) {
|
getBonus(path, label) {
|
||||||
const bonus = foundry.utils.getProperty(this.data.bonuses, path),
|
const modifiers = [];
|
||||||
modifiers = [];
|
for (const effect of Object.values(this.options.bonusEffects)) {
|
||||||
if (bonus?.bonus)
|
if (!effect.selected) continue;
|
||||||
modifiers.push({
|
for (const change of effect.changes) {
|
||||||
label: label,
|
if (!change.key.includes(path)) continue;
|
||||||
value: bonus?.bonus
|
const changeValue = game.system.api.documents.DhActiveEffect.getChangeValue(
|
||||||
});
|
this.data,
|
||||||
if (bonus?.dice?.length)
|
change,
|
||||||
modifiers.push({
|
effect.origEffect
|
||||||
label: label,
|
);
|
||||||
value: bonus?.dice
|
modifiers.push({ label: label, value: changeValue });
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return modifiers;
|
return modifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,4 +244,28 @@ export default class DHRoll extends Roll {
|
||||||
static temporaryModifierBuilder(config) {
|
static temporaryModifierBuilder(config) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bonusEffectBuilder() {
|
||||||
|
const changeKeys = this.getActionChangeKeys();
|
||||||
|
return (
|
||||||
|
this.options.effects?.reduce((acc, effect) => {
|
||||||
|
if (effect.changes.some(x => changeKeys.some(key => x.key.includes(key)))) {
|
||||||
|
acc[effect.id] = {
|
||||||
|
id: effect.id,
|
||||||
|
name: effect.name,
|
||||||
|
description: effect.description,
|
||||||
|
changes: effect.changes,
|
||||||
|
origEffect: effect,
|
||||||
|
selected: !effect.disabled
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {}) ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getActionChangeKeys() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,9 +130,14 @@ export default class DualityRoll extends D20Roll {
|
||||||
this.terms = [this.terms[0], this.terms[1], this.terms[2]];
|
this.terms = [this.terms[0], this.terms[1], this.terms[2]];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.terms[0] = new foundry.dice.terms.Die({ faces: 12 });
|
|
||||||
|
this.terms[0] = new foundry.dice.terms.Die({
|
||||||
|
faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12
|
||||||
|
});
|
||||||
this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' });
|
this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' });
|
||||||
this.terms[2] = new foundry.dice.terms.Die({ faces: 12 });
|
this.terms[2] = new foundry.dice.terms.Die({
|
||||||
|
faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
applyAdvantage() {
|
applyAdvantage() {
|
||||||
|
|
@ -173,6 +178,34 @@ export default class DualityRoll extends D20Roll {
|
||||||
return modifiers;
|
return modifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getActionChangeKeys() {
|
||||||
|
const changeKeys = new Set([`system.bonuses.roll.${this.options.actionType}`]);
|
||||||
|
|
||||||
|
if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.attack.id) {
|
||||||
|
changeKeys.add(`system.bonuses.roll.${this.options.roll.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.attack.id ||
|
||||||
|
(this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id && this.options.hasDamage)
|
||||||
|
) {
|
||||||
|
changeKeys.add(`system.bonuses.roll.attack`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.roll.trait && this.data.traits?.[this.options.roll.trait]) {
|
||||||
|
if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.spellcast.id)
|
||||||
|
changeKeys.add('system.bonuses.roll.trait');
|
||||||
|
}
|
||||||
|
|
||||||
|
const weapons = ['primaryWeapon', 'secondaryWeapon'];
|
||||||
|
weapons.forEach(w => {
|
||||||
|
if (this.options.source.item && this.options.source.item === this.data[w]?.id)
|
||||||
|
changeKeys.add(`system.bonuses.roll.${w}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return changeKeys;
|
||||||
|
}
|
||||||
|
|
||||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||||
await super.buildEvaluate(roll, config, message);
|
await super.buildEvaluate(roll, config, message);
|
||||||
|
|
||||||
|
|
@ -224,6 +257,32 @@ export default class DualityRoll extends D20Roll {
|
||||||
await super.buildPost(roll, config, message);
|
await super.buildPost(roll, config, message);
|
||||||
|
|
||||||
await DualityRoll.dualityUpdate(config);
|
await DualityRoll.dualityUpdate(config);
|
||||||
|
await DualityRoll.handleTriggers(roll, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async handleTriggers(roll, config) {
|
||||||
|
if (!config.source?.actor) return;
|
||||||
|
|
||||||
|
const updates = [];
|
||||||
|
const dualityUpdates = await game.system.registeredTriggers.runTrigger(
|
||||||
|
CONFIG.DH.TRIGGER.triggers.dualityRoll.id,
|
||||||
|
roll.data?.parent,
|
||||||
|
roll,
|
||||||
|
roll.data?.parent
|
||||||
|
);
|
||||||
|
if (dualityUpdates?.length) updates.push(...dualityUpdates);
|
||||||
|
|
||||||
|
if (config.roll.result.duality === -1) {
|
||||||
|
const fearUpdates = await game.system.registeredTriggers.runTrigger(
|
||||||
|
CONFIG.DH.TRIGGER.triggers.fearRoll.id,
|
||||||
|
roll.data?.parent,
|
||||||
|
roll,
|
||||||
|
roll.data?.parent
|
||||||
|
);
|
||||||
|
if (fearUpdates?.length) updates.push(...fearUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.resourceUpdates.addResources(updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addDualityResourceUpdates(config) {
|
static async addDualityResourceUpdates(config) {
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,4 @@ export { default as DhScene } from './scene.mjs';
|
||||||
export { default as DhToken } from './token.mjs';
|
export { default as DhToken } from './token.mjs';
|
||||||
export { default as DhTooltipManager } from './tooltipManager.mjs';
|
export { default as DhTooltipManager } from './tooltipManager.mjs';
|
||||||
export { default as DhTemplateManager } from './templateManager.mjs';
|
export { default as DhTemplateManager } from './templateManager.mjs';
|
||||||
|
export { default as DhTokenManager } from './tokenManager.mjs';
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,10 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.parent?.type === 'domainCard') {
|
if (this.parent?.type === 'domainCard') {
|
||||||
return this.parent.system.inVault;
|
const isVaultSupressed = this.parent.system.isVaultSupressed;
|
||||||
|
const domainTouchedSupressed = this.parent.system.isDomainTouchedSuppressed;
|
||||||
|
|
||||||
|
return isVaultSupressed || domainTouchedSupressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.isSuppressed;
|
return super.isSuppressed;
|
||||||
|
|
@ -106,23 +109,29 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||||
|
|
||||||
/**@inheritdoc*/
|
/**@inheritdoc*/
|
||||||
static applyField(model, change, field) {
|
static applyField(model, change, field) {
|
||||||
const isOriginTarget = change.value.toLowerCase().includes('origin.@');
|
change.value = DhActiveEffect.getChangeValue(model, change, change.effect);
|
||||||
|
super.applyField(model, change, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
static getChangeValue(model, change, effect) {
|
||||||
|
let value = change.value;
|
||||||
|
const isOriginTarget = value.toLowerCase().includes('origin.@');
|
||||||
let parseModel = model;
|
let parseModel = model;
|
||||||
if (isOriginTarget && change.effect.origin) {
|
if (isOriginTarget && effect.origin) {
|
||||||
change.value = change.value.replaceAll(/origin\.@/gi, '@');
|
value = change.value.replaceAll(/origin\.@/gi, '@');
|
||||||
try {
|
try {
|
||||||
const effect = foundry.utils.fromUuidSync(change.effect.origin);
|
const originEffect = foundry.utils.fromUuidSync(effect.origin);
|
||||||
const doc =
|
const doc =
|
||||||
effect.parent?.parent instanceof game.system.api.documents.DhpActor
|
originEffect.parent?.parent instanceof game.system.api.documents.DhpActor
|
||||||
? effect.parent
|
? originEffect.parent
|
||||||
: effect.parent.parent;
|
: originEffect.parent.parent;
|
||||||
if (doc) parseModel = doc;
|
if (doc) parseModel = doc;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const evalValue = this.effectSafeEval(itemAbleRollParse(change.value, parseModel, change.effect.parent));
|
const evalValue = this.effectSafeEval(itemAbleRollParse(value, parseModel, effect.parent));
|
||||||
change.value = evalValue ?? change.value;
|
return evalValue ?? value;
|
||||||
super.applyField(model, change, field);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,16 @@ export default class DhpActor extends Actor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _preDelete() {
|
||||||
|
if (this.prototypeToken.actorLink) {
|
||||||
|
game.system.registeredTriggers.unregisterItemTriggers(this.items);
|
||||||
|
} else {
|
||||||
|
for (const token of this.getActiveTokens()) {
|
||||||
|
game.system.registeredTriggers.unregisterItemTriggers(token.actor.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onDelete(options, userId) {
|
_onDelete(options, userId) {
|
||||||
super._onDelete(options, userId);
|
super._onDelete(options, userId);
|
||||||
for (const party of this.parties) {
|
for (const party of this.parties) {
|
||||||
|
|
@ -646,6 +656,19 @@ export default class DhpActor extends Actor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const results = await game.system.registeredTriggers.runTrigger(
|
||||||
|
CONFIG.DH.TRIGGER.triggers.postDamageReduction.id,
|
||||||
|
this,
|
||||||
|
updates,
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
if (results?.length) {
|
||||||
|
const resourceMap = new ResourceUpdateMap(results[0].originActor);
|
||||||
|
for (var result of results) resourceMap.addResources(result.updates);
|
||||||
|
resourceMap.updateResources();
|
||||||
|
}
|
||||||
|
|
||||||
updates.forEach(
|
updates.forEach(
|
||||||
u =>
|
u =>
|
||||||
(u.value =
|
(u.value =
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,12 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const config = foundry.utils.deepClone(this.system);
|
const config = foundry.utils.deepClone(this.system);
|
||||||
config.event = event;
|
config.event = event;
|
||||||
await this.system.action?.workflow.get('damage')?.execute(config, this._id, true);
|
if (this.system.action) {
|
||||||
|
const actor = await foundry.utils.fromUuid(config.source.actor);
|
||||||
|
const item = actor?.items.get(config.source.item) ?? null;
|
||||||
|
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(actor, item);
|
||||||
|
await this.system.action.workflow.get('damage')?.execute(config, this._id, true);
|
||||||
|
}
|
||||||
|
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
|
@ -189,7 +194,16 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
|
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
|
||||||
|
|
||||||
this.consumeOnSuccess();
|
this.consumeOnSuccess();
|
||||||
this.system.action?.workflow.get('applyDamage')?.execute(config, targets, true);
|
if (this.system.action) this.system.action.workflow.get('applyDamage')?.execute(config, targets, true);
|
||||||
|
else {
|
||||||
|
for (const target of targets) {
|
||||||
|
const actor = await foundry.utils.fromUuid(target.actorId);
|
||||||
|
if (!actor) continue;
|
||||||
|
|
||||||
|
if (this.system.hasHealing) actor.takeHealing(this.system.damage);
|
||||||
|
else actor.takeDamage(this.system.damage);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onRollSave(event) {
|
async onRollSave(event) {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
static async createDocuments(sources, operation) {
|
static async createDocuments(sources, operation) {
|
||||||
// Ensure that items being created are valid to the actor its being added to
|
// Ensure that items being created are valid to the actor its being added to
|
||||||
const actor = operation.parent;
|
const actor = operation.parent;
|
||||||
sources = actor?.system?.isItemValid ? sources.filter((s) => actor.system.isItemValid(s)) : sources;
|
sources = actor?.system?.isItemValid ? sources.filter(s => actor.system.isItemValid(s)) : sources;
|
||||||
return super.createDocuments(sources, operation);
|
return super.createDocuments(sources, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,6 +146,16 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
async use(event) {
|
async use(event) {
|
||||||
|
/* DomainCard check. Can be expanded or made neater */
|
||||||
|
if (this.system.isDomainTouchedSuppressed) {
|
||||||
|
return ui.notifications.warn(
|
||||||
|
game.i18n.format('DAGGERHEART.UI.Notifications.domainTouchRequirement', {
|
||||||
|
nr: this.domainTouched,
|
||||||
|
domain: game.i18n.localize(CONFIG.DH.DOMAIN.allDomains()[this.domain].label)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const actions = new Set(this.system.actionsList);
|
const actions = new Set(this.system.actionsList);
|
||||||
if (actions?.size) {
|
if (actions?.size) {
|
||||||
let action = actions.first();
|
let action = actions.first();
|
||||||
|
|
@ -198,4 +208,23 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
|
|
||||||
cls.create(msg);
|
cls.create(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteTriggers() {
|
||||||
|
const actions = Array.from(this.system.actions ?? []);
|
||||||
|
if (!actions.length) return;
|
||||||
|
|
||||||
|
const triggerKeys = actions.flatMap(action => action.triggers.map(x => x.trigger));
|
||||||
|
|
||||||
|
game.system.registeredTriggers.unregisterTriggers(triggerKeys, this.uuid);
|
||||||
|
|
||||||
|
if (!(this.actor.parent instanceof game.system.api.documents.DhToken)) {
|
||||||
|
for (const token of this.actor.getActiveTokens()) {
|
||||||
|
game.system.registeredTriggers.unregisterTriggers(triggerKeys, `${token.document.uuid}.${this.uuid}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preDelete() {
|
||||||
|
this.deleteTriggers();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,4 +37,30 @@ export default class DhScene extends Scene {
|
||||||
this.#sizeSyncBatch.clear();
|
this.#sizeSyncBatch.clear();
|
||||||
this.updateEmbeddedDocuments('Token', entries, { animation: { movementSpeed: 1.5 } });
|
this.updateEmbeddedDocuments('Token', entries, { animation: { movementSpeed: 1.5 } });
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
|
prepareBaseData() {
|
||||||
|
super.prepareBaseData();
|
||||||
|
|
||||||
|
if (this instanceof game.system.api.documents.DhScene) {
|
||||||
|
const system = new game.system.api.data.scenes.DHScene(this.flags.daggerheart);
|
||||||
|
|
||||||
|
// Register this scene to all environements
|
||||||
|
for (const environment of system.sceneEnvironments) {
|
||||||
|
environment.system.scenes?.add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDelete(options, userId) {
|
||||||
|
super._onDelete(options, userId);
|
||||||
|
|
||||||
|
if (this instanceof game.system.api.documents.DhScene) {
|
||||||
|
const system = new game.system.api.data.scenes.DHScene(this.flags.daggerheart);
|
||||||
|
|
||||||
|
// Clear this scene from all environments that aren't deleted
|
||||||
|
for (const environment of system.sceneEnvironments) {
|
||||||
|
environment?.system?.scenes?.delete(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -536,4 +536,10 @@ export default class DHToken extends CONFIG.Token.documentClass {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
async _preDelete() {
|
||||||
|
if (this.actor && !this.actor.prototypeToken?.actorLink) {
|
||||||
|
game.system.registeredTriggers.unregisterItemTriggers(this.actor.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
104
module/documents/tokenManager.mjs
Normal file
104
module/documents/tokenManager.mjs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
/**
|
||||||
|
* A singleton class that handles preview tokens.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class DhTokenManager {
|
||||||
|
#activePreview;
|
||||||
|
#actor;
|
||||||
|
#resolve;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a template preview, deactivating any existing ones.
|
||||||
|
* @param {object} data
|
||||||
|
*/
|
||||||
|
async createPreview(actor, tokenData) {
|
||||||
|
this.#actor = actor;
|
||||||
|
const token = await canvas.tokens._createPreview(
|
||||||
|
{
|
||||||
|
...actor.prototypeToken,
|
||||||
|
displayName: 50,
|
||||||
|
...tokenData
|
||||||
|
},
|
||||||
|
{ renderSheet: false, actor }
|
||||||
|
);
|
||||||
|
|
||||||
|
this.#activePreview = {
|
||||||
|
document: token.document,
|
||||||
|
object: token,
|
||||||
|
origin: { x: token.document.x, y: token.document.y }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.#activePreview.events = {
|
||||||
|
contextmenu: this.#cancelTemplate.bind(this),
|
||||||
|
mousedown: this.#confirmTemplate.bind(this),
|
||||||
|
mousemove: this.#onDragMouseMove.bind(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
canvas.stage.on('mousemove', this.#activePreview.events.mousemove);
|
||||||
|
canvas.stage.on('mousedown', this.#activePreview.events.mousedown);
|
||||||
|
canvas.app.view.addEventListener('contextmenu', this.#activePreview.events.contextmenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Currently intended for using as a preview of where to create a token. (note the flag) */
|
||||||
|
async createPreviewAsync(actor, tokenData = {}) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
this.#resolve = resolve;
|
||||||
|
this.createPreview(actor, { ...tokenData, flags: { daggerheart: { createPlacement: true } } });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the movement of the token preview on mousedrag.
|
||||||
|
* @param {mousemove Event} event
|
||||||
|
*/
|
||||||
|
#onDragMouseMove(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const { moveTime, object } = this.#activePreview;
|
||||||
|
const update = {};
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - (moveTime || 0) <= 16) return;
|
||||||
|
this.#activePreview.moveTime = now;
|
||||||
|
|
||||||
|
let cursor = event.getLocalPosition(canvas.templates);
|
||||||
|
|
||||||
|
Object.assign(update, canvas.grid.getTopLeftPoint(cursor));
|
||||||
|
|
||||||
|
object.document.updateSource(update);
|
||||||
|
object.renderFlags.set({ refresh: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the preview token on right-click.
|
||||||
|
* @param {contextmenu Event} event
|
||||||
|
*/
|
||||||
|
#cancelTemplate(_event, resolved) {
|
||||||
|
const { mousemove, mousedown, contextmenu } = this.#activePreview.events;
|
||||||
|
this.#activePreview.object.destroy();
|
||||||
|
|
||||||
|
canvas.stage.off('mousemove', mousemove);
|
||||||
|
canvas.stage.off('mousedown', mousedown);
|
||||||
|
canvas.app.view.removeEventListener('contextmenu', contextmenu);
|
||||||
|
if (this.#resolve && !resolved) this.#resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a real Actor and token at the preview location and cancels the preview.
|
||||||
|
* @param {click Event} event
|
||||||
|
*/
|
||||||
|
async #confirmTemplate(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.#cancelTemplate(event, true);
|
||||||
|
|
||||||
|
const actor = this.#actor.inCompendium
|
||||||
|
? await game.system.api.documents.DhpActor.create(this.#actor.toObject())
|
||||||
|
: this.#actor;
|
||||||
|
const tokenData = await actor.getTokenDocument();
|
||||||
|
const result = await canvas.scene.createEmbeddedDocuments('Token', [
|
||||||
|
{ ...tokenData, x: this.#activePreview.document.x, y: this.#activePreview.document.y }
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.#activePreview = undefined;
|
||||||
|
if (this.#resolve && result.length) this.#resolve(result[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -67,7 +67,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
if (item) {
|
if (item) {
|
||||||
const isAction = item instanceof game.system.api.models.actions.actionsTypes.base;
|
const isAction = item instanceof game.system.api.models.actions.actionsTypes.base;
|
||||||
const isEffect = item instanceof ActiveEffect;
|
const isEffect = item instanceof ActiveEffect;
|
||||||
await this.enrichText(item, isAction || isEffect);
|
await this.enrichText(item);
|
||||||
|
|
||||||
const type = isAction ? 'action' : isEffect ? 'effect' : item.type;
|
const type = isAction ? 'action' : isEffect ? 'effect' : item.type;
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
html = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
|
@ -202,10 +202,20 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async enrichText(item, flatStructure) {
|
async enrichText(item) {
|
||||||
const { TextEditor } = foundry.applications.ux;
|
const { TextEditor } = foundry.applications.ux;
|
||||||
|
|
||||||
|
if (item.system?.metadata?.hasDescription) {
|
||||||
|
const enrichedValue =
|
||||||
|
(await item.system?.getEnrichedDescription?.()) ??
|
||||||
|
(await TextEditor.enrichHTML(item.system.description));
|
||||||
|
foundry.utils.setProperty(item, 'system.enrichedDescription', enrichedValue);
|
||||||
|
} else if (item.description) {
|
||||||
|
const enrichedValue = await TextEditor.enrichHTML(item.description);
|
||||||
|
foundry.utils.setProperty(item, 'enrichedDescription', enrichedValue);
|
||||||
|
}
|
||||||
|
|
||||||
const enrichPaths = [
|
const enrichPaths = [
|
||||||
{ path: flatStructure ? '' : 'system', name: 'description' },
|
|
||||||
{ path: 'system', name: 'features' },
|
{ path: 'system', name: 'features' },
|
||||||
{ path: 'system', name: 'actions' },
|
{ path: 'system', name: 'actions' },
|
||||||
{ path: 'system', name: 'customActions' }
|
{ path: 'system', name: 'customActions' }
|
||||||
|
|
@ -220,12 +230,15 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
for (const [index, itemValue] of pathValue.entries()) {
|
for (const [index, itemValue] of pathValue.entries()) {
|
||||||
const itemIsAction = itemValue instanceof game.system.api.models.actions.actionsTypes.base;
|
const itemIsAction = itemValue instanceof game.system.api.models.actions.actionsTypes.base;
|
||||||
const value = itemIsAction || !itemValue?.item ? itemValue : itemValue.item;
|
const value = itemIsAction || !itemValue?.item ? itemValue : itemValue.item;
|
||||||
const enrichedValue = await TextEditor.enrichHTML(value.system?.description ?? value.description);
|
const enrichedValue =
|
||||||
|
(await value.system?.getEnrichedDescription?.()) ??
|
||||||
|
(await TextEditor.enrichHTML(value.system?.description ?? value.description));
|
||||||
if (itemIsAction) value.enrichedDescription = enrichedValue;
|
if (itemIsAction) value.enrichedDescription = enrichedValue;
|
||||||
else foundry.utils.setProperty(item, `${basePath}.${index}.enrichedDescription`, enrichedValue);
|
else foundry.utils.setProperty(item, `${basePath}.${index}.enrichedDescription`, enrichedValue);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const enrichedValue = await TextEditor.enrichHTML(pathValue);
|
const enrichedValue =
|
||||||
|
(await item.system?.getEnrichedDescription?.()) ?? (await TextEditor.enrichHTML(pathValue));
|
||||||
foundry.utils.setProperty(
|
foundry.utils.setProperty(
|
||||||
item,
|
item,
|
||||||
`${data.path ? `${data.path}.` : ''}enriched${data.name.capitalize()}`,
|
`${data.path ? `${data.path}.` : ''}enriched${data.name.capitalize()}`,
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,9 @@ export const enrichedDualityRoll = async (
|
||||||
const config = {
|
const config = {
|
||||||
event: event ?? {},
|
event: event ?? {},
|
||||||
title: title,
|
title: title,
|
||||||
|
headerTitle: label,
|
||||||
roll: {
|
roll: {
|
||||||
trait: traitValue && target ? traitValue : null,
|
trait: traitValue && target ? traitValue : null,
|
||||||
label: label,
|
|
||||||
difficulty: difficulty,
|
difficulty: difficulty,
|
||||||
advantage,
|
advantage,
|
||||||
type: reaction ? 'reaction' : null
|
type: reaction ? 'reaction' : null
|
||||||
|
|
@ -101,7 +101,7 @@ export const enrichedDualityRoll = async (
|
||||||
await target.diceRoll(config);
|
await target.diceRoll(config);
|
||||||
} else {
|
} else {
|
||||||
// For no target, call DualityRoll directly with basic data
|
// For no target, call DualityRoll directly with basic data
|
||||||
config.data = { experiences: {}, traits: {} };
|
config.data = { experiences: {}, traits: {}, rules: {} };
|
||||||
config.source = { actor: null };
|
config.source = { actor: null };
|
||||||
await CONFIG.Dice.daggerheart.DualityRoll.build(config);
|
await CONFIG.Dice.daggerheart.DualityRoll.build(config);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ export const preloadHandlebarsTemplates = async function () {
|
||||||
'systems/daggerheart/templates/actionTypes/effect.hbs',
|
'systems/daggerheart/templates/actionTypes/effect.hbs',
|
||||||
'systems/daggerheart/templates/actionTypes/beastform.hbs',
|
'systems/daggerheart/templates/actionTypes/beastform.hbs',
|
||||||
'systems/daggerheart/templates/actionTypes/countdown.hbs',
|
'systems/daggerheart/templates/actionTypes/countdown.hbs',
|
||||||
|
'systems/daggerheart/templates/actionTypes/summon.hbs',
|
||||||
'systems/daggerheart/templates/settings/components/settings-item-line.hbs',
|
'systems/daggerheart/templates/settings/components/settings-item-line.hbs',
|
||||||
'systems/daggerheart/templates/ui/tooltip/parts/tooltipChips.hbs',
|
'systems/daggerheart/templates/ui/tooltip/parts/tooltipChips.hbs',
|
||||||
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs',
|
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs',
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@ export const GMUpdateEvent = {
|
||||||
export const RefreshType = {
|
export const RefreshType = {
|
||||||
Countdown: 'DhCoundownRefresh',
|
Countdown: 'DhCoundownRefresh',
|
||||||
TagTeamRoll: 'DhTagTeamRollRefresh',
|
TagTeamRoll: 'DhTagTeamRollRefresh',
|
||||||
EffectsDisplay: 'DhEffectsDisplayRefresh'
|
EffectsDisplay: 'DhEffectsDisplayRefresh',
|
||||||
|
Scene: 'DhSceneRefresh'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const registerSocketHooks = () => {
|
export const registerSocketHooks = () => {
|
||||||
|
|
|
||||||
|
|
@ -533,33 +533,31 @@
|
||||||
"description": "<p><strong>Spend a Fear</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.YhJrP7rTBiRdX5Fp]{Zombie Legion}, which appears at Close range and immediately takes the spotlight.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.YhJrP7rTBiRdX5Fp]{Zombie Legion}, which appears at Close range and immediately takes the spotlight.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"gZg3AkzCYUTExjE6": {
|
"qSuWxC8xQOhnbBx9": {
|
||||||
"type": "effect",
|
"type": "summon",
|
||||||
"_id": "gZg3AkzCYUTExjE6",
|
"_id": "qSuWxC8xQOhnbBx9",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [],
|
||||||
{
|
|
||||||
"scalable": false,
|
|
||||||
"key": "fear",
|
|
||||||
"value": 1,
|
|
||||||
"step": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"uses": {
|
"uses": {
|
||||||
"value": null,
|
"value": null,
|
||||||
"max": "",
|
"max": "",
|
||||||
"recovery": null
|
"recovery": null,
|
||||||
},
|
"consumeOnSuccess": false
|
||||||
"effects": [],
|
|
||||||
"target": {
|
|
||||||
"type": "any",
|
|
||||||
"amount": null
|
|
||||||
},
|
},
|
||||||
|
"summon": [
|
||||||
|
{
|
||||||
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.YhJrP7rTBiRdX5Fp",
|
||||||
|
"count": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
"name": "Spend Fear",
|
"name": "Spend Fear",
|
||||||
"img": "icons/magic/death/undead-zombie-grave-green.webp",
|
|
||||||
"range": ""
|
"range": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -312,7 +312,14 @@
|
||||||
"range": "melee"
|
"range": "melee"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"changes": [],
|
"changes": [
|
||||||
|
{
|
||||||
|
"key": "system.rules.dualityRoll.defaultHopeDice",
|
||||||
|
"mode": 5,
|
||||||
|
"value": "d8",
|
||||||
|
"priority": null
|
||||||
|
}
|
||||||
|
],
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"duration": {
|
"duration": {
|
||||||
"startTime": null,
|
"startTime": null,
|
||||||
|
|
@ -323,7 +330,7 @@
|
||||||
"startRound": null,
|
"startRound": null,
|
||||||
"startTurn": null
|
"startTurn": null
|
||||||
},
|
},
|
||||||
"description": "<p>All targets aff ected replace their Hope Die with a <strong>d8</strong> until they roll a success with Hope or their next rest.</p>",
|
"description": "<p>All targets affected replace their Hope Die with a <strong>d8</strong> until they roll a success with Hope or their next rest.</p>",
|
||||||
"tint": "#ffffff",
|
"tint": "#ffffff",
|
||||||
"statuses": [],
|
"statuses": [],
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
|
|
|
||||||
|
|
@ -256,34 +256,45 @@
|
||||||
"description": "<p><strong>Spend a Fear</strong> to boil the blood of all PCs within Far range. They use a d20 as their Fear Die until the end of the scene.</p><p>@Template[type:emanation|range:f]</p>",
|
"description": "<p><strong>Spend a Fear</strong> to boil the blood of all PCs within Far range. They use a d20 as their Fear Die until the end of the scene.</p><p>@Template[type:emanation|range:f]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"V142qYppCGJn8OiN": {
|
"jKvzbQT0vp66DDOH": {
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "V142qYppCGJn8OiN",
|
"_id": "jKvzbQT0vp66DDOH",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
{
|
{
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"step": null
|
"itemId": null,
|
||||||
|
"step": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uses": {
|
"uses": {
|
||||||
"value": null,
|
"value": null,
|
||||||
"max": "",
|
"max": "",
|
||||||
"recovery": null
|
"recovery": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
},
|
},
|
||||||
"effects": [],
|
"effects": [
|
||||||
|
{
|
||||||
|
"_id": "gFeHLGgeRoDdd3VG",
|
||||||
|
"onSave": false
|
||||||
|
}
|
||||||
|
],
|
||||||
"target": {
|
"target": {
|
||||||
"type": "self",
|
"type": "hostile",
|
||||||
"amount": null
|
"amount": null
|
||||||
},
|
},
|
||||||
"name": "Spend Fear",
|
"name": "Spend Fear",
|
||||||
"img": "icons/skills/melee/maneuver-greatsword-yellow.webp",
|
"range": "far"
|
||||||
"range": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
@ -292,7 +303,51 @@
|
||||||
},
|
},
|
||||||
"_id": "a33PW8UkziliowlR",
|
"_id": "a33PW8UkziliowlR",
|
||||||
"img": "icons/skills/melee/maneuver-greatsword-yellow.webp",
|
"img": "icons/skills/melee/maneuver-greatsword-yellow.webp",
|
||||||
"effects": [],
|
"effects": [
|
||||||
|
{
|
||||||
|
"name": "Battle Lust",
|
||||||
|
"img": "icons/skills/melee/maneuver-greatsword-yellow.webp",
|
||||||
|
"origin": "Compendium.daggerheart.adversaries.Actor.5lphJAgzoqZI3VoG.Item.a33PW8UkziliowlR",
|
||||||
|
"transfer": false,
|
||||||
|
"_id": "gFeHLGgeRoDdd3VG",
|
||||||
|
"type": "base",
|
||||||
|
"system": {
|
||||||
|
"rangeDependence": {
|
||||||
|
"enabled": false,
|
||||||
|
"type": "withinRange",
|
||||||
|
"target": "hostile",
|
||||||
|
"range": "melee"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"key": "system.rules.dualityRoll.defaultFearDice",
|
||||||
|
"mode": 5,
|
||||||
|
"value": "d20",
|
||||||
|
"priority": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"disabled": false,
|
||||||
|
"duration": {
|
||||||
|
"startTime": null,
|
||||||
|
"combat": null,
|
||||||
|
"seconds": null,
|
||||||
|
"rounds": null,
|
||||||
|
"turns": null,
|
||||||
|
"startRound": null,
|
||||||
|
"startTurn": null
|
||||||
|
},
|
||||||
|
"description": "<p>You use a d20 as your Fear Die until the end of the scene.</p>",
|
||||||
|
"tint": "#ffffff",
|
||||||
|
"statuses": [],
|
||||||
|
"sort": 0,
|
||||||
|
"flags": {},
|
||||||
|
"_stats": {
|
||||||
|
"compendiumSource": null
|
||||||
|
},
|
||||||
|
"_key": "!actors.items.effects!5lphJAgzoqZI3VoG.a33PW8UkziliowlR.gFeHLGgeRoDdd3VG"
|
||||||
|
}
|
||||||
|
],
|
||||||
"folder": null,
|
"folder": null,
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
|
|
@ -457,11 +512,12 @@
|
||||||
"img": "icons/creatures/unholy/demon-fire-horned-clawed.webp",
|
"img": "icons/creatures/unholy/demon-fire-horned-clawed.webp",
|
||||||
"range": ""
|
"range": ""
|
||||||
},
|
},
|
||||||
"7G6uWlFEeOLsJIWY": {
|
"FlE6i0tbKEguF9wz": {
|
||||||
"type": "effect",
|
"type": "summon",
|
||||||
"_id": "7G6uWlFEeOLsJIWY",
|
"_id": "FlE6i0tbKEguF9wz",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>Summon [[/r 1d4]]@UUID[Compendium.daggerheart.adversaries.Actor.3tqCjDwJAQ7JKqMb]{Minor Demons}, who appear at Close range.</p>",
|
"baseAction": false,
|
||||||
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"originItem": {
|
"originItem": {
|
||||||
"type": "itemCollection"
|
"type": "itemCollection"
|
||||||
|
|
@ -474,13 +530,13 @@
|
||||||
"recovery": null,
|
"recovery": null,
|
||||||
"consumeOnSuccess": false
|
"consumeOnSuccess": false
|
||||||
},
|
},
|
||||||
"effects": [],
|
"summon": [
|
||||||
"target": {
|
{
|
||||||
"type": "any",
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.3tqCjDwJAQ7JKqMb",
|
||||||
"amount": null
|
"count": "1d4"
|
||||||
},
|
}
|
||||||
|
],
|
||||||
"name": "Summon",
|
"name": "Summon",
|
||||||
"img": "icons/creatures/unholy/demon-fire-horned-clawed.webp",
|
|
||||||
"range": ""
|
"range": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -363,33 +363,31 @@
|
||||||
"description": "<p><strong>Spend a Fear</strong> to grow three @UUID[Compendium.daggerheart.adversaries.Actor.o63nS0k3wHu6EgKP]{Treant Sapling Minions}, who appear at Close range and immediately take the spotlight.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to grow three @UUID[Compendium.daggerheart.adversaries.Actor.o63nS0k3wHu6EgKP]{Treant Sapling Minions}, who appear at Close range and immediately take the spotlight.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"84Q2b0zIY9c7Yhho": {
|
"R84DdS0OIx2cUt1w": {
|
||||||
"type": "effect",
|
"type": "summon",
|
||||||
"_id": "84Q2b0zIY9c7Yhho",
|
"_id": "R84DdS0OIx2cUt1w",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [],
|
||||||
{
|
|
||||||
"scalable": false,
|
|
||||||
"key": "fear",
|
|
||||||
"value": 1,
|
|
||||||
"step": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"uses": {
|
"uses": {
|
||||||
"value": null,
|
"value": null,
|
||||||
"max": "",
|
"max": "",
|
||||||
"recovery": null
|
"recovery": null,
|
||||||
},
|
"consumeOnSuccess": false
|
||||||
"effects": [],
|
|
||||||
"target": {
|
|
||||||
"type": "self",
|
|
||||||
"amount": null
|
|
||||||
},
|
},
|
||||||
|
"summon": [
|
||||||
|
{
|
||||||
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.o63nS0k3wHu6EgKP",
|
||||||
|
"count": "3"
|
||||||
|
}
|
||||||
|
],
|
||||||
"name": "Spend Fear",
|
"name": "Spend Fear",
|
||||||
"img": "icons/magic/unholy/orb-hands-pink.webp",
|
|
||||||
"range": ""
|
"range": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -510,34 +510,41 @@
|
||||||
"description": "<p>When the @Lookup[@name] has 3 or more HP marked, you can <strong>spend a Fear</strong> to split them into two @UUID[Compendium.daggerheart.adversaries.Actor.aLkLFuVoKz2NLoBK]{Tiny Green Oozes} (with no marked HP or Stress). Immediately spotlight both of them.</p>",
|
"description": "<p>When the @Lookup[@name] has 3 or more HP marked, you can <strong>spend a Fear</strong> to split them into two @UUID[Compendium.daggerheart.adversaries.Actor.aLkLFuVoKz2NLoBK]{Tiny Green Oozes} (with no marked HP or Stress). Immediately spotlight both of them.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"s5mLw6DRGd76MLcC": {
|
"J8U7dw3cDSsEirr5": {
|
||||||
"type": "effect",
|
"type": "summon",
|
||||||
"_id": "s5mLw6DRGd76MLcC",
|
"_id": "J8U7dw3cDSsEirr5",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
{
|
{
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"step": null
|
"itemId": null,
|
||||||
|
"step": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uses": {
|
"uses": {
|
||||||
"value": null,
|
"value": null,
|
||||||
"max": "",
|
"max": "",
|
||||||
"recovery": null
|
"recovery": null,
|
||||||
},
|
"consumeOnSuccess": false
|
||||||
"effects": [],
|
|
||||||
"target": {
|
|
||||||
"type": "self",
|
|
||||||
"amount": null
|
|
||||||
},
|
},
|
||||||
|
"summon": [
|
||||||
|
{
|
||||||
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.aLkLFuVoKz2NLoBK",
|
||||||
|
"count": "2"
|
||||||
|
}
|
||||||
|
],
|
||||||
"name": "Spend Fear",
|
"name": "Spend Fear",
|
||||||
"img": "icons/creatures/slimes/slime-movement-pseudopods-green.webp",
|
"range": "self"
|
||||||
"range": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
|
||||||
|
|
@ -474,33 +474,31 @@
|
||||||
"description": "<p><strong>Spend 2 Fear</strong> to summon [[/r 1d4]] @UUID[Compendium.daggerheart.adversaries.Actor.WWyUp6Mxl1S3KYUG]{Vampires}, who appear at Far range and immediately take the spotlight.</p>",
|
"description": "<p><strong>Spend 2 Fear</strong> to summon [[/r 1d4]] @UUID[Compendium.daggerheart.adversaries.Actor.WWyUp6Mxl1S3KYUG]{Vampires}, who appear at Far range and immediately take the spotlight.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"5Q6RMUTiauKw0tDj": {
|
"jGFOnU6PNdWU6iF4": {
|
||||||
"type": "effect",
|
"type": "summon",
|
||||||
"_id": "5Q6RMUTiauKw0tDj",
|
"_id": "jGFOnU6PNdWU6iF4",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [],
|
||||||
{
|
|
||||||
"scalable": false,
|
|
||||||
"key": "fear",
|
|
||||||
"value": 2,
|
|
||||||
"step": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"uses": {
|
"uses": {
|
||||||
"value": null,
|
"value": null,
|
||||||
"max": "",
|
"max": "",
|
||||||
"recovery": null
|
"recovery": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
},
|
},
|
||||||
"effects": [],
|
"summon": [
|
||||||
"target": {
|
{
|
||||||
"type": "any",
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.WWyUp6Mxl1S3KYUG",
|
||||||
"amount": null
|
"count": "1d4"
|
||||||
},
|
}
|
||||||
"name": "Summon Vampires",
|
],
|
||||||
"img": "icons/creatures/mammals/bat-giant-tattered-purple.webp",
|
"name": "Spend Fear",
|
||||||
"range": ""
|
"range": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -479,33 +479,31 @@
|
||||||
"description": "<p>When the @Lookup[@name] has 4 or more HP marked, you can <strong>spend a Fear</strong> to split them into two @UUID[Compendium.daggerheart.adversaries.Actor.SHXedd9zZPVfUgUa]{Green Oozes}(with no marked HP or Stress). Immediately spotlight both of them.</p>",
|
"description": "<p>When the @Lookup[@name] has 4 or more HP marked, you can <strong>spend a Fear</strong> to split them into two @UUID[Compendium.daggerheart.adversaries.Actor.SHXedd9zZPVfUgUa]{Green Oozes}(with no marked HP or Stress). Immediately spotlight both of them.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"iQsYAqpUFvJslRDr": {
|
"aeRdkiRsDNagTKhp": {
|
||||||
"type": "effect",
|
"type": "summon",
|
||||||
"_id": "iQsYAqpUFvJslRDr",
|
"_id": "aeRdkiRsDNagTKhp",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [],
|
||||||
{
|
|
||||||
"scalable": false,
|
|
||||||
"key": "fear",
|
|
||||||
"value": 1,
|
|
||||||
"step": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"uses": {
|
"uses": {
|
||||||
"value": null,
|
"value": null,
|
||||||
"max": "",
|
"max": "",
|
||||||
"recovery": null
|
"recovery": null,
|
||||||
},
|
"consumeOnSuccess": false
|
||||||
"effects": [],
|
|
||||||
"target": {
|
|
||||||
"type": "any",
|
|
||||||
"amount": null
|
|
||||||
},
|
},
|
||||||
|
"summon": [
|
||||||
|
{
|
||||||
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.SHXedd9zZPVfUgUa",
|
||||||
|
"count": "2"
|
||||||
|
}
|
||||||
|
],
|
||||||
"name": "Spend Fear",
|
"name": "Spend Fear",
|
||||||
"img": "icons/creatures/slimes/slime-movement-pseudopods-green.webp",
|
|
||||||
"range": ""
|
"range": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,35 @@
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>Summon three @Compendium[daggerheart.adversaries.Actor.C0OMQqV7pN6t7ouR], who appear at Far range.</p>",
|
"description": "<p>Summon three @Compendium[daggerheart.adversaries.Actor.C0OMQqV7pN6t7ouR], who appear at Far range.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {
|
||||||
|
"MCTBsw9lusUdubj0": {
|
||||||
|
"type": "summon",
|
||||||
|
"_id": "MCTBsw9lusUdubj0",
|
||||||
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
|
"description": "",
|
||||||
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
|
"actionType": "action",
|
||||||
|
"cost": [],
|
||||||
|
"uses": {
|
||||||
|
"value": null,
|
||||||
|
"max": "",
|
||||||
|
"recovery": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
|
},
|
||||||
|
"summon": [
|
||||||
|
{
|
||||||
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.C0OMQqV7pN6t7ouR",
|
||||||
|
"count": "3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Summon",
|
||||||
|
"range": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"subType": null,
|
"subType": null,
|
||||||
"originId": null,
|
"originId": null,
|
||||||
|
|
|
||||||
|
|
@ -258,57 +258,40 @@
|
||||||
"description": "<p>Once per scene, <strong>mark a Stress</strong> to summon <strong>1d4</strong> @UUID[Compendium.daggerheart.adversaries.Actor.B4LZcGuBAHzyVdzy]{Bladed Guards}, who appear at Far range to enforce the @Lookup[@name]’s will.</p>",
|
"description": "<p>Once per scene, <strong>mark a Stress</strong> to summon <strong>1d4</strong> @UUID[Compendium.daggerheart.adversaries.Actor.B4LZcGuBAHzyVdzy]{Bladed Guards}, who appear at Far range to enforce the @Lookup[@name]’s will.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"cUKwhq1imsTVru8D": {
|
"tioTtYfIGFIXRITN": {
|
||||||
"type": "attack",
|
"type": "summon",
|
||||||
"_id": "cUKwhq1imsTVru8D",
|
"_id": "tioTtYfIGFIXRITN",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>Once per scene, <strong>mark a Stress</strong> to summon <strong>1d4</strong> @UUID[Compendium.daggerheart.adversaries.Actor.B4LZcGuBAHzyVdzy]{Bladed Guards}, who appear at Far range to enforce the Noble’s will.</p>",
|
"baseAction": false,
|
||||||
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
{
|
{
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"step": null
|
"itemId": null,
|
||||||
|
"step": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uses": {
|
"uses": {
|
||||||
"value": null,
|
"value": null,
|
||||||
"max": "",
|
"max": "1",
|
||||||
"recovery": null
|
"recovery": "scene",
|
||||||
},
|
"consumeOnSuccess": false
|
||||||
"damage": {
|
|
||||||
"parts": [],
|
|
||||||
"includeBase": false
|
|
||||||
},
|
|
||||||
"target": {
|
|
||||||
"type": "any",
|
|
||||||
"amount": null
|
|
||||||
},
|
|
||||||
"effects": [],
|
|
||||||
"roll": {
|
|
||||||
"type": "diceSet",
|
|
||||||
"trait": null,
|
|
||||||
"difficulty": null,
|
|
||||||
"bonus": null,
|
|
||||||
"advState": "neutral",
|
|
||||||
"diceRolling": {
|
|
||||||
"multiplier": "prof",
|
|
||||||
"flatMultiplier": 1,
|
|
||||||
"dice": "d4",
|
|
||||||
"compare": null,
|
|
||||||
"treshold": null
|
|
||||||
},
|
|
||||||
"useDefault": false
|
|
||||||
},
|
|
||||||
"save": {
|
|
||||||
"trait": null,
|
|
||||||
"difficulty": null,
|
|
||||||
"damageMod": "none"
|
|
||||||
},
|
},
|
||||||
|
"summon": [
|
||||||
|
{
|
||||||
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.B4LZcGuBAHzyVdzy",
|
||||||
|
"count": "1d4"
|
||||||
|
}
|
||||||
|
],
|
||||||
"name": "Summon Guards",
|
"name": "Summon Guards",
|
||||||
"img": "icons/environment/people/infantry-armored.webp",
|
|
||||||
"range": ""
|
"range": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -313,36 +313,43 @@
|
||||||
"_id": "WGEGO0DSOs5cF0EL",
|
"_id": "WGEGO0DSOs5cF0EL",
|
||||||
"img": "icons/environment/people/charge.webp",
|
"img": "icons/environment/people/charge.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>Once per scene, <strong>mark a Stress</strong> to summon a Pirate Raiders Horde, which appears at Far range.</p>",
|
"description": "<p>Once per scene, <strong>mark a Stress</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.5YgEajn0wa4i85kC]{Pirate Raider Horde}, which appears at Far range.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"NlgIp0KrmZoS27Xy": {
|
"nuYk5WeLLpIKa69q": {
|
||||||
"type": "effect",
|
"type": "summon",
|
||||||
"_id": "NlgIp0KrmZoS27Xy",
|
"_id": "nuYk5WeLLpIKa69q",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
{
|
{
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"step": null
|
"itemId": null,
|
||||||
|
"step": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uses": {
|
"uses": {
|
||||||
"value": null,
|
"value": null,
|
||||||
"max": "",
|
"max": "",
|
||||||
"recovery": null
|
"recovery": null,
|
||||||
},
|
"consumeOnSuccess": false
|
||||||
"effects": [],
|
|
||||||
"target": {
|
|
||||||
"type": "any",
|
|
||||||
"amount": null
|
|
||||||
},
|
},
|
||||||
|
"summon": [
|
||||||
|
{
|
||||||
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.5YgEajn0wa4i85kC",
|
||||||
|
"count": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
"name": "Mark Stress",
|
"name": "Mark Stress",
|
||||||
"img": "icons/environment/people/charge.webp",
|
|
||||||
"range": ""
|
"range": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -454,33 +454,40 @@
|
||||||
"description": "<p>When the @Lookup[@name] has 3 or more HP marked, you can <strong>spend a Fear</strong> to split them into two @UUID[Compendium.daggerheart.adversaries.Actor.1fkLQXVtmILqfJ44]{Tiny Red Oozes} (with no marked HP or Stress). Immediately spotlight both of them.</p>",
|
"description": "<p>When the @Lookup[@name] has 3 or more HP marked, you can <strong>spend a Fear</strong> to split them into two @UUID[Compendium.daggerheart.adversaries.Actor.1fkLQXVtmILqfJ44]{Tiny Red Oozes} (with no marked HP or Stress). Immediately spotlight both of them.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"dw6Juw8mriH7sg0e": {
|
"BMEr77hDxaQyYBna": {
|
||||||
"type": "effect",
|
"type": "summon",
|
||||||
"_id": "dw6Juw8mriH7sg0e",
|
"_id": "BMEr77hDxaQyYBna",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
{
|
{
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"step": null
|
"itemId": null,
|
||||||
|
"step": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uses": {
|
"uses": {
|
||||||
"value": null,
|
"value": null,
|
||||||
"max": "",
|
"max": "",
|
||||||
"recovery": null
|
"recovery": null,
|
||||||
},
|
"consumeOnSuccess": false
|
||||||
"effects": [],
|
|
||||||
"target": {
|
|
||||||
"type": "any",
|
|
||||||
"amount": null
|
|
||||||
},
|
},
|
||||||
|
"summon": [
|
||||||
|
{
|
||||||
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.1fkLQXVtmILqfJ44",
|
||||||
|
"count": "2"
|
||||||
|
}
|
||||||
|
],
|
||||||
"name": "Spend Fear",
|
"name": "Spend Fear",
|
||||||
"img": "icons/creatures/slimes/slime-movement-splashing-red.webp",
|
|
||||||
"range": ""
|
"range": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -416,28 +416,6 @@
|
||||||
"description": "<p><em>Countdown (6)</em>. When the @Lookup[@name] is in the spotlight for the first time, activate the countdown. When they mark HP, tick down this countdown by the number of HP marked. When it triggers, summon a @UUID[Compendium.daggerheart.adversaries.Actor.3tqCjDwJAQ7JKqMb]{Minor Demon} who appears at Close range.</p>",
|
"description": "<p><em>Countdown (6)</em>. When the @Lookup[@name] is in the spotlight for the first time, activate the countdown. When they mark HP, tick down this countdown by the number of HP marked. When it triggers, summon a @UUID[Compendium.daggerheart.adversaries.Actor.3tqCjDwJAQ7JKqMb]{Minor Demon} who appears at Close range.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"0rixG6jLRynAYNqA": {
|
|
||||||
"type": "effect",
|
|
||||||
"_id": "0rixG6jLRynAYNqA",
|
|
||||||
"systemPath": "actions",
|
|
||||||
"description": "<p>Summon a @UUID[Compendium.daggerheart.adversaries.Actor.3tqCjDwJAQ7JKqMb]{Minor Demon} who appears at Close range.</p>",
|
|
||||||
"chatDisplay": true,
|
|
||||||
"actionType": "action",
|
|
||||||
"cost": [],
|
|
||||||
"uses": {
|
|
||||||
"value": null,
|
|
||||||
"max": "",
|
|
||||||
"recovery": null
|
|
||||||
},
|
|
||||||
"effects": [],
|
|
||||||
"target": {
|
|
||||||
"type": "any",
|
|
||||||
"amount": null
|
|
||||||
},
|
|
||||||
"name": "Summon",
|
|
||||||
"img": "icons/magic/unholy/silhouette-light-fire-blue.webp",
|
|
||||||
"range": "close"
|
|
||||||
},
|
|
||||||
"ZVXHY2fpomoKV7jG": {
|
"ZVXHY2fpomoKV7jG": {
|
||||||
"type": "countdown",
|
"type": "countdown",
|
||||||
"_id": "ZVXHY2fpomoKV7jG",
|
"_id": "ZVXHY2fpomoKV7jG",
|
||||||
|
|
@ -474,6 +452,33 @@
|
||||||
"name": "Start Countdown",
|
"name": "Start Countdown",
|
||||||
"img": "icons/magic/unholy/silhouette-light-fire-blue.webp",
|
"img": "icons/magic/unholy/silhouette-light-fire-blue.webp",
|
||||||
"range": ""
|
"range": ""
|
||||||
|
},
|
||||||
|
"YReYG6DrWp4QGSij": {
|
||||||
|
"type": "summon",
|
||||||
|
"_id": "YReYG6DrWp4QGSij",
|
||||||
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
|
"description": "",
|
||||||
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
|
"actionType": "action",
|
||||||
|
"cost": [],
|
||||||
|
"uses": {
|
||||||
|
"value": null,
|
||||||
|
"max": "",
|
||||||
|
"recovery": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
|
},
|
||||||
|
"summon": [
|
||||||
|
{
|
||||||
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.3tqCjDwJAQ7JKqMb",
|
||||||
|
"count": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Summon",
|
||||||
|
"range": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
@ -502,33 +507,31 @@
|
||||||
"description": "<p>Once per scene, when the @Lookup[@name] marks 2 or more HP, you can <strong>mark a Stress</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.NoRZ1PqB8N5wcIw0]{Demonic Hound Pack}, which appears at Close range and is immediately spotlighted.</p>",
|
"description": "<p>Once per scene, when the @Lookup[@name] marks 2 or more HP, you can <strong>mark a Stress</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.NoRZ1PqB8N5wcIw0]{Demonic Hound Pack}, which appears at Close range and is immediately spotlighted.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"JBuQUJhif2A7IlJd": {
|
"tfmY6HYkkY27NBaF": {
|
||||||
"type": "effect",
|
"type": "summon",
|
||||||
"_id": "JBuQUJhif2A7IlJd",
|
"_id": "tfmY6HYkkY27NBaF",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [],
|
||||||
{
|
|
||||||
"scalable": false,
|
|
||||||
"key": "stress",
|
|
||||||
"value": 1,
|
|
||||||
"step": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"uses": {
|
"uses": {
|
||||||
"value": null,
|
"value": null,
|
||||||
"max": "1",
|
"max": "",
|
||||||
"recovery": "scene"
|
"recovery": null,
|
||||||
},
|
"consumeOnSuccess": false
|
||||||
"effects": [],
|
|
||||||
"target": {
|
|
||||||
"type": "self",
|
|
||||||
"amount": null
|
|
||||||
},
|
},
|
||||||
|
"summon": [
|
||||||
|
{
|
||||||
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.NoRZ1PqB8N5wcIw0",
|
||||||
|
"count": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
"name": "Mark Stress",
|
"name": "Mark Stress",
|
||||||
"img": "icons/creatures/unholy/demon-fire-horned-clawed.webp",
|
|
||||||
"range": ""
|
"range": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -340,7 +340,35 @@
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When an attack from the @Lookup[@name] causes a target to mark HP and there are three or more @Lookup[@name] Minions within Close range, you can combine the Minions into a @UUID[Compendium.daggerheart.adversaries.Actor.PKSXFuaIHUCoH63A]{Tangle Bramble Swarm Horde}. The Horde’s HP is equal to the number of Minions combined.</p>",
|
"description": "<p>When an attack from the @Lookup[@name] causes a target to mark HP and there are three or more @Lookup[@name] Minions within Close range, you can combine the Minions into a @UUID[Compendium.daggerheart.adversaries.Actor.PKSXFuaIHUCoH63A]{Tangle Bramble Swarm Horde}. The Horde’s HP is equal to the number of Minions combined.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {
|
||||||
|
"g1OQ5xlMHFWsoktd": {
|
||||||
|
"type": "summon",
|
||||||
|
"_id": "g1OQ5xlMHFWsoktd",
|
||||||
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
|
"description": "",
|
||||||
|
"chatDisplay": true,
|
||||||
|
"originItem": {
|
||||||
|
"type": "itemCollection"
|
||||||
|
},
|
||||||
|
"actionType": "action",
|
||||||
|
"cost": [],
|
||||||
|
"uses": {
|
||||||
|
"value": null,
|
||||||
|
"max": "",
|
||||||
|
"recovery": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
|
},
|
||||||
|
"summon": [
|
||||||
|
{
|
||||||
|
"actorUUID": "Compendium.daggerheart.adversaries.Actor.PKSXFuaIHUCoH63A",
|
||||||
|
"count": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Summon",
|
||||||
|
"range": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"subType": null,
|
"subType": null,
|
||||||
"originId": null,
|
"originId": null,
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,14 @@
|
||||||
},
|
},
|
||||||
"name": "Clear Stress",
|
"name": "Clear Stress",
|
||||||
"img": "icons/magic/symbols/rune-sigil-black-pink.webp",
|
"img": "icons/magic/symbols/rune-sigil-black-pink.webp",
|
||||||
"range": ""
|
"range": "",
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"trigger": "dualityRoll",
|
||||||
|
"triggeringActorType": "self",
|
||||||
|
"command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n <div><div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}</div>\n <div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}</div>\n <div class=\"flexrow\" style=\"gap: 8px;\">\n <button type=\"button\" id=\"hopeButton\">\n <i class=\"fa-solid fa-hands-holding\"></i>\n <label>0</label>\n </button>\n <button type=\"button\" id=\"stressButton\">\n <i class=\"fa-solid fa-bolt-lightning\"></i>\n <label>0</label>\n </button>\n </div>\n</div>`;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,8 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 120,
|
"page": 120,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"domainTouched": 4
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "5PvMQKCjrgSxzstn",
|
"_id": "5PvMQKCjrgSxzstn",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 121,
|
"page": 121,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"domainTouched": 4
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "Gb5bqpFSBiuBxUix",
|
"_id": "Gb5bqpFSBiuBxUix",
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,8 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 123,
|
"page": 123,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"domainTouched": 4
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "ON5bvnoQBy0SYc9Y",
|
"_id": "ON5bvnoQBy0SYc9Y",
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,8 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 125,
|
"page": 125,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"domainTouched": 4
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "7Pu83ABdMukTxu3e",
|
"_id": "7Pu83ABdMukTxu3e",
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,16 @@
|
||||||
"description": "<p class=\"Body-Foundation\">When you cause an adversary to mark 1 or more Hit Points, you can <strong>spend 2 Hope</strong> to increase your Evasion by the number of Hit Points they marked. This bonus lasts until after the next attack made against you.</p>",
|
"description": "<p class=\"Body-Foundation\">When you cause an adversary to mark 1 or more Hit Points, you can <strong>spend 2 Hope</strong> to increase your Evasion by the number of Hit Points they marked. This bonus lasts until after the next attack made against you.</p>",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [
|
||||||
|
{
|
||||||
|
"scalable": false,
|
||||||
|
"key": "hope",
|
||||||
|
"value": 2,
|
||||||
|
"itemId": null,
|
||||||
|
"step": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
|
}
|
||||||
|
],
|
||||||
"uses": {
|
"uses": {
|
||||||
"value": null,
|
"value": null,
|
||||||
"max": "",
|
"max": "",
|
||||||
|
|
@ -30,8 +39,15 @@
|
||||||
"amount": null
|
"amount": null
|
||||||
},
|
},
|
||||||
"name": "Spend Hope",
|
"name": "Spend Hope",
|
||||||
"img": "icons/skills/melee/maneuver-daggers-paired-orange.webp",
|
"img": "icons/skills/melee/maneuver-sword-katana-yellow.webp",
|
||||||
"range": ""
|
"range": "",
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"trigger": "postDamageReduction",
|
||||||
|
"triggeringActorType": "other",
|
||||||
|
"command": "/* Check if sufficient hope */\nif (this.actor.system.resources.hope.value < 2) return;\n\n/* Check if hit point damage was dealt */\nconst hpDamage = damageUpdates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);\nif (hpDamage.value < 0) return;\n\n/* Dialog to give player choice */\nconst confirmed = await foundry.applications.api.DialogV2.confirm({\n window: { title: this.item?.name ?? '' },\n content: game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.ferocityContent', { bonus: hpDamage.value }),\n});\n\nif (!confirmed) return;\n\n/* Create the effect */\nthis.actor.createEmbeddedDocuments('ActiveEffect', [{\n name: this.item.name,\n img: 'icons/skills/melee/maneuver-sword-katana-yellow.webp',\n description: game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.ferocityEffectDescription', { bonus: hpDamage.value }),\n changes: [{ key: 'system.evasion', mode: 2, value: hpDamage.value }]\n}]);\n\n/* Update hope */\nreturn { updates: [{ \n originActor: this.actor, \n updates: [{\n key: CONFIG.DH.GENERAL.healingTypes.hope.id,\n value: -2,\n total: 2\n }] \n}]}"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"attribution": {
|
"attribution": {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "BFWN2cObMdlk9uVz",
|
"_id": "BFWN2cObMdlk9uVz",
|
||||||
"sort": 3400000,
|
"sort": 3500000,
|
||||||
"effects": [
|
"effects": [
|
||||||
{
|
{
|
||||||
"name": "Get Back Up",
|
"name": "Get Back Up",
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,8 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 127,
|
"page": 127,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"domainTouched": 4
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "KAuNb51AwhD8KEXk",
|
"_id": "KAuNb51AwhD8KEXk",
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,8 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 129,
|
"page": 129,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"domainTouched": 4
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "uSyGKVxOJcnp28po",
|
"_id": "uSyGKVxOJcnp28po",
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 127,
|
"page": 127,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"loadoutIgnore": true
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "IqxzvvjZiYbgx21A",
|
"_id": "IqxzvvjZiYbgx21A",
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,8 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 131,
|
"page": 131,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"domainTouched": 4
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "VOSFaQHZbmhMyXwi",
|
"_id": "VOSFaQHZbmhMyXwi",
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "4uAFGp3LxiC07woC",
|
"_id": "4uAFGp3LxiC07woC",
|
||||||
"sort": 3400000,
|
"sort": 3500000,
|
||||||
"effects": [],
|
"effects": [],
|
||||||
"ownership": {
|
"ownership": {
|
||||||
"default": 0
|
"default": 0
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 133,
|
"page": 133,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"domainTouched": 4
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "JT5dM3gVL6chDBYU",
|
"_id": "JT5dM3gVL6chDBYU",
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,8 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 134,
|
"page": 134,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"domainTouched": 4
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "k1AtYd3lSchIymBr",
|
"_id": "k1AtYd3lSchIymBr",
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,8 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 121,
|
"page": 121,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"vaultActive": true
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_id": "sWUlSPOJEaXyQLCj",
|
"_id": "sWUlSPOJEaXyQLCj",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"sorting": "a",
|
"sorting": "a",
|
||||||
"_id": "7pKKYgRQAKlQAksV",
|
"_id": "7pKKYgRQAKlQAksV",
|
||||||
"description": "",
|
"description": "",
|
||||||
"sort": 1000000,
|
"sort": 950000,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_key": "!folders!7pKKYgRQAKlQAksV"
|
"_key": "!folders!7pKKYgRQAKlQAksV"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"sorting": "a",
|
"sorting": "a",
|
||||||
"_id": "9Xc6KzNyjDtTGZkp",
|
"_id": "9Xc6KzNyjDtTGZkp",
|
||||||
"description": "",
|
"description": "",
|
||||||
"sort": 100000,
|
"sort": 700000,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_key": "!folders!9Xc6KzNyjDtTGZkp"
|
"_key": "!folders!9Xc6KzNyjDtTGZkp"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"sorting": "a",
|
"sorting": "a",
|
||||||
"_id": "o7t2fsAmRxKLoHrO",
|
"_id": "o7t2fsAmRxKLoHrO",
|
||||||
"description": "",
|
"description": "",
|
||||||
"sort": 200000,
|
"sort": 800000,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_key": "!folders!o7t2fsAmRxKLoHrO"
|
"_key": "!folders!o7t2fsAmRxKLoHrO"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"sorting": "a",
|
"sorting": "a",
|
||||||
"_id": "wWL9mV6i2EGX5xHS",
|
"_id": "wWL9mV6i2EGX5xHS",
|
||||||
"description": "",
|
"description": "",
|
||||||
"sort": 300000,
|
"sort": 850000,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_key": "!folders!wWL9mV6i2EGX5xHS"
|
"_key": "!folders!wWL9mV6i2EGX5xHS"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"sorting": "a",
|
"sorting": "a",
|
||||||
"_id": "yalAnCU3SndrYImF",
|
"_id": "yalAnCU3SndrYImF",
|
||||||
"description": "",
|
"description": "",
|
||||||
"sort": 400000,
|
"sort": 900000,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_key": "!folders!yalAnCU3SndrYImF"
|
"_key": "!folders!yalAnCU3SndrYImF"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"sorting": "a",
|
"sorting": "a",
|
||||||
"_id": "Emnx4o1DWGTVKoAg",
|
"_id": "Emnx4o1DWGTVKoAg",
|
||||||
"description": "",
|
"description": "",
|
||||||
"sort": 500000,
|
"sort": 901563,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_key": "!folders!Emnx4o1DWGTVKoAg"
|
"_key": "!folders!Emnx4o1DWGTVKoAg"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"sorting": "a",
|
"sorting": "a",
|
||||||
"_id": "EiP5dLozOFZKIeWN",
|
"_id": "EiP5dLozOFZKIeWN",
|
||||||
"description": "",
|
"description": "",
|
||||||
"sort": 600000,
|
"sort": 903125,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_key": "!folders!EiP5dLozOFZKIeWN"
|
"_key": "!folders!EiP5dLozOFZKIeWN"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"sorting": "a",
|
"sorting": "a",
|
||||||
"_id": "HAGbPLHwm0UozDeG",
|
"_id": "HAGbPLHwm0UozDeG",
|
||||||
"description": "",
|
"description": "",
|
||||||
"sort": 700000,
|
"sort": 906250,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_key": "!folders!HAGbPLHwm0UozDeG"
|
"_key": "!folders!HAGbPLHwm0UozDeG"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"sorting": "a",
|
"sorting": "a",
|
||||||
"_id": "me7ywrVh38j6T8Sm",
|
"_id": "me7ywrVh38j6T8Sm",
|
||||||
"description": "",
|
"description": "",
|
||||||
"sort": 800000,
|
"sort": 912500,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_key": "!folders!me7ywrVh38j6T8Sm"
|
"_key": "!folders!me7ywrVh38j6T8Sm"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"sorting": "a",
|
"sorting": "a",
|
||||||
"_id": "QYdeGsmVYIF34kZR",
|
"_id": "QYdeGsmVYIF34kZR",
|
||||||
"description": "",
|
"description": "",
|
||||||
"sort": 900000,
|
"sort": 925000,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_key": "!folders!QYdeGsmVYIF34kZR"
|
"_key": "!folders!QYdeGsmVYIF34kZR"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue