mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-24 16:33:38 +02:00
Merge branch 'main' into release
This commit is contained in:
commit
cd93246358
767 changed files with 10444 additions and 6815 deletions
|
|
@ -1,3 +1,3 @@
|
||||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M6.12012 0.5H51.8799C55.2901 0.500041 57.8779 3.57175 57.2998 6.93262L50.4639 46.6777C50.1604 48.4411 49.0179 49.9467 47.4014 50.7139L31.3584 58.3271C29.8661 59.0354 28.1339 59.0354 26.6416 58.3271L10.5986 50.7139C8.98214 49.9467 7.83959 48.4411 7.53613 46.6777L0.700195 6.93262C0.122088 3.57175 2.7099 0.500042 6.12012 0.5Z" fill="transparent" stroke="#18162e"/>
|
<path d="M 7.12 0.5 H 52.88 C 56.29 0.5 58.88 3.57 58.3 6.93 L 51.46 46.68 C 51.16 48.44 50.02 49.95 48.4 50.71 L 32.36 58.33 C 30.87 59.04 29.13 59.04 27.64 58.33 L 11.6 50.71 C 9.98 49.95 8.84 48.44 8.54 46.68 L 1.7 6.93 C 1.12 3.57 3.71 0.5 7.12 0.5 Z" fill="transparent" stroke="#18162e"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 476 B After Width: | Height: | Size: 397 B |
|
|
@ -1,3 +1,3 @@
|
||||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M6.12012 0.5H51.8799C55.2901 0.500041 57.8779 3.57175 57.2998 6.93262L50.4639 46.6777C50.1604 48.4411 49.0179 49.9467 47.4014 50.7139L31.3584 58.3271C29.8661 59.0354 28.1339 59.0354 26.6416 58.3271L10.5986 50.7139C8.98214 49.9467 7.83959 48.4411 7.53613 46.6777L0.700195 6.93262C0.122088 3.57175 2.7099 0.500042 6.12012 0.5Z" fill="#18152E" stroke="#F3C267"/>
|
<path d="M 7.12 0.5 H 52.88 C 56.29 0.5 58.88 3.57 58.3 6.93 L 51.46 46.68 C 51.16 48.44 50.02 49.95 48.4 50.71 L 32.36 58.33 C 30.87 59.04 29.13 59.04 27.64 58.33 L 11.6 50.71 C 9.98 49.95 8.84 48.44 8.54 46.68 L 1.7 6.93 C 1.12 3.57 3.71 0.5 7.12 0.5 Z" fill="#18152E" stroke="#F3C267"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 472 B After Width: | Height: | Size: 393 B |
|
|
@ -9,10 +9,7 @@ import * as dice from './module/dice/_module.mjs';
|
||||||
import * as fields from './module/data/fields/_module.mjs';
|
import * as fields from './module/data/fields/_module.mjs';
|
||||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||||
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
||||||
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
|
||||||
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll } from './module/dice/_module.mjs';
|
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll } from './module/dice/_module.mjs';
|
||||||
import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs';
|
|
||||||
import { enrichedFateRoll, getFateTypeData } from './module/enrichers/FateRollEnricher.mjs';
|
|
||||||
import {
|
import {
|
||||||
handlebarsRegistration,
|
handlebarsRegistration,
|
||||||
runMigrations,
|
runMigrations,
|
||||||
|
|
@ -21,7 +18,6 @@ import {
|
||||||
} from './module/systemRegistration/_module.mjs';
|
} from './module/systemRegistration/_module.mjs';
|
||||||
import { placeables, DhTokenLayer } 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 TokenManager from './module/documents/tokenManager.mjs';
|
import TokenManager from './module/documents/tokenManager.mjs';
|
||||||
|
|
||||||
CONFIG.DH = SYSTEM;
|
CONFIG.DH = SYSTEM;
|
||||||
|
|
@ -36,6 +32,8 @@ CONFIG.Dice.daggerheart = {
|
||||||
FateRoll: FateRoll
|
FateRoll: FateRoll
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);
|
||||||
|
|
||||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||||
CONFIG.Actor.dataModels = models.actors.config;
|
CONFIG.Actor.dataModels = models.actors.config;
|
||||||
CONFIG.Actor.collection = collections.DhActorCollection;
|
CONFIG.Actor.collection = collections.DhActorCollection;
|
||||||
|
|
@ -45,6 +43,7 @@ CONFIG.Item.dataModels = models.items.config;
|
||||||
|
|
||||||
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
||||||
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
||||||
|
CONFIG.ActiveEffect.changeTypes = { ...CONFIG.ActiveEffect.changeTypes, ...models.activeEffects.changeEffects };
|
||||||
|
|
||||||
CONFIG.Combat.documentClass = documents.DhpCombat;
|
CONFIG.Combat.documentClass = documents.DhpCombat;
|
||||||
CONFIG.Combat.dataModels = { base: models.DhCombat };
|
CONFIG.Combat.dataModels = { base: models.DhCombat };
|
||||||
|
|
@ -56,11 +55,13 @@ CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
|
||||||
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
|
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
|
||||||
|
|
||||||
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
||||||
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
CONFIG.Canvas.layers.regions.layerClass = placeables.DhRegionLayer;
|
||||||
CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer;
|
CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer;
|
||||||
|
|
||||||
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
||||||
|
|
||||||
|
CONFIG.Region.objectClass = placeables.DhRegion;
|
||||||
|
|
||||||
CONFIG.RollTable.documentClass = documents.DhRollTable;
|
CONFIG.RollTable.documentClass = documents.DhRollTable;
|
||||||
CONFIG.RollTable.resultTemplate = 'systems/daggerheart/templates/ui/chat/table-result.hbs';
|
CONFIG.RollTable.resultTemplate = 'systems/daggerheart/templates/ui/chat/table-result.hbs';
|
||||||
|
|
||||||
|
|
@ -84,7 +85,6 @@ CONFIG.ui.resources = applications.ui.DhFearTracker;
|
||||||
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
|
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.TokenManager = new TokenManager();
|
CONFIG.ux.TokenManager = new TokenManager();
|
||||||
CONFIG.debug.triggers = false;
|
CONFIG.debug.triggers = false;
|
||||||
|
|
||||||
|
|
@ -213,6 +213,7 @@ Hooks.once('init', () => {
|
||||||
SYSTEM.id,
|
SYSTEM.id,
|
||||||
applications.sheetConfigs.ActiveEffectConfig,
|
applications.sheetConfigs.ActiveEffectConfig,
|
||||||
{
|
{
|
||||||
|
types: ['base', 'beastform', 'horde'],
|
||||||
makeDefault: true,
|
makeDefault: true,
|
||||||
label: sheetLabel('DOCUMENT.ActiveEffect')
|
label: sheetLabel('DOCUMENT.ActiveEffect')
|
||||||
}
|
}
|
||||||
|
|
@ -270,7 +271,6 @@ Hooks.on('setup', () => {
|
||||||
...damageThresholds,
|
...damageThresholds,
|
||||||
'proficiency',
|
'proficiency',
|
||||||
'evasion',
|
'evasion',
|
||||||
'armorScore',
|
|
||||||
'scars',
|
'scars',
|
||||||
'levelData.level.current'
|
'levelData.level.current'
|
||||||
]
|
]
|
||||||
|
|
@ -332,75 +332,14 @@ Hooks.on('renderHandlebarsApplication', (_, element) => {
|
||||||
enricherRenderSetup(element);
|
enricherRenderSetup(element);
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('chatMessage', (_, message) => {
|
Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => {
|
||||||
if (message.startsWith('/dr')) {
|
if (data.openForAllPlayers && data.partyId) {
|
||||||
const result =
|
const party = game.actors.get(data.partyId);
|
||||||
message.trim().toLowerCase() === '/dr' ? { result: {} } : rollCommandToJSON(message.replace(/\/dr\s?/, ''));
|
if (!party) return;
|
||||||
if (!result) {
|
|
||||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.dualityParsing'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { result: rollCommand, flavor } = result;
|
const dialog = new game.system.api.applications.dialogs.TagTeamDialog(party);
|
||||||
|
dialog.tabGroups.application = 'tagTeamRoll';
|
||||||
const reaction = rollCommand.reaction;
|
await dialog.render({ force: true });
|
||||||
const traitValue = rollCommand.trait?.toLowerCase();
|
|
||||||
const advantage = rollCommand.advantage
|
|
||||||
? CONFIG.DH.ACTIONS.advantageState.advantage.value
|
|
||||||
: rollCommand.disadvantage
|
|
||||||
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
|
|
||||||
: undefined;
|
|
||||||
const difficulty = rollCommand.difficulty;
|
|
||||||
const grantResources = rollCommand.grantResources;
|
|
||||||
|
|
||||||
const target = getCommandTarget({ allowNull: true });
|
|
||||||
const title =
|
|
||||||
(flavor ?? traitValue)
|
|
||||||
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
|
|
||||||
})
|
|
||||||
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
|
||||||
|
|
||||||
enrichedDualityRoll({
|
|
||||||
reaction,
|
|
||||||
traitValue,
|
|
||||||
target,
|
|
||||||
difficulty,
|
|
||||||
title,
|
|
||||||
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'),
|
|
||||||
actionType: null,
|
|
||||||
advantage,
|
|
||||||
grantResources
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.startsWith('/fr')) {
|
|
||||||
const result =
|
|
||||||
message.trim().toLowerCase() === '/fr' ? { result: {} } : rollCommandToJSON(message.replace(/\/fr\s?/, ''));
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateParsing'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { result: rollCommand, flavor } = result;
|
|
||||||
const fateTypeData = getFateTypeData(rollCommand?.type);
|
|
||||||
|
|
||||||
if (!fateTypeData)
|
|
||||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
|
||||||
|
|
||||||
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
|
||||||
const target = getCommandTarget({ allowNull: true });
|
|
||||||
const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll');
|
|
||||||
|
|
||||||
enrichedFateRoll({
|
|
||||||
target,
|
|
||||||
title,
|
|
||||||
label: fateTypeLabel,
|
|
||||||
fateType
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
108
lang/en.json
108
lang/en.json
|
|
@ -14,7 +14,9 @@
|
||||||
"beastform": "Beastform"
|
"beastform": "Beastform"
|
||||||
},
|
},
|
||||||
"ActiveEffect": {
|
"ActiveEffect": {
|
||||||
"beastform": "Beastform"
|
"base": "Standard",
|
||||||
|
"beastform": "Beastform",
|
||||||
|
"horde": "Horde"
|
||||||
},
|
},
|
||||||
"Actor": {
|
"Actor": {
|
||||||
"character": "Character",
|
"character": "Character",
|
||||||
|
|
@ -53,6 +55,7 @@
|
||||||
},
|
},
|
||||||
"damage": {
|
"damage": {
|
||||||
"name": "Damage",
|
"name": "Damage",
|
||||||
|
"critical": "Damage (Critical)",
|
||||||
"tooltip": "Direct damage without a roll."
|
"tooltip": "Direct damage without a roll."
|
||||||
},
|
},
|
||||||
"effect": {
|
"effect": {
|
||||||
|
|
@ -87,9 +90,14 @@
|
||||||
},
|
},
|
||||||
"Config": {
|
"Config": {
|
||||||
"beastform": {
|
"beastform": {
|
||||||
"exact": "Beastform Max Tier",
|
"exact": { "label": "Beastform Max Tier", "hint": "The Character's Tier is used if empty" },
|
||||||
"exactHint": "The Character's Tier is used if empty",
|
"modifications": {
|
||||||
"label": "Beastform"
|
"traitBonuses": {
|
||||||
|
"label": { "single": "Trait Bonus", "plural": "Trait Bonuses" },
|
||||||
|
"hint": "Pick bonuses you apply to freely chosen traits at the time of transforming",
|
||||||
|
"bonus": "Bonus Amount"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"countdown": {
|
"countdown": {
|
||||||
"defaultOwnership": "Default Ownership",
|
"defaultOwnership": "Default Ownership",
|
||||||
|
|
@ -151,7 +159,8 @@
|
||||||
"Config": {
|
"Config": {
|
||||||
"rangeDependence": {
|
"rangeDependence": {
|
||||||
"title": "Range Dependence"
|
"title": "Range Dependence"
|
||||||
}
|
},
|
||||||
|
"stacking": { "title": "Stacking" }
|
||||||
},
|
},
|
||||||
"RangeDependance": {
|
"RangeDependance": {
|
||||||
"hint": "Settings for an optional distance at which this effect should activate",
|
"hint": "Settings for an optional distance at which this effect should activate",
|
||||||
|
|
@ -442,14 +451,15 @@
|
||||||
},
|
},
|
||||||
"DaggerheartMenu": {
|
"DaggerheartMenu": {
|
||||||
"title": "GM Tools",
|
"title": "GM Tools",
|
||||||
"refreshFeatures": "Refresh Features"
|
"refreshFeatures": "Refresh Features",
|
||||||
|
"fallingAndCollision": "Falling And Collision Damage"
|
||||||
},
|
},
|
||||||
"DeleteConfirmation": {
|
"DeleteConfirmation": {
|
||||||
"title": "Delete {type} - {name}",
|
"title": "Delete {type} - {name}",
|
||||||
"text": "Are you sure you want to delete {name}?"
|
"text": "Are you sure you want to delete {name}?"
|
||||||
},
|
},
|
||||||
"DamageReduction": {
|
"DamageReduction": {
|
||||||
"armorMarks": "Armor Marks",
|
"maxUseableArmor": "Useable Armor Slots",
|
||||||
"armorWithStress": "Spend 1 stress to use an extra mark",
|
"armorWithStress": "Spend 1 stress to use an extra mark",
|
||||||
"thresholdImmunities": "Threshold Immunities",
|
"thresholdImmunities": "Threshold Immunities",
|
||||||
"stress": "Stress",
|
"stress": "Stress",
|
||||||
|
|
@ -675,16 +685,35 @@
|
||||||
},
|
},
|
||||||
"TagTeamSelect": {
|
"TagTeamSelect": {
|
||||||
"title": "Tag Team Roll",
|
"title": "Tag Team Roll",
|
||||||
|
"FIELDS": {
|
||||||
|
"initiator": {
|
||||||
|
"memberId": { "label": "Initiating Character" },
|
||||||
|
"cost": { "label": "Initiation Cost" }
|
||||||
|
}
|
||||||
|
},
|
||||||
"leaderTitle": "Initiating Character",
|
"leaderTitle": "Initiating Character",
|
||||||
"membersTitle": "Participants",
|
"membersTitle": "Participants",
|
||||||
"partyTeam": "Party Team",
|
"partyTeam": "Party Team",
|
||||||
"hopeCost": "Hope Cost",
|
"hopeCost": "Hope Cost",
|
||||||
"initiatingCharacter": "Initiating Character",
|
"initiatingCharacter": "Initiating Character",
|
||||||
|
"selectParticipants": "Select the two participants",
|
||||||
|
"startTagTeamRoll": "Start Tag Team Roll",
|
||||||
|
"openDialogForAll": "Open Dialog For All",
|
||||||
|
"rollType": "Roll Type",
|
||||||
|
"makeYourRoll": "Make your roll",
|
||||||
|
"cancelTagTeamRoll": "Cancel Tag Team Roll",
|
||||||
|
"finishTagTeamRoll": "Finish Tag Team Roll",
|
||||||
"linkMessageHint": "Make a roll from your character sheet to link it to the Tag Team Roll",
|
"linkMessageHint": "Make a roll from your character sheet to link it to the Tag Team Roll",
|
||||||
"damageNotRolled": "Damage not rolled in chat message yet",
|
"damageNotRolled": "Damage not rolled in chat message yet",
|
||||||
"insufficientHope": "The initiating character doesn't have enough hope",
|
"insufficientHope": "The initiating character doesn't have enough hope",
|
||||||
"createTagTeam": "Create TagTeam Roll",
|
"createTagTeam": "Create Tag Team Roll",
|
||||||
"chatMessageRollTitle": "Roll"
|
"chatMessageRollTitle": "Roll",
|
||||||
|
"cancelConfirmTitle": "Cancel Tag Team Roll",
|
||||||
|
"cancelConfirmText": "Are you sure you want to cancel the Tag Team Roll? This will close it for all other players too.",
|
||||||
|
"hints": {
|
||||||
|
"completeRolls": "Set up and complete the rolls for the characters",
|
||||||
|
"selectRoll": "Select which roll value to be used for the Tag Team"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"TokenConfig": {
|
"TokenConfig": {
|
||||||
"actorSizeUsed": "Actor size is set, determining the dimensions"
|
"actorSizeUsed": "Actor size is set, determining the dimensions"
|
||||||
|
|
@ -697,6 +726,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
|
"ActiveEffectDuration": {
|
||||||
|
"temporary": "Temporary",
|
||||||
|
"act": "Next Spotlight",
|
||||||
|
"scene": "Next Scene",
|
||||||
|
"shortRest": "Next Rest",
|
||||||
|
"longRest": "Next Long Rest",
|
||||||
|
"session": "Next Session",
|
||||||
|
"custom": "Custom"
|
||||||
|
},
|
||||||
"AdversaryTrait": {
|
"AdversaryTrait": {
|
||||||
"relentless": {
|
"relentless": {
|
||||||
"name": "Relentless",
|
"name": "Relentless",
|
||||||
|
|
@ -764,6 +802,11 @@
|
||||||
"bruiser": "for each Bruiser adversary.",
|
"bruiser": "for each Bruiser adversary.",
|
||||||
"solo": "for each Solo adversary."
|
"solo": "for each Solo adversary."
|
||||||
},
|
},
|
||||||
|
"ArmorInteraction": {
|
||||||
|
"none": { "label": "Ignores Armor" },
|
||||||
|
"active": { "label": "Active w/ Armor" },
|
||||||
|
"inactive": { "label": "Inactive w/ Armor" }
|
||||||
|
},
|
||||||
"ArmorFeature": {
|
"ArmorFeature": {
|
||||||
"burning": {
|
"burning": {
|
||||||
"name": "Burning",
|
"name": "Burning",
|
||||||
|
|
@ -1114,6 +1157,12 @@
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"fallAndCollision": {
|
||||||
|
"veryClose": { "label": "Very Close", "chatTitle": "Fall Damage: Very Close" },
|
||||||
|
"close": { "label": "Close", "chatTitle": "Fall Damage: Close" },
|
||||||
|
"far": { "label": "Far", "chatTitle": "Fall Damage: Far" },
|
||||||
|
"collision": { "label": "Collision", "chatTitle": "Dangerous Collision" }
|
||||||
|
},
|
||||||
"FeatureForm": {
|
"FeatureForm": {
|
||||||
"label": "Feature Form",
|
"label": "Feature Form",
|
||||||
"passive": "Passive",
|
"passive": "Passive",
|
||||||
|
|
@ -1219,6 +1268,11 @@
|
||||||
"selectType": "Select Action Type",
|
"selectType": "Select Action Type",
|
||||||
"selectAction": "Action Selection"
|
"selectAction": "Action Selection"
|
||||||
},
|
},
|
||||||
|
"TagTeamRollTypes": {
|
||||||
|
"trait": "Trait",
|
||||||
|
"ability": "Ability",
|
||||||
|
"damageAbility": "Damage Ability"
|
||||||
|
},
|
||||||
"TargetTypes": {
|
"TargetTypes": {
|
||||||
"any": "Any",
|
"any": "Any",
|
||||||
"friendly": "Friendly",
|
"friendly": "Friendly",
|
||||||
|
|
@ -1231,8 +1285,8 @@
|
||||||
"cone": "Cone",
|
"cone": "Cone",
|
||||||
"emanation": "Emanation",
|
"emanation": "Emanation",
|
||||||
"inFront": "In Front",
|
"inFront": "In Front",
|
||||||
"rect": "Rectangle",
|
"rectangle": "Rectangle",
|
||||||
"ray": "Ray"
|
"line": "Line"
|
||||||
},
|
},
|
||||||
"TokenSize": {
|
"TokenSize": {
|
||||||
"tiny": "Tiny",
|
"tiny": "Tiny",
|
||||||
|
|
@ -1847,6 +1901,17 @@
|
||||||
"name": "Healing Roll"
|
"name": "Healing Roll"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ChangeTypes": {
|
||||||
|
"armor": {
|
||||||
|
"newArmorEffect": "Armor Effect",
|
||||||
|
"FIELDS": {
|
||||||
|
"interaction": {
|
||||||
|
"label": "Armor Interaction",
|
||||||
|
"hint": "Does the character wearing armor suppress this effect?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Duration": {
|
"Duration": {
|
||||||
"passive": "Passive",
|
"passive": "Passive",
|
||||||
"temporary": "Temporary"
|
"temporary": "Temporary"
|
||||||
|
|
@ -1871,6 +1936,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENERAL": {
|
"GENERAL": {
|
||||||
|
"Ability": {
|
||||||
|
"single": "Ability",
|
||||||
|
"plural": "Abilities"
|
||||||
|
},
|
||||||
"Action": {
|
"Action": {
|
||||||
"single": "Action",
|
"single": "Action",
|
||||||
"plural": "Actions"
|
"plural": "Actions"
|
||||||
|
|
@ -2252,6 +2321,7 @@
|
||||||
"duality": "Duality",
|
"duality": "Duality",
|
||||||
"dualityDice": "Duality Dice",
|
"dualityDice": "Duality Dice",
|
||||||
"dualityRoll": "Duality Roll",
|
"dualityRoll": "Duality Roll",
|
||||||
|
"effect": "Effect",
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"evasion": "Evasion",
|
"evasion": "Evasion",
|
||||||
"equipment": "Equipment",
|
"equipment": "Equipment",
|
||||||
|
|
@ -2324,6 +2394,10 @@
|
||||||
"rerolled": "Rerolled",
|
"rerolled": "Rerolled",
|
||||||
"rerollThing": "Reroll {thing}",
|
"rerollThing": "Reroll {thing}",
|
||||||
"resource": "Resource",
|
"resource": "Resource",
|
||||||
|
"result": {
|
||||||
|
"single": "Result",
|
||||||
|
"plural": "Results"
|
||||||
|
},
|
||||||
"roll": "Roll",
|
"roll": "Roll",
|
||||||
"rollAll": "Roll All",
|
"rollAll": "Roll All",
|
||||||
"rollDamage": "Roll Damage",
|
"rollDamage": "Roll Damage",
|
||||||
|
|
@ -2489,8 +2563,7 @@
|
||||||
"MACROS": {
|
"MACROS": {
|
||||||
"Spotlight": {
|
"Spotlight": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"noActiveCombat": "There is no active encounter",
|
"noTokenSelected": "A token on the canvas must either be selected or hovered to spotlight it"
|
||||||
"noCombatantSelected": "A combatant token must be either selected or hovered to spotlight it"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2565,6 +2638,10 @@
|
||||||
"hint": "Automatically increase the GM's fear pool on a fear duality roll result."
|
"hint": "Automatically increase the GM's fear pool on a fear duality roll result."
|
||||||
},
|
},
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
|
"autoExpireActiveEffects": {
|
||||||
|
"label": "Auto Expire Active Effects",
|
||||||
|
"hint": "Active Effects with set durations will automatically be removed when their durations are up"
|
||||||
|
},
|
||||||
"damageReductionRulesDefault": {
|
"damageReductionRulesDefault": {
|
||||||
"label": "Damage Reduction Rules Default",
|
"label": "Damage Reduction Rules Default",
|
||||||
"hint": "Wether using armor and reductions has rules on by default"
|
"hint": "Wether using armor and reductions has rules on by default"
|
||||||
|
|
@ -2937,6 +3014,8 @@
|
||||||
},
|
},
|
||||||
"EffectsDisplay": {
|
"EffectsDisplay": {
|
||||||
"removeThing": "[Right Click] Remove {thing}",
|
"removeThing": "[Right Click] Remove {thing}",
|
||||||
|
"increaseStacks": "[Left Click] Increment Stacks",
|
||||||
|
"decreaseStacks": "[Right Click] Decrement Stacks",
|
||||||
"appliedBy": "Applied By: {by}"
|
"appliedBy": "Applied By: {by}"
|
||||||
},
|
},
|
||||||
"ItemBrowser": {
|
"ItemBrowser": {
|
||||||
|
|
@ -3063,6 +3142,9 @@
|
||||||
"knowTheTide": "Know The Tide gained a token",
|
"knowTheTide": "Know The Tide gained a token",
|
||||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}"
|
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}"
|
||||||
},
|
},
|
||||||
|
"Progress": {
|
||||||
|
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
|
||||||
|
},
|
||||||
"Sidebar": {
|
"Sidebar": {
|
||||||
"actorDirectory": {
|
"actorDirectory": {
|
||||||
"tier": "Tier {tier} {type}",
|
"tier": "Tier {tier} {type}",
|
||||||
|
|
|
||||||
|
|
@ -554,7 +554,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
experiences: {
|
experiences: {
|
||||||
...this.setup.experiences,
|
...this.setup.experiences,
|
||||||
...Object.keys(this.character.system.experiences).reduce((acc, key) => {
|
...Object.keys(this.character.system.experiences).reduce((acc, key) => {
|
||||||
acc[`-=${key}`] = null;
|
acc[`${key}`] = _del;
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
this.selected = null;
|
this.selected = null;
|
||||||
this.evolved = { form: null };
|
this.evolved = { form: null };
|
||||||
this.hybrid = { forms: {}, advantages: {}, features: {} };
|
this.hybrid = { forms: {}, advantages: {}, features: {} };
|
||||||
|
this.modifications = {
|
||||||
|
traitBonuses: configData.modifications.traitBonuses.map(x => ({
|
||||||
|
trait: null,
|
||||||
|
bonus: x.bonus
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
this._dragDrop = this._createDragDropHandlers();
|
this._dragDrop = this._createDragDropHandlers();
|
||||||
}
|
}
|
||||||
|
|
@ -28,6 +34,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
selectBeastform: this.selectBeastform,
|
selectBeastform: this.selectBeastform,
|
||||||
toggleHybridFeature: this.toggleHybridFeature,
|
toggleHybridFeature: this.toggleHybridFeature,
|
||||||
toggleHybridAdvantage: this.toggleHybridAdvantage,
|
toggleHybridAdvantage: this.toggleHybridAdvantage,
|
||||||
|
toggleTraitBonus: this.toggleTraitBonus,
|
||||||
submitBeastform: this.submitBeastform
|
submitBeastform: this.submitBeastform
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
|
@ -48,6 +55,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
tabs: { template: 'systems/daggerheart/templates/dialogs/beastform/tabs.hbs' },
|
tabs: { template: 'systems/daggerheart/templates/dialogs/beastform/tabs.hbs' },
|
||||||
beastformTier: { template: 'systems/daggerheart/templates/dialogs/beastform/beastformTier.hbs' },
|
beastformTier: { template: 'systems/daggerheart/templates/dialogs/beastform/beastformTier.hbs' },
|
||||||
advanced: { template: 'systems/daggerheart/templates/dialogs/beastform/advanced.hbs' },
|
advanced: { template: 'systems/daggerheart/templates/dialogs/beastform/advanced.hbs' },
|
||||||
|
modifications: { template: 'systems/daggerheart/templates/dialogs/beastform/modifications.hbs' },
|
||||||
footer: { template: 'systems/daggerheart/templates/dialogs/beastform/footer.hbs' }
|
footer: { template: 'systems/daggerheart/templates/dialogs/beastform/footer.hbs' }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -146,6 +154,9 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
context.modifications = this.modifications;
|
||||||
|
context.traits = CONFIG.DH.ACTOR.abilities;
|
||||||
|
|
||||||
context.tier = beastformTiers[this.tabGroups.primary];
|
context.tier = beastformTiers[this.tabGroups.primary];
|
||||||
context.tierKey = this.tabGroups.primary;
|
context.tierKey = this.tabGroups.primary;
|
||||||
|
|
||||||
|
|
@ -155,6 +166,9 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
}
|
}
|
||||||
|
|
||||||
canSubmit() {
|
canSubmit() {
|
||||||
|
const modificationsFinished = this.modifications.traitBonuses.every(x => x.trait);
|
||||||
|
if (!modificationsFinished) return false;
|
||||||
|
|
||||||
if (this.selected) {
|
if (this.selected) {
|
||||||
switch (this.selected.system.beastformType) {
|
switch (this.selected.system.beastformType) {
|
||||||
case 'normal':
|
case 'normal':
|
||||||
|
|
@ -261,6 +275,13 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static toggleTraitBonus(_, button) {
|
||||||
|
const { index, trait } = button.dataset;
|
||||||
|
this.modifications.traitBonuses[index].trait =
|
||||||
|
this.modifications.traitBonuses[index].trait === trait ? null : trait;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
static async submitBeastform() {
|
static async submitBeastform() {
|
||||||
await this.close({ submitted: true });
|
await this.close({ submitted: true });
|
||||||
}
|
}
|
||||||
|
|
@ -292,6 +313,23 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const beastformEffect = selected.effects.find(x => x.type === 'beastform');
|
||||||
|
for (const traitBonus of app.modifications.traitBonuses) {
|
||||||
|
const existingChange = beastformEffect.changes.find(
|
||||||
|
x => x.key === `system.traits.${traitBonus.trait}.value`
|
||||||
|
);
|
||||||
|
if (existingChange) {
|
||||||
|
existingChange.value = Number.parseInt(existingChange.value) + traitBonus.bonus;
|
||||||
|
} else {
|
||||||
|
beastformEffect.changes.push({
|
||||||
|
key: `system.traits.${traitBonus.trait}.value`,
|
||||||
|
mode: 2,
|
||||||
|
priority: null,
|
||||||
|
value: traitBonus.bonus
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
selected: selected,
|
selected: selected,
|
||||||
evolved: { ...app.evolved, form: evolved },
|
evolved: { ...app.evolved, form: evolved },
|
||||||
|
|
|
||||||
|
|
@ -77,8 +77,8 @@ export default class CharacterResetDialog extends HandlebarsApplicationMixin(App
|
||||||
|
|
||||||
if (!this.data.optional.portrait.keep) {
|
if (!this.data.optional.portrait.keep) {
|
||||||
foundry.utils.setProperty(update, 'img', this.actor.schema.fields.img.initial(this.actor));
|
foundry.utils.setProperty(update, 'img', this.actor.schema.fields.img.initial(this.actor));
|
||||||
foundry.utils.setProperty(update, 'prototypeToken.==texture', {});
|
foundry.utils.setProperty(update, 'prototypeToken.texture', _replace({}));
|
||||||
foundry.utils.setProperty(update, 'prototypeToken.==ring', {});
|
foundry.utils.setProperty(update, 'prototypeToken.ring', _replace({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.data.optional.biography.keep)
|
if (this.data.optional.biography.keep)
|
||||||
|
|
@ -89,7 +89,7 @@ export default class CharacterResetDialog extends HandlebarsApplicationMixin(App
|
||||||
const { system, ...rest } = update;
|
const { system, ...rest } = update;
|
||||||
await this.actor.update({
|
await this.actor.update({
|
||||||
...rest,
|
...rest,
|
||||||
'==system': system ?? {}
|
system: _replace(system ?? {})
|
||||||
});
|
});
|
||||||
|
|
||||||
const inventoryItemTypes = ['weapon', 'armor', 'consumable', 'loot'];
|
const inventoryItemTypes = ['weapon', 'armor', 'consumable', 'loot'];
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
updateIsAdvantage: this.updateIsAdvantage,
|
updateIsAdvantage: this.updateIsAdvantage,
|
||||||
selectExperience: this.selectExperience,
|
selectExperience: this.selectExperience,
|
||||||
toggleReaction: this.toggleReaction,
|
toggleReaction: this.toggleReaction,
|
||||||
toggleTagTeamRoll: this.toggleTagTeamRoll,
|
|
||||||
toggleSelectedEffect: this.toggleSelectedEffect,
|
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||||
submitRoll: this.submitRoll
|
submitRoll: this.submitRoll
|
||||||
},
|
},
|
||||||
|
|
@ -71,8 +70,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.rollConfig = this.config;
|
context.rollConfig = this.config;
|
||||||
context.hasRoll = !!this.config.roll;
|
context.hasRoll = !!this.config.roll;
|
||||||
context.canRoll = true;
|
context.canRoll = true;
|
||||||
context.selectedRollMode = this.config.selectedRollMode ?? game.settings.get('core', 'rollMode');
|
context.selectedMessageMode = this.config.selectedMessageMode ?? game.settings.get('core', 'messageMode');
|
||||||
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
|
context.rollModes = Object.entries(CONFIG.ChatMessage.modes).map(([action, { label, icon }]) => ({
|
||||||
action,
|
action,
|
||||||
label,
|
label,
|
||||||
icon
|
icon
|
||||||
|
|
@ -133,12 +132,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.reactionOverride = this.reactionOverride;
|
context.reactionOverride = this.reactionOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagTeamSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
|
||||||
if (this.actor && tagTeamSetting.members[this.actor.id] && !this.config.skips?.createMessage) {
|
|
||||||
context.activeTagTeamRoll = true;
|
|
||||||
context.tagTeamSelected = this.config.tagTeamSelected;
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,10 +142,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
static updateRollConfiguration(event, _, formData) {
|
static updateRollConfiguration(_event, _, formData) {
|
||||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||||
|
|
||||||
this.config.selectedRollMode = rest.selectedRollMode;
|
this.config.selectedMessageMode = rest.selectedMessageMode;
|
||||||
|
|
||||||
if (this.config.costs) {
|
if (this.config.costs) {
|
||||||
this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs);
|
this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs);
|
||||||
|
|
@ -215,11 +208,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static toggleTagTeamRoll() {
|
|
||||||
this.config.tagTeamSelected = !this.config.tagTeamSelected;
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static toggleSelectedEffect(_event, button) {
|
static toggleSelectedEffect(_event, button) {
|
||||||
this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected;
|
this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected;
|
||||||
this.render();
|
this.render();
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
toggleSelectedEffect: this.toggleSelectedEffect,
|
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||||
|
toggleCritical: this.toggleCritical,
|
||||||
submitRoll: this.submitRoll
|
submitRoll: this.submitRoll
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
|
@ -52,8 +53,9 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
context.formula = this.roll.constructFormula(this.config);
|
||||||
context.hasHealing = this.config.hasHealing;
|
context.hasHealing = this.config.hasHealing;
|
||||||
context.directDamage = this.config.directDamage;
|
context.directDamage = this.config.directDamage;
|
||||||
context.selectedRollMode = this.config.selectedRollMode;
|
context.selectedMessageMode = this.config.selectedMessageMode;
|
||||||
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
|
context.isCritical = this.config.isCritical;
|
||||||
|
context.rollModes = Object.entries(CONFIG.ChatMessage.modes).map(([action, { label, icon }]) => ({
|
||||||
action,
|
action,
|
||||||
label,
|
label,
|
||||||
icon
|
icon
|
||||||
|
|
@ -69,11 +71,16 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||||
foundry.utils.mergeObject(this.config.roll, rest.roll);
|
foundry.utils.mergeObject(this.config.roll, rest.roll);
|
||||||
foundry.utils.mergeObject(this.config.modifiers, rest.modifiers);
|
foundry.utils.mergeObject(this.config.modifiers, rest.modifiers);
|
||||||
this.config.selectedRollMode = rest.selectedRollMode;
|
this.config.selectedMessageMode = rest.selectedMessageMode;
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static toggleCritical() {
|
||||||
|
this.config.isCritical = !this.config.isCritical;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
static toggleSelectedEffect(_event, button) {
|
static toggleSelectedEffect(_event, button) {
|
||||||
this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected;
|
this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected;
|
||||||
this.render();
|
this.render();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { damageKeyToNumber, getDamageLabel } from '../../helpers/utils.mjs';
|
import { damageKeyToNumber, getArmorSources, getDamageLabel } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -10,6 +10,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
this.reject = reject;
|
this.reject = reject;
|
||||||
this.actor = actor;
|
this.actor = actor;
|
||||||
this.damage = damage;
|
this.damage = damage;
|
||||||
|
|
||||||
this.damageType = damageType;
|
this.damageType = damageType;
|
||||||
this.rulesDefault = game.settings.get(
|
this.rulesDefault = game.settings.get(
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id,
|
||||||
|
|
@ -20,14 +21,20 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
this.rulesDefault
|
this.rulesDefault
|
||||||
);
|
);
|
||||||
|
|
||||||
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true);
|
const orderedArmorSources = getArmorSources(actor).filter(s => !s.disabled);
|
||||||
const availableArmor = actor.system.armorScore - actor.system.armor.system.marks.value;
|
const armor = orderedArmorSources.reduce((acc, { document }) => {
|
||||||
const maxArmorMarks = canApplyArmor ? availableArmor : 0;
|
const { current, max } = document.type === 'armor' ? document.system.armor : document.system.armorData;
|
||||||
|
acc.push({
|
||||||
|
effect: document,
|
||||||
|
marks: [...Array(max).keys()].reduce((acc, _, index) => {
|
||||||
|
const spent = index < current;
|
||||||
|
acc[foundry.utils.randomID()] = { selected: false, disabled: spent, spent };
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
});
|
||||||
|
|
||||||
const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => {
|
|
||||||
acc[foundry.utils.randomID()] = { selected: false };
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, []);
|
||||||
const stress = [...Array(actor.system.rules.damageReduction.maxArmorMarked.stressExtra ?? 0).keys()].reduce(
|
const stress = [...Array(actor.system.rules.damageReduction.maxArmorMarked.stressExtra ?? 0).keys()].reduce(
|
||||||
(acc, _) => {
|
(acc, _) => {
|
||||||
acc[foundry.utils.randomID()] = { selected: false };
|
acc[foundry.utils.randomID()] = { selected: false };
|
||||||
|
|
@ -121,13 +128,11 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
context.thresholdImmunities =
|
context.thresholdImmunities =
|
||||||
Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null;
|
Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null;
|
||||||
|
|
||||||
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } =
|
const { selectedStressMarks, stressReductions, currentMarks, currentDamage, maxArmorUsed, availableArmor } =
|
||||||
this.getDamageInfo();
|
this.getDamageInfo();
|
||||||
|
|
||||||
context.armorScore = this.actor.system.armorScore;
|
context.armorScore = this.actor.system.armorScore.max;
|
||||||
context.armorMarks = currentMarks;
|
context.armorMarks = currentMarks;
|
||||||
context.basicMarksUsed =
|
|
||||||
selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
|
||||||
|
|
||||||
const stressReductionStress = this.availableStressReductions
|
const stressReductionStress = this.availableStressReductions
|
||||||
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
|
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
|
||||||
|
|
@ -141,16 +146,30 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
context.maxArmorUsed = maxArmorUsed;
|
||||||
context.marks = {
|
context.availableArmor = availableArmor;
|
||||||
armor: Object.keys(this.marks.armor).reduce((acc, key, index) => {
|
context.basicMarksUsed = availableArmor === 0 || selectedStressMarks.length;
|
||||||
const mark = this.marks.armor[key];
|
|
||||||
if (!this.rulesOn || index + 1 <= maxArmor) acc[key] = mark;
|
|
||||||
|
|
||||||
return acc;
|
const armorSources = [];
|
||||||
}, {}),
|
for (const source of this.marks.armor) {
|
||||||
|
const parent = source.effect.origin
|
||||||
|
? await foundry.utils.fromUuid(source.effect.origin)
|
||||||
|
: source.effect.parent;
|
||||||
|
|
||||||
|
const useEffectName = parent.type === 'armor' || parent instanceof Actor;
|
||||||
|
const label = useEffectName ? source.effect.name : parent.name;
|
||||||
|
armorSources.push({
|
||||||
|
label: label,
|
||||||
|
uuid: source.effect.uuid,
|
||||||
|
marks: source.marks
|
||||||
|
});
|
||||||
|
}
|
||||||
|
context.marks = {
|
||||||
|
armor: armorSources,
|
||||||
stress: this.marks.stress
|
stress: this.marks.stress
|
||||||
};
|
};
|
||||||
|
|
||||||
|
context.usesStressArmor = Object.keys(context.marks.stress).length;
|
||||||
context.availableStressReductions = this.availableStressReductions;
|
context.availableStressReductions = this.availableStressReductions;
|
||||||
|
|
||||||
context.damage = getDamageLabel(this.damage);
|
context.damage = getDamageLabel(this.damage);
|
||||||
|
|
@ -167,27 +186,31 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
getDamageInfo = () => {
|
getDamageInfo = () => {
|
||||||
const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected);
|
const selectedArmorMarks = this.marks.armor.flatMap(x => Object.values(x.marks).filter(x => x.selected));
|
||||||
const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected);
|
const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected);
|
||||||
const stressReductions = this.availableStressReductions
|
const stressReductions = this.availableStressReductions
|
||||||
? Object.values(this.availableStressReductions).filter(red => red.selected)
|
? Object.values(this.availableStressReductions).filter(red => red.selected)
|
||||||
: [];
|
: [];
|
||||||
const currentMarks =
|
const currentMarks = this.actor.system.armorScore.value + selectedArmorMarks.length;
|
||||||
this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length;
|
|
||||||
|
const maxArmorUsed = this.actor.system.rules.damageReduction.maxArmorMarked.value + selectedStressMarks.length;
|
||||||
|
const availableArmor =
|
||||||
|
maxArmorUsed -
|
||||||
|
this.marks.armor.reduce((acc, source) => {
|
||||||
|
acc += Object.values(source.marks).filter(x => x.selected).length;
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
const armorMarkReduction =
|
const armorMarkReduction =
|
||||||
selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark;
|
selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark;
|
||||||
let currentDamage = Math.max(
|
let currentDamage = Math.max(this.damage - armorMarkReduction - stressReductions.length, 0);
|
||||||
this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
if (this.reduceSeverity) {
|
if (this.reduceSeverity) {
|
||||||
currentDamage = Math.max(currentDamage - this.reduceSeverity, 0);
|
currentDamage = Math.max(currentDamage - this.reduceSeverity, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
|
if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
|
||||||
|
|
||||||
return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage };
|
return { selectedStressMarks, stressReductions, currentMarks, currentDamage, maxArmorUsed, availableArmor };
|
||||||
};
|
};
|
||||||
|
|
||||||
static toggleRules() {
|
static toggleRules() {
|
||||||
|
|
@ -195,13 +218,10 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
|
|
||||||
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
||||||
this.marks = {
|
this.marks = {
|
||||||
armor: Object.keys(this.marks.armor).reduce((acc, key, index) => {
|
armor: this.marks.armor.map((mark, index) => {
|
||||||
const mark = this.marks.armor[key];
|
|
||||||
const keepSelectValue = !this.rulesOn || index + 1 <= maxArmor;
|
const keepSelectValue = !this.rulesOn || index + 1 <= maxArmor;
|
||||||
acc[key] = { ...mark, selected: keepSelectValue ? mark.selected : false };
|
return { ...mark, selected: keepSelectValue ? mark.selected : false };
|
||||||
|
}),
|
||||||
return acc;
|
|
||||||
}, {}),
|
|
||||||
stress: this.marks.stress
|
stress: this.marks.stress
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -209,8 +229,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
static setMarks(_, target) {
|
static setMarks(_, target) {
|
||||||
const currentMark = this.marks[target.dataset.type][target.dataset.key];
|
const currentMark = foundry.utils.getProperty(this.marks, target.dataset.path);
|
||||||
const { selectedStressMarks, stressReductions, currentMarks, currentDamage } = this.getDamageInfo();
|
const { selectedStressMarks, stressReductions, currentDamage, availableArmor } = this.getDamageInfo();
|
||||||
|
|
||||||
if (!currentMark.selected && currentDamage === 0) {
|
if (!currentMark.selected && currentDamage === 0) {
|
||||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.damageAlreadyNone'));
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.damageAlreadyNone'));
|
||||||
|
|
@ -218,12 +238,18 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.rulesOn) {
|
if (this.rulesOn) {
|
||||||
if (!currentMark.selected && currentMarks === this.actor.system.armorScore) {
|
if (target.dataset.type === 'armor' && !currentMark.selected && !availableArmor) {
|
||||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks'));
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stressUsed = selectedStressMarks.length;
|
||||||
|
if (target.dataset.type === 'armor' && stressUsed) {
|
||||||
|
const updateResult = this.updateStressArmor(target.dataset.id, !currentMark.selected);
|
||||||
|
if (updateResult === false) return;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentMark.selected) {
|
if (currentMark.selected) {
|
||||||
const currentDamageLabel = getDamageLabel(currentDamage);
|
const currentDamageLabel = getDamageLabel(currentDamage);
|
||||||
for (let reduction of stressReductions) {
|
for (let reduction of stressReductions) {
|
||||||
|
|
@ -232,8 +258,16 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.dataset.type === 'armor' && selectedStressMarks.length > 0) {
|
if (target.dataset.type === 'stress' && currentMark.armorMarkId) {
|
||||||
selectedStressMarks.forEach(mark => (mark.selected = false));
|
for (const source of this.marks.armor) {
|
||||||
|
const match = Object.keys(source.marks).find(key => key === currentMark.armorMarkId);
|
||||||
|
if (match) {
|
||||||
|
source.marks[match].selected = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMark.armorMarkId = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,6 +275,25 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateStressArmor(armorMarkId, select) {
|
||||||
|
let stressMarkKey = null;
|
||||||
|
if (select) {
|
||||||
|
stressMarkKey = Object.keys(this.marks.stress).find(
|
||||||
|
key => this.marks.stress[key].selected && !this.marks.stress[key].armorMarkId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
stressMarkKey = Object.keys(this.marks.stress).find(
|
||||||
|
key => this.marks.stress[key].armorMarkId === armorMarkId
|
||||||
|
);
|
||||||
|
if (!stressMarkKey)
|
||||||
|
stressMarkKey = Object.keys(this.marks.stress).find(key => this.marks.stress[key].selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stressMarkKey) return false;
|
||||||
|
|
||||||
|
this.marks.stress[stressMarkKey].armorMarkId = select ? armorMarkId : null;
|
||||||
|
}
|
||||||
|
|
||||||
static useStressReduction(_, target) {
|
static useStressReduction(_, target) {
|
||||||
const damageValue = Number(target.dataset.reduction);
|
const damageValue = Number(target.dataset.reduction);
|
||||||
const stressReduction = this.availableStressReductions[damageValue];
|
const stressReduction = this.availableStressReductions[damageValue];
|
||||||
|
|
@ -279,11 +332,18 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
static async takeDamage() {
|
static async takeDamage() {
|
||||||
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo();
|
const { selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo();
|
||||||
const armorSpent = selectedArmorMarks.length + selectedStressMarks.length;
|
const armorChanges = this.marks.armor.reduce((acc, source) => {
|
||||||
const stressSpent = selectedStressMarks.length + stressReductions.reduce((acc, red) => acc + red.cost, 0);
|
const amount = Object.values(source.marks).filter(x => x.selected).length;
|
||||||
|
if (amount) acc.push({ uuid: source.effect.uuid, amount });
|
||||||
|
|
||||||
this.resolve({ modifiedDamage: currentDamage, armorSpent, stressSpent });
|
return acc;
|
||||||
|
}, []);
|
||||||
|
const stressSpent =
|
||||||
|
selectedStressMarks.filter(x => x.armorMarkId).length +
|
||||||
|
stressReductions.reduce((acc, red) => acc + red.cost, 0);
|
||||||
|
|
||||||
|
this.resolve({ modifiedDamage: currentDamage, armorChanges, stressSpent });
|
||||||
await this.close(true);
|
await this.close(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { refreshIsAllowed } from '../../helpers/utils.mjs';
|
import { expireActiveEffects, refreshIsAllowed } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -203,7 +203,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
const msg = {
|
const msg = {
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
system: {
|
system: {
|
||||||
moves: moves,
|
moves: moves.map(move => ({ ...move, actions: Array.from(move.actions) })),
|
||||||
actor: this.actor.uuid
|
actor: this.actor.uuid
|
||||||
},
|
},
|
||||||
speaker: cls.getSpeaker(),
|
speaker: cls.getSpeaker(),
|
||||||
|
|
@ -264,6 +264,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
await feature.update({ 'system.resource.value': resetValue });
|
await feature.update({ 'system.resource.value': resetValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expireActiveEffects(this.actor, [this.shortRest ? 'shortRest' : 'longRest']);
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
} else {
|
} else {
|
||||||
this.render();
|
this.render();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
|
||||||
|
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
@ -123,16 +121,8 @@ export default class RerollDamageDialog extends HandlebarsApplicationMixin(Appli
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.message.update(update);
|
await this.message.update(update);
|
||||||
|
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
|
||||||
action: socketEvent.Refresh,
|
|
||||||
data: {
|
|
||||||
refreshType: RefreshType.TagTeamRoll
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.close();
|
await this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -67,7 +67,7 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
|
||||||
break;
|
break;
|
||||||
case 'summary':
|
case 'summary':
|
||||||
const levelKeys = Object.keys(this.levelup.levels);
|
const levelKeys = Object.keys(this.levelup.levels);
|
||||||
const actorDamageDice = this.actor.system.attack.damage.parts[0].value.dice;
|
const actorDamageDice = this.actor.system.attack.damage.parts.hitPoints.value.dice;
|
||||||
const actorRange = this.actor.system.attack.range;
|
const actorRange = this.actor.system.attack.range;
|
||||||
|
|
||||||
let achievementExperiences = [];
|
let achievementExperiences = [];
|
||||||
|
|
|
||||||
|
|
@ -477,7 +477,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
const secondaryData = Object.keys(
|
const secondaryData = Object.keys(
|
||||||
foundry.utils.getProperty(this.levelup, `${target.dataset.path}.secondaryData`)
|
foundry.utils.getProperty(this.levelup, `${target.dataset.path}.secondaryData`)
|
||||||
).reduce((acc, key) => {
|
).reduce((acc, key) => {
|
||||||
acc[`-=${key}`] = null;
|
acc[key] = _del;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
await this.levelup.updateSource({
|
await this.levelup.updateSource({
|
||||||
|
|
@ -511,9 +511,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
const current = foundry.utils.getProperty(this.levelup, `${basePath}.${button.dataset.option}`);
|
const current = foundry.utils.getProperty(this.levelup, `${basePath}.${button.dataset.option}`);
|
||||||
if (Number(button.dataset.cost) > 1 || Object.keys(current).length === 1) {
|
if (Number(button.dataset.cost) > 1 || Object.keys(current).length === 1) {
|
||||||
// Simple handling that doesn't cover potential Custom LevelTiers.
|
// Simple handling that doesn't cover potential Custom LevelTiers.
|
||||||
update[`${basePath}.-=${button.dataset.option}`] = null;
|
update[`${basePath}.${button.dataset.option}`] = _del;
|
||||||
} else {
|
} else {
|
||||||
update[`${basePath}.${button.dataset.option}.-=${button.dataset.checkboxNr}`] = null;
|
update[`${basePath}.${button.dataset.option}.${button.dataset.checkboxNr}`] = _del;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.levelup.levels[this.levelup.currentLevel].nrSelections.available < Number(button.dataset.cost)) {
|
if (this.levelup.levels[this.levelup.currentLevel].nrSelections.available < Number(button.dataset.cost)) {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,15 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
if (data.type === 'Level') {
|
||||||
|
const level = await foundry.documents.Level.fromDropData(data);
|
||||||
|
if (level?.parent === this.document) return this._onSortLevel(event, level);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const item = await foundry.utils.fromUuid(data.uuid);
|
const item = await foundry.utils.fromUuid(data.uuid);
|
||||||
if (item instanceof game.system.api.documents.DhpActor && item.type === 'environment') {
|
if (item instanceof game.system.api.documents.DhpActor && item.type === 'environment') {
|
||||||
let sceneUuid = data.uuid;
|
let sceneUuid = data.uuid;
|
||||||
|
|
@ -114,7 +122,7 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
|
||||||
|
|
||||||
for (const key of Object.keys(this.document._source.flags.daggerheart?.sceneEnvironments ?? {})) {
|
for (const key of Object.keys(this.document._source.flags.daggerheart?.sceneEnvironments ?? {})) {
|
||||||
if (!submitData.flags.daggerheart.sceneEnvironments[key]) {
|
if (!submitData.flags.daggerheart.sceneEnvironments[key]) {
|
||||||
submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null;
|
submitData.flags.daggerheart.sceneEnvironments[key] = _del;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -298,7 +298,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
||||||
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
|
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
|
||||||
await this.settings.updateSource({
|
await this.settings.updateSource({
|
||||||
[`${path}.-=${id}`]: null
|
[`${path}.${id}`]: _del
|
||||||
});
|
});
|
||||||
|
|
||||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||||
|
|
@ -322,7 +322,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
const fields = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).schema.fields;
|
const fields = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).schema.fields;
|
||||||
|
|
||||||
const removeUpdate = Object.keys(this.settings.restMoves[target.dataset.type].moves).reduce((acc, key) => {
|
const removeUpdate = Object.keys(this.settings.restMoves[target.dataset.type].moves).reduce((acc, key) => {
|
||||||
acc[`-=${key}`] = null;
|
acc[key] = _del;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
@ -382,7 +382,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
[`itemFeatures.${target.dataset.type}`]: Object.keys(
|
[`itemFeatures.${target.dataset.type}`]: Object.keys(
|
||||||
this.settings.itemFeatures[target.dataset.type]
|
this.settings.itemFeatures[target.dataset.type]
|
||||||
).reduce((acc, key) => {
|
).reduce((acc, key) => {
|
||||||
acc[`-=${key}`] = null;
|
acc[key] = _del;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
|
|
@ -455,12 +455,12 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
await this.settings.updateSource({
|
await this.settings.updateSource({
|
||||||
[`domains.-=${this.selected.domain}`]: null
|
[`domains.${this.selected.domain}`]: _del
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew);
|
const currentSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew);
|
||||||
if (currentSettings.domains[this.selected.domain]) {
|
if (currentSettings.domains[this.selected.domain]) {
|
||||||
await currentSettings.updateSource({ [`domains.-=${this.selected.domain}`]: null });
|
await currentSettings.updateSource({ [`domains.${this.selected.domain}`]: _del });
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, currentSettings);
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, currentSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -507,7 +507,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
|
|
||||||
static async deleteAdversaryType(_, target) {
|
static async deleteAdversaryType(_, target) {
|
||||||
const { key } = target.dataset;
|
const { key } = target.dataset;
|
||||||
await this.settings.updateSource({ [`adversaryTypes.-=${key}`]: null });
|
await this.settings.updateSource({ [`adversaryTypes.${key}`]: _del });
|
||||||
|
|
||||||
this.selected.adversaryType = this.selected.adversaryType === key ? null : this.selected.adversaryType;
|
this.selected.adversaryType = this.selected.adversaryType === key ? null : this.selected.adversaryType;
|
||||||
this.render();
|
this.render();
|
||||||
|
|
@ -563,7 +563,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
|
|
||||||
const { actorType, resourceKey } = target.dataset;
|
const { actorType, resourceKey } = target.dataset;
|
||||||
await this.settings.updateSource({
|
await this.settings.updateSource({
|
||||||
[`resources.${actorType}.resources.-=${resourceKey}`]: null
|
[`resources.${actorType}.resources.${resourceKey}`]: _del
|
||||||
});
|
});
|
||||||
|
|
||||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ export { default as ActionSettingsConfig } from './action-settings-config.mjs';
|
||||||
export { default as CharacterSettings } from './character-settings.mjs';
|
export { default as CharacterSettings } from './character-settings.mjs';
|
||||||
export { default as AdversarySettings } from './adversary-settings.mjs';
|
export { default as AdversarySettings } from './adversary-settings.mjs';
|
||||||
export { default as CompanionSettings } from './companion-settings.mjs';
|
export { default as CompanionSettings } from './companion-settings.mjs';
|
||||||
export { default as SettingActiveEffectConfig } from './setting-active-effect-config.mjs';
|
|
||||||
export { default as SettingFeatureConfig } from './setting-feature-config.mjs';
|
export { default as SettingFeatureConfig } from './setting-feature-config.mjs';
|
||||||
export { default as EnvironmentSettings } from './environment-settings.mjs';
|
export { default as EnvironmentSettings } from './environment-settings.mjs';
|
||||||
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
|
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getUnusedDamageTypes } from '../../helpers/utils.mjs';
|
||||||
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
||||||
|
|
||||||
const { ApplicationV2 } = foundry.applications.api;
|
const { ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
@ -35,7 +36,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
editDoc: this.editDoc,
|
editDoc: this.editDoc,
|
||||||
addTrigger: this.addTrigger,
|
addTrigger: this.addTrigger,
|
||||||
removeTrigger: this.removeTrigger,
|
removeTrigger: this.removeTrigger,
|
||||||
expandTrigger: this.expandTrigger
|
expandTrigger: this.expandTrigger,
|
||||||
|
addBeastformTraitBonus: this.addBeastformTraitBonus,
|
||||||
|
removeBeastformTraitBonus: this.removeBeastformTraitBonus
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
handler: this.updateForm,
|
handler: this.updateForm,
|
||||||
|
|
@ -104,7 +107,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects', 'summon'];
|
static CLEAN_ARRAYS = ['cost', 'effects', 'summon'];
|
||||||
|
|
||||||
_getTabs(tabs) {
|
_getTabs(tabs) {
|
||||||
for (const v of Object.values(tabs)) {
|
for (const v of Object.values(tabs)) {
|
||||||
|
|
@ -153,8 +156,13 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
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;
|
||||||
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
if (this.action.hasDamage) {
|
||||||
context.hasBaseDamage = !!this.action.parent.attack;
|
context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length;
|
||||||
|
|
||||||
|
if (this.action.damage.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
||||||
|
context.hasBaseDamage = !!this.action.parent.attack;
|
||||||
|
}
|
||||||
|
|
||||||
context.costOptions = this.getCostOptions();
|
context.costOptions = this.getCostOptions();
|
||||||
context.getRollTypeOptions = this.getRollTypeOptions();
|
context.getRollTypeOptions = this.getRollTypeOptions();
|
||||||
context.disableOption = this.disableOption.bind(this);
|
context.disableOption = this.disableOption.bind(this);
|
||||||
|
|
@ -291,18 +299,64 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
|
|
||||||
static addDamage(_event) {
|
static addDamage(_event) {
|
||||||
if (!this.action.damage.parts) return;
|
if (!this.action.damage.parts) return;
|
||||||
const data = this.action.toObject(),
|
|
||||||
part = {};
|
const choices = getUnusedDamageTypes(this.action.damage.parts);
|
||||||
if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
|
const content = new foundry.data.fields.StringField({
|
||||||
data.damage.parts.push(part);
|
label: game.i18n.localize('Damage Type'),
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
choices,
|
||||||
|
required: true
|
||||||
|
}).toFormGroup(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
localize: true,
|
||||||
|
nameAttr: 'value',
|
||||||
|
labelAttr: 'label'
|
||||||
|
}
|
||||||
|
).outerHTML;
|
||||||
|
|
||||||
|
const callback = (_, button) => {
|
||||||
|
const data = this.action.toObject();
|
||||||
|
const type = choices[button.form.elements.type.value].value;
|
||||||
|
const part = this.action.schema.fields.damage.fields.parts.element.getInitialValue();
|
||||||
|
part.applyTo = type;
|
||||||
|
if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
|
||||||
|
part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial;
|
||||||
|
|
||||||
|
data.damage.parts[type] = part;
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeDialog = new foundry.applications.api.DialogV2({
|
||||||
|
buttons: [
|
||||||
|
foundry.utils.mergeObject(
|
||||||
|
{
|
||||||
|
action: 'ok',
|
||||||
|
label: 'Confirm',
|
||||||
|
icon: 'fas fa-check',
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{ callback: callback }
|
||||||
|
)
|
||||||
|
],
|
||||||
|
content: content,
|
||||||
|
rejectClose: false,
|
||||||
|
modal: false,
|
||||||
|
window: {
|
||||||
|
title: game.i18n.localize('Add Damage')
|
||||||
|
},
|
||||||
|
position: { width: 300 }
|
||||||
|
});
|
||||||
|
|
||||||
|
typeDialog.render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeDamage(_event, button) {
|
static removeDamage(_event, button) {
|
||||||
if (!this.action.damage.parts) return;
|
if (!this.action.damage.parts) return;
|
||||||
const data = this.action.toObject(),
|
const data = this.action.toObject();
|
||||||
index = button.dataset.index;
|
const key = button.dataset.key;
|
||||||
data.damage.parts.splice(index, 1);
|
delete data.damage.parts[key];
|
||||||
|
data.damage.parts[`${key}`] = _del;
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,6 +414,21 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async addBeastformTraitBonus() {
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.beastform.modifications.traitBonuses = [
|
||||||
|
...data.beastform.modifications.traitBonuses,
|
||||||
|
this.action.schema.fields.beastform.fields.modifications.fields.traitBonuses.element.getInitialValue()
|
||||||
|
];
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static async removeBeastformTraitBonus(_event, button) {
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.beastform.modifications.traitBonuses.splice(button.dataset.index, 1);
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
updateSummonCount(event) {
|
updateSummonCount(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const wrapper = event.target.closest('.summon-count-wrapper');
|
const wrapper = event.target.closest('.summon-count-wrapper');
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,13 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
||||||
const effectData = this._addEffectData.bind(this)();
|
const effectData = this._addEffectData.bind(this)();
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject();
|
||||||
|
|
||||||
const [created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], {
|
const created = await this.action.item.createEmbeddedDocuments('ActiveEffect', [
|
||||||
render: false
|
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
|
||||||
});
|
]);
|
||||||
data.effects.push({ _id: created._id });
|
|
||||||
|
data.effects.push({ _id: created[0]._id });
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
this.action.item.effects.get(created._id).sheet.render(true);
|
this.action.item.effects.get(created[0]._id).sheet.render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig {
|
||||||
|
|
||||||
static async editEffect(event) {
|
static async editEffect(event) {
|
||||||
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
|
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
|
||||||
const updatedEffect = await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(
|
const updatedEffect = await game.system.api.applications.sheetConfigs.ActiveEffectConfig.configureSetting(
|
||||||
this.getEffectDetails(id)
|
this.getEffectDetails(id)
|
||||||
);
|
);
|
||||||
if (!updatedEffect) return;
|
if (!updatedEffect) return;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
|
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
|
||||||
changes: {
|
changes: {
|
||||||
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
|
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
|
||||||
|
templates: ['systems/daggerheart/templates/sheets/activeEffect/change.hbs'],
|
||||||
scrollable: ['ol[data-changes]']
|
scrollable: ['ol[data-changes]']
|
||||||
},
|
},
|
||||||
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
|
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
|
||||||
|
|
@ -149,6 +150,18 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
minLength: 0
|
minLength: 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
htmlElement
|
||||||
|
.querySelector('.stacking-change-checkbox')
|
||||||
|
?.addEventListener('change', this.stackingChangeToggle.bind(this));
|
||||||
|
|
||||||
|
htmlElement
|
||||||
|
.querySelector('.armor-change-checkbox')
|
||||||
|
?.addEventListener('change', this.armorChangeToggle.bind(this));
|
||||||
|
|
||||||
|
htmlElement
|
||||||
|
.querySelector('.armor-damage-thresholds-checkbox')
|
||||||
|
?.addEventListener('change', this.armorDamageThresholdToggle.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
|
|
@ -173,8 +186,166 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'settings':
|
||||||
|
const groups = {
|
||||||
|
time: _loc('EFFECT.DURATION.UNITS.GROUPS.time'),
|
||||||
|
combat: _loc('EFFECT.DURATION.UNITS.GROUPS.combat')
|
||||||
|
};
|
||||||
|
partContext.durationUnits = CONST.ACTIVE_EFFECT_DURATION_UNITS.map(value => ({
|
||||||
|
value,
|
||||||
|
label: _loc(`EFFECT.DURATION.UNITS.${value}`),
|
||||||
|
group: CONST.ACTIVE_EFFECT_TIME_DURATION_UNITS.includes(value) ? groups.time : groups.combat
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
case 'changes':
|
||||||
|
const singleTypes = ['armor'];
|
||||||
|
const typedChanges = context.source.changes.reduce((acc, change, index) => {
|
||||||
|
if (singleTypes.includes(change.type)) {
|
||||||
|
acc[change.type] = { ...change, index };
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
partContext.changes = partContext.changes.filter(c => !!c);
|
||||||
|
partContext.typedChanges = typedChanges;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return partContext;
|
return partContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stackingChangeToggle(event) {
|
||||||
|
const stackingFields = this.document.system.schema.fields.stacking.fields;
|
||||||
|
const systemData = {
|
||||||
|
stacking: event.target.checked
|
||||||
|
? { value: stackingFields.value.initial, max: stackingFields.max.initial }
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
return this.submit({ updateData: { system: systemData } });
|
||||||
|
}
|
||||||
|
|
||||||
|
armorChangeToggle(event) {
|
||||||
|
if (event.target.checked) {
|
||||||
|
this.addArmorChange();
|
||||||
|
} else {
|
||||||
|
this.removeTypedChange(event.target.dataset.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Could be generalised if needed later */
|
||||||
|
addArmorChange() {
|
||||||
|
const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form));
|
||||||
|
const changes = Object.values(submitData.system?.changes ?? {});
|
||||||
|
changes.push(game.system.api.data.activeEffects.changeTypes.armor.getInitialValue());
|
||||||
|
return this.submit({ updateData: { system: { changes } } });
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTypedChange(indexString) {
|
||||||
|
const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form));
|
||||||
|
const changes = Object.values(submitData.system.changes);
|
||||||
|
const index = Number(indexString);
|
||||||
|
changes.splice(index, 1);
|
||||||
|
return this.submit({ updateData: { system: { changes } } });
|
||||||
|
}
|
||||||
|
|
||||||
|
armorDamageThresholdToggle(event) {
|
||||||
|
const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form));
|
||||||
|
const changes = Object.values(submitData.system?.changes ?? {});
|
||||||
|
const index = Number(event.target.dataset.index);
|
||||||
|
if (event.target.checked) {
|
||||||
|
changes[index].value.damageThresholds = { major: 0, severe: 0 };
|
||||||
|
} else {
|
||||||
|
changes[index].value.damageThresholds = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.submit({ updateData: { system: { changes } } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
_renderChange(context) {
|
||||||
|
const { change, index, defaultPriority } = context;
|
||||||
|
if (!(change.type in CONFIG.DH.GENERAL.baseActiveEffectModes)) return null;
|
||||||
|
|
||||||
|
const changeTypesSchema = this.document.system.schema.fields.changes.element.types;
|
||||||
|
const fields = context.fields ?? (changeTypesSchema[change.type] ?? changeTypesSchema.add).fields;
|
||||||
|
if (typeof change.value !== 'string') change.value = JSON.stringify(change.value);
|
||||||
|
Object.assign(
|
||||||
|
change,
|
||||||
|
['key', 'type', 'value', 'priority'].reduce((paths, fieldName) => {
|
||||||
|
paths[`${fieldName}Path`] = `system.changes.${index}.${fieldName}`;
|
||||||
|
return paths;
|
||||||
|
}, {})
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
game.system.api.documents.DhActiveEffect.CHANGE_TYPES[change.type].render?.(
|
||||||
|
change,
|
||||||
|
index,
|
||||||
|
defaultPriority
|
||||||
|
) ??
|
||||||
|
foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/sheets/activeEffect/change.hbs',
|
||||||
|
{
|
||||||
|
change,
|
||||||
|
index,
|
||||||
|
defaultPriority,
|
||||||
|
fields,
|
||||||
|
types: Object.keys(CONFIG.DH.GENERAL.baseActiveEffectModes).reduce((r, key) => {
|
||||||
|
r[key] = CONFIG.DH.GENERAL.baseActiveEffectModes[key].label;
|
||||||
|
return r;
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_onChangeForm(_formConfig, event) {
|
||||||
|
if (foundry.utils.isElementInstanceOf(event.target, 'select') && event.target.name === 'system.duration.type') {
|
||||||
|
const durationSection = this.element.querySelector('.custom-duration-section');
|
||||||
|
if (event.target.value === 'custom') durationSection.classList.add('visible');
|
||||||
|
else durationSection.classList.remove('visible');
|
||||||
|
|
||||||
|
const durationDescription = this.element.querySelector('.duration-description');
|
||||||
|
if (event.target.value === 'temporary') durationDescription.classList.add('visible');
|
||||||
|
else durationDescription.classList.remove('visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_processFormData(event, form, formData) {
|
||||||
|
const submitData = super._processFormData(event, form, formData);
|
||||||
|
if (submitData.start && !submitData.start.time) submitData.start.time = '0';
|
||||||
|
else if (!submitData) submitData.start = null;
|
||||||
|
|
||||||
|
return submitData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_processSubmitData(event, form, submitData, options) {
|
||||||
|
if (this.options.isSetting) {
|
||||||
|
// Settings should update source instead
|
||||||
|
this.document.updateSource(submitData);
|
||||||
|
this.render();
|
||||||
|
} else {
|
||||||
|
return super._processSubmitData(event, form, submitData, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates an active effect config for a setting */
|
||||||
|
static async configureSetting(effect, options = {}) {
|
||||||
|
const document = new CONFIG.ActiveEffect.documentClass({ ...foundry.utils.duplicate(effect), _id: effect.id });
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const app = new this({ document, ...options, isSetting: true });
|
||||||
|
app.addEventListener(
|
||||||
|
'close',
|
||||||
|
() => {
|
||||||
|
const newEffect = app.document.toObject(true);
|
||||||
|
newEffect.id = newEffect._id;
|
||||||
|
delete newEffect._id;
|
||||||
|
resolve(newEffect);
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
app.render({ force: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
});
|
});
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null });
|
await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,8 @@ export default class DHCharacterSettings extends DHBaseActorSettings {
|
||||||
|
|
||||||
if (relinkAchievementData.length > 0) {
|
if (relinkAchievementData.length > 0) {
|
||||||
relinkAchievementData.forEach(data => {
|
relinkAchievementData.forEach(data => {
|
||||||
updates[`system.levelData.levelups.${data.levelKey}.achievements.experiences.-=${data.experience}`] =
|
updates[`system.levelData.levelups.${data.levelKey}.achievements.experiences.${data.experience}`] =
|
||||||
null;
|
_del;
|
||||||
});
|
});
|
||||||
} else if (relinkSelectionData.length > 0) {
|
} else if (relinkSelectionData.length > 0) {
|
||||||
relinkSelectionData.forEach(data => {
|
relinkSelectionData.forEach(data => {
|
||||||
|
|
@ -137,7 +137,7 @@ export default class DHCharacterSettings extends DHBaseActorSettings {
|
||||||
|
|
||||||
await this.actor.update({
|
await this.actor.update({
|
||||||
...updates,
|
...updates,
|
||||||
[`system.experiences.-=${target.dataset.experience}`]: null
|
[`system.experiences.${target.dataset.experience}`]: _del
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,6 @@ export default class DHCompanionSettings extends DHBaseActorSettings {
|
||||||
});
|
});
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null });
|
await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,9 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
||||||
*/
|
*/
|
||||||
static async #addCategory() {
|
static async #addCategory() {
|
||||||
await this.actor.update({
|
await this.actor.update({
|
||||||
[`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize(
|
[`system.potentialAdversaries.${foundry.utils.randomID()}`]: {
|
||||||
'DAGGERHEART.ACTORS.Environment.newAdversary'
|
label: game.i18n.localize('DAGGERHEART.ACTORS.Environment.newAdversary')
|
||||||
)
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #removeCategory(_, target) {
|
static async #removeCategory(_, target) {
|
||||||
await this.actor.update({ [`system.potentialAdversaries.-=${target.dataset.categoryId}`]: null });
|
await this.actor.update({ [`system.potentialAdversaries.${target.dataset.categoryId}`]: _del });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -138,4 +138,8 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _onDropItem(event, item) {
|
||||||
|
console.log(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
import autocomplete from 'autocompleter';
|
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
|
||||||
|
|
||||||
export default class SettingActiveEffectConfig extends HandlebarsApplicationMixin(ApplicationV2) {
|
|
||||||
constructor(effect) {
|
|
||||||
super({});
|
|
||||||
|
|
||||||
this.effect = foundry.utils.deepClone(effect);
|
|
||||||
this.changeChoices = game.system.api.applications.sheetConfigs.ActiveEffectConfig.getChangeChoices();
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config', 'standard-form'],
|
|
||||||
tag: 'form',
|
|
||||||
position: {
|
|
||||||
width: 560
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
submitOnChange: false,
|
|
||||||
closeOnSubmit: false,
|
|
||||||
handler: SettingActiveEffectConfig.#onSubmit
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
editImage: SettingActiveEffectConfig.#editImage,
|
|
||||||
addChange: SettingActiveEffectConfig.#addChange,
|
|
||||||
deleteChange: SettingActiveEffectConfig.#deleteChange
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static PARTS = {
|
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' },
|
|
||||||
tabs: { template: 'templates/generic/tab-navigation.hbs' },
|
|
||||||
details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] },
|
|
||||||
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
|
|
||||||
changes: {
|
|
||||||
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
|
|
||||||
scrollable: ['ol[data-changes]']
|
|
||||||
},
|
|
||||||
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
|
|
||||||
};
|
|
||||||
|
|
||||||
static TABS = {
|
|
||||||
sheet: {
|
|
||||||
tabs: [
|
|
||||||
{ id: 'details', icon: 'fa-solid fa-book' },
|
|
||||||
{ id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' },
|
|
||||||
{ id: 'changes', icon: 'fa-solid fa-gears' }
|
|
||||||
],
|
|
||||||
initial: 'details',
|
|
||||||
labelPrefix: 'EFFECT.TABS'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**@inheritdoc */
|
|
||||||
async _onFirstRender(context, options) {
|
|
||||||
await super._onFirstRender(context, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options);
|
|
||||||
context.source = this.effect;
|
|
||||||
context.fields = game.system.api.documents.DhActiveEffect.schema.fields;
|
|
||||||
context.systemFields = game.system.api.data.activeEffects.BaseEffect._schema.fields;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
|
||||||
const changeChoices = this.changeChoices;
|
|
||||||
|
|
||||||
htmlElement.querySelectorAll('.effect-change-input').forEach(element => {
|
|
||||||
autocomplete({
|
|
||||||
input: element,
|
|
||||||
fetch: function (text, update) {
|
|
||||||
if (!text) {
|
|
||||||
update(changeChoices);
|
|
||||||
} else {
|
|
||||||
text = text.toLowerCase();
|
|
||||||
var suggestions = changeChoices.filter(n => n.label.toLowerCase().includes(text));
|
|
||||||
update(suggestions);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function (item, search) {
|
|
||||||
const label = game.i18n.localize(item.label);
|
|
||||||
const matchIndex = label.toLowerCase().indexOf(search);
|
|
||||||
|
|
||||||
const beforeText = label.slice(0, matchIndex);
|
|
||||||
const matchText = label.slice(matchIndex, matchIndex + search.length);
|
|
||||||
const after = label.slice(matchIndex + search.length, label.length);
|
|
||||||
|
|
||||||
const element = document.createElement('li');
|
|
||||||
element.innerHTML =
|
|
||||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
|
||||||
' ',
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
if (item.hint) {
|
|
||||||
element.dataset.tooltip = game.i18n.localize(item.hint);
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
},
|
|
||||||
renderGroup: function (label) {
|
|
||||||
const itemElement = document.createElement('div');
|
|
||||||
itemElement.textContent = game.i18n.localize(label);
|
|
||||||
return itemElement;
|
|
||||||
},
|
|
||||||
onSelect: function (item) {
|
|
||||||
element.value = `system.${item.value}`;
|
|
||||||
},
|
|
||||||
click: e => e.fetch(),
|
|
||||||
customize: function (_input, _inputRect, container) {
|
|
||||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
|
||||||
},
|
|
||||||
minLength: 0
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _preparePartContext(partId, context) {
|
|
||||||
if (partId in context.tabs) context.tab = context.tabs[partId];
|
|
||||||
switch (partId) {
|
|
||||||
case 'details':
|
|
||||||
context.statuses = CONFIG.statusEffects.map(s => ({ value: s.id, label: game.i18n.localize(s.name) }));
|
|
||||||
context.isActorEffect = false;
|
|
||||||
context.isItemEffect = true;
|
|
||||||
const useGeneric = game.settings.get(
|
|
||||||
CONFIG.DH.id,
|
|
||||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
|
||||||
).showGenericStatusEffects;
|
|
||||||
if (!useGeneric) {
|
|
||||||
context.statuses = [
|
|
||||||
...context.statuses,
|
|
||||||
Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
|
|
||||||
value: status.id,
|
|
||||||
label: game.i18n.localize(status.name)
|
|
||||||
}))
|
|
||||||
];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'changes':
|
|
||||||
context.modes = Object.entries(CONST.ACTIVE_EFFECT_MODES).reduce((modes, [key, value]) => {
|
|
||||||
modes[value] = game.i18n.localize(`EFFECT.MODE_${key}`);
|
|
||||||
return modes;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
context.priorities = ActiveEffectConfig.DEFAULT_PRIORITIES;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #onSubmit(_event, _form, formData) {
|
|
||||||
this.data = foundry.utils.expandObject(formData.object);
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edit a Document image.
|
|
||||||
* @this {DocumentSheetV2}
|
|
||||||
* @type {ApplicationClickAction}
|
|
||||||
*/
|
|
||||||
static async #editImage(_event, target) {
|
|
||||||
if (target.nodeName !== 'IMG') {
|
|
||||||
throw new Error('The editImage action is available only for IMG elements.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const attr = target.dataset.edit;
|
|
||||||
const current = foundry.utils.getProperty(this.effect, attr);
|
|
||||||
const fp = new FilePicker.implementation({
|
|
||||||
current,
|
|
||||||
type: 'image',
|
|
||||||
callback: path => (target.src = path),
|
|
||||||
position: {
|
|
||||||
top: this.position.top + 40,
|
|
||||||
left: this.position.left + 10
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await fp.browse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new change to the effect's changes array.
|
|
||||||
* @this {ActiveEffectConfig}
|
|
||||||
* @type {ApplicationClickAction}
|
|
||||||
*/
|
|
||||||
static async #addChange() {
|
|
||||||
const { changes, ...rest } = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
|
||||||
const updatedChanges = Object.values(changes ?? {});
|
|
||||||
updatedChanges.push({});
|
|
||||||
|
|
||||||
this.effect = { ...rest, changes: updatedChanges };
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a change from the effect's changes array.
|
|
||||||
* @this {ActiveEffectConfig}
|
|
||||||
* @type {ApplicationClickAction}
|
|
||||||
*/
|
|
||||||
static async #deleteChange(event) {
|
|
||||||
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
|
||||||
const updatedChanges = Object.values(submitData.changes);
|
|
||||||
const row = event.target.closest('li');
|
|
||||||
const index = Number(row.dataset.index) || 0;
|
|
||||||
updatedChanges.splice(index, 1);
|
|
||||||
|
|
||||||
this.effect = { ...submitData, changes: updatedChanges };
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async configure(effect, options = {}) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const app = new this(effect, options);
|
|
||||||
app.addEventListener('close', () => resolve(app.data), { once: true });
|
|
||||||
app.render({ force: true });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -147,7 +147,7 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
||||||
const effectIndex = this.move.effects.findIndex(x => x.id === id);
|
const effectIndex = this.move.effects.findIndex(x => x.id === id);
|
||||||
const effect = this.move.effects[effectIndex];
|
const effect = this.move.effects[effectIndex];
|
||||||
const updatedEffect =
|
const updatedEffect =
|
||||||
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect);
|
await game.system.api.applications.sheetConfigs.ActiveEffectConfig.configureSetting(effect);
|
||||||
if (!updatedEffect) return;
|
if (!updatedEffect) return;
|
||||||
|
|
||||||
await this.updateMove({
|
await this.updateMove({
|
||||||
|
|
@ -205,7 +205,7 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.updateMove({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
await this.updateMove({ [`${this.actionsPath}.${target.dataset.id}`]: _del });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,9 @@ export default function DHTokenConfigMixin(Base) {
|
||||||
changes.height = tokenSize;
|
changes.height = tokenSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletions = { '-=actorId': null, '-=actorLink': null };
|
// const deletions = { actorId: _del };
|
||||||
const mergeOptions = { inplace: false, performDeletions: true };
|
// const mergeOptions = { inplace: false, performDeletions: true, actorLink: false };
|
||||||
this._preview.updateSource(mergeObject(changes, deletions, mergeOptions));
|
this._preview.updateSource(changes);
|
||||||
|
|
||||||
if (this._preview?.object?.destroyed === false) {
|
if (this._preview?.object?.destroyed === false) {
|
||||||
this._preview.object.initializeSources();
|
this._preview.object.initializeSources();
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||||
import DhDeathMove from '../../dialogs/deathMove.mjs';
|
import DhDeathMove from '../../dialogs/deathMove.mjs';
|
||||||
import { abilities } from '../../../config/actorConfig.mjs';
|
|
||||||
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
||||||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||||
import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs';
|
import { getArmorSources, getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs';
|
||||||
|
|
||||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
||||||
|
|
@ -35,7 +34,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
cancelBeastform: CharacterSheet.#cancelBeastform,
|
cancelBeastform: CharacterSheet.#cancelBeastform,
|
||||||
toggleResourceManagement: CharacterSheet.#toggleResourceManagement,
|
toggleResourceManagement: CharacterSheet.#toggleResourceManagement,
|
||||||
useDowntime: this.useDowntime,
|
useDowntime: this.useDowntime,
|
||||||
viewParty: CharacterSheet.#viewParty
|
viewParty: CharacterSheet.#viewParty,
|
||||||
|
toggleArmorMangement: CharacterSheet.#toggleArmorManagement
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
|
@ -639,12 +639,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateArmorMarks(event) {
|
async updateArmorMarks(event) {
|
||||||
const armor = this.document.system.armor;
|
const inputValue = Number(event.currentTarget.value);
|
||||||
if (!armor) return;
|
const { value, max } = this.document.system.armorScore;
|
||||||
|
const changeValue = Math.min(inputValue - value, max - value);
|
||||||
|
|
||||||
const maxMarks = this.document.system.armorScore;
|
event.currentTarget.value = inputValue < 0 ? 0 : value + changeValue;
|
||||||
const value = Math.min(Math.max(Number(event.currentTarget.value), 0), maxMarks);
|
this.document.system.updateArmorValue({ value: changeValue });
|
||||||
await armor.update({ 'system.marks.value': value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -720,35 +720,16 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
* Rolls an attribute check based on the clicked button's dataset attribute.
|
* Rolls an attribute check based on the clicked button's dataset attribute.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #rollAttribute(event, button) {
|
static async #rollAttribute(_event, button) {
|
||||||
const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label);
|
const result = await this.document.rollTrait(button.dataset.attribute);
|
||||||
const config = {
|
|
||||||
event: event,
|
|
||||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
|
||||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: abilityLabel
|
|
||||||
}),
|
|
||||||
effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this.document),
|
|
||||||
roll: {
|
|
||||||
trait: button.dataset.attribute,
|
|
||||||
type: 'trait'
|
|
||||||
},
|
|
||||||
hasRoll: true,
|
|
||||||
actionType: 'action',
|
|
||||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: abilityLabel
|
|
||||||
})
|
|
||||||
};
|
|
||||||
const result = await this.document.diceRoll(config);
|
|
||||||
if (!result) return;
|
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 =
|
const costResources =
|
||||||
result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value, total: -cost.total })) ||
|
result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value, total: -cost.total })) ||
|
||||||
{};
|
{};
|
||||||
config.resourceUpdates.addResources(costResources);
|
result.resourceUpdates.addResources(costResources);
|
||||||
await config.resourceUpdates.updateResources();
|
await result.resourceUpdates.updateResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: redo toggleEquipItem method
|
//TODO: redo toggleEquipItem method
|
||||||
|
|
@ -823,10 +804,13 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
* Toggles ArmorScore resource value.
|
* Toggles ArmorScore resource value.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toggleArmor(_, button, element) {
|
static async #toggleArmor(_, button, _element) {
|
||||||
const ArmorValue = Number.parseInt(button.dataset.value);
|
const { value, max } = this.document.system.armorScore;
|
||||||
const newValue = this.document.system.armor.system.marks.value >= ArmorValue ? ArmorValue - 1 : ArmorValue;
|
const inputValue = Number.parseInt(button.dataset.value);
|
||||||
await this.document.system.armor.update({ 'system.marks.value': newValue });
|
const newValue = value >= inputValue ? inputValue - 1 : inputValue;
|
||||||
|
const changeValue = Math.min(newValue - value, max - value);
|
||||||
|
|
||||||
|
this.document.system.updateArmorValue({ value: changeValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -952,6 +936,99 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #toggleArmorManagement(_event, target) {
|
||||||
|
const existingTooltip = document.body.querySelector('.locked-tooltip .armor-management-container');
|
||||||
|
if (existingTooltip) {
|
||||||
|
game.tooltip.dismissLockedTooltips();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const armorSources = getArmorSources(this.document)
|
||||||
|
.filter(s => !s.disabled)
|
||||||
|
.toReversed()
|
||||||
|
.map(({ name, document, data }) => ({
|
||||||
|
...data,
|
||||||
|
uuid: document.uuid,
|
||||||
|
name
|
||||||
|
}));
|
||||||
|
if (!armorSources.length) return;
|
||||||
|
|
||||||
|
const useResourcePips = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||||
|
).useResourcePips;
|
||||||
|
const html = document.createElement('div');
|
||||||
|
html.innerHTML = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
`systems/daggerheart/templates/ui/tooltip/armorManagement.hbs`,
|
||||||
|
{
|
||||||
|
sources: armorSources,
|
||||||
|
useResourcePips
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
game.tooltip.dismissLockedTooltips();
|
||||||
|
game.tooltip.activate(target, {
|
||||||
|
html,
|
||||||
|
locked: true,
|
||||||
|
cssClass: 'bordered-tooltip',
|
||||||
|
direction: 'DOWN'
|
||||||
|
});
|
||||||
|
|
||||||
|
html.querySelectorAll('.armor-slot').forEach(element => {
|
||||||
|
element.addEventListener('click', CharacterSheet.armorSourcePipUpdate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async armorSourceInput(event) {
|
||||||
|
const effect = await foundry.utils.fromUuid(event.target.dataset.uuid);
|
||||||
|
const value = Math.max(Math.min(Number.parseInt(event.target.value), effect.system.armorData.max), 0);
|
||||||
|
event.target.value = value;
|
||||||
|
const progressBar = event.target.closest('.status-bar.armor-slots').querySelector('progress');
|
||||||
|
progressBar.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update specific armor source */
|
||||||
|
static async armorSourcePipUpdate(event) {
|
||||||
|
const target = event.target.closest('.armor-slot');
|
||||||
|
const { uuid, value } = target.dataset;
|
||||||
|
const document = await foundry.utils.fromUuid(uuid);
|
||||||
|
|
||||||
|
let inputValue = Number.parseInt(value);
|
||||||
|
let decreasing = false;
|
||||||
|
let newCurrent = 0;
|
||||||
|
|
||||||
|
if (document.type === 'armor') {
|
||||||
|
decreasing = document.system.armor.current >= inputValue;
|
||||||
|
newCurrent = decreasing ? inputValue - 1 : inputValue;
|
||||||
|
await document.update({ 'system.armor.current': newCurrent });
|
||||||
|
} else if (document.system.armorData) {
|
||||||
|
const { current } = document.system.armorData;
|
||||||
|
decreasing = current >= inputValue;
|
||||||
|
newCurrent = decreasing ? inputValue - 1 : inputValue;
|
||||||
|
|
||||||
|
const newChanges = document.system.changes.map(change => ({
|
||||||
|
...change,
|
||||||
|
value: change.type === 'armor' ? { ...change.value, current: newCurrent } : change.value
|
||||||
|
}));
|
||||||
|
|
||||||
|
await document.update({ 'system.changes': newChanges });
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = target.closest('.slot-bar');
|
||||||
|
for (const armorSlot of container.querySelectorAll('.armor-slot i')) {
|
||||||
|
const index = Number.parseInt(armorSlot.dataset.index);
|
||||||
|
if (decreasing && index >= newCurrent) {
|
||||||
|
armorSlot.classList.remove('fa-shield');
|
||||||
|
armorSlot.classList.add('fa-shield-halved');
|
||||||
|
} else if (!decreasing && index < newCurrent) {
|
||||||
|
armorSlot.classList.add('fa-shield');
|
||||||
|
armorSlot.classList.remove('fa-shield-halved');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async #toggleResourceManagement(event, button) {
|
static async #toggleResourceManagement(event, button) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const existingTooltip = document.body.querySelector('.locked-tooltip .resource-management-container');
|
const existingTooltip = document.body.querySelector('.locked-tooltip .resource-management-container');
|
||||||
|
|
@ -985,7 +1062,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
const target = button.closest('.resource-section');
|
const target = button.closest('.resource-section');
|
||||||
|
|
||||||
game.tooltip.dismissLockedTooltips();
|
game.tooltip.dismissLockedTooltips();
|
||||||
game.tooltip.activate(target, {
|
game.tooltip.activate(target, {
|
||||||
html,
|
html,
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
refeshActions: Party.#refeshActions,
|
refeshActions: Party.#refeshActions,
|
||||||
triggerRest: Party.#triggerRest,
|
triggerRest: Party.#triggerRest,
|
||||||
tagTeamRoll: Party.#tagTeamRoll,
|
tagTeamRoll: Party.#tagTeamRoll,
|
||||||
groupRoll: Party.#groupRoll,
|
groupRoll: Party.#groupRoll
|
||||||
selectRefreshable: DaggerheartMenu.selectRefreshable,
|
|
||||||
refreshActors: DaggerheartMenu.refreshActors
|
|
||||||
},
|
},
|
||||||
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
|
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
|
||||||
};
|
};
|
||||||
|
|
@ -120,6 +118,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
secrets: this.document.isOwner,
|
secrets: this.document.isOwner,
|
||||||
relativeTo: this.document
|
relativeTo: this.document
|
||||||
});
|
});
|
||||||
|
context.tagTeamActive = Boolean(this.document.system.tagTeam.initiator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -190,11 +189,14 @@ export default class Party extends DHBaseActorSheet {
|
||||||
* Toggles a armor slot resource value.
|
* Toggles a armor slot resource value.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toggleArmorSlot(_, target, element) {
|
static async #toggleArmorSlot(_, target) {
|
||||||
const armorItem = await foundry.utils.fromUuid(target.dataset.itemUuid);
|
const actor = game.actors.get(target.dataset.actorId);
|
||||||
const armorValue = Number.parseInt(target.dataset.value);
|
const { value, max } = actor.system.armorScore;
|
||||||
const newValue = armorItem.system.marks.value >= armorValue ? armorValue - 1 : armorValue;
|
const inputValue = Number.parseInt(target.dataset.value);
|
||||||
await armorItem.update({ 'system.marks.value': newValue });
|
const newValue = value >= inputValue ? inputValue - 1 : inputValue;
|
||||||
|
const changeValue = Math.min(newValue - value, max - value);
|
||||||
|
|
||||||
|
await actor.system.updateArmorValue({ value: changeValue });
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,11 +257,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #tagTeamRoll() {
|
static async #tagTeamRoll() {
|
||||||
new game.system.api.applications.dialogs.TagTeamDialog(
|
new game.system.api.applications.dialogs.TagTeamDialog(this.document).render({ force: true });
|
||||||
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
|
||||||
).render({
|
|
||||||
force: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #groupRoll(_params) {
|
static async #groupRoll(_params) {
|
||||||
|
|
|
||||||
|
|
@ -72,20 +72,15 @@ const typeSettingsMap = {
|
||||||
*/
|
*/
|
||||||
export default function DHApplicationMixin(Base) {
|
export default function DHApplicationMixin(Base) {
|
||||||
class DHSheetV2 extends HandlebarsApplicationMixin(Base) {
|
class DHSheetV2 extends HandlebarsApplicationMixin(Base) {
|
||||||
|
#nonHeaderAttribution = ['environment', 'ancestry', 'community', 'domainCard'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {DHSheetV2Configuration} [options={}]
|
* @param {DHSheetV2Configuration} [options={}]
|
||||||
*/
|
*/
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
super(options);
|
super(options);
|
||||||
/**
|
|
||||||
* @type {foundry.applications.ux.DragDrop[]}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this._dragDrop = this._createDragDropHandlers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#nonHeaderAttribution = ['environment', 'ancestry', 'community', 'domainCard'];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default options for the sheet.
|
* The default options for the sheet.
|
||||||
* @type {DHSheetV2Configuration}
|
* @type {DHSheetV2Configuration}
|
||||||
|
|
@ -177,7 +172,6 @@ export default function DHApplicationMixin(Base) {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
this._dragDrop.forEach(d => d.bind(htmlElement));
|
|
||||||
|
|
||||||
// Handle delta inputs
|
// Handle delta inputs
|
||||||
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
|
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
|
||||||
|
|
@ -290,6 +284,16 @@ export default function DHApplicationMixin(Base) {
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
await super._onRender(context, options);
|
await super._onRender(context, options);
|
||||||
this._createTagifyElements(this.options.tagifyConfigs);
|
this._createTagifyElements(this.options.tagifyConfigs);
|
||||||
|
|
||||||
|
for (const d of this.options.dragDrop) {
|
||||||
|
new foundry.applications.ux.DragDrop.implementation({
|
||||||
|
...d,
|
||||||
|
callbacks: {
|
||||||
|
dragstart: this._onDragStart.bind(this),
|
||||||
|
drop: this._onDrop.bind(this)
|
||||||
|
}
|
||||||
|
}).bind(this.element);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -350,21 +354,6 @@ export default function DHApplicationMixin(Base) {
|
||||||
/* Drag and Drop */
|
/* Drag and Drop */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates drag-drop handlers from the configured options.
|
|
||||||
* @returns {foundry.applications.ux.DragDrop[]}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_createDragDropHandlers() {
|
|
||||||
return this.options.dragDrop.map(d => {
|
|
||||||
d.callbacks = {
|
|
||||||
dragstart: this._onDragStart.bind(this),
|
|
||||||
drop: this._onDrop.bind(this)
|
|
||||||
};
|
|
||||||
return new foundry.applications.ux.DragDrop.implementation(d);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle dragStart event.
|
* Handle dragStart event.
|
||||||
* @param {DragEvent} event
|
* @param {DragEvent} event
|
||||||
|
|
@ -499,7 +488,10 @@ export default function DHApplicationMixin(Base) {
|
||||||
icon: 'fa-solid fa-explosion',
|
icon: 'fa-solid fa-explosion',
|
||||||
condition: target => {
|
condition: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length;
|
return (
|
||||||
|
!foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) ||
|
||||||
|
!foundry.utils.isEmpty(doc?.damage?.parts)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
callback: async (target, event) => {
|
callback: async (target, event) => {
|
||||||
const doc = await getDocFromElement(target),
|
const doc = await getDocFromElement(target),
|
||||||
|
|
@ -742,11 +734,13 @@ export default function DHApplicationMixin(Base) {
|
||||||
|
|
||||||
const cls =
|
const cls =
|
||||||
type === 'action' ? game.system.api.models.actions.actionsTypes.base : getDocumentClass(documentClass);
|
type === 'action' ? game.system.api.models.actions.actionsTypes.base : getDocumentClass(documentClass);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
name: cls.defaultName({ type, parent }),
|
name: cls.defaultName({ type, parent }),
|
||||||
type,
|
type,
|
||||||
system: systemData
|
system: systemData
|
||||||
};
|
};
|
||||||
|
|
||||||
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) {
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
inactives: []
|
inactives: []
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const effect of this.actor.allApplicableEffects()) {
|
for (const effect of this.actor.allApplicableEffects({ noTransferArmor: true })) {
|
||||||
const list = effect.active ? context.effects.actives : context.effects.inactives;
|
const list = effect.active ? context.effects.actives : context.effects.inactives;
|
||||||
list.push(effect);
|
list.push(effect);
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +228,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
'systems/daggerheart/templates/ui/chat/action.hbs',
|
'systems/daggerheart/templates/ui/chat/action.hbs',
|
||||||
systemData
|
systemData
|
||||||
),
|
),
|
||||||
title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'),
|
|
||||||
speaker: cls.getSpeaker(),
|
speaker: cls.getSpeaker(),
|
||||||
flags: {
|
flags: {
|
||||||
daggerheart: {
|
daggerheart: {
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,15 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateArmorEffect(event) {
|
||||||
|
const value = Number.parseInt(event.target.value);
|
||||||
|
const armorEffect = this.document.system.armorEffect;
|
||||||
|
if (Number.isNaN(value) || !armorEffect) return;
|
||||||
|
|
||||||
|
await armorEffect.system.armorChange.updateArmorMax(value);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback function used by `tagifyElement`.
|
* Callback function used by `tagifyElement`.
|
||||||
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
||||||
|
|
||||||
async advantageOnRemove(event) {
|
async advantageOnRemove(event) {
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
[`system.advantageOn.-=${event.detail.data.value}`]: null
|
[`system.advantageOn.${event.detail.data.value}`]: _del
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,14 +108,15 @@ export default class DhRollTableSheet extends foundry.applications.sheets.RollTa
|
||||||
getSystemFlagUpdate() {
|
getSystemFlagUpdate() {
|
||||||
const deleteUpdate = Object.keys(this.document._source.flags.daggerheart?.altFormula ?? {}).reduce(
|
const deleteUpdate = Object.keys(this.document._source.flags.daggerheart?.altFormula ?? {}).reduce(
|
||||||
(acc, formulaKey) => {
|
(acc, formulaKey) => {
|
||||||
if (!this.daggerheartFlag.altFormula[formulaKey]) acc.altFormula[`-=${formulaKey}`] = null;
|
if (!this.daggerheartFlag.altFormula[formulaKey]) acc.altFormula[formulaKey] = _del;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{ altFormula: {} }
|
{ altFormula: {} }
|
||||||
);
|
);
|
||||||
|
|
||||||
return { ['flags.daggerheart']: foundry.utils.mergeObject(this.daggerheartFlag.toObject(), deleteUpdate) };
|
const flagData = this.daggerheartFlag.toObject();
|
||||||
|
return { ...flagData, altFormula: { ...flagData.altFormula, ...deleteUpdate.altFormula } };
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #addFormula() {
|
static async #addFormula() {
|
||||||
|
|
@ -127,7 +128,7 @@ export default class DhRollTableSheet extends foundry.applications.sheets.RollTa
|
||||||
|
|
||||||
static async #removeFormula(_event, target) {
|
static async #removeFormula(_event, target) {
|
||||||
await this.daggerheartFlag.updateSource({
|
await this.daggerheartFlag.updateSource({
|
||||||
[`altFormula.-=${target.dataset.key}`]: null
|
[`altFormula.${target.dataset.key}`]: _del
|
||||||
});
|
});
|
||||||
this.render({ internalRefresh: true });
|
this.render({ internalRefresh: true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,19 @@
|
||||||
export default class DhSidebar extends foundry.applications.sidebar.Sidebar {
|
export default class DhSidebar extends foundry.applications.sidebar.Sidebar {
|
||||||
|
static buildTabs() {
|
||||||
|
const { settings, ...tabs } = super.TABS;
|
||||||
|
return {
|
||||||
|
...tabs,
|
||||||
|
daggerheartMenu: {
|
||||||
|
tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title',
|
||||||
|
img: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg',
|
||||||
|
gmOnly: true
|
||||||
|
},
|
||||||
|
settings
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
static TABS = {
|
static TABS = DhSidebar.buildTabs();
|
||||||
chat: {
|
|
||||||
documentName: 'ChatMessage'
|
|
||||||
},
|
|
||||||
combat: {
|
|
||||||
documentName: 'Combat'
|
|
||||||
},
|
|
||||||
scenes: {
|
|
||||||
documentName: 'Scene',
|
|
||||||
gmOnly: true
|
|
||||||
},
|
|
||||||
actors: {
|
|
||||||
documentName: 'Actor'
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
documentName: 'Item'
|
|
||||||
},
|
|
||||||
journal: {
|
|
||||||
documentName: 'JournalEntry',
|
|
||||||
tooltip: 'SIDEBAR.TabJournal'
|
|
||||||
},
|
|
||||||
tables: {
|
|
||||||
documentName: 'RollTable'
|
|
||||||
},
|
|
||||||
cards: {
|
|
||||||
documentName: 'Cards'
|
|
||||||
},
|
|
||||||
macros: {
|
|
||||||
documentName: 'Macro'
|
|
||||||
},
|
|
||||||
playlists: {
|
|
||||||
documentName: 'Playlist'
|
|
||||||
},
|
|
||||||
compendium: {
|
|
||||||
tooltip: 'SIDEBAR.TabCompendium',
|
|
||||||
icon: 'fa-solid fa-book-atlas'
|
|
||||||
},
|
|
||||||
daggerheartMenu: {
|
|
||||||
tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title',
|
|
||||||
img: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg',
|
|
||||||
gmOnly: true
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
tooltip: 'SIDEBAR.TabSettings',
|
|
||||||
icon: 'fa-solid fa-gears'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
selectRefreshable: DaggerheartMenu.#selectRefreshable,
|
selectRefreshable: DaggerheartMenu.#selectRefreshable,
|
||||||
refreshActors: DaggerheartMenu.#refreshActors
|
refreshActors: DaggerheartMenu.#refreshActors,
|
||||||
|
createFallCollisionDamage: DaggerheartMenu.#createFallCollisionDamage
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -50,6 +51,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
context.refreshables = this.refreshSelections;
|
context.refreshables = this.refreshSelections;
|
||||||
context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected);
|
context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected);
|
||||||
|
context.fallAndCollision = CONFIG.DH.GENERAL.fallAndCollisionDamage;
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
@ -71,4 +73,22 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #createFallCollisionDamage(_event, button) {
|
||||||
|
const data = CONFIG.DH.GENERAL.fallAndCollisionDamage[button.dataset.key];
|
||||||
|
const roll = new Roll(data.damageFormula);
|
||||||
|
await roll.evaluate();
|
||||||
|
|
||||||
|
/* class BaseRoll needed to get rendered by foundryRoll.hbs */
|
||||||
|
const rollJSON = roll.toJSON();
|
||||||
|
rollJSON.class = 'BaseRoll';
|
||||||
|
|
||||||
|
foundry.documents.ChatMessage.implementation.create({
|
||||||
|
title: game.i18n.localize(data.chatTitle),
|
||||||
|
author: game.user.id,
|
||||||
|
speaker: foundry.documents.ChatMessage.implementation.getSpeaker(),
|
||||||
|
rolls: [rollJSON],
|
||||||
|
sound: CONFIG.sounds.dice
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@ 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 { default as DhSceneNavigation } from './sceneNavigation.mjs';
|
||||||
export { ItemBrowser } from './itemBrowser.mjs';
|
export { ItemBrowser } from './itemBrowser.mjs';
|
||||||
|
export { default as DhProgress } from './progress.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import { abilities } from '../../config/actorConfig.mjs';
|
import { abilities } from '../../config/actorConfig.mjs';
|
||||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
||||||
|
import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs';
|
||||||
|
import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.mjs';
|
||||||
|
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
|
@ -21,6 +24,84 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
classes: ['daggerheart']
|
classes: ['daggerheart']
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static CHAT_COMMANDS = {
|
||||||
|
...super.CHAT_COMMANDS,
|
||||||
|
dr: {
|
||||||
|
rgx: /^(?:\/dr)((?:\s)[^]*)?/,
|
||||||
|
fn: (_, match) => {
|
||||||
|
const argString = match[1]?.trim();
|
||||||
|
const result = argString ? rollCommandToJSON(argString) : { result: {} };
|
||||||
|
if (!result) {
|
||||||
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.dualityParsing'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { result: rollCommand, flavor } = result;
|
||||||
|
|
||||||
|
const reaction = rollCommand.reaction;
|
||||||
|
const traitValue = rollCommand.trait?.toLowerCase();
|
||||||
|
const advantage = rollCommand.advantage
|
||||||
|
? CONFIG.DH.ACTIONS.advantageState.advantage.value
|
||||||
|
: rollCommand.disadvantage
|
||||||
|
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
|
||||||
|
: undefined;
|
||||||
|
const difficulty = rollCommand.difficulty;
|
||||||
|
const grantResources = rollCommand.grantResources;
|
||||||
|
|
||||||
|
const target = getCommandTarget({ allowNull: true });
|
||||||
|
const title =
|
||||||
|
(flavor ?? traitValue)
|
||||||
|
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||||
|
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
|
||||||
|
})
|
||||||
|
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
||||||
|
|
||||||
|
enrichedDualityRoll({
|
||||||
|
reaction,
|
||||||
|
traitValue,
|
||||||
|
target,
|
||||||
|
difficulty,
|
||||||
|
title,
|
||||||
|
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'),
|
||||||
|
actionType: null,
|
||||||
|
advantage,
|
||||||
|
grantResources
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fr: {
|
||||||
|
rgx: /^(?:\/fr)((?:\s)[^]*)?/,
|
||||||
|
fn: (_, match) => {
|
||||||
|
const argString = match[1]?.trim();
|
||||||
|
const result = argString ? rollCommandToJSON(argString) : { result: {} };
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateParsing'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { result: rollCommand, flavor } = result;
|
||||||
|
const fateTypeData = getFateTypeData(rollCommand?.type);
|
||||||
|
|
||||||
|
if (!fateTypeData)
|
||||||
|
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
||||||
|
|
||||||
|
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
||||||
|
const target = getCommandTarget({ allowNull: true });
|
||||||
|
const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll');
|
||||||
|
|
||||||
|
enrichedFateRoll({
|
||||||
|
target,
|
||||||
|
title,
|
||||||
|
label: fateTypeLabel,
|
||||||
|
fateType
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
_getEntryContextOptions() {
|
_getEntryContextOptions() {
|
||||||
return [
|
return [
|
||||||
...super._getEntryContextOptions(),
|
...super._getEntryContextOptions(),
|
||||||
|
|
@ -175,7 +256,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
action.use(event);
|
action.use(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async rerollEvent(event, message) {
|
async rerollEvent(event, messageData) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (!event.shiftKey) {
|
if (!event.shiftKey) {
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
|
@ -187,37 +268,40 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const message = game.messages.get(messageData._id);
|
||||||
const target = event.target.closest('[data-die-index]');
|
const target = event.target.closest('[data-die-index]');
|
||||||
|
|
||||||
if (target.dataset.type === 'damage') {
|
if (target.dataset.type === 'damage') {
|
||||||
game.system.api.dice.DamageRoll.reroll(target, message);
|
const { damageType, part, dice, result } = target.dataset;
|
||||||
} else {
|
const damagePart = message.system.damage[damageType].parts[part];
|
||||||
let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0];
|
const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(damagePart, dice, result);
|
||||||
const rollClass =
|
const damageParts = message.system.damage[damageType].parts.map((damagePart, index) => {
|
||||||
game.system.api.dice[
|
if (index !== Number(part)) return damagePart;
|
||||||
message.type === 'dualityRoll'
|
return {
|
||||||
? 'DualityRoll'
|
...damagePart,
|
||||||
: target.dataset.type === 'damage'
|
total: parsedRoll.total,
|
||||||
? 'DHRoll'
|
dice: rerolledDice
|
||||||
: 'D20Roll'
|
};
|
||||||
];
|
|
||||||
|
|
||||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
|
||||||
|
|
||||||
const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message);
|
|
||||||
|
|
||||||
await game.messages.get(message._id).update({
|
|
||||||
'system.roll': newRoll,
|
|
||||||
'rolls': [parsedRoll]
|
|
||||||
});
|
});
|
||||||
|
const updateMessage = game.messages.get(message._id);
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
await updateMessage.update({
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
[`system.damage.${damageType}`]: {
|
||||||
action: socketEvent.Refresh,
|
total: parsedRoll.total,
|
||||||
data: {
|
parts: damageParts
|
||||||
refreshType: RefreshType.TagTeamRoll
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
const rerollDice = message.system.roll.dice[target.dataset.dieIndex];
|
||||||
|
await rerollDice.reroll(`/r1=${rerollDice.total}`, {
|
||||||
|
liveRoll: {
|
||||||
|
roll: message.system.roll,
|
||||||
|
actor: message.system.actionActor,
|
||||||
|
isReaction: message.system.roll.options.actionType === 'reaction'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await message.update({
|
||||||
|
rolls: [message.system.roll.toJSON()]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs';
|
import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs';
|
||||||
|
import { expireActiveEffects } from '../../helpers/utils.mjs';
|
||||||
|
import { clearPreviousSpotlight } from '../../macros/spotlightCombatant.mjs';
|
||||||
|
|
||||||
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
|
|
@ -149,13 +151,13 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
}
|
}
|
||||||
|
|
||||||
async setCombatantSpotlight(combatantId) {
|
async setCombatantSpotlight(combatantId) {
|
||||||
|
const combatant = this.viewed.combatants.get(combatantId);
|
||||||
const update = {
|
const update = {
|
||||||
system: {
|
system: {
|
||||||
'spotlight.requesting': false,
|
'spotlight.requesting': false,
|
||||||
'spotlight.requestOrderIndex': 0
|
'spotlight.requestOrderIndex': 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const combatant = this.viewed.combatants.get(combatantId);
|
|
||||||
|
|
||||||
const toggleTurn = this.viewed.combatants.contents
|
const toggleTurn = this.viewed.combatants.contents
|
||||||
.sort(this.viewed._sortCombatants)
|
.sort(this.viewed._sortCombatants)
|
||||||
|
|
@ -177,6 +179,8 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
if (autoPoints) {
|
if (autoPoints) {
|
||||||
update.system.actionTokens = Math.max(combatant.system.actionTokens - 1, 0);
|
update.system.actionTokens = Math.max(combatant.system.actionTokens - 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (combatant.actor) expireActiveEffects(combatant.actor, [CONFIG.DH.GENERAL.activeEffectDurations.act.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.viewed.update({
|
await this.viewed.update({
|
||||||
|
|
@ -184,6 +188,14 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
round: this.viewed.round + 1
|
round: this.viewed.round + 1
|
||||||
});
|
});
|
||||||
await combatant.update(update);
|
await combatant.update(update);
|
||||||
|
if (combatant.token) clearPreviousSpotlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearTurn() {
|
||||||
|
await this.viewed.update({
|
||||||
|
turn: null,
|
||||||
|
round: this.viewed.round + 1
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async requestSpotlight(_, target) {
|
static async requestSpotlight(_, target) {
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,6 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.editingCountdowns.has(countdownId)) this.editingCountdowns.delete(countdownId);
|
if (this.editingCountdowns.has(countdownId)) this.editingCountdowns.delete(countdownId);
|
||||||
this.updateSetting({ [`countdowns.-=${countdownId}`]: null });
|
this.updateSetting({ [`countdowns.${countdownId}`]: _del });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
get element() {
|
|
||||||
return document.body.querySelector('.daggerheart.dh-style.countdowns');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _renderFrame(options) {
|
async _renderFrame(options) {
|
||||||
const frame = await super._renderFrame(options);
|
const frame = await super._renderFrame(options);
|
||||||
|
|
@ -68,6 +64,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
|
|
||||||
const header = frame.querySelector('.window-header');
|
const header = frame.querySelector('.window-header');
|
||||||
header.querySelector('button[data-action="close"]').remove();
|
header.querySelector('button[data-action="close"]').remove();
|
||||||
|
header.querySelector('button[data-action="toggleControls"]').remove();
|
||||||
|
|
||||||
if (game.user.isGM) {
|
if (game.user.isGM) {
|
||||||
const editTooltip = game.i18n.localize('DAGGERHEART.APPLICATIONS.CountdownEdit.editTitle');
|
const editTooltip = game.i18n.localize('DAGGERHEART.APPLICATIONS.CountdownEdit.editTitle');
|
||||||
|
|
@ -278,10 +275,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
};
|
};
|
||||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns,
|
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||||
DhCountdowns.gmSetSetting.bind(settings),
|
refreshType: RefreshType.Countdown
|
||||||
settings, null, {
|
|
||||||
refreshType: RefreshType.Countdown
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getIconVisibleActiveEffects } from '../../helpers/utils.mjs';
|
||||||
import { RefreshType } from '../../systemRegistration/socket.mjs';
|
import { RefreshType } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
@ -48,11 +49,9 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
for (const element of this.element?.querySelectorAll('.effect-container a') ?? []) {
|
||||||
if (this.element) {
|
element.addEventListener('click', e => this.#onClickEffect(e));
|
||||||
this.element.querySelectorAll('.effect-container a').forEach(element => {
|
element.addEventListener('contextmenu', e => this.#onClickEffect(e, -1));
|
||||||
element.addEventListener('contextmenu', this.removeEffect.bind(this));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +71,7 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
|
||||||
? game.user.character
|
? game.user.character
|
||||||
: null
|
: null
|
||||||
: canvas.tokens.controlled[0].actor;
|
: canvas.tokens.controlled[0].actor;
|
||||||
return actor?.getActiveEffects() ?? [];
|
return getIconVisibleActiveEffects(actor?.getActiveEffects() ?? []);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleHidden(token, focused) {
|
toggleHidden(token, focused) {
|
||||||
|
|
@ -86,11 +85,22 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeEffect(event) {
|
async #onClickEffect(event, delta = 1) {
|
||||||
const element = event.target.closest('.effect-container');
|
const element = event.target.closest('.effect-container');
|
||||||
const effects = DhEffectsDisplay.getTokenEffects();
|
const effects = DhEffectsDisplay.getTokenEffects();
|
||||||
const effect = effects.find(x => x.id === element.dataset.effectId);
|
const effect = effects.find(x => x.id === element.dataset.effectId);
|
||||||
await effect.delete();
|
if (!effect || (delta >= 0 && !effect.system.stacking)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxValue = effect.system.stacking?.max ?? Infinity;
|
||||||
|
const newValue = Math.clamp((effect.system.stacking?.value ?? 1) + delta, 0, maxValue);
|
||||||
|
if (newValue > 0) {
|
||||||
|
await effect.update({ 'system.stacking.value': newValue });
|
||||||
|
} else {
|
||||||
|
await effect.delete();
|
||||||
|
}
|
||||||
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupHooks() {
|
setupHooks() {
|
||||||
|
|
|
||||||
27
module/applications/ui/progress.mjs
Normal file
27
module/applications/ui/progress.mjs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
export default class DhProgress {
|
||||||
|
#notification;
|
||||||
|
|
||||||
|
constructor({ max, label = '' }) {
|
||||||
|
this.max = max;
|
||||||
|
this.label = label;
|
||||||
|
this.#notification = ui.notifications.info(this.label, { progress: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMax(newMax) {
|
||||||
|
this.max = newMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance({ by = 1, label = this.label } = {}) {
|
||||||
|
if (this.value === this.max) return;
|
||||||
|
this.value = (this.value ?? 0) + Math.abs(by);
|
||||||
|
this.#notification.update({ message: label, pct: this.value / this.max });
|
||||||
|
}
|
||||||
|
|
||||||
|
close({ label = '' } = {}) {
|
||||||
|
this.#notification.update({ message: label, pct: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
static createMigrationProgress(max = 0) {
|
||||||
|
return new DhProgress({ max, label: game.i18n.localize('DAGGERHEART.UI.Progress.migrationLabel') });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,7 +31,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
|
||||||
const environments = daggerheartInfo.sceneEnvironments.filter(
|
const environments = daggerheartInfo.sceneEnvironments.filter(
|
||||||
x => x && x.testUserPermission(game.user, 'LIMITED')
|
x => x && x.testUserPermission(game.user, 'LIMITED')
|
||||||
);
|
);
|
||||||
const hasEnvironments = environments.length > 0 && x.isView;
|
const hasEnvironments = environments.length > 0 && x.active;
|
||||||
return {
|
return {
|
||||||
...x,
|
...x,
|
||||||
hasEnvironments,
|
hasEnvironments,
|
||||||
|
|
@ -39,9 +39,10 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
|
||||||
environments: environments
|
environments: environments
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
context.scenes.active = extendScenes(context.scenes.active);
|
context.scenes.active = extendScenes(context.scenes.active);
|
||||||
context.scenes.inactive = extendScenes(context.scenes.inactive);
|
context.scenes.inactive = extendScenes(context.scenes.inactive);
|
||||||
|
context.scenes.viewed = context.scenes.viewed ? extendScenes([context.scenes.viewed])[0] : null;
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,97 +1,4 @@
|
||||||
/**
|
|
||||||
* @typedef ContextMenuEntry
|
|
||||||
* @property {string} name The context menu label. Can be localized.
|
|
||||||
* @property {string} [icon] A string containing an HTML icon element for the menu item.
|
|
||||||
* @property {string} [classes] Additional CSS classes to apply to this menu item.
|
|
||||||
* @property {string} [group] An identifier for a group this entry belongs to.
|
|
||||||
* @property {ContextMenuJQueryCallback} callback The function to call when the menu item is clicked.
|
|
||||||
* @property {ContextMenuCondition|boolean} [condition] A function to call or boolean value to determine if this entry
|
|
||||||
* appears in the menu.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @callback ContextMenuCondition
|
|
||||||
* @param {jQuery|HTMLElement} html The element of the context menu entry.
|
|
||||||
* @returns {boolean} Whether the entry should be rendered in the context menu.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @callback ContextMenuCallback
|
|
||||||
* @param {HTMLElement} target The element that the context menu has been triggered for.
|
|
||||||
* @returns {unknown}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @callback ContextMenuJQueryCallback
|
|
||||||
* @param {HTMLElement|jQuery} target The element that the context menu has been triggered for. Will
|
|
||||||
* either be a jQuery object or an HTMLElement instance, depending
|
|
||||||
* on how the ContextMenu was configured.
|
|
||||||
* @returns {unknown}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef ContextMenuOptions
|
|
||||||
* @property {string} [eventName="contextmenu"] Optionally override the triggering event which can spawn the menu. If
|
|
||||||
* the menu is using fixed positioning, this event must be a MouseEvent.
|
|
||||||
* @property {ContextMenuCallback} [onOpen] A function to call when the context menu is opened.
|
|
||||||
* @property {ContextMenuCallback} [onClose] A function to call when the context menu is closed.
|
|
||||||
* @property {boolean} [fixed=false] If true, the context menu is given a fixed position rather than being
|
|
||||||
* injected into the target.
|
|
||||||
* @property {boolean} [jQuery=true] If true, callbacks will be passed jQuery objects instead of HTMLElement
|
|
||||||
* instances.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef ContextMenuRenderOptions
|
|
||||||
* @property {Event} [event] The event that triggered the context menu opening.
|
|
||||||
* @property {boolean} [animate=true] Animate the context menu opening.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A subclass of ContextMenu.
|
|
||||||
* @extends {foundry.applications.ux.ContextMenu}
|
|
||||||
*/
|
|
||||||
export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
||||||
/**
|
|
||||||
* @param {HTMLElement|jQuery} container - The HTML element that contains the context menu targets.
|
|
||||||
* @param {string} selector - A CSS selector which activates the context menu.
|
|
||||||
* @param {ContextMenuEntry[]} menuItems - An Array of entries to display in the menu
|
|
||||||
* @param {ContextMenuOptions} [options] - Additional options to configure the context menu.
|
|
||||||
*/
|
|
||||||
constructor(container, selector, menuItems, options) {
|
|
||||||
super(container, selector, menuItems, options);
|
|
||||||
|
|
||||||
/** @deprecated since v13 until v15 */
|
|
||||||
this.#jQuery = options.jQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to pass jQuery objects or HTMLElement instances to callback.
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
#jQuery;
|
|
||||||
|
|
||||||
/**@inheritdoc */
|
|
||||||
activateListeners(menu) {
|
|
||||||
menu.addEventListener('click', this.#onClickItem.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle click events on context menu items.
|
|
||||||
* @param {PointerEvent} event The click event
|
|
||||||
*/
|
|
||||||
#onClickItem(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
const element = event.target.closest('.context-item');
|
|
||||||
if (!element) return;
|
|
||||||
const item = this.menuItems.find(i => i.element === element);
|
|
||||||
item?.callback(this.#jQuery ? $(this.target) : this.target, event);
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export { default as DhMeasuredTemplate } from './measuredTemplate.mjs';
|
export { default as DhMeasuredTemplate } from './measuredTemplate.mjs';
|
||||||
export { default as DhRuler } from './ruler.mjs';
|
export { default as DhRuler } from './ruler.mjs';
|
||||||
export { default as DhTemplateLayer } from './templateLayer.mjs';
|
export { default as DhRegion } from './region.mjs';
|
||||||
|
export { default as DhRegionLayer } from './regionLayer.mjs';
|
||||||
export { default as DhTokenPlaceable } from './token.mjs';
|
export { default as DhTokenPlaceable } from './token.mjs';
|
||||||
export { default as DhTokenRuler } from './tokenRuler.mjs';
|
export { default as DhTokenRuler } from './tokenRuler.mjs';
|
||||||
|
|
|
||||||
12
module/canvas/placeables/region.mjs
Normal file
12
module/canvas/placeables/region.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import DhMeasuredTemplate from './measuredTemplate.mjs';
|
||||||
|
|
||||||
|
export default class DhRegion extends foundry.canvas.placeables.Region {
|
||||||
|
/**@inheritdoc */
|
||||||
|
_formatMeasuredDistance(distance) {
|
||||||
|
const range = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
|
||||||
|
if (!range.enabled) return super._formatMeasuredDistance(distance);
|
||||||
|
|
||||||
|
const { distance: resultDistance, units } = DhMeasuredTemplate.getRangeLabels(distance, range);
|
||||||
|
return `${resultDistance} ${units}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
98
module/canvas/placeables/regionLayer.mjs
Normal file
98
module/canvas/placeables/regionLayer.mjs
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
||||||
|
static prepareSceneControls() {
|
||||||
|
const sc = foundry.applications.ui.SceneControls;
|
||||||
|
const { tools, ...rest } = super.prepareSceneControls();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
tools: {
|
||||||
|
select: tools.select,
|
||||||
|
templateMode: tools.templateMode,
|
||||||
|
rectangle: tools.rectangle,
|
||||||
|
circle: tools.circle,
|
||||||
|
ellipse: tools.ellipse,
|
||||||
|
cone: tools.cone,
|
||||||
|
inFront: {
|
||||||
|
name: 'inFront',
|
||||||
|
order: 7,
|
||||||
|
title: 'CONTROLS.inFront',
|
||||||
|
icon: 'fa-solid fa-eye',
|
||||||
|
toolclip: {
|
||||||
|
src: 'toolclips/tools/measure-cone.webm',
|
||||||
|
heading: 'CONTROLS.inFront',
|
||||||
|
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ring: { ...tools.ring, order: 8 },
|
||||||
|
line: { ...tools.line, order: 9 },
|
||||||
|
emanation: { ...tools.emanation, order: 10 },
|
||||||
|
polygon: { ...tools.polygon, order: 11 },
|
||||||
|
hole: { ...tools.hole, order: 12 },
|
||||||
|
snap: { ...tools.snap, order: 13 },
|
||||||
|
clear: { ...tools.clear, order: 14 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_isCreationToolActive() {
|
||||||
|
return this.active && (game.activeTool === 'inFront' || game.activeTool in foundry.data.BaseShapeData.TYPES);
|
||||||
|
}
|
||||||
|
|
||||||
|
_createDragShapeData(event) {
|
||||||
|
const hole = ui.controls.controls[this.options.name].tools.hole?.active ?? false;
|
||||||
|
if (game.activeTool === 'inFront') return { type: 'cone', x: 0, y: 0, radius: 0, angle: 180, hole };
|
||||||
|
|
||||||
|
const shape = super._createDragShapeData(event);
|
||||||
|
const token =
|
||||||
|
shape?.type === 'emanation' && shape.base?.type === 'token'
|
||||||
|
? this.#findTokenInBounds(event.interactionData.origin)
|
||||||
|
: null;
|
||||||
|
if (token) {
|
||||||
|
shape.base.width = token.width;
|
||||||
|
shape.base.height = token.height;
|
||||||
|
event.interactionData.origin = token.getCenterPoint();
|
||||||
|
}
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
async placeRegion(data, options = {}) {
|
||||||
|
const preConfirm = ({ _event, document, _create, _options }) => {
|
||||||
|
const shape = document.shapes[0];
|
||||||
|
const isEmanation = shape.type === 'emanation';
|
||||||
|
if (isEmanation) {
|
||||||
|
const token = this.#findTokenInBounds(shape.base.origin);
|
||||||
|
if (!token) return options.preConfirm?.() ?? true;
|
||||||
|
const shapeData = shape.toObject();
|
||||||
|
document.updateSource({
|
||||||
|
shapes: [
|
||||||
|
{
|
||||||
|
...shapeData,
|
||||||
|
base: {
|
||||||
|
...shapeData.base,
|
||||||
|
height: token.height,
|
||||||
|
width: token.width,
|
||||||
|
x: token.x,
|
||||||
|
y: token.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return options?.preConfirm?.() ?? true;
|
||||||
|
};
|
||||||
|
|
||||||
|
super.placeRegion(data, { ...options, preConfirm });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Searches for token at origin point, returning null if there are no tokens or multiple overlapping tokens */
|
||||||
|
#findTokenInBounds(origin) {
|
||||||
|
const { x, y } = origin;
|
||||||
|
const gridSize = canvas.grid.size;
|
||||||
|
const inBounds = canvas.scene.tokens.filter(t => {
|
||||||
|
return x.between(t.x, t.x + t.width * gridSize) && y.between(t.y, t.y + t.height * gridSize);
|
||||||
|
});
|
||||||
|
return inBounds.length === 1 ? inBounds[0] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
export default class DhTemplateLayer extends foundry.canvas.layers.TemplateLayer {
|
|
||||||
static prepareSceneControls() {
|
|
||||||
const sc = foundry.applications.ui.SceneControls;
|
|
||||||
return {
|
|
||||||
name: 'templates',
|
|
||||||
order: 2,
|
|
||||||
title: 'CONTROLS.GroupMeasure',
|
|
||||||
icon: 'fa-solid fa-ruler-combined',
|
|
||||||
visible: game.user.can('TEMPLATE_CREATE'),
|
|
||||||
onChange: (event, active) => {
|
|
||||||
if (active) canvas.templates.activate();
|
|
||||||
},
|
|
||||||
onToolChange: () => canvas.templates.setAllRenderFlags({ refreshState: true }),
|
|
||||||
tools: {
|
|
||||||
circle: {
|
|
||||||
name: 'circle',
|
|
||||||
order: 1,
|
|
||||||
title: 'CONTROLS.MeasureCircle',
|
|
||||||
icon: 'fa-regular fa-circle',
|
|
||||||
toolclip: {
|
|
||||||
src: 'toolclips/tools/measure-circle.webm',
|
|
||||||
heading: 'CONTROLS.MeasureCircle',
|
|
||||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete'])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cone: {
|
|
||||||
name: 'cone',
|
|
||||||
order: 2,
|
|
||||||
title: 'CONTROLS.MeasureCone',
|
|
||||||
icon: 'fa-solid fa-angle-left',
|
|
||||||
toolclip: {
|
|
||||||
src: 'toolclips/tools/measure-cone.webm',
|
|
||||||
heading: 'CONTROLS.MeasureCone',
|
|
||||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
inFront: {
|
|
||||||
name: 'inFront',
|
|
||||||
order: 3,
|
|
||||||
title: 'CONTROLS.inFront',
|
|
||||||
icon: 'fa-solid fa-eye',
|
|
||||||
toolclip: {
|
|
||||||
src: 'toolclips/tools/measure-cone.webm',
|
|
||||||
heading: 'CONTROLS.inFront',
|
|
||||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rect: {
|
|
||||||
name: 'rect',
|
|
||||||
order: 4,
|
|
||||||
title: 'CONTROLS.MeasureRect',
|
|
||||||
icon: 'fa-regular fa-square',
|
|
||||||
toolclip: {
|
|
||||||
src: 'toolclips/tools/measure-rect.webm',
|
|
||||||
heading: 'CONTROLS.MeasureRect',
|
|
||||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ray: {
|
|
||||||
name: 'ray',
|
|
||||||
order: 5,
|
|
||||||
title: 'CONTROLS.MeasureRay',
|
|
||||||
icon: 'fa-solid fa-up-down',
|
|
||||||
toolclip: {
|
|
||||||
src: 'toolclips/tools/measure-ray.webm',
|
|
||||||
heading: 'CONTROLS.MeasureRay',
|
|
||||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clear: {
|
|
||||||
name: 'clear',
|
|
||||||
order: 6,
|
|
||||||
title: 'CONTROLS.MeasureClear',
|
|
||||||
icon: 'fa-solid fa-trash',
|
|
||||||
visible: game.user.isGM,
|
|
||||||
onChange: () => canvas.templates.deleteAll(),
|
|
||||||
button: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
activeTool: 'circle'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_onDragLeftStart(event) {
|
|
||||||
const interaction = event.interactionData;
|
|
||||||
|
|
||||||
// Snap the origin to the grid
|
|
||||||
if (!event.shiftKey) interaction.origin = this.getSnappedPoint(interaction.origin);
|
|
||||||
|
|
||||||
// Create a pending MeasuredTemplateDocument
|
|
||||||
const tool = game.activeTool === 'inFront' ? 'cone' : game.activeTool;
|
|
||||||
const previewData = {
|
|
||||||
user: game.user.id,
|
|
||||||
t: tool,
|
|
||||||
x: interaction.origin.x,
|
|
||||||
y: interaction.origin.y,
|
|
||||||
sort: Math.max(this.getMaxSort() + 1, 0),
|
|
||||||
distance: 1,
|
|
||||||
direction: 0,
|
|
||||||
fillColor: game.user.color || '#FF0000',
|
|
||||||
hidden: event.altKey
|
|
||||||
};
|
|
||||||
const defaults = CONFIG.MeasuredTemplate.defaults;
|
|
||||||
if (game.activeTool === 'cone') previewData.angle = defaults.angle;
|
|
||||||
else if (game.activeTool === 'inFront') previewData.angle = 180;
|
|
||||||
else if (game.activeTool === 'ray') previewData.width = defaults.width * canvas.dimensions.distance;
|
|
||||||
const cls = foundry.utils.getDocumentClass('MeasuredTemplate');
|
|
||||||
const doc = new cls(previewData, { parent: canvas.scene });
|
|
||||||
|
|
||||||
// Create a preview MeasuredTemplate object
|
|
||||||
const template = new this.constructor.placeableClass(doc);
|
|
||||||
doc._object = template;
|
|
||||||
interaction.preview = this.preview.addChild(template);
|
|
||||||
template.draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getIconVisibleActiveEffects } from '../../helpers/utils.mjs';
|
||||||
import DhMeasuredTemplate from './measuredTemplate.mjs';
|
import DhMeasuredTemplate from './measuredTemplate.mjs';
|
||||||
|
|
||||||
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
|
|
@ -9,6 +10,36 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
this.previewHelp ||= this.addChild(this.#drawPreviewHelp());
|
this.previewHelp ||= this.addChild(this.#drawPreviewHelp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
_refreshTurnMarker() {
|
||||||
|
// Should a Turn Marker be active?
|
||||||
|
const { turnMarker } = this.document;
|
||||||
|
const markersEnabled =
|
||||||
|
CONFIG.Combat.settings.turnMarker.enabled && turnMarker.mode !== CONST.TOKEN_TURN_MARKER_MODES.DISABLED;
|
||||||
|
const spotlighted = game.settings
|
||||||
|
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker)
|
||||||
|
.spotlightedTokens.has(this.document.uuid);
|
||||||
|
|
||||||
|
const turnIsSet = typeof game.combat?.turn === 'number';
|
||||||
|
const isTurn = game.combat?.combatant?.tokenId === this.id;
|
||||||
|
const markerActive = markersEnabled && turnIsSet ? isTurn : spotlighted;
|
||||||
|
|
||||||
|
// Activate a Turn Marker
|
||||||
|
if (markerActive) {
|
||||||
|
if (!this.turnMarker)
|
||||||
|
this.turnMarker = this.addChildAt(new foundry.canvas.placeables.tokens.TokenTurnMarker(this), 0);
|
||||||
|
canvas.tokens.turnMarkers.add(this);
|
||||||
|
this.turnMarker.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a Turn Marker
|
||||||
|
else if (this.turnMarker) {
|
||||||
|
canvas.tokens.turnMarkers.delete(this);
|
||||||
|
this.turnMarker.destroy();
|
||||||
|
this.turnMarker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _drawEffects() {
|
async _drawEffects() {
|
||||||
this.effects.renderable = false;
|
this.effects.renderable = false;
|
||||||
|
|
@ -20,7 +51,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
this.effects.overlay = null;
|
this.effects.overlay = null;
|
||||||
|
|
||||||
// Categorize effects
|
// Categorize effects
|
||||||
const activeEffects = this.actor?.getActiveEffects() ?? [];
|
const activeEffects = getIconVisibleActiveEffects(Array.from(this.actor?.allApplicableEffects() ?? []));
|
||||||
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay'));
|
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay'));
|
||||||
|
|
||||||
// Draw effects
|
// Draw effects
|
||||||
|
|
@ -29,8 +60,8 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
if (!effect.img) continue;
|
if (!effect.img) continue;
|
||||||
const promise =
|
const promise =
|
||||||
effect === overlayEffect
|
effect === overlayEffect
|
||||||
? this._drawOverlay(effect.img, effect.tint)
|
? this._drawOverlay(effect.img, effect.tint, effect)
|
||||||
: this._drawEffect(effect.img, effect.tint);
|
: this._drawEffect(effect.img, effect.tint, effect);
|
||||||
promises.push(
|
promises.push(
|
||||||
promise.then(e => {
|
promise.then(e => {
|
||||||
if (e) e.zIndex = i;
|
if (e) e.zIndex = i;
|
||||||
|
|
@ -44,6 +75,39 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
this.renderFlags.set({ refreshEffects: true });
|
this.renderFlags.set({ refreshEffects: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
async _drawEffect(src, tint, effect) {
|
||||||
|
if (!src) return;
|
||||||
|
const tex = await foundry.canvas.loadTexture(src, { fallback: 'icons/svg/hazard.svg' });
|
||||||
|
const icon = new PIXI.Sprite(tex);
|
||||||
|
icon.tint = tint ?? 0xffffff;
|
||||||
|
|
||||||
|
if (effect.system.stacking?.value > 1) {
|
||||||
|
const stackOverlay = new PIXI.Text(effect.system.stacking.value, {
|
||||||
|
fill: '#f3c267',
|
||||||
|
stroke: '#000000',
|
||||||
|
fontSize: 96,
|
||||||
|
strokeThickness: 4
|
||||||
|
});
|
||||||
|
const nrDigits = Math.floor(Math.log10(effect.system.stacking.value)) + 1;
|
||||||
|
stackOverlay.y = -8;
|
||||||
|
/* This does not account for 1:s being much less wide than other digits. I don't think it's desired however as it makes it look jumpy */
|
||||||
|
stackOverlay.x = icon.width - 8 - nrDigits * 56;
|
||||||
|
stackOverlay.anchor.set(0, 0);
|
||||||
|
|
||||||
|
icon.addChild(stackOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.effects.addChild(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _drawOverlay(src, tint, effect) {
|
||||||
|
const icon = await this._drawEffect(src, tint, effect);
|
||||||
|
if (icon) icon.alpha = 0.8;
|
||||||
|
this.effects.overlay = icon ?? null;
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the distance from this token to another token object.
|
* Returns the distance from this token to another token object.
|
||||||
* This value is corrected to handle alternate token sizes and other grid types
|
* This value is corrected to handle alternate token sizes and other grid types
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,12 @@ export const range = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* circle|cone|rect|ray used to be CONST.MEASURED_TEMPLATE_TYPES. Hardcoded for now */
|
||||||
export const templateTypes = {
|
export const templateTypes = {
|
||||||
...CONST.MEASURED_TEMPLATE_TYPES,
|
CIRCLE: 'circle',
|
||||||
|
CONE: 'cone',
|
||||||
|
RECTANGLE: 'rectangle',
|
||||||
|
LINE: 'line',
|
||||||
EMANATION: 'emanation',
|
EMANATION: 'emanation',
|
||||||
INFRONT: 'inFront'
|
INFRONT: 'inFront'
|
||||||
};
|
};
|
||||||
|
|
@ -241,8 +245,8 @@ export const defaultRestOptions = {
|
||||||
type: 'friendly'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hitPoints: {
|
||||||
applyTo: healingTypes.hitPoints.id,
|
applyTo: healingTypes.hitPoints.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -251,7 +255,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -275,8 +279,8 @@ export const defaultRestOptions = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
stress: {
|
||||||
applyTo: healingTypes.stress.id,
|
applyTo: healingTypes.stress.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -285,7 +289,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -310,8 +314,8 @@ export const defaultRestOptions = {
|
||||||
type: 'friendly'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
armor: {
|
||||||
applyTo: healingTypes.armor.id,
|
applyTo: healingTypes.armor.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -320,7 +324,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -344,8 +348,8 @@ export const defaultRestOptions = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hope: {
|
||||||
applyTo: healingTypes.hope.id,
|
applyTo: healingTypes.hope.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -354,7 +358,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
prepareWithFriends: {
|
prepareWithFriends: {
|
||||||
|
|
@ -368,8 +372,8 @@ export const defaultRestOptions = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hope: {
|
||||||
applyTo: healingTypes.hope.id,
|
applyTo: healingTypes.hope.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -378,7 +382,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -405,8 +409,8 @@ export const defaultRestOptions = {
|
||||||
type: 'friendly'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hitPoints: {
|
||||||
applyTo: healingTypes.hitPoints.id,
|
applyTo: healingTypes.hitPoints.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -415,7 +419,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -439,8 +443,8 @@ export const defaultRestOptions = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
stress: {
|
||||||
applyTo: healingTypes.stress.id,
|
applyTo: healingTypes.stress.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -449,7 +453,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -474,8 +478,8 @@ export const defaultRestOptions = {
|
||||||
type: 'friendly'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
armor: {
|
||||||
applyTo: healingTypes.armor.id,
|
applyTo: healingTypes.armor.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -484,7 +488,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -508,8 +512,8 @@ export const defaultRestOptions = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hope: {
|
||||||
applyTo: healingTypes.hope.id,
|
applyTo: healingTypes.hope.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -518,7 +522,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
prepareWithFriends: {
|
prepareWithFriends: {
|
||||||
|
|
@ -532,8 +536,8 @@ export const defaultRestOptions = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hope: {
|
||||||
applyTo: healingTypes.hope.id,
|
applyTo: healingTypes.hope.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -542,7 +546,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -954,3 +958,129 @@ export const sceneRangeMeasurementSetting = {
|
||||||
label: 'Custom'
|
label: 'Custom'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const tagTeamRollTypes = {
|
||||||
|
trait: {
|
||||||
|
id: 'trait',
|
||||||
|
label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.trait'
|
||||||
|
},
|
||||||
|
ability: {
|
||||||
|
id: 'ability',
|
||||||
|
label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.ability'
|
||||||
|
},
|
||||||
|
damageAbility: {
|
||||||
|
id: 'damageAbility',
|
||||||
|
label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.damageAbility'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const baseActiveEffectModes = {
|
||||||
|
custom: {
|
||||||
|
id: 'custom',
|
||||||
|
priority: 0,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.custom'
|
||||||
|
},
|
||||||
|
multiply: {
|
||||||
|
id: 'multiply',
|
||||||
|
priority: 10,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.multiply'
|
||||||
|
},
|
||||||
|
add: {
|
||||||
|
id: 'add',
|
||||||
|
priority: 20,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.add'
|
||||||
|
},
|
||||||
|
subtract: {
|
||||||
|
id: 'subtract',
|
||||||
|
priority: 20,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.subtract'
|
||||||
|
},
|
||||||
|
downgrade: {
|
||||||
|
id: 'downgrade',
|
||||||
|
priority: 30,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.downgrade'
|
||||||
|
},
|
||||||
|
upgrade: {
|
||||||
|
id: 'upgrade',
|
||||||
|
priority: 40,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.upgrade'
|
||||||
|
},
|
||||||
|
override: {
|
||||||
|
id: 'override',
|
||||||
|
priority: 50,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.override'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const activeEffectModes = {
|
||||||
|
armor: {
|
||||||
|
id: 'armor',
|
||||||
|
priority: 20,
|
||||||
|
label: 'TYPES.ActiveEffect.armor'
|
||||||
|
},
|
||||||
|
...baseActiveEffectModes
|
||||||
|
};
|
||||||
|
|
||||||
|
export const activeEffectArmorInteraction = {
|
||||||
|
none: { id: 'none', label: 'DAGGERHEART.CONFIG.ArmorInteraction.none.label' },
|
||||||
|
active: { id: 'active', label: 'DAGGERHEART.CONFIG.ArmorInteraction.active.label' },
|
||||||
|
inactive: { id: 'inactive', label: 'DAGGERHEART.CONFIG.ArmorInteraction.inactive.label' }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const activeEffectDurations = {
|
||||||
|
temporary: {
|
||||||
|
id: 'temporary',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.temporary'
|
||||||
|
},
|
||||||
|
act: {
|
||||||
|
id: 'act',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.act'
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
id: 'scene',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.scene'
|
||||||
|
},
|
||||||
|
shortRest: {
|
||||||
|
id: 'shortRest',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.shortRest'
|
||||||
|
},
|
||||||
|
longRest: {
|
||||||
|
id: 'longRest',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.longRest'
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
id: 'session',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.session'
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
id: 'custom',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.custom'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fallAndCollisionDamage = {
|
||||||
|
veryClose: {
|
||||||
|
id: 'veryClose',
|
||||||
|
label: 'DAGGERHEART.CONFIG.fallAndCollision.veryClose.label',
|
||||||
|
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.veryClose.chatTitle',
|
||||||
|
damageFormula: '1d10 + 3'
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
id: 'veryClose',
|
||||||
|
label: 'DAGGERHEART.CONFIG.fallAndCollision.close.label',
|
||||||
|
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.close.chatTitle',
|
||||||
|
damageFormula: '1d20 + 5'
|
||||||
|
},
|
||||||
|
far: {
|
||||||
|
id: 'veryClose',
|
||||||
|
label: 'DAGGERHEART.CONFIG.fallAndCollision.far.label',
|
||||||
|
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.far.chatTitle',
|
||||||
|
damageFormula: '1d100 + 15'
|
||||||
|
},
|
||||||
|
collision: {
|
||||||
|
id: 'veryClose',
|
||||||
|
label: 'DAGGERHEART.CONFIG.fallAndCollision.collision.label',
|
||||||
|
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.collision.chatTitle',
|
||||||
|
damageFormula: '1d20 + 5'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export const hooksConfig = {
|
export const hooksConfig = {
|
||||||
effectDisplayToggle: 'DHEffectDisplayToggle',
|
effectDisplayToggle: 'DHEffectDisplayToggle',
|
||||||
lockedTooltipDismissed: 'DHLockedTooltipDismissed'
|
lockedTooltipDismissed: 'DHLockedTooltipDismissed',
|
||||||
|
tagTeamStart: 'DHTagTeamRollStart'
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ export const armorFeatures = {
|
||||||
type: 'hostile'
|
type: 'hostile'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
stress: {
|
||||||
applyTo: 'stress',
|
applyTo: 'stress',
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -24,7 +24,7 @@ export const armorFeatures = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -489,15 +489,18 @@ export const weaponFeatures = {
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.effects.barrier.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.effects.barrier.description',
|
||||||
img: 'icons/skills/melee/shield-block-bash-blue.webp',
|
img: 'icons/skills/melee/shield-block-bash-blue.webp',
|
||||||
changes: [
|
changes: [
|
||||||
{
|
|
||||||
key: 'system.armorScore',
|
|
||||||
mode: 2,
|
|
||||||
value: 'ITEM.@system.tier + 1'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
mode: 2,
|
||||||
value: '-1'
|
value: '-1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Armor',
|
||||||
|
type: 'armor',
|
||||||
|
typeData: {
|
||||||
|
type: 'armor',
|
||||||
|
max: 'ITEM.@system.tier + 1'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -732,8 +735,8 @@ export const weaponFeatures = {
|
||||||
type: 'hostile'
|
type: 'hostile'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
stress: {
|
||||||
applyTo: 'stress',
|
applyTo: 'stress',
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -742,7 +745,7 @@ export const weaponFeatures = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -789,11 +792,6 @@ export const weaponFeatures = {
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description',
|
||||||
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
|
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
|
||||||
changes: [
|
changes: [
|
||||||
{
|
|
||||||
key: 'system.armorScore',
|
|
||||||
mode: 2,
|
|
||||||
value: '1'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||||
mode: 2,
|
mode: 2,
|
||||||
|
|
@ -808,6 +806,22 @@ export const weaponFeatures = {
|
||||||
type: 'withinRange'
|
type: 'withinRange'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.name',
|
||||||
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description',
|
||||||
|
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: 'Armor',
|
||||||
|
type: 'armor',
|
||||||
|
value: 0,
|
||||||
|
typeData: {
|
||||||
|
type: 'armor',
|
||||||
|
max: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -914,8 +928,8 @@ export const weaponFeatures = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hitPoints: {
|
||||||
applyTo: 'hitPoints',
|
applyTo: 'hitPoints',
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -924,7 +938,7 @@ export const weaponFeatures = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -1191,9 +1205,13 @@ export const weaponFeatures = {
|
||||||
img: 'icons/skills/melee/shield-block-gray-orange.webp',
|
img: 'icons/skills/melee/shield-block-gray-orange.webp',
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.armorScore',
|
key: 'Armor',
|
||||||
mode: 2,
|
type: 'armor',
|
||||||
value: 'ITEM.@system.tier'
|
value: 0,
|
||||||
|
typeData: {
|
||||||
|
type: 'armor',
|
||||||
|
max: 'ITEM.@system.tier'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,9 @@ export const gameSettings = {
|
||||||
LevelTiers: 'LevelTiers',
|
LevelTiers: 'LevelTiers',
|
||||||
Countdowns: 'Countdowns',
|
Countdowns: 'Countdowns',
|
||||||
LastMigrationVersion: 'LastMigrationVersion',
|
LastMigrationVersion: 'LastMigrationVersion',
|
||||||
TagTeamRoll: 'TagTeamRoll',
|
|
||||||
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||||
CompendiumBrowserSettings: 'CompendiumBrowserSettings'
|
CompendiumBrowserSettings: 'CompendiumBrowserSettings',
|
||||||
|
SpotlightTracker: 'SpotlightTracker'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionAutomationChoices = {
|
export const actionAutomationChoices = {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
export { default as DhCombat } from './combat.mjs';
|
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 DhRollTable } from './rollTable.mjs';
|
export { default as DhRollTable } from './rollTable.mjs';
|
||||||
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
||||||
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
||||||
|
export { default as TagTeamData } from './tagTeamData.mjs';
|
||||||
|
export { default as SpotlightTracker } from './spotlightTracker.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';
|
||||||
|
|
|
||||||
|
|
@ -26,23 +26,23 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
return {
|
return {
|
||||||
value: {
|
value: {
|
||||||
multiplier: 'prof',
|
multiplier: 'prof',
|
||||||
dice: this.item?.system?.attack.damage.parts[0].value.dice,
|
dice: this.item?.system?.attack.damage.parts.hitPoints.value.dice,
|
||||||
bonus: this.item?.system?.attack.damage.parts[0].value.bonus ?? 0
|
bonus: this.item?.system?.attack.damage.parts.hitPoints.value.bonus ?? 0
|
||||||
},
|
},
|
||||||
type: this.item?.system?.attack.damage.parts[0].type,
|
type: this.item?.system?.attack.damage.parts.hitPoints.type,
|
||||||
base: true
|
base: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get damageFormula() {
|
get damageFormula() {
|
||||||
const hitPointsPart = this.damage.parts.find(x => x.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
const hitPointsPart = this.damage.parts.hitPoints;
|
||||||
if (!hitPointsPart) return '0';
|
if (!hitPointsPart) return '0';
|
||||||
|
|
||||||
return hitPointsPart.value.getFormula();
|
return hitPointsPart.value.getFormula();
|
||||||
}
|
}
|
||||||
|
|
||||||
get altDamageFormula() {
|
get altDamageFormula() {
|
||||||
const hitPointsPart = this.damage.parts.find(x => x.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
const hitPointsPart = this.damage.parts.hitPoints;
|
||||||
if (!hitPointsPart) return '0';
|
if (!hitPointsPart) return '0';
|
||||||
|
|
||||||
return hitPointsPart.valueAlt.getFormula();
|
return hitPointsPart.valueAlt.getFormula();
|
||||||
|
|
@ -50,9 +50,8 @@ 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;
|
||||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
|
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -207,10 +207,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
* @param {Event} event Event from the button used to trigger the Action
|
* @param {Event} event Event from the button used to trigger the Action
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
async use(event) {
|
async use(event, configOptions = {}) {
|
||||||
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||||
|
|
||||||
let config = this.prepareConfig(event);
|
let config = this.prepareConfig(event, configOptions);
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
||||||
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item);
|
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item);
|
||||||
|
|
@ -231,7 +231,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
if (this.chatDisplay && !config.actionChatMessageHandled) await this.toChat();
|
if (this.chatDisplay && !config.skips.createMessage && !config.actionChatMessageHandled) await this.toChat();
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
@ -241,7 +241,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
* @param {Event} event Event from the button used to trigger the Action
|
* @param {Event} event Event from the button used to trigger the Action
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
prepareBaseConfig(event) {
|
prepareBaseConfig(event, configOptions = {}) {
|
||||||
const isActor = this.item instanceof CONFIG.Actor.documentClass;
|
const isActor = this.item instanceof CONFIG.Actor.documentClass;
|
||||||
const actionTitle = game.i18n.localize(this.name);
|
const actionTitle = game.i18n.localize(this.name);
|
||||||
const itemTitle = isActor || this.item.name === actionTitle ? '' : `${this.item.name} - `;
|
const itemTitle = isActor || this.item.name === actionTitle ? '' : `${this.item.name} - `;
|
||||||
|
|
@ -264,11 +264,20 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
hasSave: this.hasSave,
|
hasSave: this.hasSave,
|
||||||
onSave: this.save?.damageMod,
|
onSave: this.save?.damageMod,
|
||||||
isDirect: !!this.damage?.direct,
|
isDirect: !!this.damage?.direct,
|
||||||
selectedRollMode: game.settings.get('core', 'rollMode'),
|
selectedMessageMode: game.settings.get('core', 'messageMode'),
|
||||||
data: this.getRollData(),
|
data: this.getRollData(),
|
||||||
evaluate: this.hasRoll,
|
evaluate: this.hasRoll,
|
||||||
resourceUpdates: new ResourceUpdateMap(this.actor),
|
resourceUpdates: new ResourceUpdateMap(this.actor),
|
||||||
targetUuid: this.targetUuid
|
targetUuid: this.targetUuid,
|
||||||
|
...configOptions,
|
||||||
|
skips: {
|
||||||
|
resources: false,
|
||||||
|
triggers: false,
|
||||||
|
createMessage: false,
|
||||||
|
updateCountdowns: false,
|
||||||
|
reaction: false,
|
||||||
|
...(configOptions.skips ?? {})
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
DHBaseAction.applyKeybindings(config);
|
DHBaseAction.applyKeybindings(config);
|
||||||
|
|
@ -280,8 +289,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
* @param {Event} event Event from the button used to trigger the Action
|
* @param {Event} event Event from the button used to trigger the Action
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
prepareConfig(event) {
|
prepareConfig(event, configOptions = {}) {
|
||||||
const config = this.prepareBaseConfig(event);
|
const config = this.prepareBaseConfig(event, configOptions);
|
||||||
for (const clsField of Object.values(this.schema.fields)) {
|
for (const clsField of Object.values(this.schema.fields)) {
|
||||||
if (clsField?.prepareConfig) if (clsField.prepareConfig.call(this, config) === false) return false;
|
if (clsField?.prepareConfig) if (clsField.prepareConfig.call(this, config) === false) return false;
|
||||||
}
|
}
|
||||||
|
|
@ -297,17 +306,19 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
static async getEffects(actor, effectParent) {
|
static async getEffects(actor, effectParent) {
|
||||||
if (!actor) return [];
|
if (!actor) return [];
|
||||||
|
|
||||||
return Array.from(await actor.allApplicableEffects()).filter(effect => {
|
return Array.from(await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true })).filter(
|
||||||
/* Effects on weapons only ever apply for the weapon itself */
|
effect => {
|
||||||
if (effect.parent.type === 'weapon') {
|
/* Effects on weapons only ever apply for the weapon itself */
|
||||||
/* Unless they're secondary - then they apply only to other primary weapons */
|
if (effect.parent.type === 'weapon') {
|
||||||
if (effect.parent.system.secondary) {
|
/* Unless they're secondary - then they apply only to other primary weapons */
|
||||||
if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) return false;
|
if (effect.parent.system.secondary) {
|
||||||
} else if (effectParent?.id !== effect.parent.id) return false;
|
if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) return false;
|
||||||
}
|
} else if (effectParent?.id !== effect.parent.id) return false;
|
||||||
|
}
|
||||||
|
|
||||||
return !effect.isSuppressed;
|
return !effect.isSuppressed;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -326,6 +337,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
* @param {boolean} successCost
|
* @param {boolean} successCost
|
||||||
*/
|
*/
|
||||||
async consume(config, successCost = false) {
|
async consume(config, successCost = false) {
|
||||||
|
config.resourceUpdates = new ResourceUpdateMap(config.actionActor);
|
||||||
await this.workflow.get('cost')?.execute(config, successCost);
|
await this.workflow.get('cost')?.execute(config, successCost);
|
||||||
await this.workflow.get('uses')?.execute(config, successCost);
|
await this.workflow.get('uses')?.execute(config, successCost);
|
||||||
|
|
||||||
|
|
@ -354,11 +366,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasDamage() {
|
get hasDamage() {
|
||||||
return this.damage?.parts?.length && this.type !== 'healing';
|
return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type !== 'healing';
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasHealing() {
|
get hasHealing() {
|
||||||
return this.damage?.parts?.length && this.type === 'healing';
|
return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type === 'healing';
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasSave() {
|
get hasSave() {
|
||||||
|
|
@ -378,6 +390,15 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
|
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static migrateData(source) {
|
||||||
|
if (source.damage?.parts && Array.isArray(source.damage.parts)) {
|
||||||
|
source.damage.parts = source.damage.parts.reduce((acc, part) => {
|
||||||
|
acc[part.applyTo] = part;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ResourceUpdateMap extends Map {
|
export class ResourceUpdateMap extends Map {
|
||||||
|
|
|
||||||
|
|
@ -2,84 +2,4 @@ import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DhBeastformAction extends DHBaseAction {
|
export default class DhBeastformAction extends DHBaseAction {
|
||||||
static extraSchemas = [...super.extraSchemas, 'beastform'];
|
static extraSchemas = [...super.extraSchemas, 'beastform'];
|
||||||
|
|
||||||
/* async use(event, options) {
|
|
||||||
const beastformConfig = this.prepareBeastformConfig();
|
|
||||||
|
|
||||||
const abort = await this.handleActiveTransformations();
|
|
||||||
if (abort) return;
|
|
||||||
|
|
||||||
const calcCosts = game.system.api.fields.ActionFields.CostField.calcCosts.call(this, this.cost);
|
|
||||||
const hasCost = game.system.api.fields.ActionFields.CostField.hasCost.call(this, calcCosts);
|
|
||||||
if (!hasCost) {
|
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { selected, evolved, hybrid } = await BeastformDialog.configure(beastformConfig, this.item);
|
|
||||||
if (!selected) return;
|
|
||||||
|
|
||||||
const result = await super.use(event, options);
|
|
||||||
if (!result) return;
|
|
||||||
|
|
||||||
await this.transform(selected, evolved, hybrid);
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareBeastformConfig(config) {
|
|
||||||
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
|
|
||||||
const actorLevel = this.actor.system.levelData.level.current;
|
|
||||||
const actorTier =
|
|
||||||
Object.values(settingsTiers).find(
|
|
||||||
tier => actorLevel >= tier.levels.start && actorLevel <= tier.levels.end
|
|
||||||
) ?? 1;
|
|
||||||
|
|
||||||
return {
|
|
||||||
tierLimit: this.beastform.tierAccess.exact ?? actorTier
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async transform(selectedForm, evolvedData, hybridData) {
|
|
||||||
const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm.toObject();
|
|
||||||
const beastformEffect = formData.effects.find(x => x.type === 'beastform');
|
|
||||||
if (!beastformEffect) {
|
|
||||||
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (evolvedData?.form) {
|
|
||||||
const evolvedForm = selectedForm.effects.find(x => x.type === 'beastform');
|
|
||||||
if (!evolvedForm) {
|
|
||||||
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes];
|
|
||||||
formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) {
|
|
||||||
formData.system.advantageOn = Object.values(hybridData.advantages).reduce((advantages, formCategory) => {
|
|
||||||
Object.keys(formCategory).forEach(advantageKey => {
|
|
||||||
advantages[advantageKey] = formCategory[advantageKey];
|
|
||||||
});
|
|
||||||
return advantages;
|
|
||||||
}, {});
|
|
||||||
formData.system.features = [
|
|
||||||
...formData.system.features,
|
|
||||||
...Object.values(hybridData.features).flatMap(x => Object.keys(x))
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.actor.createEmbeddedDocuments('Item', [formData]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleActiveTransformations() {
|
|
||||||
const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform');
|
|
||||||
const existingEffects = beastformEffects.length > 0;
|
|
||||||
await this.actor.deleteEmbeddedDocuments(
|
|
||||||
'ActiveEffect',
|
|
||||||
beastformEffects.map(x => x.id)
|
|
||||||
);
|
|
||||||
return existingEffects;
|
|
||||||
} */
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import BaseEffect from './baseEffect.mjs';
|
import BaseEffect from './baseEffect.mjs';
|
||||||
import BeastformEffect from './beastformEffect.mjs';
|
import BeastformEffect from './beastformEffect.mjs';
|
||||||
import HordeEffect from './hordeEffect.mjs';
|
import HordeEffect from './hordeEffect.mjs';
|
||||||
|
export { changeTypes, changeEffects } from './changeTypes/_module.mjs';
|
||||||
|
|
||||||
export { BaseEffect, BeastformEffect, HordeEffect };
|
export { BaseEffect, BeastformEffect, HordeEffect };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,50 @@
|
||||||
* "Anything that uses another data model value as its value": +1 - Effects that increase traits have to be calculated first at Base priority. (EX: Raise evasion by half your agility)
|
* "Anything that uses another data model value as its value": +1 - Effects that increase traits have to be calculated first at Base priority. (EX: Raise evasion by half your agility)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class BaseEffect extends foundry.abstract.TypeDataModel {
|
import { getScrollTextData } from '../../helpers/utils.mjs';
|
||||||
|
import { changeTypes } from './_module.mjs';
|
||||||
|
|
||||||
|
export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
const baseChanges = Object.keys(CONFIG.DH.GENERAL.baseActiveEffectModes).reduce((r, type) => {
|
||||||
|
r[type] = new fields.SchemaField({
|
||||||
|
key: new fields.StringField({ required: true }),
|
||||||
|
type: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
choices: [type],
|
||||||
|
initial: type,
|
||||||
|
validate: BaseEffect.#validateType
|
||||||
|
}),
|
||||||
|
value: new fields.AnyField({
|
||||||
|
required: true,
|
||||||
|
nullable: true,
|
||||||
|
serializable: true,
|
||||||
|
initial: ''
|
||||||
|
}),
|
||||||
|
phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }),
|
||||||
|
priority: new fields.NumberField()
|
||||||
|
});
|
||||||
|
return r;
|
||||||
|
}, {});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...super.defineSchema(),
|
||||||
|
changes: new fields.ArrayField(
|
||||||
|
new fields.TypedSchemaField(
|
||||||
|
{ ...changeTypes, ...baseChanges },
|
||||||
|
{ initial: baseChanges.add.getInitialValue() }
|
||||||
|
)
|
||||||
|
),
|
||||||
|
duration: new fields.SchemaField({
|
||||||
|
type: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.activeEffectDurations,
|
||||||
|
blank: true,
|
||||||
|
label: 'DAGGERHEART.GENERAL.type'
|
||||||
|
}),
|
||||||
|
description: new fields.HTMLField({ label: 'DAGGERHEART.GENERAL.description' })
|
||||||
|
}),
|
||||||
rangeDependence: new fields.SchemaField({
|
rangeDependence: new fields.SchemaField({
|
||||||
enabled: new fields.BooleanField({
|
enabled: new fields.BooleanField({
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -41,10 +80,57 @@ export default class BaseEffect extends foundry.abstract.TypeDataModel {
|
||||||
initial: CONFIG.DH.GENERAL.range.melee.id,
|
initial: CONFIG.DH.GENERAL.range.melee.id,
|
||||||
label: 'DAGGERHEART.GENERAL.range'
|
label: 'DAGGERHEART.GENERAL.range'
|
||||||
})
|
})
|
||||||
})
|
}),
|
||||||
|
stacking: new fields.SchemaField(
|
||||||
|
{
|
||||||
|
value: new fields.NumberField({
|
||||||
|
initial: 1,
|
||||||
|
min: 1,
|
||||||
|
integer: true,
|
||||||
|
nullable: false,
|
||||||
|
label: 'DAGGERHEART.GENERAL.value'
|
||||||
|
}),
|
||||||
|
max: new fields.NumberField({ integer: true, label: 'DAGGERHEART.GENERAL.max' })
|
||||||
|
},
|
||||||
|
{ nullable: true, initial: null }
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that an {@link EffectChangeData#type} string is well-formed.
|
||||||
|
* @param {string} type The string to be validated
|
||||||
|
* @returns {true}
|
||||||
|
* @throws {Error} An error if the type string is malformed
|
||||||
|
*/
|
||||||
|
static #validateType(type) {
|
||||||
|
if (type.length < 3) throw new Error('must be at least three characters long');
|
||||||
|
if (!/^custom\.-?\d+$/.test(type) && !type.split('.').every(s => /^[a-z0-9]+$/i.test(s))) {
|
||||||
|
throw new Error(
|
||||||
|
'A change type must either be a sequence of dot-delimited, alpha-numeric substrings or of the form' +
|
||||||
|
' "custom.{number}"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSuppressed() {
|
||||||
|
for (const change of this.changes) {
|
||||||
|
if (change.isSuppressed) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get armorChange() {
|
||||||
|
return this.changes.find(x => x.type === CONFIG.DH.GENERAL.activeEffectModes.armor.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
get armorData() {
|
||||||
|
const armorChange = this.armorChange;
|
||||||
|
if (!armorChange) return null;
|
||||||
|
|
||||||
|
return armorChange.getArmorData();
|
||||||
|
}
|
||||||
|
|
||||||
static getDefaultObject() {
|
static getDefaultObject() {
|
||||||
return {
|
return {
|
||||||
name: 'New Effect',
|
name: 'New Effect',
|
||||||
|
|
@ -64,4 +150,32 @@ export default class BaseEffect extends foundry.abstract.TypeDataModel {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _preUpdate(changed, options, userId) {
|
||||||
|
const allowed = await super._preUpdate(changed, options, userId);
|
||||||
|
if (allowed === false) return false;
|
||||||
|
|
||||||
|
const autoSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||||
|
if (
|
||||||
|
autoSettings.resourceScrollTexts &&
|
||||||
|
this.parent.actor?.type === 'character' &&
|
||||||
|
this.parent.actor.system.resources.armor
|
||||||
|
) {
|
||||||
|
const armorEffect = changed.system?.changes?.find(x => x.type === 'armor');
|
||||||
|
const newArmorTotal =
|
||||||
|
armorEffect?.value?.current + (this.parent.actor.system.armor?.system?.armor?.current ?? 0);
|
||||||
|
|
||||||
|
if (armorEffect && newArmorTotal !== this.parent.actor.system.armorScore.value) {
|
||||||
|
const armorData = getScrollTextData(this.parent.actor, { value: newArmorTotal }, 'armor');
|
||||||
|
options.scrollingTextData = [armorData];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onUpdate(changed, options, userId) {
|
||||||
|
super._onUpdate(changed, options, userId);
|
||||||
|
|
||||||
|
if (this.parent.actor && options.scrollingTextData)
|
||||||
|
this.parent.actor.queueScrollText(options.scrollingTextData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ export default class BeastformEffect extends BaseEffect {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
|
...super.defineSchema(),
|
||||||
characterTokenData: new fields.SchemaField({
|
characterTokenData: new fields.SchemaField({
|
||||||
usesDynamicToken: new fields.BooleanField({ initial: false }),
|
usesDynamicToken: new fields.BooleanField({ initial: false }),
|
||||||
tokenImg: new fields.FilePathField({
|
tokenImg: new fields.FilePathField({
|
||||||
|
|
@ -24,7 +25,7 @@ export default class BeastformEffect extends BaseEffect {
|
||||||
width: new fields.NumberField({ integer: false, nullable: true })
|
width: new fields.NumberField({ integer: false, nullable: true })
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
advantageOn: new fields.ArrayField(new fields.StringField()),
|
advantageOn: new fields.TypedObjectField(new fields.SchemaField({ value: new fields.StringField() })),
|
||||||
featureIds: new fields.ArrayField(new fields.StringField()),
|
featureIds: new fields.ArrayField(new fields.StringField()),
|
||||||
effectIds: new fields.ArrayField(new fields.StringField())
|
effectIds: new fields.ArrayField(new fields.StringField())
|
||||||
};
|
};
|
||||||
|
|
@ -99,7 +100,7 @@ export default class BeastformEffect extends BaseEffect {
|
||||||
token.flags.daggerheart?.beastformSubjectTexture ?? this.characterTokenData.tokenRingImg
|
token.flags.daggerheart?.beastformSubjectTexture ?? this.characterTokenData.tokenRingImg
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'flags.daggerheart': { '-=beastformTokenImg': null, '-=beastformSubjectTexture': null }
|
'flags.daggerheart': { beastformTokenImg: _del, beastformSubjectTexture: _del }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
9
module/data/activeEffect/changeTypes/_module.mjs
Normal file
9
module/data/activeEffect/changeTypes/_module.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Armor from './armor.mjs';
|
||||||
|
|
||||||
|
export const changeEffects = {
|
||||||
|
armor: Armor.changeEffect
|
||||||
|
};
|
||||||
|
|
||||||
|
export const changeTypes = {
|
||||||
|
armor: Armor
|
||||||
|
};
|
||||||
206
module/data/activeEffect/changeTypes/armor.mjs
Normal file
206
module/data/activeEffect/changeTypes/armor.mjs
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
import { itemAbleRollParse } from '../../../helpers/utils.mjs';
|
||||||
|
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class ArmorChange extends foundry.abstract.DataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
return {
|
||||||
|
type: new fields.StringField({ required: true, choices: ['armor'], initial: 'armor' }),
|
||||||
|
priority: new fields.NumberField(),
|
||||||
|
phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }),
|
||||||
|
value: new fields.SchemaField({
|
||||||
|
current: new fields.NumberField({ integer: true, min: 0, initial: 0 }),
|
||||||
|
max: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
nullable: false,
|
||||||
|
initial: '1',
|
||||||
|
label: 'DAGGERHEART.GENERAL.max'
|
||||||
|
}),
|
||||||
|
damageThresholds: new fields.SchemaField(
|
||||||
|
{
|
||||||
|
major: new fields.StringField({
|
||||||
|
initial: '0',
|
||||||
|
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
|
||||||
|
}),
|
||||||
|
severe: new fields.StringField({
|
||||||
|
initial: '0',
|
||||||
|
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ nullable: true, initial: null }
|
||||||
|
),
|
||||||
|
interaction: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.activeEffectArmorInteraction,
|
||||||
|
initial: CONFIG.DH.GENERAL.activeEffectArmorInteraction.none.id,
|
||||||
|
label: 'DAGGERHEART.EFFECTS.ChangeTypes.armor.FIELDS.interaction.label',
|
||||||
|
hint: 'DAGGERHEART.EFFECTS.ChangeTypes.armor.FIELDS.interaction.hint'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static changeEffect = {
|
||||||
|
label: 'Armor',
|
||||||
|
defaultPriority: 20,
|
||||||
|
handler: (actor, change, _options, _field, replacementData) => {
|
||||||
|
const parsedMax = itemAbleRollParse(change.value.max, actor, change.effect.parent);
|
||||||
|
game.system.api.documents.DhActiveEffect.applyChange(
|
||||||
|
actor,
|
||||||
|
{
|
||||||
|
...change,
|
||||||
|
key: 'system.armorScore.value',
|
||||||
|
type: CONFIG.DH.GENERAL.activeEffectModes.add.id,
|
||||||
|
value: change.value.current
|
||||||
|
},
|
||||||
|
replacementData
|
||||||
|
);
|
||||||
|
game.system.api.documents.DhActiveEffect.applyChange(
|
||||||
|
actor,
|
||||||
|
{
|
||||||
|
...change,
|
||||||
|
key: 'system.armorScore.max',
|
||||||
|
type: CONFIG.DH.GENERAL.activeEffectModes.add.id,
|
||||||
|
value: parsedMax
|
||||||
|
},
|
||||||
|
replacementData
|
||||||
|
);
|
||||||
|
|
||||||
|
if (change.value.damageThresholds) {
|
||||||
|
const getThresholdValue = value => {
|
||||||
|
const parsed = itemAbleRollParse(value, actor, change.effect.parent);
|
||||||
|
const roll = new Roll(parsed).evaluateSync();
|
||||||
|
return roll ? (roll.isDeterministic ? roll.total : null) : null;
|
||||||
|
};
|
||||||
|
const major = getThresholdValue(change.value.damageThresholds.major);
|
||||||
|
const severe = getThresholdValue(change.value.damageThresholds.severe);
|
||||||
|
|
||||||
|
if (major) {
|
||||||
|
game.system.api.documents.DhActiveEffect.applyChange(
|
||||||
|
actor,
|
||||||
|
{
|
||||||
|
...change,
|
||||||
|
key: 'system.damageThresholds.major',
|
||||||
|
type: CONFIG.DH.GENERAL.activeEffectModes.override.id,
|
||||||
|
priority: 50,
|
||||||
|
value: major
|
||||||
|
},
|
||||||
|
replacementData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (severe) {
|
||||||
|
game.system.api.documents.DhActiveEffect.applyChange(
|
||||||
|
actor,
|
||||||
|
{
|
||||||
|
...change,
|
||||||
|
key: 'system.damageThresholds.severe',
|
||||||
|
type: CONFIG.DH.GENERAL.activeEffectModes.override.id,
|
||||||
|
priority: 50,
|
||||||
|
value: severe
|
||||||
|
},
|
||||||
|
replacementData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
render: null
|
||||||
|
};
|
||||||
|
|
||||||
|
get isSuppressed() {
|
||||||
|
switch (this.value.interaction) {
|
||||||
|
case CONFIG.DH.GENERAL.activeEffectArmorInteraction.active.id:
|
||||||
|
return !this.parent.parent?.actor.system.armor;
|
||||||
|
case CONFIG.DH.GENERAL.activeEffectArmorInteraction.inactive.id:
|
||||||
|
return Boolean(this.parent.parent?.actor.system.armor);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getInitialValue() {
|
||||||
|
return {
|
||||||
|
type: CONFIG.DH.GENERAL.activeEffectModes.armor.id,
|
||||||
|
value: {
|
||||||
|
current: 0,
|
||||||
|
max: 0
|
||||||
|
},
|
||||||
|
phase: 'initial',
|
||||||
|
priority: 20
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDefaultArmorEffect() {
|
||||||
|
return {
|
||||||
|
name: game.i18n.localize('DAGGERHEART.EFFECTS.ChangeTypes.armor.newArmorEffect'),
|
||||||
|
img: 'icons/equipment/chest/breastplate-helmet-metal.webp',
|
||||||
|
system: {
|
||||||
|
changes: [ArmorChange.getInitialValue()]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Helpers */
|
||||||
|
|
||||||
|
getArmorData() {
|
||||||
|
const actor = this.parent.parent?.actor?.type === 'character' ? this.parent.parent.actor : null;
|
||||||
|
const maxParse = actor ? itemAbleRollParse(this.value.max, actor, this.parent.parent.parent) : null;
|
||||||
|
const maxRoll = maxParse ? new Roll(maxParse).evaluateSync() : null;
|
||||||
|
const maxEvaluated = maxRoll ? (maxRoll.isDeterministic ? maxRoll.total : null) : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
current: this.value.current,
|
||||||
|
max: maxEvaluated ?? this.value.max
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateArmorMax(newMax) {
|
||||||
|
const newChanges = [
|
||||||
|
...this.parent.changes.map(change => ({
|
||||||
|
...change,
|
||||||
|
value:
|
||||||
|
change.type === 'armor'
|
||||||
|
? {
|
||||||
|
...change.value,
|
||||||
|
current: Math.min(change.value.current, newMax),
|
||||||
|
max: newMax
|
||||||
|
}
|
||||||
|
: change.value
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
await this.parent.parent.update({ 'system.changes': newChanges });
|
||||||
|
}
|
||||||
|
|
||||||
|
static orderEffectsForAutoChange(armorEffects, increasing) {
|
||||||
|
const getEffectWeight = effect => {
|
||||||
|
switch (effect.parent.type) {
|
||||||
|
case 'class':
|
||||||
|
case 'subclass':
|
||||||
|
case 'ancestry':
|
||||||
|
case 'community':
|
||||||
|
case 'feature':
|
||||||
|
case 'domainCard':
|
||||||
|
return 2;
|
||||||
|
case 'armor':
|
||||||
|
return 3;
|
||||||
|
case 'loot':
|
||||||
|
case 'consumable':
|
||||||
|
return 4;
|
||||||
|
case 'weapon':
|
||||||
|
return 5;
|
||||||
|
case 'character':
|
||||||
|
return 6;
|
||||||
|
default:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return armorEffects
|
||||||
|
.filter(x => !x.disabled && !x.isSuppressed)
|
||||||
|
.sort((a, b) =>
|
||||||
|
increasing ? getEffectWeight(b) - getEffectWeight(a) : getEffectWeight(a) - getEffectWeight(b)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -85,14 +85,14 @@ export default class DhpAdversary extends DhCreature {
|
||||||
type: 'attack'
|
type: 'attack'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hitPoints: {
|
||||||
type: ['physical'],
|
type: ['physical'],
|
||||||
value: {
|
value: {
|
||||||
multiplier: 'flat'
|
multiplier: 'flat'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
@ -265,12 +265,12 @@ export default class DhpAdversary extends DhCreature {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update damage in item actions
|
// Update damage in item actions
|
||||||
|
// Parse damage, and convert all formula matches in the descriptions to the new damage
|
||||||
for (const action of Object.values(item.system.actions)) {
|
for (const action of Object.values(item.system.actions)) {
|
||||||
if (!action.damage) continue;
|
|
||||||
|
|
||||||
// Parse damage, and convert all formula matches in the descriptions to the new damage
|
|
||||||
try {
|
try {
|
||||||
const result = this.#adjustActionDamage(action, { ...damageMeta, type: 'action' });
|
const result = this.#adjustActionDamage(action, { ...damageMeta, type: 'action' });
|
||||||
|
if (!result) continue;
|
||||||
|
|
||||||
for (const { previousFormula, formula } of Object.values(result)) {
|
for (const { previousFormula, formula } of Object.values(result)) {
|
||||||
const oldFormulaRegexp = new RegExp(
|
const oldFormulaRegexp = new RegExp(
|
||||||
previousFormula.replace(' ', '').replace('+', '(?:\\s)?\\+(?:\\s)?')
|
previousFormula.replace(' ', '').replace('+', '(?:\\s)?\\+(?:\\s)?')
|
||||||
|
|
@ -372,16 +372,14 @@ export default class DhpAdversary extends DhCreature {
|
||||||
/**
|
/**
|
||||||
* Updates damage to reflect a specific value.
|
* Updates damage to reflect a specific value.
|
||||||
* @throws if damage structure is invalid for conversion
|
* @throws if damage structure is invalid for conversion
|
||||||
* @returns the converted formula and value as a simplified term
|
* @returns the converted formula and value as a simplified term, or null if it doesn't deal HP damage
|
||||||
*/
|
*/
|
||||||
#adjustActionDamage(action, damageMeta) {
|
#adjustActionDamage(action, damageMeta) {
|
||||||
// The current algorithm only returns a value if there is a single damage part
|
if (!action.damage?.parts.hitPoints) return null;
|
||||||
const hpDamageParts = action.damage.parts.filter(d => d.applyTo === 'hitPoints');
|
|
||||||
if (hpDamageParts.length !== 1) throw new Error('incorrect number of hp parts');
|
|
||||||
|
|
||||||
const result = {};
|
const result = {};
|
||||||
for (const property of ['value', 'valueAlt']) {
|
for (const property of ['value', 'valueAlt']) {
|
||||||
const data = hpDamageParts[0][property];
|
const data = action.damage.parts.hitPoints[property];
|
||||||
const previousFormula = data.custom.enabled
|
const previousFormula = data.custom.enabled
|
||||||
? data.custom.formula
|
? data.custom.formula
|
||||||
: [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0]
|
: [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0]
|
||||||
|
|
|
||||||
|
|
@ -189,21 +189,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preDelete() {
|
|
||||||
/* Clear all partyMembers from tagTeam setting.*/
|
|
||||||
/* Revisit this when tagTeam is improved for many parties */
|
|
||||||
if (this.parent.parties.size > 0) {
|
|
||||||
const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
|
||||||
await tagTeam.updateSource({
|
|
||||||
initiator: this.parent.id === tagTeam.initiator ? null : tagTeam.initiator,
|
|
||||||
members: Object.keys(tagTeam.members).find(x => x === this.parent.id)
|
|
||||||
? { [`-=${this.parent.id}`]: null }
|
|
||||||
: {}
|
|
||||||
});
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _preUpdate(changes, options, userId) {
|
async _preUpdate(changes, options, userId) {
|
||||||
const allowed = await super._preUpdate(changes, options, userId);
|
const allowed = await super._preUpdate(changes, options, userId);
|
||||||
if (allowed === false) return;
|
if (allowed === false) return;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import DhCreature from './creature.mjs';
|
||||||
import { attributeField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
import { attributeField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
||||||
import { ActionField } from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
||||||
|
import { getArmorSources } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
export default class DhCharacter extends DhCreature {
|
export default class DhCharacter extends DhCreature {
|
||||||
/**@override */
|
/**@override */
|
||||||
|
|
@ -41,17 +42,16 @@ export default class DhCharacter extends DhCreature {
|
||||||
label: 'DAGGERHEART.GENERAL.proficiency'
|
label: 'DAGGERHEART.GENERAL.proficiency'
|
||||||
}),
|
}),
|
||||||
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
|
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
|
||||||
armorScore: new fields.NumberField({ integer: true, initial: 0, label: 'DAGGERHEART.GENERAL.armorScore' }),
|
|
||||||
damageThresholds: new fields.SchemaField({
|
damageThresholds: new fields.SchemaField({
|
||||||
severe: new fields.NumberField({
|
|
||||||
integer: true,
|
|
||||||
initial: 0,
|
|
||||||
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
|
|
||||||
}),
|
|
||||||
major: new fields.NumberField({
|
major: new fields.NumberField({
|
||||||
integer: true,
|
integer: true,
|
||||||
initial: 0,
|
initial: 0,
|
||||||
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
|
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
|
||||||
|
}),
|
||||||
|
severe: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
initial: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
experiences: new fields.TypedObjectField(
|
experiences: new fields.TypedObjectField(
|
||||||
|
|
@ -96,8 +96,8 @@ export default class DhCharacter extends DhCreature {
|
||||||
trait: 'strength'
|
trait: 'strength'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hitPoints: {
|
||||||
type: ['physical'],
|
type: ['physical'],
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -106,7 +106,7 @@ export default class DhCharacter extends DhCreature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
@ -465,6 +465,101 @@ export default class DhCharacter extends DhCreature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateArmorValue({ value: armorChange = 0, clear = false }) {
|
||||||
|
if (armorChange === 0 && !clear) return;
|
||||||
|
|
||||||
|
const increasing = armorChange >= 0;
|
||||||
|
let remainingChange = Math.abs(armorChange);
|
||||||
|
const orderedSources = getArmorSources(this.parent).filter(s => !s.disabled);
|
||||||
|
|
||||||
|
const handleArmorData = (embeddedUpdates, doc, armorData) => {
|
||||||
|
let usedArmorChange = 0;
|
||||||
|
if (clear) {
|
||||||
|
usedArmorChange -= armorData.current;
|
||||||
|
} else {
|
||||||
|
if (increasing) {
|
||||||
|
const remainingArmor = armorData.max - armorData.current;
|
||||||
|
usedArmorChange = Math.min(remainingChange, remainingArmor);
|
||||||
|
remainingChange -= usedArmorChange;
|
||||||
|
} else {
|
||||||
|
const changeChange = Math.min(armorData.current, remainingChange);
|
||||||
|
usedArmorChange -= changeChange;
|
||||||
|
remainingChange -= changeChange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usedArmorChange) return usedArmorChange;
|
||||||
|
else {
|
||||||
|
if (!embeddedUpdates[doc.id]) embeddedUpdates[doc.id] = { doc: doc, updates: [] };
|
||||||
|
|
||||||
|
return usedArmorChange;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const armorUpdates = [];
|
||||||
|
const effectUpdates = [];
|
||||||
|
for (const { document: armorSource } of orderedSources) {
|
||||||
|
const usedArmorChange = handleArmorData(
|
||||||
|
armorSource.type === 'armor' ? armorUpdates : effectUpdates,
|
||||||
|
armorSource.parent,
|
||||||
|
armorSource.type === 'armor' ? armorSource.system.armor : armorSource.system.armorData
|
||||||
|
);
|
||||||
|
if (!usedArmorChange) continue;
|
||||||
|
|
||||||
|
if (armorSource.type === 'armor') {
|
||||||
|
armorUpdates[armorSource.parent.id].updates.push({
|
||||||
|
'_id': armorSource.id,
|
||||||
|
'system.armor.current': armorSource.system.armor.current + usedArmorChange
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
effectUpdates[armorSource.parent.id].updates.push({
|
||||||
|
'_id': armorSource.id,
|
||||||
|
'system.changes': armorSource.system.changes.map(change => ({
|
||||||
|
...change,
|
||||||
|
value:
|
||||||
|
change.type === 'armor'
|
||||||
|
? {
|
||||||
|
...change.value,
|
||||||
|
current: armorSource.system.armorChange.value.current + usedArmorChange
|
||||||
|
}
|
||||||
|
: change.value
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingChange === 0 && !clear) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const armorUpdateValues = Object.values(armorUpdates);
|
||||||
|
for (const [index, { doc, updates }] of armorUpdateValues.entries())
|
||||||
|
await doc.updateEmbeddedDocuments('Item', updates, { render: index === armorUpdateValues.length - 1 });
|
||||||
|
|
||||||
|
const effectUpdateValues = Object.values(effectUpdates);
|
||||||
|
for (const [index, { doc, updates }] of effectUpdateValues.entries())
|
||||||
|
await doc.updateEmbeddedDocuments('ActiveEffect', updates, {
|
||||||
|
render: index === effectUpdateValues.length - 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateArmorEffectValue({ uuid, value }) {
|
||||||
|
const source = await foundry.utils.fromUuid(uuid);
|
||||||
|
if (source.type === 'armor') {
|
||||||
|
await source.update({
|
||||||
|
'system.armor.current': source.system.armor.current + value
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const effectValue = source.system.armorChange.value;
|
||||||
|
await source.update({
|
||||||
|
'system.changes': [
|
||||||
|
{
|
||||||
|
...source.system.armorChange,
|
||||||
|
value: { ...effectValue, current: effectValue.current + value }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get sheetLists() {
|
get sheetLists() {
|
||||||
const ancestryFeatures = [],
|
const ancestryFeatures = [],
|
||||||
communityFeatures = [],
|
communityFeatures = [],
|
||||||
|
|
@ -588,6 +683,10 @@ export default class DhCharacter extends DhCreature {
|
||||||
|
|
||||||
prepareBaseData() {
|
prepareBaseData() {
|
||||||
super.prepareBaseData();
|
super.prepareBaseData();
|
||||||
|
this.armorScore = {
|
||||||
|
max: this.armor?.system.armor.max ?? 0,
|
||||||
|
value: this.armor?.system.armor.current ?? 0
|
||||||
|
};
|
||||||
this.evasion += this.class.value?.system?.evasion ?? 0;
|
this.evasion += this.class.value?.system?.evasion ?? 0;
|
||||||
|
|
||||||
const currentLevel = this.levelData.level.current;
|
const currentLevel = this.levelData.level.current;
|
||||||
|
|
@ -637,14 +736,12 @@ export default class DhCharacter extends DhCreature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const armor = this.armor;
|
|
||||||
this.armorScore = armor ? armor.system.baseScore : 0;
|
|
||||||
this.damageThresholds = {
|
this.damageThresholds = {
|
||||||
major: armor
|
major: this.armor
|
||||||
? armor.system.baseThresholds.major + this.levelData.level.current
|
? this.armor.system.baseThresholds.major + this.levelData.level.current
|
||||||
: this.levelData.level.current,
|
: this.levelData.level.current,
|
||||||
severe: armor
|
severe: this.armor
|
||||||
? armor.system.baseThresholds.severe + this.levelData.level.current
|
? this.armor.system.baseThresholds.severe + this.levelData.level.current
|
||||||
: this.levelData.level.current * 2
|
: this.levelData.level.current * 2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -677,13 +774,12 @@ export default class DhCharacter extends DhCreature {
|
||||||
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;
|
||||||
|
|
||||||
this.resources.armor = {
|
this.resources.armor = {
|
||||||
|
...this.armorScore,
|
||||||
label: 'DAGGERHEART.GENERAL.armor',
|
label: 'DAGGERHEART.GENERAL.armor',
|
||||||
value: this.armor?.system?.marks?.value ?? 0,
|
|
||||||
max: this.armorScore,
|
|
||||||
isReversed: true
|
isReversed: true
|
||||||
};
|
};
|
||||||
|
|
||||||
this.attack.damage.parts[0].value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`;
|
this.attack.damage.parts.hitPoints.value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRollData() {
|
getRollData() {
|
||||||
|
|
|
||||||
|
|
@ -81,15 +81,15 @@ export default class DhCompanion extends DhCreature {
|
||||||
bonus: 0
|
bonus: 0
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hitPoints: {
|
||||||
type: ['physical'],
|
type: ['physical'],
|
||||||
value: {
|
value: {
|
||||||
dice: 'd6',
|
dice: 'd6',
|
||||||
multiplier: 'prof'
|
multiplier: 'prof'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
@ -135,7 +135,9 @@ export default class DhCompanion extends DhCreature {
|
||||||
break;
|
break;
|
||||||
case 'vicious':
|
case 'vicious':
|
||||||
if (selection.data[0] === 'damage') {
|
if (selection.data[0] === 'damage') {
|
||||||
this.attack.damage.parts[0].value.dice = adjustDice(this.attack.damage.parts[0].value.dice);
|
this.attack.damage.parts.hitPoints.value.dice = adjustDice(
|
||||||
|
this.attack.damage.parts.hitPoints.value.dice
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.attack.range = adjustRange(this.attack.range).id;
|
this.attack.range = adjustRange(this.attack.range).id;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,10 +75,6 @@ export default class DhEnvironment extends BaseDataActor {
|
||||||
);
|
);
|
||||||
scene.update({ 'flags.daggerheart.sceneEnvironments': newSceneEnvironments }).then(() => {
|
scene.update({ 'flags.daggerheart.sceneEnvironments': newSceneEnvironments }).then(() => {
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Scene });
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Scene });
|
||||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
|
||||||
action: socketEvent.Refresh,
|
|
||||||
data: { refreshType: RefreshType.TagTeamRoll }
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor from './base.mjs';
|
||||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||||
|
import TagTeamData from '../tagTeamData.mjs';
|
||||||
|
|
||||||
export default class DhParty extends BaseDataActor {
|
export default class DhParty extends BaseDataActor {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
|
|
@ -14,7 +15,8 @@ export default class DhParty extends BaseDataActor {
|
||||||
handfuls: new fields.NumberField({ initial: 1, integer: true }),
|
handfuls: new fields.NumberField({ initial: 1, integer: true }),
|
||||||
bags: new fields.NumberField({ initial: 0, integer: true }),
|
bags: new fields.NumberField({ initial: 0, integer: true }),
|
||||||
chests: new fields.NumberField({ initial: 0, integer: true })
|
chests: new fields.NumberField({ initial: 0, integer: true })
|
||||||
})
|
}),
|
||||||
|
tagTeam: new fields.EmbeddedDataField(TagTeamData)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,23 +42,6 @@ export default class DhParty extends BaseDataActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preDelete() {
|
|
||||||
/* Clear all partyMembers from tagTeam setting.*/
|
|
||||||
/* Revisit this when tagTeam is improved for many parties */
|
|
||||||
const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
|
||||||
await tagTeam.updateSource({
|
|
||||||
initiator: this.partyMembers.some(x => x.id === tagTeam.initiator) ? null : tagTeam.initiator,
|
|
||||||
members: Object.keys(tagTeam.members).reduce((acc, key) => {
|
|
||||||
if (this.partyMembers.find(x => x.id === key)) {
|
|
||||||
acc[`-=${key}`] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {})
|
|
||||||
});
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onDelete(options, userId) {
|
_onDelete(options, userId) {
|
||||||
super._onDelete(options, userId);
|
super._onDelete(options, userId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
return {
|
return {
|
||||||
title: new fields.StringField(),
|
title: new fields.StringField(),
|
||||||
actionDescription: new fields.HTMLField(),
|
actionDescription: new fields.HTMLField(),
|
||||||
roll: new fields.ObjectField(),
|
|
||||||
targets: targetsField(),
|
targets: targetsField(),
|
||||||
hasRoll: new fields.BooleanField({ initial: false }),
|
hasRoll: new fields.BooleanField({ initial: false }),
|
||||||
hasDamage: new fields.BooleanField({ initial: false }),
|
hasDamage: new fields.BooleanField({ initial: false }),
|
||||||
|
|
@ -41,7 +40,6 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
hasSave: new fields.BooleanField({ initial: false }),
|
hasSave: new fields.BooleanField({ initial: false }),
|
||||||
hasTarget: new fields.BooleanField({ initial: false }),
|
hasTarget: new fields.BooleanField({ initial: false }),
|
||||||
isDirect: new fields.BooleanField({ initial: false }),
|
isDirect: new fields.BooleanField({ initial: false }),
|
||||||
isCritical: new fields.BooleanField({ initial: false }),
|
|
||||||
onSave: new fields.StringField(),
|
onSave: new fields.StringField(),
|
||||||
source: new fields.SchemaField({
|
source: new fields.SchemaField({
|
||||||
actor: new fields.StringField(),
|
actor: new fields.StringField(),
|
||||||
|
|
@ -55,6 +53,19 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get roll() {
|
||||||
|
switch (this.parent.type) {
|
||||||
|
case 'adversaryRoll':
|
||||||
|
return this.parent.rolls.find(x => x instanceof game.system.api.dice.D20Roll);
|
||||||
|
case 'dualityRoll':
|
||||||
|
return this.parent.rolls.find(x => x instanceof game.system.api.dice.DualityRoll);
|
||||||
|
case 'fateRoll':
|
||||||
|
return this.parent.rolls.find(x => x instanceof game.system.api.dice.FateRoll);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
get actionActor() {
|
get actionActor() {
|
||||||
if (!this.source.actor) return null;
|
if (!this.source.actor) return null;
|
||||||
return fromUuidSync(this.source.actor);
|
return fromUuidSync(this.source.actor);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export { ActionCollection } from './actionField.mjs';
|
export { ActionCollection } from './actionField.mjs';
|
||||||
|
export { default as IterableTypedObjectField } from './iterableTypedObjectField.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 TriggerField } from './triggerField.mjs';
|
||||||
export { default as MappingField } from './mappingField.mjs';
|
|
||||||
export * as ActionFields from './action/_module.mjs';
|
export * as ActionFields from './action/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,21 @@ export default class BeastformField extends fields.SchemaField {
|
||||||
{ 1: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') }
|
{ 1: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') }
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
hint: 'DAGGERHEART.ACTIONS.Config.beastform.exactHint'
|
label: 'DAGGERHEART.ACTIONS.Config.beastform.exact.label',
|
||||||
|
hint: 'DAGGERHEART.ACTIONS.Config.beastform.exact.hint'
|
||||||
})
|
})
|
||||||
|
}),
|
||||||
|
modifications: new fields.SchemaField({
|
||||||
|
traitBonuses: new fields.ArrayField(
|
||||||
|
new fields.SchemaField({
|
||||||
|
bonus: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
initial: 1,
|
||||||
|
min: 1,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.bonus'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
super(beastformFields, options, context);
|
super(beastformFields, options, context);
|
||||||
|
|
@ -66,15 +79,9 @@ export default class BeastformField extends fields.SchemaField {
|
||||||
) ?? 1;
|
) ?? 1;
|
||||||
|
|
||||||
config.tierLimit = this.beastform.tierAccess.exact ?? actorTier;
|
config.tierLimit = this.beastform.tierAccess.exact ?? actorTier;
|
||||||
|
config.modifications = this.beastform.modifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO by Harry
|
|
||||||
* @param {*} selectedForm
|
|
||||||
* @param {*} evolvedData
|
|
||||||
* @param {*} hybridData
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
static async transform(selectedForm, evolvedData, hybridData) {
|
static async transform(selectedForm, evolvedData, hybridData) {
|
||||||
const formData = evolvedData?.form ?? selectedForm;
|
const formData = evolvedData?.form ?? selectedForm;
|
||||||
const beastformEffect = formData.effects.find(x => x.type === 'beastform');
|
const beastformEffect = formData.effects.find(x => x.type === 'beastform');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import FormulaField from '../formulaField.mjs';
|
import FormulaField from '../formulaField.mjs';
|
||||||
import { setsEqual } from '../../../helpers/utils.mjs';
|
import { setsEqual } from '../../../helpers/utils.mjs';
|
||||||
|
import IterableTypedObjectField from '../iterableTypedObjectField.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
|
@ -12,7 +13,7 @@ export default class DamageField extends fields.SchemaField {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
constructor(options, context = {}) {
|
constructor(options, context = {}) {
|
||||||
const damageFields = {
|
const damageFields = {
|
||||||
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
|
parts: new IterableTypedObjectField(DHDamageData),
|
||||||
includeBase: new fields.BooleanField({
|
includeBase: new fields.BooleanField({
|
||||||
initial: false,
|
initial: false,
|
||||||
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
||||||
|
|
@ -48,11 +49,14 @@ export default class DamageField extends fields.SchemaField {
|
||||||
|
|
||||||
formulas = DamageField.formatFormulas.call(this, formulas, config);
|
formulas = DamageField.formatFormulas.call(this, formulas, config);
|
||||||
|
|
||||||
|
messageId = config.message?._id ?? messageId;
|
||||||
|
const message = game.messages.get(messageId);
|
||||||
const damageConfig = {
|
const damageConfig = {
|
||||||
|
dialog: {},
|
||||||
...config,
|
...config,
|
||||||
roll: formulas,
|
roll: formulas,
|
||||||
dialog: {},
|
data: this.getRollData(),
|
||||||
data: this.getRollData()
|
isCritical: Boolean(message?.system.roll?.isCritical)
|
||||||
};
|
};
|
||||||
delete damageConfig.evaluate;
|
delete damageConfig.evaluate;
|
||||||
|
|
||||||
|
|
@ -60,7 +64,7 @@ export default class DamageField extends fields.SchemaField {
|
||||||
damageConfig.dialog.configure = false;
|
damageConfig.dialog.configure = false;
|
||||||
if (config.hasSave) config.onSave = damageConfig.onSave = this.save.damageMod;
|
if (config.hasSave) config.onSave = damageConfig.onSave = this.save.damageMod;
|
||||||
|
|
||||||
damageConfig.source.message = config.message?._id ?? messageId;
|
damageConfig.source.message = messageId;
|
||||||
damageConfig.directDamage = !!damageConfig.source?.message;
|
damageConfig.directDamage = !!damageConfig.source?.message;
|
||||||
|
|
||||||
// if(damageConfig.source?.message && game.modules.get('dice-so-nice')?.active)
|
// if(damageConfig.source?.message && game.modules.get('dice-so-nice')?.active)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export default class EffectsField extends fields.ArrayField {
|
||||||
static async execute(config, targets = null, force = false) {
|
static async execute(config, targets = null, force = false) {
|
||||||
if (!config.hasEffect) return;
|
if (!config.hasEffect) return;
|
||||||
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
|
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
|
||||||
if (!message) {
|
if (!message && !config.skips.createMessage) {
|
||||||
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
||||||
roll._evaluated = true;
|
roll._evaluated = true;
|
||||||
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
||||||
|
|
@ -106,22 +106,11 @@ export default class EffectsField extends fields.ArrayField {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply an Effect to a target or enable it if already on it
|
* Apply an Effect to a target
|
||||||
* @param {object} effect Effect object containing ActiveEffect UUID
|
* @param {object} effect Effect object containing ActiveEffect UUID
|
||||||
* @param {object} actor Actor Document
|
* @param {object} actor Actor Document
|
||||||
*/
|
*/
|
||||||
static async applyEffect(effect, actor) {
|
static async applyEffect(effect, actor) {
|
||||||
const existingEffect = actor.effects.find(e => e.origin === effect.uuid);
|
|
||||||
if (existingEffect) {
|
|
||||||
return effect.update(
|
|
||||||
foundry.utils.mergeObject({
|
|
||||||
...effect.constructor.getInitialDuration(),
|
|
||||||
disabled: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, create a new effect on the target
|
|
||||||
const effectData = foundry.utils.mergeObject({
|
const effectData = foundry.utils.mergeObject({
|
||||||
...(effect.toObject?.() ?? effect),
|
...(effect.toObject?.() ?? effect),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export default class SaveField extends fields.SchemaField {
|
||||||
if (!config.hasSave) return;
|
if (!config.hasSave) return;
|
||||||
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
|
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
|
||||||
|
|
||||||
if (!message) {
|
if (!message && !config.skips.createMessage) {
|
||||||
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
||||||
roll._evaluated = true;
|
roll._evaluated = true;
|
||||||
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import DHActionConfig from '../../applications/sheets-configs/action-config.mjs';
|
import DHActionConfig from '../../applications/sheets-configs/action-config.mjs';
|
||||||
import { itemAbleRollParse } from '../../helpers/utils.mjs';
|
import { itemAbleRollParse } from '../../helpers/utils.mjs';
|
||||||
import MappingField from './mappingField.mjs';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specialized collection type for stored actions.
|
* Specialized collection type for stored actions.
|
||||||
|
|
@ -11,9 +10,9 @@ export class ActionCollection extends Collection {
|
||||||
constructor(model, entries) {
|
constructor(model, entries) {
|
||||||
super();
|
super();
|
||||||
this.#model = model;
|
this.#model = model;
|
||||||
for (const entry of entries) {
|
for (const [key, value] of entries) {
|
||||||
if (!(entry instanceof game.system.api.models.actions.actionsTypes.base)) continue;
|
if (!(value instanceof game.system.api.models.actions.actionsTypes.base)) continue;
|
||||||
this.set(entry._id, entry);
|
this.set(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,7 +60,7 @@ export class ActionCollection extends Collection {
|
||||||
/**
|
/**
|
||||||
* Field that stores actions.
|
* Field that stores actions.
|
||||||
*/
|
*/
|
||||||
export class ActionsField extends MappingField {
|
export class ActionsField extends foundry.data.fields.TypedObjectField {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(new ActionField(), options);
|
super(new ActionField(), options);
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +69,7 @@ export class ActionsField extends MappingField {
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
initialize(value, model, options) {
|
initialize(value, model, options) {
|
||||||
const actions = Object.values(super.initialize(value, model, options));
|
const actions = Object.entries(super.initialize(value, model, options));
|
||||||
return new ActionCollection(model, actions);
|
return new ActionCollection(model, actions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -88,10 +87,11 @@ export class ActionField extends foundry.data.fields.ObjectField {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
_cleanType(value, options) {
|
_cleanType(value, options, _state) {
|
||||||
if (!(typeof value === 'object')) value = {};
|
if (!(typeof value === 'object')) value = {};
|
||||||
|
value = super._cleanType(value, options, _state);
|
||||||
const cls = this.getModel(value);
|
const cls = this.getModel(value);
|
||||||
if (cls) return cls.cleanData(value, options);
|
if (cls) return cls.cleanData(value, options, _state);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,9 +111,17 @@ export class ActionField extends foundry.data.fields.ObjectField {
|
||||||
* @param {object} sourceData Candidate source data of the root model.
|
* @param {object} sourceData Candidate source data of the root model.
|
||||||
* @param {any} fieldData The value of this field within the source data.
|
* @param {any} fieldData The value of this field within the source data.
|
||||||
*/
|
*/
|
||||||
migrateSource(sourceData, fieldData) {
|
_migrate(sourceData, _fieldData) {
|
||||||
const cls = this.getModel(fieldData);
|
const source = sourceData ?? this.options.initial;
|
||||||
if (cls) cls.migrateDataSafe(fieldData);
|
if (!source) return sourceData;
|
||||||
|
|
||||||
|
const cls = this.getModel(source);
|
||||||
|
if (cls) {
|
||||||
|
cls.migrateDataSafe(source);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,11 +245,11 @@ export function ActionMixin(Base) {
|
||||||
: foundry.utils.getProperty(result, basePath);
|
: foundry.utils.getProperty(result, basePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete() {
|
async delete() {
|
||||||
if (!this.inCollection) return this.item;
|
if (!this.inCollection) return this.item;
|
||||||
const action = foundry.utils.getProperty(this.item, `system.${this.systemPath}`)?.get(this.id);
|
const action = foundry.utils.getProperty(this.item, `system.${this.systemPath}`)?.get(this.id);
|
||||||
if (!action) return this.item;
|
if (!action) return this.item;
|
||||||
this.item.update({ [`system.${this.systemPath}.-=${this.id}`]: null });
|
await this.item.update({ [`system.${this.systemPath}.${this.id}`]: _del }); // Does not work. Unsure why. It worked in v13 <_<'
|
||||||
this.constructor._sheets.get(this.uuid)?.close();
|
this.constructor._sheets.get(this.uuid)?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,7 +310,7 @@ export function ActionMixin(Base) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ChatMessage.applyRollMode(msg, game.settings.get('core', 'rollMode'));
|
ChatMessage.applyMode(msg, game.settings.get('core', 'messageMode'));
|
||||||
cls.create(msg);
|
cls.create(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ class ResourcesField extends fields.TypedObjectField {
|
||||||
return key in CONFIG.DH.RESOURCE[this.actorType].all;
|
return key in CONFIG.DH.RESOURCE[this.actorType].all;
|
||||||
}
|
}
|
||||||
|
|
||||||
_cleanType(value, options) {
|
_cleanType(value, options, _state) {
|
||||||
value = super._cleanType(value, options);
|
value = super._cleanType(value, options, _state);
|
||||||
|
|
||||||
// If not partial, ensure all data exists
|
// If not partial, ensure all data exists
|
||||||
if (!options.partial) {
|
if (!options.partial) {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default class ForeignDocumentUUIDArrayField extends foundry.data.fields.A
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
initialize(value, model, options = {}) {
|
initialize(value, model, options = {}) {
|
||||||
const v = super.initialize(value, model, options);
|
const v = super.initialize(value ?? [], model, options);
|
||||||
return () => {
|
return () => {
|
||||||
const data = v.map(entry => (typeof entry === 'function' ? entry() : entry));
|
const data = v.map(entry => (typeof entry === 'function' ? entry() : entry));
|
||||||
return this.options.prune ? data.filter(d => !!d) : data;
|
return this.options.prune ? data.filter(d => !!d) : data;
|
||||||
|
|
|
||||||
32
module/data/fields/iterableTypedObjectField.mjs
Normal file
32
module/data/fields/iterableTypedObjectField.mjs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
export default class IterableTypedObjectField extends foundry.data.fields.TypedObjectField {
|
||||||
|
constructor(model, options = { collectionClass: foundry.utils.Collection }, context = {}) {
|
||||||
|
super(new foundry.data.fields.EmbeddedDataField(model), options, context);
|
||||||
|
this.#elementClass = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elementClass;
|
||||||
|
|
||||||
|
/** Initializes an object with an iterator. This modifies the prototype instead of */
|
||||||
|
initialize(values) {
|
||||||
|
const object = Object.create(IterableObjectPrototype);
|
||||||
|
for (const [key, value] of Object.entries(values)) {
|
||||||
|
object[key] = new this.#elementClass(value);
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The prototype of an iterable object.
|
||||||
|
* This allows the functionality of a class but also allows foundry.utils.getType() to return "Object" instead of "Unknown".
|
||||||
|
*/
|
||||||
|
const IterableObjectPrototype = {
|
||||||
|
[Symbol.iterator]: function* () {
|
||||||
|
for (const value of Object.values(this)) {
|
||||||
|
yield value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
map: function (func) {
|
||||||
|
return Array.from(this, func);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
/**
|
|
||||||
* A subclass of ObjectField that represents a mapping of keys to the provided DataField type.
|
|
||||||
*
|
|
||||||
* @param {DataField} model The class of DataField which should be embedded in this field.
|
|
||||||
* @param {MappingFieldOptions} [options={}] Options which configure the behavior of the field.
|
|
||||||
* @property {string[]} [initialKeys] Keys that will be created if no data is provided.
|
|
||||||
* @property {MappingFieldInitialValueBuilder} [initialValue] Function to calculate the initial value for a key.
|
|
||||||
* @property {boolean} [initialKeysOnly=false] Should the keys in the initialized data be limited to the keys provided
|
|
||||||
* by `options.initialKeys`?
|
|
||||||
*/
|
|
||||||
export default class MappingField extends foundry.data.fields.ObjectField {
|
|
||||||
constructor(model, options) {
|
|
||||||
if (!(model instanceof foundry.data.fields.DataField)) {
|
|
||||||
throw new Error('MappingField must have a DataField as its contained element');
|
|
||||||
}
|
|
||||||
super(options);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The embedded DataField definition which is contained in this field.
|
|
||||||
* @type {DataField}
|
|
||||||
*/
|
|
||||||
this.model = model;
|
|
||||||
model.parent = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @inheritDoc */
|
|
||||||
static get _defaults() {
|
|
||||||
return foundry.utils.mergeObject(super._defaults, {
|
|
||||||
initialKeys: null,
|
|
||||||
initialValue: null,
|
|
||||||
initialKeysOnly: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @inheritDoc */
|
|
||||||
_cleanType(value, options) {
|
|
||||||
Object.entries(value).forEach(([k, v]) => {
|
|
||||||
if (k.startsWith('-=')) return;
|
|
||||||
value[k] = this.model.clean(v, options);
|
|
||||||
});
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @inheritDoc */
|
|
||||||
getInitialValue(data) {
|
|
||||||
let keys = this.initialKeys;
|
|
||||||
const initial = super.getInitialValue(data);
|
|
||||||
if (!keys || !foundry.utils.isEmpty(initial)) return initial;
|
|
||||||
if (!(keys instanceof Array)) keys = Object.keys(keys);
|
|
||||||
for (const key of keys) initial[key] = this._getInitialValueForKey(key);
|
|
||||||
return initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the initial value for the provided key.
|
|
||||||
* @param {string} key Key within the object being built.
|
|
||||||
* @param {object} [object] Any existing mapping data.
|
|
||||||
* @returns {*} Initial value based on provided field type.
|
|
||||||
*/
|
|
||||||
_getInitialValueForKey(key, object) {
|
|
||||||
const initial = this.model.getInitialValue();
|
|
||||||
return this.initialValue?.(key, initial, object) ?? initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
_validateType(value, options = {}) {
|
|
||||||
if (foundry.utils.getType(value) !== 'Object') throw new Error('must be an Object');
|
|
||||||
const errors = this._validateValues(value, options);
|
|
||||||
if (!foundry.utils.isEmpty(errors)) {
|
|
||||||
const failure = new foundry.data.validation.DataModelValidationFailure();
|
|
||||||
failure.elements = Object.entries(errors).map(([id, failure]) => ({ id, failure }));
|
|
||||||
throw failure.asError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate each value of the object.
|
|
||||||
* @param {object} value The object to validate.
|
|
||||||
* @param {object} options Validation options.
|
|
||||||
* @returns {Record<string, Error>} An object of value-specific errors by key.
|
|
||||||
*/
|
|
||||||
_validateValues(value, options) {
|
|
||||||
const errors = {};
|
|
||||||
for (const [k, v] of Object.entries(value)) {
|
|
||||||
if (k.startsWith('-=')) continue;
|
|
||||||
const error = this.model.validate(v, options);
|
|
||||||
if (error) errors[k] = error;
|
|
||||||
}
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
initialize(value, model, options = {}) {
|
|
||||||
if (!value) return value;
|
|
||||||
const obj = {};
|
|
||||||
const initialKeys = this.initialKeys instanceof Array ? this.initialKeys : Object.keys(this.initialKeys ?? {});
|
|
||||||
const keys = this.initialKeysOnly ? initialKeys : Object.keys(value);
|
|
||||||
for (const key of keys) {
|
|
||||||
const data = value[key] ?? this._getInitialValueForKey(key, value);
|
|
||||||
obj[key] = this.model.initialize(data, model, options);
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @inheritDoc */
|
|
||||||
_getField(path) {
|
|
||||||
if (path.length === 0) return this;
|
|
||||||
else if (path.length === 1) return this.model;
|
|
||||||
path.shift();
|
|
||||||
return this.model._getField(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -19,7 +19,14 @@ export default class DHArmor extends AttachableItem {
|
||||||
...super.defineSchema(),
|
...super.defineSchema(),
|
||||||
tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }),
|
tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }),
|
||||||
equipped: new fields.BooleanField({ initial: false }),
|
equipped: new fields.BooleanField({ initial: false }),
|
||||||
baseScore: new fields.NumberField({ integer: true, initial: 0 }),
|
armor: new fields.SchemaField({
|
||||||
|
current: new fields.NumberField({ integer: true, min: 0, initial: 0 }),
|
||||||
|
max: new fields.NumberField({ required: true, integer: true, initial: 0 })
|
||||||
|
}),
|
||||||
|
baseThresholds: new fields.SchemaField({
|
||||||
|
major: new fields.NumberField({ integer: true, initial: 0 }),
|
||||||
|
severe: new fields.NumberField({ integer: true, initial: 0 })
|
||||||
|
}),
|
||||||
armorFeatures: new fields.ArrayField(
|
armorFeatures: new fields.ArrayField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
value: new fields.StringField({
|
value: new fields.StringField({
|
||||||
|
|
@ -28,14 +35,7 @@ export default class DHArmor extends AttachableItem {
|
||||||
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
|
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||||
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
|
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
|
||||||
})
|
})
|
||||||
),
|
)
|
||||||
marks: new fields.SchemaField({
|
|
||||||
value: new fields.NumberField({ initial: 0, integer: true })
|
|
||||||
}),
|
|
||||||
baseThresholds: new fields.SchemaField({
|
|
||||||
major: new fields.NumberField({ integer: true, initial: 0 }),
|
|
||||||
severe: new fields.NumberField({ integer: true, initial: 0 })
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ export default class DHArmor extends AttachableItem {
|
||||||
}
|
}
|
||||||
await this.parent.deleteEmbeddedDocuments('ActiveEffect', effectIds);
|
await this.parent.deleteEmbeddedDocuments('ActiveEffect', effectIds);
|
||||||
changes.system.actions = actionIds.reduce((acc, id) => {
|
changes.system.actions = actionIds.reduce((acc, id) => {
|
||||||
acc[`-=${id}`] = null;
|
acc[id] = _del;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
|
@ -151,13 +151,20 @@ export default class DHArmor extends AttachableItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
static migrateDocumentData(source) {
|
||||||
|
if (!source.system.armor) {
|
||||||
|
source.system.armor = { current: source.system.marks?.value ?? 0, max: source.system.baseScore ?? 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a list of localized tags based on this item's type-specific properties.
|
* Generates a list of localized tags based on this item's type-specific properties.
|
||||||
* @returns {string[]} An array of localized tag strings.
|
* @returns {string[]} An array of localized tag strings.
|
||||||
*/
|
*/
|
||||||
_getTags() {
|
_getTags() {
|
||||||
const tags = [
|
const tags = [
|
||||||
`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`,
|
`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`,
|
||||||
`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseThresholds.base')}: ${this.baseThresholds.major} / ${this.baseThresholds.severe}`
|
`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseThresholds.base')}: ${this.baseThresholds.major} / ${this.baseThresholds.severe}`
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -169,9 +176,7 @@ export default class DHArmor extends AttachableItem {
|
||||||
* @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects.
|
* @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects.
|
||||||
*/
|
*/
|
||||||
_getLabels() {
|
_getLabels() {
|
||||||
const labels = [];
|
const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`];
|
||||||
if (this.baseScore)
|
|
||||||
labels.push(`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`);
|
|
||||||
return labels;
|
return labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -222,17 +222,22 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
const autoSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
const autoSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||||
const armorChanged =
|
const armorChanged =
|
||||||
changed.system?.marks?.value !== undefined && changed.system.marks.value !== this.marks.value;
|
changed.system?.armor?.current !== undefined && changed.system.armor.current !== this.armor.current;
|
||||||
if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') {
|
if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') {
|
||||||
const armorData = getScrollTextData(this.parent.parent, changed.system.marks, 'armor');
|
const armorChangeValue = changed.system.armor.current - this.armor.current;
|
||||||
|
const armorData = getScrollTextData(
|
||||||
|
this.parent.parent,
|
||||||
|
{ value: armorChangeValue + this.parent.parent.system.armorScore.value },
|
||||||
|
'armor'
|
||||||
|
);
|
||||||
options.scrollingTextData = [armorData];
|
options.scrollingTextData = [armorData];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed.system?.actions) {
|
if (changed.system?.actions) {
|
||||||
const triggersToRemove = Object.keys(changed.system.actions).reduce((acc, key) => {
|
const triggersToRemove = Object.keys(changed.system.actions).reduce((acc, key) => {
|
||||||
if (!changed.system.actions[key]) {
|
const action = changed.system.actions[key];
|
||||||
const strippedKey = key.replace('-=', '');
|
if (action && Object.keys(action).length === 0) {
|
||||||
acc.push(...this.actions.get(strippedKey).triggers.map(x => x.trigger));
|
acc.push(...this.actions.get(key).triggers.map(x => x.trigger));
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
|
|
|
||||||
|
|
@ -99,17 +99,22 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
|
|
||||||
get beastformAttackData() {
|
get beastformAttackData() {
|
||||||
const effect = this.parent.effects.find(x => x.type === 'beastform');
|
const effect = this.parent.effects.find(x => x.type === 'beastform');
|
||||||
|
return DHBeastform.getBeastformAttackData(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getBeastformAttackData(effect) {
|
||||||
if (!effect) return null;
|
if (!effect) return null;
|
||||||
|
|
||||||
const traitBonus = effect.changes.find(x => x.key === `system.traits.${this.mainTrait}.value`)?.value ?? 0;
|
const mainTrait = effect.system.changes.find(x => x.key === 'system.rules.attack.roll.trait')?.value;
|
||||||
const evasionBonus = effect.changes.find(x => x.key === 'system.evasion')?.value ?? 0;
|
const traitBonus = effect.system.changes.find(x => x.key === `system.traits.${mainTrait}.value`)?.value ?? 0;
|
||||||
|
const evasionBonus = effect.system.changes.find(x => x.key === 'system.evasion')?.value ?? 0;
|
||||||
|
|
||||||
const damageDiceIndex = effect.changes.find(x => x.key === 'system.rules.attack.damage.diceIndex');
|
const damageDiceIndex = effect.system.changes.find(x => x.key === 'system.rules.attack.damage.diceIndex');
|
||||||
const damageDice = damageDiceIndex ? Object.keys(CONFIG.DH.GENERAL.diceTypes)[damageDiceIndex.value] : null;
|
const damageDice = damageDiceIndex ? Object.keys(CONFIG.DH.GENERAL.diceTypes)[damageDiceIndex.value] : null;
|
||||||
const damageBonus = effect.changes.find(x => x.key === 'system.rules.attack.damage.bonus')?.value ?? 0;
|
const damageBonus = effect.system.changes.find(x => x.key === 'system.rules.attack.damage.bonus')?.value ?? 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
trait: game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.mainTrait].label),
|
trait: game.i18n.localize(CONFIG.DH.ACTOR.abilities[mainTrait]?.label),
|
||||||
traitBonus: traitBonus ? Number(traitBonus).signedString() : '',
|
traitBonus: traitBonus ? Number(traitBonus).signedString() : '',
|
||||||
evasionBonus: evasionBonus ? Number(evasionBonus).signedString() : '',
|
evasionBonus: evasionBonus ? Number(evasionBonus).signedString() : '',
|
||||||
damageDice: damageDice,
|
damageDice: damageDice,
|
||||||
|
|
@ -169,17 +174,17 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
|
|
||||||
const beastformEffect = this.parent.effects.find(x => x.type === 'beastform');
|
const beastformEffect = this.parent.effects.find(x => x.type === 'beastform');
|
||||||
await beastformEffect.updateSource({
|
await beastformEffect.updateSource({
|
||||||
changes: [
|
|
||||||
...beastformEffect.changes,
|
|
||||||
{
|
|
||||||
key: 'system.advantageSources',
|
|
||||||
mode: 2,
|
|
||||||
value: Object.values(this.advantageOn)
|
|
||||||
.map(x => x.value)
|
|
||||||
.join(', ')
|
|
||||||
}
|
|
||||||
],
|
|
||||||
system: {
|
system: {
|
||||||
|
changes: [
|
||||||
|
...beastformEffect.system.changes,
|
||||||
|
{
|
||||||
|
key: 'system.advantageSources',
|
||||||
|
mode: 2,
|
||||||
|
value: Object.values(this.advantageOn)
|
||||||
|
.map(x => x.value)
|
||||||
|
.join(', ')
|
||||||
|
}
|
||||||
|
],
|
||||||
characterTokenData: {
|
characterTokenData: {
|
||||||
usesDynamicToken: this.parent.parent.prototypeToken.ring.enabled,
|
usesDynamicToken: this.parent.parent.prototypeToken.ring.enabled,
|
||||||
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,9 @@ export default class DHSubclass extends BaseDataItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preCreate(data, options, user) {
|
async _preCreate(data, options, user) {
|
||||||
|
const allowed = await super._preCreate(data, options, user);
|
||||||
|
if (allowed === false) return;
|
||||||
|
|
||||||
if (this.actor?.type === 'character') {
|
if (this.actor?.type === 'character') {
|
||||||
const dataUuid = data.uuid ?? data._stats.compendiumSource ?? `Item.${data._id}`;
|
const dataUuid = data.uuid ?? data._stats.compendiumSource ?? `Item.${data._id}`;
|
||||||
if (this.actor.system.class.subclass) {
|
if (this.actor.system.class.subclass) {
|
||||||
|
|
@ -86,9 +89,6 @@ export default class DHSubclass extends BaseDataItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowed = await super._preCreate(data, options, user);
|
|
||||||
if (allowed === false) return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
|
|
|
||||||
|
|
@ -63,15 +63,15 @@ export default class DHWeapon extends AttachableItem {
|
||||||
type: 'attack'
|
type: 'attack'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hitPoints: {
|
||||||
type: ['physical'],
|
type: ['physical'],
|
||||||
value: {
|
value: {
|
||||||
multiplier: 'prof',
|
multiplier: 'prof',
|
||||||
dice: 'd8'
|
dice: 'd8'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
@ -148,7 +148,7 @@ export default class DHWeapon extends AttachableItem {
|
||||||
|
|
||||||
await this.parent.deleteEmbeddedDocuments('ActiveEffect', removedEffectsUpdate);
|
await this.parent.deleteEmbeddedDocuments('ActiveEffect', removedEffectsUpdate);
|
||||||
changes.system.actions = removedActionsUpdate.reduce((acc, id) => {
|
changes.system.actions = removedActionsUpdate.reduce((acc, id) => {
|
||||||
acc[`-=${id}`] = null;
|
acc[id] = _del;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,11 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
autoExpireActiveEffects: new fields.BooleanField({
|
||||||
|
required: true,
|
||||||
|
initial: true,
|
||||||
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.autoExpireActiveEffects.label'
|
||||||
|
}),
|
||||||
triggers: new fields.SchemaField({
|
triggers: new fields.SchemaField({
|
||||||
enabled: new fields.BooleanField({
|
enabled: new fields.BooleanField({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
|
|
|
||||||
9
module/data/spotlightTracker.mjs
Normal file
9
module/data/spotlightTracker.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
export default class SpotlightTracker extends foundry.abstract.DataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
return {
|
||||||
|
spotlightedTokens: new fields.SetField(new fields.DocumentUUIDField())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
47
module/data/tagTeamData.mjs
Normal file
47
module/data/tagTeamData.mjs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
export default class TagTeamData extends foundry.abstract.DataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
return {
|
||||||
|
initiator: new fields.SchemaField(
|
||||||
|
{
|
||||||
|
memberId: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.TagTeamSelect.FIELDS.initiator.memberId.label'
|
||||||
|
}),
|
||||||
|
cost: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
initial: 3,
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.TagTeamSelect.FIELDS.initiator.cost.label'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ nullable: true, initial: null }
|
||||||
|
),
|
||||||
|
members: new fields.TypedObjectField(new fields.EmbeddedDataField(MemberData))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MemberData extends foundry.abstract.DataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: new fields.StringField({ required: true }),
|
||||||
|
img: new fields.StringField({ required: true }),
|
||||||
|
rollType: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.tagTeamRollTypes,
|
||||||
|
initial: CONFIG.DH.GENERAL.tagTeamRollTypes.trait.id,
|
||||||
|
label: 'Roll Type'
|
||||||
|
}),
|
||||||
|
rollChoice: new fields.StringField({ nullable: true, initial: null }),
|
||||||
|
rollData: new fields.JSONField({ nullable: true, initial: null }),
|
||||||
|
selected: new fields.BooleanField({ initial: false })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get roll() {
|
||||||
|
return this.rollData ? CONFIG.Dice.daggerheart.DualityRoll.fromData(this.rollData) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { DhCharacter } from './actor/_module.mjs';
|
|
||||||
|
|
||||||
export default class DhTagTeamRoll extends foundry.abstract.DataModel {
|
|
||||||
static defineSchema() {
|
|
||||||
const fields = foundry.data.fields;
|
|
||||||
|
|
||||||
return {
|
|
||||||
initiator: new fields.SchemaField({
|
|
||||||
id: new fields.StringField({ nullable: true, initial: null }),
|
|
||||||
cost: new fields.NumberField({ integer: true, min: 0, initial: 3 })
|
|
||||||
}),
|
|
||||||
members: new fields.TypedObjectField(
|
|
||||||
new fields.SchemaField({
|
|
||||||
messageId: new fields.StringField({ required: true, nullable: true, initial: null }),
|
|
||||||
selected: new fields.BooleanField({ required: true, initial: false })
|
|
||||||
})
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,3 +4,4 @@ export { default as DamageRoll } from './damageRoll.mjs';
|
||||||
export { default as DHRoll } from './dhRoll.mjs';
|
export { default as DHRoll } from './dhRoll.mjs';
|
||||||
export { default as DualityRoll } from './dualityRoll.mjs';
|
export { default as DualityRoll } from './dualityRoll.mjs';
|
||||||
export { default as FateRoll } from './fateRoll.mjs';
|
export { default as FateRoll } from './fateRoll.mjs';
|
||||||
|
export { diceTypes } from './die/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export default class D20Roll extends DHRoll {
|
||||||
get isCritical() {
|
get isCritical() {
|
||||||
if (!this.d20._evaluated) return;
|
if (!this.d20._evaluated) return;
|
||||||
|
|
||||||
const criticalThreshold = this.options.actionType === 'reaction' ? 20 : this.data.system.criticalThreshold;
|
const criticalThreshold = this.options.actionType === 'reaction' ? 20 : this.data.criticalThreshold;
|
||||||
return this.d20.total >= criticalThreshold;
|
return this.d20.total >= criticalThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,7 +207,7 @@ export default class D20Roll extends DHRoll {
|
||||||
rerolls: dice.results.filter(x => x.rerolled)
|
rerolls: dice.results.filter(x => x.rerolled)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
data.isCritical = config.isCritical = roll.isCritical;
|
data.isCritical = roll.isCritical;
|
||||||
data.extra = roll.dice
|
data.extra = roll.dice
|
||||||
.filter(d => !roll.baseTerms.includes(d))
|
.filter(d => !roll.baseTerms.includes(d))
|
||||||
.map(d => {
|
.map(d => {
|
||||||
|
|
@ -217,49 +217,11 @@ export default class D20Roll extends DHRoll {
|
||||||
results: d.results
|
results: d.results
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
data.modifierTotal = this.calculateTotalModifiers(roll);
|
data.modifierTotal = roll.modifierTotal;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetFormula() {
|
resetFormula() {
|
||||||
return (this._formula = this.constructor.getFormula(this.terms));
|
return (this._formula = this.constructor.getFormula(this.terms));
|
||||||
}
|
}
|
||||||
|
|
||||||
static async reroll(rollString, _target, message) {
|
|
||||||
let parsedRoll = game.system.api.dice.D20Roll.fromData(rollString);
|
|
||||||
parsedRoll = await parsedRoll.reroll();
|
|
||||||
const newRoll = game.system.api.dice.D20Roll.postEvaluate(parsedRoll, {
|
|
||||||
targets: message.system.targets,
|
|
||||||
roll: {
|
|
||||||
advantage: message.system.roll.advantage?.type,
|
|
||||||
difficulty: message.system.roll.difficulty ? Number(message.system.roll.difficulty) : null
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (game.modules.get('dice-so-nice')?.active) {
|
|
||||||
await game.dice3d.showForRoll(parsedRoll, game.user, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rerolled = {
|
|
||||||
any: true,
|
|
||||||
rerolls: [
|
|
||||||
...(message.system.roll.dice[0].rerolled?.rerolls?.length > 0
|
|
||||||
? [message.system.roll.dice[0].rerolled?.rerolls]
|
|
||||||
: []),
|
|
||||||
rollString.terms[0].results
|
|
||||||
]
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
newRoll: {
|
|
||||||
...newRoll,
|
|
||||||
dice: [
|
|
||||||
{
|
|
||||||
...newRoll.dice[0],
|
|
||||||
rerolled: rerolled
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
parsedRoll
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import DamageDialog from '../applications/dialogs/damageDialog.mjs';
|
import DamageDialog from '../applications/dialogs/damageDialog.mjs';
|
||||||
import { parseRallyDice } from '../helpers/utils.mjs';
|
import { parseRallyDice } from '../helpers/utils.mjs';
|
||||||
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
|
|
||||||
import DHRoll from './dhRoll.mjs';
|
import DHRoll from './dhRoll.mjs';
|
||||||
|
|
||||||
export default class DamageRoll extends DHRoll {
|
export default class DamageRoll extends DHRoll {
|
||||||
|
|
@ -8,6 +7,10 @@ export default class DamageRoll extends DHRoll {
|
||||||
super(formula, data, options);
|
super(formula, data, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isCritical() {
|
||||||
|
return !!this.options.isCritical;
|
||||||
|
}
|
||||||
|
|
||||||
static DefaultDialog = DamageDialog;
|
static DefaultDialog = DamageDialog;
|
||||||
|
|
||||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||||
|
|
@ -34,7 +37,7 @@ export default class DamageRoll extends DHRoll {
|
||||||
static async buildPost(roll, config, message) {
|
static async buildPost(roll, config, message) {
|
||||||
const chatMessage = config.source?.message
|
const chatMessage = config.source?.message
|
||||||
? ui.chat.collection.get(config.source.message)
|
? ui.chat.collection.get(config.source.message)
|
||||||
: getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode ?? CONST.DICE_ROLL_MODES.PUBLIC);
|
: getDocumentClass('ChatMessage').applyMode({}, config.rollMode ?? 'public');
|
||||||
if (game.modules.get('dice-so-nice')?.active) {
|
if (game.modules.get('dice-so-nice')?.active) {
|
||||||
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
||||||
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
||||||
|
|
@ -139,57 +142,53 @@ export default class DamageRoll extends DHRoll {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructFormula(config) {
|
constructFormula(config) {
|
||||||
this.options.roll.forEach((part, index) => {
|
this.options.isCritical = config.isCritical;
|
||||||
|
for (const [index, part] of this.options.roll.entries()) {
|
||||||
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
||||||
this.constructFormulaPart(config, part, index);
|
part.roll.terms = Roll.parse(part.roll.formula, config.data);
|
||||||
});
|
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||||
|
part.modifiers = this.applyBaseBonus(part);
|
||||||
|
this.addModifiers(part);
|
||||||
|
part.modifiers?.forEach(m => {
|
||||||
|
part.roll.terms.push(...this.formatModifier(m.value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To Remove When Reaction System */
|
||||||
|
if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||||
|
for (const mod in config.modifiers) {
|
||||||
|
const modifier = config.modifiers[mod];
|
||||||
|
if (modifier.beforeCrit === true && (modifier.enabled || modifier.value)) modifier.callback(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.extraFormula) {
|
||||||
|
part.roll.terms.push(
|
||||||
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
|
...this.constructor.parse(part.extraFormula, this.options.data)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.isCritical && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||||
|
const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
|
||||||
|
if (total > 0) {
|
||||||
|
part.roll.terms.push(...this.formatModifier(total));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To Remove When Reaction System */
|
||||||
|
if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||||
|
for (const mod in config.modifiers) {
|
||||||
|
const modifier = config.modifiers[mod];
|
||||||
|
if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
part.roll._formula = this.constructor.getFormula(part.roll.terms);
|
||||||
|
}
|
||||||
return this.options.roll;
|
return this.options.roll;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructFormulaPart(config, part, index) {
|
|
||||||
part.roll.terms = Roll.parse(part.roll.formula, config.data);
|
|
||||||
|
|
||||||
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
|
||||||
part.modifiers = this.applyBaseBonus(part);
|
|
||||||
this.addModifiers(part);
|
|
||||||
part.modifiers?.forEach(m => {
|
|
||||||
part.roll.terms.push(...this.formatModifier(m.value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* To Remove When Reaction System */
|
|
||||||
if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
|
||||||
for (const mod in config.modifiers) {
|
|
||||||
const modifier = config.modifiers[mod];
|
|
||||||
if (modifier.beforeCrit === true && (modifier.enabled || modifier.value)) modifier.callback(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.extraFormula) {
|
|
||||||
part.roll.terms.push(
|
|
||||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
|
||||||
...this.constructor.parse(part.extraFormula, this.options.data)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.isCritical && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
|
||||||
const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
|
|
||||||
if (total > 0) {
|
|
||||||
part.roll.terms.push(...this.formatModifier(total));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* To Remove When Reaction System */
|
|
||||||
if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
|
||||||
for (const mod in config.modifiers) {
|
|
||||||
const modifier = config.modifiers[mod];
|
|
||||||
if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (part.roll._formula = this.constructor.getFormula(part.roll.terms));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* To Remove When Reaction System */
|
/* To Remove When Reaction System */
|
||||||
static temporaryModifierBuilder(config) {
|
static temporaryModifierBuilder(config) {
|
||||||
const mods = {};
|
const mods = {};
|
||||||
|
|
@ -197,7 +196,7 @@ export default class DamageRoll extends DHRoll {
|
||||||
if (config.data.parent.appliedEffects) {
|
if (config.data.parent.appliedEffects) {
|
||||||
// Bardic Rally
|
// Bardic Rally
|
||||||
const rallyChoices = config.data?.parent?.appliedEffects.reduce((a, c) => {
|
const rallyChoices = config.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||||
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
const change = c.system.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||||
if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) });
|
if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) });
|
||||||
return a;
|
return a;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -281,10 +280,7 @@ export default class DamageRoll extends DHRoll {
|
||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async reroll(target, message) {
|
static async reroll(rollPart, dice, result) {
|
||||||
const { damageType, part, dice, result } = target.dataset;
|
|
||||||
const rollPart = message.system.damage[damageType].parts[part];
|
|
||||||
|
|
||||||
let diceIndex = 0;
|
let diceIndex = 0;
|
||||||
let parsedRoll = game.system.api.dice.DamageRoll.fromData({
|
let parsedRoll = game.system.api.dice.DamageRoll.fromData({
|
||||||
...rollPart.roll,
|
...rollPart.roll,
|
||||||
|
|
@ -353,29 +349,6 @@ export default class DamageRoll extends DHRoll {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateMessage = game.messages.get(message._id);
|
return { parsedRoll, rerolledDice };
|
||||||
const damageParts = updateMessage.system.damage[damageType].parts.map((damagePart, index) => {
|
|
||||||
if (index !== Number(part)) return damagePart;
|
|
||||||
return {
|
|
||||||
...rollPart,
|
|
||||||
total: parsedRoll.total,
|
|
||||||
dice: rerolledDice
|
|
||||||
};
|
|
||||||
});
|
|
||||||
await updateMessage.update({
|
|
||||||
[`system.damage.${damageType}`]: {
|
|
||||||
...updateMessage,
|
|
||||||
total: parsedRoll.total,
|
|
||||||
parts: damageParts
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
|
||||||
action: socketEvent.Refresh,
|
|
||||||
data: {
|
|
||||||
refreshType: RefreshType.TagTeamRoll
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@ export default class DHRoll extends Roll {
|
||||||
return game.i18n.localize('DAGGERHEART.GENERAL.Roll.basic');
|
return game.i18n.localize('DAGGERHEART.GENERAL.Roll.basic');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get modifierTotal() {
|
||||||
|
return this.constructor.calculateTotalModifiers(this);
|
||||||
|
}
|
||||||
|
|
||||||
static messageType = 'adversaryRoll';
|
static messageType = 'adversaryRoll';
|
||||||
|
|
||||||
static CHAT_TEMPLATE = 'systems/daggerheart/templates/ui/chat/roll.hbs';
|
static CHAT_TEMPLATE = 'systems/daggerheart/templates/ui/chat/roll.hbs';
|
||||||
|
|
@ -21,6 +25,9 @@ export default class DHRoll extends Roll {
|
||||||
static async build(config = {}, message = {}) {
|
static async build(config = {}, message = {}) {
|
||||||
const roll = await this.buildConfigure(config, message);
|
const roll = await this.buildConfigure(config, message);
|
||||||
if (!roll) return;
|
if (!roll) return;
|
||||||
|
|
||||||
|
if (config.skips?.createMessage) config.messageRoll = roll;
|
||||||
|
|
||||||
await this.buildEvaluate(roll, config, (message = {}));
|
await this.buildEvaluate(roll, config, (message = {}));
|
||||||
await this.buildPost(roll, config, (message = {}));
|
await this.buildPost(roll, config, (message = {}));
|
||||||
return config;
|
return config;
|
||||||
|
|
@ -30,12 +37,6 @@ export default class DHRoll extends Roll {
|
||||||
config.hooks = [...this.getHooks(), ''];
|
config.hooks = [...this.getHooks(), ''];
|
||||||
config.dialog ??= {};
|
config.dialog ??= {};
|
||||||
|
|
||||||
const actorIdSplit = config.source?.actor?.split('.');
|
|
||||||
if (actorIdSplit) {
|
|
||||||
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
|
||||||
config.tagTeamSelected = Boolean(tagTeamSettings.members[actorIdSplit[actorIdSplit.length - 1]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const hook of config.hooks) {
|
for (const hook of config.hooks) {
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
|
if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
|
||||||
}
|
}
|
||||||
|
|
@ -120,14 +121,10 @@ export default class DHRoll extends Roll {
|
||||||
rolls: [roll]
|
rolls: [roll]
|
||||||
};
|
};
|
||||||
|
|
||||||
config.selectedRollMode ??= game.settings.get('core', 'rollMode');
|
config.selectedMessageMode ??= game.settings.get('core', 'messageMode');
|
||||||
|
|
||||||
if (roll._evaluated) {
|
if (roll._evaluated) {
|
||||||
const message = await cls.create(msgData, { rollMode: config.selectedRollMode });
|
const message = await cls.create(msgData, { messageMode: config.selectedMessageMode });
|
||||||
|
|
||||||
if (config.tagTeamSelected) {
|
|
||||||
game.system.api.applications.dialogs.TagTeamDialog.assignRoll(message.speakerActor, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (roll.formula !== '' && game.modules.get('dice-so-nice')?.active) {
|
if (roll.formula !== '' && game.modules.get('dice-so-nice')?.active) {
|
||||||
await game.dice3d.waitFor3DAnimationByMessageID(message.id);
|
await game.dice3d.waitFor3DAnimationByMessageID(message.id);
|
||||||
|
|
@ -145,6 +142,7 @@ export default class DHRoll extends Roll {
|
||||||
const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options });
|
const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options });
|
||||||
return foundry.applications.handlebars.renderTemplate(template, {
|
return foundry.applications.handlebars.renderTemplate(template, {
|
||||||
...chatData,
|
...chatData,
|
||||||
|
roll: this,
|
||||||
parent: chatData.parent,
|
parent: chatData.parent,
|
||||||
targetMode: chatData.targetMode,
|
targetMode: chatData.targetMode,
|
||||||
metagamingSettings
|
metagamingSettings
|
||||||
|
|
@ -248,16 +246,21 @@ export default class DHRoll extends Roll {
|
||||||
return (this._formula = this.constructor.getFormula(this.terms));
|
return (this._formula = this.constructor.getFormula(this.terms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate total modifiers of any rolls, including non-dh rolls.
|
||||||
|
* This exists because damage rolls still may receive base roll classes
|
||||||
|
*/
|
||||||
static calculateTotalModifiers(roll) {
|
static calculateTotalModifiers(roll) {
|
||||||
let modifierTotal = 0;
|
let modifierTotal = 0;
|
||||||
for (let i = 0; i < roll.terms.length; i++) {
|
for (let i = 0; i < roll.terms.length; i++) {
|
||||||
if (
|
if (!roll.terms[i].isDeterministic) continue;
|
||||||
roll.terms[i] instanceof foundry.dice.terms.NumericTerm &&
|
const termTotal = roll.terms[i].total;
|
||||||
!!roll.terms[i - 1] &&
|
if (typeof termTotal === 'number') {
|
||||||
roll.terms[i - 1] instanceof foundry.dice.terms.OperatorTerm
|
const multiplier = roll.terms[i - 1]?.operator === " - " ? -1 : 1;
|
||||||
)
|
modifierTotal += multiplier * termTotal;
|
||||||
modifierTotal += Number(`${roll.terms[i - 1].operator}${roll.terms[i].total}`);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return modifierTotal;
|
return modifierTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,12 +272,12 @@ export default class DHRoll extends Roll {
|
||||||
const changeKeys = this.getActionChangeKeys();
|
const changeKeys = this.getActionChangeKeys();
|
||||||
return (
|
return (
|
||||||
this.options.effects?.reduce((acc, effect) => {
|
this.options.effects?.reduce((acc, effect) => {
|
||||||
if (effect.changes.some(x => changeKeys.some(key => x.key.includes(key)))) {
|
if (effect.system.changes.some(x => changeKeys.some(key => x.key.includes(key)))) {
|
||||||
acc[effect.id] = {
|
acc[effect.id] = {
|
||||||
id: effect.id,
|
id: effect.id,
|
||||||
name: effect.name,
|
name: effect.name,
|
||||||
description: effect.description,
|
description: effect.description,
|
||||||
changes: effect.changes,
|
changes: effect.system.changes,
|
||||||
origEffect: effect,
|
origEffect: effect,
|
||||||
selected: !effect.disabled
|
selected: !effect.disabled
|
||||||
};
|
};
|
||||||
|
|
|
||||||
9
module/dice/die/_module.mjs
Normal file
9
module/dice/die/_module.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import DualityDie from './dualityDie.mjs';
|
||||||
|
import AdvantageDie from './advantageDie.mjs';
|
||||||
|
import DisadvantageDie from './disadvantageDie.mjs';
|
||||||
|
|
||||||
|
export const diceTypes = {
|
||||||
|
DualityDie,
|
||||||
|
AdvantageDie,
|
||||||
|
DisadvantageDie
|
||||||
|
};
|
||||||
7
module/dice/die/advantageDie.mjs
Normal file
7
module/dice/die/advantageDie.mjs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default class AdvantageDie extends foundry.dice.terms.Die {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.modifiers = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
7
module/dice/die/disadvantageDie.mjs
Normal file
7
module/dice/die/disadvantageDie.mjs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default class DisadvantageDie extends foundry.dice.terms.Die {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.modifiers = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
62
module/dice/die/dualityDie.mjs
Normal file
62
module/dice/die/dualityDie.mjs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||||
|
|
||||||
|
export default class DualityDie extends foundry.dice.terms.Die {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.modifiers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
#getDualityState(roll) {
|
||||||
|
if (!roll) return null;
|
||||||
|
return roll.withHope ? 1 : roll.withFear ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateResources(oldDuality, newDuality, actor) {
|
||||||
|
const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||||
|
if (game.user.isGM ? !hopeFear.gm : !hopeFear.players) return;
|
||||||
|
|
||||||
|
const updates = [];
|
||||||
|
const hope = (newDuality >= 0 ? 1 : 0) - (oldDuality >= 0 ? 1 : 0);
|
||||||
|
const stress = (newDuality === 0 ? 1 : 0) - (oldDuality === 0 ? 1 : 0);
|
||||||
|
const fear = (newDuality === -1 ? 1 : 0) - (oldDuality === -1 ? 1 : 0);
|
||||||
|
|
||||||
|
if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true });
|
||||||
|
if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true });
|
||||||
|
if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true });
|
||||||
|
|
||||||
|
const resourceUpdates = new ResourceUpdateMap(actor);
|
||||||
|
resourceUpdates.addResources(updates);
|
||||||
|
resourceUpdates.updateResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
async reroll(modifier, options) {
|
||||||
|
const oldDuality = this.#getDualityState(options.liveRoll.roll);
|
||||||
|
await super.reroll(modifier, options);
|
||||||
|
|
||||||
|
if (options?.liveRoll) {
|
||||||
|
/* Can't currently test since DiceSoNice is not v14. Might need to set the appearance earlier if a roll is triggered by super.reroll */
|
||||||
|
if (game.modules.get('dice-so-nice')?.active) {
|
||||||
|
const diceSoNiceRoll = {
|
||||||
|
_evaluated: true,
|
||||||
|
dice: [this],
|
||||||
|
options: { appearance: {} }
|
||||||
|
};
|
||||||
|
|
||||||
|
const preset = await getDiceSoNicePreset(diceSoNice[key], faces);
|
||||||
|
diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
|
||||||
|
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile;
|
||||||
|
|
||||||
|
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||||
|
} else {
|
||||||
|
foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||||
|
}
|
||||||
|
|
||||||
|
await options.liveRoll.roll._evaluate();
|
||||||
|
if (options.liveRoll.isReaction) return;
|
||||||
|
|
||||||
|
const newDuality = this.#getDualityState(options.liveRoll.roll);
|
||||||
|
this.#updateResources(oldDuality, newDuality, options.liveRoll.actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||||
import D20Roll from './d20Roll.mjs';
|
import D20Roll from './d20Roll.mjs';
|
||||||
import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||||
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
|
||||||
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
|
||||||
|
|
||||||
export default class DualityRoll extends D20Roll {
|
export default class DualityRoll extends D20Roll {
|
||||||
_advantageFaces = 6;
|
_advantageFaces = 6;
|
||||||
|
|
@ -26,27 +24,31 @@ export default class DualityRoll extends D20Roll {
|
||||||
}
|
}
|
||||||
|
|
||||||
get dHope() {
|
get dHope() {
|
||||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
if (!(this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice();
|
||||||
return this.dice[0];
|
return this.dice[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
set dHope(faces) {
|
set dHope(faces) {
|
||||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
// TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dHope.faces
|
||||||
this.dice[0].faces = this.getFaces(faces);
|
this.dHope.faces = this.getFaces(faces);
|
||||||
}
|
}
|
||||||
|
|
||||||
get dFear() {
|
get dFear() {
|
||||||
if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
if (!(this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice();
|
||||||
return this.dice[1];
|
return this.dice[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
set dFear(faces) {
|
set dFear(faces) {
|
||||||
if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
// TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dFear.faces
|
||||||
this.dice[1].faces = this.getFaces(faces);
|
this.dFear.faces = this.getFaces(faces);
|
||||||
}
|
}
|
||||||
|
|
||||||
get dAdvantage() {
|
get dAdvantage() {
|
||||||
return this.dice[2];
|
return this.dice[2] instanceof game.system.api.dice.diceTypes.AdvantageDie ? this.dice[2] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get dDisadvantage() {
|
||||||
|
return this.dice[2] instanceof game.system.api.dice.diceTypes.DisadvantageDie ? this.dice[2] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get advantageFaces() {
|
get advantageFaces() {
|
||||||
|
|
@ -65,9 +67,14 @@ export default class DualityRoll extends D20Roll {
|
||||||
this._advantageNumber = Number(value);
|
this._advantageNumber = Number(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get extraDice() {
|
||||||
|
const { DualityDie, AdvantageDie, DisadvantageDie } = game.system.api.dice.diceTypes;
|
||||||
|
return this.dice.filter(x => ![DualityDie, AdvantageDie, DisadvantageDie].some(die => x instanceof die));
|
||||||
|
}
|
||||||
|
|
||||||
setRallyChoices() {
|
setRallyChoices() {
|
||||||
return this.data?.parent?.appliedEffects.reduce((a, c) => {
|
return this.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||||
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
const change = c.system.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||||
if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) });
|
if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) });
|
||||||
return a;
|
return a;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -118,22 +125,28 @@ export default class DualityRoll extends D20Roll {
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static fromData(data) {
|
static fromData(data) {
|
||||||
data.terms[0].class = foundry.dice.terms.Die.name;
|
data.terms[0].class = 'DualityDie';
|
||||||
data.terms[2].class = foundry.dice.terms.Die.name;
|
data.terms[2].class = 'DualityDie';
|
||||||
|
if (data.options.roll.advantage?.type && data.terms[4]?.faces) {
|
||||||
|
data.terms[4].class = data.options.roll.advantage.type === 1 ? 'AdvantageDie' : 'DisadvantageDie';
|
||||||
|
}
|
||||||
return super.fromData(data);
|
return super.fromData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
createBaseDice() {
|
createBaseDice() {
|
||||||
if (this.dice[0] instanceof foundry.dice.terms.Die && this.dice[1] instanceof foundry.dice.terms.Die) {
|
if (
|
||||||
|
this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie &&
|
||||||
|
this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie
|
||||||
|
) {
|
||||||
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({
|
this.terms[0] = new game.system.api.dice.diceTypes.DualityDie({
|
||||||
faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12
|
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({
|
this.terms[2] = new game.system.api.dice.diceTypes.DualityDie({
|
||||||
faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12
|
faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +192,7 @@ export default class DualityRoll extends D20Roll {
|
||||||
static async buildConfigure(config = {}, message = {}) {
|
static async buildConfigure(config = {}, message = {}) {
|
||||||
config.dialog ??= {};
|
config.dialog ??= {};
|
||||||
config.guaranteedCritical = config.data?.parent?.appliedEffects.reduce((a, c) => {
|
config.guaranteedCritical = config.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||||
const change = c.changes.find(ch => ch.key === 'system.rules.roll.guaranteedCritical');
|
const change = c.system.changes.find(ch => ch.key === 'system.rules.roll.guaranteedCritical');
|
||||||
if (change) a = true;
|
if (change) a = true;
|
||||||
return a;
|
return a;
|
||||||
}, false);
|
}, false);
|
||||||
|
|
@ -305,7 +318,6 @@ export default class DualityRoll extends D20Roll {
|
||||||
!config.source?.actor ||
|
!config.source?.actor ||
|
||||||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
|
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
|
||||||
config.actionType === 'reaction' ||
|
config.actionType === 'reaction' ||
|
||||||
config.tagTeamSelected ||
|
|
||||||
config.skips?.resources
|
config.skips?.resources
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
@ -346,7 +358,6 @@ export default class DualityRoll extends D20Roll {
|
||||||
if (
|
if (
|
||||||
automationSettings.countdownAutomation &&
|
automationSettings.countdownAutomation &&
|
||||||
config.actionType !== 'reaction' &&
|
config.actionType !== 'reaction' &&
|
||||||
!config.tagTeamSelected &&
|
|
||||||
!config.skips?.updateCountdowns
|
!config.skips?.updateCountdowns
|
||||||
) {
|
) {
|
||||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||||
|
|
@ -373,61 +384,4 @@ export default class DualityRoll extends D20Roll {
|
||||||
if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
|
if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async reroll(rollString, target, message) {
|
|
||||||
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false });
|
|
||||||
const term = parsedRoll.terms[target.dataset.dieIndex];
|
|
||||||
await term.reroll(`/r1=${term.total}`);
|
|
||||||
const result = await parsedRoll.evaluate();
|
|
||||||
|
|
||||||
if (game.modules.get('dice-so-nice')?.active) {
|
|
||||||
const diceSoNiceRoll = {
|
|
||||||
_evaluated: true,
|
|
||||||
dice: [
|
|
||||||
new foundry.dice.terms.Die({
|
|
||||||
...term,
|
|
||||||
faces: term._faces,
|
|
||||||
results: term.results.filter(x => !x.rerolled)
|
|
||||||
})
|
|
||||||
],
|
|
||||||
options: { appearance: {} }
|
|
||||||
};
|
|
||||||
|
|
||||||
const diceSoNicePresets = await getDiceSoNicePresets(result, `d${term._faces}`, `d${term._faces}`);
|
|
||||||
const type = target.dataset.type;
|
|
||||||
if (diceSoNicePresets[type]) {
|
|
||||||
diceSoNiceRoll.dice[0].options = diceSoNicePresets[type];
|
|
||||||
}
|
|
||||||
|
|
||||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newRoll = game.system.api.dice.DualityRoll.postEvaluate(parsedRoll, {
|
|
||||||
targets: message.system.targets,
|
|
||||||
roll: {
|
|
||||||
advantage: message.system.roll.advantage?.type,
|
|
||||||
difficulty: message.system.roll.difficulty ? Number(message.system.roll.difficulty) : null
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const extraIndex = newRoll.advantage ? 3 : 2;
|
|
||||||
newRoll.extra = newRoll.extra.slice(extraIndex);
|
|
||||||
|
|
||||||
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
|
||||||
|
|
||||||
const actor = message.system.source.actor ? await foundry.utils.fromUuid(message.system.source.actor) : null;
|
|
||||||
const config = {
|
|
||||||
source: { actor: message.system.source.actor ?? '' },
|
|
||||||
targets: message.system.targets,
|
|
||||||
tagTeamSelected: Object.values(tagTeamSettings.members).some(x => x.messageId === message._id),
|
|
||||||
roll: newRoll,
|
|
||||||
rerolledRoll: message.system.roll,
|
|
||||||
resourceUpdates: new ResourceUpdateMap(actor)
|
|
||||||
};
|
|
||||||
|
|
||||||
await DualityRoll.addDualityResourceUpdates(config);
|
|
||||||
await config.resourceUpdates.updateResources();
|
|
||||||
|
|
||||||
return { newRoll, parsedRoll };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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