#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
}
}