Skip to content

Commit

Permalink
Merge pull request #31 from League-of-Foundry-Developers/json-defaults
Browse files Browse the repository at this point in the history
Updates to simplify and clarify defining data for light sources - added aliases - clarified settings text
  • Loading branch information
lupestro authored Dec 7, 2022
2 parents 6657e01 + 53a966a commit 1a88d1e
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 52 deletions.
25 changes: 18 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@

## Middle Kingdom - v10 branch

### Unreleased - December 6, 2022
- [FEATURE] (Lupestro) Updated README for clarity in creating user settings. (Issue #27 - many participants)
- [FEATURE] (Lupestro) Improved and simplified source JSON syntax with defaults for most fields. (Issue #27 - many participants)
* This change shouldn't invalidate any existing light source JSON files.
- [FEATURE] (Lupestro) Introduced aliases to JSON to give new names to light sources defined elsewhere.
* Using this in a very simple custom JSON will make this module more useful to those who play in a non-English language.
- [FEATURE] (Lupestro) Provided aliases for dnd5e light sources that should provide smoother integration with ddb-importer. (Issues raised by MrPrimate and emmoth)
- [FEATURE] (Lupestro) Improved name and description of options for consuming torches, candles, etc. to avoid confusion. (In response to comments by vgeirnaert)

I'm looking at loading localized language JSON files for the out-of-the-box sources on startup in a future release. This will need the actual (i.e. matching letter-by-letter) light source names used in various games when played in different languages. Contact me if you play with light sources in another language and you're interested in helping out.

### 2.1.4 - December 4, 2022
- [BUGFIX] DnD5e Bullseye Lantern had wrong dim/bright ranges due to copy/paste error.
- [BUGFIX] Bullseye lanterns in all systems that had 53 degree radius, now have 57 degree radius.
- [BUGFIX] (Lupestro) DnD5e Bullseye Lantern had wrong dim/bright ranges due to copy/paste error. (Issue reported by Fragmenri. Thanks!)
- [BUGFIX] (Lupestro) Bullseye lanterns in all systems that had 53 degree radius, now have 57 degree radius.
* 53 degrees is technically correct for a cone (n units wide at center distance n, per rules).
* 57 degrees (1 radian) would be correct for a (spherical) sector of radius n and arc length n.
* Foundry projects light radially, so you get a spherical sector, regardless of the game rules.
Expand All @@ -13,13 +24,13 @@
* You can always reduce it back to 53 degrees with a custom JSON, of course, if you feel strongly about it.

### 2.1.3 - October 8, 2022
- [BUGFIX] Corrected issue (found by vkdolea) where user-supplied sources for new systems weren't processing properly.
- [BUGFIX] Now pulling non-dim/bright light properties for the light source configured in settings from the prototype token.
- [BUGFIX] Fixed the translation files for several languages.
- [BUGFIX] (Lupestro) Corrected issue (found by vkdolea) where user-supplied sources for new systems weren't processing properly.
- [BUGFIX] (Lupestro) Now pulling non-dim/bright light properties for the light source configured in settings from the prototype token. (Issue reported by Jensenator360)
- [BUGFIX] (Lupestro) Fixed the translation files for several languages.

### 2.1.2 - September 14, 2022
- [BUGFIX] Fixed image used for dancing lights
- [BUGFIX] Reset precedence order to user-supplied light sources first, then the source in config settings, then module-provided defaults based on game rules.
- [BUGFIX] (Lupestro) Fixed image used for dancing lights
- [BUGFIX] (Lupestro) Reset precedence order to user-supplied light sources first, then the source in config settings, then module-provided defaults based on game rules.

### 2.1.1 - September 6, 2022
- [BUGFIX] (Lupestro) Fixed issue where unlinked tokens were adjusting inventory on source token.
Expand Down
70 changes: 61 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ This module just sheds light from the location of a player token upon demand bas
Because the light source to use is now user-configurable, we no longer select a light source for you based on fallbacks. As it stands, if you do not explicitly select your light source, it will pick any among the light sources you have equipped, in no particular order.
## Customizing light sources

You can supersede these settings or supply settings for your own light sources for any system with a JSON file, which you can deliver through the "Additional Light Sources" setting.
You can supersede these settings or supply settings for your own light sources for any system with a JSON file, which you can deliver through the "Additional Light Sources" setting. The following shows a fully specified light source and a couple of aliases:
```json
{
"dnd5e": {
"system": "dnd5e",
"topology": "standard",
"quantity" : "quantity",
"aliases": {
"Lantern (Bullseye)": "Bullseye Lantern",
"Lantern (Hooded)": "Hooded Lantern"
}
"sources": {
"Candle": {
"name": "Candle",
Expand All @@ -47,19 +51,67 @@ You can supersede these settings or supply settings for your own light sources f
...
}
```
The key in the top-level hash is the id of the system.
The JSON has one top-level property per system, keyed by the Foundry id of the system. For each system:
* The `system` value, if specified, should always be identical to the key. It can (and probably should) be omitted.
* The `topology` value has two possible values, defaulting to `standard` if omitted.
* `standard` topology: equipment are `Item` objects. Almost all systems are `standard`.
* `gurps` topology: light sources are collected under a property of the actor and require GURPS-specific functions to find it, manipulate how many you've got, etc.
* If you drag items to your character to "gear up", your system is almost certainly `standard`, so omit the setting.
* The `quantity` value tells the topology which property determines how many you have. It is a path to the quantity property within the "system" subtree of the object representing the light source. It defaults to `quantity`.
* For `standard`, it is the path to the field under item.system that contains the quantity. For nearly all systems, this is `quantity`. (For Earthdawn, however, it is `amount`.)
* For `gurps`, it is the path to the field in a nested structure under the actor. It should be set to `amount`.
* Some systems may not count inventory. For these, omit it, and use the default.

* The `sources` property has a sub-property for each light source you are creating or superseding. For each source:
* The key is the name of the light source.
* The `name` value, if specified, should always be identical to the key. It can (and probably should) be omitted.
* The `type` value may be `equipment` or `spell`. If omitted, it defaults to `equipment`.
* The `consumable` value should be `true` or `false`.
* If `true`, when you use the light source, the inventory count goes down. The light source becomes unavailable when the quantity drops to zero.
* If `false`, the quantity in the inventory is ignored, but you still need the light source in your inventory to use it.
* If not specified, it defaults to `false`.
* If your system doesn't count inventory, make sure all of your light sources either don't specify this field or set it to `false`.
* If you find "counting your candles" a complete distraction from your game, you can turn this feature off using the "GM Uses Inventory" and "Player Uses Inventory" settings.
* The `states` specifies how many states the light source toggles through. This allows for sources like hooded lanterns to toggle "high - low - off" if desired. It can be omitted and the number of states will be one more than the number of objects you supply to the `light` array.
* The `light` value is an array of objects that specify the light properties for each "on" light state. It has no default.
* Values for the "off" state are taken from the settings for the actor's prototype token.
* If you supply a single object rather than an array of objects, the module will treat it as an array of one item, (a light source with a single "on" state, the most common condition), and `states` will default to 2.

* The `aliases` property lets you specify alternate names for existing sources. Each entry has a key and a value:
* The key is the new name of the source .
* The value is the name (as a string) of the existing source being duplicated.
* The source you duplicate with a new name can either be one of the predefined sources or one of the sources from the same user settings.

### Determining your system's id, topology, and quantity

There are a few simple checks that can be done in a system to determine what to use for its id, topology and quantity values.

First, create an actor and give it the light source in question, either by creating one fresh or grabbing it from a compendium. Verify that the character sheet shows that the actor owns the gear. Adjust the quantity to some easily recognizable number, like 3, 5, 7, 13, something obvious.

Suppose we create an actor named "Ebenezer" and give him a "Candle". Heck, we'll give him 16 of them.

Then, go into the browser console and type the following commands, adjusted for what you called your actor and light source:
```
game.system.id
game.actors.getName("Ebenezer").items.getName("Candle")
```
The system ID shown is the key name you should use for your system. The second value either gives you an object you can examine for the item or it gives you an error. If this returns a value, your topology is almost certainly standard.

![Console Output for Above Commands](./design/finding-quantity.png)

In the item, expand the "system" property, and look for a sub-property whose name has something to do with quantity. If it has the same value you supplied, that clinches it. That's your quantity field. This is usually a simple property name like "quantity", "count", or "amount", but it may be nested in a structure, like Star Wars FFG's "quantity.value". That's the value to use for your system's "quantity" property in the JSON.

### Defining new topologies - something you almost never need to do

We support two `topology` values at present:
* In `standard` topology, equipment are `Item` objects with a property to track how many you have. Set `quantity` to the property name used by the system to count the inventory of the item.
* In `gurps` topology, light sources are collected under a property of the actor and require GURPS-specific functions to manipulate inventory. Set `quantity` to `amount`.
If you can't find your light sources among the actor's items, you may have stumbled across a system that will need its own topology defined, much as I discovered with GURPS. This requires a little code, but only a very little code. Define the new topology in topology.js. Supply an additional Topology class with implementations for the exported methods `actorHasLightSource`, `getImage`, `getInventory`, `setInventory`, and `decrementInventory`. PRs are gratefully accepted. :)

A source with `"consumable": false`, like a lamp or a cantrip, doesn't deduct one from inventory with every use. A source with `"consumable": true`, like a torch or a candle, will have a quantity that is reduced as you use them and will become unavailable when the quantity drops to zero. If you find tracking inventory a complete distraction from your game, you can turn this feature off using the "GM Uses Inventory" and "Player Uses Inventory" settings.
For example, the "Star Wars FFG" system embeds the quantity in the name of the item, so the item name goes from "Torches (3)" to "Torches (2)" as you consume them. Aside from this naming quirk, it is completely standard. A copy/paste of the standard topology that replaced its internal `_findMatchingItem` method with a function, perhaps regular-expression-based, that weeded out the stuff in the parentheses would do the trick.

`states` specifies how many states the light source toggles through. For on/off sources, `states` is 2. For high/low/off sources, like hooded lanterns, states is 3.
Even without adding a topology, you can still use Torch with any new system - by doing nothing! If you don't define any JSON for the system, each user will get a single "self" light they can toggle, with dim/bright values the GM sets up in the game configuration. All of its other properties will come from the actor's prototype token. The downside of this is that the bright/dim game settings will apply identically to all players, regardless of what they're packing.

The `light` array contains a hash of light properties for each "on" state, so most things will have one hash between the brackets, but a hooded lantern will have two. The hash can contain any combination of valid token light properties, providing the same degree of configurability as the token's Light tab.
### Determining the real names of animation and coloration types

To use this module with game systems using different topologies will require code but very little code. Define the new topology in topology.js. PRs gratefully accepted. :)
The easiest way I've found to do this is to set an actor's prototype token to use the types you're interested in and then export the actor from the sidebar. When you examine the file it creates, you should be able to see what it called that property on the prototype token's "light" value. That will tell you what string to use for its value in the Torch user settings to get the desired effect.

## Changelog - now in [separate file](./CHANGELOG.md)

Expand Down
Binary file added design/finding-quantity.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
"torch.playerTorches.hint": "Allow players to toggle their own torches.",
"torch.brightRadius.hint": "How many grid units of bright light to emit.",
"torch.dimRadius.hint": "How many grid units of dim light to emit.",
"torch.gmUsesInventory.name": "GM Uses Inventory",
"torch.gmUsesInventory.hint": "If set, when the GM toggles a torch, it will use the actors inventory.",
"torch.gmUsesInventory.name": "GM Consumes Inventory",
"torch.gmUsesInventory.hint": "If set, when the GM toggles a torch, it will reduce the actor's inventory.",
"torch.gmInventoryItemName.name": "Inventory item name to use",
"torch.gmInventoryItemName.hint": "Name of the inventory item to use if \"GM Uses Inventory\" is set.",
"torch.gmInventoryItemName.hint": "Name of the inventory item to consume if \"GM Consumes Inventory\" is set.",
"torch.turnOffAllLights": "Force bright and dim to configured \"off\" values.",
"torch.holdCtrlOnClick": "Hold <span class=\"key\">Ctrl</span> on Click",
"torch.dancingLightVision.name": "Give Dancing Lights Vision.",
"torch.dancingLightVision.hint": "When enabled, each dancing light has vision so can be used to reveal areas of the map players can't actually see.",
"torch.playerUsesInventory.name": "Player Uses Inventory",
"torch.playerUsesInventory.hint": "If set, when the player toggles a torch, it will use the actors inventory.",
"torch.playerUsesInventory.name": "Player Consumes Inventory",
"torch.playerUsesInventory.hint": "If set, when the player toggles a torch, it will reduce the actor's inventory.",
"torch.gameLightSources.name": "Additional Light Sources",
"torch.gameLightSources.hint": "JSON file containing additional light sources to use beyond those supplied",
"torch.brightRadius.name": "Bright Light Radius",
Expand Down
66 changes: 64 additions & 2 deletions library.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,72 @@ export default class SourceLibrary {
this.library = library;
}

static applyFieldDefaults(library, reference) {
for (const system in library) {
const ref = reference && reference.hasOwnProperty(system) ? reference[system] : null;
if (!library[system].system) {
library[system].system = system;
}
if (!library[system].topology) {
library[system].topology = ref ? ref.topology : "standard";
}
if (!library[system].quantity) {
library[system].quantity = ref ? ref.quantity : "quantity";
}
if (!library[system].sources) {
library[system].sources = {};
}
const sources = library[system].sources;
for (const source in sources) {
const refsrc = ref ? ref.sources[source] : null;
if (!sources[source].name) {
sources[source].name = source;
}
if (!sources[source].type) {
sources[source].type = refsrc? refsrc.type : "equipment";
}
if (sources[source].consumable === undefined) {
sources[source].consumable = refsrc ? refsrc.consumable : false;
}
if (sources[source].consumable === 'true') {
sources[source].consumable = true;
}
if (sources[source].consumable === 'false') {
sources[source].consumable = false;
}
if (sources[source].light && (sources[source].light.constructor !== Array)) {
sources[source].light = [sources[source].light];
}
if (sources[source].light && (sources[source].light.constructor === Array) && !sources[source].states) {
sources[source].states = sources[source].light.length + 1;
}
}
// Now apply any aliases found to reference the same source
if (library[system].aliases) {
if (!library[system].sources) {
library[system].sources = {};
}
let sources = library[system].sources;
let aliases = library[system].aliases;
for (const alias in aliases) {
let aliasref = aliases[alias];
let aliasedSource = sources[aliasref] ? sources[aliasref] : ref && ref.sources[aliasref] ? ref.sources[aliasref] : null;
if (aliasedSource) {
sources[alias] = Object.assign({}, aliasedSource, {name: alias});
}
}
}
}
}

static async load(systemId, selfBright, selfDim, selfItem, userLibrary, protoLight) {
// The common library is cached - to update it, you must reload the game.
if (!SourceLibrary.commonLibrary) {
SourceLibrary.commonLibrary = await fetch('/modules/torch/sources.json')
.then( response => { return response.json(); });
}
this.applyFieldDefaults(SourceLibrary.commonLibrary);
}

let defaultLight = Object.assign({}, protoLight);
defaultLight.bright = selfBright;
defaultLight.dim = selfDim;
Expand All @@ -27,7 +87,9 @@ export default class SourceLibrary {
// The user library reloads every time you open the HUD to permit cut and try.
let mergedLibrary = userLibrary ? await fetch(userLibrary)
.then( response => { return response.json(); })
.then( userData => { return mergeLibraries (userData, SourceLibrary.commonLibrary, configuredLight); })
.then( userData => {
this.applyFieldDefaults(userData, SourceLibrary.commonLibrary);
return mergeLibraries (userData, SourceLibrary.commonLibrary, configuredLight); })
.catch(reason => {
console.warn("Failed loading user library: ", reason);
}) : mergeLibraries (
Expand Down
4 changes: 4 additions & 0 deletions sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"system": "dnd5e",
"topology": "standard",
"quantity" : "quantity",
"aliases": {
"Lantern, Hooded": "Hooded Lantern",
"Lantern, Bullseye": "Bullseye Lantern"
},
"sources": {
"Candle": {
"name": "Candle",
Expand Down
Loading

0 comments on commit 1a88d1e

Please sign in to comment.