Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token visibility 1343 #1367

Merged
merged 6 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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