/* player.m,
 *
 * This is you, dummy!  After some time, I got lazy and started
 * relying on pid0 == player1, and pid1 == player2.
 */

#include <allegro.h>
#include <math.h>
#ifndef NO_FBLEND
# include "fblend/include/fblend.h"
#endif
#include "common.h"
#include "debris/explosion.h"
#include "debris/large-chunk.h"
#include "debris/player-zoom.h"
#include "debris/spark.h"
#include "hiscore.h"
#include "input.h"
#include "map.h"
#include "nuke.h"
#include "player-stats.h"
#include "player.h"
#include "pregen-circles.h"
#include "projectile.h"
#include "projectiles/homing-missile.h"
#include "projectiles/laser.h"
#include "projectiles/missile.h"
#include "projectiles/pink.h"
#include "projectiles/toothpaste.h"
#include "projectiles/vulcan.h"
#include "seborrhea/seborrhea-allegro.h"
#include "seborrhea/seborrhea-font.h"
#include "seborrhea/seborrhea.h"
#include "unit.h"
#include "units/micronuke.h"
#include "units/robot.h"
#include "units/satellite.h"


#define foreach_orientation(i)		for (i = 0; i < NUM_ORIENTATIONS; i++)
#define NUM_ORIENTATIONS	3

enum ORIENTATION {
    ORIENTATION_CENTRE = 0,
    ORIENTATION_LEFT,
    ORIENTATION_RIGHT
};

typedef enum ORIENTATION orientation_t;

static const char *primary_weapon_level_sprite[NUM_PRIMARY_WEAPONS] = {
    "glue/level-l", "glue/level-t", "glue/level-v"
};

static const char *secondary_weapon_level_sprite[NUM_SECONDARY_WEAPONS] = {
    "glue/level-h", "glue/level-m", "glue/level-p"
};


Player *player[MAX_PLAYERS];
unsigned int num_players = 1;

static SebAnimation *body_animation[MAX_PLAYERS][NUM_ORIENTATIONS];
static SebAnimation *fire_animation[MAX_PLAYERS][NUM_ORIENTATIONS];
static Sebum<SebImage> *the_shadow[NUM_ORIENTATIONS];
static Sebum<SebImage> *status_bg;

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

@implementation Player
+ initialize
{
    /* This is supposed to be in initialize, not loadData. */
    body_animation[0][ORIENTATION_CENTRE] = (SebAnimation *)[base_sebum getSebumByName:"player/p1-centre"];
    body_animation[0][ORIENTATION_LEFT]   = (SebAnimation *)[base_sebum getSebumByName:"player/p1-left"];
    body_animation[0][ORIENTATION_RIGHT]  = (SebAnimation *)[base_sebum getSebumByName:"player/p1-right"];
    fire_animation[0][ORIENTATION_CENTRE] = (SebAnimation *)[base_sebum getSebumByName:"player/f1-centre"];
    fire_animation[0][ORIENTATION_LEFT]   = (SebAnimation *)[base_sebum getSebumByName:"player/f1-left"];
    fire_animation[0][ORIENTATION_RIGHT]  = (SebAnimation *)[base_sebum getSebumByName:"player/f1-right"];

    the_shadow[ORIENTATION_CENTRE] = [base_sebum getSebumByName:"player/shadow-c"];
    the_shadow[ORIENTATION_LEFT]   = [base_sebum getSebumByName:"player/shadow-l"];
    the_shadow[ORIENTATION_RIGHT]  = [base_sebum getSebumByName:"player/shadow-r"];

    status_bg = [base_sebum getSebumByName:"glue/statusbar-bg"];

    return self;
}

+ (void) shutdown
{
    orientation_t i;

    foreach_orientation (i) {
	body_animation[0][i] = nil;
	fire_animation[0][i] = nil;
	the_shadow[i] = NULL;
    }

    status_bg = nil;
}

- (void) setNormalAnimations
{
    orientation_t i;

    foreach_orientation (i) {
	if (animation == anims[i]) {
	    animation = body_animation[0][i];
	    sprite = [(SebAnimator *)sprite setAnimation:animation];
	}
	anims[i] = body_animation[0][i];
    }
}

- (void) setFiringAnimations
{
    orientation_t i;

    foreach_orientation (i) {
	if (animation == anims[i]) {
	    animation = fire_animation[0][i];
	    sprite = [(SebAnimator *)sprite setAnimation:animation];
	}
	anims[i] = fire_animation[0][i];
    }
}

- init
{
    [super init];

    sprite = [SebAnimator new];
    [self setNormalAnimations];
    animation = anims[ORIENTATION_CENTRE];
    [(SebAnimator *)sprite setAnimation:animation];
    shadow = the_shadow[ORIENTATION_CENTRE];

    x = screen_w / 2;
    y = screen_h - 50;
    w = 8;
    h = 7;
    health = (num_players == 1) ? 10 : 7;

    weapon[PRIMARY_WEAPON]   = PRIMARY_WEAPON_VULCAN;
    weapon[SECONDARY_WEAPON] = SECONDARY_WEAPON_NONE;
    weapon[TERTIARY_WEAPON]  = TERTIARY_WEAPON_NONE;
    weapon_tics[PRIMARY_WEAPON]   = 0;
    weapon_tics[SECONDARY_WEAPON] = 0;

    children_list = [LIST_NEW];

    pid = 0;
    chunk_colours = CHUNK_BLUE;

#ifdef DEBUG
    weapon_level[PRIMARY_WEAPON] = 2;
    weapon_level[SECONDARY_WEAPON] = 2;
    //weapon[PRIMARY_WEAPON] = PRIMARY_WEAPON_LASER;
    weapon[SECONDARY_WEAPON] = SECONDARY_WEAPON_MISSILE;
    [self setWeapon:TERTIARY_WEAPON to:TERTIARY_WEAPON_ROBOTS];
#endif

    return self;
}

- free
{
    /* Check for a high score. */
    if (is_new_high_score(player_stats[pid].score)) {
	if (pid == 0) game_flags |= FLAG_PLAYER1_HIGH_SCORE;
	if (pid == 1) game_flags |= FLAG_PLAYER2_HIGH_SCORE;
    }

    if (children_list) {
	/* We don't want to destroy units twice so unlink from this list. */
	[children_list removeAllItems];
	children_list = [children_list free];
    }

    return [super free];
}

- (void) spawnDyingExplosions
{
    health--;
    if (health == 0)			/* Just in case. */
	health = -1;

    /* Spit out come chunks, gradually get more. */
    if (rnd(health, 0) < -50) {
	if (chunk_colours & CHUNK_BLUE)
	    spawn_debris([LargeChunkBlue class], x, y, HIGH_LAYER);
	if (chunk_colours & CHUNK_COFFEE)
	    spawn_debris([LargeChunkCoffee class], x, y, HIGH_LAYER);
    }

    /* You have 3 seconds to grab a health powerup before you die! */
    if (health > - 3*SECONDS) {
	/* Pretend that the explosions are getting bigger by spawning
	   more small explosions earlier and bigger ones later. */
	if (rnd(health, 0) < -30)
	    spawn_debris([BigExplosion class], x, y, HIGH_LAYER);
	else
	    spawn_debris([Explosion class], x, y, HIGH_LAYER);
    }
    else if (not (game_flags & FLAG_LEVEL_COMPLETE)) {
	/* If you completed the level, you cheat death!
	   Congratulations.  Otherwise, die. */
	[self delete];
    }

    if (health > -150 + 15)
	flash_tics = 10;
    else if (flash_tics > 0) {
	/* Hack to make sure status bar is not displaced when we die. */
	flash_tics--;
    }
}

- (void) spawnNearDeathSparks
{
    if (rand() % 3 == 0)
	spawn_debris([Spark class],
		     rnd(x-10, x+10), rnd(y-10, y+10), HIGH_LAYER);
}

#ifdef WINFF
- (void) die
{
    [super die];
    rumble_pad_for_player_dying(pid);
}
#endif

- (void) delete
{
    ListIterator *it, *nx;

    /* Free the robots and satellites. */
    foreach_nx (it, nx, children_list) {
	Unit<PlayerChild> *unit = [it getItem];
	nx = [it next];
	[unit die];
    }

    [super delete];
}

- (void) draw:(BITMAP *)dest
{
    [super draw:dest];

    if (flash_tics > 0) {
	int alpha = MIN(0x10 * flash_tics, 0x40) / 3;
	int radius = 150 - 5 * ABS(10 - flash_tics);
	int colour = makecol(0xff, 0xb0, 0xb0);
	pregen_circlefill_add(dest, x-offsetX, y-offsetY, radius*2/3, colour, alpha);
	pregen_circlefill_add(dest, x-offsetX, y-offsetY, radius, colour, alpha);
    }
}

- (void) drawHealth:(BITMAP *)dest
{
    SebFont *status_font  = [base_sebum getSebumByName:"fonts/nano10"];
    SebFont *digital_font = [base_sebum getSebumByName:"fonts/digital2"];
    int delta_x, delta_y, c, shake_dist;

    shake_dist = MIN(flash_tics, 5);

    delta_x = rnd(-shake_dist, shake_dist);
    delta_y = rnd(-shake_dist, shake_dist);
    c  = makecol(0x40, 0x40, 0x40);

    /* Backdrop */
    [status_bg drawTo:dest X:0 Y:0 W:dest->w H:dest->h];

    {				/* Health. */
	BITMAP *block = [(SebImageAllegro *)[base_sebum getSebumByName:"glue/health"] bitmap];
	int i, xx, yy, hh;

	[status_font putString:"HEALTH" To:dest X:5+delta_x Y:10+delta_y Colour:0x40:0x40:0x40];
	hh = MIN(health, 15);
	for (yy = 20; hh > 0; yy += 10, hh -= 5)
	    for (xx = 10, i = (hh > 5 ? 5 : hh); i > 0; xx += 10, i--)
		draw_sprite(dest, block, xx+2+delta_x, yy+2+delta_y);
    }

    [status_font putString:"PRIMARY" To:dest X:5+delta_x Y:60+delta_y Colour:0x40:0x40:0x40];
    {				/* Primary weapon level. */
	const char *blockname = primary_weapon_level_sprite[weapon[PRIMARY_WEAPON]];
	BITMAP *blk = [(SebImageAllegro *)[base_sebum getSebumByName:blockname] bitmap];
	int i, xx;

	for (i = 0, xx = 10; i < weapon_level[PRIMARY_WEAPON]+1; i++, xx += 21)
	    draw_sprite(dest, blk, xx+2+delta_x, 70+2+delta_y);
    }

    [status_font putString:"SECONDARY" To:dest X:5+delta_x Y:90+delta_y Colour:0x40:0x40:0x40];
    if (weapon[SECONDARY_WEAPON] != SECONDARY_WEAPON_NONE) { /* Secondary weapon level. */
	const char *blockname = secondary_weapon_level_sprite[weapon[SECONDARY_WEAPON]];
	BITMAP *blk = [(SebImageAllegro *)[base_sebum getSebumByName:blockname] bitmap];
	int i, xx;

	for (i = 0, xx = 10; i < weapon_level[SECONDARY_WEAPON]+1; i++, xx += 21)
	    draw_sprite(dest, blk, xx+2+delta_x, 100+2+delta_y);
    }

    [status_font putString:"ADRENALINE" To:dest X:5+delta_x Y:120+delta_y Colour:0x40:0x40:0x40];
    if (adrenaline_meter > 8) {		/* Adrenaline meter. */
	int w_ = MIN(adrenaline_meter * dest->w/300, dest->w-24);

#ifndef NO_FBLEND
	fblend_rect_trans(dest, 12, 132+delta_y, w_, 8, makecol(0xff, 0x00, 0x00), 0xa0);
#else					/* Normal Allegro routines. */
	/* Lazy. */
	rectfill(dest, 12, 132+delta_y, 12+w_-1, 140+delta_y, makecol(0xff, 0x00, 0x00));
#endif					/* End: NO_FBLEND */
    }

    [status_font putString:"SCORE" To:dest X:5+delta_x Y:150+delta_y Colour:0x40:0x40:0x40];
    [digital_font putStringTo:dest X:dest->w-15+delta_x Y:160+delta_y Colour:0x40:0x80:0x20 Alignment:ALIGN_RIGHT :"%d", player_stats[pid].score];
}

- (int) update
{
    int i;
    int w_ = [sprite width]/2 - 10;
    int h_ = [sprite height]/2;

    if (adrenaline_meter)		/* Dumb. */
	adrenaline_meter--;

    if (not (game_flags & FLAG_LEVEL_COMPLETE)) {
	/* Make sure the player doesn't scroll with the map, and keep
	   them on the screen. */
	y += [current_map scrollRate];
	x = MID(w_ + offsetX, x, screen_w - w_ + offsetX);
	y = MID(h_ + offsetY, y, screen_h - h_ + offsetY);
    }

    if (invulnerable_tics > 0) {
	if (--invulnerable_tics <= 0)
	    flags &=~FLAG_INVINCIBLE;
    }
    else if (flags & FLAG_DEAD)		/* Death (rides a horse, Binky). */
	return THING_DEAD;
    else if (health <= 0) {		/* Dying. */
	flags |= (FLAG_DYING|FLAG_INVINCIBLE);

	x += xv;
	y += yv;
	//xv *= 1.25;
	//yv *= 1.25;

	[self spawnDyingExplosions];
    }
    else if (health <= 3)		/* Low health warning. */
	[self spawnNearDeathSparks];

    /* Weapons and firing. */
    for (i = 0; i < NUM_WEAPONS; i++)
	if (weapon_tics[i])
	    weapon_tics[i]--;

    /* Slight penalty for holding down fire key. */
    if (fire_tics <= -15) {
        fire_was_down = NO;
    }
    else {
        fire_tics--;
        if (fire_tics > 0)
            [self fire];
    }

    if (fire_tics < 0)
	unlockToothpaste(pid);

    if ([(SebAnimator *)sprite animationEnded])
	[self setNormalAnimations];

    return THING_NORMAL;
}

- (void) increaseVelocity:(double)xv_ :(double)yv_
{
    orientation_t orientation;

    if (flags & FLAG_DYING) {
	xv = MID(-3.0, xv + xv_/3.0, 3.0);
	yv = MID(-3.0, yv + yv_/3.0, 3.0);
    }

    /* As XOP guy pointed out, rock hard movement gives better control. */
    else {
	x += xv_;
	y += yv_;
    }

    /* -1.0 < xv < 1.0, have centre animation. */
    if (xv_ < -1.0)
	orientation = ORIENTATION_LEFT;
    else if (xv_ > 1.0)
	orientation = ORIENTATION_RIGHT;
    else
        orientation = ORIENTATION_CENTRE;

    if (animation != anims[orientation]) {
	shadow = the_shadow[orientation];
	animation = anims[orientation];
	[(SebAnimator *)sprite setAnimation:animation];
    }
}

- (void) zoom
{
    /* Used by end-level sequence. */
    spawn_debris([PlayerZoom class], x, y, MEDIUM_LAYER);

    yv -= 0.2;
    y += yv;

    flags |= FLAG_INVINCIBLE;
}

- (void) openFire
{
    if (not fire_was_down) {
        fire_was_down = YES;
        fire_tics = 15;
	adrenaline_meter = MIN(adrenaline_meter+11, 300);
    }
}

- (void) haltFire
{
    if (!fire_was_down && fire_tics <= 0)
	if (adrenaline_meter > 0)
	    adrenaline_meter--;

    fire_was_down = NO;
}

- (void) fire
{
    if (weapon_tics[PRIMARY_WEAPON] <= 0) { /* Primary weapon. */
	if (weapon[PRIMARY_WEAPON] == PRIMARY_WEAPON_LASER)
            fireLaser(x, y, weapon_level[PRIMARY_WEAPON], &weapon_tics[PRIMARY_WEAPON], pid);
	else if (weapon[PRIMARY_WEAPON] == PRIMARY_WEAPON_TOOTHPASTE)
            fireToothpaste(x, y, weapon_level[PRIMARY_WEAPON], &weapon_tics[PRIMARY_WEAPON], pid);
	else
	    fireVulcan(x, y, weapon_level[PRIMARY_WEAPON], &weapon_tics[PRIMARY_WEAPON], pid);

	[self setFiringAnimations];
    }

    if (weapon_tics[SECONDARY_WEAPON] <= 0) { /* Secondary weapon. */
	if (weapon[SECONDARY_WEAPON] == SECONDARY_WEAPON_HOMING_MISSILE)
	    fireHomingMissile(x, y, weapon_level[SECONDARY_WEAPON], &weapon_tics[SECONDARY_WEAPON], pid);
	else if (weapon[SECONDARY_WEAPON] == SECONDARY_WEAPON_MISSILE)
	    fireMissile(x, y, weapon_level[SECONDARY_WEAPON], &weapon_tics[SECONDARY_WEAPON], pid);
	else if (weapon[SECONDARY_WEAPON] == SECONDARY_WEAPON_PINK_ORANGE_RED)
	    firePinkOrangeRed(x, y, weapon_level[SECONDARY_WEAPON], &weapon_tics[SECONDARY_WEAPON], &weapon_barrel, pid);
    }

    /* Do nothing for robots.  They will query us to determine whether
       or not to fire. */
}

- (BOOL) isFiring { return (fire_tics > 0) ? YES : NO; }

	/* Powerups. */
- (void) receiveRobots
{
#ifdef CHEAT
    int delta_x[8] = { 30, -30,  40, -40,  20, -20,  10, -10 };
    int delta_y[8] = {  5,   5,  15,  15,  -5,  -5, -15, -15 };
    int i = 7;
#else
    int delta_x[2] = { 30, -30 };
    int delta_y[2] = {  5,   5 };
    int i = 1;
#endif

    ListIterator *it, *nx;
    Unit<PlayerChild> *unit;

    /* Release all children, regardless of class. */
    foreach_nx (it, nx, children_list) {
	unit = [it getItem];
	nx = [it next];
	[unit die];
    }

    for (; i >= 0; i--) {
	unit = (Robot *)spawn_unit([Robot class], x, y, ALLY_LIST, YES);
	/* Make the robots come from the bottom of the screen. */
	[[[unit setParent:self] setX:x Y:y + screen_h]
	    setDisplacementX:delta_x[i] Y:delta_y[i]];
	[children_list insertItem:unit];
    }
}

- (void) receiveSatellites
{
    ListIterator *it, *nx;
    Unit<PlayerChild> *unit;
    int n = 2;

    foreach_nx (it, nx, children_list) {
	unit = [it getItem];
	nx = [it next];

	if ([unit isKindOf:[Satellite class]])
	    [(Satellite *)unit exciteSatellite];
	else
	    [unit die];
    }

    while (n > 0) {
	unit = (Satellite *)spawn_unit([Satellite class], x, y, ALLY_LIST, YES);
	[[unit setParent:self] setAngle:deg2rad(180.0) * n];
	[children_list insertItem:unit];
	n--;
    }
}

- (void) setWeapon:(weapon_group_t)n to:(int)new_weapon
{
    player_stats[pid].powerups_gained++;

    /* If the new weapon is the same as current, increase its level. */
    if (n < TERTIARY_WEAPON && weapon[n] == new_weapon) {
	if (weapon_level[n] == 2) {
	    /* Maxed out!  Spawn a mininuke instead. */
	    [spawn_nuke(x, y, pid) setNukeRadius:100];
	}
	else
	    weapon_level[n]++;
    }
    else
	weapon[n] = new_weapon;

    /* If tertiary weapons (robots or satellites), do extra stuff. */
    if (n == TERTIARY_WEAPON) {
	if (new_weapon == TERTIARY_WEAPON_ROBOTS)
	    [self receiveRobots];
	else if (new_weapon == TERTIARY_WEAPON_SATELLITE) {
	    [self receiveSatellites];
#ifdef CHEAT
	    [self receiveSatellites];
	    [self receiveSatellites];
#endif
	}
    }
}

- (int) weaponType:(weapon_group_t)n { return weapon[n]; }
- (void) increaseScore:(int)n { player_stats[pid].score += n; }

- (void) incrementKillsFrom:(double)x_ :(double)y_
{
    stats_t *s = &player_stats[pid];
    int i;
    double dist_sq;
    
    s->kills++;
    dist_sq = SQ(x_-x) + SQ(y_-y);

    /* -10 is a small shift towards the short end. */
    i = NUM_KILL_RANGES_BUCKETS * (sqrt(dist_sq)-10) / (screen_h * 7/8);
    i = MID(0, i, NUM_KILL_RANGES_BUCKETS-1);
    s->kill_bucket[i]++;
}

- (void) disownChild:(Unit *)unit { [children_list removeItem:unit]; }
- (int) playerID { return pid; }

- (int) receiveDamage:(int)damage type:(enum DAMAGE_TYPE)type
{
    if (type == DAMAGE_TYPE_UNIT) {
	player_stats[pid].crashes++;
	player_stats[pid].hits++;
    }
    else if (damage > 0)
	player_stats[pid].hits++;

    if ((damage > 0 && flags & FLAG_INVINCIBLE) || 
	(damage > 0 && flags & FLAG_DYING) ||
	(flags & FLAG_DEAD))
	return 0;

    if (damage > 0) {			/* Was hurt. */
	health--;
	flash_tics = MIN(flash_tics+9, 25);
        if (health <= 0)
            [self die];
	else {
	    /* Make a MicroNuke to clear away nearby projectiles. */
	    spawn_unit([MicroNuke class], x, y, ALLY_LIST, YES);

	    flags |= FLAG_INVINCIBLE;
	    invulnerable_tics = 15;
	    adrenaline_meter = MIN(adrenaline_meter+25, 500);
	}

#ifdef WINFF
	rumble_pad_for_player_hurt(pid);
#endif
    }
    else {				/* Health powerup. */
	if (flags & FLAG_DYING) {
	    health = -damage;
	    player_stats[pid].cheated_death_count++;
	    flags &=~FLAG_DYING;
	}
	else {
	    health -= damage;

	    /* Max out at 15 health.  Anything more causes a nuke. */
	    if (health > 15) {
		health = 15;
		spawn_nuke(x, y, pid);
	    }
	}

	/* Picking up health also makes you invincible for a bit. */
	flags |= FLAG_INVINCIBLE;
	invulnerable_tics = 15;
    }

    return MIN(damage, 1);
}

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

- (BOOL) collidesWith:(Thing<DetectsCollision> *)object
{
    double x1, y1, x2, y2;
    int w1, h1, w2, h2;

    /* Collide with powerups when dying (to pick up health), so:
	if (flags & FLAG_DYING)
	    return NO;
    */

    [self   getX:&x1 Y:&y1 W:&w1 H:&h1];
    [object getX:&x2 Y:&y2 W:&w2 H:&h2];

    return ((x1 - w1 / 2 <= x2 + w2 / 2) && (x1 + w1 / 2 >= x2 - w2 / 2) &&
	    (y1 - h1 / 2 <= y2 + h2 / 2) && (y1 + h1 / 2 >= y2 - h2 / 2));
}

	/* Dummy to conform to OwnsChildren protocol. */
- (void) destroyChildren {}
@end

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

@implementation Player2
- (void) setNormalAnimations
{
    orientation_t i;

    foreach_orientation (i) {
	if (animation == anims[i]) {
	    animation = body_animation[1][i];
	    sprite = [(SebAnimator *)sprite setAnimation:animation];
	}
	anims[i] = body_animation[1][i];
    }
}

- (void) setFiringAnimations
{
    orientation_t i;

    foreach_orientation (i) {
	if (animation == anims[i]) {
	    animation = fire_animation[1][i];
	    sprite = [(SebAnimator *)sprite setAnimation:animation];
	}
	anims[i] = fire_animation[1][i];
    }
}

+ initialize
{
    /* This is supposed to be in initialize, not loadData. */
    body_animation[1][ORIENTATION_CENTRE] = (SebAnimation *)[base_sebum getSebumByName:"player/p2-centre"];
    body_animation[1][ORIENTATION_LEFT]   = (SebAnimation *)[base_sebum getSebumByName:"player/p2-left"];
    body_animation[1][ORIENTATION_RIGHT]  = (SebAnimation *)[base_sebum getSebumByName:"player/p2-right"];
    fire_animation[1][ORIENTATION_CENTRE] = (SebAnimation *)[base_sebum getSebumByName:"player/f2-centre"];
    fire_animation[1][ORIENTATION_LEFT]   = (SebAnimation *)[base_sebum getSebumByName:"player/f2-left"];
    fire_animation[1][ORIENTATION_RIGHT]  = (SebAnimation *)[base_sebum getSebumByName:"player/f2-right"];

    return self;
}

+ (void) shutdown
{
    orientation_t i;

    foreach_orientation (i) {
	body_animation[1][i] = nil;
	fire_animation[1][i] = nil;
    }
}

- init
{
    [super init];

    chunk_colours = CHUNK_COFFEE;
    pid = 1;

    return self;
}
@end
