Copy to Clipboard Test

[2.55] Oracle Minecarts Code

#include "TempLinkState255.zh"

// Use this function to disable some items while in a minecart
bool CanUseItemInMinecart(int itemid)
{
	// Here's an example of how you can 
	// disable an item while in the minecart
	// if(itemid==I_HAMMER)
		// return false;
	return true;
}

const bool MINECART_TURN_LINK_WITH_CART = true; // If true, Link will turn with the cart when not moving
const bool MINECART_USE_SPECIAL_RIDING_SPRITES = false; // If true, replace Link's riding sprites with the minecart's combo +12

const int SFX_MINECART = 65; //Looping sound of the minecart on the tracks
const int MINECART_SFX_FREQ = 30; //How often the sound loops
									 
const int MINECART_LINKYOFFSET = 10; //Offset Link's sprite is drawn at compared to the minecart
const int MINECART_HOLDFRAMES = 8; //How many frames Link needs to hold to get in the minecart

const int DAMAGE_MINECART_COLLISION = 8; //How much damage the minecart does when it hits enemies
const int LW_MINECART_DAMAGE = LW_SCRIPT10; //Weapon type the minecart's hitbox uses
const int LW_MINECART_DAMAGE_DEFENSE = LW_SWORD; //Defense type the minecart's hitbox uses


const int MAX_MINECARTS = 1024;
untyped GBMinecarts[MCI_LAST+MAX_MINECARTS*MCII_LAST];

enum MinecartIndices
{
	MCI_FIRSTLOAD,
	MCI_ACTIVEMINECARTS,
	MCI_INMINECART,
	MCI_CURRENTID,
	MCI_CARTX,
	MCI_CARTY,
	MCI_CARTDIR,
	MCI_CARTSPEED,
	MCI_CARTTEMPSTEP,
	MCI_CARTCOMBO,
	MCI_CARTCSET,
	MCI_SFXTIMER,
	MCI_NOAIRSCROLL_QR,
	
	MCI_LAST
	
};

enum MinecartInstanceIndices
{
	MCII_MAP,
	MCII_SCREEN,
	MCII_X,
	MCII_Y,
	MCII_NORESET,
	MCII_COMBO,
	MCII_CSET,
	MCII_DIR,
	MCII_SPEED,
	MCII_ORIGINALSCREEN,
	MCII_ORIGINALMAP,
	MCII_ORIGINALX,
	MCII_ORIGINALY,
	MCII_ORIGINALFFC,
	
	MCII_LAST
};

enum MinecartTrackTypes
{
	MTT_LANDINGPAD,
	MTT_VERTICAL,
	MTT_HORIZONTAL,
	MTT_RIGHTDOWN, 
	MTT_LEFTDOWN,
	MTT_RIGHTUP,
	MTT_LEFTUP,
	MTT_UPT,
	MTT_DOWNT,
	MTT_LEFTT,
	MTT_RIGHTT,
	MTT_4WAY
};

void SetMinecartVar(int id, int var, int value)
{
	GBMinecarts[MCII_LAST+id*MCII_LAST+var] = value;
}

int GetMinecartVar(int id, int var)
{
	return GBMinecarts[MCII_LAST+id*MCII_LAST+var];
}

generic script MinecartGeneric
{
	void run()
	{
		Screen->DrawOrigin = DRAW_ORIGIN_SPRITE;
		Screen->DrawOriginTarget = Hero;
		if(!GBMinecarts[MCI_FIRSTLOAD])
		{
			GBMinecarts[MCI_FIRSTLOAD] = true;
			GBMinecarts[MCI_NOAIRSCROLL_QR] = Game->FFRules[qr_NO_SCROLL_WHILE_IN_AIR];
		}
		
		int cartScript = Game->GetFFCScript("GBMinecart");
		int trackScript = Game->GetComboScript("GBMinecart_Track");
		
		GBMinecarts[MCI_INMINECART] = false;
		
		WaitTo(SCR_TIMING_POST_FFCS);
		
		Game->FFRules[qr_NO_SCROLL_WHILE_IN_AIR] = GBMinecarts[MCI_NOAIRSCROLL_QR];
		
		this->ReloadState[GENSCR_ST_RELOAD] = true;
		this->ReloadState[GENSCR_ST_CONTINUE] = true;
		
		int lastScreen = -1;
		int lastDMap = -1;
		
		bool waitRepositionCarts;
		while(true)
		{
			WaitTo(SCR_TIMING_POST_FFCS);
			if(lastDMap!=Game->GetCurDMap()||lastScreen!=Game->GetCurScreen())
			{
				lastScreen = Game->GetCurScreen();
				lastDMap = Game->GetCurDMap();
				
				//ListMinecartPositions();
				
				// Tell the script to wait on scrolling to update carts for the new screen
				if(GBMinecarts[MCI_INMINECART])
				{
					waitRepositionCarts = true;
				}
				SpawnCarts(cartScript);
			}
			
			int id = GBMinecarts[MCI_CURRENTID];
			
			if(waitRepositionCarts&&Game->Scrolling[SCROLL_DIR]==-1)
			{
				if(GBMinecarts[MCI_INMINECART])
				{
					GBMinecarts[MCI_CARTX] = Clamp(Link->X, 0, Region->Width-16);
					GBMinecarts[MCI_CARTY] = Clamp(Link->Y, 0, Region->Height-16);
				}
				waitRepositionCarts = false;
			}
			
			WaitTo(SCR_TIMING_POST_EWPN_SCRIPT); // Before Link moves
			if(GBMinecarts[MCI_INMINECART])
			{
				Game->FFRules[qr_NO_SCROLL_WHILE_IN_AIR] = false;
				++GBMinecarts[MCI_SFXTIMER];
				if(GBMinecarts[MCI_SFXTIMER]>=MINECART_SFX_FREQ)
				{
					GBMinecarts[MCI_SFXTIMER] = 0;
					Game->PlaySound(SFX_MINECART);
				}
				
				PreventScrolling();
		
				if(Game->Scrolling[SCROLL_DIR]>-1)
				{
					WaitTo(SCR_TIMING_POST_PLAYER_ANIMATE); // After Link moves
					
					SetSpecialLinkSprite();
					int cmb = GBMinecarts[MCI_CARTCOMBO];
					int cs = GBMinecarts[MCI_CARTCSET];
					int dir = GBMinecarts[MCI_CARTDIR];
					Screen->FastCombo(SPLAYER_NPC_DRAW, 0, 0, cmb+dir+4, cs, OP_OPAQUE);
					Screen->FastCombo(SPLAYER_PLAYER_DRAW, 0, 0, cmb+dir+8, cs, OP_OPAQUE);
				}
				else
				{
					if(!CanUseItemInMinecart(Link->ItemA))
					{
						Link->InputA = false; Link->PressA = false;
					}
					if(!CanUseItemInMinecart(Link->ItemB))
					{
						Link->InputB = false; Link->PressB = false;
					}
					if(!CanUseItemInMinecart(Link->ItemX))
					{
						Link->InputEx1 = false; Link->PressEx1 = false;
					}
					if(!CanUseItemInMinecart(Link->ItemY))
					{
						Link->InputEx2 = false; Link->PressEx2 = false;
					}
					
					// Move until hitting a platform to get off
					if(MoveCart(cartScript, trackScript))
					{
						SetMinecartVar(id, MCII_MAP, Game->GetCurMap());
						SetMinecartVar(id, MCII_SCREEN, Game->GetCurScreen());
						SetMinecartVar(id, MCII_X, GBMinecarts[MCI_CARTX]);
						SetMinecartVar(id, MCII_Y, GBMinecarts[MCI_CARTY]);
						SetMinecartVar(id, MCII_DIR, OppositeDir(GBMinecarts[MCI_CARTDIR]));
						SpawnCart(cartScript, GBMinecarts[MCI_CURRENTID], GBMinecarts[MCI_CARTX], GBMinecarts[MCI_CARTY], 1);
						GBMinecarts[MCI_INMINECART] = false;
						continue;
					}
				
					Link->X = GBMinecarts[MCI_CARTX];
					Link->Y = GBMinecarts[MCI_CARTY];
					Link->FakeZ = MINECART_LINKYOFFSET;
					Link->FakeJump = 0;
					
					WaitTo(SCR_TIMING_POST_PLAYER_ANIMATE); // After Link moves
					
					Link->X = GBMinecarts[MCI_CARTX];
					Link->Y = GBMinecarts[MCI_CARTY];
					Link->FakeZ = MINECART_LINKYOFFSET;
					Link->FakeJump = 0;
					
					SetSpecialLinkSprite();
					int cmb = GBMinecarts[MCI_CARTCOMBO];
					int cs = GBMinecarts[MCI_CARTCSET];
					int dir = GBMinecarts[MCI_CARTDIR];
					Screen->FastCombo(SPLAYER_NPC_DRAW, 0, 0, cmb+dir+4, cs, OP_OPAQUE);
					Screen->FastCombo(SPLAYER_PLAYER_DRAW, 0, 0, cmb+dir+8, cs, OP_OPAQUE);
				}
			}
			
			Waitframe();
		}
	}
	// List off all minecart locations in the console
	void ListMinecartPositions()
	{
		printf("\nMINECARTS AS OF SCREEN %d\n\n", Game->GetCurScreen());
		for(int i=0; i<GBMinecarts[MCI_ACTIVEMINECARTS]; ++i)
		{
			printf("MINECART %d\n", i);
			int map = GetMinecartVar(i, MCII_MAP);
			int omap = GetMinecartVar(i, MCII_ORIGINALMAP);
			int scrn = GetMinecartVar(i, MCII_SCREEN);
			int oscrn = GetMinecartVar(i, MCII_ORIGINALSCREEN);
			int x = GetMinecartVar(i, MCII_X);
			int ox = GetMinecartVar(i, MCII_ORIGINALX);
			int y = GetMinecartVar(i, MCII_Y);
			int oy = GetMinecartVar(i, MCII_ORIGINALY);
			int dir = GetMinecartVar(i, MCII_DIR);
			printf("  Map %d (%d)\n  Screen %d (%d)\n  X %d (%d)\n  Y %d (%d)\n  Dir %d\n", map, omap, scrn, oscrn, x, ox, y, oy, dir);
		}
	}
	// Move the cart by one step, handling turning. Returns true if dismounting
	bool MoveCart(int cartScript, int trackScript)
	{
		Link->ShadowXOffset = 1000;
		TempLinkState_UnsetCollDetection(1);
		GBMinecarts[MCI_CARTTEMPSTEP] += GBMinecarts[MCI_CARTSPEED];
		while(GBMinecarts[MCI_CARTTEMPSTEP]>=1)
		{
			// When grid aligned, process turns
			if(GBMinecarts[MCI_CARTX]%16==0&&GBMinecarts[MCI_CARTY]%16==0)
			{
				int dir = GBMinecarts[MCI_CARTDIR];
				int track[4];
				GetTrackData(trackScript, track, GBMinecarts[MCI_CARTX], GBMinecarts[MCI_CARTY]);
				int track_id = track[0];
				int track_canTurn = track[1];
				int track_biasDir = track[2];
				int track_flags = track[3];
				
				int newdir;
				// Check the track under the cart
				{
					// If you can go straight
					if(track_flags&(1<<dir))
					{
						newdir = dir;
					}
					// Else try the bias direction
					else if(track_biasDir!=OppositeDir(dir)&&track_biasDir>-1&&track_biasDir<4&&track_flags&(1<<track_biasDir))
					{
						newdir = track_biasDir;
					}
					// Just try everything man
					else
					{
						for(int i=0; i<4; ++i)
						{
							if(i!=OppositeDir(dir)&&track_flags&(1<<i))
							{
								newdir = i;
								break;
							}
						}
					}
					
					// If turning is allowed, let that influence things
					if(track_canTurn)
					{
						if(Link->InputUp&&track_flags&(1<<DIR_UP)&&OppositeDir(dir)!=DIR_UP)
							newdir = DIR_UP;
						else if(Link->InputDown&&track_flags&(1<<DIR_DOWN)&&OppositeDir(dir)!=DIR_DOWN)
							newdir = DIR_DOWN;
						else if(Link->InputLeft&&track_flags&(1<<DIR_LEFT)&&OppositeDir(dir)!=DIR_LEFT)
							newdir = DIR_LEFT;
						else if(Link->InputRight&&track_flags&(1<<DIR_RIGHT)&&OppositeDir(dir)!=DIR_RIGHT)
							newdir = DIR_RIGHT;
					}
				}
				
				// Next check the track in front of the cart
				int tx = GBMinecarts[MCI_CARTX]+DirX(newdir)*16;
				int ty = GBMinecarts[MCI_CARTY]+DirY(newdir)*16;
				GetTrackData(trackScript, track, tx, ty);
				track_id = track[0];
				track_canTurn = track[1];
				track_biasDir = track[2];
				track_flags = track[3];
				
				// If another cart is in the way, turn around
				if(BlockedByCart(cartScript, tx, ty))
				{
					GBMinecarts[MCI_CARTDIR] = OppositeDir(dir);
				}
				// If it's a landing pad, return true
				else if(track_id==MTT_LANDINGPAD)
				{
					GBMinecarts[MCI_CARTDIR] = newdir;
					GBMinecarts[MCI_CARTTEMPSTEP] = 0;
					Link->ShadowXOffset = 0;
					Game->FFRules[qr_NO_SCROLL_WHILE_IN_AIR] = GBMinecarts[MCI_NOAIRSCROLL_QR];
					return true;
				}
				// If it's not a track at all, turn around
				else if(track_id==-1)
				{
					GBMinecarts[MCI_CARTDIR] = OppositeDir(dir);
				}
				else
					GBMinecarts[MCI_CARTDIR] = newdir;
					
				TurnLinkWithCart(dir, GBMinecarts[MCI_CARTDIR]);
			}
			
			GBMinecarts[MCI_CARTX] += DirX(GBMinecarts[MCI_CARTDIR]);
			GBMinecarts[MCI_CARTY] += DirY(GBMinecarts[MCI_CARTDIR]);
			
			--GBMinecarts[MCI_CARTTEMPSTEP];
		}
		
		if(DAMAGE_MINECART_COLLISION)
		{
			lweapon hitbox = CreateLWeaponAt(LW_MINECART_DAMAGE, GBMinecarts[MCI_CARTX], GBMinecarts[MCI_CARTY]);
			hitbox->Damage = DAMAGE_MINECART_COLLISION;
			hitbox->DrawXOffset = 1000;
			hitbox->Weapon = LW_MINECART_DAMAGE_DEFENSE;
			hitbox->Timeout = 2;
		}
		
		return false;
	}
	// Goofy function that turns Link by the difference of the cart's old and new directions
	void TurnLinkWithCart(int oldDir, int newDir)
	{
		if(!MINECART_TURN_LINK_WITH_CART)
			return;
		if(Link->Action!=LA_NONE||Link->InputUp||Link->InputDown||Link->InputLeft||Link->InputRight)
			return;
		
		int sd_old, sd_new;
		switch(oldDir)
		{
			case DIR_UP:
				sd_old = 0;
				break;
			case DIR_RIGHT:
				sd_old = 1;
				break;
			case DIR_DOWN:
				sd_old = 2;
				break;
			case DIR_LEFT:
				sd_old = 3;
				break;
		}
		switch(newDir)
		{
			case DIR_UP:
				sd_new = 0;
				break;
			case DIR_RIGHT:
				sd_new = 1;
				break;
			case DIR_DOWN:
				sd_new = 2;
				break;
			case DIR_LEFT:
				sd_new = 3;
				break;
		}
		int turns = ((sd_new-sd_old)+4)%4;
		for(int i=0; i<turns; ++i)
		{
			switch(Link->Dir)
			{
				case DIR_UP:
					Link->Dir = DIR_RIGHT;
					break;
				case DIR_DOWN:
					Link->Dir = DIR_LEFT;
					break;
				case DIR_LEFT:
					Link->Dir = DIR_UP;
					break;
				case DIR_RIGHT:
					Link->Dir = DIR_DOWN;
					break;
			}
		}
	}
	// Scans over all the screen's FFCs to see if there's a cart at a position
	bool BlockedByCart(int cartScript, int x, int y)
	{
		int pos = ComboAt(x, y);
		for(int i=1; i<=MAX_FFC; ++i)
		{
			ffc f = Screen->LoadFFC(i);
			if(f->Script==cartScript&&ComboAt(f->X+8, f->Y+8)==pos)
				return true;
		}
		return false;
	}
	// Try to prevent Link from scrolling off the screen
	void PreventScrolling()
	{
		if(Link->Y<=2&&Link->InputUp)
			Link->InputUp = false;
		if(Link->Y>=158&&Link->InputDown)
			Link->InputDown = false;
		if(Link->X<=2&&Link->InputLeft)
			Link->InputLeft = false;
		if(Link->X>=238&&Link->InputRight)
			Link->InputRight = false;
	}
	// Sets Link's sprite when in the minecart
	void SetSpecialLinkSprite()
	{
		if(MINECART_USE_SPECIAL_RIDING_SPRITES&&(Link->Action==LA_NONE||Link->Action==LA_WALKING))
			TempLinkState_SetLinkTileOverride(Game->ComboTile(GBMinecarts[MCI_CARTCOMBO]+12+Link->Dir), 2);
	}
	// Gets information about track combos from an xy position
	void GetTrackData(int trackScript, int[] track, int x, int y)
	{
		int pos = ComboAt(x+8, y+8);
		mapdata l1 = Game->LoadTempScreen(1);
		mapdata l2 = Game->LoadTempScreen(2);
		bool found;
		// Check each layer 0-2 for a track
		combodata cd = Game->LoadComboData(Screen->ComboD[pos]);
		if(cd->Script==trackScript)
			found = true;
		if(!found)
		{
			cd = Game->LoadComboData(l1->ComboD[pos]);
			if(cd->Script==trackScript)
				found = true;
		}
		if(!found)
		{
			cd = Game->LoadComboData(l2->ComboD[pos]);
			if(cd->Script==trackScript)
				found = true;
		}
		// If found, set the return values
		if(found)
		{
			track[0] = cd->InitD[0];
			track[1] = cd->InitD[1];
			track[2] = cd->InitD[2];
			switch(track[0])
			{
				case MTT_LANDINGPAD:
					track[3] = 0;
					break;
				case MTT_VERTICAL:
					track[3] = 0011b;
					break;
				case MTT_HORIZONTAL:
					track[3] = 1100b;
					break;
				case MTT_RIGHTDOWN:
					track[3] = 1010b;
					break;
				case MTT_LEFTDOWN:
					track[3] = 0110b;
					break;
				case MTT_RIGHTUP:
					track[3] = 1001b;
					break;
				case MTT_LEFTUP:
					track[3] = 0101b;
					break;
				case MTT_UPT:
					track[3] = 1101b;
					break;
				case MTT_DOWNT:
					track[3] = 1110b;
					break;
				case MTT_LEFTT:
					track[3] = 0111b;
					break;
				case MTT_RIGHTT:
					track[3] = 1011b;
					break;
				case MTT_4WAY:
					track[3] = 1111b;
					break;
			}
		}
		else
		{
			// Default to -1 for a non track combo
			track[0] = -1;
			track[1] = 0;
			track[2] = -1;
			track[3] = 1111b;
		}
	}
	// Tries to spawn a cart FFC where a stationary cart should be, optionally have Link jump out of it
	void SpawnCart(int cartScript, int id, int x, int y, int jumpOut=0)
	{
		int ffcSlot = GetMinecartVar(id, MCII_ORIGINALFFC);
		
		ffc f;
		// If it's already on the screen
		if(GetMinecartVar(id, MCII_ORIGINALMAP)==Game->GetCurMap()&&GetMinecartVar(id, MCII_ORIGINALSCREEN)==Game->GetCurScreen())
		{
			f = Screen->LoadFFC(ffcSlot);
			if(f->Data&&f->Script==cartScript&&f->X==GetMinecartVar(id, MCII_ORIGINALX)&&f->Y==GetMinecartVar(id, MCII_ORIGINALY))
			{
				return;
			}
		}
		
		// Check if we're reetering the cart's home screen and don't spawn until jumping out
		if(!(GBMinecarts[MCI_INMINECART]&&id==GBMinecarts[MCI_CURRENTID])||jumpOut)
		{
			// Spawn a new minecart
			int slot = RunFFCScript(cartScript, {GetMinecartVar(id, MCII_SPEED), GetMinecartVar(id, MCII_NORESET), id+1, jumpOut});
			if(slot>0)
			{
				f = Screen->LoadFFC(slot);
				f->Data = GetMinecartVar(id, MCII_COMBO)+GetMinecartVar(id, MCII_DIR);
				f->CSet = GetMinecartVar(id, MCII_CSET);
				f->X = GetMinecartVar(id, MCII_X);
				f->Y = GetMinecartVar(id, MCII_Y);
				f->Flags[FFCF_PRELOAD] = true;
			}
		}
	}
	// Spawn all carts when entering a new screen
	void SpawnCarts(int cartScript)
	{
		for(int i=0; i<GBMinecarts[MCI_ACTIVEMINECARTS]; ++i)
		{
			if(GetMinecartVar(i, MCII_MAP)==Game->GetCurMap()&&GetMinecartVar(i, MCII_SCREEN)==Game->GetCurScreen())
			{
				SpawnCart(cartScript, i, GetMinecartVar(i, MCII_X), GetMinecartVar(i, MCII_Y));
			}
		}
	}
}

@InitD0("Speed"),
@InitDHelp0("The speed the cart travels in pixels per frame"),
@InitD1("No Reset"),
@InitDHelp1("If 1, this minecart's position will never reset when the Reset_Minecarts FFC script runs"),
@InitD2("Script Spawned"),
@InitDHelp2("Used for communication with the Generic script, leave at 0."),
@InitD3("Jump Out?"),
@InitDHelp2("Used for communication with the Generic script, leave at 0.")
ffc script GBMinecart
{
	void run(int speed, int noReset, int scriptSpawned, int jumpOut)
	{
		int id;
		if(!scriptSpawned)
		{
			id = GetMinecartID(this);
			// If this is a new minecart, set it up
			if(GetMinecartVar(id, MCII_MAP)==0)
			{
				SetMinecartVar(id, MCII_MAP, Game->GetCurMap());
				SetMinecartVar(id, MCII_SCREEN, Game->GetCurScreen());
				SetMinecartVar(id, MCII_X, this->X);
				SetMinecartVar(id, MCII_Y, this->Y);
				SetMinecartVar(id, MCII_DIR, this->Data%4);
				SetMinecartVar(id, MCII_NORESET, noReset);
				SetMinecartVar(id, MCII_ORIGINALMAP, Game->GetCurMap());
				SetMinecartVar(id, MCII_ORIGINALSCREEN, Game->GetCurScreen());
				SetMinecartVar(id, MCII_ORIGINALX, this->X);
				SetMinecartVar(id, MCII_ORIGINALY, this->Y);
				SetMinecartVar(id, MCII_ORIGINALFFC, this->ID);
				SetMinecartVar(id, MCII_COMBO, Floor(this->Data/4)*4);
				SetMinecartVar(id, MCII_CSET, this->CSet);
				SetMinecartVar(id, MCII_SPEED, speed);
				++GBMinecarts[MCI_ACTIVEMINECARTS];
			}
			else
			{
				// If it's not parked on the current screen, quit out
				if(GetMinecartVar(id, MCII_MAP)!=Game->GetCurMap()||GetMinecartVar(id, MCII_SCREEN)!=Game->GetCurScreen()||GetMinecartVar(id, MCII_X)!=this->X||GetMinecartVar(id, MCII_Y)!=this->Y)
				{
					this->Data = 0;
					Quit();
				}
				// Else make sure it faces the right way
				else
					SetMinecartVar(id, MCII_DIR, this->Data%4);
			}
		}
		else
		{
			id = scriptSpawned-1;
			this->Data = GetMinecartVar(id, MCII_COMBO)+GetMinecartVar(id, MCII_DIR);
			this->CSet = GetMinecartVar(id, MCII_CSET);
		}
		
		// The minecart origin FFC shouldn't spawn while riding it
		if(GBMinecarts[MCI_INMINECART]&&id==GBMinecarts[MCI_CURRENTID])
		{
			int oldcmb = this->Data;
			// Failsafe to prevent a false positive while F6ing in a cart. Bleh.
			this->Data = FFCS_INVISIBLE_COMBO;
			Waitframe();
			if(GBMinecarts[MCI_INMINECART]&&id==GBMinecarts[MCI_CURRENTID])
			{
				this->Data = 0;
				Quit();
			}
			this->Data = oldcmb;
		}
		
		int dir = this->Data%4;
		// Jump out animation if called via script
		if(jumpOut)
		{
			// Link is launched out in the opposite direction because 
			// the cart turns around on reaching the platform
			Link->Dir = OppositeDir(dir);
			Link->Jump = 2;
			Game->PlaySound(SFX_JUMP);
			int tx = this->X+DirX(OppositeDir(dir))*16;
			int ty = this->Y+DirY(OppositeDir(dir))*16;
			int angle = Angle(this->X, this->Y, tx, ty);
			int dist = Distance(this->X, this->Y, tx, ty);
			int linkX = Link->X;
			int linkY = Link->Y;
			for(int i=0; i<26; i++){
				linkX += VectorX(dist/26, angle);
				linkY += VectorY(dist/26, angle);
				NoAction();
				Waitdraw();
				Link->X = linkX;
				Link->Y = linkY;
				Waitframe();
			}
			Link->X = tx;
			Link->Y = ty;
			this->Data = Floor(this->Data/4)*4+dir;
		}
		this->Flags[FFCF_SOLID] = true;
		int timer[1];
		
		if(this->Flags[FFCF_PRELOAD])
			Waitframe();
			
		while(true)
		{
			if(PressAgainstCart(this, timer))
			{
				this->Flags[FFCF_SOLID] = false;
				Link->Dir = AngleDir4(Angle(Link->X, Link->Y, this->X, this->Y-MINECART_LINKYOFFSET));
				Link->Jump = 2;
				Game->PlaySound(SFX_JUMP);
				int tx = this->X;
				int ty = this->Y-MINECART_LINKYOFFSET;
				int angle = Angle(Link->X, Link->Y, tx, ty);
				int dist = Distance(Link->X, Link->Y, tx, ty);
				int linkX = Link->X;
				int linkY = Link->Y;
				for(int i=0; i<26; i++){
					linkX += VectorX(dist/26, angle);
					linkY += VectorY(dist/26, angle);
					NoAction();
					Waitdraw();
					Link->X = linkX;
					Link->Y = linkY;
					Waitframe();
				}
				Link->X = tx;
				Link->Y = ty;
				
				// Set global variables for carting based on the FFC
				GBMinecarts[MCI_INMINECART] = true;
				GBMinecarts[MCI_CURRENTID] = id;
				GBMinecarts[MCI_CARTCOMBO] = GetMinecartVar(id, MCII_COMBO);
				GBMinecarts[MCI_CARTCSET] = GetMinecartVar(id, MCII_CSET);
				GBMinecarts[MCI_CARTDIR] = dir;
				GBMinecarts[MCI_CARTSPEED] = GetMinecartVar(id, MCII_SPEED);
				GBMinecarts[MCI_CARTX] = this->X;
				GBMinecarts[MCI_CARTY] = this->Y;
				
				this->Data = 0;
				Quit();
			}
			Waitframe();
		}
	}
	int GetMinecartID(ffc this)
	{	
		// Scan over all active minecarts
		for(int i=0; i<GBMinecarts[MCI_ACTIVEMINECARTS]; ++i)
		{
			int ogMap = GetMinecartVar(i, MCII_ORIGINALMAP);
			int ogScreen = GetMinecartVar(i, MCII_ORIGINALSCREEN);
			int ogFFC = GetMinecartVar(i, MCII_ORIGINALFFC);
			// Find one that matches the screen and FFC
			if(ogMap==Game->GetCurMap()&&ogScreen==Game->GetCurScreen()&&ogFFC==this->ID)
				return i;
		}
		if(GBMinecarts[MCI_ACTIVEMINECARTS]+1>MAX_MINECARTS)
		{
			printf("ERROR: Not enough free minecart slots, please increase MAX_MINECARTS");
			return 0;
		}
		// Otherwise it's a new minecart
		return GBMinecarts[MCI_ACTIVEMINECARTS];
	}
	bool PressAgainstCart(ffc this, int[] timer)
	{
		if(Link->Z>0||Link->FakeZ>0)
			return false;
		bool pressing;
		if(Abs(Link->X-this->X)<=8&&Link->Y>this->Y&&Link->Y<=this->Y+8&&Link->InputUp)
			pressing = true;
		if(Abs(Link->X-this->X)<=8&&Link->Y>=this->Y-16&&Link->Y<this->Y&&Link->InputDown)
			pressing = true;
		if(Link->X<=this->X+16&&Link->X>this->X&&Link->Y>=this->Y-8&&Link->Y<=this->Y&&Link->InputLeft)
			pressing = true;
		if(Link->X>=this->X-16&&Link->X<this->X&&Link->Y>=this->Y-8&&Link->Y<=this->Y&&Link->InputRight)
			pressing = true;
		if(pressing)
		{
			++timer[0];
			if(timer[0]>MINECART_HOLDFRAMES)
				return true;
		}
		else
			timer[0] = 0;
		return false;
	}
}

@InitD0("Only This Map"),
@InitDHelp0("If 1, only resets minecarts on the current map")
ffc script Reset_Minecarts
{
	void run(int onlyThisMap)
	{
		if(Abs(Link->X-this->X)<=8&&Abs(Link->Y-this->Y)<=8)
		{
			for(int i=0; i<GBMinecarts[MCI_ACTIVEMINECARTS]; ++i)
			{
				if(!onlyThisMap||GetMinecartVar(i, MCII_MAP)==Game->GetCurMap())
				{
					if(!GetMinecartVar(i, MCII_NORESET))
					{
						SetMinecartVar(i, MCII_MAP, GetMinecartVar(i, MCII_ORIGINALMAP));
						SetMinecartVar(i, MCII_SCREEN, GetMinecartVar(i, MCII_ORIGINALSCREEN));
						SetMinecartVar(i, MCII_X, GetMinecartVar(i, MCII_ORIGINALX));
						SetMinecartVar(i, MCII_Y, GetMinecartVar(i, MCII_ORIGINALY));
					}
				}
			}
		}
	}
}

const int CMB_SHUTTER_OPEN = 0;

ffc script GBMinecart_Shutter
{
	void run()
	{
		mapdata shutterLayer = Game->LoadTempScreen(IsBackgroundLayer(2)?1:2);
		int combo = this->Data;
		int pos = ComboAt(this->X+8, this->Y+8);
		this->Data = FFCS_INVISIBLE_COMBO;
		bool open;
		int x = Link->X;
		int y = Link->Y;
		if(this->Flags[FFCF_PRELOAD])
		{
			if(x<=0)
				x = Region->Width-16;
			else if(x>=Region->Width-16)
				x = 0;
			if(y<=0)
				y = Region->Height-16;
			else if(y>=Region->Height-16)
				y = 0;
		}
		int triggerDist = 16+4*GBMinecarts[MCI_CARTSPEED];
		if(GBMinecarts[MCI_INMINECART]&&Abs(x-this->X)<triggerDist&&Abs(y-this->Y)<triggerDist)
			open = true;
		
		shutterLayer->ComboD[pos] = open?CMB_SHUTTER_OPEN:combo;
		shutterLayer->ComboC[pos] = this->CSet;
		
		if(this->Flags[FFCF_PRELOAD])
			Waitframe();
		while(true)
		{
			x = Link->X;
			y = Link->Y;
			if(open)
			{
				if(!(Abs(x-this->X)<triggerDist&&Abs(y-this->Y)<triggerDist))
				{
					Game->PlaySound(SFX_SHUTTER);
					shutterLayer->ComboD[pos] = combo+4;
					Waitframes(4);
					shutterLayer->ComboD[pos] = combo;
					open = false;
				}
			}
			else
			{
				if(GBMinecarts[MCI_INMINECART]&&Abs(x-this->X)<triggerDist&&Abs(y-this->Y)<triggerDist)
				{
					Game->PlaySound(SFX_SHUTTER);
					shutterLayer->ComboD[pos] = combo+4;
					Waitframes(4);
					shutterLayer->ComboD[pos] = CMB_SHUTTER_OPEN;
					open = true;
				}
			}
			Waitframe();
		}
	}
}

@InitD0("Track Type"),
@InitDHelp0("0 - Landing Pad\n1 - Vertical\n2 - Horizontal\n3 - Right-Down Corner\n4 - Left-Down Corner\n5 - Right-Up Corner\n6 - Left-Up Corner\n7 - Up T-Piece\n8 - Down T-Piece\n9 - Left T-Piece\n10 - Right T-Piece\n11 - 4-Way Junction"),
@InitD1("Can Turn"),
@InitDHelp1("If 1, Link can turn the minecart on this track"),
@InitD2("Bias Direction"),
@InitDHelp2("If >-1, the track with prioritize this direction over other possible turns")
combodata script GBMinecart_Track
{
	void run(int trackType, int canTurn, int biasDir)
	{
		// Dummy script, used for its InitD[] values, see MinecartGeneric
	}
}