mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-03-07 14:36:13 +01:00
Compare commits
31 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42a22a49f0 | ||
|
|
da77c2a190 | ||
|
|
68decf0b57 | ||
|
|
4f0670cc35 | ||
|
|
b346ce6766 | ||
|
|
dddd0581f7 | ||
|
|
83329fac46 | ||
|
|
ee0b7b2792 | ||
|
|
4d062a6892 | ||
|
|
487c1fd9a2 | ||
|
|
3aa5cd806a | ||
|
|
2e93b79633 | ||
|
|
244dbd4902 | ||
|
|
c7aed6825a | ||
|
|
9cb5112b62 | ||
|
|
81b6f7fc51 | ||
|
|
828fffd552 | ||
|
|
fc5626ac47 | ||
|
|
2e62545aa7 | ||
|
|
b09c712dd5 | ||
|
|
ca4336bd39 | ||
|
|
77ac11c522 | ||
|
|
50311679a5 | ||
|
|
3a7bcd1b0a | ||
|
|
511e4bd644 | ||
|
|
395820513b | ||
|
|
3566ea3fd3 | ||
|
|
29d502fb97 | ||
|
|
685a25d25a | ||
|
|
dd045b3df7 | ||
|
|
0aabcec340 |
255 changed files with 1331 additions and 6017 deletions
|
|
@ -1,3 +0,0 @@
|
|||
[*]
|
||||
indent_size = 4
|
||||
indent_style = spaces
|
||||
108
daggerheart.mjs
108
daggerheart.mjs
|
|
@ -3,15 +3,13 @@ import * as applications from './module/applications/_module.mjs';
|
|||
import * as data from './module/data/_module.mjs';
|
||||
import * as models from './module/data/_module.mjs';
|
||||
import * as documents from './module/documents/_module.mjs';
|
||||
import * as collections from './module/documents/collections/_module.mjs';
|
||||
import * as dice from './module/dice/_module.mjs';
|
||||
import * as fields from './module/data/fields/_module.mjs';
|
||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
||||
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
||||
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll } from './module/dice/_module.mjs';
|
||||
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll } from './module/dice/_module.mjs';
|
||||
import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs';
|
||||
import { enrichedFateRoll, getFateTypeData } from './module/enrichers/FateRollEnricher.mjs';
|
||||
import {
|
||||
handlebarsRegistration,
|
||||
runMigrations,
|
||||
|
|
@ -26,18 +24,16 @@ import TokenManager from './module/documents/tokenManager.mjs';
|
|||
CONFIG.DH = SYSTEM;
|
||||
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
||||
|
||||
CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll];
|
||||
CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll];
|
||||
CONFIG.Dice.daggerheart = {
|
||||
DHRoll: DHRoll,
|
||||
DualityRoll: DualityRoll,
|
||||
D20Roll: D20Roll,
|
||||
DamageRoll: DamageRoll,
|
||||
FateRoll: FateRoll
|
||||
DamageRoll: DamageRoll
|
||||
};
|
||||
|
||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||
CONFIG.Actor.dataModels = models.actors.config;
|
||||
CONFIG.Actor.collection = collections.DhActorCollection;
|
||||
|
||||
CONFIG.Item.documentClass = documents.DHItem;
|
||||
CONFIG.Item.dataModels = models.items.config;
|
||||
|
|
@ -60,9 +56,6 @@ CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer;
|
|||
|
||||
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
||||
|
||||
CONFIG.RollTable.documentClass = documents.DhRollTable;
|
||||
CONFIG.RollTable.resultTemplate = 'systems/daggerheart/templates/ui/chat/table-result.hbs';
|
||||
|
||||
CONFIG.Scene.documentClass = documents.DhScene;
|
||||
|
||||
CONFIG.Token.documentClass = documents.DhToken;
|
||||
|
|
@ -110,7 +103,7 @@ Hooks.once('init', () => {
|
|||
type: game.i18n.localize(typePath)
|
||||
});
|
||||
|
||||
const { Items, Actors, RollTables } = foundry.documents.collections;
|
||||
const { Items, Actors } = foundry.documents.collections;
|
||||
Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2);
|
||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Ancestry, {
|
||||
types: ['ancestry'],
|
||||
|
|
@ -195,12 +188,6 @@ Hooks.once('init', () => {
|
|||
label: sheetLabel('TYPES.Actor.party')
|
||||
});
|
||||
|
||||
RollTables.unregisterSheet('core', foundry.applications.sheets.RollTableSheet);
|
||||
RollTables.registerSheet(SYSTEM.id, applications.sheets.rollTables.RollTableSheet, {
|
||||
types: ['base'],
|
||||
makeDefault: true
|
||||
});
|
||||
|
||||
DocumentSheetConfig.unregisterSheet(
|
||||
CONFIG.ActiveEffect.documentClass,
|
||||
'core',
|
||||
|
|
@ -242,41 +229,6 @@ Hooks.on('setup', () => {
|
|||
systemEffect: true
|
||||
}))
|
||||
];
|
||||
|
||||
const damageThresholds = ['damageThresholds.major', 'damageThresholds.severe'];
|
||||
const traits = Object.keys(game.system.api.data.actors.DhCharacter.schema.fields.traits.fields).map(
|
||||
trait => `traits.${trait}.value`
|
||||
);
|
||||
const resistance = Object.values(game.system.api.data.actors.DhCharacter.schema.fields.resistance.fields).flatMap(
|
||||
type => Object.keys(type.fields).map(x => `resistance.${type.name}.${x}`)
|
||||
);
|
||||
const actorCommon = {
|
||||
bar: ['resources.stress'],
|
||||
value: [...resistance, 'advantageSources', 'disadvantageSources']
|
||||
};
|
||||
CONFIG.Actor.trackableAttributes = {
|
||||
character: {
|
||||
bar: [...actorCommon.bar, 'resources.hitPoints', 'resources.hope'],
|
||||
value: [
|
||||
...actorCommon.value,
|
||||
...traits,
|
||||
...damageThresholds,
|
||||
'proficiency',
|
||||
'evasion',
|
||||
'armorScore',
|
||||
'scars',
|
||||
'levelData.level.current'
|
||||
]
|
||||
},
|
||||
adversary: {
|
||||
bar: [...actorCommon.bar, 'resources.hitPoints'],
|
||||
value: [...actorCommon.value, ...damageThresholds, 'criticalThreshold', 'difficulty']
|
||||
},
|
||||
companion: {
|
||||
bar: [...actorCommon.bar],
|
||||
value: [...actorCommon.value, 'evasion', 'levelData.level.current']
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Hooks.on('ready', async () => {
|
||||
|
|
@ -344,15 +296,13 @@ Hooks.on('chatMessage', (_, message) => {
|
|||
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
|
||||
: undefined;
|
||||
const difficulty = rollCommand.difficulty;
|
||||
const grantResources = rollCommand.grantResources;
|
||||
|
||||
const target = getCommandTarget({ allowNull: true });
|
||||
const title =
|
||||
(flavor ?? traitValue)
|
||||
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
|
||||
})
|
||||
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
||||
const title = traitValue
|
||||
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
|
||||
})
|
||||
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
||||
|
||||
enrichedDualityRoll({
|
||||
reaction,
|
||||
|
|
@ -362,36 +312,7 @@ Hooks.on('chatMessage', (_, message) => {
|
|||
title,
|
||||
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'),
|
||||
actionType: null,
|
||||
advantage,
|
||||
grantResources
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (message.startsWith('/fr')) {
|
||||
const result =
|
||||
message.trim().toLowerCase() === '/fr' ? { result: {} } : rollCommandToJSON(message.replace(/\/fr\s?/, ''));
|
||||
|
||||
if (!result) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateParsing'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const { result: rollCommand, flavor } = result;
|
||||
const fateTypeData = getFateTypeData(rollCommand?.type);
|
||||
|
||||
if (!fateTypeData)
|
||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
||||
|
||||
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
||||
const target = getCommandTarget({ allowNull: true });
|
||||
const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll');
|
||||
|
||||
enrichedFateRoll({
|
||||
target,
|
||||
title,
|
||||
label: fateTypeLabel,
|
||||
fateType
|
||||
advantage
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
|
@ -420,7 +341,10 @@ const updateActorsRangeDependentEffects = async token => {
|
|||
// 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 = userTarget.distanceTo(token.object) <= required;
|
||||
const inRange =
|
||||
required === 5
|
||||
? userTarget.isAdjacentWith(token.object)
|
||||
: userTarget.distanceTo(token.object) <= required;
|
||||
if (reverse ? inRange : !inRange) {
|
||||
enabledEffect = false;
|
||||
break;
|
||||
|
|
@ -457,8 +381,8 @@ Hooks.on('targetToken', () => {
|
|||
debouncedRangeEffectCall();
|
||||
});
|
||||
|
||||
Hooks.on('refreshToken', (token, options) => {
|
||||
if (options.refreshPosition && !token._original) {
|
||||
Hooks.on('refreshToken', (_, options) => {
|
||||
if (options.refreshPosition) {
|
||||
debouncedRangeEffectCall();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
203
lang/en.json
203
lang/en.json
|
|
@ -192,9 +192,6 @@
|
|||
},
|
||||
"age": "Age",
|
||||
"backgroundQuestions": "Backgrounds",
|
||||
"burden": {
|
||||
"ignore": { "label": "Burden: Ignore", "hint": "Ignore burden rules" }
|
||||
},
|
||||
"companionFeatures": "Companion Features",
|
||||
"connections": "Connections",
|
||||
"contextMenu": {
|
||||
|
|
@ -217,12 +214,6 @@
|
|||
"maxEvasionBonus": "Max Evasion Increase",
|
||||
"maxHPBonus": "Max HP Increase",
|
||||
"pronouns": "Pronouns",
|
||||
"roll": {
|
||||
"guaranteedCritical": {
|
||||
"label": "Guaranteed Critical",
|
||||
"hint": "Set to 1 to always roll a critical"
|
||||
}
|
||||
},
|
||||
"story": {
|
||||
"backgroundTitle": "Background",
|
||||
"characteristics": "Characteristics",
|
||||
|
|
@ -246,13 +237,10 @@
|
|||
"confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)"
|
||||
},
|
||||
"viewLevelups": "View Levelups",
|
||||
"resetCharacter": "Reset Character",
|
||||
"viewParty": "View Party",
|
||||
"InvalidOldCharacterImportTitle": "Old Character Import",
|
||||
"InvalidOldCharacterImportText": "Character data exported prior to system version 1.1 will not generate a complete character. Do you wish to continue?",
|
||||
"cancelBeastform": "Cancel Beastform",
|
||||
"resetCharacterConfirmationTitle": "Reset Character",
|
||||
"resetCharacterConfirmationContent": "You are reseting all character data except name and portrait. Are you sure?"
|
||||
"cancelBeastform": "Cancel Beastform"
|
||||
},
|
||||
"Companion": {
|
||||
"FIELDS": {
|
||||
|
|
@ -326,8 +314,6 @@
|
|||
"selectPrimaryWeapon": "Select Primary Weapon",
|
||||
"selectSecondaryWeapon": "Select Secondary Weapon",
|
||||
"selectSubclass": "Select Subclass",
|
||||
"setupSkipTitle": "Skipping Character Setup",
|
||||
"setupSkipContent": "You are skipping the Character Setup by adding this manually. The character setup is the blinking arrows in the top-right. Are you sure you want to continue?",
|
||||
"startingItems": "Starting Items",
|
||||
"story": "Story",
|
||||
"storyExplanation": "Select which background and connection prompts you want to copy into your character's background.",
|
||||
|
|
@ -339,12 +325,6 @@
|
|||
"title": "{actor} - Character Setup",
|
||||
"traitIncreases": "Trait Increases"
|
||||
},
|
||||
"CharacterReset": {
|
||||
"title": "Reset Character",
|
||||
"alwaysDeleteSection": "Deleted Data",
|
||||
"optionalDeleteSection": "Optional Data",
|
||||
"headerTitle": "Select which data you'd like to keep"
|
||||
},
|
||||
"CombatTracker": {
|
||||
"combatStarted": "Active",
|
||||
"giveSpotlight": "Give The Spotlight",
|
||||
|
|
@ -352,12 +332,6 @@
|
|||
"requestSpotlight": "Request The Spotlight",
|
||||
"openCountdowns": "Countdowns"
|
||||
},
|
||||
"CompendiumBrowserSettings": {
|
||||
"title": "Enable Compendiums",
|
||||
"enableSource": "Enable Source",
|
||||
"disableSource": "Disable Source",
|
||||
"worldCompendiums": "World Compendiums"
|
||||
},
|
||||
"ContextMenu": {
|
||||
"disableEffect": "Disable Effect",
|
||||
"enableEffect": "Enable Effect",
|
||||
|
|
@ -458,13 +432,9 @@
|
|||
"name": "Clear Stress"
|
||||
},
|
||||
"prepare": {
|
||||
"description": "Describe how you are preparing for the next day's adventure, then gain a Hope.",
|
||||
"description": "Describe how you are preparing for the next day's adventure, then gain a Hope. If you choose to Prepare with one or more members of your party, you may each take two Hope.",
|
||||
"name": "Prepare"
|
||||
},
|
||||
"prepareWithFriends": {
|
||||
"description": "You prepare with one or more members of your party, and you each gain 2 Hope.",
|
||||
"name": "Prepare (together)"
|
||||
},
|
||||
"repairArmor": {
|
||||
"description": "Describe how you spend time repairing your armor and clear all of its Armor Slots. You may also do this to an ally's armor instead.",
|
||||
"name": "Repair Armor"
|
||||
|
|
@ -495,11 +465,7 @@
|
|||
},
|
||||
"prepare": {
|
||||
"name": "Prepare",
|
||||
"description": "Describe how you prepare yourself for the path ahead, then gain a Hope."
|
||||
},
|
||||
"prepareWithFriends": {
|
||||
"name": "Prepare (together)",
|
||||
"description": "You prepare with one or more members of your party, and you each gain 2 Hope."
|
||||
"description": "Describe how you prepare yourself for the path ahead, then gain a Hope. If you choose to Prepare with one or more members of your party, you each gain 2 Hope."
|
||||
}
|
||||
},
|
||||
"refreshable": {
|
||||
|
|
@ -511,9 +477,7 @@
|
|||
"tokenHUD": {
|
||||
"genericEffects": "Foundry Effects",
|
||||
"depositPartyTokens": "Deposit Party Tokens",
|
||||
"retrievePartyTokens": "Retrieve Party Tokens",
|
||||
"depositCompanionTokens": "Deposit Companion Token",
|
||||
"retrieveCompanionTokens": "Retrieve Companion Token"
|
||||
"retrievePartyTokens": "Retrieve Party Tokens"
|
||||
}
|
||||
},
|
||||
"ImageSelect": {
|
||||
|
|
@ -651,13 +615,6 @@
|
|||
"title": "{name} Resource",
|
||||
"rerollDice": "Reroll Dice"
|
||||
},
|
||||
"RiskItAllDialog": {
|
||||
"title": "{name} - Risk It All",
|
||||
"subtitle": "Clear Stress and Hit Points",
|
||||
"remainingTitle": "Remaining Points",
|
||||
"clearResource": "Clear {resource}",
|
||||
"finalTitle": "Final Character Resources"
|
||||
},
|
||||
"TagTeamSelect": {
|
||||
"title": "Tag Team Roll",
|
||||
"leaderTitle": "Initiating Character",
|
||||
|
|
@ -1005,10 +962,6 @@
|
|||
"outsideRange": "Outside Range"
|
||||
},
|
||||
"Condition": {
|
||||
"deathMove": {
|
||||
"name": "Death Move",
|
||||
"description": "The character is about to make a Death Move"
|
||||
},
|
||||
"dead": {
|
||||
"name": "Dead",
|
||||
"description": "The character is dead"
|
||||
|
|
@ -1031,8 +984,7 @@
|
|||
},
|
||||
"vulnerable": {
|
||||
"name": "Vulnerable",
|
||||
"description": "While a creature is Vulnerable, all rolls targeting them have advantage.\nA creature who is already Vulnerable can’t be made to take the condition again.",
|
||||
"autoAppliedByLabel": "Max Stress"
|
||||
"description": "While a creature is Vulnerable, all rolls targeting them have advantage.\nA creature who is already Vulnerable can’t be made to take the condition again."
|
||||
}
|
||||
},
|
||||
"CountdownType": {
|
||||
|
|
@ -1061,15 +1013,15 @@
|
|||
"DeathMoves": {
|
||||
"avoidDeath": {
|
||||
"name": "Avoid Death",
|
||||
"description": "Your character avoids death and faces the consequences. They temporarily drop unconscious, and then you work with the GM to describe how the situation worsens. While unconscious, your character can't move or act, and they can't be targeted by an attack. They return to consciousness when an ally clears 1 or more of their marked Hit Points or when the party finishes a long rest. After your character falls unconscious, roll your Hope Die. If its value is equal to or less than your character's level, they gain a scar: permanently cross out a Hope slot and work with the GM to determine its lasting narrative impact and how, if possible, it can be restored. If you ever cross out your last Hope slot, your character's journey ends."
|
||||
"description": "You drop unconscious temporarily and work with the GM to describe how the situation gets much worse because of it. Then roll your Fear die; if its value is equal to or under your Level, take a Scar."
|
||||
},
|
||||
"riskItAll": {
|
||||
"name": "Risk It All",
|
||||
"description": "Roll your Duality Dice. If the Hope Die is higher, your character stays on their feet and clears a number of Hit Points or Stress equal to the value of the Hope Die (you can divide the Hope Die value between Hit Points and Stress however you'd prefer). If the Fear Die is higher, your character crosses through the veil of death. If the Duality Dice show matching results, your character stays up and clears all Hit Points and Stress."
|
||||
"description": "Roll your Duality Dice. If Hope is higher, you stay on your feet and clear an amount of Hit Points and/or Stress equal to the value of the Hope die (divide the Hope die value up between these however you’d prefer). If your Fear die is higher, you cross through the veil of death. If the Duality Dice are tied, you stay on your feet and clear all Hit Points and Stress."
|
||||
},
|
||||
"blazeOfGlory": {
|
||||
"name": "Blaze Of Glory",
|
||||
"description": "Your character embraces death and goes out in a blaze of glory. Take one final action. It automatically critically succeeds (with GM approval), and then you cross through the veil of death. NOTE: A Blaze of Glory effect has been added to your character. Any Duality Roll will automatically be a critical."
|
||||
"description": "With Blaze of Glory, the player is accepting death for the character. Take one action (at GM discretion), which becomes an automatic critical success, then cross through the veil of death."
|
||||
}
|
||||
},
|
||||
"DomainCardTypes": {
|
||||
|
|
@ -1167,12 +1119,12 @@
|
|||
},
|
||||
"far": {
|
||||
"name": "Far",
|
||||
"description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility roll to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.",
|
||||
"description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility check to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.",
|
||||
"short": "Far"
|
||||
},
|
||||
"veryFar": {
|
||||
"name": "Very Far",
|
||||
"description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility roll to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.",
|
||||
"description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility check to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.",
|
||||
"short": "V. Far"
|
||||
}
|
||||
},
|
||||
|
|
@ -1295,7 +1247,6 @@
|
|||
"triggerTexts": {
|
||||
"strangePatternsContentTitle": "Matched {nr} times.",
|
||||
"strangePatternsContentSubTitle": "Increase hope and stress to a total of {nr}.",
|
||||
"strangePatternsActionExplanation": "Left click to increase, right click to decrease",
|
||||
"ferocityContent": "Spend 2 Hope to gain {bonus} bonus Evasion until after the next attack against you?",
|
||||
"ferocityEffectDescription": "Your evasion is increased by {bonus}. This bonus lasts until after the next attack made against you."
|
||||
},
|
||||
|
|
@ -1865,16 +1816,6 @@
|
|||
"singular": "Adversary",
|
||||
"plural": "Adversaries"
|
||||
},
|
||||
"Attack": {
|
||||
"hpDamageMultiplier": {
|
||||
"label": "HP Damage Multiplier",
|
||||
"hint": "Multiply any damage you deal by this number"
|
||||
},
|
||||
"hpDamageTakenMultiplier": {
|
||||
"label": "HP Damage Taken Multiplier",
|
||||
"hint": "Multiply any damage dealt to you by this number"
|
||||
}
|
||||
},
|
||||
"Bonuses": {
|
||||
"rest": {
|
||||
"downtimeAction": "Downtime Action",
|
||||
|
|
@ -2059,40 +2000,16 @@
|
|||
"reaction": "Reaction Roll"
|
||||
},
|
||||
"Rules": {
|
||||
"conditionImmunities": {
|
||||
"hidden": "Condition Immunity: Hidden",
|
||||
"restrained": "Condition Immunity: Restrained",
|
||||
"vulnerable": "Condition Immunity: Vulnerable"
|
||||
},
|
||||
"damageReduction": {
|
||||
"disabledArmor": { "label": "Disabled Armorslots" },
|
||||
"increasePerArmorMark": {
|
||||
"label": "Damage Reduction per Armor Slot",
|
||||
"hint": "A used armor slot normally reduces damage by one step. This value increases the number of steps damage is reduced by."
|
||||
},
|
||||
"magical": {
|
||||
"label": "Daamge Reduction: Only Magical",
|
||||
"hint": "Armor can only be used to reduce magical damage"
|
||||
},
|
||||
"maxArmorMarkedBonus": "Max Armor Used",
|
||||
"maxArmorMarkedStress": {
|
||||
"label": "Max Armor Used With Stress",
|
||||
"hint": "If this value is set you can use up to that much stress to spend additional Armor Marks beyond your normal maximum."
|
||||
},
|
||||
"reduceSeverity": {
|
||||
"magical": {
|
||||
"label": "Reduce Damage Severity: Magical",
|
||||
"hint": "Lowers any magical damage received by the set amount of severity degrees"
|
||||
},
|
||||
"physical": {
|
||||
"label": "Reduce Damage Severity: Physical",
|
||||
"hint": "Lowers any physical damage received by the set amount of severity degrees"
|
||||
}
|
||||
},
|
||||
"physical": {
|
||||
"label": "Damage Reduction: Only Physical",
|
||||
"hint": "Armor can only be used to reduce physical damage"
|
||||
},
|
||||
"stress": {
|
||||
"any": {
|
||||
"label": "Stress Damage Reduction: Any",
|
||||
|
|
@ -2110,12 +2027,6 @@
|
|||
"label": "Stress Damage Reduction: Minor",
|
||||
"hint": "The cost in stress you can pay to reduce minor damage to none."
|
||||
}
|
||||
},
|
||||
"thresholdImmunities": {
|
||||
"minor": {
|
||||
"label": "Threshold Immunities: Minor",
|
||||
"hint": "Automatically ignores minor damage when set to 1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"attack": {
|
||||
|
|
@ -2151,7 +2062,6 @@
|
|||
"description": "Description",
|
||||
"main": "Data",
|
||||
"information": "Information",
|
||||
"itemFeatures": "Item Features",
|
||||
"notes": "Notes",
|
||||
"inventory": "Inventory",
|
||||
"loadout": "Loadout",
|
||||
|
|
@ -2176,6 +2086,7 @@
|
|||
"tier4": "tier 4",
|
||||
"domains": "Domains",
|
||||
"downtime": "Downtime",
|
||||
"itemFeatures": "Item Features",
|
||||
"roll": "Roll",
|
||||
"rules": "Rules",
|
||||
"partyMembers": "Party Members",
|
||||
|
|
@ -2184,10 +2095,7 @@
|
|||
"questions": "Questions",
|
||||
"configuration": "Configuration",
|
||||
"base": "Base",
|
||||
"triggers": "Triggers",
|
||||
"deathMoves": "Deathmoves",
|
||||
"sources": "Sources",
|
||||
"packs": "Packs"
|
||||
"triggers": "Triggers"
|
||||
},
|
||||
"Tiers": {
|
||||
"singular": "Tier",
|
||||
|
|
@ -2211,7 +2119,6 @@
|
|||
"armorSlots": "Armor Slots",
|
||||
"artistAttribution": "Artwork By: {artist}",
|
||||
"attack": "Attack",
|
||||
"automation": "Automation",
|
||||
"basics": "Basics",
|
||||
"bonus": "Bonus",
|
||||
"burden": "Burden",
|
||||
|
|
@ -2219,7 +2126,6 @@
|
|||
"continue": "Continue",
|
||||
"criticalSuccess": "Critical Success",
|
||||
"criticalShort": "Critical",
|
||||
"currentLevel": "Current Level",
|
||||
"custom": "Custom",
|
||||
"d20Roll": "D20 Roll",
|
||||
"damage": "Damage",
|
||||
|
|
@ -2231,7 +2137,6 @@
|
|||
"dropActorsHere": "Drop Actors here",
|
||||
"dropFeaturesHere": "Drop Features here",
|
||||
"duality": "Duality",
|
||||
"dualityDice": "Duality Dice",
|
||||
"dualityRoll": "Duality Roll",
|
||||
"enabled": "Enabled",
|
||||
"evasion": "Evasion",
|
||||
|
|
@ -2241,14 +2146,11 @@
|
|||
"plural": "Experiences"
|
||||
},
|
||||
"failure": "Failure",
|
||||
"fate": "Fate",
|
||||
"fateRoll": "Fate Roll",
|
||||
"fear": "Fear",
|
||||
"features": "Features",
|
||||
"formula": "Formula",
|
||||
"general": "General",
|
||||
"gm": "GM",
|
||||
"guaranteedCriticalSuccess": "Guaranteed Critical Success",
|
||||
"healing": "Healing",
|
||||
"healingRoll": "Healing Roll",
|
||||
"hit": {
|
||||
|
|
@ -2292,7 +2194,6 @@
|
|||
"single": "Player",
|
||||
"plurial": "Players"
|
||||
},
|
||||
"portrait": "Portrait",
|
||||
"proficiency": "Proficiency",
|
||||
"quantity": "Quantity",
|
||||
"range": "Range",
|
||||
|
|
@ -2309,7 +2210,6 @@
|
|||
"rollWith": "{roll} Roll",
|
||||
"save": "Save",
|
||||
"scalable": "Scalable",
|
||||
"scars": "Scars",
|
||||
"situationalBonus": "Situational Bonus",
|
||||
"spent": "Spent",
|
||||
"step": "Step",
|
||||
|
|
@ -2325,7 +2225,6 @@
|
|||
"single": "Target",
|
||||
"plural": "Targets"
|
||||
},
|
||||
"thingsAndThing": "{things} and {thing}",
|
||||
"title": "Title",
|
||||
"tokenSize": "Token Size",
|
||||
"total": "Total",
|
||||
|
|
@ -2364,8 +2263,7 @@
|
|||
},
|
||||
"Ancestry": {
|
||||
"primaryFeature": "Primary Feature",
|
||||
"secondaryFeature": "Secondary Feature",
|
||||
"featuresLabel": "Ancestry Features"
|
||||
"secondaryFeature": "Secondary Feature"
|
||||
},
|
||||
"Armor": {
|
||||
"baseScore": "Base Score",
|
||||
|
|
@ -2418,12 +2316,7 @@
|
|||
"evolvedImagePlaceholder": "The image for the form selected for evolution will be used"
|
||||
},
|
||||
"Class": {
|
||||
"startingEvasionScore": "Starting Evasion Score",
|
||||
"startingHitPoints": "Starting Hit Points",
|
||||
"classItems": "Class Items",
|
||||
"hopeFeatureLabel": "{class}'s Hope Feature",
|
||||
"hopeFeatures": "Hope Features",
|
||||
"classFeature": "Class Feature",
|
||||
"classFeatures": "Class Features",
|
||||
"guide": {
|
||||
"suggestedEquipment": "Suggested Equipments",
|
||||
|
|
@ -2436,9 +2329,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Community": {
|
||||
"featuresLabel": "Community Feature"
|
||||
},
|
||||
"Consumable": {
|
||||
"consumeOnUse": "Consume On Use",
|
||||
"destroyOnEmpty": "Destroy On Empty"
|
||||
|
|
@ -2454,11 +2344,7 @@
|
|||
"masteryTitle": "Mastery"
|
||||
},
|
||||
"Subclass": {
|
||||
"spellcastingTrait": "Spellcasting Trait",
|
||||
"spellcastTrait": "Spellcast Trait",
|
||||
"foundationFeatures": "Foundation Features",
|
||||
"specializationFeature": "Specialization Feature",
|
||||
"masteryFeature": "Mastery Feature"
|
||||
"spellcastingTrait": "Spellcasting Trait"
|
||||
},
|
||||
"Weapon": {
|
||||
"weaponType": "Weapon Type",
|
||||
|
|
@ -2466,12 +2352,6 @@
|
|||
"secondaryWeapon": "Secondary Weapon"
|
||||
}
|
||||
},
|
||||
"ROLLTABLES": {
|
||||
"FIELDS": {
|
||||
"formulaName": { "label": "Formula Name" }
|
||||
},
|
||||
"formula": "Formula"
|
||||
},
|
||||
"SETTINGS": {
|
||||
"Appearance": {
|
||||
"FIELDS": {
|
||||
|
|
@ -2487,14 +2367,6 @@
|
|||
"hideAttribution": {
|
||||
"label": "Hide Attribution"
|
||||
},
|
||||
"showTokenDistance": {
|
||||
"label": "Show Token Distance on Hover",
|
||||
"choices": {
|
||||
"always": "Always",
|
||||
"encounters": "Encounters",
|
||||
"never": "Never"
|
||||
}
|
||||
},
|
||||
"expandedTitle": "Auto-expand Descriptions",
|
||||
"extendCharacterDescriptions": {
|
||||
"label": "Characters"
|
||||
|
|
@ -2546,21 +2418,13 @@
|
|||
"overlay": { "label": "Overlay Effect" },
|
||||
"characterDefault": { "label": "Character Default Defeated Status" },
|
||||
"adversaryDefault": { "label": "Adversary Default Defeated Status" },
|
||||
"companionDefault": { "label": "Companion Default Defeated Status" },
|
||||
"deathMove": { "label": "Death Move" },
|
||||
"dead": { "label": "Dead" },
|
||||
"defeated": { "label": "Defeated" },
|
||||
"unconscious": { "label": "Unconscious" }
|
||||
"companionDefault": { "label": "Companion Default Defeated Status" }
|
||||
},
|
||||
"hopeFear": {
|
||||
"label": "Hope & Fear",
|
||||
"gm": { "label": "GM" },
|
||||
"players": { "label": "Players" }
|
||||
},
|
||||
"vulnerableAutomation": {
|
||||
"label": "Vulnerable Automation",
|
||||
"hint": "Automatically apply the Vulnerable condition when a actor reaches max stress"
|
||||
},
|
||||
"countdownAutomation": {
|
||||
"label": "Countdown Automation",
|
||||
"hint": "Automatically progress countdowns based on their progression settings"
|
||||
|
|
@ -2587,6 +2451,10 @@
|
|||
"label": "Show Resource Change Scrolltexts",
|
||||
"hint": "When a character is damaged, uses armor etc, a scrolling text will briefly appear by the token to signify this."
|
||||
},
|
||||
"playerCanEditSheet": {
|
||||
"label": "Players Can Manually Edit Character Settings",
|
||||
"hint": "Players are allowed to access the manual Character Settings and change their statistics beyond the rules."
|
||||
},
|
||||
"roll": {
|
||||
"roll": {
|
||||
"label": "Roll",
|
||||
|
|
@ -2641,11 +2509,8 @@
|
|||
"resetMovesTitle": "Reset {type} Downtime Moves",
|
||||
"resetItemFeaturesTitle": "Reset {type}",
|
||||
"resetMovesText": "Are you sure you want to reset?",
|
||||
"deleteItemTitle": "Delete Homebrew Item",
|
||||
"deleteItemText": "Are you sure you want to delete the item?",
|
||||
"FIELDS": {
|
||||
"maxFear": { "label": "Max Fear" },
|
||||
"maxHope": { "label": "Max Hope" },
|
||||
"traitArray": { "label": "Initial Trait Modifiers" },
|
||||
"maxLoadout": {
|
||||
"label": "Max Cards in Loadout",
|
||||
|
|
@ -2793,16 +2658,7 @@
|
|||
"currentTarget": "Current"
|
||||
},
|
||||
"deathMove": {
|
||||
"title": "Death Move",
|
||||
"gainScar": "You gained a scar.",
|
||||
"avoidScar": "You have avoided a new scar.",
|
||||
"journeysEnd": "You have {scars} Scars and have crossed out your last Hope slot. Your character's journey ends.",
|
||||
"riskItAllCritical": "Critical Rolled, clearing all marked Stress and Hit Points.",
|
||||
"riskItAllFailure": "The fear die rolled higher. You have crossed through the veil of death.",
|
||||
"blazeOfGlory": "Blaze of Glory Effect Added!",
|
||||
"riskItAllDialogButton": "Clear Stress And Hit Points.",
|
||||
"riskItAllSuccessWithEnoughHope": "The Hope value is more than the marked Stress and Hit Points. Both are cleared fully.",
|
||||
"riskItAllSuccess": "The hope die rolled higher, clear up to {hope} Stress And Hit Points."
|
||||
"title": "Death Move"
|
||||
},
|
||||
"dicePool": {
|
||||
"title": "Dice Pool"
|
||||
|
|
@ -2811,7 +2667,7 @@
|
|||
"title": "Domain Card"
|
||||
},
|
||||
"dualityRoll": {
|
||||
"abilityCheckTitle": "{ability} Roll"
|
||||
"abilityCheckTitle": "{ability} Check"
|
||||
},
|
||||
"effectSummary": {
|
||||
"title": "Effects Applied",
|
||||
|
|
@ -2826,7 +2682,7 @@
|
|||
"selectLeader": "Select a Leader",
|
||||
"selectMember": "Select a Member",
|
||||
"rerollTitle": "Reroll Group Roll",
|
||||
"rerollContent": "Are you sure you want to reroll your {trait} roll?",
|
||||
"rerollContent": "Are you sure you want to reroll your {trait} check?",
|
||||
"rerollTooltip": "Reroll",
|
||||
"wholePartySelected": "The whole party is selected"
|
||||
},
|
||||
|
|
@ -2874,7 +2730,6 @@
|
|||
"ItemBrowser": {
|
||||
"title": "Daggerheart Compendium Browser",
|
||||
"hint": "Select a Folder in sidebar to start browsing through the compendium",
|
||||
"browserSettings": "Browser Settings",
|
||||
"searchPlaceholder": "Search...",
|
||||
"columnName": "Name",
|
||||
"tooltipFilters": "Filters",
|
||||
|
|
@ -2925,9 +2780,7 @@
|
|||
"noAssignedPlayerCharacter": "You have no assigned character.",
|
||||
"noSelectedToken": "You have no selected token",
|
||||
"onlyUseableByPC": "This can only be used with a PC token",
|
||||
"dualityParsing": "Duality roll not properly formatted",
|
||||
"fateParsing": "Fate roll not properly formatted",
|
||||
"fateTypeParsing": "Fate roll not properly formatted, bad fate type. Valid types are 'Hope' and 'Fear'",
|
||||
"dualityParsing": "Duality roll not properly formated",
|
||||
"attributeFaulty": "The supplied Attribute doesn't exist",
|
||||
"domainCardWrongDomain": "You don't have access to that Domain",
|
||||
"domainCardToHighLevel": "The Domain Card is too high level to be selected",
|
||||
|
|
@ -2991,18 +2844,14 @@
|
|||
"documentIsMissing": "The {documentType} is missing from the world.",
|
||||
"tokenActorMissing": "{name} is missing an Actor",
|
||||
"tokenActorsMissing": "[{names}] missing Actors",
|
||||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
||||
"knowTheTide": "Know The Tide gained a token",
|
||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}"
|
||||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used"
|
||||
},
|
||||
"Sidebar": {
|
||||
"actorDirectory": {
|
||||
"tier": "Tier {tier} {type}",
|
||||
"character": "Level {level} Character",
|
||||
"companion": "Level {level} - {partner}",
|
||||
"companionNoPartner": "No Partner",
|
||||
"duplicateToNewTier": "Duplicate to New Tier",
|
||||
"pickTierTitle": "Pick a new tier for this adversary"
|
||||
"companionNoPartner": "No Partner"
|
||||
},
|
||||
"daggerheartMenu": {
|
||||
"title": "Daggerheart Menu",
|
||||
|
|
@ -3034,7 +2883,7 @@
|
|||
"rulesOn": "Rules On",
|
||||
"rulesOff": "Rules Off",
|
||||
"remainingUses": "Uses refresh on {type}",
|
||||
"rightClickExtend": "Right-Click to extend",
|
||||
"rightClickExtand": "Right-Click to extand",
|
||||
"companionPartnerLevelBlock": "The companion needs an assigned partner to level up.",
|
||||
"configureAttribution": "Configure Attribution",
|
||||
"deleteItem": "Delete Item",
|
||||
|
|
|
|||
|
|
@ -1,143 +0,0 @@
|
|||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class CompendiumBrowserSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.browserSettings = game.settings
|
||||
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings)
|
||||
.toObject();
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'div',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'compendium-brower-settings'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-book',
|
||||
title: 'DAGGERHEART.APPLICATIONS.CompendiumBrowserSettings.title'
|
||||
},
|
||||
position: {
|
||||
width: 500
|
||||
},
|
||||
actions: {
|
||||
toggleSource: CompendiumBrowserSettings.#toggleSource,
|
||||
finish: CompendiumBrowserSettings.#finish
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
packs: {
|
||||
id: 'packs',
|
||||
template: 'systems/daggerheart/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs'
|
||||
},
|
||||
footer: { template: 'systems/daggerheart/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs' }
|
||||
};
|
||||
|
||||
static #browserPackTypes = ['Actor', 'Item'];
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
for (const element of htmlElement.querySelectorAll('.pack-checkbox'))
|
||||
element.addEventListener('change', this.toggleTypedPack.bind(this));
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
|
||||
const excludedSourceData = this.browserSettings.excludedSources;
|
||||
const excludedPackData = this.browserSettings.excludedPacks;
|
||||
context.typePackCollections = game.packs.reduce((acc, pack) => {
|
||||
const { type, label, packageType, packageName: basePackageName, id } = pack.metadata;
|
||||
if (!CompendiumBrowserSettings.#browserPackTypes.includes(type)) return acc;
|
||||
|
||||
const isWorldPack = packageType === 'world';
|
||||
const packageName = isWorldPack ? 'world' : basePackageName;
|
||||
const sourceChecked =
|
||||
!excludedSourceData[packageName] ||
|
||||
!excludedSourceData[packageName].excludedDocumentTypes.includes(type);
|
||||
|
||||
const sourceLabel =
|
||||
game.modules.get(packageName)?.title ??
|
||||
(isWorldPack
|
||||
? game.i18n.localize('DAGGERHEART.APPLICATIONS.CompendiumBrowserSettings.worldCompendiums')
|
||||
: game.system.title);
|
||||
if (!acc[type]) acc[type] = { label: game.i18n.localize(`DOCUMENT.${type}s`), sources: {} };
|
||||
if (!acc[type].sources[packageName])
|
||||
acc[type].sources[packageName] = { label: sourceLabel, checked: sourceChecked, packs: [] };
|
||||
|
||||
const checked = !excludedPackData[id] || !excludedPackData[id].excludedDocumentTypes.includes(type);
|
||||
|
||||
acc[type].sources[packageName].packs.push({
|
||||
pack: id,
|
||||
type,
|
||||
label: id === game.system.id ? game.system.title : game.i18n.localize(label),
|
||||
checked: checked
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static #toggleSource(event, button) {
|
||||
event.stopPropagation();
|
||||
|
||||
const { type, source } = button.dataset;
|
||||
const currentlyExcluded = this.browserSettings.excludedSources[source]
|
||||
? this.browserSettings.excludedSources[source].excludedDocumentTypes.includes(type)
|
||||
: false;
|
||||
|
||||
if (!this.browserSettings.excludedSources[source])
|
||||
this.browserSettings.excludedSources[source] = { excludedDocumentTypes: [] };
|
||||
this.browserSettings.excludedSources[source].excludedDocumentTypes = currentlyExcluded
|
||||
? this.browserSettings.excludedSources[source].excludedDocumentTypes.filter(x => x !== type)
|
||||
: [...(this.browserSettings.excludedSources[source]?.excludedDocumentTypes ?? []), type];
|
||||
|
||||
const toggleIcon = button.querySelector('a > i');
|
||||
toggleIcon.classList.toggle('fa-toggle-off');
|
||||
toggleIcon.classList.toggle('fa-toggle-on');
|
||||
button.closest('.source-container').querySelector('.checks-container').classList.toggle('collapsed');
|
||||
}
|
||||
|
||||
toggleTypedPack(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const { type, pack } = event.target.dataset;
|
||||
const currentlyExcluded = this.browserSettings.excludedPacks[pack]
|
||||
? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.includes(type)
|
||||
: false;
|
||||
|
||||
if (!this.browserSettings.excludedPacks[pack])
|
||||
this.browserSettings.excludedPacks[pack] = { excludedDocumentTypes: [] };
|
||||
this.browserSettings.excludedPacks[pack].excludedDocumentTypes = currentlyExcluded
|
||||
? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.filter(x => x !== type)
|
||||
: [...(this.browserSettings.excludedPacks[pack]?.excludedDocumentTypes ?? []), type];
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #finish() {
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings);
|
||||
await settings.updateSource(this.browserSettings);
|
||||
await game.settings.set(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings,
|
||||
settings.toObject()
|
||||
);
|
||||
|
||||
this.updated = true;
|
||||
this.close();
|
||||
}
|
||||
|
||||
static async configure() {
|
||||
return new Promise(resolve => {
|
||||
const app = new this();
|
||||
app.addEventListener('close', () => resolve(app.updated), { once: true });
|
||||
app.render({ force: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
export { default as AttributionDialog } from './attributionDialog.mjs';
|
||||
export { default as BeastformDialog } from './beastformDialog.mjs';
|
||||
export { default as CharacterResetDialog } from './characterResetDialog.mjs';
|
||||
export { default as d20RollDialog } from './d20RollDialog.mjs';
|
||||
export { default as DamageDialog } from './damageDialog.mjs';
|
||||
export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
|
||||
|
|
@ -15,5 +14,3 @@ export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
|||
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
|
||||
export { default as GroupRollDialog } from './group-roll-dialog.mjs';
|
||||
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
|
||||
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
|
||||
export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs';
|
||||
|
|
|
|||
|
|
@ -54,11 +54,7 @@ export default class AttributionDialog extends HandlebarsApplicationMixin(Applic
|
|||
const after = label.slice(matchIndex + search.length, label.length);
|
||||
|
||||
const element = document.createElement('li');
|
||||
element.innerHTML =
|
||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
||||
' ',
|
||||
' '
|
||||
);
|
||||
element.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||
if (item.hint) {
|
||||
element.dataset.tooltip = game.i18n.localize(item.hint);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,105 +0,0 @@
|
|||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class CharacterResetDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(actor, options = {}) {
|
||||
super(options);
|
||||
|
||||
this.actor = actor;
|
||||
this.data = {
|
||||
delete: {
|
||||
class: { keep: false, label: 'TYPES.Item.class' },
|
||||
subclass: { keep: false, label: 'TYPES.Item.subclass' },
|
||||
ancestry: { keep: false, label: 'TYPES.Item.ancestry' },
|
||||
community: { keep: false, label: 'TYPES.Item.community' }
|
||||
},
|
||||
optional: {
|
||||
portrait: { keep: true, label: 'DAGGERHEART.GENERAL.portrait' },
|
||||
name: { keep: true, label: 'Name' },
|
||||
biography: { keep: true, label: 'DAGGERHEART.GENERAL.Tabs.biography' },
|
||||
inventory: { keep: true, label: 'DAGGERHEART.GENERAL.inventory' }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'character-reset'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-arrow-rotate-left',
|
||||
title: 'DAGGERHEART.APPLICATIONS.CharacterReset.title'
|
||||
},
|
||||
actions: {
|
||||
finishSelection: this.#finishSelection
|
||||
},
|
||||
form: {
|
||||
handler: this.updateData,
|
||||
submitOnChange: true,
|
||||
submitOnClose: false
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
resourceDice: {
|
||||
id: 'resourceDice',
|
||||
template: 'systems/daggerheart/templates/dialogs/characterReset.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.data = this.data;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async updateData(event, _, formData) {
|
||||
const { data } = foundry.utils.expandObject(formData.object);
|
||||
|
||||
this.data = foundry.utils.mergeObject(this.data, data);
|
||||
this.render();
|
||||
}
|
||||
|
||||
static getUpdateData() {
|
||||
const update = {};
|
||||
if (!this.data.optional.portrait) update.if(!this.data.optional.biography);
|
||||
|
||||
if (!this.data.optional.inventory) return update;
|
||||
}
|
||||
|
||||
static async #finishSelection() {
|
||||
const update = {};
|
||||
if (!this.data.optional.name.keep) {
|
||||
const defaultName = game.system.api.documents.DhpActor.defaultName({ type: 'character' });
|
||||
foundry.utils.setProperty(update, 'name', defaultName);
|
||||
foundry.utils.setProperty(update, 'prototypeToken.name', defaultName);
|
||||
}
|
||||
|
||||
if (!this.data.optional.portrait.keep) {
|
||||
foundry.utils.setProperty(update, 'img', this.actor.schema.fields.img.initial(this.actor));
|
||||
foundry.utils.setProperty(update, 'prototypeToken.==texture', {});
|
||||
foundry.utils.setProperty(update, 'prototypeToken.==ring', {});
|
||||
}
|
||||
|
||||
if (this.data.optional.biography.keep)
|
||||
foundry.utils.setProperty(update, 'system.biography', this.actor.system.biography);
|
||||
|
||||
if (this.data.optional.inventory.keep) foundry.utils.setProperty(update, 'system.gold', this.actor.system.gold);
|
||||
|
||||
const { system, ...rest } = update;
|
||||
await this.actor.update({
|
||||
...rest,
|
||||
'==system': system ?? {}
|
||||
});
|
||||
|
||||
const inventoryItemTypes = ['weapon', 'armor', 'consumable', 'loot'];
|
||||
await this.actor.deleteEmbeddedDocuments(
|
||||
'Item',
|
||||
this.actor.items
|
||||
.filter(x => !inventoryItemTypes.includes(x.type) || !this.data.optional.inventory.keep)
|
||||
.map(x => x.id)
|
||||
);
|
||||
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -109,17 +109,11 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.roll = this.roll;
|
||||
context.rollType = this.roll?.constructor.name;
|
||||
context.rallyDie = this.roll.rallyChoices;
|
||||
|
||||
const actorExperiences = this.config.data?.system?.experiences || {};
|
||||
const companionExperiences = this.config.roll.companionRoll
|
||||
? (this.config.data?.companion?.system.experiences ?? {})
|
||||
: null;
|
||||
const experiences = companionExperiences ?? actorExperiences;
|
||||
const experiences = this.config.data?.system?.experiences || {};
|
||||
context.experiences = Object.keys(experiences).map(id => ({
|
||||
id,
|
||||
...experiences[id]
|
||||
}));
|
||||
|
||||
context.selectedExperiences = this.config.experiences;
|
||||
context.advantage = this.config.roll?.advantage;
|
||||
context.disadvantage = this.config.roll?.disadvantage;
|
||||
|
|
@ -129,7 +123,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.formula = this.roll.constructFormula(this.config);
|
||||
if (this.actor?.system?.traits) context.abilities = this.getTraitModifiers();
|
||||
|
||||
context.showReaction = !this.config.skips?.reaction && context.rollType === 'DualityRoll';
|
||||
context.showReaction = !this.config.roll?.type || context.rollType === 'DualityRoll';
|
||||
context.reactionOverride = this.reactionOverride;
|
||||
}
|
||||
|
||||
|
|
@ -165,10 +159,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
if (rest.hasOwnProperty('trait')) {
|
||||
this.config.roll.trait = rest.trait;
|
||||
if (!this.config.source.item)
|
||||
this.config.title = game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(abilities[this.config.roll.trait]?.label)
|
||||
});
|
||||
this.config.title = game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(abilities[this.config.roll.trait]?.label)
|
||||
});
|
||||
}
|
||||
this.config.extraFormula = rest.extraFormula;
|
||||
this.render();
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
import { enrichedFateRoll } from '../../enrichers/FateRollEnricher.mjs';
|
||||
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
||||
export default class DhpDeathMove extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(actor) {
|
||||
super({});
|
||||
|
||||
this.actor = actor;
|
||||
this.selectedMove = null;
|
||||
this.showRiskItAllButton = false;
|
||||
this.riskItAllButtonLabel = '';
|
||||
this.riskItAllHope = 0;
|
||||
}
|
||||
|
||||
get title() {
|
||||
|
|
@ -43,115 +38,6 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
|||
return context;
|
||||
}
|
||||
|
||||
async handleAvoidDeath(useAutomation) {
|
||||
const target = this.actor.uuid;
|
||||
const config = await enrichedFateRoll({
|
||||
target,
|
||||
title: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.avoidDeath.name'),
|
||||
label: `${game.i18n.localize('DAGGERHEART.GENERAL.hope')} ${game.i18n.localize('DAGGERHEART.GENERAL.fateRoll')}`,
|
||||
fateType: 'Hope'
|
||||
});
|
||||
|
||||
if (!config.roll.fate) return;
|
||||
if (!useAutomation) return '';
|
||||
|
||||
let returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.avoidScar');
|
||||
if (config.roll.fate.value <= this.actor.system.levelData.level.current) {
|
||||
const newScarAmount = this.actor.system.scars + 1;
|
||||
await this.actor.update({
|
||||
system: {
|
||||
scars: newScarAmount
|
||||
}
|
||||
});
|
||||
|
||||
if (newScarAmount >= this.actor.system.resources.hope.max) {
|
||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
||||
return game.i18n.format('DAGGERHEART.UI.Chat.deathMove.journeysEnd', { scars: newScarAmount });
|
||||
}
|
||||
|
||||
returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.gainScar');
|
||||
}
|
||||
|
||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.unconscious.id);
|
||||
return returnMessage;
|
||||
}
|
||||
|
||||
async handleRiskItAll(useAutomation) {
|
||||
const config = await enrichedDualityRoll({
|
||||
reaction: true,
|
||||
traitValue: null,
|
||||
target: this.actor,
|
||||
difficulty: null,
|
||||
title: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.riskItAll.name'),
|
||||
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityDice'),
|
||||
actionType: null,
|
||||
advantage: null,
|
||||
grantResources: false,
|
||||
customConfig: { skips: { resources: true, reaction: true } }
|
||||
});
|
||||
|
||||
if (!config.roll.result) return;
|
||||
if (!useAutomation) return '';
|
||||
|
||||
const clearAllStressAndHitpointsUpdates = [
|
||||
{ key: 'hitPoints', clear: true },
|
||||
{ key: 'stress', clear: true }
|
||||
];
|
||||
|
||||
let chatMessage = '';
|
||||
if (config.roll.isCritical) {
|
||||
config.resourceUpdates.addResources(clearAllStressAndHitpointsUpdates);
|
||||
chatMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.riskItAllCritical');
|
||||
}
|
||||
|
||||
if (config.roll.result.duality == 1) {
|
||||
if (
|
||||
config.roll.hope.value >=
|
||||
this.actor.system.resources.hitPoints.value + this.actor.system.resources.stress.value
|
||||
) {
|
||||
config.resourceUpdates.addResources(clearAllStressAndHitpointsUpdates);
|
||||
chatMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.riskItAllSuccessWithEnoughHope');
|
||||
} else {
|
||||
chatMessage = game.i18n.format('DAGGERHEART.UI.Chat.deathMove.riskItAllSuccess', {
|
||||
hope: config.roll.hope.value
|
||||
});
|
||||
this.showRiskItAllButton = true;
|
||||
this.riskItAllHope = config.roll.hope.value;
|
||||
this.riskItAllButtonLabel = game.i18n.format('DAGGERHEART.UI.Chat.deathMove.riskItAllDialogButton');
|
||||
}
|
||||
}
|
||||
|
||||
if (config.roll.result.duality == -1) {
|
||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
||||
chatMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.riskItAllFailure');
|
||||
}
|
||||
|
||||
await config.resourceUpdates.updateResources();
|
||||
return chatMessage;
|
||||
}
|
||||
|
||||
async handleBlazeOfGlory(useAutomation) {
|
||||
if (!useAutomation) return '';
|
||||
|
||||
this.actor.createEmbeddedDocuments('ActiveEffect', [
|
||||
{
|
||||
name: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name'),
|
||||
description: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.description'),
|
||||
img: CONFIG.DH.GENERAL.deathMoves.blazeOfGlory.img,
|
||||
changes: [
|
||||
{
|
||||
key: 'system.rules.roll.guaranteedCritical',
|
||||
mode: 2,
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
||||
return game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.blazeOfGlory');
|
||||
}
|
||||
|
||||
static selectMove(_, button) {
|
||||
const move = button.dataset.move;
|
||||
this.selectedMove = CONFIG.DH.GENERAL.deathMoves[move];
|
||||
|
|
@ -160,52 +46,23 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
|||
}
|
||||
|
||||
static async takeMove() {
|
||||
this.close();
|
||||
|
||||
let result = '';
|
||||
|
||||
const deathMoveAutomation = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Automation
|
||||
).deathMoveAutomation;
|
||||
if (CONFIG.DH.GENERAL.deathMoves.blazeOfGlory === this.selectedMove) {
|
||||
result = await this.handleBlazeOfGlory(deathMoveAutomation.blazeOfGlory);
|
||||
}
|
||||
|
||||
if (CONFIG.DH.GENERAL.deathMoves.avoidDeath === this.selectedMove) {
|
||||
result = await this.handleAvoidDeath(deathMoveAutomation.avoidDeath);
|
||||
}
|
||||
|
||||
if (CONFIG.DH.GENERAL.deathMoves.riskItAll === this.selectedMove) {
|
||||
result = await this.handleRiskItAll(deathMoveAutomation.riskItAll);
|
||||
}
|
||||
|
||||
if (result === undefined) return;
|
||||
|
||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
||||
.expandRollMessage?.desc;
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
|
||||
const msg = {
|
||||
user: game.user.id,
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/deathMove.hbs',
|
||||
{
|
||||
player: this.actor.name,
|
||||
actor: this.actor,
|
||||
actorId: this.actor._id,
|
||||
actor: { name: this.actor.name, img: this.actor.img },
|
||||
author: game.users.get(game.user.id),
|
||||
title: game.i18n.localize(this.selectedMove.name),
|
||||
img: this.selectedMove.img,
|
||||
description: game.i18n.localize(this.selectedMove.description),
|
||||
result: result,
|
||||
open: autoExpandDescription ? 'open' : '',
|
||||
showRiskItAllButton: this.showRiskItAllButton,
|
||||
riskItAllButtonLabel: this.riskItAllButtonLabel,
|
||||
riskItAllHope: this.riskItAllHope
|
||||
description: game.i18n.localize(this.selectedMove.description)
|
||||
}
|
||||
),
|
||||
title: game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.title'),
|
||||
title: game.i18n.localize(
|
||||
'DAGGERHEART.UI.Chat.deathMove.title'
|
||||
),
|
||||
speaker: cls.getSpeaker(),
|
||||
flags: {
|
||||
daggerheart: {
|
||||
|
|
@ -215,5 +72,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
|||
};
|
||||
|
||||
cls.create(msg);
|
||||
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,9 +196,6 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
|
||||
.filter(x => x.uuid !== this.actor.uuid);
|
||||
|
||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
||||
.expandRollMessage?.desc;
|
||||
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
const msg = {
|
||||
user: game.user.id,
|
||||
|
|
@ -219,8 +216,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
actor: { name: this.actor.name, img: this.actor.img },
|
||||
moves: moves,
|
||||
characters: characters,
|
||||
selfId: this.actor.uuid,
|
||||
open: autoExpandDescription ? 'open' : ''
|
||||
selfId: this.actor.uuid
|
||||
}
|
||||
),
|
||||
flags: {
|
||||
|
|
|
|||
|
|
@ -70,11 +70,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
element.appendChild(img);
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.innerHTML =
|
||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
||||
' ',
|
||||
' '
|
||||
);
|
||||
label.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||
element.appendChild(label);
|
||||
|
||||
return element;
|
||||
|
|
@ -123,11 +119,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
element.appendChild(img);
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.innerHTML =
|
||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
||||
' ',
|
||||
' '
|
||||
);
|
||||
label.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||
element.appendChild(label);
|
||||
|
||||
return element;
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
export default class RiskItAllDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(actor, resourceValue) {
|
||||
super({});
|
||||
|
||||
this.actor = actor;
|
||||
this.resourceValue = resourceValue;
|
||||
this.choices = {
|
||||
hitPoints: 0,
|
||||
stress: 0
|
||||
};
|
||||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.format('DAGGERHEART.APPLICATIONS.RiskItAllDialog.title', { name: this.actor.name });
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart', 'dh-style', 'dialog', 'views', 'risk-it-all'],
|
||||
position: { width: 280, height: 'auto' },
|
||||
window: { icon: 'fa-solid fa-dice fa-xl' },
|
||||
actions: {
|
||||
finish: RiskItAllDialog.#finish
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
application: {
|
||||
id: 'risk-it-all',
|
||||
template: 'systems/daggerheart/templates/dialogs/riskItAllDialog.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
for (const input of htmlElement.querySelectorAll('.resource-container input'))
|
||||
input.addEventListener('change', this.updateChoice.bind(this));
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.resourceValue = this.resourceValue;
|
||||
context.maxHitPointsValue = Math.min(this.resourceValue, this.actor.system.resources.hitPoints.max);
|
||||
context.maxStressValue = Math.min(this.resourceValue, this.actor.system.resources.stress.max);
|
||||
context.remainingResource = this.resourceValue - this.choices.hitPoints - this.choices.stress;
|
||||
context.unfinished = context.remainingResource !== 0;
|
||||
|
||||
context.choices = this.choices;
|
||||
context.final = {
|
||||
hitPoints: {
|
||||
value: this.actor.system.resources.hitPoints.value - this.choices.hitPoints,
|
||||
max: this.actor.system.resources.hitPoints.max
|
||||
},
|
||||
stress: {
|
||||
value: this.actor.system.resources.stress.value - this.choices.stress,
|
||||
max: this.actor.system.resources.stress.max
|
||||
}
|
||||
};
|
||||
|
||||
context;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
updateChoice(event) {
|
||||
let value = Number.parseInt(event.target.value);
|
||||
const choiceKey = event.target.dataset.choice;
|
||||
const actorValue = this.actor.system.resources[choiceKey].value;
|
||||
const remaining = this.resourceValue - this.choices.hitPoints - this.choices.stress;
|
||||
const changeAmount = value - this.choices[choiceKey];
|
||||
|
||||
/* If trying to increase beyond remaining resource points, just increase to max available */
|
||||
if (remaining - changeAmount < 0) value = this.choices[choiceKey] + remaining;
|
||||
else if (actorValue - value < 0) value = actorValue;
|
||||
|
||||
this.choices[choiceKey] = value;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #finish() {
|
||||
const resourceUpdate = Object.keys(this.choices).reduce((acc, resourceKey) => {
|
||||
const value = this.actor.system.resources[resourceKey].value - this.choices[resourceKey];
|
||||
acc[resourceKey] = { value };
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
await this.actor.update({
|
||||
'system.resources': resourceUpdate
|
||||
});
|
||||
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,7 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
classes: ['daggerheart'],
|
||||
actions: {
|
||||
combat: DHTokenHUD.#onToggleCombat,
|
||||
togglePartyTokens: DHTokenHUD.#togglePartyTokens,
|
||||
toggleCompanions: DHTokenHUD.#toggleCompanions
|
||||
togglePartyTokens: DHTokenHUD.#togglePartyTokens
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -27,7 +26,7 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
context.partyOnCanvas =
|
||||
this.actor.type === 'party' &&
|
||||
this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0);
|
||||
context.icons.toggleClowncar = 'systems/daggerheart/assets/icons/arrow-dunk.png';
|
||||
context.icons.toggleParty = 'systems/daggerheart/assets/icons/arrow-dunk.png';
|
||||
context.actorType = this.actor.type;
|
||||
context.usesEffects = this.actor.type !== 'party';
|
||||
context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type)
|
||||
|
|
@ -57,9 +56,6 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
}, {})
|
||||
: null;
|
||||
|
||||
context.hasCompanion = this.actor.system.companion;
|
||||
context.companionOnCanvas = context.hasCompanion && this.actor.system.companion.getActiveTokens().length > 0;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -105,24 +101,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
: 'DAGGERHEART.APPLICATIONS.HUD.tokenHUD.depositPartyTokens'
|
||||
);
|
||||
|
||||
await this.toggleClowncar(this.actor.system.partyMembers);
|
||||
}
|
||||
|
||||
static async #toggleCompanions(_, button) {
|
||||
const icon = button.querySelector('img');
|
||||
icon.classList.toggle('flipped');
|
||||
button.dataset.tooltip = game.i18n.localize(
|
||||
icon.classList.contains('flipped')
|
||||
? 'DAGGERHEART.APPLICATIONS.HUD.tokenHUD.retrieveCompanionTokens'
|
||||
: 'DAGGERHEART.APPLICATIONS.HUD.tokenHUD.depositCompanionTokens'
|
||||
);
|
||||
|
||||
await this.toggleClowncar([this.actor.system.companion]);
|
||||
}
|
||||
|
||||
async toggleClowncar(actors) {
|
||||
const animationDuration = 500;
|
||||
const activeTokens = actors.flatMap(member => member.getActiveTokens());
|
||||
const activeTokens = this.actor.system.partyMembers.flatMap(member => member.getActiveTokens());
|
||||
const { x: actorX, y: actorY } = this.document;
|
||||
if (activeTokens.length > 0) {
|
||||
for (let token of activeTokens) {
|
||||
|
|
@ -134,15 +114,14 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
}
|
||||
} else {
|
||||
const activeScene = game.scenes.find(x => x.id === game.user.viewedScene);
|
||||
const tokenData = [];
|
||||
for (let member of actors) {
|
||||
const partyTokenData = [];
|
||||
for (let member of this.actor.system.partyMembers) {
|
||||
const data = await member.getTokenDocument();
|
||||
tokenData.push(data.toObject());
|
||||
partyTokenData.push(data.toObject());
|
||||
}
|
||||
|
||||
const newTokens = await activeScene.createEmbeddedDocuments(
|
||||
'Token',
|
||||
tokenData.map(tokenData => ({
|
||||
partyTokenData.map(tokenData => ({
|
||||
...tokenData,
|
||||
alpha: 0,
|
||||
x: actorX,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import BaseLevelUp from './levelup.mjs';
|
||||
import { defaultCompanionTier, LevelOptionType } from '../../data/levelTier.mjs';
|
||||
import { DhCompanionLevelup as DhLevelup } from '../../data/companionLevelup.mjs';
|
||||
import { DhLevelup } from '../../data/levelup.mjs';
|
||||
import { diceTypes, range } from '../../config/generalConfig.mjs';
|
||||
|
||||
export default class DhCompanionLevelUp extends BaseLevelUp {
|
||||
|
|
@ -9,9 +9,7 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
|
|||
|
||||
this.levelTiers = this.addBonusChoices(defaultCompanionTier);
|
||||
const playerLevelupData = actor.system.levelData;
|
||||
this.levelup = new DhLevelup(
|
||||
DhLevelup.initializeData(this.levelTiers, playerLevelupData, actor.system.levelupChoicesLeft)
|
||||
);
|
||||
this.levelup = new DhLevelup(DhLevelup.initializeData(this.levelTiers, playerLevelupData));
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
|
|
|
|||
|
|
@ -70,10 +70,7 @@ export default class DhlevelUpViewMode extends HandlebarsApplicationMixin(Applic
|
|||
return checkbox;
|
||||
});
|
||||
|
||||
let label =
|
||||
optionKey === 'domainCard'
|
||||
? game.i18n.format(option.label, { maxLevel: tier.levels.end })
|
||||
: game.i18n.localize(option.label);
|
||||
let label = game.i18n.localize(option.label);
|
||||
return {
|
||||
label: label,
|
||||
checkboxGroups: chunkify(checkboxes, option.minCost, chunkedBoxes => {
|
||||
|
|
|
|||
|
|
@ -103,11 +103,7 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
|
|||
|
||||
/** @override */
|
||||
async _processSubmitData(event, form, submitData, options) {
|
||||
if (!submitData.flags) submitData.flags = {};
|
||||
submitData.flags.daggerheart = foundry.utils.mergeObject(
|
||||
this.daggerheartFlag.toObject(),
|
||||
submitData.flags.daggerheart
|
||||
);
|
||||
submitData.flags.daggerheart = this.daggerheartFlag.toObject();
|
||||
submitData.flags.daggerheart.sceneEnvironments = submitData.flags.daggerheart.sceneEnvironments.filter(x =>
|
||||
foundry.utils.fromUuidSync(x)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
|
|||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||
header: { template: 'systems/daggerheart/templates/settings/automation-settings/header.hbs' },
|
||||
general: { template: 'systems/daggerheart/templates/settings/automation-settings/general.hbs' },
|
||||
rules: { template: 'systems/daggerheart/templates/settings/automation-settings/deathMoves.hbs' },
|
||||
rules: { template: 'systems/daggerheart/templates/settings/automation-settings/rules.hbs' },
|
||||
roll: { template: 'systems/daggerheart/templates/settings/automation-settings/roll.hbs' },
|
||||
footer: { template: 'systems/daggerheart/templates/settings/automation-settings/footer.hbs' }
|
||||
};
|
||||
|
|
@ -42,7 +42,7 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
|
|||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
main: {
|
||||
tabs: [{ id: 'general' }, { id: 'deathMoves' }, { id: 'roll' }],
|
||||
tabs: [{ id: 'general' }, { id: 'rules' }, { id: 'roll' }],
|
||||
initial: 'general',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,12 +103,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
? { id: this.selected.adversaryType, ...this.settings.adversaryTypes[this.selected.adversaryType] }
|
||||
: null;
|
||||
break;
|
||||
case 'downtime':
|
||||
context.restOptions = {
|
||||
shortRest: CONFIG.DH.GENERAL.defaultRestOptions.shortRest(),
|
||||
longRest: CONFIG.DH.GENERAL.defaultRestOptions.longRest()
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return context;
|
||||
|
|
@ -171,8 +165,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
|
||||
img: 'icons/magic/life/cross-worn-green.webp',
|
||||
description: '',
|
||||
actions: [],
|
||||
effects: []
|
||||
actions: []
|
||||
}
|
||||
});
|
||||
} else if (['armorFeatures', 'weaponFeatures'].includes(type)) {
|
||||
|
|
@ -187,7 +180,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
});
|
||||
}
|
||||
|
||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
@ -228,28 +220,16 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
}
|
||||
});
|
||||
|
||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async removeItem(_, target) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.localize(`DAGGERHEART.SETTINGS.Homebrew.deleteItemTitle`)
|
||||
},
|
||||
content: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.deleteItemText')
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
const { type, id } = target.dataset;
|
||||
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
||||
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
|
||||
await this.settings.updateSource({
|
||||
[`${path}.-=${id}`]: null
|
||||
});
|
||||
|
||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -125,7 +125,6 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options, 'action');
|
||||
context.source = this.action.toObject(true);
|
||||
context.action = this.action;
|
||||
|
||||
context.summons = [];
|
||||
for (const summon of context.source.summon ?? []) {
|
||||
|
|
@ -314,7 +313,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
const index = Number.parseInt(button.dataset.index);
|
||||
const toggle = (element, codeMirror) => {
|
||||
codeMirror.classList.toggle('revealed');
|
||||
const button = element.querySelector('.expand-trigger > i');
|
||||
const button = element.querySelector('a > i');
|
||||
button.classList.toggle('fa-angle-up');
|
||||
button.classList.toggle('fa-angle-down');
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,22 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.changeChoices = DhActiveEffectConfig.getChangeChoices();
|
||||
const ignoredActorKeys = ['config', 'DhEnvironment'];
|
||||
this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => {
|
||||
if (!ignoredActorKeys.includes(key)) {
|
||||
const model = game.system.api.models.actors[key];
|
||||
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 choices = CONFIG.Token.documentClass
|
||||
.getTrackedAttributeChoices(attributes, model)
|
||||
.map(x => ({ ...x, group: group }));
|
||||
acc.push(...choices);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
|
|
@ -35,69 +50,6 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get ChangeChoices for the changes autocomplete. Static for use in this class aswell as in settings-active-effect-config.mjs
|
||||
* @returns {ChangeChoice { value: string, label: string, hint: string, group: string }[]}
|
||||
*/
|
||||
static getChangeChoices() {
|
||||
const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty'];
|
||||
|
||||
const getAllLeaves = (root, group, parentPath = '') => {
|
||||
const leaves = [];
|
||||
const rootKey = `${parentPath ? `${parentPath}.` : ''}${root.name}`;
|
||||
for (const field of Object.values(root.fields)) {
|
||||
if (field instanceof foundry.data.fields.SchemaField)
|
||||
leaves.push(...getAllLeaves(field, group, rootKey));
|
||||
else
|
||||
leaves.push({
|
||||
value: `${rootKey}.${field.name}`,
|
||||
label: game.i18n.localize(field.label),
|
||||
hint: game.i18n.localize(field.hint),
|
||||
group
|
||||
});
|
||||
}
|
||||
|
||||
return leaves;
|
||||
};
|
||||
return Object.keys(game.system.api.models.actors).reduce((acc, key) => {
|
||||
if (ignoredActorKeys.includes(key)) return acc;
|
||||
|
||||
const model = game.system.api.models.actors[key];
|
||||
const group = game.i18n.localize(model.metadata.label);
|
||||
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model.metadata.type);
|
||||
|
||||
const getTranslations = path => {
|
||||
if (path === 'resources.hope.max')
|
||||
return {
|
||||
label: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.FIELDS.maxHope.label'),
|
||||
hint: ''
|
||||
};
|
||||
|
||||
const field = model.schema.getField(path);
|
||||
return {
|
||||
label: field ? game.i18n.localize(field.label) : path,
|
||||
hint: field ? game.i18n.localize(field.hint) : ''
|
||||
};
|
||||
};
|
||||
|
||||
const bars = attributes.bar.flatMap(x => {
|
||||
const joined = `${x.join('.')}.max`;
|
||||
return { value: joined, ...getTranslations(joined), group };
|
||||
});
|
||||
const values = attributes.value.flatMap(x => {
|
||||
const joined = x.join('.');
|
||||
return { value: joined, ...getTranslations(joined), group };
|
||||
});
|
||||
|
||||
const bonuses = getAllLeaves(model.schema.fields.bonuses, group);
|
||||
const rules = getAllLeaves(model.schema.fields.rules, group);
|
||||
|
||||
acc.push(...bars, ...values, ...rules, ...bonuses);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
const changeChoices = this.changeChoices;
|
||||
|
|
@ -116,18 +68,14 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
},
|
||||
render: function (item, search) {
|
||||
const label = game.i18n.localize(item.label);
|
||||
const matchIndex = label.toLowerCase().indexOf(search.toLowerCase());
|
||||
const matchIndex = label.toLowerCase().indexOf(search);
|
||||
|
||||
const beforeText = label.slice(0, matchIndex);
|
||||
const matchText = label.slice(matchIndex, matchIndex + search.length);
|
||||
const after = label.slice(matchIndex + search.length, label.length);
|
||||
|
||||
const element = document.createElement('li');
|
||||
element.innerHTML =
|
||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
||||
' ',
|
||||
' '
|
||||
);
|
||||
element.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||
if (item.hint) {
|
||||
element.dataset.tooltip = game.i18n.localize(item.hint);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,19 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
|||
super({});
|
||||
|
||||
this.effect = foundry.utils.deepClone(effect);
|
||||
this.changeChoices = game.system.api.applications.sheetConfigs.ActiveEffectConfig.getChangeChoices();
|
||||
const ignoredActorKeys = ['config', 'DhEnvironment'];
|
||||
this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => {
|
||||
if (!ignoredActorKeys.includes(key)) {
|
||||
const model = game.system.api.models.actors[key];
|
||||
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
|
||||
const group = game.i18n.localize(model.metadata.label);
|
||||
const choices = CONFIG.Token.documentClass
|
||||
.getTrackedAttributeChoices(attributes, model)
|
||||
.map(x => ({ ...x, group: group }));
|
||||
acc.push(...choices);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
|
|
@ -91,11 +103,7 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
|||
const after = label.slice(matchIndex + search.length, label.length);
|
||||
|
||||
const element = document.createElement('li');
|
||||
element.innerHTML =
|
||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
||||
' ',
|
||||
' '
|
||||
);
|
||||
element.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||
if (item.hint) {
|
||||
element.dataset.tooltip = game.i18n.localize(item.hint);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,11 +73,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
return context;
|
||||
}
|
||||
|
||||
static async updateData(_event, _element, formData) {
|
||||
static async updateData(event, element, formData) {
|
||||
const data = foundry.utils.expandObject(formData.object);
|
||||
await this.updateMove({
|
||||
[`${this.movePath}`]: data
|
||||
});
|
||||
foundry.utils.mergeObject(this.move, data);
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
|
@ -137,7 +135,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
}
|
||||
);
|
||||
|
||||
await this.updateMove({ [`${this.actionsPath}.${action.id}`]: action });
|
||||
await this.settings.updateSource({ [`${this.actionsPath}.${action.id}`]: action });
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
@ -150,12 +150,13 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect);
|
||||
if (!updatedEffect) return;
|
||||
|
||||
await this.updateMove({
|
||||
await this.settings.updateSource({
|
||||
[`${this.movePath}.effects`]: this.move.effects.reduce((acc, effect, index) => {
|
||||
acc.push(index === effectIndex ? { ...updatedEffect, id: effect.id } : effect);
|
||||
return acc;
|
||||
}, [])
|
||||
});
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
this.render();
|
||||
} else {
|
||||
const action = this.move.actions.get(id);
|
||||
|
|
@ -170,13 +171,13 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
: existingEffectIndex === -1
|
||||
? [...currentEffects, effectData]
|
||||
: currentEffects.with(existingEffectIndex, effectData);
|
||||
await this.updateMove({
|
||||
await this.settings.updateSource({
|
||||
[`${this.movePath}.effects`]: updatedEffects
|
||||
});
|
||||
}
|
||||
|
||||
await this.updateMove({ [`${this.actionsPath}.${id}`]: updatedMove });
|
||||
|
||||
await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove });
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
this.render();
|
||||
return updatedEffects;
|
||||
}).render(true);
|
||||
|
|
@ -198,34 +199,31 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
});
|
||||
}
|
||||
}
|
||||
await this.updateMove({
|
||||
await this.settings.updateSource({
|
||||
[this.movePath]: {
|
||||
effects: move.effects.filter(x => x.id !== id),
|
||||
actions: move.actions
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await this.updateMove({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
||||
await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
||||
}
|
||||
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async addEffect() {
|
||||
static async addEffect(_, target) {
|
||||
const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`);
|
||||
|
||||
await this.updateMove({
|
||||
await this.settings.updateSource({
|
||||
[`${this.movePath}.effects`]: [
|
||||
...currentEffects,
|
||||
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
|
||||
]
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
|
||||
async updateMove(update) {
|
||||
await this.settings.updateSource(update);
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
this.render();
|
||||
}
|
||||
|
||||
static resetMoves() {}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
export * as actors from './actors/_module.mjs';
|
||||
export * as api from './api/_modules.mjs';
|
||||
export * as items from './items/_module.mjs';
|
||||
export * as rollTables from './rollTables/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||
import DhDeathMove from '../../dialogs/deathMove.mjs';
|
||||
import DhpDeathMove from '../../dialogs/deathMove.mjs';
|
||||
import { abilities } from '../../../config/actorConfig.mjs';
|
||||
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
||||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||
|
|
@ -27,7 +27,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
makeDeathMove: CharacterSheet.#makeDeathMove,
|
||||
levelManagement: CharacterSheet.#levelManagement,
|
||||
viewLevelups: CharacterSheet.#viewLevelups,
|
||||
resetCharacter: CharacterSheet.#resetCharacter,
|
||||
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
||||
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||
|
|
@ -43,11 +42,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
icon: 'fa-solid fa-angles-up',
|
||||
label: 'DAGGERHEART.ACTORS.Character.viewLevelups',
|
||||
action: 'viewLevelups'
|
||||
},
|
||||
{
|
||||
icon: 'fa-solid fa-arrow-rotate-left',
|
||||
label: 'DAGGERHEART.ACTORS.Character.resetCharacter',
|
||||
action: 'resetCharacter'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -226,6 +220,13 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
case 'header':
|
||||
const { playerCanEditSheet, levelupAuto } = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Automation
|
||||
);
|
||||
context.showSettings = game.user.isGM || !levelupAuto || (levelupAuto && playerCanEditSheet);
|
||||
break;
|
||||
case 'loadout':
|
||||
await this._prepareLoadoutContext(context, options);
|
||||
break;
|
||||
|
|
@ -665,19 +666,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
new LevelupViewMode(this.document).render({ force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the character data and removes all embedded documents.
|
||||
*/
|
||||
static async #resetCharacter() {
|
||||
new game.system.api.applications.dialogs.CharacterResetDialog(this.document).render({ force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the Death Move interface for the character.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #makeDeathMove() {
|
||||
await new DhDeathMove(this.document).render({ force: true });
|
||||
await new DhpDeathMove(this.document).render({ force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -734,9 +728,9 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
if (!result) return;
|
||||
|
||||
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
|
||||
const costResources =
|
||||
result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value, total: -cost.total })) ||
|
||||
{};
|
||||
const costResources = result.costs
|
||||
.filter(x => x.enabled)
|
||||
.map(cost => ({ ...cost, value: -cost.value, total: -cost.total }));
|
||||
config.resourceUpdates.addResources(costResources);
|
||||
await config.resourceUpdates.updateResources();
|
||||
}
|
||||
|
|
@ -962,18 +956,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
}
|
||||
|
||||
async _onDropItem(event, item) {
|
||||
const setupCriticalItemTypes = ['class', 'subclass', 'ancestry', 'community'];
|
||||
if (this.document.system.needsCharacterSetup && setupCriticalItemTypes.includes(item.type)) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.CharacterCreation.setupSkipTitle')
|
||||
},
|
||||
content: game.i18n.localize('DAGGERHEART.APPLICATIONS.CharacterCreation.setupSkipContent')
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
if (this.document.uuid === item.parent?.uuid) {
|
||||
return super._onDropItem(event, item);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,15 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
}
|
||||
};
|
||||
|
||||
/** @inheritDoc */
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
|
||||
this.element
|
||||
.querySelector('.level-value')
|
||||
?.addEventListener('change', event => this.document.updateLevel(Number(event.currentTarget.value)));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -62,10 +71,10 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.Roll.action')}: ${this.actor.name}`,
|
||||
headerTitle: `Companion ${game.i18n.localize('DAGGERHEART.GENERAL.Roll.action')}`,
|
||||
roll: {
|
||||
trait: partner.system.spellcastModifierTrait?.key,
|
||||
companionRoll: true
|
||||
trait: partner.system.spellcastModifierTrait?.key
|
||||
},
|
||||
hasRoll: true
|
||||
hasRoll: true,
|
||||
data: partner.getRollData()
|
||||
};
|
||||
|
||||
const result = await partner.diceRoll(config);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
|||
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
||||
import DhpActor from '../../../documents/actor.mjs';
|
||||
import DHItem from '../../../documents/item.mjs';
|
||||
|
||||
export default class Party extends DHBaseActorSheet {
|
||||
constructor(options) {
|
||||
|
|
@ -268,6 +269,15 @@ export default class Party extends DHBaseActorSheet {
|
|||
).render({ force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of ContextMenu options for Consumable and Loot.
|
||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||
* @this {CharacterSheet}
|
||||
* @protected
|
||||
*/
|
||||
static #getItemContextOptions() {
|
||||
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
/* Filter Tracking */
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -431,18 +431,18 @@ export default function DHApplicationMixin(Base) {
|
|||
{
|
||||
name: 'disableEffect',
|
||||
icon: 'fa-solid fa-lightbulb',
|
||||
condition: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||
condition: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc && !doc.disabled;
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
|
||||
},
|
||||
{
|
||||
name: 'enableEffect',
|
||||
icon: 'fa-regular fa-lightbulb',
|
||||
condition: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||
condition: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc && doc.disabled;
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).update({ disabled: false })
|
||||
}
|
||||
|
|
@ -536,10 +536,6 @@ export default function DHApplicationMixin(Base) {
|
|||
options.push({
|
||||
name: 'CONTROLS.CommonDelete',
|
||||
icon: 'fa-solid fa-trash',
|
||||
condition: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
return target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
if (event.shiftKey) return doc.delete();
|
||||
|
|
@ -604,7 +600,7 @@ export default function DHApplicationMixin(Base) {
|
|||
{
|
||||
relativeTo: isAction ? doc.parent : doc,
|
||||
rollData: doc.getRollData?.(),
|
||||
secrets: isAction ? doc.parent.parent.isOwner : doc.isOwner
|
||||
secrets: isAction ? doc.parent.isOwner : doc.isOwner
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
],
|
||||
dragDrop: [
|
||||
{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null },
|
||||
{ dragSelector: '.currency[data-currency] .drag-handle', dropSelector: null }
|
||||
{ dragSelector: ".currency[data-currency] .drag-handle", dropSelector: null }
|
||||
]
|
||||
};
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
value: context.source.system.gold[key]
|
||||
};
|
||||
}
|
||||
context.inventory.hasCurrency = Object.values(context.inventory.currencies).some(c => c.enabled);
|
||||
context.inventory.hasCurrency = Object.values(context.inventory.currencies).some((c) => c.enabled);
|
||||
}
|
||||
|
||||
return context;
|
||||
|
|
@ -270,9 +270,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
currency
|
||||
});
|
||||
if (quantity) {
|
||||
originActor.update({
|
||||
[`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - 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;
|
||||
|
|
@ -294,15 +292,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
|
||||
/* Handling transfer of inventoryItems */
|
||||
if (item.system.metadata.isInventoryItem) {
|
||||
if (!this.document.testUserPermission(game.user, 'OWNER', { exact: true })) {
|
||||
return ui.notifications.error(
|
||||
game.i18n.format('DAGGERHEART.UI.Notifications.lackingItemTransferPermission', {
|
||||
user: game.user.name,
|
||||
target: this.document.name
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (item.system.metadata.isQuantifiable) {
|
||||
const actorItem = originActor.items.get(data.originId);
|
||||
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||
|
|
@ -311,6 +300,14 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
});
|
||||
|
||||
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({
|
||||
|
|
@ -328,18 +325,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
}
|
||||
]);
|
||||
}
|
||||
|
||||
if (quantityTransfered === actorItem.system.quantity) {
|
||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||
} else {
|
||||
await actorItem.update({
|
||||
'system.quantity': actorItem.system.quantity - quantityTransfered
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -350,7 +339,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
*/
|
||||
async _onDragStart(event) {
|
||||
// Handle drag/dropping currencies
|
||||
const currencyEl = event.currentTarget.closest('.currency[data-currency]');
|
||||
const currencyEl = event.currentTarget.closest(".currency[data-currency]");
|
||||
if (currencyEl) {
|
||||
const currency = currencyEl.dataset.currency;
|
||||
const data = { type: 'Currency', currency, originActor: this.document.uuid };
|
||||
|
|
@ -370,8 +359,8 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
event.dataTransfer.setData('text/plain', JSON.stringify(attackData));
|
||||
event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const item = await getDocFromElement(event.target);
|
||||
if (item) {
|
||||
const dragData = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export default function ItemAttachmentSheet(Base) {
|
||||
return class extends Base {
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
dragDrop: [
|
||||
...(super.DEFAULT_OPTIONS.dragDrop || []),
|
||||
{ dragSelector: null, dropSelector: '.attachments-section' }
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export { default as RollTableSheet } from './rollTable.mjs';
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
export default class DhRollTableSheet extends foundry.applications.sheets.RollTableSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
actions: {
|
||||
changeMode: DhRollTableSheet.#onChangeMode,
|
||||
drawResult: DhRollTableSheet.#onDrawResult,
|
||||
resetResults: DhRollTableSheet.#onResetResults,
|
||||
addFormula: DhRollTableSheet.#addFormula,
|
||||
removeFormula: DhRollTableSheet.#removeFormula
|
||||
}
|
||||
};
|
||||
|
||||
static buildParts() {
|
||||
const { footer, header, sheet, results, ...parts } = super.PARTS;
|
||||
return {
|
||||
sheet: {
|
||||
...sheet,
|
||||
template: 'systems/daggerheart/templates/sheets/rollTable/sheet.hbs'
|
||||
},
|
||||
header: { template: 'systems/daggerheart/templates/sheets/rollTable/header.hbs' },
|
||||
...parts,
|
||||
results: {
|
||||
template: 'systems/daggerheart/templates/sheets/rollTable/results.hbs',
|
||||
templates: ['templates/sheets/roll-table/result-details.hbs'],
|
||||
scrollable: ['table[data-results] tbody']
|
||||
},
|
||||
summary: { template: 'systems/daggerheart/templates/sheets/rollTable/summary.hbs' },
|
||||
footer
|
||||
};
|
||||
}
|
||||
|
||||
static PARTS = DhRollTableSheet.buildParts();
|
||||
|
||||
async _preRender(context, options) {
|
||||
await super._preRender(context, options);
|
||||
|
||||
if (!options.internalRefresh)
|
||||
this.daggerheartFlag = new game.system.api.data.DhRollTable(this.document.flags.daggerheart);
|
||||
}
|
||||
|
||||
/* root PART has a blank element on _attachPartListeners, so it cannot be used to set the eventListeners for the view mode */
|
||||
async _onRender(context, options) {
|
||||
super._onRender(context, options);
|
||||
|
||||
for (const element of this.element.querySelectorAll('.system-update-field'))
|
||||
element.addEventListener('change', this.updateSystemField.bind(this));
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
|
||||
switch (partId) {
|
||||
case 'sheet':
|
||||
context.altFormula = this.daggerheartFlag.altFormula;
|
||||
context.usesAltFormula = Object.keys(this.daggerheartFlag.altFormula).length > 0;
|
||||
context.altFormulaOptions = {
|
||||
'': { name: this.daggerheartFlag.formulaName },
|
||||
...this.daggerheartFlag.altFormula
|
||||
};
|
||||
context.activeAltFormula = this.daggerheartFlag.activeAltFormula;
|
||||
context.selectedFormula = this.daggerheartFlag.getActiveFormula(this.document.formula);
|
||||
context.results = this.getExtendedResults(context.results);
|
||||
break;
|
||||
case 'header':
|
||||
context.altFormula = this.daggerheartFlag.altFormula;
|
||||
context.usesAltFormula = Object.keys(this.daggerheartFlag.altFormula).length > 0;
|
||||
context.altFormulaOptions = {
|
||||
'': { name: this.daggerheartFlag.formulaName },
|
||||
...this.daggerheartFlag.altFormula
|
||||
};
|
||||
context.activeAltFormula = this.daggerheartFlag.activeAltFormula;
|
||||
break;
|
||||
case 'summary':
|
||||
context.systemFields = this.daggerheartFlag.schema.fields;
|
||||
context.altFormula = this.daggerheartFlag.altFormula;
|
||||
context.formulaName = this.daggerheartFlag.formulaName;
|
||||
break;
|
||||
case 'results':
|
||||
context.results = this.getExtendedResults(context.results);
|
||||
break;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
getExtendedResults(results) {
|
||||
const bodyDarkMode = document.body.classList.contains('theme-dark');
|
||||
const elementLightMode = this.element.classList.contains('theme-light');
|
||||
const elementDarkMode = this.element.classList.contains('theme-dark');
|
||||
const isDarkMode = elementDarkMode || (!elementLightMode && bodyDarkMode);
|
||||
|
||||
return results.map(x => ({
|
||||
...x,
|
||||
displayImg: isDarkMode && x.img === 'icons/svg/d20-black.svg' ? 'icons/svg/d20.svg' : x.img
|
||||
}));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Flag SystemData update methods */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async updateSystemField(event) {
|
||||
const { dataset, value } = event.target;
|
||||
await this.daggerheartFlag.updateSource({ [dataset.path]: value });
|
||||
this.render({ internalRefresh: true });
|
||||
}
|
||||
|
||||
getSystemFlagUpdate() {
|
||||
const deleteUpdate = Object.keys(this.document._source.flags.daggerheart?.altFormula ?? {}).reduce(
|
||||
(acc, formulaKey) => {
|
||||
if (!this.daggerheartFlag.altFormula[formulaKey]) acc.altFormula[`-=${formulaKey}`] = null;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ altFormula: {} }
|
||||
);
|
||||
|
||||
return { ['flags.daggerheart']: foundry.utils.mergeObject(this.daggerheartFlag.toObject(), deleteUpdate) };
|
||||
}
|
||||
|
||||
static async #addFormula() {
|
||||
await this.daggerheartFlag.updateSource({
|
||||
[`altFormula.${foundry.utils.randomID()}`]: game.system.api.data.DhRollTable.getDefaultFormula()
|
||||
});
|
||||
this.render({ internalRefresh: true });
|
||||
}
|
||||
|
||||
static async #removeFormula(_event, target) {
|
||||
await this.daggerheartFlag.updateSource({
|
||||
[`altFormula.-=${target.dataset.key}`]: null
|
||||
});
|
||||
this.render({ internalRefresh: true });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Extended RollTable methods */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Alternate between view and edit modes.
|
||||
* @this {RollTableSheet}
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #onChangeMode() {
|
||||
this.mode = this.isEditMode ? 'view' : 'edit';
|
||||
await this.document.update(this.getSystemFlagUpdate());
|
||||
await this.render({ internalRefresh: true });
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
async _processSubmitData(event, form, submitData, options) {
|
||||
/* RollTable sends an empty dummy event when swapping from view/edit first time */
|
||||
if (Object.keys(submitData).length) {
|
||||
if (!submitData.flags) submitData.flags = { daggerheart: {} };
|
||||
submitData.flags.daggerheart = this.getSystemFlagUpdate();
|
||||
}
|
||||
|
||||
super._processSubmitData(event, form, submitData, options);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
static async #onResetResults() {
|
||||
await this.document.update(this.getSystemFlagUpdate());
|
||||
await this.document.resetResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll and draw a TableResult.
|
||||
* @this {RollTableSheet}
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #onDrawResult(_event, button) {
|
||||
if (this.form) await this.submit({ operation: { render: false } });
|
||||
button.disabled = true;
|
||||
const table = this.document;
|
||||
|
||||
await this.document.update(this.getSystemFlagUpdate());
|
||||
|
||||
/* Sending in the currently selectd activeFormula to table.roll to use as the formula */
|
||||
const selectedFormula = this.daggerheartFlag.getActiveFormula(this.document.formula);
|
||||
const tableRoll = await table.roll({ selectedFormula });
|
||||
const draws = table.getResultsForRoll(tableRoll.roll.total);
|
||||
if (draws.length > 0) {
|
||||
if (game.settings.get('core', 'animateRollTable')) await this._animateRoll(draws);
|
||||
await table.draw(tableRoll);
|
||||
}
|
||||
|
||||
// Reenable the button if drawing with replacement since the draw won't trigger a sheet re-render
|
||||
if (table.replacement) button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -43,54 +43,4 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
|||
event.dataTransfer.setDragImage(preview, w / 2, h / 2);
|
||||
}
|
||||
}
|
||||
|
||||
_getEntryContextOptions() {
|
||||
const options = super._getEntryContextOptions();
|
||||
options.push({
|
||||
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
||||
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
|
||||
condition: li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
return actor?.type === 'adversary' && actor.system.type !== 'social';
|
||||
},
|
||||
callback: async li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
if (!actor) throw new Error('Unexpected missing actor');
|
||||
|
||||
const tiers = [1, 2, 3, 4].filter(t => t !== actor.system.tier);
|
||||
const content = document.createElement('div');
|
||||
const select = document.createElement('select');
|
||||
select.name = 'tier';
|
||||
select.append(
|
||||
...tiers.map(t => {
|
||||
const option = document.createElement('option');
|
||||
option.value = t;
|
||||
option.textContent = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${t}`);
|
||||
return option;
|
||||
})
|
||||
);
|
||||
content.append(select);
|
||||
|
||||
const tier = await foundry.applications.api.Dialog.input({
|
||||
classes: ['dh-style', 'dialog'],
|
||||
window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' },
|
||||
content,
|
||||
ok: {
|
||||
label: 'Create Adversary',
|
||||
callback: (event, button, dialog) => Number(button.form.elements.tier.value)
|
||||
}
|
||||
});
|
||||
|
||||
if (tier === actor.system.tier) {
|
||||
ui.notifications.warn('This actor is already at this tier');
|
||||
} else if (tier) {
|
||||
const source = actor.system.adjustForTier(tier);
|
||||
await Actor.create(source);
|
||||
ui.notifications.info(`Tier ${tier} ${actor.name} created`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { RefreshFeatures } from '../../../helpers/utils.mjs';
|
||||
import { refreshIsAllowed } from '../../../helpers/utils.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
const { AbstractSidebarTab } = foundry.applications.sidebar;
|
||||
|
|
@ -54,6 +54,73 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
return context;
|
||||
}
|
||||
|
||||
async getRefreshables(types) {
|
||||
const refreshedActors = {};
|
||||
for (let actor of game.actors) {
|
||||
if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) {
|
||||
const updates = {};
|
||||
for (let item of actor.items) {
|
||||
if (item.system.metadata?.hasResource && refreshIsAllowed(types, item.system.resource?.recovery)) {
|
||||
if (!refreshedActors[actor.id])
|
||||
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
||||
refreshedActors[actor.id].refreshed.add(
|
||||
game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[item.system.resource.recovery].label)
|
||||
);
|
||||
|
||||
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
|
||||
|
||||
const increasing =
|
||||
item.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id;
|
||||
updates[item.id].system = {
|
||||
...updates[item.id].system,
|
||||
'resource.value': increasing
|
||||
? 0
|
||||
: Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
|
||||
};
|
||||
}
|
||||
if (item.system.metadata?.hasActions) {
|
||||
const refreshTypes = new Set();
|
||||
const actions = item.system.actions.filter(action => {
|
||||
if (refreshIsAllowed(types, action.uses.recovery)) {
|
||||
refreshTypes.add(action.uses.recovery);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
if (actions.length === 0) continue;
|
||||
|
||||
if (!refreshedActors[actor.id])
|
||||
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
||||
refreshedActors[actor.id].refreshed.add(
|
||||
...refreshTypes.map(type => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[type].label))
|
||||
);
|
||||
|
||||
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
|
||||
|
||||
updates[item.id].system = {
|
||||
...updates[item.id].system,
|
||||
...actions.reduce(
|
||||
(acc, action) => {
|
||||
acc.actions[action.id] = { 'uses.value': 0 };
|
||||
return acc;
|
||||
},
|
||||
{ actions: updates[item.id].system.actions ?? {} }
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for (let key in updates) {
|
||||
const update = updates[key];
|
||||
await actor.items.get(key).update(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return refreshedActors;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -66,9 +133,30 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
|
||||
static async #refreshActors() {
|
||||
const refreshKeys = Object.keys(this.refreshSelections).filter(key => this.refreshSelections[key].selected);
|
||||
await RefreshFeatures(refreshKeys);
|
||||
|
||||
await this.getRefreshables(refreshKeys);
|
||||
const types = refreshKeys.map(x => this.refreshSelections[x].label).join(', ');
|
||||
ui.notifications.info(
|
||||
game.i18n.format('DAGGERHEART.UI.Notifications.gmMenuRefresh', {
|
||||
types: `[${types}]`
|
||||
})
|
||||
);
|
||||
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
||||
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
const msg = {
|
||||
user: game.user.id,
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/refreshMessage.hbs',
|
||||
{
|
||||
types: types
|
||||
}
|
||||
),
|
||||
title: game.i18n.localize('DAGGERHEART.UI.Chat.refreshMessage.title'),
|
||||
speaker: cls.getSpeaker()
|
||||
};
|
||||
|
||||
cls.create(msg);
|
||||
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,9 +81,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
|
||||
element.addEventListener('click', this.groupRollExpandSection)
|
||||
);
|
||||
html.querySelectorAll('.risk-it-all-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
||||
);
|
||||
};
|
||||
|
||||
setupHooks() {
|
||||
|
|
@ -97,17 +94,15 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
|
||||
/** Ensure the chat theme inherits the interface theme */
|
||||
_replaceHTML(result, content, options) {
|
||||
const themedElement = result.log?.querySelector('.chat-log');
|
||||
themedElement?.classList.remove('themed', 'theme-light', 'theme-dark');
|
||||
const themedElement = result.log?.querySelector(".chat-log");
|
||||
themedElement?.classList.remove("themed", "theme-light", "theme-dark");
|
||||
super._replaceHTML(result, content, options);
|
||||
}
|
||||
|
||||
/** Remove chat log theme from notifications area */
|
||||
async _onFirstRender(result, content) {
|
||||
await super._onFirstRender(result, content);
|
||||
document
|
||||
.querySelector('#chat-notifications .chat-log')
|
||||
?.classList.remove('themed', 'theme-light', 'theme-dark');
|
||||
document.querySelector("#chat-notifications .chat-log")?.classList.remove("themed", "theme-light", "theme-dark")
|
||||
}
|
||||
|
||||
async onRollSimple(event, message) {
|
||||
|
|
@ -388,10 +383,4 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
});
|
||||
event.target.closest('.group-roll-section').querySelector('.group-roll-content').classList.toggle('closed');
|
||||
}
|
||||
|
||||
async riskItAllClearStressAndHitPoints(event, data) {
|
||||
const resourceValue = event.target.dataset.resourceValue;
|
||||
const actor = game.actors.get(event.target.dataset.actorId);
|
||||
new game.system.api.applications.dialogs.RiskItAllDialog(actor, resourceValue).render({ force: true });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
|||
position: {
|
||||
width: 222,
|
||||
height: 222
|
||||
// top: "200px",
|
||||
// left: "120px"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -64,7 +66,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
|||
max = this.maxFear,
|
||||
percent = (current / max) * 100,
|
||||
isGM = game.user.isGM;
|
||||
|
||||
// Return the data for rendering
|
||||
return { display, current, max, percent, isGM };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
/**
|
||||
|
|
@ -19,15 +17,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
|
||||
this.presets = {};
|
||||
this.compendiumBrowserTypeKey = 'compendiumBrowserDefault';
|
||||
|
||||
this.setupHooks = Hooks.on(socketEvent.Refresh, ({ refreshType }) => {
|
||||
if (refreshType === RefreshType.CompendiumBrowser) {
|
||||
if (this.rendered) {
|
||||
this.render();
|
||||
this.loadItems();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
|
|
@ -46,8 +35,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
selectFolder: this.selectFolder,
|
||||
expandContent: this.expandContent,
|
||||
resetFilters: this.resetFilters,
|
||||
sortList: this.sortList,
|
||||
openSettings: this.openSettings
|
||||
sortList: this.sortList
|
||||
},
|
||||
position: {
|
||||
left: 100,
|
||||
|
|
@ -169,8 +157,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
context.formatChoices = this.formatChoices;
|
||||
context.items = this.items;
|
||||
context.presets = this.presets;
|
||||
context.isGM = game.user.isGM;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -228,10 +214,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
loadItems() {
|
||||
let loadTimeout = this.toggleLoader(true);
|
||||
|
||||
const browserSettings = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings
|
||||
);
|
||||
const promises = [];
|
||||
|
||||
game.packs.forEach(pack => {
|
||||
|
|
@ -245,7 +227,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
|
||||
Promise.all(promises).then(async result => {
|
||||
this.items = ItemBrowser.sortBy(
|
||||
result.flatMap(r => r).filter(r => !browserSettings.isEntryExcluded.bind(browserSettings)(r)),
|
||||
result.flatMap(r => r),
|
||||
'name'
|
||||
);
|
||||
|
||||
|
|
@ -530,22 +512,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
itemListContainer.replaceChildren(...newOrder);
|
||||
}
|
||||
|
||||
static async openSettings() {
|
||||
const settingsUpdated = await game.system.api.applications.dialogs.CompendiumBrowserSettingsDialog.configure();
|
||||
if (settingsUpdated) {
|
||||
if (this.rendered) {
|
||||
this.render();
|
||||
this.loadItems();
|
||||
}
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.CompendiumBrowser
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_createDragProcess() {
|
||||
new foundry.applications.ux.DragDrop.implementation({
|
||||
dragSelector: '.item-container',
|
||||
|
|
@ -605,9 +571,4 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
headerActions.append(button);
|
||||
}
|
||||
}
|
||||
|
||||
async close(options = {}) {
|
||||
Hooks.off(socketEvent.Refresh, this.setupHooks);
|
||||
await super.close(options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,8 +63,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
|
|||
if (scene.flags.daggerheart.sceneEnvironments[0] !== environment.uuid) {
|
||||
const newEnvironments = scene.flags.daggerheart.sceneEnvironments;
|
||||
const newFirst = newEnvironments.splice(
|
||||
newEnvironments.findIndex(x => x === environment.uuid),
|
||||
1
|
||||
newEnvironments.findIndex(x => x === environment.uuid)
|
||||
)[0];
|
||||
newEnvironments.unshift(newFirst);
|
||||
emitAsGM(
|
||||
|
|
|
|||
|
|
@ -18,9 +18,8 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
|
|||
|
||||
static getRangeLabels(distanceValue, settings) {
|
||||
let result = { distance: distanceValue, units: '' };
|
||||
if (!settings.enabled) return result;
|
||||
|
||||
const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement;
|
||||
|
||||
const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
|
||||
if (sceneRangeMeasurement?.setting === disable.id) {
|
||||
result.distance = distanceValue;
|
||||
|
|
@ -28,9 +27,31 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
|
|||
return result;
|
||||
}
|
||||
|
||||
const ranges = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement : settings;
|
||||
const distanceKey = ['melee', 'veryClose', 'close', 'far'].find(r => ranges[r] >= distanceValue);
|
||||
result.distance = game.i18n.localize(`DAGGERHEART.CONFIG.Range.${distanceKey ?? 'veryFar'}.name`);
|
||||
const melee = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.melee : settings.melee;
|
||||
const veryClose =
|
||||
sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.veryClose : settings.veryClose;
|
||||
const close = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.close : settings.close;
|
||||
const far = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.far : settings.far;
|
||||
if (distanceValue <= melee) {
|
||||
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.melee.name');
|
||||
return result;
|
||||
}
|
||||
if (distanceValue <= veryClose) {
|
||||
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.veryClose.name');
|
||||
return result;
|
||||
}
|
||||
if (distanceValue <= close) {
|
||||
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.close.name');
|
||||
return result;
|
||||
}
|
||||
if (distanceValue <= far) {
|
||||
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.far.name');
|
||||
return result;
|
||||
}
|
||||
if (distanceValue > far) {
|
||||
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.veryFar.name');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import DhMeasuredTemplate from './measuredTemplate.mjs';
|
||||
|
||||
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||
/** @inheritdoc */
|
||||
async _draw(options) {
|
||||
|
|
@ -54,111 +52,30 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
if (this === target) return 0;
|
||||
|
||||
const originPoint = this.center;
|
||||
const targetPoint = target.center;
|
||||
const thisBounds = this.bounds;
|
||||
const targetBounds = target.bounds;
|
||||
const adjacencyBuffer = canvas.grid.distance * 1.75; // handles diagonals with one square elevation difference
|
||||
|
||||
// Figure out the elevation difference.
|
||||
// This intends to return "grid distance" for adjacent ones, so we add that number if not overlapping.
|
||||
const sizePerUnit = canvas.grid.size / canvas.grid.distance;
|
||||
const thisHeight = Math.max(thisBounds.width, thisBounds.height) / sizePerUnit;
|
||||
const targetHeight = Math.max(targetBounds.width, targetBounds.height) / sizePerUnit;
|
||||
const thisElevation = [this.document.elevation, this.document.elevation + thisHeight];
|
||||
const targetElevation = [target.document.elevation, target.document.elevation + targetHeight];
|
||||
const isSameAltitude =
|
||||
thisElevation[0] < targetElevation[1] && // bottom of this must be at or below the top of target
|
||||
thisElevation[1] > targetElevation[0]; // top of this must be at or above the bottom of target
|
||||
const [lower, higher] = [targetElevation, thisElevation].sort((a, b) => a[1] - b[1]);
|
||||
const elevation = isSameAltitude ? 0 : higher[0] - lower[1] + canvas.grid.distance;
|
||||
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 = (thisBounds.width * boundsCorrection) / 2;
|
||||
const targetRadius = (targetBounds.width * boundsCorrection) / 2;
|
||||
const measuredDistance = canvas.grid.measurePath([
|
||||
{ ...originPoint, elevation: 0 },
|
||||
{ ...targetPoint, elevation }
|
||||
]).distance;
|
||||
const distance = Math.floor(measuredDistance - originRadius - targetRadius + canvas.grid.distance);
|
||||
return Math.min(distance, distance > adjacencyBuffer ? Infinity : canvas.grid.distance);
|
||||
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(thisBounds, originPoint, targetPoint);
|
||||
const targetEdge = this.#getEdgeBoundary(targetBounds, originPoint, targetPoint);
|
||||
const adjustedOriginPoint = originEdge
|
||||
? canvas.grid.getTopLeftPoint({
|
||||
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
|
||||
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
|
||||
})
|
||||
: originPoint;
|
||||
const adjustDestinationPoint = targetEdge
|
||||
? canvas.grid.getTopLeftPoint({
|
||||
x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x),
|
||||
y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y)
|
||||
})
|
||||
: targetPoint;
|
||||
const distance = canvas.grid.measurePath([
|
||||
{ ...adjustedOriginPoint, elevation: 0 },
|
||||
{ ...adjustDestinationPoint, elevation }
|
||||
]).distance;
|
||||
return Math.min(distance, distance > adjacencyBuffer ? Infinity : canvas.grid.distance);
|
||||
}
|
||||
|
||||
_onHoverIn(event, options) {
|
||||
super._onHoverIn(event, options);
|
||||
|
||||
// Check if the setting is enabled
|
||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).showTokenDistance;
|
||||
if (setting === 'never' || (setting === 'encounters' && !game.combat?.started)) return;
|
||||
|
||||
// Check if this token isn't invisible and is actually being hovered
|
||||
const isTokenValid =
|
||||
this.visible &&
|
||||
this.hover &&
|
||||
!this.isPreview &&
|
||||
!this.document.isSecret &&
|
||||
!this.controlled &&
|
||||
!this.animation;
|
||||
if (!isTokenValid) return;
|
||||
|
||||
// Ensure we have a single controlled token
|
||||
const originToken = canvas.tokens.controlled[0];
|
||||
if (!originToken || canvas.tokens.controlled.length > 1) return;
|
||||
|
||||
// Determine the actual range
|
||||
const ranges = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
|
||||
const distanceResult = DhMeasuredTemplate.getRangeLabels(originToken.distanceTo(this), ranges);
|
||||
const distanceLabel = `${distanceResult.distance} ${distanceResult.units}`.trim();
|
||||
|
||||
// Create the element
|
||||
const element = document.createElement('div');
|
||||
element.id = 'token-hover-distance';
|
||||
element.classList.add('waypoint-label', 'last');
|
||||
const ruler = document.createElement('i');
|
||||
ruler.classList.add('fa-solid', 'fa-ruler');
|
||||
element.appendChild(ruler);
|
||||
const labelEl = document.createElement('span');
|
||||
labelEl.classList.add('total-measurement');
|
||||
labelEl.textContent = distanceLabel;
|
||||
element.appendChild(labelEl);
|
||||
|
||||
// Position the element and add to the DOM
|
||||
const center = this.getCenterPoint();
|
||||
element.style.setProperty('--transformY', 'calc(-100% - 10px)');
|
||||
element.style.setProperty('--position-y', `${this.y}px`);
|
||||
element.style.setProperty('--position-x', `${center.x}px`);
|
||||
element.style.setProperty('--ui-scale', String(canvas.dimensions.uiScale));
|
||||
document.querySelector('#token-hover-distance')?.remove();
|
||||
document.querySelector('#measurement').appendChild(element);
|
||||
}
|
||||
|
||||
_onHoverOut(...args) {
|
||||
super._onHoverOut(...args);
|
||||
document.querySelector('#token-hover-distance')?.remove();
|
||||
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 */
|
||||
|
|
@ -183,6 +100,11 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
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 */
|
||||
_drawBar(number, bar, data) {
|
||||
const val = Number(data.value);
|
||||
|
|
|
|||
|
|
@ -494,275 +494,3 @@ export const subclassFeatureLabels = {
|
|||
2: 'DAGGERHEART.ITEMS.DomainCard.specializationTitle',
|
||||
3: 'DAGGERHEART.ITEMS.DomainCard.masteryTitle'
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} TierData
|
||||
* @property {number} difficulty
|
||||
* @property {number} majorThreshold
|
||||
* @property {number} severeThreshold
|
||||
* @property {number} hp
|
||||
* @property {number} stress
|
||||
* @property {number} attack
|
||||
* @property {number[]} damage
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {Record<string, Record<2 | 3 | 4, TierData>}
|
||||
* Scaling data used to change an adversary's tier. Each rank is applied incrementally.
|
||||
*/
|
||||
export const adversaryScalingData = {
|
||||
bruiser: {
|
||||
2: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 5,
|
||||
severeThreshold: 10,
|
||||
hp: 1,
|
||||
stress: 2,
|
||||
attack: 2,
|
||||
},
|
||||
3: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 7,
|
||||
severeThreshold: 15,
|
||||
hp: 1,
|
||||
stress: 0,
|
||||
attack: 2,
|
||||
},
|
||||
4: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 12,
|
||||
severeThreshold: 25,
|
||||
hp: 1,
|
||||
stress: 0,
|
||||
attack: 2,
|
||||
}
|
||||
},
|
||||
horde: {
|
||||
2: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 5,
|
||||
severeThreshold: 8,
|
||||
hp: 2,
|
||||
stress: 0,
|
||||
attack: 0,
|
||||
},
|
||||
3: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 5,
|
||||
severeThreshold: 12,
|
||||
hp: 0,
|
||||
stress: 1,
|
||||
attack: 1,
|
||||
},
|
||||
4: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 10,
|
||||
severeThreshold: 15,
|
||||
hp: 2,
|
||||
stress: 0,
|
||||
attack: 0,
|
||||
}
|
||||
},
|
||||
leader: {
|
||||
2: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 6,
|
||||
severeThreshold: 10,
|
||||
hp: 0,
|
||||
stress: 0,
|
||||
attack: 1,
|
||||
},
|
||||
3: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 6,
|
||||
severeThreshold: 15,
|
||||
hp: 1,
|
||||
stress: 0,
|
||||
attack: 2,
|
||||
},
|
||||
4: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 12,
|
||||
severeThreshold: 25,
|
||||
hp: 1,
|
||||
stress: 1,
|
||||
attack: 3,
|
||||
}
|
||||
},
|
||||
minion: {
|
||||
2: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 0,
|
||||
severeThreshold: 0,
|
||||
hp: 0,
|
||||
stress: 0,
|
||||
attack: 1,
|
||||
},
|
||||
3: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 0,
|
||||
severeThreshold: 0,
|
||||
hp: 0,
|
||||
stress: 1,
|
||||
attack: 1,
|
||||
},
|
||||
4: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 0,
|
||||
severeThreshold: 0,
|
||||
hp: 0,
|
||||
stress: 0,
|
||||
attack: 1,
|
||||
}
|
||||
},
|
||||
ranged: {
|
||||
2: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 3,
|
||||
severeThreshold: 6,
|
||||
hp: 1,
|
||||
stress: 0,
|
||||
attack: 1,
|
||||
},
|
||||
3: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 7,
|
||||
severeThreshold: 14,
|
||||
hp: 1,
|
||||
stress: 1,
|
||||
attack: 2,
|
||||
},
|
||||
4: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 5,
|
||||
severeThreshold: 10,
|
||||
hp: 1,
|
||||
stress: 1,
|
||||
attack: 1,
|
||||
}
|
||||
},
|
||||
skulk: {
|
||||
2: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 3,
|
||||
severeThreshold: 8,
|
||||
hp: 1,
|
||||
stress: 1,
|
||||
attack: 1,
|
||||
},
|
||||
3: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 8,
|
||||
severeThreshold: 12,
|
||||
hp: 1,
|
||||
stress: 1,
|
||||
attack: 1,
|
||||
},
|
||||
4: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 8,
|
||||
severeThreshold: 10,
|
||||
hp: 1,
|
||||
stress: 1,
|
||||
attack: 1,
|
||||
}
|
||||
},
|
||||
solo: {
|
||||
2: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 5,
|
||||
severeThreshold: 10,
|
||||
hp: 0,
|
||||
stress: 1,
|
||||
attack: 2,
|
||||
},
|
||||
3: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 7,
|
||||
severeThreshold: 15,
|
||||
hp: 2,
|
||||
stress: 1,
|
||||
attack: 2,
|
||||
},
|
||||
4: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 12,
|
||||
severeThreshold: 25,
|
||||
hp: 0,
|
||||
stress: 1,
|
||||
attack: 3,
|
||||
}
|
||||
},
|
||||
standard: {
|
||||
2: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 3,
|
||||
severeThreshold: 8,
|
||||
hp: 0,
|
||||
stress: 0,
|
||||
attack: 1,
|
||||
},
|
||||
3: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 7,
|
||||
severeThreshold: 15,
|
||||
hp: 1,
|
||||
stress: 1,
|
||||
attack: 1,
|
||||
},
|
||||
4: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 10,
|
||||
severeThreshold: 15,
|
||||
hp: 0,
|
||||
stress: 1,
|
||||
attack: 1,
|
||||
}
|
||||
},
|
||||
support: {
|
||||
2: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 3,
|
||||
severeThreshold: 8,
|
||||
hp: 1,
|
||||
stress: 1,
|
||||
attack: 1,
|
||||
},
|
||||
3: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 7,
|
||||
severeThreshold: 12,
|
||||
hp: 0,
|
||||
stress: 0,
|
||||
attack: 1,
|
||||
},
|
||||
4: {
|
||||
difficulty: 2,
|
||||
majorThreshold: 8,
|
||||
severeThreshold: 10,
|
||||
hp: 1,
|
||||
stress: 1,
|
||||
attack: 1,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Scaling data used for an adversary's damage.
|
||||
* Tier 4 is missing certain adversary types and therefore skews upwards.
|
||||
* We manually set tier 4 data to hopefully lead to better results
|
||||
*/
|
||||
export const adversaryExpectedDamage = {
|
||||
basic: {
|
||||
1: { mean: 7.321428571428571, deviation: 1.962519002770912 },
|
||||
2: { mean: 12.444444444444445, deviation: 2.0631069425529676 },
|
||||
3: { mean: 15.722222222222221, deviation: 2.486565208464823 },
|
||||
4: { mean: 26, deviation: 5.2 }
|
||||
},
|
||||
minion: {
|
||||
1: { mean: 2.142857142857143, deviation: 1.0690449676496976 },
|
||||
2: { mean: 5, deviation: 0.816496580927726 },
|
||||
3: { mean: 6.5, deviation: 2.1213203435596424 },
|
||||
4: { mean: 11, deviation: 1 }
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ export const defeatedConditions = () => {
|
|||
acc[key] = {
|
||||
...choice,
|
||||
img: defeated[`${choice.id}Icon`],
|
||||
description: game.i18n.localize(`DAGGERHEART.CONFIG.Condition.${choice.id}.description`)
|
||||
description: `DAGGERHEART.CONFIG.Condition.${choice.id}.description`
|
||||
};
|
||||
|
||||
return acc;
|
||||
|
|
@ -179,10 +179,6 @@ export const defeatedConditions = () => {
|
|||
};
|
||||
|
||||
export const defeatedConditionChoices = {
|
||||
deathMove: {
|
||||
id: 'deathMove',
|
||||
name: 'DAGGERHEART.CONFIG.Condition.deathMove.name'
|
||||
},
|
||||
defeated: {
|
||||
id: 'defeated',
|
||||
name: 'DAGGERHEART.CONFIG.Condition.defeated.name'
|
||||
|
|
@ -202,8 +198,7 @@ export const conditions = () => ({
|
|||
id: 'vulnerable',
|
||||
name: 'DAGGERHEART.CONFIG.Condition.vulnerable.name',
|
||||
img: 'icons/magic/control/silhouette-fall-slip-prone.webp',
|
||||
description: 'DAGGERHEART.CONFIG.Condition.vulnerable.description',
|
||||
autoApplyFlagId: 'auto-vulnerable'
|
||||
description: 'DAGGERHEART.CONFIG.Condition.vulnerable.description'
|
||||
},
|
||||
hidden: {
|
||||
id: 'hidden',
|
||||
|
|
@ -237,7 +232,6 @@ export const defaultRestOptions = {
|
|||
actionType: 'action',
|
||||
chatDisplay: false,
|
||||
target: {
|
||||
amount: 1,
|
||||
type: 'friendly'
|
||||
},
|
||||
damage: {
|
||||
|
|
@ -254,8 +248,7 @@ export const defaultRestOptions = {
|
|||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
effects: []
|
||||
}
|
||||
},
|
||||
clearStress: {
|
||||
id: 'clearStress',
|
||||
|
|
@ -288,8 +281,7 @@ export const defaultRestOptions = {
|
|||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
effects: []
|
||||
}
|
||||
},
|
||||
repairArmor: {
|
||||
id: 'repairArmor',
|
||||
|
|
@ -306,7 +298,6 @@ export const defaultRestOptions = {
|
|||
actionType: 'action',
|
||||
chatDisplay: false,
|
||||
target: {
|
||||
amount: 1,
|
||||
type: 'friendly'
|
||||
},
|
||||
damage: {
|
||||
|
|
@ -323,8 +314,7 @@ export const defaultRestOptions = {
|
|||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
effects: []
|
||||
}
|
||||
},
|
||||
prepare: {
|
||||
id: 'prepare',
|
||||
|
|
@ -332,57 +322,7 @@ export const defaultRestOptions = {
|
|||
icon: 'fa-solid fa-dumbbell',
|
||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.description'),
|
||||
actions: {
|
||||
prepare: {
|
||||
type: 'healing',
|
||||
systemPath: 'restMoves.shortRest.moves.prepare.actions',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.name'),
|
||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||
actionType: 'action',
|
||||
chatDisplay: false,
|
||||
target: {
|
||||
type: 'self'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.hope.id,
|
||||
value: {
|
||||
custom: {
|
||||
enabled: true,
|
||||
formula: '1'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
prepareWithFriends: {
|
||||
type: 'healing',
|
||||
systemPath: 'restMoves.shortRest.moves.prepare.actions',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepareWithFriends.name'),
|
||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||
actionType: 'action',
|
||||
chatDisplay: false,
|
||||
target: {
|
||||
type: 'self'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.hope.id,
|
||||
value: {
|
||||
custom: {
|
||||
enabled: true,
|
||||
formula: '2'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
effects: []
|
||||
actions: {}
|
||||
}
|
||||
}),
|
||||
longRest: () => ({
|
||||
|
|
@ -401,7 +341,6 @@ export const defaultRestOptions = {
|
|||
actionType: 'action',
|
||||
chatDisplay: false,
|
||||
target: {
|
||||
amount: 1,
|
||||
type: 'friendly'
|
||||
},
|
||||
damage: {
|
||||
|
|
@ -418,8 +357,7 @@ export const defaultRestOptions = {
|
|||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
effects: []
|
||||
}
|
||||
},
|
||||
clearStress: {
|
||||
id: 'clearStress',
|
||||
|
|
@ -452,8 +390,7 @@ export const defaultRestOptions = {
|
|||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
effects: []
|
||||
}
|
||||
},
|
||||
repairArmor: {
|
||||
id: 'repairArmor',
|
||||
|
|
@ -470,7 +407,6 @@ export const defaultRestOptions = {
|
|||
actionType: 'action',
|
||||
chatDisplay: false,
|
||||
target: {
|
||||
amount: 1,
|
||||
type: 'friendly'
|
||||
},
|
||||
damage: {
|
||||
|
|
@ -487,8 +423,7 @@ export const defaultRestOptions = {
|
|||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
effects: []
|
||||
}
|
||||
},
|
||||
prepare: {
|
||||
id: 'prepare',
|
||||
|
|
@ -496,57 +431,7 @@ export const defaultRestOptions = {
|
|||
icon: 'fa-solid fa-dumbbell',
|
||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.description'),
|
||||
actions: {
|
||||
prepare: {
|
||||
type: 'healing',
|
||||
systemPath: 'restMoves.longRest.moves.prepare.actions',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.name'),
|
||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||
actionType: 'action',
|
||||
chatDisplay: false,
|
||||
target: {
|
||||
type: 'self'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.hope.id,
|
||||
value: {
|
||||
custom: {
|
||||
enabled: true,
|
||||
formula: '1'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
prepareWithFriends: {
|
||||
type: 'healing',
|
||||
systemPath: 'restMoves.longRest.moves.prepare.actions',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepareWithFriends.name'),
|
||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||
actionType: 'action',
|
||||
chatDisplay: false,
|
||||
target: {
|
||||
type: 'self'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.hope.id,
|
||||
value: {
|
||||
custom: {
|
||||
enabled: true,
|
||||
formula: '2'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
effects: []
|
||||
actions: {}
|
||||
},
|
||||
workOnAProject: {
|
||||
id: 'workOnAProject',
|
||||
|
|
@ -554,8 +439,7 @@ export const defaultRestOptions = {
|
|||
icon: 'fa-solid fa-diagram-project',
|
||||
img: 'icons/skills/social/thumbsup-approval-like.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.description'),
|
||||
actions: {},
|
||||
effects: []
|
||||
actions: {}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
|
|||
|
|
@ -467,7 +467,9 @@ export const allArmorFeatures = () => {
|
|||
};
|
||||
|
||||
export const orderedArmorFeatures = () => {
|
||||
const allFeatures = allArmorFeatures();
|
||||
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||
.armorFeatures;
|
||||
const allFeatures = { ...armorFeatures, ...homebrewFeatures };
|
||||
const all = Object.keys(allFeatures).map(key => {
|
||||
const feature = allFeatures[key];
|
||||
return {
|
||||
|
|
@ -1402,7 +1404,9 @@ export const allWeaponFeatures = () => {
|
|||
};
|
||||
|
||||
export const orderedWeaponFeatures = () => {
|
||||
const allFeatures = allWeaponFeatures();
|
||||
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||
.weaponFeatures;
|
||||
const allFeatures = { ...weaponFeatures, ...homebrewFeatures };
|
||||
const all = Object.keys(allFeatures).map(key => {
|
||||
const feature = allFeatures[key];
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ export const gameSettings = {
|
|||
LastMigrationVersion: 'LastMigrationVersion',
|
||||
TagTeamRoll: 'TagTeamRoll',
|
||||
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||
CompendiumBrowserSettings: 'CompendiumBrowserSettings'
|
||||
};
|
||||
|
||||
export const actionAutomationChoices = {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
export { default as DhCombat } from './combat.mjs';
|
||||
export { default as DhCombatant } from './combatant.mjs';
|
||||
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
||||
export { default as DhRollTable } from './rollTable.mjs';
|
||||
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
||||
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
||||
|
||||
export * as countdowns from './countdowns.mjs';
|
||||
export * as actions from './action/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -34,20 +34,6 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
};
|
||||
}
|
||||
|
||||
get damageFormula() {
|
||||
const hitPointsPart = this.damage.parts.find(x => x.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
||||
if (!hitPointsPart) return '0';
|
||||
|
||||
return hitPointsPart.value.getFormula();
|
||||
}
|
||||
|
||||
get altDamageFormula() {
|
||||
const hitPointsPart = this.damage.parts.find(x => x.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
||||
if (!hitPointsPart) return '0';
|
||||
|
||||
return hitPointsPart.valueAlt.getFormula();
|
||||
}
|
||||
|
||||
async use(event, options) {
|
||||
const result = await super.use(event, options);
|
||||
if (!result.message) return;
|
||||
|
|
|
|||
|
|
@ -114,24 +114,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
* Return Item the action is attached too.
|
||||
*/
|
||||
get item() {
|
||||
if (!this.parent.parent && this.systemPath)
|
||||
return foundry.utils.getProperty(this.parent, this.systemPath).get(this.id);
|
||||
|
||||
return this.parent.parent;
|
||||
}
|
||||
|
||||
get applyEffects() {
|
||||
if (this.item.systemPath) {
|
||||
const itemEffectIds = this.item.effects.map(x => x._id);
|
||||
const movePathSplit = this.item.systemPath.split('.');
|
||||
movePathSplit.pop();
|
||||
const move = foundry.utils.getProperty(this.parent, movePathSplit.join('.'));
|
||||
return new Collection(itemEffectIds.map(id => [id, move.effects.find(x => x.id === id)]));
|
||||
}
|
||||
|
||||
return this.item.effects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first Actor parent found.
|
||||
*/
|
||||
|
|
@ -140,7 +125,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
? this.item
|
||||
: this.item?.parent instanceof DhpActor
|
||||
? this.item.parent
|
||||
: null;
|
||||
: this.item?.actor;
|
||||
}
|
||||
|
||||
static getRollType(parent) {
|
||||
|
|
@ -229,7 +214,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||
|
||||
if (this.chatDisplay && !config.actionChatMessageHandled) await this.toChat();
|
||||
if (this.chatDisplay) await this.toChat();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -240,13 +225,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
* @returns {object}
|
||||
*/
|
||||
prepareBaseConfig(event) {
|
||||
const isActor = this.item instanceof CONFIG.Actor.documentClass;
|
||||
const actionTitle = game.i18n.localize(this.name);
|
||||
const itemTitle = isActor || this.item.name === actionTitle ? '' : `${this.item.name} - `;
|
||||
|
||||
const config = {
|
||||
event,
|
||||
title: `${itemTitle}${actionTitle}`,
|
||||
title: `${this.item instanceof CONFIG.Actor.documentClass ? '' : `${this.item.name}: `}${game.i18n.localize(this.name)}`,
|
||||
source: {
|
||||
item: this.item._id,
|
||||
originItem: this.originItem,
|
||||
|
|
@ -396,14 +377,14 @@ export class ResourceUpdateMap extends Map {
|
|||
if (!resource.key) continue;
|
||||
|
||||
const existing = this.get(resource.key);
|
||||
if (!existing || resource.clear) {
|
||||
this.set(resource.key, resource);
|
||||
} else if (!existing?.clear) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
|
||||
import { ActionField } from '../fields/actionField.mjs';
|
||||
import { commonActorRules } from './base.mjs';
|
||||
import DhCreature from './creature.mjs';
|
||||
import BaseDataActor, { commonActorRules } from './base.mjs';
|
||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||
import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs';
|
||||
import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs';
|
||||
|
||||
export default class DhpAdversary extends DhCreature {
|
||||
export default class DhpAdversary extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary'];
|
||||
|
||||
static get metadata() {
|
||||
|
|
@ -43,14 +40,7 @@ export default class DhpAdversary extends DhCreature {
|
|||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.hordeHp'
|
||||
}),
|
||||
criticalThreshold: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 1,
|
||||
max: 20,
|
||||
initial: 20,
|
||||
label: 'DAGGERHEART.ACTIONS.Settings.criticalThreshold'
|
||||
}),
|
||||
criticalThreshold: new fields.NumberField({ required: true, integer: true, min: 1, max: 20, initial: 20 }),
|
||||
damageThresholds: new fields.SchemaField({
|
||||
major: new fields.NumberField({
|
||||
required: true,
|
||||
|
|
@ -190,10 +180,6 @@ export default class DhpAdversary extends DhCreature {
|
|||
}
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
this.attack.roll.isStandardAttack = true;
|
||||
}
|
||||
|
||||
_getTags() {
|
||||
const tags = [
|
||||
game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`),
|
||||
|
|
@ -202,211 +188,4 @@ export default class DhpAdversary extends DhCreature {
|
|||
];
|
||||
return tags;
|
||||
}
|
||||
|
||||
/** Returns source data for this actor adjusted to a new tier, which can be used to create a new actor. */
|
||||
adjustForTier(tier) {
|
||||
const source = this.parent.toObject(true);
|
||||
|
||||
/** @type {(2 | 3 | 4)[]} */
|
||||
const tiers = new Array(Math.abs(tier - this.tier))
|
||||
.fill(0)
|
||||
.map((_, idx) => idx + Math.min(tier, this.tier) + 1);
|
||||
if (tier < this.tier) tiers.reverse();
|
||||
const typeData = adversaryScalingData[source.system.type] ?? adversaryScalingData[source.system.standard];
|
||||
const tierEntries = tiers.map(t => ({ tier: t, ...typeData[t] }));
|
||||
|
||||
// Apply simple tier changes
|
||||
const scale = tier > this.tier ? 1 : -1;
|
||||
for (const entry of tierEntries) {
|
||||
source.system.difficulty += scale * entry.difficulty;
|
||||
source.system.damageThresholds.major += scale * entry.majorThreshold;
|
||||
source.system.damageThresholds.severe += scale * entry.severeThreshold;
|
||||
source.system.resources.hitPoints.max += scale * entry.hp;
|
||||
source.system.resources.stress.max += scale * entry.stress;
|
||||
source.system.attack.roll.bonus += scale * entry.attack;
|
||||
}
|
||||
|
||||
// Get the mean and standard deviation of expected damage in the previous and new tier
|
||||
// The data we have is for attack scaling, but we reuse this for action scaling later
|
||||
const expectedDamageData = adversaryExpectedDamage[source.system.type] ?? adversaryExpectedDamage.basic;
|
||||
const damageMeta = {
|
||||
currentDamageRange: { tier: source.system.tier, ...expectedDamageData[source.system.tier] },
|
||||
newDamageRange: { tier, ...expectedDamageData[tier] },
|
||||
type: 'attack'
|
||||
};
|
||||
|
||||
// Update damage of base attack
|
||||
try {
|
||||
this.#adjustActionDamage(source.system.attack, damageMeta);
|
||||
} catch (err) {
|
||||
ui.notifications.warn('Failed to convert attack damage of adversary');
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
// Update damage of each item action, making sure to also update the description if possible
|
||||
const damageRegex = /@Damage\[([^\[\]]*)\]({[^}]*})?/g;
|
||||
for (const item of source.items) {
|
||||
// Replace damage inlines with new formulas
|
||||
for (const withDescription of [item.system, ...Object.values(item.system.actions)]) {
|
||||
withDescription.description = withDescription.description.replace(damageRegex, (match, inner) => {
|
||||
const { value: formula } = parseInlineParams(inner);
|
||||
if (!formula || !type) return match;
|
||||
|
||||
try {
|
||||
const adjusted = this.#calculateAdjustedDamage(formula, { ...damageMeta, type: 'action' });
|
||||
const newFormula = [
|
||||
adjusted.diceQuantity ? `${adjusted.diceQuantity}d${adjusted.faces}` : null,
|
||||
adjusted.bonus
|
||||
]
|
||||
.filter(p => !!p)
|
||||
.join('+');
|
||||
return match.replace(formula, newFormula);
|
||||
} catch {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update damage in item actions
|
||||
for (const action of Object.values(item.system.actions)) {
|
||||
if (!action.damage) continue;
|
||||
|
||||
// Parse damage, and convert all formula matches in the descriptions to the new damage
|
||||
try {
|
||||
const result = this.#adjustActionDamage(action, { ...damageMeta, type: 'action' });
|
||||
for (const { previousFormula, formula } of Object.values(result)) {
|
||||
const oldFormulaRegexp = new RegExp(
|
||||
previousFormula.replace(' ', '').replace('+', '(?:\\s)?\\+(?:\\s)?')
|
||||
);
|
||||
item.system.description = item.system.description.replace(oldFormulaRegexp, formula);
|
||||
action.description = action.description.replace(oldFormulaRegexp, formula);
|
||||
}
|
||||
} catch (err) {
|
||||
ui.notifications.warn(`Failed to convert action damage for item ${item.name}`);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally set the tier of the source data, now that everything is complete
|
||||
source.system.tier = tier;
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a damage object to a new damage range
|
||||
* @returns {{ diceQuantity: number; faces: number; bonus: number }} the adjusted result as a combined term
|
||||
* @throws error if the formula is the wrong type
|
||||
*/
|
||||
#calculateAdjustedDamage(formula, { currentDamageRange, newDamageRange, type }) {
|
||||
const terms = parseTermsFromSimpleFormula(formula);
|
||||
const flatTerms = terms.filter(t => t.diceQuantity === 0);
|
||||
const diceTerms = terms.filter(t => t.diceQuantity > 0);
|
||||
if (flatTerms.length > 1 || diceTerms.length > 1) {
|
||||
throw new Error('invalid formula for conversion');
|
||||
}
|
||||
const value = {
|
||||
...(diceTerms[0] ?? { diceQuantity: 0, faces: 1 }),
|
||||
bonus: flatTerms[0]?.bonus ?? 0
|
||||
};
|
||||
const previousExpected = calculateExpectedValue(value);
|
||||
if (previousExpected === 0) return value; // nothing to do
|
||||
|
||||
const dieSizes = [4, 6, 8, 10, 12, 20];
|
||||
const steps = newDamageRange.tier - currentDamageRange.tier;
|
||||
const increasing = steps > 0;
|
||||
const deviation = (previousExpected - currentDamageRange.mean) / currentDamageRange.deviation;
|
||||
const expected = Math.max(1, newDamageRange.mean + newDamageRange.deviation * deviation);
|
||||
|
||||
// If this was just a flat number, convert to the expected damage and exit
|
||||
if (value.diceQuantity === 0) {
|
||||
value.bonus = Math.round(expected);
|
||||
return value;
|
||||
}
|
||||
|
||||
const getExpectedDie = () => calculateExpectedValue({ diceQuantity: 1, faces: value.faces }) || 1;
|
||||
const getBaseAverage = () => calculateExpectedValue({ ...value, bonus: 0 });
|
||||
|
||||
// Check the number of base overages over the expected die. In the end, if the bonus inflates too much, we add a die
|
||||
const baseOverages = Math.floor(value.bonus / getExpectedDie());
|
||||
|
||||
// Prestep. Change number of dice for attacks, bump up/down for actions
|
||||
// We never bump up to d20, though we might bump down from it
|
||||
if (type === 'attack') {
|
||||
const minimum = increasing ? value.diceQuantity : 0;
|
||||
value.diceQuantity = Math.max(minimum, newDamageRange.tier);
|
||||
} else {
|
||||
const currentIdx = dieSizes.indexOf(value.faces);
|
||||
value.faces = dieSizes[Math.clamp(currentIdx + steps, 0, 4)];
|
||||
}
|
||||
|
||||
value.bonus = Math.round(expected - getBaseAverage());
|
||||
|
||||
// Attempt to handle negative values.
|
||||
// If we can do it with only step downs, do so. Otherwise remove tier dice, and try again
|
||||
if (value.bonus < 0) {
|
||||
let stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity);
|
||||
const currentIdx = dieSizes.indexOf(value.faces);
|
||||
|
||||
// If step downs alone don't suffice, change the flat modifier, then calculate steps required again
|
||||
// If this isn't sufficient, the result will be slightly off. This is unlikely to happen
|
||||
if (type !== 'attack' && stepsRequired > currentIdx && value.diceQuantity > 0) {
|
||||
value.diceQuantity -= increasing ? 1 : Math.abs(steps);
|
||||
value.bonus = Math.round(expected - getBaseAverage());
|
||||
if (value.bonus >= 0) return value; // complete
|
||||
}
|
||||
|
||||
stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity);
|
||||
value.faces = dieSizes[Math.max(0, currentIdx - stepsRequired)];
|
||||
value.bonus = Math.max(0, Math.round(expected - getBaseAverage()));
|
||||
}
|
||||
|
||||
// If value is really high, we add a number of dice based on the number of overages
|
||||
// This attempts to preserve a similar amount of variance when increasing an action
|
||||
const overagesToRemove = Math.floor(value.bonus / getExpectedDie()) - baseOverages;
|
||||
if (type !== 'attack' && increasing && overagesToRemove > 0) {
|
||||
value.diceQuantity += overagesToRemove;
|
||||
value.bonus = Math.round(expected - getBaseAverage());
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates damage to reflect a specific value.
|
||||
* @throws if damage structure is invalid for conversion
|
||||
* @returns the converted formula and value as a simplified term
|
||||
*/
|
||||
#adjustActionDamage(action, damageMeta) {
|
||||
// The current algorithm only returns a value if there is a single damage part
|
||||
const hpDamageParts = action.damage.parts.filter(d => d.applyTo === 'hitPoints');
|
||||
if (hpDamageParts.length !== 1) throw new Error('incorrect number of hp parts');
|
||||
|
||||
const result = {};
|
||||
for (const property of ['value', 'valueAlt']) {
|
||||
const data = hpDamageParts[0][property];
|
||||
const previousFormula = data.custom.enabled
|
||||
? data.custom.formula
|
||||
: [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0]
|
||||
.filter(p => !!p)
|
||||
.join('+');
|
||||
const value = this.#calculateAdjustedDamage(previousFormula, damageMeta);
|
||||
const formula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus]
|
||||
.filter(p => !!p)
|
||||
.join('+');
|
||||
if (value.diceQuantity) {
|
||||
data.custom.enabled = false;
|
||||
data.bonus = value.bonus;
|
||||
data.dice = `d${value.faces}`;
|
||||
data.flatMultiplier = value.diceQuantity;
|
||||
} else if (!value.diceQuantity) {
|
||||
data.custom.enabled = true;
|
||||
data.custom.formula = formula;
|
||||
}
|
||||
|
||||
result[property] = { previousFormula, formula, value };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,64 +27,21 @@ const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
|
|||
});
|
||||
|
||||
/* Common rules applying to Characters and Adversaries */
|
||||
export const commonActorRules = (extendedData = { damageReduction: {}, attack: { damage: {} } }) => ({
|
||||
export const commonActorRules = (extendedData = { damageReduction: {} }) => ({
|
||||
conditionImmunities: new fields.SchemaField({
|
||||
hidden: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.hidden'
|
||||
}),
|
||||
restrained: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.restrained'
|
||||
}),
|
||||
vulnerable: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.vulnerable'
|
||||
})
|
||||
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,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.thresholdImmunities.minor.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.thresholdImmunities.minor.hint'
|
||||
})
|
||||
minor: new fields.BooleanField({ initial: false })
|
||||
}),
|
||||
reduceSeverity: new fields.SchemaField({
|
||||
magical: new fields.NumberField({
|
||||
initial: 0,
|
||||
min: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.reduceSeverity.magical.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.reduceSeverity.magical.hint'
|
||||
}),
|
||||
physical: new fields.NumberField({
|
||||
initial: 0,
|
||||
min: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.reduceSeverity.physical.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.reduceSeverity.physical.hint'
|
||||
})
|
||||
magical: new fields.NumberField({ initial: 0, min: 0 }),
|
||||
physical: new fields.NumberField({ initial: 0, min: 0 })
|
||||
}),
|
||||
...(extendedData.damageReduction ?? {})
|
||||
}),
|
||||
attack: new fields.SchemaField({
|
||||
...extendedData.attack,
|
||||
damage: new fields.SchemaField({
|
||||
hpDamageMultiplier: new fields.NumberField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.GENERAL.Attack.hpDamageMultiplier.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Attack.hpDamageMultiplier.hint'
|
||||
}),
|
||||
hpDamageTakenMultiplier: new fields.NumberField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.GENERAL.Attack.hpDamageTakenMultiplier.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Attack.hpDamageTakenMultiplier.hint'
|
||||
}),
|
||||
...(extendedData.attack?.damage ?? {})
|
||||
})
|
||||
...extendedData.damageReduction
|
||||
})
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import { burden } from '../../config/generalConfig.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import DhLevelData from '../levelData.mjs';
|
||||
import { commonActorRules } from './base.mjs';
|
||||
import DhCreature from './creature.mjs';
|
||||
import BaseDataActor, { commonActorRules } from './base.mjs';
|
||||
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
||||
import { ActionField } from '../fields/actionField.mjs';
|
||||
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
||||
|
||||
export default class DhCharacter extends DhCreature {
|
||||
export default class DhCharacter extends BaseDataActor {
|
||||
/**@override */
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
|
||||
|
||||
|
|
@ -36,18 +35,7 @@ export default class DhCharacter extends DhCreature {
|
|||
'DAGGERHEART.ACTORS.Character.maxHPBonus'
|
||||
),
|
||||
stress: resourceField(6, 0, 'DAGGERHEART.GENERAL.stress', true),
|
||||
hope: new fields.SchemaField(
|
||||
{
|
||||
value: new fields.NumberField({
|
||||
initial: 2,
|
||||
min: 0,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.hope'
|
||||
}),
|
||||
isReversed: new fields.BooleanField({ initial: false })
|
||||
},
|
||||
{ label: 'DAGGERHEART.GENERAL.hope' }
|
||||
)
|
||||
hope: resourceField(6, 2, 'DAGGERHEART.GENERAL.hope')
|
||||
}),
|
||||
traits: new fields.SchemaField({
|
||||
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
|
||||
|
|
@ -90,7 +78,12 @@ export default class DhCharacter extends DhCreature {
|
|||
bags: new fields.NumberField({ initial: 0, integer: true }),
|
||||
chests: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
scars: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.scars' }),
|
||||
scars: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
description: new fields.StringField()
|
||||
})
|
||||
),
|
||||
biography: new fields.SchemaField({
|
||||
background: new fields.HTMLField(),
|
||||
connections: new fields.HTMLField(),
|
||||
|
|
@ -132,6 +125,14 @@ export default class DhCharacter extends DhCreature {
|
|||
}
|
||||
}
|
||||
}),
|
||||
advantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.advantageSources.hint'
|
||||
}),
|
||||
disadvantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||
label: 'DAGGERHEART.ACTORS.Character.disadvantageSources.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.disadvantageSources.hint'
|
||||
}),
|
||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||
bonuses: new fields.SchemaField({
|
||||
roll: new fields.SchemaField({
|
||||
|
|
@ -218,16 +219,8 @@ export default class DhCharacter extends DhCreature {
|
|||
rules: new fields.SchemaField({
|
||||
...commonActorRules({
|
||||
damageReduction: {
|
||||
magical: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.magical.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.magical.hint'
|
||||
}),
|
||||
physical: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.physical.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.physical.hint'
|
||||
}),
|
||||
magical: new fields.BooleanField({ initial: false }),
|
||||
physical: new fields.BooleanField({ initial: false }),
|
||||
maxArmorMarked: new fields.SchemaField({
|
||||
value: new fields.NumberField({
|
||||
required: true,
|
||||
|
|
@ -257,39 +250,36 @@ export default class DhCharacter extends DhCreature {
|
|||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
||||
}),
|
||||
disabledArmor: new fields.BooleanField({
|
||||
intial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.disabledArmor.label'
|
||||
})
|
||||
},
|
||||
attack: {
|
||||
damage: {
|
||||
diceIndex: new fields.NumberField({
|
||||
integer: true,
|
||||
min: 0,
|
||||
max: 5,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.dice.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.attack.damage.dice.hint'
|
||||
}),
|
||||
bonus: new fields.NumberField({
|
||||
required: true,
|
||||
initial: 0,
|
||||
min: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.bonus.label'
|
||||
})
|
||||
},
|
||||
roll: new fields.SchemaField({
|
||||
trait: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.ACTOR.abilities,
|
||||
nullable: true,
|
||||
initial: null,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.roll.trait.label'
|
||||
})
|
||||
})
|
||||
disabledArmor: new fields.BooleanField({ intial: false })
|
||||
}
|
||||
}),
|
||||
attack: new fields.SchemaField({
|
||||
damage: new fields.SchemaField({
|
||||
diceIndex: new fields.NumberField({
|
||||
integer: true,
|
||||
min: 0,
|
||||
max: 5,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.dice.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.attack.damage.dice.hint'
|
||||
}),
|
||||
bonus: new fields.NumberField({
|
||||
required: true,
|
||||
initial: 0,
|
||||
min: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.bonus.label'
|
||||
})
|
||||
}),
|
||||
roll: new fields.SchemaField({
|
||||
trait: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.ACTOR.abilities,
|
||||
nullable: true,
|
||||
initial: null,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.roll.trait.label'
|
||||
})
|
||||
})
|
||||
}),
|
||||
dualityRoll: new fields.SchemaField({
|
||||
defaultHopeDice: new fields.NumberField({
|
||||
nullable: false,
|
||||
|
|
@ -308,14 +298,9 @@ export default class DhCharacter extends DhCreature {
|
|||
label: 'DAGGERHEART.ACTORS.Character.defaultFearDice'
|
||||
})
|
||||
}),
|
||||
runeWard: new fields.BooleanField({ initial: false }),
|
||||
burden: new fields.SchemaField({
|
||||
ignore: new fields.BooleanField({ label: 'DAGGERHEART.ACTORS.Character.burden.ignore.label' })
|
||||
}),
|
||||
roll: new fields.SchemaField({
|
||||
guaranteedCritical: new fields.BooleanField({
|
||||
label: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.hint'
|
||||
})
|
||||
ignore: new fields.BooleanField()
|
||||
})
|
||||
})
|
||||
};
|
||||
|
|
@ -378,7 +363,7 @@ export default class DhCharacter extends DhCreature {
|
|||
const modifiers = subClasses
|
||||
?.map(sc => ({ ...this.traits[sc.system.spellcastingTrait], key: sc.system.spellcastingTrait }))
|
||||
.filter(x => x);
|
||||
return modifiers.sort((a, b) => (b.value ?? 0) - (a.value ?? 0))[0];
|
||||
return modifiers.sort((a, b) => a.value - b.value)[0];
|
||||
}
|
||||
|
||||
get spellcastModifier() {
|
||||
|
|
@ -559,18 +544,7 @@ export default class DhCharacter extends DhCreature {
|
|||
}
|
||||
|
||||
get deathMoveViable() {
|
||||
const { characterDefault } = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Automation
|
||||
).defeated;
|
||||
const deathMoveOutcomeStatuses = Object.keys(CONFIG.DH.GENERAL.defeatedConditionChoices).filter(
|
||||
key => key !== characterDefault
|
||||
);
|
||||
const deathMoveNotResolved = this.parent.statuses.every(status => !deathMoveOutcomeStatuses.includes(status));
|
||||
|
||||
const allHitPointsMarked =
|
||||
this.resources.hitPoints.max > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.max;
|
||||
return deathMoveNotResolved && allHitPointsMarked;
|
||||
return this.resources.hitPoints.max > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.max;
|
||||
}
|
||||
|
||||
get armorApplicableDamageTypes() {
|
||||
|
|
@ -668,15 +642,8 @@ export default class DhCharacter extends DhCreature {
|
|||
? armor.system.baseThresholds.severe + this.levelData.level.current
|
||||
: this.levelData.level.current * 2
|
||||
};
|
||||
|
||||
const globalHopeMax = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxHope;
|
||||
this.resources.hope.max = globalHopeMax;
|
||||
this.resources.hope.max -= Object.keys(this.scars).length;
|
||||
this.resources.hitPoints.max += this.class.value?.system?.hitPoints ?? 0;
|
||||
|
||||
/* Companion Related Data */
|
||||
this.companionData = {
|
||||
levelupChoices: this.levelData.level.current - 1
|
||||
};
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
|
|
@ -694,7 +661,6 @@ export default class DhCharacter extends DhCreature {
|
|||
}
|
||||
}
|
||||
|
||||
this.resources.hope.max -= this.scars;
|
||||
this.resources.hope.value = Math.min(baseHope, this.resources.hope.max);
|
||||
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;
|
||||
|
||||
|
|
@ -733,30 +699,6 @@ export default class DhCharacter extends DhCreature {
|
|||
changes.system.experiences[experience].core = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scars can alter the amount of current hope */
|
||||
if (changes.system?.scars) {
|
||||
const diff = this.system.scars - changes.system.scars;
|
||||
const newHopeMax = this.system.resources.hope.max + diff;
|
||||
const newHopeValue = Math.min(newHopeMax, this.system.resources.hope.value);
|
||||
if (newHopeValue != this.system.resources.hope.value) {
|
||||
if (!changes.system.resources) changes.system.resources = { hope: { value: 0 } };
|
||||
changes.system.resources.hope = {
|
||||
...changes.system.resources.hope,
|
||||
value: changes.system.resources.hope.value + newHopeValue
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* Force companion data prep */
|
||||
if (this.companion) {
|
||||
if (
|
||||
changes.system?.levelData?.level?.current !== undefined &&
|
||||
changes.system.levelData.level.current !== this._source.levelData.level.current
|
||||
) {
|
||||
this.companion.update(this.companion.toObject(), { diff: false, recursive: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _preDelete() {
|
||||
|
|
@ -772,11 +714,4 @@ export default class DhCharacter extends DhCreature {
|
|||
t => !!t
|
||||
);
|
||||
}
|
||||
|
||||
static migrateData(source) {
|
||||
if (typeof source.scars === 'object') source.scars = 0;
|
||||
if (source.resources?.hope?.max) source.scars = Math.max(6 - source.resources.hope.max, 0);
|
||||
|
||||
return super.migrateData(source);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import DhCreature from './creature.mjs';
|
||||
import BaseDataActor from './base.mjs';
|
||||
import DhLevelData from '../levelData.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import { ActionField } from '../fields/actionField.mjs';
|
||||
|
|
@ -6,7 +6,7 @@ import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
|||
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||
|
||||
export default class DhCompanion extends DhCreature {
|
||||
export default class DhCompanion extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion'];
|
||||
|
||||
/**@inheritdoc */
|
||||
|
|
@ -53,18 +53,9 @@ export default class DhCompanion extends DhCreature {
|
|||
),
|
||||
rules: new fields.SchemaField({
|
||||
conditionImmunities: new fields.SchemaField({
|
||||
hidden: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.hidden'
|
||||
}),
|
||||
restrained: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.restrained'
|
||||
}),
|
||||
vulnerable: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.vulnerable'
|
||||
})
|
||||
hidden: new fields.BooleanField({ initial: false }),
|
||||
restrained: new fields.BooleanField({ initial: false }),
|
||||
vulnerable: new fields.BooleanField({ initial: false })
|
||||
})
|
||||
}),
|
||||
attack: new ActionField({
|
||||
|
|
@ -117,11 +108,7 @@ export default class DhCompanion extends DhCreature {
|
|||
get proficiency() {
|
||||
return this.partner?.system?.proficiency ?? 1;
|
||||
}
|
||||
|
||||
get canLevelUp() {
|
||||
return this.levelupChoicesLeft > 0;
|
||||
}
|
||||
|
||||
|
||||
isItemValid() {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -140,7 +127,7 @@ export default class DhCompanion extends DhCreature {
|
|||
if (selection.data[0] === 'damage') {
|
||||
this.attack.damage.parts[0].value.dice = adjustDice(this.attack.damage.parts[0].value.dice);
|
||||
} else {
|
||||
this.attack.range = adjustRange(this.attack.range).id;
|
||||
this.attack.range = adjustRange(this.attack.range);
|
||||
}
|
||||
break;
|
||||
case 'stress':
|
||||
|
|
@ -160,17 +147,6 @@ export default class DhCompanion extends DhCreature {
|
|||
}
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
/* Partner Related Setup */
|
||||
if (this.partner) {
|
||||
this.levelData.level.changed = this.partner.system.levelData.level.current;
|
||||
this.levelupChoicesLeft = Object.values(this.levelData.levelups).reduce((acc, curr) => {
|
||||
acc = Math.max(acc - curr.selections.length, 0);
|
||||
return acc;
|
||||
}, this.partner.system.companionData.levelupChoices);
|
||||
}
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, userId) {
|
||||
const allowed = await super._preUpdate(changes, options, userId);
|
||||
if (allowed === false) return;
|
||||
|
|
@ -186,16 +162,6 @@ export default class DhCompanion extends DhCreature {
|
|||
changes.system.experiences[experience].core = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Force partner data prep */
|
||||
if (this.partner) {
|
||||
if (
|
||||
changes.system?.levelData?.level?.current !== undefined &&
|
||||
changes.system.levelData.level.current !== this._source.levelData.level.current
|
||||
) {
|
||||
this.partner.update(this.partner.toObject(), { diff: false, recursive: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _preDelete() {
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
import BaseDataActor from './base.mjs';
|
||||
|
||||
export default class DhCreature extends BaseDataActor {
|
||||
/**@inheritdoc */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
advantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.advantageSources.hint'
|
||||
}),
|
||||
disadvantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||
label: 'DAGGERHEART.ACTORS.Character.disadvantageSources.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.disadvantageSources.hint'
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
get isAutoVulnerableActive() {
|
||||
const vulnerableAppliedByOther = this.parent.effects.some(
|
||||
x => x.statuses.has('vulnerable') && !x.flags.daggerheart?.autoApplyFlagId
|
||||
);
|
||||
return !vulnerableAppliedByOther;
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, userId) {
|
||||
const allowed = await super._preUpdate(changes, options, userId);
|
||||
if (allowed === false) return;
|
||||
|
||||
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||
if (
|
||||
automationSettings.vulnerableAutomation &&
|
||||
this.parent.type !== 'companion' &&
|
||||
changes.system?.resources?.stress?.value
|
||||
) {
|
||||
const { name, description, img, autoApplyFlagId } = CONFIG.DH.GENERAL.conditions().vulnerable;
|
||||
const autoEffects = this.parent.effects.filter(
|
||||
x => x.flags.daggerheart?.autoApplyFlagId === autoApplyFlagId
|
||||
);
|
||||
if (changes.system.resources.stress.value >= this.resources.stress.max) {
|
||||
if (!autoEffects.length)
|
||||
this.parent.createEmbeddedDocuments('ActiveEffect', [
|
||||
{
|
||||
name: game.i18n.localize(name),
|
||||
description: game.i18n.localize(description),
|
||||
img: img,
|
||||
statuses: ['vulnerable'],
|
||||
flags: { daggerheart: { autoApplyFlagId } }
|
||||
}
|
||||
]);
|
||||
} else if (this.resources.stress.value >= this.resources.stress.max) {
|
||||
this.parent.deleteEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
autoEffects.map(x => x.id)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ export const config = {
|
|||
adversaryRoll: DHActorRoll,
|
||||
damageRoll: DHActorRoll,
|
||||
dualityRoll: DHActorRoll,
|
||||
fateRoll: DHActorRoll,
|
||||
groupRoll: DHGroupRoll,
|
||||
systemMessage: DHSystemMessage
|
||||
};
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
static defineSchema() {
|
||||
return {
|
||||
title: new fields.StringField(),
|
||||
actionDescription: new fields.HTMLField(),
|
||||
roll: new fields.ObjectField(),
|
||||
targets: targetsField(),
|
||||
hasRoll: new fields.BooleanField({ initial: false }),
|
||||
|
|
|
|||
|
|
@ -1,370 +0,0 @@
|
|||
import { abilities } from '../config/actorConfig.mjs';
|
||||
import { chunkify } from '../helpers/utils.mjs';
|
||||
import { LevelOptionType } from './levelTier.mjs';
|
||||
|
||||
export class DhCompanionLevelup extends foundry.abstract.DataModel {
|
||||
static initializeData(levelTierData, pcLevelData, origChoicesLeft) {
|
||||
let choicesLeft = origChoicesLeft;
|
||||
|
||||
const { current, changed } = pcLevelData.level;
|
||||
const bonusChoicesOnly = current === changed;
|
||||
const startLevel = bonusChoicesOnly ? current : current + 1;
|
||||
const endLevel = bonusChoicesOnly ? startLevel : changed;
|
||||
|
||||
const tiers = {};
|
||||
const levels = {};
|
||||
const tierKeys = Object.keys(levelTierData.tiers);
|
||||
tierKeys.forEach(key => {
|
||||
const tier = levelTierData.tiers[key];
|
||||
const belongingLevels = [];
|
||||
for (var i = tier.levels.start; i <= tier.levels.end; i++) {
|
||||
if (i <= endLevel) {
|
||||
const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {};
|
||||
const experiences = initialAchievements.experience
|
||||
? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => {
|
||||
acc[foundry.utils.randomID()] = {
|
||||
name: '',
|
||||
modifier: initialAchievements.experience.modifier
|
||||
};
|
||||
return acc;
|
||||
}, {})
|
||||
: {};
|
||||
|
||||
const currentChoices = pcLevelData.levelups[i]?.selections?.length;
|
||||
const maxSelections =
|
||||
i === endLevel
|
||||
? choicesLeft + (currentChoices ?? 0)
|
||||
: (currentChoices ?? tier.maxSelections[i]);
|
||||
if (!pcLevelData.levelups[i]) choicesLeft -= maxSelections;
|
||||
|
||||
levels[i] = DhLevelupLevel.initializeData(pcLevelData.levelups[i], maxSelections, {
|
||||
...initialAchievements,
|
||||
experiences,
|
||||
domainCards: {}
|
||||
});
|
||||
}
|
||||
|
||||
belongingLevels.push(i);
|
||||
}
|
||||
|
||||
/* Improve. Temporary handling for Companion new experiences */
|
||||
Object.keys(tier.extraAchievements ?? {}).forEach(key => {
|
||||
const level = Number(key);
|
||||
if (level >= startLevel && level <= endLevel) {
|
||||
const levelExtras = tier.extraAchievements[level];
|
||||
if (levelExtras.experience) {
|
||||
levels[level].achievements.experiences[foundry.utils.randomID()] = {
|
||||
name: '',
|
||||
modifier: levelExtras.experience.modifier
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tiers[key] = {
|
||||
name: tier.name,
|
||||
belongingLevels: belongingLevels,
|
||||
options: Object.keys(tier.options).reduce((acc, key) => {
|
||||
acc[key] = tier.options[key].toObject?.() ?? tier.options[key];
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
tiers,
|
||||
levels,
|
||||
startLevel,
|
||||
currentLevel: startLevel,
|
||||
endLevel
|
||||
};
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
tiers: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
belongingLevels: new fields.ArrayField(new fields.NumberField({ required: true, integer: true })),
|
||||
options: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
label: new fields.StringField({ required: true }),
|
||||
checkboxSelections: new fields.NumberField({ required: true, integer: true }),
|
||||
minCost: new fields.NumberField({ required: true, integer: true }),
|
||||
type: new fields.StringField({ required: true, choices: LevelOptionType }),
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
amount: new fields.NumberField({ integer: true })
|
||||
})
|
||||
)
|
||||
})
|
||||
),
|
||||
levels: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupLevel)),
|
||||
startLevel: new fields.NumberField({ required: true, integer: true }),
|
||||
currentLevel: new fields.NumberField({ required: true, integer: true }),
|
||||
endLevel: new fields.NumberField({ required: true, integer: true })
|
||||
};
|
||||
}
|
||||
|
||||
#levelFinished(levelKey) {
|
||||
const allSelectionsMade = this.levels[levelKey].nrSelections.available === 0;
|
||||
const allChoicesMade = Object.keys(this.levels[levelKey].choices).every(choiceKey => {
|
||||
const choice = this.levels[levelKey].choices[choiceKey];
|
||||
return Object.values(choice).every(checkbox => {
|
||||
switch (choiceKey) {
|
||||
case 'trait':
|
||||
case 'experience':
|
||||
case 'domainCard':
|
||||
case 'subclass':
|
||||
case 'vicious':
|
||||
return checkbox.data.length === (checkbox.amount ?? 1);
|
||||
case 'multiclass':
|
||||
const classSelected = checkbox.data.length === 1;
|
||||
const domainSelected = checkbox.secondaryData.domain;
|
||||
const subclassSelected = checkbox.secondaryData.subclass;
|
||||
return classSelected && domainSelected && subclassSelected;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
const experiencesSelected = !this.levels[levelKey].achievements.experiences
|
||||
? true
|
||||
: Object.values(this.levels[levelKey].achievements.experiences).every(exp => exp.name);
|
||||
const domainCardsSelected = Object.values(this.levels[levelKey].achievements.domainCards)
|
||||
.filter(x => x.level <= this.endLevel)
|
||||
.every(card => card.uuid);
|
||||
const allAchievementsSelected = experiencesSelected && domainCardsSelected;
|
||||
|
||||
return allSelectionsMade && allChoicesMade && allAchievementsSelected;
|
||||
}
|
||||
|
||||
get currentLevelFinished() {
|
||||
return this.#levelFinished(this.currentLevel);
|
||||
}
|
||||
|
||||
get allLevelsFinished() {
|
||||
return Object.keys(this.levels)
|
||||
.filter(level => Number(level) >= this.startLevel)
|
||||
.every(this.#levelFinished.bind(this));
|
||||
}
|
||||
|
||||
get unmarkedTraits() {
|
||||
const possibleLevels = Object.values(this.tiers).reduce((acc, tier) => {
|
||||
if (tier.belongingLevels.includes(this.currentLevel)) acc = tier.belongingLevels;
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return Object.keys(this.levels)
|
||||
.filter(key => possibleLevels.some(x => x === Number(key)))
|
||||
.reduce(
|
||||
(acc, levelKey) => {
|
||||
const level = this.levels[levelKey];
|
||||
Object.values(level.choices).forEach(choice =>
|
||||
Object.values(choice).forEach(checkbox => {
|
||||
if (
|
||||
checkbox.type === 'trait' &&
|
||||
checkbox.data.length > 0 &&
|
||||
Number(levelKey) !== this.currentLevel
|
||||
) {
|
||||
checkbox.data.forEach(data => delete acc[data]);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ ...abilities }
|
||||
);
|
||||
}
|
||||
|
||||
get classUpgradeChoices() {
|
||||
let subclasses = [];
|
||||
let multiclass = null;
|
||||
Object.keys(this.levels).forEach(levelKey => {
|
||||
const level = this.levels[levelKey];
|
||||
Object.values(level.choices).forEach(choice => {
|
||||
Object.values(choice).forEach(checkbox => {
|
||||
if (checkbox.type === 'multiclass') {
|
||||
multiclass = {
|
||||
class: checkbox.data.length > 0 ? checkbox.data[0] : null,
|
||||
domain: checkbox.secondaryData.domain ?? null,
|
||||
subclass: checkbox.secondaryData.subclass ?? null,
|
||||
tier: checkbox.tier,
|
||||
level: levelKey
|
||||
};
|
||||
}
|
||||
if (checkbox.type === 'subclass') {
|
||||
subclasses.push({
|
||||
tier: checkbox.tier,
|
||||
level: levelKey
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
return { subclasses, multiclass };
|
||||
}
|
||||
|
||||
get tiersForRendering() {
|
||||
const tierKeys = Object.keys(this.tiers);
|
||||
const selections = Object.keys(this.levels).reduce(
|
||||
(acc, key) => {
|
||||
const level = this.levels[key];
|
||||
Object.keys(level.choices).forEach(optionKey => {
|
||||
const choice = level.choices[optionKey];
|
||||
Object.keys(choice).forEach(checkboxNr => {
|
||||
const checkbox = choice[checkboxNr];
|
||||
if (!acc[checkbox.tier][optionKey]) acc[checkbox.tier][optionKey] = {};
|
||||
Object.keys(choice).forEach(checkboxNr => {
|
||||
acc[checkbox.tier][optionKey][checkboxNr] = { ...checkbox, level: Number(key) };
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
tierKeys.reduce((acc, key) => {
|
||||
acc[key] = {};
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
const { multiclass, subclasses } = this.classUpgradeChoices;
|
||||
return tierKeys.map((tierKey, tierIndex) => {
|
||||
const tier = this.tiers[tierKey];
|
||||
const multiclassInTier = multiclass?.tier === Number(tierKey);
|
||||
const subclassInTier = subclasses.some(x => x.tier === Number(tierKey));
|
||||
|
||||
return {
|
||||
name: game.i18n.localize(tier.name),
|
||||
active: this.currentLevel >= Math.min(...tier.belongingLevels),
|
||||
groups: Object.keys(tier.options).map(optionKey => {
|
||||
const option = tier.options[optionKey];
|
||||
|
||||
const checkboxes = [...Array(option.checkboxSelections).keys()].flatMap(index => {
|
||||
const checkboxNr = index + 1;
|
||||
const checkboxData = selections[tierKey]?.[optionKey]?.[checkboxNr];
|
||||
const checkbox = { ...option, checkboxNr, tier: tierKey };
|
||||
|
||||
if (checkboxData) {
|
||||
checkbox.level = checkboxData.level;
|
||||
checkbox.selected = true;
|
||||
checkbox.disabled = checkbox.level !== this.currentLevel;
|
||||
}
|
||||
|
||||
if (optionKey === 'multiclass') {
|
||||
if ((multiclass && !multiclassInTier) || subclassInTier) {
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (optionKey === 'subclass' && multiclassInTier) {
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
|
||||
return checkbox;
|
||||
});
|
||||
|
||||
let label = game.i18n.localize(option.label);
|
||||
if (optionKey === 'domainCard') {
|
||||
const maxLevel = tier.belongingLevels[tier.belongingLevels.length - 1];
|
||||
label = game.i18n.format(option.label, { maxLevel });
|
||||
}
|
||||
|
||||
return {
|
||||
label: label,
|
||||
checkboxGroups: chunkify(checkboxes, option.minCost, chunkedBoxes => {
|
||||
const anySelected = chunkedBoxes.some(x => x.selected);
|
||||
const anyDisabled = chunkedBoxes.some(x => x.disabled);
|
||||
return {
|
||||
multi: option.minCost > 1,
|
||||
checkboxes: chunkedBoxes.map(x => ({
|
||||
...x,
|
||||
selected: anySelected,
|
||||
disabled: anyDisabled
|
||||
}))
|
||||
};
|
||||
})
|
||||
};
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class DhLevelupLevel extends foundry.abstract.DataModel {
|
||||
static initializeData(levelData = { selections: [] }, maxSelections, achievements) {
|
||||
return {
|
||||
maxSelections: maxSelections,
|
||||
achievements: {
|
||||
experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {},
|
||||
domainCards: levelData.achievements?.domainCards
|
||||
? levelData.achievements.domainCards.reduce((acc, card, index) => {
|
||||
acc[index] = { ...card };
|
||||
return acc;
|
||||
}, {})
|
||||
: (achievements.domainCards ?? {}),
|
||||
proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null
|
||||
},
|
||||
choices: levelData.selections.reduce((acc, data) => {
|
||||
if (!acc[data.optionKey]) acc[data.optionKey] = {};
|
||||
acc[data.optionKey][data.checkboxNr] = { ...data };
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
maxSelections: new fields.NumberField({ required: true, integer: true }),
|
||||
achievements: new fields.SchemaField({
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
modifier: new fields.NumberField({ required: true, integer: true })
|
||||
})
|
||||
),
|
||||
domainCards: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
uuid: new fields.StringField({ required: true, nullable: true, initial: null }),
|
||||
itemUuid: new fields.StringField({ required: true }),
|
||||
level: new fields.NumberField({ required: true, integer: true })
|
||||
})
|
||||
),
|
||||
proficiency: new fields.NumberField({ integer: true })
|
||||
}),
|
||||
choices: new fields.TypedObjectField(
|
||||
new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
tier: new fields.NumberField({ required: true, integer: true }),
|
||||
minCost: new fields.NumberField({ required: true, integer: true }),
|
||||
amount: new fields.NumberField({ integer: true }),
|
||||
value: new fields.StringField(),
|
||||
data: new fields.ArrayField(new fields.StringField()),
|
||||
secondaryData: new fields.TypedObjectField(new fields.StringField()),
|
||||
type: new fields.StringField({ required: true })
|
||||
})
|
||||
)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
get nrSelections() {
|
||||
const selections = Object.keys(this.choices).reduce((acc, choiceKey) => {
|
||||
const choice = this.choices[choiceKey];
|
||||
acc += Object.values(choice).reduce((acc, x) => acc + x.minCost, 0);
|
||||
|
||||
return acc;
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
selections: selections,
|
||||
available: this.maxSelections - selections
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
export default class CompendiumBrowserSettings extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
excludedSources: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
excludedDocumentTypes: new fields.ArrayField(
|
||||
new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES })
|
||||
)
|
||||
})
|
||||
),
|
||||
excludedPacks: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
excludedDocumentTypes: new fields.ArrayField(
|
||||
new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES })
|
||||
)
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
isEntryExcluded(item) {
|
||||
const pack = game.packs.get(item.pack);
|
||||
if (!pack) return false;
|
||||
|
||||
const packageName = pack.metadata.packageType === 'world' ? 'world' : pack.metadata.packageName;
|
||||
const excludedSourceData = this.excludedSources[packageName];
|
||||
if (excludedSourceData && excludedSourceData.excludedDocumentTypes.includes(pack.metadata.type)) return true;
|
||||
|
||||
const excludedPackData = this.excludedPacks[item.pack];
|
||||
if (excludedPackData && excludedPackData.excludedDocumentTypes.includes(pack.metadata.type)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -68,8 +68,6 @@ export default class DamageField extends fields.SchemaField {
|
|||
|
||||
const damageResult = await CONFIG.Dice.daggerheart.DamageRoll.build(damageConfig);
|
||||
if (!damageResult) return false;
|
||||
if (damageResult.actionChatMessageHandled) config.actionChatMessageHandled = true;
|
||||
|
||||
config.damage = damageResult.damage;
|
||||
config.message ??= damageConfig.message;
|
||||
}
|
||||
|
|
@ -107,22 +105,12 @@ export default class DamageField extends fields.SchemaField {
|
|||
damagePromises.push(
|
||||
actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates }))
|
||||
);
|
||||
else {
|
||||
const configDamage = foundry.utils.deepClone(config.damage);
|
||||
const hpDamageMultiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1;
|
||||
const hpDamageTakenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier;
|
||||
if (configDamage.hitPoints) {
|
||||
for (const part of configDamage.hitPoints.parts) {
|
||||
part.total = Math.ceil(part.total * hpDamageMultiplier * hpDamageTakenMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
damagePromises.push(
|
||||
actor
|
||||
.takeDamage(configDamage, config.isDirect)
|
||||
.takeDamage(config.damage, config.isDirect)
|
||||
.then(updates => targetDamage.push({ token, updates }))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(damagePromises).then(async _ => {
|
||||
|
|
@ -165,8 +153,7 @@ export default class DamageField extends fields.SchemaField {
|
|||
if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt;
|
||||
|
||||
const isAdversary = this.actor.type === 'adversary';
|
||||
const isHorde = this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id;
|
||||
if (isAdversary && isHorde && this.roll?.isStandardAttack) {
|
||||
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
|
||||
const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde');
|
||||
if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export default class EffectsField extends fields.ArrayField {
|
|||
});
|
||||
|
||||
effects.forEach(async e => {
|
||||
const effect = (this.item.applyEffects ?? this.item.effects).get(e._id);
|
||||
const effect = this.item.effects.get(e._id);
|
||||
if (!token.actor || !effect) return;
|
||||
await EffectsField.applyEffect(effect, token.actor);
|
||||
});
|
||||
|
|
@ -96,7 +96,7 @@ export default class EffectsField extends fields.ArrayField {
|
|||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/effectSummary.hbs',
|
||||
{
|
||||
effects: this.effects.map(e => (this.item.applyEffects ?? this.item.effects).get(e._id)),
|
||||
effects: this.effects.map(e => this.item.effects.get(e._id)),
|
||||
targets: messageTargets
|
||||
}
|
||||
)
|
||||
|
|
@ -123,7 +123,7 @@ export default class EffectsField extends fields.ArrayField {
|
|||
|
||||
// Otherwise, create a new effect on the target
|
||||
const effectData = foundry.utils.mergeObject({
|
||||
...(effect.toObject?.() ?? effect),
|
||||
...effect.toObject(),
|
||||
disabled: false,
|
||||
transfer: false,
|
||||
origin: effect.uuid
|
||||
|
|
|
|||
|
|
@ -152,9 +152,7 @@ export function ActionMixin(Base) {
|
|||
}
|
||||
|
||||
get uuid() {
|
||||
const isItem = this.item instanceof game.system.api.documents.DHItem;
|
||||
const isActor = this.item instanceof game.system.api.documents.DhpActor;
|
||||
return isItem || isActor ? `${this.item.uuid}.${this.documentName}.${this.id}` : null;
|
||||
return `${this.item.uuid}.${this.documentName}.${this.id}`;
|
||||
}
|
||||
|
||||
get sheet() {
|
||||
|
|
@ -262,9 +260,6 @@ export function ActionMixin(Base) {
|
|||
}
|
||||
|
||||
async toChat(origin) {
|
||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
||||
.expandRollMessage?.desc;
|
||||
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
const systemData = {
|
||||
title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'),
|
||||
|
|
@ -293,7 +288,7 @@ export function ActionMixin(Base) {
|
|||
system: systemData,
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/action.hbs',
|
||||
{ ...systemData, open: autoExpandDescription ? 'open' : '' }
|
||||
systemData
|
||||
),
|
||||
flags: {
|
||||
daggerheart: {
|
||||
|
|
|
|||
|
|
@ -7,20 +7,16 @@ const attributeField = label =>
|
|||
});
|
||||
|
||||
const resourceField = (max = 0, initial = 0, label, reverse = false, maxLabel) =>
|
||||
new fields.SchemaField(
|
||||
{
|
||||
value: new fields.NumberField({ initial: initial, min: 0, integer: true, label }),
|
||||
max: new fields.NumberField({
|
||||
initial: max,
|
||||
integer: true,
|
||||
label:
|
||||
maxLabel ??
|
||||
game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', { thing: game.i18n.localize(label) })
|
||||
}),
|
||||
isReversed: new fields.BooleanField({ initial: reverse })
|
||||
},
|
||||
{ label }
|
||||
);
|
||||
new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: initial, min: 0, integer: true, label }),
|
||||
max: new fields.NumberField({
|
||||
initial: max,
|
||||
integer: true,
|
||||
label:
|
||||
maxLabel ?? game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', { thing: game.i18n.localize(label) })
|
||||
}),
|
||||
isReversed: new fields.BooleanField({ initial: reverse })
|
||||
});
|
||||
|
||||
const stressDamageReductionRule = localizationPath =>
|
||||
new fields.SchemaField({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import BaseDataItem from './base.mjs';
|
||||
import ItemLinkFields from '../../data/fields/itemLinkFields.mjs';
|
||||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
|
||||
export default class DHAncestry extends BaseDataItem {
|
||||
/** @inheritDoc */
|
||||
|
|
@ -20,6 +19,7 @@ export default class DHAncestry extends BaseDataItem {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**@override */
|
||||
|
|
@ -42,18 +42,4 @@ export default class DHAncestry extends BaseDataItem {
|
|||
get secondaryFeature() {
|
||||
return this.features.find(x => x.type === CONFIG.DH.ITEM.featureSubTypes.secondary)?.item;
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
const features = await getFeaturesHTMLData(this.features);
|
||||
|
||||
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||
const suffix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/description.hbs',
|
||||
{ label: 'DAGGERHEART.ITEMS.Ancestry.featuresLabel', features }
|
||||
);
|
||||
|
||||
return { prefix: null, value: baseDescription, suffix };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ export default class DHArmor extends AttachableItem {
|
|||
armorFeatures: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true
|
||||
required: true,
|
||||
choices: CONFIG.DH.ITEM.allArmorFeatures,
|
||||
blank: true
|
||||
}),
|
||||
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
|
||||
|
|
@ -56,11 +58,12 @@ export default class DHArmor extends AttachableItem {
|
|||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
|
||||
const features = this.armorFeatures.map(x => allFeatures[x.value]).filter(x => x);
|
||||
const features = this.armorFeatures.map(x => allFeatures[x.value]);
|
||||
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||
|
||||
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/armor/description.hbs',
|
||||
{ item: this.parent, features }
|
||||
{ features }
|
||||
);
|
||||
|
||||
return { prefix, value: baseDescription, suffix: null };
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
return await foundry.applications.ux.TextEditor.implementation.enrichHTML(fullDescription, {
|
||||
relativeTo: this,
|
||||
rollData: this.getRollData(),
|
||||
secrets: this.parent.isOwner
|
||||
secrets: this.isOwner
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -253,20 +253,4 @@ export default class DHBeastform extends BaseDataItem {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
_onCreate(_data, _options, userId) {
|
||||
if (!this.actor && game.user.id === userId) {
|
||||
const hasBeastformEffect = this.parent.effects.some(x => x.type === 'beastform');
|
||||
if (!hasBeastformEffect)
|
||||
this.parent.createEmbeddedDocuments('ActiveEffect', [
|
||||
{
|
||||
type: 'beastform',
|
||||
name: game.i18n.localize('DAGGERHEART.ITEMS.Beastform.beastformEffect'),
|
||||
img: 'icons/creatures/abilities/paw-print-pair-purple.webp'
|
||||
}
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import BaseDataItem from './base.mjs';
|
|||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
import ItemLinkFields from '../fields/itemLinkFields.mjs';
|
||||
import { addLinkedItemsDiff, getFeaturesHTMLData, updateLinkedItemApps } from '../../helpers/utils.mjs';
|
||||
import { addLinkedItemsDiff, updateLinkedItemApps } from '../../helpers/utils.mjs';
|
||||
|
||||
export default class DHClass extends BaseDataItem {
|
||||
/** @inheritDoc */
|
||||
|
|
@ -163,56 +163,4 @@ export default class DHClass extends BaseDataItem {
|
|||
|
||||
updateLinkedItemApps(options, this.parent.sheet);
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
|
||||
const getDomainLabel = domain => {
|
||||
const data = CONFIG.DH.DOMAIN.allDomains()[domain];
|
||||
return data ? game.i18n.localize(data.label) : '';
|
||||
};
|
||||
let domainsLabel = '';
|
||||
if (this.domains.length) {
|
||||
if (this.domains.length === 1) domainsLabel = getDomainLabel(this.domains[0]);
|
||||
else {
|
||||
const firstDomains = this.domains
|
||||
.slice(0, this.domains.length - 1)
|
||||
.map(getDomainLabel)
|
||||
.join(', ');
|
||||
const lastDomain = getDomainLabel(this.domains[this.domains.length - 1]);
|
||||
domainsLabel = game.i18n.format('DAGGERHEART.GENERAL.thingsAndThing', {
|
||||
things: firstDomains,
|
||||
thing: lastDomain
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const classItems = [];
|
||||
for (const itemData of this.inventory.choiceB) {
|
||||
const linkData = [
|
||||
undefined,
|
||||
'UUID', // type
|
||||
itemData.uuid // target
|
||||
];
|
||||
const contentLink = await foundry.applications.ux.TextEditor.implementation._createContentLink(linkData);
|
||||
classItems.push(contentLink.outerHTML);
|
||||
}
|
||||
|
||||
const hopeFeatures = await getFeaturesHTMLData(this.hopeFeatures);
|
||||
const classFeatures = await getFeaturesHTMLData(this.classFeatures);
|
||||
|
||||
const suffix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/class/description.hbs',
|
||||
{
|
||||
class: this.parent,
|
||||
domains: domainsLabel,
|
||||
classItems,
|
||||
hopeFeatures,
|
||||
classFeatures
|
||||
}
|
||||
);
|
||||
|
||||
return { prefix: null, value: baseDescription, suffix };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
import BaseDataItem from './base.mjs';
|
||||
|
||||
|
|
@ -25,17 +24,4 @@ export default class DHCommunity extends BaseDataItem {
|
|||
/**@override */
|
||||
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/village.svg';
|
||||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
const features = await getFeaturesHTMLData(this.features);
|
||||
|
||||
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||
const suffix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/description.hbs',
|
||||
{ label: 'DAGGERHEART.ITEMS.Community.featuresLabel', features }
|
||||
);
|
||||
|
||||
return { prefix: null, value: baseDescription, suffix };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,10 +94,8 @@ export default class DHDomainCard extends BaseDataItem {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!this.actor.system.loadoutSlot.available && !this.loadoutIgnore) {
|
||||
if (!this.actor.system.loadoutSlot.available) {
|
||||
data.system.inVault = true;
|
||||
await this.updateSource({ inVault: true });
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import ItemLinkFields from '../fields/itemLinkFields.mjs';
|
||||
import BaseDataItem from './base.mjs';
|
||||
|
|
@ -90,28 +89,4 @@ export default class DHSubclass extends BaseDataItem {
|
|||
const allowed = await super._preCreate(data, options, user);
|
||||
if (allowed === false) return;
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
|
||||
const spellcastTrait = this.spellcastingTrait
|
||||
? game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.spellcastingTrait].label)
|
||||
: null;
|
||||
const foundationFeatures = await getFeaturesHTMLData(this.foundationFeatures);
|
||||
const specializationFeatures = await getFeaturesHTMLData(this.specializationFeatures);
|
||||
const masteryFeatures = await getFeaturesHTMLData(this.masteryFeatures);
|
||||
|
||||
const suffix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/subclass/description.hbs',
|
||||
{
|
||||
spellcastTrait,
|
||||
foundationFeatures,
|
||||
specializationFeatures,
|
||||
masteryFeatures
|
||||
}
|
||||
);
|
||||
|
||||
return { prefix: null, value: baseDescription, suffix };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ export default class DHWeapon extends AttachableItem {
|
|||
weaponFeatures: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true
|
||||
required: true,
|
||||
choices: CONFIG.DH.ITEM.allWeaponFeatures,
|
||||
blank: true
|
||||
}),
|
||||
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
|
||||
|
|
@ -111,26 +113,13 @@ export default class DHWeapon extends AttachableItem {
|
|||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
|
||||
const tier = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`);
|
||||
const trait = game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.attack.roll.trait].label);
|
||||
const range = game.i18n.localize(`DAGGERHEART.CONFIG.Range.${this.attack.range}.name`);
|
||||
const damage = Roll.replaceFormulaData(this.attack.damageFormula, this.parent.parent ?? this.parent);
|
||||
const burden = game.i18n.localize(CONFIG.DH.GENERAL.burden[this.burden].label);
|
||||
|
||||
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
|
||||
const features = this.weaponFeatures.map(x => allFeatures[x.value]).filter(x => x);
|
||||
const features = this.weaponFeatures.map(x => allFeatures[x.value]);
|
||||
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||
|
||||
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/weapon/description.hbs',
|
||||
{
|
||||
features,
|
||||
tier,
|
||||
trait,
|
||||
range,
|
||||
damage,
|
||||
burden
|
||||
}
|
||||
{ features }
|
||||
);
|
||||
|
||||
return { prefix, value: baseDescription, suffix: null };
|
||||
|
|
|
|||
|
|
@ -6,12 +6,7 @@ export default class DhLevelData extends foundry.abstract.DataModel {
|
|||
|
||||
return {
|
||||
level: new fields.SchemaField({
|
||||
current: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.GENERAL.currentLevel'
|
||||
}),
|
||||
current: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
changed: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
bonuses: new fields.TypedObjectField(new fields.NumberField({ integer: true, nullable: false }))
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ export default class RegisteredTriggers extends Map {
|
|||
}
|
||||
|
||||
registerItemTriggers(item, registerOverride) {
|
||||
if (!item.actor || !item._stats.createdTime) return;
|
||||
for (const action of item.system.actions ?? []) {
|
||||
if (!action.actor) continue;
|
||||
|
||||
|
|
@ -72,21 +71,10 @@ export default class RegisteredTriggers extends Map {
|
|||
}
|
||||
}
|
||||
|
||||
unregisterSceneEnvironmentTriggers(flagSystemData) {
|
||||
const sceneData = new game.system.api.data.scenes.DHScene(flagSystemData);
|
||||
for (const environment of sceneData.sceneEnvironments) {
|
||||
if (!environment || environment.pack) continue;
|
||||
this.unregisterItemTriggers(environment.system.features);
|
||||
}
|
||||
}
|
||||
|
||||
unregisterSceneTriggers(scene) {
|
||||
this.unregisterSceneEnvironmentTriggers(scene.flags.daggerheart);
|
||||
|
||||
for (const triggerKey of Object.keys(CONFIG.DH.TRIGGER.triggers)) {
|
||||
const existingTrigger = this.get(triggerKey);
|
||||
if (!existingTrigger) continue;
|
||||
|
||||
const filtered = new Map();
|
||||
for (const [uuid, data] of existingTrigger.entries()) {
|
||||
if (!uuid.startsWith(scene.uuid)) filtered.set(uuid, data);
|
||||
|
|
@ -95,17 +83,14 @@ export default class RegisteredTriggers extends Map {
|
|||
}
|
||||
}
|
||||
|
||||
registerSceneEnvironmentTriggers(flagSystemData) {
|
||||
const sceneData = new game.system.api.data.scenes.DHScene(flagSystemData);
|
||||
for (const environment of sceneData.sceneEnvironments) {
|
||||
for (const feature of environment.system.features) {
|
||||
if (feature) this.registerItemTriggers(feature, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerSceneTriggers(scene) {
|
||||
this.registerSceneEnvironmentTriggers(scene.flags.daggerheart);
|
||||
/* TODO: Finish sceneEnvironment registration and unreg */
|
||||
// const systemData = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart);
|
||||
// for (const environment of systemData.sceneEnvironments) {
|
||||
// for (const feature of environment.system.features) {
|
||||
// if(feature) this.registerItemTriggers(feature, true);
|
||||
// }
|
||||
// }
|
||||
|
||||
for (const actor of scene.tokens.filter(x => x.actor).map(x => x.actor)) {
|
||||
if (actor.prototypeToken.actorLink) continue;
|
||||
|
|
@ -122,11 +107,13 @@ export default class RegisteredTriggers extends Map {
|
|||
if (!triggerSettings.enabled) return updates;
|
||||
|
||||
const dualityTrigger = this.get(trigger);
|
||||
if (dualityTrigger?.size) {
|
||||
const triggerActors = ['character', 'adversary', 'environment'];
|
||||
if (dualityTrigger) {
|
||||
const tokenBoundActors = ['adversary', 'environment'];
|
||||
const triggerActors = ['character', ...tokenBoundActors];
|
||||
for (let [itemUuid, { actor: actorUuid, triggeringActorType, commands }] of dualityTrigger.entries()) {
|
||||
const actor = await foundry.utils.fromUuid(actorUuid);
|
||||
if (!actor || !triggerActors.includes(actor.type)) continue;
|
||||
if (tokenBoundActors.includes(actor.type) && !actor.getActiveTokens().length) continue;
|
||||
|
||||
const triggerData = CONFIG.DH.TRIGGER.triggers[trigger];
|
||||
if (triggerData.usesActor && triggeringActorType !== 'any') {
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import FormulaField from './fields/formulaField.mjs';
|
||||
|
||||
//Extra definitions for RollTable
|
||||
export default class DhRollTable extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
formulaName: new fields.StringField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: 'Roll Formula',
|
||||
label: 'DAGGERHEART.ROLLTABLES.FIELDS.formulaName.label'
|
||||
}),
|
||||
altFormula: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: 'Roll Formula',
|
||||
label: 'DAGGERHEART.ROLLTABLES.FIELDS.formulaName.label'
|
||||
}),
|
||||
formula: new FormulaField({ label: 'Formula Roll', initial: '1d20' })
|
||||
})
|
||||
),
|
||||
activeAltFormula: new fields.StringField({ nullable: true, initial: null })
|
||||
};
|
||||
}
|
||||
|
||||
getActiveFormula(baseFormula) {
|
||||
return this.activeAltFormula ? (this.altFormula[this.activeAltFormula]?.formula ?? baseFormula) : baseFormula;
|
||||
}
|
||||
|
||||
static getDefaultFormula = () => ({
|
||||
name: game.i18n.localize('Roll Formula'),
|
||||
formula: '1d20'
|
||||
});
|
||||
}
|
||||
|
|
@ -37,30 +37,11 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
|||
extendEnvironmentDescriptions: new BooleanField(),
|
||||
extendItemDescriptions: new BooleanField(),
|
||||
expandRollMessage: new SchemaField({
|
||||
desc: new BooleanField({ initial: true }),
|
||||
desc: new BooleanField(),
|
||||
roll: new BooleanField(),
|
||||
damage: new BooleanField(),
|
||||
target: new BooleanField()
|
||||
}),
|
||||
showTokenDistance: new StringField({
|
||||
required: true,
|
||||
choices: {
|
||||
always: {
|
||||
value: 'always',
|
||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.always'
|
||||
},
|
||||
encounters: {
|
||||
value: 'encounters',
|
||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.encounters'
|
||||
},
|
||||
never: {
|
||||
value: 'never',
|
||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.never'
|
||||
}
|
||||
},
|
||||
nullable: false,
|
||||
initial: 'always'
|
||||
}),
|
||||
hideAttribution: new BooleanField(),
|
||||
showGenericStatusEffects: new BooleanField({ initial: true })
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
|
||||
})
|
||||
}),
|
||||
vulnerableAutomation: new fields.BooleanField({
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.vulnerableAutomation.label'
|
||||
}),
|
||||
countdownAutomation: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: true,
|
||||
|
|
@ -59,27 +55,15 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
initial: true,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.resourceScrollTexts.label'
|
||||
}),
|
||||
deathMoveAutomation: new fields.SchemaField({
|
||||
avoidDeath: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.CONFIG.DeathMoves.avoidDeath.name'
|
||||
}),
|
||||
riskItAll: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.CONFIG.DeathMoves.riskItAll.name'
|
||||
}),
|
||||
blazeOfGlory: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name'
|
||||
})
|
||||
playerCanEditSheet: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.playerCanEditSheet.label'
|
||||
}),
|
||||
defeated: new fields.SchemaField({
|
||||
enabled: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.enabled.label'
|
||||
}),
|
||||
overlay: new fields.BooleanField({
|
||||
|
|
@ -90,7 +74,7 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
characterDefault: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.defeatedConditionChoices,
|
||||
initial: CONFIG.DH.GENERAL.defeatedConditionChoices.deathMove.id,
|
||||
initial: CONFIG.DH.GENERAL.defeatedConditionChoices.unconscious.id,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.characterDefault.label'
|
||||
}),
|
||||
adversaryDefault: new fields.StringField({
|
||||
|
|
@ -105,29 +89,23 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
initial: CONFIG.DH.GENERAL.defeatedConditionChoices.defeated.id,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.companionDefault.label'
|
||||
}),
|
||||
deathMoveIcon: new fields.FilePathField({
|
||||
initial: 'icons/magic/life/heart-cross-purple-orange.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.deathMove.label'
|
||||
}),
|
||||
deadIcon: new fields.FilePathField({
|
||||
initial: 'icons/magic/death/grave-tombstone-glow-teal.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.dead.label'
|
||||
label: 'Dead'
|
||||
}),
|
||||
defeatedIcon: new fields.FilePathField({
|
||||
initial: 'icons/magic/control/fear-fright-mask-orange.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.defeated.label'
|
||||
label: 'Defeated'
|
||||
}),
|
||||
unconsciousIcon: new fields.FilePathField({
|
||||
initial: 'icons/magic/control/sleep-bubble-purple.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.unconscious.label'
|
||||
label: 'Unconcious'
|
||||
})
|
||||
}),
|
||||
roll: new fields.SchemaField({
|
||||
|
|
|
|||
|
|
@ -12,20 +12,6 @@ const currencyField = (initial, label, icon) =>
|
|||
icon: new foundry.data.fields.StringField({ required: true, nullable: false, blank: true, initial: icon })
|
||||
});
|
||||
|
||||
const restMoveField = () =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
name: new foundry.data.fields.StringField({ required: true }),
|
||||
icon: new foundry.data.fields.StringField({ required: true }),
|
||||
img: new foundry.data.fields.FilePathField({
|
||||
initial: 'icons/magic/life/cross-worn-green.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
description: new foundry.data.fields.HTMLField(),
|
||||
actions: new ActionsField(),
|
||||
effects: new foundry.data.fields.ArrayField(new foundry.data.fields.ObjectField())
|
||||
});
|
||||
|
||||
export default class DhHomebrew extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
|
@ -37,13 +23,6 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
initial: 12,
|
||||
label: 'DAGGERHEART.SETTINGS.Homebrew.FIELDS.maxFear.label'
|
||||
}),
|
||||
maxHope: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 0,
|
||||
initial: 6,
|
||||
label: 'DAGGERHEART.SETTINGS.Homebrew.FIELDS.maxHope.label'
|
||||
}),
|
||||
maxLoadout: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
|
|
@ -119,11 +98,37 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
restMoves: new fields.SchemaField({
|
||||
longRest: new fields.SchemaField({
|
||||
nrChoices: new fields.NumberField({ required: true, integer: true, min: 1, initial: 2 }),
|
||||
moves: new fields.TypedObjectField(restMoveField(), { initial: defaultRestOptions.longRest() })
|
||||
moves: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
icon: new fields.StringField({ required: true }),
|
||||
img: new fields.FilePathField({
|
||||
initial: 'icons/magic/life/cross-worn-green.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
description: new fields.HTMLField(),
|
||||
actions: new ActionsField()
|
||||
}),
|
||||
{ initial: defaultRestOptions.longRest() }
|
||||
)
|
||||
}),
|
||||
shortRest: new fields.SchemaField({
|
||||
nrChoices: new fields.NumberField({ required: true, integer: true, min: 1, initial: 2 }),
|
||||
moves: new fields.TypedObjectField(restMoveField(), { initial: defaultRestOptions.shortRest() })
|
||||
moves: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
icon: new fields.StringField({ required: true }),
|
||||
img: new fields.FilePathField({
|
||||
initial: 'icons/magic/life/cross-worn-green.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
description: new fields.HTMLField(),
|
||||
actions: new ActionsField()
|
||||
}),
|
||||
{ initial: defaultRestOptions.shortRest() }
|
||||
)
|
||||
})
|
||||
}),
|
||||
domains: new fields.TypedObjectField(
|
||||
|
|
|
|||
|
|
@ -3,4 +3,3 @@ export { default as D20Roll } from './d20Roll.mjs';
|
|||
export { default as DamageRoll } from './damageRoll.mjs';
|
||||
export { default as DHRoll } from './dhRoll.mjs';
|
||||
export { default as DualityRoll } from './dualityRoll.mjs';
|
||||
export { default as FateRoll } from './fateRoll.mjs';
|
||||
|
|
|
|||
|
|
@ -99,14 +99,11 @@ export default class D20Roll extends DHRoll {
|
|||
|
||||
this.options.roll.modifiers = this.applyBaseBonus();
|
||||
|
||||
const actorExperiences = this.options.roll.companionRoll
|
||||
? (this.options.data?.companion?.system.experiences ?? {})
|
||||
: (this.options.data.system?.experiences ?? {});
|
||||
this.options.experiences?.forEach(m => {
|
||||
if (actorExperiences[m])
|
||||
if (this.options.data.system?.experiences?.[m])
|
||||
this.options.roll.modifiers.push({
|
||||
label: actorExperiences[m].name,
|
||||
value: actorExperiences[m].value
|
||||
label: this.options.data.system.experiences[m].name,
|
||||
value: this.options.data.system.experiences[m].value
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import DamageDialog from '../applications/dialogs/damageDialog.mjs';
|
||||
import { parseRallyDice } from '../helpers/utils.mjs';
|
||||
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
|
||||
import DHRoll from './dhRoll.mjs';
|
||||
|
||||
|
|
@ -34,7 +33,7 @@ export default class DamageRoll extends DHRoll {
|
|||
static async buildPost(roll, config, message) {
|
||||
const chatMessage = config.source?.message
|
||||
? ui.chat.collection.get(config.source.message)
|
||||
: getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode ?? CONST.DICE_ROLL_MODES.PUBLIC);
|
||||
: getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode);
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
||||
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
||||
|
|
@ -47,14 +46,9 @@ export default class DamageRoll extends DHRoll {
|
|||
chatMessage.whisper?.length > 0 ? chatMessage.whisper : null,
|
||||
chatMessage.blind
|
||||
);
|
||||
config.mute = true;
|
||||
}
|
||||
await super.buildPost(roll, config, message);
|
||||
if (config.source?.message) {
|
||||
chatMessage.update({ 'system.damage': config.damage });
|
||||
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
}
|
||||
if (config.source?.message) chatMessage.update({ 'system.damage': config.damage });
|
||||
}
|
||||
|
||||
static unifyDamageRoll(rolls) {
|
||||
|
|
@ -198,7 +192,7 @@ export default class DamageRoll extends DHRoll {
|
|||
// Bardic Rally
|
||||
const rallyChoices = config.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||
if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) });
|
||||
if (change) a.push({ value: c.id, label: change.value });
|
||||
return a;
|
||||
}, []);
|
||||
if (rallyChoices.length) {
|
||||
|
|
|
|||
|
|
@ -96,19 +96,6 @@ export default class DHRoll extends Roll {
|
|||
}
|
||||
|
||||
static async toMessage(roll, config) {
|
||||
const item = config.data.parent?.items?.get?.(config.source.item) ?? null;
|
||||
const action = item ? item.system.actions.get(config.source.action) : null;
|
||||
let actionDescription = null;
|
||||
if (action?.chatDisplay) {
|
||||
actionDescription = action
|
||||
? await foundry.applications.ux.TextEditor.implementation.enrichHTML(action.description, {
|
||||
relativeTo: config.data,
|
||||
rollData: config.data.getRollData?.() ?? {}
|
||||
})
|
||||
: null;
|
||||
config.actionChatMessageHandled = true;
|
||||
}
|
||||
|
||||
const cls = getDocumentClass('ChatMessage'),
|
||||
msgData = {
|
||||
type: this.messageType,
|
||||
|
|
@ -116,7 +103,7 @@ export default class DHRoll extends Roll {
|
|||
title: roll.title,
|
||||
speaker: cls.getSpeaker({ actor: roll.data?.parent }),
|
||||
sound: config.mute ? null : CONFIG.sounds.dice,
|
||||
system: { ...config, actionDescription },
|
||||
system: config,
|
||||
rolls: [roll]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||
import D20Roll from './d20Roll.mjs';
|
||||
import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
||||
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||
|
||||
|
|
@ -12,7 +12,6 @@ export default class DualityRoll extends D20Roll {
|
|||
constructor(formula, data = {}, options = {}) {
|
||||
super(formula, data, options);
|
||||
this.rallyChoices = this.setRallyChoices();
|
||||
this.guaranteedCritical = options.guaranteedCritical;
|
||||
}
|
||||
|
||||
static messageType = 'dualityRoll';
|
||||
|
|
@ -26,23 +25,29 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
get dHope() {
|
||||
// if ( !(this.terms[0] instanceof foundry.dice.terms.Die) ) return;
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
return this.dice[0];
|
||||
// return this.#hopeDice;
|
||||
}
|
||||
|
||||
set dHope(faces) {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[0].faces = this.getFaces(faces);
|
||||
this.terms[0].faces = this.getFaces(faces);
|
||||
// this.#hopeDice = `d${face}`;
|
||||
}
|
||||
|
||||
get dFear() {
|
||||
// if ( !(this.terms[1] instanceof foundry.dice.terms.Die) ) return;
|
||||
if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
return this.dice[1];
|
||||
// return this.#fearDice;
|
||||
}
|
||||
|
||||
set dFear(faces) {
|
||||
if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[1].faces = this.getFaces(faces);
|
||||
// this.#fearDice = `d${face}`;
|
||||
}
|
||||
|
||||
get dAdvantage() {
|
||||
|
|
@ -68,7 +73,7 @@ export default class DualityRoll extends D20Roll {
|
|||
setRallyChoices() {
|
||||
return this.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||
if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) });
|
||||
if (change) a.push({ value: c.id, label: change.value });
|
||||
return a;
|
||||
}, []);
|
||||
}
|
||||
|
|
@ -85,29 +90,26 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
get isCritical() {
|
||||
if (this.guaranteedCritical) return true;
|
||||
if (!this.dHope._evaluated || !this.dFear._evaluated) return;
|
||||
return this.dHope.total === this.dFear.total;
|
||||
}
|
||||
|
||||
get withHope() {
|
||||
if (!this._evaluated || this.guaranteedCritical) return;
|
||||
if (!this._evaluated) return;
|
||||
return this.dHope.total > this.dFear.total;
|
||||
}
|
||||
|
||||
get withFear() {
|
||||
if (!this._evaluated || this.guaranteedCritical) return;
|
||||
if (!this._evaluated) return;
|
||||
return this.dHope.total < this.dFear.total;
|
||||
}
|
||||
|
||||
get totalLabel() {
|
||||
const label = this.guaranteedCritical
|
||||
? 'DAGGERHEART.GENERAL.guaranteedCriticalSuccess'
|
||||
: this.isCritical
|
||||
? 'DAGGERHEART.GENERAL.criticalSuccess'
|
||||
: this.withHope
|
||||
? 'DAGGERHEART.GENERAL.hope'
|
||||
: 'DAGGERHEART.GENERAL.fear';
|
||||
const label = this.withHope
|
||||
? 'DAGGERHEART.GENERAL.hope'
|
||||
: this.withFear
|
||||
? 'DAGGERHEART.GENERAL.fear'
|
||||
: 'DAGGERHEART.GENERAL.criticalSuccess';
|
||||
|
||||
return game.i18n.localize(label);
|
||||
}
|
||||
|
|
@ -176,21 +178,6 @@ export default class DualityRoll extends D20Roll {
|
|||
return modifiers;
|
||||
}
|
||||
|
||||
static async buildConfigure(config = {}, message = {}) {
|
||||
config.dialog ??= {};
|
||||
config.guaranteedCritical = config.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||
const change = c.changes.find(ch => ch.key === 'system.rules.roll.guaranteedCritical');
|
||||
if (change) a = true;
|
||||
return a;
|
||||
}, false);
|
||||
|
||||
if (config.guaranteedCritical) {
|
||||
config.dialog.configure = false;
|
||||
}
|
||||
|
||||
return super.buildConfigure(config, message);
|
||||
}
|
||||
|
||||
getActionChangeKeys() {
|
||||
const changeKeys = new Set([`system.bonuses.roll.${this.options.actionType}`]);
|
||||
|
||||
|
|
@ -236,7 +223,7 @@ export default class DualityRoll extends D20Roll {
|
|||
|
||||
data.hope = {
|
||||
dice: roll.dHope.denomination,
|
||||
value: this.guaranteedCritical ? 0 : roll.dHope.total,
|
||||
value: roll.dHope.total,
|
||||
rerolled: {
|
||||
any: roll.dHope.results.some(x => x.rerolled),
|
||||
rerolls: roll.dHope.results.filter(x => x.rerolled)
|
||||
|
|
@ -244,7 +231,7 @@ export default class DualityRoll extends D20Roll {
|
|||
};
|
||||
data.fear = {
|
||||
dice: roll.dFear.denomination,
|
||||
value: this.guaranteedCritical ? 0 : roll.dFear.total,
|
||||
value: roll.dFear.total,
|
||||
rerolled: {
|
||||
any: roll.dFear.results.some(x => x.rerolled),
|
||||
rerolls: roll.dFear.results.filter(x => x.rerolled)
|
||||
|
|
@ -256,7 +243,7 @@ export default class DualityRoll extends D20Roll {
|
|||
};
|
||||
data.result = {
|
||||
duality: roll.withHope ? 1 : roll.withFear ? -1 : 0,
|
||||
total: this.guaranteedCritical ? 0 : roll.dHope.total + roll.dFear.total,
|
||||
total: roll.dHope.total + roll.dFear.total,
|
||||
label: roll.totalLabel
|
||||
};
|
||||
|
||||
|
|
@ -274,7 +261,7 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
static async handleTriggers(roll, config) {
|
||||
if (!config.source?.actor || config.skips?.triggers) return;
|
||||
if (!config.source?.actor) return;
|
||||
|
||||
const updates = [];
|
||||
const dualityUpdates = await game.system.registeredTriggers.runTrigger(
|
||||
|
|
@ -409,9 +396,7 @@ export default class DualityRoll extends D20Roll {
|
|||
difficulty: message.system.roll.difficulty ? Number(message.system.roll.difficulty) : null
|
||||
}
|
||||
});
|
||||
|
||||
const extraIndex = newRoll.advantage ? 3 : 2;
|
||||
newRoll.extra = newRoll.extra.slice(extraIndex);
|
||||
newRoll.extra = newRoll.extra.slice(2);
|
||||
|
||||
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||
import D20Roll from './d20Roll.mjs';
|
||||
import { setDiceSoNiceForHopeFateRoll, setDiceSoNiceForFearFateRoll } from '../helpers/utils.mjs';
|
||||
|
||||
export default class FateRoll extends D20Roll {
|
||||
constructor(formula, data = {}, options = {}) {
|
||||
super(formula, data, options);
|
||||
}
|
||||
|
||||
static messageType = 'fateRoll';
|
||||
|
||||
static DefaultDialog = D20RollDialog;
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize(`DAGGERHEART.GENERAL.fateRoll`);
|
||||
}
|
||||
|
||||
get dHope() {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
return this.dice[0];
|
||||
}
|
||||
|
||||
set dHope(faces) {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[0].faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get dFear() {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
return this.dice[0];
|
||||
}
|
||||
|
||||
set dFear(faces) {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[0].faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get isCritical() {
|
||||
return false;
|
||||
}
|
||||
|
||||
get fateDie() {
|
||||
return this.data.fateType;
|
||||
}
|
||||
|
||||
static getHooks(hooks) {
|
||||
return [...(hooks ?? []), 'Fate'];
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
static fromData(data) {
|
||||
data.terms[0].class = foundry.dice.terms.Die.name;
|
||||
return super.fromData(data);
|
||||
}
|
||||
|
||||
createBaseDice() {
|
||||
if (this.dice[0] instanceof foundry.dice.terms.Die) {
|
||||
this.terms = [this.terms[0]];
|
||||
return;
|
||||
}
|
||||
this.terms[0] = new foundry.dice.terms.Die({ faces: 12 });
|
||||
}
|
||||
|
||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||
await super.buildEvaluate(roll, config, message);
|
||||
|
||||
if (roll.fateDie === 'Hope') {
|
||||
await setDiceSoNiceForHopeFateRoll(roll, config.roll.fate.dice);
|
||||
} else {
|
||||
await setDiceSoNiceForFearFateRoll(roll, config.roll.fate.dice);
|
||||
}
|
||||
}
|
||||
|
||||
static postEvaluate(roll, config = {}) {
|
||||
const data = super.postEvaluate(roll, config);
|
||||
|
||||
data.fate = {
|
||||
dice: roll.fateDie === 'Hope' ? roll.dHope.denomination : roll.dFear.denomination,
|
||||
value: roll.fateDie === 'Hope' ? roll.dHope.total : roll.dFear.total,
|
||||
fateDie: roll.fateDie
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ export { default as DhpCombat } from './combat.mjs';
|
|||
export { default as DHCombatant } from './combatant.mjs';
|
||||
export { default as DhActiveEffect } from './activeEffect.mjs';
|
||||
export { default as DhChatMessage } from './chatMessage.mjs';
|
||||
export { default as DhRollTable } from './rollTable.mjs';
|
||||
export { default as DhScene } from './scene.mjs';
|
||||
export { default as DhToken } from './token.mjs';
|
||||
export { default as DhTooltipManager } from './tooltipManager.mjs';
|
||||
|
|
|
|||
|
|
@ -61,15 +61,14 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
|||
update.img = 'icons/magic/life/heart-cross-blue.webp';
|
||||
}
|
||||
|
||||
const statuses = Object.keys(data.statuses ?? {});
|
||||
const immuneStatuses =
|
||||
statuses.filter(
|
||||
data.statuses?.filter(
|
||||
status =>
|
||||
this.parent.system.rules?.conditionImmunities &&
|
||||
this.parent.system.rules.conditionImmunities[status]
|
||||
) ?? [];
|
||||
if (immuneStatuses.length > 0) {
|
||||
update.statuses = statuses.filter(x => !immuneStatuses.includes(x));
|
||||
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', {
|
||||
|
|
@ -114,11 +113,6 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
|||
super.applyField(model, change, field);
|
||||
}
|
||||
|
||||
_applyLegacy(actor, change, changes) {
|
||||
change.value = DhActiveEffect.getChangeValue(actor, change, change.effect);
|
||||
super._applyLegacy(actor, change, changes);
|
||||
}
|
||||
|
||||
/** */
|
||||
static getChangeValue(model, change, effect) {
|
||||
let value = change.value;
|
||||
|
|
|
|||
|
|
@ -241,11 +241,6 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (this.system.companion) {
|
||||
this.system.companion.updateLevel(usedLevel);
|
||||
}
|
||||
|
||||
this.sheet.render();
|
||||
}
|
||||
}
|
||||
|
|
@ -769,24 +764,16 @@ export default class DhpActor extends Actor {
|
|||
};
|
||||
}
|
||||
} else {
|
||||
const valueFunc = (base, resource, baseMax) => {
|
||||
if (resource.clear) return baseMax && base.inverted ? baseMax : 0;
|
||||
|
||||
return (base.value ?? base) + resource.value;
|
||||
};
|
||||
switch (r.key) {
|
||||
case 'fear':
|
||||
ui.resources.updateFear(
|
||||
valueFunc(
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||
r
|
||||
)
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear) + r.value
|
||||
);
|
||||
break;
|
||||
case 'armor':
|
||||
if (this.system.armor?.system?.marks) {
|
||||
updates.armor.resources['system.marks.value'] = Math.max(
|
||||
Math.min(valueFunc(this.system.armor.system.marks, r), this.system.armorScore),
|
||||
Math.min(this.system.armor.system.marks.value + r.value, this.system.armorScore),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
|
@ -795,7 +782,7 @@ export default class DhpActor extends Actor {
|
|||
if (this.system.resources?.[r.key]) {
|
||||
updates.actor.resources[`system.resources.${r.key}.value`] = Math.max(
|
||||
Math.min(
|
||||
valueFunc(this.system.resources[r.key], r, this.system.resources[r.key].max),
|
||||
this.system.resources[r.key].value + r.value,
|
||||
this.system.resources[r.key].max
|
||||
),
|
||||
0
|
||||
|
|
@ -854,8 +841,8 @@ export default class DhpActor extends Actor {
|
|||
|
||||
async toggleDefeated(defeatedState) {
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).defeated;
|
||||
const { deathMove, unconscious, defeated, dead } = CONFIG.DH.GENERAL.conditions();
|
||||
const defeatedConditions = new Set([deathMove.id, unconscious.id, defeated.id, dead.id]);
|
||||
const { unconscious, defeated, dead } = CONFIG.DH.GENERAL.conditions();
|
||||
const defeatedConditions = new Set([unconscious.id, defeated.id, dead.id]);
|
||||
if (!defeatedState) {
|
||||
for (let defeatedId of defeatedConditions) {
|
||||
await this.toggleStatusEffect(defeatedId, { overlay: settings.overlay, active: defeatedState });
|
||||
|
|
@ -869,18 +856,6 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
}
|
||||
|
||||
async setDeathMoveDefeated(defeatedIconId) {
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).defeated;
|
||||
const actorDefault = settings[`${this.type}Default`];
|
||||
if (!settings.enabled || !settings.enabled || !actorDefault || actorDefault === defeatedIconId) return;
|
||||
|
||||
for (let defeatedId of Object.keys(CONFIG.DH.GENERAL.defeatedConditionChoices)) {
|
||||
await this.toggleStatusEffect(defeatedId, { overlay: settings.overlay, active: false });
|
||||
}
|
||||
|
||||
if (defeatedIconId) await this.toggleStatusEffect(defeatedIconId, { overlay: settings.overlay, active: true });
|
||||
}
|
||||
|
||||
queueScrollText(scrollingTextData) {
|
||||
this.#scrollTextQueue.push(...scrollingTextData.map(data => () => createScrollText(this, data)));
|
||||
if (!this.#scrollTextInterval) {
|
||||
|
|
@ -934,23 +909,10 @@ export default class DhpActor extends Actor {
|
|||
|
||||
/** Get active effects */
|
||||
getActiveEffects() {
|
||||
const conditions = CONFIG.DH.GENERAL.conditions();
|
||||
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
|
||||
const autoVulnerableActive = this.system.isAutoVulnerableActive;
|
||||
return this.effects
|
||||
.filter(x => !x.disabled)
|
||||
.reduce((acc, effect) => {
|
||||
/* Could be generalized if needed. Currently just related to Vulnerable */
|
||||
const isAutoVulnerableEffect =
|
||||
effect.flags.daggerheart?.autoApplyFlagId === conditions.vulnerable.autoApplyFlagId;
|
||||
if (isAutoVulnerableEffect) {
|
||||
if (!autoVulnerableActive) return acc;
|
||||
|
||||
effect.appliedBy = game.i18n.localize('DAGGERHEART.CONFIG.Condition.vulnerable.autoAppliedByLabel');
|
||||
effect.isLockedCondition = true;
|
||||
effect.condition = 'vulnerable';
|
||||
}
|
||||
|
||||
acc.push(effect);
|
||||
|
||||
const currentStatusActiveEffects = acc.filter(
|
||||
|
|
|
|||
|
|
@ -87,15 +87,6 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (this.type === 'fateRoll') {
|
||||
html.classList.add('fate');
|
||||
if (this.system.roll?.fate.fateDie == 'Hope') {
|
||||
html.classList.add('hope');
|
||||
}
|
||||
if (this.system.roll?.fate.fateDie == 'Fear') {
|
||||
html.classList.add('fear');
|
||||
}
|
||||
}
|
||||
|
||||
const autoExpandRoll = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
|
|
@ -110,8 +101,6 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
} else if (s.classList.contains('damage-section'))
|
||||
s.classList.toggle('expanded', autoExpandRoll.damage);
|
||||
else if (s.classList.contains('target-section')) s.classList.toggle('expanded', autoExpandRoll.target);
|
||||
else if (s.classList.contains('description-section'))
|
||||
s.classList.toggle('expanded', autoExpandRoll.desc);
|
||||
});
|
||||
if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', '');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export { default as DhActorCollection } from './actorCollection.mjs';
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
export default class DhActorCollection extends foundry.documents.collections.Actors {
|
||||
/** Ensure companions are initialized after all other subtypes. */
|
||||
_initialize() {
|
||||
super._initialize();
|
||||
const companions = [];
|
||||
for (const actor of this.values()) {
|
||||
if (actor.type === 'companion') companions.push(actor);
|
||||
}
|
||||
for (const actor of companions) {
|
||||
this.delete(actor.id);
|
||||
this.set(actor.id, actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -185,10 +185,7 @@ export default class DHItem extends foundry.documents.Item {
|
|||
tags: this._getTags()
|
||||
},
|
||||
actions: item.system.actionsList,
|
||||
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.system.description, {
|
||||
relativeTo: this.parent,
|
||||
rollData: this.parent?.getRollData() ?? {}
|
||||
})
|
||||
description: this.system.description
|
||||
};
|
||||
|
||||
const msg = {
|
||||
|
|
|
|||
|
|
@ -1,122 +0,0 @@
|
|||
export default class DhRollTable extends foundry.documents.RollTable {
|
||||
async roll({ selectedFormula, roll, recursive = true, _depth = 0 } = {}) {
|
||||
// Prevent excessive recursion
|
||||
if (_depth > 5) {
|
||||
throw new Error(`Maximum recursion depth exceeded when attempting to draw from RollTable ${this.id}`);
|
||||
}
|
||||
|
||||
const formula = selectedFormula ?? this.formula;
|
||||
|
||||
// If there is no formula, automatically calculate an even distribution
|
||||
if (!this.formula) {
|
||||
await this.normalize();
|
||||
}
|
||||
|
||||
// Reference the provided roll formula
|
||||
roll = roll instanceof Roll ? roll : Roll.create(formula);
|
||||
let results = [];
|
||||
|
||||
// Ensure that at least one non-drawn result remains
|
||||
const available = this.results.filter(r => !r.drawn);
|
||||
if (!available.length) {
|
||||
ui.notifications.warn(game.i18n.localize('TABLE.NoAvailableResults'));
|
||||
return { roll, results };
|
||||
}
|
||||
|
||||
// Ensure that results are available within the minimum/maximum range
|
||||
const minRoll = (await roll.reroll({ minimize: true })).total;
|
||||
const maxRoll = (await roll.reroll({ maximize: true })).total;
|
||||
const availableRange = available.reduce(
|
||||
(range, result) => {
|
||||
const r = result.range;
|
||||
if (!range[0] || r[0] < range[0]) range[0] = r[0];
|
||||
if (!range[1] || r[1] > range[1]) range[1] = r[1];
|
||||
return range;
|
||||
},
|
||||
[null, null]
|
||||
);
|
||||
if (availableRange[0] > maxRoll || availableRange[1] < minRoll) {
|
||||
ui.notifications.warn('No results can possibly be drawn from this table and formula.');
|
||||
return { roll, results };
|
||||
}
|
||||
|
||||
// Continue rolling until one or more results are recovered
|
||||
let iter = 0;
|
||||
while (!results.length) {
|
||||
if (iter >= 10000) {
|
||||
ui.notifications.error(
|
||||
`Failed to draw an available entry from Table ${this.name}, maximum iteration reached`
|
||||
);
|
||||
break;
|
||||
}
|
||||
roll = await roll.reroll();
|
||||
results = this.getResultsForRoll(roll.total);
|
||||
iter++;
|
||||
}
|
||||
|
||||
// Draw results recursively from any inner Roll Tables
|
||||
if (recursive) {
|
||||
const inner = [];
|
||||
for (const result of results) {
|
||||
const { type, documentUuid } = result;
|
||||
const documentName = foundry.utils.parseUuid(documentUuid)?.type;
|
||||
if (type === 'document' && documentName === 'RollTable') {
|
||||
const innerTable = await fromUuid(documentUuid);
|
||||
if (innerTable) {
|
||||
const innerRoll = await innerTable.roll({ _depth: _depth + 1 });
|
||||
inner.push(...innerRoll.results);
|
||||
}
|
||||
} else inner.push(result);
|
||||
}
|
||||
results = inner;
|
||||
}
|
||||
|
||||
// Return the Roll and the results
|
||||
return { roll, results };
|
||||
}
|
||||
|
||||
async toMessage(results, { roll, messageData = {}, messageOptions = {} } = {}) {
|
||||
messageOptions.rollMode ??= game.settings.get('core', 'rollMode');
|
||||
|
||||
// Construct chat data
|
||||
messageData = foundry.utils.mergeObject(
|
||||
{
|
||||
author: game.user.id,
|
||||
speaker: foundry.documents.ChatMessage.implementation.getSpeaker(),
|
||||
rolls: [],
|
||||
sound: roll ? CONFIG.sounds.dice : null,
|
||||
flags: { 'core.RollTable': this.id }
|
||||
},
|
||||
messageData
|
||||
);
|
||||
if (roll) messageData.rolls.push(roll);
|
||||
|
||||
// Render the chat card which combines the dice roll with the drawn results
|
||||
const detailsPromises = await Promise.allSettled(results.map(r => r.getHTML()));
|
||||
const flavorKey = `TABLE.DrawFlavor${results.length > 1 ? 'Plural' : ''}`;
|
||||
const flavor = game.i18n.format(flavorKey, {
|
||||
number: results.length,
|
||||
name: foundry.utils.escapeHTML(this.name)
|
||||
});
|
||||
messageData.content = await foundry.applications.handlebars.renderTemplate(CONFIG.RollTable.resultTemplate, {
|
||||
description: await TextEditor.implementation.enrichHTML(this.description, {
|
||||
documents: true,
|
||||
secrets: this.isOwner
|
||||
}),
|
||||
flavor: flavor,
|
||||
results: results.map((result, i) => {
|
||||
const r = result.toObject(false);
|
||||
r.details = detailsPromises[i].value ?? '';
|
||||
const useTableIcon =
|
||||
result.icon === CONFIG.RollTable.resultIcon && this.img !== this.constructor.DEFAULT_ICON;
|
||||
r.icon = useTableIcon ? this.img : result.icon;
|
||||
return r;
|
||||
}),
|
||||
rollHTML: this.displayRoll && roll ? await roll.render() : null,
|
||||
table: this
|
||||
});
|
||||
|
||||
// Create the chat message
|
||||
return foundry.documents.ChatMessage.implementation.create(messageData, messageOptions);
|
||||
}
|
||||
}
|
||||
|
|
@ -51,27 +51,6 @@ export default class DhScene extends Scene {
|
|||
}
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, user) {
|
||||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
||||
if (changes.flags?.daggerheart) {
|
||||
if (this._source.flags.daggerheart) {
|
||||
const unregisterTriggerData = (this._source.flags.daggerheart.sceneEnvironments ?? []).reduce(
|
||||
(acc, env) => {
|
||||
if (!changes.flags.daggerheart.sceneEnvironments.includes(env)) acc.sceneEnvironments.push(env);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ ...this._source.flags.daggerheart, sceneEnvironments: [] }
|
||||
);
|
||||
game.system.registeredTriggers.unregisterSceneEnvironmentTriggers(unregisterTriggerData);
|
||||
}
|
||||
|
||||
game.system.registeredTriggers.registerSceneEnvironmentTriggers(changes.flags.daggerheart);
|
||||
}
|
||||
}
|
||||
|
||||
_onDelete(options, userId) {
|
||||
super._onDelete(options, userId);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +1,78 @@
|
|||
export default class DHToken extends CONFIG.Token.documentClass {
|
||||
/**@inheritdoc */
|
||||
static getTrackedAttributeChoices(attributes, typeKey) {
|
||||
/**
|
||||
* Inspect the Actor data model and identify the set of attributes which could be used for a Token Bar.
|
||||
* @param {object} attributes The tracked attributes which can be chosen from
|
||||
* @returns {object} A nested object of attribute choices to display
|
||||
*/
|
||||
static getTrackedAttributeChoices(attributes, model) {
|
||||
attributes = attributes || this.getTrackedAttributes();
|
||||
const barGroup = game.i18n.localize('TOKEN.BarAttributes');
|
||||
const valueGroup = game.i18n.localize('TOKEN.BarValues');
|
||||
const actorModel = typeKey ? game.system.api.data.actors[`Dh${typeKey.capitalize()}`] : null;
|
||||
const getLabel = path => {
|
||||
const label = actorModel?.schema.getField(path)?.label;
|
||||
return label ? game.i18n.localize(label) : path;
|
||||
};
|
||||
|
||||
const bars = attributes.bar.map(v => {
|
||||
const a = v.join('.');
|
||||
return { group: barGroup, value: a, label: getLabel(a) };
|
||||
const modelLabel = model ? game.i18n.localize(model.schema.getField(`${a}.value`).label) : null;
|
||||
return { group: barGroup, value: a, label: modelLabel ? modelLabel : a };
|
||||
});
|
||||
bars.sort((a, b) => a.value.compare(b.value));
|
||||
bars.sort((a, b) => a.label.compare(b.label));
|
||||
|
||||
const values = attributes.value.map(v => {
|
||||
const invalidAttributes = [
|
||||
'gold',
|
||||
'levelData',
|
||||
'actions',
|
||||
'biography',
|
||||
'class',
|
||||
'multiclass',
|
||||
'companion',
|
||||
'notes',
|
||||
'partner',
|
||||
'description',
|
||||
'impulses',
|
||||
'tier',
|
||||
'type'
|
||||
];
|
||||
const values = attributes.value.reduce((acc, v) => {
|
||||
const a = v.join('.');
|
||||
return { group: valueGroup, value: a, label: getLabel(a) };
|
||||
});
|
||||
if (invalidAttributes.some(x => a.startsWith(x))) return acc;
|
||||
|
||||
const field = model ? model.schema.getField(a) : null;
|
||||
const modelLabel = field ? game.i18n.localize(field.label) : null;
|
||||
const hint = field ? game.i18n.localize(field.hint) : null;
|
||||
acc.push({ group: valueGroup, value: a, label: modelLabel ? modelLabel : a, hint: hint });
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
values.sort((a, b) => a.label.compare(b.label));
|
||||
|
||||
values.sort((a, b) => a.value.compare(b.value));
|
||||
return bars.concat(values);
|
||||
}
|
||||
|
||||
static _getTrackedAttributesFromSchema(schema, _path = []) {
|
||||
const attributes = { bar: [], value: [] };
|
||||
for (const [name, field] of Object.entries(schema.fields)) {
|
||||
const p = _path.concat([name]);
|
||||
if (field instanceof foundry.data.fields.NumberField) attributes.value.push(p);
|
||||
if (field instanceof foundry.data.fields.BooleanField && field.options.isAttributeChoice)
|
||||
attributes.value.push(p);
|
||||
if (field instanceof foundry.data.fields.StringField) attributes.value.push(p);
|
||||
if (field instanceof foundry.data.fields.ArrayField) attributes.value.push(p);
|
||||
const isSchema = field instanceof foundry.data.fields.SchemaField;
|
||||
const isModel = field instanceof foundry.data.fields.EmbeddedDataField;
|
||||
|
||||
if (isSchema || isModel) {
|
||||
const schema = isModel ? field.model.schema : field;
|
||||
const isBar = schema.has && schema.has('value') && schema.has('max');
|
||||
if (isBar) attributes.bar.push(p);
|
||||
else {
|
||||
const inner = this.getTrackedAttributes(schema, p);
|
||||
attributes.bar.push(...inner.bar);
|
||||
attributes.value.push(...inner.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
_shouldRecordMovementHistory() {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -221,7 +269,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
|
|||
|
||||
// Hexagon symmetry
|
||||
if (columns) {
|
||||
const rowData = DHToken.#getHexagonalShape(height, width, shape, false);
|
||||
const rowData = BaseToken.#getHexagonalShape(height, width, shape, false);
|
||||
if (!rowData) return null;
|
||||
|
||||
// Transpose the offsets/points of the shape in row orientation
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { abilities } from '../config/actorConfig.mjs';
|
|||
import { getCommandTarget, rollCommandToJSON } from '../helpers/utils.mjs';
|
||||
|
||||
export default function DhDualityRollEnricher(match, _options) {
|
||||
const roll = rollCommandToJSON(match[0]);
|
||||
const roll = rollCommandToJSON(match[1], match[0]);
|
||||
if (!roll) return match[0];
|
||||
|
||||
return getDualityMessage(roll.result, roll.flavor);
|
||||
|
|
@ -47,7 +47,6 @@ function getDualityMessage(roll, flavor) {
|
|||
${roll?.trait && abilities[roll.trait] ? `data-trait="${roll.trait}"` : ''}
|
||||
${roll?.advantage ? 'data-advantage="true"' : ''}
|
||||
${roll?.disadvantage ? 'data-disadvantage="true"' : ''}
|
||||
${roll?.grantResources ? 'data-grant-resources="true"' : ''}
|
||||
>
|
||||
${roll?.reaction ? '<i class="fa-solid fa-reply"></i>' : '<i class="fa-solid fa-circle-half-stroke"></i>'}
|
||||
${label}
|
||||
|
|
@ -64,8 +63,7 @@ export const renderDualityButton = async event => {
|
|||
traitValue = button.dataset.trait?.toLowerCase(),
|
||||
target = getCommandTarget({ allowNull: true }),
|
||||
difficulty = button.dataset.difficulty,
|
||||
advantage = button.dataset.advantage ? Number(button.dataset.advantage) : undefined,
|
||||
grantResources = Boolean(button.dataset?.grantResources);
|
||||
advantage = button.dataset.advantage ? Number(button.dataset.advantage) : undefined;
|
||||
|
||||
await enrichedDualityRoll(
|
||||
{
|
||||
|
|
@ -75,48 +73,36 @@ export const renderDualityButton = async event => {
|
|||
difficulty,
|
||||
title: button.dataset.title,
|
||||
label: button.dataset.label,
|
||||
advantage,
|
||||
grantResources
|
||||
advantage
|
||||
},
|
||||
event
|
||||
);
|
||||
};
|
||||
|
||||
export const enrichedDualityRoll = async (
|
||||
{ reaction, traitValue, target, difficulty, title, label, advantage, grantResources, customConfig },
|
||||
{ reaction, traitValue, target, difficulty, title, label, advantage },
|
||||
event
|
||||
) => {
|
||||
const shouldGrantResources = grantResources === undefined ? true : grantResources;
|
||||
|
||||
const config = {
|
||||
event: event ?? {},
|
||||
title: title,
|
||||
headerTitle: label,
|
||||
actionType: reaction ? 'reaction' : null,
|
||||
roll: {
|
||||
trait: traitValue && target ? traitValue : null,
|
||||
difficulty: difficulty,
|
||||
advantage
|
||||
// type: reaction ? 'reaction' : null //not needed really but keeping it for troubleshooting
|
||||
},
|
||||
skips: {
|
||||
resources: !shouldGrantResources,
|
||||
triggers: !shouldGrantResources
|
||||
advantage,
|
||||
type: reaction ? 'reaction' : null
|
||||
},
|
||||
type: 'trait',
|
||||
hasRoll: true,
|
||||
...(customConfig ?? {})
|
||||
hasRoll: true
|
||||
};
|
||||
|
||||
if (target) {
|
||||
const result = await target.diceRoll(config);
|
||||
if (!result) return;
|
||||
result.resourceUpdates.updateResources();
|
||||
await target.diceRoll(config);
|
||||
} else {
|
||||
// For no target, call DualityRoll directly with basic data
|
||||
config.data = { experiences: {}, traits: {}, rules: {} };
|
||||
config.source = { actor: null };
|
||||
await CONFIG.Dice.daggerheart.DualityRoll.build(config);
|
||||
}
|
||||
return config;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
import { getCommandTarget, rollCommandToJSON } from '../helpers/utils.mjs';
|
||||
|
||||
export default function DhFateRollEnricher(match, _options) {
|
||||
const roll = rollCommandToJSON(match[0]);
|
||||
if (!roll) return match[0];
|
||||
|
||||
return getFateMessage(roll.result, roll?.flavor);
|
||||
}
|
||||
|
||||
export function getFateTypeData(fateTypeValue) {
|
||||
const value = fateTypeValue ? fateTypeValue.capitalize() : 'Hope';
|
||||
const lowercased = fateTypeValue?.toLowerCase?.() ?? 'hope';
|
||||
switch (lowercased) {
|
||||
case 'hope':
|
||||
case 'fear':
|
||||
return { value, label: game.i18n.localize(`DAGGERHEART.GENERAL.${lowercased}`) };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getFateMessage(roll, flavor) {
|
||||
const fateTypeData = getFateTypeData(roll?.type);
|
||||
|
||||
if (!fateTypeData)
|
||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
||||
|
||||
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
||||
const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll');
|
||||
|
||||
const fateElement = document.createElement('span');
|
||||
fateElement.innerHTML = `
|
||||
<button type="button" class="fate-roll-button${roll?.inline ? ' inline' : ''}"
|
||||
data-title="${title}"
|
||||
data-label="${fateTypeLabel}"
|
||||
data-fateType="${fateType}"
|
||||
>
|
||||
${title}
|
||||
</button>
|
||||
`;
|
||||
|
||||
return fateElement;
|
||||
}
|
||||
|
||||
export const renderFateButton = async event => {
|
||||
const button = event.currentTarget,
|
||||
target = getCommandTarget({ allowNull: true });
|
||||
|
||||
const fateTypeData = getFateTypeData(button.dataset?.fatetype);
|
||||
|
||||
if (!fateTypeData) ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
||||
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
||||
|
||||
await enrichedFateRoll(
|
||||
{
|
||||
target,
|
||||
title: button.dataset.title,
|
||||
label: button.dataset.label,
|
||||
fateType: fateType
|
||||
},
|
||||
event
|
||||
);
|
||||
};
|
||||
|
||||
export const enrichedFateRoll = async ({ target, title, label, fateType }, event) => {
|
||||
const config = {
|
||||
event: event ?? {},
|
||||
title: title,
|
||||
headerTitle: label,
|
||||
roll: {},
|
||||
hasRoll: true,
|
||||
fateType: fateType,
|
||||
skips: { reaction: true }
|
||||
};
|
||||
|
||||
config.data = { experiences: {}, traits: {}, fateType: fateType };
|
||||
config.source = { actor: target?.uuid };
|
||||
await CONFIG.Dice.daggerheart.FateRoll.build(config);
|
||||
return config;
|
||||
};
|
||||
|
|
@ -4,7 +4,6 @@ export default function DhTemplateEnricher(match, _options) {
|
|||
const params = parseInlineParams(match[1]);
|
||||
const { type, angle = CONFIG.MeasuredTemplate.defaults.angle, inline = false } = params;
|
||||
const direction = Number(params.direction) || 0;
|
||||
params.range = params.range?.toLowerCase();
|
||||
const range =
|
||||
params.range && Number.isNaN(Number(params.range))
|
||||
? Object.values(CONFIG.DH.GENERAL.templateRanges).find(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import { default as DhDamageEnricher, renderDamageButton } from './DamageEnricher.mjs';
|
||||
import { default as DhDualityRollEnricher, renderDualityButton } from './DualityRollEnricher.mjs';
|
||||
import { default as DhFateRollEnricher, renderFateButton } from './FateRollEnricher.mjs';
|
||||
import { default as DhEffectEnricher } from './EffectEnricher.mjs';
|
||||
import { default as DhTemplateEnricher, renderMeasuredTemplate } from './TemplateEnricher.mjs';
|
||||
import { default as DhLookupEnricher } from './LookupEnricher.mjs';
|
||||
|
||||
export { DhDamageEnricher, DhDualityRollEnricher, DhEffectEnricher, DhTemplateEnricher, DhFateRollEnricher };
|
||||
export { DhDamageEnricher, DhDualityRollEnricher, DhEffectEnricher, DhTemplateEnricher };
|
||||
|
||||
export const enricherConfig = [
|
||||
{
|
||||
|
|
@ -16,10 +15,6 @@ export const enricherConfig = [
|
|||
pattern: /\[\[\/dr\s?(.*?)\]\]({[^}]*})?/g,
|
||||
enricher: DhDualityRollEnricher
|
||||
},
|
||||
{
|
||||
pattern: /\[\[\/fr\s?(.*?)\]\]({[^}]*})?/g,
|
||||
enricher: DhFateRollEnricher
|
||||
},
|
||||
{
|
||||
pattern: /@Effect\[([^\[\]]*)\]({[^}]*})?/g,
|
||||
enricher: DhEffectEnricher
|
||||
|
|
@ -43,10 +38,6 @@ export const enricherRenderSetup = element => {
|
|||
.querySelectorAll('.duality-roll-button')
|
||||
.forEach(element => element.addEventListener('click', renderDualityButton));
|
||||
|
||||
element
|
||||
.querySelectorAll('.fate-roll-button')
|
||||
.forEach(element => element.addEventListener('click', renderFateButton));
|
||||
|
||||
element
|
||||
.querySelectorAll('.measured-template-button')
|
||||
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import { diceTypes, getDiceSoNicePresets, getDiceSoNicePreset, range } from '../config/generalConfig.mjs';
|
||||
import { diceTypes, getDiceSoNicePresets, range } from '../config/generalConfig.mjs';
|
||||
import Tagify from '@yaireo/tagify';
|
||||
|
||||
export const capitalize = string => {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
};
|
||||
|
||||
export function rollCommandToJSON(text) {
|
||||
export function rollCommandToJSON(text, raw) {
|
||||
if (!text) return {};
|
||||
|
||||
const flavorMatch = text?.match(/{(.*)}$/);
|
||||
const flavorMatch = raw?.match(/{(.*)}$/);
|
||||
const flavor = flavorMatch ? flavorMatch[1] : null;
|
||||
|
||||
// Match key="quoted string" OR key=unquotedValue
|
||||
const PAIR_RE = /(\w+)\s*=\s*("(?:[^"\\]|\\.)*"|[^\]\}\s]+)/g; //updated regex to allow escaped quotes in quoted strings and avoid matching closing brackets/braces
|
||||
const PAIR_RE = /(\w+)=("(?:[^"\\]|\\.)*"|\S+)/g;
|
||||
const result = {};
|
||||
for (const [, key, raw] of text.matchAll(PAIR_RE)) {
|
||||
let value;
|
||||
|
|
@ -31,7 +31,7 @@ export function rollCommandToJSON(text) {
|
|||
}
|
||||
result[key] = value;
|
||||
}
|
||||
return { result, flavor };
|
||||
return Object.keys(result).length > 0 ? { result, flavor } : null;
|
||||
}
|
||||
|
||||
export const getCommandTarget = (options = {}) => {
|
||||
|
|
@ -69,20 +69,6 @@ export const setDiceSoNiceForDualityRoll = async (rollResult, advantageState, ho
|
|||
}
|
||||
};
|
||||
|
||||
export const setDiceSoNiceForHopeFateRoll = async (rollResult, hopeFaces) => {
|
||||
if (!game.modules.get('dice-so-nice')?.active) return;
|
||||
const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
|
||||
const diceSoNicePresets = await getDiceSoNicePreset(diceSoNice.hope, hopeFaces);
|
||||
rollResult.dice[0].options = diceSoNicePresets;
|
||||
};
|
||||
|
||||
export const setDiceSoNiceForFearFateRoll = async (rollResult, fearFaces) => {
|
||||
if (!game.modules.get('dice-so-nice')?.active) return;
|
||||
const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
|
||||
const diceSoNicePresets = await getDiceSoNicePreset(diceSoNice.fear, fearFaces);
|
||||
rollResult.dice[0].options = diceSoNicePresets;
|
||||
};
|
||||
|
||||
export const chunkify = (array, chunkSize, mappingFunc) => {
|
||||
var chunkifiedArray = [];
|
||||
for (let i = 0; i < array.length; i += chunkSize) {
|
||||
|
|
@ -119,8 +105,8 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}
|
|||
}),
|
||||
maxTags: typeof maxTags === 'function' ? maxTags() : maxTags,
|
||||
dropdown: {
|
||||
searchKeys: ['value', 'name'],
|
||||
mapValueTo: 'name',
|
||||
searchKeys: ['value'],
|
||||
enabled: 0,
|
||||
maxItems: 100,
|
||||
closeOnSelect: true,
|
||||
|
|
@ -472,7 +458,7 @@ export function refreshIsAllowed(allowedTypes, typeToCheck) {
|
|||
case CONFIG.DH.GENERAL.refreshTypes.scene.id:
|
||||
case CONFIG.DH.GENERAL.refreshTypes.session.id:
|
||||
case CONFIG.DH.GENERAL.refreshTypes.longRest.id:
|
||||
return allowedTypes.includes?.(typeToCheck) ?? allowedTypes.has(typeToCheck);
|
||||
return allowedTypes.includes(typeToCheck);
|
||||
case CONFIG.DH.GENERAL.refreshTypes.shortRest.id:
|
||||
return allowedTypes.some(
|
||||
x =>
|
||||
|
|
@ -495,183 +481,3 @@ export function htmlToText(html) {
|
|||
|
||||
return tempDivElement.textContent || tempDivElement.innerText || '';
|
||||
}
|
||||
|
||||
export async function getFeaturesHTMLData(features) {
|
||||
const result = [];
|
||||
for (const feature of features) {
|
||||
if (feature) {
|
||||
const base = feature.item ?? feature;
|
||||
const item = base.system ? base : await foundry.utils.fromUuid(base.uuid);
|
||||
const itemDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||
item.system.description
|
||||
);
|
||||
result.push({ label: item.name, description: itemDescription });
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a simple flavor-less formula with only +/- operators, returns a list of damage partial terms.
|
||||
* All subtracted terms become negative terms.
|
||||
* If there are no dice, it returns 0d1 for that term.
|
||||
*/
|
||||
export function parseTermsFromSimpleFormula(formula) {
|
||||
const roll = formula instanceof Roll ? formula : new Roll(formula);
|
||||
|
||||
// Parse from right to left so that when we hit an operator, we already have the term.
|
||||
return roll.terms.reduceRight((result, term) => {
|
||||
// Ignore + terms, we assume + by default
|
||||
if (term.expression === ' + ') return result;
|
||||
|
||||
// - terms modify the last term we parsed
|
||||
if (term.expression === ' - ') {
|
||||
const termToModify = result[0];
|
||||
if (termToModify) {
|
||||
if (termToModify.bonus) termToModify.bonus *= -1;
|
||||
if (termToModify.dice) termToModify.dice *= -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
result.unshift({
|
||||
bonus: term instanceof foundry.dice.terms.NumericTerm ? term.number : 0,
|
||||
diceQuantity: term instanceof foundry.dice.terms.Die ? term.number : 0,
|
||||
faces: term.faces ?? 1
|
||||
});
|
||||
|
||||
return result;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the expectede value from a formula or the results of parseTermsFromSimpleFormula.
|
||||
* @returns {number} the average result of rolling the given dice
|
||||
*/
|
||||
export function calculateExpectedValue(formulaOrTerms) {
|
||||
const terms = Array.isArray(formulaOrTerms)
|
||||
? formulaOrTerms
|
||||
: typeof formulaOrTerms === 'string'
|
||||
? parseTermsFromSimpleFormula(formulaOrTerms)
|
||||
: [formulaOrTerms];
|
||||
return terms.reduce((r, t) => r + (t.bonus ?? 0) + (t.diceQuantity ? (t.diceQuantity * (t.faces + 1)) / 2 : 0), 0);
|
||||
}
|
||||
|
||||
export function parseRallyDice(value, effect) {
|
||||
const legacyStartsWithPrefix = value.toLowerCase().startsWith('d');
|
||||
const workingValue = legacyStartsWithPrefix ? value.slice(1) : value;
|
||||
const dataParsedValue = itemAbleRollParse(workingValue, effect.parent);
|
||||
|
||||
return `d${game.system.api.documents.DhActiveEffect.effectSafeEval(dataParsedValue)}`;
|
||||
}
|
||||
/**
|
||||
* Refreshes character and/or adversary resources.
|
||||
* @param { string[] } refreshTypes Which type of features to refresh using IDs from CONFIG.DH.GENERAL.refreshTypes
|
||||
* @param { string[] = ['character', 'adversary'] } actorTypes Which actor types should refresh their features. Defaults to character and adversary.
|
||||
* @param { boolean = true } sendRefreshMessage If a chat message should be created detailing the refresh
|
||||
* @return { Actor[] } The actors that had their features refreshed
|
||||
*/
|
||||
export async function RefreshFeatures(
|
||||
refreshTypes = [],
|
||||
actorTypes = ['character', 'adversary'],
|
||||
sendNotificationMessage = true,
|
||||
sendRefreshMessage = true
|
||||
) {
|
||||
const refreshedActors = {};
|
||||
for (let actor of game.actors) {
|
||||
if (actorTypes.includes(actor.type) && actor.prototypeToken.actorLink) {
|
||||
const updates = {};
|
||||
for (let item of actor.items) {
|
||||
if (
|
||||
item.system.metadata?.hasResource &&
|
||||
refreshIsAllowed(refreshTypes, item.system.resource?.recovery)
|
||||
) {
|
||||
if (!refreshedActors[actor.id])
|
||||
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
||||
refreshedActors[actor.id].refreshed.add(
|
||||
game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[item.system.resource.recovery].label)
|
||||
);
|
||||
|
||||
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
|
||||
|
||||
const increasing =
|
||||
item.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id;
|
||||
updates[item.id].system = {
|
||||
...updates[item.id].system,
|
||||
'resource.value': increasing
|
||||
? 0
|
||||
: game.system.api.documents.DhActiveEffect.effectSafeEval(
|
||||
Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
|
||||
)
|
||||
};
|
||||
}
|
||||
if (item.system.metadata?.hasActions) {
|
||||
const usedTypes = new Set();
|
||||
const actions = item.system.actions.filter(action => {
|
||||
if (refreshIsAllowed(refreshTypes, action.uses.recovery)) {
|
||||
usedTypes.add(action.uses.recovery);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
if (actions.length === 0) continue;
|
||||
|
||||
if (!refreshedActors[actor.id])
|
||||
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
||||
refreshedActors[actor.id].refreshed.add(
|
||||
...usedTypes.map(type => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[type].label))
|
||||
);
|
||||
|
||||
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
|
||||
|
||||
updates[item.id].system = {
|
||||
...updates[item.id].system,
|
||||
...actions.reduce(
|
||||
(acc, action) => {
|
||||
acc.actions[action.id] = { 'uses.value': 0 };
|
||||
return acc;
|
||||
},
|
||||
{ actions: updates[item.id].system.actions ?? {} }
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for (let key in updates) {
|
||||
const update = updates[key];
|
||||
await actor.items.get(key).update(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const types = refreshTypes.map(x => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[x].label)).join(', ');
|
||||
|
||||
if (sendNotificationMessage) {
|
||||
ui.notifications.info(
|
||||
game.i18n.format('DAGGERHEART.UI.Notifications.gmMenuRefresh', {
|
||||
types: `[${types}]`
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (sendRefreshMessage) {
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
const msg = {
|
||||
user: game.user.id,
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/refreshMessage.hbs',
|
||||
{
|
||||
types: types
|
||||
}
|
||||
),
|
||||
title: game.i18n.localize('DAGGERHEART.UI.Chat.refreshMessage.title'),
|
||||
speaker: cls.getSpeaker()
|
||||
};
|
||||
|
||||
cls.create(msg);
|
||||
}
|
||||
|
||||
return refreshedActors;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ export const preloadHandlebarsTemplates = async function () {
|
|||
'systems/daggerheart/templates/dialogs/downtime/activities.hbs',
|
||||
'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs',
|
||||
'systems/daggerheart/templates/ui/chat/parts/roll-part.hbs',
|
||||
'systems/daggerheart/templates/ui/chat/parts/description-part.hbs',
|
||||
'systems/daggerheart/templates/ui/chat/parts/damage-part.hbs',
|
||||
'systems/daggerheart/templates/ui/chat/parts/target-part.hbs',
|
||||
'systems/daggerheart/templates/ui/chat/parts/button-part.hbs',
|
||||
|
|
|
|||
|
|
@ -212,7 +212,6 @@ export async function runMigrations() {
|
|||
}
|
||||
|
||||
if (foundry.utils.isNewerVersion('1.5.5', lastMigrationVersion)) {
|
||||
/* Clear out Environments that were added directly from compendium */
|
||||
for (const scene of game.scenes) {
|
||||
if (!scene.flags.daggerheart) continue;
|
||||
const systemData = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart);
|
||||
|
|
@ -227,25 +226,6 @@ export async function runMigrations() {
|
|||
|
||||
lastMigrationVersion = '1.5.5';
|
||||
}
|
||||
|
||||
if (foundry.utils.isNewerVersion('1.6.0', lastMigrationVersion)) {
|
||||
/* Delevel any companions that are higher level than their partner character */
|
||||
for (const companion of game.actors.filter(x => x.type === 'companion')) {
|
||||
if (companion.system.levelData.level.current <= 1) continue;
|
||||
|
||||
if (!companion.system.partner) {
|
||||
await companion.updateLevel(1);
|
||||
} else {
|
||||
const endLevel = companion.system.partner.system.levelData.level.current;
|
||||
if (endLevel < companion.system.levelData.level.current) {
|
||||
companion.system.levelData.level.changed = companion.system.levelData.level.current;
|
||||
await companion.updateLevel(endLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastMigrationVersion = '1.6.0';
|
||||
}
|
||||
//#endregion
|
||||
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
DhHomebrewSettings,
|
||||
DhVariantRuleSettings
|
||||
} from '../applications/settings/_module.mjs';
|
||||
import { CompendiumBrowserSettings, DhTagTeamRoll } from '../data/_module.mjs';
|
||||
import { DhTagTeamRoll } from '../data/_module.mjs';
|
||||
|
||||
export const registerDHSettings = () => {
|
||||
registerMenuSettings();
|
||||
|
|
@ -126,7 +126,7 @@ const registerNonConfigSettings = () => {
|
|||
type: Number,
|
||||
default: 0,
|
||||
onChange: () => {
|
||||
if (ui.resources) ui.resources.render();
|
||||
if (ui.resources) ui.resources.render({ force: true });
|
||||
ui.combat.render({ force: true });
|
||||
}
|
||||
});
|
||||
|
|
@ -142,12 +142,6 @@ const registerNonConfigSettings = () => {
|
|||
config: false,
|
||||
type: DhTagTeamRoll
|
||||
});
|
||||
|
||||
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings, {
|
||||
scope: 'client',
|
||||
config: false,
|
||||
type: CompendiumBrowserSettings
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -38,8 +38,7 @@ export const RefreshType = {
|
|||
Countdown: 'DhCoundownRefresh',
|
||||
TagTeamRoll: 'DhTagTeamRollRefresh',
|
||||
EffectsDisplay: 'DhEffectsDisplayRefresh',
|
||||
Scene: 'DhSceneRefresh',
|
||||
CompendiumBrowser: 'DhCompendiumBrowserRefresh'
|
||||
Scene: 'DhSceneRefresh'
|
||||
};
|
||||
|
||||
export const registerSocketHooks = () => {
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
"enabled": false
|
||||
},
|
||||
"flatMultiplier": 3,
|
||||
"dice": "d20",
|
||||
"dice": "d10",
|
||||
"bonus": null,
|
||||
"multiplier": "flat"
|
||||
},
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue