Use WE Expressions instead of js evaluation (#2941)

* Implement thread-safe expression evaluation

* Update `{args}` to `plot` automatically

* Stringify more money/balance/price occurrences with EconHandler#format
This commit is contained in:
Hannes Greule 2021-01-02 17:32:07 +01:00 committed by GitHub
parent f47561b580
commit ad99ca1723
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 157 additions and 152 deletions

View File

@ -36,6 +36,7 @@ import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.checkerframework.checker.nullness.qual.NonNull;
@Singleton public class BukkitEconHandler extends EconHandler {
@ -84,6 +85,11 @@ import org.bukkit.plugin.RegisteredServiceProvider;
return plotArea.useEconomy();
}
@Override
public @NonNull String format(double balance) {
return this.econ.format(balance);
}
@Override
public boolean isSupported() {
return true;

View File

@ -46,8 +46,8 @@ import com.plotsquared.core.plot.world.PlotAreaManager;
import com.plotsquared.core.services.plots.AutoService;
import com.plotsquared.core.util.EconHandler;
import com.plotsquared.core.util.EventDispatcher;
import com.plotsquared.core.util.Expression;
import com.plotsquared.core.util.Permissions;
import com.plotsquared.core.util.PlotExpression;
import com.plotsquared.core.util.task.AutoClaimFinishTask;
import com.plotsquared.core.util.task.RunnableVal;
import com.plotsquared.core.util.task.TaskManager;
@ -276,24 +276,24 @@ public class Auto extends SubCommand {
}
}
if (this.econHandler != null && plotarea.useEconomy()) {
Expression<Double> costExp = plotarea.getPrices().get("claim");
double cost = costExp.evaluate((double) (Settings.Limit.GLOBAL ?
PlotExpression costExp = plotarea.getPrices().get("claim");
double cost = costExp.evaluate(Settings.Limit.GLOBAL ?
player.getPlotCount() :
player.getPlotCount(plotarea.getWorldName())));
player.getPlotCount(plotarea.getWorldName()));
cost = (size_x * size_z) * cost;
if (cost > 0d) {
if (!force && this.econHandler.getMoney(player) < cost) {
player.sendMessage(
TranslatableCaption.of("economy.cannot_afford_plot"),
Template.of("money", String.valueOf(cost)),
Template.of("balance", String.valueOf(this.econHandler.getMoney(player)))
Template.of("money", this.econHandler.format(cost)),
Template.of("balance", this.econHandler.format(this.econHandler.getMoney(player)))
);
return true;
}
this.econHandler.withdrawMoney(player, cost);
player.sendMessage(
TranslatableCaption.of("economy.removed_balance"),
Template.of("money", String.valueOf(cost))
Template.of("money", this.econHandler.format(cost))
);
}
}

View File

@ -92,15 +92,15 @@ public class Buy extends Command {
}
checkTrue(this.econHandler.getMoney(player) >= price,
TranslatableCaption.of("economy.cannot_afford_plot"),
Template.of("money", String.valueOf(price)),
Template.of("balance", String.valueOf(this.econHandler.getMoney(player))));
Template.of("money", this.econHandler.format(price)),
Template.of("balance", this.econHandler.format(this.econHandler.getMoney(player))));
this.econHandler.withdrawMoney(player, price);
// Failure
// Success
confirm.run(this, () -> {
player.sendMessage(
TranslatableCaption.of("economy.removed_balance"),
Template.of("money", String.valueOf(price))
Template.of("money", this.econHandler.format(price))
);
this.econHandler.depositMoney(PlotSquared.platform().playerManager().getOfflinePlayer(plot.getOwnerAbs()), price);
@ -111,7 +111,7 @@ public class Buy extends Command {
TranslatableCaption.of("economy.plot_sold"),
Template.of("plot", plot.getId().toString()),
Template.of("player", player.getName()),
Template.of("price", String.valueOf(price))
Template.of("price", this.econHandler.format(price))
);
}
PlotFlag<?, ?> plotFlag = plot.getFlagContainer().getFlag(PriceFlag.class);

View File

@ -42,8 +42,8 @@ import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.plot.PlotArea;
import com.plotsquared.core.util.EconHandler;
import com.plotsquared.core.util.EventDispatcher;
import com.plotsquared.core.util.Expression;
import com.plotsquared.core.util.Permissions;
import com.plotsquared.core.util.PlotExpression;
import com.plotsquared.core.util.task.TaskManager;
import net.kyori.adventure.text.minimessage.Template;
import org.slf4j.Logger;
@ -136,20 +136,22 @@ public class Claim extends SubCommand {
}
}
if (this.econHandler.isEnabled(area) && !force) {
Expression<Double> costExr = area.getPrices().get("claim");
double cost = costExr.evaluate((double) currentPlots);
PlotExpression costExr = area.getPrices().get("claim");
double cost = costExr.evaluate(currentPlots);
if (cost > 0d) {
if (this.econHandler.getMoney(player) < cost) {
player.sendMessage(
TranslatableCaption.of("economy.cannot_afford_plot"),
Template.of("money", String.valueOf(cost))
Template.of("money", this.econHandler.format(cost)),
Template.of("balance", this.econHandler.format(this.econHandler.getMoney(player)))
);
}
return false;
}
this.econHandler.withdrawMoney(player, cost);
player.sendMessage(
TranslatableCaption.of("economy.removed_balance"),
Template.of("money", String.valueOf(cost)),
Template.of("balance", String.valueOf(this.econHandler.getMoney(player)))
Template.of("money", this.econHandler.format(cost)),
Template.of("balance", this.econHandler.format(this.econHandler.getMoney(player)))
);
}
}

View File

@ -26,24 +26,23 @@
package com.plotsquared.core.command;
import com.google.inject.Inject;
import com.plotsquared.core.events.TeleportCause;
import com.plotsquared.core.permissions.Permission;
import com.plotsquared.core.configuration.Settings;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.events.Result;
import com.plotsquared.core.events.TeleportCause;
import com.plotsquared.core.location.Location;
import com.plotsquared.core.permissions.Permission;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.plot.PlotArea;
import com.plotsquared.core.util.EconHandler;
import com.plotsquared.core.util.EventDispatcher;
import com.plotsquared.core.util.Expression;
import com.plotsquared.core.util.Permissions;
import com.plotsquared.core.util.PlotExpression;
import com.plotsquared.core.util.task.TaskManager;
import net.kyori.adventure.text.minimessage.Template;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@CommandDeclaration(command = "delete",
@ -106,13 +105,13 @@ public class Delete extends SubCommand {
boolean result = plot.getPlotModificationManager().deletePlot(player, () -> {
plot.removeRunning();
if (this.econHandler.isEnabled(plotArea)) {
Expression<Double> valueExr = plotArea.getPrices().get("sell");
double value = plots.size() * valueExr.evaluate((double) currentPlots);
PlotExpression valueExr = plotArea.getPrices().get("sell");
double value = plots.size() * valueExr.evaluate(currentPlots);
if (value > 0d) {
this.econHandler.depositMoney(player, value);
player.sendMessage(
TranslatableCaption.of("economy.added_balance"),
Template.of("money", String.valueOf(value))
Template.of("money", this.econHandler.format(value))
);
}
}

View File

@ -27,10 +27,10 @@ package com.plotsquared.core.command;
import com.google.inject.Injector;
import com.plotsquared.core.PlotSquared;
import com.plotsquared.core.permissions.Permission;
import com.plotsquared.core.configuration.Settings;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.location.Location;
import com.plotsquared.core.permissions.Permission;
import com.plotsquared.core.player.ConsolePlayer;
import com.plotsquared.core.player.MetaDataAccess;
import com.plotsquared.core.player.PlayerMetaDataKeys;
@ -38,8 +38,8 @@ import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.plot.PlotArea;
import com.plotsquared.core.util.EconHandler;
import com.plotsquared.core.util.Expression;
import com.plotsquared.core.util.Permissions;
import com.plotsquared.core.util.PlotExpression;
import com.plotsquared.core.util.task.RunnableVal2;
import com.plotsquared.core.util.task.RunnableVal3;
import org.slf4j.Logger;
@ -190,11 +190,10 @@ public class MainCommand extends Command {
CmdConfirm.addPending(player, cmd.getUsage(), () -> {
PlotArea area = player.getApplicablePlotArea();
if (area != null && econHandler.isEnabled(area)) {
Expression<Double> priceEval =
PlotExpression priceEval =
area.getPrices().get(cmd.getFullId());
Double price = priceEval != null ? priceEval.evaluate(0d) : 0d;
if (price != null
&& econHandler.getMoney(player) < price) {
double price = priceEval != null ? priceEval.evaluate(0d) : 0d;
if (econHandler.getMoney(player) < price) {
if (failure != null) {
failure.run();
}
@ -207,17 +206,15 @@ public class MainCommand extends Command {
});
return;
}
if (econHandler != null) {
PlotArea area = player.getApplicablePlotArea();
if (area != null) {
Expression<Double> priceEval = area.getPrices().get(cmd.getFullId());
Double price = priceEval != null ? priceEval.evaluate(0d) : 0d;
if (price != 0d && econHandler.getMoney(player) < price) {
if (failure != null) {
failure.run();
}
return;
PlotArea area = player.getApplicablePlotArea();
if (area != null && econHandler.isEnabled(area)) {
PlotExpression priceEval = area.getPrices().get(cmd.getFullId());
double price = priceEval != null ? priceEval.evaluate(0d) : 0d;
if (price != 0d && econHandler.getMoney(player) < price) {
if (failure != null) {
failure.run();
}
return;
}
}
if (success != null) {
@ -277,19 +274,16 @@ public class MainCommand extends Command {
if ("f".equals(args[0].substring(1))) {
confirm = new RunnableVal3<Command, Runnable, Runnable>() {
@Override public void run(Command cmd, Runnable success, Runnable failure) {
if (PlotSquared.platform().econHandler() != null) {
PlotArea area = player.getApplicablePlotArea();
if (area != null) {
Expression<Double> priceEval =
area.getPrices().get(cmd.getFullId());
Double price = priceEval != null ? priceEval.evaluate(0d) : 0d;
if (price != 0d
&& PlotSquared.platform().econHandler().getMoney(player) < price) {
if (failure != null) {
failure.run();
}
return;
if (area != null && PlotSquared.platform().econHandler().isEnabled(area)) {
PlotExpression priceEval =
area.getPrices().get(cmd.getFullId());
double price = priceEval != null ? priceEval.evaluate(0d) : 0d;
if (price != 0d
&& PlotSquared.platform().econHandler().getMoney(player) < price) {
if (failure != null) {
failure.run();
}
return;
}
}
if (success != null) {

View File

@ -39,8 +39,8 @@ import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.plot.PlotArea;
import com.plotsquared.core.util.EconHandler;
import com.plotsquared.core.util.EventDispatcher;
import com.plotsquared.core.util.Expression;
import com.plotsquared.core.util.Permissions;
import com.plotsquared.core.util.PlotExpression;
import com.plotsquared.core.util.StringMan;
import net.kyori.adventure.text.minimessage.Template;
@ -162,8 +162,8 @@ public class Merge extends SubCommand {
return false;
}
final PlotArea plotArea = plot.getArea();
Expression<Double> priceExr = plotArea.getPrices().getOrDefault("merge", null);
final double price = priceExr == null ? 0d : priceExr.evaluate((double) size);
PlotExpression priceExr = plotArea.getPrices().getOrDefault("merge", null);
final double price = priceExr == null ? 0d : priceExr.evaluate(size);
UUID uuid = player.getUUID();
if (direction == Direction.ALL) {
@ -184,7 +184,8 @@ public class Merge extends SubCommand {
this.econHandler.withdrawMoney(player, price);
player.sendMessage(
TranslatableCaption.of("economy.removed_balance"),
Template.of("money", String.valueOf(price))
Template.of("money", this.econHandler.format(price)),
Template.of("balance", this.econHandler.format(this.econHandler.getMoney(player)))
);
}
player.sendMessage(TranslatableCaption.of("merge.success_merge"));
@ -205,7 +206,7 @@ public class Merge extends SubCommand {
&& this.econHandler.getMoney(player) < price) {
player.sendMessage(
TranslatableCaption.of("economy.cannot_afford_merge"),
Template.of("money", String.valueOf(price))
Template.of("money", this.econHandler.format(price))
);
return false;
}
@ -228,7 +229,7 @@ public class Merge extends SubCommand {
this.econHandler.withdrawMoney(player, price);
player.sendMessage(
TranslatableCaption.of("economy.removed_balance"),
Template.of("money", String.valueOf(price))
Template.of("money", this.econHandler.format(price))
);
}
player.sendMessage(TranslatableCaption.of("merge.success_merge"));
@ -268,14 +269,14 @@ public class Merge extends SubCommand {
if (!force && this.econHandler.getMoney(player) < price) {
player.sendMessage(
TranslatableCaption.of("economy.cannot_afford_merge"),
Template.of("money", String.valueOf(price))
Template.of("money", this.econHandler.format(price))
);
return;
}
this.econHandler.withdrawMoney(player, price);
player.sendMessage(
TranslatableCaption.of("economy.removed_balance"),
Template.of("money", String.valueOf(price))
Template.of("money", this.econHandler.format(price))
);
}
player.sendMessage(TranslatableCaption.of("merge.success_merge"));

View File

@ -186,7 +186,7 @@ public class ComponentPresetManager {
} else {
econHandler.withdrawMoney(getPlayer(), componentPreset.getCost());
getPlayer().sendMessage(TranslatableCaption.of("economy.removed_balance"),
Template.of("money", componentPreset.getCost() + ""));
Template.of("money", econHandler.format(componentPreset.getCost())));
}
}

View File

@ -57,8 +57,8 @@ import com.plotsquared.core.plot.flag.implementations.DoneFlag;
import com.plotsquared.core.plot.flag.types.DoubleFlag;
import com.plotsquared.core.queue.GlobalBlockQueue;
import com.plotsquared.core.queue.QueueCoordinator;
import com.plotsquared.core.util.Expression;
import com.plotsquared.core.util.MathMan;
import com.plotsquared.core.util.PlotExpression;
import com.plotsquared.core.util.RegionUtil;
import com.plotsquared.core.util.StringMan;
import com.sk89q.worldedit.math.BlockVector2;
@ -134,7 +134,7 @@ public abstract class PlotArea {
private int maxBuildHeight = 256;
private int minBuildHeight = 1;
private GameMode gameMode = GameModes.CREATIVE;
private Map<String, Expression<Double>> prices = new HashMap<>();
private Map<String, PlotExpression> prices = new HashMap<>();
private List<String> schematics = new ArrayList<>();
private boolean roadFlags = false;
private boolean worldBorder = false;
@ -306,7 +306,12 @@ public abstract class PlotArea {
if (this.useEconomy) {
this.prices = new HashMap<>();
for (String key : priceSection.getKeys(false)) {
this.prices.put(key, Expression.doubleExpression(priceSection.getString(key)));
String raw = priceSection.getString(key);
if (raw.contains("{args}")) {
raw = raw.replace("{args}", "plots");
priceSection.set(key, raw); // update if replaced
}
this.prices.put(key, PlotExpression.compile(raw, "plots"));
}
}
this.plotChat = config.getBoolean("chat.enabled");
@ -1315,7 +1320,7 @@ public abstract class PlotArea {
return this.gameMode;
}
public Map<String, Expression<Double>> getPrices() {
public Map<String, PlotExpression> getPrices() {
return this.prices;
}

View File

@ -29,7 +29,7 @@ import com.plotsquared.core.player.ConsolePlayer;
import com.plotsquared.core.player.OfflinePlotPlayer;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.PlotArea;
import com.sk89q.worldedit.EditSession;
import org.checkerframework.checker.nullness.qual.NonNull;
public abstract class EconHandler {
@ -75,6 +75,14 @@ public abstract class EconHandler {
*/
public abstract boolean isEnabled(PlotArea plotArea);
/**
* Formats the given balance into a human-readable number.
*
* @param balance the balance to format.
* @return the balance as formatted string.
*/
public abstract @NonNull String format(double balance);
/**
* Returns whether economy is supported by the server or not.
*
@ -114,6 +122,11 @@ public abstract class EconHandler {
return false;
}
@Override
public @NonNull String format(double balance) {
return "";
}
@Override
public boolean isSupported() {
return false;

View File

@ -1,85 +0,0 @@
/*
* _____ _ _ _____ _
* | __ \| | | | / ____| | |
* | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| |
* | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` |
* | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| |
* |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_|
* | |
* |_|
* PlotSquared plot management system for Minecraft
* Copyright (C) 2021 IntellectualSites
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.core.util;
import com.plotsquared.core.command.DebugExec;
import com.plotsquared.core.command.MainCommand;
import com.plotsquared.core.configuration.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.script.ScriptException;
public abstract class Expression<T> {
private static final Logger logger = LoggerFactory.getLogger("P2/" + Expression.class.getSimpleName());
public static <U> Expression<U> constant(final U value) {
return new Expression<U>() {
@Override public U evaluate(U arg) {
return value;
}
};
}
public static Expression<Double> linearDouble(final Double value) {
return new Expression<Double>() {
@Override public Double evaluate(Double arg) {
return (arg * value);
}
};
}
public static Expression<Double> doubleExpression(final String expression) {
try {
return constant(Double.parseDouble(expression));
} catch (NumberFormatException ignore) {
}
if (expression.endsWith("*{arg}")) {
try {
return linearDouble(
Double.parseDouble(expression.substring(0, expression.length() - 6)));
} catch (NumberFormatException ignore) {
}
}
return new Expression<Double>() {
@Override public Double evaluate(Double arg) {
DebugExec exec = (DebugExec) MainCommand.getInstance().getCommand(DebugExec.class);
try {
return (Double) exec.getEngine().eval(expression.replace("{arg}", "" + arg));
} catch (ScriptException e) {
if (Settings.DEBUG) {
logger.info("Invalid expression: {}", expression);
}
e.printStackTrace();
}
return 0d;
}
};
}
public abstract T evaluate(T arg);
}

View File

@ -0,0 +1,70 @@
/*
* _____ _ _ _____ _
* | __ \| | | | / ____| | |
* | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| |
* | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` |
* | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| |
* |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_|
* | |
* |_|
* PlotSquared plot management system for Minecraft
* Copyright (C) 2021 IntellectualSites
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.core.util;
import com.sk89q.worldedit.internal.expression.Expression;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* An expression that can be evaluated.
* Evaluation is thread-safe.
* This is a wrapper for {@link Expression}.
*/
public class PlotExpression {
private final Expression expression;
private final Object lock = new Object();
/**
* Compiles an expression from a string.
*
* @param expression the expression to compile.
* @param variableNames the variables that can be set in {@link #evaluate(double...)}.
* @return the compiled expression.
*/
public static @NonNull PlotExpression compile(final @NonNull String expression,
final @NonNull String @NonNull ... variableNames) {
return new PlotExpression(expression, variableNames);
}
private PlotExpression(final @NonNull String rawExpression, final @NonNull String @NonNull [] variableNames) {
this.expression = Expression.compile(rawExpression, variableNames);
}
/**
* Evaluates the expression with the given variable values.
*
* @param values the values to set the variables to.
* @return the result of the evaluation.
*/
public double evaluate(double... values) {
double evaluate;
// synchronization is likely the best option in terms of memory and cpu consumption
synchronized (this.lock) {
evaluate = this.expression.evaluate(values);
}
return evaluate;
}
}