Copy to Clipboard Test

Solid FFCs/Sideview Moving Platforms Code

const int SPR_LINKCRUSH = 88; //Sprite of Link being crushed
const int SFX_LINKCRUSH = 11; //Sound that plays when Link gets crushed
const int SFX_ENEMYCRUSH = 11; //Sound that plays when an enemy gets crushed
const int DAMAGE_LINKCRUSH = 8; //Damage taken from being crushed

const int DELAY_CRUSH = 40; //Delay before Link respawns after being crushed
const int DELAYMAX_CRUSH = 300; //Max delay before Link respawns

const int SOLIDOBJ_MAX = 32; //Maximum number of solid objects
const int SOLIDOBJ_CRUSH_BEHAVIOR = 1; //How solid objects behave with crushing Link (0=No interaction, 1=Return to screen entrance, 2=Instant death)
const int SOLIDOBJ_CRUSH_REPOSITION = 1; //If crushing Link should move him to the room entrance
const int SOLIDOBJ_COMPLEX_CRUSH_ANIM = 1; //If crushing animation should have 6 animations or just 2
const int SOLIDOBJ_PUSH_NPC = 0; //Whether or not to push NPCs

const int SOLIDOBJ_CRUSH_SAFETY = 4; //Safety pixels before being crushed

const int SOLIDOBJ_LINKXTRIM = 3; //Amount trimmed from the sides of Link's hitbox
const int SOLIDOBJ_LINKYTRIM = 0; //Amount trimmed from the top of Link's hitbox in sideview

//Internal constants - Please don't change these

const int __SOLIDOBJ_COUNT = 0; //Array index keeping track of the number of solid objects
const int __SOLIDOBJ_STARTLINKX = 1; //Link's starting X position
const int __SOLIDOBJ_STARTLINKY = 2; //Link's starting Y position
const int __SOLIDOBJ_CRUSHCOUNTER = 3; //Array index for the timer keeping track of Link getting crushed
const int __SOLIDOBJ_LASTDMAP = 4; //Last DMap visited
const int __SOLIDOBJ_LASTSCREEN = 5; //Last screen visited
const int __SOLIDOBJ_ONPLATFORM = 6; //The current platform Link is on
const int __SOLIDOBJ_FORCERESPAWNCOUNTER = 7; //How many frames until Link is force to respawn, regardless of if a block is covering him
const int __SOLIDOBJ_FORCELINKX = 8; //X position where Link got crushed
const int __SOLIDOBJ_FORCELINKY = 9; //Y position where Link got crushed
const int __SOLIDOBJ_LINKLASTX = 10; //Link's last X position
const int __SOLIDOBJ_LINKLASTY = 11; //Link's last Y position

const int __SOLIDOBJ_STARTINDEX = 12; //Starting index for objects in the array
const int __SOLIDOBJ_NUMATTRIBINDEX = 8; //Number of indices in an object

//Array indices (__SOLIDOBJ_STARTINDEX+__SOLIDOBJ_NUMATTRIBINDEX*y+x) for various attributes of each object 
//where y is the object ID and x is the property
const int __SOLIDOBJ_OBJ_X = 0;
const int __SOLIDOBJ_OBJ_Y = 1;
const int __SOLIDOBJ_OBJ_WIDTH = 2;
const int __SOLIDOBJ_OBJ_HEIGHT = 3;
const int __SOLIDOBJ_OBJ_VX = 4;
const int __SOLIDOBJ_OBJ_VY = 5;
const int __SOLIDOBJ_OBJ_ID = 6;
const int __SOLIDOBJ_OBJ_FLAGS = 7;

const int SFFCF_TOPONLY = 00000001b; //Only the top face of the FFC is solid
const int SFFCF_PUSHNPC = 00000010b; //Can push NPCs

int SolidObjects[268]; //Buffer for the solid object. Size should be 12+SOLIDOBJ_MAX*8

void SolidObjects_Add(int ID, int x, int y, int width, int height, int vX, int vY, int flags){
	int i = __SOLIDOBJ_STARTINDEX+SolidObjects[__SOLIDOBJ_COUNT]*__SOLIDOBJ_NUMATTRIBINDEX; //Get the starting index of the object
	
	//Set all the object's attributes
	SolidObjects[i+__SOLIDOBJ_OBJ_X] = x;
	SolidObjects[i+__SOLIDOBJ_OBJ_Y] = y;
	SolidObjects[i+__SOLIDOBJ_OBJ_WIDTH] = width;
	SolidObjects[i+__SOLIDOBJ_OBJ_HEIGHT] = height;
	SolidObjects[i+__SOLIDOBJ_OBJ_VX] = vX;
	SolidObjects[i+__SOLIDOBJ_OBJ_VY] = vY;
	SolidObjects[i+__SOLIDOBJ_OBJ_ID] = ID;
	SolidObjects[i+__SOLIDOBJ_OBJ_FLAGS] = flags;
	
	//Increment the count so the script knows where to add the next object
	SolidObjects[__SOLIDOBJ_COUNT] = Min(SolidObjects[__SOLIDOBJ_COUNT]+1, SOLIDOBJ_MAX);
}

void SolidObjects_Init(){
	//Reset global variables to their default states
	SolidObjects[__SOLIDOBJ_COUNT] = 0;
	SolidObjects[__SOLIDOBJ_STARTLINKX] = Link->X;
	SolidObjects[__SOLIDOBJ_STARTLINKY] = Link->Y;
	SolidObjects[__SOLIDOBJ_CRUSHCOUNTER] = 0;
	SolidObjects[__SOLIDOBJ_LASTDMAP] = Game->GetCurDMap();
	SolidObjects[__SOLIDOBJ_LASTSCREEN] = Game->GetCurScreen();
	SolidObjects[__SOLIDOBJ_ONPLATFORM] = 0;
}

void SolidObjects_Update1(){
	if(Link->Action != LA_SCROLLING){
		//If Link is currently being crushed
		if(SolidObjects[__SOLIDOBJ_CRUSHCOUNTER]>0){
			NoAction();
			Link->Jump = 0;
			SolidObjects[__SOLIDOBJ_CRUSHCOUNTER]--;
			Link->CollDetection = false;
			Link->Invisible = true;
			Link->X = SolidObjects[__SOLIDOBJ_FORCELINKX];
			Link->Y = SolidObjects[__SOLIDOBJ_FORCELINKY];
			//When the counter hits 0
			if(SolidObjects[__SOLIDOBJ_CRUSHCOUNTER]==0){
				if(SOLIDOBJ_CRUSH_REPOSITION){
					Link->X = SolidObjects[__SOLIDOBJ_STARTLINKX];
					Link->Y = SolidObjects[__SOLIDOBJ_STARTLINKY];
				}
				//If Link isn't colliding with a solid object
				if(!SolidObjects_CollideWithLink(Link->X, Link->Y)||SolidObjects[__SOLIDOBJ_FORCERESPAWNCOUNTER]>=DELAYMAX_CRUSH){
					SolidObjects[__SOLIDOBJ_FORCERESPAWNCOUNTER] = 0;
					Link->CollDetection = true;
					Link->Invisible = false;
					Link->HP -= DAMAGE_LINKCRUSH;
					Link->Action = LA_GOTHURTLAND;
					Link->HitDir = -1;
					Game->PlaySound(SFX_OUCH);
				}
				//Otherwise raise the crush counter so it checks again next frame
				else{
					SolidObjects[__SOLIDOBJ_CRUSHCOUNTER] = 1;
					SolidObjects[__SOLIDOBJ_FORCERESPAWNCOUNTER]++;
				}
			}
		}
	}
}

void SolidObjects_Update2(){
	if(Link->Action != LA_SCROLLING){
		//If Link has moved to a different DMap or screen, update the starting position
		if(SolidObjects[__SOLIDOBJ_LASTDMAP]!=Game->GetCurDMap()||SolidObjects[__SOLIDOBJ_LASTSCREEN]!=Game->GetCurScreen()){
			SolidObjects[__SOLIDOBJ_LASTDMAP] = Game->GetCurDMap();
			SolidObjects[__SOLIDOBJ_LASTSCREEN] = Game->GetCurScreen();
			
			SolidObjects[__SOLIDOBJ_STARTLINKX] = Link->X;
			SolidObjects[__SOLIDOBJ_STARTLINKY] = Link->Y;
		}
		
		//Only move Link around if he's not currently being crushed
		if(SolidObjects[__SOLIDOBJ_CRUSHCOUNTER]==0)
			SolidObjects_UpdateLink();
		
		//SFX: Demonic chanting as enemies twitch around awkwardly
		if(SOLIDOBJ_PUSH_NPC){
			for(int i=Screen->NumNPCs(); i>=1; i--){
				npc n = Screen->LoadNPC(i);
				if(!SolidObjects_CanPushEnemy(n))
					continue;
				SolidObjects_UpdateEnemy(n);
			}
		}
		
		//Clear solid object slots to be set by scripts again the next frame
		SolidObjects_ClearObjects();
		
		SolidObjects[__SOLIDOBJ_LINKLASTX] = Link->X;
		SolidObjects[__SOLIDOBJ_LINKLASTY] = Link->Y;
	}
}

//Returns true if an object being pushed around can move in a direction
bool SolidObjects_CanWalk(int x, int y, int dir, int width, int height, int xoff, int yoff, bool noEdge) {
    int i; int xx; int yy;
	bool offscreen;
	if(dir==DIR_UP||dir==DIR_DOWN){
		for(i=0; i<=width-1; i=Min(i+8, width-1)){
			xx = x+xoff+i;
			if(dir==DIR_UP)
				yy = y+yoff-1;
			else
				yy = y+yoff+height;
			if(xx<0||xx>255||yy<0||yy>175){
				if(noEdge)
					offscreen = true;
				else
					return false;
			}
			if(Screen->isSolid(xx, yy)&&!offscreen)
				return false;
			if(i==width-1)
				break;
		}
		return true;
	}
	else if(dir==DIR_LEFT||dir==DIR_RIGHT){
		for(i=0; i<=height-1; i=Min(i+8, height-1)){
			yy = y+yoff+i;
			if(dir==DIR_LEFT)
				xx = x+xoff-1;
			else
				xx = x+xoff+width;
			if(xx<0||xx>255||yy<0||yy>175){
				if(noEdge)
					offscreen = true;
				else
					return false;
			}
			if(Screen->isSolid(xx, yy)&&!offscreen)
				return false;
			if(i==height-1)
				break;
		}
		return true;
	}
	return false;
}

void SolidObjects_UpdateLink(){
	int totalUp;
	int totalDown;
	int totalLeft;
	int totalRight;
	
	int tempVx;
	int tempVy;
	
	int x; int y; int width; int height; int vX; int vY; int flags;
	
	int linkX = Link->X+SOLIDOBJ_LINKXTRIM;
	int linkY = Link->Y+8;
	int linkWidth = 16-SOLIDOBJ_LINKXTRIM*2;
	int linkHeight = 8;
	
	int platID; //A unique number given to solid objects treated as platforms, to keep track of them between frames
	int platformCandidate = -1; //The platform under Link as found when he steps on it for the first time
	int platformIndex = -1; //The platform under Link as found by its ID
	int platformY;
	int platformPushX;
	int platformPushY;
	
	bool crushDir[4];
	//Link's hitbox is different in sideview
	if(IsSideview()){
		linkX = Link->X+SOLIDOBJ_LINKXTRIM;
		linkY = Link->Y+SOLIDOBJ_LINKYTRIM;
		linkWidth = 16-SOLIDOBJ_LINKXTRIM*2;
		linkHeight = 16-SOLIDOBJ_LINKYTRIM;
	}
	
	for(int i=0; i<SOLIDOBJ_MAX; i++){ //Cycle through all possible objects
		x = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_X];
		y = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_Y];
		width = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_WIDTH];
		height = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_HEIGHT];
		platID = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_ID];
		vX = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_VX];
		vY = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_VY];
		flags = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_FLAGS];
		
		if(width>0){ //Check if there's a solid object at the current index
			if(IsSideview()){
				//If Link is standing on the same platform as his current platform ID
				//Update platform index to that object
				if(SolidObjects[__SOLIDOBJ_ONPLATFORM]==platID&&platID>0){
					platformIndex = i;
				}
			}
			
			if(RectCollision(x, y, x+width-1, y+height-1, linkX, linkY, linkX+linkWidth-1, linkY+linkHeight-1)){
				//Find Link and the object's center points
				int cx1 = x+width/2;
				int cy1 = y+height/2;
				int cx2 = linkX+linkWidth/2;
				int cy2 = linkY+linkHeight/2;
				
				if(cy2<cy1){ //Link is above the object
					tempVy = cy1-cy2-(height+linkHeight)/2;
					if(vY<0)
						crushDir[DIR_UP] = true;
				}
				else{ //Link is below the object
					tempVy = cy1-cy2+(height+linkHeight)/2;
					if(vY>0)
						crushDir[DIR_DOWN] = true;
				}
				
				if(cx2<cx1){ //Link is left of the object
					tempVx = cx1-cx2-(width+linkWidth)/2;
					if(vX<0)
						crushDir[DIR_LEFT] = true;
				}
				else{
					tempVx = cx1-cx2+(width+linkWidth)/2;
					if(vX>0)
						crushDir[DIR_RIGHT] = true;
				}
				
				//Prevent jump-through platforms pushing in any direction but up
				if(flags&SFFCF_TOPONLY){
					tempVx = 1000; //We're setting Vx/Vy to 1000 to cancel them out from calculations
					if(tempVy>0)
						tempVy = 1000;
					if(Link->Y<SolidObjects[__SOLIDOBJ_LINKLASTY])
						tempVy = 1000;
				}
				//If both vectors are cancelled, set them to 0
				if(tempVx==1000&&tempVy==1000){
					tempVx = 0;
					tempVy = 0;
				}
				if(Abs(tempVy)<Abs(tempVx)){ //Find out which push would take less effort
					if(tempVy<0){ //If it's an upwards push
						if(totalUp>tempVy){ //If it's larger than the current max
							totalUp = tempVy; //Update the current max
							//If the platform is going up and has an ID >0, update platformCandidate
							if(IsSideview()&&platID>0){
								platformCandidate = i;
							}
						}
					}
					else if(tempVy>0){ //If it's a downwards push
						if(totalDown<tempVy) //If it's larger than the current max
							totalDown = tempVy; //Update the current max
					}
				}
				else{
					if(tempVx<0){ //If it's a left push
						if(totalLeft>tempVx) //If it's larger than the current max
							totalLeft = tempVx; //Update the current max
					}
					else if(tempVx>0){ //If it's a right push
						if(totalRight<tempVx) //If it's larger than the current max
							totalRight = tempVx; //Update the current max
					}
				}
			}
			//Detect collision with FFCs while Link is standing on the ground next to them
			else if(IsSideview()&&!SolidObjects_CanWalk(Link->X, Link->Y, DIR_DOWN, 8, 16, 4, 0, true)){
				if(RectCollision(x, y, x+width-1, y+height-1, linkX, linkY, linkX+linkWidth-1, linkY+linkHeight-1+2)){
					if(platID>0)
						platformCandidate = i;
				}
			}
		}
	}
	
	if(IsSideview()){
		//If Link isn't on a platform yet
		if(SolidObjects[__SOLIDOBJ_ONPLATFORM]==0){
			//If there's a candidate available and he's going down,
			//set his current platform to that platform's ID
			if(platformCandidate>-1&&Link->Jump<=0){
				SolidObjects[__SOLIDOBJ_ONPLATFORM] = SolidObjects[__SOLIDOBJ_STARTINDEX+platformCandidate*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_ID];
				platformY = SolidObjects[__SOLIDOBJ_STARTINDEX+platformCandidate*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_Y]-16;
				Link->Jump = 0;
			}
		}
		else{
			//If the platform Link is on has been found
			if(platformIndex>-1){
				if(platformCandidate>-1){
					//If there's a platform candidate and it has a higher Y velocity
					if(SolidObjects[__SOLIDOBJ_STARTINDEX+platformCandidate*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_VY]<SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_VY]){
						platformIndex = platformCandidate;
						SolidObjects[__SOLIDOBJ_ONPLATFORM] = SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_ID];
						Link->Jump = 0;
					}
				}
				x = SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_X];
				y = SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_Y];
				width = SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_WIDTH];
				height = SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_HEIGHT];
				platformY = y-16;
				platformPushX = SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_VX];
				platformPushY = platformY-Link->Y; //Link's Y push is based on the difference between Link's position and the position above the platform
				Link->Jump = 0;
				//If Link isn't touching the platform, detach him from it
				if(!RectCollision(x, y, x+width-1, y+height-1, linkX+platformPushX, linkY+platformPushY, linkX+linkWidth-1+platformPushX, linkY+linkHeight-1+4+platformPushY)){
					SolidObjects[__SOLIDOBJ_ONPLATFORM] = 0;
				}
			}
			//If the platform Link is on doesn't exist, detach him
			else{
				SolidObjects[__SOLIDOBJ_ONPLATFORM] = 0;
			}
		}
	}
	
	//Debug draws
	// Screen->DrawInteger(6, 8, 8, FONT_Z1, 0x01, 0x0F, -1, -1, totalUp, 0, 128);
	// Screen->DrawInteger(6, 8, 16, FONT_Z1, 0x01, 0x0F, -1, -1, totalDown, 0, 128);
	// Screen->DrawInteger(6, 8, 24, FONT_Z1, 0x01, 0x0F, -1, -1, totalLeft, 0, 128);
	// Screen->DrawInteger(6, 8, 32, FONT_Z1, 0x01, 0x0F, -1, -1, totalRight, 0, 128);
	
	//Get which directions Link can go in
	//This is based on the speed of Link being pushed out of the platform as well as its current velocity
	bool canGoUp = (totalDown==0)||!crushDir[DIR_DOWN];
	bool canGoDown = (totalUp==0)||!crushDir[DIR_UP];
	bool canGoLeft = (totalRight==0)||!crushDir[DIR_RIGHT];
	bool canGoRight = (totalLeft==0)||!crushDir[DIR_LEFT];
	
	//Add rough offsets to cancel out Link's movement
	int cancelMoveX = -LinkMovement[LM_STICKX];
	int cancelMoveY = -LinkMovement[LM_STICKY];
	if(IsSideview())
		cancelMoveY = 0;
	
	//Update the valid directions based on screen solidity in the way
	if(IsSideview()){ //Link's hitbox is different in sideview
		if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_UP, 8, 16, 4, 0, true))
			canGoUp = false;
		if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_DOWN, 8, 16, 4, 0, true)){
			canGoDown = false;
			//Detach Link from a platform if he's on the ground and not directly above the platform
			if(Abs(platformY-Link->Y)>2)
				SolidObjects[__SOLIDOBJ_ONPLATFORM] = 0;
		}
		if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_LEFT, 16, 16, 0, 0, true))
			canGoLeft = false;
		if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_RIGHT, 16, 16, 0, 0, true))
			canGoRight = false;
	}
	else{
		if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_UP, 16, 8, 0, 8, true))
			canGoUp = false;
		if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_DOWN, 16, 8, 0, 8, true))
			canGoDown = false;
		if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_LEFT, 16, 8, 0, 8, true))
			canGoLeft = false;
		if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_RIGHT, 16, 8, 0, 8, true))
			canGoRight = false;
	}
	//Set Link's jump to 0 if he's jumping up into a ceiling
	if(Link->Jump>0&&totalDown>0&&IsSideview())
		Link->Jump = 0;
	
	int crush = -1;
	if(SOLIDOBJ_CRUSH_BEHAVIOR){
		int crushStrengthX = Max(Abs(totalLeft), Abs(totalRight));
		int crushStrengthY = Max(Abs(totalUp), Abs(totalDown));
		//Detect if Link is between two walls
		if(!canGoUp&&!canGoDown){
			//Detect if he's far enough in to be crushed
			if(crushStrengthY>=SOLIDOBJ_CRUSH_SAFETY){
				crush = 4;
				//Set crush direction if Link is pushed against a wall
				if(Abs(totalUp)>0&&totalDown==0)
					crush = DIR_UP;
				else if(Abs(totalDown)>0&&totalUp==0)
					crush = DIR_DOWN;
			}
		}
		if(!canGoLeft&&!canGoRight){
			if(crushStrengthX>=SOLIDOBJ_CRUSH_SAFETY){
				//If he's being more crushed vertically than horizontally, prioritize that
				if((crush==DIR_UP||crush==DIR_DOWN||crush==4)&&crushStrengthY>crushStrengthX){
					crush = 4;
					if(Abs(totalUp)>0&&totalDown==0)
						crush = DIR_UP;
					else if(Abs(totalDown)>0&&totalUp==0)
						crush = DIR_DOWN;
				}
				else{
					crush = 5;
					if(Abs(totalLeft)>0&&totalRight==0)
						crush = DIR_LEFT;
					else if(Abs(totalRight)>0&&totalLeft==0)
						crush = DIR_RIGHT;
				}
			}
		}
	}
	
	//If Link is being crushed
	if(crush>-1&&Link->CollDetection){
		if(SOLIDOBJ_CRUSH_BEHAVIOR==2){ //Instant death crush
			Link->HP = 0;
		}
		else{ //Teleport crush
			SolidObjects[__SOLIDOBJ_FORCELINKX] = Link->X;
			SolidObjects[__SOLIDOBJ_FORCELINKY] = Link->Y;
			lweapon lcrush = CreateLWeaponAt(LW_SPARKLE, Link->X, Link->Y);
			lcrush->UseSprite(SPR_LINKCRUSH);
			lcrush->DeadState = Max(lcrush->ASpeed*lcrush->NumFrames-1, 1);
			if(SOLIDOBJ_COMPLEX_CRUSH_ANIM){
				lcrush->OriginalTile += crush*20;
				lcrush->Tile = lcrush->OriginalTile;
			}
			else{
				if(crush==DIR_LEFT||crush==DIR_RIGHT||crush==5){ //Offset tiles based on direction of the crushing
					lcrush->OriginalTile += 20;
					lcrush->Tile = lcrush->OriginalTile;
				}
			}
			Link->Invisible = true;
			Link->CollDetection = false;
			SolidObjects[__SOLIDOBJ_CRUSHCOUNTER] = lcrush->DeadState+DELAY_CRUSH;
			Game->PlaySound(SFX_LINKCRUSH);
			SolidObjects[__SOLIDOBJ_ONPLATFORM] = 0;
		}
	}
	else{ //Otherwise, move him around
		if(IsSideview()&&(totalUp+totalDown)<0&&SolidObjects[__SOLIDOBJ_ONPLATFORM]>0)
			Link->Jump = 0;
		SolidObjects_SafePush2NoEdge(totalLeft+totalRight+platformPushX, totalUp+totalDown+platformPushY); //Move Link by the combination of strongest inputs. Opposing Left/Right, Up/Down should cancel out
	}
}

//This function prevents the script from overfilling the push counter
void SolidObjects_SafePush2NoEdge(int vX, int vY){
	if(Abs(LinkMovement[LM_PUSHX2B])>=1)
		vX = 0;
	if(Abs(LinkMovement[LM_PUSHY2B])>=1)
		vY = 0;
	LinkMovement_Push2NoEdge(vX, vY);
}

bool SolidObjects_CollideWithLink(int checkX, int checkY){
	int x; int y; int width; int height; int vX; int vY;
	int linkX = checkX+SOLIDOBJ_LINKXTRIM;
	int linkY = checkY+8;
	int linkWidth = 16-SOLIDOBJ_LINKXTRIM*2;
	int linkHeight = 8;
	if(IsSideview()){
		linkX = Link->X+SOLIDOBJ_LINKXTRIM;
		linkY = Link->Y+SOLIDOBJ_LINKYTRIM;
		linkWidth = 16-SOLIDOBJ_LINKXTRIM*2;
		linkHeight = 16-SOLIDOBJ_LINKYTRIM;
	}
	for(int i=0; i<SOLIDOBJ_MAX; i++){ //Cycle through all possible objects
		x = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_X];
		y = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_Y];
		width = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_WIDTH];
		height = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_HEIGHT];
		//If one of them collides with Link, return true
		if(RectCollision(x, y, x+width-1, y+height-1, linkX, linkY, linkX+linkWidth-1, linkY+linkHeight-1)){
			return true;
		}
	}
	return false;
}

void SolidObjects_UpdateEnemy(npc n){
	int totalUp;
	int totalDown;
	int totalLeft;
	int totalRight;
	
	int tempVx;
	int tempVy;
	
	int x; int y; int width; int height;
	int nX = n->X+n->HitXOffset;
	int nY = n->Y+n->HitYOffset;
	int nWidth = n->HitWidth;
	int nHeight = n->HitHeight;
	for(int i=0; i<SOLIDOBJ_MAX; i++){ //Cycle through all possible objects
		x = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_X];
		y = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_Y];
		width = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_WIDTH];
		height = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_HEIGHT];
		int flags = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_FLAGS];
		if(width>0&&flags&SFFCF_PUSHNPC){ //Check if there's a solid object at the current index
			//Debug hitbox draw
			//Screen->Rectangle(2, x, y, x+width-1, y+height-1, 0x01, 1, 0, 0, 0, true, 64);
			if(RectCollision(x, y, x+width-1, y+height-1, nX, nY, nX+nWidth-1, nY+nHeight-1)){
				//Find NPC and the object's center points
				int cx1 = x+width/2;
				int cy1 = y+height/2;
				int cx2 = nX+nWidth/2;
				int cy2 = nY+nHeight/2;
				
				if(cy2<cy1){ //NPC is above the object
					tempVy = cy1-cy2-(height+nHeight)/2;
				}
				else{ //NPC is below the object
					tempVy = cy1-cy2+(height+nHeight)/2;
				}
				
				if(cx2<cx1){ //NPC is left of the object
					tempVx = cx1-cx2-(width+nWidth)/2;
				}
				else{
					tempVx = cx1-cx2+(width+nWidth)/2;
				}
				
				//Prevent jump-through platforms pushing in any direction but up
				if(flags&SFFCF_TOPONLY){
					tempVx = 1000; //We're setting Vx/Vy to 1000 to cancel them out from calculations
					if(tempVy>0)
						tempVy = 1000;
				}
				//If both vectors are cancelled, set them to 0
				if(tempVx==1000&&tempVy==1000){
					tempVx = 0;
					tempVy = 0;
				}
				if(Abs(tempVy)<Abs(tempVx)){ //Find out which push would take less effort
					if(tempVy<0){ //If it's an upwards push
						if(totalUp>tempVy) //If it's larger than the current max
							totalUp = tempVy; //Update the current max
					}
					else if(tempVy>0){ //If it's a downwards push
						if(totalDown<tempVy) //If it's larger than the current max
							totalDown = tempVy; //Update the current max
					}
				}
				else{
					if(tempVx<0){ //If it's a left push
						if(totalLeft>tempVx) //If it's larger than the current max
							totalLeft = tempVx; //Update the current max
					}
					else if(tempVx>0){ //If it's a right push
						if(totalRight<tempVx) //If it's larger than the current max
							totalRight = tempVx; //Update the current max
					}
				}
			}
		}
	}
	//Debug draws
	// Screen->DrawInteger(6, 8, 8, FONT_Z1, 0x01, 0x0F, -1, -1, totalUp, 0, 128);
	// Screen->DrawInteger(6, 8, 16, FONT_Z1, 0x01, 0x0F, -1, -1, totalDown, 0, 128);
	// Screen->DrawInteger(6, 8, 24, FONT_Z1, 0x01, 0x0F, -1, -1, totalLeft, 0, 128);
	// Screen->DrawInteger(6, 8, 32, FONT_Z1, 0x01, 0x0F, -1, -1, totalRight, 0, 128);
	
	bool canGoUp = (totalDown==0);
	bool canGoDown = (totalUp==0);
	bool canGoLeft = (totalRight==0);
	bool canGoRight = (totalLeft==0);
	
	if(!SolidObjects_CanWalk(n->X, n->Y, DIR_UP, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
		canGoUp = false;
	if(!SolidObjects_CanWalk(n->X, n->Y, DIR_DOWN, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
		canGoDown = false;
	if(!SolidObjects_CanWalk(n->X, n->Y, DIR_LEFT, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
		canGoLeft = false;
	if(!SolidObjects_CanWalk(n->X, n->Y, DIR_RIGHT, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
		canGoRight = false;
	
	int crush = 0;
	if(SOLIDOBJ_CRUSH_BEHAVIOR){
		int crushStrengthX = Max(Abs(totalLeft), Abs(totalRight));
		int crushStrengthY = Max(Abs(totalUp), Abs(totalDown));
		//Detect if NPC is between two walls
		if(!canGoUp&&!canGoDown){
			//Detect if it's far enough in to be crushed
			if(crushStrengthY>=SOLIDOBJ_CRUSH_SAFETY){
				crush = 1;
			}
		}
		if(!canGoLeft&&!canGoRight){
			if(crushStrengthX>=SOLIDOBJ_CRUSH_SAFETY){
				crush = 1;
			}
		}
	}
	if(crush&&n->CollDetection){
		n->HP = 0;
		Game->PlaySound(SFX_ENEMYCRUSH);
	}
	else
		SolidObjects_PushEnemy(n, totalLeft+totalRight, totalUp+totalDown);//Move NPC by the combination of strongest inputs. Opposing Left/Right, Up/Down should cancel out
}

void SolidObjects_PushEnemy(npc n, int pushX, int pushY){
	pushX = Round(pushX);
	pushY = Round(pushY);
	//Until both pushX and pushY are drained down to 0, continue to push the enemy
	while(pushX!=0||pushY!=0){
		if(pushX<0){
			//But not through solid objects
			if(SolidObjects_CanWalk(n->X, n->Y, DIR_LEFT, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
				SetEnemyProperty(n, ENPROP_X, GetEnemyProperty(n, ENPROP_X)-1);
			pushX++;
		}
		else if(pushX>0){
			if(SolidObjects_CanWalk(n->X, n->Y, DIR_RIGHT, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
				SetEnemyProperty(n, ENPROP_X, GetEnemyProperty(n, ENPROP_X)+1);
			pushX--;
		}
		if(pushY<0){
			if(SolidObjects_CanWalk(n->X, n->Y, DIR_UP, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
				SetEnemyProperty(n, ENPROP_Y, GetEnemyProperty(n, ENPROP_Y)-1);
			pushY++;
		}
		else if(pushY>0){
			if(SolidObjects_CanWalk(n->X, n->Y, DIR_DOWN, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
				SetEnemyProperty(n, ENPROP_Y, GetEnemyProperty(n, ENPROP_Y)+1);
			pushY--;
		}
	}
	//If the enemy gets pushed off the screen, kill it
	if(GetEnemyProperty(n, ENPROP_X)<-n->HitXOffset-n->HitWidth ||
		GetEnemyProperty(n, ENPROP_X)>256 ||
		GetEnemyProperty(n, ENPROP_Y)<-n->HitYOffset-n->HitHeight ||
		GetEnemyProperty(n, ENPROP_Y)>176 ){
			
		SetEnemyProperty(n, ENPROP_HP, -1000);
		n->ItemSet = -1000;
		n->DrawYOffset = -1000;
	}
}

bool SolidObjects_CanPushEnemy(npc n){
	int type = n->Type;
	//If the enemy is invulnerable, don't push it
	if(Abs(n->HitXOffset)>=1000||Abs(n->HitYOffset)>=1000)
		return false;
	//If the enemy is in the air, don't push it
	if(n->Z>0)
		return false;
	//Check if the enemy is a type that can be pushed
	if(type==NPCT_WALK)
		return true;
	if(type==NPCT_TEKTITE)
		return true;
	if(type==NPCT_LEEVER)
		return true;
	if(type==NPCT_ZORA)
		return true;
	if(type==NPCT_GHINI)
		return true;
	if(type==NPCT_ARMOS)
		return true;
	if(type==NPCT_WIZZROBE)
		return true;
	if(type==NPCT_OTHERFLOAT)
		return true;
	if(type==NPCT_OTHER)
		return true;
	
	return false;
}

void SolidObjects_ClearObjects(){
	for(int i=0; i<SOLIDOBJ_MAX; i++){ //Cycle through all possible objects
		//Clear the properties
		SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_X] = 0;
		SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_Y] = 0;
		SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_WIDTH] = 0;
		SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_HEIGHT] = 0;
		SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_FLAGS] = 0;
	}
	//Reset the count
	SolidObjects[__SOLIDOBJ_COUNT] = 0;
}

//D0: Width of the hitbox
//D1: Height of the hitbox
//D2: X offset from the FFC's position for the hitbox
//D3: Y offset from the FFC's position for the hitbox
//D4: Flags. Add these together to get the result:
//		1 - Only the top of the FFC is solid
//		2 - The FFC pushes enemies
//D5: FFC to link movement to
//D6: Combo ID that makes the FFC nonsolid when it switches to
ffc script Solid_FFC{
	void run(int width, int height, int xoff, int yoff, int flags, int refFFC, int nonSolidCMB){
		//Default width/height
		if(width==0){
			width = this->TileWidth*16;
			height = this->TileHeight*16;
		}
		
		//Set the platform ID to this FFC's number
		int ID;
		for(int i=1; i<=32; i++){
			ffc f = Screen->LoadFFC(i);
			if(f==this)
				ID = i;
		}
		int lastX = this->X;
		int lastY = this->Y;
		ffc ref;
		if(refFFC>0)
			ref = Screen->LoadFFC(refFFC);
		while(true){
			//Handle scripted FFC linking
			if(refFFC>0){
				if(ref->Delay==0){
					this->Vx = ref->Vx;
					this->Vy = ref->Vy;
				}
				else{
					this->Vx = 0;
					this->Vy = 0;
				}
			}
			//We're using difference in position instead of the FFC's Vx and Vy
			//because of float imprecision. Doing it the other way caused Link to get
			//desynced from the FFC
			int vX = this->X-lastX;
			int vY = this->Y-lastY;
			lastX = this->X;
			lastY = this->Y;
			if(this->Delay>0){
				vX = 0;
				vY = 0;
			}
			if(width>0&&this->Data!=nonSolidCMB){
				SolidObjects_Add(ID, this->X+xoff, this->Y+yoff, width, height, vX, vY, flags);
			}
			Waitframe();
		}
	}
}

//D0: Radius to put the platform at
//D1: Starting angle for the platform
//D2: Rotation speed of the platform
//D3: Flags. Add these together to get the result:
//		1 - Only the top of the FFC is solid
//		2 - The FFC pushes enemies
ffc script Moving_Platform_Circular{
	void run(int r, int ang, int rot, int flags){
		//Set the platform ID to this FFC's number
		int ID;
		for(int i=1; i<=32; i++){
			ffc f = Screen->LoadFFC(i);
			if(f==this)
				ID = i;
		}
		int startX = this->X;
		int startY = this->Y;
		int lastX = this->X;
		int lastY = this->Y;
		while(true){
			int x = startX+VectorX(r, ang);
			int y = startY+VectorY(r, ang);
			this->X = x;
			this->Y = y;
			//We're using difference in position instead of the FFC's Vx and Vy
			//because of float imprecision. Doing it the other way caused Link to get
			//desynced from the FFC
			int vX = this->X-lastX;
			int vY = this->Y-lastY;
			lastX = this->X;
			lastY = this->Y;
			ang = WrapDegrees(ang+rot);
			SolidObjects_Add(ID, this->X, this->Y, this->EffectWidth, this->EffectHeight, vX, vY, flags);
			Waitframe();
		}
	}
}

//D0: How many frames to shake for
//D1: Flags. Add these together to get the result:
//		1 - Only the top of the FFC is solid
//		2 - The FFC pushes enemies
ffc script Moving_Platform_StepActivate{
	void run(int shakeFrames, int flags){
		//Set the platform ID to this FFC's number
		int ID;
		for(int i=1; i<=32; i++){
			ffc f = Screen->LoadFFC(i);
			if(f==this)
				ID = i;
		}
		//Store the FFC's starting state
		int startX = this->X;
		int startY = this->Y;
		int savedVX = this->Vx;
		int savedVY = this->Vy;
		int savedAX = this->Ax;
		int savedAY = this->Ay;
		int savedDelay = this->Delay;
		//Clear the FFC's state
		this->Vx = 0;
		this->Vy = 0;
		this->Ax = 0;
		this->Ay = 0;
		this->Delay = 0;
		//If __SOLIDOBJ_ONPLATFORM doesn't equal this FFC's number, Link hasn't stepped on the platform yet
		while(SolidObjects[__SOLIDOBJ_ONPLATFORM]!=ID){
			SolidObjects_Add(ID, this->X, this->Y, this->EffectWidth, this->EffectHeight, 0, 0, flags);
			Waitframe();
		}
		int lastX = this->X;
		int lastY = this->Y;
		int vX; int vY;
		//Play a shake animation for the specified number of frames before "falling"
		if(shakeFrames>0){
			for(int i=0; i<shakeFrames; i++){
				if(i%8<2){
					this->X = startX-1;
				}
				else if(i%8>=4&&i%8<6){
					this->X = startX+1;
				}
				else{
					this->X = startX;
				}
				
				vX = this->X-lastX;
				vY = this->Y-lastY;
				lastX = this->X;
				lastY = this->Y;
				
				SolidObjects_Add(ID, this->X, this->Y, this->EffectWidth, this->EffectHeight, vX, vY, flags);
				Waitframe();
			}
			this->X = startX;
			this->Y = startY;
		}
		//Restore the FFC to the starting state when it "falls"
		this->Vx = savedVX;
		this->Vy = savedVY;
		this->Ax = savedAX;
		this->Ay = savedAY;
		this->Delay = savedDelay;
		while(true){
			//We're using difference in position instead of the FFC's Vx and Vy
			//because of float imprecision. Doing it the other way caused Link to get
			//desynced from the FFC
			vX = this->X-lastX;
			vY = this->Y-lastY;
			lastX = this->X;
			lastY = this->Y;
			
			SolidObjects_Add(ID, this->X, this->Y, this->EffectWidth, this->EffectHeight, vX, vY, flags);
			Waitframe();
		}
	}
}


item script FeatherAction{
	void run(){
		//This script makes Link able to jump with Roc's feather when on a sideview platform
		if(SolidObjects[__SOLIDOBJ_ONPLATFORM]){
			Game->PlaySound(SFX_JUMP);
			Link->Jump = (this->Power+2)*0.8;
			SolidObjects[__SOLIDOBJ_ONPLATFORM] = 0;
		}
	}
}

//Example global script combined with ghost and LinkMovement
global script SolidObject_Example_Combined{
	void run(){
		StartGhostZH();
		LinkMovement_Init();
		SolidObjects_Init();
		while(true){
			UpdateGhostZH1();
			SolidObjects_Update1();
			LinkMovement_Update1();
			Waitdraw();
			UpdateGhostZH2();
			SolidObjects_Update2();
			LinkMovement_Update2();
			Waitframe();
		}
	}
}

//Example global script with just this script's functions
global script SolidObject_Example{
	void run(){
		SolidObjects_Init();
		while(true){
			SolidObjects_Update1();
			Waitdraw();
			SolidObjects_Update2();
			Waitframe();
		}
	}
}