diff --git a/src/main/java/ch/njol/skript/expressions/ExprVehicle.java b/src/main/java/ch/njol/skript/expressions/ExprVehicle.java index b45da85eedd..443943b8f44 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVehicle.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVehicle.java @@ -1,9 +1,10 @@ package ch.njol.skript.expressions; +import java.util.function.Predicate; + import org.bukkit.entity.Entity; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityDismountEvent; -import org.bukkit.event.entity.EntityEvent; import org.bukkit.event.entity.EntityMountEvent; import org.bukkit.event.vehicle.VehicleEnterEvent; import org.bukkit.event.vehicle.VehicleExitEvent; @@ -12,175 +13,139 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.effects.Delay; import ch.njol.skript.entity.EntityData; -import ch.njol.skript.expressions.base.SimplePropertyExpression; -import ch.njol.util.coll.CollectionUtils; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.registrations.EventValues; +import ch.njol.util.Kleenean; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; +import org.bukkit.entity.Player; -/** - * @author Peter Güttinger - */ @Name("Vehicle") -@Description({"The vehicle an entity is in, if any. This can actually be any entity, e.g. spider jockeys are skeletons that ride on a spider, so the spider is the 'vehicle' of the skeleton.", - "See also: passenger"}) -@Examples({"vehicle of the player is a minecart"}) +@Description({ + "The vehicle an entity is in, if any.", + "This can actually be any entity, e.g. spider jockeys are skeletons that ride on a spider, so the spider is the 'vehicle' of the skeleton.", + "See also: passenger" +}) +@Example(""" + set the vehicle of {game::players::*} to a saddled pig + give {game::players::*} a carrot on a stick +""") +@Example(""" + on vehicle enter: + vehicle is a horse + add 1 to {statistics::horseMounting::%uuid of player%} +""") @Since("2.0") -public class ExprVehicle extends SimplePropertyExpression { - - private static final boolean HAS_NEW_MOUNT_EVENTS = Skript.classExists("org.bukkit.event.entity.EntityMountEvent"); - - private static final boolean HAS_OLD_MOUNT_EVENTS; - @Nullable - private static final Class OLD_MOUNT_EVENT_CLASS; - @Nullable - private static final MethodHandle OLD_GETMOUNT_HANDLE; - @Nullable - private static final Class OLD_DISMOUNT_EVENT_CLASS; - @Nullable - private static final MethodHandle OLD_GETDISMOUNTED_HANDLE; +public class ExprVehicle extends PropertyExpression { static { - register(ExprVehicle.class, Entity.class, "vehicle[s]", "entities"); - - // legacy support - boolean hasOldMountEvents = !HAS_NEW_MOUNT_EVENTS && - Skript.classExists("org.spigotmc.event.entity.EntityMountEvent"); - Class oldMountEventClass = null; - MethodHandle oldGetMountHandle = null; - Class oldDismountEventClass = null; - MethodHandle oldGetDismountedHandle = null; - if (hasOldMountEvents) { - try { - MethodHandles.Lookup lookup = MethodHandles.lookup(); - MethodType entityReturnType = MethodType.methodType(Entity.class); - // mount event - oldMountEventClass = Class.forName("org.spigotmc.event.entity.EntityMountEvent"); - oldGetMountHandle = lookup.findVirtual(oldMountEventClass, "getMount", entityReturnType); - // dismount event - oldDismountEventClass = Class.forName("org.spigotmc.event.entity.EntityDismountEvent"); - oldGetDismountedHandle = lookup.findVirtual(oldDismountEventClass, "getDismounted", entityReturnType); - } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException e) { - hasOldMountEvents = false; - oldMountEventClass = null; - oldGetMountHandle = null; - oldDismountEventClass = null; - oldGetDismountedHandle = null; - Skript.exception(e, "Failed to load old mount event support."); - } - } - HAS_OLD_MOUNT_EVENTS = hasOldMountEvents; - OLD_MOUNT_EVENT_CLASS = oldMountEventClass; - OLD_GETMOUNT_HANDLE = oldGetMountHandle; - OLD_DISMOUNT_EVENT_CLASS = oldDismountEventClass; - OLD_GETDISMOUNTED_HANDLE = oldGetDismountedHandle; - } - - @Override - protected Entity[] get(final Event e, final Entity[] source) { - return get(source, entity -> { - if (getTime() >= 0 && e instanceof VehicleEnterEvent && entity.equals(((VehicleEnterEvent) e).getEntered()) && !Delay.isDelayed(e)) { - return ((VehicleEnterEvent) e).getVehicle(); - } - if (getTime() >= 0 && e instanceof VehicleExitEvent && entity.equals(((VehicleExitEvent) e).getExited()) && !Delay.isDelayed(e)) { - return ((VehicleExitEvent) e).getVehicle(); - } - if ( - (HAS_OLD_MOUNT_EVENTS || HAS_NEW_MOUNT_EVENTS) - && getTime() >= 0 && !Delay.isDelayed(e) - && e instanceof EntityEvent && entity.equals(((EntityEvent) e).getEntity()) - ) { - if (HAS_NEW_MOUNT_EVENTS) { - if (e instanceof EntityMountEvent) - return ((EntityMountEvent) e).getMount(); - if (e instanceof EntityDismountEvent) - return ((EntityDismountEvent) e).getDismounted(); - } else { // legacy mount event support - try { - assert OLD_MOUNT_EVENT_CLASS != null; - if (OLD_MOUNT_EVENT_CLASS.isInstance(e)) { - assert OLD_GETMOUNT_HANDLE != null; - return (Entity) OLD_GETMOUNT_HANDLE.invoke(e); - } - assert OLD_DISMOUNT_EVENT_CLASS != null; - if (OLD_DISMOUNT_EVENT_CLASS.isInstance(e)) { - assert OLD_GETDISMOUNTED_HANDLE != null; - return (Entity) OLD_GETDISMOUNTED_HANDLE.invoke(e); - } - } catch (Throwable ex) { - Skript.exception(ex, "An error occurred while trying to invoke legacy mount event support."); - } - } - } - return entity.getVehicle(); - }); + if (Skript.classExists("org.bukkit.event.entity.EntityMountEvent")) + registerDefault(ExprVehicle.class, Entity.class, "vehicle[s]", "entities"); } - - @Override - @Nullable - public Entity convert(final Entity e) { - assert false; - return e.getVehicle(); - } - + @Override - public Class getReturnType() { - return Entity.class; + @SuppressWarnings("unchecked") + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr((Expression) expressions[0]); + return true; } - + @Override - protected String getPropertyName() { - return "vehicle"; + protected Entity[] get(Event event, Entity[] source) { + if (event instanceof EntityDismountEvent entityDismountEvent && getTime() != EventValues.TIME_FUTURE) { + return get(source, e -> e.equals(entityDismountEvent.getEntity()) ? entityDismountEvent.getDismounted() : e.getVehicle()); + } else if (event instanceof VehicleEnterEvent vehicleEnterEvent && getTime() != EventValues.TIME_PAST) { + return get(source, e -> e.equals(vehicleEnterEvent.getEntered()) ? vehicleEnterEvent.getVehicle() : e.getVehicle()); + } else if (event instanceof VehicleExitEvent vehicleExitEvent && getTime() != EventValues.TIME_FUTURE) { + return get(source, e -> e.equals(vehicleExitEvent.getExited()) ? vehicleExitEvent.getVehicle() : e.getVehicle()); + } else if (event instanceof EntityMountEvent entityMountEvent && getTime() != EventValues.TIME_PAST) { + return get(source, e -> e.equals(entityMountEvent.getEntity()) ? entityMountEvent.getMount() : e.getVehicle()); + } else { + return get(source, Entity::getVehicle); + } } - + @Override - @Nullable - public Class[] acceptChange(final ChangeMode mode) { + public Class @Nullable [] acceptChange(ChangeMode mode) { if (mode == ChangeMode.SET) { + if (isDefault() && getParser().isCurrentEvent(VehicleExitEvent.class, EntityDismountEvent.class)) { + Skript.error("Setting the vehicle during a dismount/exit vehicle event will create an infinite mounting loop."); + return null; + } return new Class[] {Entity.class, EntityData.class}; } return super.acceptChange(mode); } - + @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { if (mode == ChangeMode.SET) { - assert delta != null; - final Entity[] ps = getExpr().getArray(e); - if (ps.length == 0) + // The player can desync if setting an entity as it's currently mounting it. + // Remember that there can be other entity types aside from players, so only cancel this for players. + Predicate predicate = Player.class::isInstance; + if (event instanceof EntityMountEvent entityMountEvent && predicate.test(entityMountEvent.getEntity())) { + return; + } + if (event instanceof VehicleEnterEvent vehicleEnterEvent && predicate.test(vehicleEnterEvent.getEntered())) { + return; + } + Entity[] passengers = getExpr().getArray(event); + if (passengers.length == 0) return; - final Object o = delta[0]; - if (o instanceof Entity) { - ((Entity) o).eject(); - final Entity p = CollectionUtils.getRandom(ps); - assert p != null; - p.leaveVehicle(); - ((Entity) o).setPassenger(p); - } else if (o instanceof EntityData) { - for (final Entity p : ps) { - final Entity v = ((EntityData) o).spawn(p.getLocation()); - if (v == null) + assert delta != null; + Object object = delta[0]; + if (object instanceof Entity entity) { + entity.eject(); + for (Entity passenger : passengers) { + // Avoid infinity mounting + if (event instanceof VehicleExitEvent && predicate.test(passenger) && passenger.equals(((VehicleExitEvent) event).getExited())) + continue; + if (event instanceof EntityDismountEvent && predicate.test(passenger) && passenger.equals(((EntityDismountEvent) event).getEntity())) + continue; + assert passenger != null; + passenger.leaveVehicle(); + entity.addPassenger(passenger); + } + } else if (object instanceof EntityData entityData) { + VehicleExitEvent vehicleExitEvent = event instanceof VehicleExitEvent ? (VehicleExitEvent) event : null; + EntityDismountEvent entityDismountEvent = event instanceof EntityDismountEvent ? (EntityDismountEvent) event : null; + for (Entity passenger : passengers) { + // Avoid infinity mounting + if (vehicleExitEvent != null && predicate.test(passenger) && passenger.equals(vehicleExitEvent.getExited())) + continue; + if (entityDismountEvent != null && predicate.test(passenger) && passenger.equals(entityDismountEvent.getEntity())) + continue; + Entity vehicle = entityData.spawn(passenger.getLocation()); + if (vehicle == null) continue; - v.setPassenger(p); + vehicle.addPassenger(passenger); } } else { assert false; } } else { - super.change(e, delta, mode); + super.change(event, delta, mode); } } - - @SuppressWarnings("unchecked") + @Override - public boolean setTime(final int time) { - return super.setTime(time, getExpr(), VehicleEnterEvent.class, VehicleExitEvent.class); + public boolean setTime(int time) { + return super.setTime(time, getExpr(), VehicleEnterEvent.class, VehicleExitEvent.class, EntityMountEvent.class, EntityDismountEvent.class); } - + + @Override + public Class getReturnType() { + return Entity.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "vehicle of " + getExpr().toString(event, debug); + } + }