const int SFX_LIGHTPANEL_TURN = 6; //Sound to play when a light panel turns
const int SFX_LIGHTPATH_SOLVED = 27; //Sound to play when Light Path puzzle has been solved
const int CF_LIGHTPATH_SOURCE_NODE = 100; //Combo flag to set node as source node.
const int CT_LIGHTPATH_WRAPAROUND_PORTAL = 50; //Combo type used for wraparound portals.
const int LIGHTPATH_ALLOW_ROTATE_TILES_WITHOUT_SWORD = 1; //Set to >0 to allow Link to rotate tiles just by standing on them and pressing A.
//!\Don`t Edit//!\
const int LIGHTPATH_SPCMB_SIZE = 32; //Don`t edit
const int LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UL_DR = 4;//Double bends.
const int LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UR_DL = 8;
const int LIGHTPATH_SPCMB_OFFSET_BRIDGE = 12;//Path can intersect without connecting
const int LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UL_DR_ROTATEABLE = 16;//Double bends. Can be rotated
const int LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UR_DL_ROTATEABLE = 20;
const int LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UL_DR_SOLID = 24;//Double bends. Solid, can ber set as pushable.
const int LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UR_DL_SOLID = 28;
const int LIGHTPATH_SPCMB_OFFSET_BRIDGE_SOLID = 32;//Bridge, solid, can be set as pushable.
ffc script LightPath{
void run(int cmbNode, int cmbRotatepath, int cmbFloorPath, int cmbSolidPath, int cmbRotateNode, int cmbSpecial){
bool alreadyTriggered;
if(Screen->State[ST_SECRET])
alreadyTriggered = true;
int mainState[176]; //The type of each combo
int dirState[176]; //Binary flag for the four directions of the combo
int active[176]; //Whether or not the combo is activated
int rotTime[176]; //Timer for rotating panels
//First we cycle through every combo on the screen and
//assign it a type based on ComboD.
int vars[16] = {1, cmbFloorPath, cmbRotatepath, cmbNode, cmbNode, cmbSolidPath, cmbRotateNode, cmbRotateNode, cmbSpecial};
for(int i=0; i<176; i++){
int cd = Screen->ComboD[i];
if(cd>=cmbFloorPath&&cd<=cmbFloorPath+31){
mainState[i] = 1; //Fixed Path
dirState[i] = (cd-cmbFloorPath)%16;
}
else if(cd>=cmbSolidPath&&cd<=cmbSolidPath+31){
mainState[i] = 5; //Solid Path
dirState[i] = (cd-cmbSolidPath)%16;
}
else if(cd>=cmbRotatepath&&cd<=cmbRotatepath+31){
mainState[i] = 2; //Rotating Path
dirState[i] = (cd-cmbRotatepath)%16;
}
else if(cd>=cmbNode&&cd<=cmbNode+15){
mainState[i] = 3; //Trigger Node
dirState[i] = (cd-cmbNode)%16;
}
else if(cd>=cmbNode+16&&cd<=cmbNode+31){
Screen->ComboF[i] = CF_LIGHTPATH_SOURCE_NODE;
mainState[i] = 4; //Source Node
dirState[i] = (cd-cmbNode+16)%16;
active[i]=1;
}
else if(cd>=cmbRotateNode&&cd<=cmbRotateNode+15){
mainState[i] = 6; //Trigger Node (rotateable)
dirState[i] = (cd-cmbRotateNode)%16;
}
else if(cd>=cmbRotateNode+16&&cd<=cmbRotateNode+31){
Screen->ComboF[i] = CF_LIGHTPATH_SOURCE_NODE;
mainState[i] = 7; //Source Node (rotateable)
dirState[i] = (cd-cmbRotateNode+16)%16;
active[i]=1;
}
else if (cd>=cmbSpecial&&cd<=cmbSpecial+LIGHTPATH_SPCMB_SIZE){
mainState[i] = 8; //Special LightPath tile
dirState[i] = ((cd - cmbSpecial)>>2<<2)+4;
}
else{
mainState[i]=0; //No paths
dirState[i]=0;
}
}
//Update the path for the start of the puzzle
LightPath_UpdatePath(mainState, dirState, active);
while(true){
//If there's no active nodes left and the puzzle hasn't already been
//solved, play the chime and solve it.
if(vars[0]<=0&&!alreadyTriggered){
alreadyTriggered = true;
Game->PlaySound(SFX_LIGHTPATH_SOLVED);
Screen->TriggerSecrets();
Screen->State[ST_SECRET] = true;
}
//Keep the combos up to date and keep track of active nodes
UpdateExistingPathCombos(mainState, dirState, cmbFloorPath, cmbRotatepath, cmbNode, cmbSolidPath, cmbRotateNode, cmbSpecial);
LightPath_UpdatePath(mainState, dirState, active);
LightPath_UpdateCombos(vars, mainState, dirState, active, rotTime);
Waitframe();
}
}
//Finds the combo next to another combo
int LightPath_AdjacentPos(int pos, int dir){
if(dir==DIR_UP){
if(pos<16)
return -1;
return pos-16;
}
if(dir==DIR_DOWN){
if(pos>159)
return -1;
return pos+16;
}
if(dir==DIR_LEFT){
if(pos%16==0)
return -1;
return pos-1;
}
if(dir==DIR_RIGHT){
if(pos%16==15)
return -1;
return pos+1;
}
}
//Finds the next direction in a node with two or less directions
int LightPath_FindNextDir(int node, int dirState, int lastDir){
for(int i=0; i<4; i++){
if(i!=OppositeDir(lastDir)){
if(dirState[node]&(1<<i)){
return i;
}
}
}
return lastDir;
}
//Rotate a panel's directions clockwise
void LightPath_Rotate(int node, int mainState,int dirState){
int newState;
if (mainState[node]==8){
if ((dirState[node]==LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UR_DL_ROTATEABLE)) dirState[node]= LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UL_DR_ROTATEABLE;
else if ((dirState[node]==LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UL_DR_ROTATEABLE)) dirState[node]= LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UR_DL_ROTATEABLE;
return;
}
if(dirState[node]&(1<<DIR_UP))
newState |= (1<<DIR_RIGHT);
if(dirState[node]&(1<<DIR_RIGHT))
newState |= (1<<DIR_DOWN);
if(dirState[node]&(1<<DIR_DOWN))
newState |= (1<<DIR_LEFT);
if(dirState[node]&(1<<DIR_LEFT))
newState |= (1<<DIR_UP);
dirState[node] = newState;
}
//Run the light path from a branching node until it hits a dead end
//or runs through all directions.
void LightPath_RunFromNode(int node, int mainState, int dirState, int active){
int nextPos;
int dir = -1;
for(int i=0; i<4; i++){ //Run through all directions
if((dirState[node]&(1<<i))||(mainState[i]==8)){ //If that direction is valid
nextPos = LightPath_AdjacentPos(node, i);
dir = i;
if ((Screen->ComboT[nextPos])==CT_LIGHTPATH_WRAPAROUND_PORTAL) nextPos = LightpathProcessWraparound (nextPos, dir);
while(true){
if ((active[nextPos])&&(mainState[nextPos]!=8)) break;
if ((mainState[nextPos]!=8)&&(!(dirState[nextPos]&(1<<OppositeDir(dir))))) break;
if (mainState[nextPos]!=8){
active[nextPos] = 1;
if(dirState[nextPos]==1111b||dirState[nextPos]==0111b||dirState[nextPos]==1011b||dirState[nextPos]==1101b||dirState[nextPos]==1110b){
//You play a dangerous game there, Moosh...
//Working with powers beyond your understanding...
//Will ZScript break under the strain of the recursive functions?
//Only time will tell...
LightPath_RunFromNode(nextPos, mainState, dirState, active);
break;
}
if (dirState[nextPos]==(1<<OppositeDir(dir))) break;// Capped dead end reached.
}
else{
dir = LightPath_RunThroughSpecialTile(dir, nextPos, dirState, active);
if (dir<0) break;
}
if (mainState[nextPos]!=8) dir = LightPath_FindNextDir(nextPos, dirState, dir);
nextPos = LightPath_AdjacentPos(nextPos, dir);
if ((Screen->ComboT[nextPos])==CT_LIGHTPATH_WRAPAROUND_PORTAL) nextPos = LightpathProcessWraparound (nextPos, dir);
if(nextPos==-1) //If it goes off the screen, break out
break;
}
}
}
}
//Update the entire path
void LightPath_UpdatePath(int mainState, int dirState, int active){
//Cycle through an deactivate all nodes but the start
for(int i=0; i<176; i++){
if((mainState[i]!=4)&&(mainState[i]!=7)) //Don't deactivate source nodes
active[i] = 0;
}
//Cycle to any start nodes and run from there
for(int i=0; i<176; i++){
if((mainState[i]==4)||(mainState[i]==7)){ //Source Node
LightPath_RunFromNode(i, mainState, dirState, active);
}
}
}
//Processes light run through wrap around portals.
int LightpathProcessWraparound (int loc, int dir){
int cmb = loc;
while(cmb>0){
cmb = LightPath_AdjacentPos(cmb, OppositeDir(dir));
if ((Screen->ComboT[cmb])==CT_LIGHTPATH_WRAPAROUND_PORTAL) return LightPath_AdjacentPos(cmb, dir);
}
return cmb;
}
//Set combo GFX and keep track of if the puzzle has been solved
void LightPath_UpdateCombos(int vars, int mainState, int dirState, int active, int rotTime){
vars[0] = 0;
if (LIGHTPATH_ALLOW_ROTATE_TILES_WITHOUT_SWORD>0){
int pos = ComboAt (CenterLinkX(), CenterLinkY());
if(CanBeRotated(pos, mainState, dirState)){
if (Link->PressA){
if(rotTime[pos]==0){
Game->PlaySound(SFX_LIGHTPANEL_TURN);
rotTime[pos] = 1;
}
}
}
}
else{
//Detect the sword collision
lweapon sword = LoadLWeaponOf(LW_SWORD);
if(sword->isValid()){
int pos = LightPath_SwordCombo(sword, mainState,dirState);
if(pos>-1){ //Rotating path
if(Distance(Link->X, Link->Y, sword->X, sword->Y)>10){
if(rotTime[pos]==0){
Game->PlaySound(SFX_LIGHTPANEL_TURN);
rotTime[pos] = 1;
}
}
}
}
}
for(int i=0; i<176; i++){
if(CanBeRotated(i, mainState, dirState)){ //Rotating Path
if(rotTime[i]>0){
Screen->FastCombo(2, ComboX(i), ComboY(i), Screen->UnderCombo, Screen->UnderCSet, 128);
Screen->DrawCombo(2, ComboX(i), ComboY(i), Screen->ComboD[i], 1, 1, Screen->ComboC[i], -1, -1, ComboX(i), ComboY(i), rotTime[i]*6, -1, 0, false, 128);
rotTime[i]++;
if(rotTime[i]>=15){
rotTime[i] = 0;
LightPath_Rotate(i, mainState, dirState);
LightPath_UpdatePath(mainState, dirState, active);
//A weird bug happened here, so I added this hack fix
//to ensure the puzzle can't be solved on the same frame
//as a panel was flipped.
vars[0] = 1;
}
}
}
if((mainState[i]==3)||(mainState[i]==6)||(mainState[i]==7)){ //Trigger Node
if(!active[i])
vars[0]++;
}
if (mainState[i]==8) Screen->ComboD[i]= vars[mainState[i]] + dirState[i]-4+active[i];
else if (mainState[i]>0) Screen->ComboD[i] = vars[mainState[i]]+dirState[i]+16*active[i];
}
}
}
//Update combos to handle changing paths by other means, like push blocks to assemble wires.
void UpdateExistingPathCombos(int mainState, int dirState, int cmbFloorPath, int cmbRotatepath, int cmbNode, int cmbSolidPath, int cmbRotateNode, int cmbSpecial){
for(int i=0; i<176; i++){
int cd = Screen->ComboD[i];
if(cd>=cmbFloorPath&&cd<=cmbFloorPath+31){
mainState[i] = 1; //Fixed Path
dirState[i] = (cd-cmbFloorPath)%16;
}
else if(cd>=cmbSolidPath&&cd<=cmbSolidPath+31){
mainState[i] = 5; //Fixed Path
dirState[i] = (cd-cmbSolidPath)%16;
}
else if(cd>=cmbRotatepath&&cd<=cmbRotatepath+31){
mainState[i] = 2; //Rotating Path
if (dirState==0)dirState[i] = (cd-cmbRotatepath)%16;
}
else if(cd>=cmbNode&&cd<=cmbNode+15){
mainState[i] = 3; //Trigger Node
dirState[i] = (cd-cmbNode)%16;
}
else if(cd>=cmbNode+16&&cd<=cmbNode+31){
if (ComboFI(i,CF_LIGHTPATH_SOURCE_NODE))mainState[i] = 4; //Source Node
else mainState[i] = 3; //Trigger Node
dirState[i] = (cd-cmbNode+16)%16;
}
else if(cd>=cmbRotateNode&&cd<=cmbRotateNode+15){
mainState[i] = 6; //Trigger Node (rotateable)
dirState[i] = (cd-cmbRotateNode)%16;
}
else if(cd>=cmbRotateNode+16&&cd<=cmbRotateNode+31){
if (ComboFI(i,CF_LIGHTPATH_SOURCE_NODE))mainState[i] = 7; //Source Node (rotateable)
else mainState[i] = 6; //Trigger Node (rotateable)
dirState[i] = (cd-cmbRotateNode+16)%16;
}
else if (cd>=cmbSpecial&&cd<=cmbSpecial+LIGHTPATH_SPCMB_SIZE){//Special Path
mainState[i] = 8; //Special LightPath tile
dirState[i] = ((cd - cmbSpecial)>>2<<2)+4;
}
else if (mainState[i]!=8) {
mainState[i]=0; //No paths
dirState[i]=0;
}
}
}
//Process light run through special tiles, like double bends and bridges.
int LightPath_RunThroughSpecialTile(int dir,int cmb, int dirState, int active){
if ((dirState[cmb]==LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UL_DR)
||(dirState[cmb]==LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UL_DR_ROTATEABLE)
||(dirState[cmb]==LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UL_DR_SOLID)){
if (dir==DIR_UP){
active[cmb]|=2;
return DIR_RIGHT;
}
else if (dir==DIR_DOWN){
active[cmb]|=1;
return DIR_LEFT;
}
else if (dir==DIR_LEFT){
active[cmb]|=2;
return DIR_DOWN;
}
else if (dir==DIR_RIGHT){
active[cmb]|=1;
return DIR_UP;
}
}
else if ((dirState[cmb]==LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UR_DL)
||(dirState[cmb]==LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UR_DL_ROTATEABLE)
||(dirState[cmb]==LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UR_DL_SOLID)){
if (dir==DIR_UP){
active[cmb]|=2;
return DIR_LEFT;
}
else if (dir==DIR_DOWN){
active[cmb]|=1;
return DIR_RIGHT;
}
else if (dir==DIR_LEFT){
active[cmb]|=1;
return DIR_UP;
}
else if (dir==DIR_RIGHT){
active[cmb]|=2;
return DIR_DOWN;
}
}
else if ((dirState[cmb]==LIGHTPATH_SPCMB_OFFSET_BRIDGE)
||(dirState[cmb]==LIGHTPATH_SPCMB_OFFSET_BRIDGE_SOLID)){
if (dir==DIR_UP)active[cmb]|=1;
else if (dir==DIR_DOWN) active[cmb]|=1;
else if (dir==DIR_LEFT) active[cmb]|=2;
else if (dir==DIR_RIGHT)active[cmb]|=2;
return dir;
}
}
//Processess interaction between Link and special LightPath tiles, like rotating double bends.
void LightPath_ProcessSpecialCombosInteraction(int cmb, int mainState, int dirState, int rotTime){
if (mainState[cmb]!=8) return;
if ((dirState[cmb]==LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UR_DL_ROTATEABLE)
||(dirState[cmb]==LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UL_DR_ROTATEABLE)){
if (LIGHTPATH_ALLOW_ROTATE_TILES_WITHOUT_SWORD>0){
int pos = ComboAt (CenterLinkX(), CenterLinkY());
if (Link->PressA){
if(rotTime[pos]==0){
Game->PlaySound(SFX_LIGHTPANEL_TURN);
rotTime[pos] = 1;
}
}
}
else{
lweapon sword = LoadLWeaponOf(LW_SWORD);
if(sword->isValid()){
int pos = LightPath_SwordCombo(sword, mainState, dirState);
if(pos== cmb){ //Rotating path
if(Distance(Link->X, Link->Y, sword->X, sword->Y)>10){
if(rotTime[pos]==0){
Game->PlaySound(SFX_LIGHTPANEL_TURN);
rotTime[pos] = 1;
}
}
}
}
}
}
}
//Returns TRUE, if LightPath tile can be rotated
bool CanBeRotated(int cmb, int mainState, int dirState){
if (mainState[cmb] == 2) return true;
if (mainState[cmb] == 7) return true;
if (mainState[cmb] == 6) return true;
if (mainState[cmb] == 8){
if (dirState[cmb]==LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UR_DL_ROTATEABLE) return true;
if (dirState[cmb]==LIGHTPATH_SPCMB_OFFSET_DOUBLEBEND_UL_DR_ROTATEABLE) return true;
return false;
}
return false;
}
//Find a panel combo under a sword weapon
int LightPath_SwordCombo(lweapon sword, int mainState, int dirState){
//A charged sword doesn't flip panels
if(Link->Action==LA_CHARGING)
return -1;
//Only flip panels when the sword is held in front of Link
if((Link->Dir==DIR_UP||Link->Dir==DIR_DOWN)&&Abs(sword->X-Link->X)>4)
return -1;
if((Link->Dir==DIR_LEFT||Link->Dir==DIR_RIGHT)&&Abs(sword->Y-Link->Y)>4)
return -1;
int uc = ComboAt(sword->X+8, sword->Y+8);
if((CanBeRotated(uc, mainState, dirState)))
return uc;
for(int x=0; x<2; x++){
for(int y=0; y<2; y++){
uc = ComboAt(sword->X+6+x*3, sword->Y+6+y*3);
if((CanBeRotated(uc, mainState, dirState)))
return uc;
}
}
return -1;
}