Compare commits

..

10 commits
main ... 1.2.2

Author SHA1 Message Date
WBHarry
77ac11c522 Merge branch 'main' into release 2025-11-17 10:17:50 +01:00
WBHarry
50311679a5 Merge branch 'main' into release 2025-11-11 22:15:30 +01:00
WBHarry
3a7bcd1b0a Merge branch 'main' into release 2025-11-11 18:04:23 +01:00
WBHarry
511e4bd644 Merge branch 'main' into release 2025-11-11 16:23:35 +01:00
WBHarry
395820513b Merge branch 'main' into release 2025-11-11 16:06:03 +01:00
WBHarry
3566ea3fd3 Merge branch 'main' into release 2025-08-26 20:32:04 +02:00
WBHarry
29d502fb97 Merge branch 'main' into release 2025-08-24 21:11:38 +02:00
WBHarry
685a25d25a Merge branch 'main' into release 2025-08-22 01:47:03 +02:00
WBHarry
dd045b3df7 Merge branch 'main' into release 2025-08-19 20:58:05 +02:00
WBHarry
0aabcec340 Raised version 2025-08-19 18:56:30 +02:00
1410 changed files with 25117 additions and 14566 deletions

View file

@ -17,64 +17,12 @@ import {
socketRegistration socketRegistration
} from './module/systemRegistration/_module.mjs'; } from './module/systemRegistration/_module.mjs';
import { placeables } from './module/canvas/_module.mjs'; import { placeables } from './module/canvas/_module.mjs';
import { registerRollDiceHooks } from './module/dice/dhRoll.mjs';
import './node_modules/@yaireo/tagify/dist/tagify.css'; import './node_modules/@yaireo/tagify/dist/tagify.css';
import TemplateManager from './module/documents/templateManager.mjs'; import TemplateManager from './module/documents/templateManager.mjs';
CONFIG.DH = SYSTEM;
CONFIG.TextEditor.enrichers.push(...enricherConfig);
CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll];
CONFIG.Dice.daggerheart = {
DHRoll: DHRoll,
DualityRoll: DualityRoll,
D20Roll: D20Roll,
DamageRoll: DamageRoll
};
CONFIG.Actor.documentClass = documents.DhpActor;
CONFIG.Actor.dataModels = models.actors.config;
CONFIG.Item.documentClass = documents.DHItem;
CONFIG.Item.dataModels = models.items.config;
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
CONFIG.Combat.documentClass = documents.DhpCombat;
CONFIG.Combat.dataModels = { base: models.DhCombat };
CONFIG.Combatant.documentClass = documents.DHCombatant;
CONFIG.Combatant.dataModels = { base: models.DhCombatant };
CONFIG.ChatMessage.dataModels = models.chatMessages.config;
CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
CONFIG.Canvas.rulerClass = placeables.DhRuler;
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
CONFIG.Scene.documentClass = documents.DhScene;
CONFIG.Token.documentClass = documents.DhToken;
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
CONFIG.ui.combat = applications.ui.DhCombatTracker;
CONFIG.ui.chat = applications.ui.DhChatLog;
CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay;
CONFIG.ui.hotbar = applications.ui.DhHotbar;
CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
CONFIG.ui.actors = applications.sidebar.DhActorDirectory;
CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu;
CONFIG.ui.resources = applications.ui.DhFearTracker;
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
CONFIG.ux.TemplateManager = new TemplateManager();
Hooks.once('init', () => { Hooks.once('init', () => {
CONFIG.DH = SYSTEM;
game.system.api = { game.system.api = {
applications, applications,
data, data,
@ -84,102 +32,67 @@ Hooks.once('init', () => {
fields fields
}; };
CONFIG.TextEditor.enrichers.push(...enricherConfig);
CONFIG.Dice.daggerheart = {
DHRoll: DHRoll,
DualityRoll: DualityRoll,
D20Roll: D20Roll,
DamageRoll: DamageRoll
};
CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll];
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
const { DocumentSheetConfig } = foundry.applications.apps; const { DocumentSheetConfig } = foundry.applications.apps;
CONFIG.Token.documentClass = documents.DhToken;
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig); DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, { DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, {
makeDefault: true makeDefault: true
}); });
const sheetLabel = typePath => () => CONFIG.Item.documentClass = documents.DHItem;
game.i18n.format('DAGGERHEART.GENERAL.typeSheet', {
type: game.i18n.localize(typePath) //Registering the Item DataModel
}); CONFIG.Item.dataModels = models.items.config;
const { Items, Actors } = foundry.documents.collections; const { Items, Actors } = foundry.documents.collections;
Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2); Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2);
Items.registerSheet(SYSTEM.id, applications.sheets.items.Ancestry, { Items.registerSheet(SYSTEM.id, applications.sheets.items.Ancestry, { types: ['ancestry'], makeDefault: true });
types: ['ancestry'], Items.registerSheet(SYSTEM.id, applications.sheets.items.Community, { types: ['community'], makeDefault: true });
makeDefault: true, Items.registerSheet(SYSTEM.id, applications.sheets.items.Class, { types: ['class'], makeDefault: true });
label: sheetLabel('TYPES.Item.ancestry') Items.registerSheet(SYSTEM.id, applications.sheets.items.Subclass, { types: ['subclass'], makeDefault: true });
}); Items.registerSheet(SYSTEM.id, applications.sheets.items.Feature, { types: ['feature'], makeDefault: true });
Items.registerSheet(SYSTEM.id, applications.sheets.items.Community, { Items.registerSheet(SYSTEM.id, applications.sheets.items.DomainCard, { types: ['domainCard'], makeDefault: true });
types: ['community'],
makeDefault: true,
label: sheetLabel('TYPES.Item.community')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Class, {
types: ['class'],
makeDefault: true,
label: sheetLabel('TYPES.Item.class')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Subclass, {
types: ['subclass'],
makeDefault: true,
label: sheetLabel('TYPES.Item.subclass')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Feature, {
types: ['feature'],
makeDefault: true,
label: sheetLabel('TYPES.Item.feature')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.DomainCard, {
types: ['domainCard'],
makeDefault: true,
label: sheetLabel('TYPES.Item.domainCard')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Loot, { Items.registerSheet(SYSTEM.id, applications.sheets.items.Loot, {
types: ['loot'], types: ['loot'],
makeDefault: true, makeDefault: true
label: sheetLabel('TYPES.Item.loot')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Consumable, {
types: ['consumable'],
makeDefault: true,
label: sheetLabel('TYPES.Item.consumable')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Weapon, {
types: ['weapon'],
makeDefault: true,
label: sheetLabel('TYPES.Item.weapon')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Armor, {
types: ['armor'],
makeDefault: true,
label: sheetLabel('TYPES.Item.armor')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Beastform, {
types: ['beastform'],
makeDefault: true,
label: sheetLabel('TYPES.Item.beastform')
}); });
Items.registerSheet(SYSTEM.id, applications.sheets.items.Consumable, { types: ['consumable'], makeDefault: true });
Items.registerSheet(SYSTEM.id, applications.sheets.items.Weapon, { types: ['weapon'], makeDefault: true });
Items.registerSheet(SYSTEM.id, applications.sheets.items.Armor, { types: ['armor'], makeDefault: true });
Items.registerSheet(SYSTEM.id, applications.sheets.items.Beastform, { types: ['beastform'], makeDefault: true });
CONFIG.Actor.documentClass = documents.DhpActor;
CONFIG.Actor.dataModels = models.actors.config;
Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2); Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2);
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Character, { Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Character, { types: ['character'], makeDefault: true });
types: ['character'], Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Companion, { types: ['companion'], makeDefault: true });
makeDefault: true, Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Adversary, { types: ['adversary'], makeDefault: true });
label: sheetLabel('TYPES.Actor.character')
});
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Companion, {
types: ['companion'],
makeDefault: true,
label: sheetLabel('TYPES.Actor.companion')
});
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Adversary, {
types: ['adversary'],
makeDefault: true,
label: sheetLabel('TYPES.Actor.adversary')
});
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Environment, { Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Environment, {
types: ['environment'], types: ['environment'],
makeDefault: true, makeDefault: true
label: sheetLabel('TYPES.Actor.environment')
}); });
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, { Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, {
types: ['party'], types: ['party'],
makeDefault: true, makeDefault: true
label: sheetLabel('TYPES.Actor.party')
}); });
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
DocumentSheetConfig.unregisterSheet( DocumentSheetConfig.unregisterSheet(
CONFIG.ActiveEffect.documentClass, CONFIG.ActiveEffect.documentClass,
'core', 'core',
@ -190,20 +103,50 @@ Hooks.once('init', () => {
SYSTEM.id, SYSTEM.id,
applications.sheetConfigs.ActiveEffectConfig, applications.sheetConfigs.ActiveEffectConfig,
{ {
makeDefault: true, makeDefault: true
label: sheetLabel('DOCUMENT.ActiveEffect')
} }
); );
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
CONFIG.Combat.dataModels = {
base: models.DhCombat
};
CONFIG.Combatant.dataModels = {
base: models.DhCombatant
};
CONFIG.ChatMessage.dataModels = models.chatMessages.config;
CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
CONFIG.Canvas.rulerClass = placeables.DhRuler;
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
CONFIG.Combat.documentClass = documents.DhpCombat;
CONFIG.ui.combat = applications.ui.DhCombatTracker;
CONFIG.ui.chat = applications.ui.DhChatLog;
CONFIG.ui.hotbar = applications.ui.DhHotbar;
CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu;
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
CONFIG.ui.resources = applications.ui.DhFearTracker;
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
CONFIG.ux.TemplateManager = new TemplateManager();
game.socket.on(`system.${SYSTEM.id}`, socketRegistration.handleSocketEvent); game.socket.on(`system.${SYSTEM.id}`, socketRegistration.handleSocketEvent);
// Make Compendium Dialog resizable // Make Compendium Dialog resizable
foundry.applications.sidebar.apps.Compendium.DEFAULT_OPTIONS.window.resizable = true; foundry.applications.sidebar.apps.Compendium.DEFAULT_OPTIONS.window.resizable = true;
DocumentSheetConfig.unregisterSheet(foundry.documents.Scene, 'core', foundry.applications.sheets.SceneConfig);
DocumentSheetConfig.registerSheet(foundry.documents.Scene, SYSTEM.id, applications.scene.DhSceneConfigSettings, { DocumentSheetConfig.registerSheet(foundry.documents.Scene, SYSTEM.id, applications.scene.DhSceneConfigSettings, {
makeDefault: true, makeDefault: true,
label: sheetLabel('DOCUMENT.Scene') label: 'Daggerheart'
}); });
settingsRegistration.registerDHSettings(); settingsRegistration.registerDHSettings();
@ -233,13 +176,11 @@ Hooks.on('ready', async () => {
ui.countdowns.render({ force: true }); ui.countdowns.render({ force: true });
} }
ui.effectsDisplay = new CONFIG.ui.effectsDisplay();
ui.effectsDisplay.render({ force: true });
if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser)) if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser))
ui.compendiumBrowser = new applications.ui.ItemBrowser(); ui.compendiumBrowser = new applications.ui.ItemBrowser();
socketRegistration.registerSocketHooks(); socketRegistration.registerSocketHooks();
registerRollDiceHooks();
socketRegistration.registerUserQueries(); socketRegistration.registerUserQueries();
if (!game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage)) { if (!game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage)) {
@ -255,9 +196,9 @@ Hooks.on('ready', async () => {
Hooks.once('dicesoniceready', () => {}); Hooks.once('dicesoniceready', () => {});
Hooks.on('renderChatMessageHTML', (document, element) => { Hooks.on('renderChatMessageHTML', (_, element, message) => {
enricherRenderSetup(element); enricherRenderSetup(element);
const cssClass = document.flags?.daggerheart?.cssClass; const cssClass = message.message.flags?.daggerheart?.cssClass;
if (cssClass) cssClass.split(' ').forEach(cls => element.classList.add(cls)); if (cssClass) cssClass.split(' ').forEach(cls => element.classList.add(cls));
}); });
@ -310,70 +251,51 @@ Hooks.on('chatMessage', (_, message) => {
} }
}); });
const updateActorsRangeDependentEffects = async token => { Hooks.on('moveToken', async (movedToken, data) => {
const rangeMeasurement = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.variantRules
).rangeMeasurement;
for (let effect of token.actor?.allApplicableEffects() ?? []) {
if (!effect.system.rangeDependence?.enabled) continue;
const { target, range, type } = effect.system.rangeDependence;
// If there are no targets, assume false. Otherwise, start with the effect enabled.
let enabledEffect = game.user.targets.size !== 0;
// Expect all targets to meet the rangeDependence requirements
for (let userTarget of game.user.targets) {
const disposition = userTarget.document.disposition;
if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1)) {
enabledEffect = false;
break;
}
// Get required distance and special case 5 feet to test adjacency
const required = rangeMeasurement[range];
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
const inRange =
required === 5
? userTarget.isAdjacentWith(token.object)
: userTarget.distanceTo(token.object) <= required;
if (reverse ? inRange : !inRange) {
enabledEffect = false;
break;
}
}
await effect.update({ disabled: !enabledEffect });
}
};
const updateAllRangeDependentEffects = async () => {
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects; const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
if (!effectsAutomation.rangeDependent) return; if (!effectsAutomation.rangeDependent) return;
const tokens = canvas.scene.tokens; const rangeDependantEffects = movedToken.actor.effects.filter(effect => effect.system.rangeDependence?.enabled);
if (game.user.character) {
// The character updates their character's token. There can be only one token. const updateEffects = async (disposition, token, effects, effectUpdates) => {
const characterToken = tokens.find(x => x.actor === game.user.character); const rangeMeasurement = game.settings.get(
updateActorsRangeDependentEffects(characterToken); CONFIG.DH.id,
} else if (game.user.isActiveGM) { CONFIG.DH.SETTINGS.gameSettings.variantRules
// The GM is responsible for all other tokens. ).rangeMeasurement;
const playerCharacters = game.users.players.filter(x => x.active).map(x => x.character);
for (const token of tokens.filter(x => !playerCharacters.includes(x.actor))) { for (let effect of effects.filter(x => x.system.rangeDependence?.enabled)) {
updateActorsRangeDependentEffects(token); const { target, range, type } = effect.system.rangeDependence;
if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1))
return false;
const distanceBetween = canvas.grid.measurePath([
{ ...movedToken.toObject(), x: data.destination.x, y: data.destination.y },
token
]).distance;
const distance = rangeMeasurement[range];
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
const newDisabled = reverse ? distanceBetween <= distance : distanceBetween > distance;
const oldDisabled = effectUpdates[effect.uuid] ? effectUpdates[effect.uuid].disabled : newDisabled;
effectUpdates[effect.uuid] = {
disabled: oldDisabled || newDisabled,
value: effect
};
} }
};
const effectUpdates = {};
for (let token of game.scenes.find(x => x.active).tokens) {
if (token.id !== movedToken.id) {
await updateEffects(token.disposition, token, rangeDependantEffects, effectUpdates);
}
if (token.actor) await updateEffects(movedToken.disposition, token, token.actor.effects, effectUpdates);
} }
};
const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50); for (let key in effectUpdates) {
const effect = effectUpdates[key];
Hooks.on('targetToken', () => { await effect.value.update({ disabled: effect.disabled });
debouncedRangeEffectCall();
});
Hooks.on('refreshToken', (_, options) => {
if (options.refreshPosition) {
debouncedRangeEffectCall();
} }
}); });

View file

@ -36,7 +36,6 @@
}, },
"DAGGERHEART": { "DAGGERHEART": {
"CharacterSheet": "Character Sheet",
"ACTIONS": { "ACTIONS": {
"TYPES": { "TYPES": {
"attack": { "attack": {
@ -47,10 +46,6 @@
"name": "Beastform", "name": "Beastform",
"tooltip": "Shapeshift the user into another form." "tooltip": "Shapeshift the user into another form."
}, },
"countdown": {
"name": "Countdown",
"tooltip": "Start a countdown"
},
"damage": { "damage": {
"name": "Damage", "name": "Damage",
"tooltip": "Direct damage without a roll." "tooltip": "Direct damage without a roll."
@ -78,10 +73,6 @@
"exactHint": "The Character's Tier is used if empty", "exactHint": "The Character's Tier is used if empty",
"label": "Beastform" "label": "Beastform"
}, },
"countdown": {
"defaultOwnership": "Default Ownership",
"startCountdown": "Start Countdown"
},
"damage": { "damage": {
"multiplier": "Multiplier", "multiplier": "Multiplier",
"flatMultiplier": "Flat Multiplier" "flatMultiplier": "Flat Multiplier"
@ -104,7 +95,6 @@
"Settings": { "Settings": {
"attackBonus": "Attack Bonus", "attackBonus": "Attack Bonus",
"attackName": "Attack Name", "attackName": "Attack Name",
"criticalThreshold": "Critical Threshold",
"includeBase": { "label": "Include Item Damage" }, "includeBase": { "label": "Include Item Damage" },
"multiplier": "Multiplier", "multiplier": "Multiplier",
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.", "saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
@ -132,8 +122,7 @@
"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",
"title": "Range Dependant" "title": "Range Dependant"
}, }
"immuneStatusText": "Immunity: {status}"
}, },
"ACTORS": { "ACTORS": {
"Adversary": { "Adversary": {
@ -226,7 +215,6 @@
"confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)" "confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)"
}, },
"viewLevelups": "View Levelups", "viewLevelups": "View Levelups",
"viewParty": "View Party",
"InvalidOldCharacterImportTitle": "Old Character Import", "InvalidOldCharacterImportTitle": "Old Character Import",
"InvalidOldCharacterImportText": "Character data exported prior to system version 1.1 will not generate a complete character. Do you wish to continue?", "InvalidOldCharacterImportText": "Character data exported prior to system version 1.1 will not generate a complete character. Do you wish to continue?",
"cancelBeastform": "Cancel Beastform" "cancelBeastform": "Cancel Beastform"
@ -327,34 +315,25 @@
"equip": "Equip", "equip": "Equip",
"sendToChat": "Send To Chat", "sendToChat": "Send To Chat",
"toLoadout": "Send to Loadout", "toLoadout": "Send to Loadout",
"recall": "Recall",
"toVault": "Send to Vault", "toVault": "Send to Vault",
"unequip": "Unequip", "unequip": "Unequip",
"useItem": "Use Item" "useItem": "Use Item"
}, },
"Countdown": { "Countdown": {
"addCountdown": "Add Countdown", "addCountdown": "Add Countdown",
"loopingTypes": {
"noLooping": "No Looping",
"looping": "Looping",
"increasing": "Increasing",
"decreasing": "Decreasing"
},
"FIELDS": { "FIELDS": {
"countdowns": { "countdowns": {
"element": { "element": {
"name": { "label": "Name" }, "name": { "label": "Name" },
"progress": { "progress": {
"current": { "label": "Current" }, "current": { "label": "Current" },
"looping": { "label": "Looping" }, "max": { "label": "Max" },
"start": { "label": "Start" },
"startFormula": { "label": "Start Formula" },
"type": { "type": {
"label": { "label": "Label", "hint": "Used for custom" }, "label": { "label": "Label", "hint": "Used for custom" },
"value": { "label": "Value" } "value": { "label": "Value" }
} }
}, },
"type": { "label": "Progression Type" } "type": { "label": "Countdown Type" }
} }
} }
}, },
@ -376,21 +355,17 @@
"newCountdown": "New Countdown", "newCountdown": "New Countdown",
"removeCountdownTitle": "Remove Countdown", "removeCountdownTitle": "Remove Countdown",
"removeCountdownText": "Are you sure you want to remove the countdown: {name}?", "removeCountdownText": "Are you sure you want to remove the countdown: {name}?",
"current": "Current Value", "current": "Current",
"start": "Start Value", "max": "Max",
"startFormula": "Randomized Start Value Formula", "currentCountdownValue": "Current: {value}",
"currentCountdownCurrent": "Current: {value}", "currentCountdownMax": "Max: {value}",
"currentCountdownStart": "Start: {value}",
"category": "Category", "category": "Category",
"progressionType": "Progression", "type": "Type",
"decreasing": "Decreasing",
"looping": "Looping",
"defaultOwnershipTooltip": "The default player ownership of countdowns", "defaultOwnershipTooltip": "The default player ownership of countdowns",
"hideNewCountdowns": "Hide New Countdowns" "hideNewCountdowns": "Hide New Countdowns"
}, },
"DaggerheartMenu": { "DaggerheartMenu": {
"title": "GM Tools", "title": "GM Tools"
"refreshFeatures": "Refresh Features"
}, },
"DeleteConfirmation": { "DeleteConfirmation": {
"title": "Delete {type} - {name}", "title": "Delete {type} - {name}",
@ -404,8 +379,7 @@
"stressReduction": "Reduce By Stress", "stressReduction": "Reduce By Stress",
"title": "Damage Reduction", "title": "Damage Reduction",
"unncessaryStress": "You don't need to expend stress", "unncessaryStress": "You don't need to expend stress",
"usedMarks": "Used Marks", "usedMarks": "Used Marks"
"reduceSeverity": "Severity Reduced By {nr}"
}, },
"DeathMove": { "DeathMove": {
"selectMove": "Select Move", "selectMove": "Select Move",
@ -473,9 +447,6 @@
"title": "Select Image", "title": "Select Image",
"selectImage": "Select Image" "selectImage": "Select Image"
}, },
"ItemTransfer": {
"transfer": "Transfer"
},
"Levelup": { "Levelup": {
"actions": { "actions": {
"creatureComfort": { "creatureComfort": {
@ -508,7 +479,6 @@
}, },
"navigateLevel": "To Level {level}", "navigateLevel": "To Level {level}",
"navigateToLevelup": "Return To Levelup", "navigateToLevelup": "Return To Levelup",
"finishLevelup": "Finish Levelup",
"navigateToSummary": "To Summary", "navigateToSummary": "To Summary",
"options": { "options": {
"trait": "Gain a +1 bonus to two unmarked character traits and mark them.", "trait": "Gain a +1 bonus to two unmarked character traits and mark them.",
@ -588,7 +558,6 @@
}, },
"OwnershipSelection": { "OwnershipSelection": {
"title": "Ownership Selection - {name}", "title": "Ownership Selection - {name}",
"noPlayers": "No players to assign ownership to",
"default": "Default Ownership" "default": "Default Ownership"
}, },
"ReactionRoll": { "ReactionRoll": {
@ -615,9 +584,6 @@
"insufficientHope": "The initiating character doesn't have enough hope", "insufficientHope": "The initiating character doesn't have enough hope",
"createTagTeam": "Create TagTeam Roll", "createTagTeam": "Create TagTeam Roll",
"chatMessageRollTitle": "Roll" "chatMessageRollTitle": "Roll"
},
"TokenConfig": {
"actorSizeUsed": "Actor size is set, determining the dimensions"
} }
}, },
"CLASS": { "CLASS": {
@ -627,6 +593,11 @@
} }
}, },
"CONFIG": { "CONFIG": {
"ActionType": {
"passive": "Passive",
"action": "Action",
"reaction": "Reaction"
},
"AdversaryTrait": { "AdversaryTrait": {
"relentless": { "relentless": {
"name": "Relentless", "name": "Relentless",
@ -686,14 +657,6 @@
"description": "Enemies that enhance their allies and/or disrupt their opponents." "description": "Enemies that enhance their allies and/or disrupt their opponents."
} }
}, },
"AdversaryTypeCost": {
"minion": "for each group of Minions equal to the size of the party.",
"support": "for each Social or Support adversary.",
"standard": "for each Horde, Ranged, Skulk, or Standard adversary.",
"leader": "for each Leader adversary.",
"bruiser": "for each Bruiser adversary.",
"solo": "for each Solo adversary."
},
"ArmorFeature": { "ArmorFeature": {
"burning": { "burning": {
"name": "Burning", "name": "Burning",
@ -917,30 +880,6 @@
"evolved": "Evolved", "evolved": "Evolved",
"hybrid": "Hybrid" "hybrid": "Hybrid"
}, },
"BPModifiers": {
"increaseDamage": {
"description": "if you add +1d4 (or a static +2) to all adversaries' damage rolls (to increase the challenge without lengthening the battle)",
"effect": {
"name": "Increase Damage",
"description": "Add 1d4 to damage"
}
},
"lessDifficult": {
"description": "if the fight should be less difficult or shorter."
},
"lowerTier": {
"description": "if you choose an adversary from a lower tier."
},
"manySolos": {
"description": "if you're using 2 or more Solo adversaries."
},
"moreDangerous": {
"description": "if the fight should be more dangerous or last longer"
},
"noToughies": {
"description": "if you don't include any Bruisers, Hordes, Leaders, or Solos"
}
},
"Burden": { "Burden": {
"oneHanded": "One-Handed", "oneHanded": "One-Handed",
"twoHanded": "Two-Handed" "twoHanded": "Two-Handed"
@ -976,12 +915,9 @@
} }
}, },
"CountdownType": { "CountdownType": {
"actionRoll": "Action Roll", "spotlight": "Spotlight",
"characterAttack": "Character Attack",
"characterSpotlight": "Character Spotlight",
"custom": "Custom", "custom": "Custom",
"fear": "Fear", "characterAttack": "Character Attack"
"spotlight": "Spotlight"
}, },
"DamageType": { "DamageType": {
"physical": { "physical": {
@ -1035,12 +971,6 @@
"description": "" "description": ""
} }
}, },
"FeatureForm": {
"label": "Feature Form",
"passive": "Passive",
"action": "Action",
"reaction": "Reaction"
},
"Gold": { "Gold": {
"title": "Gold", "title": "Gold",
"coins": "Coins", "coins": "Coins",
@ -1081,8 +1011,7 @@
}, },
"ItemResourceType": { "ItemResourceType": {
"simple": "Simple", "simple": "Simple",
"diceValue": "Dice Value", "diceValue": "Dice Value"
"die": "Die"
}, },
"Range": { "Range": {
"self": { "self": {
@ -1154,14 +1083,6 @@
"rect": "Rectangle", "rect": "Rectangle",
"ray": "Ray" "ray": "Ray"
}, },
"TokenSize": {
"tiny": "Tiny",
"small": "Small",
"medium": "Medium",
"large": "Large",
"huge": "Huge",
"gargantuan": "Gargantuan"
},
"Traits": { "Traits": {
"agility": { "agility": {
"name": "Agility", "name": "Agility",
@ -1271,7 +1192,7 @@
}, },
"burning": { "burning": {
"name": "Burning", "name": "Burning",
"description": "When you roll a 6 on a damage die, the target must mark a Stress.", "description": "When you roll the maximum value on a damage die, roll an additional damage die.",
"actions": { "actions": {
"burn": { "burn": {
"name": "Burn", "name": "Burn",
@ -1802,9 +1723,7 @@
"label": "Long Rest: Bonus Long Rest Moves", "label": "Long Rest: Bonus Long Rest Moves",
"hint": "The number of extra Long Rest Moves the character can take during a Long Rest." "hint": "The number of extra Long Rest Moves the character can take during a Long Rest."
} }
}, }
"target": "Target",
"targetSelf": "Self"
}, },
"maxLoadout": { "maxLoadout": {
"label": "Max Loadout Cards Bonus" "label": "Max Loadout Cards Bonus"
@ -1819,7 +1738,6 @@
"plural": "Costs" "plural": "Costs"
}, },
"Damage": { "Damage": {
"massive": "Massive",
"severe": "Severe", "severe": "Severe",
"major": "Major", "major": "Major",
"minor": "Minor", "minor": "Minor",
@ -2085,7 +2003,6 @@
"basics": "Basics", "basics": "Basics",
"bonus": "Bonus", "bonus": "Bonus",
"burden": "Burden", "burden": "Burden",
"condition": "Condition",
"continue": "Continue", "continue": "Continue",
"criticalSuccess": "Critical Success", "criticalSuccess": "Critical Success",
"criticalShort": "Critical", "criticalShort": "Critical",
@ -2112,7 +2029,6 @@
"fear": "Fear", "fear": "Fear",
"features": "Features", "features": "Features",
"formula": "Formula", "formula": "Formula",
"general": "General",
"gm": "GM", "gm": "GM",
"healing": "Healing", "healing": "Healing",
"healingRoll": "Healing Roll", "healingRoll": "Healing Roll",
@ -2127,7 +2043,6 @@
}, },
"hope": "Hope", "hope": "Hope",
"hordeHp": "Horde HP", "hordeHp": "Horde HP",
"icon": "Icon",
"identify": "Identity", "identify": "Identity",
"imagePath": "Image Path", "imagePath": "Image Path",
"inactiveEffects": "Inactive Effects", "inactiveEffects": "Inactive Effects",
@ -2149,7 +2064,6 @@
"missingDragDropThing": "Drop {thing} here", "missingDragDropThing": "Drop {thing} here",
"multiclass": "Multiclass", "multiclass": "Multiclass",
"newCategory": "New Category", "newCategory": "New Category",
"newThing": "New {thing}",
"none": "None", "none": "None",
"noTarget": "No current target", "noTarget": "No current target",
"partner": "Partner", "partner": "Partner",
@ -2164,7 +2078,6 @@
"recovery": "Recovery", "recovery": "Recovery",
"refresh": "Refresh", "refresh": "Refresh",
"reroll": "Reroll", "reroll": "Reroll",
"rerolled": "Rerolled",
"rerollThing": "Reroll {thing}", "rerollThing": "Reroll {thing}",
"resource": "Resource", "resource": "Resource",
"roll": "Roll", "roll": "Roll",
@ -2185,12 +2098,10 @@
"plural": "Targets" "plural": "Targets"
}, },
"title": "Title", "title": "Title",
"tokenSize": "Token Size",
"total": "Total", "total": "Total",
"traitModifier": "Trait Modifier", "traitModifier": "Trait Modifier",
"true": "True", "true": "True",
"type": "Type", "type": "Type",
"typeSheet": "System {type} Sheet",
"unarmed": "Unarmed", "unarmed": "Unarmed",
"unarmedAttack": "Unarmed Attack", "unarmedAttack": "Unarmed Attack",
"unarmored": "Unarmored", "unarmored": "Unarmored",
@ -2243,7 +2154,6 @@
"tokenRingImg": { "label": "Subject Texture" }, "tokenRingImg": { "label": "Subject Texture" },
"tokenSize": { "tokenSize": {
"placeholder": "Using character dimensions", "placeholder": "Using character dimensions",
"disabledPlaceholder": "Set by character size",
"height": { "label": "Height" }, "height": { "label": "Height" },
"width": { "label": "Width" } "width": { "label": "Width" }
}, },
@ -2267,11 +2177,7 @@
"evolvedDrag": "Drag a form here to evolve it.", "evolvedDrag": "Drag a form here to evolve it.",
"hybridize": "Hybridize", "hybridize": "Hybridize",
"hybridizeFeatureTitle": "Hybrid Features", "hybridizeFeatureTitle": "Hybrid Features",
"hybridizeDrag": "Drag a form here to hybridize it.", "hybridizeDrag": "Drag a form here to hybridize it."
"mainTrait": "Main Trait",
"traitBonus": "Trait Bonus",
"evolvedTokenHint": "An evolved beastform's token is based on that of the form you evolve",
"evolvedImagePlaceholder": "The image for the form selected for evolution will be used"
}, },
"Class": { "Class": {
"hopeFeatures": "Hope Features", "hopeFeatures": "Hope Features",
@ -2380,10 +2286,6 @@
"gm": { "label": "GM" }, "gm": { "label": "GM" },
"players": { "label": "Players" } "players": { "label": "Players" }
}, },
"countdownAutomation": {
"label": "Countdown Automation",
"hint": "Automatically progress countdowns based on their progression settings"
},
"levelupAuto": { "levelupAuto": {
"label": "Levelup Automation", "label": "Levelup Automation",
"hint": "When you've made your choices and finish levelup, the numerical changes are automatically applied to your character." "hint": "When you've made your choices and finish levelup, the numerical changes are automatically applied to your character."
@ -2447,8 +2349,8 @@
"newDowntimeMove": "Downtime Move", "newDowntimeMove": "Downtime Move",
"downtimeMove": "Downtime Move", "downtimeMove": "Downtime Move",
"armorFeature": "Armor Feature", "armorFeature": "Armor Feature",
"weaponFeature": "Weapon Feature", "weaponFeature": "Weapon Feaure",
"newFeature": "New Item Feature", "newFeature": "New ItemFeature",
"downtimeMoves": "Downtime Moves", "downtimeMoves": "Downtime Moves",
"itemFeatures": "Item Features", "itemFeatures": "Item Features",
"nrChoices": "# Moves Per Rest", "nrChoices": "# Moves Per Rest",
@ -2465,12 +2367,9 @@
}, },
"currency": { "currency": {
"title": "Currency Overrides", "title": "Currency Overrides",
"changeIcon": "Change Currency Icon",
"currencyName": "Currency Name", "currencyName": "Currency Name",
"coinName": "Coin Name", "coinName": "Coin Name",
"handfulName": "Handful Name", "handfulName": "Handful Name",
"iconName": "Icon Name",
"iconNameHint": "Icons are from fontawesome",
"bagName": "Bag Name", "bagName": "Bag Name",
"chestName": "Chest Name" "chestName": "Chest Name"
}, },
@ -2522,8 +2421,7 @@
"texture": "Texture", "texture": "Texture",
"colorset": "Theme", "colorset": "Theme",
"material": "Material", "material": "Material",
"system": "Dice Preset", "system": "Dice Preset"
"font": "Font"
} }
}, },
"variantRules": { "variantRules": {
@ -2532,11 +2430,6 @@
"hint": "Apply variant rules from the Daggerheart system", "hint": "Apply variant rules from the Daggerheart system",
"name": "Variant Rules", "name": "Variant Rules",
"actionTokens": "Action Tokens" "actionTokens": "Action Tokens"
},
"SpotlightRequestQueue": {
"name": "Spotlight Request Queue",
"label": "Spotlight Request Queue",
"hint": "Adds more structure to spotlight requests by ordering them from oldest to newest"
} }
}, },
"Resources": { "Resources": {
@ -2550,10 +2443,6 @@
"actionTokens": { "actionTokens": {
"enabled": { "label": "Enabled" }, "enabled": { "label": "Enabled" },
"tokens": { "label": "Tokens" } "tokens": { "label": "Tokens" }
},
"massiveDamage": {
"title": "Massive Damage",
"enabled": { "label": "Enabled" }
} }
} }
}, },
@ -2613,8 +2502,7 @@
"abilityCheckTitle": "{ability} Check" "abilityCheckTitle": "{ability} Check"
}, },
"effectSummary": { "effectSummary": {
"title": "Effects Applied", "title": "Effects Applied"
"immunityTo": "Immunity: {immunities}"
}, },
"featureTitle": "Class Feature", "featureTitle": "Class Feature",
"groupRoll": { "groupRoll": {
@ -2626,8 +2514,7 @@
"selectMember": "Select a Member", "selectMember": "Select a Member",
"rerollTitle": "Reroll Group Roll", "rerollTitle": "Reroll Group Roll",
"rerollContent": "Are you sure you want to reroll your {trait} check?", "rerollContent": "Are you sure you want to reroll your {trait} check?",
"rerollTooltip": "Reroll", "rerollTooltip": "Reroll"
"wholePartySelected": "The whole party is selected"
}, },
"healingRoll": { "healingRoll": {
"title": "Heal - {damage}", "title": "Heal - {damage}",
@ -2658,14 +2545,7 @@
"Countdowns": { "Countdowns": {
"title": "Countdowns", "title": "Countdowns",
"toggleIconMode": "Toggle Icon Only", "toggleIconMode": "Toggle Icon Only",
"noPlayerAccess": "This countdown isn't visible to any players", "noPlayerAccess": "This countdown isn't visible to any players"
"loop": "Looping",
"decreasingLoop": "Decreasing Looping",
"increasingLoop": "Increasing Looping"
},
"EffectsDisplay": {
"removeThing": "[Right Click] Remove {thing}",
"appliedBy": "Applied By: {by}"
}, },
"ItemBrowser": { "ItemBrowser": {
"title": "Daggerheart Compendium Browser", "title": "Daggerheart Compendium Browser",
@ -2743,7 +2623,7 @@
"cardTooHighLevel": "The card is too high level!", "cardTooHighLevel": "The card is too high level!",
"duplicateCard": "You cannot select the same card more than once.", "duplicateCard": "You cannot select the same card more than once.",
"duplicateCharacter": "This actor is already registered in the party members list.", "duplicateCharacter": "This actor is already registered in the party members list.",
"onlyCharactersInPartySheet": "You can only drag characters, companions and adversaries to the party sheet.", "onlyCharactersInPartySheet": "You can only drag characters, companions and adverasries to the party sheet.",
"notPrimary": "The weapon is not a primary weapon!", "notPrimary": "The weapon is not a primary weapon!",
"notSecondary": "The weapon is not a secondary weapon!", "notSecondary": "The weapon is not a secondary weapon!",
"itemTooHighTier": "The item must be from Tier1", "itemTooHighTier": "The item must be from Tier1",
@ -2781,17 +2661,9 @@
"gmRequired": "This action requires an online GM", "gmRequired": "This action requires an online GM",
"gmOnly": "This can only be accessed by the GM", "gmOnly": "This can only be accessed by the GM",
"noActorOwnership": "You do not have permissions for this character", "noActorOwnership": "You do not have permissions for this character",
"documentIsMissing": "The {documentType} is missing from the world.", "documentIsMissing": "The {documentType} is missing from the world."
"tokenActorMissing": "{name} is missing an Actor",
"tokenActorsMissing": "[{names}] missing Actors"
}, },
"Sidebar": { "Sidebar": {
"actorDirectory": {
"tier": "Tier {tier} {type}",
"character": "Level {level} Character",
"companion": "Level {level} - {partner}",
"companionNoPartner": "No Partner"
},
"daggerheartMenu": { "daggerheartMenu": {
"title": "Daggerheart Menu", "title": "Daggerheart Menu",
"startSession": "Start Session", "startSession": "Start Session",
@ -2825,10 +2697,7 @@
"rightClickExtand": "Right-Click to extand", "rightClickExtand": "Right-Click to extand",
"companionPartnerLevelBlock": "The companion needs an assigned partner to level up.", "companionPartnerLevelBlock": "The companion needs an assigned partner to level up.",
"configureAttribution": "Configure Attribution", "configureAttribution": "Configure Attribution",
"deleteItem": "Delete Item", "deleteItem": "Delete Item"
"immune": "Immune",
"middleClick": "[Middle Click] Keep tooltip view",
"tokenSize": "The token size used on the canvas"
} }
} }
} }

View file

@ -6,7 +6,6 @@ export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
export { default as DeathMove } from './deathMove.mjs'; export { default as DeathMove } from './deathMove.mjs';
export { default as Downtime } from './downtime.mjs'; export { default as Downtime } from './downtime.mjs';
export { default as ImageSelectDialog } from './imageSelectDialog.mjs'; export { default as ImageSelectDialog } from './imageSelectDialog.mjs';
export { default as ItemTransferDialog } from './itemTransfer.mjs';
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs'; export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
export { default as OwnershipSelection } from './ownershipSelection.mjs'; export { default as OwnershipSelection } from './ownershipSelection.mjs';
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs'; export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';

View file

@ -57,11 +57,7 @@ export default class ActionSelectionDialog extends HandlebarsApplicationMixin(Ap
/** @inheritDoc */ /** @inheritDoc */
async _prepareContext(options) { async _prepareContext(options) {
const actions = this.#item.system.actionsList.map(action => ({ const actions = this.#item.system.actionsList,
...action.toObject(),
id: action.id,
img: action.baseAction ? action.parent.parent.img : action.img
})),
itemName = this.#item.name; itemName = this.#item.name;
return { return {
...(await super._prepareContext(options)), ...(await super._prepareContext(options)),

View file

@ -278,26 +278,19 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
'close', 'close',
async () => { async () => {
const selected = app.selected.toObject(); const selected = app.selected.toObject();
const evolved = app.evolved.form ? app.evolved.form.toObject() : null;
const data = await game.system.api.data.items.DHBeastform.getWildcardImage( const data = await game.system.api.data.items.DHBeastform.getWildcardImage(
app.configData.data.parent, app.configData.data.parent,
evolved ?? app.selected app.selected
); );
if (data) { if (data) {
if (!data.selectedImage) selected = null; if (!data.selectedImage) selected = null;
else { else {
const imageSource = evolved ?? selected; if (data.usesDynamicToken) selected.system.tokenRingImg = data.selectedImage;
if (imageSource.usesDynamicToken) imageSource.system.tokenRingImg = data.selectedImage; else selected.system.tokenImg = data.selectedImage;
else imageSource.system.tokenImg = data.selectedImage;
} }
} }
resolve({ resolve({ selected: selected, evolved: app.evolved, hybrid: app.hybrid, item: featureItem });
selected: selected,
evolved: { ...app.evolved, form: evolved },
hybrid: app.hybrid,
item: featureItem
});
}, },
{ once: true } { once: true }
); );

View file

@ -104,7 +104,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.roll = this.roll; context.roll = this.roll;
context.rollType = this.roll?.constructor.name; context.rollType = this.roll?.constructor.name;
context.rallyDie = this.roll.rallyChoices; context.rallyDie = this.roll.rallyChoices;
const experiences = this.config.data?.system?.experiences || {}; const experiences = this.config.data?.experiences || {};
context.experiences = Object.keys(experiences).map(id => ({ context.experiences = Object.keys(experiences).map(id => ({
id, id,
...experiences[id] ...experiences[id]
@ -116,14 +116,14 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.isLite = this.config.roll?.lite; context.isLite = this.config.roll?.lite;
context.extraFormula = this.config.extraFormula; context.extraFormula = this.config.extraFormula;
context.formula = this.roll.constructFormula(this.config); context.formula = this.roll.constructFormula(this.config);
if (this.actor?.system?.traits) context.abilities = this.getTraitModifiers(); if (this.actor.system.traits) context.abilities = this.getTraitModifiers();
context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll'; context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll';
context.reactionOverride = this.reactionOverride; context.reactionOverride = this.reactionOverride;
} }
const tagTeamSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); 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) { if (tagTeamSetting.members[this.actor.id] && !this.config.skips?.createMessage) {
context.activeTagTeamRoll = true; context.activeTagTeamRoll = true;
context.tagTeamSelected = this.config.tagTeamSelected; context.tagTeamSelected = this.config.tagTeamSelected;
} }
@ -185,7 +185,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
extKey: button.dataset.key, extKey: button.dataset.key,
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope', key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
value: 1, value: 1,
name: this.config.data?.system.experiences?.[button.dataset.key]?.name name: this.config.data?.experiences?.[button.dataset.key]?.name
} }
]; ];
this.render(); this.render();
@ -195,9 +195,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
if (this.config.roll) { if (this.config.roll) {
this.reactionOverride = !this.reactionOverride; this.reactionOverride = !this.reactionOverride;
this.config.actionType = this.reactionOverride this.config.actionType = this.reactionOverride
? 'reaction' ? CONFIG.DH.ITEM.actionTypes.reaction.id
: this.config.actionType === 'reaction' : this.config.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id
? 'action' ? null
: this.config.actionType; : this.config.actionType;
this.render(); this.render();
} }

View file

@ -10,7 +10,6 @@ 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.rulesDefault = game.settings.get( this.rulesDefault = game.settings.get(
CONFIG.DH.id, CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Automation CONFIG.DH.SETTINGS.gameSettings.Automation
@ -58,11 +57,6 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
null null
); );
this.reduceSeverity = this.damageType.reduce((value, curr) => {
return Math.max(this.actor.system.rules.damageReduction.reduceSeverity[curr], value);
}, 0);
this.actor.system.rules.damageReduction.reduceSeverity[this.damageType];
this.thresholdImmunities = Object.keys(actor.system.rules.damageReduction.thresholdImmunities).reduce( this.thresholdImmunities = Object.keys(actor.system.rules.damageReduction.thresholdImmunities).reduce(
(acc, key) => { (acc, key) => {
if (actor.system.rules.damageReduction.thresholdImmunities[key]) if (actor.system.rules.damageReduction.thresholdImmunities[key])
@ -117,9 +111,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id, CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id,
CONFIG.DH.GENERAL.ruleChoice.offWithToggle.id CONFIG.DH.GENERAL.ruleChoice.offWithToggle.id
].includes(this.rulesDefault); ].includes(this.rulesDefault);
context.reduceSeverity = this.reduceSeverity; context.thresholdImmunities = this.thresholdImmunities;
context.thresholdImmunities =
Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null;
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } = const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } =
this.getDamageInfo(); this.getDamageInfo();
@ -181,9 +173,6 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length, this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length,
0 0
); );
if (this.reduceSeverity) {
currentDamage = Math.max(currentDamage - this.reduceSeverity, 0);
}
if (this.thresholdImmunities[currentDamage]) currentDamage = 0; if (this.thresholdImmunities[currentDamage]) currentDamage = 0;

View file

@ -1,5 +1,3 @@
import { refreshIsAllowed } from '../../helpers/utils.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV2) { export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV2) {
@ -93,10 +91,14 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
} }
getRefreshables() { getRefreshables() {
const actionItems = this.actor.items.filter(x => this.actor.system.isItemAvailable(x)).reduce((acc, x) => { const actionItems = this.actor.items.reduce((acc, x) => {
if (x.system.actions) { if (x.system.actions) {
const recoverable = x.system.actions.reduce((acc, action) => { const recoverable = x.system.actions.reduce((acc, action) => {
if (refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], action.uses.recovery)) { if (
action.uses.recovery &&
((action.uses.recovery === 'longRest' && !this.shortrest) ||
action.uses.recovery === 'shortRest')
) {
acc.push({ acc.push({
title: x.name, title: x.name,
name: action.name, name: action.name,
@ -118,7 +120,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
if ( if (
x.system.resource && x.system.resource &&
x.system.resource.type && x.system.resource.type &&
refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], x.system.resource.recovery) ((x.system.resource.recovery === 'longRest') === !this.shortrest ||
x.system.resource.recovery === 'shortRest')
) { ) {
acc.push({ acc.push({
title: game.i18n.localize(`TYPES.Item.${x.type}`), title: game.i18n.localize(`TYPES.Item.${x.type}`),
@ -181,17 +184,12 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
.filter(x => category.moves[x].selected) .filter(x => category.moves[x].selected)
.flatMap(key => { .flatMap(key => {
const move = category.moves[key]; const move = category.moves[key];
const needsTarget = move.actions.filter(x => x.target?.type && x.target.type !== 'self').length > 0;
return [...Array(move.selected).keys()].map(_ => ({ return [...Array(move.selected).keys()].map(_ => ({
...move, ...move,
movePath: `${categoryKey}.moves.${key}`, movePath: `${categoryKey}.moves.${key}`
needsTarget: needsTarget
})); }));
}); });
}); });
const characters = game.actors.filter(x => x.type === 'character')
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
.filter(x => x.uuid !== this.actor.uuid);
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');
const msg = { const msg = {
@ -211,9 +209,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title` `DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
), ),
actor: { name: this.actor.name, img: this.actor.img }, actor: { name: this.actor.name, img: this.actor.img },
moves: moves, moves: moves
characters: characters,
selfId: this.actor.uuid
} }
), ),
flags: { flags: {

View file

@ -1,62 +0,0 @@
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class ItemTransferDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(data) {
super({});
this.data = data;
}
get title() {
return this.data.title;
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dh-style', 'dialog', 'item-transfer'],
position: { width: 400, height: 'auto' },
window: { icon: 'fa-solid fa-hand-holding-hand' },
actions: {
finish: ItemTransferDialog.#finish
}
};
static PARTS = {
main: { template: 'systems/daggerheart/templates/dialogs/item-transfer.hbs', root: true }
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
return foundry.utils.mergeObject(context, this.data);
}
static async #finish() {
this.selected = this.form.elements.quantity.valueAsNumber || null;
this.close();
}
static #determineTransferOptions({ originActor, targetActor, item, currency }) {
originActor ??= item?.actor;
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
return {
originActor,
targetActor,
itemImage: item?.img,
currencyIcon: currencySetting?.icon,
max: item?.system.quantity ?? originActor.system.gold[currency] ?? 0,
title: item?.name ?? currencySetting?.label
};
}
static async configure(options) {
return new Promise(resolve => {
const data = this.#determineTransferOptions(options);
if (data.max <= 1) return resolve(data.max);
const app = new this(data);
app.addEventListener('close', () => resolve(app.selected), { once: true });
app.render({ force: true });
});
}
}

View file

@ -38,6 +38,7 @@ export default class OwnershipSelection extends HandlebarsApplicationMixin(Appli
async _prepareContext(_options) { async _prepareContext(_options) {
const context = await super._prepareContext(_options); const context = await super._prepareContext(_options);
context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels;
context.ownershipOptions = CONFIG.DH.GENERAL.simpleOwnershiplevels; context.ownershipOptions = CONFIG.DH.GENERAL.simpleOwnershiplevels;
context.defaultOwnership = this.defaultOwnership; context.defaultOwnership = this.defaultOwnership;
context.ownership = game.users.reduce((acc, user) => { context.ownership = game.users.reduce((acc, user) => {
@ -51,7 +52,6 @@ export default class OwnershipSelection extends HandlebarsApplicationMixin(Appli
return acc; return acc;
}, {}); }, {});
context.showOwnership = Boolean(Object.keys(context.ownership).length);
return context; return context;
} }

View file

@ -1,4 +1,3 @@
import { getCritDamageBonus } from '../../helpers/utils.mjs';
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -77,37 +76,28 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
cost: this.data.initiator.cost cost: this.data.initiator.cost
}; };
const selectedMember = Object.values(context.members).find(x => x.selected && x.roll); context.selectedData = Object.values(context.members).reduce(
const selectedIsCritical = selectedMember?.roll?.system?.isCritical; (acc, member) => {
context.selectedData = { if (!member.roll) return acc;
result: selectedMember if (member.selected) {
? `${selectedMember.roll.system.roll.total} ${selectedMember.roll.system.roll.result.label}` acc.result = `${member.roll.system.roll.total} ${member.roll.system.roll.result.label}`;
: null, }
damageValues: null
};
for (const member of Object.values(context.members)) { if (context.usesDamage) {
if (!member.roll) continue; if (!acc.damageValues) acc.damageValues = {};
if (context.usesDamage) { for (let damage of member.damageValues) {
if (!context.selectedData.damageValues) context.selectedData.damageValues = {}; if (acc.damageValues[damage.key]) {
for (let damage of member.damageValues) { acc.damageValues[damage.key].total += damage.total;
const damageTotal = member.roll.system.isCritical } else {
? damage.total acc.damageValues[damage.key] = foundry.utils.deepClone(damage);
: selectedIsCritical }
? damage.total + (await getCritDamageBonus(member.roll.system.damage[damage.key].formula))
: damage.total;
if (context.selectedData.damageValues[damage.key]) {
context.selectedData.damageValues[damage.key].total += damageTotal;
} else {
context.selectedData.damageValues[damage.key] = {
...foundry.utils.deepClone(damage),
total: damageTotal
};
} }
} }
}
}
return acc;
},
{ result: null, damageValues: null }
);
context.showResult = Object.values(context.members).reduce((enabled, member) => { context.showResult = Object.values(context.members).reduce((enabled, member) => {
if (!member.roll) return enabled; if (!member.roll) return enabled;
if (context.usesDamage) { if (context.usesDamage) {
@ -211,41 +201,21 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
.map(key => game.messages.get(this.data.members[key].messageId)); .map(key => game.messages.get(this.data.members[key].messageId));
const systemData = foundry.utils.deepClone(mainRoll).system.toObject(); const systemData = foundry.utils.deepClone(mainRoll).system.toObject();
const criticalRoll = systemData.roll.isCritical;
for (let roll of secondaryRolls) { for (let roll of secondaryRolls) {
if (roll.system.hasDamage) { if (roll.system.hasDamage) {
for (let key in roll.system.damage) { for (let key in roll.system.damage) {
var damage = roll.system.damage[key]; var damage = roll.system.damage[key];
const damageTotal =
!roll.system.isCritical && criticalRoll
? (await getCritDamageBonus(damage.formula)) + damage.total
: damage.total;
const updatedDamageParts = damage.parts;
if (systemData.damage[key]) { if (systemData.damage[key]) {
if (!roll.system.isCritical && criticalRoll) { systemData.damage[key].total += damage.total;
for (let part of updatedDamageParts) { systemData.damage[key].parts = [...systemData.damage[key].parts, ...damage.parts];
const criticalDamage = await getCritDamageBonus(part.formula);
if (criticalDamage) {
damage.formula = `${damage.formula} + ${criticalDamage}`;
part.formula = `${part.formula} + ${criticalDamage}`;
part.modifierTotal = part.modifierTotal + criticalDamage;
part.total += criticalDamage;
part.roll = new Roll(part.formula);
}
}
}
systemData.damage[key].formula = `${systemData.damage[key].formula} + ${damage.formula}`;
systemData.damage[key].total += damageTotal;
systemData.damage[key].parts = [...systemData.damage[key].parts, ...updatedDamageParts];
} else { } else {
systemData.damage[key] = { ...damage, total: damageTotal, parts: updatedDamageParts }; systemData.damage[key] = damage;
} }
} }
} }
} }
systemData.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle'); systemData.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
const cls = getDocumentClass('ChatMessage'), const cls = getDocumentClass('ChatMessage'),
msgData = { msgData = {
type: 'dualityRoll', type: 'dualityRoll',
@ -263,16 +233,14 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
const fearUpdate = { key: 'fear', value: null, total: null, enabled: true }; const fearUpdate = { key: 'fear', value: null, total: null, enabled: true };
for (let memberId of Object.keys(this.data.members)) { for (let memberId of Object.keys(this.data.members)) {
const resourceUpdates = []; const resourceUpdates = [];
const rollGivesHope = systemData.roll.isCritical || systemData.roll.result.duality === 1; if (systemData.roll.isCritical || systemData.roll.result.duality === 1) {
if (memberId === this.data.initiator.id) { const value =
const value = this.data.initiator.cost memberId !== this.data.initiator.id
? rollGivesHope ? 1
? 1 - this.data.initiator.cost : this.data.initiator.cost
: -this.data.initiator.cost ? 1 - this.data.initiator.cost
: 1; : 1;
resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true }); resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true });
} else if (rollGivesHope) {
resourceUpdates.push({ key: 'hope', value: 1, total: -1, enabled: true });
} }
if (systemData.roll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true }); if (systemData.roll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true });
if (systemData.roll.result.duality === -1) { if (systemData.roll.result.duality === -1) {

View file

@ -21,7 +21,6 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
async _prepareContext(options) { async _prepareContext(options) {
const context = await super._prepareContext(options); const context = await super._prepareContext(options);
if (!this.actor) return context;
context.partyOnCanvas = context.partyOnCanvas =
this.actor.type === 'party' && this.actor.type === 'party' &&
@ -32,13 +31,9 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type) context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type)
? false ? false
: context.canToggleCombat; : context.canToggleCombat;
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => { context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
const effect = context.statusEffects[key]; const effect = context.statusEffects[key];
if (effect.systemEffect) { if (effect.systemEffect) acc[key] = effect;
const disabled = !effect.isActive && this.actor.system.rules?.conditionImmunities?.[key];
acc[key] = { ...effect, disabled };
}
return acc; return acc;
}, {}); }, {});
@ -60,33 +55,14 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
} }
static async #onToggleCombat() { static async #onToggleCombat() {
const tokensWithoutActors = canvas.tokens.controlled.filter(t => !t.actor);
const warning =
tokensWithoutActors.length === 1
? game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorMissing', {
name: tokensWithoutActors[0].name
})
: game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorsMissing', {
names: tokensWithoutActors.map(x => x.name).join(', ')
});
const tokens = canvas.tokens.controlled const tokens = canvas.tokens.controlled
.filter(t => t.actor && !DHTokenHUD.#nonCombatTypes.includes(t.actor.type)) .filter(t => !t.actor || !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
.map(t => t.document); .map(t => t.document);
if (!this.object.controlled && this.document.actor) tokens.push(this.document); if (!this.object.controlled) tokens.push(this.document);
try { try {
if (this.document.inCombat) { if (this.document.inCombat) await TokenDocument.implementation.deleteCombatants(tokens);
const tokensInCombat = tokens.filter(t => t.inCombat); else await TokenDocument.implementation.createCombatants(tokens);
await TokenDocument.implementation.deleteCombatants([...tokensInCombat, ...tokensWithoutActors]);
} else {
if (tokensWithoutActors.length) {
ui.notifications.warn(warning);
}
const tokensOutOfCombat = tokens.filter(t => !t.inCombat);
await TokenDocument.implementation.createCombatants(tokensOutOfCombat);
}
} catch (err) { } catch (err) {
ui.notifications.warn(err.message); ui.notifications.warn(err.message);
} }
@ -214,20 +190,16 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
} }
// Update the status of effects which are active for the token actor // Update the status of effects which are active for the token actor
const activeEffects = this.actor?.getActiveEffects() || []; const activeEffects = this.actor?.effects || [];
for (const effect of activeEffects) { for (const effect of activeEffects) {
for (const statusId of effect.statuses) { for (const statusId of effect.statuses) {
const status = choices[statusId]; const status = choices[statusId];
if (!status) continue; if (!status) continue;
status.instances = 1 + (status.instances ?? 0);
status.locked = status.locked || effect.condition || status.instances > 1;
if (!status) continue;
if (status._id) { if (status._id) {
if (status._id !== effect.id) continue; if (status._id !== effect.id) continue;
} }
status.isActive = true; status.isActive = true;
if (effect.getFlag?.('core', 'overlay')) status.isOverlay = true; if (effect.getFlag('core', 'overlay')) status.isOverlay = true;
} }
} }

View file

@ -280,19 +280,11 @@ export default class DhCharacterLevelUp extends LevelUpBase {
break; break;
case 'experience': case 'experience':
if (!advancement[choiceKey]) advancement[choiceKey] = []; if (!advancement[choiceKey]) advancement[choiceKey] = [];
const allExperiences = {
...this.actor.system.experiences,
...Object.values(this.levelup.levels).reduce((acc, level) => {
for (const key of Object.keys(level.achievements.experiences)) {
acc[key] = level.achievements.experiences[key];
}
return acc;
}, {})
};
const data = checkbox.data.map(data => { const data = checkbox.data.map(data => {
const experience = Object.keys(allExperiences).find(x => x === data); const experience = Object.keys(this.actor.system.experiences).find(
return allExperiences[experience]?.name ?? ''; x => x === data
);
return this.actor.system.experiences[experience]?.name ?? '';
}); });
advancement[choiceKey].push({ data: data, value: checkbox.value }); advancement[choiceKey].push({ data: data, value: checkbox.value });
break; break;

View file

@ -357,23 +357,11 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases'); const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
if (experienceIncreaseTagify) { if (experienceIncreaseTagify) {
const allExperiences = {
...this.actor.system.experiences,
...Object.values(this.levelup.levels).reduce((acc, level) => {
for (const key of Object.keys(level.achievements.experiences)) {
acc[key] = level.achievements.experiences[key];
}
return acc;
}, {})
};
tagifyElement( tagifyElement(
experienceIncreaseTagify, experienceIncreaseTagify,
Object.keys(allExperiences).reduce((acc, id) => { Object.keys(this.actor.system.experiences).reduce((acc, id) => {
const experience = allExperiences[id]; const experience = this.actor.system.experiences[id];
if (experience.name) { acc.push({ id: id, label: experience.name });
acc.push({ id: id, label: experience.name });
}
return acc; return acc;
}, []), }, []),

View file

@ -144,7 +144,6 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
context.diceSoNiceSystems = Object.fromEntries( context.diceSoNiceSystems = Object.fromEntries(
[...game.dice3d.DiceFactory.systems].map(([k, v]) => [k, v.name]) [...game.dice3d.DiceFactory.systems].map(([k, v]) => [k, v.name])
); );
context.diceSoNiceFonts = game.dice3d.exports.Utils.prepareFontList();
foundry.utils.mergeObject( foundry.utils.mergeObject(
context.dsnTabs, context.dsnTabs,

View file

@ -32,7 +32,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
icon: 'fa-solid fa-gears' icon: 'fa-solid fa-gears'
}, },
actions: { actions: {
editCurrencyIcon: this.changeCurrencyIcon,
addItem: this.addItem, addItem: this.addItem,
editItem: this.editItem, editItem: this.editItem,
removeItem: this.removeItem, removeItem: this.removeItem,
@ -44,7 +43,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
deleteAdversaryType: this.deleteAdversaryType, deleteAdversaryType: this.deleteAdversaryType,
selectAdversaryType: this.selectAdversaryType, selectAdversaryType: this.selectAdversaryType,
save: this.save, save: this.save,
resetTokenSizes: this.resetTokenSizes,
reset: this.reset reset: this.reset
}, },
form: { handler: this.updateData, submitOnChange: true } form: { handler: this.updateData, submitOnChange: true }
@ -117,45 +115,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
this.render(); this.render();
} }
static async changeCurrencyIcon(_, target) {
const type = target.dataset.currency;
const currentIcon = this.settings.currency[type].icon;
const icon = await foundry.applications.api.DialogV2.input({
classes: ['daggerheart', 'dh-style', 'change-currency-icon'],
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/settings/homebrew-settings/change-currency-icon.hbs',
{ currentIcon }
),
window: {
title: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.currency.changeIcon'),
icon: 'fa-solid fa-coins'
},
render: (_, dialog) => {
const icon = dialog.element.querySelector('.displayed-icon i');
const input = dialog.element.querySelector('input');
const reset = dialog.element.querySelector('button[data-action=reset]');
input.addEventListener('input', () => {
icon.classList.value = input.value;
});
reset.addEventListener('click', () => {
const currencyField = DhHomebrew.schema.fields.currency.fields[type];
const initial = currencyField.fields.icon.getInitialValue();
input.value = icon.classList.value = initial;
});
},
ok: {
callback: (_, button) => button.form.elements.icon.value
}
});
if (icon !== null) {
await this.settings.updateSource({
[`currency.${type}.icon`]: icon
});
this.render();
}
}
static async addItem(_, target) { static async addItem(_, target) {
const { type } = target.dataset; const { type } = target.dataset;
if (['shortRest', 'longRest'].includes(type)) { if (['shortRest', 'longRest'].includes(type)) {
@ -425,14 +384,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
this.close(); this.close();
} }
static async resetTokenSizes() {
await this.settings.updateSource({
tokenSizes: this.settings.schema.fields.tokenSizes.initial
});
this.render();
}
static async reset() { static async reset() {
const confirmed = await foundry.applications.api.DialogV2.confirm({ const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { window: {

View file

@ -1,5 +1,4 @@
export { default as ActionConfig } from './action-config.mjs'; export { default as ActionConfig } from './action-config.mjs';
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';

View file

@ -1,236 +0,0 @@
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
const { ApplicationV2 } = foundry.applications.api;
export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) {
constructor(action) {
super({});
this.action = action;
this.openSection = null;
}
get title() {
return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}: ${this.action.name}`;
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dh-style', 'dialog', 'max-800'],
window: {
icon: 'fa-solid fa-wrench',
resizable: false
},
position: { width: 600, height: 'auto' },
actions: {
toggleSection: this.toggleSection,
addEffect: this.addEffect,
removeEffect: this.removeEffect,
addElement: this.addElement,
removeElement: this.removeElement,
editEffect: this.editEffect,
addDamage: this.addDamage,
removeDamage: this.removeDamage
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
};
static PARTS = {
header: {
id: 'header',
template: 'systems/daggerheart/templates/sheets-settings/action-settings/header.hbs'
},
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
base: {
id: 'base',
template: 'systems/daggerheart/templates/sheets-settings/action-settings/base.hbs'
},
configuration: {
id: 'configuration',
template: 'systems/daggerheart/templates/sheets-settings/action-settings/configuration.hbs'
},
effect: {
id: 'effect',
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
}
};
static TABS = {
base: {
active: true,
cssClass: '',
group: 'primary',
id: 'base',
icon: null,
label: 'DAGGERHEART.GENERAL.Tabs.base'
},
config: {
active: false,
cssClass: '',
group: 'primary',
id: 'config',
icon: null,
label: 'DAGGERHEART.GENERAL.Tabs.configuration'
},
effect: {
active: false,
cssClass: '',
group: 'primary',
id: 'effect',
icon: null,
label: 'DAGGERHEART.GENERAL.Tabs.effects'
}
};
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects'];
_getTabs(tabs) {
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
v.cssClass = v.active ? 'active' : '';
}
return tabs;
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options, 'action');
context.source = this.action.toObject(true);
context.openSection = this.openSection;
context.tabs = this._getTabs(this.constructor.TABS);
context.config = CONFIG.DH;
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
context.hasBaseDamage = !!this.action.parent.attack;
context.costOptions = this.getCostOptions();
context.getRollTypeOptions = this.getRollTypeOptions();
context.disableOption = this.disableOption.bind(this);
context.isNPC = this.action.actor?.isNPC;
context.baseSaveDifficulty = this.action.actor?.baseSaveDifficulty;
context.baseAttackBonus = this.action.actor?.system.attack?.roll.bonus;
context.hasRoll = this.action.hasRoll;
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
context.tierOptions = [
{ key: 1, label: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') },
...Object.values(settingsTiers).map(x => ({ key: x.tier, label: x.name }))
];
return context;
}
static toggleSection(_, button) {
this.openSection = button.dataset.section === this.openSection ? null : button.dataset.section;
this.render(true);
}
getCostOptions() {
const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
const resource = this.action.parent.resource;
if (resource) {
options.resource = {
label: 'DAGGERHEART.GENERAL.itemResource',
group: 'Global'
};
}
if (this.action.parent.metadata?.isQuantifiable) {
options.quantity = {
label: 'DAGGERHEART.GENERAL.itemQuantity',
group: 'Global'
};
}
return options;
}
getRollTypeOptions() {
const types = foundry.utils.deepClone(CONFIG.DH.GENERAL.rollTypes);
if (!this.action.actor) return types;
Object.values(types).forEach(t => {
if (this.action.actor.type !== 'character' && t.playerOnly) delete types[t.id];
});
return types;
}
disableOption(index, costOptions, choices) {
const filtered = foundry.utils.deepClone(costOptions);
Object.keys(filtered).forEach(o => {
if (choices.find((c, idx) => c.type === o && index !== idx)) filtered[o].disabled = true;
});
return filtered;
}
_prepareSubmitData(_event, formData) {
const submitData = foundry.utils.expandObject(formData.object);
const itemAbilityCostKeys = Object.keys(CONFIG.DH.GENERAL.itemAbilityCosts);
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
const data = foundry.utils.getProperty(submitData, keyPath);
const dataValues = data ? Object.values(data) : [];
if (keyPath === 'cost') {
for (var value of dataValues) {
value.itemId = itemAbilityCostKeys.includes(value.key) ? this.action.parent.parent.id : null;
}
}
if (data) foundry.utils.setProperty(submitData, keyPath, dataValues);
}
return submitData;
}
static async updateForm(event, _, formData) {
const submitData = this._prepareSubmitData(event, formData),
data = foundry.utils.mergeObject(this.action.toObject(), submitData);
this.action = await this.action.update(data);
this.sheetUpdate?.(this.action);
this.render();
}
static addElement(event) {
const data = this.action.toObject(),
key = event.target.closest('[data-key]').dataset.key;
if (!this.action[key]) return;
data[key].push(this.action.defaultValues[key] ?? {});
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static removeElement(event, button) {
event.stopPropagation();
const data = this.action.toObject(),
key = event.target.closest('[data-key]').dataset.key,
index = button.dataset.index;
data[key].splice(index, 1);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static addDamage(_event) {
if (!this.action.damage.parts) return;
const data = this.action.toObject(),
part = {};
if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
data.damage.parts.push(part);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static removeDamage(_event, button) {
if (!this.action.damage.parts) return;
const data = this.action.toObject(),
index = button.dataset.index;
data.damage.parts.splice(index, 1);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
/** Specific implementation in extending classes **/
static async addEffect(_event) {}
static removeEffect(_event, _button) {}
static editEffect(_event) {}
async close(options) {
this.tabGroups.primary = 'base';
await super.close(options);
}
}

View file

@ -1,32 +1,240 @@
import DHActionBaseConfig from './action-base-config.mjs'; import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
const { ApplicationV2 } = foundry.applications.api;
export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
constructor(action, sheetUpdate) {
super({});
this.action = action;
this.sheetUpdate = sheetUpdate;
this.openSection = null;
}
get title() {
return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}: ${this.action.name}`;
}
export default class DHActionConfig extends DHActionBaseConfig {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
...DHActionBaseConfig.DEFAULT_OPTIONS, tag: 'form',
classes: ['daggerheart', 'dh-style', 'dialog', 'max-800'],
window: {
icon: 'fa-solid fa-wrench',
resizable: false
},
position: { width: 600, height: 'auto' },
actions: { actions: {
...DHActionBaseConfig.DEFAULT_OPTIONS.actions, toggleSection: this.toggleSection,
addEffect: this.addEffect, addEffect: this.addEffect,
removeEffect: this.removeEffect, removeEffect: this.removeEffect,
editEffect: this.editEffect addElement: this.addElement,
removeElement: this.removeElement,
editEffect: this.editEffect,
addDamage: this.addDamage,
removeDamage: this.removeDamage
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
} }
}; };
async _prepareContext(options) { static PARTS = {
const context = await super._prepareContext(options); header: {
if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id)); id: 'header',
context.getEffectDetails = this.getEffectDetails.bind(this); template: 'systems/daggerheart/templates/sheets-settings/action-settings/header.hbs'
},
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
base: {
id: 'base',
template: 'systems/daggerheart/templates/sheets-settings/action-settings/base.hbs'
},
configuration: {
id: 'configuration',
template: 'systems/daggerheart/templates/sheets-settings/action-settings/configuration.hbs'
},
effect: {
id: 'effect',
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
}
};
static TABS = {
base: {
active: true,
cssClass: '',
group: 'primary',
id: 'base',
icon: null,
label: 'DAGGERHEART.GENERAL.Tabs.base'
},
config: {
active: false,
cssClass: '',
group: 'primary',
id: 'config',
icon: null,
label: 'DAGGERHEART.GENERAL.Tabs.configuration'
},
effect: {
active: false,
cssClass: '',
group: 'primary',
id: 'effect',
icon: null,
label: 'DAGGERHEART.GENERAL.Tabs.effects'
}
};
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects'];
_getTabs(tabs) {
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
v.cssClass = v.active ? 'active' : '';
}
return tabs;
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options, 'action');
context.source = this.action.toObject(false);
context.openSection = this.openSection;
context.tabs = this._getTabs(this.constructor.TABS);
context.config = CONFIG.DH;
if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id));
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
context.hasBaseDamage = !!this.action.parent.attack;
context.getEffectDetails = this.getEffectDetails.bind(this);
context.costOptions = this.getCostOptions();
context.getRollTypeOptions = this.getRollTypeOptions();
context.disableOption = this.disableOption.bind(this);
context.isNPC = this.action.actor?.isNPC;
context.baseSaveDifficulty = this.action.actor?.baseSaveDifficulty;
context.baseAttackBonus = this.action.actor?.system.attack?.roll.bonus;
context.hasRoll = this.action.hasRoll;
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
context.tierOptions = [
{ key: 1, label: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') },
...Object.values(settingsTiers).map(x => ({ key: x.tier, label: x.name }))
];
return context; return context;
} }
static async addEffect(_event) { static toggleSection(_, button) {
if (!this.action.effects) return; this.openSection = button.dataset.section === this.openSection ? null : button.dataset.section;
const effectData = this._addEffectData.bind(this)(); this.render(true);
const data = this.action.toObject(); }
const [created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], { getCostOptions() {
render: false const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
const resource = this.action.parent.resource;
if (resource) {
options.resource = {
label: 'DAGGERHEART.GENERAL.itemResource',
group: 'Global'
};
}
if (this.action.parent.metadata?.isQuantifiable) {
options.quantity = {
label: 'DAGGERHEART.GENERAL.itemQuantity',
group: 'Global'
};
}
return options;
}
getRollTypeOptions() {
const types = foundry.utils.deepClone(CONFIG.DH.GENERAL.rollTypes);
if (!this.action.actor) return types;
Object.values(types).forEach(t => {
if (this.action.actor.type !== 'character' && t.playerOnly) delete types[t.id];
}); });
return types;
}
disableOption(index, costOptions, choices) {
const filtered = foundry.utils.deepClone(costOptions);
Object.keys(filtered).forEach(o => {
if (choices.find((c, idx) => c.type === o && index !== idx)) filtered[o].disabled = true;
});
return filtered;
}
getEffectDetails(id) {
return this.action.item.effects.get(id);
}
_prepareSubmitData(_event, formData) {
const submitData = foundry.utils.expandObject(formData.object);
const itemAbilityCostKeys = Object.keys(CONFIG.DH.GENERAL.itemAbilityCosts);
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
const data = foundry.utils.getProperty(submitData, keyPath);
const dataValues = data ? Object.values(data) : [];
if (keyPath === 'cost') {
for (var value of dataValues) {
value.itemId = itemAbilityCostKeys.includes(value.key) ? this.action.parent.parent.id : null;
}
}
if (data) foundry.utils.setProperty(submitData, keyPath, dataValues);
}
return submitData;
}
static async updateForm(event, _, formData) {
const submitData = this._prepareSubmitData(event, formData),
data = foundry.utils.mergeObject(this.action.toObject(), submitData);
this.action = await this.action.update(data);
this.sheetUpdate?.(this.action);
this.render();
}
static addElement(event) {
const data = this.action.toObject(),
key = event.target.closest('[data-key]').dataset.key;
if (!this.action[key]) return;
data[key].push({});
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static removeElement(event, button) {
event.stopPropagation();
const data = this.action.toObject(),
key = event.target.closest('[data-key]').dataset.key,
index = button.dataset.index;
data[key].splice(index, 1);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static addDamage(event) {
if (!this.action.damage.parts) return;
const data = this.action.toObject(),
part = {};
if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
data.damage.parts.push(part);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static removeDamage(event, button) {
if (!this.action.damage.parts) return;
const data = this.action.toObject(),
index = button.dataset.index;
data.damage.parts.splice(index, 1);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static async addEffect(event) {
if (!this.action.effects) return;
const effectData = this._addEffectData.bind(this)(),
[created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], { render: false }),
data = this.action.toObject();
data.effects.push({ _id: created._id }); data.effects.push({ _id: created._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._id).sheet.render(true);
@ -46,10 +254,6 @@ export default class DHActionConfig extends DHActionBaseConfig {
}; };
} }
getEffectDetails(id) {
return this.action.item.effects.get(id);
}
static removeEffect(event, button) { static removeEffect(event, button) {
if (!this.action.effects) return; if (!this.action.effects) return;
const index = button.dataset.index, const index = button.dataset.index,

View file

@ -1,66 +0,0 @@
import DHActionBaseConfig from './action-base-config.mjs';
export default class DHActionSettingsConfig extends DHActionBaseConfig {
constructor(action, effects, sheetUpdate) {
super(action);
this.effects = effects;
this.sheetUpdate = sheetUpdate;
}
static DEFAULT_OPTIONS = {
...DHActionBaseConfig.DEFAULT_OPTIONS,
actions: {
...DHActionBaseConfig.DEFAULT_OPTIONS.actions,
addEffect: this.addEffect,
removeEffect: this.removeEffect,
editEffect: this.editEffect
}
};
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.effects = this.effects;
context.getEffectDetails = this.getEffectDetails.bind(this);
return context;
}
getEffectDetails(id) {
return this.effects.find(x => x.id === id);
}
static async addEffect(_event) {
if (!this.action.effects) return;
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject();
const data = this.action.toObject();
this.sheetUpdate(data, effectData);
this.effects = [...this.effects, effectData];
data.effects.push({ _id: effectData.id });
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static removeEffect(event, button) {
if (!this.action.effects) return;
const index = button.dataset.index,
effectId = this.action.effects[index]._id;
this.constructor.removeElement.bind(this)(event, button);
this.sheetUpdate(
this.action.toObject(),
this.effects.find(x => x.id === effectId),
true
);
}
static async editEffect(event) {
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
const updatedEffect = await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(
this.getEffectDetails(id)
);
if (!updatedEffect) return;
this.effects = await this.sheetUpdate(this.action.toObject(), { ...updatedEffect, id });
this.render();
}
}

View file

@ -9,9 +9,6 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
if (!ignoredActorKeys.includes(key)) { if (!ignoredActorKeys.includes(key)) {
const model = game.system.api.models.actors[key]; const model = game.system.api.models.actors[key];
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model); const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
// As per DHToken._getTrackedAttributesFromSchema, attributes.bar have a max version as well.
const maxAttributes = attributes.bar.map(x => [...x, 'max']);
attributes.value.push(...maxAttributes);
const group = game.i18n.localize(model.metadata.label); const group = game.i18n.localize(model.metadata.label);
const choices = CONFIG.Token.documentClass const choices = CONFIG.Token.documentClass
.getTrackedAttributeChoices(attributes, model) .getTrackedAttributeChoices(attributes, model)

View file

@ -51,19 +51,6 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
} }
}; };
async _prepareContext(options) {
const context = await super._prepareContext(options);
const featureForms = ['passive', 'action', 'reaction'];
context.features = context.document.system.features.sort((a, b) =>
a.system.featureForm !== b.system.featureForm
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
: a.sort - b.sort
);
return context;
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**

View file

@ -49,19 +49,6 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
} }
}; };
async _prepareContext(options) {
const context = await super._prepareContext(options);
const featureForms = ['passive', 'action', 'reaction'];
context.features = context.document.system.features.sort((a, b) =>
a.system.featureForm !== b.system.featureForm
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
: a.sort - b.sort
);
return context;
}
/** /**
* Adds a new category entry to the actor. * Adds a new category entry to the actor.
* @type {ApplicationClickAction} * @type {ApplicationClickAction}
@ -122,9 +109,9 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
async _onDrop(event) { async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await fromUuid(data.uuid); if (data.fromInternal) return;
if (data.fromInternal && item?.parent?.uuid === this.actor.uuid) return;
const item = await fromUuid(data.uuid);
if (item.type === 'adversary' && event.target.closest('.category-container')) { if (item.type === 'adversary' && event.target.closest('.category-container')) {
const target = event.target.closest('.category-container'); const target = event.target.closest('.category-container');
const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`; const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`;

View file

@ -1,30 +1,20 @@
import DHTokenConfigMixin from './token-config-mixin.mjs'; export default class DhPrototypeTokenConfig extends foundry.applications.sheets.PrototypeTokenConfig {
import { getActorSizeFromForm } from './token-config-mixin.mjs';
export default class DhPrototypeTokenConfig extends DHTokenConfigMixin(
foundry.applications.sheets.PrototypeTokenConfig
) {
/** @inheritDoc */ /** @inheritDoc */
static DEFAULT_OPTIONS = { async _prepareResourcesTab() {
...super.DEFAULT_OPTIONS, const token = this.token;
form: { handler: DhPrototypeTokenConfig.#onSubmit } const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
}; const attributeSource =
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
/** ? this.actor?.type
* Process form submission for the sheet : this.actor?.system;
* @this {PrototypeTokenConfig} const TokenDocument = foundry.utils.getDocumentClass('Token');
* @type {ApplicationFormSubmission} const attributes = TokenDocument.getTrackedAttributes(attributeSource);
*/ return {
static async #onSubmit(event, form, formData) { barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
const submitData = this._processFormData(event, form, formData); bar1: token.getBarAttribute?.('bar1'),
submitData.detectionModes ??= []; // Clear detection modes array bar2: token.getBarAttribute?.('bar2'),
this._processChanges(submitData); turnMarkerModes: DhPrototypeTokenConfig.TURN_MARKER_MODES,
const changes = { prototypeToken: submitData }; turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
};
const changedTokenSizeValue = getActorSizeFromForm(this.element, this.actor);
if (changedTokenSizeValue) changes.system = { size: changedTokenSizeValue };
this.actor.validate({ changes, clean: true, fallback: false });
await this.actor.update(changes);
} }
} }

View file

@ -23,7 +23,7 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
} }
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config', 'standard-form'], classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config'],
tag: 'form', tag: 'form',
position: { position: {
width: 560 width: 560
@ -131,7 +131,6 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
if (partId in context.tabs) context.tab = context.tabs[partId]; if (partId in context.tabs) context.tab = context.tabs[partId];
switch (partId) { switch (partId) {
case 'details': case 'details':
context.statuses = CONFIG.statusEffects.map(s => ({ value: s.id, label: game.i18n.localize(s.name) }));
context.isActorEffect = false; context.isActorEffect = false;
context.isItemEffect = true; context.isItemEffect = true;
const useGeneric = game.settings.get( const useGeneric = game.settings.get(
@ -139,13 +138,10 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
CONFIG.DH.SETTINGS.gameSettings.appearance CONFIG.DH.SETTINGS.gameSettings.appearance
).showGenericStatusEffects; ).showGenericStatusEffects;
if (!useGeneric) { if (!useGeneric) {
context.statuses = [ context.statuses = Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
...context.statuses, value: status.id,
Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({ label: game.i18n.localize(status.name)
value: status.id, }));
label: game.i18n.localize(status.name)
}))
];
} }
break; break;
case 'changes': case 'changes':
@ -161,7 +157,7 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
return context; return context;
} }
static async #onSubmit(_event, _form, formData) { static async #onSubmit(event, form, formData) {
this.data = foundry.utils.expandObject(formData.object); this.data = foundry.utils.expandObject(formData.object);
this.close(); this.close();
} }
@ -197,11 +193,11 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
* @type {ApplicationClickAction} * @type {ApplicationClickAction}
*/ */
static async #addChange() { static async #addChange() {
const { changes, ...rest } = foundry.utils.expandObject(new FormDataExtended(this.form).object); const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
const updatedChanges = Object.values(changes ?? {}); const changes = Object.values(submitData.changes ?? {});
updatedChanges.push({}); changes.push({});
this.effect = { ...rest, changes: updatedChanges }; this.effect.changes = changes;
this.render(); this.render();
} }
@ -212,12 +208,12 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
*/ */
static async #deleteChange(event) { static async #deleteChange(event) {
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object); const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
const updatedChanges = Object.values(submitData.changes); const changes = Object.values(submitData.changes);
const row = event.target.closest('li'); const row = event.target.closest('li');
const index = Number(row.dataset.index) || 0; const index = Number(row.dataset.index) || 0;
updatedChanges.splice(index, 1); changes.splice(index, 1);
this.effect = { ...submitData, changes: updatedChanges }; this.effect.changes = changes;
this.render(); this.render();
} }

View file

@ -1,5 +1,5 @@
import { actionsTypes } from '../../data/action/_module.mjs'; import { actionsTypes } from '../../data/action/_module.mjs';
import ActionSettingsConfig from './action-settings-config.mjs'; import DHActionConfig from './action-config.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -102,8 +102,6 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
return ( return (
(await foundry.applications.api.DialogV2.input({ (await foundry.applications.api.DialogV2.input({
window: { title: game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectType') }, window: { title: game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectType') },
position: { width: 300 },
classes: ['daggerheart', 'dh-style'],
content: await foundry.applications.handlebars.renderTemplate( content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/actionTypes/actionType.hbs', 'systems/daggerheart/templates/actionTypes/actionType.hbs',
{ types: CONFIG.DH.ACTIONS.actionTypes } { types: CONFIG.DH.ACTIONS.actionTypes }
@ -160,55 +158,16 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
this.render(); this.render();
} else { } else {
const action = this.move.actions.get(id); const action = this.move.actions.get(id);
await new ActionSettingsConfig(action, this.move.effects, async (updatedMove, effectData, deleteEffect) => { await new DHActionConfig(action, async updatedMove => {
let updatedEffects = null;
if (effectData) {
const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`);
const existingEffectIndex = currentEffects.findIndex(x => x.id === effectData.id);
updatedEffects = deleteEffect
? currentEffects.filter(x => x.id !== effectData.id)
: existingEffectIndex === -1
? [...currentEffects, effectData]
: currentEffects.with(existingEffectIndex, effectData);
await this.settings.updateSource({
[`${this.movePath}.effects`]: updatedEffects
});
}
await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove }); await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove });
this.move = foundry.utils.getProperty(this.settings, this.movePath); this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render(); this.render();
return updatedEffects;
}).render(true); }).render(true);
} }
} }
static async removeItem(_, target) { static async removeItem(_, target) {
const { type, id } = target.dataset; await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
if (type === 'effect') {
const move = foundry.utils.getProperty(this.settings, this.movePath);
for (const action of move.actions) {
const remainingEffects = action.effects.filter(x => x._id !== id);
if (action.effects.length !== remainingEffects.length) {
await action.update({
effects: remainingEffects.map(x => {
const { _id, ...rest } = x;
return { ...rest, _id: _id };
})
});
}
}
await this.settings.updateSource({
[this.movePath]: {
effects: move.effects.filter(x => x.id !== id),
actions: move.actions
}
});
} else {
await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
}
this.move = foundry.utils.getProperty(this.settings, this.movePath); this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render(); this.render();
} }

View file

@ -1,114 +0,0 @@
export default function DHTokenConfigMixin(Base) {
class DHTokenConfigBase extends Base {
/** @override */
static PARTS = {
tabs: super.PARTS.tabs,
identity: super.PARTS.identity,
appearance: {
template: 'systems/daggerheart/templates/sheets-settings/token-config/appearance.hbs',
scrollable: ['']
},
vision: super.PARTS.vision,
light: super.PARTS.light,
resources: super.PARTS.resources,
footer: super.PARTS.footer
};
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
switch (partId) {
case 'appearance':
htmlElement
.querySelector('#dhTokenSize')
?.addEventListener('change', this.onTokenSizeChange.bind(this));
break;
}
}
/** @inheritDoc */
async _prepareResourcesTab() {
const token = this.token;
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
const attributeSource =
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
? this.actor?.type
: this.actor?.system;
const TokenDocument = foundry.utils.getDocumentClass('Token');
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
return {
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
bar1: token.getBarAttribute?.('bar1'),
bar2: token.getBarAttribute?.('bar2'),
turnMarkerModes: DHTokenConfigBase.TURN_MARKER_MODES,
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
};
}
async _prepareAppearanceTab() {
const context = await super._prepareAppearanceTab();
context.tokenSizes = CONFIG.DH.ACTOR.tokenSize;
context.tokenSize = this.actor?.system?.size;
context.usesActorSize = this.actor?.system?.metadata?.usesSize;
context.actorSizeDisable = context.usesActorSize && this.actor.system.size !== 'custom';
return context;
}
/** @inheritDoc */
_previewChanges(changes) {
if (!changes || !this._preview) return;
const tokenSizeSelect = this.element?.querySelector('#dhTokenSize');
if (this.actor && tokenSizeSelect && tokenSizeSelect.value !== 'custom') {
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
const tokenSize = tokenSizes[tokenSizeSelect.value];
changes.width = tokenSize;
changes.height = tokenSize;
}
const deletions = { '-=actorId': null, '-=actorLink': null };
const mergeOptions = { inplace: false, performDeletions: true };
this._preview.updateSource(mergeObject(changes, deletions, mergeOptions));
if (this._preview?.object?.destroyed === false) {
this._preview.object.initializeSources();
this._preview.object.renderFlags.set({ refresh: true });
}
}
async onTokenSizeChange(event) {
const value = event.target.value;
const tokenSizeDimensions = this.element.querySelector('#tokenSizeDimensions');
if (tokenSizeDimensions) {
const disabled = value !== 'custom';
tokenSizeDimensions.dataset.tooltip = disabled
? game.i18n.localize('DAGGERHEART.APPLICATIONS.TokenConfig.actorSizeUsed')
: '';
const disabledIcon = tokenSizeDimensions.querySelector('i');
if (disabledIcon) {
disabledIcon.style.opacity = disabled ? '' : '0';
}
const dimensionsInputs = tokenSizeDimensions.querySelectorAll('.form-fields input');
for (const input of dimensionsInputs) {
input.disabled = disabled;
}
}
}
}
return DHTokenConfigBase;
}
export function getActorSizeFromForm(element, actor) {
const tokenSizeSelect = element.querySelector('#dhTokenSize');
const isSizeDifferent = tokenSizeSelect?.value !== actor?.system?.size;
if (tokenSizeSelect && actor && isSizeDifferent) {
return tokenSizeSelect.value;
}
return null;
}

View file

@ -1,11 +1,20 @@
import DHTokenConfigMixin from './token-config-mixin.mjs'; export default class DhTokenConfig extends foundry.applications.sheets.TokenConfig {
import { getActorSizeFromForm } from './token-config-mixin.mjs'; /** @inheritDoc */
async _prepareResourcesTab() {
export default class DhTokenConfig extends DHTokenConfigMixin(foundry.applications.sheets.TokenConfig) { const token = this.token;
async _processSubmitData(event, form, submitData, options) { const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
const changedTokenSizeValue = getActorSizeFromForm(this.element, this.actor); const attributeSource =
if (changedTokenSizeValue) this.token.actor.update({ 'system.size': changedTokenSizeValue }); this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
? this.actor?.type
super._processSubmitData(event, form, submitData, options); : this.actor?.system;
const TokenDocument = foundry.utils.getDocumentClass('Token');
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
return {
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
bar1: token.getBarAttribute?.('bar1'),
bar2: token.getBarAttribute?.('bar2'),
turnMarkerModes: DhTokenConfig.TURN_MARKER_MODES,
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
};
} }
} }

View file

@ -25,13 +25,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
action: 'editAttribution' action: 'editAttribution'
} }
] ]
}, }
dragDrop: [
{
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
dropSelector: null
}
],
}; };
static PARTS = { static PARTS = {
@ -93,13 +87,6 @@ export default class AdversarySheet extends DHBaseActorSheet {
context.resources.stress.emptyPips = context.resources.stress.emptyPips =
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0; context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
const featureForms = ['passive', 'action', 'reaction'];
context.features = this.document.system.features.sort((a, b) =>
a.system.featureForm !== b.system.featureForm
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
: a.sort - b.sort
);
return context; return context;
} }
@ -176,16 +163,6 @@ export default class AdversarySheet extends DHBaseActorSheet {
}); });
} }
/** @inheritdoc */
async _onDragStart(event) {
const inventoryItem = event.currentTarget.closest('.inventory-item');
if (inventoryItem) {
event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0);
}
super._onDragStart(event);
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Application Clicks Actions */ /* Application Clicks Actions */
/* -------------------------------------------- */ /* -------------------------------------------- */

View file

@ -8,6 +8,7 @@ import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
const { TextEditor } = foundry.applications.ux;
export default class CharacterSheet extends DHBaseActorSheet { export default class CharacterSheet extends DHBaseActorSheet {
/**@inheritdoc */ /**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
@ -30,10 +31,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
toggleEquipItem: CharacterSheet.#toggleEquipItem, toggleEquipItem: CharacterSheet.#toggleEquipItem,
toggleResourceDice: CharacterSheet.#toggleResourceDice, toggleResourceDice: CharacterSheet.#toggleResourceDice,
handleResourceDice: CharacterSheet.#handleResourceDice, handleResourceDice: CharacterSheet.#handleResourceDice,
advanceResourceDie: CharacterSheet.#advanceResourceDie,
cancelBeastform: CharacterSheet.#cancelBeastform, cancelBeastform: CharacterSheet.#cancelBeastform,
useDowntime: this.useDowntime, useDowntime: this.useDowntime
viewParty: CharacterSheet.#viewParty,
}, },
window: { window: {
resizable: true, resizable: true,
@ -47,7 +46,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
}, },
dragDrop: [ dragDrop: [
{ {
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]', dragSelector: '[data-item-id][draggable="true"]',
dropSelector: null dropSelector: null
} }
], ],
@ -140,15 +139,15 @@ export default class CharacterSheet extends DHBaseActorSheet {
element.addEventListener('change', this.updateItemResource.bind(this)); element.addEventListener('change', this.updateItemResource.bind(this));
element.addEventListener('click', e => e.stopPropagation()); element.addEventListener('click', e => e.stopPropagation());
}); });
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
element.addEventListener('change', this.updateItemQuantity.bind(this));
element.addEventListener('click', e => e.stopPropagation());
});
// Add listener for armor marks input // Add listener for armor marks input
htmlElement.querySelectorAll('.armor-marks-input').forEach(element => { htmlElement.querySelectorAll('.armor-marks-input').forEach(element => {
element.addEventListener('change', this.updateArmorMarks.bind(this)); element.addEventListener('change', this.updateArmorMarks.bind(this));
}); });
htmlElement.querySelectorAll('.item-resource.die').forEach(element => {
element.addEventListener('contextmenu', this.lowerResourceDie.bind(this));
});
} }
/** @inheritdoc */ /** @inheritdoc */
@ -211,8 +210,34 @@ export default class CharacterSheet extends DHBaseActorSheet {
context.resources.stress.emptyPips = context.resources.stress.emptyPips =
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0; context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
context.inventory = { currencies: {} };
const { title, ...currencies } = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Homebrew
).currency;
for (let key in currencies) {
context.inventory.currencies[key] = {
...currencies[key],
field: context.systemFields.gold.fields[key],
value: context.source.system.gold[key]
};
}
// context.inventory = {
// currency: {
// title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'),
// coins: game.i18n.localize('DAGGERHEART.CONFIG.Gold.coins'),
// handfuls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfuls'),
// bags: game.i18n.localize('DAGGERHEART.CONFIG.Gold.bags'),
// chests: game.i18n.localize('DAGGERHEART.CONFIG.Gold.chests')
// }
// };
context.beastformActive = this.document.effects.find(x => x.type === 'beastform'); context.beastformActive = this.document.effects.find(x => x.type === 'beastform');
// if (context.inventory.length === 0) {
// context.inventory = Array(1).fill(Array(5).fill([]));
// }
return context; return context;
} }
@ -319,40 +344,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
} }
}, },
{
name: 'recall',
icon: 'fa-solid fa-bolt-lightning',
condition: target => {
const doc = getDocFromElementSync(target);
return doc && doc.system.inVault;
},
callback: async (target, event) => {
const doc = await getDocFromElement(target);
const actorLoadout = doc.actor.system.loadoutSlot;
if (!actorLoadout.available) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
return;
}
if (doc.system.recallCost == 0) {
return doc.update({ 'system.inVault': false });
}
const type = 'effect';
const cls = game.system.api.models.actions.actionsTypes[type];
const action = new cls({
...cls.getSourceConfig(doc.system),
type: type,
chatDisplay: false,
cost: [{
key: 'stress',
value: doc.system.recallCost
}]
}, { parent: doc.system });
const config = await action.use(event);
if (config) {
return doc.update({ 'system.inVault': false });
}
}
},
{ {
name: 'toVault', name: 'toVault',
icon: 'fa-solid fa-arrow-down', icon: 'fa-solid fa-arrow-down',
@ -624,6 +615,14 @@ export default class CharacterSheet extends DHBaseActorSheet {
this.render(); this.render();
} }
async updateItemQuantity(event) {
const item = await getDocFromElement(event.currentTarget);
if (!item) return;
await item.update({ 'system.quantity': event.currentTarget.value });
this.render();
}
async updateArmorMarks(event) { async updateArmorMarks(event) {
const armor = this.document.system.armor; const armor = this.document.system.armor;
if (!armor) return; if (!armor) return;
@ -710,21 +709,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
roll: { roll: {
trait: button.dataset.attribute trait: button.dataset.attribute
}, },
hasRoll: true, hasRoll: true
actionType: 'action', };
const result = await this.document.diceRoll({
...config,
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`, headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
ability: abilityLabel ability: abilityLabel
}) })
}; });
const result = await this.document.diceRoll(config);
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */ if (result) game.system.api.fields.ActionFields.CostField.execute.call(this, result);
const costResources = result.costs
.filter(x => x.enabled)
.map(cost => ({ ...cost, value: -cost.value, total: -cost.total }));
config.resourceUpdates.addResources(costResources);
await config.resourceUpdates.updateResources();
} }
//TODO: redo toggleEquipItem method //TODO: redo toggleEquipItem method
@ -863,27 +858,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
}); });
} }
/** */
static #advanceResourceDie(_, target) {
this.updateResourceDie(target, true);
}
lowerResourceDie(event) {
event.preventDefault();
event.stopPropagation();
this.updateResourceDie(event.target, false);
}
async updateResourceDie(target, advance) {
const item = await getDocFromElement(target);
if (!item) return;
const advancedValue = item.system.resource.value + (advance ? 1 : -1);
await item.update({
'system.resource.value': Math.min(advancedValue, Number(item.system.resource.dieFaces.split('d')[1]))
});
}
/** /**
* *
*/ */
@ -893,41 +867,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(item); game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(item);
} }
static async #viewParty(_, target) {
const parties = this.document.parties;
if (parties.size <= 1) {
parties.first()?.sheet.render({ force: true });
return;
}
const buttons = parties.map((p) => {
const button = document.createElement("button");
button.type = "button";
button.classList.add("plain");
const img = document.createElement("img");
img.src = p.img;
button.append(img);
const name = document.createElement("span");
name.textContent = p.name;
button.append(name);
button.addEventListener("click", () => {
p.sheet?.render({ force: true });
game.tooltip.dismissLockedTooltips();
});
return button;
});
const html = document.createElement("div");
html.classList.add("party-list");
html.append(...buttons);
game.tooltip.dismissLockedTooltips();
game.tooltip.activate(target, {
html,
locked: true,
})
}
/** /**
* Open the downtime application. * Open the downtime application.
* @type {ApplicationClickAction} * @type {ApplicationClickAction}
@ -938,18 +877,34 @@ export default class CharacterSheet extends DHBaseActorSheet {
}); });
} }
/** @inheritdoc */
async _onDragStart(event) { async _onDragStart(event) {
const inventoryItem = event.currentTarget.closest('.inventory-item'); const item = await getDocFromElement(event.target);
if (inventoryItem) {
event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0); const dragData = {
} type: item.documentName,
uuid: item.uuid
};
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
super._onDragStart(event); super._onDragStart(event);
} }
async _onDropItem(event, item) { async _onDrop(event) {
if (this.document.uuid === item.parent?.uuid) { // Prevent event bubbling to avoid duplicate handling
return super._onDropItem(event, item); event.preventDefault();
event.stopPropagation();
super._onDrop(event);
this._onDropItem(event, TextEditor.getDragEventData(event));
}
async _onDropItem(event, data) {
const item = await Item.implementation.fromDropData(data);
const itemData = item.toObject();
if (item.type === 'domainCard' && !this.document.system.loadoutSlot.available) {
itemData.system.inVault = true;
} }
if (item.type === 'beastform') { if (item.type === 'beastform') {
@ -959,27 +914,20 @@ export default class CharacterSheet extends DHBaseActorSheet {
); );
} }
const itemData = item.toObject();
const data = await game.system.api.data.items.DHBeastform.getWildcardImage(this.document, itemData); const data = await game.system.api.data.items.DHBeastform.getWildcardImage(this.document, itemData);
if (!data?.selectedImage) { if (data) {
return; if (!data.selectedImage) return;
} else if (data) { else {
if (data.usesDynamicToken) itemData.system.tokenRingImg = data.selectedImage; if (data.usesDynamicToken) itemData.system.tokenRingImg = data.selectedImage;
else itemData.system.tokenImg = data.selectedImage; else itemData.system.tokenImg = data.selectedImage;
return await this._onDropItemCreate(itemData); }
} }
} }
// If this is a type that gets deleted, delete it first (but still defer to super) if (this.document.uuid === item.parent?.uuid) return this._onSortItem(event, itemData);
const typesThatReplace = ['ancestry', 'community']; const createdItem = await this._onDropItemCreate(itemData);
if (typesThatReplace.includes(item.type)) {
await this.document.deleteEmbeddedDocuments(
'Item',
this.document.items.filter(x => x.type === item.type).map(x => x.id)
);
}
return super._onDropItem(event, item); return createdItem;
} }
async _onDropItemCreate(itemData, event) { async _onDropItemCreate(itemData, event) {

View file

@ -25,12 +25,7 @@ export default class DhpEnvironment extends DHBaseActorSheet {
toggleResourceDice: DhpEnvironment.#toggleResourceDice, toggleResourceDice: DhpEnvironment.#toggleResourceDice,
handleResourceDice: DhpEnvironment.#handleResourceDice handleResourceDice: DhpEnvironment.#handleResourceDice
}, },
dragDrop: [ dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }]
{
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
dropSelector: null
}
]
}; };
/**@override */ /**@override */
@ -42,11 +37,11 @@ export default class DhpEnvironment extends DHBaseActorSheet {
header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' },
features: { features: {
template: 'systems/daggerheart/templates/sheets/actors/environment/features.hbs', template: 'systems/daggerheart/templates/sheets/actors/environment/features.hbs',
scrollable: ['.feature-section'] scrollable: ['feature-section']
}, },
potentialAdversaries: { potentialAdversaries: {
template: 'systems/daggerheart/templates/sheets/actors/environment/potentialAdversaries.hbs', template: 'systems/daggerheart/templates/sheets/actors/environment/potentialAdversaries.hbs',
scrollable: ['.items-section'] scrollable: ['items-sections']
}, },
notes: { template: 'systems/daggerheart/templates/sheets/actors/environment/notes.hbs' } notes: { template: 'systems/daggerheart/templates/sheets/actors/environment/notes.hbs' }
}; };
@ -79,9 +74,6 @@ export default class DhpEnvironment extends DHBaseActorSheet {
case 'header': case 'header':
await this._prepareHeaderContext(context, options); await this._prepareHeaderContext(context, options);
break;
case 'features':
await this._prepareFeaturesContext(context, options);
break; break;
case 'notes': case 'notes':
await this._prepareNotesContext(context, options); await this._prepareNotesContext(context, options);
@ -118,22 +110,6 @@ export default class DhpEnvironment extends DHBaseActorSheet {
} }
} }
/**
* Prepare render context for the features part.
* @param {ApplicationRenderContext} context
* @param {ApplicationRenderOptions} options
* @returns {Promise<void>}
* @protected
*/
async _prepareFeaturesContext(context, _options) {
const featureForms = ['passive', 'action', 'reaction'];
context.features = this.document.system.features.sort((a, b) =>
a.system.featureForm !== b.system.featureForm
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
: a.sort - b.sort
);
}
/** /**
* Prepare render context for the Header part. * Prepare render context for the Header part.
* @param {ApplicationRenderContext} context * @param {ApplicationRenderContext} context
@ -154,13 +130,12 @@ export default class DhpEnvironment extends DHBaseActorSheet {
/* -------------------------------------------- */ /* -------------------------------------------- */
async _onDragStart(event) { async _onDragStart(event) {
const item = event.currentTarget.closest('.inventory-item[data-type=adversary]'); const item = event.currentTarget.closest('.inventory-item');
if (item) { if (item) {
const adversaryData = { type: 'Actor', uuid: item.dataset.itemUuid }; const adversaryData = { type: 'Actor', uuid: item.dataset.itemUuid };
event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData)); event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData));
event.dataTransfer.setDragImage(item, 60, 0); event.dataTransfer.setDragImage(item, 60, 0);
} else {
return super._onDragStart(event);
} }
} }

View file

@ -7,6 +7,7 @@ import { socketEvent } from '../../../systemRegistration/socket.mjs';
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs'; import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
import DhpActor from '../../../documents/actor.mjs'; import DhpActor from '../../../documents/actor.mjs';
import DHItem from '../../../documents/item.mjs'; import DHItem from '../../../documents/item.mjs';
import DhParty from '../../../data/actor/party.mjs';
export default class Party extends DHBaseActorSheet { export default class Party extends DHBaseActorSheet {
constructor(options) { constructor(options) {
@ -20,7 +21,7 @@ export default class Party extends DHBaseActorSheet {
classes: ['party'], classes: ['party'],
position: { position: {
width: 550, width: 550,
height: 900 height: 900,
}, },
window: { window: {
resizable: true resizable: true
@ -40,7 +41,7 @@ export default class Party extends DHBaseActorSheet {
selectRefreshable: DaggerheartMenu.selectRefreshable, selectRefreshable: DaggerheartMenu.selectRefreshable,
refreshActors: DaggerheartMenu.refreshActors refreshActors: DaggerheartMenu.refreshActors
}, },
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }] dragDrop: [{ dragSelector: '.actors-section .inventory-item', dropSelector: null }]
}; };
/**@override */ /**@override */
@ -93,6 +94,25 @@ export default class Party extends DHBaseActorSheet {
/* Prepare Context */ /* Prepare Context */
/* -------------------------------------------- */ /* -------------------------------------------- */
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.inventory = { currencies: {} };
const { title, ...currencies } = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Homebrew
).currency;
for (let key in currencies) {
context.inventory.currencies[key] = {
...currencies[key],
field: context.systemFields.gold.fields[key],
value: context.source.system.gold[key]
};
}
return context;
}
async _preparePartContext(partId, context, options) { async _preparePartContext(partId, context, options) {
context = await super._preparePartContext(partId, context, options); context = await super._preparePartContext(partId, context, options);
switch (partId) { switch (partId) {
@ -420,8 +440,24 @@ export default class Party extends DHBaseActorSheet {
/* -------------------------------------------- */ /* -------------------------------------------- */
async _onDropActor(event, document) { async _onDragStart(event) {
const item = event.currentTarget.closest('.inventory-item');
if (item) {
const adversaryData = { type: 'Actor', uuid: item.dataset.itemUuid };
event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData));
event.dataTransfer.setDragImage(item, 60, 0);
}
}
async _onDrop(event) {
// Prevent event bubbling to avoid duplicate handling
event.preventDefault();
event.stopPropagation();
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const document = await foundry.utils.fromUuid(data.uuid);
if (document instanceof DhpActor && Party.ALLOWED_ACTOR_TYPES.includes(document.type)) { if (document instanceof DhpActor && Party.ALLOWED_ACTOR_TYPES.includes(document.type)) {
const currentMembers = this.document.system.partyMembers.map(x => x.uuid); const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
if (currentMembers.includes(data.uuid)) { if (currentMembers.includes(data.uuid)) {
@ -429,11 +465,11 @@ export default class Party extends DHBaseActorSheet {
} }
await this.document.update({ 'system.partyMembers': [...currentMembers, document.uuid] }); await this.document.update({ 'system.partyMembers': [...currentMembers, document.uuid] });
} else if (document instanceof DHItem) {
this.document.createEmbeddedDocuments('Item', [document.toObject()]);
} else { } else {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.onlyCharactersInPartySheet')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.onlyCharactersInPartySheet'));
} }
return null;
} }
static async #deletePartyMember(event, target) { static async #deletePartyMember(event, target) {

View file

@ -178,79 +178,6 @@ export default function DHApplicationMixin(Base) {
_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)); this._dragDrop.forEach(d => d.bind(htmlElement));
// Handle delta inputs
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
deltaInput.dataset.numValue = deltaInput.value;
deltaInput.inputMode = 'numeric';
const handleUpdate = (delta = 0) => {
const min = Number(deltaInput.min) || 0;
const max = Number(deltaInput.max) || Infinity;
const current = Number(deltaInput.dataset.numValue);
const rawNumber = Number(deltaInput.value);
if (Number.isNaN(rawNumber)) {
deltaInput.value = delta ? Math.clamp(current + delta, min, max) : current;
return;
}
const newValue =
deltaInput.value.startsWith('+') || deltaInput.value.startsWith('-')
? Math.clamp(current + rawNumber + delta, min, max)
: Math.clamp(rawNumber + delta, min, max);
deltaInput.value = deltaInput.dataset.numValue = newValue;
};
// Force valid characters while inputting
deltaInput.addEventListener('input', () => {
deltaInput.value = /[+=\-]?\d*/.exec(deltaInput.value)?.at(0) ?? deltaInput.value;
});
// Recreate Keyup/Keydown support
deltaInput.addEventListener('keydown', event => {
const step = event.key === 'ArrowUp' ? 1 : event.key === 'ArrowDown' ? -1 : 0;
if (step !== 0) {
handleUpdate(step);
deltaInput.dispatchEvent(new Event("change", { bubbles: true }));
}
});
// Mousewheel while focused support
deltaInput.addEventListener(
'wheel',
event => {
if (deltaInput === document.activeElement) {
event.preventDefault();
handleUpdate(Math.sign(-1 * event.deltaY));
deltaInput.dispatchEvent(new Event("change", { bubbles: true }));
}
},
{ passive: false }
);
deltaInput.addEventListener('change', () => {
handleUpdate();
});
}
// Handle contenteditable
for (const input of htmlElement.querySelectorAll('[contenteditable][data-property]')) {
const property = input.dataset.property;
input.addEventListener("blur", () => {
const selection = document.getSelection();
if (input.contains(selection.anchorNode)) {
selection.empty();
}
this.document.update({ [property]: input.textContent });
});
input.addEventListener("keydown", event => {
if (event.key === "Enter") input.blur();
});
// Chrome sometimes add <br>, which aren't a problem for the value but are for the placeholder
input.addEventListener("input", () => input.querySelectorAll("br").forEach((i) => i.remove()));
}
} }
/**@inheritdoc */ /**@inheritdoc */
@ -395,11 +322,10 @@ export default function DHApplicationMixin(Base) {
_onDrop(event) { _onDrop(event) {
event.stopPropagation(); event.stopPropagation();
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
if (data.type === 'ActiveEffect' && data.fromInternal !== this.document.uuid) { if (data.fromInternal === this.document.uuid) return;
if (data.type === 'ActiveEffect') {
this.document.createEmbeddedDocuments('ActiveEffect', [data.data]); this.document.createEmbeddedDocuments('ActiveEffect', [data.data]);
} else {
// Fallback to super, but note that item sheets do not have this function
return super._onDrop?.(event);
} }
} }
@ -736,9 +662,6 @@ export default function DHApplicationMixin(Base) {
}; };
if (inVault) data['system.inVault'] = true; if (inVault) data['system.inVault'] = true;
if (disabled) data.disabled = true; if (disabled) data.disabled = true;
if (type === "domainCard" && parent?.system.domains?.length) {
data.system.domain = parent.system.domains[0];
}
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey }); const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
if (parentIsItem && type === 'feature') { if (parentIsItem && type === 'feature') {

View file

@ -1,4 +1,3 @@
import { getDocFromElement, itemIsIdentical } from '../../../helpers/utils.mjs';
import DHBaseActorSettings from './actor-setting.mjs'; import DHBaseActorSettings from './actor-setting.mjs';
import DHApplicationMixin from './application-mixin.mjs'; import DHApplicationMixin from './application-mixin.mjs';
@ -34,10 +33,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
} }
} }
], ],
dragDrop: [ dragDrop: [{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null }]
{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null },
{ dragSelector: ".currency[data-currency] .drag-handle", dropSelector: null }
]
}; };
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -72,29 +68,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
context.showAttribution = !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance) context.showAttribution = !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
.hideAttribution; .hideAttribution;
// Prepare inventory data
if (['party', 'character'].includes(this.document.type)) {
context.inventory = {
currencies: {},
weapons: this.document.itemTypes.weapon.sort((a, b) => a.sort - b.sort),
armor: this.document.itemTypes.armor.sort((a, b) => a.sort - b.sort),
consumables: this.document.itemTypes.consumable.sort((a, b) => a.sort - b.sort),
loot: this.document.itemTypes.loot.sort((a, b) => a.sort - b.sort)
};
const { title, ...currencies } = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Homebrew
).currency;
for (const key in currencies) {
context.inventory.currencies[key] = {
...currencies[key],
field: context.systemFields.gold.fields[key],
value: context.source.system.gold[key]
};
}
context.inventory.hasCurrency = Object.values(context.inventory.currencies).some((c) => c.enabled);
}
return context; return context;
} }
@ -138,10 +111,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
_attachPartListeners(partId, htmlElement, options) { _attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options); super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
element.addEventListener('change', this.updateItemQuantity.bind(this));
element.addEventListener('click', e => e.stopPropagation());
});
htmlElement.querySelectorAll('.item-button .action-uses-button').forEach(element => { htmlElement.querySelectorAll('.item-button .action-uses-button').forEach(element => {
element.addEventListener('contextmenu', DHBaseActorSheet.#modifyActionUses); element.addEventListener('contextmenu', DHBaseActorSheet.#modifyActionUses);
}); });
@ -180,15 +149,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true }); return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
} }
/* -------------------------------------------- */
/* Application Listener Actions */
/* -------------------------------------------- */
async updateItemQuantity(event) {
const item = await getDocFromElement(event.currentTarget);
await item?.update({ 'system.quantity': event.currentTarget.value });
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Application Clicks Actions */ /* Application Clicks Actions */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -257,98 +217,13 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
/* Application Drag/Drop */ /* Application Drag/Drop */
/* -------------------------------------------- */ /* -------------------------------------------- */
async _onDrop(event) {
event.stopPropagation();
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
if (data.type === 'Currency' && ['character', 'party'].includes(this.document.type)) {
const originActor = await foundry.utils.fromUuid(data.originActor);
if (!originActor || originActor.uuid === this.document.uuid) return;
const currency = data.currency;
const quantity = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
originActor,
targetActor: this.document,
currency
});
if (quantity) {
originActor.update({ [`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - quantity) });
this.document.update({ [`system.gold.${currency}`]: this.document.system.gold[currency] + quantity });
}
return;
}
return super._onDrop(event);
}
async _onDropItem(event, item) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const originActor = item.actor;
if (
item.actor?.uuid === this.document.uuid ||
!originActor ||
!['character', 'party'].includes(this.document.type)
) {
return super._onDropItem(event, item);
}
/* Handling transfer of inventoryItems */
if (item.system.metadata.isInventoryItem) {
if (item.system.metadata.isQuantifiable) {
const actorItem = originActor.items.get(data.originId);
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
item,
targetActor: this.document
});
if (quantityTransfered) {
if (quantityTransfered === actorItem.system.quantity) {
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
} else {
await actorItem.update({
'system.quantity': actorItem.system.quantity - quantityTransfered
});
}
const existingItem = this.document.items.find(x => itemIsIdentical(x, item));
if (existingItem) {
await existingItem.update({
'system.quantity': existingItem.system.quantity + quantityTransfered
});
} else {
const createData = item.toObject();
await this.document.createEmbeddedDocuments('Item', [
{
...createData,
system: {
...createData.system,
quantity: quantityTransfered
}
}
]);
}
}
} else {
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
}
}
}
/** /**
* On dragStart on the item. * On dragStart on the item.
* @param {DragEvent} event - The drag event * @param {DragEvent} event - The drag event
*/ */
async _onDragStart(event) { async _onDragStart(event) {
// Handle drag/dropping currencies
const currencyEl = event.currentTarget.closest(".currency[data-currency]");
if (currencyEl) {
const currency = currencyEl.dataset.currency;
const data = { type: 'Currency', currency, originActor: this.document.uuid };
event.dataTransfer.setData('text/plain', JSON.stringify(data));
return;
}
// Handle drag/dropping attacks
const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]'); const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]');
if (attackItem) { if (attackItem) {
const attackData = { const attackData = {
type: 'Attack', type: 'Attack',
@ -358,20 +233,8 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
}; };
event.dataTransfer.setData('text/plain', JSON.stringify(attackData)); event.dataTransfer.setData('text/plain', JSON.stringify(attackData));
event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0); event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0);
return; } else if (this.document.type !== 'environment') {
super._onDragStart(event);
} }
const item = await getDocFromElement(event.target);
if (item) {
const dragData = {
originActor: this.document.uuid,
originId: item.id,
type: item.documentName,
uuid: item.uuid
};
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
}
super._onDragStart(event);
} }
} }

View file

@ -33,9 +33,9 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
removeResource: DHBaseItemSheet.#removeResource removeResource: DHBaseItemSheet.#removeResource
}, },
dragDrop: [ dragDrop: [
{ dragSelector: null, dropSelector: '.drop-section' }, { dragSelector: null, dropSelector: '.tab.features .drop-section' },
{ dragSelector: '.feature-item', dropSelector: null }, { dragSelector: '.feature-item', dropSelector: null },
{ dragSelector: '.inventory-item', dropSelector: null } { dragSelector: '.action-item', dropSelector: null }
], ],
contextMenus: [ contextMenus: [
{ {
@ -199,32 +199,18 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
static async #deleteFeature(_, element) { static async #deleteFeature(_, element) {
const target = element.closest('[data-item-uuid]'); const target = element.closest('[data-item-uuid]');
const feature = await getDocFromElement(target); const feature = await getDocFromElement(target);
if (!feature) { if (!feature) {
await this.document.update({ await this.document.update({
'system.features': this.document.system.features 'system.features': this.document.system.features
.filter(x => x.item) .filter(x => x.item)
.map(x => ({ ...x, item: x.item.uuid })) .map(x => ({ ...x, item: x.item.uuid }))
}); });
} else { } else
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
type: game.i18n.localize('TYPES.Item.feature'),
name: feature.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
});
if (!confirmed) return;
await this.document.update({ await this.document.update({
'system.features': this.document.system.features 'system.features': this.document.system.features
.filter(x => target.dataset.type !== x.type || x.item.uuid !== feature.uuid) .filter(x => target.dataset.type !== x.type || x.item.uuid !== feature.uuid)
.map(x => ({ ...x, item: x.item.uuid })) .map(x => ({ ...x, item: x.item.uuid }))
}); });
}
} }
/** /**
@ -256,30 +242,37 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
* @param {DragEvent} event - The drag event * @param {DragEvent} event - The drag event
*/ */
async _onDragStart(event) { async _onDragStart(event) {
/* Can prolly be improved a lot, but I don't wanna >_< */
const featureItem = event.currentTarget.closest('.feature-item'); const featureItem = event.currentTarget.closest('.feature-item');
const inventoryItem = event.currentTarget.closest('.inventory-item');
const lineItem = event.currentTarget.closest('.item-line');
const dragItemData = featureItem ?? inventoryItem ?? lineItem;
const dragItem = await foundry.utils.fromUuid(dragItemData.dataset.itemUuid); if (featureItem) {
if (dragItem) { const feature = this.document.system.features.find(x => x?.id === featureItem.id);
if (!dragItem) { if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return; return;
} }
let dragData = {}; const featureData = { type: 'Item', data: { ...feature.toObject(), _id: null }, fromInternal: true };
if (dragItemData.dataset.type === 'effect') event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
dragData = { event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0);
type: 'ActiveEffect', } else {
fromInternal: this.document.uuid, const actionItem = event.currentTarget.closest('.action-item');
data: { ...dragItem, uuid: dragItem.uuid, id: dragItem.id } if (actionItem) {
}; const action = this.document.system.actions[actionItem.dataset.index];
else dragData = { type: 'Item', uuid: dragItem.uuid, id: dragItem.id, fromInternal: this.document.id }; if (!action) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionIsMissing'));
return;
}
event.dataTransfer.setData('text/plain', JSON.stringify(dragData)); const actionData = {
event.dataTransfer.setDragImage(dragItemData.querySelector('img'), 60, 0); type: 'Action',
data: { ...action.toObject(), id: action.id, itemUuid: this.document.uuid },
fromInternal: true
};
event.dataTransfer.setData('text/plain', JSON.stringify(actionData));
event.dataTransfer.setDragImage(actionItem.querySelector('img'), 60, 0);
} else {
super._onDragStart(event);
}
} }
} }
@ -291,7 +284,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
super._onDrop(event); super._onDrop(event);
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
if (data.fromInternal === this.document.id) return; if (data.fromInternal) return;
const target = event.target.closest('fieldset.drop-section'); const target = event.target.closest('fieldset.drop-section');
let item = await fromUuid(data.uuid); let item = await fromUuid(data.uuid);

View file

@ -77,7 +77,6 @@ export default class BeastformSheet extends DHBaseItemSheet {
name: context.document.system.advantageOn[key].value name: context.document.system.advantageOn[key].value
})) }))
); );
context.dimensionsDisabled = context.document.system.tokenSize.size !== 'custom';
break; break;
case 'effects': case 'effects':
context.effects.actives = context.effects.actives.map(effect => { context.effects.actives = context.effects.actives.map(effect => {

View file

@ -125,8 +125,8 @@ export default class ClassSheet extends DHBaseItemSheet {
async _onDrop(event) { async _onDrop(event) {
event.stopPropagation(); event.stopPropagation();
const data = TextEditor.getDragEventData(event); const data = TextEditor.getDragEventData(event);
const item = await fromUuid(data.uuid); const item = data.data ?? (await fromUuid(data.uuid));
const itemType = data.type === 'ActiveEffect' ? data.type : item.type; const itemType = data.data ? data.type : item.type;
const target = event.target.closest('fieldset.drop-section'); const target = event.target.closest('fieldset.drop-section');
if (itemType === 'subclass') { if (itemType === 'subclass') {
if (item.system.linkedClass) { if (item.system.linkedClass) {

View file

@ -31,11 +31,4 @@ export default class FeatureSheet extends DHBaseItemSheet {
labelPrefix: 'DAGGERHEART.GENERAL.Tabs' labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
} }
}; };
//Might be wrong location but testing out if here is okay.
/**@override */
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.featureFormChoices = CONFIG.DH.ITEM.featureForm;
return context;
}
} }

View file

@ -1,3 +1,2 @@
export { default as DaggerheartMenu } from './tabs/daggerheartMenu.mjs'; export { default as DaggerheartMenu } from './tabs/daggerheartMenu.mjs';
export { default as DhActorDirectory } from './tabs/actorDirectory.mjs';
export { default as DhSidebar } from './sidebar.mjs'; export { default as DhSidebar } from './sidebar.mjs';

View file

@ -1,46 +0,0 @@
export default class DhActorDirectory extends foundry.applications.sidebar.tabs.ActorDirectory {
static DEFAULT_OPTIONS = {
renderUpdateKeys: ['system.levelData.level.current', 'system.partner', 'system.tier']
};
static _entryPartial = 'systems/daggerheart/templates/ui/sidebar/actor-document-partial.hbs';
async _prepareDirectoryContext(context, options) {
await super._prepareDirectoryContext(context, options);
const adversaryTypes = CONFIG.DH.ACTOR.allAdversaryTypes();
const environmentTypes = CONFIG.DH.ACTOR.environmentTypes;
context.getTypeLabel = document => {
return document.type === 'adversary'
? game.i18n.localize(adversaryTypes[document.system.type]?.label ?? 'TYPES.Actor.adversary')
: document.type === 'environment'
? game.i18n.localize(environmentTypes[document.system.type]?.label ?? 'TYPES.Actor.environment')
: null;
};
}
/** @inheritDoc */
_onDragStart(event) {
let actor;
const { entryId } = event.currentTarget.dataset;
if (entryId) {
actor = this.collection.get(entryId);
if (!actor?.visible) return false;
}
super._onDragStart(event);
// Create the drag preview.
if (actor && canvas.ready) {
const img = event.currentTarget.querySelector('img');
const pt = actor.prototypeToken;
const usesSize = actor.system.metadata.usesSize;
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
const width = usesSize ? tokenSizes[actor.system.size] : pt.width;
const height = usesSize ? tokenSizes[actor.system.size] : pt.height;
const w = width * canvas.dimensions.size * Math.abs(pt.texture.scaleX) * canvas.stage.scale.x;
const h = height * canvas.dimensions.size * Math.abs(pt.texture.scaleY) * canvas.stage.scale.y;
const preview = foundry.applications.ux.DragDrop.implementation.createDragImage(img, w, h);
event.dataTransfer.setDragImage(preview, w / 2, h / 2);
}
}
}

View file

@ -1,5 +1,3 @@
import { refreshIsAllowed } from '../../../helpers/utils.mjs';
const { HandlebarsApplicationMixin } = foundry.applications.api; const { HandlebarsApplicationMixin } = foundry.applications.api;
const { AbstractSidebarTab } = foundry.applications.sidebar; const { AbstractSidebarTab } = foundry.applications.sidebar;
/** /**
@ -60,7 +58,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) { if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) {
const updates = {}; const updates = {};
for (let item of actor.items) { for (let item of actor.items) {
if (item.system.metadata?.hasResource && refreshIsAllowed(types, item.system.resource?.recovery)) { if (item.system.metadata.hasResource && types.includes(item.system.resource?.recovery)) {
if (!refreshedActors[actor.id]) if (!refreshedActors[actor.id])
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() }; refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
refreshedActors[actor.id].refreshed.add( refreshedActors[actor.id].refreshed.add(
@ -78,10 +76,10 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
: Roll.replaceFormulaData(item.system.resource.max, actor.getRollData()) : Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
}; };
} }
if (item.system.metadata?.hasActions) { if (item.system.metadata.hasActions) {
const refreshTypes = new Set(); const refreshTypes = new Set();
const actions = item.system.actions.filter(action => { const actions = item.system.actions.filter(action => {
if (refreshIsAllowed(types, action.uses.recovery)) { if (types.includes(action.uses.recovery)) {
refreshTypes.add(action.uses.recovery); refreshTypes.add(action.uses.recovery);
return true; return true;
} }

View file

@ -2,7 +2,6 @@ export { default as CountdownEdit } from './countdownEdit.mjs';
export { default as DhCountdowns } from './countdowns.mjs'; export { default as DhCountdowns } from './countdowns.mjs';
export { default as DhChatLog } from './chatLog.mjs'; export { default as DhChatLog } from './chatLog.mjs';
export { default as DhCombatTracker } from './combatTracker.mjs'; export { default as DhCombatTracker } from './combatTracker.mjs';
export { default as DhEffectsDisplay } from './effectsDisplay.mjs';
export { default as DhFearTracker } from './fearTracker.mjs'; export { default as DhFearTracker } from './fearTracker.mjs';
export { default as DhHotbar } from './hotbar.mjs'; export { default as DhHotbar } from './hotbar.mjs';
export { ItemBrowser } from './itemBrowser.mjs'; export { ItemBrowser } from './itemBrowser.mjs';

View file

@ -55,28 +55,27 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
]; ];
} }
addChatListeners = async (document, html, data) => { addChatListeners = async (app, html, data) => {
const message = data?.message ?? document.toObject(false);
html.querySelectorAll('.simple-roll-button').forEach(element => html.querySelectorAll('.simple-roll-button').forEach(element =>
element.addEventListener('click', event => this.onRollSimple(event, message)) element.addEventListener('click', event => this.onRollSimple(event, data.message))
); );
html.querySelectorAll('.ability-use-button').forEach(element => html.querySelectorAll('.ability-use-button').forEach(element =>
element.addEventListener('click', event => this.abilityUseButton(event, message)) element.addEventListener('click', event => this.abilityUseButton(event, data.message))
); );
html.querySelectorAll('.action-use-button').forEach(element => html.querySelectorAll('.action-use-button').forEach(element =>
element.addEventListener('click', event => this.actionUseButton(event, message)) element.addEventListener('click', event => this.actionUseButton(event, data.message))
); );
html.querySelectorAll('.reroll-button').forEach(element => html.querySelectorAll('.reroll-button').forEach(element =>
element.addEventListener('click', event => this.rerollEvent(event, message)) element.addEventListener('click', event => this.rerollEvent(event, data.message))
); );
html.querySelectorAll('.group-roll-button').forEach(element => html.querySelectorAll('.group-roll-button').forEach(element =>
element.addEventListener('click', event => this.groupRollButton(event, message)) element.addEventListener('click', event => this.groupRollButton(event, data.message))
); );
html.querySelectorAll('.group-roll-reroll').forEach(element => html.querySelectorAll('.group-roll-reroll').forEach(element =>
element.addEventListener('click', event => this.groupRollReroll(event, message)) element.addEventListener('click', event => this.groupRollReroll(event, data.message))
); );
html.querySelectorAll('.group-roll-success').forEach(element => html.querySelectorAll('.group-roll-success').forEach(element =>
element.addEventListener('click', event => this.groupRollSuccessEvent(event, message)) element.addEventListener('click', event => this.groupRollSuccessEvent(event, data.message))
); );
html.querySelectorAll('.group-roll-header-expand-section').forEach(element => html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
element.addEventListener('click', this.groupRollExpandSection) element.addEventListener('click', this.groupRollExpandSection)
@ -134,9 +133,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
async actionUseButton(event, message) { async actionUseButton(event, message) {
const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset; const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset;
const targetUuid = event.currentTarget.closest('.action-use-button-parent').querySelector('select')?.value; const parent = await foundry.utils.fromUuid(message.system.actor);
const parent = await foundry.utils.fromUuid(targetUuid || message.system.actor)
const actionType = message.system.moves[moveIndex].actions[actionIndex]; const actionType = message.system.moves[moveIndex].actions[actionIndex];
const cls = game.system.api.models.actions.actionsTypes[actionType.type]; const cls = game.system.api.models.actions.actionsTypes[actionType.type];
const action = new cls( const action = new cls(
@ -148,8 +145,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
type: CONFIG.DH.ITEM.originItemType.restMove, type: CONFIG.DH.ITEM.originItemType.restMove,
itemPath: movePath, itemPath: movePath,
actionIndex: actionIndex actionIndex: actionIndex
}, }
targetUuid: targetUuid
}, },
{ parent: parent.system } { parent: parent.system }
); );
@ -236,8 +232,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
hasRoll: true, hasRoll: true,
skips: { skips: {
createMessage: true, createMessage: true,
resources: !isLeader, resources: !isLeader
updateCountdowns: !isLeader
} }
}; };
const result = await actor.diceRoll({ const result = await actor.diceRoll({
@ -249,6 +244,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
}); });
if (!result) return; if (!result) return;
await game.system.api.fields.ActionFields.CostField.execute.call({ actor }, result);
const newMessageData = foundry.utils.deepClone(message.system); const newMessageData = foundry.utils.deepClone(message.system);
foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll); foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll);
@ -295,8 +291,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
}, },
hasRoll: true, hasRoll: true,
skips: { skips: {
createMessage: true, createMessage: true
updateCountdowns: true
} }
}; };
const result = await actor.diceRoll({ const result = await actor.diceRoll({

View file

@ -1,11 +1,10 @@
import { AdversaryBPPerEncounter } from '../../config/encounterConfig.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 = {
actions: { actions: {
requestSpotlight: this.requestSpotlight, requestSpotlight: this.requestSpotlight,
toggleSpotlight: this.toggleSpotlight, toggleSpotlight: this.toggleSpotlight,
setActionTokens: this.setActionTokens setActionTokens: this.setActionTokens,
openCountdowns: this.openCountdowns
} }
}; };
@ -21,33 +20,11 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
} }
}; };
/** @inheritDoc */
async _preparePartContext(_partId, context, _options) {
return context;
}
async _prepareContext(options) {
const context = await super._prepareContext(options);
await this._prepareTrackerContext(context, options);
await this._prepareCombatContext(context, options);
return context;
}
async _prepareCombatContext(context, options) { async _prepareCombatContext(context, options) {
await super._prepareCombatContext(context, options); await super._prepareCombatContext(context, options);
const modifierBP =
this.combats
.find(x => x.active)
?.system?.extendedBattleToggles?.reduce((acc, toggle) => (acc ?? 0) + toggle.category, null) ?? null;
const maxBP = CONFIG.DH.ENCOUNTER.BaseBPPerEncounter(context.characters.length) + modifierBP;
const currentBP = AdversaryBPPerEncounter(context.adversaries, context.characters);
Object.assign(context, { Object.assign(context, {
fear: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), fear: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
battlepoints: { max: maxBP, current: currentBP, hasModifierBP: modifierBP !== null }
}); });
} }
@ -56,26 +33,21 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
const adversaries = context.turns?.filter(x => x.isNPC) ?? []; const adversaries = context.turns?.filter(x => x.isNPC) ?? [];
const characters = context.turns?.filter(x => !x.isNPC) ?? []; const characters = context.turns?.filter(x => !x.isNPC) ?? [];
const spotlightQueueEnabled = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.SpotlightRequestQueue
);
const spotlightRequests = characters const spotlightRequests = characters
?.filter(x => !x.isNPC && spotlightQueueEnabled) ?.filter(x => !x.isNPC)
.filter(x => x.system.spotlight.requestOrderIndex > 0) .filter(x => x.system.spotlight.requestOrderIndex > 0)
.sort((a, b) => { .sort((a, b) => {
const valueA = a.system.spotlight.requestOrderIndex; const valueA = a.system.spotlight.requestOrderIndex;
const valueB = b.system.spotlight.requestOrderIndex; const valueB = b.system.spotlight.requestOrderIndex;
return valueA - valueB; return valueA - valueB;
}); });
Object.assign(context, { Object.assign(context, {
actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens, actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens,
adversaries, adversaries,
characters: characters characters: characters?.filter(x => !x.isNPC).filter(x => x.system.spotlight.requestOrderIndex == 0),
?.filter(x => !x.isNPC)
.filter(x => !spotlightQueueEnabled || x.system.spotlight.requestOrderIndex == 0),
spotlightRequests spotlightRequests
}); });
} }
@ -127,7 +99,6 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
resource, resource,
active: index === combat.turn, active: index === combat.turn,
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'), canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
type: combatant.actor?.system?.type,
img: await this._getCombatantThumbnail(combatant) img: await this._getCombatantThumbnail(combatant)
}; };
@ -165,14 +136,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
if (this.viewed.turn !== toggleTurn) { if (this.viewed.turn !== toggleTurn) {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
if (combatant.actor?.type === 'character') { await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.spotlight.id);
await updateCountdowns(
CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id,
CONFIG.DH.GENERAL.countdownProgressionTypes.characterSpotlight.id
);
} else {
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id);
}
const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints; const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints;
if (autoPoints) { if (autoPoints) {

View file

@ -1,5 +1,4 @@
import { DhCountdown } from '../../data/countdowns.mjs'; import { DhCountdown } from '../../data/countdowns.mjs';
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -27,7 +26,6 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
toggleCountdownEdit: CountdownEdit.#toggleCountdownEdit, toggleCountdownEdit: CountdownEdit.#toggleCountdownEdit,
editCountdownImage: CountdownEdit.#editCountdownImage, editCountdownImage: CountdownEdit.#editCountdownImage,
editCountdownOwnership: CountdownEdit.#editCountdownOwnership, editCountdownOwnership: CountdownEdit.#editCountdownOwnership,
randomiseCountdownStart: CountdownEdit.#randomiseCountdownStart,
removeCountdown: CountdownEdit.#removeCountdown removeCountdown: CountdownEdit.#removeCountdown
}, },
form: { handler: this.updateData, submitOnChange: true } form: { handler: this.updateData, submitOnChange: true }
@ -46,32 +44,18 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels; context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels;
context.defaultOwnership = this.data.defaultOwnership; context.defaultOwnership = this.data.defaultOwnership;
context.countdownBaseTypes = CONFIG.DH.GENERAL.countdownBaseTypes; context.countdownBaseTypes = CONFIG.DH.GENERAL.countdownBaseTypes;
context.countdownProgressionTypes = CONFIG.DH.GENERAL.countdownProgressionTypes; context.countdownTypes = CONFIG.DH.GENERAL.countdownTypes;
context.countdownLoopingTypes = CONFIG.DH.GENERAL.countdownLoopingTypes;
context.hideNewCountdowns = this.hideNewCountdowns; context.hideNewCountdowns = this.hideNewCountdowns;
context.countdowns = Object.keys(this.data.countdowns).reduce((acc, key) => { context.countdowns = Object.keys(this.data.countdowns).reduce((acc, key) => {
const countdown = this.data.countdowns[key]; const countdown = this.data.countdowns[key];
const isLooping = countdown.progress.looping !== CONFIG.DH.GENERAL.countdownLoopingTypes.noLooping;
const loopTooltip = isLooping
? countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
? 'DAGGERHEART.UI.Countdowns.increasingLoop'
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
: 'DAGGERHEART.UI.Countdowns.loop'
: null;
const randomizeValid = !new Roll(countdown.progress.startFormula ?? '').isDeterministic;
acc[key] = { acc[key] = {
...countdown, ...countdown,
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].label), typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].name),
progress: { progress: {
...countdown.progress, ...countdown.progress,
typeName: game.i18n.localize( typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownTypes[countdown.progress.type].label)
CONFIG.DH.GENERAL.countdownProgressionTypes[countdown.progress.type].label
)
}, },
editing: this.editingCountdowns.has(key), editing: this.editingCountdowns.has(key)
randomizeValid,
loopTooltip
}; };
return acc; return acc;
@ -127,26 +111,18 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
// Sync current and max if max is changing and they were equal before // Sync current and max if max is changing and they were equal before
for (const [id, countdown] of Object.entries(settingsData.countdowns ?? {})) { for (const [id, countdown] of Object.entries(settingsData.countdowns ?? {})) {
const existing = this.data.countdowns[id]; const existing = this.data.countdowns[id];
countdown.progress.current = this.getMatchingCurrentValue( const wasEqual = existing && existing.progress.current === existing.progress.max;
existing, if (wasEqual && countdown.progress.max !== existing.progress.max) {
countdown.progress.start, countdown.progress.current = countdown.progress.max;
countdown.progress.current } else {
); countdown.progress.current = Math.min(countdown.progress.current, countdown.progress.max);
}
} }
this.hideNewCountdowns = hideNewCountdowns; this.hideNewCountdowns = hideNewCountdowns;
this.updateSetting(settingsData); this.updateSetting(settingsData);
} }
getMatchingCurrentValue(oldCount, newStart, newCurrent) {
const wasEqual = oldCount && oldCount.progress.current === oldCount.progress.start;
if (wasEqual && newStart !== oldCount.progress.start) {
return newStart;
} else {
return Math.min(newCurrent, newStart);
}
}
async gmSetSetting(data) { async gmSetSetting(data) {
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data), await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
game.socket.emit(`system.${CONFIG.DH.id}`, { game.socket.emit(`system.${CONFIG.DH.id}`, {
@ -202,21 +178,6 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
this.updateSetting({ [`countdowns.${button.dataset.countdownId}`]: data }); this.updateSetting({ [`countdowns.${button.dataset.countdownId}`]: data });
} }
static async #randomiseCountdownStart(_, button) {
const countdown = this.data.countdowns[button.dataset.countdownId];
const roll = await new Roll(countdown.progress.startFormula).roll();
const message = await roll.toMessage({ title: 'Countdown' });
await waitForDiceSoNice(message);
await this.updateSetting({
[`countdowns.${button.dataset.countdownId}.progress`]: {
start: roll.total,
current: this.getMatchingCurrentValue(countdown, roll.total, countdown.progress.current)
}
});
this.render();
}
static async #removeCountdown(event, button) { static async #removeCountdown(event, button) {
const { countdownId } = button.dataset; const { countdownId } = button.dataset;

View file

@ -1,4 +1,3 @@
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -14,6 +13,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
constructor(options = {}) { constructor(options = {}) {
super(options); super(options);
this.sidebarCollapsed = true;
this.setupHooks(); this.setupHooks();
} }
@ -33,7 +33,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
actions: { actions: {
toggleViewMode: DhCountdowns.#toggleViewMode, toggleViewMode: DhCountdowns.#toggleViewMode,
editCountdowns: DhCountdowns.#editCountdowns, editCountdowns: DhCountdowns.#editCountdowns,
loopCountdown: DhCountdowns.#loopCountdown,
decreaseCountdown: (_, target) => this.editCountdown(false, target), decreaseCountdown: (_, target) => this.editCountdown(false, target),
increaseCountdown: (_, target) => this.editCountdown(true, target) increaseCountdown: (_, target) => this.editCountdown(true, target)
}, },
@ -82,6 +81,13 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
return frame; return frame;
} }
/**@inheritdoc */
async _onFirstRender(context, options) {
await super._onFirstRender(context, options);
this.toggleCollapsedPosition(undefined, !ui.sidebar.expanded);
}
/** Returns countdown data filtered by ownership */ /** Returns countdown data filtered by ownership */
#getCountdowns() { #getCountdowns() {
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
@ -90,17 +96,18 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
countdown, countdown,
ownership: DhCountdowns.#getPlayerOwnership(game.user, setting, countdown) ownership: DhCountdowns.#getPlayerOwnership(game.user, setting, countdown)
})); }));
return values.filter(v => v.ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE); return values.filter((v) => v.ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE);
} }
/** @override */ /** @override */
async _prepareContext(options) { async _prepareContext(options) {
const context = await super._prepareContext(options); const context = await super._prepareContext(options);
context.isGM = game.user.isGM; context.isGM = game.user.isGM;
context.sidebarCollapsed = this.sidebarCollapsed;
context.iconOnly = context.iconOnly =
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) === game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) ===
CONFIG.DH.GENERAL.countdownAppMode.iconOnly; CONFIG.DH.GENERAL.countdownAppMode.iconOnly;
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
context.countdowns = this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => { context.countdowns = this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => {
const playersWithAccess = game.users.reduce((acc, user) => { const playersWithAccess = game.users.reduce((acc, user) => {
@ -111,27 +118,11 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
return acc; return acc;
}, []); }, []);
const nonGmPlayers = game.users.filter(x => !x.isGM); const nonGmPlayers = game.users.filter(x => !x.isGM);
const countdownEditable = game.user.isGM || ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
const isLooping = countdown.progress.looping !== CONFIG.DH.GENERAL.countdownLoopingTypes.noLooping;
const loopTooltip = isLooping
? countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
? 'DAGGERHEART.UI.Countdowns.increasingLoop'
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
: 'DAGGERHEART.UI.Countdowns.loop'
: null;
const loopDisabled =
!countdownEditable ||
(isLooping && (countdown.progress.current > 0 || countdown.progress.start === '0'));
acc[key] = { acc[key] = {
...countdown, ...countdown,
editable: countdownEditable, editable: game.user.isGM || ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER,
noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0, playerAccess: playersWithAccess.length !== nonGmPlayers.length ? playersWithAccess : [],
shouldLoop: isLooping && countdown.progress.current === 0 && countdown.progress.start > 0, noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0
loopDisabled: isLooping ? loopDisabled : null,
loopTooltip: isLooping && game.i18n.localize(loopTooltip)
}; };
return acc; return acc;
}, {}); }, {});
@ -146,6 +137,14 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
: playerOwnership; : playerOwnership;
} }
toggleCollapsedPosition = async (_, collapsed) => {
if (!this.element) return;
this.sidebarCollapsed = collapsed;
if (!collapsed) this.element.classList.add('expanded');
else this.element.classList.remove('expanded');
};
cooldownRefresh = ({ refreshType }) => { cooldownRefresh = ({ refreshType }) => {
if (refreshType === RefreshType.Countdown) this.render(); if (refreshType === RefreshType.Countdown) this.render();
}; };
@ -177,46 +176,13 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
new game.system.api.applications.ui.CountdownEdit().render(true); new game.system.api.applications.ui.CountdownEdit().render(true);
} }
static async #loopCountdown(_, target) {
if (!DhCountdowns.canPerformEdit()) return;
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
const countdown = settings.countdowns[target.id];
let progressMax = countdown.progress.start;
let message = null;
if (countdown.progress.startFormula) {
const roll = await new Roll(countdown.progress.startFormula).evaluate();
progressMax = roll.total;
message = await roll.toMessage();
}
const newMax =
countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
? Number(progressMax) + 1
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
? Math.max(Number(progressMax) - 1, 0)
: progressMax;
await waitForDiceSoNice(message);
await settings.updateSource({
[`countdowns.${target.id}.progress`]: {
current: newMax,
start: newMax
}
});
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
refreshType: RefreshType.Countdown
});
}
static async editCountdown(increase, target) { static async editCountdown(increase, target) {
if (!DhCountdowns.canPerformEdit()) return; if (!DhCountdowns.canPerformEdit()) return;
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
const countdown = settings.countdowns[target.id]; const countdown = settings.countdowns[target.id];
const newCurrent = increase const newCurrent = increase
? Math.min(countdown.progress.current + 1, countdown.progress.start) ? Math.min(countdown.progress.current + 1, countdown.progress.max)
: Math.max(countdown.progress.current - 1, 0); : Math.max(countdown.progress.current - 1, 0);
await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent }); await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent });
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, { await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
@ -234,6 +200,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
} }
setupHooks() { setupHooks() {
Hooks.on('collapseSidebar', this.toggleCollapsedPosition.bind());
Hooks.on(socketEvent.Refresh, this.cooldownRefresh.bind()); Hooks.on(socketEvent.Refresh, this.cooldownRefresh.bind());
} }
@ -241,24 +208,16 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */ /* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
if (options.closeKey) return; if (options.closeKey) return;
Hooks.off('collapseSidebar', this.toggleCollapsedPosition);
Hooks.off(socketEvent.Refresh, this.cooldownRefresh); Hooks.off(socketEvent.Refresh, this.cooldownRefresh);
return super.close(options); return super.close(options);
} }
/** static async updateCountdowns(progressType) {
* Sends updates of the countdowns to the GM player. Since this is asynchronous, be sure to
* update all the countdowns at the same time.
*
* @param {...any} progressTypes Countdowns to be updated
*/
static async updateCountdowns(...progressTypes) {
const { countdownAutomation } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
if (!countdownAutomation) return;
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
const updatedCountdowns = Object.keys(countdownSetting.countdowns).reduce((acc, key) => { const updatedCountdowns = Object.keys(countdownSetting.countdowns).reduce((acc, key) => {
const countdown = countdownSetting.countdowns[key]; const countdown = countdownSetting.countdowns[key];
if (progressTypes.indexOf(countdown.progress.type) !== -1 && countdown.progress.current > 0) { if (countdown.progress.type === progressType && countdown.progress.current > 0) {
acc.push(key); acc.push(key);
} }
@ -266,7 +225,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
}, []); }, []);
const countdownData = countdownSetting.toObject(); const countdownData = countdownSetting.toObject();
const settings = { await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, {
...countdownData, ...countdownData,
countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => { countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => {
const countdown = foundry.utils.deepClone(countdownData.countdowns[key]); const countdown = foundry.utils.deepClone(countdownData.countdowns[key]);
@ -277,19 +236,18 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
acc[key] = countdown; acc[key] = countdown;
return acc; return acc;
}, {}) }, {})
};
await emitAsGM(GMUpdateEvent.UpdateCountdowns,
DhCountdowns.gmSetSetting.bind(settings),
settings, null, {
refreshType: RefreshType.Countdown
}); });
const data = { refreshType: RefreshType.Countdown };
await game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data
});
Hooks.callAll(socketEvent.Refresh, data);
} }
async _onRender(context, options) { async _onRender(context, options) {
await super._onRender(context, options); await super._onRender(context, options);
this.element.hidden = !game.user.isGM && this.#getCountdowns().length === 0; this.element.hidden = !game.user.isGM && this.#getCountdowns().length === 0;
if (options?.force) {
document.getElementById('ui-right-column-1')?.appendChild(this.element);
}
} }
} }

View file

@ -1,117 +0,0 @@
import { RefreshType } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
/**
* A UI element which displays the Active Effects on a selected token.
*
* @extends ApplicationV2
* @mixes HandlebarsApplication
*/
export default class DhEffectsDisplay extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(options = {}) {
super(options);
this.setupHooks();
}
/** @inheritDoc */
static DEFAULT_OPTIONS = {
id: 'effects-display',
tag: 'div',
classes: ['daggerheart', 'dh-style', 'effects-display'],
window: {
frame: false,
positioned: false,
resizable: false,
minimizable: false
},
actions: {}
};
/** @override */
static PARTS = {
resources: {
root: true,
template: 'systems/daggerheart/templates/ui/effects-display.hbs'
}
};
get element() {
return document.body.querySelector('.daggerheart.dh-style.effects-display');
}
get hidden() {
return this.element.classList.contains('hidden');
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
if (this.element) {
this.element.querySelectorAll('.effect-container a').forEach(element => {
element.addEventListener('contextmenu', this.removeEffect.bind(this));
});
}
}
/** @override */
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.effects = DhEffectsDisplay.getTokenEffects();
return context;
}
static getTokenEffects = token => {
const actor = token
? token.actor
: canvas.tokens.controlled.length === 0
? !game.user.isGM
? game.user.character
: null
: canvas.tokens.controlled[0].actor;
return actor?.getActiveEffects() ?? [];
};
toggleHidden(token, focused) {
const effects = DhEffectsDisplay.getTokenEffects(focused ? token : null);
this.element.hidden = effects.length === 0;
Hooks.callAll(CONFIG.DH.HOOKS.effectDisplayToggle, this.element.hidden, token);
this.render();
}
async removeEffect(event) {
const element = event.target.closest('.effect-container');
const effects = DhEffectsDisplay.getTokenEffects();
const effect = effects.find(x => x.id === element.dataset.effectId);
await effect.delete();
this.render();
}
setupHooks() {
Hooks.on('controlToken', this.toggleHidden.bind(this));
Hooks.on(RefreshType.EffectsDisplay, this.toggleHidden.bind(this));
}
async close(options) {
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
if (options.closeKey) return;
Hooks.off('controlToken', this.toggleHidden);
Hooks.off(RefreshType.EffectsDisplay, this.toggleHidden);
return super.close(options);
}
async _onRender(context, options) {
await super._onRender(context, options);
this.element.hidden = context.effects.length === 0;
if (options?.force) {
document.getElementById('ui-right-column-1')?.appendChild(this.element);
}
}
}

View file

@ -22,7 +22,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
/** @inheritDoc */ /** @inheritDoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
id: 'itemBrowser', id: 'itemBrowser',
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser', 'daggerheart-loader'], classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser', 'loader'],
tag: 'div', tag: 'div',
window: { window: {
frame: true, frame: true,
@ -277,7 +277,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
toggleLoader(state) { toggleLoader(state) {
const container = this.element.querySelector('.item-list'); const container = this.element.querySelector('.item-list');
return setTimeout(() => { return setTimeout(() => {
container.classList.toggle('daggerheart-loader', state); container.classList.toggle('loader', state);
}, 100); }, 100);
} }

View file

@ -10,7 +10,29 @@ 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 statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
const activeEffects = (this.actor ? this.actor.effects.filter(x => !x.disabled) : []).reduce((acc, effect) => {
acc.push(effect);
const currentStatusActiveEffects = acc.filter(
x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first())?.name)
);
for (var status of effect.statuses) {
if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) {
const statusData = statusMap.get(status);
if (statusData) {
acc.push({
name: game.i18n.localize(statusData.name),
statuses: [status],
img: statusData.icon,
tint: effect.tint
});
}
}
}
return acc;
}, []);
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
@ -34,69 +56,6 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
this.renderFlags.set({ refreshEffects: true }); this.renderFlags.set({ refreshEffects: true });
} }
/**
* Returns the distance from this token to another token object.
* This value is corrected to handle alternate token sizes and other grid types
* according to the diagonal rules.
*/
distanceTo(target) {
if (!canvas.ready) return NaN;
if (this === target) return 0;
const originPoint = this.center;
const destinationPoint = target.center;
// Compute for gridless. This version returns circular edge to edge + grid distance,
// so that tokens that are touching return 5.
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
const boundsCorrection = canvas.grid.distance / canvas.grid.size;
const originRadius = this.bounds.width * boundsCorrection / 2;
const targetRadius = target.bounds.width * boundsCorrection / 2;
const distance = canvas.grid.measurePath([originPoint, destinationPoint]).distance;
return distance - originRadius - targetRadius + canvas.grid.distance;
}
// Compute what the closest grid space of each token is, then compute that distance
const originEdge = this.#getEdgeBoundary(this.bounds, originPoint, destinationPoint);
const targetEdge = this.#getEdgeBoundary(target.bounds, originPoint, destinationPoint);
const adjustedOriginPoint = canvas.grid.getTopLeftPoint({
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
});
const adjustDestinationPoint = canvas.grid.getTopLeftPoint({
x: targetEdge.x + Math.sign(destinationPoint.x - targetEdge.x),
y: targetEdge.y + Math.sign(destinationPoint.y - targetEdge.y)
});
return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance;
}
/** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */
#getEdgeBoundary(bounds, originPoint, destinationPoint) {
const points = [
{ x: bounds.x, y: bounds.y },
{ x: bounds.x + bounds.width, y: bounds.y },
{ x: bounds.x + bounds.width, y: bounds.y + bounds.height },
{ x: bounds.x, y: bounds.y + bounds.height }
];
const pairsToTest = [
[points[0], points[1]],
[points[1], points[2]],
[points[2], points[3]],
[points[3], points[0]]
];
for (const pair of pairsToTest) {
const result = foundry.utils.lineSegmentIntersection(originPoint, destinationPoint, pair[0], pair[1]);
if (result) return result;
}
return null;
}
/** Tests if the token is at least adjacent with another, with some leeway for diagonals */
isAdjacentWith(token) {
return this.distanceTo(token) <= (canvas.grid.distance * 1.5);
}
/** @inheritDoc */ /** @inheritDoc */
_drawBar(number, bar, data) { _drawBar(number, bar, data) {
const val = Number(data.value); const val = Number(data.value);

View file

@ -2,10 +2,8 @@ export * as actionConfig from './actionConfig.mjs';
export * as actorConfig from './actorConfig.mjs'; export * as actorConfig from './actorConfig.mjs';
export * as domainConfig from './domainConfig.mjs'; export * as domainConfig from './domainConfig.mjs';
export * as effectConfig from './effectConfig.mjs'; export * as effectConfig from './effectConfig.mjs';
export * as encounterConfig from './encounterConfig.mjs';
export * as flagsConfig from './flagsConfig.mjs'; export * as flagsConfig from './flagsConfig.mjs';
export * as generalConfig from './generalConfig.mjs'; export * as generalConfig from './generalConfig.mjs';
export * as hooksConfig from './hooksConfig.mjs';
export * as itemConfig from './itemConfig.mjs'; export * as itemConfig from './itemConfig.mjs';
export * as settingsConfig from './settingsConfig.mjs'; export * as settingsConfig from './settingsConfig.mjs';
export * as systemConfig from './system.mjs'; export * as systemConfig from './system.mjs';

View file

@ -2,15 +2,9 @@ export const actionTypes = {
attack: { attack: {
id: 'attack', id: 'attack',
name: 'DAGGERHEART.ACTIONS.TYPES.attack.name', name: 'DAGGERHEART.ACTIONS.TYPES.attack.name',
icon: 'fa-hand-fist', icon: 'fa-khanda',
tooltip: 'DAGGERHEART.ACTIONS.TYPES.attack.tooltip' tooltip: 'DAGGERHEART.ACTIONS.TYPES.attack.tooltip'
}, },
countdown: {
id: 'countdown',
name: 'DAGGERHEART.ACTIONS.TYPES.countdown.name',
icon: 'fa-hourglass-half',
tooltip: 'DAGGERHEART.ACTIONS.TYPES.countdown.tooltip'
},
healing: { healing: {
id: 'healing', id: 'healing',
name: 'DAGGERHEART.ACTIONS.TYPES.healing.name', name: 'DAGGERHEART.ACTIONS.TYPES.healing.name',

View file

@ -108,64 +108,52 @@ export const adversaryTypes = {
bruiser: { bruiser: {
id: 'bruiser', id: 'bruiser',
label: 'DAGGERHEART.CONFIG.AdversaryType.bruiser.label', label: 'DAGGERHEART.CONFIG.AdversaryType.bruiser.label',
description: 'DAGGERHEART.ACTORS.Adversary.bruiser.description', description: 'DAGGERHEART.ACTORS.Adversary.bruiser.description'
bpCost: 4
}, },
horde: { horde: {
id: 'horde', id: 'horde',
label: 'DAGGERHEART.CONFIG.AdversaryType.horde.label', label: 'DAGGERHEART.CONFIG.AdversaryType.horde.label',
description: 'DAGGERHEART.ACTORS.Adversary.horde.description', description: 'DAGGERHEART.ACTORS.Adversary.horde.description'
bpCost: 2
}, },
leader: { leader: {
id: 'leader', id: 'leader',
label: 'DAGGERHEART.CONFIG.AdversaryType.leader.label', label: 'DAGGERHEART.CONFIG.AdversaryType.leader.label',
description: 'DAGGERHEART.ACTORS.Adversary.leader.description', description: 'DAGGERHEART.ACTORS.Adversary.leader.description'
bpCost: 3,
bpDescription: 'DAGGERHEART.CONFIG.AdversaryType.leader.'
}, },
minion: { minion: {
id: 'minion', id: 'minion',
label: 'DAGGERHEART.CONFIG.AdversaryType.minion.label', label: 'DAGGERHEART.CONFIG.AdversaryType.minion.label',
description: 'DAGGERHEART.ACTORS.Adversary.minion.description', description: 'DAGGERHEART.ACTORS.Adversary.minion.description'
bpCost: 1,
partyAmountPerBP: true
}, },
ranged: { ranged: {
id: 'ranged', id: 'ranged',
label: 'DAGGERHEART.CONFIG.AdversaryType.ranged.label', label: 'DAGGERHEART.CONFIG.AdversaryType.ranged.label',
description: 'DAGGERHEART.ACTORS.Adversary.ranged.description', description: 'DAGGERHEART.ACTORS.Adversary.ranged.description'
bpCost: 2
}, },
skulk: { skulk: {
id: 'skulk', id: 'skulk',
label: 'DAGGERHEART.CONFIG.AdversaryType.skulk.label', label: 'DAGGERHEART.CONFIG.AdversaryType.skulk.label',
description: 'DAGGERHEART.ACTORS.Adversary.skulk.description', description: 'DAGGERHEART.ACTORS.Adversary.skulk.description'
bpCost: 2
}, },
social: { social: {
id: 'social', id: 'social',
label: 'DAGGERHEART.CONFIG.AdversaryType.social.label', label: 'DAGGERHEART.CONFIG.AdversaryType.social.label',
description: 'DAGGERHEART.ACTORS.Adversary.social.description', description: 'DAGGERHEART.ACTORS.Adversary.social.description'
bpCost: 1
}, },
solo: { solo: {
id: 'solo', id: 'solo',
label: 'DAGGERHEART.CONFIG.AdversaryType.solo.label', label: 'DAGGERHEART.CONFIG.AdversaryType.solo.label',
description: 'DAGGERHEART.ACTORS.Adversary.solo.description', description: 'DAGGERHEART.ACTORS.Adversary.solo.description'
bpCost: 5
}, },
standard: { standard: {
id: 'standard', id: 'standard',
label: 'DAGGERHEART.CONFIG.AdversaryType.standard.label', label: 'DAGGERHEART.CONFIG.AdversaryType.standard.label',
description: 'DAGGERHEART.ACTORS.Adversary.standard.description', description: 'DAGGERHEART.ACTORS.Adversary.standard.description'
bpCost: 2
}, },
support: { support: {
id: 'support', id: 'support',
label: 'DAGGERHEART.CONFIG.AdversaryType.support.label', label: 'DAGGERHEART.CONFIG.AdversaryType.support.label',
description: 'DAGGERHEART.ACTORS.Adversary.support.description', description: 'DAGGERHEART.ACTORS.Adversary.support.description'
bpCost: 1
} }
}; };
@ -211,44 +199,6 @@ export const adversaryTraits = {
} }
}; };
export const tokenSize = {
custom: {
id: 'custom',
value: 0,
label: 'DAGGERHEART.GENERAL.custom'
},
tiny: {
id: 'tiny',
value: 1,
label: 'DAGGERHEART.CONFIG.TokenSize.tiny'
},
small: {
id: 'small',
value: 2,
label: 'DAGGERHEART.CONFIG.TokenSize.small'
},
medium: {
id: 'medium',
value: 3,
label: 'DAGGERHEART.CONFIG.TokenSize.medium'
},
large: {
id: 'large',
value: 4,
label: 'DAGGERHEART.CONFIG.TokenSize.large'
},
huge: {
id: 'huge',
value: 5,
label: 'DAGGERHEART.CONFIG.TokenSize.huge'
},
gargantuan: {
id: 'gargantuan',
value: 6,
label: 'DAGGERHEART.CONFIG.TokenSize.gargantuan'
}
};
export const levelChoices = { export const levelChoices = {
attributes: { attributes: {
name: 'attributes', name: 'attributes',

View file

@ -1,146 +0,0 @@
export const BaseBPPerEncounter = nrCharacters => 3 * nrCharacters + 2;
export const AdversaryBPPerEncounter = (adversaries, characters) => {
const adversaryTypes = CONFIG.DH.ACTOR.allAdversaryTypes();
return adversaries
.reduce((acc, adversary) => {
const existingEntry = acc.find(
x => x.adversary.name === adversary.name && x.adversary.type === adversary.type
);
if (existingEntry) {
existingEntry.nr += 1;
} else if (adversary.type) {
acc.push({ adversary, nr: 1 });
}
return acc;
}, [])
.reduce((acc, entry) => {
const adversary = entry.adversary;
const type = adversaryTypes[adversary.type];
const bpCost = type.bpCost ?? 0;
if (type.partyAmountPerBP) {
acc += characters.length === 0 ? 0 : Math.ceil(entry.nr / characters.length);
} else {
acc += bpCost * entry.nr;
}
return acc;
}, 0);
};
export const adversaryTypeCostBrackets = {
1: [
{
sort: 1,
types: ['minion'],
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.minion'
},
{
sort: 2,
types: ['social', 'support'],
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.support'
}
],
2: [
{
sort: 1,
types: ['horde', 'ranged', 'skulk', 'standard'],
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.standard'
}
],
3: [
{
sort: 1,
types: ['leader'],
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.leader'
}
],
4: [
{
sort: 1,
types: ['bruiser'],
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.bruiser'
}
],
5: [
{
sort: 1,
types: ['solo'],
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.solo'
}
]
};
export const BPModifiers = {
[-2]: {
manySolos: {
sort: 1,
description: 'DAGGERHEART.CONFIG.BPModifiers.manySolos.description',
automatic: true,
conditional: (_combat, adversaries) => {
return adversaries.filter(x => x.system.type === 'solo').length > 1;
}
},
increaseDamage: {
sort: 2,
description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.description',
effectTargetTypes: ['adversary'],
effects: [
{
name: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.name',
description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.description',
img: 'icons/magic/control/buff-flight-wings-red.webp',
changes: [
{
key: 'system.bonuses.damage.physical.dice',
mode: 2,
value: '1d4'
},
{
key: 'system.bonuses.damage.magical.dice',
mode: 2,
value: '1d4'
}
]
}
]
}
},
[-1]: {
lessDifficult: {
sort: 2,
description: 'DAGGERHEART.CONFIG.BPModifiers.lessDifficult.description'
}
},
1: {
lowerTier: {
sort: 1,
description: 'DAGGERHEART.CONFIG.BPModifiers.lowerTier.description',
automatic: true,
conditional: (_combat, adversaries, characters) => {
const characterMaxTier = characters.reduce((maxTier, character) => {
return character.system.tier > maxTier ? character.system.tier : maxTier;
}, 1);
return adversaries.some(adversary => adversary.system.tier < characterMaxTier);
}
},
noToughies: {
sort: 2,
description: 'DAGGERHEART.CONFIG.BPModifiers.noToughies.description',
automatic: true,
conditional: (_combat, adversaries) => {
const toughyTypes = ['bruiser', 'horde', 'leader', 'solo'];
return (
adversaries.length > 0 &&
!adversaries.some(adversary => toughyTypes.includes(adversary.system.type))
);
}
}
},
2: {
moreDangerous: {
sort: 2,
description: 'DAGGERHEART.CONFIG.BPModifiers.moreDangerous.description'
}
}
};

View file

@ -26,5 +26,3 @@ export const userFlags = {
welcomeMessage: 'welcome-message', welcomeMessage: 'welcome-message',
countdownMode: 'countdown-mode' countdownMode: 'countdown-mode'
}; };
export const combatToggle = 'combat-toggle-origin';

View file

@ -232,7 +232,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'friendly' type: 'self'
}, },
damage: { damage: {
parts: [ parts: [
@ -298,7 +298,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'friendly' type: 'self'
}, },
damage: { damage: {
parts: [ parts: [
@ -341,7 +341,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'friendly' type: 'self'
}, },
damage: { damage: {
parts: [ parts: [
@ -407,7 +407,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'friendly' type: 'self'
}, },
damage: { damage: {
parts: [ parts: [
@ -611,30 +611,18 @@ export const abilityCosts = {
resource: itemAbilityCosts.resource resource: itemAbilityCosts.resource
}; };
export const countdownProgressionTypes = { export const countdownTypes = {
actionRoll: { spotlight: {
id: 'actionRoll', id: 'spotlight',
label: 'DAGGERHEART.CONFIG.CountdownType.actionRoll' label: 'DAGGERHEART.CONFIG.CountdownType.spotlight'
}, },
characterAttack: { characterAttack: {
id: 'characterAttack', id: 'characterAttack',
label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack' label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack'
}, },
characterSpotlight: {
id: 'characterSpotlight',
label: 'DAGGERHEART.CONFIG.CountdownType.characterSpotlight'
},
custom: { custom: {
id: 'custom', id: 'custom',
label: 'DAGGERHEART.CONFIG.CountdownType.custom' label: 'DAGGERHEART.CONFIG.CountdownType.custom'
},
fear: {
id: 'fear',
label: 'DAGGERHEART.CONFIG.CountdownType.fear'
},
spotlight: {
id: 'spotlight',
label: 'DAGGERHEART.CONFIG.CountdownType.spotlight'
} }
}; };
export const rollTypes = { export const rollTypes = {
@ -685,30 +673,11 @@ export const simpleOwnershiplevels = {
export const countdownBaseTypes = { export const countdownBaseTypes = {
narrative: { narrative: {
id: 'narrative', id: 'narrative',
label: 'DAGGERHEART.APPLICATIONS.Countdown.types.narrative' name: 'DAGGERHEART.APPLICATIONS.Countdown.types.narrative'
}, },
encounter: { encounter: {
id: 'encounter', id: 'encounter',
label: 'DAGGERHEART.APPLICATIONS.Countdown.types.encounter' name: 'DAGGERHEART.APPLICATIONS.Countdown.types.encounter'
}
};
export const countdownLoopingTypes = {
noLooping: {
id: 'noLooping',
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.noLooping'
},
looping: {
id: 'looping',
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.looping'
},
increasing: {
id: 'increasing',
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.increasing'
},
decreasing: {
id: 'decreasing',
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.decreasing'
} }
}; };

View file

@ -1,5 +0,0 @@
const hooksConfig = {
effectDisplayToggle: 'DHEffectDisplayToggle'
};
export default hooksConfig;

View file

@ -5,6 +5,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'damage', type: 'damage',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.description',
@ -173,6 +174,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.description',
@ -186,6 +188,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.description',
@ -228,6 +231,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.description',
@ -265,6 +269,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.description',
@ -301,6 +306,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'attack', type: 'attack',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.description',
@ -347,6 +353,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.description',
@ -366,6 +373,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'attack', type: 'attack',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.description',
@ -393,6 +401,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.description',
@ -435,8 +444,7 @@ export const armorFeatures = {
{ {
key: 'system.resistance.magical.reduction', key: 'system.resistance.magical.reduction',
mode: 2, mode: 2,
value: '@system.armorScore', value: '@system.armorScore'
priority: 21
} }
] ]
} }
@ -451,16 +459,7 @@ export const allArmorFeatures = () => {
...armorFeatures, ...armorFeatures,
...Object.keys(homebrewFeatures).reduce((acc, key) => { ...Object.keys(homebrewFeatures).reduce((acc, key) => {
const feature = homebrewFeatures[key]; const feature = homebrewFeatures[key];
const actions = feature.actions.map(action => ({ acc[key] = { ...feature, label: feature.name };
...action,
effects: action.effects.map(effect => feature.effects.find(x => x.id === effect._id)),
type: action.type
}));
const actionEffects = actions.flatMap(a => a.effects);
const effects = feature.effects.filter(effect => !actionEffects.some(x => x.id === effect.id));
acc[key] = { ...feature, label: feature.name, effects, actions };
return acc; return acc;
}, {}) }, {})
}; };
@ -529,6 +528,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.description',
@ -573,6 +573,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.description',
@ -586,6 +587,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.description',
@ -599,6 +601,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.description',
@ -635,6 +638,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.description',
@ -675,6 +679,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.description',
@ -688,6 +693,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.description',
@ -710,8 +716,7 @@ export const weaponFeatures = {
{ {
key: 'system.evasion', key: 'system.evasion',
mode: 2, mode: 2,
value: '@system.armorScore', value: '@system.armorScore'
priority: 21
} }
] ]
} }
@ -725,6 +730,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'damage', type: 'damage',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.descriptive', description: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.descriptive',
@ -769,6 +775,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.description',
@ -819,6 +826,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.description',
@ -832,6 +840,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.description',
@ -845,6 +854,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', // Should prompt a dc 14 reaction save on adversaries type: 'effect', // Should prompt a dc 14 reaction save on adversaries
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.description',
@ -858,6 +868,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.description',
@ -877,6 +888,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description',
@ -908,6 +920,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'healing', type: 'healing',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.description',
@ -955,6 +968,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.description',
@ -968,6 +982,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.description',
@ -981,6 +996,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.description',
@ -994,6 +1010,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.description',
@ -1007,6 +1024,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.description',
@ -1020,6 +1038,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.description',
@ -1033,6 +1052,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.description',
@ -1070,6 +1090,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.description',
@ -1115,6 +1136,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.description',
@ -1128,6 +1150,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.description',
@ -1164,6 +1187,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.description',
@ -1207,6 +1231,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.description',
@ -1244,6 +1269,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.description',
@ -1257,6 +1283,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.description',
@ -1270,6 +1297,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.description',
@ -1283,6 +1311,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.description',
@ -1326,8 +1355,7 @@ export const weaponFeatures = {
{ {
key: 'system.bonuses.damage.primaryWeapon.bonus', key: 'system.bonuses.damage.primaryWeapon.bonus',
mode: 2, mode: 2,
value: '@system.traits.agility.value', value: '@system.traits.agility.value'
priority: 21
} }
] ]
} }
@ -1339,6 +1367,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.description',
@ -1352,6 +1381,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.description',
@ -1371,6 +1401,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.description',
@ -1383,21 +1414,11 @@ export const weaponFeatures = {
export const allWeaponFeatures = () => { export const allWeaponFeatures = () => {
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
.weaponFeatures; .weaponFeatures;
return { return {
...weaponFeatures, ...weaponFeatures,
...Object.keys(homebrewFeatures).reduce((acc, key) => { ...Object.keys(homebrewFeatures).reduce((acc, key) => {
const feature = homebrewFeatures[key]; const feature = homebrewFeatures[key];
acc[key] = { ...feature, label: feature.name };
const actions = feature.actions.map(action => ({
...action,
effects: action.effects.map(effect => feature.effects.find(x => x.id === effect._id)),
type: action.type
}));
const actionEffects = actions.flatMap(a => a.effects);
const effects = feature.effects.filter(effect => !actionEffects.some(x => x.id === effect.id));
acc[key] = { ...feature, label: feature.name, effects, actions };
return acc; return acc;
}, {}) }, {})
}; };
@ -1418,12 +1439,6 @@ export const orderedWeaponFeatures = () => {
return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label))); return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)));
}; };
export const featureForm = {
passive: 'DAGGERHEART.CONFIG.FeatureForm.passive',
action: 'DAGGERHEART.CONFIG.FeatureForm.action',
reaction: 'DAGGERHEART.CONFIG.FeatureForm.reaction'
};
export const featureTypes = { export const featureTypes = {
ancestry: { ancestry: {
id: 'ancestry', id: 'ancestry',
@ -1481,6 +1496,21 @@ export const featureSubTypes = {
mastery: 'mastery' mastery: 'mastery'
}; };
export const actionTypes = {
passive: {
id: 'passive',
label: 'DAGGERHEART.CONFIG.ActionType.passive'
},
action: {
id: 'action',
label: 'DAGGERHEART.CONFIG.ActionType.action'
},
reaction: {
id: 'reaction',
label: 'DAGGERHEART.CONFIG.ActionType.reaction'
}
};
export const itemResourceTypes = { export const itemResourceTypes = {
simple: { simple: {
id: 'simple', id: 'simple',
@ -1489,10 +1519,6 @@ export const itemResourceTypes = {
diceValue: { diceValue: {
id: 'diceValue', id: 'diceValue',
label: 'DAGGERHEART.CONFIG.ItemResourceType.diceValue' label: 'DAGGERHEART.CONFIG.ItemResourceType.diceValue'
},
die: {
id: 'die',
label: 'DAGGERHEART.CONFIG.ItemResourceType.die'
} }
}; };

View file

@ -28,8 +28,7 @@ export const gameSettings = {
LevelTiers: 'LevelTiers', LevelTiers: 'LevelTiers',
Countdowns: 'Countdowns', Countdowns: 'Countdowns',
LastMigrationVersion: 'LastMigrationVersion', LastMigrationVersion: 'LastMigrationVersion',
TagTeamRoll: 'TagTeamRoll', TagTeamRoll: 'TagTeamRoll'
SpotlightRequestQueue: 'SpotlightRequestQueue',
}; };
export const actionAutomationChoices = { export const actionAutomationChoices = {

View file

@ -1,20 +1,17 @@
import * as GENERAL from './generalConfig.mjs'; import * as GENERAL from './generalConfig.mjs';
import * as DOMAIN from './domainConfig.mjs'; import * as DOMAIN from './domainConfig.mjs';
import * as ENCOUNTER from './encounterConfig.mjs';
import * as ACTOR from './actorConfig.mjs'; import * as ACTOR from './actorConfig.mjs';
import * as ITEM from './itemConfig.mjs'; import * as ITEM from './itemConfig.mjs';
import * as SETTINGS from './settingsConfig.mjs'; import * as SETTINGS from './settingsConfig.mjs';
import * as EFFECTS from './effectConfig.mjs'; import * as EFFECTS from './effectConfig.mjs';
import * as ACTIONS from './actionConfig.mjs'; import * as ACTIONS from './actionConfig.mjs';
import * as FLAGS from './flagsConfig.mjs'; import * as FLAGS from './flagsConfig.mjs';
import HOOKS from './hooksConfig.mjs'; import * as ITEMBROWSER from './itemBrowserConfig.mjs'
import * as ITEMBROWSER from './itemBrowserConfig.mjs';
export const SYSTEM_ID = 'daggerheart'; export const SYSTEM_ID = 'daggerheart';
export const SYSTEM = { export const SYSTEM = {
id: SYSTEM_ID, id: SYSTEM_ID,
ENCOUNTER,
GENERAL, GENERAL,
DOMAIN, DOMAIN,
ACTOR, ACTOR,
@ -23,6 +20,5 @@ export const SYSTEM = {
EFFECTS, EFFECTS,
ACTIONS, ACTIONS,
FLAGS, FLAGS,
HOOKS,
ITEMBROWSER ITEMBROWSER
}; };

View file

@ -2,7 +2,6 @@ export { default as DhCombat } from './combat.mjs';
export { default as DhCombatant } from './combatant.mjs'; export { default as DhCombatant } from './combatant.mjs';
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs'; export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
export * as countdowns from './countdowns.mjs';
export * as actions from './action/_module.mjs'; export * as actions from './action/_module.mjs';
export * as activeEffects from './activeEffect/_module.mjs'; export * as activeEffects from './activeEffect/_module.mjs';
export * as actors from './actor/_module.mjs'; export * as actors from './actor/_module.mjs';

View file

@ -1,7 +1,6 @@
import AttackAction from './attackAction.mjs'; import AttackAction from './attackAction.mjs';
import BaseAction from './baseAction.mjs'; import BaseAction from './baseAction.mjs';
import BeastformAction from './beastformAction.mjs'; import BeastformAction from './beastformAction.mjs';
import CountdownAction from './countdownAction.mjs';
import DamageAction from './damageAction.mjs'; import DamageAction from './damageAction.mjs';
import EffectAction from './effectAction.mjs'; import EffectAction from './effectAction.mjs';
import HealingAction from './healingAction.mjs'; import HealingAction from './healingAction.mjs';
@ -11,7 +10,6 @@ import SummonAction from './summonAction.mjs';
export const actionsTypes = { export const actionsTypes = {
base: BaseAction, base: BaseAction,
attack: AttackAction, attack: AttackAction,
countdown: CountdownAction,
damage: DamageAction, damage: DamageAction,
healing: HealingAction, healing: HealingAction,
summon: SummonAction, summon: SummonAction,

View file

@ -37,10 +37,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.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.countdownTypes.characterAttack.id);
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
}
return result; return result;
} }

View file

@ -22,7 +22,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
_id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }), _id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }),
systemPath: new fields.StringField({ required: true, initial: 'actions' }), systemPath: new fields.StringField({ required: true, initial: 'actions' }),
type: new fields.StringField({ initial: undefined, readonly: true, required: true }), type: new fields.StringField({ initial: undefined, readonly: true, required: true }),
baseAction: new fields.BooleanField({ initial: false }),
name: new fields.StringField({ initial: undefined }), name: new fields.StringField({ initial: undefined }),
description: new fields.HTMLField(), description: new fields.HTMLField(),
img: new fields.FilePathField({ initial: undefined, categories: ['IMAGE'], base64: false }), img: new fields.FilePathField({ initial: undefined, categories: ['IMAGE'], base64: false }),
@ -33,8 +32,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
initial: 'action', initial: 'action',
nullable: false, nullable: false,
required: true required: true
}), })
targetUuid: new fields.StringField({ initial: undefined })
}; };
this.extraSchemas.forEach(s => { this.extraSchemas.forEach(s => {
@ -45,13 +43,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return schemaFields; return schemaFields;
} }
/**
* The default values to supply to schema fields when they are created in the actionConfig. Defined by implementing classes.
*/
get defaultValues() {
return {};
}
/** /**
* Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property. * Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property.
* *
@ -96,9 +87,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
prepareData() { prepareData() {
this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name); this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name);
this.img = this.img ?? this.parent?.parent?.img; this.img = this.img ?? this.parent?.parent?.img;
/* Fallback to feature description */
this.description = this.description || this.parent?.description;
} }
/** /**
@ -163,9 +151,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* @returns {object} * @returns {object}
*/ */
getRollData(data = {}) { getRollData(data = {}) {
const actorData = this.actor ? this.actor.getRollData(false) : {}; if (!this.actor) return null;
const actorData = this.actor.getRollData(false);
// Add Roll results to RollDatas
actorData.result = data.roll?.total ?? 1; actorData.result = data.roll?.total ?? 1;
actorData.scale = data.costs?.length // Right now only return the first scalable cost. actorData.scale = data.costs?.length // Right now only return the first scalable cost.
? (data.costs.find(c => c.scalable)?.total ?? 1) ? (data.costs.find(c => c.scalable)?.total ?? 1)
: 1; : 1;
@ -194,6 +185,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
async use(event) { async use(event) {
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.");
if (this.chatDisplay) await this.toChat();
let config = this.prepareConfig(event); let config = this.prepareConfig(event);
if (!config) return; if (!config) return;
@ -207,12 +200,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
// Execute the Action Worflow in order based of schema fields // Execute the Action Worflow in order based of schema fields
await this.executeWorkflow(config); await this.executeWorkflow(config);
await config.resourceUpdates.updateResources();
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) await this.toChat();
return config; return config;
} }
@ -241,11 +231,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
isDirect: !!this.damage?.direct, isDirect: !!this.damage?.direct,
selectedRollMode: game.settings.get('core', 'rollMode'), selectedRollMode: game.settings.get('core', 'rollMode'),
data: this.getRollData(), data: this.getRollData(),
evaluate: this.hasRoll, evaluate: this.hasRoll
resourceUpdates: new ResourceUpdateMap(this.actor),
targetUuid: this.targetUuid
}; };
DHBaseAction.applyKeybindings(config); DHBaseAction.applyKeybindings(config);
return config; return config;
} }
@ -327,46 +314,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* @returns {string[]} An array of localized tag strings. * @returns {string[]} An array of localized tag strings.
*/ */
_getTags() { _getTags() {
const tags = [game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${this.type}.name`)]; const tags = [
game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${this.type}.name`),
game.i18n.localize(`DAGGERHEART.CONFIG.ActionType.${this.actionType}`)
];
return tags; return tags;
} }
} }
export class ResourceUpdateMap extends Map {
#actor;
constructor(actor) {
super();
this.#actor = actor;
}
addResources(resources) {
for (const resource of resources) {
if (!resource.key) continue;
const existing = this.get(resource.key);
if (existing) {
this.set(resource.key, {
...existing,
value: existing.value + (resource.value ?? 0),
total: existing.total + (resource.total ?? 0)
});
} else {
this.set(resource.key, resource);
}
}
}
#getResources() {
return Array.from(this.values());
}
async updateResources() {
if (this.#actor) {
const target = this.#actor.system.partner ?? this.#actor;
await target.modifyResource(this.#getResources());
}
}
}

View file

@ -1,49 +0,0 @@
import DHBaseAction from './baseAction.mjs';
export default class DhCountdownAction extends DHBaseAction {
static extraSchemas = [...super.extraSchemas, 'countdown'];
get defaultValues() {
return {
...super.defaultValues,
countdown: {
name: this.parent.parent.name,
img: this.img,
progress: {
startFormula: '1'
}
}
};
}
/** @inheritdoc */
static getSourceConfig(parent) {
const updateSource = game.system.api.data.actions.actionsTypes.base.getSourceConfig(parent);
updateSource.name = game.i18n.localize('DAGGERHEART.ACTIONS.Config.countdown.startCountdown');
updateSource['countdown'] = [
{
...game.system.api.data.countdowns.DhCountdown.defaultCountdown(),
name: parent.parent.name,
img: parent.parent.img,
progress: {
startFormula: '1'
}
}
];
return updateSource;
}
/** @inheritDoc */
static migrateData(source) {
for (const countdown of source.countdown) {
if (countdown.progress.max) {
countdown.progress.startFormula = countdown.progress.max;
countdown.progress.start = 1;
countdown.progress.max = null;
}
}
return super.migrateData(source);
}
}

View file

@ -2,17 +2,4 @@ import DHBaseAction from './baseAction.mjs';
export default class DHDamageAction extends DHBaseAction { export default class DHDamageAction extends DHBaseAction {
static extraSchemas = [...super.extraSchemas, 'damage', 'target', 'effects']; static extraSchemas = [...super.extraSchemas, 'damage', 'target', 'effects'];
/**
* Return a display ready damage formula string
* @returns Formula string
*/
getDamageFormula() {
const strings = [];
for (const { value } of this.damage.parts) {
strings.push(Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {}));
}
return strings.join(' + ');
}
} }

View file

@ -1,17 +1,3 @@
/** -- Changes Type Priorities --
* - Base Number -
* Custom: 0
* Multiply: 10
* Add: 20
* Downgrade: 30
* Upgrade: 40
* Override: 50
*
* - Changes Value Priorities -
* Standard: +0
* "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 { export default class BaseEffect extends foundry.abstract.TypeDataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;

View file

@ -19,8 +19,8 @@ export default class BeastformEffect extends BaseEffect {
base64: false base64: false
}), }),
tokenSize: new fields.SchemaField({ tokenSize: new fields.SchemaField({
height: new fields.NumberField({ integer: false, nullable: true }), height: new fields.NumberField({ integer: true, nullable: true }),
width: new fields.NumberField({ integer: false, nullable: true }) width: new fields.NumberField({ integer: true, nullable: true })
}) })
}), }),
advantageOn: new fields.ArrayField(new fields.StringField()), advantageOn: new fields.ArrayField(new fields.StringField()),
@ -29,14 +29,6 @@ export default class BeastformEffect extends BaseEffect {
}; };
} }
/** @inheritDoc */
static migrateData(source) {
if (!source.characterTokenData.tokenSize.height) source.characterTokenData.tokenSize.height = 1;
if (!source.characterTokenData.tokenSize.width) source.characterTokenData.tokenSize.width = 1;
return super.migrateData(source);
}
async _onCreate(_data, _options, userId) { async _onCreate(_data, _options, userId) {
if (userId !== game.user.id) return; if (userId !== game.user.id) return;
@ -65,38 +57,19 @@ export default class BeastformEffect extends BaseEffect {
} }
}; };
const updateToken = token => { const updateToken = token => ({
let x = null, ...baseUpdate,
y = null; 'texture': {
if (token.object?.scene?.grid) { enabled: this.characterTokenData.usesDynamicToken,
const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid( src: token.flags.daggerheart.beastformTokenImg
token.object.scene.grid, },
{ x: token.x, y: token.y, elevation: token.elevation }, 'ring': {
baseUpdate.width, subject: {
baseUpdate.height texture: token.flags.daggerheart.beastformSubjectTexture
); }
},
x = positionData.x; 'flags.daggerheart': { '-=beastformTokenImg': null, '-=beastformSubjectTexture': null }
y = positionData.y; });
}
return {
...baseUpdate,
x,
y,
'texture': {
enabled: this.characterTokenData.usesDynamicToken,
src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg
},
'ring': {
subject: {
texture:
token.flags.daggerheart?.beastformSubjectTexture ?? this.characterTokenData.tokenRingImg
}
},
'flags.daggerheart': { '-=beastformTokenImg': null, '-=beastformSubjectTexture': null }
};
};
await updateActorTokens(this.parent.parent, update, updateToken); await updateActorTokens(this.parent.parent, update, updateToken);

View file

@ -1,6 +1,6 @@
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs'; import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
import { ActionField } from '../fields/actionField.mjs'; import { ActionField } from '../fields/actionField.mjs';
import BaseDataActor, { commonActorRules } from './base.mjs'; import BaseDataActor from './base.mjs';
import { resourceField, bonusField } from '../fields/actorField.mjs'; import { resourceField, bonusField } from '../fields/actorField.mjs';
export default class DhpAdversary extends BaseDataActor { export default class DhpAdversary extends BaseDataActor {
@ -11,8 +11,7 @@ export default class DhpAdversary extends BaseDataActor {
label: 'TYPES.Actor.adversary', label: 'TYPES.Actor.adversary',
type: 'adversary', type: 'adversary',
settingSheet: DHAdversarySettings, settingSheet: DHAdversarySettings,
hasAttribution: true, hasAttribution: true
usesSize: true
}); });
} }
@ -40,7 +39,6 @@ export default class DhpAdversary extends BaseDataActor {
integer: true, integer: true,
label: 'DAGGERHEART.GENERAL.hordeHp' label: 'DAGGERHEART.GENERAL.hordeHp'
}), }),
criticalThreshold: new fields.NumberField({ required: true, integer: true, min: 1, max: 20, initial: 20 }),
damageThresholds: new fields.SchemaField({ damageThresholds: new fields.SchemaField({
major: new fields.NumberField({ major: new fields.NumberField({
required: true, required: true,
@ -59,9 +57,6 @@ export default class DhpAdversary extends BaseDataActor {
hitPoints: resourceField(0, 0, 'DAGGERHEART.GENERAL.HitPoints.plural', true), hitPoints: resourceField(0, 0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
stress: resourceField(0, 0, 'DAGGERHEART.GENERAL.stress', true) stress: resourceField(0, 0, 'DAGGERHEART.GENERAL.stress', true)
}), }),
rules: new fields.SchemaField({
...commonActorRules()
}),
attack: new ActionField({ attack: new ActionField({
initial: { initial: {
name: 'Attack', name: 'Attack',
@ -126,10 +121,6 @@ export default class DhpAdversary extends BaseDataActor {
return this.parent.items.filter(x => x.type === 'feature'); return this.parent.items.filter(x => x.type === 'feature');
} }
isItemValid(source) {
return source.type === 'feature';
}
async _preUpdate(changes, options, user) { async _preUpdate(changes, options, user) {
const allowed = await super._preUpdate(changes, options, user); const allowed = await super._preUpdate(changes, options, user);
if (allowed === false) return false; if (allowed === false) return false;

View file

@ -1,24 +1,21 @@
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs'; import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
import DHItem from '../../documents/item.mjs';
import { getScrollTextData } from '../../helpers/utils.mjs'; import { getScrollTextData } from '../../helpers/utils.mjs';
const fields = foundry.data.fields;
const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) => const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
new fields.SchemaField({ new foundry.data.fields.SchemaField({
resistance: new fields.BooleanField({ resistance: new foundry.data.fields.BooleanField({
initial: false, initial: false,
label: `${resistanceLabel}.label`, label: `${resistanceLabel}.label`,
hint: `${resistanceLabel}.hint`, hint: `${resistanceLabel}.hint`,
isAttributeChoice: true isAttributeChoice: true
}), }),
immunity: new fields.BooleanField({ immunity: new foundry.data.fields.BooleanField({
initial: false, initial: false,
label: `${immunityLabel}.label`, label: `${immunityLabel}.label`,
hint: `${immunityLabel}.hint`, hint: `${immunityLabel}.hint`,
isAttributeChoice: true isAttributeChoice: true
}), }),
reduction: new fields.NumberField({ reduction: new foundry.data.fields.NumberField({
integer: true, integer: true,
initial: 0, initial: 0,
label: `${reductionLabel}.label`, label: `${reductionLabel}.label`,
@ -26,25 +23,6 @@ const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
}) })
}); });
/* Common rules applying to Characters and Adversaries */
export const commonActorRules = (extendedData = { damageReduction: {} }) => ({
conditionImmunities: new fields.SchemaField({
hidden: new fields.BooleanField({ initial: false }),
restrained: new fields.BooleanField({ initial: false }),
vulnerable: new fields.BooleanField({ initial: false })
}),
damageReduction: new fields.SchemaField({
thresholdImmunities: new fields.SchemaField({
minor: new fields.BooleanField({ initial: false })
}),
reduceSeverity: new fields.SchemaField({
magical: new fields.NumberField({ initial: 0, min: 0 }),
physical: new fields.NumberField({ initial: 0, min: 0 })
}),
...extendedData.damageReduction
})
});
/** /**
* Describes metadata about the actor data model type * Describes metadata about the actor data model type
* @typedef {Object} ActorDataModelMetadata * @typedef {Object} ActorDataModelMetadata
@ -63,8 +41,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
settingSheet: null, settingSheet: null,
hasResistances: true, hasResistances: true,
hasAttribution: false, hasAttribution: false,
hasLimitedView: true, hasLimitedView: true
usesSize: false
}; };
} }
@ -75,6 +52,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
/** @inheritDoc */ /** @inheritDoc */
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields;
const schema = {}; const schema = {};
if (this.metadata.hasAttribution) { if (this.metadata.hasAttribution) {
@ -98,13 +76,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
'DAGGERHEART.GENERAL.DamageResistance.magicalReduction' 'DAGGERHEART.GENERAL.DamageResistance.magicalReduction'
) )
}); });
if (this.metadata.usesSize)
schema.size = new fields.StringField({
required: true,
nullable: false,
choices: CONFIG.DH.ACTOR.tokenSize,
initial: CONFIG.DH.ACTOR.tokenSize.custom.id
});
return schema; return schema;
} }
@ -135,32 +106,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
return data; return data;
} }
/**
* Checks if an item is available for use, such as multiclass features being disabled
* on a character.
*
* @param {DHItem} item The item being checked for availability
* @return {boolean} whether the item is available
*/
isItemAvailable(item) {
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;

View file

@ -1,7 +1,7 @@
import { burden } from '../../config/generalConfig.mjs'; import { burden } from '../../config/generalConfig.mjs';
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
import DhLevelData from '../levelData.mjs'; import DhLevelData from '../levelData.mjs';
import BaseDataActor, { commonActorRules } from './base.mjs'; import BaseDataActor from './base.mjs';
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; import { attributeField, resourceField, 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';
@ -217,41 +217,40 @@ export default class DhCharacter extends BaseDataActor {
}), }),
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }), companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
rules: new fields.SchemaField({ rules: new fields.SchemaField({
...commonActorRules({ damageReduction: new fields.SchemaField({
damageReduction: { maxArmorMarked: new fields.SchemaField({
magical: new fields.BooleanField({ initial: false }), value: new fields.NumberField({
physical: new fields.BooleanField({ initial: false }), required: true,
maxArmorMarked: new fields.SchemaField({
value: new fields.NumberField({
required: true,
integer: true,
initial: 1,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
}),
stressExtra: new fields.NumberField({
required: true,
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
})
}),
stressDamageReduction: new fields.SchemaField({
severe: stressDamageReductionRule(
'DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'
),
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
}),
increasePerArmorMark: new fields.NumberField({
integer: true, integer: true,
initial: 1, initial: 1,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label', label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
}), }),
disabledArmor: new fields.BooleanField({ intial: false }) stressExtra: new fields.NumberField({
} required: true,
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
})
}),
stressDamageReduction: new fields.SchemaField({
severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
}),
increasePerArmorMark: new fields.NumberField({
integer: true,
initial: 1,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
}),
magical: new fields.BooleanField({ initial: false }),
physical: new fields.BooleanField({ initial: false }),
thresholdImmunities: new fields.SchemaField({
minor: new fields.BooleanField({ initial: false })
}),
disabledArmor: new fields.BooleanField({ intial: false })
}), }),
attack: new fields.SchemaField({ attack: new fields.SchemaField({
damage: new fields.SchemaField({ damage: new fields.SchemaField({
@ -427,33 +426,6 @@ export default class DhCharacter extends BaseDataActor {
return attack; return attack;
} }
/** @inheritDoc */
isItemAvailable(item) {
if (!super.isItemAvailable(this)) return false;
/**
* Preventing subclass features from being available if the chacaracter does not
* have the right subclass advancement
*/
if (item.system.originItemType !== CONFIG.DH.ITEM.featureTypes.subclass.id) {
return true;
}
if (!this.class.subclass) return false;
const prop = item.system.multiclassOrigin ? 'multiclass' : 'class';
const subclassState = this[prop].subclass?.system?.featureState;
if (!subclassState) return false;
if (
item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation ||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) ||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
) {
return true;
} else {
return false;
}
}
get sheetLists() { get sheetLists() {
const ancestryFeatures = [], const ancestryFeatures = [],
communityFeatures = [], communityFeatures = [],
@ -462,7 +434,7 @@ export default class DhCharacter extends BaseDataActor {
companionFeatures = [], companionFeatures = [],
features = []; features = [];
for (let item of this.parent.items.filter(x => this.isItemAvailable(x))) { for (let item of this.parent.items) {
if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) { if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
ancestryFeatures.push(item); ancestryFeatures.push(item);
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) { } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) {
@ -470,7 +442,20 @@ export default class DhCharacter extends BaseDataActor {
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) { } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) {
classFeatures.push(item); classFeatures.push(item);
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) { } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
subclassFeatures.push(item); if (this.class.subclass) {
const prop = item.system.multiclassOrigin ? 'multiclass' : 'class';
const subclassState = this[prop].subclass?.system?.featureState;
if (!subclassState) continue;
if (
item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation ||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization &&
subclassState >= 2) ||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
) {
subclassFeatures.push(item);
}
}
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) { } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) {
companionFeatures.push(item); companionFeatures.push(item);
} else if (item.type === 'feature' && !item.system.type) { } else if (item.type === 'feature' && !item.system.type) {
@ -684,8 +669,6 @@ export default class DhCharacter extends BaseDataActor {
} }
async _preDelete() { async _preDelete() {
super._preDelete();
if (this.companion) { if (this.companion) {
this.companion.updateLevel(1); this.companion.updateLevel(1);
} }

View file

@ -1,7 +1,7 @@
import BaseDataActor from './base.mjs'; import BaseDataActor from './base.mjs';
import DhLevelData from '../levelData.mjs'; import DhLevelData from '../levelData.mjs';
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
import { ActionField } from '../fields/actionField.mjs'; import { ActionField, ActionsField } from '../fields/actionField.mjs';
import { adjustDice, adjustRange } from '../../helpers/utils.mjs'; import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs'; import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
import { resourceField, bonusField } from '../fields/actorField.mjs'; import { resourceField, bonusField } from '../fields/actorField.mjs';
@ -51,13 +51,6 @@ export default class DhCompanion extends BaseDataActor {
} }
} }
), ),
rules: new fields.SchemaField({
conditionImmunities: new fields.SchemaField({
hidden: new fields.BooleanField({ initial: false }),
restrained: new fields.BooleanField({ initial: false }),
vulnerable: new fields.BooleanField({ initial: false })
})
}),
attack: new ActionField({ attack: new ActionField({
initial: { initial: {
name: 'Attack', name: 'Attack',
@ -109,10 +102,6 @@ export default class DhCompanion extends BaseDataActor {
return this.partner?.system?.proficiency ?? 1; return this.partner?.system?.proficiency ?? 1;
} }
isItemValid() {
return false;
}
prepareBaseData() { prepareBaseData() {
this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0; this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0;

View file

@ -51,8 +51,4 @@ export default class DhEnvironment extends BaseDataActor {
get features() { get features() {
return this.parent.items.filter(x => x.type === 'feature'); return this.parent.items.filter(x => x.type === 'feature');
} }
isItemValid(source) {
return source.type === "feature";
}
} }

View file

@ -25,10 +25,6 @@ export default class DhParty extends BaseDataActor {
/* -------------------------------------------- */ /* -------------------------------------------- */
isItemValid(source) {
return ['weapon', 'armor', 'consumable', 'loot'].includes(source.type);
}
prepareBaseData() { prepareBaseData() {
super.prepareBaseData(); super.prepareBaseData();
@ -40,23 +36,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);

View file

@ -1,40 +1,6 @@
export default class DhCombat extends foundry.abstract.TypeDataModel { export default class DhCombat extends foundry.abstract.TypeDataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;
return { return {};
battleToggles: new fields.ArrayField(
new fields.SchemaField({
category: new fields.NumberField({ required: true, integer: true }),
grouping: new fields.StringField({ required: true })
})
)
};
}
/** Includes automatic BPModifiers */
get extendedBattleToggles() {
const modifiers = CONFIG.DH.ENCOUNTER.BPModifiers;
const adversaries =
this.parent.turns?.filter(x => x.actor && x.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ??
[];
const characters = this.parent.turns?.filter(x => x.actor && !x.isNPC) ?? [];
const activeAutomatic = Object.keys(modifiers).reduce((acc, categoryKey) => {
const category = modifiers[categoryKey];
acc.push(
...Object.keys(category).reduce((acc, groupingKey) => {
const grouping = category[groupingKey];
if (grouping.automatic && grouping.conditional?.(this.parent, adversaries, characters)) {
acc.push({ category: Number(categoryKey), grouping: groupingKey });
}
return acc;
}, [])
);
return acc;
}, []);
return [...this.battleToggles, ...activeAutomatic];
} }
} }

View file

@ -1,5 +1,3 @@
import FormulaField from './fields/formulaField.mjs';
export default class DhCountdowns extends foundry.abstract.DataModel { export default class DhCountdowns extends foundry.abstract.DataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;
@ -107,8 +105,8 @@ class DhOldCountdown extends foundry.abstract.DataModel {
type: new fields.SchemaField({ type: new fields.SchemaField({
value: new fields.StringField({ value: new fields.StringField({
required: true, required: true,
choices: CONFIG.DH.GENERAL.countdownProgressionTypes, choices: CONFIG.DH.GENERAL.countdownTypes,
initial: CONFIG.DH.GENERAL.countdownProgressionTypes.custom.id, initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
label: 'DAGGERHEART.GENERAL.type' label: 'DAGGERHEART.GENERAL.type'
}), }),
label: new fields.StringField({ label: new fields.StringField({
@ -167,27 +165,16 @@ export class DhCountdown extends foundry.abstract.DataModel {
initial: 1, initial: 1,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.current.label' label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.current.label'
}), }),
start: new fields.NumberField({ max: new fields.NumberField({
required: true, required: true,
integer: true, integer: true,
initial: 1, initial: 1,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.start.label', label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.max.label'
deterministic: false
}),
startFormula: new FormulaField({
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.startFormula.label',
deterministic: false
}),
looping: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.countdownLoopingTypes,
initial: CONFIG.DH.GENERAL.countdownLoopingTypes.noLooping.id,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.looping.label'
}), }),
type: new fields.StringField({ type: new fields.StringField({
required: true, required: true,
choices: CONFIG.DH.GENERAL.countdownProgressionTypes, choices: CONFIG.DH.GENERAL.countdownTypes,
initial: CONFIG.DH.GENERAL.countdownProgressionTypes.custom.id, initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.type.label' label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.type.label'
}) })
}) })
@ -211,7 +198,7 @@ export class DhCountdown extends foundry.abstract.DataModel {
ownership: ownership, ownership: ownership,
progress: { progress: {
current: 1, current: 1,
start: 1 max: 1
} }
}; };
} }
@ -230,15 +217,4 @@ export class DhCountdown extends foundry.abstract.DataModel {
return acc; return acc;
}, {}); }, {});
} }
/** @inheritDoc */
static migrateData(source) {
if (source.progress.max) {
source.progress.start = Number(source.progress.max);
source.progress.max = null;
source.progress.startFormula = null;
}
return super.migrateData(source);
}
} }

View file

@ -1,5 +1,4 @@
export { default as CostField } from './costField.mjs'; export { default as CostField } from './costField.mjs';
export { default as CountdownField } from './countdownField.mjs';
export { default as UsesField } from './usesField.mjs'; export { default as UsesField } from './usesField.mjs';
export { default as RangeField } from './rangeField.mjs'; export { default as RangeField } from './rangeField.mjs';
export { default as TargetField } from './targetField.mjs'; export { default as TargetField } from './targetField.mjs';

View file

@ -76,7 +76,7 @@ export default class BeastformField extends fields.SchemaField {
* @returns * @returns
*/ */
static async transform(selectedForm, evolvedData, hybridData) { static async transform(selectedForm, evolvedData, hybridData) {
const formData = evolvedData?.form ?? selectedForm; const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm;
const beastformEffect = formData.effects.find(x => x.type === 'beastform'); const beastformEffect = formData.effects.find(x => x.type === 'beastform');
if (!beastformEffect) { if (!beastformEffect) {
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect'); ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
@ -92,18 +92,6 @@ export default class BeastformField extends fields.SchemaField {
beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes]; beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes];
formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)]; formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)];
const baseSize = evolvedData.form.system.tokenSize.size;
const evolvedSize =
baseSize === 'custom'
? 'custom'
: (Object.keys(CONFIG.DH.ACTOR.tokenSize).find(
x => CONFIG.DH.ACTOR.tokenSize[x].value === CONFIG.DH.ACTOR.tokenSize[baseSize].value + 1
) ?? baseSize);
formData.system.tokenSize = {
...evolvedData.form.system.tokenSize,
size: evolvedSize
};
} }
if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) { if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) {

View file

@ -75,7 +75,7 @@ export default class CostField extends fields.ArrayField {
} }
}, []); }, []);
config.resourceUpdates.addResources(resources); await actor.modifyResource(resources);
} }
/** /**

View file

@ -1,110 +0,0 @@
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../../systemRegistration/socket.mjs';
const fields = foundry.data.fields;
export default class CountdownField extends fields.ArrayField {
constructor(options = {}, context = {}) {
const element = new fields.SchemaField({
...game.system.api.data.countdowns.DhCountdown.defineSchema(),
type: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.countdownBaseTypes,
initial: CONFIG.DH.GENERAL.countdownBaseTypes.encounter.id,
label: 'DAGGERHEART.GENERAL.type'
}),
name: new fields.StringField({
required: true,
initial: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.newCountdown'),
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.name.label'
}),
defaultOwnership: new fields.NumberField({
required: true,
choices: CONFIG.DH.GENERAL.simpleOwnershiplevels,
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT,
label: 'DAGGERHEART.ACTIONS.Config.countdown.defaultOwnership'
})
});
super(element, options, context);
}
/**
* Countdown Action Workflow part.
* Must be called within Action context or similar. Requires a GM online to edit the game setting for countdowns.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
static async execute(config) {
const noGM = !game.users.find(x => x.isGM && x.active);
if (noGM) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired'));
return;
}
const data = { countdowns: {} };
const countdownMessages = [];
for (let countdown of config.countdowns) {
let startFormula = countdown.progress.startFormula ? countdown.progress.startFormula : null;
let countdownStart = startFormula ?? '1';
if (startFormula) {
const roll = await new Roll(startFormula).roll();
if (roll.dice.length > 0) {
countdownStart = roll.total;
const message = await roll.toMessage();
countdownMessages.push(message);
} else {
startFormula = null;
}
}
data.countdowns[foundry.utils.randomID()] = {
...countdown,
progress: {
...countdown.progress,
current: countdownStart,
start: countdownStart,
startFormula
}
};
}
if (game.modules.get('dice-so-nice')?.active) {
await Promise.all(
countdownMessages.map(message => {
return game.dice3d.waitFor3DAnimationByMessageID(message.id);
})
);
}
await emitAsGM(
GMUpdateEvent.UpdateCountdowns,
async () => {
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
await countdownSetting.updateSource(data);
await game.settings.set(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Countdowns,
countdownSetting.toObject()
),
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.Countdown }
});
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
},
data,
null,
{
refreshType: RefreshType.Countdown
}
);
}
/**
* Update Action Workflow config object.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
prepareConfig(config) {
config.countdowns = this.countdown;
return config;
}
}

View file

@ -47,30 +47,15 @@ export default class EffectsField extends fields.ArrayField {
static async applyEffects(targets) { static async applyEffects(targets) {
if (!this.effects?.length || !targets?.length) return; if (!this.effects?.length || !targets?.length) return;
const conditions = CONFIG.DH.GENERAL.conditions();
let effects = this.effects; let effects = this.effects;
const messageTargets = []; const messageTargets = [];
targets.forEach(async baseToken => { targets.forEach(async baseToken => {
if (this.hasSave && baseToken.saved.success === true) effects = this.effects.filter(e => e.onSave === true); if (this.hasSave && token.saved.success === true) effects = this.effects.filter(e => e.onSave === true);
if (!effects.length) return; if (!effects.length) return;
const token = const token = canvas.tokens.get(baseToken.id);
canvas.tokens.get(baseToken.id) ?? foundry.utils.fromUuidSync(baseToken.actorId).prototypeToken;
if (!token) return; if (!token) return;
messageTargets.push(token.document);
const messageToken = token.document ?? token;
const conditionImmunities = messageToken.actor.system.rules.conditionImmunities ?? {};
messageTargets.push({
token: messageToken,
conditionImmunities: Object.values(conditionImmunities).some(x => x)
? game.i18n.format('DAGGERHEART.UI.Chat.effectSummary.immunityTo', {
immunities: Object.keys(conditionImmunities)
.filter(x => conditionImmunities[x])
.map(x => game.i18n.localize(conditions[x].name))
.join(', ')
})
: null
});
effects.forEach(async e => { effects.forEach(async e => {
const effect = this.item.effects.get(e._id); const effect = this.item.effects.get(e._id);

View file

@ -46,7 +46,7 @@ export default class SaveField extends fields.SchemaField {
if (SaveField.getAutomation() !== CONFIG.DH.SETTINGS.actionAutomationChoices.never.id || force) { if (SaveField.getAutomation() !== CONFIG.DH.SETTINGS.actionAutomationChoices.never.id || force) {
targets ??= config.targets.filter(t => !config.hasRoll || t.hit); targets ??= config.targets.filter(t => !config.hasRoll || t.hit);
await SaveField.rollAllSave.call(this, targets, config.event, message); await SaveField.rollAllSave.call(this, targets, config.event, message);
} else return; } else return false;
} }
/** /**
@ -124,21 +124,29 @@ export default class SaveField extends fields.SchemaField {
*/ */
static async updateSaveMessage(result, message, targetId) { static async updateSaveMessage(result, message, targetId) {
if (!result) return; if (!result) return;
const updateMsg = async function (message, targetId, result) {
const chatMessage = ui.chat.collection.get(message._id), // setTimeout(async () => {
changes = { const chatMessage = ui.chat.collection.get(message._id),
flags: { changes = {
[game.system.id]: { flags: {
reactionRolls: { [game.system.id]: {
[targetId]: { reactionRolls: {
result: result.roll.total, [targetId]: {
success: result.roll.success result: result.roll.total,
success: result.roll.success
}
} }
} }
} }
} };
}; await chatMessage.update(changes);
await chatMessage.update(changes); // }, 100);
};
if (game.modules.get('dice-so-nice')?.active)
game.dice3d
.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id)
.then(async () => await updateMsg(message, targetId, result));
else await updateMsg(message, targetId, result);
} }
/** /**

View file

@ -25,12 +25,9 @@ export default class TargetField extends fields.SchemaField {
config.hasTarget = true; config.hasTarget = true;
let targets; let targets;
// If the Action is configured as self-targeted, set targets as the owner. Probably better way than to fallback to getDependentTokens // If the Action is configured as self-targeted, set targets as the owner. Probably better way than to fallback to getDependentTokens
if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id) { if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id)
targets = [this.actor.token ?? this.actor.prototypeToken]; targets = [this.actor.token ?? this.actor.prototypeToken];
} else if (config.targetUuid) { else {
const actor = fromUuidSync(config.targetUuid);
targets = [actor.token ?? actor.prototypeToken];
} else {
targets = Array.from(game.user.targets); targets = Array.from(game.user.targets);
if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) { if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) {
targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type)); targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type));
@ -82,7 +79,7 @@ export default class TargetField extends fields.SchemaField {
return { return {
id: token.id, id: token.id,
actorId: token.actor.uuid, actorId: token.actor.uuid,
name: token.name, name: token.actor.name,
img: token.actor.img, img: token.actor.img,
difficulty: token.actor.system.difficulty, difficulty: token.actor.system.difficulty,
evasion: token.actor.system.evasion, evasion: token.actor.system.evasion,

View file

@ -141,12 +141,6 @@ export function ActionMixin(Base) {
return this.documentName; return this.documentName;
} }
//Getter for icons
get typeIcon() {
const config = CONFIG.DH.ACTIONS.actionTypes[this.type];
return config?.icon || 'fa-question'; // Fallback icon just in case
}
get relativeUUID() { get relativeUUID() {
return `.Item.${this.item.id}.Action.${this.id}`; return `.Item.${this.item.id}.Action.${this.id}`;
} }
@ -262,27 +256,18 @@ export function ActionMixin(Base) {
async toChat(origin) { async toChat(origin) {
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');
const systemData = { const systemData = {
title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'), title: game.i18n.localize('DAGGERHEART.CONFIG.ActionType.action'),
origin: origin, origin: origin,
action: { action: { name: this.name, img: this.img, tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'] },
name: this.name,
img: this.baseAction ? this.parent.parent.img : this.img,
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10']
},
itemOrigin: this.item, itemOrigin: this.item,
description: this.description || (this.item instanceof Item ? this.item.system.description : '') description: this.description
}; };
const speaker = cls.getSpeaker();
const msg = { const msg = {
type: 'abilityUse', type: 'abilityUse',
user: game.user.id, user: game.user.id,
actor: { name: this.actor.name, img: this.actor.img }, actor: { name: this.actor.name, img: this.actor.img },
author: this.author, author: this.author,
speaker: { speaker: cls.getSpeaker(),
speaker,
actor: speaker.actor ?? this.actor
},
title: game.i18n.localize('DAGGERHEART.UI.Chat.action.title'), title: game.i18n.localize('DAGGERHEART.UI.Chat.action.title'),
system: systemData, system: systemData,
content: await foundry.applications.handlebars.renderTemplate( content: await foundry.applications.handlebars.renderTemplate(

View file

@ -43,12 +43,6 @@ export default class DHBeastform extends BaseDataItem {
base64: false base64: false
}), }),
tokenSize: new fields.SchemaField({ tokenSize: new fields.SchemaField({
size: new fields.StringField({
required: true,
nullable: false,
choices: CONFIG.DH.ACTOR.tokenSize,
initial: CONFIG.DH.ACTOR.tokenSize.custom.id
}),
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }), height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }) width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
}), }),
@ -196,18 +190,9 @@ export default class DHBeastform extends BaseDataItem {
await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [beastformEffect.toObject()]); await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [beastformEffect.toObject()]);
const autoTokenSize =
this.tokenSize.size !== 'custom'
? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes[
this.tokenSize.size
]
: null;
const width = autoTokenSize ?? this.tokenSize.width;
const height = autoTokenSize ?? this.tokenSize.height;
const prototypeTokenUpdate = { const prototypeTokenUpdate = {
height, height: this.tokenSize.height,
width, width: this.tokenSize.width,
texture: { texture: {
src: this.tokenImg src: this.tokenImg
}, },
@ -217,33 +202,16 @@ export default class DHBeastform extends BaseDataItem {
} }
} }
}; };
const tokenUpdate = token => {
let x = null,
y = null;
if (token.object?.scene?.grid) {
const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid(
token.object.scene.grid,
{ x: token.x, y: token.y, elevation: token.elevation },
width ?? token.width,
height ?? token.height
);
x = positionData.x; const tokenUpdate = token => ({
y = positionData.y; ...prototypeTokenUpdate,
} flags: {
daggerheart: {
return { beastformTokenImg: token.texture.src,
...prototypeTokenUpdate, beastformSubjectTexture: token.ring.subject.texture
x,
y,
flags: {
daggerheart: {
beastformTokenImg: token.texture.src,
beastformSubjectTexture: token.ring.subject.texture
}
} }
}; }
}; });
await updateActorTokens(this.parent.parent, prototypeTokenUpdate, tokenUpdate); await updateActorTokens(this.parent.parent, prototypeTokenUpdate, tokenUpdate);

View file

@ -66,10 +66,6 @@ export default class DHDomainCard extends BaseDataItem {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateDomainCard')); ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateDomainCard'));
return false; return false;
} }
if (!this.actor.system.loadoutSlot.available) {
data.system.inVault = true;
}
} }
} }

View file

@ -30,13 +30,7 @@ export default class DHFeature extends BaseDataItem {
initial: null initial: null
}), }),
multiclassOrigin: new fields.BooleanField({ initial: false }), multiclassOrigin: new fields.BooleanField({ initial: false }),
identifier: new fields.StringField(), identifier: new fields.StringField()
featureForm: new fields.StringField({
required: true,
initial: 'passive',
choices: CONFIG.DH.ITEM.featureForm,
label: 'DAGGERHEART.CONFIG.FeatureForm.label'
})
}; };
} }
} }

View file

@ -51,8 +51,6 @@ export default class DHWeapon extends AttachableItem {
name: 'Attack', name: 'Attack',
img: 'icons/skills/melee/blood-slash-foam-red.webp', img: 'icons/skills/melee/blood-slash-foam-red.webp',
_id: foundry.utils.randomID(), _id: foundry.utils.randomID(),
baseAction: true,
chatDisplay: false,
systemPath: 'attack', systemPath: 'attack',
type: 'attack', type: 'attack',
range: 'melee', range: 'melee',

View file

@ -14,8 +14,7 @@ export default class DhAppearance extends foundry.abstract.DataModel {
texture: new StringField({ initial: 'astralsea', required: true, blank: false }), texture: new StringField({ initial: 'astralsea', required: true, blank: false }),
colorset: new StringField({ initial: 'inspired', required: true, blank: false }), colorset: new StringField({ initial: 'inspired', required: true, blank: false }),
material: new StringField({ initial: 'metal', required: true, blank: false }), material: new StringField({ initial: 'metal', required: true, blank: false }),
system: new StringField({ initial: 'standard', required: true, blank: false }), system: new StringField({ initial: 'standard', required: true, blank: false })
font: new StringField({ initial: 'auto', required: true, blank: false })
}); });
return { return {

View file

@ -18,11 +18,6 @@ export default class DhAutomation extends foundry.abstract.DataModel {
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label' label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
}) })
}), }),
countdownAutomation: new fields.BooleanField({
required: true,
initial: true,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.countdownAutomation.label'
}),
levelupAuto: new fields.BooleanField({ levelupAuto: new fields.BooleanField({
required: true, required: true,
initial: true, initial: true,

View file

@ -1,15 +1,14 @@
import { defaultRestOptions } from '../../config/generalConfig.mjs'; import { defaultRestOptions } from '../../config/generalConfig.mjs';
import { ActionsField } from '../fields/actionField.mjs'; import { ActionsField } from '../fields/actionField.mjs';
const currencyField = (initial, label, icon) => const currencyField = (initial, label) =>
new foundry.data.fields.SchemaField({ new foundry.data.fields.SchemaField({
enabled: new foundry.data.fields.BooleanField({ required: true, initial: true }), enabled: new foundry.data.fields.BooleanField({ required: true, initial: true }),
label: new foundry.data.fields.StringField({ label: new foundry.data.fields.StringField({
required: true, required: true,
initial, initial,
label label
}), })
icon: new foundry.data.fields.StringField({ required: true, nullable: false, blank: true, initial: icon })
}); });
export default class DhHomebrew extends foundry.abstract.DataModel { export default class DhHomebrew extends foundry.abstract.DataModel {
@ -40,60 +39,16 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
traitArray: new fields.ArrayField(new fields.NumberField({ required: true, integer: true }), { traitArray: new fields.ArrayField(new fields.NumberField({ required: true, integer: true }), {
initial: () => [2, 1, 1, 0, 0, -1] initial: () => [2, 1, 1, 0, 0, -1]
}), }),
tokenSizes: new fields.SchemaField({
tiny: new fields.NumberField({
integer: false,
initial: 0.5,
label: 'DAGGERHEART.CONFIG.TokenSize.tiny'
}),
small: new fields.NumberField({
integer: false,
initial: 0.8,
label: 'DAGGERHEART.CONFIG.TokenSize.small'
}),
medium: new fields.NumberField({
integer: false,
initial: 1,
label: 'DAGGERHEART.CONFIG.TokenSize.medium'
}),
large: new fields.NumberField({
integer: false,
initial: 2,
label: 'DAGGERHEART.CONFIG.TokenSize.large'
}),
huge: new fields.NumberField({
integer: false,
initial: 3,
label: 'DAGGERHEART.CONFIG.TokenSize.huge'
}),
gargantuan: new fields.NumberField({
integer: false,
initial: 4,
label: 'DAGGERHEART.CONFIG.TokenSize.gargantuan'
})
}),
currency: new fields.SchemaField({ currency: new fields.SchemaField({
title: new fields.StringField({ title: new fields.StringField({
required: true, required: true,
initial: 'Gold', initial: 'Gold',
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.currencyName' label: 'DAGGERHEART.SETTINGS.Homebrew.currency.currencyName'
}), }),
coins: currencyField( coins: currencyField('Coins', 'DAGGERHEART.SETTINGS.Homebrew.currency.coinName'),
'Coins', handfuls: currencyField('Handfuls', 'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName'),
'DAGGERHEART.SETTINGS.Homebrew.currency.coinName', bags: currencyField('Bags', 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName'),
'fa-solid fa-coin-front' chests: currencyField('Chests', 'DAGGERHEART.SETTINGS.Homebrew.currency.chestName')
),
handfuls: currencyField(
'Handfuls',
'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName',
'fa-solid fa-coins'
),
bags: currencyField('Bags', 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName', 'fa-solid fa-sack'),
chests: currencyField(
'Chests',
'DAGGERHEART.SETTINGS.Homebrew.currency.chestName',
'fa-solid fa-treasure-chest'
)
}), }),
restMoves: new fields.SchemaField({ restMoves: new fields.SchemaField({
longRest: new fields.SchemaField({ longRest: new fields.SchemaField({
@ -184,10 +139,22 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
/** @inheritDoc */ /** @inheritDoc */
_initializeSource(source, options = {}) { _initializeSource(source, options = {}) {
source = super._initializeSource(source, options); source = super._initializeSource(source, options);
for (const type of ['coins', 'handfuls', 'bags', 'chests']) { source.currency.coins = {
const initial = this.schema.fields.currency.fields[type].getInitialValue(); enabled: source.currency.coins.enabled ?? true,
source.currency[type] = foundry.utils.mergeObject(initial, source.currency[type], { inplace: false }); label: source.currency.coins.label || source.currency.coins
} };
source.currency.handfuls = {
enabled: source.currency.handfuls.enabled ?? true,
label: source.currency.handfuls.label || source.currency.handfuls
};
source.currency.bags = {
enabled: source.currency.bags.enabled ?? true,
label: source.currency.bags.label || source.currency.bags
};
source.currency.chests = {
enabled: source.currency.chests.enabled ?? true,
label: source.currency.chests.label || source.currency.chests
};
return source; return source;
} }
} }

View file

@ -39,13 +39,6 @@ export default class DhVariantRules extends foundry.abstract.DataModel {
label: 'DAGGERHEART.CONFIG.Range.close.name' label: 'DAGGERHEART.CONFIG.Range.close.name'
}), }),
far: new fields.NumberField({ required: true, initial: 60, label: 'DAGGERHEART.CONFIG.Range.far.name' }) far: new fields.NumberField({ required: true, initial: 60, label: 'DAGGERHEART.CONFIG.Range.far.name' })
}),
massiveDamage: new fields.SchemaField({
enabled: new fields.BooleanField({
required: true,
initial: false,
label: 'DAGGERHEART.SETTINGS.VariantRules.FIELDS.massiveDamage.enabled.label'
})
}) })
}; };
} }

View file

@ -13,6 +13,8 @@ export default class D20Roll extends DHRoll {
DISADVANTAGE: -1 DISADVANTAGE: -1
}; };
static CRITICAL_TRESHOLD = 20;
static DefaultDialog = D20RollDialog; static DefaultDialog = D20RollDialog;
get title() { get title() {
@ -35,7 +37,7 @@ export default class D20Roll extends DHRoll {
get isCritical() { get isCritical() {
if (!this.d20._evaluated) return; if (!this.d20._evaluated) return;
return this.d20.total >= this.data.system.criticalThreshold; return this.d20.total >= this.constructor.CRITICAL_TRESHOLD;
} }
get hasAdvantage() { get hasAdvantage() {
@ -98,10 +100,10 @@ export default class D20Roll extends DHRoll {
this.options.roll.modifiers = this.applyBaseBonus(); this.options.roll.modifiers = this.applyBaseBonus();
this.options.experiences?.forEach(m => { this.options.experiences?.forEach(m => {
if (this.options.data.system?.experiences?.[m]) if (this.options.data.experiences?.[m])
this.options.roll.modifiers.push({ this.options.roll.modifiers.push({
label: this.options.data.system.experiences[m].name, label: this.options.data.experiences[m].name,
value: this.options.data.system.experiences[m].value value: this.options.data.experiences[m].value
}); });
}); });

View file

@ -32,7 +32,7 @@ export default class DHRoll extends Roll {
const actorIdSplit = config.source?.actor?.split('.'); const actorIdSplit = config.source?.actor?.split('.');
if (actorIdSplit) { if (actorIdSplit) {
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
config.tagTeamSelected = Boolean(tagTeamSettings.members[actorIdSplit[actorIdSplit.length - 1]]); config.tagTeamSelected = tagTeamSettings.members[actorIdSplit[actorIdSplit.length - 1]];
} }
for (const hook of config.hooks) { for (const hook of config.hooks) {
@ -115,7 +115,7 @@ export default class DHRoll extends Roll {
game.system.api.applications.dialogs.TagTeamDialog.assignRoll(message.speakerActor, message); game.system.api.applications.dialogs.TagTeamDialog.assignRoll(message.speakerActor, message);
} }
if (roll.formula !== '' && game.modules.get('dice-so-nice')?.active) { if (game.modules.get('dice-so-nice')?.active) {
await game.dice3d.waitFor3DAnimationByMessageID(message.id); await game.dice3d.waitFor3DAnimationByMessageID(message.id);
} }
@ -236,3 +236,52 @@ export default class DHRoll extends Roll {
return {}; return {};
} }
} }
export const registerRollDiceHooks = () => {
Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
const hopeFearAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hopeFear;
if (
!config.source?.actor ||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
config.actionType === 'reaction' ||
config.tagTeamSelected ||
config.skips?.resources
)
return;
const actor = await fromUuid(config.source.actor);
let updates = [];
if (!actor) return;
if (config.roll.isCritical || config.roll.result.duality === 1)
updates.push({ key: 'hope', value: 1, total: -1, enabled: true });
if (config.roll.isCritical) updates.push({ key: 'stress', value: 1, total: -1, enabled: true });
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true });
if (config.rerolledRoll) {
if (config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1)
updates.push({ key: 'hope', value: -1, total: 1, enabled: true });
if (config.rerolledRoll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true });
if (config.rerolledRoll.result.duality === -1)
updates.push({ key: 'fear', value: -1, total: 1, enabled: true });
}
if (updates.length) {
const target = actor.system.partner ?? actor;
if (!['dead', 'defeated', 'unconscious'].some(x => actor.statuses.has(x))) {
if (config.rerolledRoll) target.modifyResource(updates);
else config.costs = [...(config.costs ?? []), ...updates];
}
}
if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;
const rollResult = config.roll.success || config.targets.some(t => t.hit),
looseSpotlight = !rollResult || config.roll.result.duality === -1;
if (looseSpotlight && game.combat?.active) {
const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
}
return;
});
};

View file

@ -2,7 +2,6 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
import D20Roll from './d20Roll.mjs'; import D20Roll from './d20Roll.mjs';
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
import { getDiceSoNicePresets } from '../config/generalConfig.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;
@ -20,7 +19,7 @@ export default class DualityRoll extends D20Roll {
get title() { get title() {
return game.i18n.localize( return game.i18n.localize(
`DAGGERHEART.GENERAL.${this.options?.actionType === 'reaction' ? 'reactionRoll' : 'dualityRoll'}` `DAGGERHEART.GENERAL.${this.options?.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id ? 'reactionRoll' : 'dualityRoll'}`
); );
} }
@ -220,88 +219,6 @@ export default class DualityRoll extends D20Roll {
return data; return data;
} }
static async buildPost(roll, config, message) {
await super.buildPost(roll, config, message);
await DualityRoll.dualityUpdate(config);
}
static async addDualityResourceUpdates(config) {
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
const hopeFearAutomation = automationSettings.hopeFear;
if (
!config.source?.actor ||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
config.actionType === 'reaction' ||
config.tagTeamSelected ||
config.skips?.resources
)
return;
const actor = await fromUuid(config.source.actor);
let updates = [];
if (!actor) return;
if (config.rerolledRoll) {
if (config.roll.result.duality != config.rerolledRoll.result.duality) {
const hope =
(config.roll.isCritical || config.roll.result.duality === 1 ? 1 : 0) -
(config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1 ? 1 : 0);
const stress = (config.roll.isCritical ? 1 : 0) - (config.rerolledRoll.isCritical ? 1 : 0);
const fear =
(config.roll.result.duality === -1 ? 1 : 0) - (config.rerolledRoll.result.duality === -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 });
}
} else {
if (config.roll.isCritical || config.roll.result.duality === 1)
updates.push({ key: 'hope', value: 1, total: -1, enabled: true });
if (config.roll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true });
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true });
}
if (updates.length) {
// const target = actor.system.partner ?? actor;
if (!['dead', 'defeated', 'unconscious'].some(x => actor.statuses.has(x))) {
config.resourceUpdates.addResources(updates);
}
}
}
static async dualityUpdate(config) {
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
if (
automationSettings.countdownAutomation &&
config.actionType !== 'reaction' &&
!config.tagTeamSelected &&
!config.skips?.updateCountdowns
) {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
if (config.roll.result.duality === -1) {
await updateCountdowns(
CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id,
CONFIG.DH.GENERAL.countdownProgressionTypes.fear.id
);
} else {
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id);
}
}
await DualityRoll.addDualityResourceUpdates(config);
if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;
const rollResult = config.roll.success || config.targets?.some(t => t.hit),
looseSpotlight = !rollResult || config.roll.result.duality === -1;
if (looseSpotlight && game.combat?.active) {
const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
}
}
static async reroll(rollString, target, message) { static async reroll(rollString, target, message) {
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false }); let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false });
const term = parsedRoll.terms[target.dataset.dieIndex]; const term = parsedRoll.terms[target.dataset.dieIndex];
@ -340,20 +257,14 @@ export default class DualityRoll extends D20Roll {
newRoll.extra = newRoll.extra.slice(2); newRoll.extra = newRoll.extra.slice(2);
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
Hooks.call(`${CONFIG.DH.id}.postRollDuality`, {
const actor = message.system.source.actor ? await foundry.utils.fromUuid(message.system.source.actor) : null;
const config = {
source: { actor: message.system.source.actor ?? '' }, source: { actor: message.system.source.actor ?? '' },
targets: message.system.targets, targets: message.system.targets,
tagTeamSelected: Object.values(tagTeamSettings.members).some(x => x.messageId === message._id), tagTeamSelected: Object.values(tagTeamSettings.members).some(x => x.messageId === message._id),
roll: newRoll, roll: newRoll,
rerolledRoll: message.system.roll, rerolledRoll:
resourceUpdates: new ResourceUpdateMap(actor) newRoll.result.duality !== message.system.roll.result.duality ? message.system.roll : undefined
}; });
await DualityRoll.addDualityResourceUpdates(config);
await config.resourceUpdates.updateResources();
return { newRoll, parsedRoll }; return { newRoll, parsedRoll };
} }
} }

View file

@ -1,10 +1,8 @@
export { default as DhpActor } from './actor.mjs'; export { default as DhpActor } from './actor.mjs';
export { default as DHItem } from './item.mjs'; export { default as DHItem } from './item.mjs';
export { default as DhpCombat } from './combat.mjs'; export { default as DhpCombat } from './combat.mjs';
export { default as DHCombatant } from './combatant.mjs';
export { default as DhActiveEffect } from './activeEffect.mjs'; export { default as DhActiveEffect } from './activeEffect.mjs';
export { default as DhChatMessage } from './chatMessage.mjs'; export { default as DhChatMessage } from './chatMessage.mjs';
export { default as DhScene } from './scene.mjs';
export { default as DhToken } from './token.mjs'; export { default as DhToken } from './token.mjs';
export { default as DhTooltipManager } from './tooltipManager.mjs'; export { default as DhTooltipManager } from './tooltipManager.mjs';
export { default as DhTemplateManager } from './templateManager.mjs'; export { default as DhTemplateManager } from './templateManager.mjs';

View file

@ -1,5 +1,4 @@
import { itemAbleRollParse } from '../helpers/utils.mjs'; import { itemAbleRollParse } from '../helpers/utils.mjs';
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
export default class DhActiveEffect extends foundry.documents.ActiveEffect { export default class DhActiveEffect extends foundry.documents.ActiveEffect {
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -58,27 +57,6 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
update.img = 'icons/magic/life/heart-cross-blue.webp'; update.img = 'icons/magic/life/heart-cross-blue.webp';
} }
const immuneStatuses =
data.statuses?.filter(
status =>
this.parent.system.rules?.conditionImmunities &&
this.parent.system.rules.conditionImmunities[status]
) ?? [];
if (immuneStatuses.length > 0) {
update.statuses = data.statuses.filter(x => !immuneStatuses.includes(x));
const conditions = CONFIG.DH.GENERAL.conditions();
const scrollingTexts = immuneStatuses.map(status => ({
text: game.i18n.format('DAGGERHEART.ACTIVEEFFECT.immuneStatusText', {
status: game.i18n.localize(conditions[status].name)
})
}));
if (update.statuses.length > 0) {
setTimeout(() => scrollingTexts, 500);
} else {
this.parent.queueScrollText(scrollingTexts);
}
}
if (Object.keys(update).length > 0) { if (Object.keys(update).length > 0) {
await this.updateSource(update); await this.updateSource(update);
} }
@ -86,20 +64,6 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
await super._preCreate(data, options, user); await super._preCreate(data, options, user);
} }
/** @inheritdoc */
_onCreate(data, options, userId) {
super._onCreate(data, options, userId);
Hooks.callAll(RefreshType.EffectsDisplay);
}
/** @inheritdoc */
_onDelete(data, options, userId) {
super._onDelete(data, options, userId);
Hooks.callAll(RefreshType.EffectsDisplay);
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Methods */ /* Methods */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -112,10 +76,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
change.value = change.value.replaceAll(/origin\.@/gi, '@'); change.value = change.value.replaceAll(/origin\.@/gi, '@');
try { try {
const effect = foundry.utils.fromUuidSync(change.effect.origin); const effect = foundry.utils.fromUuidSync(change.effect.origin);
const doc = const doc = effect.parent?.parent;
effect.parent?.parent instanceof game.system.api.documents.DhpActor
? effect.parent
: effect.parent.parent;
if (doc) parseModel = doc; if (doc) parseModel = doc;
} catch (_) {} } catch (_) {}
} }
@ -194,10 +155,27 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
} }
prepareDerivedData() { prepareDerivedData() {
/* Check for item availability such as in the case of subclass advancement. */ /* Preventing subclass features from transferring to actor if they do not have the right subclass advancement */
if (this.parent?.parent?.system?.isItemAvailable) { if (this.parent?.type === 'feature') {
if (!this.parent.parent.system.isItemAvailable(this.parent)) { const origSubclassParent = this.parent.system.originItemType === 'subclass';
this.transfer = false; if (origSubclassParent) {
const subclass = this.parent.parent.items.find(
x =>
x.type === 'subclass' &&
x.system.isMulticlass === (this.parent.system.identifier === 'multiclass')
);
if (subclass) {
const featureState = subclass.system.featureState;
if (
(this.parent.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization &&
featureState < 2) ||
(this.parent.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && featureState < 3)
) {
this.transfer = false;
}
}
} }
} }
} }

View file

@ -1,9 +1,8 @@
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs'; import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
import { LevelOptionType } from '../data/levelTier.mjs'; import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.mjs'; import DHFeature from '../data/item/feature.mjs';
import { createScrollText, damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs'; import { createScrollText, damageKeyToNumber } from '../helpers/utils.mjs';
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs'; import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
export default class DhpActor extends Actor { export default class DhpActor extends Actor {
parties = new Set(); parties = new Set();
@ -74,27 +73,16 @@ export default class DhpActor extends Actor {
/**@inheritdoc */ /**@inheritdoc */
async _preCreate(data, options, user) { async _preCreate(data, options, user) {
if ((await super._preCreate(data, options, user)) === false) return false; if ((await super._preCreate(data, options, user)) === false) return false;
const update = {};
// Set default token size. Done here as we do not want to set a datamodel default, since that would apply the sizing to third party actor modules that aren't set up with the size system.
if (this.system.metadata.usesSize && !data.system?.size) {
Object.assign(update, {
system: {
size: CONFIG.DH.ACTOR.tokenSize.medium.id
}
});
}
// Configure prototype token settings // Configure prototype token settings
const prototypeToken = {};
if (['character', 'companion', 'party'].includes(this.type)) if (['character', 'companion', 'party'].includes(this.type))
Object.assign(update, { Object.assign(prototypeToken, {
prototypeToken: { sight: { enabled: true },
sight: { enabled: true }, actorLink: true,
actorLink: true, disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
}
}); });
this.updateSource(update); this.updateSource({ prototypeToken });
} }
_onUpdate(changes, options, userId) { _onUpdate(changes, options, userId) {
@ -216,7 +204,7 @@ export default class DhpActor extends Actor {
for (let domainCard of domainCards) { for (let domainCard of domainCards) {
const itemCard = this.items.find(x => x.uuid === domainCard); const itemCard = this.items.find(x => x.uuid === domainCard);
itemCard?.delete(); itemCard.delete();
} }
} }
@ -349,8 +337,6 @@ export default class DhpActor extends Actor {
const embeddedItem = await this.createEmbeddedDocuments('Item', [ const embeddedItem = await this.createEmbeddedDocuments('Item', [
{ {
...multiclassData, ...multiclassData,
uuid: multiclassItem.uuid,
_stats: multiclassItem._stats,
system: { system: {
...multiclassData.system, ...multiclassData.system,
features: multiclassData.system.features.filter(x => x.type !== 'hope'), features: multiclassData.system.features.filter(x => x.type !== 'hope'),
@ -363,8 +349,6 @@ export default class DhpActor extends Actor {
await this.createEmbeddedDocuments('Item', [ await this.createEmbeddedDocuments('Item', [
{ {
...subclassData, ...subclassData,
uuid: subclassItem.uuid,
_stats: subclassItem._stats,
system: { system: {
...subclassData.system, ...subclassData.system,
isMulticlass: true isMulticlass: true
@ -379,15 +363,12 @@ export default class DhpActor extends Actor {
for (var domainCard of domainCards) { for (var domainCard of domainCards) {
if (levelupAuto) { if (levelupAuto) {
const cardItem = await foundry.utils.fromUuid(domainCard.data[0]); const itemData = (await foundry.utils.fromUuid(domainCard.data[0])).toObject();
const cardData = cardItem.toObject();
const embeddedItem = await this.createEmbeddedDocuments('Item', [ const embeddedItem = await this.createEmbeddedDocuments('Item', [
{ {
...cardData, ...itemData,
uuid: cardItem.uuid,
_stats: cardItem._stats,
system: { system: {
...cardData.system, ...itemData.system,
inVault: true inVault: true
} }
} }
@ -401,15 +382,12 @@ export default class DhpActor extends Actor {
const achievementDomainCards = []; const achievementDomainCards = [];
if (levelupAuto) { if (levelupAuto) {
for (var card of Object.values(level.achievements.domainCards)) { for (var card of Object.values(level.achievements.domainCards)) {
const cardItem = await foundry.utils.fromUuid(card.uuid); const itemData = (await foundry.utils.fromUuid(card.uuid)).toObject();
const cardData = cardItem.toObject();
const embeddedItem = await this.createEmbeddedDocuments('Item', [ const embeddedItem = await this.createEmbeddedDocuments('Item', [
{ {
...cardData, ...itemData,
uuid: cardItem.uuid,
_stats: cardItem._stats,
system: { system: {
...cardData.system, ...itemData.system,
inVault: true inVault: true
} }
} }
@ -489,7 +467,6 @@ export default class DhpActor extends Actor {
async diceRoll(config) { async diceRoll(config) {
config.source = { ...(config.source ?? {}), actor: this.uuid }; config.source = { ...(config.source ?? {}), actor: this.uuid };
config.data = this.getRollData(); config.data = this.getRollData();
config.resourceUpdates = new ResourceUpdateMap(this);
const rollClass = config.roll.lite ? CONFIG.Dice.daggerheart['DHRoll'] : this.rollClass; const rollClass = config.roll.lite ? CONFIG.Dice.daggerheart['DHRoll'] : this.rollClass;
return await rollClass.build(config); return await rollClass.build(config);
} }
@ -539,11 +516,7 @@ export default class DhpActor extends Actor {
/**@inheritdoc */ /**@inheritdoc */
getRollData() { getRollData() {
const rollData = foundry.utils.deepClone(super.getRollData()); const rollData = super.getRollData();
/* system gets repeated infinately which causes issues when trying to use the data for document creation */
delete rollData.system;
rollData.id = this.id;
rollData.name = this.name; rollData.name = this.name;
rollData.system = this.system.getRollData(); rollData.system = this.system.getRollData();
rollData.prof = this.system.proficiency ?? 1; rollData.prof = this.system.proficiency ?? 1;
@ -631,19 +604,6 @@ export default class DhpActor extends Actor {
} }
} }
} }
if (this.type === 'adversary') {
const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => {
return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value);
}, 0);
hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0);
if (
hpDamage.value &&
this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]
) {
hpDamage.value -= 1;
}
}
} }
updates.forEach( updates.forEach(
@ -709,10 +669,6 @@ export default class DhpActor extends Actor {
return updates; return updates;
} }
/**
* Resources are modified asynchronously, so be careful not to update the same resource in
* quick succession.
*/
async modifyResource(resources) { async modifyResource(resources) {
if (!resources?.length) return; if (!resources?.length) return;
@ -795,11 +751,6 @@ export default class DhpActor extends Actor {
} }
convertDamageToThreshold(damage) { convertDamageToThreshold(damage) {
const massiveDamageEnabled = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules)
.massiveDamage.enabled;
if (massiveDamageEnabled && damage >= this.system.damageThresholds.severe * 2) {
return 4;
}
return damage >= this.system.damageThresholds.severe ? 3 : damage >= this.system.damageThresholds.major ? 2 : 1; return damage >= this.system.damageThresholds.severe ? 3 : damage >= this.system.damageThresholds.major ? 2 : 1;
} }
@ -883,65 +834,4 @@ export default class DhpActor extends Actor {
if (this.system._getTags) tags.push(...this.system._getTags()); if (this.system._getTags) tags.push(...this.system._getTags());
return tags; return tags;
} }
/** Get active effects */
getActiveEffects() {
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
return this.effects
.filter(x => !x.disabled)
.reduce((acc, effect) => {
acc.push(effect);
const currentStatusActiveEffects = acc.filter(
x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first())?.name)
);
for (var status of effect.statuses) {
if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) {
const statusData = statusMap.get(status);
if (statusData) {
acc.push({
condition: status,
appliedBy: game.i18n.localize(effect.name),
name: game.i18n.localize(statusData.name),
statuses: new Set([status]),
img: statusData.icon ?? statusData.img,
description: game.i18n.localize(statusData.description),
tint: effect.tint
});
}
}
}
return acc;
}, []);
}
/* Temporarily copying the foundry method to add a fix to a bug with scenes
https://discord.com/channels/170995199584108546/1296292044011995136/1446693077443149856
*/
getDependentTokens({ scenes, linked = false } = {}) {
if (this.isToken && !scenes) return [this.token];
if (scenes) scenes = Array.isArray(scenes) ? scenes : [scenes];
else scenes = Array.from(this._dependentTokens.keys());
/* Code to filter out nonexistant scenes */
scenes = scenes.filter(scene => game.scenes.some(x => x.id === scene.id));
if (this.isToken) {
const parent = this.token.parent;
return scenes.includes(parent) ? [this.token] : [];
}
const allTokens = [];
for (const scene of scenes) {
if (!scene) continue;
const tokens = this._dependentTokens.get(scene);
for (const token of tokens ?? []) {
if (!linked || token.actorLink) allTokens.push(token);
}
}
return allTokens;
}
} }

View file

@ -16,54 +16,4 @@ export default class DhpCombat extends Combat {
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
} }
async toggleModifierEffects(add, actors, category, groupingKey) {
const effectData = category && groupingKey ? [{ category, grouping: groupingKey }] : this.system.battleToggles;
if (add) {
const effects = effectData.reduce((acc, toggle) => {
const grouping = CONFIG.DH.ENCOUNTER.BPModifiers[toggle.category]?.[toggle.grouping];
if (!grouping?.effects?.length) return acc;
acc.push(
...grouping.effects.map(effect => ({
...effect,
name: game.i18n.localize(effect.name),
description: game.i18n.localize(effect.description),
effectTargetTypes: grouping.effectTargetTypes ?? [],
flags: {
[`${CONFIG.DH.id}.${CONFIG.DH.FLAGS.combatToggle}`]: {
category: toggle.category,
grouping: toggle.grouping
}
}
}))
);
return acc;
}, []);
if (!effects.length) return;
for (let actor of actors) {
await actor.createEmbeddedDocuments(
'ActiveEffect',
effects.filter(x => x.effectTargetTypes.includes(actor.type))
);
}
} else {
for (let actor of actors) {
await actor.deleteEmbeddedDocuments(
'ActiveEffect',
actor.effects
.filter(x => {
const flag = x.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.combatToggle);
if (!flag) return false;
return effectData.some(
data => flag.category == data.category && flag.grouping === data.grouping
);
})
.map(x => x.id)
);
}
}
}
} }

View file

@ -1,6 +0,0 @@
export default class DhCombatant extends Combatant {
/**@inheritdoc */
get isNPC() {
return this.actor?.isNPC ?? (!this.actor || !this.hasPlayerOwner);
}
}

View file

@ -28,13 +28,6 @@ export default class DHItem extends foundry.documents.Item {
return doc; return doc;
} }
static async createDocuments(sources, operation) {
// Ensure that items being created are valid to the actor its being added to
const actor = operation.parent;
sources = actor?.system?.isItemValid ? sources.filter((s) => actor.system.isItemValid(s)) : sources;
return super.createDocuments(sources, operation);
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @inheritDoc */ /** @inheritDoc */

Some files were not shown because too many files have changed in this diff Show more