const int SPR_LINKCRUSH = 88; //Sprite of Link being crushed
const int SFX_LINKCRUSH = 11; //Sound that plays when Link gets crushed
const int SFX_ENEMYCRUSH = 11; //Sound that plays when an enemy gets crushed
const int DAMAGE_LINKCRUSH = 8; //Damage taken from being crushed
const int DELAY_CRUSH = 40; //Delay before Link respawns after being crushed
const int DELAYMAX_CRUSH = 300; //Max delay before Link respawns
const int SOLIDOBJ_MAX = 32; //Maximum number of solid objects
const int SOLIDOBJ_CRUSH_BEHAVIOR = 1; //How solid objects behave with crushing Link (0=No interaction, 1=Return to screen entrance, 2=Instant death)
const int SOLIDOBJ_CRUSH_REPOSITION = 1; //If crushing Link should move him to the room entrance
const int SOLIDOBJ_COMPLEX_CRUSH_ANIM = 1; //If crushing animation should have 6 animations or just 2
const int SOLIDOBJ_PUSH_NPC = 0; //Whether or not to push NPCs
const int SOLIDOBJ_CRUSH_SAFETY = 4; //Safety pixels before being crushed
const int SOLIDOBJ_LINKXTRIM = 3; //Amount trimmed from the sides of Link's hitbox
const int SOLIDOBJ_LINKYTRIM = 0; //Amount trimmed from the top of Link's hitbox in sideview
//Internal constants - Please don't change these
const int __SOLIDOBJ_COUNT = 0; //Array index keeping track of the number of solid objects
const int __SOLIDOBJ_STARTLINKX = 1; //Link's starting X position
const int __SOLIDOBJ_STARTLINKY = 2; //Link's starting Y position
const int __SOLIDOBJ_CRUSHCOUNTER = 3; //Array index for the timer keeping track of Link getting crushed
const int __SOLIDOBJ_LASTDMAP = 4; //Last DMap visited
const int __SOLIDOBJ_LASTSCREEN = 5; //Last screen visited
const int __SOLIDOBJ_ONPLATFORM = 6; //The current platform Link is on
const int __SOLIDOBJ_FORCERESPAWNCOUNTER = 7; //How many frames until Link is force to respawn, regardless of if a block is covering him
const int __SOLIDOBJ_FORCELINKX = 8; //X position where Link got crushed
const int __SOLIDOBJ_FORCELINKY = 9; //Y position where Link got crushed
const int __SOLIDOBJ_LINKLASTX = 10; //Link's last X position
const int __SOLIDOBJ_LINKLASTY = 11; //Link's last Y position
const int __SOLIDOBJ_STARTINDEX = 12; //Starting index for objects in the array
const int __SOLIDOBJ_NUMATTRIBINDEX = 8; //Number of indices in an object
//Array indices (__SOLIDOBJ_STARTINDEX+__SOLIDOBJ_NUMATTRIBINDEX*y+x) for various attributes of each object
//where y is the object ID and x is the property
const int __SOLIDOBJ_OBJ_X = 0;
const int __SOLIDOBJ_OBJ_Y = 1;
const int __SOLIDOBJ_OBJ_WIDTH = 2;
const int __SOLIDOBJ_OBJ_HEIGHT = 3;
const int __SOLIDOBJ_OBJ_VX = 4;
const int __SOLIDOBJ_OBJ_VY = 5;
const int __SOLIDOBJ_OBJ_ID = 6;
const int __SOLIDOBJ_OBJ_FLAGS = 7;
const int SFFCF_TOPONLY = 00000001b; //Only the top face of the FFC is solid
const int SFFCF_PUSHNPC = 00000010b; //Can push NPCs
int SolidObjects[268]; //Buffer for the solid object. Size should be 12+SOLIDOBJ_MAX*8
void SolidObjects_Add(int ID, int x, int y, int width, int height, int vX, int vY, int flags){
int i = __SOLIDOBJ_STARTINDEX+SolidObjects[__SOLIDOBJ_COUNT]*__SOLIDOBJ_NUMATTRIBINDEX; //Get the starting index of the object
//Set all the object's attributes
SolidObjects[i+__SOLIDOBJ_OBJ_X] = x;
SolidObjects[i+__SOLIDOBJ_OBJ_Y] = y;
SolidObjects[i+__SOLIDOBJ_OBJ_WIDTH] = width;
SolidObjects[i+__SOLIDOBJ_OBJ_HEIGHT] = height;
SolidObjects[i+__SOLIDOBJ_OBJ_VX] = vX;
SolidObjects[i+__SOLIDOBJ_OBJ_VY] = vY;
SolidObjects[i+__SOLIDOBJ_OBJ_ID] = ID;
SolidObjects[i+__SOLIDOBJ_OBJ_FLAGS] = flags;
//Increment the count so the script knows where to add the next object
SolidObjects[__SOLIDOBJ_COUNT] = Min(SolidObjects[__SOLIDOBJ_COUNT]+1, SOLIDOBJ_MAX);
}
void SolidObjects_Init(){
//Reset global variables to their default states
SolidObjects[__SOLIDOBJ_COUNT] = 0;
SolidObjects[__SOLIDOBJ_STARTLINKX] = Link->X;
SolidObjects[__SOLIDOBJ_STARTLINKY] = Link->Y;
SolidObjects[__SOLIDOBJ_CRUSHCOUNTER] = 0;
SolidObjects[__SOLIDOBJ_LASTDMAP] = Game->GetCurDMap();
SolidObjects[__SOLIDOBJ_LASTSCREEN] = Game->GetCurScreen();
SolidObjects[__SOLIDOBJ_ONPLATFORM] = 0;
}
void SolidObjects_Update1(){
if(Link->Action != LA_SCROLLING){
//If Link is currently being crushed
if(SolidObjects[__SOLIDOBJ_CRUSHCOUNTER]>0){
NoAction();
Link->Jump = 0;
SolidObjects[__SOLIDOBJ_CRUSHCOUNTER]--;
Link->CollDetection = false;
Link->Invisible = true;
Link->X = SolidObjects[__SOLIDOBJ_FORCELINKX];
Link->Y = SolidObjects[__SOLIDOBJ_FORCELINKY];
//When the counter hits 0
if(SolidObjects[__SOLIDOBJ_CRUSHCOUNTER]==0){
if(SOLIDOBJ_CRUSH_REPOSITION){
Link->X = SolidObjects[__SOLIDOBJ_STARTLINKX];
Link->Y = SolidObjects[__SOLIDOBJ_STARTLINKY];
}
//If Link isn't colliding with a solid object
if(!SolidObjects_CollideWithLink(Link->X, Link->Y)||SolidObjects[__SOLIDOBJ_FORCERESPAWNCOUNTER]>=DELAYMAX_CRUSH){
SolidObjects[__SOLIDOBJ_FORCERESPAWNCOUNTER] = 0;
Link->CollDetection = true;
Link->Invisible = false;
Link->HP -= DAMAGE_LINKCRUSH;
Link->Action = LA_GOTHURTLAND;
Link->HitDir = -1;
Game->PlaySound(SFX_OUCH);
}
//Otherwise raise the crush counter so it checks again next frame
else{
SolidObjects[__SOLIDOBJ_CRUSHCOUNTER] = 1;
SolidObjects[__SOLIDOBJ_FORCERESPAWNCOUNTER]++;
}
}
}
}
}
void SolidObjects_Update2(){
if(Link->Action != LA_SCROLLING){
//If Link has moved to a different DMap or screen, update the starting position
if(SolidObjects[__SOLIDOBJ_LASTDMAP]!=Game->GetCurDMap()||SolidObjects[__SOLIDOBJ_LASTSCREEN]!=Game->GetCurScreen()){
SolidObjects[__SOLIDOBJ_LASTDMAP] = Game->GetCurDMap();
SolidObjects[__SOLIDOBJ_LASTSCREEN] = Game->GetCurScreen();
SolidObjects[__SOLIDOBJ_STARTLINKX] = Link->X;
SolidObjects[__SOLIDOBJ_STARTLINKY] = Link->Y;
}
//Only move Link around if he's not currently being crushed
if(SolidObjects[__SOLIDOBJ_CRUSHCOUNTER]==0)
SolidObjects_UpdateLink();
//SFX: Demonic chanting as enemies twitch around awkwardly
if(SOLIDOBJ_PUSH_NPC){
for(int i=Screen->NumNPCs(); i>=1; i--){
npc n = Screen->LoadNPC(i);
if(!SolidObjects_CanPushEnemy(n))
continue;
SolidObjects_UpdateEnemy(n);
}
}
//Clear solid object slots to be set by scripts again the next frame
SolidObjects_ClearObjects();
SolidObjects[__SOLIDOBJ_LINKLASTX] = Link->X;
SolidObjects[__SOLIDOBJ_LINKLASTY] = Link->Y;
}
}
//Returns true if an object being pushed around can move in a direction
bool SolidObjects_CanWalk(int x, int y, int dir, int width, int height, int xoff, int yoff, bool noEdge) {
int i; int xx; int yy;
bool offscreen;
if(dir==DIR_UP||dir==DIR_DOWN){
for(i=0; i<=width-1; i=Min(i+8, width-1)){
xx = x+xoff+i;
if(dir==DIR_UP)
yy = y+yoff-1;
else
yy = y+yoff+height;
if(xx<0||xx>255||yy<0||yy>175){
if(noEdge)
offscreen = true;
else
return false;
}
if(Screen->isSolid(xx, yy)&&!offscreen)
return false;
if(i==width-1)
break;
}
return true;
}
else if(dir==DIR_LEFT||dir==DIR_RIGHT){
for(i=0; i<=height-1; i=Min(i+8, height-1)){
yy = y+yoff+i;
if(dir==DIR_LEFT)
xx = x+xoff-1;
else
xx = x+xoff+width;
if(xx<0||xx>255||yy<0||yy>175){
if(noEdge)
offscreen = true;
else
return false;
}
if(Screen->isSolid(xx, yy)&&!offscreen)
return false;
if(i==height-1)
break;
}
return true;
}
return false;
}
void SolidObjects_UpdateLink(){
int totalUp;
int totalDown;
int totalLeft;
int totalRight;
int tempVx;
int tempVy;
int x; int y; int width; int height; int vX; int vY; int flags;
int linkX = Link->X+SOLIDOBJ_LINKXTRIM;
int linkY = Link->Y+8;
int linkWidth = 16-SOLIDOBJ_LINKXTRIM*2;
int linkHeight = 8;
int platID; //A unique number given to solid objects treated as platforms, to keep track of them between frames
int platformCandidate = -1; //The platform under Link as found when he steps on it for the first time
int platformIndex = -1; //The platform under Link as found by its ID
int platformY;
int platformPushX;
int platformPushY;
bool crushDir[4];
//Link's hitbox is different in sideview
if(IsSideview()){
linkX = Link->X+SOLIDOBJ_LINKXTRIM;
linkY = Link->Y+SOLIDOBJ_LINKYTRIM;
linkWidth = 16-SOLIDOBJ_LINKXTRIM*2;
linkHeight = 16-SOLIDOBJ_LINKYTRIM;
}
for(int i=0; i<SOLIDOBJ_MAX; i++){ //Cycle through all possible objects
x = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_X];
y = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_Y];
width = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_WIDTH];
height = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_HEIGHT];
platID = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_ID];
vX = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_VX];
vY = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_VY];
flags = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_FLAGS];
if(width>0){ //Check if there's a solid object at the current index
if(IsSideview()){
//If Link is standing on the same platform as his current platform ID
//Update platform index to that object
if(SolidObjects[__SOLIDOBJ_ONPLATFORM]==platID&&platID>0){
platformIndex = i;
}
}
if(RectCollision(x, y, x+width-1, y+height-1, linkX, linkY, linkX+linkWidth-1, linkY+linkHeight-1)){
//Find Link and the object's center points
int cx1 = x+width/2;
int cy1 = y+height/2;
int cx2 = linkX+linkWidth/2;
int cy2 = linkY+linkHeight/2;
if(cy2<cy1){ //Link is above the object
tempVy = cy1-cy2-(height+linkHeight)/2;
if(vY<0)
crushDir[DIR_UP] = true;
}
else{ //Link is below the object
tempVy = cy1-cy2+(height+linkHeight)/2;
if(vY>0)
crushDir[DIR_DOWN] = true;
}
if(cx2<cx1){ //Link is left of the object
tempVx = cx1-cx2-(width+linkWidth)/2;
if(vX<0)
crushDir[DIR_LEFT] = true;
}
else{
tempVx = cx1-cx2+(width+linkWidth)/2;
if(vX>0)
crushDir[DIR_RIGHT] = true;
}
//Prevent jump-through platforms pushing in any direction but up
if(flags&SFFCF_TOPONLY){
tempVx = 1000; //We're setting Vx/Vy to 1000 to cancel them out from calculations
if(tempVy>0)
tempVy = 1000;
if(Link->Y<SolidObjects[__SOLIDOBJ_LINKLASTY])
tempVy = 1000;
}
//If both vectors are cancelled, set them to 0
if(tempVx==1000&&tempVy==1000){
tempVx = 0;
tempVy = 0;
}
if(Abs(tempVy)<Abs(tempVx)){ //Find out which push would take less effort
if(tempVy<0){ //If it's an upwards push
if(totalUp>tempVy){ //If it's larger than the current max
totalUp = tempVy; //Update the current max
//If the platform is going up and has an ID >0, update platformCandidate
if(IsSideview()&&platID>0){
platformCandidate = i;
}
}
}
else if(tempVy>0){ //If it's a downwards push
if(totalDown<tempVy) //If it's larger than the current max
totalDown = tempVy; //Update the current max
}
}
else{
if(tempVx<0){ //If it's a left push
if(totalLeft>tempVx) //If it's larger than the current max
totalLeft = tempVx; //Update the current max
}
else if(tempVx>0){ //If it's a right push
if(totalRight<tempVx) //If it's larger than the current max
totalRight = tempVx; //Update the current max
}
}
}
//Detect collision with FFCs while Link is standing on the ground next to them
else if(IsSideview()&&!SolidObjects_CanWalk(Link->X, Link->Y, DIR_DOWN, 8, 16, 4, 0, true)){
if(RectCollision(x, y, x+width-1, y+height-1, linkX, linkY, linkX+linkWidth-1, linkY+linkHeight-1+2)){
if(platID>0)
platformCandidate = i;
}
}
}
}
if(IsSideview()){
//If Link isn't on a platform yet
if(SolidObjects[__SOLIDOBJ_ONPLATFORM]==0){
//If there's a candidate available and he's going down,
//set his current platform to that platform's ID
if(platformCandidate>-1&&Link->Jump<=0){
SolidObjects[__SOLIDOBJ_ONPLATFORM] = SolidObjects[__SOLIDOBJ_STARTINDEX+platformCandidate*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_ID];
platformY = SolidObjects[__SOLIDOBJ_STARTINDEX+platformCandidate*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_Y]-16;
Link->Jump = 0;
}
}
else{
//If the platform Link is on has been found
if(platformIndex>-1){
if(platformCandidate>-1){
//If there's a platform candidate and it has a higher Y velocity
if(SolidObjects[__SOLIDOBJ_STARTINDEX+platformCandidate*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_VY]<SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_VY]){
platformIndex = platformCandidate;
SolidObjects[__SOLIDOBJ_ONPLATFORM] = SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_ID];
Link->Jump = 0;
}
}
x = SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_X];
y = SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_Y];
width = SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_WIDTH];
height = SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_HEIGHT];
platformY = y-16;
platformPushX = SolidObjects[__SOLIDOBJ_STARTINDEX+platformIndex*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_VX];
platformPushY = platformY-Link->Y; //Link's Y push is based on the difference between Link's position and the position above the platform
Link->Jump = 0;
//If Link isn't touching the platform, detach him from it
if(!RectCollision(x, y, x+width-1, y+height-1, linkX+platformPushX, linkY+platformPushY, linkX+linkWidth-1+platformPushX, linkY+linkHeight-1+4+platformPushY)){
SolidObjects[__SOLIDOBJ_ONPLATFORM] = 0;
}
}
//If the platform Link is on doesn't exist, detach him
else{
SolidObjects[__SOLIDOBJ_ONPLATFORM] = 0;
}
}
}
//Debug draws
// Screen->DrawInteger(6, 8, 8, FONT_Z1, 0x01, 0x0F, -1, -1, totalUp, 0, 128);
// Screen->DrawInteger(6, 8, 16, FONT_Z1, 0x01, 0x0F, -1, -1, totalDown, 0, 128);
// Screen->DrawInteger(6, 8, 24, FONT_Z1, 0x01, 0x0F, -1, -1, totalLeft, 0, 128);
// Screen->DrawInteger(6, 8, 32, FONT_Z1, 0x01, 0x0F, -1, -1, totalRight, 0, 128);
//Get which directions Link can go in
//This is based on the speed of Link being pushed out of the platform as well as its current velocity
bool canGoUp = (totalDown==0)||!crushDir[DIR_DOWN];
bool canGoDown = (totalUp==0)||!crushDir[DIR_UP];
bool canGoLeft = (totalRight==0)||!crushDir[DIR_RIGHT];
bool canGoRight = (totalLeft==0)||!crushDir[DIR_LEFT];
//Add rough offsets to cancel out Link's movement
int cancelMoveX = -LinkMovement[LM_STICKX];
int cancelMoveY = -LinkMovement[LM_STICKY];
if(IsSideview())
cancelMoveY = 0;
//Update the valid directions based on screen solidity in the way
if(IsSideview()){ //Link's hitbox is different in sideview
if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_UP, 8, 16, 4, 0, true))
canGoUp = false;
if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_DOWN, 8, 16, 4, 0, true)){
canGoDown = false;
//Detach Link from a platform if he's on the ground and not directly above the platform
if(Abs(platformY-Link->Y)>2)
SolidObjects[__SOLIDOBJ_ONPLATFORM] = 0;
}
if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_LEFT, 16, 16, 0, 0, true))
canGoLeft = false;
if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_RIGHT, 16, 16, 0, 0, true))
canGoRight = false;
}
else{
if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_UP, 16, 8, 0, 8, true))
canGoUp = false;
if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_DOWN, 16, 8, 0, 8, true))
canGoDown = false;
if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_LEFT, 16, 8, 0, 8, true))
canGoLeft = false;
if(!SolidObjects_CanWalk(Link->X+cancelMoveX, Link->Y+cancelMoveY, DIR_RIGHT, 16, 8, 0, 8, true))
canGoRight = false;
}
//Set Link's jump to 0 if he's jumping up into a ceiling
if(Link->Jump>0&&totalDown>0&&IsSideview())
Link->Jump = 0;
int crush = -1;
if(SOLIDOBJ_CRUSH_BEHAVIOR){
int crushStrengthX = Max(Abs(totalLeft), Abs(totalRight));
int crushStrengthY = Max(Abs(totalUp), Abs(totalDown));
//Detect if Link is between two walls
if(!canGoUp&&!canGoDown){
//Detect if he's far enough in to be crushed
if(crushStrengthY>=SOLIDOBJ_CRUSH_SAFETY){
crush = 4;
//Set crush direction if Link is pushed against a wall
if(Abs(totalUp)>0&&totalDown==0)
crush = DIR_UP;
else if(Abs(totalDown)>0&&totalUp==0)
crush = DIR_DOWN;
}
}
if(!canGoLeft&&!canGoRight){
if(crushStrengthX>=SOLIDOBJ_CRUSH_SAFETY){
//If he's being more crushed vertically than horizontally, prioritize that
if((crush==DIR_UP||crush==DIR_DOWN||crush==4)&&crushStrengthY>crushStrengthX){
crush = 4;
if(Abs(totalUp)>0&&totalDown==0)
crush = DIR_UP;
else if(Abs(totalDown)>0&&totalUp==0)
crush = DIR_DOWN;
}
else{
crush = 5;
if(Abs(totalLeft)>0&&totalRight==0)
crush = DIR_LEFT;
else if(Abs(totalRight)>0&&totalLeft==0)
crush = DIR_RIGHT;
}
}
}
}
//If Link is being crushed
if(crush>-1&&Link->CollDetection){
if(SOLIDOBJ_CRUSH_BEHAVIOR==2){ //Instant death crush
Link->HP = 0;
}
else{ //Teleport crush
SolidObjects[__SOLIDOBJ_FORCELINKX] = Link->X;
SolidObjects[__SOLIDOBJ_FORCELINKY] = Link->Y;
lweapon lcrush = CreateLWeaponAt(LW_SPARKLE, Link->X, Link->Y);
lcrush->UseSprite(SPR_LINKCRUSH);
lcrush->DeadState = Max(lcrush->ASpeed*lcrush->NumFrames-1, 1);
if(SOLIDOBJ_COMPLEX_CRUSH_ANIM){
lcrush->OriginalTile += crush*20;
lcrush->Tile = lcrush->OriginalTile;
}
else{
if(crush==DIR_LEFT||crush==DIR_RIGHT||crush==5){ //Offset tiles based on direction of the crushing
lcrush->OriginalTile += 20;
lcrush->Tile = lcrush->OriginalTile;
}
}
Link->Invisible = true;
Link->CollDetection = false;
SolidObjects[__SOLIDOBJ_CRUSHCOUNTER] = lcrush->DeadState+DELAY_CRUSH;
Game->PlaySound(SFX_LINKCRUSH);
SolidObjects[__SOLIDOBJ_ONPLATFORM] = 0;
}
}
else{ //Otherwise, move him around
if(IsSideview()&&(totalUp+totalDown)<0&&SolidObjects[__SOLIDOBJ_ONPLATFORM]>0)
Link->Jump = 0;
SolidObjects_SafePush2NoEdge(totalLeft+totalRight+platformPushX, totalUp+totalDown+platformPushY); //Move Link by the combination of strongest inputs. Opposing Left/Right, Up/Down should cancel out
}
}
//This function prevents the script from overfilling the push counter
void SolidObjects_SafePush2NoEdge(int vX, int vY){
if(Abs(LinkMovement[LM_PUSHX2B])>=1)
vX = 0;
if(Abs(LinkMovement[LM_PUSHY2B])>=1)
vY = 0;
LinkMovement_Push2NoEdge(vX, vY);
}
bool SolidObjects_CollideWithLink(int checkX, int checkY){
int x; int y; int width; int height; int vX; int vY;
int linkX = checkX+SOLIDOBJ_LINKXTRIM;
int linkY = checkY+8;
int linkWidth = 16-SOLIDOBJ_LINKXTRIM*2;
int linkHeight = 8;
if(IsSideview()){
linkX = Link->X+SOLIDOBJ_LINKXTRIM;
linkY = Link->Y+SOLIDOBJ_LINKYTRIM;
linkWidth = 16-SOLIDOBJ_LINKXTRIM*2;
linkHeight = 16-SOLIDOBJ_LINKYTRIM;
}
for(int i=0; i<SOLIDOBJ_MAX; i++){ //Cycle through all possible objects
x = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_X];
y = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_Y];
width = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_WIDTH];
height = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_HEIGHT];
//If one of them collides with Link, return true
if(RectCollision(x, y, x+width-1, y+height-1, linkX, linkY, linkX+linkWidth-1, linkY+linkHeight-1)){
return true;
}
}
return false;
}
void SolidObjects_UpdateEnemy(npc n){
int totalUp;
int totalDown;
int totalLeft;
int totalRight;
int tempVx;
int tempVy;
int x; int y; int width; int height;
int nX = n->X+n->HitXOffset;
int nY = n->Y+n->HitYOffset;
int nWidth = n->HitWidth;
int nHeight = n->HitHeight;
for(int i=0; i<SOLIDOBJ_MAX; i++){ //Cycle through all possible objects
x = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_X];
y = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_Y];
width = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_WIDTH];
height = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_HEIGHT];
int flags = SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_FLAGS];
if(width>0&&flags&SFFCF_PUSHNPC){ //Check if there's a solid object at the current index
//Debug hitbox draw
//Screen->Rectangle(2, x, y, x+width-1, y+height-1, 0x01, 1, 0, 0, 0, true, 64);
if(RectCollision(x, y, x+width-1, y+height-1, nX, nY, nX+nWidth-1, nY+nHeight-1)){
//Find NPC and the object's center points
int cx1 = x+width/2;
int cy1 = y+height/2;
int cx2 = nX+nWidth/2;
int cy2 = nY+nHeight/2;
if(cy2<cy1){ //NPC is above the object
tempVy = cy1-cy2-(height+nHeight)/2;
}
else{ //NPC is below the object
tempVy = cy1-cy2+(height+nHeight)/2;
}
if(cx2<cx1){ //NPC is left of the object
tempVx = cx1-cx2-(width+nWidth)/2;
}
else{
tempVx = cx1-cx2+(width+nWidth)/2;
}
//Prevent jump-through platforms pushing in any direction but up
if(flags&SFFCF_TOPONLY){
tempVx = 1000; //We're setting Vx/Vy to 1000 to cancel them out from calculations
if(tempVy>0)
tempVy = 1000;
}
//If both vectors are cancelled, set them to 0
if(tempVx==1000&&tempVy==1000){
tempVx = 0;
tempVy = 0;
}
if(Abs(tempVy)<Abs(tempVx)){ //Find out which push would take less effort
if(tempVy<0){ //If it's an upwards push
if(totalUp>tempVy) //If it's larger than the current max
totalUp = tempVy; //Update the current max
}
else if(tempVy>0){ //If it's a downwards push
if(totalDown<tempVy) //If it's larger than the current max
totalDown = tempVy; //Update the current max
}
}
else{
if(tempVx<0){ //If it's a left push
if(totalLeft>tempVx) //If it's larger than the current max
totalLeft = tempVx; //Update the current max
}
else if(tempVx>0){ //If it's a right push
if(totalRight<tempVx) //If it's larger than the current max
totalRight = tempVx; //Update the current max
}
}
}
}
}
//Debug draws
// Screen->DrawInteger(6, 8, 8, FONT_Z1, 0x01, 0x0F, -1, -1, totalUp, 0, 128);
// Screen->DrawInteger(6, 8, 16, FONT_Z1, 0x01, 0x0F, -1, -1, totalDown, 0, 128);
// Screen->DrawInteger(6, 8, 24, FONT_Z1, 0x01, 0x0F, -1, -1, totalLeft, 0, 128);
// Screen->DrawInteger(6, 8, 32, FONT_Z1, 0x01, 0x0F, -1, -1, totalRight, 0, 128);
bool canGoUp = (totalDown==0);
bool canGoDown = (totalUp==0);
bool canGoLeft = (totalRight==0);
bool canGoRight = (totalLeft==0);
if(!SolidObjects_CanWalk(n->X, n->Y, DIR_UP, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
canGoUp = false;
if(!SolidObjects_CanWalk(n->X, n->Y, DIR_DOWN, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
canGoDown = false;
if(!SolidObjects_CanWalk(n->X, n->Y, DIR_LEFT, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
canGoLeft = false;
if(!SolidObjects_CanWalk(n->X, n->Y, DIR_RIGHT, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
canGoRight = false;
int crush = 0;
if(SOLIDOBJ_CRUSH_BEHAVIOR){
int crushStrengthX = Max(Abs(totalLeft), Abs(totalRight));
int crushStrengthY = Max(Abs(totalUp), Abs(totalDown));
//Detect if NPC is between two walls
if(!canGoUp&&!canGoDown){
//Detect if it's far enough in to be crushed
if(crushStrengthY>=SOLIDOBJ_CRUSH_SAFETY){
crush = 1;
}
}
if(!canGoLeft&&!canGoRight){
if(crushStrengthX>=SOLIDOBJ_CRUSH_SAFETY){
crush = 1;
}
}
}
if(crush&&n->CollDetection){
n->HP = 0;
Game->PlaySound(SFX_ENEMYCRUSH);
}
else
SolidObjects_PushEnemy(n, totalLeft+totalRight, totalUp+totalDown);//Move NPC by the combination of strongest inputs. Opposing Left/Right, Up/Down should cancel out
}
void SolidObjects_PushEnemy(npc n, int pushX, int pushY){
pushX = Round(pushX);
pushY = Round(pushY);
//Until both pushX and pushY are drained down to 0, continue to push the enemy
while(pushX!=0||pushY!=0){
if(pushX<0){
//But not through solid objects
if(SolidObjects_CanWalk(n->X, n->Y, DIR_LEFT, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
SetEnemyProperty(n, ENPROP_X, GetEnemyProperty(n, ENPROP_X)-1);
pushX++;
}
else if(pushX>0){
if(SolidObjects_CanWalk(n->X, n->Y, DIR_RIGHT, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
SetEnemyProperty(n, ENPROP_X, GetEnemyProperty(n, ENPROP_X)+1);
pushX--;
}
if(pushY<0){
if(SolidObjects_CanWalk(n->X, n->Y, DIR_UP, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
SetEnemyProperty(n, ENPROP_Y, GetEnemyProperty(n, ENPROP_Y)-1);
pushY++;
}
else if(pushY>0){
if(SolidObjects_CanWalk(n->X, n->Y, DIR_DOWN, n->HitWidth, n->HitHeight, n->HitXOffset, n->HitYOffset, true))
SetEnemyProperty(n, ENPROP_Y, GetEnemyProperty(n, ENPROP_Y)+1);
pushY--;
}
}
//If the enemy gets pushed off the screen, kill it
if(GetEnemyProperty(n, ENPROP_X)<-n->HitXOffset-n->HitWidth ||
GetEnemyProperty(n, ENPROP_X)>256 ||
GetEnemyProperty(n, ENPROP_Y)<-n->HitYOffset-n->HitHeight ||
GetEnemyProperty(n, ENPROP_Y)>176 ){
SetEnemyProperty(n, ENPROP_HP, -1000);
n->ItemSet = -1000;
n->DrawYOffset = -1000;
}
}
bool SolidObjects_CanPushEnemy(npc n){
int type = n->Type;
//If the enemy is invulnerable, don't push it
if(Abs(n->HitXOffset)>=1000||Abs(n->HitYOffset)>=1000)
return false;
//If the enemy is in the air, don't push it
if(n->Z>0)
return false;
//Check if the enemy is a type that can be pushed
if(type==NPCT_WALK)
return true;
if(type==NPCT_TEKTITE)
return true;
if(type==NPCT_LEEVER)
return true;
if(type==NPCT_ZORA)
return true;
if(type==NPCT_GHINI)
return true;
if(type==NPCT_ARMOS)
return true;
if(type==NPCT_WIZZROBE)
return true;
if(type==NPCT_OTHERFLOAT)
return true;
if(type==NPCT_OTHER)
return true;
return false;
}
void SolidObjects_ClearObjects(){
for(int i=0; i<SOLIDOBJ_MAX; i++){ //Cycle through all possible objects
//Clear the properties
SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_X] = 0;
SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_Y] = 0;
SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_WIDTH] = 0;
SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_HEIGHT] = 0;
SolidObjects[__SOLIDOBJ_STARTINDEX+i*__SOLIDOBJ_NUMATTRIBINDEX+__SOLIDOBJ_OBJ_FLAGS] = 0;
}
//Reset the count
SolidObjects[__SOLIDOBJ_COUNT] = 0;
}
//D0: Width of the hitbox
//D1: Height of the hitbox
//D2: X offset from the FFC's position for the hitbox
//D3: Y offset from the FFC's position for the hitbox
//D4: Flags. Add these together to get the result:
// 1 - Only the top of the FFC is solid
// 2 - The FFC pushes enemies
//D5: FFC to link movement to
//D6: Combo ID that makes the FFC nonsolid when it switches to
ffc script Solid_FFC{
void run(int width, int height, int xoff, int yoff, int flags, int refFFC, int nonSolidCMB){
//Default width/height
if(width==0){
width = this->TileWidth*16;
height = this->TileHeight*16;
}
//Set the platform ID to this FFC's number
int ID;
for(int i=1; i<=32; i++){
ffc f = Screen->LoadFFC(i);
if(f==this)
ID = i;
}
int lastX = this->X;
int lastY = this->Y;
ffc ref;
if(refFFC>0)
ref = Screen->LoadFFC(refFFC);
while(true){
//Handle scripted FFC linking
if(refFFC>0){
if(ref->Delay==0){
this->Vx = ref->Vx;
this->Vy = ref->Vy;
}
else{
this->Vx = 0;
this->Vy = 0;
}
}
//We're using difference in position instead of the FFC's Vx and Vy
//because of float imprecision. Doing it the other way caused Link to get
//desynced from the FFC
int vX = this->X-lastX;
int vY = this->Y-lastY;
lastX = this->X;
lastY = this->Y;
if(this->Delay>0){
vX = 0;
vY = 0;
}
if(width>0&&this->Data!=nonSolidCMB){
SolidObjects_Add(ID, this->X+xoff, this->Y+yoff, width, height, vX, vY, flags);
}
Waitframe();
}
}
}
//D0: Radius to put the platform at
//D1: Starting angle for the platform
//D2: Rotation speed of the platform
//D3: Flags. Add these together to get the result:
// 1 - Only the top of the FFC is solid
// 2 - The FFC pushes enemies
ffc script Moving_Platform_Circular{
void run(int r, int ang, int rot, int flags){
//Set the platform ID to this FFC's number
int ID;
for(int i=1; i<=32; i++){
ffc f = Screen->LoadFFC(i);
if(f==this)
ID = i;
}
int startX = this->X;
int startY = this->Y;
int lastX = this->X;
int lastY = this->Y;
while(true){
int x = startX+VectorX(r, ang);
int y = startY+VectorY(r, ang);
this->X = x;
this->Y = y;
//We're using difference in position instead of the FFC's Vx and Vy
//because of float imprecision. Doing it the other way caused Link to get
//desynced from the FFC
int vX = this->X-lastX;
int vY = this->Y-lastY;
lastX = this->X;
lastY = this->Y;
ang = WrapDegrees(ang+rot);
SolidObjects_Add(ID, this->X, this->Y, this->EffectWidth, this->EffectHeight, vX, vY, flags);
Waitframe();
}
}
}
//D0: How many frames to shake for
//D1: Flags. Add these together to get the result:
// 1 - Only the top of the FFC is solid
// 2 - The FFC pushes enemies
ffc script Moving_Platform_StepActivate{
void run(int shakeFrames, int flags){
//Set the platform ID to this FFC's number
int ID;
for(int i=1; i<=32; i++){
ffc f = Screen->LoadFFC(i);
if(f==this)
ID = i;
}
//Store the FFC's starting state
int startX = this->X;
int startY = this->Y;
int savedVX = this->Vx;
int savedVY = this->Vy;
int savedAX = this->Ax;
int savedAY = this->Ay;
int savedDelay = this->Delay;
//Clear the FFC's state
this->Vx = 0;
this->Vy = 0;
this->Ax = 0;
this->Ay = 0;
this->Delay = 0;
//If __SOLIDOBJ_ONPLATFORM doesn't equal this FFC's number, Link hasn't stepped on the platform yet
while(SolidObjects[__SOLIDOBJ_ONPLATFORM]!=ID){
SolidObjects_Add(ID, this->X, this->Y, this->EffectWidth, this->EffectHeight, 0, 0, flags);
Waitframe();
}
int lastX = this->X;
int lastY = this->Y;
int vX; int vY;
//Play a shake animation for the specified number of frames before "falling"
if(shakeFrames>0){
for(int i=0; i<shakeFrames; i++){
if(i%8<2){
this->X = startX-1;
}
else if(i%8>=4&&i%8<6){
this->X = startX+1;
}
else{
this->X = startX;
}
vX = this->X-lastX;
vY = this->Y-lastY;
lastX = this->X;
lastY = this->Y;
SolidObjects_Add(ID, this->X, this->Y, this->EffectWidth, this->EffectHeight, vX, vY, flags);
Waitframe();
}
this->X = startX;
this->Y = startY;
}
//Restore the FFC to the starting state when it "falls"
this->Vx = savedVX;
this->Vy = savedVY;
this->Ax = savedAX;
this->Ay = savedAY;
this->Delay = savedDelay;
while(true){
//We're using difference in position instead of the FFC's Vx and Vy
//because of float imprecision. Doing it the other way caused Link to get
//desynced from the FFC
vX = this->X-lastX;
vY = this->Y-lastY;
lastX = this->X;
lastY = this->Y;
SolidObjects_Add(ID, this->X, this->Y, this->EffectWidth, this->EffectHeight, vX, vY, flags);
Waitframe();
}
}
}
item script FeatherAction{
void run(){
//This script makes Link able to jump with Roc's feather when on a sideview platform
if(SolidObjects[__SOLIDOBJ_ONPLATFORM]){
Game->PlaySound(SFX_JUMP);
Link->Jump = (this->Power+2)*0.8;
SolidObjects[__SOLIDOBJ_ONPLATFORM] = 0;
}
}
}
//Example global script combined with ghost and LinkMovement
global script SolidObject_Example_Combined{
void run(){
StartGhostZH();
LinkMovement_Init();
SolidObjects_Init();
while(true){
UpdateGhostZH1();
SolidObjects_Update1();
LinkMovement_Update1();
Waitdraw();
UpdateGhostZH2();
SolidObjects_Update2();
LinkMovement_Update2();
Waitframe();
}
}
}
//Example global script with just this script's functions
global script SolidObject_Example{
void run(){
SolidObjects_Init();
while(true){
SolidObjects_Update1();
Waitdraw();
SolidObjects_Update2();
Waitframe();
}
}
}