"use strict";
/* Don't be this guy */
var GO_Boss_Bobo = function(){
	this.sprite= new Sprite(GO_Boss_Bobo.spriteSheet,"BossBobo");
	this.sprite.collision_flags = 
		DEFINE_SPRITE_COLLISION_FLAGS.GROUND |
		DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_ACTIVE |
		DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_PASSIVE;
	this.sprite.setBoundingBox(0,0,31,28);
	//this.sprite.constraints=constraints;
	this.sprite.max_step = 2;
	this.sprite.game_object = this; // no longer a generic GO

	this.jump_index = 0;
	this.fall_index = 0;
	this.grounded = true;

	//this.hp = 16; // nice Po2 value to help regulate speed
	this.hp = 12; // ok, ok, Bobo might possibly be OP.  Or y'all might just be a dang bunch of weak whiners ;)
	this.infection = 1;
	this.smoke = [];

	this.controls = new Controls(); // just to init

	this.ANIM_STATES = {
		"crouch" : 0,
		"chatter" : 1,
		"air" : 2, // jump or fall
		"windup" : 3,
		"throw" : 4,
		"stun" : 5,
		"dead" : 6
	};
	this.anim_state = this.ANIM_STATES.bob;

	this.CONTROL_STATES = {
		"prefight" : 0,
		"wait" : 1,
		"jump" : 2,
		"catch" : 3,
		"aim" : 4,
		"fire" : 5,
		"stun" : 6,
		"postfight" : 7
	};
	this.control_state = this.CONTROL_STATES.prefight;
	this.control_timer = 0;

	this.anim_frames = [
		[1], //"crouch" : 
		[1,1,1,1,1,0,1,0,1,0], //"chatter" : 
		[2], //"air" : 
		[4], //"windup" 
		[4,5], //"throw" 
		[6], //"stun"
		[7], //"dead"
	];
	this.frame_timer=0;

	this.ball=null;

	this.barriers = []; // block off the exit ourself

	this.meli = null;
	this.meliFind=false; // she probably won't be in the map when we init; hopefully when we update
};
GO_Boss_Bobo.spriteImg = SDS_Load_Image("art/sheets/sprite/bobo_infected.png");
GO_Boss_Bobo.spriteSheet = new TileSheet(5, 5, 1, 2, GO_Boss_Bobo.spriteImg);

GO_Boss_Bobo.prototype = new GameObject(
	new Sprite(
		GO_Boss_Bobo.spriteSheet,
		"BossBobo"
	),
	"BossBobo"
	);

	GO_Boss_Bobo.prototype.interactions = 
		DEFINE_GAMEOBJECT_INTERACTIONS.CONTROL |
		DEFINE_GAMEOBJECT_INTERACTIONS.CHECK;

	GO_Boss_Bobo.prototype.jump_rise = [ //72px = 4.5 blocks
		-5,-5,-5,-5,-5,-5,
		-4,-4,-4,-4,-4,
		-3,-3,-3,-3,-3,
		-2,-2,-2,
		-1,
		0, 0];

	GO_Boss_Bobo.prototype.fall = [ 
		1, 
		2, 2, 2, 
		3, 3, 3, 
		4, 4, 4, 4, 4, 4, 
		5, 5, 5, 5, 5, 5,
		6, 6, 6, 6, 6, 6,
		7, 7, 7, 7, 7, 7, 
		8];

	GO_Boss_Bobo.prototype.checkEvent = function(sourceGO){
		//if (sourceGO && sourceGO.sprite && this.control_state!=this.CONTROL_STATES.postfight) this.direction = (sourceGO.sprite.x<this.sprite.x) ? -1 : 1;
		
		//var retval=null;
		this._heap.retval=null;

		//if (this.control_state==this.CONTROL_STATES.prefight) this._heap.retval = "Bobo_bossfight_1";
		if (this.control_state==this.CONTROL_STATES.postfight) this._heap.retval = "Bobo_bossfight_2";
		
		return this._heap.retval;
	};

	GO_Boss_Bobo.prototype.startFight = function(){
		for (this._heap.i=0; this._heap.i<3; ++this._heap.i){
			this.barriers[this._heap.i] = new GO_Forceblock();

			this.barriers[this._heap.i].sprite.x = (10+this._heap.i)<<4;
			this.barriers[this._heap.i].sprite.y = 20<<4;
			this.barriers[this._heap.i].core.x = (10+this._heap.i)<<4;
			this.barriers[this._heap.i].core.y = 20<<4;

			this.sprite.map_room.addSprite(this.barriers[this._heap.i].sprite);
			this.sprite.map_room.addSprite(this.barriers[this._heap.i].core);
			this.manager.add(this.barriers[this._heap.i]);
		}

		this.control_state = this.CONTROL_STATES.wait;
		this.sprite.collision_flags = 
			DEFINE_SPRITE_COLLISION_FLAGS.GROUND | 
			DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_PASSIVE;
		this.interactions = 
			DEFINE_GAMEOBJECT_INTERACTIONS.CONTROL |
			DEFINE_GAMEOBJECT_INTERACTIONS.DAMAGE |
			DEFINE_GAMEOBJECT_INTERACTIONS.INFLICT;
	};

	GO_Boss_Bobo.prototype.endFight = function(){
		SDS.bgmName = null;
		SDS.bgm.stop();
		//SDS.bgm = new Chiptune(SDS.bgmName,SDS.bgmchip);
		//SDS.bgm.play(true);
		
		this.control_state = this.CONTROL_STATES.postfight;
		this.sprite.collision_flags = 
			DEFINE_SPRITE_COLLISION_FLAGS.GROUND | 
			DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_ACTIVE |
			DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_PASSIVE;
		this.interactions = 
			DEFINE_GAMEOBJECT_INTERACTIONS.CONTROL |
			DEFINE_GAMEOBJECT_INTERACTIONS.CHECK;

		// JUST in case any haven't found their way to the killblocks yet,
		// be sure they don't fall down and block the exit.
		// hoised into endFight from postFight to mitigate awkward dialog over simul-kill
		this._heap.ball = this.manager.getNamedObject("Ball");
		while (this._heap.ball){
			this._heap.poof = new GO_Poof();
			this._heap.poof.spawnOnSprite(this._heap.ball.sprite,this.manager);

			this._heap.ball.sprite.map_room.removeSprite(this._heap.ball.sprite);
			//this._heap.ball.manager._remove(this._heap.ball); // outside the update loop, so do it immediately
			this._heap.ball.manager.remove(this._heap.ball);
			this._heap.ball = this.manager.getNamedObject("Ball");
		}
		this._heap.poof = null;


		// full-room plot trigger (hack)
		this._heap.triggerFn = function(sourceGO){
			if (GLOBAL_SCRATCHPAD.check("BOSS_FINISH")) return "";

			GLOBAL_SCRATCHPAD.note("BOSS_FINISH",true);
			return "Bobo_boss_end";
		};

		this._heap.trig = new GO_Trigger(this.sprite.map_room.pixel_width,this.sprite.map_room.pixel_height,this._heap.triggerFn);
		this._heap.trig.sprite.x = 0;
		this._heap.trig.sprite.y = 0;

		this.sprite.map_room.addSprite(this._heap.trig.sprite);
		this.manager.add(this._heap.trig);
		this._heap.trig = null;
		this._heap.triggerFn = null;
	};

	GO_Boss_Bobo.prototype.cleanupFight = function(){
		for (this._heap.i=0; this._heap.i<this.barriers.length; ++this._heap.i){
			this.sprite.map_room.removeSprite(this.barriers[this._heap.i].sprite);
			this.sprite.map_room.removeSprite(this.barriers[this._heap.i].core);
			this.manager._remove(this.barriers[this._heap.i]); // outside the update loop, so do it immediately
			this.barriers[this._heap.i] = null;
		}

		this._heap.soul = new GO_Soul("BOBO");
		this._heap.soul.spawnOnSprite(this.sprite,this.manager);
		this._heap.soul = null;

		this._heap.poof = new GO_Poof();
		this._heap.poof.spawnOnSprite(this.sprite,this.manager);
		this._heap.poof = null;

		this.sprite.map_room.removeSprite(this.sprite);
		this.manager._remove(this); // outside the update loop, so do it immediately
	};

	GO_Boss_Bobo.prototype.acquireBall = function(ball){
		this.ball = ball;
		this.ball.grab();

		// my ball not yours!
		// now, there's a highly intuitive chance that 2 things fighting for the grab of one ball
		// will result in weird behavior, particularly when the cached context updates stack.
		// However, I'm pretty sure we provably dodge a bullet here.
		// Bobo won't grab the ball unless it hits him for damage, which it won't do
		// if Meli is still holding it.
		// (and jic, we test for grabability before we get in here)
		// And with grab disabled while Bobo has it, Meli won't be able to try to grab it.
		this.ball.interactions &= ~DEFINE_GAMEOBJECT_INTERACTIONS.GRAB; 

		this.ball.pushContext(["Update"]);
		this.ball.Update = noop;

		this.ball.sprite.x = this.sprite.x + 3 - this.ball.sprite.center.x;
		this.ball.sprite.y = this.sprite.y + 1 - this.ball.sprite.box_bottom;

		// however, I would be completely unsurprised if the same legacy collision behavior
		// which lets Guy run into stationary boulders also means that when Bobo goes stunned
		// and drops his held ball, that ball hits him for bonus damage
	};
	GO_Boss_Bobo.prototype.releaseBall = function(){
		if (this.ball!=null){
			this.ball.popContext();
			
			this.ball.sprite.collision_flags = 
				DEFINE_SPRITE_COLLISION_FLAGS.GROUND | 
				DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_ACTIVE |
				DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_PASSIVE;
			this.ball.interactions |= DEFINE_GAMEOBJECT_INTERACTIONS.GRAB; // restore grabability
			this.ball.drop();
			this.ball=null;
		}
	};

	// Enemy-style coroutines
	GO_Boss_Bobo.prototype.damage = function(source,points){
		if (!this.ball && (source instanceof GO_Ball) && (source.interactions & DEFINE_GAMEOBJECT_INTERACTIONS.GRAB) && this.control_state===this.CONTROL_STATES.catch){
			// patched a crunchy issue with this after properly implementing onContextPush:
			// the MGO_Projectile surrogate Update's damage loop called us here, 
			// we acquireBall which does a context push, which triggers MGO_Projectile's
			// onContextPush popContext, leaving metaVars scrambled or undefined.
			// MGO_Projectile now checks for changes in metaVars and aborts if no longer relevant.
			this.acquireBall(source);

			return true; // say we're dead so the ball doesn't break on "impact"
		}
		else if (source===this.ball){
			// crunchy corner case if a thrown ball ever hits us twice in one frame
			return true;
		}
		else if (points>0){
			this.hp-=points;

			this.releaseBall();

			//var cough = new GO_Smoke();
			this._heap.cough = new GO_Smoke();
			this._heap.cough.sprite.x = this.sprite.x+16;
			this._heap.cough.sprite.y = this.sprite.y+16;
			//this._heap.cough.launch_dir = {x:this.direction, y:0};
			this._heap.cough.launch_dir.x=1;
			this._heap.cough.launch_dir.y=0;
			this._heap.cough.host_obj = this;

			this.sprite.map_room.addSprite(this._heap.cough.sprite);
			this.manager.add(this._heap.cough);
			this.smoke.push(this._heap.cough);

			this._heap.cough = null;

			this.control_state = this.CONTROL_STATES.stun;
			this.control_timer = 0;

			//this.infection--;
			return (this.hp<=0);
		}
		else{
			return false;
		}
	};
	GO_Boss_Bobo.prototype._inflictVal = {hp:2, chi:0};
	GO_Boss_Bobo.prototype.inflict = function(){
		return this._inflictVal;
	};

	GO_Boss_Bobo.prototype.removeSmoke = function(smoke){
		var index = this.smoke.indexOf(smoke);
		if (index!=-1){
			this.smoke.splice(index,1);
			smoke.host_obj=null;
		}
	};
	GO_Boss_Bobo.prototype.infect = function(){
		//this.infection++;
		this.hp+=2;
		
		if (this.control_state == this.CONTROL_STATES.stun){
			this.control_state = this.CONTROL_STATES.wait;
			this.control_timer = 0;
		}
	};

	GO_Boss_Bobo.prototype.hpScaled = function(time){
		return (time*this.hp)>>4;
	};

	// A = jump
	// B = shoot
	GO_Boss_Bobo.prototype.aiControl = function(){
		switch (this.control_state){
			case this.CONTROL_STATES.prefight:
				// DEBUG
				//this.controls.press(this.controls,"A");
			break;
			case this.CONTROL_STATES.wait:

				++this.control_timer;
				if (this.control_timer >= this.hpScaled(90)){
					this.control_timer = 0;
					this.controls.unpress(this.controls,"right");
					this.controls.unpress(this.controls,"left");
					this.controls.unpress(this.controls,"down");
					this.controls.unpress(this.controls,"up");

					this.controls.press(this.controls,"A");

					this.control_state = this.CONTROL_STATES.jump;
				}
			break;
			case this.CONTROL_STATES.jump:
				if (this.grounded){
					this.control_timer = 0;
					this.controls.unpress(this.controls,"A");
					this.controls.press(this.controls,"B");
					this.control_state = this.CONTROL_STATES.catch;
				}
			break;
			case this.CONTROL_STATES.catch:
				if (this.ball!=null){
					this.control_timer = 0;
					this.control_state = this.CONTROL_STATES.aim;
				}
				else{
					++this.control_timer;
					if (this.control_timer>90){
						// somehow, the requested ball never arrived...
						this.control_timer = 0;
						this.controls.press(this.controls,"A");
						this.control_state = this.CONTROL_STATES.jump;
					}
				}
			break;
			case this.CONTROL_STATES.aim:
				if (this.meli){
					/*if (this.meli.sprite.y+this.meli.sprite.box_bottom < this.sprite.y+this.sprite.box_top){
						this.controls.unpress(this.controls,"down");
						this.controls.press(this.controls,"up");
					}
					else if (this.meli.sprite.y+this.meli.sprite.box_top > this.sprite.y+this.sprite.box_bottom){
						this.controls.unpress(this.controls,"up");
						this.controls.press(this.controls,"down");
					}
					else{
						this.controls.unpress(this.controls,"down");
						this.controls.unpress(this.controls,"up");
					}*/
					if (this.meli.sprite.y+this.meli.sprite.box_bottom < this.sprite.y+this.sprite.box_bottom+32){
						// meli is on the top tier- high-toss across
						this.controls.unpress(this.controls,"down");
						this.controls.press(this.controls,"up");
					}
					else if (this.meli.sprite.y+this.meli.sprite.box_bottom < this.sprite.y+this.sprite.box_bottom+96){
						// meli is on the middle tier- low-pitch to catch the slope
						this.controls.unpress(this.controls,"up");
						this.controls.press(this.controls,"down");
					}
					else{
						// meli is on the lower tier- be clever and neutral-pitch to bank off the top platform
						// and be headed the right way to get tot he bottom tier quickly
						this.controls.unpress(this.controls,"up");
						this.controls.unpress(this.controls,"down");
					}
				}

				++this.control_timer;
				if (this.control_timer >= this.hpScaled(120)){
					this.control_timer = 0;

					this.controls.unpress(this.controls,"B");

					this.control_state = this.CONTROL_STATES.fire;
				}
			break;
			case this.CONTROL_STATES.fire:
				++this.control_timer;
				if (this.control_timer >= 15){
					this.control_timer = 0;

					this.controls.unpress(this.controls,"B");

					this.controls.unpress(this.controls,"right");
					this.controls.unpress(this.controls,"left");
					this.controls.unpress(this.controls,"down");
					this.controls.unpress(this.controls,"up");

					this.control_state = this.CONTROL_STATES.wait;
				}
			break;
			case this.CONTROL_STATES.stun:
				this.controls.unpress(this.controls,"right");
				this.controls.unpress(this.controls,"left");
				this.controls.unpress(this.controls,"down");
				this.controls.unpress(this.controls,"up");
				this.controls.unpress(this.controls,"A");
				this.controls.unpress(this.controls,"B");

				++this.control_timer;
				if (this.control_timer >= this.hpScaled(150)){
					this.control_timer = this.hpScaled(75); // intentionally reduce wait time
					this.control_state = this.CONTROL_STATES.wait;
				}
			break;
			case this.CONTROL_STATES.postfight:
				this.controls.unpress(this.controls,"right");
				this.controls.unpress(this.controls,"left");
				this.controls.unpress(this.controls,"down");
				this.controls.unpress(this.controls,"up");
				this.controls.unpress(this.controls,"A");
				this.controls.unpress(this.controls,"B");
			break;
		}
	};

	GO_Boss_Bobo.prototype.Update = function(){
		// peaceful mob style target lookup
		if (!this.meli && !this.meliFind){
			this.meli = this.manager.getNamedObject("Meli");
			this.meliFind = true;
		}
		//if (this.meli){
		//	this.direction = (this.meli.sprite.x>this.sprite.x) ? 1 : -1;
		//}

		this.aiControl();

		// generic mob style control update
		/*var dx = 0;
		var dy = 0;
		var old_xs = [];
		var new_xs = [];
		var dxs = [];
		var old_ys = [];
		var new_ys = [];
		var dys = [];
		var spriteMoveRes = null;

		var leftMoveArr = null;
		var rightMoveArr = null;
		var upMoveArr = null;
		var downMoveArr = null;

		var oldAnimState = this.anim_state;

		var i=0;*/
		this._heap.dx = 0;
		this._heap.dy = 0;
		//var old_xs = [];
		//var new_xs = [];
		//var dxs = [];
		//var old_ys = [];
		//var new_ys = [];
		//var dys = [];

		this._heap.leftMoveArr = null;
		this._heap.rightMoveArr = null;
		this._heap.upMoveArr = null;
		this._heap.downMoveArr = null;

		this._heap.oldAnimState = this.anim_state;

		this._heap.i=0;

		this._heap.oldAnimState = this.anim_state;
		this._heap.wasGrounded = this.grounded;
		this.grounded = false;
		this.anim_state = (this._heap.oldAnimState==this.ANIM_STATES.throw && this.frame_timer<15) ? this.ANIM_STATES.throw : this.ANIM_STATES.chatter;

		// jump to shake down a spikefruit
		if (this.controls.pressed.A && this._heap.wasGrounded){
			this.anim_state = this.ANIM_STATES.air;
			this.jump_index=0;
		}
		else if (this.controls.held.A && this._heap.oldAnimState == this.ANIM_STATES.air && this.jump_index<this.jump_rise.length){
			this.anim_state = this.ANIM_STATES.air;

			this.sprite.move(0,this.jump_rise[this.jump_index],  this._heap.spriteMoveRes);

			if (this._heap.spriteMoveRes.flags & DEFINE_SPRITE_MOVE_RESULT_EJECTED_V){
				this.jump_index = this.jump_rise.length;
			}
			else{
				++this.jump_index;
			}
		}
		else{
			if (!this.controls.held.A){
				this.jump_index = this.jump_rise.length;
			}

			this.sprite.move(0,this.fall[this.fall_index],  this._heap.spriteMoveRes);

			if (this._heap.spriteMoveRes.flags & DEFINE_SPRITE_MOVE_RESULT_EJECTED_V){
				this.grounded = true;

				if (this.fall_index>5){ // arbitrary at-speed
					
					if (this.control_state == this.CONTROL_STATES.prefight && this.sprite.map_room!==DATA_SDS_MAP_ROOMS['monkeyville_bossroom']){
						// co-opt Meli's sfx chip since ours is going away when the room changes
						if (this.meli){
							if (this.meli.sfx) this.meli.sfx.stop();
							this.meli.sfx = new Chiptune("FX_fallcrash",SDS.sfxchip);
							this.meli.sfx.play(false);
						}
						else{
							if (this.sfx) this.sfx.stop();
							this.sfx = new Chiptune("FX_fallcrash",SDS.sfxchip);
							this.sfx.play(false);
						}

						// wreck room
						// need to know where we are to know where to go; encapsulate this.sprite.map_room in a closure
						this._heap.getDestName = function(room){
							return function(){
								return (room.name==="monkeyville_toproom") ? "monkeyville_breakroom" : "monkeyville_bossroom";
							};
						}(this.sprite.map_room);
						// full-room autoport trigger
						this._heap.port = new GO_Passage(this.sprite.map_room.pixel_width,this.sprite.map_room.pixel_height,this._heap.getDestName,noop);
						this._heap.port.sprite.x = 0;
						this._heap.port.sprite.y = 0;

						this.sprite.map_room.addSprite(this._heap.port.sprite);
						this.manager.add(this._heap.port);
						this._heap.port = null;
						this._heap.getDestName = null;
					}
					else{
						if (this.sfx) this.sfx.stop();
						this.sfx = new Chiptune("FX_fallcrash",SDS.sfxchip);
						this.sfx.play(false);

						this._heap.ball = new GO_Ball(GO_Ball.TYPES.SPIKEFRUIT);
						//this.rock.cachedUpdate = this.rock.Update;
						//this.rock.Update = noop;
						this._heap.ball.sprite.x = this.sprite.x + 3 - this._heap.ball.sprite.center.x;
						this._heap.ball.sprite.y = this.sprite.map_room.pixel_margin;

						this.sprite.map_room.addSprite(this._heap.ball.sprite);
						this.manager.add(this._heap.ball);
					}
				}

				this.fall_index=0;
				
			}
			else{
				++this.fall_index;
				this.fall_index = Math.min(this.fall_index,this.fall.length-1);
			}
		}
		
		// pre-apply object's IndisposedUpdate in case it causes itself to be un-held
		//if (this.ball!=null && this.ball.IndisposedUpdate) this.ball.IndisposedUpdate();
		// this line is included only for reference of "proper" usage.
		// Anything which un-holds itself PROBABLY un-holds itself from Meli, not Bobo
		// which would be awkward.  Better just not call it in this limited case.

		// throw boulder
		if (this.controls.held.B){
			this.anim_state = this.ANIM_STATES.windup;
		}
		else if (this.controls.released.B){
			this.anim_state = this.ANIM_STATES.throw;

			if (this.ball!=null){
				this._heap.ball = this.ball;
				this.releaseBall();

				if (this.controls.held.down){
					this._heap.ball.sprite.x = this.sprite.x+this.sprite.box_right+1; // avoid self-harming behavior
				}
				if (!this.controls.held.up){
					this._heap.ball.sprite.x = this.sprite.x+this.sprite.box_right+1; // devious monkey- set up to bank off the walkway
				}

				MGO_Projectile(
					this._heap.ball,
					1,
					this.controls.held.up ? -1 : this.controls.held.down ? 1 : 0);
				// play a bit fast and loose here, knowing ball will twiddle its own inflict when and if it lands.
				// we want it able ot hit Meli in the mean time.
				this._heap.ball.interactions |= DEFINE_GAMEOBJECT_INTERACTIONS.INFLICT;

				this._heap.ball=null;
			}
		}

		if (this.ball!=null){
			this.ball.sprite.x = this.sprite.x + 3 - this.ball.sprite.center.x;
			this.ball.sprite.y = this.sprite.y + 1 - this.ball.sprite.box_bottom;
		}

		if (this.control_state==this.CONTROL_STATES.postfight){
			this.anim_state = this.ANIM_STATES.dead;
			this.sprite.setBoundingBox(0,16,31,28);
		}
		else if (this.control_state==this.CONTROL_STATES.stun){
			this.anim_state = this.ANIM_STATES.stun;
			this.sprite.setBoundingBox(0,16,31,28);
		}
		else{
			this.sprite.setBoundingBox(0,0,31,28);
		}
		

		if (this._heap.oldAnimState != this.anim_state){
			this.frame_timer=0;
		}
		this.frame_timer++;
		this._heap.aniFrame = (this.frame_timer>>2);
		if (this.anim_state==this.ANIM_STATES.chatter){
			this._heap.aniFrame = this._heap.aniFrame% this.anim_frames[this.anim_state].length;
		}
		else{
			this._heap.aniFrame = Math.min(this._heap.aniFrame,this.anim_frames[this.anim_state].length-1);
		}

		this.sprite.frame = this.anim_frames[this.anim_state][this._heap.aniFrame];

		//this.sprite.frame = this.sprite.frame & 7;
		//if (this.rock!=null) this.sprite.frame+=4;
		//if (this.direction<0) this.sprite.frame+=16;



		// Enemy-style check HP and destroy
		// TODO: instead, throw an event
		if (this.hp<=0 && this.control_state!=this.CONTROL_STATES.postfight){
			this.releaseBall();

			while (this.smoke.length>0){
				// var s;
				//this._heap.s = this.smoke.pop();
				//if (this._heap.s.state!="dissipate"){
				//	this._heap.s.state="dissipate";
				//	this._heap.s.state_timer = 0;
				//	this._heap.s.frame_timer = 0;
				//	this._heap.s.interactions = 0;
				//}
				if (this.smoke[0].state!="dissipate"){
					this.smoke[0].state="dissipate";
					this.smoke[0].state_timer = 0;
					this.smoke[0].frame_timer = 0;
					this.smoke[0].interactions = 0;
				}
				this.smoke.shift();
			}

			this.endFight();
		}
	};
	// does the basic make-dangerous-iff-infected check.
	// some enemies may want to be dangerous even when uninfected,
	// so let individual AIs handle the call.
	GO_Boss_Bobo.prototype.toggleInteractionsStd = function(){
		if (this.infection>0){
			this.interactions |= 
				DEFINE_GAMEOBJECT_INTERACTIONS.DAMAGE |
				DEFINE_GAMEOBJECT_INTERACTIONS.INFLICT;
		}
		else{
			this.interactions &=
				~DEFINE_GAMEOBJECT_INTERACTIONS.DAMAGE &
				~DEFINE_GAMEOBJECT_INTERACTIONS.INFLICT;
		}
	};

