/* boss1.m,
 *
 * This is the boss for the space level.  No shadows are required.
 * 20% Bonus damage for vulcan.
 */

#include <allegro.h>
#include <assert.h>
#include <math.h>
#ifndef NO_FBLEND
# include "fblend/include/fblend.h"
#endif
#include "colour-conv.h"
#include "common.h"
#include "debris/explosion.h"
#include "debris/large-chunk.h"
#include "debris/particle.h"
#include "debris/satellite-trail.h"
#include "debris/smoke.h"
#include "end-level.h"
#include "map.h"
#include "projectiles/fireball.h"
#include "projectiles/pulse-laser.h"
#include "projectiles/swarm-missile.h"
#include "rotate.h"
#include "seborrhea/container-animation.h"
#include "seborrhea/seborrhea-allegro.h"
#include "seborrhea/seborrhea-spline.h"
#include "seborrhea/seborrhea.h"
#include "sound.h"
#include "unit-seeking.h"
#include "units/boss1.h"


#define foreach_child(c)	for (c = 0; c < BOSS1_MAX_SATELLITES; c++)
#define ROTATION_DIST		90.0
#define ROTATION_TICS		(ROTATION_DIST)
#define EXTENSION_DIST		45.0
#define EXTENSION_TICS		(2*EXTENSION_DIST)
#define ROT_PLUS_EXTENSION_TICS	(ROTATION_TICS+EXTENSION_TICS)

#define FANG_FIRE_DELAY			60
#define LASERBALL_CREATION_FRAMES	9
#define LASERBALL_HOLD_TICS		8

#define BODY_HEALTH		400
#define SMALL_WING_HEALTH	600
#define LARGE_WING_HEALTH	900
#define PHASE1_HEALTH		(BODY_HEALTH+SMALL_WING_HEALTH+LARGE_WING_HEALTH)
#define PHASE2_HEALTH		(BODY_HEALTH+SMALL_WING_HEALTH)
#define PHASE3_HEALTH		(BODY_HEALTH)


static SebFile *boss_sebum;
static SebAnimation *fire_anim;
static SebAnimation *laserball_anim;
static SebSpline *the_way;

/*------------------------------------------------------------*/

@interface Boss1LargeLeftWingDebri: Debri
@end

@interface Boss1LargeRightWingDebri: Boss1LargeLeftWingDebri
@end

@interface Boss1SmallLeftWing: Unit <Boss1Child>
{
    Unit<OwnsChildren> *parent;
    Sebum<SebImage> *sprite2;		/* Bottom half of wing. */
    Sebum<SebImage> *sprite3;		/* Entire wing picture. */
    Sebum<SebImage> *sprited;		/* Destroyed wing picture. */
    int t;				/* Time after activation. */

    Weapon *bay1, *bay2, *bay3;
}
@end

@interface Boss1SmallRightWing: Boss1SmallLeftWing
@end

@interface Boss1Satellite: Unit <Boss1Child>
{
    Unit<OwnsChildren> *parent;
    double phi, psi;			/* Angle used for movement. */
    double sign;
    double distance;
    int direction;
}
- (void) setDirection:(int)dir;
@end

@interface Boss1LaserBall: Projectile
{
    int fire_tics;
    int fire_method, shots_remaining;
    double fire_angle;
    unsigned int frame;
}
@end

/*--------------------------------------------------------------*/
/* The body.							*/
/*--------------------------------------------------------------*/

@implementation Boss1
+ (BOOL) loadPrerequisites
{
    return (LOAD_PROJECTILE_DATA_FOR(Fireball) &&
	    LOAD_PROJECTILE_DATA_FOR(PulseLaser) &&
	    LOAD_PROJECTILE_DATA_FOR(SwarmMissile));
}

+ (BOOL) loadData
{
    set_colour_conversion_keep_alpha();

    boss_sebum = [SebFile new];
    if (not [boss_sebum loadSebumDirectory:"data/boss1"])
	return NO;

    set_color_conversion(COLORCONV_TOTAL);

    fire_anim = (SebAnimation *)[boss_sebum getSebumByName:"fang-fire"];
    laserball_anim = (SebAnimation *)[boss_sebum getSebumByName:"laserball"];

    the_way = (SebSpline *)[boss_sebum getSebumByName:"path"];
    return YES;
}

+ (void) shutdown
{
    FREE_SEBFILE(boss_sebum);
    fire_anim = NULL;
    laserball_anim = NULL;
    the_way = NULL;
}

- init
{
    [super init];
    sprite = [boss_sebum getSebumByName:"body-large"];
    health = PHASE1_HEALTH;
    w = [sprite width];
    h = [sprite height];

    outer_gun[0] = [[[[[[RingWeapon new]
			   setProjectile:[Fireball class]]
			  setShotDelay:100] setFireTics:30]
			setStepSize:deg2rad(5.0)] setNumSteps:5];
    outer_gun[1] = [[[[[[RingWeapon new]
			   setProjectile:[Fireball class]]
			  setShotDelay:100] setFireTics:80]
			setStepSize:deg2rad(5.0)] setNumSteps:5];

    flags |= FLAG_MOVING_ENABLED|FLAG_FIRING_ENABLED;

    return self;
}

- free
{
    outer_gun[0] = [outer_gun[0] free];
    outer_gun[1] = [outer_gun[1] free];
    return [super free];
}

- (void) spawnDyingExplosions
{
    if (health < -50)			/* 2 seconds. */
	spawn_debris([LargeChunkCoffee class], x, y, HIGH_LAYER);
    if (health > -150) {		/* 3 seconds. */
	spawn_debris([BigExplosion class],
		     rnd(x-w/2, x+w/2), rnd(y-h/2, y+h/2), HIGH_LAYER);
	play_explosion_sample(x);
    }
    else {
	if (boss == self)
	    boss = nil;

	end_level(5*SECONDS);
        [self delete];
    }

    health--;
    flash_tics += 2;
}

- (void) drawHealthMeter:(BITMAP *)dest
{
    BITMAP *bg, *fg;
    int ww;

    if (flags & FLAG_DYING)
	return;

    if (health > PHASE2_HEALTH) {
	bg = [(SebImageAllegro *)[base_sebum getSebumByName:"glue/meter-purple-dark"] bitmap];
	fg = [(SebImageAllegro *)[base_sebum getSebumByName:"glue/meter-red"] bitmap];
	ww = 75 * (health-PHASE2_HEALTH) / LARGE_WING_HEALTH;
    }
    else {
	bg = [(SebImageAllegro *)[base_sebum getSebumByName:"glue/meter-gray"] bitmap];
	fg = [(SebImageAllegro *)[base_sebum getSebumByName:"glue/meter-purple"] bitmap];
	ww = 75 * health / PHASE2_HEALTH;
    }

    blit(bg, dest, 0, 0, dest->w/2-75, dest->h-10, 150, 8);
    blit(fg, dest, 75-ww, 1, dest->w/2-ww, dest->h-9, 2*ww, 6);
}

- (void) disownChild:(Unit *)unit
{
    int c;
    assert(unit);

    /* This is only used by satellites. */
    foreach_child (c) {
	if (child[c] == unit) {
	    child[c] = nil;
	    return;
	}
    }

    assert("Attempted to disown non-child-unit from Boss1" && NO);
}

- (void) die
{
    int c;

    foreach_child (c) {
	if (child[c]) {
	    [child[c] die];
	    child[c] = nil;
	}
    }

    [super die];
}

	/*----- Large wing phase. -----*/
- (void) moveLargeWingPhase
{
    Unit *target = find_closest_unit(x, y, ALLY_LIST);

    [the_way follow:&path_progress :&x :&y :100.0
	     :path_x_displacement :150];

    if (target) {
	double dest_x, dest_y;
	[target getX:&dest_x Y:&dest_y];

	path_x_displacement = 0.9*path_x_displacement + 0.1*dest_x;
	path_x_displacement = MID(w/2, path_x_displacement, screen_w-w/2);
    }
}

- (void) fireLargeWingPhase
{
    double theta;

    theta = ANGLE_TO_CLOSEST_ALLY(x-90,y+80) - 2*deg2rad(5.0);
    [outer_gun[0] fireFromX:x-90 Y:y+80 angle:theta];

    theta = ANGLE_TO_CLOSEST_ALLY(x+90,y+80) - 2*deg2rad(5.0);
    [outer_gun[1] fireFromX:x+90 Y:y+80 angle:theta];

    fire_tics--;

    if (fire_tics == 10)
	[spawn_projectile([PulseLaser class], x-70, y, NO) setAngle:deg2rad( -75.0)];
    if (fire_tics <= 0) {
	fire_tics = 60;
	[spawn_projectile([PulseLaser class], x+70, y, NO) setAngle:deg2rad(-105.0)];
    }
}

	/*----- Small wing phase. -----*/
- (void) moveSmallWingPhase
{
    if (fire_tics < LASERBALL_CREATION_FRAMES*2 &&
	fire_tics > -LASERBALL_HOLD_TICS)
	return;

    if (fire_tics < 0) {
	y = MAX(50.0, y + fire_tics/5.0);
	return;
    }

    if (fire_tics < FANG_FIRE_DELAY - 10) {
	if (y < screen_h/3)
	    y += 0.75;
    }

    {
	Unit *target = find_closest_unit(x, y, ALLY_LIST);

	if (target) {
	    double dest_x, dest_y;
	    [target getX:&dest_x Y:&dest_y];

	    if (ABS(dest_x - x) < 2.5)
		x = dest_x;
	    else if (dest_x > x)
		x += 2.5;
	    else
		x -= 2.5;
	}
    }
}

- (void) fireSmallWingPhase
{
    if (fang_y >= 55.0) {
	fire_tics--;

	if (fire_tics == 0)
	    spawn_projectile([Boss1LaserBall class], x, y+65, NO);

	if (fire_tics <= -LASERBALL_HOLD_TICS - LASERBALL_CREATION_FRAMES)
	    fire_tics = FANG_FIRE_DELAY;
    }
    else {
	fang_y += 0.25;

	if (fang_y >= 45.0)
	    flags &=~FLAG_INVINCIBLE;
    }
}

	/*----- Kamikaze phase. -----*/
- (void) moveKamikazePhase
{
    double theta_desired = angle;
    Unit *target = find_closest_ally_according_to_rotation(x, y, angle);

    if (target)
	theta_desired = angle_towards_unit(x, y, target);

    if (thrust_v < 0.5) {
	limited_rotate(&angle, theta_desired, deg2rad(2.5));
	x += 0.1*cos(angle);
	y -= 0.1*sin(angle);

	/* Close to angle of player, thrust! */
	if (ABS(angle - theta_desired) < deg2rad(1.0)) {
	    skidding = NO;
	    thrust_v = 0.5;
	}
    }
    else {
	double xv, yv;

	if (skidding) {
	    limited_rotate(&angle, theta_desired, deg2rad(4.0));
	}
	else {
	    limited_rotate(&angle, theta_desired, deg2rad(0.03));
	    skid_angle = angle;
	}
	xv =  thrust_v*cos(skid_angle);
	yv = -thrust_v*sin(skid_angle);

	x += xv;
	y += yv;

	if ((x < screen_w*2/5 && xv < 0) || (x > screen_w*3/5 && xv > 0) ||
	    (y < screen_h*2/5 && yv < 0) || (y > screen_h*3/5 && yv > 0))
	    skidding = YES;

	/* Begin slowing down 50 pixels from the edge of the screen,
	   and stop when 10 pixel from the edge.

	   So a graph of velocity vs. distance:
	    v
	    ^
	    |   *---	7.0
	    |  * 
	    | *  
	    +------->	dist
	      ^ ^
	     10 50

	     => v = (7/40)*(dist-10)
	*/

	if ((x < 50 && xv < 0 && -xv > (6.5/40.0)*(x-10)) ||
	    (y < 50 && yv < 0 && -yv > (6.5/40.0)*(y-10)) ||
	    (x > screen_w-50 && xv > 0 && xv > (6.5/40.0)*(screen_w-x-10)) ||
	    (y > screen_h-50 && yv > 0 && yv > (6.5/40.0)*(screen_h-y-10))) {
	    thrust_v = MAX(0.0, (thrust_v * 0.75) - 0.1);
	}
	else {
	    thrust_v = MIN(thrust_v + 0.5, 6.5);
	    spawn_debris([Smoke class], x, y, MEDIUM_LAYER);
	}
    }

    simplify_angle(&angle);
}

- (void) fireKamikazePhase
{
    fire_tics--;

    if (fire_tics <= 0) {
	double theta;

	/* A ring of fireballs. */
	for (theta = 0.0; theta < 2*M_PI; theta += deg2rad(5.0))
	    [spawn_projectile([Fireball class], x, y, NO) setAngle:theta];
	
	flags &=~FLAG_FIRING_ENABLED;
	flags &=~FLAG_INVINCIBLE;
    }
}

- (void) move
{
    if (health > PHASE2_HEALTH)
	[self moveLargeWingPhase];
    else if (health > PHASE3_HEALTH)
	[self moveSmallWingPhase];
    else if (not (flags & FLAG_FIRING_ENABLED)) {
	/* Only charge when we are not firing. */
	[self moveKamikazePhase];
    }
}

- (void) fire
{
    if (health > PHASE2_HEALTH)
	[self fireLargeWingPhase];
    else if (health > PHASE3_HEALTH)
	[self fireSmallWingPhase];
    else
	[self fireKamikazePhase];    
}

- (int) update
{
    int i, num_satellites;

    for (i = 0, num_satellites = 0; i < BOSS1_MAX_SATELLITES; i++) {
	if (child[i])
	    num_satellites++;
    }

    /* Already enough satellites. */
    if (health <= PHASE3_HEALTH ||
	num_satellites >= BOSS1_MAX_SATELLITES ||
	(health > PHASE2_HEALTH && num_satellites >= 2))
	return [super update];


    /* Spawn a new satellite every second. */
    satellite_tics++;
    if (satellite_tics >= 50) {
	Boss1Satellite *unit;
	satellite_tics = 0;

	for (i = 0; i < BOSS1_MAX_SATELLITES; i++) {
	    if (not child[i])
		break;
	}

	unit = (Boss1Satellite *)spawn_unit([Boss1Satellite class], x, y, ACTIVE_AIR_LIST, NO);
	[unit setParent:self];
	
	if (i % 2 == 0)
	    [unit setDirection:-1];	/* Clockwise */
	else
	    [unit setDirection:-1];	/* Counter-clockwise */
	child[i] = unit;
    }

    return [super update];
}

- (void) draw:(BITMAP *)dest
{
    if (rotatable_unit) {
	[super draw:dest];
	return;
    }

    if (fang_y > 0) {
	Sebum<SebImage> *spr;

	if (fire_tics >= LASERBALL_CREATION_FRAMES*2)
	    spr = [fire_anim getFrame:0];
	else if (fire_tics >= 0)
	    spr = [fire_anim getFrame:LASERBALL_CREATION_FRAMES-fire_tics/2-1];
	else if (fire_tics >= -LASERBALL_HOLD_TICS)
	    spr = [fire_anim getFrame:-1];
	else
	    spr = [fire_anim getFrame:(fire_tics + LASERBALL_HOLD_TICS)];

	[spr drawTo:dest X:x-[spr width]/2-offsetX
	     Y:y-[spr height]/2-offsetY+fang_y];
    }
    [super draw:dest];
}

- (BOOL) readyToActivate
{
    if ([super readyToActivate]) {
	int i;
	boss = self;

	/* Spawn these children later. */
	wings[BOSS1_SMALL_LEFT_WING]  = nil;
	wings[BOSS1_SMALL_RIGHT_WING] = nil;
	for (i = 0; i < BOSS1_MAX_SATELLITES; i++)
	    child[i] = nil;
	satellite_tics = 0;

	[(<SebSample>)[boss_sebum getSebumByName:"evil"] playWithVolume:sound_vol/SOUND_VOLMAX];
	return YES;
    }
    else
	return NO;
}

- (BOOL) collidesWith:(Thing<DetectsCollision> *)object
{
    if (rotatable_unit)
	return [super collidesWithRotatedUnit:object];
    else
	return [super collidesWith:object];
}

- (int) receiveDamage:(int)damage type:(enum DAMAGE_TYPE)type
{
    int old_health = health;

    if (type == DAMAGE_TYPE_UNIT)
	return 0;

    damage = [super receiveDamage:damage type:type];
    if (not damage)
	return 0;

    /* Shots push him backwards.  We are in space after all. */
    if (not rotatable_unit) {
	y = MAX(50.0, y - damage/4.0);
    }

    if (old_health > PHASE2_HEALTH && health <= PHASE2_HEALTH) {
	/* Our big wings have recently been shot off. */
	sprite = [boss_sebum getSebumByName:"body"];
	w = [sprite width];
	h = [sprite height];

	wings[BOSS1_SMALL_LEFT_WING]  = spawn_unit([Boss1SmallLeftWing class],  x, y, ACTIVE_AIR_LIST, YES);
	wings[BOSS1_SMALL_RIGHT_WING] = spawn_unit([Boss1SmallRightWing class], x, y, ACTIVE_AIR_LIST, YES);
	[wings[BOSS1_SMALL_LEFT_WING]  setParent:self];
	[wings[BOSS1_SMALL_RIGHT_WING] setParent:self];

	fang_y = 0;
	fire_tics = FANG_FIRE_DELAY;

	/* Invincible for a while. */
	flags |= FLAG_INVINCIBLE;

	/* Shed our large wings. */
	spawn_debris([Boss1LargeLeftWingDebri  class], x-62, y, LOW_LAYER);
	spawn_debris([Boss1LargeRightWingDebri class], x+62, y, LOW_LAYER);
    }

    if (old_health > PHASE3_HEALTH && health <= PHASE3_HEALTH) {
	sprite = [boss_sebum getSebumByName:"body-kamikaze"];
	w = [sprite width];
	h = [sprite height];
	rotatable_unit = YES;

	[wings[BOSS1_SMALL_LEFT_WING]  die];
	[wings[BOSS1_SMALL_RIGHT_WING] die];
	wings[BOSS1_SMALL_LEFT_WING]  = nil;
	wings[BOSS1_SMALL_RIGHT_WING] = nil;

	/* When our small wings explode, we want to spawn a ring of
	   fireballs for evilness. */
	fire_tics = 90;
	flags |= FLAG_INVINCIBLE;
    }

    return damage;
}

- derive_fixedAngle;
- derive_setXYAlwaysCentred;
- derive_airBossCollisionLists;
@end


/*--------------------------------------------------------------*/
/* Large wings.							*/
/*--------------------------------------------------------------*/

@implementation Boss1LargeLeftWingDebri
- init
{
    [super init];
    health = 75;
    sprite = [boss_sebum getSebumByName:"bigwing-l"];
    w = [sprite width];
    h = [sprite height];
    angle = deg2rad(-150.0);
    speed = 1.8;
    return self;
}

- (int) update
{
    spawn_debris([BigExplosion class], 
		 rnd(x-w/2, x+w/2), rnd(y-h/2, y+h/2), MEDIUM_LAYER);
    play_explosion_sample(x);

    health--;
    if (health <= 50)
	spawn_debris([LargeChunkRed class], x, y, MEDIUM_LAYER);

    return [super update];
}
@end

@implementation Boss1LargeRightWingDebri
- init
{
    [super init];
    sprite = [boss_sebum getSebumByName:"bigwing-r"];
    angle = deg2rad(-30.0);
    return self;
}
@end


/*--------------------------------------------------------------*/
/* Small wings.							*/
/*--------------------------------------------------------------*/

@implementation Boss1SmallLeftWing
- init
{
    [super init];

    sprite  = [boss_sebum getSebumByName:"wing-l1"];
    sprite2 = [boss_sebum getSebumByName:"wing-l2"];
    sprite3 = [boss_sebum getSebumByName:"wing-l"];
    sprited = [boss_sebum getSebumByName:"wing-ld"];
    rotatable_unit = YES;
    angle = deg2rad(-90.0);

    /* The wing hasn't expanded yet, so make the height smaller. */
    w = [sprite width] - 10;
    h = [sprite height] - 80 - EXTENSION_DIST;

    bay1 = [[[[Weapon new] 
		setProjectile:[SwarmMissile class]]
	       setShotDelay:80] setFireTics:0];
    bay2 = [[[[Weapon new] 
		setProjectile:[SwarmMissile class]]
	       setShotDelay:80] setFireTics:2];
    bay3 = [[[[Weapon new] 
		setProjectile:[SwarmMissile class]]
	       setShotDelay:80] setFireTics:4];

    flags |= FLAG_MOVING_ENABLED|FLAG_INVINCIBLE;
    return self;
}

- free 
{
    bay1 = [bay1 free];
    bay2 = [bay2 free];
    bay3 = [bay3 free];
    return [super free]; 
}

- (void) die
{
    [super die];
    sprite = sprited;
}

- (void) spawnDyingExplosions
{
    if (health < -50)			/* 1 seconds. */
	spawn_debris([LargeChunkCoffee class], x, y, HIGH_LAYER);
    if (health > -100) {		/* 2 seconds. */
	spawn_debris([BigExplosion class],
		     rnd(x-w/2, x+w/2), rnd(y-h/2, y+h/2), HIGH_LAYER);
	play_explosion_sample(x);
    }
    else
        [self delete];

    health--;
    flash_tics += 2;
}

- (void) setParent:(Unit<OwnsChildren> *)unit { parent = unit; }

- (void) draw:(BITMAP *)dest
{
    double backup_x = x;
    double backup_y = y;
    int sign = (angle < 0) ? -1 : 1;

    if (t < ROTATION_TICS) {
	/* Yeah, I know this isn't good.  This is to make the
	   animation not jerk when we change the sprites.  I don't
	   know where the numbers come from :/ */
	x +=  3 * (ROTATION_TICS - t)/ROTATION_TICS * sign;
	y -= 29 * (ROTATION_TICS - t)/ROTATION_TICS;
    }
    
    /* Draw sprite2 manually. */
    if (t < ROTATION_TICS) {		/* Rotation sequence. */
	int w_ = [sprite2 width];
	int h_ = [sprite2 height];
	double x_ = x - offsetX - EXTENSION_DIST*cos(deg2rad(t*ROTATION_DIST/ROTATION_TICS)) * sign;
	double y_ = y - offsetY - EXTENSION_DIST*sin(deg2rad(t*ROTATION_DIST/ROTATION_TICS));

	pivot_sprite(dest, [(SebImageAllegro *)sprite2 bitmap], x_, y_,
		     w_/2, h_/2, rad2fix(angle));
    }
    else if (t < ROT_PLUS_EXTENSION_TICS) {	/* Extension sequence. */
	int w_ = [sprite2 width];
	int h_ = [sprite2 height];
	double x_ = x - offsetX - w_/2;
	double y_ = y - offsetY - h_/2;
	y_ += (t - ROT_PLUS_EXTENSION_TICS) * (EXTENSION_DIST/EXTENSION_TICS);

	draw_sprite(dest, [(SebImageAllegro *)sprite2 bitmap], x_, y_);
    }

    [super draw:dest];
    x = backup_x;
    y = backup_y;
}

- (int) update
{
    if (t < ROT_PLUS_EXTENSION_TICS) {
	[self move];

	if (t < ROTATION_TICS) {
	    if (angle < 0)
		angle += deg2rad(ROTATION_DIST / ROTATION_TICS);
	    if (angle > 0)
		angle -= deg2rad(ROTATION_DIST / ROTATION_TICS);
	}
	else
	    rotatable_unit = NO;
	
	/* Finished activating? */
	t++;
	if (t >= ROT_PLUS_EXTENSION_TICS) {
	    sprite = sprite3;
	    sprite2 = nil;
	    sprite3 = nil;

	    /* Make the collision size proper. */
	    w = [sprite width] - 10;
	    h = [sprite height] - 80;

	    flags &=~FLAG_INVINCIBLE;
	    flags |= FLAG_FIRING_ENABLED;
	}
	return THING_NORMAL;
    }
    else
	return [super update];
}

- (void) move
{
    /* Parent not set or deleted before us? */
    assert(parent && 
	   unit_exists(parent, ACTIVE_AIR_LIST) &&
	   "Parent (Boss1) deleted before child!");

    [parent getX:&x Y:&y];
    x -= 61.0;
    y += 36.0;
}

- (void) fire
{
    [bay1 fireFromX:x-7  Y:y-20];
    [bay2 fireFromX:x+10 Y:y-20];
    [bay3 fireFromX:x+28 Y:y-20];
}

- (int) receiveDamage:(int)damage type:(enum DAMAGE_TYPE)type
{
    if (flags & FLAG_INVINCIBLE)
	return 0;
    if (parent) {
	damage = [parent receiveDamage:damage type:type];
	if (damage)
	    flash_tics = MIN(flash_tics+9, 25);
	return damage;
    }
    return 0;
}

- derive_alwaysInfluenceGameplay;
+ derive_airBossCollisionLists;
@end

@implementation Boss1SmallRightWing
- init
{
    [super init];

    /* Note if you look VERY closely, you'll notice the destroyed
       picture is wrong.  That's because I'm lazy drawing-wise. */
    sprite  = [boss_sebum getSebumByName:"wing-r1"];
    sprite2 = [boss_sebum getSebumByName:"wing-r2"];
    sprite3 = [boss_sebum getSebumByName:"wing-r"];
    sprited = [boss_sebum getSebumByName:"wing-rd"];

    angle = deg2rad(ROTATION_DIST);

    [bay1 setFireTics:35];
    [bay2 setFireTics:40];
    [bay3 setFireTics:45];

    return self;
}

- (void) move
{
    /* Parent not set or deleted before us? */
    assert(parent && 
	   unit_exists(parent, ACTIVE_AIR_LIST) &&
	   "Parent (Boss1) deleted before child!");

    [parent getX:&x Y:&y];
    x += 60.0;
    y += 36.0;
}

- (void) fire
{
    [bay1 fireFromX:x+7  Y:y-20];
    [bay2 fireFromX:x-10 Y:y-20];
    [bay3 fireFromX:x-28 Y:y-20];
}
@end


/*--------------------------------------------------------------*/
/* Satellites.							*/
/*--------------------------------------------------------------*/

@implementation Boss1Satellite
- init
{
    [super init];

    sprite = [boss_sebum getSebumByName:"satellite"];
    rotatable_unit = YES;
    health = 25;
    w = h = 24;
    distance = 0;
    flags |= FLAG_MOVING_ENABLED;

    return self;
}

- (void) delete
{
    if (parent) {
	[parent disownChild:self];
	parent = nil;
    }
    [super delete];
}

- (void) setParent:(Unit<OwnsChildren> *)unit { parent = unit; }

- (void) setDirection:(int)dir
{
    direction = dir;

    if (parent) {
	[parent getX:&x Y:&y];
	x -= 23.0 * direction;
    }

    if (direction < 0)
	phi = M_PI;
}

- (void) move
{
    /* Parent not set or deleted before us? */
    double dest_x, dest_y, delta_x, delta_y;
    assert(parent && 
	   unit_exists(parent, ACTIVE_AIR_LIST) &&
	   "Parent (Boss1) deleted before child!");

    spawn_debris([SatelliteTrail class], x, y, HIGH_LAYER);

    if (distance < 150)
	distance += 2;

    delta_x = x;
    delta_y = y;
    [parent getX:&dest_x Y:&dest_y];

    /* Shift to left/right for cw/ccw satellites. */
    dest_x -= 23.0 * direction;
    dest_y += distance;

    phi += deg2rad(3.5) * direction;
    psi += deg2rad(0.3) * direction;
    rotate(distance/3.0*cos(phi), distance*sin(phi), psi, &x, &y);
    x += dest_x;
    y += dest_y;

    angle = atan2(delta_y-y, x-delta_x);
}

- derive_alwaysInfluenceGameplay;
@end


/*--------------------------------------------------------------*/
/* Laserball.							*/
/*--------------------------------------------------------------*/

@implementation Boss1LaserBall
- init
{
    [super init];

    sprite = [laserball_anim getFrame:0];
    frame = 0;
    fire_method = rnd(1, 3);
    shots_remaining = 13;

    return self;
}

- (void) draw:(BITMAP *)dest
{
    if (sprite) {
	int sprite_w_2 = [sprite width]/2;
	int sprite_h_2 = [sprite height]/2;
	int x_ = x-sprite_w_2-offsetX;
	int y_ = y-sprite_h_2-offsetY;

#ifndef NO_FBLEND
        fblend_rgba_trans([(SebImageAllegro *)sprite bitmap], dest, x_, y_, 0);
#else  /* Normal Allegro routines. */
        set_alpha_blender();
        draw_trans_sprite(dest, [(SebImageAllegro *)sprite bitmap], x_, y_);
#endif /* NO_FBLEND */
    }
}

- (int) update
{
    /* Animate. */
    if ((frame/3) < [laserball_anim numFrames]-1) {
	frame++;
	sprite = [laserball_anim getFrame:frame/3];
    }
    else {
	/* Loop the animation. */
	frame -= 8*3;
	sprite = [laserball_anim getFrame:frame/3];
    }

    /* Still in creation process.  Don't fire yet. */
    if (frame/3 < LASERBALL_CREATION_FRAMES)
	return THING_NORMAL;

    /* Spit out some particles. */
    {
	int c;
	Particle *p;

	switch (rnd(0, 2)) {
	  case 0: c = makecol(0xff, 0xff, 0x80); break; /* Yellow */
	  case 1: c = makecol(0x80, 0x80, 0xff); break; /* Blue */
	  default:
	  case 2: c = makecol(0xff, 0xff, 0xff); break; /* White */
	}

	p = (Particle *)spawn_debris([Particle class], x, y, HIGH_LAYER);
	[[p setAngle:deg2rad(rnd(-180.0, 180.0))] setColour:c];
    }

    /* Ready to fire? */
    fire_tics--;
    if (fire_tics <= 0) {
	switch (fire_method) {
	  case 1:			/* Target player. */
	      fire_angle = ANGLE_TO_CLOSEST_ALLY(x, y);

	      if (fire_angle > deg2rad(90.0))
		  fire_angle = -M_PI;
	      else if (fire_angle > deg2rad(0.0))
		  fire_angle = 0.0;
	      break;
	  case 2:			/* Sweep ccw. */
	      if (shots_remaining == 13)
		  fire_angle = deg2rad(-140.0);

	      fire_angle += deg2rad(100.0/13);
	      break;

	  default:
	  case 3:			/* Sweep cw. */
	      if (shots_remaining == 13)
		  fire_angle = deg2rad(-40.0);
	      fire_angle -= deg2rad(100.0/13);
	}

	[spawn_projectile([PulseLaser class], x, y, NO) setAngle:fire_angle];

	shots_remaining--;
	if (shots_remaining <= 0)
	    return THING_DEAD;
	fire_tics = 7;
    }

    return THING_NORMAL;
}

+ (int) collisionLists { return 0; }
@end
