|
1 | 1 | package ch.njol.skript.expressions; |
2 | 2 |
|
| 3 | +import java.util.function.Predicate; |
| 4 | + |
3 | 5 | import org.bukkit.entity.Entity; |
4 | 6 | import org.bukkit.event.Event; |
5 | 7 | import org.bukkit.event.entity.EntityDismountEvent; |
6 | | -import org.bukkit.event.entity.EntityEvent; |
7 | 8 | import org.bukkit.event.entity.EntityMountEvent; |
8 | 9 | import org.bukkit.event.vehicle.VehicleEnterEvent; |
9 | 10 | import org.bukkit.event.vehicle.VehicleExitEvent; |
|
12 | 13 | import ch.njol.skript.Skript; |
13 | 14 | import ch.njol.skript.classes.Changer.ChangeMode; |
14 | 15 | import ch.njol.skript.doc.Description; |
15 | | -import ch.njol.skript.doc.Examples; |
| 16 | +import ch.njol.skript.doc.Example; |
16 | 17 | import ch.njol.skript.doc.Name; |
17 | 18 | import ch.njol.skript.doc.Since; |
18 | | -import ch.njol.skript.effects.Delay; |
19 | 19 | import ch.njol.skript.entity.EntityData; |
20 | | -import ch.njol.skript.expressions.base.SimplePropertyExpression; |
21 | | -import ch.njol.util.coll.CollectionUtils; |
| 20 | +import ch.njol.skript.expressions.base.PropertyExpression; |
| 21 | +import ch.njol.skript.lang.Expression; |
| 22 | +import ch.njol.skript.lang.SkriptParser.ParseResult; |
| 23 | +import ch.njol.skript.registrations.EventValues; |
| 24 | +import ch.njol.util.Kleenean; |
22 | 25 |
|
23 | | -import java.lang.invoke.MethodHandle; |
24 | | -import java.lang.invoke.MethodHandles; |
25 | | -import java.lang.invoke.MethodType; |
| 26 | +import org.bukkit.entity.Player; |
26 | 27 |
|
27 | | -/** |
28 | | - * @author Peter Güttinger |
29 | | - */ |
30 | 28 | @Name("Vehicle") |
31 | | -@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.", |
32 | | - "See also: <a href='#ExprPassenger'>passenger</a>"}) |
33 | | -@Examples({"vehicle of the player is a minecart"}) |
| 29 | +@Description({ |
| 30 | + "The vehicle an entity is in, if any.", |
| 31 | + "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.", |
| 32 | + "See also: <a href='#ExprPassenger'>passenger</a>" |
| 33 | +}) |
| 34 | +@Example(""" |
| 35 | + set the vehicle of {game::players::*} to a saddled pig |
| 36 | + give {game::players::*} a carrot on a stick |
| 37 | + """) |
| 38 | +@Example(""" |
| 39 | + on vehicle enter: |
| 40 | + vehicle is a horse |
| 41 | + add 1 to {statistics::horseMounting::%uuid of player%} |
| 42 | + """) |
34 | 43 | @Since("2.0") |
35 | | -public class ExprVehicle extends SimplePropertyExpression<Entity, Entity> { |
36 | | - |
37 | | - private static final boolean HAS_NEW_MOUNT_EVENTS = Skript.classExists("org.bukkit.event.entity.EntityMountEvent"); |
38 | | - |
39 | | - private static final boolean HAS_OLD_MOUNT_EVENTS; |
40 | | - @Nullable |
41 | | - private static final Class<?> OLD_MOUNT_EVENT_CLASS; |
42 | | - @Nullable |
43 | | - private static final MethodHandle OLD_GETMOUNT_HANDLE; |
44 | | - @Nullable |
45 | | - private static final Class<?> OLD_DISMOUNT_EVENT_CLASS; |
46 | | - @Nullable |
47 | | - private static final MethodHandle OLD_GETDISMOUNTED_HANDLE; |
| 44 | +public class ExprVehicle extends PropertyExpression<Entity, Entity> { |
48 | 45 |
|
49 | 46 | static { |
50 | | - register(ExprVehicle.class, Entity.class, "vehicle[s]", "entities"); |
51 | | - |
52 | | - // legacy support |
53 | | - boolean hasOldMountEvents = !HAS_NEW_MOUNT_EVENTS && |
54 | | - Skript.classExists("org.spigotmc.event.entity.EntityMountEvent"); |
55 | | - Class<?> oldMountEventClass = null; |
56 | | - MethodHandle oldGetMountHandle = null; |
57 | | - Class<?> oldDismountEventClass = null; |
58 | | - MethodHandle oldGetDismountedHandle = null; |
59 | | - if (hasOldMountEvents) { |
60 | | - try { |
61 | | - MethodHandles.Lookup lookup = MethodHandles.lookup(); |
62 | | - MethodType entityReturnType = MethodType.methodType(Entity.class); |
63 | | - // mount event |
64 | | - oldMountEventClass = Class.forName("org.spigotmc.event.entity.EntityMountEvent"); |
65 | | - oldGetMountHandle = lookup.findVirtual(oldMountEventClass, "getMount", entityReturnType); |
66 | | - // dismount event |
67 | | - oldDismountEventClass = Class.forName("org.spigotmc.event.entity.EntityDismountEvent"); |
68 | | - oldGetDismountedHandle = lookup.findVirtual(oldDismountEventClass, "getDismounted", entityReturnType); |
69 | | - } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException e) { |
70 | | - hasOldMountEvents = false; |
71 | | - oldMountEventClass = null; |
72 | | - oldGetMountHandle = null; |
73 | | - oldDismountEventClass = null; |
74 | | - oldGetDismountedHandle = null; |
75 | | - Skript.exception(e, "Failed to load old mount event support."); |
76 | | - } |
77 | | - } |
78 | | - HAS_OLD_MOUNT_EVENTS = hasOldMountEvents; |
79 | | - OLD_MOUNT_EVENT_CLASS = oldMountEventClass; |
80 | | - OLD_GETMOUNT_HANDLE = oldGetMountHandle; |
81 | | - OLD_DISMOUNT_EVENT_CLASS = oldDismountEventClass; |
82 | | - OLD_GETDISMOUNTED_HANDLE = oldGetDismountedHandle; |
83 | | - } |
84 | | - |
85 | | - @Override |
86 | | - protected Entity[] get(final Event e, final Entity[] source) { |
87 | | - return get(source, entity -> { |
88 | | - if (getTime() >= 0 && e instanceof VehicleEnterEvent && entity.equals(((VehicleEnterEvent) e).getEntered()) && !Delay.isDelayed(e)) { |
89 | | - return ((VehicleEnterEvent) e).getVehicle(); |
90 | | - } |
91 | | - if (getTime() >= 0 && e instanceof VehicleExitEvent && entity.equals(((VehicleExitEvent) e).getExited()) && !Delay.isDelayed(e)) { |
92 | | - return ((VehicleExitEvent) e).getVehicle(); |
93 | | - } |
94 | | - if ( |
95 | | - (HAS_OLD_MOUNT_EVENTS || HAS_NEW_MOUNT_EVENTS) |
96 | | - && getTime() >= 0 && !Delay.isDelayed(e) |
97 | | - && e instanceof EntityEvent && entity.equals(((EntityEvent) e).getEntity()) |
98 | | - ) { |
99 | | - if (HAS_NEW_MOUNT_EVENTS) { |
100 | | - if (e instanceof EntityMountEvent) |
101 | | - return ((EntityMountEvent) e).getMount(); |
102 | | - if (e instanceof EntityDismountEvent) |
103 | | - return ((EntityDismountEvent) e).getDismounted(); |
104 | | - } else { // legacy mount event support |
105 | | - try { |
106 | | - assert OLD_MOUNT_EVENT_CLASS != null; |
107 | | - if (OLD_MOUNT_EVENT_CLASS.isInstance(e)) { |
108 | | - assert OLD_GETMOUNT_HANDLE != null; |
109 | | - return (Entity) OLD_GETMOUNT_HANDLE.invoke(e); |
110 | | - } |
111 | | - assert OLD_DISMOUNT_EVENT_CLASS != null; |
112 | | - if (OLD_DISMOUNT_EVENT_CLASS.isInstance(e)) { |
113 | | - assert OLD_GETDISMOUNTED_HANDLE != null; |
114 | | - return (Entity) OLD_GETDISMOUNTED_HANDLE.invoke(e); |
115 | | - } |
116 | | - } catch (Throwable ex) { |
117 | | - Skript.exception(ex, "An error occurred while trying to invoke legacy mount event support."); |
118 | | - } |
119 | | - } |
120 | | - } |
121 | | - return entity.getVehicle(); |
122 | | - }); |
| 47 | + if (Skript.classExists("org.bukkit.event.entity.EntityMountEvent")) |
| 48 | + registerDefault(ExprVehicle.class, Entity.class, "vehicle[s]", "entities"); |
123 | 49 | } |
124 | | - |
125 | | - @Override |
126 | | - @Nullable |
127 | | - public Entity convert(final Entity e) { |
128 | | - assert false; |
129 | | - return e.getVehicle(); |
130 | | - } |
131 | | - |
| 50 | + |
132 | 51 | @Override |
133 | | - public Class<? extends Entity> getReturnType() { |
134 | | - return Entity.class; |
| 52 | + @SuppressWarnings("unchecked") |
| 53 | + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { |
| 54 | + setExpr((Expression<Entity>) expressions[0]); |
| 55 | + return true; |
135 | 56 | } |
136 | | - |
| 57 | + |
137 | 58 | @Override |
138 | | - protected String getPropertyName() { |
139 | | - return "vehicle"; |
| 59 | + protected Entity[] get(Event event, Entity[] source) { |
| 60 | + if (event instanceof EntityDismountEvent entityDismountEvent && getTime() != EventValues.TIME_FUTURE) { |
| 61 | + return get(source, e -> e.equals(entityDismountEvent.getEntity()) ? entityDismountEvent.getDismounted() : e.getVehicle()); |
| 62 | + } else if (event instanceof VehicleEnterEvent vehicleEnterEvent && getTime() != EventValues.TIME_PAST) { |
| 63 | + return get(source, e -> e.equals(vehicleEnterEvent.getEntered()) ? vehicleEnterEvent.getVehicle() : e.getVehicle()); |
| 64 | + } else if (event instanceof VehicleExitEvent vehicleExitEvent && getTime() != EventValues.TIME_FUTURE) { |
| 65 | + return get(source, e -> e.equals(vehicleExitEvent.getExited()) ? vehicleExitEvent.getVehicle() : e.getVehicle()); |
| 66 | + } else if (event instanceof EntityMountEvent entityMountEvent && getTime() != EventValues.TIME_PAST) { |
| 67 | + return get(source, e -> e.equals(entityMountEvent.getEntity()) ? entityMountEvent.getMount() : e.getVehicle()); |
| 68 | + } else { |
| 69 | + return get(source, Entity::getVehicle); |
| 70 | + } |
140 | 71 | } |
141 | | - |
| 72 | + |
142 | 73 | @Override |
143 | | - @Nullable |
144 | | - public Class<?>[] acceptChange(final ChangeMode mode) { |
| 74 | + public Class<?> @Nullable [] acceptChange(ChangeMode mode) { |
145 | 75 | if (mode == ChangeMode.SET) { |
| 76 | + if (isDefault() && getParser().isCurrentEvent(VehicleExitEvent.class, EntityDismountEvent.class)) { |
| 77 | + Skript.error("Setting the vehicle during a dismount/exit vehicle event will create an infinite mounting loop."); |
| 78 | + return null; |
| 79 | + } |
146 | 80 | return new Class[] {Entity.class, EntityData.class}; |
147 | 81 | } |
148 | 82 | return super.acceptChange(mode); |
149 | 83 | } |
150 | | - |
| 84 | + |
151 | 85 | @Override |
152 | | - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { |
| 86 | + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { |
153 | 87 | if (mode == ChangeMode.SET) { |
154 | | - assert delta != null; |
155 | | - final Entity[] ps = getExpr().getArray(e); |
156 | | - if (ps.length == 0) |
| 88 | + // The player can desync if setting an entity as it's currently mounting it. |
| 89 | + // Remember that there can be other entity types aside from players, so only cancel this for players. |
| 90 | + Predicate<Entity> predicate = Player.class::isInstance; |
| 91 | + if (event instanceof EntityMountEvent entityMountEvent && predicate.test(entityMountEvent.getEntity())) { |
| 92 | + return; |
| 93 | + } |
| 94 | + if (event instanceof VehicleEnterEvent vehicleEnterEvent && predicate.test(vehicleEnterEvent.getEntered())) { |
| 95 | + return; |
| 96 | + } |
| 97 | + Entity[] passengers = getExpr().getArray(event); |
| 98 | + if (passengers.length == 0) |
157 | 99 | return; |
158 | | - final Object o = delta[0]; |
159 | | - if (o instanceof Entity) { |
160 | | - ((Entity) o).eject(); |
161 | | - final Entity p = CollectionUtils.getRandom(ps); |
162 | | - assert p != null; |
163 | | - p.leaveVehicle(); |
164 | | - ((Entity) o).setPassenger(p); |
165 | | - } else if (o instanceof EntityData) { |
166 | | - for (final Entity p : ps) { |
167 | | - final Entity v = ((EntityData<?>) o).spawn(p.getLocation()); |
168 | | - if (v == null) |
| 100 | + assert delta != null; |
| 101 | + Object object = delta[0]; |
| 102 | + if (object instanceof Entity entity) { |
| 103 | + entity.eject(); |
| 104 | + for (Entity passenger : passengers) { |
| 105 | + // Avoid infinity mounting |
| 106 | + if (event instanceof VehicleExitEvent && predicate.test(passenger) && passenger.equals(((VehicleExitEvent) event).getExited())) |
| 107 | + continue; |
| 108 | + if (event instanceof EntityDismountEvent && predicate.test(passenger) && passenger.equals(((EntityDismountEvent) event).getEntity())) |
| 109 | + continue; |
| 110 | + assert passenger != null; |
| 111 | + passenger.leaveVehicle(); |
| 112 | + entity.addPassenger(passenger); |
| 113 | + } |
| 114 | + } else if (object instanceof EntityData entityData) { |
| 115 | + VehicleExitEvent vehicleExitEvent = event instanceof VehicleExitEvent ? (VehicleExitEvent) event : null; |
| 116 | + EntityDismountEvent entityDismountEvent = event instanceof EntityDismountEvent ? (EntityDismountEvent) event : null; |
| 117 | + for (Entity passenger : passengers) { |
| 118 | + // Avoid infinity mounting |
| 119 | + if (vehicleExitEvent != null && predicate.test(passenger) && passenger.equals(vehicleExitEvent.getExited())) |
| 120 | + continue; |
| 121 | + if (entityDismountEvent != null && predicate.test(passenger) && passenger.equals(entityDismountEvent.getEntity())) |
| 122 | + continue; |
| 123 | + Entity vehicle = entityData.spawn(passenger.getLocation()); |
| 124 | + if (vehicle == null) |
169 | 125 | continue; |
170 | | - v.setPassenger(p); |
| 126 | + vehicle.addPassenger(passenger); |
171 | 127 | } |
172 | 128 | } else { |
173 | 129 | assert false; |
174 | 130 | } |
175 | 131 | } else { |
176 | | - super.change(e, delta, mode); |
| 132 | + super.change(event, delta, mode); |
177 | 133 | } |
178 | 134 | } |
179 | | - |
180 | | - @SuppressWarnings("unchecked") |
| 135 | + |
181 | 136 | @Override |
182 | | - public boolean setTime(final int time) { |
183 | | - return super.setTime(time, getExpr(), VehicleEnterEvent.class, VehicleExitEvent.class); |
| 137 | + public boolean setTime(int time) { |
| 138 | + return super.setTime(time, getExpr(), VehicleEnterEvent.class, VehicleExitEvent.class, EntityMountEvent.class, EntityDismountEvent.class); |
184 | 139 | } |
185 | | - |
| 140 | + |
| 141 | + @Override |
| 142 | + public Class<? extends Entity> getReturnType() { |
| 143 | + return Entity.class; |
| 144 | + } |
| 145 | + |
| 146 | + @Override |
| 147 | + public String toString(@Nullable Event event, boolean debug) { |
| 148 | + return "vehicle of " + getExpr().toString(event, debug); |
| 149 | + } |
| 150 | + |
186 | 151 | } |
0 commit comments