/* satellite.m,
 *
 * Little ally ships that orbit the player.  They are considered as a
 * tertiary weapon.
 */

#include <allegro.h>
#include <math.h>
#include "common.h"
#include "debris/satellite-trail.h"
#include "debris/spark.h"
#include "map.h"
#include "seborrhea/seborrhea.h"
#include "units/satellite.h"


#define MAX_ORBITAL_DISTANCES	6

#define INCOMING_VELOCITY	deg2rad(4.0)
#define ORBIT_VELOCITY		deg2rad(8.0)
#define INCOMING_RATE		0.95
#define OUTGOING_RATE		1.01

#ifdef CHEAT
# define ESCAPE_DISTANCE	4
#else
# define ESCAPE_DISTANCE	1
#endif


static double distances[MAX_ORBITAL_DISTANCES] = {
    35.0, 50.0, 65.0, 80.0, 95.0, 100.0
};


@implementation Satellite
- init
{
    [super init];

    health = 22;
    speed = 6.0;

    sprite = [base_sebum getSebumByName:"satellite/body"];
    shadow = [base_sebum getSebumByName:"satellite/shadow"];
    w = 20;
    h = 20;
    rotatable_unit = YES;

    orbital_distance = screen_w;
    desired_distance = distances[0];
    ccw = YES;
    in_orbit = NO;

    /* Start invincible when coming from bottom of screen. */
    flags |= FLAG_INVINCIBLE;
    return self;
}

- (id) setParent:(Player *)p { parent = p; return self; }

- (void) exciteSatellite
{
    int i;

    for (i = 0; i < MAX_ORBITAL_DISTANCES; i++) {
	if (desired_distance < distances[i]) {
	    desired_distance = distances[i];

	    if (i > ESCAPE_DISTANCE)
		[self die];
	    break;
	}
    }

    ccw = !ccw;
    health += 3;
}

- (void) spawnDyingExplosions
{
    if (rnd(0, 2 * health) == 0)
	spawn_debris([Spark class], x, y, HIGH_LAYER);
}

- (void) die
{
    health = 0.0;
    flags |= (FLAG_DYING|FLAG_INVINCIBLE);

    if (parent) {
	[parent disownChild:self];
	parent = nil;
    }
}

- (BOOL) mayStillInfluenceGameplay
{
    /* We were just picked up as as powerup and are arriving from
       outside the screen, or are not dying. */
    if (!in_orbit || !(flags & FLAG_DYING))
	return YES;

    /* We died and are off the screen. */
    if (x < -50 || x > screen_w+50 ||
	y < offsetY-50 || y > screen_h+offsetY+50)
	return NO;
    return YES;
}

- (int) update
{
    if (flags & FLAG_DEAD)
        return THING_DEAD;

    if (health <= 2)
        [self spawnDyingExplosions];

    if (!(flags & FLAG_DYING) && in_orbit &&
	(desired_distance > distances[ESCAPE_DISTANCE]))
	[self die];
    else
	[self move];

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

    return [self mayStillInfluenceGameplay] ? THING_NORMAL : THING_DEAD;
}

- (void) move
{ 
    if (flags & FLAG_DYING)
        [super move];
    else if (parent) {
        double x_, y_, theta;
	int sign = ccw ? 1 : -1;

        [parent getX:&x_ Y:&y_];

        angle += sign * (in_orbit ? ORBIT_VELOCITY : INCOMING_VELOCITY);
        simplify_angle(&angle);
        theta = angle - sign * M_PI_2;

	x = x_ + orbital_distance * cos(theta);
	y = y_ - orbital_distance * sin(theta);

	if (!in_orbit) {
	    orbital_distance *= INCOMING_RATE;
	    if (orbital_distance - desired_distance < 10.0) {
		in_orbit = YES;
		flags &=~FLAG_INVINCIBLE;
	    }
	}
	else
            orbital_distance = MIN(desired_distance, orbital_distance * OUTGOING_RATE + 0.1);
    }
}

- (int) receiveDamage:(int)damage type:(enum DAMAGE_TYPE)type
{
    int ret = [super receiveDamage:damage type:type];

    /* Satellites are meant to smash other units.  Give the player
       some score. */
    if (type == DAMAGE_TYPE_UNIT && ret && parent)
	[parent increaseScore:ret*200];

    return ret;
}

- (int) collisionLists { return ACTIVE_AIR_LIST|COLLIDES_WITH_PROJECTILES; }
@end
