Skip to content
Open
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
07025a1
Fix vehicle
TheLimeGlass Mar 11, 2024
5f9001e
Update src/main/java/ch/njol/skript/expressions/ExprVehicle.java
TheLimeGlass Mar 23, 2024
728cb8f
Re-add delays
TheLimeGlass Mar 23, 2024
0873fce
Merge branch 'fix/vehicle-register' of https://github.com/TheLimeGlas…
TheLimeGlass Mar 23, 2024
6949abd
Fix suggestion commit
TheLimeGlass Mar 23, 2024
2e00061
Merge branch 'dev/patch' into fix/vehicle-register
Moderocky Apr 8, 2024
a99038d
Time state changes
TheLimeGlass Jul 17, 2024
d84617b
Merge branch 'fix/vehicle-register' of https://github.com/TheLimeGlas…
TheLimeGlass Jul 17, 2024
a39e1b8
Avoid infinite mounting
TheLimeGlass Jul 17, 2024
e8999ec
Avoid infinite mounting
TheLimeGlass Jul 17, 2024
c404493
Better examples
TheLimeGlass Jul 17, 2024
9935ed1
Merge branch 'dev/feature' into fix/vehicle-register
Moderocky Aug 15, 2024
47b2a99
Update src/main/java/ch/njol/skript/expressions/ExprVehicle.java
TheLimeGlass Aug 18, 2024
306e3b9
Use Java 17
TheLimeGlass Aug 18, 2024
b4a49ee
Update to latest
TheLimeGlass Jul 29, 2025
07111a1
git conflicts
TheLimeGlass Jul 29, 2025
756381e
Update src/main/java/ch/njol/skript/expressions/ExprVehicle.java
TheLimeGlass Aug 11, 2025
aa6679a
Update src/main/java/ch/njol/skript/expressions/ExprVehicle.java
TheLimeGlass Aug 11, 2025
baad7e5
Requested changes
TheLimeGlass Aug 11, 2025
e10e431
Update src/main/java/ch/njol/skript/expressions/ExprVehicle.java
TheLimeGlass Aug 11, 2025
5131dc4
Update src/main/java/ch/njol/skript/expressions/ExprVehicle.java
TheLimeGlass Aug 11, 2025
c9a2355
requested changes
TheLimeGlass Aug 12, 2025
ecefec5
Merge branch 'fix/vehicle-register' of https://github.com/TheLimeGlas…
TheLimeGlass Aug 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 104 additions & 139 deletions src/main/java/ch/njol/skript/expressions/ExprVehicle.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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: <a href='#ExprPassenger'>passenger</a>"})
@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: <a href='#ExprPassenger'>passenger</a>"
})
@Example("""
set the vehicle of {game::players::*} to a saddled pig
give {game::players::*} a carrot on a stick
""")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
""")
""")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is not proper Java standards. Avoids the addition of a blank line.

Copy link
Member

@sovdeeth sovdeeth Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indentation of the block string is determined by the location of the left-most non-space character, OR the ending """. If you put the """ all the way to the left like that, you cause the example string to be indented by a tab. We don't want that, so the """ needs to be on the same column as the example text.
See https://www.baeldung.com/java-text-blocks#indentation for examples

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would you randomly put a new line break at the end. The existing annotations don't do this. This would essentially force a documentation page to check if the example ended with a new line break or not to differ between the annotations. Just keep it standardized and don't include a random new line break at the end of the returned string so the documentation page doesn't have to worry about varation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This formatting still contains a line break at the end. If you wanted no line break, you would write:

@Example("""
	set the vehicle of {game::players::*} to a saddled pig
	give {game::players::*} a carrot on a stick""")

which is pretty ugly.

The difference between

@Example("""
	set the vehicle of {game::players::*} to a saddled pig
	give {game::players::*} a carrot on a stick
""")

and

@Example("""
	set the vehicle of {game::players::*} to a saddled pig
	give {game::players::*} a carrot on a stick
	""")

is simply indentation, and the second is what we want. The first causes the lines to have a tab at the front, the second doesn't have tabs.

@Example("""
on vehicle enter:
vehicle is a horse
add 1 to {statistics::horseMounting::%uuid of player%}
""")
@Since("2.0")
public class ExprVehicle extends SimplePropertyExpression<Entity, Entity> {

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<Entity, Entity> {

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<? extends Entity> getReturnType() {
return Entity.class;
@SuppressWarnings("unchecked")
public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
setExpr((Expression<Entity>) 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<Entity> 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());
Comment on lines +102 to +123
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure about these checks within the loops, same reasoning as with #get
Would like thoughts from other team members

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<? extends Entity> getReturnType() {
return Entity.class;
}

@Override
public String toString(@Nullable Event event, boolean debug) {
return "vehicle of " + getExpr().toString(event, debug);
}

}