const int SPR_FFWIZZROBE_ENEMY_HEAL = 96;//Ssprite to display, when Wizzrobe`s healing spell hits enemy for healing.
const int SFX_FFWIZZROBE_ENEMY_HEAL = 25;//Sound to play, when Wizzrobe`s healing spell hits enemy for healing.
const int FF_WIZZROBE_DEFAULT_SUMMON_ID = 106;//Default summoned enemy ID.
//Freeform teleporting Wizzrobe. Teleports around, fires eweapons at Link
ffc script FF_Wizzrobe{
void run(int enemyID){
npc ghost = Ghost_InitAutoGhost(this, enemyID);
int MinLinkDistance = 128 - (ghost->Homing)/2;//Minimum distance to Link when teleporting
int WPND = ghost->WeaponDamage;//Magic damage
int Teledelay = Ghost_GetAttribute(ghost, 0, 120);//Delay between teleporting
int MagicHealEnemies = Ghost_GetAttribute(ghost, 1, 0);//If magic hits enemy it heals that value
int Wpn = Ghost_GetAttribute(ghost, 2, 0);//Attack type: 0 - 1 shot, 1- 4 shots orthogonally, 2-8 shots in orthogonal and diagonal directions, 3 - summon enemies, 4 - nukes the whole screen for damage anywhere!, 5 - Aimed eweapon at Link, 6 - tripled aimed eweapon.
int EWType = Ghost_GetAttribute(ghost, 3, -1);//Weapon sprite / Enemy ID / nuke flash color
int ewSound = Ghost_GetAttribute(ghost, 4, -1);// Weapon fire sound / enemy count
int WPNSPD = Ghost_GetAttribute(ghost, 5, 300);//Eweapon firing speed, in 100th of pixel per frame.
int proximitywarp = Ghost_GetAttribute(ghost, 6, 512);//Minimum Proximity distance for Wizzrobe to perform teleportation. Too close and it teleports.
int flickertime = Ghost_GetAttribute(ghost, 7, 32);//Flickering duration, in frames.
ghost->Extend=3;
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;
int ewflags=0;
int dist=0;
if (ghost->Weapon<139 || ghost->Weapon>=143)ewflags|=EWF_ROTATE;
while(true){
if (State==0){
ghost->DrawXOffset = 1000;
ghost->HitXOffset = 1000;
if (StateCounter == 0){
ghost->DrawXOffset = 0;
int combo = WizzrobeFindSuitableSpot(ghost, MinLinkDistance, true, false, false, false);
if (combo>0)Ghost_X=ComboX(combo);
if (combo>0)Ghost_Y=ComboY(combo);
State = 1;
StateCounter = flickertime;
dir = WizzrobeFaceLink(ghost);
Ghost_ForceDir(dir);
}
}
if (State==1){
if(IsOdd(StateCounter)) ghost->DrawXOffset=1000;
else ghost->DrawXOffset=0;
if (StateCounter == 0){
ghost->HitXOffset = 0;
State = 2;
StateCounter = 32;
}
}
if (State==2){
if (StateCounter == 0){
State = 3;
StateCounter = 32;
eweapon e;
if (Wpn == 0){
e = FireNonAngularEWeapon(ghost->Weapon, Ghost_X, Ghost_Y, ghost->Dir, WPNSPD, WPND, EWType,ewSound, ewflags);
}
if (MagicHealEnemies>0){
while(e->isValid()){
for (int i=1; i<=Screen->NumNPCs(); i++){
npc h = Screen->LoadNPC(i);
if (h==ghost) continue;
if (Collision(h,e)){
h->HP+=MagicHealEnemies;
Remove(e);
Game->PlaySound(SFX_FFWIZZROBE_ENEMY_HEAL);
lweapon s = CreateLWeaponAt(LW_SPARKLE, h->X, h->Y);
s->UseSprite(SPR_FFWIZZROBE_ENEMY_HEAL);
s->CollDetection=false;
}
}
Ghost_Waitframe(this, ghost);
}
}
else if (Wpn == 1){
int dirs[4]= {DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT};
Game->PlaySound(ewSound);
for (int i=0;i<SizeOfArray(dirs);i++){
e = FireNonAngularEWeapon(ghost->Weapon, Ghost_X, Ghost_Y, dirs[i], WPNSPD, WPND, EWType,0, ewflags);
}
}
else if (Wpn == 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, Ghost_X, Ghost_Y, dirs[i], WPNSPD, WPND, EWType,0, ewflags);
}
}
else if (Wpn == 3){
Game->PlaySound(SFX_SUMMON);
for (int i=1; i<=ewSound;i++){
npc en = CreateNPCAt(Cond(EWType>0,EWType,FF_WIZZROBE_DEFAULT_SUMMON_ID), Ghost_X,Ghost_Y);
}
}
else if (Wpn == 4){
e = FireEWeapon(EW_SCRIPT10, Link->X+InFrontX(Link->Dir, 12), Link->Y+InFrontY(Link->Dir, 12), 0, 0, WPND, 22, ewSound, 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, Cond(EWType>0,EWType,22), 1, 0, 0, 0, true, 64);
Ghost_Waitframe(this, ghost);
}
}
else if (Wpn == 5){
e = FireAimedEWeapon(ghost->Weapon, CenterX(ghost)-8, CenterY(ghost)-8, 0, WPNSPD, WPND, EWType, -1, ewflags);
}
else if (Wpn == 6){
e = FireAimedEWeapon(ghost->Weapon, CenterX(ghost)-8, CenterY(ghost)-8, 0, WPNSPD, WPND, EWType, -1, ewflags);
e = FireAimedEWeapon(ghost->Weapon, CenterX(ghost)-8, CenterY(ghost)-8, 0.2, WPNSPD, WPND, EWType, -1, ewflags);
e = FireAimedEWeapon(ghost->Weapon, CenterX(ghost)-8, CenterY(ghost)-8, -0.2, WPNSPD, WPND, EWType, -1, ewflags);
}
}
}
if (State==3){
if (StateCounter == 0){
dist = Distance(Link->X,Link->Y,Ghost_X,Ghost_Y);
if (dist<proximitywarp){
State = 4;
StateCounter = flickertime;
}
else StateCounter = 30;
}
}
if (State==4){
if(IsOdd(StateCounter)) ghost->DrawXOffset=1000;
else ghost->DrawXOffset=0;
if (StateCounter == 0){
ghost->DrawXOffset = 1000;
ghost->HitXOffset = 1000;
State = 0;
StateCounter = Teledelay;
}
}
StateCounter--;
Ghost_ForceDir(dir);
WizzrobeAnimation(ghost, OrigTile, State);
Ghost_Waitframe(this, ghost);
}
}
void WizzrobeAnimation(npc ghost, int origtile, int state){
int offset = 0;
if (state==2 )offset = 20*ghost->TileHeight;
if (state==3 )offset = 40*ghost->TileHeight;
ghost->OriginalTile = origtile + offset;
}
int WizzrobeFaceLink(npc ghost){
int angle = Angle(Ghost_X+Ghost_TileWidth*8-8,Ghost_Y+Ghost_TileHeight*8-8,CenterLinkX(),CenterLinkY());
return AngleDir4(angle);
}
int WizzrobeFindSuitableSpot(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());//Wizzrobes 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
}
}