28 apply damage through chat (#40)

* Add Rightclick menu to apply damage/heal

Changes:
 + add rightclick context menu for rolls
 + only appears if keyword Schaden exists
 + calculate damage from all rolls with keyword Schaden
 + possible to apply damage or heal actor or if present token

ToDo:
 + add button that does the same

* Add Rüstungsschutz to damage calculation

Changes:
 + Add Damage application from chat
 + take token first, if none exist, take actor
 + include damage reduction from equipped armor
 + damage appliaction is only accesible through right click on roll

* Fix falsy display of rightclick context menu
This commit is contained in:
Byroks 2023-12-06 11:14:35 +01:00 committed by GitHub
parent 0fe61e5bbb
commit 2b4e6b18d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 119 additions and 8 deletions

View File

@ -184,6 +184,8 @@
"midgard5.brawl": "Raufen", "midgard5.brawl": "Raufen",
"midgard5.poisonResistance": "Gifttolleranz", "midgard5.poisonResistance": "Gifttolleranz",
"midgard5.enduranceBonus": "Ausdauerbonus", "midgard5.enduranceBonus": "Ausdauerbonus",
"midgard5.lpProtection": "Rüstungsschutz (LP)",
"midgard5.apProtection": "Rüstungsschutz (AP)",
"midgard5.new-skill": "Neue Fertigkeit", "midgard5.new-skill": "Neue Fertigkeit",
"midgard5.special": "Spezial", "midgard5.special": "Spezial",
@ -279,7 +281,6 @@
"midgard5.mod-operation-multiply": "Multiplizieren", "midgard5.mod-operation-multiply": "Multiplizieren",
"midgard5.mod-operation-division": "Dividieren", "midgard5.mod-operation-division": "Dividieren",
"midgard5.mod-stat-defenseBonus": "Abwehrbonus", "midgard5.mod-stat-defenseBonus": "Abwehrbonus",
"midgard5.mod-stat-attackBonus": "Angriffsbonus", "midgard5.mod-stat-attackBonus": "Angriffsbonus",
"midgard5.mod-stat-damageBonus": "Schadensbonus", "midgard5.mod-stat-damageBonus": "Schadensbonus",
@ -291,6 +292,8 @@
"midgard5.mod-stat-poisonResistance": "Gifttolleranz", "midgard5.mod-stat-poisonResistance": "Gifttolleranz",
"midgard5.mod-stat-lp": "Lebenspunkte", "midgard5.mod-stat-lp": "Lebenspunkte",
"midgard5.mod-stat-ap": "Ausdauerpunkte", "midgard5.mod-stat-ap": "Ausdauerpunkte",
"midgard5.mod-stat-lpProtection": "Rüstungsschutz (LP)",
"midgard5.mod-stat-apProtection": "Rüstungsschutz (AP)",
"midgard5.mod-type": "Typ der Modifikation", "midgard5.mod-type": "Typ der Modifikation",
"midgard5.mod-id": "Was soll modifiziert werden", "midgard5.mod-id": "Was soll modifiziert werden",

View File

@ -86,6 +86,10 @@ Hooks.once("init", async () => {
return param.replace(regex, ""); return param.replace(regex, "");
}); });
Handlebars.registerHelper("contains", (label: string, contains: string) => {
return label.toLowerCase().includes(contains.toLowerCase());
});
// Default Sheet für Items definieren und das Standardsheet deaktivieren // Default Sheet für Items definieren und das Standardsheet deaktivieren
Items.unregisterSheet("core", ItemSheet); Items.unregisterSheet("core", ItemSheet);
Items.registerSheet("midgard5", M5ItemSheet, { makeDefault: true }); Items.registerSheet("midgard5", M5ItemSheet, { makeDefault: true });
@ -96,7 +100,6 @@ Hooks.once("init", async () => {
CONFIG.Actor.documentClass = M5Character; CONFIG.Actor.documentClass = M5Character;
CONFIG.Item.documentClass = M5Item; CONFIG.Item.documentClass = M5Item;
//RegisterSettings(); //RegisterSettings();
await preloadTemplates(); await preloadTemplates();
}); });
@ -105,6 +108,102 @@ Hooks.once("setup", () => {
Logger.log("Template module is being setup."); Logger.log("Template module is being setup.");
}); });
Hooks.on("getChatLogEntryContext", function (html, options) {
options.push(
{
name: "LP & AP Schaden",
icon: '<i class="fas fa-tint"></i>',
condition: (li) => {
// Only show this context menu if there are re-rollable dice in the message
const damageRolls = li.find(".apply-damage").length;
console.log(game["user"].isGm);
// All must be true to show the reroll dialogue
// The button doesn't work for the GM right now, so we don't need to show it
return game["user"].character && damageRolls > 0;
},
callback: (li) => applyDamage(li, 2),
},
{
name: "AP Schaden",
icon: '<i class="fas fa-shield-alt"></i>',
condition: (li) => {
// Only show this context menu if there are re-rollable dice in the message
const damageRolls = li.find(".apply-damage").length;
// All must be true to show the reroll dialogue
// The button doesn't work for the GM right now, so we don't need to show it
return game["user"].character && damageRolls > 0;
},
callback: (li) => applyDamage(li, 1),
},
{
name: "LP & AP Heilen",
icon: '<i class="fas fa-heart"></i>',
condition: (li) => {
// Only show this context menu if there are re-rollable dice in the message
const damageRolls = li.find(".apply-damage").length;
// All must be true to show the reroll dialogue
// The button doesn't work for the GM right now, so we don't need to show it
return game["user"].character && damageRolls > 0;
},
callback: (li) => applyDamage(li, -1),
},
{
name: "AP Heilen",
icon: '<i class="far fa-heart"></i>',
condition: (li) => {
// Only show this context menu if there are re-rollable dice in the message
const damageRolls = li.find(".apply-damage").length;
// All must be true to show the reroll dialogue
// The button doesn't work for the GM right now, so we don't need to show it
return game["user"].character && damageRolls > 0;
},
callback: (li) => applyDamage(li, -2),
}
);
});
Hooks.once("ready", () => { Hooks.once("ready", () => {
Logger.ok("Template module is now ready."); Logger.ok("Template module is now ready.");
}); });
async function applyDamage(roll, direction) {
const damageValue = Array.from(roll.find(".apply-damage") as HTMLElement[])
.map((x) => Math.max(0, Number(x.innerText)))
.reduce((prev, curr) => prev + curr, 0);
const userId = game["user"].character.id;
const viewedSceneId = game["user"].viewedScene;
const token = game["actors"].get(userId).getDependentTokens(viewedSceneId)[0]?.delta.syntheticActor;
const actor = game["user"].character;
if (token) {
switch (direction) {
case 2:
token["system"].lp.value -= Math.max(0, damageValue - token["system"].calc.stats.lpProtection.value);
case 1:
token["system"].ap.value -= Math.max(0, damageValue - token["system"].calc.stats.apProtection.value);
break;
case -1:
token["system"].lp.value += Math.min(damageValue, token["system"].lp.max - damageValue);
case -2:
token["system"].ap.value += Math.min(damageValue, token["system"].ap.max - damageValue);
}
token.render();
} else {
switch (direction) {
case 2:
actor["system"].lp.value -= Math.max(0, damageValue - actor["system"].calc.stats.lpProtection.value);
case 1:
actor["system"].ap.value -= Math.max(0, damageValue - actor["system"].calc.stats.apProtection.value);
break;
case -1:
actor["system"].lp.value += Math.min(damageValue, actor["system"].lp.max - damageValue);
case -2:
actor["system"].ap.value += Math.min(damageValue, actor["system"].ap.max - damageValue);
}
actor.render();
}
}

View File

@ -82,6 +82,8 @@ export enum M5Stats {
POISON_RESISTANCE = "poisonResistance", POISON_RESISTANCE = "poisonResistance",
LP = "lp", LP = "lp",
AP = "ap", AP = "ap",
PROTECTION_LP = "lpProtection",
PROTECTION_AP = "apProtection",
} }
export enum M5ModType { export enum M5ModType {
@ -143,7 +145,8 @@ export interface M5CharacterCalculatedData {
stats: { stats: {
lp: M5ModResult; lp: M5ModResult;
ap: M5ModResult; ap: M5ModResult;
armor: number; lpProtection: M5ModResult;
apProtection: M5ModResult;
defense: M5ModResult; defense: M5ModResult;
damageBonus: M5ModResult; damageBonus: M5ModResult;
attackBonus: M5ModResult; attackBonus: M5ModResult;

View File

@ -53,7 +53,8 @@ export class M5Character extends Actor {
stats: { stats: {
lp: { value: 0, mods: [] }, lp: { value: 0, mods: [] },
ap: { value: 0, mods: [] }, ap: { value: 0, mods: [] },
armor: 0, lpProtection: { value: 0, mods: [] },
apProtection: { value: 0, mods: [] },
defense: { value: 0, mods: [] }, defense: { value: 0, mods: [] },
damageBonus: { value: 0, mods: [] }, damageBonus: { value: 0, mods: [] },
attackBonus: { value: 0, mods: [] }, attackBonus: { value: 0, mods: [] },
@ -116,7 +117,8 @@ export class M5Character extends Actor {
ret.stats.lp = this.modResult(data.lp); ret.stats.lp = this.modResult(data.lp);
ret.stats.ap = this.modResult(data.ap); ret.stats.ap = this.modResult(data.ap);
ret.stats.armor = 0; ret.stats.lpProtection = this.modResult(0);
ret.stats.apProtection = this.modResult(0);
ret.stats.defense = this.modResult(M5Character.defenseFromLevel(ret.level)); ret.stats.defense = this.modResult(M5Character.defenseFromLevel(ret.level));
ret.stats.damageBonus = this.modResult(Math.floor(ret.attributes.st.value / 20) + Math.floor(ret.attributes.gs.value / 30) - 3); ret.stats.damageBonus = this.modResult(Math.floor(ret.attributes.st.value / 20) + Math.floor(ret.attributes.gs.value / 30) - 3);
ret.stats.attackBonus = this.modResult(ret.attributes.gs.bonus); ret.stats.attackBonus = this.modResult(ret.attributes.gs.bonus);

View File

@ -39,6 +39,8 @@ export default class M5ModAggregate {
this.push({ type: M5ModType.STAT, id: M5Stats.POISON_RESISTANCE, operation: M5ModOperation.SET, value: calc.stats.poisonResistance.value }, characterString); this.push({ type: M5ModType.STAT, id: M5Stats.POISON_RESISTANCE, operation: M5ModOperation.SET, value: calc.stats.poisonResistance.value }, characterString);
this.push({ type: M5ModType.STAT, id: M5Stats.LP, operation: M5ModOperation.SET, value: calc.stats.lp.value }, characterString); this.push({ type: M5ModType.STAT, id: M5Stats.LP, operation: M5ModOperation.SET, value: calc.stats.lp.value }, characterString);
this.push({ type: M5ModType.STAT, id: M5Stats.AP, operation: M5ModOperation.SET, value: calc.stats.ap.value }, characterString); this.push({ type: M5ModType.STAT, id: M5Stats.AP, operation: M5ModOperation.SET, value: calc.stats.ap.value }, characterString);
this.push({ type: M5ModType.STAT, id: M5Stats.PROTECTION_LP, operation: M5ModOperation.SET, value: calc.stats.lpProtection.value }, characterString);
this.push({ type: M5ModType.STAT, id: M5Stats.PROTECTION_AP, operation: M5ModOperation.SET, value: calc.stats.apProtection.value }, characterString);
} }
push(mod: M5ItemMod, source: string) { push(mod: M5ItemMod, source: string) {

View File

@ -111,6 +111,8 @@ export class M5Item extends Item {
itemData.mods[1] = { type: "stat", id: "attackBonus", operation: "add", value: itemData.stats.attackBonus }; itemData.mods[1] = { type: "stat", id: "attackBonus", operation: "add", value: itemData.stats.attackBonus };
itemData.mods[2] = { type: "stat", id: "movement", operation: "add", value: itemData.stats.movementBonus }; itemData.mods[2] = { type: "stat", id: "movement", operation: "add", value: itemData.stats.movementBonus };
itemData.mods[3] = { type: "attribute", id: "gw", operation: "add100", value: itemData.attributeMod.gw }; itemData.mods[3] = { type: "attribute", id: "gw", operation: "add100", value: itemData.attributeMod.gw };
itemData.mods[4] = { type: "stat", id: "lpProtection", operation: "set", value: itemData.lpProtection };
itemData.mods[5] = { type: "stat", id: "apProtection", operation: "set", value: itemData.apProtection };
} else if (itemType === "spell") { } else if (itemType === "spell") {
calc.fw = 0; calc.fw = 0;
if (actor) { if (actor) {

View File

@ -82,7 +82,7 @@
<tr class="roll-row {{roll.css}}"> <tr class="roll-row {{roll.css}}">
<td>{{roll.label}}</td> <td>{{roll.label}}</td>
<td class="roll-result"> <td class="roll-result">
<span class="roll-total">{{roll.totalStr}}</span> <span class="roll-total {{#if (contains roll.label "Schaden")}} apply-damage{{/if}}">{{roll.totalStr}}</span>
<span class="roll-detail">{{roll.result}}</span> <span class="roll-detail">{{roll.result}}</span>
</td> </td>
</tr> </tr>

View File

@ -22,10 +22,10 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td>{{localize "midgard5.actor-lp"}}</td> <td>{{localize "midgard5.lpProtection"}}</td>
<td><input name="data.lpProtection" type="text" value="{{data.lpProtection}}" data-dtype="Number" /></td> <td><input name="data.lpProtection" type="text" value="{{data.lpProtection}}" data-dtype="Number" /></td>
<td>{{localize "midgard5.actor-ap"}}</td> <td>{{localize "midgard5.apProtection"}}</td>
<td><input name="data.apProtection" type="text" value="{{data.apProtection}}" data-dtype="Number" /></td> <td><input name="data.apProtection" type="text" value="{{data.apProtection}}" data-dtype="Number" /></td>
</tr> </tr>