const int SFX_LIGHTPANEL_TURN = 6; //Sound when a light panel turns
ffc script LightPath{
void run(int cmbNode, int cmbRotatepath, int cmbFloorPath, int sfx){
bool alreadyTriggered;
if(Screen->State[ST_SECRET])
alreadyTriggered = true;
if(sfx==0)
sfx = 27;
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.
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>=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){
mainState[i] = 4; //Source Node
dirState[i] = (cd-cmbNode+16)%16;
}
}
int vars[16] = {1, cmbNode, cmbRotatepath, cmbFloorPath};
//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);
Screen->TriggerSecrets();
Screen->State[ST_SECRET] = true;
}
//Keep the combos up to date and keep track of active nodes
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;
}
return 0;
}
//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 dirState){
int newState;
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;
}
//Find a panel combo under a sword weapon
int LightPath_SwordCombo(lweapon sword, int mainState){
//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(mainState[uc]==2)
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(mainState[uc]==2)
return uc;
}
}
return -1;
}
//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;
for(int i=0; i<4; i++){ //Run through all directions
if(dirState[node]&(1<<i)){ //If that direction is valid
nextPos = LightPath_AdjacentPos(node, i);
int dir = i;
while(!active[nextPos]&&dirState[nextPos]&(1<<OppositeDir(dir))){
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;
}
dir = LightPath_FindNextDir(nextPos, dirState, dir);
nextPos = LightPath_AdjacentPos(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) //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){ //Source Node
LightPath_RunFromNode(i, mainState, dirState, active);
}
}
}
//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;
//Detect the sword collision
lweapon sword = LoadLWeaponOf(LW_SWORD);
if(sword->isValid()){
int pos = LightPath_SwordCombo(sword, mainState);
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(mainState[i]==1){ //Fixed Path
Screen->ComboD[i] = vars[3]+dirState[i]+16*active[i];
}
if(mainState[i]==2){ //Rotating Path
Screen->ComboD[i] = vars[2]+dirState[i]+16*active[i];
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, true, 128);
rotTime[i]++;
if(rotTime[i]>=15){
rotTime[i] = 0;
LightPath_Rotate(i, 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;
}
}
}
else if(mainState[i]==3){ //Trigger Node
if(!active[i])
vars[0]++;
Screen->ComboD[i] = vars[1]+dirState[i]+16*active[i];
}
}
}
}