Improves behavior when signs share conditions
Makes sure that the paid sign matching the most conditions is always chosen as the matching paid sign Makes sure to always choose the most expensive paid sign if two or more paid signs with the same amount of conditions are matching. Changes some classes to records to reduce some boilerplate code
This commit is contained in:
parent
32ec713994
commit
38717a6b91
@ -130,7 +130,7 @@ public class EditCommand extends TokenizedCommand {
|
||||
PaidSign updatedSign = new PaidSign(signName, cost, permission, ignoreCase, ignoreColor, matchAnyCondition);
|
||||
for (short line : conditions.keySet()) {
|
||||
PaidSignCondition condition = conditions.get(line);
|
||||
updatedSign.addCondition(line, condition.getStringToMatch(), condition.executeRegex(),
|
||||
updatedSign.addCondition(line, condition.stringToMatch(), condition.executeRegex(),
|
||||
OptionState.getFromBoolean(condition.ignoreCase()),
|
||||
OptionState.getFromBoolean(condition.ignoreColor()));
|
||||
}
|
||||
@ -154,7 +154,7 @@ public class EditCommand extends TokenizedCommand {
|
||||
PaidSignConditionProperty property, String newValue) {
|
||||
PaidSignCondition condition = sign.getConditions().get(conditionIndex);
|
||||
String stringToMatch = property == PaidSignConditionProperty.STRING_TO_MATCH ? newValue :
|
||||
condition.getStringToMatch();
|
||||
condition.stringToMatch();
|
||||
boolean executeRegEx = property == PaidSignConditionProperty.EXECUTE_REG_EX ? Boolean.parseBoolean(newValue) :
|
||||
condition.executeRegex();
|
||||
boolean ignoreCase = property == PaidSignConditionProperty.IGNORE_CASE ? OptionState.getBooleanValue(
|
||||
|
@ -107,7 +107,7 @@ public class ListCommand extends TokenizedCommand {
|
||||
PaidSignCondition condition) {
|
||||
sender.sendMessage(StringFormatter.replacePlaceholders(Translator.getTranslatedMessage(
|
||||
TranslatableMessage.PAID_SIGN_CONDITION_INFO), new String[]{"{name}", "{line}", "{match}", "{regex}",
|
||||
"{case}", "{color}"}, new String[]{signName, String.valueOf(signLine + 1), condition.getStringToMatch(),
|
||||
"{case}", "{color}"}, new String[]{signName, String.valueOf(signLine + 1), condition.stringToMatch(),
|
||||
translateBoolean(condition.executeRegex()), translateBoolean(condition.ignoreCase()),
|
||||
translateBoolean(condition.ignoreColor())}));
|
||||
}
|
||||
@ -124,7 +124,7 @@ public class ListCommand extends TokenizedCommand {
|
||||
for (short lineIndex : signConditions.keySet()) {
|
||||
String format = Translator.getTranslatedMessage(TranslatableMessage.PAID_SIGN_INFO_CONDITION_FORMAT);
|
||||
conditions.append(StringFormatter.replacePlaceholders(format, new String[]{"{line}", "{condition}"},
|
||||
new String[]{String.valueOf((lineIndex + 1)), signConditions.get(lineIndex).getStringToMatch()}));
|
||||
new String[]{String.valueOf((lineIndex + 1)), signConditions.get(lineIndex).stringToMatch()}));
|
||||
}
|
||||
|
||||
sender.sendMessage(replacePlaceholders(Translator.getTranslatedMessage(
|
||||
|
@ -148,13 +148,15 @@ public class PaidSign {
|
||||
* @param ignoreCase <p>Whether to ignore case when matching against the condition</p>
|
||||
* @param ignoreColor <p>Whether to ignore color when matching against the condition</p>
|
||||
*/
|
||||
public void addCondition(short line, String stringToMatch, boolean executeRegex, OptionState ignoreCase, OptionState ignoreColor) {
|
||||
public void addCondition(short line, String stringToMatch, boolean executeRegex, OptionState ignoreCase,
|
||||
OptionState ignoreColor) {
|
||||
if (line < 0 || line > 3) {
|
||||
throw new IllegalArgumentException("Invalid sign line given for new paid sign condition");
|
||||
}
|
||||
boolean ignoreCaseBoolean = OptionState.getBooleanValue(ignoreCase, this.getIgnoreCase());
|
||||
boolean ignoreColorBoolean = OptionState.getBooleanValue(ignoreColor, this.getIgnoreColor());
|
||||
this.conditions.put(line, new PaidSignCondition(stringToMatch, executeRegex, ignoreCaseBoolean, ignoreColorBoolean));
|
||||
this.conditions.put(line, new PaidSignCondition(stringToMatch, executeRegex, ignoreCaseBoolean,
|
||||
ignoreColorBoolean));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,64 +4,14 @@ import net.knarcraft.paidsigns.utility.ColorHelper;
|
||||
|
||||
/**
|
||||
* A condition for deciding if a paid sign matches a sign line
|
||||
*
|
||||
* @param stringToMatch <p>The string/regular expression the line has to match to fulfill this condition</p>
|
||||
* @param executeRegex <p>Whether to execute the match string as a regular expression</p>
|
||||
* @param ignoreCase <p>Whether to ignore uppercase/lowercase when comparing against this condition</p>
|
||||
* @param ignoreColor <p>Whether to ignore color codes when comparing against this condition</p>
|
||||
*/
|
||||
public class PaidSignCondition {
|
||||
|
||||
final String stringToMatch;
|
||||
final boolean executeRegex;
|
||||
final boolean ignoreCase;
|
||||
final boolean ignoreColor;
|
||||
|
||||
/**
|
||||
* Instantiates a new paid sign condition
|
||||
*
|
||||
* @param stringToMatch <p>The string/regular expression the line has to match to fulfill this condition</p>
|
||||
* @param executeRegex <p>Whether to execute the match string as a regular expression</p>
|
||||
* @param ignoreCase <p>Whether to ignore uppercase/lowercase when comparing against this condition</p>
|
||||
* @param ignoreColor <p>Whether to ignore color codes when comparing against this condition</p>
|
||||
*/
|
||||
public PaidSignCondition(String stringToMatch, boolean executeRegex, boolean ignoreCase, boolean ignoreColor) {
|
||||
this.stringToMatch = stringToMatch;
|
||||
this.executeRegex = executeRegex;
|
||||
this.ignoreCase = ignoreCase;
|
||||
this.ignoreColor = ignoreColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string this condition should match
|
||||
*
|
||||
* @return <p>The string this condition should match</p>
|
||||
*/
|
||||
public String getStringToMatch() {
|
||||
return this.stringToMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to execute the match string as RegEx
|
||||
*
|
||||
* @return <p>Whether to execute the match string as RegEx</p>
|
||||
*/
|
||||
public boolean executeRegex() {
|
||||
return this.executeRegex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to ignore case when trying to match strings
|
||||
*
|
||||
* @return <p>Whether to ignore case when trying to match strings</p>
|
||||
*/
|
||||
public boolean ignoreCase() {
|
||||
return this.ignoreCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to ignore color when trying to match strings
|
||||
*
|
||||
* @return <p>Whether to ignore color when trying to match strings</p>
|
||||
*/
|
||||
public boolean ignoreColor() {
|
||||
return this.ignoreColor;
|
||||
}
|
||||
public record PaidSignCondition(String stringToMatch, boolean executeRegex, boolean ignoreCase,
|
||||
boolean ignoreColor) {
|
||||
|
||||
/**
|
||||
* Tests whether the given line matches this condition
|
||||
|
@ -0,0 +1,19 @@
|
||||
package net.knarcraft.paidsigns.container;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A container class for number of condition matches for a paid sign
|
||||
*
|
||||
* @param paidSign <p>The paid sign this class keeps track of matches for</p>
|
||||
* @param conditionMatches <p>The number of conditions matched for the paid sign</p>
|
||||
*/
|
||||
public record PaidSignConditionMatch(PaidSign paidSign,
|
||||
int conditionMatches) implements Comparable<PaidSignConditionMatch> {
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull PaidSignConditionMatch other) {
|
||||
return Integer.compare(this.conditionMatches, other.conditionMatches);
|
||||
}
|
||||
|
||||
}
|
@ -4,39 +4,10 @@ import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A representation of a sign placed by a player that matched a paid sign
|
||||
*
|
||||
* @param playerId <p>The unique id of the player that created the sign</p>
|
||||
* @param cost <p>The cost the player paid for creating the sign</p>
|
||||
*/
|
||||
public class TrackedSign {
|
||||
|
||||
private final UUID playerId;
|
||||
private final double cost;
|
||||
|
||||
/**
|
||||
* Instantiates a new tracked sign
|
||||
*
|
||||
* @param playerId <p>The unique id of the player that created the sign</p>
|
||||
* @param cost <p>The cost the player paid for creating the sign</p>
|
||||
*/
|
||||
public TrackedSign(UUID playerId, double cost) {
|
||||
this.playerId = playerId;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of the player that created this tracked sign
|
||||
*
|
||||
* @return <p>The player that created this tracked sign</p>
|
||||
*/
|
||||
public UUID getPlayerId() {
|
||||
return this.playerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cost the player paid for creating this paid sign
|
||||
*
|
||||
* @return <p>The cost paid for creating this sign</p>
|
||||
*/
|
||||
public double getCost() {
|
||||
return this.cost;
|
||||
}
|
||||
public record TrackedSign(UUID playerId, double cost) {
|
||||
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package net.knarcraft.paidsigns.listener;
|
||||
|
||||
import net.knarcraft.paidsigns.PaidSigns;
|
||||
import net.knarcraft.paidsigns.container.PaidSign;
|
||||
import net.knarcraft.paidsigns.container.PaidSignConditionMatch;
|
||||
import net.knarcraft.paidsigns.formatting.StringFormatter;
|
||||
import net.knarcraft.paidsigns.formatting.TranslatableMessage;
|
||||
import net.knarcraft.paidsigns.manager.EconomyManager;
|
||||
@ -13,6 +14,9 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.SignChangeEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -28,36 +32,90 @@ public class SignListener implements Listener {
|
||||
}
|
||||
|
||||
String[] lines = event.getLines();
|
||||
Map<String, PaidSign> matchingSigns = PaidSigns.getInstance().getSignManager().getAllPaidSigns();
|
||||
for (PaidSign paidSign : matchingSigns.values()) {
|
||||
//If a match is found, just return
|
||||
if (matchSign(paidSign, lines, event)) {
|
||||
return;
|
||||
}
|
||||
Map<String, PaidSign> allPaidSigns = PaidSigns.getInstance().getSignManager().getAllPaidSigns();
|
||||
|
||||
//Generate the sorted list of the number of paid sign conditions matched for every paid sign
|
||||
List<PaidSignConditionMatch> allConditionMatches = new ArrayList<>();
|
||||
for (PaidSign paidSign : allPaidSigns.values()) {
|
||||
calculateConditionMatches(paidSign, lines, allConditionMatches);
|
||||
}
|
||||
allConditionMatches.sort(Collections.reverseOrder());
|
||||
|
||||
//Make the player pay for the paid sign
|
||||
PaidSign mostExpensive = getMatchingSign(allConditionMatches);
|
||||
if (mostExpensive != null) {
|
||||
performPaidSignCheck(mostExpensive, event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given paid sign matches the given sign lines
|
||||
* Gets the most specific paid sign match
|
||||
*
|
||||
* <p>This method chooses the most specific match, if any. If two matches have the same number of matched
|
||||
* conditions, it will choose the most expensive one.</p>
|
||||
*
|
||||
* @param allConditionMatches <p>A sorted list of all paid sign condition matches</p>
|
||||
* @return <p>The best matching paid sign, or null if no paid sign matches</p>
|
||||
*/
|
||||
private PaidSign getMatchingSign(List<PaidSignConditionMatch> allConditionMatches) {
|
||||
PaidSign mostExpensive = null;
|
||||
int bestMatch = 1;
|
||||
for (PaidSignConditionMatch paidSignConditionMatch : allConditionMatches) {
|
||||
int conditionMatches = paidSignConditionMatch.conditionMatches();
|
||||
/* Stop if there is no matching paid signs. Also stop if a paid sign is encountered with fewer conditions
|
||||
matching */
|
||||
if (conditionMatches >= bestMatch) {
|
||||
PaidSign paidSign = paidSignConditionMatch.paidSign();
|
||||
//Choose the most expensive paid sign between those with the same number of matched conditions
|
||||
if (mostExpensive == null || paidSign.getCost() > mostExpensive.getCost()) {
|
||||
mostExpensive = paidSign;
|
||||
bestMatch = conditionMatches;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return mostExpensive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of conditions matching for the given paid sign
|
||||
*
|
||||
* <p>This calculates the number of matches, allowing the most specific paid sign to be chosen</p>
|
||||
*
|
||||
* @param paidSign <p>The paid sign to calculate matches for</p>
|
||||
* @param lines <p>The lines of a sign</p>
|
||||
* @param allConditionMatches <p>The number of conditions matched</p>
|
||||
*/
|
||||
private void calculateConditionMatches(PaidSign paidSign, String[] lines, List<PaidSignConditionMatch> allConditionMatches) {
|
||||
if (paidSign.matches(lines)) {
|
||||
if (paidSign.matchAnyCondition()) {
|
||||
//For any match, it can only be assumed one lines matches
|
||||
allConditionMatches.add(new PaidSignConditionMatch(paidSign, 1));
|
||||
} else {
|
||||
//For a normal match, the number of conditions is equal to the number of matches
|
||||
allConditionMatches.add(new PaidSignConditionMatch(paidSign, paidSign.getConditions().size()));
|
||||
}
|
||||
} else {
|
||||
allConditionMatches.add(new PaidSignConditionMatch(paidSign, 0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs necessary checks and performs the paid sign transaction
|
||||
*
|
||||
* @param paidSign <p>The paid sign to test against the sign lines</p>
|
||||
* @param lines <p>The lines of a sign</p>
|
||||
* @param event <p>The triggered sign change event to cancel if necessary</p>
|
||||
* @return <p>True if a match was found and actions have been taken</p>
|
||||
*/
|
||||
private boolean matchSign(PaidSign paidSign, String[] lines, SignChangeEvent event) {
|
||||
if (paidSign.matches(lines)) {
|
||||
Player player = event.getPlayer();
|
||||
String permission = paidSign.getPermission();
|
||||
//If a match is found, but the player is missing the permission, assume no plugin sign was created
|
||||
if (permission != null && !permission.trim().isEmpty() && !player.hasPermission(permission)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
performPaidSignTransaction(paidSign, player, event);
|
||||
return true;
|
||||
private void performPaidSignCheck(PaidSign paidSign, SignChangeEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
String permission = paidSign.getPermission();
|
||||
//If a match is found, but the player is missing the permission, assume no plugin sign was created
|
||||
if (permission != null && !permission.trim().isEmpty() && !player.hasPermission(permission)) {
|
||||
return;
|
||||
}
|
||||
return false;
|
||||
|
||||
performPaidSignTransaction(paidSign, player, event);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,7 +156,7 @@ public final class PaidSignManager {
|
||||
Map<Short, PaidSignCondition> signConditions = sign.getConditions();
|
||||
for (short lineIndex : signConditions.keySet()) {
|
||||
PaidSignCondition condition = signConditions.get(lineIndex);
|
||||
conditionsSection.set(lineIndex + ".stringToMatch", condition.getStringToMatch());
|
||||
conditionsSection.set(lineIndex + ".stringToMatch", condition.stringToMatch());
|
||||
conditionsSection.set(lineIndex + ".executeRegEx", condition.executeRegex());
|
||||
conditionsSection.set(lineIndex + ".ignoreCase", condition.ignoreCase());
|
||||
conditionsSection.set(lineIndex + ".ignoreColor", condition.ignoreColor());
|
||||
|
@ -126,8 +126,8 @@ public final class TrackedSignManager {
|
||||
TrackedSign sign = trackedSigns.get(signLocation);
|
||||
String locationString = Objects.requireNonNull(signLocation.getWorld()).getUID() + "," +
|
||||
signLocation.getBlockX() + "," + signLocation.getBlockY() + "," + signLocation.getBlockZ();
|
||||
signSection.set(locationString + ".cost", sign.getCost());
|
||||
signSection.set(locationString + ".playerId", sign.getPlayerId().toString());
|
||||
signSection.set(locationString + ".cost", sign.cost());
|
||||
signSection.set(locationString + ".playerId", sign.playerId().toString());
|
||||
}
|
||||
configuration.save(signsFile);
|
||||
}
|
||||
@ -142,8 +142,8 @@ public final class TrackedSignManager {
|
||||
if (!PaidSigns.getInstance().areRefundsEnabled() || !refund) {
|
||||
return;
|
||||
}
|
||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(trackedSign.getPlayerId());
|
||||
double refundSum = trackedSign.getCost() / 100 * PaidSigns.getInstance().getRefundPercentage();
|
||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(trackedSign.playerId());
|
||||
double refundSum = trackedSign.cost() / 100 * PaidSigns.getInstance().getRefundPercentage();
|
||||
EconomyManager.deposit(offlinePlayer, refundSum);
|
||||
if (offlinePlayer instanceof Player player) {
|
||||
player.sendMessage(String.format(StringFormatter.replacePlaceholders(
|
||||
|
Loading…
Reference in New Issue
Block a user