R
I do not know much about datapacks, but a function like this should work, even in multiplayer. All the commands can go into the same function and the function should be executed every tick.
This setup uses these dummy objectives:
rotationOne (for y rotation)
rotationTwo (for x rotation)
lastRotationOne
lastRotationTwo
timer
looksLeft
looksRight
looksUp
looksDown
directionChanged
lastDirection (holds a value based on what kind of rotation the player did last)
stepOne, stepTwo,... (for "remembering" the last few actions, you can add as many as you want)
global (for holding parameters)
rotationOne bascially devides a ring around the player into sectors (numbered 0-12 if the number in the first command is 0.036), if a player looked into one sector in one tick, and a different one in the next, then that gets registered as looking right, or left. Imidiately after loading the map these values seem to be negative for a while, this problem seems to resolve itself after a few seconds.
timer is a timer that is used to detect if the player did NOT rotate for a while
global holds the parameters min, max, left, right, up, down, and timerTime.
min and max are used for the sectors, min should always be 0, max is the highest value that rotationOne can have before rolling over to 0. This changes if you change the number in the first command. Make sure to keep max updated if you decide to change the number in the first command.
timerTime is used by the timer and defines how long a player has to stand still until it counts as "not moving". A value of 20 sets the timer to 1 second.
left, right, up, and down are values assigned to each direction, I recommend using 1, 2, 4, and 8. The value for "no movement" will always be 0
Using these values you would initialize your scoreboards like so:
scoreboard objectives add rotationOne dummy
scoreboard objectives add rotationTwo dummy
scoreboard objectives add lastRotationOne dummy
scoreboard objectives add lastRotationTwo dummy
scoreboard objectives add timer dummy
scoreboard objectives add looksLeft dummy
scoreboard objectives add looksRight dummy
scoreboard objectives add looksUp dummy
scoreboard objectives add looksDown dummy
scoreboard objectives add directionChanged dummy
scoreboard objectives add lastDirection dummy
scoreboard objectives add stepOne dummy
scoreboard objectives add stepTwo dummy
scoreboard objectives add stepThree dummy
scoreboard objectives add stepFour dummy
scoreboard objectives add stepFive dummy
scoreboard objectives add stepSix dummy
scoreboard objectives add global dummy
scoreboard players set min global 0
scoreboard players set max global 12
scoreboard players set left global 1
scoreboard players set right global 2
scoreboard players set up global 4
scoreboard players set down global 8
scoreboard players set timerTime global 50 (for 2.5 seconds)
In the first two commands you decide the precision, a higher number at the end will require the movements to be more precise, lower numbers will require bigger movements. In the first command that means that there will be more, or less sectors in the ring. Make sure to set max to the correct number if you change the first command. The first two commands also store the current rotation of each player, which will be used later.
execute as @a store result score @s rotationOne run data get entity @s Rotation[0] 0.036
execute as @a store result score @s rotationTwo run data get entity @s Rotation[1] 0.036
After that you can run the following commands. You should not have to change anything about them, though they could probably be more efficient.
execute as @a unless score @s rotationOne = min global store success score @s looksLeft if score @s lastRotationOne > @s rotationOne
execute as @a if score @s rotationOne = min global store success score @s looksLeft unless score max global = @s lastRotationOne unless score min global = @s lastRotationOne
execute as @a if score @s rotationOne = max global store success score @s looksLeft if score min global = @s lastRotationOne
execute as @a unless score @s rotationOne = max global store success score @s looksRight if score @s lastRotationOne < @s rotationOne
execute as @a if score @s rotationOne = max global store success score @s looksRight unless score min global = @s lastRotationOne unless score max global = @s lastRotationOne
execute as @a if score @s rotationOne = min global store success score @s looksRight if score max global = @s lastRotationOne
execute as @a store success score @s looksUp if score @s lastRotationTwo > @s rotationTwo
execute as @a store success score @s looksDown if score @s lastRotationTwo < @s rotationTwo
execute as @a store success score @s directionChanged unless score @s rotationOne = @s lastRotationOne
execute as @a[scores={directionChanged=0}] store success score @s directionChanged unless score @s rotationTwo = @s lastRotationTwo
execute as @a run scoreboard players operation @s lastRotationOne = @s rotationOne
execute as @a run scoreboard players operation @s lastRotationTwo = @s rotationTwo
scoreboard players remove @a timer 1
execute as @a[scores={directionChanged=1}] run scoreboard players operation @s timer = timerTime global
execute as @a[scores={timer=..0}] run scoreboard players set @s directionChanged 1
execute as @a[scores={timer=..0}] run scoreboard players operation @s timer = timerTime global
execute as @a[scores={directionChanged=1}] run scoreboard players set @s lastDirection 0
execute as @a[scores={looksLeft=1}] run scoreboard players operation @s lastDirection = left global
execute as @a[scores={looksRight=1}] run scoreboard players operation @s lastDirection = right global
execute as @a[scores={looksUp=1}] run scoreboard players operation @s lastDirection = up global
execute as @a[scores={looksDown=1}] run scoreboard players operation @s lastDirection = down global
execute as @a if score @s lastDirection = @s stepFive run scoreboard players set @s directionChanged 0
To confirm to a player, when a direction has been registered, you would use these commands:
execute as @a[scores={directionChanged=1,looksLeft=1}] run msg @s left
execute as @a[scores={directionChanged=1,looksRight=1}] run msg @s right
execute as @a[scores={directionChanged=1,looksUp=1}] run msg @s up
execute as @a[scores={directionChanged=1,looksDown=1}] run msg @s down
execute as @a[scores={directionChanged=1,lastDirection=0}] run msg @s stopped
At this point you should decide how many inputs you want to use for your most complicated ability and add as many as you need according to this pattern:
execute as @a[scores={directionChanged=1}] run scoreboard players operation @s stepOne = @s stepTwo
execute as @a[scores={directionChanged=1}] run scoreboard players operation @s stepTwo = @s stepThree
execute as @a[scores={directionChanged=1}] run scoreboard players operation @s stepThree = @s stepFour
execute as @a[scores={directionChanged=1}] run scoreboard players operation @s stepFour = @s stepFive
execute as @a[scores={directionChanged=1}] run scoreboard players operation @s stepFive = @s lastDirection
And finally you get to make the abilities, you basically just make a long command with a lot of ifs, one for every step that is important for activating that ability. For an ability that should activate after "left, right, not moving, up, down" you would use a command like this:
execute as @a[scores={directionChanged=1}] if score @s stepOne = left global if score @s stepTwo = right global if score @s stepThree matches 0 if score @s stepFour = up global if score @s stepFive = down global run
If you want to add more abilities that are shorter, then you start at a higher step and ignore the first few steps, to make an ability for "up, down, up" you can use this command:
execute as @a[scores={directionChanged=1}] if score @s stepThree = up global if score @s stepFour = down global if score @s stepFive = up global run
I used all of these commands in that order in a long chain of command blocks. I hope you can just copy and paste them into a function file.