Skillsbo Posted January 7, 2021 Share Posted January 7, 2021 What? This tutorial will be outlining the process and steps of writing my Yandu's Arrow script to hopefully teach others about what is possible in Skript with few lines of code. (If you don't know who Yandu is, he's some blue bad/good guy in the Guardians of The Galaxy movies who uses a magic arrow and it flies through a handful of enemies by itself) So that's basically what we will be creating today The Start So where would I start? I would personally observe how the arrow acts in the movies, you can see that it flies through an enemy once to get the ones behind/next to and each one (mostly) never gets hit more than once. So how could we emulate this behavior? You might think that just doing them based on the distance to you would be the answer, and you would be right, ish, it would get the job done but it won't not look as clean (see below) So if that's not quite what we want, what's the better option? What if we still sort by distance but not by the player? What if we first get the closest zombie to the player, and then get the closest one to that one and then rinse and repeat? That would produce a much better looking result (see below) Now that we know how we should sort the zombies we can move onto actually writing the script. Writing The Script So we can begin writing the script now, we can start by adding the main event, for me I'll just make it activate when you right click with an arrow with a special name and it will just spawn an arrow with no gravity (prevent it from falling) on right click with arrow: if name of player's tool = "&9Yandu's arrow": spawn 1 arrow at player set gravity of last spawned arrow to false So now, how do we get the zombie that's closest to the player? Sadly `all x in radius y of z` does not return the entities in order of distance we will have to make our own part that does that. This is a simple sorting thing, it loops through all the (zombies in this case) in radius 20 and compares the distance to the other ones and the resulting closest zombie is in `{_z}` set {_d} to 21 loop all zombies in radius 20 of player: if distance between loop-zombie and player < {_d}: set {_z} to loop-zombie set {_d} to distance between loop-zombie and player So now we need to sort all the zombies in the order we discussed earlier, one way to do this is to use recursion as it greatly reduces the amount of lines we need (recursion basically means defining something in terms of itself) So we put this in our main event (keep in mind that the `{_t::*}` variable has not been set and we want it that way) set {_z::*} to closestZombie({_z}, {_t::*}) And now we need to define the `closestZombie` function function closestZombie(a: entity, t: entities) :: entities: set {_d} to 100 loop all zombies in radius 20 of {_a}: if {_t::*} does not contain loop-zombie: if distance between loop-zombie and {_a} < {_d}: set {_z} to loop-zombie set {_d} to distance between loop-zombie and {_a} add {_z} to {_t::*} add {_z} to {_z::*} set {_c::*} to closestZombie({_z}, {_t::*}) add {_c::*} to {_z::*} return {_z::*} Basically the function gets the closest zombie to the current zombie and then gets the closest zombie to that one and so on and so forth while also keeping track of which zombie has been already "taken". So once we have that defined we can move on the final part (the visuals) loop size of {_z::*} times: set {_i} to loop-value-1 + 1 if {_i} <= size of {_z::*}: loop 5 times: set {_l} to linearBezier((({_z::%loop-value-1%} ~ vector(0, 1.5, 0)) and ({_z::%{_i}%} ~ vector(0, 1.5, 0))), loop-value-2 / 5) play 5 of flame at {_l} offset by vector(0, 0, 0) with extra .05 set {_v} to vector between {_z::%loop-value-1%} and {_z::%{_i}%} set vector length of {_v} to .01 teleport last spawned arrow to {_l} set velocity of last spawned arrow to {_v} wait 1 tick kill {_z::%loop-value-1%} kill {_z::%loop-value-1%} kill last spawned arrow The code may look daunting at first but its pretty simple, we loop through the list of zombies (that are now in order thanks to our epic sorting function) then we check to make sure we are not at the second to last zombie (to make sure the visuals play out nicely between the zombies) and then we use another function called `linearBezier` which basically just means a line from A to B at the position t (time) that will make us a line to where we can teleport the arrow to and create a few particles to add a little pizzazz, we use a vector here just to make the arrow face in the right direction, its nothing too fancy. function definition for `linearBezier` below function linearBezier(l: locations, t: number) :: location: set {_x} to (1 - {_t}) * x-loc of {_l::1} + {_t} * x-loc of {_l::2} set {_y} to (1 - {_t}) * y-loc of {_l::1} + {_t} * y-loc of {_l::2} set {_z} to (1 - {_t}) * z-loc of {_l::1} + {_t} * z-loc of {_l::2} return location({_x}, {_y}, {_z}, world of {_l::1}) The End Slapping it all together and we get function linearBezier(l: locations, t: number) :: location: set {_x} to (1 - {_t}) * x-loc of {_l::1} + {_t} * x-loc of {_l::2} set {_y} to (1 - {_t}) * y-loc of {_l::1} + {_t} * y-loc of {_l::2} set {_z} to (1 - {_t}) * z-loc of {_l::1} + {_t} * z-loc of {_l::2} return location({_x}, {_y}, {_z}, world of {_l::1}) function closestEntity(a: entity, t: entities) :: entities: set {_d} to 100 loop all zombies in radius 20 of {_a}: if {_t::*} does not contain loop-zombie: if distance between loop-zombie and {_a} < {_d}: set {_z} to loop-zombie set {_d} to distance between loop-zombie and {_a} add {_z} to {_t::*} add {_z} to {_z::*} set {_c::*} to closestEntity({_z}, {_t::*}) add {_c::*} to {_z::*} return {_z::*} on right click with arrow: if name of player's tool = "&9Yandu's arrow": spawn 1 arrow at player set gravity of last spawned arrow to false set {_d} to 21 loop all zombies in radius 20 of player: if distance between loop-zombie and player < {_d}: set {_z} to loop-zombie set {_d} to distance between loop-zombie and player set {_z::*} to closestEntity({_z}, {_t::*}) loop size of {_z::*} times: set {_i} to loop-value-1 + 1 if {_i} <= size of {_z::*}: loop 5 times: set {_l} to linearBezier((({_z::%loop-value-1%} ~ vector(0, 1.5, 0)) and ({_z::%{_i}%} ~ vector(0, 1.5, 0))), loop-value-2 / 5) play 5 of flame at {_l} offset by vector(0, 0, 0) with extra .05 set {_v} to vector between {_z::%loop-value-1%} and {_z::%{_i}%} set vector length of {_v} to .01 teleport last spawned arrow to {_l} set velocity of last spawned arrow to {_v} wait 1 tick kill {_z::%loop-value-1%} kill {_z::%loop-value-1%} kill last spawned arrow I might do more of these later one with other cool concepts (maybe laser beams will be next) 2 Link to comment Share on other sites More sharing options...
chillins Posted January 7, 2021 Share Posted January 7, 2021 This is amazing, well done Skillsy! 𝖈𝖍𝖎𝖑𝖎𝖓𝖘 Server Rules • Staff Applications • News & UpdatesReports • Appeals Retired Moderator (June 30th 2020 - May 17th 2021) Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now