const int CF_CEILING_MASTER_TRIGGER_NEXT = 98;//If Ceiling Master lands onto combo flagged with this flag, that combo will change to next in list.
const int CF_CEILING_MASTER_TRIGGER_SECRET = 99;//If Ceiling Master lands onto combo flagged with this flag, secrets will trigger.
const int CEILING_MASTER_QUAKE_THRESHOLD = 6.4;//If Ceiling Master falls too fast, it can cause screen quake.
const int CEILING_MASTER_QUAKE_POWER = 60;//Power of quake caused by falling Ceiling Master.
const int SFX_CEILING_MASTER_EAT_BAIT = 0;//Sound that plays when monster eats bait.
//Ceiling Master.
//A hand-shaped enemy that lurks on the ceiling. Follows Link, then tries to drop on him. If he grabs hero, it will either hold in place, draining HP and a counter, or tossing him at the dungeon entrance. Use Overhead combos for protection. Also certain flagged combos can change to next in list or trigger secrets, if Ceiling Master lands onto them.
//Animation: 3 frames. 1 empty frame, then 1 for open hand and 1 for closed hand.
//Step Speed and other constant movement params are used for moving on ceiling, including hunger factor.
ffc script CeilingMaster{
void run(int enemyID){
npc ghost = Ghost_InitAutoGhost(this, enemyID);
int HF = ghost->Homing;
int HR = ghost->Haltrate;
int RR = ghost->Rate;
int HNG = ghost->Hunger;
int SPD = ghost->Step;
int WPND = ghost->WeaponDamage;
int Warntime = Ghost_GetAttribute(ghost, 0, 300);//How long Ceiling Master walks on ceiling before being ready to drop, in frames.
int DropSFX = Ghost_GetAttribute(ghost, 1, 0);//Sound played on drop off ceiling.
int LandSFX = Ghost_GetAttribute(ghost, 2, 0);//Sound played on landing.
int Fallspeed = Ghost_GetAttribute(ghost, 3, 0);//Drop speed. 0 - default gravity and acceleration. >CEILING_MASTER_QUAKE_THRESHOLD - causes quake on landing.
int chargetime = Ghost_GetAttribute(ghost, 4, 0);//Time (in frames) between stopping moving on ceiling and falling.
int counterdrain = Ghost_GetAttribute(ghost, 5, 0);//Counter to drain on grabbing hero. <0 - send him to dungeon entrance.
int countertime = Ghost_GetAttribute(ghost, 6, 30);//Interval (in frames) between counter drains.
int countercost = Ghost_GetAttribute(ghost, 7, 0);//Counter amount to drain on each timer cycle.
int InitZ = Ghost_GetAttribute(ghost, 8, 128);//Z position of enemy when it moves on ceiling.
int RiseSpeed = Ghost_GetAttribute(ghost,9, SPD);//Rising speed after fall.
ghost->Extend=3;
Ghost_SetFlag(GHF_NORMAL);
Ghost_SetFlag(GHF_NO_FALL);
Ghost_SetFlag(GHF_IGNORE_ALL_TERRAIN);
Ghost_SetFlag(GHF_FLYING_ENEMY);
Ghost_UnsetFlag(GHF_KNOCKBACK);
int OrigTile = ghost->OriginalTile;
int State = 0;
Ghost_Z = InitZ;
int timer = Warntime;
int cmb = -1;
int haltcounter = -1;
int defs[18];
Ghost_StoreDefenses(ghost,defs);
ghost->CollDetection = false;
while(true){
if (State==0){
if (HF>=255) Ghost_MoveTowardLink(SPD/100, 2);
else haltcounter = Ghost_ConstantWalk4(haltcounter, SPD, RR, HF, HNG);
timer--;
if (timer<=0){
State=1;
timer=chargetime;
}
}
else if (State==1){
timer--;
if (timer<=0){
Game->PlaySound(DropSFX);
State=2;
if (Fallspeed==0) Ghost_UnsetFlag(GHF_NO_FALL);
}
}
else if (State==2){
Ghost_Z-=Fallspeed/100;
if (Ghost_Z<=0){
Game->PlaySound(LandSFX);
if (Fallspeed>CEILING_MASTER_QUAKE_THRESHOLD)Screen->Quake+=CEILING_MASTER_QUAKE_POWER;
cmb = ComboAt(CenterX(ghost), CenterY(ghost));
if (ComboFI(cmb,CF_CEILING_MASTER_TRIGGER_NEXT)){
Screen->ComboF[cmb]=0;
Screen->ComboD[cmb]++;
}
if (ComboFI(cmb,CF_CEILING_MASTER_TRIGGER_SECRET)&&!Screen->State[ST_SECRET]){
Screen->ComboD[cmb]++;
Game->PlaySound(SFX_SECRET);
Screen->TriggerSecrets();
Screen->State[ST_SECRET]=true;
}
if (HNG>=3){
for (int i=1; i<=Screen->NumLWeapons(); i++){
lweapon b = Screen->LoadLWeapon(i);
if (b->ID!=LW_BAIT) continue;
if (!Collision(b, ghost)) continue;
Game->PlaySound(SFX_CEILING_MASTER_EAT_BAIT);
Remove(b);
break;
}
}
if (LinkCollision(ghost) && Screen->ComboT[cmb]!=CT_OVERHEAD){
Game->PlaySound(SFX_OUCH);
Link->HP-=ghost->Damage;
Ghost_X=Link->X;
Ghost_Y=Link->Y;
if (counterdrain<0){
timer=60;
State=4;
}
else{
ghost->CollDetection = true;
timer = countertime;
State=5;
}
}
else{
if (Screen->ComboT[cmb]!=CT_OVERHEAD) ghost->CollDetection = true;
timer=120;
State=3;
}
}
}
else if (State==3){
if (timer>0)timer--;
else{
ghost->CollDetection = false;
Ghost_SetFlag(GHF_NO_FALL);
Ghost_Z+=RiseSpeed/100;
if (Ghost_Z>=InitZ){
Ghost_Z=InitZ;
timer = Warntime;
State=0;
}
}
}
else if (State==4){
NoAction();
Link->Action=LA_WALKING;
Link->Jump=0;
if (timer>0)timer--;
else{
ghost->CollDetection = false;
Ghost_SetFlag(GHF_NO_FALL);
Ghost_Z+=RiseSpeed/100;
Link->Z=Ghost_Z;
if (Ghost_Z>=InitZ){
Link->Warp(Game->LastEntranceDMap, Game->LastEntranceScreen);
}
}
}
else if (State==5){
Link->X=ghost->X;
Link->Y=ghost->Y;
timer--;
if (timer<=0){
if (Game->Counter[counterdrain]>=countercost)Game->Counter[counterdrain]-=countercost;
else Game->Counter[counterdrain]=0;
timer = countertime;
}
}
Animation(ghost, OrigTile, State, 2);
Ghost_Waitframe(this, ghost);
}
}
}
void Animation(npc ghost, int origtile, int state, int numframes){
int offset = 1;
if (state==4||state==5)offset=2;
ghost->OriginalTile = origtile + offset;
}