diff --git a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java index e5bb37b2c9a..bd2a602adb8 100644 --- a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java @@ -37,6 +37,7 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.localization.Noun; import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; @@ -136,6 +137,12 @@ public boolean init() { hasValue = getters.get(event) != null; continue; } + if (EventValues.hasMultipleGetters(event, c, getTime()) == Kleenean.TRUE) { + Noun typeName = Classes.getExactClassInfo(componentType).getName(); + log.printError("There are multiple " + typeName.toString(true) + " in " + Utils.a(getParser().getCurrentEventName()) + " event. " + + "You must define which " + typeName + " to use."); + return false; + } Getter getter; if (exact) { getter = EventValues.getExactEventValueGetter(event, c, getTime()); diff --git a/src/main/java/ch/njol/skript/registrations/EventValues.java b/src/main/java/ch/njol/skript/registrations/EventValues.java index 255dbfcb6a8..d6956f32ee1 100644 --- a/src/main/java/ch/njol/skript/registrations/EventValues.java +++ b/src/main/java/ch/njol/skript/registrations/EventValues.java @@ -31,6 +31,7 @@ import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.util.Getter; +import ch.njol.util.Kleenean; /** * @author Peter Güttinger @@ -136,46 +137,53 @@ public String getExcludeErrorMessage() { return futureEventValues; throw new IllegalArgumentException("time must be -1, 0, or 1"); } - + /** * Registers an event value. * - * @param e the event type - * @param c the type of the default value - * @param g the getter to get the value - * @param time -1 if this is the value before the event, 1 if after, and 0 if it's the default or this value doesn't have distinct states. + * @param event the event type class. + * @param type the return type of the getter for the event value. + * @param getter the getter to get the value with the provided event. + * @param time value of TIME_PAST if this is the value before the event, TIME_FUTURE if after, and TIME_NOW if it's the default or this value doesn't have distinct states. * Always register a default state! You can leave out one of the other states instead, e.g. only register a default and a past state. The future state will * default to the default state in this case. */ - public static void registerEventValue(Class e, Class c, Getter g, int time) { - registerEventValue(e, c, g, time, null, (Class[]) null); + public static void registerEventValue(Class event, Class type, Getter getter, int time) { + registerEventValue(event, type, getter, time, null, (Class[]) null); } - + /** - * Same as {@link #registerEventValue(Class, Class, Getter, int)} + * Registers an event value and with excluded events. + * Excluded events are events that this event value can't operate in. * - * @param e - * @param c - * @param g - * @param time -1 if this is the value before the event, 1 if after, and 0 if it's the default or this value doesn't have distinct states. + * @param event the event type class. + * @param type the return type of the getter for the event value. + * @param getter the getter to get the value with the provided event. + * @param time value of TIME_PAST if this is the value before the event, TIME_FUTURE if after, and TIME_NOW if it's the default or this value doesn't have distinct states. * Always register a default state! You can leave out one of the other states instead, e.g. only register a default and a past state. The future state will * default to the default state in this case. - * @param excludes Subclasses of the event for which this event value should not be registered for + * @param excludeErrorMessage The error message to display when used in the excluded events. + * @param excludes subclasses of the event for which this event value should not be registered for */ @SafeVarargs - public static void registerEventValue(Class e, Class c, Getter g, int time, @Nullable String excludeErrorMessage, @Nullable Class... excludes) { + public static void registerEventValue(Class event, Class type, Getter getter, int time, @Nullable String excludeErrorMessage, @Nullable Class... excludes) { Skript.checkAcceptRegistrations(); List> eventValues = getEventValuesList(time); for (int i = 0; i < eventValues.size(); i++) { EventValueInfo info = eventValues.get(i); - if (info.event != e ? info.event.isAssignableFrom(e) : info.c.isAssignableFrom(c)) { - eventValues.add(i, new EventValueInfo<>(e, c, g, excludeErrorMessage, excludes)); + // We don't care for exact duplicates. Prefer Skript's over any addon. + if (info.event.equals(event) && info.c.equals(type)) + return; + // If the events don't match, we prefer the highest subclass event. + // If the events match, we prefer the highest subclass type. + if (!info.event.equals(event) ? info.event.isAssignableFrom(event) : info.c.isAssignableFrom(type)) { + eventValues.add(i, new EventValueInfo<>(event, type, getter, excludeErrorMessage, excludes)); return; } } - eventValues.add(new EventValueInfo<>(e, c, g, excludeErrorMessage, excludes)); + eventValues.add(new EventValueInfo<>(event, type, getter, excludeErrorMessage, excludes)); } - + /** * Gets a specific value from an event. Returns null if the event doesn't have such a value (conversions are done to try and get the desired value). *

@@ -225,42 +233,74 @@ public static T getEventValue(E e, Class c, int time) { return null; } + /** + * Checks if an event has multiple getters, including default ones. + * + * @param event the event class the getter will be getting from. + * @param type type of getter. + * @param time the event-value's time. + * @return true or false if the event and type have multiple getters. + */ + public static Kleenean hasMultipleGetters(Class event, Class type, int time) { + List> getters = getEventValueGetters(event, type, time, true); + if (getters == null) + return Kleenean.UNKNOWN; + return Kleenean.get(getters.size() > 1); + } + /** * Returns a getter to get a value from in an event. *

* Can print an error if the event value is blocked for the given event. * - * @param event the event class the getter will be getting from - * @param c type of getter - * @param time the event-value's time - * @return A getter to get values for a given type of events + * @param event the event class the getter will be getting from. + * @param type type of getter. + * @param time the event-value's time. + * @return A getter to get values for a given type of events. * @see #registerEventValue(Class, Class, Getter, int) * @see EventValueExpression#EventValueExpression(Class) */ @Nullable - public static Getter getEventValueGetter(Class event, Class c, int time) { - return getEventValueGetter(event, c, time, true); + public static Getter getEventValueGetter(Class event, Class type, int time) { + return getEventValueGetter(event, type, time, true); + } + + @Nullable + private static Getter getEventValueGetter(Class event, Class type, int time, boolean allowDefault) { + List> list = getEventValueGetters(event, type, time, allowDefault); + if (list == null || list.isEmpty()) + return null; + return list.get(0); } + /* + * We need to be able to collect all possible event-values to a list for determining problematic collisions. + * Always return after the loop check if the list is not empty. + */ @Nullable @SuppressWarnings("unchecked") - private static Getter getEventValueGetter(Class event, Class c, int time, boolean allowDefault) { + private static List> getEventValueGetters(Class event, Class type, int time, boolean allowDefault) { List> eventValues = getEventValuesList(time); + List> list = new ArrayList<>(); // First check for exact classes matching the parameters. - Getter exact = (Getter) getExactEventValueGetter(event, c, time); - if (exact != null) - return exact; + Getter exact = (Getter) getExactEventValueGetter(event, type, time); + if (exact != null) { + list.add(exact); + return list; + } // Second check for assignable subclasses. for (EventValueInfo eventValueInfo : eventValues) { - if (!c.isAssignableFrom(eventValueInfo.c)) + if (!type.isAssignableFrom(eventValueInfo.c)) continue; if (!checkExcludes(eventValueInfo, event)) return null; - if (eventValueInfo.event.isAssignableFrom(event)) - return (Getter) eventValueInfo.getter; + if (eventValueInfo.event.isAssignableFrom(event)) { + list.add((Getter) eventValueInfo.getter); + continue; + } if (!event.isAssignableFrom(eventValueInfo.event)) continue; - return new Getter() { + list.add(new Getter() { @Override @Nullable public T get(E event) { @@ -268,67 +308,80 @@ public T get(E event) { return null; return ((Getter) eventValueInfo.getter).get(event); } - }; + }); + continue; } + if (!list.isEmpty()) + return list; // Most checks have returned before this below is called, but Skript will attempt to convert or find an alternative. // Third check is if the returned object matches the class. for (EventValueInfo eventValueInfo : eventValues) { - if (!eventValueInfo.c.isAssignableFrom(c)) + if (!eventValueInfo.c.isAssignableFrom(type)) continue; boolean checkInstanceOf = !eventValueInfo.event.isAssignableFrom(event); if (checkInstanceOf && !event.isAssignableFrom(eventValueInfo.event)) continue; if (!checkExcludes(eventValueInfo, event)) return null; - return new Getter() { + list.add(new Getter() { @Override @Nullable public T get(E event) { if (checkInstanceOf && !eventValueInfo.event.isInstance(event)) return null; Object object = ((Getter) eventValueInfo.getter).get(event); - if (c.isInstance(object)) + if (type.isInstance(object)) return (T) object; return null; } - }; + }); + continue; } + if (!list.isEmpty()) + return list; // Fourth check will attempt to convert the event value to the requesting type. // This first for loop will check that the events are exact. See issue #5016 for (EventValueInfo eventValueInfo : eventValues) { if (!event.equals(eventValueInfo.event)) continue; - Getter getter = (Getter) getConvertedGetter(eventValueInfo, c, false); + Getter getter = (Getter) getConvertedGetter(eventValueInfo, type, false); if (getter == null) continue; if (!checkExcludes(eventValueInfo, event)) return null; - return getter; + list.add(getter); + continue; } + if (!list.isEmpty()) + return list; // This loop will attempt to look for converters assignable to the class of the provided event. for (EventValueInfo eventValueInfo : eventValues) { // The requesting event must be assignable to the event value's event. Otherwise it'll throw an error. if (!event.isAssignableFrom(eventValueInfo.event)) continue; - Getter getter = (Getter) getConvertedGetter(eventValueInfo, c, true); + Getter getter = (Getter) getConvertedGetter(eventValueInfo, type, true); if (getter == null) continue; if (!checkExcludes(eventValueInfo, event)) return null; - return getter; + list.add(getter); + continue; } + if (!list.isEmpty()) + return list; // If the check should try again matching event values with a 0 time (most event values). if (allowDefault && time != 0) - return getEventValueGetter(event, c, 0, false); + return getEventValueGetters(event, type, 0, false); return null; } /** * Check if the event value states to exclude events. + * False if the current EventValueInfo cannot operate in the provided event. * * @param info The event value info that will be used to grab the value from * @param event The event class to check the excludes against. diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 4d0bfe3dff3..cd51c2e1efe 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1987,7 +1987,7 @@ types: inventory: inventor¦y¦ies @an player: player¦s @a offlineplayer: offline player¦s @a - commandsender: player¦¦s¦/console @a + commandsender: command sender¦s @a inventoryholder: inventory holder¦s @an gamemode: gamemode¦s @a material: material¦s @a