const int BOSS_FACADE_STATE_TELEPORTING_IN = 1;
const int BOSS_FACADE_STATE_TELEPORTING_OUT = 4;
const int BOSS_FACADE_STATE_CHARGING = 2;
const int BOSS_FACADE_STATE_FIRED = 3;
const int BOSS_FACADE_STATE_NOT_IN_PLANE = 0;
const int SFX_BOSS_FACADE_NUKE = 37;//Sound to play, when BossFacade casts nuclear flash spell.
const int BOSS_FACADE_NUKE_FLASH_COLOR = 1;//Color used for nuke flash
const int CF_CHANGEABLE_COMBO = 98;//Combo Flag to define combos that can be changed.
//Facade boss variant. Face-like enemy that teleports and fires stuff at Link. Use only Bombs. Goes berserk (faster teleport/attack sequence speed), when HP dropped below specific threshold.
//Animation - 4 frales set vertically: teleporting in, charging, firing, teleporting out.
ffc script BossFacadeFloor{
void run(int enemyID){
npc ghost = Ghost_InitAutoGhost(this, enemyID);
int MinLinkDistance = 128 - (ghost->Homing)/2;//Minimum distance to Link when teleporting 256 - can telefrag Link.
int WPND = ghost->WeaponDamage;//Eweapon damage
int Teledelay = Ghost_GetAttribute(ghost, 0, 120);//Delay between teleporting
int EwSize = Ghost_GetAttribute(ghost, 1, 3);//Enemy size, X*X square.
int Wpn = Ghost_GetAttribute(ghost, 2, 15);//Attack type, add together: +1- 4 shots orthogonally, +2 - 8 shots in orthogonal and diagonal directions, +4 - summon enemies, +8 - nukes the whole screen for damage anywhere! +16 - change random combos to specific one The boss picks attack at random and if RNG lands on banned attack type, Facade will fire a single aimed eweapon at Link.
int ewsprite = Ghost_GetAttribute(ghost, 3, -1);//Eweapon sprite
int Encount = Ghost_GetAttribute(ghost, 4, 4);//Enemy count for enemy spawning attack
int berserkHP = Ghost_GetAttribute(ghost, 5, 4);//Berserk HP threshold
int berserkDelay = Ghost_GetAttribute(ghost, 6, 60);//Delay between teleporting, if boss went berserk
int EnID = Ghost_GetAttribute(ghost, 7, 167);//Enemy ID for enemy spawning attack
int numcmbchange = Ghost_GetAttribute(ghost, 8, 5);//Number of random combos to change
int changecmb = Ghost_GetAttribute(ghost, 9, 0);//ID of combo to replace to
int cmbs[176];
for (int i=0;i<176;i++){
if (ComboFI(i,CF_CHANGEABLE_COMBO))cmbs[i]=i;
else cmbs[i]=-1;
}
int curcmb=0;
int temp=0;
ghost->Extend=3;
Ghost_SetSize(this, ghost, EwSize, EwSize);
if (EwSize>2)Ghost_SetHitOffsets(ghost, 8, 8, 8, 8);
Ghost_SetFlag(GHF_NORMAL);
Ghost_SetFlag(GHF_NO_FALL);
Ghost_UnsetFlag(GHF_KNOCKBACK);
int OrigTile = ghost->OriginalTile;
int State = 0;
int haltcounter = -1;
int StateCounter =Teledelay;
int dir = Ghost_Dir;
float suckx = Link->X;
float sucky = Link->Y;
int soundcounter=30;
int ewpn = 0;
bool berserk=false;
while(true){
if (State==BOSS_FACADE_STATE_NOT_IN_PLANE){
ghost->DrawXOffset = 1000;
ghost->HitXOffset = 1000;
if (StateCounter == 0){
ghost->DrawXOffset = 0;
ghost->HitXOffset = 0;
int combo = BossFacadeFindSuitableSpot(ghost, MinLinkDistance, true, false, false, false);
if (combo>0)Ghost_X=ComboX(combo);
if (combo>0)Ghost_Y=ComboY(combo);
State = BOSS_FACADE_STATE_TELEPORTING_IN;
StateCounter = 32;
dir = BossFacadeFaceLink(ghost);
}
}
if (State==BOSS_FACADE_STATE_TELEPORTING_IN){
if(IsOdd(StateCounter)) ghost->DrawXOffset=StateCounter/4;
else ghost->DrawXOffset=-StateCounter/4;
if (StateCounter == 0){
State = BOSS_FACADE_STATE_CHARGING;
StateCounter = 32;
}
}
if (State==BOSS_FACADE_STATE_CHARGING){
if (StateCounter == 0){
State = BOSS_FACADE_STATE_FIRED;
StateCounter = 32;
eweapon e;
ewpn =1<<( Rand(5));
if ((ewpn&Wpn)==0){
e = FireAimedEWeapon(ghost->Weapon, CenterX(ghost), CenterY(ghost), 0, 200, WPND, ewsprite, -1, 0);
}
else if (ewpn == 1){
int dirs[4]= {DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT};
// Game->PlaySound(ewSound);
for (int i=0;i<SizeOfArray(dirs);i++){
eweapon e = FireNonAngularEWeapon(ghost->Weapon, CenterX(ghost), CenterY(ghost), dirs[i], 300, WPND, ewsprite,-1, EWF_ROTATE);
}
}
else if (ewpn == 2){
int dirs[8] = {DIR_UP, DIR_RIGHTUP, DIR_RIGHT, DIR_RIGHTDOWN, DIR_DOWN, DIR_LEFTDOWN, DIR_LEFT, DIR_LEFTUP};
// Game->PlaySound(ewSound);
for (int i=0;i<SizeOfArray(dirs);i++){
e = FireNonAngularEWeapon(ghost->Weapon, CenterX(ghost), CenterY(ghost), dirs[i], 300, WPND, ewsprite,-1, EWF_ROTATE);
}
}
else if (ewpn == 4){
Game->PlaySound(SFX_SUMMON);
for (int i=1; i<=Encount;i++){
npc en = SpawnNPC(EnID);
}
}
else if (ewpn == 8){
eweapon e = FireEWeapon(EW_SCRIPT10, Link->X+InFrontX(Link->Dir, 12), Link->Y+InFrontY(Link->Dir, 12), 0, 0, WPND, 22, SFX_BOSS_FACADE_NUKE, EWF_UNBLOCKABLE);
e->Dir = Link->Dir;
e->DrawYOffset = -1000;
SetEWeaponLifespan(e, EWL_TIMER, 1);
SetEWeaponDeathEffect(e, EWD_VANISH, 0);
for (int i=1; i<=60;i++){
if(i % 2 == 0) Screen->Rectangle(6, 0, 0, 256, 172, BOSS_FACADE_NUKE_FLASH_COLOR, 1, 0, 0, 0, true, 64);
Ghost_Waitframe(this, ghost);
}
}
else if (ewpn == 16){
ShuffleArray(cmbs);
curcmb = 0;
for (int i=1; i<=numcmbchange;i++){
while(cmbs[curcmb]<0){
if (cmbs[curcmb]<0)curcmb++;
if (curcmb>=175)break;
}
temp = cmbs[curcmb];
Screen->ComboD[temp]=changecmb;
curcmb++;
}
}
}
}
if (State==BOSS_FACADE_STATE_FIRED){
if (StateCounter == 0){
State = BOSS_FACADE_STATE_TELEPORTING_OUT;
StateCounter = 32;
}
}
if (State==BOSS_FACADE_STATE_TELEPORTING_OUT){
if(IsOdd(StateCounter)) ghost->DrawXOffset=1000;
else ghost->DrawXOffset=0;
if (StateCounter == 0){
ghost->DrawXOffset = 1000;
ghost->HitXOffset = 1000;
State = BOSS_FACADE_STATE_NOT_IN_PLANE;
StateCounter = Cond(berserk,berserkDelay,Teledelay);
}
}
StateCounter--;
if (!berserk){
if (Ghost_HP<=berserkHP){
Game->PlaySound(SFX_SUMMON);
berserk=true;
}
}
BossFacadeAnimation(ghost, OrigTile, State, 1);
if (!Ghost_Waitframe(this, ghost, false, false)){
ghost->DrawXOffset=0;
Ghost_DeathAnimation(this, ghost, GHD_EXPLODE);
Quit();
}
}
}
}
void BossFacadeAnimation(npc ghost, int origtile, int state, int numframes){
int offset = state-1;
if (offset<0)offset=0;
ghost->OriginalTile = origtile + 20*offset*ghost->TileHeight;
}
int BossFacadeFaceLink(npc ghost){
int cmb = ComboAt (CenterLinkX(), CenterLinkY());
int ghostcmb = ComboAt (Ghost_X+8, Ghost_Y+8);
if (ComboY(cmb)<ComboY(ghostcmb)) return DIR_UP;
else if (ComboY(cmb)>ComboY(ghostcmb)) return DIR_DOWN;
else if (ComboX(cmb)<ComboX(ghostcmb)) return DIR_LEFT;
else return DIR_RIGHT;
}
int BossFacadeFindSuitableSpot(npc ghost, int MinLinkDistance, bool landOK, bool wallsOK, bool waterOK, bool pitsOK){
int tileRatings[176];
int checkCombo;
int checkX;
int checkY;
int bestRating;
int bestCount;
int counter;
int choice;
int tries;
npc otherNPC;
// First, rate each tile for suitability. Lower is better,
// but negative means it's strictly off-limits.
for(int i=Screen->NumNPCs(); i>0; i--) {// Tiles too close to other enemies are undesirable
otherNPC=Screen->LoadNPC(i);
checkCombo=ComboAt(otherNPC->X, otherNPC->Y);
tileRatings[checkCombo]+=100;
if(checkCombo>15)
tileRatings[checkCombo-16]+=1;
if(checkCombo<160)
tileRatings[checkCombo+16]+=1;
if(checkCombo%16>0)
tileRatings[checkCombo-1]+=1;
if(checkCombo%16<15)
tileRatings[checkCombo+1]+=1;
}
// Mark prohibited tiles
for(int i=0; i<176; i++) {
// Screen edges in NES dungeon
if((Screen->Flags[SF_ROOMTYPE]&010b)!=0 && (i<32 || i>143 || i%16<2 || i%16>13)) tileRatings[i]=-1;
else if(IsWater(i)){// Water
if(!waterOK)tileRatings[i]=-1;
}
else if(__IsPit(i)) {// Pits
if(!pitsOK)tileRatings[i]=-1;
}
// "No enemy" flag and combos
else if(Screen->ComboF[i]==CF_NOENEMY || Screen->ComboI[i]==CF_NOENEMY ||
Screen->ComboT[i]==CT_NOENEMY || Screen->ComboT[i]==CT_NOFLYZONE ||
Screen->ComboT[i]==CT_NOJUMPZONE)
tileRatings[i]=-1;
// Too close to Link
else if(Abs(ComboX(i)-Link->X)<32 && Abs(ComboY(i)-Link->Y)<32)
tileRatings[i]+=150;
// All other combos
else {// If land is okay, but not walls (i.e. walkable only)
if(landOK && !wallsOK){
checkX=ComboX(i);
checkY=ComboY(i);
if(Screen->isSolid(checkX, checkY) ||
Screen->isSolid(checkX+8, checkY) ||
Screen->isSolid(checkX, checkY+8) ||
Screen->isSolid(checkX+8, checkY+8))
tileRatings[i]=-1;
}
// If walls are okay, but not land (i.e. unwalkable only)
else if(!landOK && wallsOK) {
checkX=ComboX(i);
checkY=ComboY(i);
if(!Screen->isSolid(checkX, checkY) ||
!Screen->isSolid(checkX+8, checkY) ||
!Screen->isSolid(checkX, checkY+8) ||
!Screen->isSolid(checkX+8, checkY+8))
tileRatings[i]=-1;
}
// Neither land nor walls are okay
else if(!landOK && !wallsOK)tileRatings[i]=-1;
}
if (tileRatings[i]>=0 && ghost->Homing>0){
tileRatings[i]+=20;
int cmb = ComboAt (CenterLinkX(), CenterLinkY());//BossFacades prefer aligning cardinally with Link
if (ComboX(i)==ComboX(cmb))tileRatings[i]-=8;
if (ComboY(i)==ComboY(cmb))tileRatings[i]-=8;
if (Distance(ComboX(i), ComboY(i), ComboX(cmb), ComboY(cmb))<MinLinkDistance)tileRatings[i]+=12;
}
}
// Find the best rating and count the number of tiles with that rating
bestRating=10000;
bestCount=0;
for(int i=0; i<176; i++){
if(tileRatings[i]<0) continue;
if(tileRatings[i]==bestRating) bestCount++;
else if(tileRatings[i]<bestRating) {
bestRating=tileRatings[i];
bestCount=1;
}
}
// The loop below might hang if every tile is unusable
if(bestCount==0) return 0;
// Pick at random from the best rated tiles
counter=Rand(bestCount)+1;
for(choice=0; counter>0; choice++) {
if(tileRatings[choice]==bestRating)counter--;
}
return choice-1;// Subtract 1 because the for loop overshot
}
//Swaps two elements in the given array
void SwapArray(int arr, int pos1, int pos2){
int r = arr[pos1];
arr[pos1]=arr[pos2];
arr[pos2]=r;
}
//Shuffles the given array like deck of playing cards
void ShuffleArray(int arr){
int size = SizeOfArray(arr)-1;
for (int i=0; i<=size*size; i++){
int r1 = Rand(size);
int r2 = Rand(size);
SwapArray(arr, r1, r2);
}
}