From 6c3ce5310129c773b4db817c686b51d19fc25147 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Mon, 12 May 2025 22:55:13 +0100 Subject: [PATCH] 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. --- .gitattributes | 1 + .gitignore | 1 + LICENSE | 373 +++++++++++++++++++++++++++++ README.md | 73 ++++++ assets/default-borders/base.webp | Bin 0 -> 4118 bytes assets/default-borders/played.webp | Bin 0 -> 3572 bytes lang/en.json | 18 ++ lang/fr.json | 18 ++ module.json | 76 ++++++ packs/macros/000005.ldb | Bin 0 -> 8239 bytes packs/macros/000013.ldb | Bin 0 -> 4256 bytes packs/macros/000069.log | 0 packs/macros/CURRENT | 1 + packs/macros/LOCK | 0 packs/macros/LOG | 3 + packs/macros/LOG.old | 7 + packs/macros/MANIFEST-000068 | Bin 0 -> 192 bytes scripts/mills_fabula.mjs | 1 + scripts/token_combat_border.mjs | 234 ++++++++++++++++++ 19 files changed, 806 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 assets/default-borders/base.webp create mode 100644 assets/default-borders/played.webp create mode 100644 lang/en.json create mode 100644 lang/fr.json create mode 100644 module.json create mode 100644 packs/macros/000005.ldb create mode 100644 packs/macros/000013.ldb create mode 100644 packs/macros/000069.log create mode 100644 packs/macros/CURRENT create mode 100644 packs/macros/LOCK create mode 100644 packs/macros/LOG create mode 100644 packs/macros/LOG.old create mode 100644 packs/macros/MANIFEST-000068 create mode 100644 scripts/mills_fabula.mjs create mode 100644 scripts/token_combat_border.mjs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4ee706a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +packs/** binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d1767f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a06a054 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" +means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software. + +1.2. "Contributor Version" +means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" +means Covered Software of a particular Contributor. + +1.4. "Covered Software" +means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof. + +1.5. "Incompatible With Secondary Licenses" +means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" +means any form of the work other than Source Code Form. + +1.7. "Larger Work" +means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software. + +1.8. "License" +means this document. + +1.9. "Licensable" +means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License. + +1.10. "Modifications" +means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor +means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version. + +1.12. "Secondary License" +means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses. + +1.13. "Source Code Form" +means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") +means an individual or a legal entity exercising rights under this +License. For legal entities, "You" includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) +Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its +Contributions, either on an unmodified basis, with Modifications, or +as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer +for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; +or + +(b) for infringements caused by: (i) Your and any other third party's +modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor +Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of +its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code +Form, as described in Section 3.1, and You must inform recipients of +the Executable Form how they can obtain a copy of such Source Code +Form by reasonable means in a timely manner, at a charge no more +than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this +License, or sublicense it under different terms, provided that the +license for the Executable Form does not attempt to limit or alter +the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + +This Source Code Form is "Incompatible With Secondary Licenses", as +defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md new file mode 100644 index 0000000..064c09a --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# The Mill's Fabula - v0.1.0 + +This little [FoundryVTT](https://foundryvtt.com/) module is a collection of compendiums and functionalities +to power our Fabula Ultima campaigns. + +# Compatibility + +This is *only* compatible with Foundry v12 currently. + +# Installation + +Currently, this can only be installed via manifest file, using the following URL : +``` +https://git.tfk-astrodome.net/trotFunky/TheMillsFabula/raw/branch/release/module.json +``` + +# Features + +This module is an aggregate of different things that are useful for us or that the GM wanted +to play with, so they don't necessarily connect with each other. + +They are described below in their own secions. + +## Combat border + +This part of the module adds a dynamic border around player tokens, which changes during combat. + +The border is always visible, but after a player has taken their turn it will switch to a different border, +highlighting the tokens that have played for this combat turn. + +The module supports going back and forth in the combat rounds, as well as going back in the turn order. +(Though because of limitations of the Fabula Ultima system, does not allow going *forward* in the turn order.) + +## Settings + +- An image to use for the default/idle border +- An image to use for the took turn/played/inactive border + +## Limitations + +There are currently two main issues that need to be fixed : +1. The tokens will not be updated when the GM is not on the scene. + - Indeed, the Fabula Ultima system seems to prevent players from receiving combat events, + so the GM is the only one that can receive them and update the tokens. That means they need to be in the active + combat scene for the changes to take effect. + - However, given that the module only uses the current state, if the GM comes back to the scene and a combat event is + triggered, the token borders will become correct. +2. The token borders will be incorrect when switching to a new scene + - It is unclear why, but apparently switching to another scene is very different from loading a new scene, + and the combat encounter of the scene is not available when tokens are created. This means that the combat status, + and thus the border type, cannot be properly determined on scene switch. + - This can be fixed by receiving a combat update, either from the players or the GM, on the scene. + +# Compendia + +- The only thing in the compendium pack is a macro automating the tinkerer's alchemy potions, + allowing to choose how many PIs to spend, how to allocate the results, previews and a final chat message + with the results. + +# Dependencies + +Modules: + - socketlib + +System: + - Fabula Ultima + +# License + +The code present in this repository is licensed under the Mozilla Public License 2.0. + +Assets that we created in this repository are licensed under +[CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), unless otherwise specified. diff --git a/assets/default-borders/base.webp b/assets/default-borders/base.webp new file mode 100644 index 0000000000000000000000000000000000000000..a2ceb4136d0c4345287fb8e981d05b57319f443d GIT binary patch literal 4118 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 literal 0 HcmV?d00001 diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..c438434 --- /dev/null +++ b/lang/en.json @@ -0,0 +1,18 @@ +{ + "the-mills-fabula.title": "Fabula Moletrina", + "the-mills-fabula.description": "A module implementing our idiosyncratic ideas for the Mill's campaigns.", + "MF": { + "Border": { + "Settings": { + "BaseBorder": { + "Name": "Base border", + "Hint": "The image to use as a border for player tokens at all times." + }, + "PlayedBorder": { + "Name": "Turn taken border", + "Hint": "The image to use as a border for player tokens that have taken their turn." + } + } + } + } +} diff --git a/lang/fr.json b/lang/fr.json new file mode 100644 index 0000000..dd036a2 --- /dev/null +++ b/lang/fr.json @@ -0,0 +1,18 @@ +{ + "the-mills-fabula.title": "Fabula Moletrina", + "the-mills-fabula.description": "Un module implémentant nos idiosyncrasies et idées pour les campagnes du Moulin.", + "MF": { + "Border": { + "Settings": { + "BaseBorder": { + "Name": "Bordure par défaut", + "Hint": "L'image à utiliser comme bordure pour les jetons joueurs, présente en permanence." + }, + "PlayedBorder": { + "Name": "Bordure de tour joué", + "Hint": "L'image à utiliser comme bordure pour les jetons joueurs qui ont joué leur tour." + } + } + } + } +} diff --git a/module.json b/module.json new file mode 100644 index 0000000..5bc184b --- /dev/null +++ b/module.json @@ -0,0 +1,76 @@ +{ + "id": "the-mills-fabula", + "title": "Fabula Moletrina", + "description": "A module implementing our idiosyncratic ideas for the Mill's campaigns.", + "version": "0.1.0", + "compatibility": { + "minimum": "12", + "verified": "12", + "maximum": "12" + }, + "languages": [ + { + "lang": "en", + "name": "English", + "path": "lang/en.json" + }, + { + "lang": "fr", + "name": "Français", + "path": "lang/fr.json" + } + ], + "relationships": { + "systems": [ + { + "id": "projectfu", + "type": "system", + "compatibility": { + "verified": [ + "2.4.10", + "3.0.2" + ] + } + } + ], + "modules": [ + { + "id": "socketlib", + "type": "module", + "compatibility": { + "verified": "1.1.2" + } + } + ] + }, + "authors": [ + { + "name": "trotFunky Sparks", + "flags": {} + } + ], + "esmodules": [ + "scripts/token_combat_border.mjs" + ], + "socket": true, + "packs": [ + { + "name": "Macros", + "label": "The Mill's Macros", + "path": "packs/macros", + "type": "Macro", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "system": "projectfu", + "flags": {} + } + ], + + "license": "LICENSE", + "readme": "README.md", + "url": "https://git.tfk-astrodome.net/trotFunky/TheMillsFabula/src/branch/release", + "manifest": "https://git.tfk-astrodome.net/trotFunky/TheMillsFabula/raw/branch/release/module.json", + "download": "https://git.tfk-astrodome.net/trotFunky/TheMillsFabula/archive/v0.1.0.zip" +} diff --git a/packs/macros/000005.ldb b/packs/macros/000005.ldb new file mode 100644 index 0000000000000000000000000000000000000000..c9f27f1ac53c96bf6e04ec49205ee47ced08b5ab GIT binary patch literal 8239 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 PpS{01a!X@z$4~zQCn0M& literal 0 HcmV?d00001 diff --git a/packs/macros/000013.ldb b/packs/macros/000013.ldb new file mode 100644 index 0000000000000000000000000000000000000000..ff1a6200c8624f40944c19adc063703101875128 GIT binary patch literal 4256 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 Y?ktJEKw|6CpMH+~{`8%#mAyauf0wk@761SM literal 0 HcmV?d00001 diff --git a/packs/macros/000069.log b/packs/macros/000069.log new file mode 100644 index 0000000..e69de29 diff --git a/packs/macros/CURRENT b/packs/macros/CURRENT new file mode 100644 index 0000000..284d53f --- /dev/null +++ b/packs/macros/CURRENT @@ -0,0 +1 @@ +MANIFEST-000068 diff --git a/packs/macros/LOCK b/packs/macros/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/packs/macros/LOG b/packs/macros/LOG new file mode 100644 index 0000000..a1ba921 --- /dev/null +++ b/packs/macros/LOG @@ -0,0 +1,3 @@ +2025/05/09-18:11:40.073652 7253c0dec6c0 Recovering log #66 +2025/05/09-18:11:40.092496 7253c0dec6c0 Delete type=3 #64 +2025/05/09-18:11:40.092522 7253c0dec6c0 Delete type=0 #66 diff --git a/packs/macros/LOG.old b/packs/macros/LOG.old new file mode 100644 index 0000000..53e9bd4 --- /dev/null +++ b/packs/macros/LOG.old @@ -0,0 +1,7 @@ +2025/05/09-17:35:50.464775 7253c1dee6c0 Recovering log #62 +2025/05/09-17:35:50.483617 7253c1dee6c0 Delete type=3 #60 +2025/05/09-17:35:50.483640 7253c1dee6c0 Delete type=0 #62 +2025/05/09-18:11:37.487216 7253ae46b6c0 Level-0 table #67: started +2025/05/09-18:11:37.487234 7253ae46b6c0 Level-0 table #67: 0 bytes OK +2025/05/09-18:11:37.498365 7253ae46b6c0 Delete type=0 #65 +2025/05/09-18:11:37.548398 7253ae46b6c0 Manual compaction at level-0 from '!macros!AXWjXCoFJKa5KmSU' @ 72057594037927935 : 1 .. '!macros!AXWjXCoFJKa5KmSU' @ 0 : 0; will stop at (end) diff --git a/packs/macros/MANIFEST-000068 b/packs/macros/MANIFEST-000068 new file mode 100644 index 0000000000000000000000000000000000000000..a1413e839fffc99e12bd8714b7147e76a3403d13 GIT binary patch literal 192 zcmX?MDm#4@10$nUPHI_dPD+xVQ)NkNd1i5{bAE0?Vo_pAei1t(?*c^y#oWZ?qWoe- z$B6K(2~`3OE)!0NjGL-fhb^SVqNcmQxzjK pnlTk=Nj`aD=DDF+mLbU|nHh|X5CsMMi@Z1(7@1r-8JOKzSOG4aGX4Mn literal 0 HcmV?d00001 diff --git a/scripts/mills_fabula.mjs b/scripts/mills_fabula.mjs new file mode 100644 index 0000000..537f73a --- /dev/null +++ b/scripts/mills_fabula.mjs @@ -0,0 +1 @@ +export const id = "the-mills-fabula"; diff --git a/scripts/token_combat_border.mjs b/scripts/token_combat_border.mjs new file mode 100644 index 0000000..1ffa328 --- /dev/null +++ b/scripts/token_combat_border.mjs @@ -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()