mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 23:13:39 +02:00
Initial rolls working
This commit is contained in:
parent
c48842dd2d
commit
a0fa743b8e
20 changed files with 483 additions and 577 deletions
|
|
@ -1850,6 +1850,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENERAL": {
|
"GENERAL": {
|
||||||
|
"Ability": {
|
||||||
|
"single": "Ability",
|
||||||
|
"plural": "Abilities"
|
||||||
|
},
|
||||||
"Action": {
|
"Action": {
|
||||||
"single": "Action",
|
"single": "Action",
|
||||||
"plural": "Actions"
|
"plural": "Actions"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { getCritDamageBonus } from '../../helpers/utils.mjs';
|
import Party from '../sheets/actors/party.mjs';
|
||||||
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -7,15 +6,19 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
constructor(party) {
|
constructor(party) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.data = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
|
||||||
this.party = party;
|
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.setupHooks = Hooks.on(socketEvent.Refresh, ({ refreshType }) => {
|
this.tabGroups.application = Object.keys(party.system.tagTeam.members).length
|
||||||
if (refreshType === RefreshType.TagTeamRoll) {
|
? 'tagTeamRoll'
|
||||||
this.data = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
: 'initialization';
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
|
|
@ -27,321 +30,205 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'],
|
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'],
|
||||||
position: { width: 550, height: 'auto' },
|
position: { width: 550, height: 'auto' },
|
||||||
actions: {
|
actions: {
|
||||||
removeMember: TagTeamDialog.#removeMember,
|
toggleSelectMember: TagTeamDialog.#toggleSelectMember,
|
||||||
unlinkMessage: TagTeamDialog.#unlinkMessage,
|
startTagTeamRoll: TagTeamDialog.#startTagTeamRoll,
|
||||||
selectMessage: TagTeamDialog.#selectMessage,
|
makeRoll: TagTeamDialog.#makeRoll,
|
||||||
createTagTeam: TagTeamDialog.#createTagTeam
|
removeRoll: TagTeamDialog.#removeRoll
|
||||||
},
|
},
|
||||||
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
|
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
application: {
|
initialization: {
|
||||||
id: 'tag-team-dialog',
|
id: 'initialization',
|
||||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog.hbs'
|
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) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._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
|
|
||||||
);
|
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSource(update) {
|
async _preparePartContext(partId, context, options) {
|
||||||
await this.data.updateSource(update);
|
const partContext = await super._preparePartContext(partId, context, options);
|
||||||
|
switch (partId) {
|
||||||
|
case 'initialization':
|
||||||
|
partContext.memberSelection = this.partyMembers;
|
||||||
|
partContext.allSelected = partContext.memberSelection.filter(x => x.selected).length >= 2;
|
||||||
|
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;
|
||||||
|
|
||||||
if (game.user.isGM) {
|
partContext.members = Object.keys(this.party.system.tagTeam.members).reduce((acc, actorId) => {
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, this.data.toObject());
|
const data = this.party.system.tagTeam.members[actorId];
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
const actor = game.actors.get(actorId);
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
const rollOptions = actor.items.reduce((acc, item) => {
|
||||||
action: socketEvent.Refresh,
|
if (item.system.metadata.hasActions)
|
||||||
data: {
|
acc.push(
|
||||||
refreshType: RefreshType.TagTeamRoll
|
...item.system.actions.reduce((acc, action) => {
|
||||||
}
|
if (action.hasRoll)
|
||||||
|
acc.push({
|
||||||
|
value: action.uuid,
|
||||||
|
label: action.name,
|
||||||
|
group: item.name
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
return acc;
|
||||||
action: socketEvent.GMUpdate,
|
}, [])
|
||||||
data: {
|
);
|
||||||
action: GMUpdateEvent.UpdateSetting,
|
|
||||||
uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll,
|
return acc;
|
||||||
update: this.data.toObject(),
|
}, []);
|
||||||
refresh: { refreshType: RefreshType.TagTeamRoll }
|
|
||||||
}
|
acc[actorId] = {
|
||||||
});
|
...data,
|
||||||
}
|
readyToRoll: Boolean(data.rollChoice),
|
||||||
|
hasRolled: Boolean(data.rollData),
|
||||||
|
rollOptions
|
||||||
|
};
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateData(_event, _element, formData) {
|
return partContext;
|
||||||
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 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.updateSource(update);
|
static async updateData(_event, _, formData) {
|
||||||
|
const form = foundry.utils.expandObject(formData.object);
|
||||||
|
await this.party.update(form);
|
||||||
|
this.render(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region Initialization
|
||||||
|
static #toggleSelectMember(_, button) {
|
||||||
|
const member = this.partyMembers.find(x => x.id === button.dataset.id);
|
||||||
|
member.selected = !member.selected;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #removeMember(_, button) {
|
static async #startTagTeamRoll() {
|
||||||
const update = { [`members.-=${button.dataset.characterId}`]: null };
|
await this.party.update({
|
||||||
if (this.data.initiator.id === button.dataset.characterId) {
|
'system.==tagTeam': new game.system.api.data.TagTeamData({
|
||||||
update.iniator = { id: null };
|
...this.party.system.tagTeam.toObject(),
|
||||||
}
|
members: this.partyMembers.reduce((acc, member) => {
|
||||||
|
if (member.selected)
|
||||||
await this.updateSource(update);
|
acc[member.id] = {
|
||||||
}
|
name: member.name,
|
||||||
|
img: member.img,
|
||||||
static async #unlinkMessage(_, button) {
|
rollType: CONFIG.DH.GENERAL.tagTeamRollTypes.trait.id
|
||||||
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) {
|
|
||||||
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));
|
|
||||||
|
|
||||||
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) {
|
|
||||||
const criticalDamage = await getCritDamageBonus(part.formula);
|
|
||||||
if (criticalDamage) {
|
|
||||||
damage.formula = `${damage.formula} + ${criticalDamage}`;
|
|
||||||
part.formula = `${part.formula} + ${criticalDamage}`;
|
|
||||||
part.modifierTotal = part.modifierTotal + criticalDamage;
|
|
||||||
part.total += criticalDamage;
|
|
||||||
part.roll = new Roll(part.formula);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
systemData.damage[key].formula = `${systemData.damage[key].formula} + ${damage.formula}`;
|
|
||||||
systemData.damage[key].total += damageTotal;
|
|
||||||
systemData.damage[key].parts = [...systemData.damage[key].parts, ...updatedDamageParts];
|
|
||||||
} else {
|
|
||||||
systemData.damage[key] = { ...damage, total: damageTotal, parts: updatedDamageParts };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
systemData.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,
|
|
||||||
sound: null,
|
|
||||||
flags: { core: { RollTable: true } }
|
|
||||||
};
|
};
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
/* Update Party data and refresh all views */
|
||||||
|
this.tabGroups.application = 'tagTeamRoll';
|
||||||
|
|
||||||
await cls.create(msgData);
|
this.render();
|
||||||
|
|
||||||
const fearUpdate = { key: 'fear', value: null, total: null, enabled: true };
|
|
||||||
for (let memberId of Object.keys(this.data.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
|
|
||||||
? rollGivesHope
|
|
||||||
? 1 - this.data.initiator.cost
|
|
||||||
: -this.data.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) {
|
|
||||||
fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1;
|
|
||||||
fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1;
|
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
//#region Tag Team Roll
|
||||||
|
|
||||||
this.party.find(x => x.id === memberId).modifyResource(resourceUpdates);
|
async updateRollType(event) {
|
||||||
}
|
await this.party.update({
|
||||||
|
[`system.tagTeam.members.${event.target.dataset.member}`]: {
|
||||||
if (fearUpdate.value) {
|
rollType: event.target.value,
|
||||||
this.party.find(x => x.id === mainRollId).modifyResource([fearUpdate]);
|
rollChoice: null
|
||||||
}
|
|
||||||
|
|
||||||
/* 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}`, {
|
this.render();
|
||||||
action: socketEvent.GMUpdate,
|
}
|
||||||
data: {
|
|
||||||
action: GMUpdateEvent.UpdateSetting,
|
static async #removeRoll(_, button) {
|
||||||
uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll,
|
await this.party.update({
|
||||||
update: update,
|
[`system.tagTeam.members.${button.dataset.member}`]: {
|
||||||
refresh: { refreshType: RefreshType.TagTeamRoll }
|
rollData: null,
|
||||||
|
rollChoice: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
||||||
|
result = await this.makeAbilityRoll(event, member);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
const rollData = result.messageRoll.toJSON();
|
||||||
|
delete rollData.options.messageRoll;
|
||||||
|
await this.party.update({
|
||||||
|
[`system.tagTeam.members.${member}.rollData`]: rollData
|
||||||
|
});
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
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.traitDiceRoll(memberData.rollChoice, {
|
||||||
|
skips: {
|
||||||
|
createMessage: true,
|
||||||
|
resources: true,
|
||||||
|
triggers: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static async assignRoll(char, message) {
|
async makeAbilityRoll(event, memberKey) {
|
||||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
const actor = game.actors.find(x => x.id === memberKey);
|
||||||
const character = settings.members[char.id];
|
if (!actor) return;
|
||||||
if (!character) return;
|
|
||||||
|
|
||||||
await settings.updateSource({ [`members.${char.id}.messageId`]: message.id });
|
const memberData = this.party.system.tagTeam.members[memberKey];
|
||||||
|
const action = await foundry.utils.fromUuid(memberData.rollChoice);
|
||||||
|
|
||||||
if (game.user.isGM) {
|
return await action.use(event, {
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, settings);
|
skips: {
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
createMessage: true,
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
resources: true,
|
||||||
action: socketEvent.Refresh,
|
triggers: true
|
||||||
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 = {}) {
|
//#endregion
|
||||||
Hooks.off(socketEvent.Refresh, this.setupHooks);
|
|
||||||
await super.close(options);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||||
import DhDeathMove from '../../dialogs/deathMove.mjs';
|
import DhDeathMove from '../../dialogs/deathMove.mjs';
|
||||||
import { abilities } from '../../../config/actorConfig.mjs';
|
|
||||||
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
||||||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||||
|
|
@ -711,26 +710,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #rollAttribute(event, button) {
|
static async #rollAttribute(event, button) {
|
||||||
const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label);
|
const result = await this.document.traitDiceRoll(button.dataset.attribute);
|
||||||
const config = {
|
|
||||||
event: event,
|
|
||||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
|
||||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: abilityLabel
|
|
||||||
}),
|
|
||||||
effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this.document),
|
|
||||||
roll: {
|
|
||||||
trait: button.dataset.attribute,
|
|
||||||
type: 'trait'
|
|
||||||
},
|
|
||||||
hasRoll: true,
|
|
||||||
actionType: 'action',
|
|
||||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: abilityLabel
|
|
||||||
})
|
|
||||||
};
|
|
||||||
const result = await this.document.diceRoll(config);
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
|
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
||||||
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||||
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
||||||
import DhpActor from '../../../documents/actor.mjs';
|
import DhpActor from '../../../documents/actor.mjs';
|
||||||
import DHItem from '../../../documents/item.mjs';
|
|
||||||
|
|
||||||
export default class Party extends DHBaseActorSheet {
|
export default class Party extends DHBaseActorSheet {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
|
@ -256,11 +255,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #tagTeamRoll() {
|
static async #tagTeamRoll() {
|
||||||
new game.system.api.applications.dialogs.TagTeamDialog(
|
new game.system.api.applications.dialogs.TagTeamDialog(this.document).render({ force: true });
|
||||||
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
|
||||||
).render({
|
|
||||||
force: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #groupRoll(_params) {
|
static async #groupRoll(_params) {
|
||||||
|
|
|
||||||
|
|
@ -848,3 +848,14 @@ export const sceneRangeMeasurementSetting = {
|
||||||
label: 'Custom'
|
label: 'Custom'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const tagTeamRollTypes = {
|
||||||
|
trait: {
|
||||||
|
id: 'trait',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Trait.single'
|
||||||
|
},
|
||||||
|
ability: {
|
||||||
|
id: 'ability',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Ability.single'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
||||||
export { default as DhRollTable } from './rollTable.mjs';
|
export { default as DhRollTable } from './rollTable.mjs';
|
||||||
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
||||||
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
||||||
|
export { default as TagTeamData } from './tagTeamData.mjs';
|
||||||
|
|
||||||
export * as countdowns from './countdowns.mjs';
|
export * as countdowns from './countdowns.mjs';
|
||||||
export * as actions from './action/_module.mjs';
|
export * as actions from './action/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,8 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
|
|
||||||
async use(event, options) {
|
async use(event, options) {
|
||||||
const result = await super.use(event, options);
|
const result = await super.use(event, options);
|
||||||
if (!result.message) return;
|
|
||||||
|
|
||||||
if (result.message.system.action.roll?.type === 'attack') {
|
if (result.message?.system.action.roll?.type === 'attack') {
|
||||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
|
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -207,10 +207,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
* @param {Event} event Event from the button used to trigger the Action
|
* @param {Event} event Event from the button used to trigger the Action
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
async use(event) {
|
async use(event, configOptions = {}) {
|
||||||
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||||
|
|
||||||
let config = this.prepareConfig(event);
|
let config = this.prepareConfig(event, configOptions);
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
||||||
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item);
|
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item);
|
||||||
|
|
@ -229,7 +229,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
if (this.chatDisplay && !config.actionChatMessageHandled) await this.toChat();
|
if (this.chatDisplay && !config.skips.createMessage && !config.actionChatMessageHandled) await this.toChat();
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
@ -239,7 +239,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
* @param {Event} event Event from the button used to trigger the Action
|
* @param {Event} event Event from the button used to trigger the Action
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
prepareBaseConfig(event) {
|
prepareBaseConfig(event, configOptions = {}) {
|
||||||
const isActor = this.item instanceof CONFIG.Actor.documentClass;
|
const isActor = this.item instanceof CONFIG.Actor.documentClass;
|
||||||
const actionTitle = game.i18n.localize(this.name);
|
const actionTitle = game.i18n.localize(this.name);
|
||||||
const itemTitle = isActor || this.item.name === actionTitle ? '' : `${this.item.name} - `;
|
const itemTitle = isActor || this.item.name === actionTitle ? '' : `${this.item.name} - `;
|
||||||
|
|
@ -266,7 +266,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
data: this.getRollData(),
|
data: this.getRollData(),
|
||||||
evaluate: this.hasRoll,
|
evaluate: this.hasRoll,
|
||||||
resourceUpdates: new ResourceUpdateMap(this.actor),
|
resourceUpdates: new ResourceUpdateMap(this.actor),
|
||||||
targetUuid: this.targetUuid
|
targetUuid: this.targetUuid,
|
||||||
|
...configOptions
|
||||||
};
|
};
|
||||||
|
|
||||||
DHBaseAction.applyKeybindings(config);
|
DHBaseAction.applyKeybindings(config);
|
||||||
|
|
@ -278,8 +279,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
* @param {Event} event Event from the button used to trigger the Action
|
* @param {Event} event Event from the button used to trigger the Action
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
prepareConfig(event) {
|
prepareConfig(event, configOptions = {}) {
|
||||||
const config = this.prepareBaseConfig(event);
|
const config = this.prepareBaseConfig(event, configOptions);
|
||||||
for (const clsField of Object.values(this.schema.fields)) {
|
for (const clsField of Object.values(this.schema.fields)) {
|
||||||
if (clsField?.prepareConfig) if (clsField.prepareConfig.call(this, config) === false) return false;
|
if (clsField?.prepareConfig) if (clsField.prepareConfig.call(this, config) === false) return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor from './base.mjs';
|
||||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||||
|
import TagTeamData from '../tagTeamData.mjs';
|
||||||
|
|
||||||
export default class DhParty extends BaseDataActor {
|
export default class DhParty extends BaseDataActor {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
|
|
@ -14,7 +15,8 @@ export default class DhParty extends BaseDataActor {
|
||||||
handfuls: new fields.NumberField({ initial: 1, integer: true }),
|
handfuls: new fields.NumberField({ initial: 1, integer: true }),
|
||||||
bags: new fields.NumberField({ initial: 0, integer: true }),
|
bags: new fields.NumberField({ initial: 0, integer: true }),
|
||||||
chests: new fields.NumberField({ initial: 0, integer: true })
|
chests: new fields.NumberField({ initial: 0, integer: true })
|
||||||
})
|
}),
|
||||||
|
tagTeam: new fields.EmbeddedDataField(TagTeamData)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export default class EffectsField extends fields.ArrayField {
|
||||||
static async execute(config, targets = null, force = false) {
|
static async execute(config, targets = null, force = false) {
|
||||||
if (!config.hasEffect) return;
|
if (!config.hasEffect) return;
|
||||||
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
|
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
|
||||||
if (!message) {
|
if (!message && !config.skips.createMessage) {
|
||||||
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
||||||
roll._evaluated = true;
|
roll._evaluated = true;
|
||||||
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export default class SaveField extends fields.SchemaField {
|
||||||
if (!config.hasSave) return;
|
if (!config.hasSave) return;
|
||||||
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
|
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
|
||||||
|
|
||||||
if (!message) {
|
if (!message && !config.skips.createMessage) {
|
||||||
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
||||||
roll._evaluated = true;
|
roll._evaluated = true;
|
||||||
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
||||||
|
|
|
||||||
28
module/data/tagTeamData.mjs
Normal file
28
module/data/tagTeamData.mjs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
export default class TagTeamData extends foundry.abstract.DataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
return {
|
||||||
|
members: new fields.TypedObjectField(
|
||||||
|
new fields.SchemaField({
|
||||||
|
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 })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get roll() {
|
||||||
|
const roll = CONFIG.Dice.daggerheart.DualityRoll(JSON.parse(this.rollData));
|
||||||
|
|
||||||
|
return this.rollData ? CONFIG.Dice.daggerheart.DualityRoll(JSON.parse(this.rollData)) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,8 @@ export default class DHRoll extends Roll {
|
||||||
static async build(config = {}, message = {}) {
|
static async build(config = {}, message = {}) {
|
||||||
const roll = await this.buildConfigure(config, message);
|
const roll = await this.buildConfigure(config, message);
|
||||||
if (!roll) return;
|
if (!roll) return;
|
||||||
|
|
||||||
|
config.messageRoll = roll;
|
||||||
await this.buildEvaluate(roll, config, (message = {}));
|
await this.buildEvaluate(roll, config, (message = {}));
|
||||||
await this.buildPost(roll, config, (message = {}));
|
await this.buildPost(roll, config, (message = {}));
|
||||||
return config;
|
return config;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import DHFeature from '../data/item/feature.mjs';
|
||||||
import { createScrollText, damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs';
|
import { createScrollText, damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs';
|
||||||
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
|
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
|
||||||
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||||
|
import { abilities } from '../config/actorConfig.mjs';
|
||||||
|
|
||||||
export default class DhpActor extends Actor {
|
export default class DhpActor extends Actor {
|
||||||
parties = new Set();
|
parties = new Set();
|
||||||
|
|
@ -509,6 +510,30 @@ export default class DhpActor extends Actor {
|
||||||
return await rollClass.build(config);
|
return await rollClass.build(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async traitDiceRoll(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() {
|
get rollClass() {
|
||||||
return CONFIG.Dice.daggerheart[['character', 'companion'].includes(this.type) ? 'DualityRoll' : 'D20Roll'];
|
return CONFIG.Dice.daggerheart[['character', 'companion'].includes(this.type) ? 'DualityRoll' : 'D20Roll'];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@
|
||||||
@import './reroll-dialog/sheet.less';
|
@import './reroll-dialog/sheet.less';
|
||||||
|
|
||||||
@import './group-roll/group-roll.less';
|
@import './group-roll/group-roll.less';
|
||||||
|
|
||||||
|
@import './tag-team-dialog/initialization.less';
|
||||||
@import './tag-team-dialog/sheet.less';
|
@import './tag-team-dialog/sheet.less';
|
||||||
|
|
||||||
@import './image-select/sheet.less';
|
@import './image-select/sheet.less';
|
||||||
|
|
|
||||||
32
styles/less/dialog/tag-team-dialog/initialization.less
Normal file
32
styles/less/dialog/tag-team-dialog/initialization.less
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
.daggerheart.dialog.dh-style.views.tag-team-dialog {
|
||||||
|
.initialization-container {
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,178 +1,136 @@
|
||||||
.daggerheart.dialog.dh-style.views.tag-team-dialog {
|
.daggerheart.dialog.dh-style.views.tag-team-dialog {
|
||||||
.tag-team-container {
|
.tag-team-roll-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
|
||||||
.tag-team-data-container {
|
.team-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
gap: 16px;
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
.form-group {
|
.member-container {
|
||||||
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;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
.participant-outer-container {
|
.member-info {
|
||||||
padding: 8px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 6px;
|
|
||||||
|
|
||||||
&.selected,
|
|
||||||
&:hover {
|
|
||||||
background-color: light-dark(@golden-40, @golden-40);
|
|
||||||
}
|
|
||||||
|
|
||||||
.participant-container {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
width: 100%;
|
||||||
|
|
||||||
.participant-inner-container {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
height: 48px;
|
height: 64px;
|
||||||
width: 48px;
|
border-radius: 6px;
|
||||||
border-radius: 50%;
|
border: 1px solid light-dark(@dark-blue, @golden);
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-labels {
|
.member-name {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--font-size-18);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.roll-tools {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
|
||||||
|
.roll-button {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.roll-label {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2px;
|
||||||
|
background: light-dark(darkblue, gold);
|
||||||
|
color: light-dark(white, black);
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid light-dark(white, black);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button i {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.roll-data {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2px;
|
|
||||||
|
|
||||||
.participant-label-title {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.participant-label-info {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
||||||
.participant-label-info-part {
|
&.hope {
|
||||||
border: 1px solid light-dark(white, white);
|
--text-color: @golden;
|
||||||
border-radius: 4px;
|
--bg-color: @golden-40;
|
||||||
padding: 2px 4px;
|
|
||||||
background-color: light-dark(@beige-80, @soft-white-shadow);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-empty-roll-container {
|
&.fear {
|
||||||
border: 1px dashed white;
|
--text-color: @chat-blue;
|
||||||
padding: 8px 2px;
|
--bg-color: @chat-blue-40;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.critical {
|
||||||
|
--text-color: @chat-purple;
|
||||||
|
--bg-color: @chat-purple-40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duality-label {
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: var(--font-size-24);
|
||||||
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-style: italic;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.participant-roll-outer-container {
|
.roll-dice-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 {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: 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;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
.damage-container {
|
.roll-dice {
|
||||||
border: 1px solid light-dark(white, white);
|
position: relative;
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0 4px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
justify-content: center;
|
||||||
|
|
||||||
.result-damages-container {
|
.dice-label {
|
||||||
display: flex;
|
position: absolute;
|
||||||
flex-wrap: wrap;
|
color: white;
|
||||||
gap: 4px;
|
font-size: 1rem;
|
||||||
|
paint-order: stroke fill;
|
||||||
.result-damage-container {
|
-webkit-text-stroke: 2px black;
|
||||||
border: 1px solid light-dark(white, white);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 32px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.roll-leader-container {
|
.roll-operator {
|
||||||
display: grid;
|
font-size: var(--font-size-24);
|
||||||
grid-template-columns: 1fr 1fr;
|
}
|
||||||
gap: 8px;
|
}
|
||||||
|
|
||||||
|
.roll-total {
|
||||||
|
background: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
15
templates/dialogs/tagTeamDialog/initialization.hbs
Normal file
15
templates/dialogs/tagTeamDialog/initialization.hbs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<section class="initialization-container tab {{#if tabs.initialization.active}} active{{/if}}" data-group="{{tabs.initialization.group}}" data-tab="{{tabs.initialization.id}}">
|
||||||
|
<h2>{{localize "Select the two participants"}}</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}}">
|
||||||
|
<span class="member-name">{{member.name}}</span>
|
||||||
|
<img src="{{member.img}}" />
|
||||||
|
</a>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<button type="button" data-action="startTagTeamRoll" {{#unless allSelected}}disabled{{/unless}}>{{localize "Start Tag Team Roll"}} <i class="fa-solid fa-arrow-right-long"></i></button>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
74
templates/dialogs/tagTeamDialog/tagTeamRoll.hbs
Normal file
74
templates/dialogs/tagTeamDialog/tagTeamRoll.hbs
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
<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">
|
||||||
|
<div class="team-container">
|
||||||
|
{{#each members as |member key|}}
|
||||||
|
<fieldset class="member-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 "Roll Type"}}</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 "Trait"}}</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}}
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-fields">
|
||||||
|
<label>{{localize "Ability"}}</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>
|
||||||
|
|
||||||
|
<div class="roll-tools">
|
||||||
|
<a class="roll-button" data-action="makeRoll" data-member="{{key}}" {{#unless member.readyToRoll}}disabled{{/unless}}>
|
||||||
|
<span class="roll-label">{{#if member.hasRolled}}{{localize "DAGGERHEART.GENERAL.reroll"}}{{else}}{{localize "DAGGERHEART.GENERAL.roll"}}{{/if}}</span>
|
||||||
|
<img src="systems/daggerheart/assets/icons/dice/duality/Daggerheart%20Foundry_g519.png" />
|
||||||
|
</a>
|
||||||
|
<a class="delete-button" data-action="removeRoll" data-member="{{key}}" {{#unless member.hasRolled}}disabled{{/unless}}><i class="fa-solid fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#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">{{localize "DAGGERHEART.GENERAL.withThing" thing=this.result.label}}</div>
|
||||||
|
<div class="roll-dice-container">
|
||||||
|
<div class="roll-dice">
|
||||||
|
<span class="dice-label">{{this.hope.value}}</span>
|
||||||
|
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" this.hope.dice ".svg"}}" />
|
||||||
|
</div>
|
||||||
|
<span class="roll-operator">+</span>
|
||||||
|
<div class="roll-dice">
|
||||||
|
<span class="dice-label">{{this.fear.value}}</span>
|
||||||
|
<img src="{{concat "systems/daggerheart/assets/icons/dice/fear/" this.fear.dice ".svg"}}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="roll-total">{{../rollData.formula}}</div>
|
||||||
|
</div>
|
||||||
|
{{/with}}
|
||||||
|
{{/if}}
|
||||||
|
</fieldset>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue