import "std.zh"
const int CMB_AUTOWARP = 3;//An autowarp A combo.
// The guard's speed if D2 is left at 0.
const int CG_DEFAULT_SPEED = 0.75;
// The guard will play this sound when it sees Link.
// If this is set to 0, no sound will be played.
const int CG_SOUND = 60;
bool CG_FIND_LINK_IF_CLOSE = true
// How close Link must be to be found when out of sight.
const int CG_FIND_LINK_DISTANCE = 24;
// The angle of the guard's field of vision.
const int CG_VISION_ANGLE = 75;//You may want to reduce this slightly.
// The maximum distance at which the guard can see Link.
const int CG_VISION_RANGE = 160;//You'll almost certainly want to reduce this, since the guard seems to be able to see you anywhere.
// The guard will walk along a path of this flag
const int CG_FLAG_WALK = 8;//Changed to raft flag to see if it any flag could be used.
//Apparently true.
// This flag blocks the guard's vision
const int CG_FLAG_BLOCK = 103;//Changed to raft bounce flag for same reason.
// Movement types
const int CG_MV_PATROL_LEFT = 0;
const int CG_MV_PATROL_RIGHT = 1;
const int CG_MV_STAND = 2;
const int CG_MV_ROTATE_CW = 3;
const int CG_MV_ROTATE_CCW = 4;
const int CG_MV_LEFT_AND_RIGHT = 5;
// Reaction types
const int CG_RE_WARP = 0;
const int CG_RE_BECOME_ENEMY = 1;
// ffc->Misc[] index
const int CG_IDX_ALERT = 0;
ffc script CastleGuard{
void run(int movement, int dir, int reaction, float speed,
int message, int enemyID){
// First standing and walking combos
int standCombo=this->Data-dir;
int walkCombo=standCombo+4;
if(speed==0)
speed=CG_DEFAULT_SPEED;
// Stand in place
if(movement==CG_MV_STAND){
while(true)
CGWaitframe(this, dir, reaction, message, enemyID);
}
// Turn in place
else if(movement==CG_MV_ROTATE_CW || movement==CG_MV_ROTATE_CCW){
while(true){
CGWaitframes(this, dir, reaction, message, enemyID, 120/speed);
if(movement==CG_MV_ROTATE_CW)
dir=RightFrom(dir);
else // CCW
dir=LeftFrom(dir);
this->Data=standCombo+dir;
}
}
// Turn left and right
else if(movement==CG_MV_LEFT_AND_RIGHT){
// 3 0 1 2 = left right right left
for(int i=3; true; i=(i+1)%4){
CGWaitframes(this, dir, reaction, message, enemyID, 120/speed);
if(i>=2)
dir=LeftFrom(dir);
else
dir=RightFrom(dir);
this->Data=standCombo+dir;
}
}
else{ // Patrolling
int walkCombo=standCombo+4;
int dist=GetWalkDistance(this, dir);
int step;
while(true){
this->Data=walkCombo+dir;
// Walk as far as possible
for(dist; dist>0; dist-=speed){
step=Min(speed, dist);
if(dir==DIR_UP)
this->Y-=step;
else if(dir==DIR_DOWN)
this->Y+=step;
else if(dir==DIR_LEFT)
this->X-=step;
else // Right
this->X+=step;
CGWaitframe(this, dir, reaction, message, enemyID);
}
// Can't go any farther; turn
if(movement==CG_MV_PATROL_LEFT){
// Try turning left
dir=LeftFrom(dir);
dist=GetWalkDistance(this, dir);
if(dist>0)
continue;
// Can't go that way; try right
dir=OppositeDir(dir);
dist=GetWalkDistance(this, dir);
if(dist>0)
continue;
// Can't go that way, either; just turn around
dir=RightFrom(dir);
dist=GetWalkDistance(this, dir);
}
else{ // Patrol/right
// Same thing, opposite directions
dir=RightFrom(dir);
dist=GetWalkDistance(this, dir);
if(dist>0)
continue;
dir=OppositeDir(dir);
dist=GetWalkDistance(this, dir);
if(dist>0)
continue;
dir=LeftFrom(dir);
dist=GetWalkDistance(this, dir);
}
}
}
}
bool CanSeeLink(int dir, ffc this){
// Is Link invisible?
if(Link->Invisible)
return false;
if(this->X==Link->X && this->Y==Link->Y) // It can happen
return true;
// Check the distance
float distance=Distance(this->X, this->Y, Link->X, Link->Y);
if(distance>CG_VISION_RANGE)
return false;
if(CG_FIND_LINK_IF_CLOSE && distance<=CG_FIND_LINK_DISTANCE)
return true;
// See if the angle's okay
float angle=ArcTan(Link->X-this->X, Link->Y-this->Y)*180/PI;
float adjustedAngle=angle;
if(dir==DIR_UP)
adjustedAngle+=90;
else if(dir==DIR_DOWN)
adjustedAngle-=90;
else if(dir==DIR_LEFT)
adjustedAngle+=180;
if(adjustedAngle>180)
adjustedAngle-=360;
if(Abs(adjustedAngle)>CG_VISION_ANGLE/2)
return false;
// Check each tile along the line
float x=this->X+8;
float y=this->Y+8;
float endX=Link->X+8;
float endY=Link->Y+8;
float xDiff=endX-x;
float yDiff=endY-y;
float xRatio;
float yRatio;
int nextX;
int nextY;
float top=Min(y, endY)&0xF0;
float bottom=(Max(y, endY)&0xF0)+15;
float left=Min(x, endX)&0xF0;
float right=(Max(x, endX)&0xF0)+15;
bool leftToRight=x<endX;
bool topToBottom=y<endY;
bool nextIsX;
xRatio=xDiff;
if(yDiff!=0)
xRatio/=yDiff;
yRatio=yDiff;
if(xDiff!=0)
yRatio/=xDiff;
while(y>=top && y<=bottom && x>=left && x<=right){
if(ComboFI(x,y,CG_FLAG_BLOCK))
return false;
// Find the next tile edge each way
if(leftToRight)
nextX=(x&0xF0)+16;
else
nextX=(x&0xF0)-1;
if(topToBottom)
nextY=(y&0xF0)+16;
else
nextY=(y&0xF0)-1;
// Does X or Y come next?
if(xRatio==0)
nextIsX=false;
else if(yRatio==0)
nextIsX=true;
else
nextIsX=Abs((nextX-x)/xDiff)<Abs((nextY-y)/yDiff);
// Move to the next tile
if(nextIsX){
y+=yRatio*(nextX-x);
x=nextX;
}
else{
x+=xRatio*(nextY-y);
y=nextY;
}
}
return true;
}
// Called instead of Waitframe(). Handles things that need to be done
// every frame.
void CGWaitframe(ffc this, int dir, int reaction, int message, int enemyID){
// First, see if someone else just saw Link
if(this->Misc[CG_IDX_ALERT]==1)
{
// A warping guard won't react
if(reaction==CG_RE_WARP)
this->Misc[CG_IDX_ALERT]=0;
// An enemy guard will change
else{
npc enemy=Screen->CreateNPC(enemyID);
enemy->X=this->X;
enemy->Y=this->Y;
enemy->Dir=dir;
this->Data=0;
Quit();
}
}
// See Link?
if(CanSeeLink(dir, this)){
// Alert the others, play the sound, and show the message
ffc other;
for(int i=1; i<=32; i++){
other=Screen->LoadFFC(i);
if(other->Script==this->Script)
other->Misc[CG_IDX_ALERT]=1;
}
Game->PlaySound(CG_SOUND);
if(message>0){
Screen->Message(message);
// If this is a warping guard, don't let Link move
if(reaction==CG_RE_WARP){
while(!Link->InputA)
WaitNoAction();
}
}
if(reaction==CG_RE_WARP)
this->Data = CMB_AUTOWARP;
//This doesn't always seem to work. I think it might because of the enormous vision range of the guards.
else{ // Become an enemy
npc enemy=Screen->CreateNPC(enemyID);
enemy->X=this->X;
enemy->Y=this->Y;
enemy->Dir=dir;
this->Data=0;
Quit();
}
}
Waitframe();
}
void CGWaitframes(ffc this, int dir, int reaction, int message, int enemyID, int numFrames)
{
for(numframes; numFrames>0; numFrames--)
CGWaitframe(this, dir, reaction, message, enemyID);
}
// Find the guard's new direction if it turns left
int LeftFrom(int dir){
if(dir==DIR_UP)
return DIR_LEFT;
else if(dir==DIR_DOWN)
return DIR_RIGHT;
else if(dir==DIR_LEFT)
return DIR_DOWN;
else
return DIR_UP;
}
// Find the guard's new direction if it turns right
int RightFrom(int dir){
if(dir==DIR_UP)
return DIR_RIGHT;
else if(dir==DIR_DOWN)
return DIR_LEFT;
else if(dir==DIR_LEFT)
return DIR_UP;
else
return DIR_DOWN;
}
// Find how far a patrolling guard can walk
int GetWalkDistance(ffc this, int dir){
int numFlags=0;
int x=this->X;
int y=this->Y;
// Count walk flags in the given direction; assume the guard's on a flag already
while(true){
// Check every 16 pixels
if(dir==DIR_UP)
y-=16;
else if(dir==DIR_DOWN)
y+=16;
else if(dir==DIR_LEFT)
x-=16;
else // Right
x+=16;
// Is the next tile offscreen? Can't go farther than that.
if(x<0 || x>255 || y<0 || y>176)
return numFlags*16;
// No more flags? That's it.
else if(!ComboFI(x,y,CG_FLAG_WALK)
return numFlags*16;
else
numFlags++;
}
}
// Walks forward
void Move(int dir, float speed, ffc this){
if(dir==DIR_UP)
this->Y-=speed;
else if(dir==DIR_DOWN)
this->Y+=speed;
else if(dir==DIR_LEFT)
this->X-=speed;
else // Right
this->X+=speed;
}
}