-
Notifications
You must be signed in to change notification settings - Fork 262
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
Add unique light sources on tokens, and macros for manipulating them. #4356
Add unique light sources on tokens, and macros for manipulating them. #4356
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't look much at how macros work so I can't say much about TokenLightFunctions but it looks good.
For the other stuff I added some comments.
src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java
Outdated
Show resolved
Hide resolved
src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java
Outdated
Show resolved
Hide resolved
eeedfaf
to
f156b59
Compare
This involved reworking `CampaignPropertiesDialog` and `LightSourceCreator` as the only places the needed to mutate `LightSource`. New static factories make it clearer which kind of light is being created while enforcing nullability that doesn't agree with the "primary" constructor. The light's ID must be provided by the caller now instead of `LightSource` deciding it for itself (avoids the need for `setId()`). `Light` was already in practice immutable, but now both `Light` and `LightSource` are `final` classes with `final` fields. `LightSource.lightList` remains a mutable list for serialization purposes, but is guarded to prevent any mutations.
The protocol for adding and removing lights was streamlined: since the light source GUID is the only piece needed for this functionality, that is now all that gets passed around via protobuf. Previously the entire light source definition was passed around for these operations. `AttachedLightSource` is now more encapsulated so that resolving `LightSource` from `GUID` is easier. Callers no longer need to bother with grabbing the `GUID` from the `AttachedLightSource`, nor do they have to know where to use it. Instead they can just ask the `AttachedLightSource` to do the lookup, providing only the `Campaign` for context. This will enable future changes to how light sources can looked up. `AttachedLightSource` has also been made immutable by finalizing the class and `lightSourceId` field.
f156b59
to
411ea18
Compare
It is now possible for tokens to carry light source definitions, much like campaigns can. These new light sources are called "unique lights" as they are only available to the token that defines them. If any unique light source are defined on a token, they can be accessed like any other lights via the right-click menu, under the special category "Unique". In order to support lookup of such light sources, `AttachedLightSource#resolve()` now also accepts a `Token` for context, in addition to the `Campaign`. The token is checked first for a matching light source, then the campaign is checked as a fallback. For networking, the `TokenPropertyValueDto.lightSource` field was added back in because creating a unique light source now requires the complete definition to be passed. This commit does not add any way for a user to add any light sources to token. That will come later.
These functions have been updated: - `getLights([category, [delim, [token, [map]]]])` - `setLight(category, name, state, [token, [map]])` - `hasLightSource(category, name, [token, [map]])` They now support a special category `"$unique"` to identify unique light sources. Meanwhile the `"*"` category wildcard will also find unique light sources in addition to campaign light sources.
The following new functions have been added: - `createUniqueLightSource(lightSourceJson, [token, [map]])` to create a new unique light source. - `updateUniqueLightSource(lightSourceJson, [token, [map]])` to modify the unique light source with the matching name. - `deleteUniqueLightSource(name, [token, [map]])` to delete a unique light source by name. - `getUniqueLightSource(name, [token, [map]])` to get a unique light source by name, as a JSON object. - `getUniqueLightSources([token, [map]])` to get all of a token's light sources as a JSON array of JSON objects. - `getUniqueLightSourceNames([token, [map]])` to get the names of all of a token's light sources, as a JSON array. The JSON format for a unique light source look like: ```json { "name": "Torch - 20", "type": "NORMAL", "scale": false, "lights": [ {"shape": "CIRCLE", "range": 20, "lumens": 100}, {"shape": "CIRCLE", "range": 40, "color": "#000000", "lumens": 50} ] } ``` Note that any boolean values are JSON booleans, not 0/1. Light sources have the following fields: - `"name"`: `string`; required. - `"type"`: (`"NORMAL"`|`"AURA"`); defaults to `"NORMAL"`. - `"scale"`: `boolean`; defaults to `false`. - `"lights"` `array`; defauls to `[]`. Each light has these fields: - `"range"`: `number`, required. - `"shape"`: (`"SQUARE"`|`"CIRCLE"`|`"CONE"`|`"HEX"`|`"GRID"`), defaults to `"CIRCLE"`. - `"offset"`: `number`, defaults to `0`. Only permitted if the shape is `"CONE"`. - `"arc"`: `number`, defaults to `0`. Only permitted if the shape is `"CONE"`. - `"gmOnly"`: `boolean`, defaults to `false`. Only permitted if the light source type is `"AURA"`. - `"ownerOnly"`: `boolean`, defaults to `false`. Only permitted if the light source is `"AURA"`. - `"color"`: `string` of the form `"#rrggbb`, defaults to `null`. - `"lumens"`: `number` (integer), defaults to 100.
It's possible for only some lights in an aura light source to be marked as GM-only (or OWNER-only). These were not being shown in the right-click menu despite players being able to see them once equipped. Now these auras will show in the menu, and will only be hidden if the entire auras is GM-only. This also include a refactor to avoid duplicated logic between campaign light sources and unique light sources, while also avoiding some of the complicated looping.
411ea18
to
29e4660
Compare
Resolved the conflicts with the Cover VBL addition. |
Umm.. hello? Can we undo this please? You know, until it works and I can edit tokens and stuff. |
Identify the Bug or Feature request
Implements #331 and so also relates to #3087.
Description of the Change
This changes adds the concept of "unique light sources", which are lights sources defined on tokens rather than in the campaign as a whole. A token with such a unique light source can equipped or unequip it just like any other light sources in its right-click menu.
Macro functions are provided for defining and manipulating unique light sources. No UI is provided at this time for defining the lights (doing so would round out #3087).
Existing macro function changes
The following existing macro functions have been updated:
getLights([category, [delim, [token, [map]]]])
setLight(category, name, state, [token, [map]])
hasLightSource(category, name, [token, [map]])
The
category
parameter of each can now be set to the special"$unique"
category to identify unique light sources. The wildcard"*"
will likewise now include the token's unique light sources. I.e., these function treat the token's unique light sources as though they just belong to a specially named category.New macro functions
The following new macro functions have been added:
createUniqueLightSource(lightSourceJson, [token, [map]])
to create a new unique light source.updateUniqueLightSource(lightSourceJson, [token, [map]])
to modify a unique light source with the matching name.deleteUniqueLightSource(name, [token, [map]])
to delete a unique light source by name.getUniqueLightSource(name, [token, [map]])
to get a unique light source by name, as a JSON object.getUniqueLightSources([token, [map]])
to get all of a token's unique light sources as a JSON array of JSON objects.getUniqueLightSourceNames([token, [map]])
to get the names of all of a token's light sources, as a JSON array.A consistent JSON format is used for light sources in the parameters and return values of these functions, and uses terminology from the Campaign Properties dialog. It looks like this:
The exact fields are:
"name"
:string
; required."type"
: ("NORMAL"
|"AURA"
); defaults to"NORMAL"
."scale"
:boolean
; defaults tofalse
."lights"
array
; defauls to[]
.And for each light:
"range"
:number
, required."shape"
: ("SQUARE"
|"CIRCLE"
|"CONE"
|"HEX"
|"GRID"
), defaults to"CIRCLE"
."offset"
:number
, defaults to0
. Only permitted if the shape is"CONE"
."arc"
:number
, defaults to0
. Only permitted if the shape is"CONE"
."gmOnly"
:boolean
, defaults tofalse
. Only permitted if the light source type is"AURA"
."ownerOnly"
:boolean
, defaults tofalse
. Only permitted if the light source type is"AURA"
."color"
:string
of the form"#rrggbb
, defaults tonull
."lumens"
:number
(integer), defaults to 100.Refactoring
There was some refactoring done as groundwork.
LightSource
is now immutable to avoid worries about cloning them along with tokens. In practice lights weren't been modified anyways - onlyCampaignPropertiesDialog
andLightSourceCreator
used the mutators, and that was only to build the light. Once built, theLightSource
instances were never being modifed.The existing token update messages for light sources (e.g., for attaching to a token) were reduced to passing around the light source ID instead of the entire light source definition. The ID was the only thing ever used, so there was no point serializing everything for one GUID. Now the only time a complete light source is sent in a token update is the new case of creating a unique light source.
AttachedLightSource
has been encapsulated more and made responsible for the common case of looking up light sources by ID. Callers just need to provide the current campaign (and now also the associated token) and theAttachedLightSource
will look itself up and return aLightSource
.Possible Drawbacks
There were some changes to the model. Should be benign, but need to watch for deserialization failures on existing campaigns.
If someone is already using
$unique
as a light source category, it won't be possible to find them with the macro functions now.Documentation Notes
For the existing functions
getLights()
,setLight()
, andhasLightSource()
, the type parameter (which should be called "category") can now be set to"$unique"
to access unique light sources on the token.createUniqueLightSource() function
Trusted function
Creates a new unique light source on a token.
Usage
Parameters
lightSourceJson
- a JSON object defining the new unique light source. See below for details.token
- the name or ID of the token to add the unique light source to. Defaults to the current token.map
- the name or ID of the map on which to findtoken
. Defaults to the current map.The
lightSourceJson
is a JSON object with these fields:"name"
: The user-visible name of the unique light source. A unique light source with the same name must not already exist on the token. Required field."type"
: The type of the unique light source. Either"NORMAL"
or"AURA"
; defaults to"NORMAL"
."scale"
: Whether to add the token footprint (size) to the range, so that the light starts at the token's edge vs the token's center. Eitherjson.true
orjson.false
; defaults tojson.false
."lights"
: A JSON array of lights; defauls to[]
.Each light has this JSON format:
"range"
: The distance that the light extends, in map units. Required field."shape"
: The shape of the boundary of the light. Must be one of"SQUARE"
,"CIRCLE"
,"CONE"
,"HEX"
, or"GRID"
; defaults to"CIRCLE"
."offset"
: (for cones only) The counterclockwise angle (in degrees) to shift the cone relative to the token's facing. Defaults to0
."arc"
: (for cones only) The angle of the cone in degrees. Defaults to0
."gmOnly"
: (for auras only) Whether the aura should only be shown to GMs. Eitherjson.true
orjson.false
; defaults tojson.false
."ownerOnly"
: (for auras only) Whether the aura should only be shown to the tokens owners. Eitherjson.true
orjson.false
; defaults tojson.false
."color"
: The color of the light, in the form"#rrggbb"
; defaults to no color (transparent)."lumens"
: How bright the light should be treated, with negative values indicating darkness. Defaults to 100.Return value
The name of the new unique light source.
Examples
To create a standard D&D torch, but with a blue flame:
And the same example, but using defaults where possible:
updateUniqueLightSource() function
Trusted function
Modifies a unique light source on a token.
Usage
Parameters
lightSourceJson
- a JSON object defining the new unique light source. The format is the same as forcreateUniqueLightSource()
.token
- the name or ID of the token to to find the unique light source on. Defaults to the current token.map
- the name or ID of the map on which to findtoken
. Defaults to the current map.Return value
The name of the modified unique light source.
deleteUniqueLightSource() function
Removes a unique light source from a token.
Note: if you merely want to unequip or turn off the unique light source, but keep it around for later, use
setLight()
instead.Usage
Parameters
name
- the name of the unique light source to delete.token
- the name or ID of the token to delete the unique light source from. Defaults to the current token. This parameter can only be used in a trusted macro.map
- the name or ID of the map on which to findtoken
. Defaults to the current map.getUniqueLightSource() function
Looks up a unique light source by name on a token, returning the
Usage
Parameters
name
- the name of the unique light source to delete.token
- the name or ID of the token to find the unique light source on. Defaults to the current token. This parameter can only be used in a trusted macro.map
- the name or ID of the map on which to findtoken
. Defaults to the current map.Return value
The matching unique light source, as a JSON object. See
createUniqueLightSource()
for the JSON format of a light source.If there is no match unique light source, an empty string is returned.
Examples
This will print
{"name":"Simple Torch","type":"NORMAL","scale":false,"lights":[{"shape":"CIRCLE","range":40.0,"lumens":100}]}
getUniqueLightSources() function
Gets all the unique light sources defined on a token.
Usage
Parameters
token
- the name or ID of the token to get the unique light sources from. Defaults to the current token. This parameter can only be used in a trusted macro.map
- the name or ID of the map on which to findtoken
. Defaults to the current map.** Return value**
A JSON array of all the token's unique light sources. As if
getUniqueLightSource()
was call for each unique light source on the token and the results put in an array.getUniqueLightSourceNames() function
Get the names of all the unique light sources defined on a token.
Usage
Parameters
token
- the name or ID of the token to get the unique light source from. Defaults to the current token. This parameter can only be used in a trusted macro.map
- the name or ID of the map on which to findtoken
. Defaults to the current map.** Return value**
A JSON array of all the names of the token's unique light sources.
Release Notes
This change is