Skip to content

Commit

Permalink
Merge pull request #4449 from bubblobill/LineLight
Browse files Browse the repository at this point in the history
All this in order to introduce ShapeType.LINE
  • Loading branch information
cwisniew authored Nov 24, 2023
2 parents a7a0061 + a40f4a0 commit 1768ead
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ private static Light parseLightJson(JsonObject lightDef, LightSource.Type lightS
? ShapeType.valueOf(lightDef.get("shape").getAsString())
: ShapeType.CIRCLE;
// Cones permit the fields arc and offset, but no other shape accepts them.
if (shape != ShapeType.CONE) {
if (shape != ShapeType.CONE && shape != ShapeType.BEAM) {
if (lightDef.has("offset")) {
throw new ParserException(
I18N.getText("Facing offset provided but the shape is not a cone"));
Expand Down Expand Up @@ -465,6 +465,11 @@ private static JsonObject lightToJson(LightSource source, Light light) {
final var lightDef = new JsonObject();
lightDef.addProperty("shape", light.getShape().toString());

if (light.getShape() == ShapeType.BEAM) {
lightDef.addProperty("offset", light.getFacingOffset());
lightDef.addProperty("arc", light.getArcAngle());
}

if (light.getShape() == ShapeType.CONE) {
lightDef.addProperty("offset", light.getFacingOffset());
lightDef.addProperty("arc", light.getArcAngle());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,24 @@ private String updateSightPanel(Map<String, SightType> sightTypeMap) {
.append(StringUtil.formatDecimal(sight.getDistance()))
.append(' ');
break;
case BEAM:
builder.append("beam ");
if (sight.getArc() != 0) {
builder.append("arc=").append(StringUtil.formatDecimal(sight.getArc())).append(' ');
} else {
builder.append("arc=4").append(StringUtil.formatDecimal(sight.getArc())).append(' ');
}
if (sight.getOffset() != 0)
builder
.append("offset=")
.append(StringUtil.formatDecimal(sight.getOffset()))
.append(' ');
if (sight.getDistance() != 0)
builder
.append("distance=")
.append(StringUtil.formatDecimal(sight.getDistance()))
.append(' ');
break;
case CONE:
builder.append("cone ");
if (sight.getArc() != 0)
Expand Down Expand Up @@ -373,6 +391,17 @@ private String updateLightPanel(Map<String, Map<GUID, LightSource>> lightSources
// TODO: Make this a preference
shape = light.getShape().toString().toLowerCase();
break;
case BEAM:
{
lastArc = light.getArcAngle();
shape = "beam arc=" + StringUtil.formatDecimal(lastArc);
if (light.getFacingOffset() != 0)
builder
.append(" offset=")
.append(StringUtil.formatDecimal(light.getFacingOffset()))
.append(' ');
}
break;
case CONE:
// if (light.getArcAngle() != 0 && light.getArcAngle() != 90 && light.getArcAngle()
// != lastArc)
Expand Down Expand Up @@ -504,6 +533,7 @@ private void commitSightMap(final String text) {
assert arg.length() > 0; // The split() uses "one or more spaces", removing empty strings
try {
shape = ShapeType.valueOf(arg.toUpperCase());
arc = shape == ShapeType.BEAM ? 4 : arc;
continue;
} catch (IllegalArgumentException iae) {
// Expected when not defining a shape
Expand Down Expand Up @@ -714,6 +744,7 @@ private Map<String, Map<GUID, LightSource>> commitLightMap(
// Shape designation ?
try {
shape = ShapeType.valueOf(arg.toUpperCase());
arc = shape == ShapeType.BEAM ? 4 : arc;
continue;
} catch (IllegalArgumentException iae) {
// Expected when not defining a shape
Expand Down Expand Up @@ -748,7 +779,10 @@ private Map<String, Map<GUID, LightSource>> commitLightMap(
if ("arc".equalsIgnoreCase(key)) {
try {
arc = StringUtil.parseDecimal(value);
shape = ShapeType.CONE; // If the user specifies an arc, force the shape to CONE
shape =
(shape != ShapeType.CONE && shape != ShapeType.BEAM)
? ShapeType.CONE
: shape; // If the user specifies an arc, force the shape to CONE
} catch (ParseException pe) {
errlog.add(
I18N.getText("msg.error.mtprops.light.arc", reader.getLineNumber(), value));
Expand Down
32 changes: 21 additions & 11 deletions src/main/java/net/rptools/maptool/model/Grid.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,8 @@
package net.rptools.maptool.model;

import com.google.common.base.Stopwatch;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashSet;
Expand Down Expand Up @@ -352,7 +344,7 @@ public void setSize(int size) {
/**
* Called by SightType and Light class to return a vision area based upon a specified distance
*
* @param shape CIRCLE, GRID, SQUARE or CONE
* @param shape CIRCLE, GRID, SQUARE, CONE or LINE
* @param token Used to position the shape and to provide footprint
* @param range As specified in the vision or light definition
* @param arcAngle Only used by cone
Expand Down Expand Up @@ -404,6 +396,24 @@ public Area getShapedArea(
new Rectangle2D.Double(
-visionRange, -visionRange, visionRange * 2, visionRange * 2));
break;
case BEAM:
if (token.getFacing() == null) {
token.setFacing(0);
}
Shape lineShape =
new Rectangle2D.Double(
0,
getSize() / -2d * Math.sin(Math.toRadians(arcAngle / 2d)),
visionRange,
getSize() * Math.sin(Math.toRadians(arcAngle / 2d)));
Shape visibleShape = new GeneralPath(lineShape);

visibleArea =
new Area(
AffineTransform.getRotateInstance(
Math.toRadians(offsetAngle) - Math.toRadians(token.getFacing()))
.createTransformedShape(visibleShape));
break;
case CONE:
if (token.getFacing() == null) {
token.setFacing(0);
Expand Down
34 changes: 28 additions & 6 deletions src/main/java/net/rptools/maptool/model/IsometricGrid.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@
*/
package net.rptools.maptool.model;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
Expand Down Expand Up @@ -349,6 +344,33 @@ public Area getShapedArea(
int x[] = {0, (int) visionRange * 2, 0, (int) -visionRange * 2};
int y[] = {(int) -visionRange, 0, (int) visionRange, 0};
visibleArea = new Area(new Polygon(x, y, 4));
break;
case BEAM:
if (token.getFacing() == null) {
token.setFacing(0);
}
Shape visibleShape =
new Rectangle2D.Double(
0,
getSize() / -2d * Math.sin(Math.toRadians(arcAngle / 2.0)),
visionRange,
getSize() * Math.sin(Math.toRadians(arcAngle / 2.0)));

// new angle, corrected for isometric view
double theta = Math.toRadians(offsetAngle) + Math.toRadians(token.getFacing());
Point2D angleVector = new Point2D.Double(Math.cos(theta), Math.sin(theta));
AffineTransform at = new AffineTransform();
at.rotate(Math.PI / 4);
at.scale(1.0, 0.5);
at.deltaTransform(angleVector, angleVector);

theta = -Math.atan2(angleVector.getY(), angleVector.getX());

visibleArea =
new Area(
AffineTransform.getRotateInstance(theta + Math.toRadians(45))
.createTransformedShape(visibleShape));

break;
case CONE:
if (token.getFacing() == null) {
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/net/rptools/maptool/model/ShapeType.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
package net.rptools.maptool.model;

public enum ShapeType {
SQUARE,
CIRCLE,
CONE,
GRID,
HEX,
GRID
BEAM,
SQUARE
}
1 change: 1 addition & 0 deletions src/main/proto/data_transfer_objects.proto
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ enum ShapeTypeDto {
CONE = 2;
HEX = 3;
GRID = 4;
BEAM = 5;
}

message LightDto {
Expand Down
77 changes: 64 additions & 13 deletions src/main/resources/net/rptools/maptool/language/i18n.properties
Original file line number Diff line number Diff line change
Expand Up @@ -845,12 +845,13 @@ CampaignPropertiesDialog.label.sight = <html>\
defaults for all values (e.g. Normal vision).</p>\
<p><b> Options:</b></p>\
<dl>\
<dt>shape</dt><dd>shape may be circle, square, hex, grid or cone</dd>\
<dt>distance=y</dt><dd>Distance is the range at which this vision type ends. Can be a decimal value. Ex: distance=10</dd>\
<dt>shape</dt><dd>shape may be circle, square, hex, grid, cone or beam</dd>\
<dt>distance=y</dt><dd>Distance is the range at which this vision type ends. Can be a decimal value, e.g. distance=10.5</dd>\
<dt>scale</dt><dd>An optional keyword that adds the token's footprint (size) to the distance so vision starts at token's edge vs the token's center</dd>\
<dt>arc=y</dt><dd>When used with the cone shape, it signifies how large a field of vision the token has. Can be an interger value. Ex: arc=120</dd>\
<dt>offset=y</dt><dd>When used with the cone shape, it signifies how much offset the cone begins from the center of the tokens facing. Can be an integer value. Ex: offset=120</dd>\
<dt>x#</dt><dd>Magnifier, multiplies each light source radius by this value. Can be a decimal value. Ex: x2</dd>\
<dt>arc=y</dt><dd><p>When used with the cone shape, it signifies how wide a field of vision the token has. Can be an integer value. e,g, arc=120</p>\
<p>When used with the beam shape, it determines how wide the beam will be. Can be an integer value from 0 to 180, e.g. arc=4</p></dd>\
<dt>offset=y</dt><dd>When used with the cone or beam shape, it signifies how many degrees off centre the shape begins. Can be an integer value. e.g. offset=120</dd>\
<dt>x#</dt><dd>Magnifier, multiplies each light source radius by this value. Can be a decimal value, e.g. x2.5</dd>\
<dt>r#</dt><dd><p>Range of personal light. The token will have a light source centered on them that only they can see with a radius of this value. You can define multiple personal lights per sight type.</p>\
<p>Can have an optional #rrggbb hex color component appended to it as per typical CSS rules.</p>\
<p>Can also have an optional +x lumens value. Lumens defaults to 100, meaning it will see through any darkness with a lumens value from -1 to -99. See discussion of lumens on the Light tab for more info.</p>\
Expand All @@ -870,11 +871,38 @@ the token's popup menu under the <b>Light Sources</b> element, and \
individual light source definitions become sub-entries. Group names are \
displayed in sorted order, and the sub-entries are alphabetically sorted \
as well.</p>\
<dl>\
<dt>name</dt><dd>A user-visible name for light definition.</dd>\
<dt>type</dt><dd>An optional type for the light. Currently only <b>aura</b> is valid. (See below for a description of auras.)</dd>\
<dt>shape</dt><dd>shape may be circle, square, hex, grid, cone or beam</dd>\
<dt>distance=y</dt><dd>Distance is the range at which this vision type ends. Can be a decimal value, e.g. distance=10.5</dd>\
<dt>scale</dt><dd>An optional keyword that adds the token's footprint (size) to the distance so vision starts at token's edge vs the token's center</dd>\
<dt>arc=y</dt><dd><p>When used with the cone shape, it signifies how large a field of vision the token has. Can be an integer value. e,g, arc=120</p>\
<p>When used with the beam shape, it determines how wide the beam will be. Can be an integer value from 0 to 180, e.g. arc=4</p></dd>\
<dt>offset=y</dt><dd>When used with the cone or beam shape, it signifies how many degrees off centre the shape begins. Can be an integer value. e.g. offset=120</dd>\
<dt>x#</dt><dd>Magnifier, multiplies each light source radius by this value. Can be a decimal value, e.g. x2.5</dd>\
<dt>r#</dt><dd><p>Range of personal light. The token will have a light source centered on them that only they can see with a radius of this value. You can define multiple personal lights per sight type.</p>\
<p>Can have an optional #rrggbb hex color component appended to it as per typical CSS rules.</p>\
<p>Can also have an optional +x lumens value. Lumens defaults to 100, meaning it will see through any darkness with a lumens value from -1 to -99. See discussion of lumens on the Light tab for more info.</p>\
<p>Ex: r60#bb0000+120</p></dd>\
</dl>\
<b>Format:</b> \
<pre>Group name<br>\
----------<br>\
name: type shape OPTIONS scale range#rrggbb+x</pre>\
<p>Define groups of light types with a line of text to indicate the group \
name followed by one or more <b>name</b> definitions. End a group with a \
blank line. All text is case-insensitive. Lines that start with a "-" are \
considered comments and are ignored. The group name becomes an entry on \
the token's popup menu under the <b>Light Sources</b> element, and \
individual light source definitions become sub-entries. Group names are \
displayed in sorted order, and the sub-entries are alphabetically sorted \
as well.</p>\
<dl>\
<dt>name</dt><dd>A user-visible name for light definition.</dd>\
<dt>type</dt><dd>An optional type for the light. Currently only <b>aura</b> is valid. (See below for a description of auras.)</dd>\
<dt>shape</dt><dd>An optional shape for the light emanation. Can be <b>circle</b> (default), \
<b>square</b>, <b>cone</b> (cones also have <b>arc=<i>angle</i></b> and \
<b>square</b>, <b>cone</b>, <b>beam</b> (beams and cones also have <b>arc=<i>angle</i></b> and \
<b>offset=<i>y</i></b> fields) or <b>grid</b> (for systems like Pathfinder if \
you want to see exactly which grid cells are affected by a Light/Aura). Shapes \
apply from the point they are included in the definition until the end of the \
Expand Down Expand Up @@ -902,7 +930,7 @@ as well.</p>\
</dl>\
<p><b>Example:</b></p>\
<pre>\
Sample Lights - 5' square grid <br>\
Sample Lights - 5' grid <br>\
----<br>\
Candle - 5 : 7.5 12.5#000000 <br>\
Lamp - 15 : 17.5 32.5#000000 <br>\
Expand All @@ -923,29 +951,52 @@ Darkness - 20 (CL4): circle 22.5-40<br> \
<p>To create an aura, put the keyword <b>aura</b> at the beginning of the \
definition. Auras radiate a colored area and interact with the vision \
blocking layer (i.e. are blocked by VBL), but do not actually cast any \
visible light and therefore do not expose any fog-of-war. GM-only auras \
are only visible by clients logged into the server as GM. They remain \
visible even when the map is in <i>Show As Player</i> mode. Owner-only \
auras are only visible to the player(s) who owns the token they are \
attached to and are always visible to the GM. See examples, below.</p>\
visible light and do not expose any fog-of-war. GM-only auras are only \
visible by clients logged into the server as GM. They remain visible \
even when the map is in <i>Show As Player</i> mode. Owner-only auras are\
only visible to the player(s) who owns the token. They are always visible\
to the GM. See examples, below.</p>\
<p>In the following example, the GM-only auras are red, the owner-only \
auras are green, and the player auras are blue. You can of course define \
your own colors.</p>\
<pre>\
Sample Auras - 5" square grid<br>\
Sample Auras: 5' grid<br>\
----<br>\
Aura red 5 : aura square GM 2.5#ff0000<br>\
Aura red 10 : aura GM 7.5#ff0000<br>\
Aura Owner Also 5 : aura square owner 2.5#00ff00<br>\
Aura Owner Also 10 : aura owner 7.5#00ff00<br>\
Aura Player Also 5 : aura square 2.5#0000ff<br>\
Aura Player Also 10 : aura 7.5#0000ff<br>\
\
</pre>\
<p>The first line creates a square GM-only aura in red that is 5-feet across. \
The second creates a round GM-only aura in red with a 15-foot diameter. \
Lines three and four do the same thing, but with a green aura that is only \
visible to players who own the token with the aura attached to it. Lines \
five and six are visible to all players and are in blue.</p>\
<pre>\
New Interesting Auras: 5' grid<br>\
----<br>\
Side Fields - 12.5: aura cone arc=90 12.5#6666ff offset=90 12.5#aadd00 offset=-90 12.5#aadd00 offset=180 12.5#bb00aa
Doughnut Hole - 40: aura circle 20 40#ffff00<br>\
Doughnut Cone - 20: aura cone arc=30 20#ffff00<br>\
Range Circles 30/60/90: aura circle 30.5 30.9#000000 60.5 60.9#000000 90.5 90.9#000000<br>\
Range Arcs 30/60/90: aura cone arc=135 30.5 30.9#000000 60.5 60.9#000000 90.5 90.9#000000<br>\
Beam - 50: aura beam arc=4 150#ffff00<br>\
</pre>\
<dl>\
<dt>Side fields</dt><dd>4 cones with an arc of 90 degrees and range of 12.5.\
Each one offset to cover a different region.</dd>\
<dt>Doughnut Hole</dt><dd>This one hides a secret. If you have a range with\
no colour it becomes the range the light source begins. In this case the light\
begins at 20 and ends at 40.</dd>\
<dt>Doughnut Cone</dt><dd>The same trick done with a cone.</dd>\
<dt>Range Circles</dt><dd>A series of circles starting every\
30 ft and ending 0.4 ft later.</dd>\
<dt>Range Arcs</dt><dd>Another repeat, this one using cones.</dd>\
<dt>Beam</dt><dd>A beam 150 ft long. The arc determines its width\
and is 4 degrees. At 180 degrees it would be as wide as the token.</dd>\
</html>
CampaignPropertiesDialog.label.title = Campaign Properties
CampaignPropertiesDialog.label.group = Group:
Expand Down
Loading

0 comments on commit 1768ead

Please sign in to comment.