15 add duration to active effects (#50)

* Add Effect Duration & Rework Combat tracker

Changes:
 + rework combat tracker to display every uneven round as Movementphase
 + rework combat tracker to display every even round as actionphase
 + add possibility for effects durations
 + effects duration decreases with every actionphase
 + add enum for time Units
 + update localization
 + add display of remaining time on effects
 + add duration for spelleffect "Bärenwut"

* add LP AP Manipulation through effects

Changes:
 + add LimitHeal function
 + adjust LP AP Values if Mod exists in an effect

* Fix linked actor not being updated
This commit is contained in:
Byroks 2023-12-11 17:36:12 +01:00 committed by GitHub
parent 30e94741bf
commit 1e27e135cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 120 additions and 17 deletions

View File

@ -5,11 +5,22 @@
"TYPES.Item.item": "Gegenstand", "TYPES.Item.item": "Gegenstand",
"TYPES.Item.weapon": "Waffe", "TYPES.Item.weapon": "Waffe",
"ITEM.TypeDefensiveWeapon": "Verteidigungswaffe", "TYPES.Item.defensiveWeapon": "Verteidigungswaffe",
"TYPES.Item.armor": "Rüstung", "TYPES.Item.armor": "Rüstung",
"TYPES.Item.spell": "Zauber", "TYPES.Item.spell": "Zauber",
"TYPES.Item.effect": "Aktive Effekte", "TYPES.Item.effect": "Aktive Effekte",
"ITEM.type.kampfkunst": "Kampfkünste", "TYPES.Item.kampfkunst": "Kampfkünste",
"midgard5.phase-action": "Handlungsphase",
"midgard5.phase-movement": "Bewegungsphase",
"midgard5.no-encounter": "Kein Kampf",
"midgard5.encounter-not-started": "Kein aktiver Kampf",
"midgard5.time-duration": "Dauer",
"midgard5.time-round": "Runde(n)",
"midgard5.time-minute": "Minute(n)",
"midgard5.time-hour": "Stunde(n)",
"midgard5.time-limitless": "Unbegrenzt",
"midgard5.doRoll": "Würfeln", "midgard5.doRoll": "Würfeln",
"midgard5.learn": "Lernen", "midgard5.learn": "Lernen",

View File

@ -6,6 +6,7 @@
"description": "<p>ARK S. 144</p>", "description": "<p>ARK S. 144</p>",
"equippable": false, "equippable": false,
"equipped": true, "equipped": true,
"duration": { "time": 12, "unit": "round" },
"value": 0, "value": 0,
"magic": true, "magic": true,
"rolls": { "formulas": {}, "output": "" }, "rolls": { "formulas": {}, "output": "" },

View File

@ -2,7 +2,7 @@ import Logger from "./utils/Logger";
import M5CharacterSheet from "./module/sheets/M5CharacterSheet"; import M5CharacterSheet from "./module/sheets/M5CharacterSheet";
import preloadTemplates from "./PreloadTemplates"; import preloadTemplates from "./PreloadTemplates";
import { M5Character } from "./module/actors/M5Character"; import { M5Character } from "./module/actors/M5Character";
import { M5Skill } from "./module/M5Base"; import { M5ItemMod, M5ModOperation, M5Skill, M5TimeUnit } from "./module/M5Base";
import { M5ItemSheet } from "./module/sheets/M5ItemSheet"; import { M5ItemSheet } from "./module/sheets/M5ItemSheet";
import { M5Item } from "./module/items/M5Item"; import { M5Item } from "./module/items/M5Item";
@ -58,10 +58,10 @@ Hooks.once("init", async () => {
return `${obj}`; return `${obj}`;
}); });
Handlebars.registerHelper("actorItemValue", (actorId: any, itemId: string, path: string) => { Handlebars.registerHelper("actorItemValue", (actorId: any, itemId: string, path: string, token?: boolean) => {
//console.log("actorItemValue", actorId, itemId, path) //console.log("actorItemValue", actorId, itemId, path)
const actor = (game as Game).actors.get(actorId); const actor = (game as Game).actors.get(actorId);
let obj = actor.items.get(itemId).system; let obj = actor.items.get(itemId)?.system;
path.split(".").forEach((p) => { path.split(".").forEach((p) => {
if (obj) obj = obj[p]; if (obj) obj = obj[p];
}); });
@ -116,7 +116,6 @@ Hooks.on("getChatLogEntryContext", function (html, options) {
condition: (li) => { condition: (li) => {
// Only show this context menu if there are re-rollable dice in the message // Only show this context menu if there are re-rollable dice in the message
const damageRolls = li.find(".apply-damage").length; const damageRolls = li.find(".apply-damage").length;
console.log(game["user"].isGm);
// All must be true to show the reroll dialogue // 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 // The button doesn't work for the GM right now, so we don't need to show it
@ -166,6 +165,70 @@ Hooks.on("getChatLogEntryContext", function (html, options) {
); );
}); });
Hooks.on("updateCombat", function (combat: Combat, updateData: { round: number; turn: number }, updateOptions: { advanceTime: number; direction: number }) {
if (combat.round % 2 === 0 && combat.turn !== null) {
const tokenId = combat.current.tokenId;
const actorId = combat.combatant["actorId"];
let currentToken = game["actors"].tokens[tokenId];
if (!currentToken) {
currentToken = game["actors"].get(actorId);
}
let activeEffects = currentToken.items.filter((x) => x.type === "effect" && x.system.equipped) || [];
activeEffects.forEach((effect) => {
if (effect.system?.duration?.time > 0) {
if (effect.system.duration.unit === M5TimeUnit.ROUND) {
effect.system.duration.time -= 1;
}
}
if (effect.system?.duration.time === 0 && effect.system.duration.unit !== M5TimeUnit.LIMITLESS) {
effect.system.equipped = false;
}
for (const key in effect.system.mods) {
if (effect.system.mods[key].operation === M5ModOperation.SUBTRACT) {
switch (effect.system.mods[key].id) {
case "lp":
currentToken["system"].lp.value -= effect.system.mods[key].value;
break;
case "ap":
currentToken["system"].ap.value -= effect.system.mods[key].value;
break;
}
} else if (effect.system.mods[key].operation === M5ModOperation.ADD) {
switch (effect.system.mods[key].id) {
case "lp":
currentToken["system"].lp.value += limitHeal(effect.system.mods[key].value, currentToken["system"].lp.value, currentToken["system"].lp.max);
break;
case "ap":
currentToken["system"].ap.value += limitHeal(effect.system.mods[key].value, currentToken["system"].ap.value, currentToken["system"].ap.max);
break;
}
}
}
});
currentToken.render();
}
});
function limitHeal(heal: number, current: number, max: number): number {
if (current === max) {
return 0;
} else if (heal + current > max) {
return max - current;
}
return heal;
}
Hooks.on("renderCombatTracker", (combatTracker, html, context) => {
if (context.combat === null) {
html.find("h3.encounter-title")[0].innerHTML = game["i18n"].localize("midgard5.no-encounter");
} else if (Math.ceil(context.round / 2) === 0) {
html.find("h3.encounter-title")[0].innerHTML = game["i18n"].localize("midgard5.encounter-not-started");
} else {
html.find("h3.encounter-title")[0].innerHTML =
(context.round % 2 == 1 ? game["i18n"].localize("midgard5.phase-movement") : game["i18n"].localize("midgard5.phase-action")) + " " + Math.ceil(context.round / 2);
}
});
Hooks.once("ready", () => { Hooks.once("ready", () => {
Logger.ok("Template module is now ready."); Logger.ok("Template module is now ready.");
}); });
@ -187,9 +250,9 @@ async function applyDamage(roll, direction) {
token["system"].ap.value -= Math.max(0, damageValue - token["system"].calc.stats.apProtection.value); token["system"].ap.value -= Math.max(0, damageValue - token["system"].calc.stats.apProtection.value);
break; break;
case -1: case -1:
token["system"].lp.value += Math.min(damageValue, token["system"].lp.max - damageValue); token["system"].lp.value += limitHeal(damageValue, token["system"].lp.value, token["system"].lp.max);
case -2: case -2:
token["system"].ap.value += Math.min(damageValue, token["system"].ap.max - damageValue); token["system"].ap.value += limitHeal(damageValue, token["system"].ap.value, token["system"].ap.max);
} }
token.render(); token.render();
} else { } else {
@ -200,9 +263,9 @@ async function applyDamage(roll, direction) {
actor["system"].ap.value -= Math.max(0, damageValue - actor["system"].calc.stats.apProtection.value); actor["system"].ap.value -= Math.max(0, damageValue - actor["system"].calc.stats.apProtection.value);
break; break;
case -1: case -1:
actor["system"].lp.value += Math.min(damageValue, actor["system"].lp.max - damageValue); actor["system"].lp.value += limitHeal(damageValue, token["system"].lp.value, token["system"].lp.max);
case -2: case -2:
actor["system"].ap.value += Math.min(damageValue, actor["system"].ap.max - damageValue); actor["system"].ap.value += limitHeal(damageValue, token["system"].ap.value, token["system"].ap.max);
} }
actor.render(); actor.render();
} }

View File

@ -102,6 +102,13 @@ export enum M5ModOperation {
DIVISION = "division", DIVISION = "division",
} }
export enum M5TimeUnit {
ROUND = "round",
MINUTE = "minute",
HOUR = "hour",
LIMITLESS = "limitless",
}
export interface M5ItemMod { export interface M5ItemMod {
type: M5ModType; type: M5ModType;
id: string; id: string;

View File

@ -188,7 +188,8 @@ export class M5Character extends Actor {
label: label, label: label,
magic: item.system.magic, magic: item.system.magic,
calc: item.system.calc, calc: item.system.calc,
equipped: item.system?.equipped, equipped: item.system?.equipped || false,
duration: item.system?.duration || { time: 0, unit: "" },
}; };
}); });
} }

View File

@ -201,6 +201,14 @@
"value": 0, "value": 0,
"magic": false "magic": false
}, },
"durationSelection": {
"durationSelection": {
"round": "midgard5.time-round",
"minute": "midgard5.time-minute",
"hour": "midgard5.time-hour",
"limitless": "midgard5.time-limitless"
}
},
"spellSelection": { "spellSelection": {
"spellProcessSelection": { "spellProcessSelection": {
"none": "midgard5.spell-process-none", "none": "midgard5.spell-process-none",
@ -295,7 +303,7 @@
"calc": {} "calc": {}
}, },
"effect": { "effect": {
"templates": ["itemDescription", "equippable", "physical"], "templates": ["itemDescription", "equippable", "physical", "durationSelection"],
"rolls": { "rolls": {
"formulas": {}, "formulas": {},
"output": "" "output": ""

View File

@ -12,6 +12,9 @@
<span class="edit-item">{{item.label}}</span> <span class="edit-item">{{item.label}}</span>
{{#if item.equipped}} {{#if item.equipped}}
<span class="spell-process">{{localize "midgard5.active"}}</span> <span class="spell-process">{{localize "midgard5.active"}}</span>
{{#unless (or (eq item.duration.unit "") (eq item.duration.unit "limitless"))}}
<span class="spell-process">{{item.duration.time}} {{localize (concat "midgard5.time-" item.duration.unit)}}</span>
{{/unless}}
{{/if}} {{/if}}
</td> </td>
<td class="fixed-value"><a class="item-delete" title="Delete Item"><i class="fas fa-trash"></i></a></td> <td class="fixed-value"><a class="item-delete" title="Delete Item"><i class="fas fa-trash"></i></a></td>

View File

@ -29,7 +29,7 @@
<table> <table>
<thead> <thead>
<tr> <tr>
<th class="title">{{localize "ITEM.TypeDefensiveWeapon"}}</th> <th class="title">{{localize "TYPES.Item.defensiveWeapon"}}</th>
<th class="title">{{localize "midgard5.ew"}}</th> <th class="title">{{localize "midgard5.ew"}}</th>
<th class="title"></th> <th class="title"></th>
<th class="title"></th> <th class="title"></th>
@ -90,7 +90,7 @@
<span class="spell-process">{{localize "midgard5.equipped"}}</span> <span class="spell-process">{{localize "midgard5.equipped"}}</span>
{{/if}} {{/if}}
</td> </td>
<td class="fixed-value">{{actorItemValue ../actor._id itemId "lpProtection"}}</td> <td class="fixed-value">{{actorItemValue ../actor._id itemId "lpProtection" ../actor.isToken}}</td>
<td class="fixed-value">{{actorItemValue ../actor._id itemId "apProtection"}}</td> <td class="fixed-value">{{actorItemValue ../actor._id itemId "apProtection"}}</td>
<td class="fixed-value">{{actorItemValue ../actor._id itemId "stats.attackBonus"}}</td> <td class="fixed-value">{{actorItemValue ../actor._id itemId "stats.attackBonus"}}</td>
<td class="fixed-value">{{actorItemValue ../actor._id itemId "stats.defenseBonus"}}</td> <td class="fixed-value">{{actorItemValue ../actor._id itemId "stats.defenseBonus"}}</td>

View File

@ -1,7 +1,7 @@
<table> <table>
<thead> <thead>
<tr> <tr>
<th class="title">{{localize "ITEM.type.kampfkunst"}}</th> <th class="title">{{localize "TYPES.Item.kampfkunst"}}</th>
<th class="title">{{localize "midgard5.ew"}}</th> <th class="title">{{localize "midgard5.ew"}}</th>
<th class="title"></th> <th class="title"></th>
<th class="title"></th> <th class="title"></th>

View File

@ -22,8 +22,17 @@
<tr> <tr>
<td> <td>
<div class="flexrow"> <div class="flexrow">
<span>{{localize "midgard5.item-value"}}</span> <span>{{localize "midgard5.time-duration"}}</span>
<input name="data.value" type="text" value="{{data.value}}" data-dtype="Number" /> <input name="data.duration.time" type="text" value="{{data.duration.time}}" data-dtype="Number" />
<select class="select-mod-operation" name="data.duration.unit" data-type="String">
{{#select data.duration.unit}}
<option value=""></option>
<option value="round">{{localize "midgard5.time-round"}}</option>
<option value="minute">{{localize "midgard5.time-minute"}}</option>
<option value="hour">{{localize "midgard5.time-hour"}}</option>
<option value="limitless">{{localize "midgard5.time-limitless"}}</option>
{{/select}}
</select>
</div> </div>
</td> </td>
</tr> </tr>