From 2d328659b2364f052bf56a48025d2ca8e7aefbba Mon Sep 17 00:00:00 2001 From: LeRatierBretonnien Date: Thu, 6 Apr 2023 16:57:19 +0200 Subject: [PATCH] Combat Tracker + power enhancements --- modules/hero6-actor.js | 53 ++++--- modules/hero6-combat.js | 134 +++++++++++++++++- modules/hero6-config.js | 6 + modules/hero6-main.js | 7 +- modules/hero6-utility.js | 3 +- system.json | 4 +- template.json | 3 + templates/apps/combat-tracker.hbs | 127 +++++++++++++++++ templates/items/item-maneuver-sheet.hbs | 3 + templates/items/item-power-sheet.hbs | 2 + .../partial-power-maneuver-effect.hbs | 20 +++ 11 files changed, 327 insertions(+), 35 deletions(-) create mode 100644 templates/apps/combat-tracker.hbs create mode 100644 templates/partials/partial-power-maneuver-effect.hbs diff --git a/modules/hero6-actor.js b/modules/hero6-actor.js index 055c61e..efb6c92 100644 --- a/modules/hero6-actor.js +++ b/modules/hero6-actor.js @@ -3,12 +3,9 @@ import { Hero6Utility } from "./hero6-utility.js"; import { Hero6RollDialog } from "./hero6-roll-dialog.js"; /* -------------------------------------------- */ -const coverBonusTable = { "nocover": 0, "lightcover": 2, "heavycover": 4, "entrenchedcover": 6 }; -const statThreatLevel = ["agi", "str", "phy", "com", "def", "per"] -const __subkey2title = { - "melee-dmg": "Melee Damage", "melee-atk": "Melee Attack", "ranged-atk": "Ranged Attack", - "ranged-dmg": "Ranged Damage", "dmg-res": "Damare Resistance" -} +const __speed2Segments = [[0], [7], [6, 12], [4, 8, 12], [3, 6, 9, 12], [3, 5, 8, 10, 12], [2, 4, 6, 8, 10, 12] + [2, 4, 6, 7, 9, 11, 12], [2, 3, 5, 6, 8, 9, 11, 12], [2, 3, 4, 6, 7, 8, 10, 11, 12], [2, 3, 4, 5, 6, 8, 9, 10, 11, 12], + [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]] /* -------------------------------------------- */ /* -------------------------------------------- */ @@ -67,8 +64,8 @@ export class Hero6Actor extends Actor { } } computeDicesValue() { - this.system.biodata.presenceattack = Hero6Utility.getDerivatedDiceFormulas(this.system.characteristics.pre.value ) - this.system.characteristics.str.strdice = Hero6Utility.getDerivatedDiceFormulas(this.system.characteristics.str.value ) + this.system.biodata.presenceattack = Hero6Utility.getDerivatedDiceFormulas(this.system.characteristics.pre.value) + this.system.characteristics.str.strdice = Hero6Utility.getDerivatedDiceFormulas(this.system.characteristics.str.value) } /* -------------------------------------------- */ prepareDerivedData() { @@ -275,13 +272,13 @@ export class Hero6Actor extends Actor { return 0; } getManeuvers() { - let maneuvers = { + let maneuvers = { general: this.items.filter(item => item.type == "maneuver" && item.system.maneuvertype == "general"), offensive: this.items.filter(item => item.type == "maneuver" && item.system.maneuvertype == "offensive"), defensive: this.items.filter(item => item.type == "maneuver" && item.system.maneuvertype == "defensive") } - Hero6Utility.sortArrayObjectsByName(maneuvers.general) - Hero6Utility.sortArrayObjectsByName(maneuvers.offensive) + Hero6Utility.sortArrayObjectsByName(maneuvers.general) + Hero6Utility.sortArrayObjectsByName(maneuvers.offensive) Hero6Utility.sortArrayObjectsByName(maneuvers.defensive) return maneuvers } @@ -292,32 +289,32 @@ export class Hero6Actor extends Actor { } getEquipments() { let list = this.items.filter(item => item.type == "equipment" && item.system.subtype == "equipment"); - Hero6Utility.sortArrayObjectsByName(list) + Hero6Utility.sortArrayObjectsByName(list) return list } getWeapons() { let list = this.items.filter(item => item.type == "equipment" && item.system.subtype == "weapon"); - Hero6Utility.sortArrayObjectsByName(list) + Hero6Utility.sortArrayObjectsByName(list) return list } getArmors() { let list = this.items.filter(item => item.type == "equipment" && item.system.subtype == "armor"); - Hero6Utility.sortArrayObjectsByName(list) + Hero6Utility.sortArrayObjectsByName(list) return list } getShields() { let list = this.items.filter(item => item.type == "equipment" && item.system.subtype == "shield"); - Hero6Utility.sortArrayObjectsByName(list) + Hero6Utility.sortArrayObjectsByName(list) return list } getEquipmentsMoneys() { let list = duplicate(this.items.filter(item => item.type == "equipment" && (item.system.subtype == "equipment" || item.system.subtype == "money")) || []) - Hero6Utility.sortArrayObjectsByName(list) + Hero6Utility.sortArrayObjectsByName(list) return list } getEquipmentsOnly() { let list = duplicate(this.items.filter(item => item.type == "equipment" && item.system.subtype == "equipment") || []) - Hero6Utility.sortArrayObjectsByName(list) + Hero6Utility.sortArrayObjectsByName(list) return list } @@ -412,13 +409,23 @@ export class Hero6Actor extends Actor { await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity } } + /* -------------------------------------------- */ - getInitiativeScore(combatId, combatantId) { - if (this.type == 'character') { - this.rollMR(true, combatId, combatantId) - } - console.log("Init required !!!!") - return -1; + hasPhase( segmentNumber) { + let index = Math.min( Math.max(this.system.characteristics.spd.value, 1), 12) // Security bounds + let phases = __speed2Segments[index] + return phases.includes(segmentNumber) + } + /* -------------------------------------------- */ + getSegments() { + let index = Math.min( Math.max(this.system.characteristics.spd.value, 1), 12) // Security bounds + return __speed2Segments[index] + } + /* -------------------------------------------- */ + getBaseInit() { + let r = new Roll("1d6").roll({async: false}) + let base = this.system.characteristics.dex.value + (r.total / 10) + return base } /* -------------------------------------------- */ diff --git a/modules/hero6-combat.js b/modules/hero6-combat.js index 5874f1a..2c076aa 100644 --- a/modules/hero6-combat.js +++ b/modules/hero6-combat.js @@ -1,21 +1,141 @@ import { Hero6Utility } from "./hero6-utility.js"; /* -------------------------------------------- */ -export class Hero6Combat extends Combat { - +export class Hero6CombatTracker extends CombatTracker { + /* -------------------------------------------- */ - async rollInitiative(ids, formula = undefined, messageOptions = {} ) { + static get defaultOptions() { + var path = "systems/fvtt-hero-system-6/templates/apps/combat-tracker.hbs"; + return foundry.utils.mergeObject(super.defaultOptions, { + template: path, + }); + } +} + +/* -------------------------------------------- */ +export class Hero6Combat extends Combat { + + /* -------------------------------------------- */ + static init() { + Hooks.on("getCombatTrackerEntryContext", (html, options) => { Hero6Combat.pushMenuOptions(html, options); }); + } + /* -------------------------------------------- */ + static pushMenuOptions(html, options) { + let newOpt + for (let i = 0; i < options.length; i++) { + let option = options[i]; + if (option.name == 'COMBAT.CombatantReroll') { // Replace ! + option.name = "Hold action"; + option.condition = true; + option.icon = ''; + option.callback = target => { + Hero6Combat.holdAction(target.data('combatant-id')); + } + newOpt = duplicate(option) + } + } + newOpt.name = "Abort action" + newOpt.callback = target => { + Hero6Combat.abortAction(target.data('combatant-id')); + } + options.push( newOpt) + } + + /* -------------------------------------------- */ + static holdAction(combatantId) { + console.log("Combatant HOLD : ", combatantId) + const combatant = game.combat.combatants.get(combatantId) + combatant.setFlag("world", "hero6-hold-action", true) + combatant.update({name: combatant.name + " (H)"}) + console.log("HOLD", combatant) + } + /* -------------------------------------------- */ + static abortAction(html, combatantId) { + console.log("Combatant ABORT : ", combatantId); + const combatant = game.combat.combatants.get(combatantId); + combatant.setFlag("world", "hero6-abort-action", true) + combatant.update({name: combatant.name + " (A)"}) + console.log("ABORT", combatant) + } + + /* -------------------------------------------- */ + constructor(data, context) { + super(data, context); + + this.turnNumber = 1; + this.segmentNumber = 12; + } + + /* -------------------------------------------- */ + async computeInitiative(c) { + let id = c._id || c.id + if (c.actor.hasPhase(this.segmentNumber)) { + let baseInit = c.actor ? c.actor.getBaseInit() : - 1; + await this.updateEmbeddedDocuments("Combatant", [{ _id: id, initiative: baseInit }]); + } else { + await this.updateEmbeddedDocuments("Combatant", [{ _id: id, initiative: -1, visible: false, active: false }]); + } + console.log("Combatant", c) + } + /* -------------------------------------------- */ + async rollInitiative(ids, formula = undefined, messageOptions = {}) { ids = typeof ids === "string" ? [ids] : ids; for (let cId = 0; cId < ids.length; cId++) { - const c = this.combatants.get(ids[cId]); - let id = c._id || c.id; - let initBonus = c.actor ? c.actor.getInitiativeScore( this.id, id ) : -1; - await this.updateEmbeddedDocuments("Combatant", [ { _id: id, initiative: initBonus } ]); + const c = this.combatants.get(ids[cId]) + await this.computeInitiative(c) } return this; } + /* -------------------------------------------- */ + nextRound() { + let turn = this.turn === null ? null : 0; // Preserve the fact that it's no-one's turn currently. + if (this.settings.skipDefeated && (turn !== null)) { + turn = this.turns.findIndex(t => !t.isDefeated); + if (turn === -1) { + ui.notifications.warn("COMBAT.NoneRemaining", { localize: true }); + turn = 0; + } + } + let advanceTime = Math.max(this.turns.length - this.turn, 0) * CONFIG.time.turnTime; + advanceTime += CONFIG.time.roundTime; + let nextRound = this.round + 1; + + let turnData = this.getFlag("world", "hero6-turn-data") + if (!turnData) { + turnData = { turnNumber: 1, segmentNumber: 12 } + this.setFlag("world", "hero6-turn-data", turnData) + } + turnData = duplicate(turnData) + turnData.segmentNumber -= 1 + if (turnData.segmentNumber <= 0) { + turnData.segmentNumber = 12 + turnData.turnNumber++ + } + this.setFlag("world", "hero6-turn-data", turnData) + this.turnNumber = turnData.turnNumber; + this.segmentNumber = turnData.segmentNumber; + + // Re-compute init of actors + for (let c of this.combatants) { + this.computeInitiative(c) + } + + // Update the document, passing data through a hook first + const updateData = { round: nextRound, turn, segmentNumber: turnData.segmentNumber, turnNumber: turnData.turnNumber }; + const updateOptions = { advanceTime, direction: 1 }; + Hooks.callAll("combatRound", this, updateData, updateOptions); + return this.update(updateData, updateOptions); + } + + + /* -------------------------------------------- */ + async _onCreateEmbeddedDocuments(type, documents, result, options, userId) { + console.log(">>>>", documents) + super._onCreateEmbeddedDocuments(type, documents, result, options, userId) + } + /* -------------------------------------------- */ _onUpdate(changed, options, userId) { } diff --git a/modules/hero6-config.js b/modules/hero6-config.js index 7ba5b9b..421a617 100644 --- a/modules/hero6-config.js +++ b/modules/hero6-config.js @@ -48,5 +48,11 @@ export const Hero6_CONFIG = { powerSenseAffecting: { "none": "None", "senseaffecting": "Sense-Affecting", + }, + powerEffectRoll: { + "standard": "Standard", + "normal": "Normal", + "killing": "Killing", + "countbody": "Killing (Count BODY)" } } \ No newline at end of file diff --git a/modules/hero6-main.js b/modules/hero6-main.js index bd49680..2bfa2ad 100644 --- a/modules/hero6-main.js +++ b/modules/hero6-main.js @@ -13,7 +13,7 @@ import { Hero6ItemSheet } from "./hero6-item-sheet.js"; import { Hero6ActorSheet } from "./hero6-actor-sheet.js"; import { Hero6NPCSheet } from "./hero6-npc-sheet.js"; import { Hero6Utility } from "./hero6-utility.js"; -import { Hero6Combat } from "./hero6-combat.js"; +import { Hero6Combat, Hero6CombatTracker } from "./hero6-combat.js"; import { Hero6Item } from "./hero6-item.js"; import { Hero6Hotbar } from "./hero6-hotbar.js" import { Hero6Commands } from "./hero6-commands.js" @@ -41,7 +41,7 @@ Hooks.once("init", async function () { // Set an initiative formula for the system CONFIG.Combat.initiative = { formula: "1d6", - decimals: 1 + decimals: 3 }; /* -------------------------------------------- */ @@ -54,6 +54,8 @@ Hooks.once("init", async function () { CONFIG.Combat.documentClass = Hero6Combat CONFIG.Actor.documentClass = Hero6Actor CONFIG.Item.documentClass = Hero6Item + CONFIG.ui.combat = Hero6CombatTracker; + /* -------------------------------------------- */ // Register sheet application classes @@ -65,6 +67,7 @@ Hooks.once("init", async function () { Items.registerSheet("fvtt-hero-system-6", Hero6ItemSheet, { makeDefault: true }); Hero6Utility.init() + Hero6Combat.init() }); /* -------------------------------------------- */ diff --git a/modules/hero6-utility.js b/modules/hero6-utility.js index 8bcaa2d..99ba541 100644 --- a/modules/hero6-utility.js +++ b/modules/hero6-utility.js @@ -179,7 +179,8 @@ export class Hero6Utility { 'systems/fvtt-hero-system-6/templates/partials/partial-power-equipment-cost.hbs', 'systems/fvtt-hero-system-6/templates/partials/partial-item-hasroll.hbs', 'systems/fvtt-hero-system-6/templates/partials/partial-actor-equipment.hbs', - 'systems/fvtt-hero-system-6/templates/partials/partial-actor-equipment-section.hbs' + 'systems/fvtt-hero-system-6/templates/partials/partial-actor-equipment-section.hbs', + 'systems/fvtt-hero-system-6/templates/partials/partial-power-maneuver-effect.hbs' ] return loadTemplates(templatePaths); } diff --git a/system.json b/system.json index 0555c58..7444a90 100644 --- a/system.json +++ b/system.json @@ -91,7 +91,7 @@ "styles": [ "styles/simple.css" ], - "version": "10.0.33", + "version": "10.0.34", "compatibility": { "minimum": "10", "verified": "10", @@ -99,7 +99,7 @@ }, "title": "Hero System v6 for FoundrtVTT (Official)", "manifest": "https://www.uberwald.me/gitea/uberwald/fvtt-hero-system-6/raw/branch/main/system.json", - "download": "https://www.uberwald.me/gitea/uberwald/fvtt-hero-system-6/archive/fvtt-hero-system-6-v10.0.33.zip", + "download": "https://www.uberwald.me/gitea/uberwald/fvtt-hero-system-6/archive/fvtt-hero-system-6-v10.0.34.zip", "url": "https://www.uberwald.me/gitea/uberwald/", "background": "images/ui/hro6_welcome_page.webp", "id": "fvtt-hero-system-6" diff --git a/template.json b/template.json index 28d5d06..28b7955 100644 --- a/template.json +++ b/template.json @@ -346,6 +346,9 @@ "hasroll": false, "roll": 0, "computebody": false, + "haseffectroll": false, + "effectroll": "standard", + "effectrollformula": "", "items": {} } }, diff --git a/templates/apps/combat-tracker.hbs b/templates/apps/combat-tracker.hbs new file mode 100644 index 0000000..3f7e69e --- /dev/null +++ b/templates/apps/combat-tracker.hbs @@ -0,0 +1,127 @@ +
+
+ {{#if user.isGM}} + + {{/if}} + +
+ {{#if user.isGM}} + + + + + + + {{/if}} + + {{#if combatCount}} + {{#if combat.round}} +

Turn {{combat.turnNumber}} Segment {{combat.segmentNumber}}

+ {{else}} +

{{localize 'COMBAT.NotStarted'}}

+ {{/if}} + {{else}} +

{{localize "COMBAT.None"}}

+ {{/if}} + + {{#if user.isGM}} + + + + + + + + + + {{/if}} +
+
+ +
    + {{#each turns}} + {{#if (ne this.initiative "-1")}} +
  1. + {{this.name}} +
    +

    {{this.name}} {{log this}} {{#if this.holdAction}}(H){{/if}}

    +
    + {{#if ../user.isGM}} + + + + + + + {{/if}} + {{#if this.canPing}} + + + + {{/if}} +
    + {{#each this.effects}} + + {{/each}} +
    +
    +
    + + {{#if this.hasResource}} +
    + {{this.resource}} +
    + {{/if}} + +
    + {{#if this.hasRolled}} + {{this.initiative}} + {{else if this.owner}} + + {{/if}} +
    +
  2. + {{/if}} + {{/each}} +
+ + +
diff --git a/templates/items/item-maneuver-sheet.hbs b/templates/items/item-maneuver-sheet.hbs index f1e65e5..02d7e2b 100644 --- a/templates/items/item-maneuver-sheet.hbs +++ b/templates/items/item-maneuver-sheet.hbs @@ -27,6 +27,8 @@ + {{> systems/fvtt-hero-system-6/templates/partials/partial-power-maneuver-effect.hbs}} +
  • @@ -46,6 +48,7 @@
  • + {{> systems/fvtt-hero-system-6/templates/partials/partial-item-cost.hbs}} diff --git a/templates/items/item-power-sheet.hbs b/templates/items/item-power-sheet.hbs index d53025a..09bc8e6 100644 --- a/templates/items/item-power-sheet.hbs +++ b/templates/items/item-power-sheet.hbs @@ -18,6 +18,8 @@