Skip to content

Commit

Permalink
Merge pull request #4267 from kwvanderlinde/feature/3565-getIllumination
Browse files Browse the repository at this point in the history
Add macro function getIllumination()
  • Loading branch information
cwisniew committed Aug 30, 2023
2 parents bcde2a6 + d1ecf4e commit d335783
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class MapToolExpressionParser extends ExpressionParser {
ExecFunction.getInstance(),
FindTokenFunctions.getInstance(),
HasImpersonated.getInstance(),
IlluminationFunctions.getInstance(),
InitiativeRoundFunction.getInstance(),
InputFunction.getInstance(),
IsTrustedFunction.getInstance(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* This software Copyright by the RPTools.net development team, and
* licensed under the Affero GPL Version 3 or, at your option, any later
* version.
*
* MapTool Source Code is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* You should have received a copy of the GNU Affero General Public
* License * along with this source Code. If not, please visit
* <http://www.gnu.org/licenses/> and specifically the Affero license
* text at <http://www.gnu.org/licenses/agpl.html>.
*/
package net.rptools.maptool.client.functions;

import com.google.gson.JsonNull;
import java.awt.geom.Point2D;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import net.rptools.maptool.client.MapTool;
import net.rptools.maptool.client.functions.json.JSONMacroFunctions;
import net.rptools.maptool.client.ui.zone.PlayerView;
import net.rptools.maptool.client.ui.zone.ZoneRenderer;
import net.rptools.maptool.language.I18N;
import net.rptools.maptool.model.Token;
import net.rptools.maptool.util.FunctionUtil;
import net.rptools.parser.Parser;
import net.rptools.parser.ParserException;
import net.rptools.parser.VariableResolver;
import net.rptools.parser.function.AbstractFunction;
import net.rptools.parser.function.Function;

public class IlluminationFunctions extends AbstractFunction {
private static final IlluminationFunctions instance = new IlluminationFunctions();

private IlluminationFunctions() {
super(0, Function.UNLIMITED_PARAMETERS, "getIllumination");
}

public static IlluminationFunctions getInstance() {
return instance;
}

@Override
public Object childEvaluate(
Parser parser, VariableResolver resolver, String functionName, List<Object> parameters)
throws ParserException {
if (functionName.equalsIgnoreCase("getIllumination")) {
return BigDecimal.valueOf(getIllumination(functionName, parameters));
}

throw new ParserException(I18N.getText("macro.function.general.unknownFunction", functionName));
}

private int getIllumination(String functionName, List<Object> parameters) throws ParserException {
FunctionUtil.blockUntrustedMacro(functionName);
FunctionUtil.checkNumberParam(functionName, parameters, 2, 4);

final var x = FunctionUtil.paramAsInteger(functionName, parameters, 0, false);
final var y = FunctionUtil.paramAsInteger(functionName, parameters, 1, false);
final var point = new Point2D.Double(x, y);

final var renderer = FunctionUtil.getZoneRendererFromParam(functionName, parameters, 2);
final var playerView = getPlayerView(functionName, renderer, parameters, 3);

for (final var lumensLevel :
renderer.getZoneView().getDisjointObscuredLumensLevels(playerView)) {
if (lumensLevel.darknessArea().contains(point)) {
return -lumensLevel.lumensStrength();
} else if (lumensLevel.lightArea().contains(point)) {
return lumensLevel.lumensStrength();
}
}

return 0;
}

/**
* Builds a player view for `getIllumination()`
*
* @param functionName
* @param renderer
* @param parameters
* @param tokenListIndex
* @return If the token list is not provided, the current view for the zone. If `json.null` is
* provided, a non-token view. If a token ID list is provided, a token view containing the
* identified tokens. is returned. If the token ID list is empty, a token view with no in it.
* @throws ParserException
*/
private PlayerView getPlayerView(
String functionName, ZoneRenderer renderer, List<Object> parameters, int tokenListIndex)
throws ParserException {
if (parameters.size() <= tokenListIndex) {
return renderer.getPlayerView();
}

final var parameter = parameters.get(tokenListIndex);
if (parameter instanceof JsonNull) {
// Explicitly requesting without token view.
return new PlayerView(MapTool.getPlayer().getEffectiveRole());
}

// Tokens for the view passed as a JSON array.
final var jsonList =
JSONMacroFunctions.getInstance().asJsonElement(parameters.get(3).toString());
if (!jsonList.isJsonArray()) {
// Whoops, we need an array.
throw new ParserException(I18N.getText("macro.function.general.argumentTypeA", functionName));
}
final var jsonArray = jsonList.getAsJsonArray();

final var tokens = new ArrayList<Token>();
for (final var element : jsonArray) {
final var identifier = JSONMacroFunctions.getInstance().jsonToScriptString(element);
final var token = renderer.getZone().resolveToken(identifier);
if (token == null) {
throw new ParserException(
I18N.getText("macro.function.general.unknownToken", functionName, identifier));
}

tokens.add(token);
}

return new PlayerView(MapTool.getPlayer().getEffectiveRole(), tokens);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public Optional<LumensLevel> getObscuredLumensLevel(int lumensStrength) {
* <p>This is useful for rendering the lumens levels, so that well-defined boundaries exist
* between each lumens level.
*
* @return The obscured lumens levels.
* @return The obscured lumens levels, ordered from strong to weak lumens.
*/
public @Nonnull List<LumensLevel> getDisjointObscuredLumensLevels() {
if (disjointObscuredLumensLevels == null) {
Expand Down
30 changes: 28 additions & 2 deletions src/main/java/net/rptools/maptool/client/ui/zone/PlayerView.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,31 @@ public class PlayerView {
// Optimization
private final String hash;

/**
* Creates a player view that does not use token views.
*
* <p>Calling `isUsingTokenView()` on the new player view will return {@code false} and {@link
* #getTokens()} should not be called.
*
* @param role The player role for the view.
*/
public PlayerView(Player.Role role) {
this(role, null);
this.role = role;
this.tokens = null;
hash = calculateHashcode();
}

/**
* Creates a player view for a token view.
*
* <p>Calling `isUsingTokenView()` on the new player view will return {@code false} and {@link
* #getTokens()} can be called to retrieve the list of tokens.
*
* @param role The player role for the view.
*/
public PlayerView(Player.Role role, List<Token> tokens) {
this.role = role;
this.tokens = tokens != null && !tokens.isEmpty() ? tokens : null;
this.tokens = tokens;
hash = calculateHashcode();
}

Expand All @@ -45,6 +63,13 @@ public boolean isGMView() {
return role == Player.Role.GM;
}

/**
* Gets the tokens for this view.
*
* <p>This method should only be used when {@link #isUsingTokenView()} returns {@code true}.
*
* @return The tokens for this view.
*/
public List<Token> getTokens() {
return tokens;
}
Expand Down Expand Up @@ -74,6 +99,7 @@ private String calculateHashcode() {
StringBuilder builder = new StringBuilder();
builder.append(role);
if (tokens != null) {
builder.append('|'); // Distinguishes null and empty case.
for (Token token : tokens) {
builder.append(token.getId());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,10 @@ public PlayerView getPlayerView(Player.Role role, boolean selected) {
? zone.getOwnedTokensWithSight(MapTool.getPlayer())
: zone.getPlayerTokensWithSight();
}
if (selectedTokens == null || selectedTokens.isEmpty()) {
return new PlayerView(role);
}

return new PlayerView(role, selectedTokens);
}

Expand Down Expand Up @@ -1512,9 +1516,6 @@ private void renderLumensOverlay(
timer.stop("renderLumensOverlay:setTransform");

timer.start("renderLumensOverlay:drawLumens");
// Lumens are ordered to be weak to strong. That works for us as we will draw the stronger
// areas overtop the weaker areas using `AlphaComposite.Src` to make sure the stronger one
// "wins".
for (final var lumensLevel : disjointLumensLevels) {
final var lumensStrength = lumensLevel.lumensStrength();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ public Illumination getIllumination(PlayerView view) {
*
* @param view
* @return The various lumens levels, with any stronger lumens areas being subtracted from weaker
* lumens areas.
* lumens areas and ordered from strong to weak lumens.
*/
public List<LumensLevel> getDisjointObscuredLumensLevels(PlayerView view) {
final var illumination = getIllumination(view);
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/net/rptools/maptool/util/FunctionUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ public static Token getTokenFromParam(
ZoneRenderer zoneRenderer;
if (map == null) {
zoneRenderer = MapTool.getFrame().getCurrentZoneRenderer();
if (zoneRenderer == null) {
throw new ParserException(I18N.getText("macro.function.map.none", functionName));
}
} else {
zoneRenderer = getZoneRenderer(functionName, map);
}
Expand Down

0 comments on commit d335783

Please sign in to comment.