Skip to content

Commit

Permalink
Handle hidden actors on combat tracker
Browse files Browse the repository at this point in the history
Handle hidden actors.  Hidden actors have their initiative slots shown but names and images hidden from players.

GM view:

![image](https://github.com/StarWarsFoundryVTT/StarWarsFFG/assets/4709746/9d4f28fd-ead8-40cc-af91-a8767c7cb5f3)

Player view of the same combat at the same time:

![image](https://github.com/StarWarsFoundryVTT/StarWarsFFG/assets/4709746/6b0882a1-bc9b-429b-aef7-413c575237df)
  • Loading branch information
wrycu authored Feb 22, 2024
1 parent 8a728bc commit d4d1373
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 15 deletions.
Binary file added images/combat/hidden.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@
"SWFFG.Combats.Actors.Friendly": "Friendly Actors",
"SWFFG.Combats.Actors.Enemy": "Enemy Actors",
"SWFFG.Combats.Actors.Neutral": "Neutral Actors",
"SWFFG.Combats.Actors.Hidden": "Hidden Actor",
"SWFFG.Settings.UseGenericSlots.Name": "Use Generic Slots",
"SWFFG.Settings.UseGenericSlots.Hint": "Replaces named slots in the combat tracker with generic, claimable slots",
"SWFFG.Settings.showMinionCount.Name": "Show Minion Count",
Expand Down
132 changes: 123 additions & 9 deletions modules/combat-ffg.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,40 @@ export class CombatFFG extends Combat {
return [];
}

/**
* Check if a given combatant has any claims in the current combat round
* @param combatantId - STRING - the combatant ID (NOT token ID, NOT actor ID)
* @returns {boolean|string} - false if no claims, otherwise the round of the claimant
*/
hasClaims(combatantId) {
const claims = this.getClaims(this.round);
if (!claims) {
return false;
}
if (Object.values(claims).includes(combatantId)) {
return Object.keys(claims).find(key => claims[key] === combatantId);
} else {
return false;
}
}

async handleCombatantRemoval(combatant, options, combatantId) {
CONFIG.logger.debug(`Handling combatant removal of ${combatant?.name}`);
const claims = this.hasClaims(combatant.id);
if (!claims) {
CONFIG.logger.debug("No claimed slots found, nothing to do!");
return;
}
CONFIG.logger.debug("Claimed slots found, unclaiming...");
await this.unclaimSlot(this.round, claims);
CONFIG.logger.debug("...Done!");
}

async handleCombatantAddition(combatant, context, options, combatantI) {
// there may be cases when this is needed, but for now, we don't need to do anything
// (leaving as a placeholder until we know for sure)
}

/**
* Claim a slot for a given combatant
* @param round - INT - the round
Expand Down Expand Up @@ -359,12 +393,18 @@ export class CombatTrackerFFG extends CombatTracker {

/** @override */
async getData(options) {
const data = await super.getData(options);
const combat = this.viewed;
if (!combat) {
return await super.getData(options);
}

if (!combat) {
return data;
// create a copy of the turn data, then set hidden to false so non-GMs can view all turns, then set the data back
const tempData = foundry.utils.deepClone(this.viewed.turns);
for (const turn of this.viewed.turns) {
turn.hidden = false;
}
const data = await super.getData(options);
this.viewed.turns = tempData;

const initiatives = combat.combatants.reduce((accumulator, combatant) => {
accumulator[combatant.id] = [{activationId: -1, initiative: combatant.initiative}];
Expand All @@ -390,6 +430,7 @@ export class CombatTrackerFFG extends CombatTracker {
let claim = {};

if (combat.started && claimant) {
CONFIG.logger.debug(`slot ${index} has been claimed by ${claimant.name}`);
let defeated = claimant.isDefeated;

const effects = new Set();
Expand All @@ -410,19 +451,41 @@ export class CombatTrackerFFG extends CombatTracker {
}
}

// propagate this to the overall turn data, so we can gray out claimed slots
data.turns.find(i => i.id === claimant.id).claimed = true;
const hidden = this._getTokenHidden(claimant.tokenId);

if (!hidden && turn.css) {
turn.css = turn?.css?.replace('hidden', '');
}

claim = {
id: claimant.id,
name: claimant.name,
img: claimant.img ?? CONST.DEFAULT_TOKEN,
owner: claimant.owner,
defeated,
hidden: claimant.hidden,
hidden: hidden,
canPing: claimant.sceneId === canvas.scene?.id && game.user.hasPermission("PING_CANVAS"),
effects,
};
turn.hidden = hidden;
turn.tokenId = claimant.tokenId;
} else {
CONFIG.logger.debug(`slot ${index} is unclaimed`);
combatant.hidden = this._getTokenHidden(combatant.tokenId);
turn.tokenId = combatant.tokenId;
// sync the turn state to the token state
turn.hidden = combatant.hidden;
}

if (combatant.css === undefined) {
combatant.css = "";
}
if (combat.turn === index) {
combatant.active = true;
combatant.css += " active";
} else {
combatant.active = false;
combatant.css = "";
}
const disposition = combatant.token?.disposition ?? combatant.actor?.token.disposition ?? 0;
let slotType;
Expand Down Expand Up @@ -464,6 +527,30 @@ export class CombatTrackerFFG extends CombatTracker {
Neutral: data.turns.filter(i => combat.combatants.get(i.id).token.disposition === CONST.TOKEN_DISPOSITIONS.NEUTRAL),
};

// update visibility state for each token
for (const turn of turnData['Friendly']) {
turn.hidden = this._getTokenHidden(turn.tokenId);
turn.claimed = combat.hasClaims(combat.combatants.find(i => i.tokenId === turn.tokenId).id);
}

for (const turn of turnData['Enemy']) {
const combatant = combat.combatants.get(turn.id);
const claimantId = combat.hasClaims(combat.combatants.find(i => i.tokenId === turn.tokenId).id);
const claimant = claimantId ? (combat.combatants.get(claimantId)) : undefined;
if (combat.started && claimant) {
turn.hidden = this._getTokenHidden(claimant.tokenId);
turn.claimed = combat.hasClaims(combat.combatants.find(i => i.tokenId === claimant.tokenId).id );
} else {
turn.hidden = this._getTokenHidden(combatant.tokenId);
turn.claimed = combat.hasClaims(combat.combatants.find(i => i.tokenId === combatant.tokenId).id);
}
}

for (const turn of turnData['Neutral']) {
turn.hidden = this._getTokenHidden(turn.tokenId);
turn.claimed = combat.hasClaims(combat.combatants.find(i => i.tokenId === turn.tokenId).id);
}

return {
...data,
turns,
Expand All @@ -487,7 +574,7 @@ export class CombatTrackerFFG extends CombatTracker {
return rawAlive.length + defeated.filter(i => i.id && claimed.includes(i.id)).length;
}

/* @override */
/** @override */
_getEntryContextOptions() {
const baseEntries = super._getEntryContextOptions();

Expand All @@ -506,7 +593,7 @@ export class CombatTrackerFFG extends CombatTracker {
return [...baseEntries, unClaimSlot];
}

/* @override */
/** @override */
async _onCombatantHoverIn(event) {
event.preventDefault();

Expand All @@ -516,7 +603,7 @@ export class CombatTrackerFFG extends CombatTracker {
return super._onCombatantHoverIn(event);
}

/* @override */
/** @override */
async _onCombatantMouseDown(event) {
event.preventDefault();

Expand All @@ -525,4 +612,31 @@ export class CombatTrackerFFG extends CombatTracker {
}
return super._onCombatantMouseDown(event);
}

/**
* Determine the hidden status of a token, since the state in the combat tracker seems to lag
* @param tokenId
* @returns {boolean}
* @private
*/
_getTokenHidden(tokenId) {
let hidden = true;
const scene = game.scenes.get(this.viewed.scene.id);
const token = scene.tokens.get(tokenId);
if (token) {
hidden = token.hidden;
}
CONFIG.logger.debug(`looking up hidden state for ${token?.name}/${tokenId} on scene ${scene.id}: ${hidden}`);
return hidden;
}
}

/**
* Force the combat tracker to re-render, which picks up "hidden" state changes of tokens
*/
export function updateCombatTracker() {
// Used to force the tracker to re-render based on updated visibility state
if (game.combat && game.settings.get("starwarsffg", "useGenericSlots")) {
ui.combat.render(true);
}
}
15 changes: 14 additions & 1 deletion modules/swffg-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// Import Modules
import { FFG } from "./swffg-config.js";
import { ActorFFG } from "./actors/actor-ffg.js";
import {CombatFFG, CombatTrackerFFG} from "./combat-ffg.js";
import {CombatFFG, CombatTrackerFFG, updateCombatTracker} from "./combat-ffg.js";
import { ItemFFG } from "./items/item-ffg.js";
import { ItemSheetFFG } from "./items/item-sheet-ffg.js";
import { ItemSheetFFGV2 } from "./items/item-sheet-ffg-v2.js";
Expand Down Expand Up @@ -314,6 +314,19 @@ Hooks.once("init", async function () {
}
}
});

Hooks.on("updateToken", async (tokenDocument, options, diffData, tokenId) => {
if (Object.keys(options).includes('hidden')) {
updateCombatTracker();
}
});

Hooks.on("preCreateCombatant", async (combatant, context, options, combatantId) => {
await game.combat.handleCombatantAddition(combatant, context, options, combatantId);
});
Hooks.on("preDeleteCombatant", async (combatant, options, unknownId) => {
await game.combat.handleCombatantRemoval(combatant, options, unknownId);
});
}

await gameSkillsList();
Expand Down
50 changes: 45 additions & 5 deletions templates/dialogs/combat-tracker.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,15 @@ <h3 class="encounter-title noborder">{{localize "COMBAT.None"}}</h3>
</div>
<div class="encounter-controls flexrow flex-center{{#if hasCombat}} combat{{/if}}">
{{#each turnData.Friendly }}
<img class="token-image actor-header friendly{{#if claimed}} slot-claimed{{/if}}" src="{{img}}" alt="{{name}}"/>
{{#if ../user.isGM }}
<img class="combatant token-image actor-header friendly{{#if claimed}} slot-claimed{{/if}}" src="{{img}}" alt="{{name}}" data-combatant-id="{{id}}"/>
{{else}}
{{#if hidden}}
<img class="combatant token-image actor-header enemy{{#if claimed}} slot-claimed{{/if}}" src="systems/starwarsffg/images/combat/hidden.png" alt="{{localize "SWFFG.Combats.Actors.Hidden"}}"/>
{{else}}
<img class="combatant token-image actor-header friendly{{#if claimed}} slot-claimed{{/if}}" src="{{img}}" alt="{{name}}" data-combatant-id="{{id}}"/>
{{/if}}
{{/if}}
{{/each}}
</div>
{{/if}}
Expand All @@ -77,7 +85,15 @@ <h3 class="encounter-title noborder">{{localize "COMBAT.None"}}</h3>
</div>
<div class="encounter-controls flexrow flex-center{{#if hasCombat}} combat{{/if}}">
{{#each turnData.Enemy }}
<img class="combatant token-image actor-header enemy{{#if claimed}} slot-claimed{{/if}}" src="{{img}}" alt="{{name}}" data-combatant-id="{{id}}"/>
{{#if ../user.isGM }}
<img class="combatant token-image actor-header enemy{{#if claimed}} slot-claimed{{/if}}" src="{{img}}" alt="{{name}}" data-combatant-id="{{id}}"/>
{{else}}
{{#if hidden}}
<img class="combatant token-image actor-header enemy{{#if claimed}} slot-claimed{{/if}}" src="systems/starwarsffg/images/combat/hidden.png" alt="{{localize "SWFFG.Combats.Actors.Hidden"}}"/>
{{else}}
<img class="combatant token-image actor-header enemy{{#if claimed}} slot-claimed{{/if}}" src="{{img}}" alt="{{name}}" data-combatant-id="{{id}}"/>
{{/if}}
{{/if}}
{{/each}}
</div>
{{/if}}
Expand All @@ -87,16 +103,40 @@ <h3 class="encounter-title noborder">{{localize "COMBAT.None"}}</h3>
</div>
<div class="encounter-controls flexrow flex-center{{#if hasCombat}} combat{{/if}}">
{{#each turnData.Neutral }}
<img class="combatant token-image actor-header neutral{{#if claimed}} slot-claimed{{/if}}" src="{{img}}" alt="{{name}}" data-combatant-id="{{id}}"/>
{{#if ../user.isGM }}
<img class="combatant token-image actor-header neutral{{#if claimed}} slot-claimed{{/if}}" src="{{img}}" alt="{{name}}" data-combatant-id="{{id}}"/>
{{else}}
{{#if hidden}}
<img class="combatant token-image actor-header neutral{{#if claimed}} slot-claimed{{/if}}" src="systems/starwarsffg/images/combat/hidden.png" alt="{{localize "SWFFG.Combats.Actors.Hidden"}}"/>
{{else}}
<img class="combatant token-image actor-header neutral{{#if claimed}} slot-claimed{{/if}}" src="{{img}}" alt="{{name}}" data-combatant-id="{{id}}"/>
{{/if}}
{{/if}}
{{/each}}
</div>
{{/if}}
{{#each turns}}
{{#if claimed}}
<li class="combatant actor directory-item flexrow {{css}} slot-{{slotType}} claimed" data-combatant-id="{{id}}" data-slot-index="{{@index}}" data-activation-id="{{activationId}}">
<img class="token-image" data-src="{{img}}" alt="{{name}}"/>
<li class="combatant actor directory-item flexrow {{css}}{{#if ../user.isGM}}{{#if hidden}} hidden{{/if}}{{/if}} slot-{{slotType}} claimed" data-combatant-id="{{id}}" data-slot-index="{{@index}}" data-activation-id="{{activationId}}">
{{#if ../user.isGM}}
<img class="token-image" data-src="{{img}}" alt="{{name}}"/>
{{else}}
{{#if hidden }}
<img class="token-image" data-src="systems/starwarsffg/images/combat/hidden.png" alt="{{localize "SWFFG.Combats.Actors.Hidden"}}"/>
{{else}}
<img class="token-image" data-src="{{img}}" alt="{{name}}"/>
{{/if}}
{{/if}}
<div class="token-name flexcol">
{{#if ../user.isGM}}
<h4>{{name}}</h4>
{{else}}
{{#if hidden }}
<h4>{{localize "SWFFG.Combats.Actors.Hidden"}}</h4>
{{else}}
<h4>{{name}}</h4>
{{/if}}
{{/if}}
<div class="combatant-controls flexrow">
{{#if ../user.isGM}}
{{#if (not hasRolled)}}
Expand Down

0 comments on commit d4d1373

Please sign in to comment.