/* * This file is part of Skript. * * Skript 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. * * Skript 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 Skript. If not, see <http://www.gnu.org/licenses/>. * * * Copyright 2011-2014 Peter Güttinger * */ package ch.njol.skript.conditions; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import ch.njol.skript.classes.Comparator; import ch.njol.skript.classes.Comparator.Relation; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionList; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; import ch.njol.skript.registrations.Comparators; import ch.njol.skript.util.Patterns; import ch.njol.skript.util.Utils; import ch.njol.util.Checker; import ch.njol.util.Kleenean; /** * @author Peter Güttinger */ @Name("Comparison") @Description({"A very general condition, it simply compares two values. Usually you can only compare for equality (e.g. block is/isn't of <type>), " + "but some values can also be compared using greater than/less than. In that case you can also test for whether an object is between two others.", "Note: This is the only element where not all patterns are shown. It has actually another two sets of similar patters, " + "but with <code>(was|were)</code> or <code>will be</code> instead of <code>(is|are)</code> respectively, " + "which check different <a href='../expressions/#ExprTimeState'>time states</a> of the first expression."}) @Examples({"the clicked block is a stone slab or a double stone slab", "time in the player's world is greater than 8:00", "the creature is not an enderman or an ender dragon"}) @Since("1.0") public class CondCompare extends Condition { private final static Patterns<Relation> patterns = new Patterns<Relation>(new Object[][] { {"(1¦neither|) %objects% ((is|are)(|2¦(n't| not|4¦ neither)) ((greater|more|higher|bigger|larger) than|above)|\\>) %objects%", Relation.GREATER}, {"(1¦neither|) %objects% ((is|are)(|2¦(n't| not|4¦ neither)) (greater|more|higher|bigger|larger|above) [than] or (equal to|the same as)|\\>=) %objects%", Relation.GREATER_OR_EQUAL}, {"(1¦neither|) %objects% ((is|are)(|2¦(n't| not|4¦ neither)) ((less|smaller) than|below)|\\<) %objects%", Relation.SMALLER}, {"(1¦neither|) %objects% ((is|are)(|2¦(n't| not|4¦ neither)) (less|smaller|below) [than] or (equal to|the same as)|\\<=) %objects%", Relation.SMALLER_OR_EQUAL}, {"(1¦neither|) %objects% (2¦)((is|are) (not|4¦neither)|isn't|aren't|!=) [equal to] %objects%", Relation.EQUAL}, {"(1¦neither|) %objects% (is|are|=) [(equal to|the same as)] %objects%", Relation.EQUAL}, {"(1¦neither|) %objects% (is|are) between %objects% and %objects%", Relation.EQUAL}, {"(1¦neither|) %objects% (2¦)(is not|are not|isn't|aren't) between %objects% and %objects%", Relation.EQUAL}, {"(1¦neither|) %objects@-1% (was|were)(|2¦(n't| not|4¦ neither)) ((greater|more|higher|bigger|larger) than|above) %objects%", Relation.GREATER}, {"(1¦neither|) %objects@-1% (was|were)(|2¦(n't| not|4¦ neither)) (greater|more|higher|bigger|larger|above) [than] or (equal to|the same as) %objects%", Relation.GREATER_OR_EQUAL}, {"(1¦neither|) %objects@-1% (was|were)(|2¦(n't| not|4¦ neither)) ((less|smaller) than|below) %objects%", Relation.SMALLER}, {"(1¦neither|) %objects@-1% (was|were)(|2¦(n't| not|4¦ neither)) (less|smaller|below) [than] or (equal to|the same as) %objects%", Relation.SMALLER_OR_EQUAL}, {"(1¦neither|) %objects@-1% (2¦)((was|were) (not|4¦neither)|wasn't|weren't) [equal to] %objects%", Relation.EQUAL}, {"(1¦neither|) %objects@-1% (was|were) [(equal to|the same as)] %objects%", Relation.EQUAL}, {"(1¦neither|) %objects@-1% (was|were) between %objects% and %objects%", Relation.EQUAL}, {"(1¦neither|) %objects@-1% (2¦)(was not|were not|wasn't|weren't) between %objects% and %objects%", Relation.EQUAL}, {"(1¦neither|) %objects@1% (will be|2¦(will (not|4¦neither) be|won't be)) ((greater|more|higher|bigger|larger) than|above) %objects%", Relation.GREATER}, {"(1¦neither|) %objects@1% (will be|2¦(will (not|4¦neither) be|won't be)) (greater|more|higher|bigger|larger|above) [than] or (equal to|the same as) %objects%", Relation.GREATER_OR_EQUAL}, {"(1¦neither|) %objects@1% (will be|2¦(will (not|4¦neither) be|won't be)) ((less|smaller) than|below) %objects%", Relation.SMALLER}, {"(1¦neither|) %objects@1% (will be|2¦(will (not|4¦neither) be|won't be)) (less|smaller|below) [than] or (equal to|the same as) %objects%", Relation.SMALLER_OR_EQUAL}, {"(1¦neither|) %objects@1% (2¦)((will (not|4¦neither) be|won't be)|(isn't|aren't|is not|are not) (turning|changing) [in]to) [equal to] %objects%", Relation.EQUAL}, {"(1¦neither|) %objects@1% (will be [(equal to|the same as)]|(is|are) (turning|changing) [in]to) %objects%", Relation.EQUAL}, {"(1¦neither|) %objects@1% will be between %objects% and %objects%", Relation.EQUAL}, {"(1¦neither|) %objects@1% (2¦)(will not be|won't be) between %objects% and %objects%", Relation.EQUAL} }); static { Skript.registerCondition(CondCompare.class, patterns.getPatterns()); } @SuppressWarnings("null") private Expression<?> first; @SuppressWarnings("null") Expression<?> second; @Nullable Expression<?> third; @SuppressWarnings("null") Relation relation; @SuppressWarnings("rawtypes") @Nullable Comparator comp; @SuppressWarnings("null") @Override public boolean init(final Expression<?>[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { first = vars[0]; second = vars[1]; if (vars.length == 3) third = vars[2]; relation = patterns.getInfo(matchedPattern); if ((parser.mark & 0x2) != 0) // "not" somewhere in the condition setNegated(true); if ((parser.mark & 0x1) != 0) // "neither" on the left side setNegated(!isNegated()); if ((parser.mark & 0x4) != 0) {// "neither" on the right side if (second instanceof ExpressionList) ((ExpressionList<?>) second).invertAnd(); if (third instanceof ExpressionList) ((ExpressionList<?>) third).invertAnd(); } final boolean b = init(); final Expression<?> third = this.third; if (!b) { if (third == null && first.getReturnType() == Object.class && second.getReturnType() == Object.class) { return false; } else { Skript.error("Can't compare " + f(first) + " with " + f(second) + (third == null ? "" : " and " + f(third)), ErrorQuality.NOT_AN_EXPRESSION); return false; } } @SuppressWarnings("rawtypes") final Comparator comp = this.comp; if (comp != null) { if (third == null) { if (!relation.isEqualOrInverse() && !comp.supportsOrdering()) { Skript.error("Can't test " + f(first) + " for being '" + relation + "' " + f(second), ErrorQuality.NOT_AN_EXPRESSION); return false; } } else { if (!comp.supportsOrdering()) { Skript.error("Can't test " + f(first) + " for being 'between' " + f(second) + " and " + f(third), ErrorQuality.NOT_AN_EXPRESSION); return false; } } } return true; } public final static String f(final Expression<?> e) { if (e.getReturnType() == Object.class) return e.toString(null, false); return Classes.getSuperClassInfo(e.getReturnType()).getName().withIndefiniteArticle(); } @SuppressWarnings("unchecked") private boolean init() { final RetainingLogHandler log = SkriptLogger.startRetainingLog(); Expression<?> third = this.third; try { if (first.getReturnType() == Object.class) { final Expression<?> e = first.getConvertedExpression(Object.class); if (e == null) { log.printErrors(); return false; } first = e; } if (second.getReturnType() == Object.class) { final Expression<?> e = second.getConvertedExpression(Object.class); if (e == null) { log.printErrors(); return false; } second = e; } if (third != null && third.getReturnType() == Object.class) { final Expression<?> e = third.getConvertedExpression(Object.class); if (e == null) { log.printErrors(); return false; } this.third = third = e; } log.printLog(); } finally { log.stop(); } final Class<?> f = first.getReturnType(), s = third == null ? second.getReturnType() : Utils.getSuperType(second.getReturnType(), third.getReturnType()); if (f == Object.class || s == Object.class) return true; comp = Comparators.getComparator(f, s); return comp != null; } /* * # := condition (e.g. is, is less than, contains, is enchanted with, has permission, etc.) * !# := not # * * a and b # x === a # x && b # x * a or b # x === a # x || b # x * a # x and y === a # x && a # y * a # x or y === a # x || a # y * a and b # x and y === a # x and y && b # x and y === a # x && a # y && b # x && b # y * a and b # x or y === a # x or y && b # x or y * a or b # x and y === a # x and y || b # x and y * a or b # x or y === a # x or y || b # x or y * * * a and b !# x === a !# x && b !# x * neither a nor b # x === a !# x && b !# x // nor = and * a or b !# x === a !# x || b !# x * * a !# x and y === a !# x || a !# y // e.g. "player doesn't have 2 emeralds and 5 gold ingots" == "NOT(player has 2 emeralds and 5 gold ingots)" == "player doesn't have 2 emeralds OR player doesn't have 5 gold ingots" * a # neither x nor y === a !# x && a !# y // nor = or // e.g. "player has neither 2 emeralds nor 5 gold ingots" == "player doesn't have 2 emeralds AND player doesn't have 5 gold ingots" * a # neither x nor y === a !# x && a !# y // nor = or // e.g. "player is neither the attacker nor the victim" == "player is not the attacker AND player is not the victim" * a !# x or y === a !# x && a !# y // e.g. "player doesn't have 2 emeralds or 5 gold ingots" == "NOT(player has 2 emeralds or 5 gold ingots)" == "player doesn't have 2 emeralds AND player doesn't have 5 gold ingots" * * a and b !# x and y === a !# x and y && b !# x and y === (a !# x || a !# y) && (b !# x || b !# y) * a and b !# x or y === a !# x or y && b !# x or y * a and b # neither x nor y === a # neither x nor y && b # neither x nor y * * a or b !# x and y === a !# x and y || b !# x and y * a or b !# x or y === a !# x or y || b !# x or y * a or b # neither x nor y === a # neither x nor y || b # neither x nor y * * neither a nor b # x and y === a !# x and y && b !# x and y // nor = and * neither a nor b # x or y === a !# x or y && b !# x or y // nor = and */ @Override public boolean check(final Event e) { final Expression<?> third = this.third; return first.check(e, new Checker<Object>() { @Override public boolean check(final Object o1) { return second.check(e, new Checker<Object>() { @Override public boolean check(final Object o2) { if (third == null) return relation.is(comp != null ? comp.compare(o1, o2) : Comparators.compare(o1, o2)); return third.check(e, new Checker<Object>() { @Override public boolean check(final Object o3) { return relation == Relation.NOT_EQUAL ^ (Relation.GREATER_OR_EQUAL.is(comp != null ? comp.compare(o1, o2) : Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.is(comp != null ? comp.compare(o1, o3) : Comparators.compare(o1, o3))); } }); } }, isNegated()); } }); } @Override public String toString(final @Nullable Event e, final boolean debug) { String s; final Expression<?> third = this.third; if (third == null) s = first.toString(e, debug) + " is " + (isNegated() ? "not " : "") + relation + " " + second.toString(e, debug); else s = first.toString(e, debug) + " is " + (isNegated() ? "not " : "") + "between " + second.toString(e, debug) + " and " + third.toString(e, debug); if (debug) s += " (comparator: " + comp + ")"; return s; } }