Copy to Clipboard Test

Big Tektite Code

//import "std.zh"
//import "string.zh"
//import "ghost.zh"

// Idle settings
const int BTEK_MIN_IDLE_TIME = 45;
const int BTEK_MAX_IDLE_TIME = 90;
const int BTEK_MIN_FIRE_TIME = 25;
const int BTEK_MAX_FIRE_TIME = 60;

// Regular jump settings
const int BTEK_JUMP_CHARGE_TIME = 15;
const float BTEK_MIN_JUMPS_FACTOR = 0.5; // Used with halt rate
const float BTEK_MAX_JUMPS_FACTOR = 1.5; // Used with halt rate
const float BTEK_MIN_JUMP_VEL = 2;
const float BTEK_MAX_JUMP_VEL = 4.5;

// Super jump settings
const int BTEK_SJUMP_CHARGE_TIME = 60;
const float BTEK_SJUMP_DAMAGE_MULTIPLIER = 2;
const float BTEK_SJUMP_SPEED = 8;
const int BTEK_SJUMP_MIN_TIME = 240;
const int BTEK_SJUMP_MAX_TIME = 360;
const int BTEK_SJUMP_QUAKE_TIME = 90;

// Super jump - launched debris
const int BTEK_EW_DEBRIS = 31; // Also applies to dropped debris
const int BTEK_MIN_DEBRIS_LAUNCHED = 8;
const int BTEK_MAX_DEBRIS_LAUNCHED = 12;
const int BTEK_MIN_DEBRIS_STEP = 100;
const int BTEK_MAX_DEBRIS_STEP = 200;
const float BTEK_MIN_DEBRIS_VEL = 1.5;
const float BTEK_MAX_DEBRIS_VEL = 2.5;

// Super jump - falling objects
const int BTEK_ENEMY_LIMIT = 10;
const int BTEK_MIN_ENEMIES_DROPPED = 4; // Fewer will be used if near the limit
const int BTEK_MAX_ENEMIES_DROPPED = 5;
const int BTEK_MIN_DEBRIS_DROPPED = 10;
const int BTEK_MAX_DEBRIS_DROPPED = 15;

// Default settings (can be overridden by attributes)
const int BTEK_DEFAULT_SIZE = 2;
const int BTEK_DEFAULT_DEBRIS_SPRITE = 18;
const int BTEK_DEFAULT_STUN_TIME = 90;
const int BTEK_DEFAULT_DEBRIS_DAMAGE = 4; // Overridden by weapon damage


// npc->Attributes[] indices
const int BTEK_ATTR_SIZE = 0;
const int BTEK_ATTR_FLAGS = 1;
const int BTEK_ATTR_STUN_TIME = 2;
const int BTEK_ATTR_DROPPED_OBJECT = 3;
const int BTEK_ATTR_SFX_JUMP = 4;
const int BTEK_ATTR_SFX_SJUMP = 5;
const int BTEK_ATTR_SFX_FALL = 6;
const int BTEK_ATTR_SFX_LAND = 7;
const int BTEK_ATTR_DEBRIS_SPRITE = 8;
const int BTEK_ATTR_DEATH_TYPE = 9;

// ffc->Misc[] indices
const int BTEK_IDX_BASE_COMBO = 0;
const int BTEK_IDX_LINK_STUN_TIME = 1;

// Combo offsets
const int BTEK_CMB_IDLE = 0;
const int BTEK_CMB_CROUCH = 1;
const int BTEK_CMB_JUMP = 2;
const int BTEK_CMB_STUN = 3;

// Flags
const int BTEK_FLAG_FREEZABLE =    00000001b; // 1
const int BTEK_FLAG_NO_SJUMP =     00000010b; // 2
const int BTEK_FLAG_STUN_LINK =    00000100b; // 4
const int BTEK_FLAG_DEBRIS =       00001000b; // 8
const int BTEK_FLAG_DROP_OBJECTS = 00010000b; // 16
const int BTEK_FLAG_STUN_TO_HIT =  00100000b; // 32
const int BTEK_FLAG_UNBLOCKABLE =  01000000b; // 64
const int BTEK_FLAG_INTRO =        10000000b; // 128

ffc script BigTektite
{
    void run(int enemyID)
    {
        npc ghost=Ghost_InitAutoGhost(this, enemyID);
        this->Flags[FFCF_OVERLAY]=true;
        Ghost_SetFlag(GHF_IGNORE_ALL_TERRAIN);
        if((ghost->Attributes[BTEK_ATTR_FLAGS]&BTEK_FLAG_FREEZABLE)!=0)
        {
            Ghost_SetFlag(GHF_STUN);
            Ghost_SetFlag(GHF_CLOCK);
        }
        
        this->Misc[BTEK_IDX_BASE_COMBO]=Ghost_Data;
        
        int defenses[18];
        if((ghost->Attributes[BTEK_ATTR_FLAGS]&BTEK_FLAG_STUN_TO_HIT)!=0)
        {
            Ghost_StoreDefenses(ghost, defenses);
            Ghost_SetAllDefenses(ghost, NPCDT_BLOCK);
        }
        
        Ghost_TileWidth=Ghost_GetAttribute(ghost, BTEK_ATTR_SIZE, BTEK_DEFAULT_SIZE, 1, 4);
        Ghost_TileHeight=Ghost_TileWidth;
        if(Ghost_TileHeight>1)
            Ghost_SetHitOffsets(ghost, 0.33, 0.1, 0, 0);
            
        if((ghost->Attributes[BTEK_ATTR_FLAGS]&BTEK_FLAG_INTRO)!=0)
            DoIntro(this, ghost);
        else if(Ghost_Z==0)
            Ghost_SpawnAnimationPuff(this, ghost);
        else // Dropping from the ceiling?
        {
            while(Ghost_Z>0)
                BTWaitframe(this, ghost);
        }
        
        int numJumps;
        
        while(true)
        {
            // The for loop doesn't make much sense if super jumps are disabled,
            // but whatever...
            for(int i=Rand(3, 5); i>0; i--)
            {
                Idle(this, ghost);
                
                // Number of successive jumps is based on halt rate
                numJumps=Rand((16-ghost->Haltrate)*BTEK_MIN_JUMPS_FACTOR,
                              (16-ghost->Haltrate)*BTEK_MAX_JUMPS_FACTOR);
                if(numJumps<1)
                    numJumps=1;
                for(; numJumps>0; numJumps--)
                    Jump(this, ghost);
            }
            
            if((ghost->Attributes[BTEK_ATTR_FLAGS]&BTEK_FLAG_NO_SJUMP)==0)
            {
                Idle(this, ghost);
                SuperJump(this, ghost, defenses);
            }
        }
    }
    
    // ========== Idle ==========
    
    // Just stand in place, maybe shooting fireballs or something.
    void Idle(ffc this, npc ghost)
    {
        int idleTime=Rand(BTEK_MIN_IDLE_TIME, BTEK_MAX_IDLE_TIME);
        int fireTime=Rand(BTEK_MIN_FIRE_TIME, BTEK_MAX_FIRE_TIME);
        
        Ghost_Data=this->Misc[BTEK_IDX_BASE_COMBO]+BTEK_CMB_IDLE;
        
        // Wait until timer counts down or the tektite is hit
        for(; idleTime>0 && !Ghost_GotHit(); idleTime--)
        {
            fireTime--;
            if(fireTime==0)
            {
                FireIdleWeapon(ghost);
                fireTime=Rand(BTEK_MIN_FIRE_TIME, BTEK_MAX_FIRE_TIME);
            }
            
            BTWaitframe(this, ghost);
        }
    }
    
    // Fire any weapons used while standing on the ground
    void FireIdleWeapon(npc ghost)
    {
        int weaponID=WeaponTypeToID(ghost->Weapon);
        int flags=0;
        if((ghost->Attributes[BTEK_ATTR_FLAGS]&BTEK_FLAG_UNBLOCKABLE)!=0)
            flags|=EWF_UNBLOCKABLE;
            
	    // Angular weapons
	    if(weaponID==EW_FIREBALL || weaponID==EW_BRANG)
	    {
	        FireAimedEWeapon(weaponID, BTCenterX()-8, BTCenterY()-8,
	                         0, 200, ghost->WeaponDamage, -1, -1, flags);
        }
        
        // Fireball 2: 3-way
        else if(weaponID==EW_FIREBALL2)
        {
            eweapon fb;
            fb=FireAimedEWeapon(weaponID, BTCenterX()-8, BTCenterY()-8,
	                            0, 200, ghost->WeaponDamage, -1, -1, flags);
	        SetEWeaponMovement(fb, EWM_DRIFT_WAIT, DIR_UP, 0);
	        
	        fb=FireAimedEWeapon(weaponID, BTCenterX()-8, BTCenterY()-8,
                                0, 200, ghost->WeaponDamage, -1, -1, flags);
            SetEWeaponMovement(fb, EWM_DRIFT_WAIT, DIR_UP, 0.5);
            
	        fb=FireAimedEWeapon(weaponID, BTCenterX()-8, BTCenterY()-8,
                                0, 200, ghost->WeaponDamage, -1, -1, flags);
            SetEWeaponMovement(fb, EWM_DRIFT_WAIT, DIR_DOWN, 0.5);
        }
        
        // Non-angular weapons
        else if(weaponID==EW_WIND || weaponID==EW_ARROW || weaponID==EW_BEAM ||
                weaponID==EW_ROCK || weaponID==EW_MAGIC)
        {
            if(!(weaponID==EW_WIND || weaponID==EW_ROCK))
                flags|=EWF_ROTATE;
            
            int dir=AngleDir4(Angle(BTCenterX(), BTCenterY(),
                                    Link->X+8, Link->Y+8));
            FireNonAngularEWeapon(weaponID, BTCenterX()-8, BTCenterY()-8,
                                  dir, 200, ghost->WeaponDamage, -1, -1, flags);
        }
    }
    
    // ========== Jumping ==========
    
    void Jump(ffc this, npc ghost)
    {
        float jumpHeight;
        float angle;
        lweapon bait;
        
        // Find bait if there's any chance the tektite will go for it
        if(ghost->Hunger>0)
            bait=LoadLWeaponOf(LW_BAIT);
        
        // Charge for a moment
        Ghost_Data=this->Misc[BTEK_IDX_BASE_COMBO]+BTEK_CMB_CROUCH;
        
        for(int i=0; i<BTEK_JUMP_CHARGE_TIME; i++)
            BTWaitframe(this, ghost);
        
        // Decide where to jump
        
        // Go for bait?
        if(Rand(4)<ghost->Hunger && bait->isValid())
        {
            float dist=BTDistanceTo(bait->X+8, bait->Y+8);
            jumpHeight=GH_GRAVITY*dist/(ghost->Step/50);
            angle=Angle(BTCenterX(), BTCenterY(), bait->X+8, bait->Y+8);
        }
        
        // Try to hit Link?
        else if(Rand(256)<ghost->Homing)
        {
            // A rough approximation, but it works pretty well.
            float dist=BTDistanceTo(Link->X+8, Link->Y+8);
            jumpHeight=GH_GRAVITY*dist/(ghost->Step/50);
            angle=Angle(BTCenterX(), BTCenterY(), Link->X+8, Link->Y+8);
        }
        
        // Jump randomly
        else
        {
            jumpHeight=Rand(BTEK_MIN_JUMP_VEL, BTEK_MAX_JUMP_VEL);
            angle=Rand(360);
        }
        
        // In case the target wasn't actually in range...
        jumpHeight=Clamp(jumpHeight, BTEK_MIN_JUMP_VEL, BTEK_MAX_JUMP_VEL);
        
        // Jump
        Ghost_Data=this->Misc[BTEK_IDX_BASE_COMBO]+BTEK_CMB_JUMP;
        Game->PlaySound(ghost->Attributes[BTEK_ATTR_SFX_JUMP]);
        Ghost_Jump=jumpHeight;
        Ghost_Vx=VectorX(ghost->Step/100, angle);
        Ghost_Vy=VectorY(ghost->Step/100, angle);
        FireJumpingWeapon(ghost);
        
        // And then just wait to land
        do
        {
            BounceOffScreenEdges();
            BTWaitframe(this, ghost);
        } while(Ghost_Z>0);
        
        FireLandingWeapon(ghost);
        Ghost_Vx=0;
        Ghost_Vy=0;
    }
    
    // Leave a weapon behind when jumping (bomb, super bomb, fire trail)
    void FireJumpingWeapon(npc ghost)
    {
        eweapon wpn;
        
        if(ghost->Weapon==WPN_ENEMYLITBOMB)
        {
            // Always unblockable
            wpn=FireNonAngularEWeapon(EW_BOMB, BTCenterX()-8, BTCenterY()-8,
                                      0, 0, 0, -1, -1, EWF_UNBLOCKABLE);
            SetEWeaponLifespan(wpn, EWL_TIMER, 30);
            SetEWeaponDeathEffect(wpn, EWD_EXPLODE, ghost->WeaponDamage);
        }
        else if(ghost->Weapon==WPN_ENEMYLITSBOMB)
        {
            wpn=FireNonAngularEWeapon(EW_SBOMB, BTCenterX()-8, BTCenterY()-8,
                                      0, 0, 0, -1, -1, EWF_UNBLOCKABLE);
            SetEWeaponLifespan(wpn, EWL_TIMER, 30);
            SetEWeaponDeathEffect(wpn, EWD_SBOMB_EXPLODE, ghost->WeaponDamage);
        }
        else if(ghost->Weapon==WPN_ENEMYFIRETRAIL)
        {
            // 1x1: Single flame
            if(Ghost_TileWidth==1)
            {
                wpn=FireNonAngularEWeapon(EW_FIRETRAIL, Ghost_X, Ghost_Y,
                                          0, 0, ghost->WeaponDamage, -1, -1, EWF_UNBLOCKABLE);
                SetEWeaponLifespan(wpn, EWL_TIMER, 30);
                SetEWeaponDeathEffect(wpn, EWD_VANISH, 0);
            }
            
            // 2x2 or bigger: One on each tile except on the top row
            else
            {
                for(int row=1; row<Ghost_TileHeight; row++)
                {
                    for(int col=0; col<Ghost_TileWidth; col++)
                    {
                        wpn=FireNonAngularEWeapon(EW_FIRETRAIL,
                                                  Ghost_X+16*col, Ghost_Y+16*row,
                                                  0, 0, ghost->WeaponDamage,
                                                  -1, -1, EWF_UNBLOCKABLE);
                        SetEWeaponLifespan(wpn, EWL_TIMER, 60);
                        SetEWeaponDeathEffect(wpn, EWD_VANISH, 0);
                    }
                }
            }
        }
    }
    
    // Use weapons when landing (fire, explosion)
    void FireLandingWeapon(npc ghost)
    {
        int weaponID;
        
        // Explosions are disabled for other-type enemies in 2.50.
        // This should work in 2.50.1, though...
        if(ghost->Weapon==WPN_ENEMYBOMB || ghost->Weapon==WPN_ENEMYSBOMB)
        {
            eweapon wpn;
            
            if(ghost->Weapon==WPN_ENEMYBOMB)
                weaponID=EW_BOMBBLAST;
            else
                weaponID=EW_SBOMBBLAST;
            
            wpn=Screen->CreateEWeapon(weaponID);
            wpn->Damage=ghost->WeaponDamage;
            wpn->Dir=-1;
            wpn->X=BTCenterX()-8;
            wpn->Y=BTCenterY()-8;
        }
        
        // 8-way fires
        // WPN_ENEMYFLAME2 is incorrect...
        else if(ghost->Weapon==WPN_ENEMYFLAME || ghost->Weapon==142)
        {
            int i;
            int flags=0;
            if((ghost->Attributes[BTEK_ATTR_FLAGS]&BTEK_FLAG_UNBLOCKABLE)!=0)
                flags|=EWF_UNBLOCKABLE;
            
            if(ghost->Weapon==WPN_ENEMYFLAME)
                weaponID=EW_FIRE;
            else
                weaponID=EW_FIRE2;
                
            for(i=0; i<4; i++)
                FireNonAngularEWeapon(weaponID,
                                      BTCenterX()-8, BTCenterY()-8,
                                      i, 100, ghost->WeaponDamage, -1, 0, flags);
            for(i=4; i<8; i++)
                FireNonAngularEWeapon(weaponID,
                                      BTCenterX()-8, BTCenterY()-8,
                                      i, 71, ghost->WeaponDamage, -1, 0, flags);
            Game->PlaySound(SFX_FIRE);
        }
    }
    
    void BounceOffScreenEdges()
    {
        if(Ghost_X<=16)
            Ghost_Vx=Abs(Ghost_Vx);
        else if(Ghost_X+16*Ghost_TileWidth>=240)
            Ghost_Vx=-Abs(Ghost_Vx);
        
        if(Ghost_Y<=16)
            Ghost_Vy=Abs(Ghost_Vy);
        else if(Ghost_Y+16*Ghost_TileHeight>=160)
            Ghost_Vy=-Abs(Ghost_Vy);
    }
    
    // ========== Super jump ==========
    
    void SuperJump(ffc this, npc ghost, int defenses)
    {
        int timer;
        float targetAngle;
        float targetSpeed;
        float dist;
        float targetVx;
        float targetVy;
        
        // Charge up
        Ghost_Data=this->Misc[BTEK_IDX_BASE_COMBO]+BTEK_CMB_CROUCH;
        for(timer=0; timer<BTEK_SJUMP_CHARGE_TIME; timer++)
            BTWaitframe(this, ghost);
        
        // Jump...
        Ghost_SetFlag(GHF_NO_FALL); // Override gravity handling
        Ghost_Data=this->Misc[BTEK_IDX_BASE_COMBO]+BTEK_CMB_JUMP;
        Game->PlaySound(ghost->Attributes[BTEK_ATTR_SFX_SJUMP]);
        while(Ghost_Z<176)
        {
            Ghost_Z+=BTEK_SJUMP_SPEED;
            BTWaitframe(this, ghost);
        }
        
        // Follow Link around for a while
        for(timer=Rand(BTEK_SJUMP_MIN_TIME, BTEK_SJUMP_MAX_TIME); timer>0; timer--)
        {
            dist=BTDistanceTo(Link->X+8, Link->Y+8);
            
            // If far from Link, adjust aim; otherwise, try to stop
            if(dist>4)
            {
                targetAngle=Angle(BTCenterX(), BTCenterY(), Link->X+8, Link->Y+8);
                targetSpeed=ghost->Step/200;
            }
            else
                targetSpeed=0;
            
            // Don't change direction suddenly
            targetVx=targetSpeed*Cos(targetAngle);
            targetVy=targetSpeed*Sin(targetAngle);
            
            if(Ghost_Vx<targetVx)
                Ghost_Vx=Min(Ghost_Vx+0.05, targetVx);
            else if(Ghost_Vx>targetVx)
                Ghost_Vx=Max(Ghost_Vx-0.05, targetVx);
            
            if(Ghost_Vy<targetVy)
                Ghost_Vy=Min(Ghost_Vy+0.05, targetVy);
            else if(Ghost_Vy>targetVy)
                Ghost_Vy=Max(Ghost_Vy-0.05, targetVy);
            
            BTWaitframe(this, ghost);
        }
        
        // Fall
        Ghost_Vx=0;
        Ghost_Vy=0;
        ghost->Damage*=BTEK_SJUMP_DAMAGE_MULTIPLIER;
        Game->PlaySound(ghost->Attributes[BTEK_ATTR_SFX_FALL]);
        while(Ghost_Z>0)
        {
            Ghost_Z-=BTEK_SJUMP_SPEED;
            BTWaitframe(this, ghost);
        }
        
        // Land
        Ghost_Z=0;
        ghost->Damage/=BTEK_SJUMP_DAMAGE_MULTIPLIER;
        LaunchDebris(ghost);
        DropObjects(ghost);
        Screen->Quake=BTEK_SJUMP_QUAKE_TIME;
        Game->PlaySound(ghost->Attributes[BTEK_ATTR_SFX_LAND]);
        if((ghost->Attributes[BTEK_ATTR_FLAGS]&BTEK_FLAG_STUN_LINK)!=0 && Link->Z==0)
            this->Misc[BTEK_IDX_LINK_STUN_TIME]=BTEK_SJUMP_QUAKE_TIME;
        
        // Be stunned for a bit
        if((ghost->Attributes[BTEK_ATTR_FLAGS]&BTEK_FLAG_STUN_TO_HIT)!=0)
            Ghost_SetDefenses(ghost, defenses);
        Ghost_Data=this->Misc[BTEK_IDX_BASE_COMBO]+BTEK_CMB_STUN;
        timer=Ghost_GetAttribute(ghost, BTEK_ATTR_STUN_TIME, BTEK_DEFAULT_STUN_TIME);
        for(; timer>0; timer--)
            BTWaitframe(this, ghost);
        
        if((ghost->Attributes[BTEK_ATTR_FLAGS]&BTEK_FLAG_STUN_TO_HIT)!=0)
            Ghost_SetAllDefenses(ghost, NPCDT_BLOCK);
        
        Ghost_UnsetFlag(GHF_NO_FALL);
    }
    
    void LaunchDebris(npc ghost)
    {
        if((ghost->Attributes[BTEK_ATTR_FLAGS]&BTEK_FLAG_DEBRIS)==0)
            return;
        
        int count=Rand(BTEK_MIN_DEBRIS_LAUNCHED, BTEK_MAX_DEBRIS_LAUNCHED);
        int sprite=Ghost_GetAttribute(ghost, BTEK_ATTR_DEBRIS_SPRITE,
                                      BTEK_DEFAULT_DEBRIS_SPRITE);
        int damage=ghost->WeaponDamage;
        if(damage==0)
            damage=BTEK_DEFAULT_DEBRIS_DAMAGE;
        float angle;
        eweapon debris;
        
        for(; count>0; count--)
        {
            angle=Randf(PI2);
            // Large function calls!
            debris=FireEWeapon(BTEK_EW_DEBRIS, 
                               BTCenterX()-8+8*RadianCos(angle),
                               BTCenterY()-8+8*RadianSin(angle),
                               angle,
                               Rand(BTEK_MIN_DEBRIS_STEP, BTEK_MAX_DEBRIS_STEP),
                               damage,
                               sprite,
                               0,
                               EWF_SHADOW|EWF_UNBLOCKABLE);
            SetEWeaponMovement(debris,
                               EWM_THROW,
                               Randf(BTEK_MIN_DEBRIS_VEL, BTEK_MAX_DEBRIS_VEL),
                               EWMF_DIE);
            SetEWeaponDeathEffect(debris, EWD_VANISH, 0);
        }
    }
    
    void DropObjects(npc ghost)
    {
        // Don't do anything if the flag isn't set.
        if((ghost->Attributes[BTEK_ATTR_FLAGS]&BTEK_FLAG_DROP_OBJECTS)==0)
            return;
        
        // Or if the object to drop isn't valid.
        int objID=ghost->Attributes[BTEK_ATTR_DROPPED_OBJECT];
        if(objID!=0 && !(objID>=20 && objID<=511))
            return;
        
        // Drop debris
        if(objID==0)
        {
            eweapon debris;
            int count=Rand(BTEK_MIN_DEBRIS_DROPPED, BTEK_MAX_DEBRIS_DROPPED);
            int sprite=Ghost_GetAttribute(ghost, BTEK_ATTR_DEBRIS_SPRITE,
                                          BTEK_DEFAULT_DEBRIS_SPRITE);
            int damage=ghost->WeaponDamage;
            if(damage==0)
                damage=BTEK_DEFAULT_DEBRIS_DAMAGE;
            
            for(; count>0; count--)
            {
                debris=FireEWeapon(BTEK_EW_DEBRIS, Rand(16, 224), Rand(16, 144),
                                   0, 0, damage, sprite, 0,
                                   EWF_SHADOW|EWF_UNBLOCKABLE);
                SetEWeaponMovement(debris, EWM_FALL, Rand(160, 224), EWMF_DIE);
                SetEWeaponDeathEffect(debris, EWD_VANISH, 0);
            }
        }
        
        // Drop enemies
        else
        {
            // Don't do anything if there are too many already
            if(Screen->NumNPCs()>=BTEK_ENEMY_LIMIT)
                return;
            
            npc enemy;
            int count=Rand(BTEK_MIN_ENEMIES_DROPPED, BTEK_MAX_ENEMIES_DROPPED);
            if(Screen->NumNPCs()+count>BTEK_ENEMY_LIMIT)
                count=BTEK_ENEMY_LIMIT-Screen->NumNPCs();
            
            for(; count>0; count--)
            {
                enemy=SpawnNPC(objID);
                enemy->Z=Rand(160, 224);
            }
        }
    }
    
    // ========== Other ==========
    
    // Fall onto the screen at the start of the fight
    void DoIntro(ffc this, npc ghost)
    {
        Ghost_X=128-8*Ghost_TileWidth;
        Ghost_Y=96-16*Ghost_TileHeight;
        Ghost_Z=176;
        Ghost_SetFlag(GHF_NO_FALL);
        Ghost_SetFlag(GHF_STATIC_SHADOW);
        ghost->CollDetection=false;
        
        // Wait half a second, then fall
        Ghost_Data=this->Misc[BTEK_IDX_BASE_COMBO]+BTEK_CMB_JUMP;
        Ghost_Waitframes(this, ghost, true, true, 60);
        
        Game->PlaySound(ghost->Attributes[BTEK_ATTR_SFX_FALL]);
        while(Ghost_Z>0)
        {
            Ghost_Z-=BTEK_SJUMP_SPEED;
            Ghost_Waitframe(this, ghost, true, true);
        }
        
        // On impact, shake the screen and disable Link for a moment
        Screen->Quake=BTEK_SJUMP_QUAKE_TIME;
        Ghost_Data=this->Misc[BTEK_IDX_BASE_COMBO]+BTEK_CMB_CROUCH;
        Game->PlaySound(ghost->Attributes[BTEK_ATTR_SFX_LAND]);
        while(Screen->Quake>0)
        {
            NoAction();
            Ghost_Waitframe(this, ghost, true, true);
        }
        
        // Back to normal
        Ghost_UnsetFlag(GHF_NO_FALL);
        Ghost_UnsetFlag(GHF_STATIC_SHADOW);
        ghost->CollDetection=true;
    }
    
    void BTWaitframe(ffc this, npc ghost)
    {
        // Is Link stunned?
        if(this->Misc[BTEK_IDX_LINK_STUN_TIME]>0)
        {
            NoAction();
            this->Misc[BTEK_IDX_LINK_STUN_TIME]--;
        }
        
        bool noDeathAnim=ghost->Attributes[BTEK_ATTR_DEATH_TYPE]==0;
        if(!Ghost_Waitframe(this, ghost, noDeathAnim, noDeathAnim))
        {
            Ghost_DeathAnimation(this, ghost, ghost->Attributes[BTEK_ATTR_DEATH_TYPE]);
            Quit();
        }
    }
    
    float BTCenterX()
    {
        return Ghost_X+8*Ghost_TileWidth;
    }
    
    // Doesn't really return the center unless the tektite is 1x1; if it's
    // bigger, it's actually the center of the shadow, which might be either
    // 1x1 or 2x2 depending on size and ghost.zh settings.
    float BTCenterY()
    {
        if(GH_FAKE_Z!=0 && GH_LARGE_SHADOW_TILE!=0)
        {
            if(Ghost_TileHeight==1) // 1x1 shadow
                return Ghost_Y+8;
            else if(Ghost_TileHeight==2) // 1x1
                return Ghost_Y+24;
            else if(Ghost_TileHeight==3) // 2x2
                return Ghost_Y+32;
            else // 2x2
                return Ghost_Y+48;
        }
        else // 1x1
        {
            if(Ghost_TileHeight==1)
                return Ghost_Y+8;
            else
                return Ghost_Y+16+8*(Ghost_TileHeight-1); // 24, 40, 56
        }
    }
    
    // Distance from "center" to a given point
    float BTDistanceTo(float x, float y)
    {
        float dx=BTCenterX()-x;
        float dy=BTCenterY()-y;
        
        return Sqrt(dx*dx+dy*dy);
    }
}