Compare commits

...

3 Commits

Author SHA1 Message Date
Ender 64fd95c814 Function for combat phase as character info 2024-05-22 18:59:51 +02:00
Ender 976cd83a04 Added Movemenmt and action tab 2024-05-20 23:26:42 +02:00
Ender aef48dd46b Subject: Enhance combat mechanics and localization
- Introduced new combat-related properties (initiative, group, unable to act) to characters for an enriched gaming experience.
- Added German translations for newly introduced terms to ensure consistency within the game's interface.
- Capitalized the German translation for "magic" to align with existing convention.
- Refactored settings management in player and NPC sheets for better clarity and maintenance.
- Implemented logic for handling characters' inability to act in combat, including UI elements for managing this state, thereby increasing gameplay depth.
- Adjusted display and management of items and effects to accommodate new combat features, streamlining user interaction and integration with the combat system.
- Minor UI and styling adjustments to enhance readability and user experience across character and item sheets.

These changes aim to make combat encounters more dynamic and engaging, while also improving the user interface for ease of interaction and consistency.
2024-05-14 13:33:53 +02:00
11 changed files with 274 additions and 24 deletions

View File

@ -16,10 +16,18 @@
"midgard5": {
"phase-action": "Handlungsphase",
"actionThisTurn": "Handlung",
"phase-movement": "Bewegungsphase",
"no-encounter": "Kein Kampf",
"encounter-not-started": "Kein aktiver Kampf",
"initiative": "Initiative",
"initiativeRoll": "Initiative würfeln",
"unableToAct": "Handlungsunfähig",
"unablesToAct": "Macht handlungsunfähig",
"continue": "Weiter",
"endTurn": "Zug beenden",
"group": "Gruppe",
"leader": "Anführer",
"actionrank": "Handlungsrang",
"combat-join": "Kampf Beitreten/Handlungsrang zurücksetzen",
@ -93,6 +101,7 @@
"maximum": "Max.",
"attrvalue": "Wert",
"movementRange": "Bewegungsweite",
"movedThisTurn": "Bewegt",
"base_values": "Grundwerte",
"skills": "Fertigkeiten",
@ -276,7 +285,7 @@
"defensive-weapon": "Verteidigungswaffe",
"defensive-weapons": "Verteidigungswaffen",
"no-skill": "Keine Fertigkeit",
"magic": "magisch",
"magic": "Magisch",
"valuable": "Vermögen",
"equipped": "Ausgerüstet",
"active": "Aktiv",

0
source/combat.ts Normal file
View File

View File

@ -95,6 +95,9 @@ Hooks.on("getChatLogEntryContext", function (html, options) {
);
});
Hooks.on("createCombatant", function () {
});
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;
@ -140,15 +143,8 @@ Hooks.on("updateCombat", function (combat: Combat, updateData: { round: number;
});
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);
}
});
handleRenderCombatTracker(combatTracker, html, context);
});
Hooks.once("ready", () => {
Logger.ok("Template module is now ready.");
@ -201,3 +197,28 @@ function limitHeal(heal: number, current: number, max: number): number {
}
return heal;
}
function handleRenderCombatTracker(combatTracker, html, context) {
let combatPhase = -1;
if (context.combat === null) {
combatPhase = 0;
html.find("h3.encounter-title")[0].innerHTML = game["i18n"].localize("midgard5.no-encounter");
} else if (Math.ceil(context.round / 2) === 0) {
combatPhase = 1;
html.find("h3.encounter-title")[0].innerHTML = game["i18n"].localize("midgard5.encounter-not-started");
} else if (context.round % 2 == 1) {
combatPhase = 2;
html.find("h3.encounter-title")[0].innerHTML = game["i18n"].localize("midgard5.phase-movement");
} else {
combatPhase = 3;
html.find("h3.encounter-title")[0].innerHTML = game["i18n"].localize("midgard5.phase-action") + " " + Math.ceil(context.round / 2);
}
console.log("Combat Phase: ", combatPhase);
context.combat?.setFlag('world', 'combatPhase', combatPhase);
for (const key in context.combat?.combatants.contents) {
let actorId = context.combat?.combatants.contents[key].actorId;
let actor = (game as Game).actors.get(actorId);
actor.render();
}
}

View File

@ -162,6 +162,15 @@ export interface M5AttributeCalculated extends M5ModResult {
export interface M5CharacterCalculatedData {
level: number;
initiative: number;
unableToAct: {
effectId: string;
enabled: boolean;
rounds: number;
reason: string;
};
group: string;
combatPhase: number;
movement: number;
attributes: {
st: M5AttributeCalculated;

View File

@ -1,3 +1,4 @@
import { log } from "console";
import { M5Item } from "../items/M5Item";
import { M5Attribute, M5CharacterCalculatedData, M5ItemMod, M5ItemType, M5ModOperation, M5ModResult, M5RollData, M5Skill, M5SkillCalculated } from "../M5Base";
import M5ModAggregate from "./M5ModAggregate";
@ -86,6 +87,7 @@ export class M5Character extends Actor {
): M5CharacterCalculatedData {
let ret: M5CharacterCalculatedData = {
level: 0,
initiative: 0,
attributes: {
st: { value: 0, bonus: 0, mods: [] },
gs: { value: 0, bonus: 0, mods: [] },
@ -151,6 +153,10 @@ export class M5Character extends Actor {
if (!data) return null;
ret.level = M5Character.levelFromExp(data.info.race === "Zwerg" ? Math.min(data.calc.stats?.hoard * 2 || 0, data.es) : data.es);
ret.initiative = M5Character.initiativeFromAnfuehren(data.calc.skills?.general, (game as Game).i18n.localize("midgard5.anfuehren"), data.skills.general.anfuehren.fw);
ret.unableToAct = M5Character.unableToActFromEffect(data.calc.gear?.effects);
ret.group = M5Character.groupFromDisposition(this);
ret.combatPhase = M5Character.combatPhaseFromEncounter(this);
ret.attributes.st.value = M5Character.attributeMinMax(data.attributes.st); // TODO item effects
ret.attributes.gs.value = M5Character.attributeMinMax(data.attributes.gs);
@ -494,6 +500,7 @@ export class M5Character extends Actor {
magic: item.system.magic,
calc: item.system.calc,
equipped: item.system?.equipped || false,
unablesToAct: item.system?.unablesToAct || false,
duration: item.system?.duration || { time: 0, unit: "" },
};
});
@ -604,6 +611,55 @@ export class M5Character extends Actor {
return ret === -1 ? M5Character.levelThreshold.length : ret;
}
static initiativeFromAnfuehren(list: object, anfuehren: string, unlearned: number): number {
for (const element in list) {
if (list[element].label.toLowerCase() === anfuehren.toLowerCase()) {
return list[element].calc.ew;
}
}
return unlearned;
}
static unableToActFromEffect(list: object): { effectId: string; enabled: boolean; rounds: number; reason: string } {
console.log("Effects:", list);
for (const element in list) {
if (list[element].unablesToAct) {
return {
effectId: list[element],
enabled: list[element].equipped,
rounds: list[element].duration.time,
reason: list[element].label,
};
}
}
return { effectId: "", enabled: false, rounds: 0, reason: "" };
}
static groupFromDisposition(actor: any): string {
// console.log("Group:", actor);
let disposition:number = 0;
if (actor.isToken) {
disposition = actor.token.disposition;
} else {
disposition = actor.prototypeToken.disposition;
}
switch ( disposition ) {
case 1: return "Spieler";
case -1: return "Gegner";
case 0: return "Gegner 2";
case 2: return "Gegner 3";
default: return "Unbekannt";
}
}
static combatPhaseFromEncounter(actor: any): any {
if (actor.inCombat) {
let combatPhase = (game as Game).combat.getFlag('world', 'combatPhase');
return combatPhase ? combatPhase : -1;
}
return -1;
}
static readonly defenseThreshold: Array<[number, number]> = [
[30, 18],
[25, 17],

View File

@ -5,6 +5,7 @@
.midgard5 {
.flexbox {
display: flex;
align-items: stretch;
flex-direction: row;
flex-wrap: wrap;
}
@ -17,26 +18,31 @@
.flexcolumn-1 {
flex-basis: 100%;
flex-wrap: wrap;
flex-direction: column;
}
.flexcolumn-2 {
flex-basis: 50%;
flex-wrap: wrap;
flex-direction: column;
}
.flexcolumn-3 {
flex-basis: 33%;
flex-wrap: wrap;
flex-direction: column;
}
.flexcolumn-4 {
flex-basis: 25%;
flex-wrap: wrap;
flex-direction: column;
}
.flexcolumn-5 {
flex-basis: 20%;
flex-wrap: wrap;
flex-direction: column;
}
.flexpart {
@ -63,7 +69,7 @@
}
.flexrow {
align-items: center;
flex-direction: row;
}
h3 {
@ -79,6 +85,11 @@
border-radius: 10px;
}
.fixed-value {
width: 3rem;
text-align: center;
}
.profile-img {
display: block;
margin-left: auto;

View File

@ -23,13 +23,16 @@
"origin": "",
"faith": "",
"level": 1,
"leader": false,
"gold": 0,
"silver": 0,
"copper": 0,
"showAllItems": false,
"showUnlearned": false
"copper": 0
}
},
"settings": {
"showAllItems": false,
"showUnlearned": false
},
"characterBars": {
"lp": {
"value": 15,
@ -163,11 +166,11 @@
}
},
"character": {
"templates": ["characterBars", "attributes", "characterDescription", "characterHeader", "skills", "gear"],
"templates": ["characterBars", "settings", "attributes", "characterDescription", "characterHeader", "skills", "gear"],
"calc": {}
},
"npc": {
"templates": ["characterBars", "attributes", "characterDescription", "skills", "gear"],
"templates": ["characterBars", "settings", "attributes", "characterDescription", "skills", "gear"],
"calc": {}
}
},
@ -374,6 +377,7 @@
},
"effect": {
"templates": ["itemDescription", "equippable", "physical", "durationSelection"],
"unablesToAct": false,
"rolls": {
"formulas": {},
"output": ""

View File

@ -1,5 +1,140 @@
<div class="flexbox">
<h3>{{localize "midgard5.combatPhases"}}</h3>
<div class="flexbox flexpart">
<div class="flexcolumn-2 flexpart" style="flex: 1 1 200px;">
<div class="flexpart-header"><img src="icons/magic/time/arrows-circling-pink.webp" class="flexpart-icon">{{localize "midgard5.initiative"}}</div>
<br>
<div class="flexrow">
<div style="font-weight: 800;">{{localize "midgard5.group"}}:</div>
<div class="fixed-value" style="font-weight: 800;">{{data.calc.group}}</div>
</div>
<div class="flexrow">
<div style="font-weight: 800;">{{localize "midgard5.anfuehren"}}:</div>
<div class="fixed-value" style="font-weight: 800;">{{data.calc.initiative}}</div>
</div>
<br>
{{#if data.calc.unableToAct.enabled}}
<div class="flexrow">
<div>{{localize "midgard5.unableToAct"}}</div>
<div class="fixed-value;"><input type="checkbox" name="data.calc.unableToAct.enabled" {{checked data.calc.unableToAct.enabled}} disabled="disabled"></div>
<div class="fixed-value"><button class="roll-button roll-unableToAct-button"></button></div>
</div>
<div class="flexrow">
&nbsp; &nbsp;
<div>{{localize "midgard5.time-round"}}</div>
<div class="fixed-value"><input type="number" name="data.calc.unableToAct.roundsRemaining" value="{{data.calc.unableToAct.roundsRemaining}}"></div>
</div>
{{/if}}
{{#unless data.calc.unableToAct.enabled}}
<div class="flexrow">
<div>{{localize "midgard5.leader"}}</div>
<div class="fixed-value;"><input type="checkbox" name="data.info.leader" {{checked data.info.leader}}></div>
</div>
{{#if data.info.leader}}
<div class="flexrow">
<button class="wide-button rollInitiative">{{localize "midgard5.initiativeRoll"}}</button>
</div>
{{/if}}
{{/unless}}
<div class="flexpart-header"><img src="icons/magic/time/arrows-circling-pink.webp" class="flexpart-icon">{{localize "midgard5.phase-movement"}}</div>
<br>
<div class="flexrow">
<div style="font-weight: 800;">{{localize "midgard5.movementRange"}}:</div>
<div class="fixed-value" style="font-weight: 800;">{{data.calc.stats.movement.value}}</div>
</div>
<br>
<div class="flexrow">
<div><label for="tM1">{{localize "midgard5.movedThisTurn"}}: max. 1m</label></div>
<div><input type="radio" id="tM1" name="gwModificator" value="1"></div>
</div>
<div class="flexrow">
<div><label for="tM2">{{localize "midgard5.movedThisTurn"}}: max.B/2m</label></div>
<div><input type="radio" id="tM2" name="gwModificator" value="0.5"></div>
</div>
<div class="flexrow">
<div><label for="tM3">{{localize "midgard5.movedThisTurn"}}: max. {{data.calc.stats.movement.value}}m</label></div>
<div><input type="radio" id="tm3" name="gwModificator" value="0"></div>
</div>
<div class="flexrow">
<div><label for="tM4">Eigener GW Multiplikator</label></div>
<div><input type="radio" id="tm4" name="dedicatedgwModificator" value="1"></div>
</div>
<div class="flexrow">
<div>&nbsp;&nbsp;<label for="tM5">GW Multiplikator:</label></div>
<div class="fixed-value;"><input type="float" id="tm5" name="gwModificator" value="0" ></div>
</div>
<div class="flexrow">
<button class="wide-button continueToAction">{{localize "midgard5.continue"}}</button>
</div>
</div>
<div class="flexcolumn-2 flexpart" style="flex: 1 1 200px;">
<div class="flexpart-header"><img src="icons/magic/time/arrows-circling-pink.webp" class="flexpart-icon">{{localize "midgard5.phase-action"}}</div>
<br>
<div class="flexrow">
<div style="font-weight: 800;">{{localize "midgard5.actionrank"}} :</div>
<div class="fixed-value" style="font-weight: 800;">{{data.calc.attributes.gw.value}}</div>
</div>
<br>
<div class="flexrow">
<div><label for="a1">{{localize "midgard5.actionThisTurn"}}: 10s</label></div>
<div><input type="radio" id="a1" name="gwModificator" value="1"></div>
</div>
<div class="flexrow">
<div><label for="a2">{{localize "midgard5.actionThisTurn"}}: 5s</label></div>
<div><input type="radio" id="a2" name="gwModificator" value="0.5"></div>
</div>
<div class="flexrow">
<div><label for="a3">{{localize "midgard5.actionThisTurn"}}: 1s</label></div>
<div><input type="radio" id="a3" name="gwModificator" value="0"></div>
</div>
<div class="flexrow">
<div><label for="a4">Manuelle Eingabe</label></div>
<div><input type="radio" id="a4" name="dedicatedgwModificator" value="1"></div>
</div>
<div class="flexrow">
<div>&nbsp;&nbsp;<label for="a5">{{localize "midgard5.actionrank"}}:</label></div>
<div class="fixed-value;"><input type="float" id="a5" name="midgard5.actionThisTurn" value="0" ></div>
</div>
<div class="flexrow">
<button class="wide-button endTurn">{{localize "midgard5.endTurn"}}</button>
</div>
<div class="flexpart-header"><img src="icons/magic/time/arrows-circling-pink.webp" class="flexpart-icon">{{localize "midgard5.phase-action"}}</div>
<br>
<div class="flexrow">
<div style="font-weight: 800;">{{localize "midgard5.actionrank"}} :</div>
<div class="fixed-value" style="font-weight: 800;">{{data.calc.attributes.gw.value}}</div>
</div>
<br>
<div class="flexrow">
<div><label for="a1">{{localize "midgard5.actionThisTurn"}}: 10s</label></div>
<div><input type="radio" id="a1" name="gwModificator" value="1"></div>
</div>
<div class="flexrow">
<div><label for="a2">{{localize "midgard5.actionThisTurn"}}: 5s</label></div>
<div><input type="radio" id="a2" name="gwModificator" value="0.5"></div>
</div>
<div class="flexrow">
<div><label for="a3">{{localize "midgard5.actionThisTurn"}}: 1s</label></div>
<div><input type="radio" id="a3" name="gwModificator" value="0"></div>
</div>
<div class="flexrow">
<div><label for="a4">Manuelle Eingabe</label></div>
<div><input type="radio" id="a4" name="dedicatedgwModificator" value="1"></div>
</div>
<div class="flexrow">
<div>&nbsp;&nbsp;<label for="a5">{{localize "midgard5.actionrank"}}:</label></div>
<div class="fixed-value;"><input type="float" id="a5" name="midgard5.actionThisTurn" value="0" ></div>
</div>
<div class="flexrow">
<button class="wide-button endTurn">{{localize "midgard5.endTurn"}}</button>
</div>
</div>
</div>
<h3>{{localize "midgard5.combat"}}</h3>
<div class="flexbox">
<div class="flexcolumn-2">
<div class="flexpart">
<div class="flexpart-header"><img src="icons/magic/time/arrows-circling-pink.webp" class="flexpart-icon">{{localize "midgard5.calculated-values"}}</div>

View File

@ -3,7 +3,7 @@
<tr>
<th class="title"><img src="/icons/svg/eye.svg" class="table-icon"></th>
<th class="title">{{localize "TYPES.Item.effect"}}</th>
<td><a class="title add-effect"><i class="fa-regular fa-plus"></i></a></th>
<th><a class="title add-effect"><i class="fa-regular fa-plus"></i></a></th>
</tr>
</thead>

View File

@ -217,8 +217,8 @@
<h3>
{{localize "midgard5.allItems"}}
<input id="data.info.showAllItems" class="checkbox" type="checkbox" name="data.info.showAllItems" {{checked data.info.showAllItems}} style="float: right;">
<label for="data.info.showAllItems" style="font-size: small; font-weight: normal; font-style: italic; float: right;">{{localize "midgard5.showAll"}}&nbsp;</label>
<input id="data.settings.showAllItems" class="checkbox" type="checkbox" name="data.settings.showAllItems" {{checked data.settings.showAllItems}} style="float: right;">
<label for="data.settings.showAllItems" style="font-size: small; font-weight: normal; font-style: italic; float: right;">{{localize "midgard5.showAll"}}&nbsp;</label>
</h3>
<div class="flexbox">
<div class="flexcolumn-2">
@ -295,7 +295,7 @@
<tbody class="items-list">
<tr height = 10px></tr>
{{#each data.calc.gear.items as |item itemId|}}
{{#if (or ../data.info.showAllItems (eq item.containerId ""))}}
{{#if (or ../data.settings.showAllItems (eq item.containerId ""))}}
<tr data-item-id="{{itemId}}" class="item">
<td class="flexpart-img"><img src={{item.icon}} class="flexpart-icon"></td>
<td class="padding">
@ -351,7 +351,7 @@
</thead>
<tbody class="items-list">
{{#each data.calc.gear.weapons as |item itemId|}}
{{#if (or ../data.info.showAllItems (eq item.containerId ""))}}
{{#if (or ../data.settings.showAllItems (eq item.containerId ""))}}
<tr data-item-id="{{itemId}}" class="item">
<td class="flexpart-img"><img src={{item.icon}} class="flexpart-icon"></td>
<td class="padding edit-item">{{item.label}}</td>
@ -397,7 +397,7 @@
</thead>
<tbody class="items-list">
{{#each data.calc.gear.defensiveWeapons as |item itemId|}}
{{#if (or ../data.info.showAllItems (eq item.containerId ""))}}
{{#if (or ../data.settings.showAllItems (eq item.containerId ""))}}
<tr data-item-id="{{itemId}}" class="item">
<td class="flexpart-img"><img src={{item.icon}} class="flexpart-icon"></td>
<td class="padding edit-item">{{item.label}}</td>
@ -443,7 +443,7 @@
</thead>
<tbody class="items-list">
{{#each data.calc.gear.armor as |item itemId|}}
{{#if (or ../data.info.showAllItems (eq item.containerId ""))}}
{{#if (or ../data.settings.showAllItems (eq item.containerId ""))}}
<tr data-item-id="{{itemId}}" class="item">
<td class="flexpart-img"><img src={{item.icon}} class="flexpart-icon"></td>
<td class="padding edit-item">{{item.label}}</td>

View File

@ -16,6 +16,11 @@
<input id="data.magic" type="checkbox" name="data.magic" {{checked data.magic}}>
<label for="data.magic">{{localize "midgard5.magic"}}</label>
</span>
<span>
<input id="data.unablesToAct" type="checkbox" name="data.unablesToAct" {{checked data.unablesToAct}}>
<label for="data.unablesToAct">{{localize "midgard5.unablesToAct"}}</label>
</span>
</div>
</td>
</tr>