mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
Temp
This commit is contained in:
parent
0d60cd90b6
commit
c952580f6b
10 changed files with 418 additions and 56 deletions
16
lang/en.json
16
lang/en.json
|
|
@ -207,6 +207,12 @@
|
|||
"Session": "Session",
|
||||
"Shortrest": "Short Rest",
|
||||
"Longrest": "Long Rest"
|
||||
},
|
||||
"Damage": {
|
||||
"Severe": "Severe",
|
||||
"Major": "Major",
|
||||
"Minor": "Minor",
|
||||
"None": "None"
|
||||
}
|
||||
},
|
||||
"ActionType": {
|
||||
|
|
@ -1084,6 +1090,16 @@
|
|||
"Title": "Ownership Selection - {name}",
|
||||
"Default": "Default Ownership"
|
||||
},
|
||||
"DamageReduction": {
|
||||
"Title": "Damage Reduction",
|
||||
"ArmorMarks": "Armor Marks",
|
||||
"UsedMarks": "Used Marks",
|
||||
"Stress": "Stress",
|
||||
"Notifications": {
|
||||
"DamageAlreadyNone": "The damage has already been reduced to none",
|
||||
"NotEnoughArmor": "You don't have enough unspent armor marks"
|
||||
}
|
||||
},
|
||||
"Sheets": {
|
||||
"PC": {
|
||||
"Name": "Name",
|
||||
|
|
|
|||
112
module/applications/damageReductionDialog.mjs
Normal file
112
module/applications/damageReductionDialog.mjs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import { getDamageLabel } from '../helpers/utils.mjs';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class DamageReductionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(resolve, reject, actor, damage) {
|
||||
super({});
|
||||
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
this.actor = actor;
|
||||
this.damage = damage;
|
||||
|
||||
this.availableArmorMarks = {
|
||||
max: actor.system.rules.maxArmorMarked.total + (actor.system.rules.stressExtra ?? 0),
|
||||
maxUseable: actor.system.armorScore - actor.system.armor.system.marks.value,
|
||||
stressIndex:
|
||||
(actor.system.rules.stressExtra ?? 0) > 0 ? actor.system.rules.maxArmorMarked.total : undefined,
|
||||
selected: 0
|
||||
};
|
||||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.DamageReduction.Title');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'views', 'damage-reduction'],
|
||||
position: {
|
||||
width: 240,
|
||||
height: 'auto'
|
||||
},
|
||||
actions: {
|
||||
setMarks: this.setMarks,
|
||||
takeDamage: this.takeDamage
|
||||
},
|
||||
form: {
|
||||
handler: this.updateData,
|
||||
submitOnChange: true,
|
||||
closeOnSubmit: false
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
damageSelection: {
|
||||
id: 'damageReduction',
|
||||
template: 'systems/daggerheart/templates/views/damageReduction.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritDoc */
|
||||
get title() {
|
||||
return `Damage Options`;
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.armorScore = this.actor.system.armorScore;
|
||||
context.armorMarks = this.actor.system.armor.system.marks.value + this.availableArmorMarks.selected;
|
||||
context.availableArmorMarks = this.availableArmorMarks;
|
||||
|
||||
context.damage = getDamageLabel(this.damage);
|
||||
context.reducedDamage =
|
||||
this.availableArmorMarks.selected > 0
|
||||
? getDamageLabel(this.damage - this.availableArmorMarks.selected)
|
||||
: null;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static updateData(event, _, formData) {
|
||||
const form = foundry.utils.expandObject(formData.object);
|
||||
this.render(true);
|
||||
}
|
||||
|
||||
static setMarks(_, target) {
|
||||
const index = Number(target.dataset.index);
|
||||
if (index >= this.availableArmorMarks.maxUseable) {
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.DamageReduction.Notifications.NotEnoughArmor'));
|
||||
return;
|
||||
}
|
||||
|
||||
const isDecreasing = index < this.availableArmorMarks.selected;
|
||||
if (!isDecreasing && this.damage - this.availableArmorMarks.selected === 0) {
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.DamageReduction.Notifications.DamageAlreadyNone'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.availableArmorMarks.selected = isDecreasing ? index : index + 1;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async takeDamage() {
|
||||
const armorSpent = this.availableArmorMarks.selected;
|
||||
const modifiedDamage = this.damage - armorSpent;
|
||||
|
||||
this.resolve({ modifiedDamage, armorSpent });
|
||||
await this.close(true);
|
||||
}
|
||||
|
||||
async close(fromSave) {
|
||||
if (!fromSave) {
|
||||
this.reject();
|
||||
}
|
||||
|
||||
await super.close({});
|
||||
}
|
||||
}
|
||||
|
|
@ -83,6 +83,18 @@ export default class DhCharacter extends BaseDataActor {
|
|||
attack: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
spellcast: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
armorScore: new fields.NumberField({ integer: true, initial: 0 })
|
||||
}),
|
||||
rules: new fields.SchemaField({
|
||||
maxArmorMarked: new fields.SchemaField({
|
||||
value: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
bonus: new fields.NumberField({ required: true, integer: true, initial: 0 }),
|
||||
stressExtra: new fields.NumberField({ required: true, integer: true, initial: 0 })
|
||||
}),
|
||||
stressDamageReduction: new fields.SchemaField({
|
||||
enabled: new fields.BooleanField({ required: true, initial: false }),
|
||||
cost: new fields.NumberField({ integer: true }),
|
||||
fromSeverity: new fields.NumberField({ integer: true, max: 3 })
|
||||
})
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
@ -232,6 +244,9 @@ export default class DhCharacter extends BaseDataActor {
|
|||
experience.total = experience.value + experience.bonus;
|
||||
}
|
||||
|
||||
this.rules.maxArmorMarked.total = this.rules.maxArmorMarked.value + this.rules.maxArmorMarked.bonus;
|
||||
|
||||
this.armorScore = this.armor ? this.armor.system.baseScore + (this.bonuses.armorScore ?? 0) : 0;
|
||||
this.resources.hitPoints.maxTotal = this.resources.hitPoints.max + this.resources.hitPoints.bonus;
|
||||
this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus;
|
||||
this.evasion.total = (this.class?.evasion ?? 0) + this.evasion.bonus;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ export default class DHArmor extends BaseDataItem {
|
|||
})
|
||||
),
|
||||
marks: new fields.SchemaField({
|
||||
max: new fields.NumberField({ initial: 6, integer: true }),
|
||||
value: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
baseThresholds: new fields.SchemaField({
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import RollSelectionDialog from '../applications/rollSelectionDialog.mjs';
|
|||
import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
|
||||
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||
import DHDualityRoll from '../data/chat-message/dualityRoll.mjs';
|
||||
import DamageReductionDialog from '../applications/damageReductionDialog.mjs';
|
||||
|
||||
export default class DhpActor extends Actor {
|
||||
async _preCreate(data, options, user) {
|
||||
|
|
@ -266,21 +267,21 @@ export default class DhpActor extends Actor {
|
|||
*/
|
||||
async diceRoll(config, action) {
|
||||
// console.log(config)
|
||||
config.source = {...(config.source ?? {}), actor: this.id};
|
||||
config.source = { ...(config.source ?? {}), actor: this.id };
|
||||
const newConfig = {
|
||||
// data: {
|
||||
...config,
|
||||
/* action, */
|
||||
// actor: this.getRollData(),
|
||||
actor: this.system
|
||||
...config,
|
||||
/* action, */
|
||||
// actor: this.getRollData(),
|
||||
actor: this.system
|
||||
// },
|
||||
// options: {
|
||||
// dialog: false,
|
||||
// dialog: false,
|
||||
// },
|
||||
// event: config.event
|
||||
}
|
||||
};
|
||||
// console.log(this, newConfig)
|
||||
const roll = CONFIG.Dice.daggerheart[this.type === 'character' ? 'DualityRoll' : 'D20Roll'].build(newConfig)
|
||||
const roll = CONFIG.Dice.daggerheart[this.type === 'character' ? 'DualityRoll' : 'D20Roll'].build(newConfig);
|
||||
return config;
|
||||
/* let hopeDice = 'd12',
|
||||
fearDice = 'd12',
|
||||
|
|
@ -508,67 +509,67 @@ export default class DhpActor extends Actor {
|
|||
async takeDamage(damage, type) {
|
||||
const hpDamage =
|
||||
damage >= this.system.damageThresholds.severe
|
||||
? -3
|
||||
? 3
|
||||
: damage >= this.system.damageThresholds.major
|
||||
? -2
|
||||
? 2
|
||||
: damage >= this.system.damageThresholds.minor
|
||||
? -1
|
||||
? 1
|
||||
: 0;
|
||||
await this.modifyResource([{value: hpDamage, type}]);
|
||||
/* const update = {
|
||||
'system.resources.hitPoints.value': Math.min(
|
||||
this.system.resources.hitPoints.value + hpDamage,
|
||||
this.system.resources.hitPoints.max
|
||||
)
|
||||
};
|
||||
|
||||
if (game.user.isGM) {
|
||||
await this.update(update);
|
||||
} else {
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateDocument,
|
||||
uuid: this.uuid,
|
||||
update: update
|
||||
}
|
||||
if (
|
||||
this.type === 'character' &&
|
||||
this.system.armor &&
|
||||
this.system.armor.system.marks.value < this.system.armorScore
|
||||
) {
|
||||
new Promise((resolve, reject) => {
|
||||
new DamageReductionDialog(resolve, reject, this, hpDamage).render(true);
|
||||
}).then(async ({ modifiedDamage, armorSpent }) => {
|
||||
const resources = [
|
||||
{ value: modifiedDamage, type: 'hitPoints' },
|
||||
...(armorSpent ? [{ value: armorSpent, type: 'armorStack' }] : [])
|
||||
];
|
||||
await this.modifyResource(resources);
|
||||
});
|
||||
} */
|
||||
} else {
|
||||
await this.modifyResource([{ value: hpDamage, type: 'hitPoints' }]);
|
||||
}
|
||||
}
|
||||
|
||||
async modifyResource(resources) {
|
||||
if(!resources.length) return;
|
||||
let updates = { actor: { target: this, resources: {} }, armor: { target: this.armor, resources: {} } };
|
||||
if (!resources.length) return;
|
||||
let updates = { actor: { target: this, resources: {} }, armor: { target: this.system.armor, resources: {} } };
|
||||
resources.forEach(r => {
|
||||
switch (type) {
|
||||
case 'armorStrack':
|
||||
// resource = 'system.stacks.value';
|
||||
// target = this.armor;
|
||||
// update = Math.min(this.marks.value + value, this.marks.max);
|
||||
updates.armor.resources['system.stacks.value'] = Math.min(this.marks.value + value, this.marks.max);
|
||||
switch (r.type) {
|
||||
case 'armorStack':
|
||||
updates.armor.resources['system.marks.value'] = Math.min(
|
||||
this.system.armor.system.marks.value + r.value,
|
||||
this.system.armorScore
|
||||
);
|
||||
break;
|
||||
default:
|
||||
// resource = `system.resources.${type}`;
|
||||
// target = this;
|
||||
// update = Math.min(this.resources[type].value + value, this.resources[type].max);
|
||||
updates.armor.resources[`system.resources.${type}`] = Math.min(this.resources[type].value + value, this.resources[type].max);
|
||||
updates.actor.resources[`system.resources.${r.type}.value`] = Math.min(
|
||||
this.system.resources[r.type].value + r.value,
|
||||
this.system.resources[r.type].max
|
||||
);
|
||||
break;
|
||||
}
|
||||
})
|
||||
Object.values(updates).forEach(async (u) => {
|
||||
if (game.user.isGM) {
|
||||
await u.target.update(u.resources);
|
||||
} else {
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateDocument,
|
||||
uuid: u.target.uuid,
|
||||
update: u.resources
|
||||
}
|
||||
});
|
||||
});
|
||||
Object.values(updates).forEach(async u => {
|
||||
if (Object.keys(u.resources).length > 0) {
|
||||
if (game.user.isGM) {
|
||||
await u.target.update(u.resources);
|
||||
} else {
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateDocument,
|
||||
uuid: u.target.uuid,
|
||||
update: u.resources
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/* async takeHealing(healing, type) {
|
||||
|
|
|
|||
|
|
@ -235,3 +235,16 @@ Roll.replaceFormulaData = function (formula, data, { missing, warn = false } = {
|
|||
formula = terms.reduce((a, c) => a.replaceAll(`@${c.term}`, data[c.term] ?? c.default), formula);
|
||||
return nativeReplaceFormulaData(formula, data, { missing, warn });
|
||||
};
|
||||
|
||||
export const getDamageLabel = damage => {
|
||||
switch (damage) {
|
||||
case 3:
|
||||
return game.i18n.localize('DAGGERHEART.General.Damage.Severe');
|
||||
case 2:
|
||||
return game.i18n.localize('DAGGERHEART.General.Damage.Major');
|
||||
case 1:
|
||||
return game.i18n.localize('DAGGERHEART.General.Damage.Minor');
|
||||
case 0:
|
||||
return game.i18n.localize('DAGGERHEART.General.Damage.None');
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3113,6 +3113,80 @@ div.daggerheart.views.multiclass {
|
|||
.daggerheart.views.ownership-selection .ownership-outer-container .ownership-container select {
|
||||
margin: 4px 0;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .window-content {
|
||||
padding: 8px 0;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container .section-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container .padded {
|
||||
padding: 0 8px;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container .armor-title {
|
||||
margin: 0;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-container {
|
||||
cursor: pointer;
|
||||
border: 1px solid light-dark(#18162e, #f3c267);
|
||||
border-radius: 6px;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.4;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-container.selected {
|
||||
opacity: 1;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-container.disabled {
|
||||
cursor: initial;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container .markers-subtitle {
|
||||
margin: -4px 0 0 0;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container .markers-subtitle.bold {
|
||||
font-variant: all-small-caps;
|
||||
font-weight: bold;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container .damage-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
font-weight: bold;
|
||||
height: 18px;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container .damage-container i {
|
||||
font-size: 18px;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container .damage-container .reduced-value {
|
||||
opacity: 0.4;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container footer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
.daggerheart.views.damage-reduction .damage-reduction-container footer button {
|
||||
flex: 1;
|
||||
}
|
||||
:root {
|
||||
--shadow-text-stroke: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
|
||||
--fear-animation: background 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease, opacity 0.3s ease;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
@import './characterCreation.less';
|
||||
@import './levelup.less';
|
||||
@import './ownershipSelection.less';
|
||||
@import './damageReduction.less';
|
||||
@import './resources.less';
|
||||
@import './countdown.less';
|
||||
@import './settings.less';
|
||||
|
|
|
|||
91
styles/damageReduction.less
Normal file
91
styles/damageReduction.less
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
.daggerheart.views.damage-reduction {
|
||||
.window-content {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.damage-reduction-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
.section-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.padded {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.armor-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mark-selection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
|
||||
.mark-container {
|
||||
cursor: pointer;
|
||||
border: 1px solid light-dark(#18162e, #f3c267);
|
||||
border-radius: 6px;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.4;
|
||||
|
||||
&.selected {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.markers-subtitle {
|
||||
margin: -4px 0 0 0;
|
||||
|
||||
&.bold {
|
||||
font-variant: all-small-caps;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.damage-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
font-weight: bold;
|
||||
height: 18px;
|
||||
|
||||
i {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.reduced-value {
|
||||
opacity: 0.4;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
templates/views/damageReduction.hbs
Normal file
40
templates/views/damageReduction.hbs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<div class="damage-reduction-container">
|
||||
<div class="section-container padded">
|
||||
<h4 class="armor-title">{{localize "DAGGERHEART.DamageReduction.ArmorMarks"}}</h4>
|
||||
<div class="markers-subtitle">{{armorMarks}}/{{armorScore}}</div>
|
||||
</div>
|
||||
|
||||
<div class="section-container">
|
||||
<h4 class="mark-selection divider">
|
||||
{{#times availableArmorMarks.max}}
|
||||
<div
|
||||
class="mark-container {{#if (lt this @root.availableArmorMarks.selected)}}selected{{/if}} {{#if (gte this this.maxUseable)}}disabled{{/if}}"
|
||||
data-action="setMarks" data-index="{{this}}"
|
||||
>
|
||||
{{#if (or (not @root.availableArmorMarks.stressIndex) (lt this @root.availableArmorMarks.stressIndex))}}
|
||||
<i class="fa-solid fa-shield"></i>
|
||||
{{else}}
|
||||
<i class="fa-solid fa-bolt"></i>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
{{/times}}
|
||||
</h4>
|
||||
<div class="markers-subtitle bold">{{localize "DAGGERHEART.DamageReduction.UsedMarks"}}</div>
|
||||
</div>
|
||||
|
||||
<div class="section-container">
|
||||
<div>{{localize "Incoming Damage"}}</div>
|
||||
<div class="damage-container">
|
||||
<div class="{{#if this.reducedDamage}}reduced-value{{/if}}">{{this.damage}}</div>
|
||||
{{#if this.reducedDamage}}
|
||||
<i class="fa-solid fa-arrow-right-long"></i>
|
||||
<div>{{this.reducedDamage}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="padded">
|
||||
<button type="button" data-action="takeDamage">{{localize "Take Damage"}}</button>
|
||||
</footer>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue