const int LAYER_BEAMOS1 = 2; //Beamos use this layer to draw the bottom half
const int LAYER_BEAMOS2 = 3; //Beamos use this layer to draw the top half and eye
const int BEAMOS_IMPRECISION = 6; //How close the beamos must be aiming at Link to shoot
const int BEAMOS_FLASHFRAMES = 10; //How many frames the beamos flashes for before firing
const int BEAMOS_LASER_SPEED = 6; //How fast the laser shoots out
const int BEAMOS_LASER_LENGTH = 80; //How long the laser is
const int BEAMOS_LASER_MINDIST = 16; //How far the beamos can see/shoot through solid objects
const int COLOR_BEAMOS_LASER1 = 0x84; //The color of the inner laser
const int COLOR_BEAMOS_LASER2 = 0x85; //The color of the outer laser
const int CMB_BEAMOS_LASER_ENDPOINT = 1010; //The combo used for the ends of the laser
const int CS_BEAMOS_LASER_ENDPOINT = 8; //The cset used for the ends of the laser
const int SFX_BEAMOS_SIGHT = 69; //The sound that plays when the beamos sees Link
const int SFX_BEAMOS_LASER = 70; //The sound that plays when the beamos fires its laser
//LttP Beamos Script
//Attribute 1: The starting angle of the eye in degrees.
//Attribute 2: How fast the eye turns each frame in degrees. Make negative for a counterclockwise turn.
//Attribute 3: How long the enemy waits before it can fire another laser in frames. (60ths of a second)
//Attribute 4: Whether or not the enemy has collision. 0 = Yes, 1 = No. No collision is mostly for placing over a solid combo.
//Attribute 11: The first of 10 combos. Statue top, statue bottom, eye up, eye down, eye left, eye right, eye left-up, eye right-up, eye left-down, eye right-down
//Attribute 12: The slot this script is loaded into
ffc script Beamos{
//This function draws the parts of the beamos in the right order
void Beamos_Draw(ffc this, npc ghost, int Combo, int EyeAngle){
int EyeX = Ghost_X+VectorX(8, EyeAngle);
int EyeY = Ghost_Y+4+VectorY(5, EyeAngle);
int EyeCombo = Combo+2+AngleDir8(EyeAngle);
if(Link->HP>0){
if(EyeAngle<=0)
Screen->FastCombo(LAYER_BEAMOS2, EyeX, EyeY, EyeCombo, this->CSet, 128);
Screen->FastCombo(LAYER_BEAMOS2, Ghost_X, Ghost_Y, Combo, this->CSet, 128);
if(EyeAngle>0)
Screen->FastCombo(LAYER_BEAMOS2, EyeX, EyeY, EyeCombo, this->CSet, 128);
Screen->FastCombo(LAYER_BEAMOS1, Ghost_X, Ghost_Y+16, Combo+1, this->CSet, 128);
}
}
//This function checks the path of the beamos' laser before firing
bool CheckPath(int X, int Y, int Angle, int Distance, int SafeDist, int Step){
for(int i = 0; i<Distance-Step; i+=Step){
X += VectorX(Step, Angle);
Y += VectorY(Step, Angle);
if(Screen->isSolid(X, Y)&&i>SafeDist)
return false;
}
return true;
}
//This function checks if the laser is colliding with Link and deals damage
void CheckBeamosLaser(npc ghost, int StartX, int StartY, int EndX, int EndY){
if(lineBoxCollision(StartX, StartY, EndX, EndY, Link->X, Link->Y, Link->X+Link->HitWidth, Link->Y+Link->HitHeight, 0)){
eweapon e = FireEWeapon(EW_SCRIPT10, Link->X+InFrontX(Link->Dir, 10), Link->Y+InFrontY(Link->Dir, 10), 0, 0, ghost->WeaponDamage, -1, -1, EWF_UNBLOCKABLE);
SetEWeaponLifespan(e, EWL_TIMER, 1);
SetEWeaponDeathEffect(e, EWD_VANISH, 0);
e->DrawYOffset = -1000;
}
}
void run(int enemyid){
npc ghost = Ghost_InitAutoGhost(this, enemyid);
int StartAngle = ghost->Attributes[0];
int IncrementAngle = ghost->Attributes[1];
int LaserCooldown = ghost->Attributes[2];
int NoCollision = ghost->Attributes[3];
int Combo = ghost->Attributes[10];
Ghost_Transform(this, ghost, GH_INVISIBLE_COMBO, ghost->CSet, 1, 2);
Ghost_Y-=8;
if(NoCollision==1)
ghost->CollDetection = false;
Ghost_SetHitOffsets(ghost, 16, 0, 0, 0);
int EyeAngle = WrapDegrees(StartAngle);
this->Flags[FFCF_IGNOREHOLDUP] = true;
while(true){
EyeAngle = WrapDegrees(EyeAngle+IncrementAngle);
int EyeX = Ghost_X+VectorX(8, EyeAngle);
int EyeY = Ghost_Y+4+VectorY(5, EyeAngle);
int AngleLink = Angle(EyeX+8, EyeY+8, CenterLinkX(), CenterLinkY());
int DistLink = Distance(EyeX+8, EyeY+8, CenterLinkX(), CenterLinkY());
//Check if the eye is aimed at Link and there's a clear path before firing
if(Abs(angleDifference(EyeAngle, AngleLink))<BEAMOS_IMPRECISION&&CheckPath(EyeX+8, EyeY+8, AngleLink, DistLink, BEAMOS_LASER_MINDIST, 6)){
int Angle = Angle(Ghost_X+8, Ghost_Y+8, CenterLinkX(), CenterLinkY());
int LStartX = EyeX+8;
int LStartY = EyeY+8;
int LEndX = LStartX;
int LEndY = LStartY;
int LaserLength = 0;
bool LaserEnded = false;
//Play the line of sight sound and flash briefly
Game->PlaySound(SFX_BEAMOS_SIGHT);
Ghost_StartFlashing(BEAMOS_FLASHFRAMES);
for(int i=0; i<BEAMOS_FLASHFRAMES; i++){
Beamos_Draw(this, ghost, Combo, EyeAngle);
Ghost_Waitframe(this, ghost, true, true);
}
//Fire the laser until it reaches full length or hits a wall
Game->PlaySound(SFX_BEAMOS_LASER);
while(Distance(LStartX, LStartY, LEndX, LEndY)<BEAMOS_LASER_LENGTH&&!LaserEnded){
int LDist = Distance(LStartX, LStartY, LEndX, LEndY);
LEndX += VectorX(BEAMOS_LASER_SPEED, Angle);
LEndY += VectorY(BEAMOS_LASER_SPEED, Angle);
LaserLength += BEAMOS_LASER_SPEED;
if((Screen->isSolid(LEndX, LEndY)&&Distance(EyeX+8, EyeY+8, LEndX, LEndY)>=BEAMOS_LASER_MINDIST)||(LEndX<-16||LEndX>272||LEndY<-16||LEndY>192))
LaserEnded = true;
//Draw everything in order
int EyeCombo = Combo+2+AngleDir8(EyeAngle);
if(Link->HP>0){
if(EyeAngle<=0){
Screen->FastCombo(LAYER_BEAMOS2, EyeX, EyeY, EyeCombo, this->CSet, 128);
Screen->Rectangle(LAYER_BEAMOS2, LStartX, LStartY-1, LStartX+LDist, LStartY+1, COLOR_BEAMOS_LASER2, 1, LStartX, LStartY, Angle, true, 128);
Screen->Line(LAYER_BEAMOS2, LStartX, LStartY, LEndX, LEndY, COLOR_BEAMOS_LASER1, 1, 0, 0, 0, 128);
Screen->FastCombo(LAYER_BEAMOS2, LStartX-8, LStartY-8, CMB_BEAMOS_LASER_ENDPOINT, CS_BEAMOS_LASER_ENDPOINT, 128);
}
Screen->FastCombo(LAYER_BEAMOS2, Ghost_X, Ghost_Y, Combo, this->CSet, 128);
if(EyeAngle>0){
Screen->FastCombo(LAYER_BEAMOS2, EyeX, EyeY, EyeCombo, this->CSet, 128);
Screen->Rectangle(LAYER_BEAMOS2, LStartX, LStartY-1, LStartX+LDist, LStartY+1, COLOR_BEAMOS_LASER2, 1, LStartX, LStartY, Angle, true, 128);
Screen->Line(LAYER_BEAMOS2, LStartX, LStartY, LEndX, LEndY, COLOR_BEAMOS_LASER1, 1, 0, 0, 0, 128);
Screen->FastCombo(LAYER_BEAMOS2, LStartX-8, LStartY-8, CMB_BEAMOS_LASER_ENDPOINT, CS_BEAMOS_LASER_ENDPOINT, 128);
}
Screen->FastCombo(LAYER_BEAMOS1, Ghost_X, Ghost_Y+16, Combo+1, this->CSet, 128);
}
CheckBeamosLaser(ghost, LStartX, LStartY, LEndX, LEndY);
Ghost_Waitframe(this, ghost, true, true);
}
//If it reaches full length
if(!LaserEnded){
while((!Screen->isSolid(LEndX, LEndY)&&!(LEndX<-16||LEndX>272||LEndY<-16||LEndY>192))||Distance(EyeX+8, EyeY+8, LEndX, LEndY)<BEAMOS_LASER_MINDIST){
int LDist = Distance(LStartX, LStartY, LEndX, LEndY);
LStartX += VectorX(BEAMOS_LASER_SPEED, Angle);
LStartY += VectorY(BEAMOS_LASER_SPEED, Angle);
LEndX += VectorX(BEAMOS_LASER_SPEED, Angle);
LEndY += VectorY(BEAMOS_LASER_SPEED, Angle);
if(Screen->isSolid(LEndX, LEndY))
LaserEnded = true;
if(EyeAngle<=0){
Screen->Rectangle(LAYER_BEAMOS2, LStartX, LStartY-1, LStartX+LDist, LStartY+1, COLOR_BEAMOS_LASER2, 1, LStartX, LStartY, Angle, true, 128);
Screen->Line(LAYER_BEAMOS2, LStartX, LStartY, LEndX, LEndY, COLOR_BEAMOS_LASER1, 1, 0, 0, 0, 128);
}
Beamos_Draw(this, ghost, Combo, EyeAngle);
if(EyeAngle>0){
Screen->Rectangle(LAYER_BEAMOS2, LStartX, LStartY-1, LStartX+LDist, LStartY+1, COLOR_BEAMOS_LASER2, 1, LStartX, LStartY, Angle, true, 128);
Screen->Line(LAYER_BEAMOS2, LStartX, LStartY, LEndX, LEndY, COLOR_BEAMOS_LASER1, 1, 0, 0, 0, 128);
}
Screen->Rectangle(LAYER_BEAMOS2, LStartX, LStartY-1, LStartX+LDist, LStartY+1, COLOR_BEAMOS_LASER2, 1, LStartX, LStartY, Angle, true, 128);
Screen->Line(LAYER_BEAMOS2, LStartX, LStartY, LEndX, LEndY, COLOR_BEAMOS_LASER1, 1, 0, 0, 0, 128);
CheckBeamosLaser(ghost, LStartX, LStartY, LEndX, LEndY);
Ghost_Waitframe(this, ghost, true, true);
}
}
//Once the laser has hit a wall, make the ends meet again
while(Distance(LStartX, LStartY, LEndX, LEndY)>BEAMOS_LASER_SPEED){
int LDist = Distance(LStartX, LStartY, LEndX, LEndY);
if(LaserLength>BEAMOS_LASER_LENGTH){
LStartX += VectorX(BEAMOS_LASER_SPEED, Angle);
LStartY += VectorY(BEAMOS_LASER_SPEED, Angle);
}
if(LaserLength<=BEAMOS_LASER_LENGTH)
LaserLength += BEAMOS_LASER_SPEED;
//Draw everything in order
int EyeCombo = Combo+2+AngleDir8(EyeAngle);
if(Link->HP>0){
if(EyeAngle<=0){
Screen->FastCombo(LAYER_BEAMOS2, EyeX, EyeY, EyeCombo, this->CSet, 128);
Screen->Rectangle(LAYER_BEAMOS2, LStartX, LStartY-1, LStartX+LDist, LStartY+1, COLOR_BEAMOS_LASER2, 1, LStartX, LStartY, Angle, true, 128);
Screen->Line(LAYER_BEAMOS2, LStartX, LStartY, LEndX, LEndY, COLOR_BEAMOS_LASER1, 1, 0, 0, 0, 128);
if(LaserLength<=BEAMOS_LASER_LENGTH)
Screen->FastCombo(LAYER_BEAMOS2, LStartX-8, LStartY-8, CMB_BEAMOS_LASER_ENDPOINT, CS_BEAMOS_LASER_ENDPOINT, 128);
Screen->FastCombo(LAYER_BEAMOS2, LEndX-8, LEndY-8, CMB_BEAMOS_LASER_ENDPOINT, CS_BEAMOS_LASER_ENDPOINT, 128);
}
Screen->FastCombo(LAYER_BEAMOS2, Ghost_X, Ghost_Y, Combo, this->CSet, 128);
if(EyeAngle>0){
Screen->FastCombo(LAYER_BEAMOS2, EyeX, EyeY, EyeCombo, this->CSet, 128);
Screen->Rectangle(LAYER_BEAMOS2, LStartX, LStartY-1, LStartX+LDist, LStartY+1, COLOR_BEAMOS_LASER2, 1, LStartX, LStartY, Angle, true, 128);
Screen->Line(LAYER_BEAMOS2, LStartX, LStartY, LEndX, LEndY, COLOR_BEAMOS_LASER1, 1, 0, 0, 0, 128);
if(LaserLength<=BEAMOS_LASER_LENGTH)
Screen->FastCombo(LAYER_BEAMOS2, LStartX-8, LStartY-8, CMB_BEAMOS_LASER_ENDPOINT, CS_BEAMOS_LASER_ENDPOINT, 128);
Screen->FastCombo(LAYER_BEAMOS2, LEndX-8, LEndY-8, CMB_BEAMOS_LASER_ENDPOINT, CS_BEAMOS_LASER_ENDPOINT, 128);
}
Screen->FastCombo(LAYER_BEAMOS1, Ghost_X, Ghost_Y+16, Combo+1, this->CSet, 128);
}
CheckBeamosLaser(ghost, LStartX, LStartY, LEndX, LEndY);
Ghost_Waitframe(this, ghost, true, true);
}
for(int i=0; i<LaserCooldown; i++){
EyeAngle = WrapDegrees(EyeAngle+IncrementAngle);
Beamos_Draw(this, ghost, Combo, EyeAngle);
Ghost_Waitframe(this, ghost, true, true);
}
}
Beamos_Draw(this, ghost, Combo, EyeAngle);
Ghost_Waitframe(this, ghost, true, true);
}
}
}
//These following two functions are taken from theRandomHeader.zh. If you're using that, you can delete them here.
// Function to see if a box has collided with a line
bool lineBoxCollision(int lineX1, int lineY1, int lineX2, int lineY2, int boxX1, int boxY1, int boxX2, int boxY2, int boxBorder)
{
// Shrink down the box for the border
boxX1 += boxBorder; boxY1 += boxBorder;
boxX2 -= boxBorder; boxY2 -= boxBorder;
// If the line isn't vertical
if(lineX2!=lineX1)
{
float i0 = (boxX1 - lineX1)/(lineX2-lineX1);
float i1 = (boxX2 - lineX1)/(lineX2-lineX1);
float yA = lineY1 + i0*(lineY2-lineY1);
float yB = lineY1 + i1*(lineY2-lineY1);
if(Max(boxX1, boxX2) >= Min(lineX1, lineX2) && Min(boxX1, boxX2) <= Max(lineX1, lineX2) &&
Max(boxY1, boxY2) >= Min(lineY1, lineY2) && Min(boxY1, boxY2) <= Max(lineY1, lineY2))
{
if(Min(boxY1, boxY2) > Max(yA, yB) || Max(boxY1, boxY2) < Min(yA, yB))
return false;
else
return true;
}
else
return false;
}
// If the line is vertical
else if(lineX1 >= boxX1 && lineX1 <= boxX2)
{
// Basically we need to find the top and bottom y values of the line to check for intersection
float lineYMin = lineY1;
float lineYMax = lineY2;
if(lineYMin > lineYMax)
{
lineYMin = lineY2;
lineYMax = lineY1;
}
// If either point intersects
if((boxY1 >= lineYMin && boxY1 <= lineYMax) || (boxY2 >= lineYMin && boxY2 <= lineYMax))
return true;
}
return false;
} //! End of lineBoxCollision
// Function to get the difference between two angles
float angleDifference(float angle1, float angle2)
{
// Get the difference between the two angles
float dif = angle2 - angle1;
// Compensate for the difference being outside of normal bounds
if(dif >= PI)
dif -= 2 * PI;
else if(dif <= -1 * PI)
dif += 2 * PI;
return dif;
}