Copy to Clipboard Test

LinkMovement.zh Code

const int FOUR_WAY_MOVEMENT = 0; //Set to 1 if your quest has four-way movement. 
								//Note that four-way movement Link has a step speed of 1.33 instead of 1.5.
								//This happens seemingly in a pattern of 1-1-1-2-1-2 pixel steps.
								//This settings is all around buggy and there's not much I can do about it, unfortunately. :(

const int MAX_PUSH = 4; //This is the maximum number of pixels Link can be pushed in a single frame.
						//Extra pixels past this maximum will be handled the next frame and so on.
						//Increasing this will increase the max number of interations in loops in the
						//__LinkMovement_UpdatePush() function. This shouldn't be too big a deal in terms of
						//slowdown, but to keep it reasonable.

const int LM_PUSH_BLOCK_FIX = 1; //Set to 1 to detect push blocks in solidity detection

int LinkMovement[16];
//These constants are all array indeces for the global array
const int LM_PUSHX1A = 0;
const int LM_PUSHY1A = 1;
const int LM_PUSHX1B = 2;
const int LM_PUSHY1B = 3;
const int LM_PUSHX2A = 4;
const int LM_PUSHY2A = 5;
const int LM_PUSHX2B = 6;
const int LM_PUSHY2B = 7;
const int LM_STICKX = 8;
const int LM_STICKY = 9;
const int LM_MOVEBOOST = 10;
const int LM_LASTX = 11;
const int LM_LASTY = 12;
const int LM_UNDOLINKMOVEMENT = 13;
const int LM_LASTDMAP = 14;
const int LM_LASTDMAPSCREEN = 15;

//INTERNAL FUNCTIONS

//This function keeps track of Link's last X and Y input.
//If two opposing inputs are held at once, this should hopefully keep track of which one ZC will prioritize.
void __LinkMovement_UpdateInput(){
	if(LinkMovement[LM_STICKY]==0){ //If no Y axis pressed
		if(Link->PressUp&&Link->PressDown) //Default to up when buttons pressed simultaneously
			LinkMovement[LM_STICKY] = -1;
		else if(Link->PressUp||Link->InputUp) //Set axis based on which button what pressed
			LinkMovement[LM_STICKY] = -1;
		else if(Link->PressDown||Link->InputDown)
			LinkMovement[LM_STICKY] = 1;
	}
	else{ //If Y axis pressed
		if(!Link->InputUp&&!Link->InputDown) //Release Y axis if neither button pressed
			LinkMovement[LM_STICKY] = 0;
		else if(LinkMovement[LM_STICKY]==-1&&!Link->InputUp) //Reverse Y axis if opposite direction held and button released
			LinkMovement[LM_STICKY] = 1;
		else if(LinkMovement[LM_STICKY]==1&&!Link->InputDown)
			LinkMovement[LM_STICKY] = -1;
	}
	
	if(LinkMovement[LM_STICKX]==0){ //If no X axis pressed
		if(Link->PressLeft&&Link->PressRight) //Default to left when buttons pressed simultaneously
			LinkMovement[LM_STICKX] = -1;
		else if(Link->PressLeft||Link->InputLeft) //Set axis based on which button what pressed
			LinkMovement[LM_STICKX] = -1;
		else if(Link->PressRight||Link->InputRight)
			LinkMovement[LM_STICKX] = 1;
	}
	else{ //If Y axis pressed
		if(!Link->InputLeft&&!Link->InputRight) //Release Y axis if neither button pressed
			LinkMovement[LM_STICKX] = 0;
		else if(LinkMovement[LM_STICKX]==-1&&!Link->InputLeft) //Reverse Y axis if opposite direction held and button released
			LinkMovement[LM_STICKX] = 1;
		else if(LinkMovement[LM_STICKX]==1&&!Link->InputRight)
			LinkMovement[LM_STICKX] = -1;
	}
}

//Function adds extra movement to Link's step speed
void __LinkMovement_SpeedChange(){
	if(Link->Action==LA_NONE||Link->Action==LA_WALKING||Link->Action==LA_CHARGING){
		if(LinkMovement[LM_STICKX]!=0||LinkMovement[LM_STICKY]!=0){
			float movementSpeed = LinkMovement[LM_MOVEBOOST];
			//We're calculating CanWalk for all directions in advance to hopefully save a bit on speed.
			bool CanWalk[4];
			CanWalk[DIR_UP] = CanWalk(Link->X, Link->Y, DIR_UP, 1, false);
			CanWalk[DIR_DOWN] = CanWalk(Link->X, Link->Y, DIR_DOWN, 1, false);
			CanWalk[DIR_LEFT] = CanWalk(Link->X, Link->Y, DIR_LEFT, 1, false);
			CanWalk[DIR_RIGHT] = CanWalk(Link->X, Link->Y, DIR_RIGHT, 1, false);
			//If four way movement isn't on, Link's speed boost should be dampened for diagonals since he moves slower
			if(!FOUR_WAY_MOVEMENT){
				if(LinkMovement[LM_STICKX]!=0&&LinkMovement[LM_STICKY]!=0){
					//If he's pressing against a wall or in sideview, the speed dampening won't take effect
					if(CanWalk[Cond(LinkMovement[LM_STICKX]==-1, DIR_LEFT, DIR_RIGHT)] &&
					CanWalk[Cond(LinkMovement[LM_STICKY]==-1, DIR_UP, DIR_DOWN)] &&
					!IsSideview() )
						movementSpeed = movementSpeed*0.7071; //Reduce movement speed at a diagonal
				}
			}
			//Otherwise, disable directions Link isn't facing
			else{
				if(LinkMovement[LM_STICKX]!=0&&(Link->Dir==DIR_UP||Link->Dir==DIR_DOWN)){
					CanWalk[DIR_LEFT] = false;
					CanWalk[DIR_RIGHT] = false;
				}
				if(LinkMovement[LM_STICKY]!=0&&(Link->Dir==DIR_LEFT||Link->Dir==DIR_RIGHT)){
					CanWalk[DIR_UP] = false;
					CanWalk[DIR_DOWN] = false;
				}
			}
			
			//If there's a moving block in play, cancel the speed boost if Link is touching it
			if(Screen->MovingBlockX>-1){
				//Predict where Link will move to so he doesn't clip into the block
				int projX = Link->X+movementSpeed*LinkMovement[LM_STICKX];
				int projY = Link->Y+movementSpeed*LinkMovement[LM_STICKY];
				if(RectCollision(projX, projY+8, projX+15, projY+15, Screen->MovingBlockX, Screen->MovingBlockY, Screen->MovingBlockX+15, Screen->MovingBlockY+15)){
					LinkMovement[LM_MOVEBOOST] = 0;
					return;
				}
			}
			
			//This chunk is pretty much the same for both positive and negative movement speeds.
			//The only difference is that negative needs to check both opposing directions before adding the push.
			if(movementSpeed>0){
				//Left
				if(LinkMovement[LM_STICKX]<0&&CanWalk[DIR_LEFT])
					LinkMovement[LM_PUSHX2B] -= movementSpeed;
				//Right
				else if(LinkMovement[LM_STICKX]>0&&CanWalk[DIR_RIGHT])
					LinkMovement[LM_PUSHX2B] += movementSpeed;
				//Up
				if(LinkMovement[LM_STICKY]<0&&CanWalk[DIR_UP])
					LinkMovement[LM_PUSHY2B] -= movementSpeed;
				//Down
				else if(LinkMovement[LM_STICKY]>0&&CanWalk[DIR_DOWN])
					LinkMovement[LM_PUSHY2B] += movementSpeed;
			}
			else if(movementSpeed<0){
				//Left
				if(LinkMovement[LM_STICKX]<0&&CanWalk[DIR_LEFT]&&CanWalk[DIR_RIGHT])
					LinkMovement[LM_PUSHX2B] -= movementSpeed;
				//Right
				else if(LinkMovement[LM_STICKX]>0&&CanWalk[DIR_LEFT]&&CanWalk[DIR_RIGHT])
					LinkMovement[LM_PUSHX2B] += movementSpeed;
				//Up
				if(LinkMovement[LM_STICKY]<0&&CanWalk[DIR_UP]&&CanWalk[DIR_DOWN])
					LinkMovement[LM_PUSHY2B] -= movementSpeed;
				//Down
				else if(LinkMovement[LM_STICKY]>0&&CanWalk[DIR_UP]&&CanWalk[DIR_DOWN])
					LinkMovement[LM_PUSHY2B] += movementSpeed;
			}
		}	
	}
	LinkMovement[LM_MOVEBOOST] = 0; //Movement boost gets reset at the end of every frame to prevent confusion 
									//with what it's set to or what should turn it on/off
}

//Heckin gross function. Why do I need to do this
void __LinkMovement_UglyReverseMovementFix(){
	//So basically if Link's movement boost is enough to make him go backwards
	//some extra stuff needs to happen so he scrolls screens properly.
	//It's opposite day, everybody!
	if(LinkMovement[LM_MOVEBOOST]<-1.5){
		//If he's moving towards the edge, move him away from it
		if(Link->X<=0&&LinkMovement[LM_STICKX]<0)
			Link->X+=2;
		else if(Link->X>=240&&LinkMovement[LM_STICKX]>0)
			Link->X-=2;
		if(Link->Y<=0&&LinkMovement[LM_STICKY]<0)
			Link->Y+=2;
		else if(Link->Y>=160&&LinkMovement[LM_STICKY]>0)
			Link->Y-=2;
		
		//If he's moving away from the edge, push him towards it
		if(Link->X<=0&&LinkMovement[LM_STICKX]>0)
			Link->X-=2;
		else if(Link->X>=240&&LinkMovement[LM_STICKX]<0)
			Link->X+=2;
		if(Link->Y<=0&&LinkMovement[LM_STICKY]>0)
			Link->Y-=2;
		else if(Link->Y>=160&&LinkMovement[LM_STICKY]<0)
			Link->Y+=2;
	}
}

//isSolid() function that accounts for push blocks
bool __LinkMovement_IsSolid(int x, int y){
	if(LM_PUSH_BLOCK_FIX){
		if(Screen->MovingBlockX>-1){
			if(x>=Screen->MovingBlockX&&x<=Screen->MovingBlockX+15&&y>=Screen->MovingBlockY&&y<=Screen->MovingBlockY+15)
				return true;
		}
	}
	return Screen->isSolid(x, y);
}


//The ugly deformed child of the original CanWalk() function and something else entirely
bool __LinkMovement_CanWalk(int x, int y, int dir, int step, bool full_tile, bool noEdge) {
    int i; int xx; int yy;
	int xoffset = 0; int yoffset = 0;
	int width = 16; int height = 16;
	//Link's sideview hitbox is 8 pixels wide and centered on him
	if(IsSideview()&&(dir==DIR_UP||dir==DIR_DOWN)){
		xoffset = 4;
		width = 8;
	}
	//If !full_tile, trim the top half off the hitbox
	else if(!full_tile){
		yoffset = 8;
		height = 8;
	}
	if(dir==DIR_UP||dir==DIR_DOWN){ 
		//Loop between three points of collision (two if sideview)
		for(i=0; i<=width-1; i=Min(i+8, width-1)){
			if(dir==DIR_UP){
				xx = x+xoffset+i;
				yy = y+yoffset-step;
			}
			else if(dir==DIR_DOWN){
				xx = x+xoffset+i;
				yy = y+yoffset+height-1+step;
			}
			//If !noEdge, positions off the screen are considered solid
			if(xx<0||xx>255||yy<0||yy>175){
				if(!noEdge)
					return false;
			}
			if(__LinkMovement_IsSolid(xx, yy))
				return false;
			if(i==width-1)
				break;
		}
		return true;
	}
	else if(dir==DIR_LEFT||dir==DIR_RIGHT){
		//Loop between three points of collision (two if !full_tile)
		for(i=0; i<=height-1; i=Min(i+8, height-1)){
			if(dir==DIR_LEFT){
				xx = x+xoffset-step;
				yy = y+yoffset+i;
			}
			else if(dir==DIR_RIGHT){
				xx = x+xoffset+width-1+step;;
				yy = y+yoffset+i;
			}
			//If !noEdge, positions off the screen are considered solid
			if(xx<0||xx>255||yy<0||yy>175){
				if(!noEdge)
					return false;
			}
			if(__LinkMovement_IsSolid(xx, yy))
				return false;
			if(i==height-1)
				break;
		}
		return true;
	}
	return false;
}

//This function moves Link based on the values of the push indices in the global array
void __LinkMovement_UpdatePush(int indexX, int indexY, bool noEdge){
	int Imprecision = 0; //This is currently unused, but I guess I left it in in case I 
						//ever see a reason to use it again...three cheers for bloat!
	
	//So what we have here is a series of for loops that run for as long as:
	//	-The absolute value of the push array is greater than 0
	//	-Link isn't being blocked by a wall
	//  -Link hasn't been moved the max number of pixels that frame
	
	//Any movement of less than a pixel is left over in the array and any time the function successfully moves Link,
	//the array counts back down towards 0. This way movements of less than a pixel can "add up" to a full pixel movement.
	
	//Left
	for(int i=0; i<MAX_PUSH&&LinkMovement[indexX]<=-1; i++){
		if(__LinkMovement_CanWalk(Link->X, Link->Y, DIR_LEFT, 1, false, noEdge)){
			Link->X--;
			LinkMovement[indexX]++;
		}
		//This is unused, but would snap Link to the grid if he's close enough to aligned
		else if(Imprecision>0&&Abs(GridY(Link->Y+8)-Link->Y)<Imprecision&&__LinkMovement_CanWalk(Link->X, GridY(Link->Y+8), DIR_LEFT, 1, false, noEdge)){
			Link->Y = GridY(Link->Y+8);
			Link->X--;
			LinkMovement[indexX]++;
		}
		//If Link has been blocked, clear the push value. This will also end the loop.
		else{
			LinkMovement[indexX] = 0;
		}
	}
	//Right
	for(int i=0; i<MAX_PUSH&&LinkMovement[indexX]>=1; i++){
		if(__LinkMovement_CanWalk(Link->X, Link->Y, DIR_RIGHT, 1, false, noEdge)){
			Link->X++;
			LinkMovement[indexX]--;
		}
		//This is unused, but would snap Link to the grid if he's close enough to aligned
		else if(Imprecision>0&&Abs(GridY(Link->Y+8)-Link->Y)<Imprecision&&__LinkMovement_CanWalk(Link->X, GridY(Link->Y+8), DIR_RIGHT, 1, false, noEdge)){
			Link->Y = GridY(Link->Y+8);
			Link->X++;
			LinkMovement[indexX]--;
		}
		//If Link has been blocked, clear the push value. This will also end the loop.
		else{
			LinkMovement[indexX] = 0;
		}
	}
	//Up
	for(int i=0; i<MAX_PUSH&&LinkMovement[indexY]<=-1; i++){
		if(__LinkMovement_CanWalk(Link->X, Link->Y, DIR_UP, 1, false, noEdge)){
			Link->Y--;
			LinkMovement[indexY]++;
		}
		//This is unused, but would snap Link to the grid if he's close enough to aligned
		else if(Imprecision>0&&Abs(GridX(Link->X+8)-Link->X)<Imprecision&&__LinkMovement_CanWalk(GridX(Link->X+8), Link->Y, DIR_UP, 1, false, noEdge)){
			Link->X = GridX(Link->X+8);
			Link->Y--;
			LinkMovement[indexY]++;
		}
		//If Link has been blocked, clear the push value. This will also end the loop.
		else{
			LinkMovement[indexY] = 0;
		}
	}
	//Down
	for(int i=0; i<MAX_PUSH&&LinkMovement[indexY]>=1; i++){
		if(__LinkMovement_CanWalk(Link->X, Link->Y, DIR_DOWN, 1, false, noEdge)){
			Link->Y++;
			LinkMovement[indexY]--;
		}
		//This is unused, but would snap Link to the grid if he's close enough to aligned
		else if(Imprecision>0&&Abs(GridX(Link->X+8)-Link->X)<Imprecision&&__LinkMovement_CanWalk(GridX(Link->X+8), Link->Y, DIR_DOWN, 1, false, noEdge)){
			Link->X = GridX(Link->X+8);
			Link->Y++;
			LinkMovement[indexY]--;
		}
		//If Link has been blocked, clear the push value. This will also end the loop.
		else{
			LinkMovement[indexY] = 0;
		}
	}
}

//Update functions for undoing Link's movement. This relies on a certain order of events during the frame
//	1.) Pushing before waitdraw
//	2.) Waitdraw (engine movement happens now)
//	3.) Link's movement is undone. This negates pushing done before waitdraw but also engine movement.
//	4.) Pushing after waitdraw
//	5.) Link's position is stored to undo movement for the next frame
// 	6.) Waitframe

//This function handles step 3, resetting Link to his last position
void __LinkMovement_UndoLinkMovementUpdate1(){
	if(LinkMovement[LM_UNDOLINKMOVEMENT]){
		//Check that we're on the same map and screen. We don't want to reset Link's position to where he was on a different screen.
		if(LinkMovement[LM_LASTDMAP]==Game->GetCurDMap()&&LinkMovement[LM_LASTDMAPSCREEN]==Game->GetCurDMapScreen()){
			//We can undo X and Y movement selectively
			if(LinkMovement[LM_UNDOLINKMOVEMENT]&01b)
				Link->X = LinkMovement[LM_LASTX];
			if(LinkMovement[LM_UNDOLINKMOVEMENT]&10b)
				Link->Y = LinkMovement[LM_LASTY];
		}
		LinkMovement[LM_UNDOLINKMOVEMENT] = 0; //This property is also undone each frame to prevent confusion
	}
}

//This function handles step 5, storing Link's position to use the next frame
void __LinkMovement_UndoLinkMovementUpdate2(){
	//Also keep the DMap and screen tracking up to date
	LinkMovement[LM_LASTDMAP] = Game->GetCurDMap();
	LinkMovement[LM_LASTDMAPSCREEN] = Game->GetCurDMapScreen();
	
	LinkMovement[LM_LASTX] = Link->X;
	LinkMovement[LM_LASTY] = Link->Y;
}

//GLOBAL SCRIPT FUNCTIONS

//This function goes before waitframe, and resets all the global variables back to their defaults
void LinkMovement_Init(){
	LinkMovement[LM_PUSHX1A] = 0;
	LinkMovement[LM_PUSHY1A] = 0;
	LinkMovement[LM_PUSHX1B] = 0;
	LinkMovement[LM_PUSHY1B] = 0;
	LinkMovement[LM_PUSHX2A] = 0;
	LinkMovement[LM_PUSHY2A] = 0;
	LinkMovement[LM_PUSHX2B] = 0;
	LinkMovement[LM_PUSHY2B] = 0;
	LinkMovement[LM_STICKX] = 0;
	LinkMovement[LM_STICKY] = 0;
	LinkMovement[LM_MOVEBOOST] = 0;
	LinkMovement[LM_LASTX] = Link->X;
	LinkMovement[LM_LASTY] = Link->Y;
	LinkMovement[LM_LASTDMAP] = Game->GetCurDMap();
	LinkMovement[LM_LASTDMAPSCREEN] = Game->GetCurDMapScreen();
}

//This function goes within the while(true) loop and before Waitdraw() it handles:
//	-Storing Link's inputs
//	-Scrolling stuff when speed boost < -1.5
//	-Pre-waitdraw push

void LinkMovement_Update1(){
	__LinkMovement_UpdateInput();
	__LinkMovement_UglyReverseMovementFix();
	__LinkMovement_UpdatePush(LM_PUSHX1A, LM_PUSHY1A, false);
	__LinkMovement_UpdatePush(LM_PUSHX1B, LM_PUSHY1B, true);
}

//This function goes within the while(true) loop and after Waitdraw() it handles:
//	-Undoing base movement when instructed
//	-Applying speed boosts
//	-Post-waitdraw push

void LinkMovement_Update2(){
	__LinkMovement_UndoLinkMovementUpdate1();
	__LinkMovement_SpeedChange();
	__LinkMovement_UpdatePush(LM_PUSHX2A, LM_PUSHY2A, false);
	__LinkMovement_UpdatePush(LM_PUSHX2B, LM_PUSHY2B, true);
	__LinkMovement_UndoLinkMovementUpdate2();
}

//Here's an example global script with just the LinkMovement update functions. 
//Combine this with your other global scripts if you have any.

global script LinkMovement_Example{
	void run(){
		LinkMovement_Init();
		while(true){
			LinkMovement_Update1();
			Waitdraw();
			LinkMovement_Update2();
			Waitframe();
		}
	}
}

//USER SCRIPT FUNCTIONS

//Push Functions:
//All four of these functions do the same thing: Push Link around by an amount on the X and Y axis.
//pX: Amount to move Link on the X axis in pixels
//pY: Amount to move Link on the Y axis in pixels
//When moved by these functions, Link will not move through solid objects of any kind.

//Will push Link before Waitdraw()
//Will not push Link past the edge of the screen
void LinkMovement_Push(int pX, int pY){
	LinkMovement[LM_PUSHX1A] += pX;
	LinkMovement[LM_PUSHY1A] += pY;
}

//Will push Link before Waitdraw()
//Will push Link past the edge of the screen
void LinkMovement_PushNoEdge(int pX, int pY){
	LinkMovement[LM_PUSHX1B] += pX;
	LinkMovement[LM_PUSHY1B] += pY;
}

//Will push Link after Waitdraw()
//Will not push Link past the edge of the screen
void LinkMovement_Push2(int pX, int pY){
	LinkMovement[LM_PUSHX2A] += pX;
	LinkMovement[LM_PUSHY2A] += pY;
}

//Will push Link after Waitdraw()
//Will push Link past the edge of the screen
void LinkMovement_Push2NoEdge(int pX, int pY){
	LinkMovement[LM_PUSHX2B] += pX;
	LinkMovement[LM_PUSHY2B] += pY;
}

//Speed boost functions:
//These change the speed boost variable for the frame. 
//This is once again measured in pixels and will reset at the end of the frame.
//Setting a speed boost adds extra movement onto Link's base speed of 1.5 pixels per frames.
//Using these functions is preferable to writing to the speed boost directly in case things change in a future update.

//This function adds to the speed boost.
void LinkMovement_AddLinkSpeedBoost(float i){
	LinkMovement[LM_MOVEBOOST] += i;
}

//This function sets the speed boost.
void LinkMovement_SetLinkSpeedBoost(float i){
	LinkMovement[LM_MOVEBOOST] = i;
}

//This function returns the value of the current speed boost.
float LinkMovement_GetLinkSpeedBoost(){
	return LinkMovement[LM_MOVEBOOST];
}

//This function returns the current X value of Link's directional inputs.
// 0 = No InputLeft or InputRight
//-1 = InputLeft
// 1 = InputRight
int LinkMovement_StickX(){
	return LinkMovement[LM_STICKX];
}

//This function returns the current Y value of Link's directional inputs.
// 0 = No InputUp or InputDown
//-1 = InputUp
// 1 = InputDown
int LinkMovement_StickY(){
	return LinkMovement[LM_STICKY];
}

//This function sets the global script to undo Link's movement for the next frame
void LinkMovement_UndoLinkMovement(){
	LinkMovement[LM_UNDOLINKMOVEMENT] = 3;
}

//This function sets the global script to undo Link's movement selectively for the next frame
//	-bool undoX: Undo movement on the X-axis
//	-bool undoY: Undo movement on the Y-axis
void LinkMovement_UndoLinkMovement(bool undoX, bool undoY){
	LinkMovement[LM_UNDOLINKMOVEMENT] = Cond(undoX, 1, 0)+2*Cond(undoY, 1, 0);
}