Okay, I finished the script finally. It is implemented so that when a player holds the "R" button down and then presses a directional button perpendicular to where he is looking it does the crescent dodge (for example, if he is looking up, the user presses left or right to select which arc he wants to follow). The global values at the top of the script are all you will need to change to have the script work as you want. First, change "canusecd" to false in the actual quest, and have another script change it to true when a player learns the move. Link can move in either a circle or an ellipse. Keep the diameters the same for a circle, or different for an ellipse. The default values below is for the 3x3 circle you asked about above (Link only moves across 2 tiles, which is 32 pixels). All the other global values are explained in the script. If you come across any bugs, just let me know (hopefully there should be none left, I tried everything I could think of that might cause problems).
import "std.zh"//Global vars/constants
bool canusecd=true;
const int CD_DIAMETER_A=32; //diameter of ellipse in pixels (parallel to Link's Direction)
const int CD_DIAMETER_B=32; //diameter of ellipse in pixels (perpendicular to Link's Direction)
const int CD_LINKTOP=8; //These are for collision detection. Default values (8,0,15,15) mimics the collision detection that the games uses.
const int CD_LINKLEFT=0;
const int CD_LINKRIGHT=15;
const int CD_LINKBOTTOM=15;
const float CD_SPEED=2.0; //how many pixels Link moves per frame. values above 7 may break collision detection
const int CD_SPIN=1; //0=no spin, 1=normal spin, 2=backwards spin,3=fast spin
const int CD_WAIT=5; //number of frames user must wait between each use of CD
const int SFX_CRESDODGE=54; //default is the sound of the spin attack
//Used in arclength function.
float alfx(float x,float ra,float rb)
{
return Sqrt(ra*ra*Sin(x)*Sin(x)+rb*rb*Cos(x)*Cos(x));
}
//Takes the two radii of an ellipse (keep both vals the same for a circle) and approximates the arclength of half of it
float arclength(float radius_a,float radius_b)
{
int i;
float step=PI/10;
float arclen=0;
arclen=alfx(0,radius_a,radius_b)+alfx(PI,radius_a,radius_b);
for(i=1;i<10;i++)
{
if(i%2==1) arclen+=4*alfx(step*i,radius_a,radius_b);
else arclen+=2*alfx(step*i,radius_a,radius_b);
}
arclen*=(step/3);
return arclen;
// DETAILS (sort of) of algorithm used
// It uses the definition of arclength, which is the integral of the square root of dx/dt squared plus dy/dt squared, with
// respect to t, and it goes from c to d, where c <= t <= d. In this case, x(t)=a*cos(t) and y(t)=b*sin(t), where
// a and b are set by CD_DIAMETER_A and CD_DIAMETER_B. c and d is 0 and PI, which has t go only half the circle/ellipse. The
// integral itself is estimated using Simpson's Rule, which you can look up using pretty much any calculus book.
}
//Checks all four corners of Link's "collision box" (based on global values above) to see if any are in a solid object
bool CD_collision(int newx,int newy)
{
if(isSolid(newx+CD_LINKLEFT,newy+CD_LINKTOP)) return true;
if(isSolid(newx+CD_LINKRIGHT,newy+CD_LINKTOP)) return true;
if(isSolid(newx+CD_LINKLEFT,newy+CD_LINKBOTTOM)) return true;
if(isSolid(newx+CD_LINKRIGHT,newy+CD_LINKBOTTOM)) return true;
return false;
}
//Used in crescentdodge() to spin Link while doing the move
int getnextdir(int currentdir,float angle)
{
int ret;
if(CD_SPIN==0) return currentdir;
else if(CD_SPIN==2) angle*=-1;
if(angle>0) //spin clockwise
{
if(currentdir==DIR_UP) ret=DIR_RIGHT;
if(currentdir==DIR_LEFT) ret=DIR_UP;
if(currentdir==DIR_DOWN) ret=DIR_LEFT;
if(currentdir==DIR_RIGHT) ret=DIR_DOWN;
}
if(angle<0) //spin counter-clockwise
{
if(currentdir==DIR_UP) ret=DIR_LEFT;
if(currentdir==DIR_LEFT) ret=DIR_DOWN;
if(currentdir==DIR_DOWN) ret=DIR_RIGHT;
if(currentdir==DIR_RIGHT) ret=DIR_UP;
}
return ret;
}
//This function is pretty self-explanatory 😊
void ignorelink()
{
Link->InputUp=false;
Link->InputDown=false;
Link->InputLeft=false;
Link->InputRight=false;
Link->InputL=false;
Link->InputR=false;
Link->InputA=false;
Link->InputB=false;
}
//This boolean, by Saffith (although slightly edited), checks for the solidity of combos, and is used for walkability detection
bool isSolid(int x, int y)
{
if(x<0 || x>255 || y<0 || y>175) return true; //edited to fix bug when on side of screen and using CD.
int mask=1111b;
if(x%16<8) mask&=0011b;
else mask&=1100b;
if(y%16<8) mask&=0101b;
else mask&=1010b;
if((Screen->ComboS[ComboAt(x, y)]&mask)==0) return false;
else return true;
}
//Rounds float to nearest integer
int Round(float val)
{
if(val-Floor(val) >= 0.5) return Ceiling(val);
else return Floor(val);
}
//Allows Link to travel in semicircle when using "R" plus a directional key prependicular to where he is facing
global script crescentdodge
{
void run()
{
//Script variables
float totalpixels=arclength(CD_DIAMETER_A/2,CD_DIAMETER_B/2); //Approximate number of pixels in semicircle (as if it were drawn)
float stepangle=(180*CD_SPEED)/totalpixels; //Calculates the number of degrees Link will move each frame
float currentangle;
float startangle;
float endanglemin;
float endanglemax;
float radx;
float rady;
float newangle;
float speed;
int center_x;
int center_y;
int primarydir;
int seconddir;
int cd_counter=0;
int nextx;
int nexty;
int nextdir;
int currentspin=0;
bool done=false;
while(true)
{
if(canusecd && Link->InputR && cd_counter==0)
{
center_x=0;
center_y=0;
primarydir=Link->Dir;
//Find center of circle/ellipse, set starting angle, and read which half of semicircle user wants to travel on
if(primarydir==DIR_UP || primarydir==DIR_DOWN)
{
if(primarydir==DIR_UP)
{
startangle=270;
center_y-=CD_DIAMETER_A/2;
}
else
{
startangle=90;
center_y+=CD_DIAMETER_A/2;
}
if((Link->InputUp || Link->InputDown) && !Link->InputLeft && !Link->InputRight) done=true; //fixes bug
while(!Link->InputLeft && !Link->InputRight && Link->InputR && !done) Waitframe(); //Read inputs until user selects which side to cd on
if(Link->InputLeft) seconddir=DIR_LEFT;
else if(Link->InputRight) seconddir=DIR_RIGHT;
else done=true; //R was let go
}
if(primarydir==DIR_LEFT || primarydir==DIR_RIGHT)
{
if(primarydir==DIR_LEFT)
{
startangle=180;
center_x-=CD_DIAMETER_A/2;
}
else
{
startangle=0;
center_x+=CD_DIAMETER_A/2;
}
if((Link->InputLeft || Link->InputRight) && !Link->InputUp && !Link->InputDown) done=true; //fixes bug
while(!Link->InputUp && !Link->InputDown && Link->InputR) Waitframe();
if(Link->InputUp) seconddir=DIR_UP;
else if(Link->InputDown) seconddir=DIR_DOWN;
else done=true;
}
//Read in Link's current position and fix Link's direction
center_x+=Link->X;
center_y+=Link->Y;
if(Link->Dir!=primarydir) done=true;
//Set initial current angle,the min/max endangles, and what direction Link will be moving around circle (clockwise/counterclockwise)
currentangle=startangle;
endanglemin=((startangle+180)-stepangle/2)%360;
endanglemax=(endanglemin+stepangle)%360;
if(primarydir==DIR_UP && seconddir==DIR_RIGHT) stepangle*=-1;
else if(primarydir==DIR_DOWN && seconddir==DIR_LEFT) stepangle*=-1;
else if(primarydir==DIR_LEFT && seconddir==DIR_UP) stepangle*=-1;
else if(primarydir==DIR_RIGHT && seconddir==DIR_DOWN) stepangle*=-1;
//Get the radii of the ellipse
if(primarydir==DIR_UP || primarydir==DIR_DOWN)
{
radx=-1*CD_DIAMETER_B/2;
rady=-1*CD_DIAMETER_A/2;
}
else
{
radx=-1*CD_DIAMETER_A/2;
rady=-1*CD_DIAMETER_B/2;
}
//If the move has not been cancelled yet, play the sound effect and set the counter
if(!done)
{
Game->PlaySound(SFX_CRESDODGE);
cd_counter=CD_WAIT;
}
//Move Link around the circle until he has moved 180 degrees
while(!done)
{
ignorelink();
currentangle+=stepangle;
if(currentangle<0) currentangle+=360;
else if(currentangle>360) currentangle-=360;
nextx=Round(radx*Cos(currentangle)+center_x);
nexty=Round(rady*Sin(currentangle)+center_y);
//Collision detection
if(CD_collision(nextx,nexty))
{
done=true; //Link stops moving
speed=Floor(CD_SPEED);
while(CD_collision(nextx,nexty)) //Moves Link as close to the solid object as possible.
{
newangle=currentangle-stepangle+(180*speed)/totalpixels;
if(newangle<0) newangle+=360;
else if(newangle>360) newangle-=360;
if(speed>0)
{
nextx=Round(radx*Cos(newangle)+center_x);
nexty=Round(rady*Sin(newangle)+center_y);
}
else
{
nextx=Link->X;
nexty=Link->Y;
}
speed--;
}
Link->Dir=primarydir;
}
if(nexty<0) nexty=0; //fixes bug when using CD at top of screen
if(nextx<0) nextx=0;
Link->X=nextx;
Link->Y=nexty;
//Make Link spin while doing the move
if(Abs(currentangle-startangle)>=135 && currentspin<3)
{
Link->Dir=getnextdir(Link->Dir,stepangle);
if(CD_SPIN!=3) currentspin=3;
}
else if(Abs(currentangle-startangle)>=90 && currentspin<2)
{
Link->Dir=getnextdir(Link->Dir,stepangle);
if(CD_SPIN!=3) currentspin=2;
}
else if(Abs(currentangle-startangle)>=45 && currentspin<1)
{
Link->Dir=getnextdir(Link->Dir,stepangle);
if(CD_SPIN!=3) currentspin=1;
}
//Stop Link when he has traveled 180 degrees
if(currentangle<=endanglemax && currentangle>=endanglemin)
{
done=true;
Link->Dir=primarydir;
}
else if(endanglemin>endanglemax && (currentangle<=endanglemax || currentangle>=endanglemin))
{
done=true;
Link->Dir=primarydir;
}
Waitframe();
}
done=false;
stepangle=Abs(stepangle);
currentspin=0;
}
if(cd_counter>0) cd_counter--;
if(Link->InputR) ignorelink(); //fixes a rather annoying, random bug
Waitframe();
}
}
// DETAILS of algorithm
// The way this is implemented is the scripter chooses initial values of the diameters of the ellipse (basically how many tiles Link does move in), which is used
// to create an ellipse. Using the arclength formula, it estimates the amount of pixels in half of the ellipse, and then uses a variable also set by the scripter to
// determine how many degrees at a time Link should move each frame, until Link has traveled the full 180 degrees. Finally, it determines how far from the center of
// the ellipse the next point for Link to go to next is in each frame. Collision detection prevents Link from moving through solid objects and off the side of the
// screen. Finally, it prevents the user from doing the move again for the number of frames again set by the scripter.
}