const int BOSS_SLIME_WALL_STATE_TELEPORTING_IN = 1; const int BOSS_SLIME_WALL_STATE_TELEPORTING_OUT = 4; const int BOSS_SLIME_WALL_STATE_CHARGING = 2; const int BOSS_SLIME_WALL_STATE_FIRED = 3; const int BOSS_SLIME_WALL_STATE_NOT_IN_PLANE = 0; const int SFX_BOSS_SLIME_WALL_NUKE = 37;//Sound to play, when SlimeWall casts nuclear flash spell. const int BOSS_SLIME_WALL_NUKE_FLASH_COLOR = 1;//Color used for nuke flash const int CF_CHANGEABLE_COMBO = 98;//Combo Flag to define combos that can be changed. //Eye for Slime wall boss. A huge wall of red stuff that is really painful to touch. An eye teleports inside blob mass, firing eweapons at Link, spawning enemies. //When eye`s HP is low enough, the blob expands ala Demon Wall in attewmpt to fill screen completely. killing eye destroys entire Slime Wall and all summons. //1. Compile the script. 2 FFC slots used. //2. Set up BossSlimeWall ghosted enemy script for eye itself ////Animation - 4 sequences of frames set vertically: teleporting in, charging, firing, teleporting out. ////Attribute 1: Delay between teleporting ////Attribute 2: Enemy size, X*X square. ////Attribute 3: 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! ////Attribute 4: Eweapon sprite ////Attribute 5: Enemy count for enemy spawning attack ////Attribute 6: Berserk HP threshold ////Attribute 7: Delay between teleporting, if boss went berserk ////Attribute 8: Enemy ID for enemy spawning attack //3. Set up 3 consecutive combos: 1 for edge of slime wall, then damaging slime innards then another slime innards combo with inherent flag #97 2nd combo must cycle into 3rd combo. //4. Screen: place FFC with SlimeWallBody script where slime wall edge will be. And put 1 eye enemy. //D0 - slot used by eye enemy. //D1 - Slime wall facing direction, floor that on opposite direction must be filled with 3rd combo from step 3 (all combos above FFC, if D1 is DIR_DOWN etc.), while the rest of arena must have NO_FLYING_ENEMIES combo type to avoid eye spawning outside blob wall. //D2 - When The Blob goes berserk once, he starts moving st that speed. //D3 - HP threshold for blob expanding //D4 - When HP falls below that threshold, blob expanding speed increases //D5 - Blob expanding speed MULTIPLIER when eye HP is below D4 ffc script BossSlimeWall{ 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, 1);//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! 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);//Unused int changecmb = Ghost_GetAttribute(ghost, 9, 0);//Unused 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); Ghost_SetFlag(GHF_FLYING_ENEMY); int OrigTile = ghost->OriginalTile; int State = 0; int haltcounter = -1; int StateCounter =Teledelay; int dir = Ghost_Dir; int ewpn = 0; int combo = 0; bool berserk=false; while(true){ if (State==BOSS_SLIME_WALL_STATE_NOT_IN_PLANE){ ghost->DrawXOffset = 1000; ghost->HitXOffset = 1000; if (StateCounter == 0){ ghost->DrawXOffset = 0; ghost->HitXOffset = 0; combo = FindSpawnPoint(true, false, false, false); if (combo>0)Ghost_X=ComboX(combo); if (combo>0)Ghost_Y=ComboY(combo); State = BOSS_SLIME_WALL_STATE_TELEPORTING_IN; StateCounter = 32; dir = SlimeWallFaceLink(ghost); } } if (State==BOSS_SLIME_WALL_STATE_TELEPORTING_IN){ // if(IsOdd(StateCounter)) ghost->DrawXOffset=StateCounter/4; // else ghost->DrawXOffset=-StateCounter/4; if (StateCounter == 0){ State = BOSS_SLIME_WALL_STATE_CHARGING; StateCounter = 32; } } if (State==BOSS_SLIME_WALL_STATE_CHARGING){ if (StateCounter == 0){ State = BOSS_SLIME_WALL_STATE_FIRED; StateCounter = 32; eweapon e; ewpn =1<<( Rand(5)); if ((ewpn&Wpn)==0){ e = FireAimedEWeapon(ghost->Weapon, Ghost_X, Ghost_Y, 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, Ghost_X, Ghost_Y, 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, Ghost_X, Ghost_Y, dirs[i], 300, WPND, ewsprite,-1, EWF_ROTATE); } } else if (ewpn == 4){ Game->PlaySound(SFX_SUMMON); for (int i=1; i<=Encount;i++){ combo = SlimeWallFindSuitableSpot(ghost, MinLinkDistance,true, false, false, false); npc en = CreateNPCAt(EnID,ComboX(combo), ComboY(combo)); en->Z=128; TraceNL(); } } 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_SLIME_WALL_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_SLIME_WALL_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_SLIME_WALL_STATE_FIRED){ if (StateCounter == 0){ State = BOSS_SLIME_WALL_STATE_TELEPORTING_OUT; StateCounter = 32; } } if (State==BOSS_SLIME_WALL_STATE_TELEPORTING_OUT){ if(IsOdd(StateCounter)) ghost->DrawXOffset=1000; else ghost->DrawXOffset=0; if (StateCounter == 0){ ghost->DrawXOffset = 1000; ghost->HitXOffset = 1000; State = BOSS_SLIME_WALL_STATE_NOT_IN_PLANE; StateCounter = Cond(berserk,berserkDelay,Teledelay); } } StateCounter--; if (!berserk){ if (Ghost_HP<=berserkHP){ Game->PlaySound(SFX_SUMMON); berserk=true; } } SlimeWallAnimation(ghost, OrigTile, State, 1); if (!Ghost_Waitframe(this, ghost, false, false)){ ghost->DrawXOffset=0; Ghost_DeathAnimation(this, ghost, GHD_EXPLODE); Quit(); } } } } void SlimeWallAnimation(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 SlimeWallFaceLink(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 SlimeWallFindSuitableSpot(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++){ if((Screen->Flags[SF_ROOMTYPE]&010b)!=0 && (i<32 || i>143 || i%16<2 || i%16>13)) tileRatings[i]=-1; // Screen edges in NES dungeon else if(IsWater(i)){// Water if(!waterOK)tileRatings[i]=-1; } else if(__IsPit(i)){// Pits if(!pitsOK)tileRatings[i]=-1; } else if(Screen->ComboF[i]==CF_NOENEMY || Screen->ComboI[i]==CF_NOENEMY || Screen->ComboT[i]==CT_NOENEMY || Screen->ComboT[i]==CT_NOJUMPZONE)tileRatings[i]=-1; // "No enemy" flag and combos else if(Abs(ComboX(i)-Link->X)<32 && Abs(ComboY(i)-Link->Y)<32) tileRatings[i]+=150;// Too close to Link else{ // All other combos if(landOK && !wallsOK){// If land is okay, but not walls (i.e. walkable only) if (Screen->ComboS[i]>0) tileRatings[i]=-1; } else if(!landOK && wallsOK){ // If walls are okay, but not land (i.e. unwalkable only) if(Screen->ComboS[i]<15) tileRatings[i]=-1; } else if(!landOK && !wallsOK)tileRatings[i]=-1; // Neither land nor walls are okay } if (tileRatings[i]>=0){ tileRatings[i]+=20; if (DamageComboPower(i)>0) tileRatings[i]+=1200; int cmb = ComboAt (CenterLinkX(), CenterLinkY()); if (Distance(ComboX(i), ComboY(i), ComboX(cmb), ComboY(cmb))<MinLinkDistance)tileRatings[i]+=12; } } bestRating=10000;// Find the best rating and count the number of tiles with that rating 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; } } if(bestCount==0)return 0; // The loop below might hang if every tile is unusable counter=Rand(bestCount)+1;// Pick at random from the best rated tiles 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); } } const int SFX_SLIMEWALL_DEATH = 3; const int SPR_SLIMEWALL_DEATH = 92; ffc script SlimeWallBody{ void run(int npcslot, int dir, int speed, int threshold1, int threshold2, int speed2){ for(int i=0;i<4;i++){ Screen->FastCombo(0, this->X, this->Y, this->Data, this->CSet, OP_OPAQUE); if (dir==DIR_UP){Screen->FastCombo(0, this->X, this->Y+16, this->Data+2, this->CSet, OP_OPAQUE);} if (dir==DIR_DOWN){Screen->FastCombo(0, this->X, this->Y-16, this->Data+2, this->CSet, OP_OPAQUE);} if (dir==DIR_LEFT){Screen->FastCombo(0, this->X-16, this->Y, this->Data+2, this->CSet, OP_OPAQUE);} if (dir==DIR_RIGHT){Screen->FastCombo(0, this->X+16, this->Y, this->Data+2, this->CSet, OP_OPAQUE);} if (dir>1){ for (int i=1; i<16; i++){ if (!Screen->isSolid(this->X, this->Y-16*i))Screen->FastCombo(0, this->X, this->Y-16*i, this->Data, this->CSet, OP_OPAQUE); if (!Screen->isSolid(this->X, this->Y+16*i))Screen->FastCombo(0, this->X, this->Y+16*i, this->Data, this->CSet, OP_OPAQUE); if (!Screen->isSolid(this->X, this->Y-16*i))Screen->FastCombo(0, this->X+Cond(dir==DIR_LEFT,16,-16), this->Y-16*i, this->Data+2, this->CSet, OP_OPAQUE); if (!Screen->isSolid(this->X, this->Y+16*i))Screen->FastCombo(0, this->X+Cond(dir==DIR_LEFT,16,-16), this->Y+16*i, this->Data+2, this->CSet, OP_OPAQUE); } } else{ for (int i=1; i<16; i++){ if (!Screen->isSolid(this->X-16*i, this->Y))Screen->FastCombo(0, this->X-16*i, this->Y, this->Data, this->CSet, OP_OPAQUE); if (!Screen->isSolid(this->X+16*i, this->Y))Screen->FastCombo(0, this->X+16*i, this->Y, this->Data, this->CSet, OP_OPAQUE); if (!Screen->isSolid(this->X-16*i, this->Y))Screen->FastCombo(0, this->X-16*i, this->Y+Cond(dir==DIR_UP,16,-16), this->Data+2, this->CSet, OP_OPAQUE); if (!Screen->isSolid(this->X+16*i, this->Y))Screen->FastCombo(0, this->X+16*i, this->Y+Cond(dir==DIR_UP,16,-16), this->Data+2, this->CSet, OP_OPAQUE); } } Waitframe(); } npc en = Screen->LoadNPC(npcslot); int drawx = 0; int drawy = 0; if (!en->isValid()){ Game->PlaySound(SFX_GANON); Quit(); } int moving = 0; lweapon explosion; while(true){ if (en->HP<=0){ this->Vx=0; this->Vy=0; for (int i=0;i<176;i++){ if (Screen->ComboD[i]!=this->Data+1 && Screen->ComboD[i]!=this->Data+2)continue; Screen->ComboD[i]=Screen->UnderCombo; Screen->ComboC[i]=Screen->UnderCSet; explosion=Screen->CreateLWeapon(LW_SPARKLE); explosion->X=ComboX(i); explosion->Y=ComboY(i); explosion->UseSprite(SPR_SLIMEWALL_DEATH); explosion->CollDetection=false; } Game->PlaySound(SFX_SLIMEWALL_DEATH); this->Data=0; Quit(); } if (moving==0){ if (en->HP<=threshold1){ if (dir==DIR_UP){ this->Vy = -speed; } if (dir==DIR_DOWN){ this->Vy = speed; } if (dir==DIR_LEFT){ this->Vx = -speed; } if (dir==DIR_RIGHT){ this->Vy = -speed; } moving=1; } } else if (moving==1){ if (en->HP<=threshold2){ if (dir==DIR_UP){ this->Vy *= speed2; } if (dir==DIR_DOWN){ this->Vy *= speed2; } if (dir==DIR_LEFT){ this->Vx *= -speed2; } if (dir==DIR_RIGHT){ this->Vy *= -speed2; } for (int i=1;i<=32;i++){ ffc f =Screen->LoadFFC(i); if (!f->Flags[FFCF_CHANGER])continue; if (f->Data!=this->Data)continue; f->Vx*=speed2; f->Vy*=speed2; } moving=2; } } for (int i=0;i<176;i++){ if (Screen->ComboS[i]>0) continue; // if (Screen->ComboD[i]==this->Data+1 || Screen->ComboD[i]==this->Data+2) continue; if (dir==DIR_UP){ if (ComboY(i)>=this->Y+16){ if (Screen->ComboD[i]!=this->Data+1 && Screen->ComboD[i]!=this->Data+2){ Screen->ComboD[i]=(this->Data)+1; Screen->ComboC[i]=this->CSet; } } else{ if (Screen->ComboD[i]==this->Data+1 || Screen->ComboD[i]==this->Data+2){ Screen->ComboD[i]=Screen->UnderCombo; Screen->ComboC[i]=Screen->UnderCSet; } } } if (dir==DIR_DOWN){ if (ComboY(i)<=this->Y-16){ if (Screen->ComboD[i]!=this->Data+1 && Screen->ComboD[i]!=this->Data+2){ Screen->ComboD[i]=(this->Data)+1; Screen->ComboC[i]=this->CSet; } } else{ if (Screen->ComboD[i]==this->Data+1 || Screen->ComboD[i]==this->Data+2){ Screen->ComboD[i]=Screen->UnderCombo; Screen->ComboC[i]=Screen->UnderCSet; } } } if (dir==DIR_LEFT){ if (ComboX(i)>=this->X+16){ if (Screen->ComboD[i]!=this->Data+1 && Screen->ComboD[i]!=this->Data+2){ Screen->ComboD[i]=(this->Data)+1; Screen->ComboC[i]=this->CSet; } } else{ if (Screen->ComboD[i]==this->Data+1 || Screen->ComboD[i]==this->Data+2){ Screen->ComboD[i]=Screen->UnderCombo; Screen->ComboC[i]=Screen->UnderCSet; } } } if (dir==DIR_RIGHT){ if (ComboX(i)<=this->X-16){ if (Screen->ComboD[i]!=this->Data+1 && Screen->ComboD[i]!=this->Data+2){ Screen->ComboD[i]=(this->Data)+1; Screen->ComboC[i]=this->CSet; } } else{ if (Screen->ComboD[i]==this->Data+1 || Screen->ComboD[i]==this->Data+2){ Screen->ComboD[i]=Screen->UnderCombo; Screen->ComboC[i]=Screen->UnderCSet; } } } } Screen->FastCombo(0, this->X, this->Y, this->Data, this->CSet, OP_OPAQUE); if (dir==DIR_UP){Screen->FastCombo(0, this->X, this->Y+16, this->Data+2, this->CSet, OP_OPAQUE); } if (dir==DIR_DOWN){Screen->FastCombo(0, this->X, this->Y-16, this->Data+2, this->CSet, OP_OPAQUE); } if (dir==DIR_LEFT){Screen->FastCombo(0, this->X-16, this->Y, this->Data+2, this->CSet, OP_OPAQUE); } if (dir==DIR_RIGHT){Screen->FastCombo(0, this->X+16, this->Y, this->Data+2, this->CSet, OP_OPAQUE); } if (dir>1){ for (int i=1; i<16; i++){ if (!Screen->isSolid(this->X, this->Y-16*i))Screen->FastCombo(0, this->X, this->Y-16*i, this->Data, this->CSet, OP_OPAQUE); if (!Screen->isSolid(this->X, this->Y+16*i))Screen->FastCombo(0, this->X, this->Y+16*i, this->Data, this->CSet, OP_OPAQUE); if (!Screen->isSolid(this->X, this->Y-16*i))Screen->FastCombo(0, this->X+Cond(dir==DIR_LEFT,16,-16), this->Y-16*i, this->Data+2, this->CSet, OP_OPAQUE); if (!Screen->isSolid(this->X, this->Y+16*i))Screen->FastCombo(0, this->X+Cond(dir==DIR_LEFT,16,-16), this->Y+16*i, this->Data+2, this->CSet, OP_OPAQUE); } } else{ for (int i=1; i<16; i++){ if (!Screen->isSolid(this->X-16*i, this->Y))Screen->FastCombo(0, this->X-16*i, this->Y, this->Data, this->CSet, OP_OPAQUE); if (!Screen->isSolid(this->X+16*i, this->Y))Screen->FastCombo(0, this->X+16*i, this->Y, this->Data, this->CSet, OP_OPAQUE); if (!Screen->isSolid(this->X-16*i, this->Y))Screen->FastCombo(0, this->X-16*i, this->Y+Cond(dir==DIR_UP,16,-16), this->Data+2, this->CSet, OP_OPAQUE); if (!Screen->isSolid(this->X+16*i, this->Y))Screen->FastCombo(0, this->X+16*i, this->Y+Cond(dir==DIR_UP,16,-16), this->Data+2, this->CSet, OP_OPAQUE); } } // debugValue(1, this->Data+1); Waitframe(); } } } //Returns power of damage combo at given coordinates, or 0, it`s not a damage combo. int DamageComboPower(int cmb){ if (Screen->ComboT[cmb] == CT_DAMAGE1) return 2; else if (Screen->ComboT[cmb] == CT_DAMAGE2) return 4; else if (Screen->ComboT[cmb] == CT_DAMAGE3) return 8; else if (Screen->ComboT[cmb] == CT_DAMAGE4) return 16; else if (Screen->ComboT[cmb] == CT_DAMAGE5) return 32; else if (Screen->ComboT[cmb] == CT_DAMAGE6) return 64; else if (Screen->ComboT[cmb] == CT_DAMAGE7) return 128; else return 0; }