const int SFX_PUSHBLOCK_MOVE = 50; //Sound to play when moving a block.
const int SFX_PUSHBLOCK_LAND_ON_TRIGGER = 16; //Sound to play when block gets stuck when landing on trigger.
const int SFX_PUSHBLOCK_STUCK_AFTER_ONE_PUSH = 16; //Sound to play when block flagged with #52,53,54,55,56,57 or 58 gets stuck after one push.
const int SFX_ICEBLOCK_PUSH = 21; //Sound to play when ice block gets pushed.
const int SFX_ICEBLOCK_STOP = 16; //Sound to play when ice block stops by hitting obstacle.
const int SFX_BLOCKHOLE = 0; //Sound to play when block falls into block hole (flag #91).
const int SFX_ICEBLOCK_PAINT = 0; //Sound to play when painting iceblock paints floor tile
const int SFX_PUSHBLOCK_DESTROY = 32;//Sound to play when pushblock is destroyed
const int SPR_PUSHBLOCK_DESTROY = 32; //Sprite to display, when pushblock is destroyed
const int CF_FFICEBLOCK = 98; //Combo Place Flag used by AutomaticFreeformPushblockPuzzle script to generate ice blocks.
const int CIF_STACKPUSH_ONLY = 99; //Combo Inherent Flag used by AutomaticFreeformPushblockPuzzle script to generate StackPush only blocks.
const int IC_MULTIPUSHBLOCK = 18; //Class of items used to allow Link to push stacked blocks
const int I_MULTIPUSHBLOCK = 56; //If set to >0 Link would not be able to push more than 1 block at time without this item.
const int PUSHBLOCK_SENSIVITY = 8; //Pushblock time sensivity, in frames.
//FFC misc flags. Set to avoid conflicts with other FFC scripts.
//const int FFC_MISC_PUSHBLOCK_IMPULSE = 0; //FFC Misс variable to track block moving direction
//const int FFC_MISC_PUSHBLOCK_POWER = 1;//FFC Misс variable to track Link`s pushing power granted by IC_MULTIPUSHBLOCK item class.
//Maximum stack weight cannot exceed this value.
//const int FFC_MISC_PUSHBLOCK_UNDERCSET = 2;//FFC Misс variable to track Cset of combo under pushblock FFC.
//Special block flags, OR them toether to define special pushblock properties. DON`T EDIT THESE CONSTANTS
const int PUSHBLOCK_SPECIAL_ENEMY_WAIT = 1; //Cannot be pushed until all enemies on screen are killed.
const int PUSHBLOCK_SPECIAL_CAN_PUSH_OFF_TRIGGERS = 2; //Can be pushed off triggers.
const int PUSHBLOCK_SPECIAL_ONLY_ONE_PUSH= 4; //Gets stuck after 1 push.
const int PUSHBLOCK_SPECIAL_TRIGGER= 8; //Triggers screen secrets when moved.
const int PUSHBLOCK_SPECIAL_ICEBLOCK = 16; //Turns this block into Ice Pushblock that after push continues to move until hitting obstacle.
const int PUSHBLOCK_SPECIAL_REMOVE_ON_SECRET = 32; //Removed when triggering secrets.
const int PUSHBLOCK_SPECIAL_MULTIPUSH_ONLY = 64; //Cannot be pushed directly, only in stack with other blocks.
const int PUSHBLOCK_SPECIAL_ICEBLOCK_PAINTS_FLOOR = 128; //Iceblock paints floor, for floor painting puzzles
const int PUSHBLOCK_SPECIAL_CHANGE_NEXT = 256; //Changes combo to next in the list after every push
const int PUSHBLOCK_SPECIAL_DESTRUCTIVE_UNDERCOMBO = 512; //Always leave behind screen`s undercombo on every push
//Update V2.1
//Pushblock FFC is now invisible, when idle.
//Update V2.2
// Fixed stackpushing of iceblocks
// Ice blocks can now get stuck if landed on triggers. Set D0 to 1 in FFC with
// AutomaticFreeformPushblockPuzzle script to avoid that.
// Added new FFC script - Remote controlled Pushblocks. Stand on FFC, face the chosen direction and press EX1 to push all pushblocks
//Update V2.3
//If painting iceblock FFC and floor that needs to be painted match CSets, floor combo, instead, will change to next one in the list.
//Added iceblock painting sound.
//New script - PlaceableIceblock
//New puzzle option - blocks change to next combo in the list after every push
//Update V2.4
//No Longer uses Misc variables.
//Fixed bug that caused bracelet items to be unused, if ID exceeds 143.
//UpdateFreeformBlockPower item pickup script is become redundant, as GetPraceletPower function was rewritten to increase perfomance.
//Update V2.5
//New script Match-2 puzzle
//New flag for pushblocks - always leave undercombos behind, like ZC engine pushables.
//Update V2.6
//PlaceableIceblock - added default values for arguments.
//Fixed wrong constant usage for pushblock destruction sprite.
// Automatic Freeform Push Block generator script.
//1. Place FFC anywhere on the screen
//2. Construct block puzzle as if you did it in ZC 2.53- versions, but use only placed flags.
// -Flag Ice PushBlocks with CF_FFICEBLOCK flags
// - The only inherent flag that works is CIF_STACKPUSH_ONLY that restricts blocks to StackPush only
//3. D0 - Allow blocks to be pushed off triggers, like in classic Sokoban.
// D1 - Remove all blocks and clean up FFC`s when puzzle is solved.
// D2 - Special Puzzle mode
// 1.Ice Push blocks paint floor into his own Cset.
// If all combos flagged with CF_BLOCKTRIGGER are painted, secrets pop open.
// 2.Colored blocks and block triggers. Each block trigger must be covered by block with matching cset to solve.
// If D0 is set to 0, blocks can get stuck on mismatching triggers, failing the puzzle.
// 3.Pushblocks change to next combo in the list after every push.
// D3 - 1 - Always leave udercombos behind
ffc script AutomaticFreeformPushblockPuzzle{
void run(int pushofftriggers, int secretremove, int icepaint, int undercombos){
int str[] = "FreeformPushBlock";
int scr = Game->GetFFCScript(str);
int genericpushable[22] = {1, 2, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, CF_FFICEBLOCK};
int dirup[3] = {48, 55, 62};
int dirdown[3] = {49, 56, 63};
int dirleft[3] = {50, 57, 64};
int dirright[3] = {51, 58, 65};
int dirhoriz[3] = {47, 53, 60};
int dirvert[3] = {1, 52, 59};
int dir4way[3] = {2, 54, 61};
int pushonce[7] = {52, 53, 54, 55, 56, 57, 58};
int pushtrigger[7] = {1, 2, 47, 48, 49, 50, 51};
int ctwait[3] = {8, 10, 27};
int ctweight1[2] = {9, 10};
int ctweight2[2] = {26, 27};
int pushflags[177];
for (int i=1; i<176; i++){
if (MatchComboI(genericpushable, i)) pushflags[i] = Screen->ComboI[i];
if (MatchComboF(genericpushable, i)) pushflags[i] = Screen->ComboF[i];
if (pushflags[i]==0)continue;
int args[3]= {0, 0, 0};
for (int q=0; q<3; q++){
if (pushflags[i]==dirup[q]) args[0] = 1;
}
for (int q=0; q<3; q++){
if (pushflags[i]==dirdown[q]) args[0] = 2;
}
for (int q=0; q<3; q++){
if (pushflags[i]==dirleft[q]) args[0] = 4;
}
for (int q=0; q<3; q++){
if (pushflags[i]==dirright[q]) args[0] = 8;
}
for (int q=0; q<3; q++){
if (pushflags[i]==dirhoriz[q]) args[0] = 12;
}
for (int q=0; q<3; q++){
if (pushflags[i]==dirvert[q]) args[0] = 3;
}
for (int q=0; q<3; q++){
if (pushflags[i]==dir4way[q]) args[0] = 15;
}
for (int q=0; q<7; q++){
if (pushflags[i]==pushonce[q]) args[2] |= 4;
}
for (int q=0; q<7; q++){
if (pushflags[i]==pushtrigger[q]) args[2] |= 12;
}
for (int q=0; q<3; q++){
if (Screen->ComboT[i]==ctwait[q]) args[2] |= 1;
}
for (int q=0; q<2; q++){
if (Screen->ComboT[i]==ctweight1[q]) args[1] = 1;
}
for (int q=0; q<2; q++){
if (Screen->ComboT[i]==ctweight2[q]) args[1] = 2;
}
if (Screen->ComboI[i]==CIF_STACKPUSH_ONLY) args[2] |= 64;
if (pushflags[i]==CF_FFICEBLOCK){
args [0] = 15;
args [1] = 0;
args [2] = 18;
if (pushofftriggers==0) args[2]-=2;
if (icepaint==1) args [2] |= PUSHBLOCK_SPECIAL_ICEBLOCK_PAINTS_FLOOR;
}
if (pushofftriggers>0) args[2]|=2;
if (secretremove>0) args[2]|=32;
if (icepaint==3)args[2]|=256;
if (undercombos>0)args[2]|=512;
ffc block = RunFFCScriptOrQuit(scr, args);
block->X= ComboX(i);
block->Y= ComboY(i);
Screen->ComboF[i] = 0;
}
int tr[] = "FreeformPushBlockTriggers";
scr = Game->GetFFCScript(tr);
int trarg[8] = {0, 0, 0, 0, 0, 0, 0, 0};
if (secretremove>0) trarg[0] =1;
if (icepaint==2) trarg[1] =1;
ffc trig = RunFFCScriptOrQuit(scr,trarg);
}
}
//Freeform Push Block script. Unlike normal push blocks. Link can push stack of these blocks, instead of 1 at time, if he has the powerful bracelet.
// Place FFC at position of block.
// D0 - Allowed push directions for this block.
// D1 - Weight of block.
// D2 - Special flags, ORed together. Refer to PUSHBLOCK_SPECIAL_* constants at the top of this script file.
ffc script FreeformPushBlock {
void run ( int dircs, int weight, int special ){
int pos = ComboAt (CenterX (this), CenterY (this));
this->Data = Screen->ComboD[pos];
this->CSet = Screen->ComboC[pos];
this->X= ComboX(pos);
this->Y = ComboY(pos);
this->Flags[FFCF_LENSVIS] = true;
int undercombo = Screen->UnderCombo;
this->InitD[6] = Screen->UnderCSet;
int framecounter=0;
int movecounter = 0;
bool stackpush = true;
if (special&PUSHBLOCK_SPECIAL_MULTIPUSH_ONLY) stackpush = false;
int itm = GetCurrentItem(IC_MULTIPUSHBLOCK);
this->InitD[5]=0;
if (itm>=0){
itemdata it= Game->LoadItemData(itm);
this->InitD[5] = it->Power;
}
this->InitD[7]= -1;
if ((special&PUSHBLOCK_SPECIAL_ICEBLOCK_PAINTS_FLOOR)>0) this->InitD[6] = this->CSet;
while (true){
// Check if Link is pushing against the block
if((Link->X == this->X - 16 && (Link->Y < this->Y + 1 && Link->Y > this->Y - 12) && Link->InputRight && Link->Dir == DIR_RIGHT) || // Right
(Link->X == this->X + 16 && (Link->Y < this->Y + 1 && Link->Y > this->Y - 12) && Link->InputLeft && Link->Dir == DIR_LEFT) || // Left
(Link->Y == this->Y - 16 && (Link->X < this->X + 4 && Link->X > this->X - 4) && Link->InputDown && Link->Dir == DIR_DOWN) || // Down
(Link->Y == this->Y + 8 && (Link->X < this->X + 4 && Link->X > this->X - 4) && Link->InputUp && Link->Dir == DIR_UP)) { // Up
framecounter++;
}
else {
// Reset the frame counter
framecounter = 0;
}
if (framecounter>=8){
if (CanBePushed(this, Link->Dir, weight, stackpush)){
if ((special&PUSHBLOCK_SPECIAL_ICEBLOCK)>0)Game->PlaySound(SFX_ICEBLOCK_PUSH);
else Game->PlaySound(SFX_PUSHBLOCK_MOVE);
}
framecounter=0;
}
if (this->InitD[7]>=0 && this->InitD[7]<=3){
if (movecounter==0){
movecounter = 16;
this->Flags[FFCF_LENSVIS] = false;
if ((special&PUSHBLOCK_SPECIAL_DESTRUCTIVE_UNDERCOMBO)>0){
Screen->ComboD[pos] = Screen->UnderCombo;
Screen->ComboC[pos] = Screen->UnderCSet;
}
else{
Screen->ComboD[pos] = undercombo;
Screen->ComboC[pos] = this->InitD[6];
}
}
else {
if ((special&PUSHBLOCK_SPECIAL_ICEBLOCK)>0 && movecounter >8){
if (!IceblockCanContinueSlide(this)) movecounter=0;
}
if (movecounter>0){
NoAction();
movecounter--;
if (this->InitD[7]==DIR_UP) this->Y--;
if (this->InitD[7]==DIR_DOWN) this->Y++;
if (this->InitD[7]==DIR_LEFT) this->X--;
if (this->InitD[7]==DIR_RIGHT) this->X++;
if ((special&PUSHBLOCK_SPECIAL_ICEBLOCK)>0){
movecounter--;
if (this->InitD[7]==DIR_UP) this->Y--;
if (this->InitD[7]==DIR_DOWN) this->Y++;
if (this->InitD[7]==DIR_LEFT) this->X--;
if (this->InitD[7]==DIR_RIGHT) this->X++;
}
}
if (movecounter<=0){
if ((special&PUSHBLOCK_SPECIAL_ICEBLOCK_PAINTS_FLOOR)>0){
pos = ComboAt (CenterX (this), CenterY (this));
this->X = ComboX(pos);
this->Y = ComboY(pos);
if (ComboFI(pos, CF_BLOCKTRIGGER)){
if (Screen->ComboC[pos]==this->CSet){
Game->PlaySound(SFX_ICEBLOCK_PAINT);
Screen->ComboF[pos]=0;
Screen->ComboD[pos]++;
}
else if (Screen->ComboC[pos]!=this->CSet){
Game->PlaySound(SFX_ICEBLOCK_PAINT);
Screen->ComboC[pos] = this->CSet;
Screen->ComboF[pos]=0;
}
}
for (int i=0; i<=176; i++){
if (i==176){
if (Screen->State[ST_SECRET]) break;
Game->PlaySound(SFX_SECRET);
Screen->TriggerSecrets();
Screen->State[ST_SECRET] = true;
break;
}
if ((ComboFI(i,CF_BLOCKTRIGGER))) break;
//if ((Screen->ComboC[i]) != (this->CSet)) break;
}
}
if (IceblockCanContinueSlide(this)) movecounter=16;
else{
pos = ComboAt (CenterX (this), CenterY (this));
this->X = ComboX(pos);
this->Y = ComboY(pos);
undercombo = Screen->ComboD[pos];
this->InitD[6] = Screen->ComboC[pos];
int flag = Screen->ComboI[pos];
if (ComboFI(pos, CF_BLOCKHOLE)){
Game->PlaySound(SFX_BLOCKHOLE);
Screen->ComboD[pos]++;
ClearFFC(FFCNum(this));
Quit();
}
if ((special&PUSHBLOCK_SPECIAL_CHANGE_NEXT)>0)this->Data++;
Screen->ComboD[pos] = this->Data;
Screen->ComboC[pos] =this-> CSet;
this->InitD[7]=-1;
this->Flags[FFCF_LENSVIS] = true;
//this->InitD[7]=0;
if ((special&PUSHBLOCK_SPECIAL_ICEBLOCK)>0) Game->PlaySound(SFX_ICEBLOCK_STOP);
if ((special&PUSHBLOCK_SPECIAL_TRIGGER)>0){
Game->PlaySound(27);
Screen->TriggerSecrets();
Screen->State[ST_SECRET]= true;
this->InitD[0]=0;
}
else{
if ((special&PUSHBLOCK_SPECIAL_ONLY_ONE_PUSH)>0){
Game->PlaySound(SFX_PUSHBLOCK_STUCK_AFTER_ONE_PUSH);
this->InitD[0]=0;
}
if ((Screen->ComboF[pos]==CF_BLOCKTRIGGER)||(flag==CF_BLOCKTRIGGER)){
if((special&PUSHBLOCK_SPECIAL_CAN_PUSH_OFF_TRIGGERS)==0){
Game->PlaySound(SFX_PUSHBLOCK_LAND_ON_TRIGGER);
this->InitD[0]=0;
}
}
}
}
}
}
}
if (this->InitD[7]==4){//Set FFC`s InitD[7] to 4 to destroy pushblock.
Game->PlaySound(SFX_PUSHBLOCK_DESTROY);
Screen->ComboD[pos] = undercombo;
Screen->ComboC[pos] = this->InitD[6];
if (SPR_PUSHBLOCK_DESTROY>0){
lweapon s = CreateLWeaponAt(LW_SPARKLE, ComboX(pos), ComboY(pos));
s->UseSprite(SPR_PUSHBLOCK_DESTROY);
s->CollDetection=false;
}
ClearFFC(FFCNum(this));
Quit();
}
if (((special&32)>0)&&(Screen->State[ST_SECRET])){
Screen->ComboD[pos] = undercombo;
Screen->ComboC[pos] = this->InitD[6];
ClearFFC(FFCNum(this));
Quit();
}
//Screen->Rectangle(3, this->X, this->Y, this->X+15, this->Y+15, 3, 1, 0, 0, 0, false, OP_OPAQUE);
//Screen->DrawInteger(3, this->X, this->Y,0, 1,0, -1, -1, FFCNum(this), 0, OP_OPAQUE);
//debugValue(1, special);
//debugValue(2, this->InitD[0]);
Waitframe();
}
}
}
int OccupiedByPushblock(int cmb){
int str[] = "FreeformPushBlock";
int scr = Game->GetFFCScript(str);
for (int i=1; i<=32; i++){
ffc f = Screen->LoadFFC(i);
if (f->Script!=scr)continue;
int pos = ComboAt (CenterX (f), CenterY (f));
if (pos == cmb) return i;
}
return 0;
}
bool IceblockCanContinueSlide(ffc f){
if (((f->InitD[2])&PUSHBLOCK_SPECIAL_ICEBLOCK)==0) return false;
if (f->InitD[7]<0) return false;
//if (f->InitD[7]>0) return false;
int pos = ComboAt (CenterX (f), CenterY (f));
int adj = AdjacentComboFix(pos, f->InitD[7]);
if (ComboFI(adj, CF_BLOCKHOLE)) return true; //Iceblocks will slide over block holes unless obstructed.
if (ComboFI(adj, CF_NOBLOCKS)) return false;
if (Screen->ComboS[adj]>0) return false;
int ff = OccupiedByPushblock(adj);
if (ff==0) return true;
ffc n = Screen->LoadFFC(ff);
if (!IceblockCanContinueSlide(n)) return false;
return true;
}
// Returns TRUE, if all onscreen beatable enemies were have been defeated.
bool NoEnemiesLeft (){
for (int i = 1; i<= Screen->NumNPCs(); i++){
npc n = Screen->LoadNPC(i);
if (!(n->isValid())) return true;
if ((n->MiscFlags&NPCMF_NOT_BEATABLE)==0) return false;
}
return true;
}
bool IsOnColoredTergger(ffc f){
int pos = ComboAt (CenterX (f), CenterY (f));
int cset = Screen->ComboC[pos];
if (f->CSet == cset) return true;
return false;
}
bool CanBePushed(ffc f, int dir, int weight, bool stackpushonly){
if (!stackpushonly) return false;
if (((f->InitD[2])&PUSHBLOCK_SPECIAL_ENEMY_WAIT)>0){
if (!NoEnemiesLeft ()) return false;
}
int str[] = "FreeformPushBlock";
int scr = Game->GetFFCScript(str);
if ((f->Script)!=scr) return false;
//Game->PlaySound(SFX_HAMMER);
if (dir==DIR_UP){
if (((f->InitD[0])&1)==0) return false;
}
if (dir==DIR_DOWN){
if (((f->InitD[0])&2)==0) return false;
}
if (dir==DIR_LEFT){
if (((f->InitD[0])&4)==0) return false;
}
if (dir==DIR_RIGHT){
if (((f->InitD[0])&8)==0) return false;
}
int power = GetBraceletPower();
if (power<weight) return false;
int cmb=ComboAt (CenterX (f), CenterY (f));
int adj = AdjacentComboFix(cmb, dir);
if (adj<0) return false;
if (ComboFI(adj, CF_NOBLOCKS)) return false;
if (Screen->ComboS[adj]>0){
if (ComboFI(adj, 91)){
f->InitD[7]= dir;
return true;
}
//if (((f->InitD[2])&PUSHBLOCK_SPECIAL_ICEBLOCK)>0) return false;
if ((I_MULTIPUSHBLOCK>0)&&(!Link->Item[I_MULTIPUSHBLOCK])) return false;
for (int i=1; i<=32; i++){
ffc next = Screen->LoadFFC(i);
if ((next->Script) != scr) continue;
if (next->X != ComboX(adj)) continue;
if (next->Y != ComboY(adj)) continue;
if (((next->InitD[2])&PUSHBLOCK_SPECIAL_ICEBLOCK)!= ((f->InitD[2])&PUSHBLOCK_SPECIAL_ICEBLOCK)) return false;
if (CanBePushed(next, dir, (weight+next->InitD[1]), true)){
f->InitD[7]= dir;
return true;
}
}
return false;
}
f->InitD[7]= dir;
return true;
}
// Returns Link`s pushing power
int GetBraceletPower(){
int result = -1;
int highestlevel = -1;
for(int i=0; i<=MAX_HIGHEST_LEVEL_ITEM_CHECK; ++i) {
itemdata id = Game->LoadItemData(i);
if(id->Family == IC_BRACELET && Link->Item[i]){
if(id->Level >= highestlevel){
highestlevel = id->Level;
result=i;
}
}
}
if (result<0)return 0;
itemdata it = Game->LoadItemData(result);
return it->Power;
}
//Item script needed to update Link`s pushing power, id you have Freeform Pushblocks and IC_MULTIPUSHBLOCK items on the same screen.
//it`s needed to avoid calling very slow GetHighestLevelItemOwned function every frame just for a single change.
// item script UpdateFreeformBlockPower{
// void run(){
// int str[] = "FreeformPushBlock";
// int scr = Game->GetFFCScript(str);
// for (int i=1; i<=32; i++){
// ffc next = Screen->LoadFFC(i);
// if ((next->Script) != scr) continue;
// next->InitD[5] = this->Power;
// }
// }
// }
//FFC script used to track block triggers.
// Place 1 FFC anywhere on the screen.
// D0 - remove FFC pushblocks when puzzle is solved.
ffc script FreeformPushBlockTriggers{
void run(int secretremover, int color){
if (Screen->State[ST_SECRET])Quit();
int scr_iceblock[] = "FreeformPushBlock";
int ffcscript_iceblock= Game->GetFFCScript(scr_iceblock);
ffc blocks[31];
int triggerx[31];
int triggery[31];
int num_push_blocks = 0;
int num_triggers = 0;
int good_counter = 0;
for(int i = 0; i < 176 && num_triggers < 31; i++) {
if(Screen->ComboF[i] == CF_BLOCKTRIGGER || Screen->ComboI[i] == CF_BLOCKTRIGGER) {
triggerx[num_triggers] = (i % 16) * 16;
triggery[num_triggers] = Floor(i / 16) * 16;
num_triggers++;
}
}
if(num_triggers == 0) Quit();
for(int i = 1; i <= 32; i++) {
ffc temp = Screen->LoadFFC(i);
if(temp->Script == ffcscript_iceblock) {
blocks[num_push_blocks] = temp;
num_push_blocks++;
}
}
if(num_push_blocks == 0) Quit();
while(true) {
for(int i = 0; i < num_push_blocks; i++) {
//Check if blocks are on switches and not moving
for(int j = 0; j < num_triggers; j++) {
if(blocks[i]->X == triggerx[j] && blocks[i]->Y == triggery[j] && blocks[i]->Vx == 0 && blocks[i]->InitD[7] < 0) {
if (color==0){
good_counter++;
break;
}
else{
int col = blocks[i]->InitD[6];
int cs = blocks[i]->CSet;
if (cs==col) good_counter++;
break;
}
}
}
}
if(good_counter == num_triggers) {
Game->PlaySound(SFX_SECRET);
if (secretremover>0){
for (int i=1; i<=32; i++){
ffc f = Screen->LoadFFC(i);
if (f->Script==ffcscript_iceblock) ClearFFC(i);
}
}
Screen->TriggerSecrets();
if((Screen->Flags[SF_SECRETS] & 2) == 0) Screen->State[ST_SECRET] = true;
Quit();
}
//debugValue(2, good_counter);
good_counter = 0;
Waitframe();
}
}
}
//Fixed variant of AdjacentCombo function from std_extension.zh
int AdjacentComboFix(int cmb, int dir)
{
int combooffsets[13]={-0x10, 0x10, -1, 1, -0x11, -0x0F, 0x0F, 0x11};
if ( cmb % 16 == 0 ) combooffsets[9] = -1;//if it's the left edge
if ( (cmb % 16) == 15 ) combooffsets[10] = -1; //if it's the right edge
if ( cmb < 0x10 ) combooffsets[11] = -1; //if it's the top row
if ( cmb > 0x9F ) combooffsets[12] = -1; //if it's on the bottom row
if ( combooffsets[9]==-1 && ( dir == DIR_LEFT || dir == DIR_LEFTUP || dir == DIR_LEFTDOWN ) ) return -1; //if the left columb
if ( combooffsets[10]==-1 && ( dir == DIR_RIGHT || dir == DIR_RIGHTUP || dir == DIR_RIGHTDOWN ) ) return -1; //if the right column
if ( combooffsets[11]==-1 && ( dir == DIR_UP || dir == DIR_RIGHTUP || dir == DIR_LEFTUP ) ) return -1; //if the top row
if ( combooffsets[12]==-1 && ( dir == DIR_DOWN || dir == DIR_RIGHTDOWN || dir == DIR_LEFTDOWN ) ) return -1; //if the bottom row
if ( cmb >= 0 && cmb < 176 ) return cmb + combooffsets[dir];
else return -1;
}
//Remote controlled iceblocks. Stand on control panel, face the chosen direction and press EX1 to send all iceblocks
//(or other pushblocks) moving.
//Rquires StackPushblocks EX 2.2
//Place FFC at control panel`s position. No arguments needed.
ffc script RemoteControlledIceblocks{
void run(){
int str[] = "FreeformPushBlock";
int scr = Game->GetFFCScript(str);
while(true){
if (RectCollision(Link->X+7, Link->Y+7, Link->X+8, Link->Y+8, this->X, this->Y, this->X+this->EffectWidth-1, this->Y+this->EffectHeight-1)){
if (Link->PressEx1){
Game->PlaySound(SFX_ICEBLOCK_PUSH);
for (int i=1; i<=32; i++){
ffc f = Screen->LoadFFC(i);
if (f->Script!=scr) continue;
if (f->InitD[7]>=0) continue;
bool push = CanBePushed(f, Link->Dir, 0, true);
}
}
}
Waitframe();
}
}
}
//Requires Stack pushblock 2.3+
//Placeable iceblock for painting puzzle.
//Place anywhere in the screen.
//D0 - ID of combo - defaults to FFC`s combo
//D1 - CSet of combo - defaults to FFC`s CSet
//D2 - sound to play on placement - defults to sound that plays when moving iceblock hits obstacle and stops.
ffc script PlaceableIceblock{
void run (int icecmb, int icecset, int sound){
if (icecmb==0)icecmb=this->Data;
if (icecset ==0)icecset = this->CSet;
if (sound==0)sound = SFX_ICEBLOCK_STOP;
while(true){
if (Link->PressEx1){
int pos = ComboAt(CenterLinkX(),CenterLinkY());
if (Screen->ComboF[pos]==CF_BLOCKTRIGGER){
Game->PlaySound(sound);
Screen->ComboD[pos] = icecmb;
Screen->ComboC[pos] = icecset;
Screen->ComboF[pos]=CF_FFICEBLOCK;
int str[] = "AutomaticFreeformPushblockPuzzle";
int scr = Game->GetFFCScript(str);
int args[3]={1,0,1};
ffc f = RunFFCScriptOrQuit(scr, args);
//Trace(FFCNum(f));
Quit();
}
}
Waitframe();
}
}
}
const int CSET_MATCH_VANISH_UNMATCHABLE=11;//Cset used for unmatсhable blocks that are not needed to be destroyed to trigger secret.
//If two or more pushblocks with the same combo ID and CSet collide, they disappear. Remove all pushblocks to solve the puzzle.
//Place anywhere in the screen. No arguments needed.
ffc script MatchVanishPuzzle{
void run(){
Waitframe();
int scr_iceblock[] = "FreeformPushBlock";
int scr= Game->GetFFCScript(scr_iceblock);
bool secret = false;
bool moving = false;
while(true){
moving=false;
secret=true;
for (int i=1;i<=32;i++){
ffc f=Screen->LoadFFC(i);
if (f->Script!=scr) continue;
if (f->CSet!= CSET_MATCH_VANISH_UNMATCHABLE)secret=false;
if (f->InitD[7]>=0){
moving=true;
break;
}
int cmb = ComboAt(f->X+1, f->Y+1);
for (int d=0;d<4;d++){
int adjcmb = AdjacentComboFix(cmb, d);
if (Screen->ComboD[adjcmb]==Screen->ComboD[cmb] && Screen->ComboC[adjcmb]==Screen->ComboC[cmb]&& f->CSet!= CSET_MATCH_VANISH_UNMATCHABLE) f->InitD[7]=4;
}
}
if (moving){
for (int i=1;i<=32;i++){
ffc f=Screen->LoadFFC(i);
if (f->Script!=scr) continue;
if (f->InitD[7]==4)f->InitD[7]=-1;
}
}
if (secret){
Game->PlaySound(SFX_SECRET);
Screen->TriggerSecrets();
Screen->State[ST_SECRET] = true;
Quit();
}
Waitframe();
}
}
}