// This script makes an FFC act as a guard. If Link enters its field of
// vision, it will either warp him to another screen or become an enemy.
// It can also play a sound and display a message when it spots him.
// Setup
//
// The guard uses eight combos. The first four show it standing still,
// facing up, down, left, and right, respectively. The next four show
// it walking in the same directions. The combos facing upward must have
// numbers divisible by 4; that is, they must be in the left column of
// the list. The FFC must start out on one of the standing combos, even
// if it will walk constantly. If the combos are set up correctly, it
// will automatically figure out which way it's facing.
//
// Set flag 46 on flags that guards should not be able to see through.
// If the guard will move, set flag 44 and 45 to mark its path. It will
// always move straight ahead if it can. When it reaches the point where
// it can't go any farther, if it is standing on flag 45, it will stop
// and turn around for a moment before moving on. Both placed and inherent
// flags will work.
// Arguments
//
// If an argument has a default value, this will be used if you leave the
// value at 0. The script will try to correct invalid values except for D0.
//
// D0: Movement type
// Determines how the guard moves.
// 0: Walks along flags 44 and 45; at an intersection, the guard will
// turn left if possible
// 1: The same as 0, except it will try to turn right instead of left
// 2: The guard will not move at all
// 3: The guard will stand in place, turning clockwise
// 4: The guard will stand in place, turning counterclockwise
// 5: The guard stand in place, turning left and right
// D1: Reaction to Link
// What the guard will do if it sees Link.
// 0: Warp Link to another screen
// 1: Turn into an enemy
// D2: Speed
// How fast the guard will move. Affects both walking and turning speed.
// You can set a default below, which will be used if D2 is left at 0.
// D3: Message
// When the guard sees Link, it will show this message. If this is left
// at 0, no message will be displayed.
// D4: Destination DMap
// If the guard warps Link when it spots him, this is the DMap it will
// warp him to.
// D5: Destination screen
// If the guard warps Link when it spots him, this is the screen it will
// warp him to. Remember that this must be converted from the hexadecimal
// numbers used in ZQuest.
// D6: Enemy ID
// If the guard becomes an enemy when it spots Link, this is the ID number
// of the type of enemy it will become.
// D7: Screen->D index
// When the guard spots Link, it will set this screen variable. This will
// alert all other guards that use the same number, and potentially other
// scripts. This must be a whole number between 0 and 7. If you have both
// warping and enemy guards on the same screen, they should not use the
// same number. If this is set to a negative number, it will not be used.
// The guard's speed if D2 is left at 0.
const int CGUARD_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 CGUARD_SOUND=88;
// If this is set to anything but 0, the guard will notice Link if he's very
// close, even if he's out of sight.
const int CGUARD_FIND_LINK_IF_CLOSE=1;
// How close Link must be to be found when out of sight.
const int CGUARD_FIND_LINK_DISTANCE=24;
// The angle of the guard's field of vision.
const int CGUARD_VISION_ANGLE=75;
// The maximum distance at which the guard can see Link.
const int CGUARD_VISION_RANGE=160;
// Movement types
const int CGUARD_PATROL_LEFT=0;
const int CGUARD_PATROL_RIGHT=1;
const int CGUARD_STAND=2;
const int CGUARD_ROTATE_CW=3;
const int CGUARD_ROTATE_CCW=4;
const int CGUARD_LEFT_AND_RIGHT=5;
// Reaction types
const int CGUARD_WARP=0;
const int CGUARD_BECOME_ENEMY=1;
const int CGUARD_WALK_FLAG=44;
const int CGUARD_TURN_FLAG=45;
const int CGUARD_BLOCK_FLAG=46;
ffc script CastleGuard
{
void run(int movement, int reaction, float speed, int message,
int destDMap, int destScreen, int enemyID, int screenDIndex)
{
CheckStop(this);
// Infer direction from combo number
int direction=this->Data%4;
// First standing and walking combos
int baseStandCombo=this->Data-direction;
int baseWalkCombo=baseStandCombo+4;
// Initialize the screen variable
if(screenDIndex>=0)
Screen->D[screenDIndex]=0;
if(speed==0)
speed=CGUARD_DEFAULT_SPEED;
// Stand in place
if(movement==CGUARD_STAND)
{
while(true)
Wait(1, reaction, message, destDMap, destScreen,
enemyID, screenDIndex, this);
}
// Turn clockwise
else if(movement==CGUARD_ROTATE_CW)
{
while(true)
{
Wait(120/speed, reaction, message, destDMap, destScreen,
enemyID, screenDIndex, this);
direction=TurnRight(direction);
this->Data=baseStandCombo+direction;
}
}
// Turn clockwise
else if(movement==CGUARD_ROTATE_CCW)
{
while(true)
{
Wait(120/speed, reaction, message, destDMap, destScreen,
enemyID, screenDIndex, this);
direction=TurnLeft(direction);
this->Data=baseStandCombo+direction;
}
}
// Turn left and right
else if(movement==CGUARD_LEFT_AND_RIGHT)
{
while(true)
{
Wait(120/speed, reaction, message, destDMap, destScreen, enemyID, screenDIndex, this);
direction=TurnLeft(direction);
this->Data=baseStandCombo+direction;
Wait(120/speed, reaction, message, destDMap, destScreen,
enemyID, screenDIndex, this);
direction=TurnRight(direction);
this->Data=baseStandCombo+direction;
Wait(120/speed, reaction, message, destDMap, destScreen,
enemyID, screenDIndex, this);
direction=TurnRight(direction);
this->Data=baseStandCombo+direction;
Wait(120/speed, reaction, message, destDMap, destScreen,
enemyID, screenDIndex, this);
direction=TurnLeft(direction);
this->Data=baseStandCombo+direction;
}
}
// Patrol mode; don't need to compare again
this->Data=baseWalkCombo+direction;
while(true)
{
// Walk forward, if possible
if(CanMove(direction, speed, this))
Move(direction, speed, this);
// Can't go any farther this direction?
else
{
// Align with the tile, first
this->X+=8;
this->X-=this->X%16;
this->Y+=8;
this->Y-=this->Y%16;
// See if this was a "stop and turn" tile
if(Screen->ComboF[ComboAt(this->X, this->Y)]==CGUARD_TURN_FLAG ||
Screen->ComboI[ComboAt(this->X, this->Y)]==CGUARD_TURN_FLAG)
{
// If it was, then do so
direction=TurnAround(direction);
this->Data=baseStandCombo+direction;
Wait(60/speed, reaction, message, destDMap, destScreen,
enemyID, screenDIndex, this);
direction=TurnAround(direction);
}
// If this guard tries turning left first...
if(movement==CGUARD_PATROL_LEFT)
{
// Turn left and try moving that way
direction=TurnLeft(direction);
if(!CanMove(direction, speed, this))
{
// Maybe right?
direction=TurnAround(direction);
if(!CanMove(direction, speed, this))
// Better go back the other way, then
direction=TurnRight(direction);
}
}
// Try right first
else
{
direction=TurnRight(direction);
if(!CanMove(direction, speed, this))
{
direction=TurnAround(direction);
if(!CanMove(direction, speed, this))
direction=TurnLeft(direction);
}
}
// Keep going
this->Data=baseWalkCombo+direction;
Move(direction, speed, this);
}
Wait(1, reaction, message, destDMap, destScreen, enemyID,
screenDIndex, this);
}
}
bool CheckForLink(int direction, ffc this)
{
float distance;
float angle;
float adjustedAngle;
// Check the distance first
distance=Distance(this->X, this->Y, Link->X, Link->Y);
if(distance>CGUARD_VISION_RANGE)
return false;
if(CGUARD_FIND_LINK_IF_CLOSE!=0 && distance<=CGUARD_FIND_LINK_DISTANCE)
return true;
// See if the angle's good, then
angle=ArcTan(Link->X-this->X, Link->Y-this->Y)*180/PI;
adjustedAngle=angle;
if(direction==DIR_UP)
adjustedAngle+=90;
else if(direction==DIR_DOWN)
adjustedAngle-=90;
else if(direction==DIR_LEFT)
adjustedAngle+=180;
if(adjustedAngle>180)
adjustedAngle-=360;
if(Abs(adjustedAngle)>CGUARD_VISION_ANGLE/2)
return false;
// Check each tile along the line
// This is kind of a crude way of doing it, but it'll work for now
int checkX=this->X+8;
int checkY=this->Y+8;
float xDiff=Cos(angle)/5;
float yDiff=Sin(angle)/5;
int i;
for(i=0; i<=distance*5; i++)
{
if(Screen->ComboF[ComboAt(checkX, checkY)]==CGUARD_BLOCK_FLAG ||
Screen->ComboI[ComboAt(checkX, checkY)]==CGUARD_BLOCK_FLAG)
return false;
checkX+=xDiff;
checkY+=yDiff;
}
return true;
}
// Called instead of Waitframe(). Handles things that need to be done
// every frame.
void Wait(int numFrames, int reaction, int message, int destDMap,
int destScreen, int enemyID, int screenDIndex, ffc this)
{
// Can't take another argument, so just figure out the direction
int direction=this->Data%4;
for(; numFrames>0; numFrames--)
{
if(screenDIndex>=0)
{
// First, see if someone else just saw Link; if so...
if(Screen->D[screenDIndex]==1)
{
// A warping guard will let the other guy handle it
if(reaction==CGUARD_WARP)
Quit();
// An enemy guard will change
else
{
npc enemy=Screen->CreateNPC(enemyID);
enemy->X=this->X;
enemy->Y=this->Y;
enemy->Dir=direction;
this->Data=0;
Quit();
}
}
}
// See Link?
if(CheckForLink(direction, this))
{
// Alert the others, play the sound, and show the message
if(screenDIndex>=0)
Screen->D[screenDIndex]=1;
Game->PlaySound(CGUARD_SOUND);
if(message>0)
{
Screen->Message(message);
// If this is a warping guard, don't let Link move
if(reaction==CGUARD_WARP)
{
Waitframe();
CheckStop(this);
//while(!Link->InputA)
//{
// Link->InputB=false;
// Link->InputUp=false;
// Link->InputDown=false;
// Link->InputLeft=false;
// Link->InputRight=false;
// Waitframe();
// CheckStop(this);
//}
}
}
if(reaction==CGUARD_WARP)
Link->Warp(destDMap, destScreen);
else
// Become an enemy
{
npc enemy=Screen->CreateNPC(enemyID);
enemy->X=this->X;
enemy->Y=this->Y;
enemy->Dir=direction;
this->Data=0;
Quit();
}
}
Waitframe();
CheckStop(this);
}
}
// Find the guard's new direction if it turns left
int TurnLeft(int dir)
{
if((dir&10b)==0)
return (dir+10b);
else
return ((dir^11b)&11b);
}
// Find the guard's new direction if it turns right
int TurnRight(int dir)
{
if((dir&10b)==0)
return ((dir^11b)&11b);
else
return (dir-10b);
}
// Find the guard's new direction if it turns around
int TurnAround(int dir)
{
return dir^((~dir&10b)|01b);
}
// Checks whether the guard can walk on the tile it's trying to move to
bool CanMove(int dir, float speed, ffc this)
{
int xPos;
int yPos;
if(dir==DIR_UP)
{
xPos=this->X;
yPos=Floor(this->Y-speed);
}
else if(dir==DIR_DOWN)
{
xPos=this->X;
yPos=Ceiling(this->Y+15+speed);
}
else if(dir==DIR_LEFT)
{
xPos=Floor(this->X-speed);
yPos=this->Y;
}
else // Right
{
xPos=Ceiling(this->X+15+speed);
yPos=this->Y;
}
return Screen->ComboF[ComboAt(xPos, yPos)]==CGUARD_WALK_FLAG ||
Screen->ComboF[ComboAt(xPos, yPos)]==CGUARD_TURN_FLAG ||
Screen->ComboI[ComboAt(xPos, yPos)]==CGUARD_WALK_FLAG ||
Screen->ComboI[ComboAt(xPos, yPos)]==CGUARD_TURN_FLAG;
}
// 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;
}
// Check if it's time to quit.
void CheckStop(ffc this)
{
if(Screen->D[1]==1)
{
this->Data=0;
Quit();
}
}
}