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);
}
return false;
}
void 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();
}
}