Select entities with a specific player as a passenger in a Minecraft command (1.15.1)

I need to use an entity selector in an /execute command to select all boats who have at least one player on board who is holding a specific item. However, because the id tag does not exist for players, no entity selectors arguments exist for selecting an entity with specific passengers, and I cannot select them by the type selector argument as passengers of an arbitrary entity can only be selected by their NBT data, I cannot select mounts with specific player passengers using an entity selector. Is there a workaround for this?


Solution 1:

Minecraft has no proper method to compare NBT of two entities. So you have to choose one of many possible workarounds, each has their own downsides.

The simplest method: Select a player that is at the expected coordinates:

/execute at @e[type=boat] positioned ~ ~-.45 ~ if entity @p[distance=..1,nbt={SelectedItem:{id:"minecraft:stone"}}] run <command>

This has several downsides:

  • It is lag and motion dependent. If you ride a boat quickly on blue ice and have a bit of lag, that command will not match you, even though you are sitting in the boat.
  • Someone standing on or next to the boat can get selected as well. To prevent this, you could reduce the radius, but that would amplify your lag dependency problem.
  • The exact offset depends on the vehicle, so you need to figure out a new offset if you want to do the same for e.g. minecarts.
  • It needs to be done three times, once for a single passenger, once for the first and once for the second passenger of a boat with two passengers. (Going backwards or forwards relative to the boat can be done with "local coordinates": ^ ^ ^)

The exact method: I couldn't find a way without either looping over both all boats and all players, so this needs to be done in a function.

First you store part of the UUID of every player's root vehicle in a scoreboard. You could do this with the entire UUID, if you want, but that is much more complicated (it might require binary search), so I will only show the method of storing part of it:

/execute as @a store result score @s vehicle run data get entity @s RootVehicle.Entity.UUIDMost 0.00000000023283064365386962890625

That scale factor is 2^-32, that ensures that every possible value of UUIDMost fits into the score by cutting off the lower half.

Even if you have one boat in every single block up to render distance 32, the probability to match the wrong one is just 0.025%, so this method should be fine.

Now do the same for the UUID of all boats, but on their own scores:

/execute as @e[type=boat] store result score @s vehicle run data get entity @s UUIDMost 0.00000000023283064365386962890625

Comparing and doing something for every boat that has the same score in this scoreboard as a player with certain properties is the complicated part.

Execute a function as and at every player holding the item (or matching whatever condition):

/execute as @a[nbt={SelectedItem:{id:"minecraft:stone"}}] at @s run function test:vehicle

It would also work without at, but if you use at, in almost all cases only a single boat needs to be checked.

The contents of that function:

function test:vehicle_loop
tag @e[type=boat] remove checked
say @e[type=boat,tag=found]
tag @e[type=boat] remove found

This function calls a function that loops over all boats and eventually comes up with a single boat tagged "found", then cleans up (which is important, if you want to run it for multiple players in one tick) and gives you an opportunity to do something with the boat (still as the player, in case that is important).

The contents of the function vehicle_loop:

execute if score @e[type=boat,tag=!checked,sort=nearest,limit=1] vehicle = @s vehicle run tag @e[type=boat,tag=!checked,sort=nearest,limit=1] add found
tag @e[type=boat,tag=!checked,sort=nearest,limit=1] add checked
execute unless entity @e[type=boat,tag=found] if entity @e[type=boat,tag=!checked] run function test:vehicle_loop

This function first checks if the vehicle score of the closest boat to the player that has not been checked yet is the same as the vehicle score of the player. If that happens, you have successfully found the ridden boat for that player, so it gets tagged with "found". Then the boat gets tagged with "checked", no matter if it's the right one or not. Then the function calls itself if there are still any boats left to check (this is how loops are done in functions).

Solution 2:

You cannot select an entity with specific passengers, but you can select an entity without specific passengers.

You can create named boats and select ones without a specific entity as passenger.

/summon minecraft:boat ~ ~ ~ {CustomName:"\"mcboat\"",Passengers:[{id:"minecraft:villager",CustomName:"\"boaty\""}]}
/summon minecraft:boat ~ ~ ~ {CustomName:"\"mcboat\""}
/kill @e[name="mcboat",nbt=!{Passengers:[{id:"minecraft:villager"}]}]

Unfortunately I can only get it to work by sorting passengers by id and not another tag like name. I am using this to make sure my riding villagers stay on their horse. Im using 1.14.4.