I am trying to make a snowball that triggers a command when it hits a player in 1.15, but I don't know how to do that. When I do execute at @e[type=snowball] run gamemode spectator @a[distance=..2]
it only runs the command if it goes right next to them.
-
If you want a 1.16 compatible version, the question should be edited to not ask for a 1.15 answer as that doesn’t make sense and as a reader in the future will be confusing. It should ask for a 1.15 and 1.16 version– PenguinCommented Nov 21, 2020 at 5:41
-
@Wipqozn consider this from FabianRöling: “ ...1. No new answer is given during the bounty period, then I automatically get it without doing anything. 2. A new answer is given that works in 1.16, but not 1.15, then it gets downvoted and/or removed, because it doesn't answer the question. Then it does not get the bounty and I get it again. 3. A new answer is given that includes one part about 1.15 and one part about 1.16 and the 1.16 part gets edited out because it doesn't answer the question. Then the bounty target can be chosen, but the question in the bounty isn't answered.”– PenguinCommented Nov 23, 2020 at 19:20
-
@Penguin It might be worth raising a new meta question regarding version-specific questions, as the current consensus is to have one question, with answers to each version. (As an example of one that I edited recently to handle this - How do I respawn the Ender Dragon?)– Robotnik ♦Commented Nov 23, 2020 at 22:24
1 Answer
Detecting where a snowball hit a player or block is tricky. You can't do something at the position of something that doesn't exist anymore, a snowball never gets the OnGround
tag, the view direction of a snowball is completely unrelated to its flight direction and not even the advancement trigger entity_hurt_player
works for it.
So what you need to do instead is moving a dummy entity with it, keep checking if there is still a snowball nearby and if not, assume that it has hit something or someone. You can for example do that like this (all commands executed every tick):
/execute as @e[type=armor_stand,tag=tracker] at @s run tp @s @e[type=snowball,tag=tracked,distance=..2,sort=nearest,limit=1]
/execute at @e[type=snowball,tag=!tracked] run summon armor_stand ~ ~ ~ {Tags:["tracker"],Marker:1,NoAI:1,NoGravity:1,Invisible:1}
/tag @e[type=snowball,tag=!tracked] add tracked
/execute at @e[type=armor_stand,tag=tracker] unless entity @e[type=snowball,distance=..2] run …
/execute as @e[type=armor_stand,tag=tracker] unless entity @e[type=snowball,distance=..2] run kill @s
Explanation:
It's easiest to explain this system in a different order than it is there, because that order is made for performance and responsiveness.
The second command creates a dummy armour stand at every snowball that doesn't have one yet.
The third command then tags that snowball as one that already got its dummy armour stand.
The first command keeps teleporting the armour stand to the snowball, as long as the snowball still exists.
The fourth command is the one you actually care about: When the armour stand can't find a snowball nearby anymore, we assume that that's because it was broken by the ground, a player, etc. You can execute whatever command here and it will happen at the last place that the snowball existed at (so still in air, not exactly at the block).
The fifth command then removes the armour stand so that you don't have lots of useless armour stands lingering around.
Issues and improvements
- In my tests the radius of 2m always included the snowball, even if it was falling straight down. But maybe there can be cases that cause it to fly faster (for example with the help of explosions) or you also want to be able to teleport the snowball around. You could link the snowball and armour stand together more thoroughly using IDs. That can either be a custom ID system using scoreboards (tutorials for that can be found online) or using the UUID of the snowball. In 1.16+ you can compare the entire UUID relatively easily, in 1.15- you need to scale it down to get parts of it or you can just hope that you don't get any UUID collisions if you only compare the lower half of the number (because the UUID is too long to be stored in a scoreboard, so you get an overflow, which should still usually give you the same result only if the original number was the same, but it's not guaranteed).
- The snowball could also disappear because it fell into the void, got removed with
/kill
or because it flew into unloaded chunks. In those cases you will probably not want to do something at that position. There is no good way to catch all of these cases, so you'll have to handle them all individually, if you want to be that precise. - If you want to be more precise with the location of where the snowball hit (instead of being one tick behind), you can try removing
Marker
,NoAI
andNoGravity
from the armour stand and also copying theMotion
tag from the snowball to the armour stand. In my tests that had promising results, but I am not sure if it will always work well. And of course you need to delay executing your command and killing the armour stand by one tick then, but that can easily be done by moving the fourth and fifth command to the start of the chain.
If this doesn't work, you can try moving the armour stand "manually" (with teleportation) according to the snowball'sMotion
tag. You could also try half of that, to get the average location of the range where it could have hit something. Or you could even do a fancy block detection with raycasting./tp
with afacing
argument is probably helpful for rotating something from one position to the next in that case. - You could also consider alternatives to snowballs. For example arrows have a
damage
tag that you could set to0
, they stick around after they have landed, they triggerentity_hurt_player
and they have a booleaninGround
tag and a complexinBlockState
tag that can be used for many things.
-
If this is not working for anyone, add
at @s
in the 4th command after theas @e[...]
statement. (/execute as @e[type=armor_stand,tag=tracker] at @s unless entity @e[type=snowball,distance=..2] run kill @s
)– JackCommented Aug 5, 2020 at 21:02 -
An update may have broken this answer, indicated by the bounty. Please check if it still works. Commented Nov 21, 2020 at 5:29
-
Then the bounty was put onto the wrong question. Also, not much changed with commands in 1.16, it should still work. Commented Nov 21, 2020 at 14:46