The theory behind this is that your MoveX and MoveY values are the change in the X and Y position every frame. If you make a triangle of width MoveX and height MoveY, the hypotenuse is the speed. As you've worked out, you can get the speed by pythagoras, i.e.
sqrt('MoveX' ^ 2 + 'MoveY' ^ 2)
So now you can make an event which runs when the speed is too high, using Compare Values:
sqrt(Player('MoveX') ^ 2 + Player('MoveY') ^ 2)
0.5 (or whatever maximum speed you want).
Now all you want to do in this event is set the speed to your maximum speed, i.e. the hypotenuse must be 0.5. So all you have to do is work out what the values for MoveX and MoveY are for a triangle of hypotenuse 0.5 at the current angle. So add these actions:
Set MoveX to 0.5 * cos(.Angle)
Set MoveY to 0.5 * sin(.Angle)
And there you have it, whenever your speed goes above 0.5, it's adjusted to exactly 0.5.
Unfortunately, this doesn't work. I found a bug in Construct while doing this
To work around it, you need to adjust the Compare Values condition to eliminate the square root. You can easily do this by squaring both sides of the equation, eg:
sqrt(Player('MoveX') ^ 2 + Player('MoveY') ^ 2) > 0.5
Player('MoveX') ^ 2 + Player('MoveY') ^ 2 > 0.25
Why it doesn't work I'm not particularly sure and need to debug it at some point, but I guess SOMEHOW it's trying to square root a negative number, which is mathematically impossible, and shouldn't be possible in this case because both the values passed to it are squared. Weird, I'll look in to it.
Anyways, hope that helps
Edit: one more thing, with custom movements you really should have some TimeDelta expressions in there somewhere. Otherwise if yuo V-sync it people with different refresh rates will have it run at different speeds. If MoveX and MoveY are in values of pixels per second, and you adjust the Player's X and Y coordinates as such:
.X + 'MoveX' * TimeDelta
.Y + 'MoveY' * TimeDelta
then you will avoid this problem, and it will run the same speed for everyone.