namespace Trampoline
{
const int TRAMPOLINE_AFRAMES = 8; // Animation speed for the bounce animation
const int TRAMPOLINE_MAX_Z = 176; // The Z height where Link bonks his head on the ceiling
const int CF_TRAMPOLINE_WARP = CF_SCRIPT1; // The flag marking which positions on the screen you can jump up a floor at
const int TRAMPOLINE_WARP_JUMP = 3.2; // Movement speed when bouncing up to another floor
@Flag0("Is Warp"),
@FlagHelp0("If true, the trampoline will bounce Link all the way off the top of the screen and warp him. If CF_TRAMPOLINE_WARP is not placed on any layer above him, he'll bonk off the ceiling."),
@Flag1("Direct Warp"),
@FlagHelp1("If true, the warp keeps Link's position."),
@Attribyte0("Bounce SFX"),
@AttribyteHelp0("This is the sound to play when bouncing off the trampoline"),
@Attribyte1("Bonk SFX"),
@AttribyteHelp1("This is the sound to play when bonking off a ceiling"),
@Attribyte2("Extra Floors"),
@AttribyteHelp2("If >0, the trampoline will send Link up this many extra floors"),
@InitD0("Jump Height"),
@InitDHelp0("How high to jump after bouncing on the trampoline or jumping out of a pit"),
@InitD1("Tile Warp ID"),
@InitDHelp1("Which tile warp to use for the warp")
combodata script Trampoline
{
void run(int jumpHeight, int tileWarpID)
{
bool fromAbove = Link->Z>0;
bool isWarp = this->Flags[0];
bool direct = this->Flags[1];
int bounceSFX = this->Attribytes[0];
int bonkSFX = this->Attribytes[1];
int extraFloors = this->Attribytes[2];
mapdata lyr = Game->LoadTempScreen(this->Layer);
int animCounter;
bool attemptingWarp;
bool warpFailed;
while(true)
{
// Keep the trampoline animating and turn off solidity in the air
UpdateSolid(this);
DrawBounce(this, lyr, animCounter);
if(animCounter)
--animCounter;
// Detect collision with the trampoline from above
if(!attemptingWarp&&Link->Z==0&&Link->FakeZ==0&&ComboAt(Link->X+7, Link->Y+12)==this->Pos)
{
int x = Link->X;
int y = Link->Y;
// Snap Link to the trampoline's position
while(Distance(x, y, this->X, this->Y)>4)
{
int ang = Angle(x, y, this->X, this->Y);
x += VectorX(4, ang);
y += VectorY(4, ang);
NoAction();
Waitdraw();
Link->X = x;
Link->Y = y;
Waitframe();
}
Link->X = this->X;
Link->Y = this->Y;
// If entering from the screen above, bounce off
if(fromAbove)
{
Link->Dir = FindExitDir();
NoAction();
Waitframe();
if(bounceSFX)
Game->PlaySound(bounceSFX);
Link->Jump = 1.2;
animCounter = TRAMPOLINE_AFRAMES*3;
// Bounce off animation
for(int i=0; i<16; ++i)
{
UpdateSolid(this);
DrawBounce(this, lyr, animCounter);
if(animCounter)
--animCounter;
Link->MoveXY(DirX(Link->Dir), DirY(Link->Dir));
NoAction();
Waitframe();
}
fromAbove = false;
}
else
{
if(bounceSFX)
Game->PlaySound(bounceSFX);
Link->Jump = jumpHeight;
if(isWarp)
{
attemptingWarp = true;
warpFailed = false;
}
animCounter = TRAMPOLINE_AFRAMES*3;
}
}
if(attemptingWarp)
{
// Prevent inputs warping
NoAction();
Waitdraw();
// And also snap Link into place
Link->X = this->X;
Link->Y = this->Y;
// If he's already hit the ceiling, move him downwards
if(warpFailed)
{
// Bounce off the trampoline when hitting the ground
if(Link->Z<=0)
{
Link->Dir = FindExitDir();
NoAction();
Waitframe();
if(bounceSFX)
Game->PlaySound(bounceSFX);
Link->Jump = 1.2;
animCounter = TRAMPOLINE_AFRAMES*3;
// Bounce off animation
for(int i=0; i<16; ++i)
{
UpdateSolid(this);
DrawBounce(this, lyr, animCounter);
if(animCounter)
--animCounter;
Link->MoveXY(DirX(Link->Dir), DirY(Link->Dir));
NoAction();
Waitframe();
}
NoAction();
Waitdraw();
attemptingWarp = false;
warpFailed = false;
}
}
// Otherwise he's rising
else
{
if(Link->Jump<0)
Link->Jump = 0;
Link->Z += TRAMPOLINE_WARP_JUMP-Link->Jump;
int maxZ = TRAMPOLINE_MAX_Z;
if(maxZ<0)
maxZ = Link->Y+16;
// Check for bonking and warping
if(Link->Z>=maxZ)
{
// Check all 7 layers for the warp marker flag
bool blocked = true;
for(int i=0; i<=6; ++i)
{
mapdata md = Game->LoadTempScreen(i);
if(md->ComboF[this->Pos]==CF_TRAMPOLINE_WARP||md->ComboI[this->Pos]==CF_TRAMPOLINE_WARP)
{
blocked = false;
break;
}
}
if(blocked)
{
Screen->Quake = 10;
if(bonkSFX)
Game->PlaySound(bonkSFX);
warpFailed = true;
}
else
{
// Run a generic script to handle jumping out of the pit on the next screen
genericdata gd = Game->LoadGenericData(Game->GetGenericScript("TrampolineGeneric"));
gd->DataSize = TDI_SIZE;
gd->Data[TDI_RUNFROMCOMBO] = true;
gd->Data[TDI_JUMP] = jumpHeight;
gd->Data[TDI_FALLING] = false;
gd->Data[TDI_FREEZEFALL] = false;
gd->Data[TDI_EXTRAFLOORS] = extraFloors;
gd->Data[TDI_WARPID] = tileWarpID;
gd->Data[TDI_DIRECTWARP] = direct;
gd->Data[TDI_BONKSFX] = bonkSFX;
gd->Running = true;
Link->MoveFlags[HEROMV_CAN_PITFALL] = false;
Link->Invisible = true;
Link->Z = 0;
TakeTileWarp(tileWarpID, direct);
}
}
}
}
Waitframe();
}
}
bool IsPushBlock(int flag)
{
switch(flag)
{
case CF_PUSHUPDOWN:
case CF_PUSH4WAY:
case CF_PUSHLR...CF_PUSHRIGHTINS:
case CF_PUSHED:
return true;
}
return false;
}
void DrawBounce(combodata this, mapdata lyr, int animCounter)
{
int drawLayer = this->Layer;
// If the trampoline is a push block, its draw layer must change
if(IsPushBlock(lyr->ComboF[this->Pos]))
drawLayer = SPLAYER_PUSHBLOCK;
if(animCounter>0)
{
// The combo animation is handled by drawing tiles over the combo on its layer
int frame = Floor((animCounter-1)/TRAMPOLINE_AFRAMES);
if(frame==0)
Screen->FastTile(drawLayer, this->X, this->Y, this->Tile+2, lyr->ComboC[this->Pos], OP_OPAQUE);
else if(frame==2)
Screen->FastTile(drawLayer, this->X, this->Y, this->Tile+1, lyr->ComboC[this->Pos], OP_OPAQUE);
}
}
void UpdateSolid(combodata this)
{
// Swap out solidity and no enemy flags
// This combo cannot have an inherent flag
if(Link->Z>0||Link->FakeZ>0)
{
this->Walk = 0x0;
this->Flag = CF_NOGROUNDENEMY;
}
else
{
this->Walk = 0xF;
this->Flag = 0;
}
}
int FindExitDir()
{
int vX = DirX(Link->Dir)*16;
int vY = DirY(Link->Dir)*16;
// Try the direction Link is facing
if(Link->CanMoveXY(vX, vY))
return Link->Dir;
// Then the opposite one
else if(Link->CanMoveXY(-vX, -vY))
return OppositeDir(Link->Dir);
// Then all four others starting from down
for(int i=0; i<4; ++i)
{
vX = DirX(OppositeDir(i))*16;
vY = DirY(OppositeDir(i))*16;
if(Link->CanMoveXY(vX, vY))
return OppositeDir(i);
}
// Default to facing down
return DIR_DOWN;
}
void TakeTileWarp(int i, bool direct)
{
int effect;
int type = Screen->GetTileWarpType(i);
int dmap = Screen->GetTileWarpDMap(i);
int scrn = Screen->GetTileWarpScreen(i);
int x = -1;
int y = Screen->TileWarpReturnSquare[i];
if(direct)
{
x = Link->X;
y = Link->Y;
}
int flags = WARP_FLAG_PLAYMUSIC;
switch(type)
{
case WT_NOWARP:
return;
case WT_ENTRANCEEXIT:
effect = WARPEFFECT_OPENWIPE;
flags |= WARP_FLAG_SETENTRANCESCREEN;
flags |= WARP_FLAG_SETENTRANCEDMAP;
flags |= WARP_FLAG_SETCONTINUESCREEN;
flags |= WARP_FLAG_SETCONTINUEDMAP;
break;
case WT_IWARPBLACKOUT:
effect = WARPEFFECT_INSTANT;
break;
case WT_IWARPOPENWIPE:
effect = WARPEFFECT_OPENWIPE;
break;
case WT_IWARPZAP:
effect = WARPEFFECT_ZAP;
break;
case WT_IWARPWAVE:
effect = WARPEFFECT_WAVE;
break;
}
Link->WarpEx({type, dmap, scrn, x, y, effect, 0, flags});
}
}
@InitD0("Z Height"),
@InitDHelp0("How high to place Link in the air after scrolling onto the new screen"),
@InitD1("Freeze"),
@InitDHelp1("If >0, Link can't move while in the air")
combodata script PitfallZAxis
{
void run(int zheight, int freeze)
{
if(!zheight)
{
if(TRAMPOLINE_MAX_Z<0)
zheight = this->Y+16;
else
zheight = TRAMPOLINE_MAX_Z;
}
while(true)
{
// Wait for Link to fall in the pit
int LinkPos = ComboAt(Link->X+8, Link->Y+12);
if(Link->Falling==70&&LinkPos==this->Pos) // nice
{
// Run the generic script
genericdata gd = Game->LoadGenericData(Game->GetGenericScript("TrampolineGeneric"));
gd->DataSize = TDI_SIZE;
gd->Data[TDI_RUNFROMCOMBO] = true;
gd->Data[TDI_JUMP] = zheight;
gd->Data[TDI_FALLING] = true;
gd->Data[TDI_FREEZEFALL] = freeze;
gd->Running = true;
Quit();
}
Waitframe();
}
}
}
enum TrampolineDataIndex
{
TDI_RUNFROMCOMBO,
TDI_JUMP,
TDI_FALLING,
TDI_FREEZEFALL,
TDI_EXTRAFLOORS,
TDI_WARPID,
TDI_DIRECTWARP,
TDI_BONKSFX,
TDI_SIZE
};
generic script TrampolineGeneric
{
void run()
{
this->DataSize = TDI_SIZE;
this->ReloadState[GENSCR_ST_RELOAD] = true;
this->ReloadState[GENSCR_ST_CONTINUE] = true;
// If this script was restarted via F6, quit out
if(!this->Data[TDI_RUNFROMCOMBO])
{
Link->MoveFlags[HEROMV_CAN_PITFALL] = true;
Link->Invisible = false;
Quit();
}
int dmap = Game->GetCurDMap();
int scrn = Game->GetCurDMapScreen();
this->Data[TDI_RUNFROMCOMBO] = false;
// Wait for the screen to change
while(dmap==Game->GetCurDMap()&&scrn==Game->GetCurDMapScreen())
{
Waitframe();
}
// If falling from a previous screen
if(this->Data[TDI_FALLING])
{
Link->Invisible = false;
Link->Z = this->Data[TDI_JUMP];
int x = Link->X;
int y = Link->Y;
while(Link->Z>0)
{
WaitTo(SCR_TIMING_POST_POLL_INPUT);
if(this->Data[TDI_FREEZEFALL])
NoAction();
WaitTo(SCR_TIMING_WAITDRAW);
if(this->Data[TDI_FREEZEFALL])
{
Link->X = x;
Link->Y = y;
}
Waitframe();
}
}
// If jumping out of a pit
else
{
int x = Link->X;
int y = Link->Y;
// If bounced up extra floors
bool hitCeiling;
while(this->Data[TDI_EXTRAFLOORS]>0&&!hitCeiling)
{
int maxZ = TRAMPOLINE_MAX_Z;
if(maxZ<0)
maxZ = Link->Y+16;
Link->Invisible = false;
Link->MoveFlags[HEROMV_CAN_PITFALL] = true;
// Launch to the top
while(Link->Z<maxZ)
{
if(Link->Jump<0)
Link->Jump = 0;
Link->Z += TRAMPOLINE_WARP_JUMP-Link->Jump;
WaitTo(SCR_TIMING_POST_POLL_INPUT);
NoAction();
Waitframe();
}
// Check for bonking or warping
// Check all 7 layers for the warp marker flag
bool blocked = true;
int pos = ComboAt(Link->X+8, Link->Y+8);
for(int i=0; i<=6; ++i)
{
mapdata md = Game->LoadTempScreen(i);
if(md->ComboF[pos]==CF_TRAMPOLINE_WARP||md->ComboI[pos]==CF_TRAMPOLINE_WARP)
{
blocked = false;
break;
}
}
if(blocked)
{
Screen->Quake = 10;
if(this->Data[TDI_BONKSFX])
Game->PlaySound(this->Data[TDI_BONKSFX]);
hitCeiling = true;
Link->MoveFlags[HEROMV_CAN_PITFALL] = true;
}
else
{
Link->MoveFlags[HEROMV_CAN_PITFALL] = false;
Link->Invisible = true;
Link->Z = 0;
Trampoline.TakeTileWarp(this->Data[TDI_WARPID], this->Data[TDI_DIRECTWARP]);
WaitTo(SCR_TIMING_POST_POLL_INPUT);
NoAction();
Waitframe();
}
--this->Data[TDI_EXTRAFLOORS];
}
if(hitCeiling)
{
// Fall to the ground after hitting a ceiling
while(Link->Z>0)
{
WaitTo(SCR_TIMING_POST_POLL_INPUT);
NoAction();
Waitframe();
}
}
else
{
Link->Invisible = false;
Link->Jump = this->Data[TDI_JUMP];
Link->MoveFlags[HEROMV_CAN_PITFALL] = true;
}
}
}
}
}