const int SCRIPTEDPUSHBLOCK_DELAY = 16; //Frames before pushing const int SCRIPTEDPUSHBLOCK_PUSHSPEED = 1.5; //Push speed of the blocks const int SCRIPTEDPUSHBLOCK_STOP_ON_TRIGGER = 1; //Set to 1 if the push blocks stop moving after covering block triggers const int SFX_SCRIPTPUSHBLOCK = 50; //Sound when a block is pushed const int CF_LARGEPUSH_TRIGGERED = 105; //This flag doesn't have a constant in std for some reason //This script is used for regular rectangular push blocks, up to 4x4 in size. //Place the FFC over the top left corner of your push block. //D0: Width of the block in tiles //D1: Height of the block in tiles //D4: Set to 1 if it's a one time push //D5: Set to to the sum of the following flags to enable pushing in certain directions, if 0 defaults to 15 (all directions) // 1 - Up // 2 - Down // 4 - Left // 8 - Right //D6: Set to affect the push level requirement of the block. 0: None, 1: Bracelet L2, 2: Bracelet L3 //D7: Set to a secret sfx for block puzzles if you're using a custom one ffc script LargePushBlock{ void run(int w, int h, int dummy1, int dummy2, int onetime, int canpushdir, int reqpushlevel, int secretSFX){ this->Flags[FFCF_ETHEREAL] = true; int i; int j; int k; int x; int y; w = Clamp(w, 1, 4); if(h==0) h = w; h = Clamp(h, 1, 4); if(canpushdir==0) canpushdir = 1111b; if(secretSFX==0) secretSFX = SFX_SECRET; int blockShape[16]; //Track undercombo stuff for the full shape of the push block int underCombo[176]; int underCSet[176]; int underFlag[176]; for(i=0; i<176; ++i){ underCombo[i] = Screen->UnderCombo; underCSet[i] = Screen->UnderCSet; underFlag[i] = 0; } //Track combo data, cset, flag, and solidity of combos that are part of the push block int subCombo[16]; int subCSet[16]; int subFlag[16]; int subSolid[16]; for(x=0; x<w; ++x){ for(y=0; y<h; ++y){ i = x+y*4; j = ComboAt(this->X+8, this->Y+8)+x+y*16; subCombo[i] = Screen->ComboD[j]; subCSet[i] = Screen->ComboC[j]; subFlag[i] = Screen->ComboF[j]; subSolid[i] = Screen->ComboS[j]; blockShape[i] = 1; } } int blockX; int blockY; int scriptedPushBlock[16]; scriptedPushBlock[0] = 0; //Push Timer scriptedPushBlock[1] = this->X; //Block X scriptedPushBlock[2] = this->Y; //Block Y scriptedPushBlock[3] = 0; //State scriptedPushBlock[4] = 0; //Dir scriptedPushBlock[5] = 16; //Substep scriptedPushBlock[6] = 0; //Can't Push scriptedPushBlock[7] = underCombo; scriptedPushBlock[8] = underCSet; scriptedPushBlock[9] = underFlag; scriptedPushBlock[10] = subCombo; scriptedPushBlock[11] = subCSet; scriptedPushBlock[12] = subFlag; scriptedPushBlock[13] = subSolid; scriptedPushBlock[14] = blockShape; while(true){ //Update the combos that make up the push block in case they are changed if(scriptedPushBlock[3]==0){ //State: Idle for(x=0; x<w; ++x){ for(y=0; y<h; ++y){ i = x+y*4; j = ComboAt(scriptedPushBlock[1]+8, scriptedPushBlock[2]+8)+x+y*16; subCombo[i] = Screen->ComboD[j]; subCSet[i] = Screen->ComboC[j]; subFlag[i] = Screen->ComboF[j]; subSolid[i] = Screen->ComboS[j]; } } } ScriptedPushBlock_Update(scriptedPushBlock, onetime, canpushdir, reqpushlevel, secretSFX); Waitframe(); } } } //This script is used for irregularly shaped push blocks, up to 4x4 in size. //Place the FFC over the top left corner of your push block shape. //D0-D3 represent the shape of the push block by row, being a number from 0000 to 1111 //For example, an O shaped block would look like // D0: 1110 // D1: 1010 // D2: 1110 // D3: 0000 //D4: Set to 1 if it's a one time push //D5: Set to to the sum of the following flags to enable pushing in certain directions, if 0 defaults to 15 (all directions) // 1 - Up // 2 - Down // 4 - Left // 8 - Right //D6: Set to affect the push level requirement of the block. 0: None, 1: Bracelet L2, 2: Bracelet L3 //D7: Set to a secret sfx for block puzzles if you're using a custom one ffc script OddlyShapedPushBlock{ void SetBlockRow(int blockShape, int row, int rowNum){ //Hi. This function is dumb. //I wrote it to quarantine these calculations from the rest of the script so I don't have to look at them. //I also did this for you, end user! blockShape[rowNum*4+0] = Floor((row%10000)/1000); blockShape[rowNum*4+1] = Floor((row%1000)/100); blockShape[rowNum*4+2] = Floor((row%100)/10); blockShape[rowNum*4+3] = (row%10); } void run(int row1, int row2, int row3, int row4, int onetime, int canpushdir, int reqpushlevel, int secretSFX){ this->Flags[FFCF_ETHEREAL] = true; int i; int j; int k; int x; int y; if(canpushdir==0) canpushdir = 1111b; if(secretSFX==0) secretSFX = SFX_SECRET; int blockShape[16]; SetBlockRow(blockShape, row1, 0); SetBlockRow(blockShape, row2, 1); SetBlockRow(blockShape, row3, 2); SetBlockRow(blockShape, row4, 3); //Track undercombo stuff for the full shape of the push block int underCombo[176]; int underCSet[176]; int underFlag[176]; for(i=0; i<176; ++i){ underCombo[i] = Screen->UnderCombo; underCSet[i] = Screen->UnderCSet; underFlag[i] = 0; } //Track combo data, cset, flag, and solidity of combos that are part of the push block int subCombo[16]; int subCSet[16]; int subFlag[16]; int subSolid[16]; for(x=0; x<4; ++x){ for(y=0; y<4; ++y){ i = x+y*4; if(blockShape[i]){ j = ComboAt(this->X+8, this->Y+8)+x+y*16; subCombo[i] = Screen->ComboD[j]; subCSet[i] = Screen->ComboC[j]; subFlag[i] = Screen->ComboF[j]; subSolid[i] = Screen->ComboS[j]; } } } int blockX; int blockY; int scriptedPushBlock[16]; scriptedPushBlock[0] = 0; //Push Timer scriptedPushBlock[1] = this->X; //Block X scriptedPushBlock[2] = this->Y; //Block Y scriptedPushBlock[3] = 0; //State scriptedPushBlock[4] = 0; //Dir scriptedPushBlock[5] = 16; //Substep scriptedPushBlock[6] = 0; //Can't Push scriptedPushBlock[7] = underCombo; scriptedPushBlock[8] = underCSet; scriptedPushBlock[9] = underFlag; scriptedPushBlock[10] = subCombo; scriptedPushBlock[11] = subCSet; scriptedPushBlock[12] = subFlag; scriptedPushBlock[13] = subSolid; scriptedPushBlock[14] = blockShape; while(true){ //Update the combos that make up the push block in case they are changed if(scriptedPushBlock[3]==0){ //State: Idle for(x=0; x<4; ++x){ for(y=0; y<4; ++y){ i = x+y*4; if(blockShape[i]){ j = ComboAt(scriptedPushBlock[1]+8, scriptedPushBlock[2]+8)+x+y*16; subCombo[i] = Screen->ComboD[j]; subCSet[i] = Screen->ComboC[j]; subFlag[i] = Screen->ComboF[j]; subSolid[i] = Screen->ComboS[j]; } } } } ScriptedPushBlock_Update(scriptedPushBlock, onetime, canpushdir, reqpushlevel, secretSFX); Waitframe(); } } } bool ScriptedPushBlock_HasPushLevel(int reqpushlevel){ if(reqpushlevel>1&&Link->Item[I_BRACELET2]) return true; if(reqpushlevel>0&&(Link->Item[I_BRACELET2]||Link->Item[I_BRACELET3])) return true; if(reqpushlevel==0) return true; return false; } void ScriptedPushBlock_Update(int scriptedPushBlock, int onetime, int canpushdir, int reqpushlevel, int secretSFX){ int i; int j; int x; int y; int underCombo = scriptedPushBlock[7]; int underCSet = scriptedPushBlock[8]; int underFlag = scriptedPushBlock[9]; int subCombo = scriptedPushBlock[10]; int subCSet = scriptedPushBlock[11]; int subFlag = scriptedPushBlock[12]; int subSolid = scriptedPushBlock[13]; int blockShape = scriptedPushBlock[14]; if(scriptedPushBlock[3]==0){ //State: Idle //If the block can be pushed, pushDir is the direction, -1 if not int pushDir = ScriptedPushBlock_DetectLinkPush(scriptedPushBlock, canpushdir, reqpushlevel); if(pushDir>-1){ if(scriptedPushBlock[4]!=pushDir) //If the pushing direction changes, reset the timer scriptedPushBlock[0] = 0; ++scriptedPushBlock[0]; //After pushing for long enough, set the state to moving if(scriptedPushBlock[0]>SCRIPTEDPUSHBLOCK_DELAY){ scriptedPushBlock[0] = 0; scriptedPushBlock[3] = 1; scriptedPushBlock[5] = 16; Game->PlaySound(SFX_SCRIPTPUSHBLOCK); //Place down undercombos for(x=0; x<4; ++x){ for(y=0; y<4; ++y){ i = x+y*4; if(blockShape[i]){ j = ComboAt(scriptedPushBlock[1]+8, scriptedPushBlock[2]+8)+x+y*16; Screen->ComboD[j] = underCombo[j]; Screen->ComboC[j] = underCSet[j]; Screen->ComboF[j] = underFlag[j]; } } } ScriptedPushBlock_DrawMovingBlock(scriptedPushBlock, scriptedPushBlock[1], scriptedPushBlock[2]); } } else{ scriptedPushBlock[0] = 0; } scriptedPushBlock[4] = pushDir; } else if(scriptedPushBlock[3]==1){ //State: Moving int pushDir = scriptedPushBlock[4]; //Calculate the distance offset for movement, ranges from 0 to 16 pixels in front scriptedPushBlock[5] -= SCRIPTEDPUSHBLOCK_PUSHSPEED; scriptedPushBlock[5] = Max(scriptedPushBlock[5], 0); int move = 16 - scriptedPushBlock[5]; //Draw the block and place solidity hitboxes in front of its position if(pushDir==DIR_UP) ScriptedPushBlock_DrawMovingBlock(scriptedPushBlock, scriptedPushBlock[1], scriptedPushBlock[2]-move); else if(pushDir==DIR_DOWN) ScriptedPushBlock_DrawMovingBlock(scriptedPushBlock, scriptedPushBlock[1], scriptedPushBlock[2]+move); else if(pushDir==DIR_LEFT) ScriptedPushBlock_DrawMovingBlock(scriptedPushBlock, scriptedPushBlock[1]-move, scriptedPushBlock[2]); else if(pushDir==DIR_RIGHT) ScriptedPushBlock_DrawMovingBlock(scriptedPushBlock, scriptedPushBlock[1]+move, scriptedPushBlock[2]); //When the block has completed movement, place the combos again if(scriptedPushBlock[5]==0){ //Offset the block's internal position by 16 if(pushDir==DIR_UP) scriptedPushBlock[2] -= 16; else if(pushDir==DIR_DOWN) scriptedPushBlock[2] += 16; else if(pushDir==DIR_LEFT) scriptedPushBlock[1] -= 16; else if(pushDir==DIR_RIGHT) scriptedPushBlock[1] += 16; //Update the undercombos and place the block bool coverAllTriggers = true; bool pushedOntoTrigger = false; for(x=0; x<4; ++x){ for(y=0; y<4; ++y){ i = x+y*4; if(blockShape[i]){ j = ComboAt(scriptedPushBlock[1]+8, scriptedPushBlock[2]+8)+x+y*16; underCombo[j] = Screen->ComboD[j]; underCSet[j] = Screen->ComboC[j]; //Don't mess with block triggered flags for undercombos if(Screen->ComboF[j]!=CF_LARGEPUSH_TRIGGERED) underFlag[j] = Screen->ComboF[j]; if(!ComboFI(j, CF_BLOCKTRIGGER)) coverAllTriggers = false; else pushedOntoTrigger = true; Screen->ComboD[j] = subCombo[i]; Screen->ComboC[j] = subCSet[i]; Screen->ComboF[j] = subFlag[i]; } } } //Set block triggered flags for communication with vanilla push blocks for(x=0; x<4; ++x){ for(y=0; y<4; ++y){ i = x+y*4; if(blockShape[i]){ j = ComboAt(scriptedPushBlock[1]+8, scriptedPushBlock[2]+8)+x+y*16; if(ComboFI(j, CF_BLOCKTRIGGER)) Screen->ComboF[j] = CF_LARGEPUSH_TRIGGERED; } } } //Stop the block from moving when overlapping a trigger or one time push if((coverAllTriggers&&SCRIPTEDPUSHBLOCK_STOP_ON_TRIGGER)||onetime) scriptedPushBlock[6] = 1; //Can't Push //Simulate triggering screen secrets for puzzles (messy and limited) if(pushedOntoTrigger&&(coverAllTriggers||!SCRIPTEDPUSHBLOCK_STOP_ON_TRIGGER)) ScriptedPushBlock_DoBlockTriggerSecret(secretSFX); scriptedPushBlock[3] = 0; scriptedPushBlock[4] = -1; } } } int ScriptedPushBlock_DetectLinkPush(int scriptedPushBlock, int canpushdir, int reqpushlevel){ int blockShape = scriptedPushBlock[14]; int blockX = Round(scriptedPushBlock[1]); int blockY = Round(scriptedPushBlock[2]); int pushDir = -1; //Check if the block was already pushed if(scriptedPushBlock[6]) return -1; //Check if Link has the required bracelets if(!ScriptedPushBlock_HasPushLevel(reqpushlevel)) return -1; //Detect Link pushing against the combos for(int i=0; i<16; ++i){ int x = blockX+(i%4)*16; int y = blockY+Floor(i/4)*16; if(blockShape[i]){ if(RectCollision(Link->X, Link->Y, Link->X+15, Link->Y+15, x-1, y-1, x+16, y+16)){ if(Link->Dir==DIR_UP){ if(Abs(Link->X-x)<16&&Link->Y>=y&&Link->InputUp&&!CanWalk(Link->X, Link->Y, DIR_UP, 1, 0)) pushDir = DIR_UP; } else if(Abs(Link->X-x)<16&&Link->Dir==DIR_DOWN){ if(Link->Y<=y-8&&Link->InputDown&&!CanWalk(Link->X, Link->Y, DIR_DOWN, 1, 0)) pushDir = DIR_DOWN; } else if(Link->Dir==DIR_LEFT){ if(Link->Y>y-16&&Link->Y<y+8&&Link->X>=x+8&&Link->InputLeft&&!CanWalk(Link->X, Link->Y, DIR_LEFT, 1, 0)) pushDir = DIR_LEFT; } else if(Link->Dir==DIR_RIGHT){ if(Link->Y>y-16&&Link->Y<y+8&&Link->X<=x-8&&Link->InputRight&&!CanWalk(Link->X, Link->Y, DIR_RIGHT, 1, 0)) pushDir = DIR_RIGHT; } } } } //Detect if the block is blocked from being pushed if(pushDir>-1){ //If trying to push in a direction flagged as illegal, stop it, get some help if(pushDir==DIR_UP&&!(canpushdir&0001b)) return -1; else if(pushDir==DIR_DOWN&&!(canpushdir&0010b)) return -1; else if(pushDir==DIR_LEFT&&!(canpushdir&0100b)) return -1; else if(pushDir==DIR_LEFT&&!(canpushdir&1000b)) return -1; //Do collisions for every combo in the set for(int i=0; i<16; ++i){ if(blockShape[i]){ int x = blockX+(i%4)*16; int y = blockY+Floor(i/4)*16; if(!ScriptedPushBlock_CanWalkSubCombo(x, y, blockShape, i, pushDir)){ return -1; } } } } return pushDir; } bool ScriptedPushBlock_IsSolid(int x, int y) { int pos = ComboAt(x, y); //Respect no push if(Screen->ComboF[pos]==CF_NOBLOCKS) return true; //Get a combination of the solidity mask on layers 0, 1, and 2, don't push blocks into combos with any solidity int comboS = Screen->ComboS[pos]; if(Screen->LayerMap(1)>0) comboS |= GetLayerComboS(1, pos); if(Screen->LayerMap(2)>0) comboS |= GetLayerComboS(2, pos); if(comboS) return true; return false; } bool ScriptedPushBlock_CanWalk(int x, int y, int dir, int step, bool full_tile) { //Yeah this one's just CanWalk int c=8; int xx = x+15; int yy = y+15; if(full_tile) c=0; if(dir==0) return !(y-step<0||ScriptedPushBlock_IsSolid(x,y+c-step)||ScriptedPushBlock_IsSolid(x+8,y+c-step)||ScriptedPushBlock_IsSolid(xx,y+c-step)); else if(dir==1) return !(yy+step>=176||ScriptedPushBlock_IsSolid(x,yy+step)||ScriptedPushBlock_IsSolid(x+8,yy+step)||ScriptedPushBlock_IsSolid(xx,yy+step)); else if(dir==2) return !(x-step<0||ScriptedPushBlock_IsSolid(x-step,y+c)||ScriptedPushBlock_IsSolid(x-step,y+c+7)||ScriptedPushBlock_IsSolid(x-step,yy)); else if(dir==3) return !(xx+step>=256||ScriptedPushBlock_IsSolid(xx+step,y+c)||ScriptedPushBlock_IsSolid(xx+step,y+c+7)||ScriptedPushBlock_IsSolid(xx+step,yy)); return false; //invalid direction } bool ScriptedPushBlock_CanWalkSubCombo(int x, int y, int blockShape, int index, int dir){ //Blocks that are adjacent to other blocks in the shape in the given direction should be ignored //If not in one of these cases, do ScriptedPushBlock_CanWalk() if(dir==DIR_UP){ if(index>=4){ if(blockShape[index-4]) return true; } return ScriptedPushBlock_CanWalk(x, y, DIR_UP, 1, true); } else if(dir==DIR_DOWN){ if(index<=11){ if(blockShape[index+4]) return true; } return ScriptedPushBlock_CanWalk(x, y, DIR_DOWN, 1, true); } else if(dir==DIR_LEFT){ if(index%4>=1){ if(blockShape[index-1]) return true; } return ScriptedPushBlock_CanWalk(x, y, DIR_LEFT, 1, true); } else if(dir==DIR_RIGHT){ if(index%4<=2){ if(blockShape[index+1]) return true; } return ScriptedPushBlock_CanWalk(x, y, DIR_RIGHT, 1, true); } } bool ScriptedPushBlock_DrawMovingBlock(int scriptedPushBlock, int blockX, int blockY){ int subCombo = scriptedPushBlock[10]; int subCSet = scriptedPushBlock[11]; int subSolid = scriptedPushBlock[13]; int blockShape = scriptedPushBlock[14]; int layer = 2; if(ScreenFlag(1, 4)&&layer==2) //Layer -2 layer = 1; blockX = Round(blockX); blockY = Round(blockY); for(int i=0; i<16; ++i){ int x = blockX+(i%4)*16; int y = blockY+Floor(i/4)*16; if(blockShape[i]){ Screen->FastCombo(layer, x, y, subCombo[i], subCSet[i], 128); if(subSolid[i]){ if(subSolid[i]==1111b) //Full Solid SolidObjects_Add(0, x, y, 16, 16, 0, 0, 0); else if(subSolid[i]==0101b) //Top Half Solid SolidObjects_Add(0, x, y, 16, 8, 0, 0, 0); else if(subSolid[i]==1010b) //Bottom Half Solid SolidObjects_Add(0, x, y+8, 16, 8, 0, 0, 0); else if((subSolid[i]&0011b)==0011b){ //Left Half Solid SolidObjects_Add(0, x, y, 8, 16, 0, 0, 0); if(subSolid[i]&0100b) //Top Right SolidObjects_Add(0, x+8, y, 8, 8, 0, 0, 0); else if(subSolid[i]&1000b) //Bottom Right SolidObjects_Add(0, x+8, y+8, 8, 8, 0, 0, 0); } else if((subSolid[i]&1100b)==1100b){ //Right Half Solid SolidObjects_Add(0, x+8, y, 8, 16, 0, 0, 0); if(subSolid[i]&0001b) //Top Left SolidObjects_Add(0, x, y, 8, 8, 0, 0, 0); else if(subSolid[i]&0010b) //Bottom Left SolidObjects_Add(0, x, y+8, 8, 8, 0, 0, 0); } else{ //Quarter Tile Solid if(subSolid[i]&0100b) //Top Right SolidObjects_Add(0, x+8, y, 8, 8, 0, 0, 0); else if(subSolid[i]&1000b) //Bottom Right SolidObjects_Add(0, x+8, y+8, 8, 8, 0, 0, 0); else if(subSolid[i]&0001b) //Top Left SolidObjects_Add(0, x, y, 8, 8, 0, 0, 0); else if(subSolid[i]&0010b) //Bottom Left SolidObjects_Add(0, x, y+8, 8, 8, 0, 0, 0); } } } } } void ScriptedPushBlock_DoBlockTriggerSecret(int secretSFX){ int triggeredBlocks; for(int i=0; i<176; ++i){ if(ComboFI(i, CF_BLOCKTRIGGER)) return; if(ComboFI(i, CF_LARGEPUSH_TRIGGERED)) ++triggeredBlocks; } if(triggeredBlocks){ Game->PlaySound(secretSFX); Screen->TriggerSecrets(); } }