Code
import "std.zh" //////////////////////////////////////////////////////////////// //// Constants //// Physics. const float GRAV = 0.16; const float TERM = 3.2; //// Platform. // In freefall. const int PLATFORM_AIR = 0; // Standing on a hard platform. const int PLATFORM_HARD = 1; // Standing on a soft platform. const int PLATFORM_SOFT = 2; //// Stairs const int STAIR_NONE = 0; // Standing on a up-left down-right stair. const int STAIR_POS = 1; // Standing on a up-right down-left stair. const int STAIR_NEG = -1; // Soft platforms const int CF_SOFT = 98; // Stairs. const int CT_NEG = 142; const int CT_CROSS = 143; const int CT_POS = 144; //// Link Movement const int JUMP = -4; const int LOW_JUMP = -1; const float WALK = 1.5; //////////////////////////////////////////////////////////////// //// Misc Indices //// NPCs // Vertical speed. const int MISC_NPC_VY = 0; //////////////////////////////////////////////////////////////// //// Global Variables. //// Link State // Link's actual location. Every frame Link->X and Link->Y are // set to these values. This is so Link can have partial-pixel movement. float LinkX = 0; float LinkY = 0; // Link's vertical velocity. float LinkVy = 0; // What Link is currently standing on. int LinkPlatform = PLATFORM_AIR; // If Link is on a stair. int LinkStair = STAIR_NONE; // The y offset for being on a stair. int LinkStairY = 0; // The left edge of the stair. int LinkStairLeft = 0; // The right edge of the stair. int LinkStairRight = 0; // If the stairs need to be recalculated. bool LinkStairRecalc = false; // If we are overriding link's position this frame. bool LinkPosOverride = false; //// Link's State last frame. int LinkLastX = -1; int LinkLastY = -1; int LinkLastAction = LA_NONE; //// Inputs last frame. bool LastInputUp = false; bool LastInputDown = false; bool LastInputLeft = false; bool LastInputRight = false; global script Active { void run() { LinkX = Link->X; LinkY = Link->Y; while (true) { LinkUpdate(); ActionJump(); //EnemyUpdate(); InputsUpdate(); Waitdraw(); ScreenChange_Update(); LinkUpdate2(); InputsUpdate2(); Waitframe();}}} int PlatformType(int x, int y) { if (!Screen->isSolid(x, y)) { return PLATFORM_AIR;} else if (ComboFI(x, y, CF_SOFT) || IsStair(x, y)) { return PLATFORM_SOFT;} else { return PLATFORM_HARD;}} bool IsStair(int x, int y) { return IsStair(ComboAt(x, y));} bool IsStair(int loc) { int ct = Screen->ComboT[loc]; return ct >= CT_NEG && ct <= CT_POS;} // Update Link's variables. void LinkUpdate() { // Update Last frame variables. LinkLastX = LinkX; LinkLastY = LinkY; LinkLastAction = Link->Action; // If the screen changed and we're on a stair, update // stair information for the new screen once we stop scrolling. if (LinkStair != STAIR_NONE && ScreenChanged) { LinkStairRecalc = true;} // Check if we're no longer on a stair. if (LinkStair != STAIR_NONE && Link->Action != LA_SCROLLING) { // Recalculate stairs if (LinkStairRecalc) { int loc = ComboAt(Link->X + 8, Link->Y + 20); if (IsStair(loc)) { StairLocate(loc);} else { if (LinkStair == STAIR_POS) { loc = ComboAt(Link->X - 2, Link->Y + 12);} else if (LinkStair == STAIR_NEG) { loc = ComboAt(Link->X + 18, Link->Y + 12);} if (IsStair(loc)) { StairLocate(loc);} else { LinkStair = STAIR_NONE;}}} // We were hurt, so fall off. if (Link->Action == LA_GOTHURTLAND) { LinkStair = STAIR_NONE;} else { // Check if we're no longer on a stair. int under = ComboAt(LinkX + 8, LinkY + 20); int forward = -1; if (LinkStair == STAIR_POS) {forward = ComboAt(LinkX - 2, LinkY + 12);} else if (LinkStair == STAIR_NEG) {forward = ComboAt(LinkX + 18, LinkY + 12);} int ct = Screen->ComboT[under]; bool hasStair = ct == CT_CROSS || ct == CT_CROSS + LinkStair; if (!hasStair && forward != -1) { ct = Screen->ComboT[forward]; hasStair = ct == CT_CROSS || ct == CT_CROSS + LinkStair;} if (!hasStair) { LinkStair = STAIR_NONE;}}} // Move accordinng to our special movement. if (LinkPosOverride) { LinkOverridePosition();} // Or we're relying on the engine for something, // so change our x and y to theirs. else { LinkX = Link->X; LinkY = Link->Y;} // If we've moved and have no action, set the action to walking. if (Link->Action == LA_NONE && (Link->InputLeft || Link->InputRight)) { Link->Action = LA_WALKING;}} void LinkUpdate2() { // Check if we're using the engine's movement this frame. LinkPosOverride = Link->Action != LA_FROZEN && Link->Action != LA_SCROLLING && Link->Action != LA_GOTHURTLAND && !ScreenChanged; // If we're on a stair and the screen is scrolling, then // change Link's X to match a change in Y and vice versa. //if (Link->Action == LA_SCROLLING && LinkStair != STAIR_NONE) { // int dx = LinkX - LinkLastX; // int dy = LinkY - LinkLastY; // if (dy != 0) { // LinkY = LinkX * LinkStair + LinkStairY;} // else if (dx != 0) { // LinkX = (LinkY - LinkStairY) / LinkStair;}} // Move Link to new location. // If we're frozen, it probably means the hookshot is being used. if (LinkPosOverride) { Link->X = Round(LinkX); Link->Y = Round(LinkY);} // Clear Jump and Z. Link->Jump = 0; Link->Z = 0; // Set direction. if (Link->Action != LA_FROZEN && Link->Action != LA_SCROLLING) { if (LastInputUp) {Link->Dir = DIR_UP;} else if (LastInputDown) {Link->Dir = DIR_DOWN;} else if (Link->InputLeft) {Link->Dir = DIR_LEFT;} else if (Link->InputRight) {Link->Dir = DIR_RIGHT;}}} // Override where the engine would have placed Link. void LinkOverridePosition() { // Move by inputs. if (Link->Action == LA_NONE || Link->Action == LA_WALKING || (Link->Action == LA_ATTACKING && LinkPlatform == PLATFORM_AIR)) { if (Link->InputLeft) { LinkX -= WALK;} else if (Link->InputRight) { LinkX += WALK;}} bool movedLeft = false; // Check if we can move left. if (LinkX < LinkLastX) { movedLeft = true; int left1 = PlatformType(LinkX, LinkY); int left2 = PlatformType(LinkX, LinkY + 8); int left3 = PlatformType(LinkX, LinkY + 15); if (LinkStair == STAIR_NONE && (left1 == PLATFORM_HARD || left2 == PLATFORM_HARD || left3 == PLATFORM_HARD)) { LinkX += 8 - (LinkX % 8);}} bool movedRight = false; // Check if we can move right. if (LinkX > LinkLastX) { movedRight = true; int left1 = PlatformType(LinkX + 15, LinkY); int left2 = PlatformType(LinkX + 15, LinkY + 8); int left3 = PlatformType(LinkX + 15, LinkY + 15); if (LinkStair == STAIR_NONE && (left1 == PLATFORM_HARD || left2 == PLATFORM_HARD || left3 == PLATFORM_HARD)) { LinkX &= 0x1FFF8;}} // Move by vertical velocity. LinkY += LinkVy; // Move according to a staircase. if (LinkStair != STAIR_NONE) { int x = Clamp(LinkX, LinkStairLeft, LinkStairRight); LinkY = x * LinkStair + LinkStairY;} // Not on a stair, check regular platforms else { // Check if we hit our head on something. if (LinkVy < 0) { int overLinkLeft = PlatformType(LinkX + 5, LinkY); int overLinkRight = PlatformType(LinkX + 11, LinkY); // We ran into a hard block. if (overLinkLeft == PLATFORM_HARD || overLinkRight == PLATFORM_HARD) { LinkVy = 0; // Move to the next multiple of 8. LinkY += 8 - (LinkY % 8);}} // See if we're standing on a platform. int underLinkLeft = PlatformType(LinkX + 5, LinkY + 16); int underLinkRight = PlatformType(LinkX + 11, LinkY + 16); // We're on a hard platform. if (LinkVy >= 0 && (underLinkLeft == PLATFORM_HARD || underLinkRight == PLATFORM_HARD)) { LinkPlatform = PLATFORM_HARD; LinkVy = 0; // Round down to nearest multiple of 8. LinkY &= 0x1FFF8;} // We're on a soft platform. else if (LinkVy >= 0 && (underLinkLeft == PLATFORM_SOFT || underLinkRight == PLATFORM_SOFT)) { // If we're falling and holding down, or we started below it, // go through it. if ((LinkVy > 0 && Link->InputDown) || (LinkLastY > (LinkY & 0x1FFF8))) { LinkPlatform = PLATFORM_AIR; // Accelerate from gravity. LinkVy += GRAV; if (LinkVy >= TERM) { LinkVy = TERM;}} // Otherwise stop. else { LinkPlatform = PLATFORM_SOFT; LinkVy = 0; // Round down to nearest multiple of 8. LinkY &= 0x1FFF8;}} // We're in freefall. else { LinkPlatform = PLATFORM_AIR; // Accelerate from gravity. LinkVy += GRAV; if (LinkVy >= TERM) { LinkVy = TERM;}}} // Check for stairs. if (LinkStair == STAIR_NONE && (LinkPlatform == PLATFORM_HARD || LinkPlatform == PLATFORM_SOFT)) { // Up-going stairs. if (movedLeft && Link->InputUp) { int loc = ComboAt(LinkX, LinkY + 15); int ct = Screen->ComboT[loc]; if (ct == CT_CROSS || ct == CT_POS) { LinkStair = STAIR_POS; StairLocate(loc);}} else if (movedRight && Link->InputUp) { int loc = ComboAt(LinkX + 15, LinkY + 15); int ct = Screen->ComboT[loc]; if (ct == CT_CROSS || ct == CT_NEG) { LinkStair = STAIR_NEG; StairLocate(loc);}} // Down-going stairs. if (movedLeft && Link->InputDown) { int loc = ComboAt(LinkX, LinkY + 18); int ct = Screen->ComboT[loc]; if (ct == CT_CROSS || ct == CT_NEG) { LinkStair = STAIR_NEG; StairLocate(loc);}} else if (movedRight && Link->InputDown) { int loc = ComboAt(LinkX + 15, LinkY + 18); int ct = Screen->ComboT[loc]; if (ct == CT_CROSS || ct == CT_POS) { LinkStair = STAIR_POS; StairLocate(loc);}}}} // Jump if Link has been set to. void ActionJump() { if ((LinkPlatform != PLATFORM_AIR || LinkStair != STAIR_NONE) && Link->PressL && (Link->Action == LA_NONE || Link->Action == LA_WALKING)) { LinkStair = STAIR_NONE; if (Link->InputDown) { LinkVy = LOW_JUMP;} else { LinkVy = JUMP;} Game->PlaySound(SFX_JUMP);}} // Clear inputs that we don't want the engine to use. void InputsUpdate() { LastInputUp = Link->InputUp; LastInputDown = Link->InputDown; LastInputLeft = Link->InputLeft; LastInputRight = Link->InputRight; Link->InputL = false; Link->PressL = false; if (Link->Action == LA_FROZEN) {return;} // Cancel left/right movement for drawing if there is up/down movement. if (Link->InputUp || Link->InputDown) { Link->InputLeft = false; Link->PressLeft = false; Link->InputRight = false; Link->PressRight = false;} //Link->InputUp = false; //Link->InputDown = false; } void StairLocate(int start) { LinkStairY = ComboY(start) - 16 - ComboX(start) * LinkStair; //// Right edge. int x = start % 16; int y = start >> 4; int ct = Screen->ComboT[start]; while (x <= 15 && y >= 0 && y <= 10 && (ct == CT_CROSS || ct == CT_CROSS + LinkStair)) { x++; y += LinkStair; ct = Screen->ComboT[y * 16 + x];} LinkStairRight = (x + Cond(LinkStair == STAIR_POS, 0, -1)) * 16; // We fell off the edge of the screen, so let Link keep going. if (ct == CT_CROSS || ct == CT_CROSS + LinkStair) { LinkStairRight += 16;} //// Left edge. x = start % 16; y = start >> 4; ct = Screen->ComboT[start]; while (x >= 0 && y >= 0 && y <= 10 && (ct == CT_CROSS || ct == CT_CROSS + LinkStair)) { x--; y -= LinkStair; ct = Screen->ComboT[y * 16 + x];} LinkStairLeft = (x + Cond(LinkStair == STAIR_POS, 1, 0)) * 16; // We fell off the edge of the screen, so let Link keep going. if (ct == CT_CROSS || ct == CT_CROSS + LinkStair) { LinkStairLeft -= 16;} LinkStairRecalc = false;} // Clear inputs that we don't want the engine to use. void InputsUpdate2() { if (Link->Action == LA_FROZEN) {return;} Link->InputLeft = false; Link->InputRight = false;} void EnemyUpdate() { for (int i = 1; i <= Screen->NumNPCs(); i++) { EnemyUpdate(Screen->LoadNPC(i));}} void EnemyUpdate(npc enemy) { enemy->Z = 0; // Don't let them go up or down. if (enemy->Dir == DIR_UP || enemy->Dir == DIR_DOWN) { int dir = DIR_LEFT + Rand(2); if (CanWalk(enemy->X, enemy->Y, dir, Max(1, enemy->Step), true)) { enemy->Dir = dir;} else { dir ^= 1; if (CanWalk(enemy->X, enemy->Y, dir, Max(1, enemy->Step), true)) { enemy->Dir = dir;}}} // Check if we hit our head on something. if (enemy->Misc[MISC_NPC_VY] < 0) { int overLeft = PlatformType(enemy->X + 5, enemy->Y); int overRight = PlatformType(enemy->Y + 11, enemy->Y); // We ran into a hard block. if (overLeft == PLATFORM_HARD || overRight == PLATFORM_HARD) { enemy->Misc[MISC_NPC_VY] = 0; // Move to the next multiple of 8. enemy->Y += 8 - (enemy->Y % 8);}} // See if we're standing on a platform. int underLeft = PlatformType(enemy->X + 5, enemy->Y + 16); int underRight = PlatformType(enemy->Y + 11, enemy->Y + 16); int platform; // We're on a hard platform. if (enemy->Misc[MISC_NPC_VY] >= 0 && (underLeft == PLATFORM_HARD || underRight == PLATFORM_HARD)) { platform = PLATFORM_HARD; enemy->Misc[MISC_NPC_VY] = 0; // Round down to nearest multiple of 8. enemy->Y &= 0x1FFF8;} // We're on a soft platform. else if (enemy->Misc[MISC_NPC_VY] >= 0 && (underLeft == PLATFORM_SOFT || underRight == PLATFORM_SOFT)) { platform = PLATFORM_SOFT; enemy->Misc[MISC_NPC_VY] = 0; // Round down to nearest multiple of 8. enemy->Y &= 0x1FFF8;} // We're in freefall. else { platform = PLATFORM_AIR; enemy->Misc[MISC_NPC_VY] += GRAV;} // Move by velocity. enemy->Y += enemy->Misc[MISC_NPC_VY];} // Test for changes in screen. // Last noticed DMap. int LastDMap = -1; // Last noticed DScreen. int LastDScreen = -1; // If the screen has changed. bool ScreenChanged = false; // If the dmap has changed. bool DMapChanged = false; void ScreenChange_Update() { int dMap = Game->GetCurDMap(); int dScreen = Game->GetCurDMapScreen(); DMapChanged = dMap != LastDMap; ScreenChanged = DMapChanged || dScreen != LastDScreen; LastDMap = dMap; LastDScreen = dScreen;}
Here's another update. This should fix all the weird Link teleporting bugs and such.
For the crossing stairs in the first area, you set them up wrong. Those staircases just pass right by each other, so you don't actually need to have the 'cross' type stair in there. If they were one tile further apart, they'd meet in the middle - that's when you use the cross type.
Having stairs span multiple screens is harder than I thought it would be. Apparently you can't manipulate Link at all while the screen is scrolling, so at the very least you couldn't have him smoothly walking up the steps while the screen is changing.
And the quest looks really nice, by the way.