+
{{localize "wv.weapons.attacks.ranges"}}: {{template.raw.displayRanges}}
+ {{#if details}} {{!backward compatibility}}
+
{{localize "wv.weapons.modifiers.hit.range"}}: {{localize template.keys.rangeBracket}} ({{details.range.distance}})
+
+
{{localize "wv.sheets.actor.secondary.criticals.title"}}
+
+ - {{localize "wv.sheets.actor.secondary.criticals.success"}}: {{details.criticals.success}}
+ - {{localize "wv.sheets.actor.secondary.criticals.failure"}}: {{details.criticals.failure}}
+
+
+
+
+ {{localize "wv.weapons.modifiers.listingTitles.hit"}}
+
-
-
- {{localize "wv.weapons.modifiers.listingTitles.hit"}}
-
-
-
- {{localize "wv.weapons.modifiers.base"}}
- {{details.hit.base}}
- {{#each details.hit.modifiers as |modifier|}}
- {{localize modifier.key}}
- {{modifier.amount}}
- {{/each}}
- {{localize "wv.weapons.modifiers.total"}}
- {{details.hit.total}}
-
+
+ {{localize "wv.weapons.modifiers.base"}}
+ {{details.hit.base}}
+ {{#each details.hit.modifiers as |modifier|}}
+ {{localize modifier.key}}
+ {{modifier.amount}}
+ {{/each}}
+ {{localize "wv.weapons.modifiers.total"}}
+ {{details.hit.total}}
-
-
- {{localize "wv.weapons.modifiers.listingTitles.damageBase"}}
-
-
-
- {{localize "wv.weapons.modifiers.base"}}
- {{details.damage.base.base}}
- {{#each details.damage.base.modifiers as |modifier|}}
- {{localize modifier.key}}
- {{modifier.amount}}
- {{/each}}
- {{localize "wv.weapons.modifiers.total"}}
- {{details.damage.base.total}}
-
+
+
+
+ {{localize "wv.weapons.modifiers.listingTitles.damageBase"}}
+
-
-
- {{localize "wv.weapons.modifiers.listingTitles.damageDice"}}
-
-
-
- {{localize "wv.weapons.modifiers.base"}}
- {{details.damage.dice.base}}
- {{#each details.damage.dice.modifiers as |modifier|}}
- {{localize modifier.key}}
- {{modifier.amount}}
- {{/each}}
- {{localize "wv.weapons.modifiers.total"}}
- {{details.damage.dice.total}}
-
+
+ {{localize "wv.weapons.modifiers.base"}}
+ {{details.damage.base.base}}
+ {{#each details.damage.base.modifiers as |modifier|}}
+ {{localize modifier.key}}
+ {{modifier.amount}}
+ {{/each}}
+ {{localize "wv.weapons.modifiers.total"}}
+ {{details.damage.base.total}}
- {{/if}}
-
+
+
+
+ {{localize "wv.weapons.modifiers.listingTitles.damageDice"}}
+
+
+
+ {{localize "wv.weapons.modifiers.base"}}
+ {{details.damage.dice.base}}
+ {{#each details.damage.dice.modifiers as |modifier|}}
+ {{localize modifier.key}}
+ {{modifier.amount}}
+ {{/each}}
+ {{localize "wv.weapons.modifiers.total"}}
+ {{details.damage.dice.total}}
+
+
+ {{/if}}
+
+
+{{#if executed}}
{{!hit roll}}
diff --git a/src/main/lang/de.json b/src/main/lang/de.json
index 41f7765b..5227b65c 100644
--- a/src/main/lang/de.json
+++ b/src/main/lang/de.json
@@ -294,6 +294,7 @@
},
"ranges": {
"brackets": {
+ "outOfRange": "Außer Reichweite",
"long": "Weit",
"medium": "Mittel",
"short": "Kurz",
diff --git a/src/main/lang/en.json b/src/main/lang/en.json
index f1b670fe..8d6f19c6 100644
--- a/src/main/lang/en.json
+++ b/src/main/lang/en.json
@@ -294,6 +294,7 @@
},
"ranges": {
"brackets": {
+ "outOfRange": "Out of range",
"long": "Long",
"medium": "Medium",
"short": "Short",
diff --git a/src/main/typescript/hooks/renderChatMessage/decorateSystemMessage/decorateWeaponAttack.ts b/src/main/typescript/hooks/renderChatMessage/decorateSystemMessage/decorateWeaponAttack.ts
index 2215673f..83777279 100644
--- a/src/main/typescript/hooks/renderChatMessage/decorateSystemMessage/decorateWeaponAttack.ts
+++ b/src/main/typescript/hooks/renderChatMessage/decorateSystemMessage/decorateWeaponAttack.ts
@@ -17,11 +17,37 @@ export default async function decorateWeaponAttack(
const content = getContentElement(html);
+ const commonData: CommonWeaponAttackTemplateData = {
+ ...flags,
+ template: {
+ keys: {
+ rangeBracket: getRangeBracketKey(flags)
+ },
+ raw: {
+ displayRanges: getDisplayRanges(
+ flags.weaponSystemData,
+ flags.ownerSpecials
+ ),
+ mainHeading:
+ flags.weaponName !== flags.weaponSystemData.name
+ ? flags.weaponName
+ : flags.weaponSystemData.name,
+ subHeading:
+ flags.weaponName !== flags.weaponSystemData.name
+ ? `${flags.weaponSystemData.name} - ${flags.attackName}`
+ : flags.attackName
+ }
+ }
+ };
+
if (!flags.executed) {
const data: NotExecutedAttackTemplateData = {
...flags,
+ ...commonData,
template: {
+ ...commonData.template,
keys: {
+ ...commonData.template.keys,
notExecutedReason: getNotExecutedReasonKey(flags)
}
}
@@ -32,7 +58,9 @@ export default async function decorateWeaponAttack(
const data: ExecutedAttackTemplateData = {
...flags,
+ ...commonData,
template: {
+ ...commonData.template,
damage: {
results: flags.rolls.damage.results.map((result) => {
return {
@@ -42,22 +70,8 @@ export default async function decorateWeaponAttack(
})
},
keys: {
- hit: getHitResultKey(flags),
- rangeBracket: getRangeBracketKey(flags)
- },
- raw: {
- displayRanges: getDisplayRanges(
- flags.weaponSystemData,
- flags.ownerSpecials
- ),
- mainHeading:
- flags.weaponName !== flags.weaponSystemData.name
- ? flags.weaponName
- : flags.weaponSystemData.name,
- subHeading:
- flags.weaponName !== flags.weaponSystemData.name
- ? `${flags.weaponSystemData.name} - ${flags.attackName}`
- : flags.attackName
+ ...commonData.template.keys,
+ hit: getHitResultKey(flags)
}
}
};
@@ -87,8 +101,12 @@ function getHitResultKey(flags: ExecutedAttackFlags): string {
}
/** Get the i18n key for the range bracket. */
-function getRangeBracketKey(flags: ExecutedAttackFlags): string | undefined {
+function getRangeBracketKey(
+ flags: CommonWeaponAttackFlags
+): string | undefined {
switch (flags.details?.range.bracket) {
+ case RangeBracket.OUT_OF_RANGE:
+ return "wv.weapons.ranges.brackets.outOfRange";
case RangeBracket.LONG:
return "wv.weapons.ranges.brackets.long";
case RangeBracket.MEDIUM:
@@ -104,24 +122,9 @@ function getRangeBracketKey(flags: ExecutedAttackFlags): string | undefined {
export type WeaponAttackFlags = NotExecutedAttackFlags | ExecutedAttackFlags;
/** The common weapon attack chat message flags */
-interface CommonWeaponAttackFlags {
+export interface CommonWeaponAttackFlags {
type: "weaponAttack";
- weaponName: string;
- weaponImage: string | null;
- weaponSystemData: WeaponDataProperties["data"];
attackName: string;
-}
-
-/** The attack chat message flags for a unexecuted attack */
-export interface NotExecutedAttackFlags extends CommonWeaponAttackFlags {
- executed: false;
- reason?: "insufficientAp" | "outOfRange";
-}
-
-/** The attack chat message flags for an executed attack */
-export interface ExecutedAttackFlags extends CommonWeaponAttackFlags {
- executed: true;
- ownerSpecials?: Partial | undefined;
details?: {
criticals: {
failure: number;
@@ -137,6 +140,21 @@ export interface ExecutedAttackFlags extends CommonWeaponAttackFlags {
distance: number;
};
};
+ ownerSpecials?: Partial | undefined;
+ weaponImage: string | null;
+ weaponName: string;
+ weaponSystemData: WeaponDataProperties["data"];
+}
+
+/** The attack chat message flags for a unexecuted attack */
+export type NotExecutedAttackFlags = CommonWeaponAttackFlags & {
+ executed: false;
+ reason?: "insufficientAp" | "outOfRange";
+};
+
+/** The attack chat message flags for an executed attack */
+export type ExecutedAttackFlags = CommonWeaponAttackFlags & {
+ executed: true;
rolls: {
damage: {
formula: string;
@@ -150,7 +168,7 @@ export interface ExecutedAttackFlags extends CommonWeaponAttackFlags {
total: number;
};
};
-}
+};
export interface ModifierFlags {
amount: number;
@@ -163,26 +181,10 @@ interface DetailsListingInfo {
total: number;
}
-/** The data for rendering the not executed weapon attack template */
-interface NotExecutedAttackTemplateData extends NotExecutedAttackFlags {
+/** The template data common for both executed and not executed attacks. */
+type CommonWeaponAttackTemplateData = CommonWeaponAttackFlags & {
template: {
keys: {
- notExecutedReason: string;
- };
- };
-}
-
-/** The data for rendering the executed weapon attack template */
-interface ExecutedAttackTemplateData extends ExecutedAttackFlags {
- template: {
- damage: {
- results: {
- class: string;
- value: number;
- }[];
- };
- keys: {
- hit: string;
rangeBracket: string | undefined;
};
raw: {
@@ -191,4 +193,30 @@ interface ExecutedAttackTemplateData extends ExecutedAttackFlags {
subHeading: string;
};
};
-}
+};
+
+/** The data for rendering the not executed weapon attack template */
+type NotExecutedAttackTemplateData = CommonWeaponAttackTemplateData &
+ NotExecutedAttackFlags & {
+ template: {
+ keys: {
+ notExecutedReason: string;
+ };
+ };
+ };
+
+/** The data for rendering the executed weapon attack template */
+type ExecutedAttackTemplateData = CommonWeaponAttackTemplateData &
+ ExecutedAttackFlags & {
+ template: {
+ damage: {
+ results: {
+ class: string;
+ value: number;
+ }[];
+ };
+ keys: {
+ hit: string;
+ };
+ };
+ };
diff --git a/src/main/typescript/item/weapon/attack.ts b/src/main/typescript/item/weapon/attack.ts
index e2ca1ec3..4fbc94cd 100644
--- a/src/main/typescript/item/weapon/attack.ts
+++ b/src/main/typescript/item/weapon/attack.ts
@@ -70,27 +70,26 @@ export default class Attack {
alias
};
- // Get range bracket and check bracket -------------------------------------
+ // Create common chat message data -----------------------------------------
+ const commonData: ChatMessageDataConstructorData =
+ this.createDefaultMessageData(speaker, options);
+
+ // Get range bracket -------------------------------------------------------
const rangeBracket = ranges.getRangeBracket(
this.weapon.systemData.ranges,
range,
specials
);
- if (rangeBracket === ranges.RangeBracket.OUT_OF_RANGE) {
- this.createOutOfRangeMessage(speaker, options);
- return;
- }
- // Check AP and subtract in combat -----------------------------------------
- if (actor?.getActiveTokens(true).some((token) => token.inCombat)) {
- const currentAp = actor.data.data.vitals.actionPoints.value;
- const apUse = this.data.ap;
- if (currentAp < apUse) {
- this.createNotEnoughApMessage(speaker, options);
- return;
- }
- actor.updateActionPoints(currentAp - apUse);
- }
+ // Calculate damage dice ---------------------------------------------------
+ const strengthDamageDiceMod = this.getStrengthDamageDiceMod(
+ specials.strength
+ );
+ const rangeDamageDiceMod = this.getRangeDamageDiceMod(rangeBracket);
+ const damageDice = this.getDamageDice(
+ strengthDamageDiceMod,
+ rangeDamageDiceMod
+ );
// Calculate hit roll target -----------------------------------------------
const rangeModifier = ranges.getRangeModifier(
@@ -105,6 +104,40 @@ export default class Attack {
critFailure
);
+ // Create common attack flags ----------------------------------------------
+ const commonFlags: deco.CommonWeaponAttackFlags =
+ this.createCommonWeaponAttackFlags(
+ critFailure,
+ critSuccess,
+ strengthDamageDiceMod,
+ rangeDamageDiceMod,
+ damageDice,
+ skillTotal,
+ rangeModifier,
+ promptHitModifier,
+ hitTotal,
+ rangeBracket,
+ range,
+ specials
+ );
+
+ // Check range -------------------------------------------------------------
+ if (rangeBracket === ranges.RangeBracket.OUT_OF_RANGE) {
+ this.createOutOfRangeMessage(commonData, commonFlags);
+ return;
+ }
+
+ // Check AP and subtract in combat -----------------------------------------
+ if (actor?.getActiveTokens(true).some((token) => token.inCombat)) {
+ const currentAp = actor.data.data.vitals.actionPoints.value;
+ const apUse = this.data.ap;
+ if (currentAp < apUse) {
+ this.createNotEnoughApMessage(commonData, commonFlags);
+ return;
+ }
+ actor.updateActionPoints(currentAp - apUse);
+ }
+
// Hit roll ----------------------------------------------------------------
const hitRoll = new Roll(
Formulator.skill(hitTotal)
@@ -112,62 +145,13 @@ export default class Attack {
.toString()
).evaluate({ async: false });
- // Calculate damage dice ---------------------------------------------------
- const strengthDamageDiceMod = this.getStrengthDamageDiceMod(
- specials.strength
- );
- const rangeDamageDiceMod = this.getRangeDamageDiceMod(rangeBracket);
- const damageDice = this.getDamageDice(
- strengthDamageDiceMod,
- rangeDamageDiceMod
- );
-
// Damage roll -------------------------------------------------------------
const damageRoll = new Roll(
Formulator.damage(this.data.damage.base, damageDice).toString()
).evaluate({ async: false });
- // Compose details ---------------------------------------------------------
- const details: NonNullable = {
- criticals: {
- failure: critFailure,
- success: critSuccess
- },
- damage: {
- base: {
- base: this.data.damage.base,
- modifiers: this.getDamageBaseModifierFlags(),
- total: this.data.damage.base
- },
- dice: {
- base: this.data.damage.dice,
- modifiers: this.getDamageDiceModifierFlags(
- strengthDamageDiceMod,
- rangeDamageDiceMod
- ),
- total: damageDice
- }
- },
- hit: {
- base: skillTotal,
- modifiers: this.getHitModifierFlags(rangeModifier, promptHitModifier),
- total: hitTotal
- },
- range: {
- bracket: rangeBracket,
- distance: range
- }
- };
-
// Create attack message ---------------------------------------------------
- this.createAttackMessage(
- speaker,
- specials,
- details,
- hitRoll,
- damageRoll,
- options
- );
+ this.createAttackMessage(commonData, commonFlags, hitRoll, damageRoll);
}
/** Get the system formula representation of the damage of this attack. */
@@ -385,99 +369,134 @@ export default class Attack {
}
/** Get the default ChatMessage flags for this Weapon Attack. */
- protected get defaultChatMessageFlags(): deco.WeaponAttackFlags {
+ protected createCommonWeaponAttackFlags(
+ critFailure: number,
+ critSuccess: number,
+ strengthDamageDiceMod: number,
+ rangeDamageDiceMod: number,
+ damageDice: number,
+ skillTotal: number,
+ rangeModifier: number,
+ promptHitModifier: number,
+ hitTotal: number,
+ rangeBracket: ranges.RangeBracket,
+ range: number,
+ ownerSpecials: Partial
+ ): Required {
return {
type: "weaponAttack",
- weaponName: this.weapon.data.name,
- weaponImage: this.weapon.img,
- weaponSystemData: this.weapon.systemData,
attackName: this.name,
- executed: false
+ details: {
+ criticals: {
+ failure: critFailure,
+ success: critSuccess
+ },
+ damage: {
+ base: {
+ base: this.data.damage.base,
+ modifiers: this.getDamageBaseModifierFlags(),
+ total: this.data.damage.base
+ },
+ dice: {
+ base: this.data.damage.dice,
+ modifiers: this.getDamageDiceModifierFlags(
+ strengthDamageDiceMod,
+ rangeDamageDiceMod
+ ),
+ total: damageDice
+ }
+ },
+ hit: {
+ base: skillTotal,
+ modifiers: this.getHitModifierFlags(rangeModifier, promptHitModifier),
+ total: hitTotal
+ },
+ range: {
+ bracket: rangeBracket,
+ distance: range
+ }
+ },
+ ownerSpecials,
+ weaponImage: this.weapon.img,
+ weaponName: this.weapon.data.name,
+ weaponSystemData: this.weapon.systemData
};
}
/** Create a weapon attack message, signaling out of range. */
protected createOutOfRangeMessage(
- speaker: foundry.data.ChatMessageData["speaker"]["_source"],
- options?: RollOptions
+ commonData: ChatMessageDataConstructorData,
+ commonFlags: deco.CommonWeaponAttackFlags
): void {
+ const flags: deco.NotExecutedAttackFlags = {
+ ...commonFlags,
+ executed: false,
+ reason: "outOfRange"
+ };
+
ChatMessage.create({
- ...this.createDefaultMessageData(speaker, options),
- flags: {
- [CONSTANTS.systemId]: {
- ...this.defaultChatMessageFlags,
- executed: false,
- reason: "outOfRange"
- }
- }
+ ...commonData,
+ flags: { [CONSTANTS.systemId]: flags }
});
}
/** Create a weapon attack message, signaling insufficient AP. */
protected createNotEnoughApMessage(
- speaker: foundry.data.ChatMessageData["speaker"]["_source"],
- options?: RollOptions
+ commonData: ChatMessageDataConstructorData,
+ commonFlags: deco.CommonWeaponAttackFlags
): void {
+ const flags: deco.NotExecutedAttackFlags = {
+ ...commonFlags,
+ executed: false,
+ reason: "insufficientAp"
+ };
+
ChatMessage.create({
- ...this.createDefaultMessageData(speaker, options),
- flags: {
- [CONSTANTS.systemId]: {
- ...this.defaultChatMessageFlags,
- executed: false,
- reason: "insufficientAp"
- }
- }
+ ...commonData,
+ flags: { [CONSTANTS.systemId]: flags }
});
}
/** Create a chat message for an executed attack. */
protected async createAttackMessage(
- speaker: foundry.data.ChatMessageData["speaker"]["_source"],
- specials: Partial,
- details: NonNullable,
+ commonData: ChatMessageDataConstructorData,
+ commonFlags: deco.CommonWeaponAttackFlags,
hitRoll: Roll,
- damageRoll: Roll,
- options?: RollOptions
+ damageRoll: Roll
): Promise {
- const defaultData = this.createDefaultMessageData(speaker, options);
const actorId =
- defaultData.speaker?.actor instanceof WvActor
- ? defaultData.speaker.actor.id
- : defaultData.speaker?.actor;
+ commonData.speaker?.actor instanceof WvActor
+ ? commonData.speaker.actor.id
+ : commonData.speaker?.actor;
await Promise.all([
- diceSoNice(hitRoll, defaultData.whisper ?? null, { actor: actorId }),
- diceSoNice(damageRoll, defaultData.whisper ?? null, { actor: actorId })
+ diceSoNice(hitRoll, commonData.whisper ?? null, { actor: actorId }),
+ diceSoNice(damageRoll, commonData.whisper ?? null, { actor: actorId })
]);
- const data: ChatMessageDataConstructorData = {
- ...defaultData,
- flags: {
- [CONSTANTS.systemId]: {
- ...this.defaultChatMessageFlags,
- executed: true,
- ownerSpecials: specials,
- details: details,
- rolls: {
- damage: {
- formula: damageRoll.formula,
- results:
- damageRoll.dice[0]?.results.map((result) => result.result) ??
- [],
- total: damageRoll.total ?? this.data.damage.base
- },
- hit: {
- critical: hitRoll.dice[0]?.results[0]?.critical,
- formula: hitRoll.formula,
- result: hitRoll.dice[0]?.results[0]?.result ?? 0,
- total: hitRoll.total ?? 0
- }
- }
+ const flags: deco.ExecutedAttackFlags = {
+ ...commonFlags,
+ executed: true,
+ rolls: {
+ damage: {
+ formula: damageRoll.formula,
+ results:
+ damageRoll.dice[0]?.results.map((result) => result.result) ?? [],
+ total: damageRoll.total ?? this.data.damage.base
+ },
+ hit: {
+ critical: hitRoll.dice[0]?.results[0]?.critical,
+ formula: hitRoll.formula,
+ result: hitRoll.dice[0]?.results[0]?.result ?? 0,
+ total: hitRoll.total ?? 0
}
}
};
- ChatMessage.create(data);
+ ChatMessage.create({
+ ...commonData,
+ flags: { [CONSTANTS.systemId]: flags }
+ });
}
}
diff --git a/src/main/typescript/lang.d.ts b/src/main/typescript/lang.d.ts
index 96e0881d..0d9c8a52 100644
--- a/src/main/typescript/lang.d.ts
+++ b/src/main/typescript/lang.d.ts
@@ -562,6 +562,8 @@ export interface LangSchema {
ranges: {
/** Labels for range bracket names */
brackets: {
+ /** The name of the out of range range bracket */
+ outOfRange: string;
/** The name of the long range bracket */
long: string;
/** The name of the medium range bracket */