mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 23:13:39 +02:00
[V14] TagTeamRoll Rework (#1732)
* Initial rolls working * Fixed reroll * more * More work * Added results section * . * Visual improvements * . * Removed traces of old TagTeamRoll * Added initiator handling * Added updating for other players * Fixed sync start * Completed finish method * Damage reroll * Fixed localization * Fixed crit damage * Fixes * Added visual of advantage and disadvantage dice
This commit is contained in:
parent
a7eda31aec
commit
3031531b14
46 changed files with 1301 additions and 738 deletions
|
|
@ -402,6 +402,17 @@ Hooks.on('chatMessage', (_, message) => {
|
|||
}
|
||||
});
|
||||
|
||||
Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => {
|
||||
if (data.openForAllPlayers && data.partyId) {
|
||||
const party = game.actors.get(data.partyId);
|
||||
if (!party) return;
|
||||
|
||||
const dialog = new game.system.api.applications.dialogs.TagTeamDialog(party);
|
||||
dialog.tabGroups.application = 'tagTeamRoll';
|
||||
await dialog.render({ force: true });
|
||||
}
|
||||
});
|
||||
|
||||
const updateActorsRangeDependentEffects = async token => {
|
||||
const rangeMeasurement = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
|
|
|
|||
36
lang/en.json
36
lang/en.json
|
|
@ -677,16 +677,35 @@
|
|||
},
|
||||
"TagTeamSelect": {
|
||||
"title": "Tag Team Roll",
|
||||
"FIELDS": {
|
||||
"initiator": {
|
||||
"memberId": { "label": "Initiating Character" },
|
||||
"cost": { "label": "Initiation Cost" }
|
||||
}
|
||||
},
|
||||
"leaderTitle": "Initiating Character",
|
||||
"membersTitle": "Participants",
|
||||
"partyTeam": "Party Team",
|
||||
"hopeCost": "Hope Cost",
|
||||
"initiatingCharacter": "Initiating Character",
|
||||
"selectParticipants": "Select the two participants",
|
||||
"startTagTeamRoll": "Start Tag Team Roll",
|
||||
"openDialogForAll": "Open Dialog For All",
|
||||
"rollType": "Roll Type",
|
||||
"makeYourRoll": "Make your roll",
|
||||
"cancelTagTeamRoll": "Cancel Tag Team Roll",
|
||||
"finishTagTeamRoll": "Finish Tag Team Roll",
|
||||
"linkMessageHint": "Make a roll from your character sheet to link it to the Tag Team Roll",
|
||||
"damageNotRolled": "Damage not rolled in chat message yet",
|
||||
"insufficientHope": "The initiating character doesn't have enough hope",
|
||||
"createTagTeam": "Create TagTeam Roll",
|
||||
"chatMessageRollTitle": "Roll"
|
||||
"createTagTeam": "Create Tag Team Roll",
|
||||
"chatMessageRollTitle": "Roll",
|
||||
"cancelConfirmTitle": "Cancel Tag Team Roll",
|
||||
"cancelConfirmText": "Are you sure you want to cancel the Tag Team Roll? This will close it for all other players too.",
|
||||
"hints": {
|
||||
"completeRolls": "Set up and complete the rolls for the characters",
|
||||
"selectRoll": "Select which roll value to be used for the Tag Team"
|
||||
}
|
||||
},
|
||||
"TokenConfig": {
|
||||
"actorSizeUsed": "Actor size is set, determining the dimensions"
|
||||
|
|
@ -1230,6 +1249,11 @@
|
|||
"selectType": "Select Action Type",
|
||||
"selectAction": "Action Selection"
|
||||
},
|
||||
"TagTeamRollTypes": {
|
||||
"trait": "Trait",
|
||||
"ability": "Ability",
|
||||
"damageAbility": "Damage Ability"
|
||||
},
|
||||
"TargetTypes": {
|
||||
"any": "Any",
|
||||
"friendly": "Friendly",
|
||||
|
|
@ -1882,6 +1906,10 @@
|
|||
}
|
||||
},
|
||||
"GENERAL": {
|
||||
"Ability": {
|
||||
"single": "Ability",
|
||||
"plural": "Abilities"
|
||||
},
|
||||
"Action": {
|
||||
"single": "Action",
|
||||
"plural": "Actions"
|
||||
|
|
@ -2335,6 +2363,10 @@
|
|||
"rerolled": "Rerolled",
|
||||
"rerollThing": "Reroll {thing}",
|
||||
"resource": "Resource",
|
||||
"result": {
|
||||
"single": "Result",
|
||||
"plural": "Results"
|
||||
},
|
||||
"roll": "Roll",
|
||||
"rollAll": "Roll All",
|
||||
"rollDamage": "Roll Damage",
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
updateIsAdvantage: this.updateIsAdvantage,
|
||||
selectExperience: this.selectExperience,
|
||||
toggleReaction: this.toggleReaction,
|
||||
toggleTagTeamRoll: this.toggleTagTeamRoll,
|
||||
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||
submitRoll: this.submitRoll
|
||||
},
|
||||
|
|
@ -133,12 +132,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.reactionOverride = this.reactionOverride;
|
||||
}
|
||||
|
||||
const tagTeamSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
if (this.actor && tagTeamSetting.members[this.actor.id] && !this.config.skips?.createMessage) {
|
||||
context.activeTagTeamRoll = true;
|
||||
context.tagTeamSelected = this.config.tagTeamSelected;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -215,11 +208,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
}
|
||||
|
||||
static toggleTagTeamRoll() {
|
||||
this.config.tagTeamSelected = !this.config.tagTeamSelected;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static toggleSelectedEffect(_event, button) {
|
||||
this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected;
|
||||
this.render();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
|
@ -123,16 +121,8 @@ export default class RerollDamageDialog extends HandlebarsApplicationMixin(Appli
|
|||
return acc;
|
||||
}, {})
|
||||
};
|
||||
|
||||
await this.message.update(update);
|
||||
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
|
||||
await this.close();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { MemberData } from '../../data/tagTeamData.mjs';
|
||||
import { getCritDamageBonus } from '../../helpers/utils.mjs';
|
||||
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import Party from '../sheets/actors/party.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -7,15 +9,23 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
constructor(party) {
|
||||
super();
|
||||
|
||||
this.data = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
this.party = party;
|
||||
this.partyMembers = party.system.partyMembers
|
||||
.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||
.map(member => ({
|
||||
...member.toObject(),
|
||||
uuid: member.uuid,
|
||||
id: member.id,
|
||||
selected: false
|
||||
}));
|
||||
this.intiator = null;
|
||||
this.openForAllPlayers = true;
|
||||
|
||||
this.setupHooks = Hooks.on(socketEvent.Refresh, ({ refreshType }) => {
|
||||
if (refreshType === RefreshType.TagTeamRoll) {
|
||||
this.data = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
this.tabGroups.application = Object.keys(party.system.tagTeam.members).length
|
||||
? 'tagTeamRoll'
|
||||
: 'initialization';
|
||||
|
||||
Hooks.on(socketEvent.Refresh, this.tagTeamRefresh.bind());
|
||||
}
|
||||
|
||||
get title() {
|
||||
|
|
@ -24,324 +34,633 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'TagTeamDialog',
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'],
|
||||
position: { width: 550, height: 'auto' },
|
||||
actions: {
|
||||
removeMember: TagTeamDialog.#removeMember,
|
||||
unlinkMessage: TagTeamDialog.#unlinkMessage,
|
||||
selectMessage: TagTeamDialog.#selectMessage,
|
||||
createTagTeam: TagTeamDialog.#createTagTeam
|
||||
toggleSelectMember: TagTeamDialog.#toggleSelectMember,
|
||||
startTagTeamRoll: TagTeamDialog.#startTagTeamRoll,
|
||||
makeRoll: TagTeamDialog.#makeRoll,
|
||||
removeRoll: TagTeamDialog.#removeRoll,
|
||||
rerollDice: TagTeamDialog.#rerollDice,
|
||||
makeDamageRoll: TagTeamDialog.#makeDamageRoll,
|
||||
removeDamageRoll: TagTeamDialog.#removeDamageRoll,
|
||||
rerollDamageDice: TagTeamDialog.#rerollDamageDice,
|
||||
selectRoll: TagTeamDialog.#selectRoll,
|
||||
cancelRoll: TagTeamDialog.#onCancelRoll,
|
||||
finishRoll: TagTeamDialog.#finishRoll
|
||||
},
|
||||
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
application: {
|
||||
id: 'tag-team-dialog',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog.hbs'
|
||||
initialization: {
|
||||
id: 'initialization',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/initialization.hbs'
|
||||
},
|
||||
tagTeamRoll: {
|
||||
id: 'tagTeamRoll',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
application: {
|
||||
tabs: [{ id: 'initialization' }, { id: 'tagTeamRoll' }]
|
||||
}
|
||||
};
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
for (const element of htmlElement.querySelectorAll('.roll-type-select'))
|
||||
element.addEventListener('change', this.updateRollType.bind(this));
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.hopeCost = this.hopeCost;
|
||||
context.data = this.data;
|
||||
|
||||
context.memberOptions = this.party.filter(c => !this.data.members[c.id]);
|
||||
context.selectedCharacterOptions = this.party.filter(c => this.data.members[c.id]);
|
||||
|
||||
context.members = Object.keys(this.data.members).map(id => {
|
||||
const roll = this.data.members[id].messageId ? game.messages.get(this.data.members[id].messageId) : null;
|
||||
|
||||
context.usesDamage =
|
||||
context.usesDamage === undefined
|
||||
? roll?.system.hasDamage
|
||||
: context.usesDamage && roll?.system.hasDamage;
|
||||
return {
|
||||
character: this.party.find(x => x.id === id),
|
||||
selected: this.data.members[id].selected,
|
||||
roll: roll,
|
||||
damageValues: roll
|
||||
? Object.keys(roll.system.damage).map(key => ({
|
||||
key: key,
|
||||
name: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[key].label),
|
||||
total: roll.system.damage[key].total
|
||||
}))
|
||||
: null
|
||||
};
|
||||
});
|
||||
|
||||
const initiatorChar = this.party.find(x => x.id === this.data.initiator.id);
|
||||
context.initiator = {
|
||||
character: initiatorChar,
|
||||
cost: this.data.initiator.cost
|
||||
};
|
||||
|
||||
const selectedMember = Object.values(context.members).find(x => x.selected && x.roll);
|
||||
const selectedIsCritical = selectedMember?.roll?.system?.isCritical;
|
||||
context.selectedData = {
|
||||
result: selectedMember
|
||||
? `${selectedMember.roll.system.roll.total} ${selectedMember.roll.system.roll.result.label}`
|
||||
: null,
|
||||
damageValues: null
|
||||
};
|
||||
|
||||
for (const member of Object.values(context.members)) {
|
||||
if (!member.roll) continue;
|
||||
if (context.usesDamage) {
|
||||
if (!context.selectedData.damageValues) context.selectedData.damageValues = {};
|
||||
for (let damage of member.damageValues) {
|
||||
const damageTotal = member.roll.system.isCritical
|
||||
? damage.total
|
||||
: selectedIsCritical
|
||||
? damage.total + (await getCritDamageBonus(member.roll.system.damage[damage.key].formula))
|
||||
: damage.total;
|
||||
if (context.selectedData.damageValues[damage.key]) {
|
||||
context.selectedData.damageValues[damage.key].total += damageTotal;
|
||||
} else {
|
||||
context.selectedData.damageValues[damage.key] = {
|
||||
...foundry.utils.deepClone(damage),
|
||||
total: damageTotal
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.showResult = Object.values(context.members).reduce((enabled, member) => {
|
||||
if (!member.roll) return enabled;
|
||||
if (context.usesDamage) {
|
||||
enabled = enabled === null ? member.damageValues.length > 0 : enabled && member.damageValues.length > 0;
|
||||
} else {
|
||||
enabled = enabled === null ? Boolean(member.roll) : enabled && Boolean(member.roll);
|
||||
}
|
||||
|
||||
return enabled;
|
||||
}, null);
|
||||
|
||||
context.createDisabled =
|
||||
!context.selectedData.result ||
|
||||
!this.data.initiator.id ||
|
||||
Object.keys(this.data.members).length === 0 ||
|
||||
Object.values(context.members).some(x =>
|
||||
context.usesDamage ? !x.damageValues || x.damageValues.length === 0 : !x.roll
|
||||
);
|
||||
context.isEditable = this.getIsEditable();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async updateSource(update) {
|
||||
await this.data.updateSource(update);
|
||||
async _preparePartContext(partId, context, options) {
|
||||
const partContext = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
case 'initialization':
|
||||
partContext.tagTeamFields = this.party.system.schema.fields.tagTeam.fields;
|
||||
partContext.memberSelection = this.partyMembers;
|
||||
const selectedMembers = partContext.memberSelection.filter(x => x.selected);
|
||||
|
||||
if (game.user.isGM) {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, this.data.toObject());
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
partContext.allSelected = selectedMembers.length === 2;
|
||||
partContext.canStartTagTeam = partContext.allSelected && this.initiator;
|
||||
partContext.initiator = this.initiator;
|
||||
partContext.initiatorOptions = selectedMembers.map(x => ({ value: x.id, label: x.name }));
|
||||
partContext.initiatorDisabled = !selectedMembers.length;
|
||||
partContext.openForAllPlayers = this.openForAllPlayers;
|
||||
|
||||
break;
|
||||
case 'tagTeamRoll':
|
||||
partContext.fields = this.party.system.schema.fields.tagTeam.fields;
|
||||
partContext.data = this.party.system.tagTeam;
|
||||
partContext.rollTypes = CONFIG.DH.GENERAL.tagTeamRollTypes;
|
||||
partContext.traitOptions = CONFIG.DH.ACTOR.abilities;
|
||||
|
||||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||
const critSelected = !selectedRoll
|
||||
? undefined
|
||||
: (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
|
||||
|
||||
partContext.members = {};
|
||||
for (const actorId in this.party.system.tagTeam.members) {
|
||||
const data = this.party.system.tagTeam.members[actorId];
|
||||
const actor = game.actors.get(actorId);
|
||||
|
||||
const rollOptions = [];
|
||||
const damageRollOptions = [];
|
||||
for (const item of actor.items) {
|
||||
if (item.system.metadata.hasActions) {
|
||||
const actions = [
|
||||
...item.system.actions,
|
||||
...(item.system.attack ? [item.system.attack] : [])
|
||||
];
|
||||
for (const action of actions) {
|
||||
if (action.hasRoll) {
|
||||
const actionItem = {
|
||||
value: action.uuid,
|
||||
label: action.name,
|
||||
group: item.name,
|
||||
baseAction: action.baseAction
|
||||
};
|
||||
rollOptions.push(actionItem);
|
||||
if (action.hasDamage) damageRollOptions.push(actionItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const damage = data.rollData?.options?.damage;
|
||||
partContext.hasDamage |= Boolean(damage);
|
||||
const critHitPointsDamage = await this.getCriticalDamage(damage);
|
||||
|
||||
partContext.members[actorId] = {
|
||||
...data,
|
||||
isEditable: actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER),
|
||||
key: actorId,
|
||||
readyToRoll: Boolean(data.rollChoice),
|
||||
hasRolled: Boolean(data.rollData),
|
||||
rollOptions,
|
||||
damageRollOptions,
|
||||
damage: damage,
|
||||
critDamage: critHitPointsDamage,
|
||||
useCritDamage:
|
||||
critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical)
|
||||
};
|
||||
}
|
||||
|
||||
partContext.hintText = await this.getInfoTexts(this.party.system.tagTeam.members);
|
||||
partContext.joinedRoll = await this.getJoinedRoll({
|
||||
overrideIsCritical: critSelected,
|
||||
displayVersion: true
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return partContext;
|
||||
}
|
||||
|
||||
static async updateData(_event, _, formData) {
|
||||
const { initiator, openForAllPlayers, ...partyData } = foundry.utils.expandObject(formData.object);
|
||||
this.initiator = initiator;
|
||||
this.openForAllPlayers = openForAllPlayers !== undefined ? openForAllPlayers : this.openForAllPlayers;
|
||||
|
||||
this.updatePartyData(partyData);
|
||||
}
|
||||
|
||||
async updatePartyData(updata, options = { render: true }) {
|
||||
await this.party.update(updata);
|
||||
|
||||
if (options.render) {
|
||||
this.render(true);
|
||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateSetting,
|
||||
uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll,
|
||||
update: this.data.toObject(),
|
||||
refresh: { refreshType: RefreshType.TagTeamRoll }
|
||||
}
|
||||
data: { refreshType: RefreshType.TagTeamRoll, action: 'refresh' }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static async updateData(_event, _element, formData) {
|
||||
const { selectedAddMember, initiator } = foundry.utils.expandObject(formData.object);
|
||||
const update = { initiator: initiator };
|
||||
if (selectedAddMember) {
|
||||
const member = await foundry.utils.fromUuid(selectedAddMember);
|
||||
update[`members.${member.id}`] = { messageId: null };
|
||||
getIsEditable() {
|
||||
return this.party.system.partyMembers.some(actor => {
|
||||
const selected = Boolean(this.party.system.tagTeam.members[actor.id]);
|
||||
return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER);
|
||||
});
|
||||
}
|
||||
|
||||
await this.updateSource(update);
|
||||
tagTeamRefresh = ({ refreshType, action }) => {
|
||||
if (refreshType !== RefreshType.TagTeamRoll) return;
|
||||
|
||||
switch (action) {
|
||||
case 'startTagTeamRoll':
|
||||
this.tabGroups.application = 'tagTeamRoll';
|
||||
break;
|
||||
case 'refresh':
|
||||
this.render();
|
||||
break;
|
||||
case 'close':
|
||||
this.close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
async close(options = {}) {
|
||||
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
|
||||
if (options.closeKey) return;
|
||||
|
||||
Hooks.off(socketEvent.Refresh, this.tagTeamRefresh);
|
||||
return super.close(options);
|
||||
}
|
||||
|
||||
static async #removeMember(_, button) {
|
||||
const update = { [`members.${button.dataset.characterId}`]: _del };
|
||||
if (this.data.initiator.id === button.dataset.characterId) {
|
||||
update.iniator = { id: null };
|
||||
}
|
||||
|
||||
await this.updateSource(update);
|
||||
}
|
||||
|
||||
static async #unlinkMessage(_, button) {
|
||||
await this.updateSource({ [`members.${button.id}.messageId`]: null });
|
||||
}
|
||||
|
||||
static async #selectMessage(_, button) {
|
||||
const member = this.data.members[button.id];
|
||||
const currentSelected = Object.keys(this.data.members).find(key => this.data.members[key].selected);
|
||||
const curretSelectedUpdate =
|
||||
currentSelected && currentSelected !== button.id ? { [`${currentSelected}`]: { selected: false } } : {};
|
||||
await this.updateSource({
|
||||
members: {
|
||||
[`${button.id}`]: { selected: !member.selected },
|
||||
...curretSelectedUpdate
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async #createTagTeam() {
|
||||
const mainRollId = Object.keys(this.data.members).find(key => this.data.members[key].selected);
|
||||
const mainRoll = game.messages.get(this.data.members[mainRollId].messageId);
|
||||
|
||||
if (this.data.initiator.cost) {
|
||||
const initiator = this.party.find(x => x.id === this.data.initiator.id);
|
||||
if (initiator.system.resources.hope.value < this.data.initiator.cost) {
|
||||
checkInitiatorHopeError(initiator) {
|
||||
if (initiator.cost && initiator.memberId) {
|
||||
const actor = game.actors.get(initiator.memberId);
|
||||
if (actor.system.resources.hope.value < initiator.cost) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.insufficientHope')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const secondaryRolls = Object.keys(this.data.members)
|
||||
.filter(key => key !== mainRollId)
|
||||
.map(key => game.messages.get(this.data.members[key].messageId));
|
||||
//#region Initialization
|
||||
static #toggleSelectMember(_, button) {
|
||||
const member = this.partyMembers.find(x => x.id === button.dataset.id);
|
||||
if (member.selected && this.initiator?.memberId === member.id) this.initiator = null;
|
||||
|
||||
const systemData = foundry.utils.deepClone(mainRoll).system.toObject();
|
||||
const criticalRoll = systemData.roll.isCritical;
|
||||
for (let roll of secondaryRolls) {
|
||||
if (roll.system.hasDamage) {
|
||||
for (let key in roll.system.damage) {
|
||||
var damage = roll.system.damage[key];
|
||||
const damageTotal =
|
||||
!roll.system.isCritical && criticalRoll
|
||||
? (await getCritDamageBonus(damage.formula)) + damage.total
|
||||
: damage.total;
|
||||
const updatedDamageParts = damage.parts;
|
||||
if (systemData.damage[key]) {
|
||||
if (!roll.system.isCritical && criticalRoll) {
|
||||
for (let part of updatedDamageParts) {
|
||||
member.selected = !member.selected;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #startTagTeamRoll() {
|
||||
const error = this.checkInitiatorHopeError(this.initiator);
|
||||
if (error) return error;
|
||||
|
||||
await this.party.update({
|
||||
'system.tagTeam': _replace(
|
||||
new game.system.api.data.TagTeamData({
|
||||
...this.party.system.tagTeam.toObject(),
|
||||
initiator: this.initiator,
|
||||
members: this.partyMembers.reduce((acc, member) => {
|
||||
if (member.selected)
|
||||
acc[member.id] = {
|
||||
name: member.name,
|
||||
img: member.img,
|
||||
rollType: CONFIG.DH.GENERAL.tagTeamRollTypes.trait.id
|
||||
};
|
||||
return acc;
|
||||
}, {})
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
const hookData = { openForAllPlayers: this.openForAllPlayers, partyId: this.party.id };
|
||||
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, hookData);
|
||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.TagTeamStart,
|
||||
data: hookData
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
//#endregion
|
||||
//#region Tag Team Roll
|
||||
|
||||
async getInfoTexts(members) {
|
||||
let rollsAreFinished = true;
|
||||
let rollIsSelected = false;
|
||||
for (const member of Object.values(members)) {
|
||||
const rollFinished = Boolean(member.rollData);
|
||||
const damageFinished =
|
||||
member.rollData?.options?.hasDamage !== undefined ? member.rollData.options.damage : true;
|
||||
|
||||
rollsAreFinished = rollsAreFinished && rollFinished && damageFinished;
|
||||
rollIsSelected = rollIsSelected || member.selected;
|
||||
}
|
||||
|
||||
let hint = null;
|
||||
if (!rollsAreFinished) hint = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.hints.completeRolls');
|
||||
else if (!rollIsSelected) hint = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.hints.selectRoll');
|
||||
|
||||
return hint;
|
||||
}
|
||||
|
||||
async updateRollType(event) {
|
||||
this.updatePartyData({
|
||||
[`system.tagTeam.members.${event.target.dataset.member}`]: {
|
||||
rollType: event.target.value,
|
||||
rollChoice: null
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async #removeRoll(_, button) {
|
||||
this.updatePartyData({
|
||||
[`system.tagTeam.members.${button.dataset.member}`]: {
|
||||
rollData: null,
|
||||
rollChoice: null,
|
||||
selected: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async #makeRoll(event, button) {
|
||||
const { member } = button.dataset;
|
||||
|
||||
let result = null;
|
||||
switch (this.party.system.tagTeam.members[member].rollType) {
|
||||
case CONFIG.DH.GENERAL.tagTeamRollTypes.trait.id:
|
||||
result = await this.makeTraitRoll(member);
|
||||
break;
|
||||
case CONFIG.DH.GENERAL.tagTeamRollTypes.ability.id:
|
||||
case CONFIG.DH.GENERAL.tagTeamRollTypes.damageAbility.id:
|
||||
result = await this.makeAbilityRoll(event, member);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!result) return;
|
||||
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
|
||||
const rollData = result.messageRoll.toJSON();
|
||||
delete rollData.options.messageRoll;
|
||||
this.updatePartyData({
|
||||
[`system.tagTeam.members.${member}.rollData`]: rollData
|
||||
});
|
||||
}
|
||||
|
||||
async makeTraitRoll(memberKey) {
|
||||
const actor = game.actors.find(x => x.id === memberKey);
|
||||
if (!actor) return;
|
||||
|
||||
const memberData = this.party.system.tagTeam.members[memberKey];
|
||||
return await actor.rollTrait(memberData.rollChoice, {
|
||||
skips: {
|
||||
createMessage: true,
|
||||
resources: true,
|
||||
triggers: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async makeAbilityRoll(event, memberKey) {
|
||||
const actor = game.actors.find(x => x.id === memberKey);
|
||||
if (!actor) return;
|
||||
|
||||
const memberData = this.party.system.tagTeam.members[memberKey];
|
||||
const action = await foundry.utils.fromUuid(memberData.rollChoice);
|
||||
|
||||
return await action.use(event, {
|
||||
skips: {
|
||||
createMessage: true,
|
||||
resources: true,
|
||||
triggers: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async #rerollDice(_, button) {
|
||||
const { member, diceType } = button.dataset;
|
||||
const memberData = this.party.system.tagTeam.members[member];
|
||||
|
||||
const dieIndex = diceType === 'hope' ? 0 : diceType === 'fear' ? 2 : 4;
|
||||
|
||||
const { parsedRoll, newRoll } = await game.system.api.dice.DualityRoll.reroll(
|
||||
memberData.rollData,
|
||||
dieIndex,
|
||||
diceType
|
||||
);
|
||||
const rollData = parsedRoll.toJSON();
|
||||
this.updatePartyData({
|
||||
[`system.tagTeam.members.${member}.rollData`]: {
|
||||
...rollData,
|
||||
options: {
|
||||
...rollData.options,
|
||||
roll: newRoll
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async #makeDamageRoll(event, button) {
|
||||
const { memberKey } = button.dataset;
|
||||
const actor = game.actors.find(x => x.id === memberKey);
|
||||
if (!actor) return;
|
||||
|
||||
const memberData = this.party.system.tagTeam.members[memberKey];
|
||||
const action = await foundry.utils.fromUuid(memberData.rollChoice);
|
||||
const config = {
|
||||
...memberData.rollData.options,
|
||||
dialog: {
|
||||
configure: !event.shiftKey
|
||||
},
|
||||
skips: {
|
||||
createMessage: true,
|
||||
resources: true,
|
||||
triggers: true
|
||||
}
|
||||
};
|
||||
|
||||
await action.workflow.get('damage').execute(config, null, true);
|
||||
if (!config.damage) return;
|
||||
|
||||
// const damage = config.roll.isCritical ? await this.getNonCriticalDamage(config, actor) : config.damage;
|
||||
|
||||
const current = this.party.system.tagTeam.members[memberKey].rollData;
|
||||
await this.updatePartyData({
|
||||
[`system.tagTeam.members.${memberKey}.rollData`]: {
|
||||
...current,
|
||||
options: {
|
||||
...current.options,
|
||||
damage: config.damage
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async #removeDamageRoll(_, button) {
|
||||
const { memberKey } = button.dataset;
|
||||
const current = this.party.system.tagTeam.members[memberKey].rollData;
|
||||
this.updatePartyData({
|
||||
[`system.tagTeam.members.${memberKey}.rollData`]: {
|
||||
...current,
|
||||
options: {
|
||||
...current.options,
|
||||
damage: null
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async #rerollDamageDice(_, button) {
|
||||
const { memberKey, damageKey, part, dice } = button.dataset;
|
||||
const memberData = this.party.system.tagTeam.members[memberKey];
|
||||
const partData = memberData.rollData.options.damage[damageKey].parts[part];
|
||||
const activeDiceResultKey = Object.keys(partData.dice[dice].results).find(
|
||||
index => partData.dice[dice].results[index].active
|
||||
);
|
||||
const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(
|
||||
partData,
|
||||
dice,
|
||||
activeDiceResultKey
|
||||
);
|
||||
|
||||
const rollData = this.party.system.tagTeam.members[memberKey].rollData;
|
||||
rollData.options.damage[damageKey].parts = rollData.options.damage[damageKey].parts.map((damagePart, index) => {
|
||||
if (index !== Number.parseInt(part)) return damagePart;
|
||||
|
||||
return {
|
||||
...damagePart,
|
||||
total: parsedRoll.total,
|
||||
dice: rerolledDice
|
||||
};
|
||||
});
|
||||
rollData.options.damage[damageKey].total = rollData.options.damage[damageKey].parts.reduce((acc, part) => {
|
||||
acc += part.total;
|
||||
return acc;
|
||||
}, 0);
|
||||
|
||||
this.updatePartyData({
|
||||
[`system.tagTeam.members.${memberKey}.rollData`]: rollData
|
||||
});
|
||||
}
|
||||
|
||||
async getCriticalDamage(damage) {
|
||||
const newDamage = foundry.utils.deepClone(damage);
|
||||
for (let key in newDamage) {
|
||||
var damage = newDamage[key];
|
||||
damage.formula = '';
|
||||
damage.total = 0;
|
||||
|
||||
for (let part of damage.parts) {
|
||||
const criticalDamage = await getCritDamageBonus(part.formula);
|
||||
if (criticalDamage) {
|
||||
damage.formula = `${damage.formula} + ${criticalDamage}`;
|
||||
part.formula = `${part.formula} + ${criticalDamage}`;
|
||||
part.modifierTotal = part.modifierTotal + criticalDamage;
|
||||
part.modifierTotal += criticalDamage;
|
||||
part.total += criticalDamage;
|
||||
part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`;
|
||||
part.roll = new Roll(part.formula);
|
||||
}
|
||||
|
||||
damage.formula = [damage.formula, part.formula].filter(x => x).join(' + ');
|
||||
damage.total += part.total;
|
||||
}
|
||||
}
|
||||
|
||||
systemData.damage[key].formula = `${systemData.damage[key].formula} + ${damage.formula}`;
|
||||
systemData.damage[key].total += damageTotal;
|
||||
systemData.damage[key].parts = [...systemData.damage[key].parts, ...updatedDamageParts];
|
||||
return newDamage;
|
||||
}
|
||||
|
||||
async getNonCriticalDamage(config) {
|
||||
const newDamage = foundry.utils.deepClone(config.damage);
|
||||
for (let key in newDamage) {
|
||||
var damage = newDamage[key];
|
||||
damage.formula = '';
|
||||
damage.total = 0;
|
||||
|
||||
for (let part of damage.parts) {
|
||||
const critDamageBonus = await getCritDamageBonus(part.formula);
|
||||
part.modifierTotal -= critDamageBonus;
|
||||
part.total -= critDamageBonus;
|
||||
part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`;
|
||||
part.roll = new Roll(part.formula);
|
||||
|
||||
damage.formula = [damage.formula, part.formula].filter(x => x).join(' + ');
|
||||
damage.total += part.total;
|
||||
}
|
||||
}
|
||||
|
||||
return newDamage;
|
||||
}
|
||||
|
||||
static async #selectRoll(_, button) {
|
||||
const { memberKey } = button.dataset;
|
||||
this.updatePartyData({
|
||||
[`system.tagTeam.members`]: Object.entries(this.party.system.tagTeam.members).reduce(
|
||||
(acc, [key, member]) => {
|
||||
acc[key] = { selected: key === memberKey ? !member.selected : false };
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
async getJoinedRoll({ overrideIsCritical, displayVersion } = {}) {
|
||||
const memberValues = Object.values(this.party.system.tagTeam.members);
|
||||
const selectedRoll = memberValues.find(x => x.selected);
|
||||
let baseMainRoll = selectedRoll ?? memberValues[0];
|
||||
let baseSecondaryRoll = selectedRoll
|
||||
? memberValues.find(x => !x.selected)
|
||||
: memberValues.length > 1
|
||||
? memberValues[1]
|
||||
: null;
|
||||
|
||||
if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null;
|
||||
|
||||
const mainRoll = new MemberData(baseMainRoll.toObject());
|
||||
const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData;
|
||||
const systemData = mainRoll.rollData.options;
|
||||
const isCritical = overrideIsCritical ?? systemData.roll.isCritical;
|
||||
if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage);
|
||||
|
||||
if (secondaryRollData?.options.hasDamage) {
|
||||
const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical)
|
||||
? await this.getCriticalDamage(secondaryRollData.options.damage)
|
||||
: secondaryRollData.options.damage;
|
||||
if (systemData.damage) {
|
||||
for (const key in secondaryDamage) {
|
||||
const damage = secondaryDamage[key];
|
||||
systemData.damage[key].formula = [systemData.damage[key].formula, damage.formula]
|
||||
.filter(x => x)
|
||||
.join(' + ');
|
||||
systemData.damage[key].total += damage.total;
|
||||
systemData.damage[key].parts.push(...damage.parts);
|
||||
}
|
||||
} else {
|
||||
systemData.damage[key] = { ...damage, total: damageTotal, parts: updatedDamageParts };
|
||||
}
|
||||
}
|
||||
systemData.damage = secondaryDamage;
|
||||
}
|
||||
}
|
||||
|
||||
systemData.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
||||
return mainRoll;
|
||||
}
|
||||
|
||||
static async #onCancelRoll(_event, _button, options = { confirm: true }) {
|
||||
this.cancelRoll(options);
|
||||
}
|
||||
|
||||
async cancelRoll(options = { confirm: true }) {
|
||||
if (options.confirm) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.cancelConfirmTitle')
|
||||
},
|
||||
content: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.cancelConfirmText')
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
await this.updatePartyData(
|
||||
{
|
||||
'system.tagTeam': {
|
||||
initiator: null,
|
||||
members: _replace({})
|
||||
}
|
||||
},
|
||||
{ render: false }
|
||||
);
|
||||
|
||||
this.close();
|
||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: { refreshType: RefreshType.TagTeamRoll, action: 'close' }
|
||||
});
|
||||
}
|
||||
|
||||
static async #finishRoll() {
|
||||
const error = this.checkInitiatorHopeError(this.party.system.tagTeam.initiator);
|
||||
if (error) return error;
|
||||
|
||||
const mainRoll = (await this.getJoinedRoll()).rollData;
|
||||
|
||||
const mainActor = this.party.system.partyMembers.find(x => x.uuid === mainRoll.options.source.actor);
|
||||
mainRoll.options.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
||||
const cls = getDocumentClass('ChatMessage'),
|
||||
msgData = {
|
||||
type: 'dualityRoll',
|
||||
user: game.user.id,
|
||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'),
|
||||
speaker: cls.getSpeaker({ actor: this.party.find(x => x.id === mainRollId) }),
|
||||
system: systemData,
|
||||
rolls: mainRoll.rolls,
|
||||
speaker: cls.getSpeaker({ actor: mainActor }),
|
||||
system: mainRoll.options,
|
||||
rolls: [mainRoll],
|
||||
sound: null,
|
||||
flags: { core: { RollTable: true } }
|
||||
};
|
||||
|
||||
await cls.create(msgData);
|
||||
|
||||
/* Handle resource updates from the finished TagTeamRoll */
|
||||
const tagTeamData = this.party.system.tagTeam;
|
||||
const fearUpdate = { key: 'fear', value: null, total: null, enabled: true };
|
||||
for (let memberId of Object.keys(this.data.members)) {
|
||||
for (let memberId in tagTeamData.members) {
|
||||
const resourceUpdates = [];
|
||||
const rollGivesHope = systemData.roll.isCritical || systemData.roll.result.duality === 1;
|
||||
if (memberId === this.data.initiator.id) {
|
||||
const value = this.data.initiator.cost
|
||||
const rollGivesHope = mainRoll.options.roll.isCritical || mainRoll.options.roll.result.duality === 1;
|
||||
if (memberId === tagTeamData.initiator.memberId) {
|
||||
const value = tagTeamData.initiator.cost
|
||||
? rollGivesHope
|
||||
? 1 - this.data.initiator.cost
|
||||
: -this.data.initiator.cost
|
||||
? 1 - tagTeamData.initiator.cost
|
||||
: -tagTeamData.initiator.cost
|
||||
: 1;
|
||||
resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true });
|
||||
} else if (rollGivesHope) {
|
||||
resourceUpdates.push({ key: 'hope', value: 1, total: -1, enabled: true });
|
||||
}
|
||||
if (systemData.roll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true });
|
||||
if (systemData.roll.result.duality === -1) {
|
||||
if (mainRoll.options.roll.isCritical)
|
||||
resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true });
|
||||
if (mainRoll.options.roll.result.duality === -1) {
|
||||
fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1;
|
||||
fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1;
|
||||
}
|
||||
|
||||
this.party.find(x => x.id === memberId).modifyResource(resourceUpdates);
|
||||
game.actors.get(memberId).modifyResource(resourceUpdates);
|
||||
}
|
||||
|
||||
if (fearUpdate.value) {
|
||||
this.party.find(x => x.id === mainRollId).modifyResource([fearUpdate]);
|
||||
mainActor.modifyResource([fearUpdate]);
|
||||
}
|
||||
|
||||
/* Improve by fetching default from schema */
|
||||
const update = { members: [], initiator: { id: null, cost: 3 } };
|
||||
if (game.user.isGM) {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, update);
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateSetting,
|
||||
uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll,
|
||||
update: update,
|
||||
refresh: { refreshType: RefreshType.TagTeamRoll }
|
||||
}
|
||||
});
|
||||
}
|
||||
/* Fin */
|
||||
this.cancelRoll({ confirm: false });
|
||||
}
|
||||
|
||||
static async assignRoll(char, message) {
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
const character = settings.members[char.id];
|
||||
if (!character) return;
|
||||
|
||||
await settings.updateSource({ [`members.${char.id}.messageId`]: message.id });
|
||||
|
||||
if (game.user.isGM) {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, settings);
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateSetting,
|
||||
uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll,
|
||||
update: settings,
|
||||
refresh: { refreshType: RefreshType.TagTeamRoll }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async close(options = {}) {
|
||||
Hooks.off(socketEvent.Refresh, this.setupHooks);
|
||||
await super.close(options);
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||
import DhDeathMove from '../../dialogs/deathMove.mjs';
|
||||
import { abilities } from '../../../config/actorConfig.mjs';
|
||||
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
||||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||
|
|
@ -720,35 +719,16 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
* Rolls an attribute check based on the clicked button's dataset attribute.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #rollAttribute(event, button) {
|
||||
const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label);
|
||||
const config = {
|
||||
event: event,
|
||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: abilityLabel
|
||||
}),
|
||||
effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this.document),
|
||||
roll: {
|
||||
trait: button.dataset.attribute,
|
||||
type: 'trait'
|
||||
},
|
||||
hasRoll: true,
|
||||
actionType: 'action',
|
||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: abilityLabel
|
||||
})
|
||||
};
|
||||
const result = await this.document.diceRoll(config);
|
||||
static async #rollAttribute(_event, button) {
|
||||
const result = await this.document.rollTrait(button.dataset.attribute);
|
||||
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 })) ||
|
||||
{};
|
||||
config.resourceUpdates.addResources(costResources);
|
||||
await config.resourceUpdates.updateResources();
|
||||
result.resourceUpdates.addResources(costResources);
|
||||
await result.resourceUpdates.updateResources();
|
||||
}
|
||||
|
||||
//TODO: redo toggleEquipItem method
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ export default class Party extends DHBaseActorSheet {
|
|||
refeshActions: Party.#refeshActions,
|
||||
triggerRest: Party.#triggerRest,
|
||||
tagTeamRoll: Party.#tagTeamRoll,
|
||||
groupRoll: Party.#groupRoll,
|
||||
selectRefreshable: DaggerheartMenu.selectRefreshable,
|
||||
refreshActors: DaggerheartMenu.refreshActors
|
||||
groupRoll: Party.#groupRoll
|
||||
},
|
||||
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
|
||||
};
|
||||
|
|
@ -120,6 +118,7 @@ export default class Party extends DHBaseActorSheet {
|
|||
secrets: this.document.isOwner,
|
||||
relativeTo: this.document
|
||||
});
|
||||
context.tagTeamActive = Boolean(this.document.system.tagTeam.initiator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -255,11 +254,7 @@ export default class Party extends DHBaseActorSheet {
|
|||
}
|
||||
|
||||
static async #tagTeamRoll() {
|
||||
new game.system.api.applications.dialogs.TagTeamDialog(
|
||||
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||
).render({
|
||||
force: true
|
||||
});
|
||||
new game.system.api.applications.dialogs.TagTeamDialog(this.document).render({ force: true });
|
||||
}
|
||||
|
||||
static async #groupRoll(_params) {
|
||||
|
|
|
|||
|
|
@ -190,7 +190,24 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
const target = event.target.closest('[data-die-index]');
|
||||
|
||||
if (target.dataset.type === 'damage') {
|
||||
game.system.api.dice.DamageRoll.reroll(target, message);
|
||||
const { damageType, part, dice, result } = target.dataset;
|
||||
const damagePart = message.system.damage[damageType].parts[part];
|
||||
const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(damagePart, dice, result);
|
||||
const damageParts = message.system.damage[damageType].parts.map((damagePart, index) => {
|
||||
if (index !== Number(part)) return damagePart;
|
||||
return {
|
||||
...damagePart,
|
||||
total: parsedRoll.total,
|
||||
dice: rerolledDice
|
||||
};
|
||||
});
|
||||
const updateMessage = game.messages.get(message._id);
|
||||
await updateMessage.update({
|
||||
[`system.damage.${damageType}`]: {
|
||||
total: parsedRoll.total,
|
||||
parts: damageParts
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0];
|
||||
const rollClass =
|
||||
|
|
@ -204,20 +221,16 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
|
||||
const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message);
|
||||
const { newRoll, parsedRoll } = await rollClass.reroll(
|
||||
originalRoll_parsed,
|
||||
target.dataset.dieIndex,
|
||||
target.dataset.type
|
||||
);
|
||||
|
||||
await game.messages.get(message._id).update({
|
||||
'system.roll': newRoll,
|
||||
'rolls': [parsedRoll]
|
||||
});
|
||||
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,10 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
|||
if (game.activeTool === 'inFront') return { type: 'cone', x: 0, y: 0, radius: 0, angle: 180, hole };
|
||||
|
||||
const shape = super._createDragShapeData(event);
|
||||
const token = shape?.type === 'emanation' && shape.base?.type === 'token' ? this.#findTokenInBounds(event.interactionData.origin) : null;
|
||||
const token =
|
||||
shape?.type === 'emanation' && shape.base?.type === 'token'
|
||||
? this.#findTokenInBounds(event.interactionData.origin)
|
||||
: null;
|
||||
if (token) {
|
||||
shape.base.width = token.width;
|
||||
shape.base.height = token.height;
|
||||
|
|
|
|||
|
|
@ -959,6 +959,21 @@ export const sceneRangeMeasurementSetting = {
|
|||
}
|
||||
};
|
||||
|
||||
export const tagTeamRollTypes = {
|
||||
trait: {
|
||||
id: 'trait',
|
||||
label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.trait'
|
||||
},
|
||||
ability: {
|
||||
id: 'ability',
|
||||
label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.ability'
|
||||
},
|
||||
damageAbility: {
|
||||
id: 'damageAbility',
|
||||
label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.damageAbility'
|
||||
}
|
||||
};
|
||||
|
||||
export const activeEffectModes = {
|
||||
custom: {
|
||||
id: 'custom',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export const hooksConfig = {
|
||||
effectDisplayToggle: 'DHEffectDisplayToggle',
|
||||
lockedTooltipDismissed: 'DHLockedTooltipDismissed'
|
||||
lockedTooltipDismissed: 'DHLockedTooltipDismissed',
|
||||
tagTeamStart: 'DHTagTeamRollStart'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ export const gameSettings = {
|
|||
LevelTiers: 'LevelTiers',
|
||||
Countdowns: 'Countdowns',
|
||||
LastMigrationVersion: 'LastMigrationVersion',
|
||||
TagTeamRoll: 'TagTeamRoll',
|
||||
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||
CompendiumBrowserSettings: 'CompendiumBrowserSettings'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
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 { default as TagTeamData } from './tagTeamData.mjs';
|
||||
|
||||
export * as countdowns from './countdowns.mjs';
|
||||
export * as actions from './action/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -50,9 +50,8 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
|
||||
async use(event, options) {
|
||||
const result = await super.use(event, options);
|
||||
if (!result.message) return;
|
||||
|
||||
if (result.message.system.action.roll?.type === 'attack') {
|
||||
if (result.message?.system.action.roll?.type === 'attack') {
|
||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,10 +207,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
* @param {Event} event Event from the button used to trigger the Action
|
||||
* @returns {object}
|
||||
*/
|
||||
async use(event) {
|
||||
async use(event, configOptions = {}) {
|
||||
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||
|
||||
let config = this.prepareConfig(event);
|
||||
let config = this.prepareConfig(event, configOptions);
|
||||
if (!config) return;
|
||||
|
||||
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item);
|
||||
|
|
@ -231,7 +231,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||
|
||||
if (this.chatDisplay && !config.actionChatMessageHandled) await this.toChat();
|
||||
if (this.chatDisplay && !config.skips.createMessage && !config.actionChatMessageHandled) await this.toChat();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -241,7 +241,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
* @param {Event} event Event from the button used to trigger the Action
|
||||
* @returns {object}
|
||||
*/
|
||||
prepareBaseConfig(event) {
|
||||
prepareBaseConfig(event, configOptions = {}) {
|
||||
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} - `;
|
||||
|
|
@ -268,7 +268,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
data: this.getRollData(),
|
||||
evaluate: this.hasRoll,
|
||||
resourceUpdates: new ResourceUpdateMap(this.actor),
|
||||
targetUuid: this.targetUuid
|
||||
targetUuid: this.targetUuid,
|
||||
...configOptions
|
||||
};
|
||||
|
||||
DHBaseAction.applyKeybindings(config);
|
||||
|
|
@ -280,8 +281,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
* @param {Event} event Event from the button used to trigger the Action
|
||||
* @returns {object}
|
||||
*/
|
||||
prepareConfig(event) {
|
||||
const config = this.prepareBaseConfig(event);
|
||||
prepareConfig(event, configOptions = {}) {
|
||||
const config = this.prepareBaseConfig(event, configOptions);
|
||||
for (const clsField of Object.values(this.schema.fields)) {
|
||||
if (clsField?.prepareConfig) if (clsField.prepareConfig.call(this, config) === false) return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,19 +189,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
|||
return true;
|
||||
}
|
||||
|
||||
async _preDelete() {
|
||||
/* Clear all partyMembers from tagTeam setting.*/
|
||||
/* Revisit this when tagTeam is improved for many parties */
|
||||
if (this.parent.parties.size > 0) {
|
||||
const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
await tagTeam.updateSource({
|
||||
initiator: this.parent.id === tagTeam.initiator ? null : tagTeam.initiator,
|
||||
members: Object.keys(tagTeam.members).find(x => x === this.parent.id) ? { [this.parent.id]: _del } : {}
|
||||
});
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam);
|
||||
}
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, userId) {
|
||||
const allowed = await super._preUpdate(changes, options, userId);
|
||||
if (allowed === false) return;
|
||||
|
|
|
|||
|
|
@ -75,10 +75,6 @@ export default class DhEnvironment extends BaseDataActor {
|
|||
);
|
||||
scene.update({ 'flags.daggerheart.sceneEnvironments': newSceneEnvironments }).then(() => {
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Scene });
|
||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: { refreshType: RefreshType.TagTeamRoll }
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import BaseDataActor from './base.mjs';
|
||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
import TagTeamData from '../tagTeamData.mjs';
|
||||
|
||||
export default class DhParty extends BaseDataActor {
|
||||
/**@inheritdoc */
|
||||
|
|
@ -14,7 +15,8 @@ export default class DhParty extends BaseDataActor {
|
|||
handfuls: new fields.NumberField({ initial: 1, integer: true }),
|
||||
bags: new fields.NumberField({ initial: 0, integer: true }),
|
||||
chests: new fields.NumberField({ initial: 0, integer: true })
|
||||
})
|
||||
}),
|
||||
tagTeam: new fields.EmbeddedDataField(TagTeamData)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -40,23 +42,6 @@ export default class DhParty extends BaseDataActor {
|
|||
}
|
||||
}
|
||||
|
||||
async _preDelete() {
|
||||
/* Clear all partyMembers from tagTeam setting.*/
|
||||
/* Revisit this when tagTeam is improved for many parties */
|
||||
const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
await tagTeam.updateSource({
|
||||
initiator: this.partyMembers.some(x => x.id === tagTeam.initiator) ? null : tagTeam.initiator,
|
||||
members: Object.keys(tagTeam.members).reduce((acc, key) => {
|
||||
if (this.partyMembers.find(x => x.id === key)) {
|
||||
acc[key] = _del;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
});
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam);
|
||||
}
|
||||
|
||||
_onDelete(options, userId) {
|
||||
super._onDelete(options, userId);
|
||||
|
||||
|
|
|
|||
|
|
@ -50,9 +50,9 @@ export default class DamageField extends fields.SchemaField {
|
|||
formulas = DamageField.formatFormulas.call(this, formulas, config);
|
||||
|
||||
const damageConfig = {
|
||||
dialog: {},
|
||||
...config,
|
||||
roll: formulas,
|
||||
dialog: {},
|
||||
data: this.getRollData()
|
||||
};
|
||||
delete damageConfig.evaluate;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export default class EffectsField extends fields.ArrayField {
|
|||
static async execute(config, targets = null, force = false) {
|
||||
if (!config.hasEffect) return;
|
||||
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
|
||||
if (!message) {
|
||||
if (!message && !config.skips.createMessage) {
|
||||
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
||||
roll._evaluated = true;
|
||||
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export default class SaveField extends fields.SchemaField {
|
|||
if (!config.hasSave) return;
|
||||
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
|
||||
|
||||
if (!message) {
|
||||
if (!message && !config.skips.createMessage) {
|
||||
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
||||
roll._evaluated = true;
|
||||
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export default class IterableTypedObjectField extends foundry.data.fields.TypedO
|
|||
* This allows the functionality of a class but also allows foundry.utils.getType() to return "Object" instead of "Unknown".
|
||||
*/
|
||||
const IterableObjectPrototype = {
|
||||
[Symbol.iterator]: function*() {
|
||||
[Symbol.iterator]: function* () {
|
||||
for (const value of Object.values(this)) {
|
||||
yield value;
|
||||
}
|
||||
|
|
|
|||
47
module/data/tagTeamData.mjs
Normal file
47
module/data/tagTeamData.mjs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
export default class TagTeamData extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
initiator: new fields.SchemaField(
|
||||
{
|
||||
memberId: new fields.StringField({
|
||||
required: true,
|
||||
label: 'DAGGERHEART.APPLICATIONS.TagTeamSelect.FIELDS.initiator.memberId.label'
|
||||
}),
|
||||
cost: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: 3,
|
||||
label: 'DAGGERHEART.APPLICATIONS.TagTeamSelect.FIELDS.initiator.cost.label'
|
||||
})
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
members: new fields.TypedObjectField(new fields.EmbeddedDataField(MemberData))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class MemberData extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
name: new fields.StringField({ required: true }),
|
||||
img: new fields.StringField({ required: true }),
|
||||
rollType: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.tagTeamRollTypes,
|
||||
initial: CONFIG.DH.GENERAL.tagTeamRollTypes.trait.id,
|
||||
label: 'Roll Type'
|
||||
}),
|
||||
rollChoice: new fields.StringField({ nullable: true, initial: null }),
|
||||
rollData: new fields.JSONField({ nullable: true, initial: null }),
|
||||
selected: new fields.BooleanField({ initial: false })
|
||||
};
|
||||
}
|
||||
|
||||
get roll() {
|
||||
return this.rollData ? CONFIG.Dice.daggerheart.DualityRoll.fromData(this.rollData) : null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { DhCharacter } from './actor/_module.mjs';
|
||||
|
||||
export default class DhTagTeamRoll extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
initiator: new fields.SchemaField({
|
||||
id: new fields.StringField({ nullable: true, initial: null }),
|
||||
cost: new fields.NumberField({ integer: true, min: 0, initial: 3 })
|
||||
}),
|
||||
members: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
messageId: new fields.StringField({ required: true, nullable: true, initial: null }),
|
||||
selected: new fields.BooleanField({ required: true, initial: false })
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
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';
|
||||
|
||||
export default class DamageRoll extends DHRoll {
|
||||
|
|
@ -281,10 +280,7 @@ export default class DamageRoll extends DHRoll {
|
|||
return mods;
|
||||
}
|
||||
|
||||
static async reroll(target, message) {
|
||||
const { damageType, part, dice, result } = target.dataset;
|
||||
const rollPart = message.system.damage[damageType].parts[part];
|
||||
|
||||
static async reroll(rollPart, dice, result) {
|
||||
let diceIndex = 0;
|
||||
let parsedRoll = game.system.api.dice.DamageRoll.fromData({
|
||||
...rollPart.roll,
|
||||
|
|
@ -353,29 +349,6 @@ export default class DamageRoll extends DHRoll {
|
|||
};
|
||||
});
|
||||
|
||||
const updateMessage = game.messages.get(message._id);
|
||||
const damageParts = updateMessage.system.damage[damageType].parts.map((damagePart, index) => {
|
||||
if (index !== Number(part)) return damagePart;
|
||||
return {
|
||||
...rollPart,
|
||||
total: parsedRoll.total,
|
||||
dice: rerolledDice
|
||||
};
|
||||
});
|
||||
await updateMessage.update({
|
||||
[`system.damage.${damageType}`]: {
|
||||
...updateMessage,
|
||||
total: parsedRoll.total,
|
||||
parts: damageParts
|
||||
}
|
||||
});
|
||||
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
return { parsedRoll, rerolledDice };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ export default class DHRoll extends Roll {
|
|||
static async build(config = {}, message = {}) {
|
||||
const roll = await this.buildConfigure(config, message);
|
||||
if (!roll) return;
|
||||
|
||||
if (config.skips?.createMessage) config.messageRoll = roll;
|
||||
|
||||
await this.buildEvaluate(roll, config, (message = {}));
|
||||
await this.buildPost(roll, config, (message = {}));
|
||||
return config;
|
||||
|
|
@ -30,12 +33,6 @@ export default class DHRoll extends Roll {
|
|||
config.hooks = [...this.getHooks(), ''];
|
||||
config.dialog ??= {};
|
||||
|
||||
const actorIdSplit = config.source?.actor?.split('.');
|
||||
if (actorIdSplit) {
|
||||
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
config.tagTeamSelected = Boolean(tagTeamSettings.members[actorIdSplit[actorIdSplit.length - 1]]);
|
||||
}
|
||||
|
||||
for (const hook of config.hooks) {
|
||||
if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,9 +374,9 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
}
|
||||
|
||||
static async reroll(rollString, target, message) {
|
||||
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false });
|
||||
const term = parsedRoll.terms[target.dataset.dieIndex];
|
||||
static async reroll(rollBase, dieIndex, diceType) {
|
||||
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollBase, evaluated: false });
|
||||
const term = parsedRoll.terms[dieIndex];
|
||||
await term.reroll(`/r1=${term.total}`);
|
||||
const result = await parsedRoll.evaluate();
|
||||
|
||||
|
|
@ -393,35 +393,35 @@ export default class DualityRoll extends D20Roll {
|
|||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
const diceSoNicePresets = await getDiceSoNicePresets(result, `d${term._faces}`, `d${term._faces}`);
|
||||
const type = target.dataset.type;
|
||||
if (diceSoNicePresets[type]) {
|
||||
diceSoNiceRoll.dice[0].options = diceSoNicePresets[type];
|
||||
const diceSoNicePresets = await getDiceSoNicePresets(`d${term._faces}`, `d${term._faces}`);
|
||||
if (diceSoNicePresets[diceType]) {
|
||||
diceSoNiceRoll.dice[0].options = diceSoNicePresets[diceType];
|
||||
}
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
} else {
|
||||
foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
}
|
||||
|
||||
const newRoll = game.system.api.dice.DualityRoll.postEvaluate(parsedRoll, {
|
||||
targets: message.system.targets,
|
||||
targets: parsedRoll.options.targets ?? [],
|
||||
roll: {
|
||||
advantage: message.system.roll.advantage?.type,
|
||||
difficulty: message.system.roll.difficulty ? Number(message.system.roll.difficulty) : null
|
||||
advantage: parsedRoll.options.roll.advantage?.type,
|
||||
difficulty: parsedRoll.options.roll.difficulty ? Number(parsedRoll.options.roll.difficulty) : null
|
||||
}
|
||||
});
|
||||
|
||||
const extraIndex = newRoll.advantage ? 3 : 2;
|
||||
newRoll.extra = newRoll.extra.slice(extraIndex);
|
||||
|
||||
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
|
||||
const actor = message.system.source.actor ? await foundry.utils.fromUuid(message.system.source.actor) : null;
|
||||
const actor = parsedRoll.options.source.actor
|
||||
? await foundry.utils.fromUuid(parsedRoll.options.source.actor)
|
||||
: null;
|
||||
const config = {
|
||||
source: { actor: message.system.source.actor ?? '' },
|
||||
targets: message.system.targets,
|
||||
tagTeamSelected: Object.values(tagTeamSettings.members).some(x => x.messageId === message._id),
|
||||
source: { actor: parsedRoll.options.source.actor ?? '' },
|
||||
targets: parsedRoll.targets,
|
||||
roll: newRoll,
|
||||
rerolledRoll: message.system.roll,
|
||||
rerolledRoll: parsedRoll.roll,
|
||||
resourceUpdates: new ResourceUpdateMap(actor)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import DHFeature from '../data/item/feature.mjs';
|
|||
import { createScrollText, damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs';
|
||||
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
|
||||
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||
import { abilities } from '../config/actorConfig.mjs';
|
||||
|
||||
export default class DhpActor extends Actor {
|
||||
parties = new Set();
|
||||
|
|
@ -509,6 +510,30 @@ export default class DhpActor extends Actor {
|
|||
return await rollClass.build(config);
|
||||
}
|
||||
|
||||
async rollTrait(trait, options = {}) {
|
||||
const abilityLabel = game.i18n.localize(abilities[trait].label);
|
||||
const config = {
|
||||
event: event,
|
||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.name}`,
|
||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: abilityLabel
|
||||
}),
|
||||
effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this),
|
||||
roll: {
|
||||
trait: trait,
|
||||
type: 'trait'
|
||||
},
|
||||
hasRoll: true,
|
||||
actionType: 'action',
|
||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.name}`,
|
||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: abilityLabel
|
||||
}),
|
||||
...options
|
||||
};
|
||||
return await this.diceRoll(config);
|
||||
}
|
||||
|
||||
get rollClass() {
|
||||
return CONFIG.Dice.daggerheart[['character', 'companion'].includes(this.type) ? 'DualityRoll' : 'D20Roll'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,14 +177,6 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(actor, item);
|
||||
await this.system.action.workflow.get('damage')?.execute(config, this._id, true);
|
||||
}
|
||||
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async onApplyDamage(event) {
|
||||
|
|
|
|||
|
|
@ -528,7 +528,8 @@ export function expireActiveEffects(actor, allowedTypes = null) {
|
|||
|
||||
export async function getCritDamageBonus(formula) {
|
||||
const critRoll = new Roll(formula);
|
||||
return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.number, 0);
|
||||
await critRoll.evaluate();
|
||||
return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.results.filter(r => r.active).length, 0);
|
||||
}
|
||||
|
||||
export function htmlToText(html) {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export const preloadHandlebarsTemplates = async function () {
|
|||
'systems/daggerheart/templates/ui/tooltip/parts/tooltipChips.hbs',
|
||||
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs',
|
||||
'systems/daggerheart/templates/dialogs/downtime/activities.hbs',
|
||||
'systems/daggerheart/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.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',
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ export async function runMigrations() {
|
|||
}
|
||||
|
||||
if (foundry.utils.isNewerVersion('1.2.7', lastMigrationVersion)) {
|
||||
const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
const tagTeam = game.settings.get(CONFIG.DH.id, 'TagTeamRoll');
|
||||
const initatorMissing = tagTeam.initiator && !game.actors.some(actor => actor.id === tagTeam.initiator);
|
||||
const missingMembers = Object.keys(tagTeam.members).reduce((acc, id) => {
|
||||
if (!game.actors.some(actor => actor.id === id)) {
|
||||
|
|
@ -206,7 +206,7 @@ export async function runMigrations() {
|
|||
initiator: initatorMissing ? null : tagTeam.initiator,
|
||||
members: missingMembers
|
||||
});
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam);
|
||||
await game.settings.set(CONFIG.DH.id, 'TagTeamRoll', tagTeam);
|
||||
|
||||
lastMigrationVersion = '1.2.7';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
DhMetagamingSettings,
|
||||
DhVariantRuleSettings
|
||||
} from '../applications/settings/_module.mjs';
|
||||
import { CompendiumBrowserSettings, DhTagTeamRoll } from '../data/_module.mjs';
|
||||
import { CompendiumBrowserSettings } from '../data/_module.mjs';
|
||||
|
||||
export const registerDHSettings = () => {
|
||||
registerMenuSettings();
|
||||
|
|
@ -157,12 +157,6 @@ const registerNonConfigSettings = () => {
|
|||
type: DhCountdowns
|
||||
});
|
||||
|
||||
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, {
|
||||
scope: 'world',
|
||||
config: false,
|
||||
type: DhTagTeamRoll
|
||||
});
|
||||
|
||||
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings, {
|
||||
scope: 'world',
|
||||
config: false,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ export function handleSocketEvent({ action = null, data = {} } = {}) {
|
|||
case socketEvent.DowntimeTrigger:
|
||||
Party.downtimeMoveQuery(data);
|
||||
break;
|
||||
case socketEvent.TagTeamStart:
|
||||
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -22,7 +25,8 @@ export const socketEvent = {
|
|||
GMUpdate: 'DhGMUpdate',
|
||||
Refresh: 'DhRefresh',
|
||||
DhpFearUpdate: 'DhFearUpdate',
|
||||
DowntimeTrigger: 'DowntimeTrigger'
|
||||
DowntimeTrigger: 'DowntimeTrigger',
|
||||
TagTeamStart: 'DhTagTeamStart'
|
||||
};
|
||||
|
||||
export const GMUpdateEvent = {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@
|
|||
{
|
||||
"trigger": "dualityRoll",
|
||||
"triggeringActorType": "self",
|
||||
"command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n <div><div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}</div>\n <div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}</div>\n<div>${game.i18n.localize('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsActionExplanation')}</div>\n <div class=\"flexrow\" style=\"gap: 8px;\">\n <button type=\"button\" id=\"hopeButton\">\n <i class=\"fa-solid fa-hands-holding\"></i>\n <label>0</label>\n </button>\n <button type=\"button\" id=\"stressButton\">\n <i class=\"fa-solid fa-bolt-lightning\"></i>\n <label>0</label>\n </button>\n </div>\n</div>`;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;"
|
||||
"command": "/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n <div><div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}</div>\n <div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}</div>\n<div>${game.i18n.localize('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsActionExplanation')}</div>\n <div class=\"flexrow\" style=\"gap: 8px;\">\n <button type=\"button\" id=\"hopeButton\">\n <i class=\"fa-solid fa-hands-holding\"></i>\n <label>0</label>\n </button>\n <button type=\"button\" id=\"stressButton\">\n <i class=\"fa-solid fa-bolt-lightning\"></i>\n <label>0</label>\n </button>\n </div>\n</div>`;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,29 +69,6 @@
|
|||
background: light-dark(@dark-blue-40, @golden-40);
|
||||
}
|
||||
}
|
||||
|
||||
.tag-team-controller {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 5px;
|
||||
width: fit-content;
|
||||
gap: 5px;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
background: light-dark(@dark-blue-10, @golden-10);
|
||||
color: light-dark(@dark-blue, @golden);
|
||||
|
||||
.label {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: var(--font-size-14);
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: light-dark(@dark-blue-40, @golden-40);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.roll-dialog-container {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@
|
|||
@import './reroll-dialog/sheet.less';
|
||||
|
||||
@import './group-roll/group-roll.less';
|
||||
|
||||
@import './tag-team-dialog/initialization.less';
|
||||
@import './tag-team-dialog/sheet.less';
|
||||
|
||||
@import './image-select/sheet.less';
|
||||
|
|
|
|||
59
styles/less/dialog/tag-team-dialog/initialization.less
Normal file
59
styles/less/dialog/tag-team-dialog/initialization.less
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
.daggerheart.dialog.dh-style.views.tag-team-dialog {
|
||||
.initialization-container {
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.members-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
gap: 8px;
|
||||
|
||||
.member-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
&.inactive {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.initiator-container {
|
||||
margin-top: 8px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
|
||||
&.inactive {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.finish-tools {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
&.inactive {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,178 +1,251 @@
|
|||
.daggerheart.dialog.dh-style.views.tag-team-dialog {
|
||||
.tag-team-container {
|
||||
.tag-team-roll-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.tag-team-data-container {
|
||||
&.inactive {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.team-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 16px;
|
||||
|
||||
.form-group {
|
||||
flex: 0;
|
||||
|
||||
label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&.flex-group {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
h2 {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
select {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.participants-container {
|
||||
margin-top: 8px;
|
||||
.member-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
|
||||
.participant-outer-container {
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
|
||||
&.selected,
|
||||
&:hover {
|
||||
background-color: light-dark(@golden-40, @golden-40);
|
||||
&.inactive {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.participant-container {
|
||||
.data-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.member-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
img {
|
||||
height: 64px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid light-dark(@dark-blue, @golden);
|
||||
}
|
||||
|
||||
.member-name {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: var(--font-size-18);
|
||||
}
|
||||
}
|
||||
|
||||
.roll-setup {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.roll-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.roll-title {
|
||||
font-size: var(--font-size-20);
|
||||
font-weight: bold;
|
||||
color: light-dark(@dark-blue, @golden);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.participant-inner-container {
|
||||
&::before,
|
||||
&::after {
|
||||
color: light-dark(@dark-blue, @golden);
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, light-dark(@dark-blue, @golden) 100%);
|
||||
}
|
||||
|
||||
&::after {
|
||||
background: linear-gradient(90deg, light-dark(@dark-blue, @golden) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.roll-tools {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
|
||||
&:hover {
|
||||
text-shadow: none;
|
||||
filter: drop-shadow(0 0 8px var(--golden));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.roll-data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
img {
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
border-radius: 50%;
|
||||
&.hope {
|
||||
--text-color: @golden;
|
||||
--bg-color: @golden-40;
|
||||
}
|
||||
|
||||
.participant-labels {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
.participant-label-title {
|
||||
font-size: 18px;
|
||||
&.fear {
|
||||
--text-color: @chat-blue;
|
||||
--bg-color: @chat-blue-40;
|
||||
}
|
||||
|
||||
.participant-label-info {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
.participant-label-info-part {
|
||||
border: 1px solid light-dark(white, white);
|
||||
border-radius: 4px;
|
||||
padding: 2px 4px;
|
||||
background-color: light-dark(@beige-80, @soft-white-shadow);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.critical {
|
||||
--text-color: @chat-purple;
|
||||
--bg-color: @chat-purple-40;
|
||||
}
|
||||
|
||||
.participant-empty-roll-container {
|
||||
border: 1px dashed white;
|
||||
padding: 8px 2px;
|
||||
.duality-label {
|
||||
color: var(--text-color);
|
||||
font-size: var(--font-size-20);
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
|
||||
.unused-damage {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
.participant-roll-outer-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
color: light-dark(@dark-blue, @golden);
|
||||
|
||||
h4 {
|
||||
text-align: center;
|
||||
color: light-dark(@dark-blue, @golden);
|
||||
}
|
||||
|
||||
.participant-roll-container {
|
||||
.roll-dice-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
|
||||
.participant-roll-text-container {
|
||||
padding: 0 8px;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.damage-values-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.damage-container {
|
||||
border: 1px solid light-dark(white, white);
|
||||
border-radius: 6px;
|
||||
padding: 0 4px;
|
||||
.roll-dice {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.dice-label {
|
||||
position: absolute;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
paint-order: stroke fill;
|
||||
-webkit-text-stroke: 2px black;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.roll-operator {
|
||||
font-size: var(--font-size-24);
|
||||
}
|
||||
|
||||
.roll-value {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.roll-total {
|
||||
background: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
border-radius: 4px;
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.select-roll-button {
|
||||
margin-top: 8px;
|
||||
|
||||
i {
|
||||
color: light-dark(@dark-blue, @golden);
|
||||
font-size: 48px;
|
||||
|
||||
&.inactive {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
.results-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border: 2px solid light-dark(@dark-blue, @golden);
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
|
||||
.result-container-label {
|
||||
font-size: var(--font-size-24);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.results-inner-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
|
||||
.result-section-label {
|
||||
font-size: var(--font-size-20);
|
||||
}
|
||||
|
||||
.result-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
||||
.result-damages-container {
|
||||
.result-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
|
||||
.result-damage-container {
|
||||
border: 1px solid light-dark(white, white);
|
||||
border-radius: 6px;
|
||||
padding: 0 4px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.roll-leader-container {
|
||||
.finish-container {
|
||||
gap: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
|
||||
.finish-button {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
.hint {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,12 @@
|
|||
background-image: url('../assets/parchments/dh-parchment-dark.png');
|
||||
}
|
||||
}, {
|
||||
&.party {
|
||||
&.sheet.actor.dh-style.party {
|
||||
background: url('../assets/parchments/dh-parchment-light.png');
|
||||
|
||||
.tab .actions-section .active-action {
|
||||
animation: glow-dark 0.75s infinite alternate;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -40,6 +44,10 @@
|
|||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.active-action {
|
||||
animation: glow 0.75s infinite alternate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,4 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if (and @root.hasRoll @root.activeTagTeamRoll)}}
|
||||
<div class="tag-team-controller {{#if @root.tagTeamSelected}}selected{{/if}}" data-action="toggleTagTeamRoll">
|
||||
<span><i class="{{ifThen @root.tagTeamSelected "fa-solid" "fa-regular"}} fa-circle"></i></span>
|
||||
<span class="label">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.title"}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
</header>
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
<div>
|
||||
<div class="tag-team-container">
|
||||
<fieldset>
|
||||
<legend>{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.partyTeam"}}</legend>
|
||||
|
||||
<div class="form-group flex-group">
|
||||
<label>{{localize "TYPES.Actor.character"}}</label>
|
||||
|
||||
<div class="form-fields">
|
||||
<select name="selectedAddMember">
|
||||
{{selectOptions memberOptions labelAttr="name" valueAttr="uuid" blank=""}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="participants-container">
|
||||
{{#each members as |member|}}
|
||||
<div class="participant-outer-container {{#if member.selected}}selected{{/if}}" data-action="selectMessage" id="{{member.character.id}}">
|
||||
<div class="participant-container">
|
||||
<div class="participant-inner-container">
|
||||
<img src="{{member.character.img}}" />
|
||||
<div class="participant-labels">
|
||||
<div class="participant-label-title">{{member.character.name}}</div>
|
||||
<div class="participant-label-info">
|
||||
{{#if member.character.system.class.value}}
|
||||
<div class="participant-label-info-part">{{member.character.system.class.value.name}}</div>
|
||||
{{/if}}
|
||||
{{#if member.system.multiclass.value}}
|
||||
<div class="participant-label-info-part">{{member.character.system.multiclass.value.name}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a data-action="removeMember" data-character-id="{{member.character.id}}"><i class="fa-solid fa-trash"></i></a>
|
||||
</div>
|
||||
{{#if member.roll}}
|
||||
<div class="participant-roll-outer-container">
|
||||
<div class="participant-roll-container">
|
||||
<h4>
|
||||
<a data-action="unlinkMessage" id="{{member.character.id}}"><i class="fa-solid fa-link"></i></a>
|
||||
{{member.roll.system.title}}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="participant-roll-container">
|
||||
<side-line-div class="invert"></side-line-div>
|
||||
<div class="participant-roll-text-container">
|
||||
{{member.roll.system.roll.total}}
|
||||
{{localize "DAGGERHEART.GENERAL.withThing" thing=member.roll.system.roll.result.label}}
|
||||
</div>
|
||||
<side-line-div></side-line-div>
|
||||
</div>
|
||||
{{#if member.roll.system.hasDamage}}
|
||||
<h4>{{localize "DAGGERHEART.GENERAL.damage"}}</h4>
|
||||
<div class="damage-values-container">
|
||||
{{#if member.damageValues}}
|
||||
{{#each member.damageValues as |damage|}}
|
||||
<div class="damage-container">
|
||||
<div>{{damage.name}}</div>
|
||||
<div>{{damage.total}}</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.damageNotRolled"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="participant-empty-roll-container">
|
||||
{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.linkMessageHint"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="roll-leader-container">
|
||||
<h4>
|
||||
<strong>{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.initiatingCharacter"}}</strong>
|
||||
<select name="initiator.id">
|
||||
{{selectOptions selectedCharacterOptions selected=initiator.character.id labelAttr="name" valueAttr="id" blank=""}}
|
||||
</select>
|
||||
</h4>
|
||||
<h4>
|
||||
<strong>{{localize "DAGGERHEART.GENERAL.Cost.single"}}</strong>
|
||||
<input type="text" data-dtype="Number" min="0" name="initiator.cost" value="{{initiator.cost}}" />
|
||||
</h4>
|
||||
</div>
|
||||
{{#if showResult}}
|
||||
{{#if selectedData.result}}
|
||||
<div class="result-container">
|
||||
<h4><strong>{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.title"}}:</strong> {{selectedData.result}}</h4>
|
||||
{{#if usesDamage}}
|
||||
<div class="result-damages-container">
|
||||
<label><strong>{{localize "DAGGERHEART.GENERAL.damage"}}:</strong></label>
|
||||
{{#each selectedData.damageValues as |damage|}}
|
||||
<div class="result-damage-container">
|
||||
{{damage.name}}
|
||||
{{damage.total}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<button data-action="createTagTeam" {{disabled createDisabled}}>{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.createTagTeam"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
34
templates/dialogs/tagTeamDialog/initialization.hbs
Normal file
34
templates/dialogs/tagTeamDialog/initialization.hbs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<section class="initialization-container tab {{#if tabs.initialization.active}} active{{/if}}" data-group="{{tabs.initialization.group}}" data-tab="{{tabs.initialization.id}}">
|
||||
<h2>{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.selectParticipants"}}</h2>
|
||||
<div class="members-container">
|
||||
{{#each memberSelection as |member|}}
|
||||
<a
|
||||
class="member-container {{#unless member.selected}}inactive {{#if ../allselected}}locked{{/if}}{{/unless}}"
|
||||
data-action="toggleSelectMember" data-id="{{member.id}}" {{#if (and (not member.selected) ../allSelected)}}disabled{{/if}}
|
||||
>
|
||||
<span class="member-name">{{member.name}}</span>
|
||||
<img src="{{member.img}}" />
|
||||
</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div class="initiator-container {{#if initiatorDisabled}}inactive{{/if}}">
|
||||
<div class="form-group">
|
||||
<label>{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.FIELDS.initiator.memberId.label"}}</label>
|
||||
<div class="form-fields">
|
||||
<select name="initiator.memberId" {{#if initiatorDisabled}}disabled{{/if}}>
|
||||
{{selectOptions initiatorOptions selected=initiator.memberId blank="" }}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{formGroup tagTeamFields.initiator.fields.cost name="initiator.cost" value=initiator.cost disabled=initiatorDisabled localize=true }}
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<button type="button" data-action="startTagTeamRoll" {{#unless canStartTagTeam}}disabled{{/unless}}>{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.startTagTeamRoll"}} <i class="fa-solid fa-arrow-right-long"></i></button>
|
||||
<div class="finish-tools {{#unless canStartTagTeam}}inactive{{/unless}}">
|
||||
<span>{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.openDialogForAll"}}</span>
|
||||
<input type="checkbox" name="openForAllPlayers" {{#unless canStartTagTeam}}disabled{{/unless}} {{checked openForAllPlayers}} />
|
||||
</div>
|
||||
</footer>
|
||||
</section>
|
||||
25
templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs
Normal file
25
templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{{#each damage as |damage key|}}
|
||||
<div class="roll-data {{#if isCritical}}critical{{/if}}">
|
||||
<div class="duality-label">
|
||||
<span>{{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}:</span>
|
||||
<span>{{damage.total}}</span>
|
||||
</div>
|
||||
{{#each damage.parts as |part|}}
|
||||
<div class="roll-dice-container">
|
||||
{{#each part.dice as |dice index|}}
|
||||
<a class="roll-dice" data-action="rerollDamageDice" data-member-key="{{@../../../key}}" data-damage-key="{{@../../key}}" data-part="{{@../index}}" data-dice="{{index}}">
|
||||
<span class="dice-label">{{dice.total}}</span>
|
||||
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" dice.dice ".svg"}}" />
|
||||
</a>
|
||||
{{#unless @last}}
|
||||
<span class="roll-operator">+</span>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
{{#if part.modifierTotal}}
|
||||
<span class="roll-operator">{{#if (gte part.modifierTotal 0)}}+{{else}}-{{/if}}</span>
|
||||
<span class="roll-value">{{positive part.modifierTotal}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/each}}
|
||||
173
templates/dialogs/tagTeamDialog/tagTeamRoll.hbs
Normal file
173
templates/dialogs/tagTeamDialog/tagTeamRoll.hbs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
<section class="tag-team-roll tab {{#if tabs.tagTeamRoll.active}} active{{/if}}" data-group="{{tabs.tagTeamRoll.group}}" data-tab="{{tabs.tagTeamRoll.id}}">
|
||||
<div class="tag-team-roll-container {{#unless isEditable}}inactive{{/unless}}">
|
||||
<div class="team-container">
|
||||
{{#each members as |member key|}}
|
||||
<fieldset class="member-container {{#unless member.isEditable}}inactive{{/unless}}">
|
||||
<div class="data-container">
|
||||
<div class="member-info">
|
||||
<img src="{{member.img}}" />
|
||||
<span class="member-name">{{member.name}}</span>
|
||||
</div>
|
||||
<div class="roll-setup">
|
||||
<div class="form-group">
|
||||
<div class="form-fields">
|
||||
<label>{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.rollType"}}</label>
|
||||
<select class="roll-type-select" data-member="{{key}}" {{#if member.hasRolled}}disabled{{/if}}>
|
||||
{{selectOptions ../rollTypes selected=member.rollType localize=true}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if (eq member.rollType 'trait')}}
|
||||
<div class="form-group">
|
||||
<div class="form-fields">
|
||||
<label>{{localize "DAGGERHEART.GENERAL.Trait.single"}}</label>
|
||||
<select name="{{concat "system.tagTeam.members." key ".rollChoice"}}" {{#if member.hasRolled}}disabled{{/if}}>
|
||||
{{selectOptions ../traitOptions selected=member.rollChoice localize=true blank=""}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{else if (eq member.rollType 'abilityDamage')}}
|
||||
<div class="form-group">
|
||||
<div class="form-fields">
|
||||
<label>{{localize "DAGGERHEART.CONFIG.TagTeamRollTypes.damageAbility"}}</label>
|
||||
<select name="{{concat "system.tagTeam.members." key ".rollChoice"}}" {{#if member.hasRolled}}disabled{{/if}}>
|
||||
{{selectOptions member.damageRollOptions selected=member.rollChoice localize=true blank=""}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="form-group">
|
||||
<div class="form-fields">
|
||||
<label>{{localize "DAGGERHEART.GENERAL.Ability.single"}}</label>
|
||||
<select name="{{concat "system.tagTeam.members." key ".rollChoice"}}" {{#if member.hasRolled}}disabled{{/if}}>
|
||||
{{selectOptions member.rollOptions selected=member.rollChoice localize=true blank=""}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if member.readyToRoll}}
|
||||
<div class="roll-container">
|
||||
<span class="roll-title">
|
||||
{{localize "DAGGERHEART.GENERAL.roll"}}
|
||||
<div class="roll-tools">
|
||||
<a class="roll-button" data-action="makeRoll" data-member="{{key}}">
|
||||
<img src="systems/daggerheart/assets/icons/dice/hope/d12.svg" />
|
||||
</a>
|
||||
|
||||
{{#if member.hasRolled}}
|
||||
<a class="delete-button" data-action="removeRoll" data-member="{{key}}">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
</span>
|
||||
|
||||
{{#if member.rollData}}
|
||||
{{#with member.rollData.options.roll}}
|
||||
<div class="roll-data {{#if this.isCritical}}critical{{else}}{{#if (eq this.result.duality 1)}}hope{{else}}fear{{/if}}{{/if}}">
|
||||
<div class="duality-label">{{this.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=this.result.label}}</div>
|
||||
<div class="roll-dice-container">
|
||||
<a class="roll-dice" data-action="rerollDice" data-member="{{../key}}" data-dice-type="hope">
|
||||
<span class="dice-label">{{this.hope.value}}</span>
|
||||
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" this.hope.dice ".svg"}}" />
|
||||
</a>
|
||||
<span class="roll-operator">+</span>
|
||||
<a class="roll-dice" data-action="rerollDice" data-member="{{../key}}" data-dice-type="fear">
|
||||
<span class="dice-label">{{this.fear.value}}</span>
|
||||
<img src="{{concat "systems/daggerheart/assets/icons/dice/fear/" this.fear.dice ".svg"}}" />
|
||||
</a>
|
||||
{{#if this.advantage}}
|
||||
<span class="roll-operator">{{#if (eq this.advantage.type 1)}}+{{else}}-{{/if}}</span>
|
||||
<span class="roll-dice">
|
||||
<span class="dice-label">{{this.advantage.value}}</span>
|
||||
<img src="{{concat "systems/daggerheart/assets/icons/dice/" (ifThen (eq this.advantage.type 1) "adv/" "disadv/") this.advantage.dice ".svg"}}" />
|
||||
</span>
|
||||
{{/if}}
|
||||
<span class="roll-operator">{{#if (gte this.modifierTotal 0)}}+{{else}}-{{/if}}</span>
|
||||
<span class="roll-value">{{positive this.modifierTotal}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{/with}}
|
||||
{{else}}
|
||||
<span class="hint">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if member.rollData.options.hasDamage}}
|
||||
<div class="roll-container">
|
||||
<span class="roll-title">
|
||||
{{localize "DAGGERHEART.GENERAL.damage"}}
|
||||
<div class="roll-tools">
|
||||
<a class="roll-button" data-action="makeDamageRoll" data-member-key="{{key}}" {{#unless member.readyToRoll}}disabled{{/unless}}>
|
||||
<img src="systems/daggerheart/assets/icons/dice/hope/d20.svg" />
|
||||
</a>
|
||||
|
||||
{{#if damage}}
|
||||
<a class="delete-button" data-action="removeDamageRoll" data-member-key="{{key}}" {{#unless member.rollData.options.damage}}disabled{{/unless}}>
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
</span>
|
||||
{{#if damage}}
|
||||
{{#if useCritDamage}}
|
||||
{{> "systems/daggerheart/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs" damage=critDamage isCritical=true }}
|
||||
{{else}}
|
||||
{{> "systems/daggerheart/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs" damage=damage }}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="hint">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if member.hasRolled}}
|
||||
<a class="select-roll-button" data-action="selectRoll" data-member-key="{{key}}">
|
||||
<i class="{{#if member.selected}}fa-solid fa-circle-check{{else}}fa-regular fa-circle inactive{{/if}}"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
</fieldset>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div class="results-container">
|
||||
<span class="result-container-label">{{localize "DAGGERHEART.GENERAL.result.plural"}}</span>
|
||||
<div class="results-inner-container">
|
||||
{{#if hintText}}
|
||||
<div class="hint">{{localize hintText}}</div>
|
||||
{{else}}
|
||||
{{#if joinedRoll.rollData}}
|
||||
<div class="result-container">
|
||||
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.dualityRoll"}}</span>
|
||||
<div class="result-info">
|
||||
<div class="damage-info">{{joinedRoll.rollData.options.roll.total}}</div>
|
||||
<div>{{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.rollData.options.roll.result.label}}</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if hasDamage}}
|
||||
<div class="result-container">
|
||||
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.damage"}}</span>
|
||||
{{#each joinedRoll.rollData.options.damage as |damage key|}}
|
||||
<div class="result-info">
|
||||
<div>{{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}</div>
|
||||
<div class="damage-info">{{damage.total}}</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="finish-container">
|
||||
<button type="button" data-action="cancelRoll">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.cancelTagTeamRoll"}}</button>
|
||||
<button type="button" data-action="finishRoll" {{#if hintText}}disabled{{/if}} class="finish-button">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.finishTagTeamRoll"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
>
|
||||
|
||||
<div class="actions-section">
|
||||
<button data-action="tagTeamRoll">
|
||||
<button data-action="tagTeamRoll" class="{{#if tagTeamActive}}active-action{{/if}}">
|
||||
<i class="fa-solid fa-user-group"></i>
|
||||
<span>Tag Team Roll</span>
|
||||
</button>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue