//=============================================================================
// stdExtra.zh version 3.2
// Latest update:
//	* Added getFlip()
//=============================================================================

//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
const int SS_MOVES_LIMIT	= 30;	//How many moves to attempt in equipItemX()

//=============================================================================

//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 is at Y=-64

//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_UP )
		return 1.5 * PI;
	if ( dir == DIR_DOWN )
		return .5 * PI;
	if ( dir == DIR_LEFT )
		return PI;
	if ( dir == DIR_RIGHT )
		return 0;
}

int dirToDeg(int dir){
	if ( dir == DIR_UP )
		return 90;
	if ( dir == DIR_DOWN )
		return 270;
	if ( dir == DIR_LEFT )
		return 180;
	if ( dir == DIR_RIGHT )
		return 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 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);
}

//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;
		
	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;
	
	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;
	
	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;
	
	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;
		
	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;
		
	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]);
	}
}

//Tries to equip an item to A/B
//Gives up instead of freezing game if not found
//Item must be reachable by only moving right
//Returns true if item was selected; false if not
bool equipItemA(int id){
	//Switch until you're equipping it
	int movesLeft = SS_MOVES_LIMIT; //Failsafe: Don't crash game if item not found
	while ( movesLeft-- > 0 && GetEquipmentA() != id )
		Link->SelectAWeapon(DIR_RIGHT);
	return(GetEquipmentA() == id);
}
bool equipItemB(int id){
	//Switch until you're equipping it
	int movesLeft = SS_MOVES_LIMIT; //Failsafe: Don't crash game if item not found
	while ( movesLeft-- > 0 && GetEquipmentB() != id )
		Link->SelectBWeapon(DIR_RIGHT);
	return(GetEquipmentB() == id);
}


//===============================================================================
//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 < 8) dir |= 8;
	else dir &= ~8;
	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 ){
	if ( weapon->Dir == DIR_DOWN )
		weapon->Flip = FLIP_B;
	else if ( weapon->Dir == DIR_LEFT )
		weapon->Flip = ROTATE_CCW;
	else if ( weapon->Dir == DIR_RIGHT )
		weapon->Flip = ROTATE_CW;
}
void setFlip ( eweapon weapon ){
	if ( weapon->Dir == DIR_DOWN )
		weapon->Flip = FLIP_B;
	else if ( weapon->Dir == DIR_LEFT )
		weapon->Flip = ROTATE_CCW;
	else if ( weapon->Dir == DIR_RIGHT )
		weapon->Flip = ROTATE_CW;
}

//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, 56, 256, 176, 0, true); //Draw the bitmap
}

bool LinkOnComboType(int type){
	 if(Screen->ComboT[ComboAt(CenterLinkX(), CenterLinkY())] == type) return true;
	 return false;
}

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
}

//Taken from the Shop script by Joe123
bool SelectPressInput(int input){
	if(input == 0) return Link->PressA;
	else if(input == 1) return Link->PressB;
	else if(input == 2) return Link->PressL;
	else if(input == 3) return Link->PressR;
}
void SetInput(int input, bool state){
	if(input == 0) Link->InputA = state;
	else if(input == 1) Link->InputB = state;
	else if(input == 2) Link->InputL = state;
	else if(input == 3) Link->InputR = state;
}

//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);
//}

//===============================================================================
//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);
}