Initial commit - v0.1.0
This initial version adds a custom compendium with a macro for the tinkerer, and a dynamic token border to track player turn status. It has a couple of known major bugs but is needed for an upcoming game, with fixes hopefully later. It is only compatible with Foundry v12 at this time.
This commit is contained in:
commit
6c3ce53101
19 changed files with 806 additions and 0 deletions
1
scripts/mills_fabula.mjs
Normal file
1
scripts/mills_fabula.mjs
Normal file
|
@ -0,0 +1 @@
|
|||
export const id = "the-mills-fabula";
|
234
scripts/token_combat_border.mjs
Normal file
234
scripts/token_combat_border.mjs
Normal file
|
@ -0,0 +1,234 @@
|
|||
import * as MillsFabula from "./mills_fabula.mjs";
|
||||
import { FUHooks } from "/systems/projectfu/module/hooks.mjs"
|
||||
import { FU } from "/systems/projectfu/module/helpers/config.mjs"
|
||||
|
||||
// NOTES
|
||||
// Tokens are working PIXI.js objects, can .addChild() a sprite
|
||||
// Get Token : game.scenes.active.collections.tokens.contents[0]._object
|
||||
// Paths are relative to either the core data, or the base data. No need to do anything.
|
||||
// https://pixijs.download/v4.8.0/docs/PIXI.Sprite.html
|
||||
|
||||
/**
|
||||
* Socket to use to execute things on clients, may be shared by other scripts of the module.
|
||||
* @type {SocketlibSocket}
|
||||
*/
|
||||
let socket;
|
||||
const SocketMessages = Object.freeze({
|
||||
UpdateBorder: "update-border",
|
||||
})
|
||||
|
||||
const BorderSettings = Object.freeze({
|
||||
BaseBorderPath: "baseBorderPath",
|
||||
PlayedBorderPath: "playedBorderPath",
|
||||
})
|
||||
|
||||
/**
|
||||
* The texture that will be used to create the base player token borders
|
||||
* Will be created after fetching a path from the settings.
|
||||
* @type {PIXI.BaseTexture}
|
||||
*/
|
||||
let base_border;
|
||||
/**
|
||||
* The texture that will be used to create the borders for player tokens that have taken their turns
|
||||
* Will be created after fetching a path from the settings.
|
||||
* @type {PIXI.BaseTexture}
|
||||
*/
|
||||
let played_border;
|
||||
|
||||
// Enum for the border types.
|
||||
const BorderTypes = Object.freeze({
|
||||
Active: "active",
|
||||
Played: "played",
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {BorderTypes} type The kind of border to create, deciding which texture to use
|
||||
* @param {number} width Width of the final sprite on the canvas, in pixels
|
||||
* @param {number} height Height of the final sprite on the canvas, in pixels
|
||||
* @param {boolean} visible Should the new sprite be visible now ?
|
||||
* @returns {PIXI.Sprite} A new sprite object corresponding to the border requested
|
||||
*/
|
||||
function create_new_border(type, width, height, visible) {
|
||||
let border_texture;
|
||||
switch (type) {
|
||||
case BorderTypes.Active:
|
||||
border_texture = base_border;
|
||||
break;
|
||||
case BorderTypes.Played:
|
||||
border_texture = played_border;
|
||||
break;
|
||||
default:
|
||||
throw new TypeError("Invalid BorderTypes Enum value: " + type);
|
||||
}
|
||||
/** @type {PIXI.Sprite} */
|
||||
let border = PIXI.Sprite.from(border_texture);
|
||||
border.interactive = false;
|
||||
border.width = width;
|
||||
border.height = height;
|
||||
border.visible = visible;
|
||||
|
||||
/**
|
||||
* Custom member, to be able to toggle border visibility dynamically
|
||||
* @type {BorderTypes}
|
||||
*/
|
||||
border.borderType = type;
|
||||
|
||||
return border;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the borders of a token by ID, as we cannot pass the Token object via SocketLib (most objects being recursive).
|
||||
* @param {string} token_id The ID of the token to update
|
||||
* @param {boolean} played Status of the token to update
|
||||
*/
|
||||
function token_set_border_visibility(token_id, played) {
|
||||
let token = canvas.scene?.tokens.get(token_id).object;
|
||||
// Get the borders we manually added in the `drawToken` hook, if it has any.
|
||||
let borders = token.children.filter((child) => child.borderType);
|
||||
// console.debug("↓↓↓ set_border_visibility ↓↓↓");
|
||||
// console.debug(token)
|
||||
for (let border of borders) {
|
||||
if (border.borderType === BorderTypes.Active) {
|
||||
border.visible = !played;
|
||||
} else {
|
||||
border.visible = played;
|
||||
}
|
||||
}
|
||||
// console.debug("↑↑↑ set_border_visibility ↑↑↑");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a token has played in a specific round, or the current combat round if it exists for the token.
|
||||
* @param {Token} token The actual token object to check combat status
|
||||
* @param {number} [round] The round to check the token's status in, will check the token's combat current turn otherwise
|
||||
* @returns {boolean} If the token has played in the current or requested round
|
||||
*/
|
||||
function token_has_played(token, round = -1) {
|
||||
// console.debug("↓↓↓ token_has_played ↓↓↓");
|
||||
// console.debug(token.inCombat)
|
||||
if (!token.inCombat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* As we check beforehand, combat should always exist.
|
||||
* @type {Combat}
|
||||
*/
|
||||
let combat = token.combatant.combat;
|
||||
let round_turns_taken = combat?.flags.projectfu.CombatantsTurnTaken[round < 0 ? combat?.round : round]
|
||||
// console.debug(`Testing played for round : ${combat?.round}`)
|
||||
console.debug(round_turns_taken)
|
||||
|
||||
// No token has taken a turn, or all turns were reverted.
|
||||
if (!round_turns_taken || round_turns_taken.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// console.debug("↑↑↑ token_has_played ↑↑↑");
|
||||
// Token might have played, let's search now.
|
||||
return round_turns_taken.includes(token.combatant.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by turn and round hooks, so the borders can be updated when going backwards as well as forwards.
|
||||
* @param {Combat} combat The combat in which the token is active
|
||||
* @param {{round: number, turn: number}} data The turn data for this update
|
||||
*/
|
||||
function combat_hook_update_token_borders(combat, data) {
|
||||
// Turns is the array of *tokens having turns in this encounter*.
|
||||
// console.debug("↓↓↓ combat_hook_update_token_borders ↓↓↓");
|
||||
// console.debug(combat)
|
||||
// console.debug(data)
|
||||
for (let combatant of combat.turns) {
|
||||
// The combat passed by the hook still is on the previous round or turn, which would make the check
|
||||
// use the previous round rather than the new one. Use the round contained in data instead, which is always
|
||||
// the new one.
|
||||
socket.executeForEveryone(SocketMessages.UpdateBorder, combatant.token.id,
|
||||
token_has_played(combatant?.token.object, data.round)).then();
|
||||
}
|
||||
// console.debug("↑↑↑ combat_hook_update_token_borders ↑↑↑");
|
||||
}
|
||||
|
||||
function combat_border_main() {
|
||||
Hooks.once("init", () => {
|
||||
game.settings.register(MillsFabula.id, BorderSettings.BaseBorderPath, {
|
||||
name: game.i18n.localize('MF.Border.Settings.BaseBorder.Name'),
|
||||
hint: game.i18n.localize('MF.Border.Settings.BaseBorder.Hint'),
|
||||
type: String,
|
||||
config: true,
|
||||
scope: 'world',
|
||||
requiresReload: true,
|
||||
filePicker: 'image',
|
||||
default: 'modules/the-mills-fabula/assets/default-borders/base.webp'
|
||||
});
|
||||
|
||||
game.settings.register(MillsFabula.id, BorderSettings.PlayedBorderPath, {
|
||||
name: game.i18n.localize('MF.Border.Settings.PlayedBorder.Name'),
|
||||
hint: game.i18n.localize('MF.Border.Settings.PlayedBorder.Hint'),
|
||||
type: String,
|
||||
config: true,
|
||||
scope: 'world',
|
||||
requiresReload: true,
|
||||
filePicker: 'image',
|
||||
default: 'modules/the-mills-fabula/assets/default-borders/played.webp'
|
||||
})
|
||||
|
||||
// Create the border textures based on the image chose in the settings.
|
||||
base_border = PIXI.BaseTexture.from(game.settings.get(MillsFabula.id, BorderSettings.BaseBorderPath));
|
||||
played_border = PIXI.BaseTexture.from(game.settings.get(MillsFabula.id, BorderSettings.PlayedBorderPath));
|
||||
});
|
||||
|
||||
Hooks.once("socketlib.ready", () => {
|
||||
socket = socketlib.registerModule(MillsFabula.id);
|
||||
socket.register(SocketMessages.UpdateBorder, token_set_border_visibility);
|
||||
})
|
||||
|
||||
// Create the borders from defined textures and add them to the tokens when they are first created on canvas.
|
||||
// FIXME: Does not work on scene change !
|
||||
Hooks.on("drawToken", (drawn_token) => {
|
||||
// Only apply the borders to player tokens
|
||||
if (drawn_token.actor?.type !== "character")
|
||||
return;
|
||||
|
||||
let has_played_turn = token_has_played(drawn_token);
|
||||
// console.log(drawn_token)
|
||||
// console.log(`Is in combat ? ${drawn_token.inCombat}`)
|
||||
// console.log(`Combatant ? ${drawn_token.combatant}`)
|
||||
// console.log(`Combat ?`)
|
||||
// console.log(drawn_token.combatant.combat);
|
||||
// console.log(game.combats.combats)
|
||||
|
||||
const token_size = drawn_token.getSize();
|
||||
drawn_token.addChild(create_new_border(BorderTypes.Active, token_size.width, token_size.height, !has_played_turn));
|
||||
drawn_token.addChild(create_new_border(BorderTypes.Played, token_size.width, token_size.height, has_played_turn));
|
||||
|
||||
// console.log("============")
|
||||
})
|
||||
|
||||
|
||||
Hooks.on("ready", () => {
|
||||
// Players cannot run the combat hooks used here, which trigger for the GM no matter what.
|
||||
// So register them for the GM only, who will execute the updates for players via SocketLib.
|
||||
if (!game.user?.isGM) {
|
||||
return;
|
||||
}
|
||||
|
||||
// console.debug("↓↓↓ Registering ↓↓↓")
|
||||
Hooks.on("combatTurn", combat_hook_update_token_borders);
|
||||
Hooks.on("combatRound", combat_hook_update_token_borders);
|
||||
// console.debug("↑↑↑ Registering ↑↑↑")
|
||||
|
||||
// No Foundry hook on end of combat, so use Fabula Ultima's.
|
||||
// FIXME: Does not work when the GM is in another scene, as they don't receive the event.
|
||||
Hooks.on(FUHooks.COMBAT_EVENT, (combat_event) => {
|
||||
if (combat_event.type === FU.combatEvent.endOfCombat) {
|
||||
for (let combatant of combat_event.combatants) {
|
||||
// End of combat, clear all tokens.
|
||||
socket.executeForEveryone(SocketMessages.UpdateBorder, combatant.token?.id, false).then();
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
combat_border_main()
|
Loading…
Add table
Add a link
Reference in a new issue