2

My brain refuses to explain a problem without context, so please bear with me.

I'm attempting to make a PVP map, but I want there to be a specific and custom "death message" for arrows being "hit" because there are multiple different arrows I intend on having (custom tipped arrows); e.g. I have an arrow that should be an instant kill that I want to have announce *target* was sniped by *source*. Because I have showDeathMessages disabled for this and other ways of dying, and I also want there to be a specific set of commands to run post-death, I can't simply kill the target with the arrow and let Minecraft's main death messages handle it.

Here's the actual problem. In order to do this while still keeping it accurate to the target's health, I want to calculate the speed of an arrow and, provided it causes the damage of the arrow to be equal to or higher than said health, run a command; in this case, I want to teleport the target back to spawn.

As of this moment in time. I already have the following set up;

  • A scoreboard (uuid) set up to find and match UUIDs from an Arrow's Owner[0] to a player's UUID[0] -- this was made assuming that the first object in the IntArray will always be unique (due in part to UUID's meaning);
  • A tag for it having been found, named "ping";
  • A command chain running every tick to find, compare, and match said UUIDs;
  • Scoreboards (vx, vy, and vz) set up to find and store an Arrow's three Motion tags;
  • Command chains running every tick to do the above, provided the Arrow isn't in the ground

The primary issue is that the command (below) I'm attempting to use to determine a "hit" isn't doing anything. For this, I'm assuming a fully charged bow and that an arrow needs to be less than half a block near the target before Minecraft actually recognizes it as a hit;

execute as @e[tag=ping,scores={vx=2..}] at @s if entity @p[distance=..0.5,scores={health=..6}] unless score @p uuid = @s uuid run tp @p 127 179 8

What I understand the command to be doing should be;

  • As every entity with the tag ping whose vx score is 2 or larger,
    • ...if the nearest player is within .5 blocks and has <=6 health,
    • ...unless that player has the same UUID as the Owner tag of the entity,
  • teleport the player to the coordinates.

Instead, the arrow simply hits and damages the player regardless of health, and thus so will kill the player before they teleport anywhere. Since I want other commands and death messages to run and be displayed after teleporting, I can't let it just kill them.

Is the syntax of the command wrong? I thought it was a problem with the distance selector (at if entity @p [distance=..0.5]...), but even after setting that to ..1 instead of ..0.5, it still did nothing.

1 Answer 1

2
+50

Issues with your command

In your question, you are attempting to detect when a player is about to be hit by an arrow. I assume you are doing this because you want to maintain the Owner of the arrow, a.k.a who shot it. This will then be used in your death message.

To do this, you calculate its speed and then detect when it is within half a block distance. But as you said, it did not work. Here are some possible reasons your command failed:

Issue 1: random damage

An arrow's speed determine the damage it inflicts. When fired from a fully-charged unenchanted bow, arrows do 6(♥♥♥) of damage, with a smaller chance to damage for up to 11(♥ × 5.5) via critical hits. They inflict up to 5(♥♥♥) damage from a medium-charged bow, and 1(♥) from a bow with no charge. Arrows fired from dispensers always do 3(♥♥) of damage unless their velocity is modified by an external source. Arrows fired from crossbows do 6(♥♥♥) to 11(♥ × 5.5) of damage.

~From the Official Minecraft Wiki.

  • You won't be able to accurately imitate vanilla mechanics, since a player might die even if they have more than 6 health. I considered the possibility of using arrows with 0 damage (maybe Instant Health arrows?) and artificially dealing damage, but that is beginning to get way too complex. There are simpler solutions.

Issue 2: projectile motion

  • Minecraft projectiles appear to move smoothly through the air, but they actually teleport in small increments. If the arrow is moving fast enough, it is possible that it might "teleport" more than half a block and fail the distance check.

Issue 3: distance selector

  • You use the distance=..0.5 selector, but this is not what you need. Intuituvely, it seems like this would detect if the arrow is within 0.5 blocks of the player's hitbox, but the distance selector selects other entities whose feet/origin are within a 0.5 spherical radius. This means that if the arrow hits the head of a player, approximately 2 blocks above the player's feet/origin, this will fail the distance check.

  • This can be fixed by using a volumetric cuboid selection, dx, dy, and dz, like so: @p[dx=0]. This selects the nearest player whose hitbox is within the same block as the arrow. This isn't perfect, either, though.

Issue 4: storing Motion

  • You stored the arrow's x, y, and z motion onto a scoreboard. In your command, you only test for x motion, but you also only test for a motion of positive 2 or greater. This means that if the arrow is traveling in the z direction, your selector would fail. Or, if the arrow is traveling in the negative x direction, the x motion would actually be negative 2. Also, if the arrow is traveling in a diagonal direction, it's x, y, and z components might all be less than 2, but the total speed of the arrow might be high enough to deal full damage.

Issue 5: using only one part of the UUID

  • In your question, you said that the "first object in the IntArray will always be unique". I'm not sure that is true, I believe that it is possible for 2 entities to have the same UUID[0] but differ in the other 3 UUID parts. I bring this up not because this is what caused your command to fail, but it is a possible, albeit unlikely source of failure.

The Situation

I'm glad you gave context, because your situation is quite complex. You need to detect arrow hits, what type of arrow it was, if the player died, the player who killed them, etc.

I didn't really see how what you tried would manage to fit in with all these factors, so I came up with a different approach.

To solve your problem, we need a few key components.

To identify the custom arrow, we will be using an amplifier-based identification system. This will allow you to create all the special arrow effects and death messages you want.

To detect where the arrow lands, we will be using a passenger system. This is primarily used to save the tipped arrow's custom effect even after the arrow hits a player and ceases to exist. It will also be useful if we want to run a special arrow effect at the location the arrow hit.

To detect a player death and their killer, we will be using good ol' scoreboards.

These will all be explained in the next section.


The Datapack Explanation

My answer here uses a datapack, because for large projects like these, command blocks lack the necessary power. Here is the file. Make sure to unzip it, then place it in .minecraft/saves/YOUR_WORLD_NAME/datapacks.

I recommend to download the datapack so you can follow along with the explanation.

The explanation will begin with the main.mcfunction file, located in data/custom_arrows/functions/dont_edit. This function will run every tick, as specified in data/minecraft/tags/functions/tick.json.

execute as @e[type=arrow,tag=!checked] run function custom_arrows:dont_edit/new
...

First line of the file—similar to your approach, I use a tag called checked to initialize all the arrows that are shot. These new arrows run the function data/custom_arrows/functions/dont_edit/new:

execute at @s run summon arrow ~ ~ ~ {Tags:["checked","new"],Passengers:[{id:"minecraft:marker",Tags:["arrow_rider"]}]}
data modify entity @e[type=arrow,tag=new,limit=1] Motion set from entity @s Motion
data modify entity @e[type=arrow,tag=new,limit=1] Fire set from entity @s Fire
data modify entity @e[type=arrow,tag=new,limit=1] pickup set from entity @s pickup
data modify entity @e[type=arrow,tag=new,limit=1] player set from entity @s player
data modify entity @e[type=arrow,tag=new,limit=1] Owner set from entity @s Owner
data modify entity @e[type=arrow,tag=new,limit=1] damage set from entity @s damage
data modify entity @e[type=arrow,tag=new,limit=1] crit set from entity @s crit
data modify entity @e[type=arrow,tag=new,limit=1] ShotFromCrossbow set from entity @s ShotFromCrossbow
data modify entity @e[type=arrow,tag=new,limit=1] PierceLevel set from entity @s PierceLevel
data modify entity @e[type=arrow,tag=new,limit=1] SoundEvent set from entity @s SoundEvent
data modify entity @e[type=arrow,tag=new,limit=1] Color set from entity @s Color
data modify entity @e[type=arrow,tag=new,limit=1] Potion set from entity @s Potion
data modify entity @e[type=arrow,tag=new,limit=1] CustomPotionEffects set from entity @s CustomPotionEffects
data modify entity @e[type=marker,tag=arrow_rider,limit=1] data set from entity @s CustomPotionEffects[]

kill @s
tag @e[type=arrow,tag=new,limit=1] remove new

This might seem like a lot, but all it does is summon a new arrow that is identical, then killing the original. It does this by using lots of data modify commands to copy all the important data. The only difference is that it has a passenger riding on it; a marker entity. As explained in the passenger system video, This marker entity will remain behind even when the projectile hits an entity and disappears. This is important, because we want to know what arrow hit the player, even if they died. To do this, we also copy the original arrow's CustomPotionEffects onto the marker entity's data tag.

Jumping back out to the main.mcfunction file, we arrive at the second line:

...
#If there is an arrow that has hit an entity (i.e. it disappeared and the marker is no longer riding anything) and there is a player who just died, run a function that identifies the amplifier and runs the corresponding custom arrow kill function.
execute as @e[type=marker,tag=arrow_rider,predicate=!custom_arrows:is_riding_arrow] if entity @a[scores={deaths=1..,killed_by_player=1..}] at @s run function custom_arrows:arrow_kill
...

Here, several new players come onto the scene. The custom_arrows:is_riding_arrow predicate, a deaths score, and a killed_by_player score. Before this gets explained, it is probably best to jump into another mcfunction file first, init.mcfunction. (data/custom_arrows/functions/dont_edit/init.mcfunction.)

This file will run every time the server is reloaded (/reload) or every time the world is loaded, as specified by data/minecraft/tags/functions/load.json. It is a great place to put initialization commands. In this case, init.mcfunction only contains 3 commands:

scoreboard objectives add killed_by_player minecraft.killed_by:minecraft.player
scoreboard objectives add player_kills playerKillCount
scoreboard objectives add deaths deathCount

2 of these commands we saw in main.mcfunction, deaths and killed_by_player. There is a third, player_kills. Together, these 3 scoreboards allow us to identify a killer and a victim, although not 100% accurately. If 2 players are killed within the same tick, there will be 2 players with a score of deaths=1.. and player_kills=1... This could result in the incorrect player being identified as the killer/victim. If there is a better way to do this, I am not aware of it.

Before we jump back to main.mcfunction, we also need to know what a predicate is. Predicates are a powerful tool we can use to check many many different properties. They are stored in JSON format, so I use this generator to create them.

In this datapack, a custom predicate, is_riding_arrow, was made (data/custom_arrows/predicates/is_riding_arrow.json):

{
  "condition": "minecraft:entity_properties",
  "entity": "this",
  "predicate": {
    "nbt": "{Tags:[\"arrow_rider\"]}",
    "vehicle": {
      "type": "minecraft:arrow"
    }
  }
}

As the name suggests, it checks if the entity running the command is riding an arrow. It also checks if the entity has the tag, arrow_rider (which was given to our marker entity). Pretty simple.

BACK TO OUR main.mcfunction file, we can begin to make sense of the second line, re-pasted here:

...
#If there is an arrow that has hit an entity (i.e. it disappeared and the marker is no longer riding anything) and there is a player who just died, run a function that identifies the amplifier and runs the corresponding custom arrow kill function.
execute as @e[type=marker,tag=arrow_rider,predicate=!custom_arrows:is_riding_arrow] if entity @a[scores={deaths=1..,killed_by_player=1..}] at @s run function custom_arrows:arrow_kill
...

As the comment describes, this command will execute as all marker entities who are NOT riding an arrow. This is because when the arrow hits an entity, it will disappear, and the marker entity will no longer be riding an arrow. This serves as our hit detection. The marker entity then checks to see if a player was killed by another player. If so, the marker entity runs the arrow_kill function:

### This function runs as the marker rider, at the location where the arrow hit.
### Edit this stuff
### Make a new function file for each arrow

#If a player is killed by a tipped arrow of Luck I:
execute if entity @s[nbt={data:{Id:26b,Amplifier:0b}}] run function custom_arrows:killed_by_sniper_arrow

#If a player is killed by a tipped arrow of Luck II:
execute if entity @s[nbt={data:{Id:26b,Amplifier:1b}}] run function custom_arrows:killed_by_silverfish_arrow




### Do not delete
kill @s

(As you may have noticed, some of the files are located in a folder named dont_edit, and this one even has the comments, "### Edit this stuff" and "### Do not delete" at the top and bottom, respectively. Yes, to add your own custom arrows, you will have to edit these files.)

In arrow_kill.mcfunction, the marker entity is checked to see what data it holds. Remember in new.mcfunction, when we copied the arrow's CustomPotionEffects onto it? Here, it sees its purpose! Even after the arrow has disappeared after killing an entity, we can still identify which custom arrow it was. We do this by checking the CustomPotionEffects's amplifier, which is our custom arrow identification system. This does mean that your custom arrows cannot be the vanilla default tipped arrows! The vanilla arrows do not contain any CustomPotionEffects, only Potion.

Once we have identified what arrow killed the player, we can run another function, killed_by_sniper_arrow.mcfunction:

#Send a custom death message. "<Victim> was sniped by <Killer>"
execute as @a[scores={deaths=1..,killed_by_player=1..}] run tellraw @a [{"selector":"@s"},{"text":" was sniped by "},{"selector":"@p[scores={player_kills=1..}]"}]

#Teleport any dead players to spawn. This would require the gamerule doImmediateRespawn to be true (since you can't teleport players that are on the Respawn screen)
tp @p[scores={deaths=1..,killed_by_player=1..}] 127 179 8

This is the fun part. In this example custom arrow file, several events occur when a player is killed with an arrow of Luck I. This example is also the most relevant to your question, since it sees the usage of a custom death message and teleporting the player to spawn, as you wanted. Alternatively, under this system, you could just set their spawnpoint there.

I also included another example, the silverfish arrow, which runs when a player is killed with an arrow of Luck II:

#Send a custom death message. "<Killer> gave <Victim> a fatal dose of Entomophobia"
execute as @a[scores={deaths=1..,killed_by_player=1..}] run tellraw @a [{"selector":"@p[scores={player_kills=1..}]"},{"text":" gave "},{"selector":"@s"},{"text":" a fatal dose of Entomophobia"}]

#Summon 1 silverfish
summon silverfish ~ ~ ~

#Play silverfish ambient sound
playsound minecraft:entity.silverfish.ambient neutral @a ~ ~ ~

#Teleport any dead players to spawn. This would require the gamerule doImmediateRespawn to be true (since you can't teleport players that are on the Respawn screen)
tp @p[scores={deaths=1..,killed_by_player=1..}] 127 179 8

Fun stuff.

If you've been following along with the datapack file, you might have noticed 2 similarly named files: hit_by_sniper_arrow.mcfunction and hit_by_silverfish_arrow.mcfunction. The files we just looked at are the death event functions, but we will also need functions for the hit event. The hit event function requires several conditions to run, starting back at our good old main.mcfunction's third line:

...
#If a player has the Luck effect (meaning they didn't die but got hit by the tipped arrow), run a function that identifies the amplifier and runs the corresponding custom arrow hit function.
execute as @a[nbt={ActiveEffects:[{Id:26b}]}] at @s run function custom_arrows:arrow_hit
...

If the player survived the arrow hit, the arrow will still disappear, and the marker entity will still dismount. To differentiate between a hit and a death, we can use the fact that players will lose their effects on death.

So, we detect this by simply executing as all players who have the Luck effect, and having them run the function, arrow_hit:

### This function runs as the player who was hit, at their origin/feet.
### Edit this stuff
### Make a new function file for each arrow

execute if entity @s[nbt={ActiveEffects:[{Id:26b,Amplifier:0b}]}] run function custom_arrows:hit_by_sniper_arrow
execute if entity @s[nbt={ActiveEffects:[{Id:26b,Amplifier:1b}]}] run function custom_arrows:hit_by_silverfish_arrow

Similarly to arrow_kill, this function is a directory for identifying the specific amplifier they have been affected with. This will make them run the corresponding custom arrow file. Let's look into the hit_by_sniper_arrow function:

#Give the player who was hit slowness for 10 seconds. Keep in mind that the slowness gets reapplied if the player has the Luck I effect! So if the luck lasts 2 seconds, the total slowness will be 12 seconds.
effect give @s slowness 10 4 true

#Play an explosion particle where the arrow hit. (Not 100% reliable)
execute as @e[type=marker,predicate=!is_riding_arrow,sort=nearest,limit=1] at @s run particle explosion ~ ~ ~

Like before, everything that goes on here is up to you.

We are almost through the entire datapack! These are the final lines of main.mcfunction:

...
#If the marker somehow loses it's arrow (hit a block and despawned), and hasn't ran a hit/kill function, kill it
execute as @e[type=marker,tag=arrow_rider,predicate=!custom_arrows:is_riding_arrow] run kill @s

scoreboard players reset @a deaths
scoreboard players reset @a killed_by_player
scoreboard players reset @a player_kills

Quite self-explanatory. If the marker entity is no longer riding an arrow, (but didn't run the hit or kill functions, which kills the marker after), we just kill the marker entity so it doesn't cause any troubles.

FINALLY, we reset the scores used for killer/victim detection.


Final Words

That was a lot of time spent writing the datapack, debugging, and writing this answer! Unfortunately, I was unable to test it in a multiplayer environment on friends, because... well yea I just wasn't able to. If you come across any bugs, I'll be putting this up on a Planet Minecraft page, where I can share this datapack with others and you can leave a comment.

1
  • thank you so much! i wasn't expecting a full datapack lmao. there are a couple things i want to discuss/talk about that wouldn't fit here though, so i'll move it to the PMC page you linked. Jul 7, 2021 at 16:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .