//=============================================================================
// stdExtra.zh version 3.6.1
// Latest update:
// * Updated InvertedCircle() to work properly in ZC 2.50.2
// (but not in earlier versions!)
//=============================================================================
//Pre-requisites
//import "std.zh"
//import "ffcscript.zh"
//import "string.zh"
//==== * Quest-Specific Settings * ============================================
//Text colors
const int COLOR_WHITE = 1;
const int COLOR_BLACK = 15;
//Combos
const int CMB_BLANK = 0; // Leave this be - combo 0 should always be invisible and no properties
const int CMB_FREEZEALL = 844; // Combo with "Freeze all (Except FFCs)" type
const int CMB_FREEZEFFC = 845; // Combo with "Freeze all (FFCs only)" type
//FFC Slots
const int FFC_FREEZEALL = 31;
const int FFC_FREEZEFFC = 32;
//Other Settings
const int MAX_ITEMS = 255; // Set to the highest item ID you use to make GetCurrentItem() more efficient
//=============================================================================
//Screen Dimensions
const int SCREEN_WIDTH = 256;
const int SCREEN_HEIGHT = 176;
const int SCREEN_VISIBLEHEIGHT = 168; // The height of the screen you can see (bottom 8 pixels cut off)
const int SCREEN_SSTOP = -64; // The top of the subscreen
const int SCREEN_COMBOS = 176; // Number of combos on screen
//Sprite Flips and rotations
const int FLIP_NO = 0; // Not flipped
const int FLIP_H = 1; // Horizontal
const int FLIP_V = 2; // Vertical
const int FLIP_B = 3; // Vertical & Horizontal
const int ROTATE_CW = 4; // Clockwise
const int ROTATE_CCW = 7; // Counter-clockwise
const int ROTATE_CW_FLIP = 5;
const int ROTATE_CCW_FLIP = 6;
//NPC Misc Flags
const int NPCMF_DAMAGE = 0x0001; // Damaged by Power 0 weapons
const int NPCMF_INVISIBLE = 0x0002; // Is invisible
const int NPCMF_NOTRETURN = 0x0004; // Never returns after death
const int NPCMF_NOTENEMY = 0x0008; // Doesn't count as beatable enemy
const int NPCMF_SPAWNFLICKER = 0x0010; // Spawn animation = flicker (???)
const int NPCMF_LENSONLY = 0x0020; // Can only be seen with Lens of Truth
const int NPCMF_FLASHING = 0x0040;
const int NPCMF_FLICKERING = 0x0080;
const int NPCMF_TRANSLUCENT = 0x0100;
const int NPCMF_SHIELDFRONT = 0x0200;
const int NPCMF_SHIELDLEFT = 0x0400;
const int NPCMF_SHIELDRIGHT = 0x0800;
const int NPCMF_SHIELDBACK = 0x1000;
const int NPCMF_HAMMERBREAK = 0x2000; // Hammer can break shield
//===============================================================================
//Reusable scripts
//===============================================================================
//Run an FFC script
//D0-D6: Arguments for the FFC Script
//D7: The script number to run
item script ffcItem
{
void run(int d0, int d1, int d2, int d3, int d4, int d5, int d6, int ffc_id)
{
int d[7] = {d0,d1,d2,d3,d4,d5,d6};
if(FindFFCRunning(ffc_id) <= 0)
{
RunFFCScriptOrQuit(ffc_id, d);
}
}
}
//===============================================================================
//Position and movement
//===============================================================================
//Prevents moving in any direction
void NoMovement()
{
Link->InputUp = false; Link->PressUp = false;
Link->InputDown = false; Link->PressDown = false;
Link->InputLeft = false; Link->PressLeft = false;
Link->InputRight = false; Link->PressRight = false;
}
//Converts velocity into a direction.
int VelocityToDir(float x, float y)
{
if(x == 0 && y == 0) return -1;
return RadianAngleDir8(RadianAngle(0, x, 0, y));
}
int VelocityToDir4(float x, float y)
{
if(x == 0 && y == 0) return -1;
return RadianAngleDir4(RadianAngle(0, x, 0, y));
}
//Converts the x component of a velocity into a direction.
int XSpeedToDir(float x)
{
return VelocityToDir(x, 0);
}
//Converts the y component of a velocity into a direction.
int YSpeedToDir(float y)
{
return VelocityToDir(0, y);
}
//Takes a direction of movement and gets the xspeed.
float DirToXSpeed(int dir)
{
if(dir<4)
return Cond((dir*2)-5 < -1, 0, (dir*2)-5);
else
return Cond(dir<6, -1.5, 1.5);
}
//Takes a direction of movement and gets the yspeed.
float DirToYSpeed(int dir)
{
if(dir > 7) dir = toggleBlock(dir);
if(dir < 2) return 0;
float ret = 1;
if(dir >= 4) ret += .5;
return Cond(IsEven(dir), -ret, ret);
}
//Returns the angle in radians of a direction; used for weapon angles
float dirToRad(int dir)
{
if ( dir == DIR_RIGHT )
return 0;
if ( dir == DIR_RIGHTDOWN )
return .25 * PI;
if ( dir == DIR_DOWN )
return .5 * PI;
if ( dir == DIR_LEFTDOWN )
return .75 * PI;
if ( dir == DIR_LEFT )
return PI;
if ( dir == DIR_LEFTUP )
return 1.25 * PI;
if ( dir == DIR_UP )
return 1.5 * PI;
if ( dir == DIR_RIGHTUP )
return 1.75 * PI;
return 0; // Default to 0
}
int dirToDeg(int dir)
{
if ( dir == DIR_RIGHT )
return 0;
if ( dir == DIR_RIGHTDOWN )
return 45;
if ( dir == DIR_DOWN )
return 90;
if ( dir == DIR_LEFTDOWN )
return 135;
if ( dir == DIR_LEFT )
return 180;
if ( dir == DIR_LEFTUP )
return 225;
if ( dir == DIR_UP )
return 270;
if ( dir == DIR_RIGHTUP )
return 315;
return 0; // Default to 0
}
// Returns value from 0 to 360 rather than -180 to 180
float AnglePos(int x1, int y1, int x2, int y2)
{
float angle = ArcTan(x2-x1, y2-y1)*57.2958;
if(angle < 0)
angle += 360;
return angle;
}
// Returns the X component of a radian angle.
float RadianVectorX(float len, float angle)
{
return len * RadianCos(angle);
}
// Returns the Y component of a radian angle.
float RadianVectorY(float len, float angle)
{
return len * RadianSin(angle);
}
//Returns the distance between the given Coordinate and Link's Center.
float DistanceLink(float x, float y)
{
return Distance(CenterLinkX(), CenterLinkY(), x, y);
}
//Returns the distance between Link's center and an object's center.
float DistanceLink(ffc f)
{
return Distance(CenterLinkX(), CenterLinkY(), CenterX(f), CenterY(f));
}
float DistanceLink(npc n)
{
return Distance(CenterLinkX(), CenterLinkY(), CenterX(n), CenterY(n));
}
float DistanceLink(lweapon l)
{
return Distance(CenterLinkX(), CenterLinkY(), CenterX(l), CenterY(l));
}
float DistanceLink(eweapon e)
{
return Distance(CenterLinkX(), CenterLinkY(), CenterX(e), CenterY(e));
}
//Converts 8-way direction to 4-way
int dir8ToDir4(int dir)
{
if(dir != Clamp(dir, 0, 15)) return -1;
dir &= 7;
if((dir & 4) == 0)
return dir;
else
return Cond(IsEven(dir), DIR_UP, DIR_DOWN);
}
//Returns the reverse of the given direction.
int reverseDir(int dir)
{
if(dir != Clamp(dir, 0, 15)) return -1; //Invalid direction
return Cond(dir<8, OppositeDir(dir), ((dir+4)%8)+8);
}
// Rotates a given direction
int rotateDir(int dir, bool ccw, bool eightway)
{
if(dir==DIR_UP) dir = 0;
else if(dir==DIR_RIGHTUP) dir = 1;
else if(dir==DIR_RIGHT) dir = 2;
else if(dir==DIR_RIGHTDOWN) dir = 3;
else if(dir==DIR_DOWN) dir = 4;
else if(dir==DIR_LEFTDOWN) dir = 5;
else if(dir==DIR_LEFT) dir = 6;
else if(dir==DIR_LEFTUP) dir = 7;
else return -1; //Invalid Direction;
if(eightway)
dir=(dir+Cond(ccw, 7,1))%8;
else
dir=(dir+Cond(ccw,6,2))%8; //Will never be odd. And needs to wrap by 8 regardless.
if(dir==0) return DIR_UP;
else if(dir==1) return DIR_RIGHTUP;
else if(dir==2) return DIR_RIGHT;
else if(dir==3) return DIR_RIGHTDOWN;
else if(dir==4) return DIR_DOWN;
else if(dir==5) return DIR_LEFTDOWN;
else if(dir==6) return DIR_LEFT;
else if(dir==7) return DIR_LEFTUP;
}
//Move the specified object a set distance in a set direction
//Walkable: Don't move onto a solid space
//PreventOffScreen: Don't move off screen
bool moveLink ( int dir, int dist, bool walkable, bool preventOffScreen )
{
//Can't move
if ( walkable && !CanWalk(Link->X, Link->Y, dir, dist, false) )
return false;
//Otherwise, check direction
if ( dir == DIR_UP && (!preventOffScreen || Link->Y - dist > 0) )
Link->Y -= dist;
else if ( dir == DIR_DOWN && (!preventOffScreen || Link->Y + dist < SCREEN_HEIGHT) )
Link->Y += dist;
else if ( dir == DIR_LEFT && (!preventOffScreen || Link->X - dist > 0))
Link->X -= dist;
else if ( dir == DIR_RIGHT && (!preventOffScreen || Link->X + dist < SCREEN_WIDTH) )
Link->X += dist;
else
return false;
return true;
}
bool move ( ffc this, int dir, int dist, bool walkable, bool preventOffScreen )
{
//Can't move
if ( walkable && !CanWalk(this->X, this->Y, dir, dist, false) )
return false;
//Otherwise, check direction
if ( dir == DIR_UP && (!preventOffScreen || this->Y - dist > 0) )
this->Y -= dist;
else if ( dir == DIR_DOWN && (!preventOffScreen || this->Y + dist < SCREEN_HEIGHT) )
this->Y += dist;
else if ( dir == DIR_LEFT && (!preventOffScreen || this->X - dist > 0))
this->X -= dist;
else if ( dir == DIR_RIGHT && (!preventOffScreen || this->X + dist < SCREEN_WIDTH) )
this->X += dist;
else
return false;
return true;
}
bool move ( npc enem, int dir, int dist, bool walkable, bool preventOffScreen )
{
//Can't move
if ( walkable && !CanWalk(enem->X, enem->Y, dir, dist, false) )
return false;
//Otherwise, check direction
if ( dir == DIR_UP && (!preventOffScreen || enem->Y - dist > 0) )
enem->Y -= dist;
else if ( dir == DIR_DOWN && (!preventOffScreen || enem->Y + dist < SCREEN_HEIGHT) )
enem->Y += dist;
else if ( dir == DIR_LEFT && (!preventOffScreen || enem->X - dist > 0))
enem->X -= dist;
else if ( dir == DIR_RIGHT && (!preventOffScreen || enem->X + dist < SCREEN_WIDTH) )
enem->X += dist;
else
return false;
return true;
}
bool move ( lweapon weap, int dir, int dist, bool walkable, bool preventOffScreen )
{
//Can't move
if ( walkable && !CanWalk(weap->X, weap->Y, dir, dist, false) )
return false;
//Otherwise, check direction
if ( dir == DIR_UP && (!preventOffScreen || weap->Y - dist > 0) )
weap->Y -= dist;
else if ( dir == DIR_DOWN && (!preventOffScreen || weap->Y + dist < SCREEN_HEIGHT) )
weap->Y += dist;
else if ( dir == DIR_LEFT && (!preventOffScreen || weap->X - dist > 0))
weap->X -= dist;
else if ( dir == DIR_RIGHT && (!preventOffScreen || weap->X + dist < SCREEN_WIDTH) )
weap->X += dist;
else
return false;
return true;
}
bool move ( eweapon weap, int dir, int dist, bool walkable, bool preventOffScreen )
{
//Can't move
if ( walkable && !CanWalk(weap->X, weap->Y, dir, dist, false) )
return false;
//Otherwise, check direction
if ( dir == DIR_UP && (!preventOffScreen || weap->Y - dist > 0) )
weap->Y -= dist;
else if ( dir == DIR_DOWN && (!preventOffScreen || weap->Y + dist < SCREEN_HEIGHT) )
weap->Y += dist;
else if ( dir == DIR_LEFT && (!preventOffScreen || weap->X - dist > 0))
weap->X -= dist;
else if ( dir == DIR_RIGHT && (!preventOffScreen || weap->X + dist < SCREEN_WIDTH) )
weap->X += dist;
else
return false;
return true;
}
bool move ( item theItem, int dir, int dist, bool walkable, bool preventOffScreen )
{
//Can't move
if ( walkable && !CanWalk(theItem->X, theItem->Y, dir, dist, false) )
return false;
//Otherwise, check direction
if ( dir == DIR_UP && (!preventOffScreen || theItem->Y - dist > 0) )
theItem->Y -= dist;
else if ( dir == DIR_DOWN && (!preventOffScreen || theItem->Y + dist < SCREEN_HEIGHT) )
theItem->Y += dist;
else if ( dir == DIR_LEFT && (!preventOffScreen || theItem->X - dist > 0))
theItem->X -= dist;
else if ( dir == DIR_RIGHT && (!preventOffScreen || theItem->X + dist < SCREEN_WIDTH) )
theItem->X += dist;
else
return false;
return true;
}
//===============================================================================
//Items and Equipment
//===============================================================================
//Returns true if Link is pressing the button for an item
bool pressingItem(int id)
{
return ( (GetEquipmentA()==id && Link->PressA)
||(GetEquipmentB()==id && Link->PressB) );
}
//Returns the id of the highest level item of the given class that Link has acquired.
//Unlike GetHighestLevelItem(), only applies to items Link owns
int GetCurrentItem(int itemclass)
{
itemdata id;
int ret = -1;
int curlevel = -1000;
for(int i = 0; i < MAX_ITEMS; i++){
if(!Link->Item[i])
continue;
id = Game->LoadItemData(i);
if(id->Family != itemclass)
continue;
if(id->Level > curlevel){
curlevel = id->Level;
ret = i;
}
}
return ret;
}
//Gives the specified item with hold up animation and optional fanfare
//keep: Whether to actually give the item
//twoHand: Use 1 or 2 hand animation
//sfx: Whether to play item fanfare
void holdUpItem(int id, bool keep, bool twoHand, bool sfx)
{
if ( sfx )
Game->PlaySound(SFX_PICKUP);
if ( twoHand )
Link->Action = LA_HOLD2LAND;
else
Link->Action = LA_HOLD1LAND;
Link->HeldItem = id;
//Give the item and its counter effects
if(keep){
Link->Item[id] = true;
itemdata data = Game->LoadItemData(id);
//Increase capacity
if(data->MaxIncrement > 0 && data->Max > Game->MCounter[data->Counter]){
Game->MCounter[data->Counter] = Min(Game->MCounter[data->Counter]+data->MaxIncrement, data->Max);
}
//Increase count
if(data->Amount > 0)
Game->Counter[data->Counter] = Min(Game->Counter[data->Counter]+data->Amount, Game->MCounter[data->Counter]);
}
}
//===============================================================================
//Screen Freezing
//===============================================================================
//WARNING: DO NOT USE IN AN FFC SCRIPT OR THE FFC WILL FREEZE ITSELF!
//Use in global scripts only.
void freezeScreen()
{
ffc freezeAll = Screen->LoadFFC(FFC_FREEZEALL);
freezeAll->Data = CMB_FREEZEALL;
ffc freezeFFC = Screen->LoadFFC(FFC_FREEZEFFC);
freezeFFC->Data = CMB_FREEZEFFC;
}
void unfreezeScreen()
{
ffc freezeAll = Screen->LoadFFC(FFC_FREEZEALL);
freezeAll->Data = CMB_BLANK;
ffc freezeFFC = Screen->LoadFFC(FFC_FREEZEFFC);
freezeFFC->Data = CMB_BLANK;
}
//===============================================================================
//Weapons
//===============================================================================
//Toggles weapon blockability by adjusting its direction
int toggleBlock (int dir)
{
if(dir == DIR_UP)
return 8;
if(dir == DIR_DOWN)
return 12;
if(dir == DIR_LEFT)
return 14;
if(dir == DIR_RIGHT)
return 10;
if(dir == DIR_LEFTUP)
return 15;
if(dir == DIR_RIGHTUP)
return 9;
if(dir == DIR_LEFTDOWN)
return 13;
if(dir == DIR_RIGHTDOWN)
return 11;
if(dir==8)
return DIR_UP;
if(dir==9)
return DIR_RIGHTUP;
if(dir==10)
return DIR_RIGHT;
if(dir==11)
return DIR_RIGHTDOWN;
if(dir==12)
return DIR_DOWN;
if(dir==13)
return DIR_LEFTDOWN;
if(dir==14)
return DIR_LEFT;
if(dir==15)
return DIR_LEFTUP;
return dir;
}
//Get the correct flip for a 4-dir weapon based on its direction
int getFlip(int dir)
{
if ( dir == DIR_UP )
return FLIP_NO;
if ( dir == DIR_DOWN )
return FLIP_B;
if ( dir == DIR_LEFT )
return ROTATE_CCW;
if ( dir == DIR_RIGHT )
return ROTATE_CW;
return -1;
}
//Sets the flip for a 4-dir weapon based on a single sprite
void setFlip ( lweapon weapon )
{
int flip = getFlip(weapon->Dir);
if(flip >= 0)
weapon->Flip = flip;
}
void setFlip ( eweapon weapon ){
int flip = getFlip(weapon->Dir);
if(flip >= 0)
weapon->Flip = flip;
}
//Sets flip for a 4-dir sword based on two sprites (up and right)
void setFlipSword ( lweapon weapon ){
if ( weapon->Dir >= DIR_LEFT )
weapon->Tile++;
if ( weapon->Dir == DIR_DOWN )
weapon->Flip = FLIP_B;
else if ( weapon->Dir == DIR_LEFT )
weapon->Flip = FLIP_H;
}
void setFlipSword ( eweapon weapon ){
if ( weapon->Dir >= DIR_LEFT )
weapon->Tile++;
if ( weapon->Dir == DIR_DOWN )
weapon->Flip = FLIP_B;
else if ( weapon->Dir == DIR_LEFT )
weapon->Flip = FLIP_H;
}
//===============================================================================
//Other
//===============================================================================
void permaSecrets(){
Screen->TriggerSecrets();
Screen->State[ST_SECRET] = true;
}
void tempSecrets(){
Screen->TriggerSecrets();
Screen->State[ST_SECRET] = false;
}
//Makes secrets permanent if "Secrets are temporary" is not checked
void screenSecrets(){
Screen->TriggerSecrets();
if(!(Screen->Flags[SF_SECRETS]&2)){
Screen->State[ST_SECRET] = true;
}
}
bool WaitframeCheckScreenChange(){
int old_dmap_screen = Game->GetCurDMapScreen();
int old_dmap = Game->GetCurDMap();
Waitframe();
return (old_dmap!=Game->GetCurDMap() || old_dmap_screen!=Game->GetCurDMapScreen());
}
bool WaitframeCheckWarp(){
return ( WaitframeCheckScreenChange() && !(Link->Action==LA_SCROLLING));
}
//Draw an inverted circle (fill whole screen except circle)
void InvertedCircle(int bitmapID, int layer, int x, int y, int radius, int scale, int fillcolor){
Screen->SetRenderTarget(bitmapID); //Set the render target to the bitmap.
Screen->Rectangle(layer, 0, 0, 256, 176, fillcolor, 1, 0, 0, 0, true, 128); //Cover the screen
Screen->Circle(layer, x, y, radius, 0, scale, 0, 0, 0, true, 128); //Draw a transparent circle.
Screen->SetRenderTarget(RT_SCREEN); //Set the render target back to the screen.
Screen->DrawBitmap(layer, bitmapID, 0, 0, 256, 176, 0, 0, 256, 176, 0, true); //Draw the bitmap
}
bool LinkOnComboType(int type){
return(Screen->ComboT[ComboAt(CenterLinkX(), CenterLinkY())] == type);
}
bool LinkOnTallGrass(){
return ( LinkOnComboType(CT_TALLGRASS)
|| LinkOnComboType(CT_TALLGRASSC)
|| LinkOnComboType(CT_TALLGRASSNEXT)
);
}
void closingWipe(int wipetime){
for(int i = wipetime; i > 0; i--){
InvertedCircle(4, 6, CenterLinkX(), CenterLinkY(), Floor(300/wipetime)*i, 1, 15);
WaitNoAction();
}
}
void openingWipe(int wipetime){
for(int i = 0; i < wipetime; i++){
InvertedCircle(4, 6, CenterLinkX(), CenterLinkY(), Floor(300/wipetime)*i, 1, 15);
WaitNoAction();
}
}
//Get the color value given CSet and in-CSet color
int color(int cset, int csetColor){
return (cset*16) + csetColor;
}
//Simulates a 2D array
//Returns the index of an array given row, column, and number of rows
int arr2D ( int row, int col, int numCols ){
return (row * numCols) + col;
}
//Extracted this method from ffcscript.zh for common usage
//If force is true, takes over the last FFC even if it's used
ffc loadUnusedFFC(bool force){
ffc theFFC;
for(int i = FFCS_MIN_FFC; i <= FFCS_MAX_FFC; i++){
theFFC=Screen->LoadFFC(i); //Check each FFC
if ( ffcIsBlank(theFFC) )
return theFFC; //Return it
if ( force && i == FFCS_MAX_FFC ) //Force last FFC
return theFFC;
}
//No FFC found; return an invalid one
ffc invalidFFC;
return invalidFFC;
}
//Tells whether an FFC is blank and unused
bool ffcIsBlank(ffc this){
return ( this->Script == 0 && //If not running a script
( this->Data == 0 || this->Data == FFCS_INVISIBLE_COMBO)); //And blank combo
}
//Draws the given time in frames as minutes and seconds
//Taken from "Hot Rooms" by Zecora
void drawTime(int layer, int x, int y, int frames){
drawTime(layer, x, y, FONT_S, COLOR_WHITE, COLOR_BLACK, TF_NORMAL, frames);
}
void drawTime(int layer, int x, int y, int font, int color, int bgcolor, int format, int frames){
int seconds = Div(frames, 60); //Total seconds
int minutes = Div(seconds, 60); //Total minutes
seconds %= 60; //Remaining seconds (0 - 59)
int string[5]; //Create an array of characters.
itoa(string, 0, minutes); //Add the minutes to the array.
string[strlen(string)] = ':'; //Add the : after the minutes.
if(seconds < 10) //Single-digit seconds: add '0' before count
string[strlen(string)] = '0';
itoa(string, strlen(string), seconds);
Screen->DrawString(layer, x, y, font, color, bgcolor, format, string, 128);
}
// Has a 1 in n chance of returning true
bool nChance(int chance)
{
return Rand(chance) == 0;
}
// Given integer n, has an n% chance of returning true.
bool percentChance(int chance)
{
return Rand(100) < chance;
}
// Rounds n to the nearest multiple of m.
// Assumes n and m are both positive.
float roundTo(float n, float m)
{
float remainder = n % m;
n -= remainder;
if(remainder > m / 2) {
n += m;
}
return n;
}
//===============================================================================
//Debug
//===============================================================================
//Shortcut for drawInteger for debug output
//Each debug item should have a unique "num" (match between value and label)
void debugValue ( int num, float value ){
debugValue(num, value, 0);
}
void debugValue ( int num, float value, int places ){
places = Clamp(places, 0, 4);
Screen->DrawInteger(6, 100, 2+10*num, FONT_S, COLOR_WHITE, COLOR_BLACK, -1, -1, value, places, OP_OPAQUE);
}
void debugValue( int num, bool value){
int trueString[] = "true";
int falseString[] = "false";
Screen->DrawString(6, 100, 2+10*num, FONT_S, COLOR_WHITE, COLOR_BLACK, TF_NORMAL, Cond(value, trueString, falseString), OP_OPAQUE);
}
void debugLabel ( int num, int string ){
Screen->DrawString(6, 2, 2+10*num, FONT_S, COLOR_WHITE, COLOR_BLACK, TF_NORMAL, string, OP_OPAQUE);
}
//Both functions in one call, matching "num"
void debug ( int num, int string, float value ){
debugLabel(num, string);
debugValue(num, value, 0);
}
void debug ( int num, int string, float value, int places ){
debugLabel(num, string);
debugValue(num, value, places);
}
void debug ( int num, int string, bool value ){
debugLabel(num, string);
debugValue(num, value);
}