"use strict";
/* Gabe, acolyte of the sky shrine.  Not a psyche that needed Malfose-gas. */
var GO_Boss_Gabe = function(){
	this.sprite = new Sprite(GO_Boss_Gabe.spriteSheet,"BossGabe");
	this.sprite.collision_flags = 
		DEFINE_SPRITE_COLLISION_FLAGS.GROUND |
		DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_ACTIVE |
		DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_PASSIVE;
	this.sprite.setBoundingBox(9,5,22,27);
	this.sprite.game_object = this; // no longer a generic GO

	this.chibiSprite = new Sprite(GO_Boss_Gabe.chibiSheet,"Gabe");
	this.chibiSprite.collision_flags = 0;
	this.chibiSprite.setBoundingBox(2,0,13,15);
	this.chibiSprite.game_object = this; // no longer a generic GO
	this.chibiSprite.frame = 5;
	this.chibiSprite.visible = false;

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

	this.horizontal_move_index=0;
	this.vertical_move_index=0;
	this.jump_index = 0;
	this.fall_index = 0;
	this.grounded = false;
	this.in_water_cache = false;
	this.anim_state = this.ANIM_STATES.fall;
	this.fall_air = this.fall_air_fast;
	this.frame_timer=0;

	this.control_state = this.CONTROL_STATES.prefight;

	this.direction = 1; // +/-1 for facing

	this.casting_cooldown = 0;
	this.casting_burntime = 0;
	this.flying = false;
	this.flight_timer = 0;
	this.hurt = false;
	this.hurt_timer = 0;

	this.spell = this.SPELLS.NONE; //temp
	this.spell_target = null; //temp
	this.spell_target_name = "Meli"; // temp
	this.spell_cooldown = 0;
	this.spell_timer = 0;
	this.spellshots = [];

	this.hp = 20;
	this.infection = 10;
	this.smoke = [];

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

GO_Boss_Gabe.prototype = new GameObject(
	new Sprite(
		GO_Boss_Gabe.spriteSheet,
		"BossGabe"
	),
	"BossGabe"
	);

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

	// NPC walk controls
	GO_Boss_Gabe.prototype.leftward_move_land = [0,-1,-2];
	GO_Boss_Gabe.prototype.rightward_move_land = [0,1,2];
	GO_Boss_Gabe.prototype.leftward_move_water = [0,-1];
	GO_Boss_Gabe.prototype.rightward_move_water = [0,1];
	GO_Boss_Gabe.prototype.jump_rise_air = [ //80px = 5 blocks
		-5,-5,-5,-5,-5,-5,
		-4,-4,-4,-4,-4,-4,
		-3,-3,-3,-3,-3, 
		-2,-2,-2,-2,
		-1,-1,-1,
		0, 0];
	GO_Boss_Gabe.prototype.fall_air_slow = [ 
		1, 
		2, 2, 2, 
		3, 3, 3]; // truncated at 3 because wings
	GO_Boss_Gabe.prototype.fall_air_fast = [ 
		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_Gabe.prototype.jump_rise_water = [ //8px = .5 blocks
		-2, -2, -2, 
		-1, -1, 
		0]; // maybe a bird; not a duck.
	GO_Boss_Gabe.prototype.fall_water = [ 
		-1]; // naturally buoyant; "fall" towards surface

	// SimpleMove (fly) controls
	GO_Boss_Gabe.prototype.leftward_move = GO_Boss_Gabe.prototype.leftward_move_land;
	GO_Boss_Gabe.prototype.rightward_move = GO_Boss_Gabe.prototype.rightward_move_land;
	GO_Boss_Gabe.prototype.upward_move = GO_Boss_Gabe.prototype.leftward_move_land;
	GO_Boss_Gabe.prototype.downward_move = GO_Boss_Gabe.prototype.rightward_move_land;


	GO_Boss_Gabe.prototype.ANIM_STATES = {
		"stand" : 0,
		"walk" : 1,
		"fly" : 2,
		"jump" : 3,
		"fall" : 4,
		"cast" : 5,
		"discharge" : 6,
		"down" : 7,
		"dive" : 8,
		"glide" : 9,
		"hurt" : 10
	};
	GO_Boss_Gabe.prototype.anim_frames = [
		[0], //"stand" : 
		[12,13,14], //"walk" : 
		[12,13,14], //"fly" : 
		[8], //"jump" : 
		[9], //"fall" : 
		[6], //"cast" : 
		[7], //"discharge" : 
		[5], //"down" : 
		[10], //"dive" : 
		[9], //"glide" : 
		[4], //"hurt" : 
	];

	GO_Boss_Gabe.prototype.CONTROL_STATES = {
		"prefight" : 0,
		"fight" : 1,
		"postfight" : 2
	};

	GO_Boss_Gabe.prototype.SPELLS = {
		"NONE" : 0,
		"LIGHTNING" : 1,
		"DARK" : 2,
		"LIGHT" : 3,
	};

	GO_Boss_Gabe.prototype.SPELL_PARAMS = [
		{ // NONE
			spell_cooldown: -1, // time between castSpell logic runs while holding, -1 for infinite (one-shot)
			casting_cooldown: 0 // discharge state length
		},
		{ // LIGHTNING
			spell_cooldown: 1, // time between castSpell logic runs while holding, -1 for infinite (one-shot)
			casting_cooldown: 8 // discharge state length
		},
		{ // DARK
			spell_cooldown: 5, // 1 spark every 5 ticks
			casting_cooldown: 15 // acknowledging time for sparks to fade
		},
		{ // LIGHT
			spell_cooldown: 5, // 1 spark every 5 ticks
			casting_cooldown: 15 // acknowledging time for sparks to fade
		},
	];

	GO_Boss_Gabe.prototype.addToRoom = function(mapRoom){
		mapRoom.addSprite(this.sprite);
		mapRoom.addSprite(this.chibiSprite);
	}
	GO_Boss_Gabe.prototype.clearFromRoom = function(mapRoom){
		this.cleanSpell();
		this.flushSpellshots();
		mapRoom.removeSprite(this.sprite);
		mapRoom.removeSprite(this.chibiSprite);
	}

	GO_Boss_Gabe.prototype.checkEvent = function(sourceGO){
		if (sourceGO && sourceGO.sprite) this.direction = (sourceGO.sprite.x<this.sprite.x) ? -1 : 1;
		
		// just for fun & debugging
		//if (sourceGO.controls){
		//	this.controls = sourceGO.controls;
		//}

		//if (this.control_state===this.CONTROL_STATES.prefight) this.startFight();

		//return Dialogue_getCurrentEvent[this.name]();

		if (this.control_state == this.CONTROL_STATES.postfight){
			if (this.hp<=0){
				return "Gabe_death";
			}
			else{
				this._heap.faith = 0;
				if (GLOBAL_DIARY.check(4,"LIKED_GABE")) ++this._heap.faith;
				if (GLOBAL_DIARY.check(4,"DISLIKED_GABE")) --this._heap.faith;
				if (GLOBAL_DIARY.check(5,"LIKED_GABE")) ++this._heap.faith;
				if (GLOBAL_DIARY.check(5,"DISLIKED_GABE")) --this._heap.faith;

				if (this._heap.faith >= 0){
					// good path low% has a choice that may leave Gabe intact
					if (GLOBAL_SCRATCHPAD.check("GABE_CHECKED")){
						return "Gabe_endfight_good2";
					}
					else{
						GLOBAL_SCRATCHPAD.note("GABE_CHECKED",true);
						return "Gabe_endfight_good";
					}
				}
				else{
					return "Gabe_endfight_bad";
				}
			}
		}
	}

	GO_Boss_Gabe.prototype.castSpell = function(){
		if (this.spell_cooldown!==0) return;

		switch (this.spell){
			case this.SPELLS.NONE:
				// nop
			break;
			case this.SPELLS.LIGHTNING:
				if (this.grounded){
					this._heap.lit = new GO_Lightning(this.direction<<3,0);
					this._heap.lit.sprite.x = (this.direction>0) ?
						this.sprite.x+16 :
						this.sprite.x+8;
					this._heap.lit.sprite.y = this.sprite.y+12;
				}
				else{
					this._heap.lit = new GO_Lightning(0,8);
					this._heap.lit.sprite.x = this.sprite.x+12;
					this._heap.lit.sprite.y = this.sprite.y+20;
				}
				
				this._heap.lit.addToRoom(this.sprite.map_room);
				this.manager.add(this._heap.lit);

				this._heap.lit = null;
			break;
			case this.SPELLS.DARK:
				this._heap.spell = {
					type: this.spell, 
					obj: new GO_SinOrbit(this.spell_target,6,3,0,0),
					lifetime: 300
				};
				this.spellshots.push(this._heap.spell);
				this.sprite.map_room.addSprite(this._heap.spell.obj.sprite);
				this.manager.add(this._heap.spell.obj);

				this._heap.spell=null;
			break;
			case this.SPELLS.LIGHT:
				this._heap.spell = {
					type: this.spell, 
					obj: new GO_SinOrbit(this.spell_target,3,5,1,2),
					lifetime: 300
				};
				this.spellshots.push(this._heap.spell);
				this.sprite.map_room.addSprite(this._heap.spell.obj.sprite);
				this.manager.add(this._heap.spell.obj);

				this._heap.spell=null;
			break;
		}

		this.spell_cooldown = this.SPELL_PARAMS[this.spell].spell_cooldown;
	}

	GO_Boss_Gabe.prototype.upkeepSpell = function(){
		++this.spell_timer;
		this.spell_timer = this.spell_timer&255; // should be enough for anything

		// per spell object updates
		// (for objects which aren't self-managing)
		for (this._heap.i=0; this._heap.i<this.spellshots.length; ++this._heap.i){
			this._heap.spell = this.spellshots[this._heap.i];

			switch(this._heap.spell.type){
				case this.SPELLS.NONE:
					// nop
				break;
				case this.SPELLS.DARK:
					this._heap.spell.obj.sprite.frame = ((this.spell_timer+this._heap.i)&31)>>2;
				break;
				case this.SPELLS.LIGHT:
					this._heap.spell.obj.sprite.frame = ((this.spell_timer+this._heap.i)&31)>>2;
				break;
			}

			if (this._heap.spell.lifetime>0) --this._heap.spell.lifetime;

			if (this._heap.spell.lifetime===0){
				this.sprite.map_room.removeSprite(this._heap.spell.obj.sprite);
				this.manager.remove(this._heap.spell.obj);
				this.spellshots.splice(this._heap.i,1);
				--this._heap.i;
			}
		}
		this._heap.spell=null;
	}

	GO_Boss_Gabe.prototype.cleanSpell = function(){
		switch(this.spell){
			case this.SPELLS.NONE:
				/*for (this._heap.i=0; this._heap.i<this.spellshots.length; ++this._heap.i){
					if (this.spellshots[this._heap.i].type===this.SPELLS.KILL){
						this.spellshots[this._heap.i].lifetime=this.spellshots[this._heap.i].lifetime>>3;
					}
				}*/
				//this._heap.spell=null;
			break;
			case this.SPELLS.DARK:
				for (this._heap.i=0; this._heap.i<this.spellshots.length; ++this._heap.i){
					if (this.spellshots[this._heap.i].type===this.SPELLS.DARK){
						this.spellshots[this._heap.i].lifetime=this.spellshots[this._heap.i].lifetime>>3;
					}
				}
				this._heap.spell=null;
			break;
			case this.SPELLS.LIGHT:
				for (this._heap.i=0; this._heap.i<this.spellshots.length; ++this._heap.i){
					if (this.spellshots[this._heap.i].type===this.SPELLS.LIGHT){
						this.spellshots[this._heap.i].lifetime=this.spellshots[this._heap.i].lifetime>>3;
					}
				}
				this._heap.spell=null;
			break;
		}

		this.spell_cooldown = 0;
	}

	GO_Boss_Gabe.prototype.flushSpellshots = function(){
		while (this.spellshots.length){
			this._heap.spell = this.spellshots.pop();

			this.sprite.map_room.removeSprite(this._heap.spell.obj.sprite);
			this.manager.remove(this._heap.spell.obj);
		}
		this._heap.spell=null;
	}

	GO_Boss_Gabe.prototype.startFight = function(){
		this.control_state = this.CONTROL_STATES.fight;
		this.spell = this.SPELLS.LIGHTNING;
		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.DAMAGE;// |
			//DEFINE_GAMEOBJECT_INTERACTIONS.INFLICT;
	};

	GO_Boss_Gabe.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.controls.unpress(this.controls,"A");
		this.controls.unpress(this.controls,"B");
		this.controls.unpress(this.controls,"left");
		this.controls.unpress(this.controls,"right");
		this.controls.unpress(this.controls,"up");
		this.controls.unpress(this.controls,"down");

		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;
	};

	GO_Boss_Gabe.prototype.cleanupFight = function(){
		/*
		this._heap.soul = new GO_Soul("GUY");
		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
		*/
	};

	// Enemy-style support routines
	GO_Boss_Gabe.prototype.damage = function(source,points){
		this.hurt = true;
		this.hurt_timer = 16;
		this.anim_state = this.ANIM_STATES.hurt;

		if (points>0){
			this.hp-=points;
			
			this.direction = (source.sprite.x+source.sprite.center.x) < (this.sprite.x+this.sprite.center.x) ? -1 : 1;
			
			if (this.hp<=0){
				if (DATA_SDS_DIALOGUE["Gabe_c5_boss_scream"]){
					SDS.dlgMgr.open("Gabe_c5_boss_scream",function(index,retval){});
				}
			}

			return (this.hp<=0);
		}
		else{
			//var cough = new GO_Smoke();
			this._heap.cough = new GO_Smoke();
			this._heap.cough.sprite.x = this.sprite.x;
			this._heap.cough.sprite.y = this.sprite.y;
			//this._heap.cough.launch_dir = {x:this.direction, y:0};
			this._heap.cough.launch_dir.x=-this.direction;
			this._heap.cough.launch_dir.y=-1;
			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.infection;

			this.direction = (source.sprite.x+source.sprite.center.x) < (this.sprite.x+this.sprite.center.x) ? -1 : 1;

			return false;
		}
	};
	GO_Boss_Gabe.prototype._inflictVal = {hp:1, chi:0};
	GO_Boss_Gabe.prototype.inflict = function(){
		return this._inflictVal;
	};

	GO_Boss_Gabe.prototype.removeSmoke = function(smoke){
		var index = this.smoke.indexOf(smoke);
		if (index!=-1){
			this.smoke.splice(index,1);
			smoke.host_obj=null;
		}
	};
	GO_Boss_Gabe.prototype.infect = function(){
		++this.infection;
	};

	GO_Boss_Gabe.prototype.aiControl = function(){
		if (!this.meli) return;
		if (this.control_state === this.CONTROL_STATES.prefight) return;
		if (this.control_state === this.CONTROL_STATES.postfight){
			if (this.grounded) this.controls.press(this.controls,"down");

			return;
		} 

		this._heap.cx = this.sprite.x + this.sprite.center.x;
		this._heap.cy = this.sprite.y + this.sprite.center.y;
		this._heap.mcx = this.meli.sprite.x + this.meli.sprite.center.x;
		this._heap.mcy = this.meli.sprite.y + this.meli.sprite.center.y;

		// if Meli is above, jump into fly and then
		// diagonal-up towards her
		if (this._heap.mcy < this._heap.cy){
			// exit any casting state
			this.controls.unpress(this.controls,"B");

			if (this.anim_state === this.ANIM_STATES.fly){
				// if flying, fly.
				this.controls.unpress(this.controls,"down");
				this.controls.press(this.controls,"up");

				if (this._heap.mcx < this._heap.cx){
					this.controls.unpress(this.controls,"right");
					this.controls.press(this.controls,"left");
				}
				else if (this._heap.mcx > this._heap.cx){
					this.controls.unpress(this.controls,"left");
					this.controls.press(this.controls,"right");
				}
				else{
					this.controls.unpress(this.controls,"left");
					this.controls.unpress(this.controls,"right");
				}
			}
			else if (this.anim_state === this.ANIM_STATES.fall){
				if (this.controls.held.A){
					// if we're falling with A held, we've topped out or bonked.
					// release A so we can pick up flight.
					this.controls.unpress(this.controls,"A");
				}
				else{
					// otherwise, if in normal fall, press A to start fly
					this.controls.press(this.controls,"A");
				}
			}
			else if (this.grounded){
				if (this.controls.held.A){
					// corner case if e.g. we've been knocked out
					// of fly somehow, or flown just in line with the ground
					// release A so we can jump
					this.controls.unpress(this.controls,"A");
				}
				else{
					// otherwise, press A to jump
					this.controls.press(this.controls,"A");
				}
			}
		}
		else{
			// finish any ongoing jump and get to fly state
			if (this.anim_state === this.ANIM_STATES.jump){
				// NOP
			}
			else if (this.anim_state === this.ANIM_STATES.fall && !this.controls.held.down){
				if (this.controls.held.A){
					// if we're falling with A held, we've topped out or bonked.
					// release A so we can pick up flight.
					this.controls.unpress(this.controls,"A");
				}
				else{
					// otherwise, if in normal fall, press A to start fly
					this.controls.press(this.controls,"A");
				}
			}
			// if mid-dive, continue until grounded
			else if (this.controls.held.down){
				this.controls.unpress(this.controls,"B");

				if (this.grounded){
					this.controls.unpress(this.controls,"down");
				}
			}
			else{
				// if Meli is below, lightning down and 
				// track towards her horizontally.

				// these inputs will also result in horizontal lightning 
				// shot in meli's direction when grounded.

				// there are geometric configurations where gabe can permapin
				// the player in a corner with beam lightning.
				// let him get tired now and again.
				if (this.casting_burntime>=240){
					this.controls.unpress(this.controls,"A");
					this.controls.unpress(this.controls,"B");
					this.controls.unpress(this.controls,"left");
					this.controls.unpress(this.controls,"right");
					this.controls.unpress(this.controls,"down");
					this.controls.unpress(this.controls,"up");
					this.hurt = true;
					this.hurt_timer = 24;
					this.anim_state = this.ANIM_STATES.hurt;
				}
				else{
					this.controls.press(this.controls,"B");
					this.controls.unpress(this.controls,"down");
					this.controls.unpress(this.controls,"up");

					// note that once grounded, left and right will NOT change
					// lightning direction until B is released
					if (this._heap.mcx < this._heap.cx){
						if (this.direction>0) this.controls.unpress(this.controls,"B");
						this.controls.unpress(this.controls,"right");
						this.controls.press(this.controls,"left");
					}
					else if (this._heap.mcx > this._heap.cx){
						if (this.direction<0) this.controls.unpress(this.controls,"B");
						this.controls.unpress(this.controls,"left");
						this.controls.press(this.controls,"right");
					}
					else{
						this.controls.unpress(this.controls,"left");
						this.controls.unpress(this.controls,"right");
					}

					// if Meli is below but obstructed,
					// drop to the platform she's under.
					// Rather, begin a dive in the current 
					// left/right direction.
					// This is what you need to fake out
					// to get him to whiff and crash on the
					// blockfloor.
					if (Math.abs(this._heap.mcx - this._heap.cx)<=8 && !this.grounded){
						if (this.sprite.map_room.findVertically(DEFINE_MAP_TERRAIN_TYPES.GROUND,this._heap.cx,this._heap.cy,1,this.sprite.map_room.pixel_height,false) < this._heap.mcy - this._heap.cy){
							this.controls.unpress(this.controls,"A");
							this.controls.press(this.controls,"down");
						}
					}
				}
			}
		}

		if (this.controls.held.B){
			++this.casting_burntime;
		}
		else{
			this.casting_burntime=0;
		}

		this._heap.cx = null;
		this._heap.cy = null;
		this._heap.mcx = null;
		this._heap.mcy = null;
	}

	GO_Boss_Gabe.prototype.Update = function(){
		// peaceful mob style target lookup
		if (!this.meli && !this.meliFind){
			this.meli = this.manager.getNamedObject("Meli");
			this.meliFind = true;
		}

		this.aiControl();

		this._heap.oldAnimState = this.anim_state;

		if (this.hurt){
			this.interactions &= ~DEFINE_GAMEOBJECT_INTERACTIONS.DAMAGE;
			this.sprite.move(-this.direction,0,  this._heap.spriteMoveRes);
			--this.hurt_timer;
			if (this.hurt_timer<=0){
				this.hurt = false;
				this.interactions |= DEFINE_GAMEOBJECT_INTERACTIONS.DAMAGE;
			}
			else if (!(this.hurt_timer & 4)){
				this.sprite.move(0,-1,  this._heap.spriteMoveRes);
			}
		}
		else{
			// BEWARE
			// GARBAGE CODE AHEAD
			// srsly just ripped Ravi's flight/cast code
			// and bolted on some case-specific conditionals
			// for dive & glide fakestates

			// check controls, set flying
			// to fly, "jump" in midair & hold
			if ((this.flying && this.controls.held.A) || (!this.grounded && !this.in_water_cache && this.controls.pressed.A)){
				this.flying = true;
				this.fall_index = 0;
			}
			else{
				this.flying=false;
			}

			
			if (this.controls.held.B && (this.grounded || this.flying)){
				this.casting_cooldown = this.SPELL_PARAMS[this.spell].casting_cooldown;
				this.fall_index = 0;
				this.anim_state = this.flying ? this.ANIM_STATES.glide : this.ANIM_STATES.cast;

				if (this.flying){ // Gabe can cast while flying; spell will likely always be down-bolt
					this.vertical_move_index=0;
					this.SimpleMove();
					//this.anim_state=this.ANIM_STATES.fly;
					if (this.controls.held.left) this.direction=-1;
					if (this.controls.held.right) this.direction=1;
				}
			}
			else if (this.casting_cooldown>0){
				--this.casting_cooldown;
				this.anim_state = this.flying ? this.ANIM_STATES.glide : this.ANIM_STATES.discharge;
			}
			else if (this.flying){
				this.SimpleMove();
				this.anim_state=this.ANIM_STATES.fly;
				if (this.controls.held.left) this.direction=-1;
				if (this.controls.held.right) this.direction=1;
			}
			else{
				this.fall_air = this.controls.held.A ? this.fall_air_slow : this.fall_air_fast;
				if (this.controls.held.down) this.fall_index = this.fall_air.length-1;
				this.NPCWalk();
				// interferes with natural fall state
				//if (this.controls.held.down){
				//	this.anim_state=this.ANIM_STATES.dive;
				//}

				if (!this.flying && this.grounded && this.controls.held.down){
					this.anim_state = this.ANIM_STATES.down;
				}
			}
			
			// actual spells logic
			if (this.spell_cooldown>0) --this.spell_cooldown;
			if (this.controls.held.B){
				if (this.spell_target_name && !this.spell_target){
					this.spell_target = this.manager.getNamedObject(this.spell_target_name);
				}
				if (!this.spell_target){
					this.spell_target = this; // for Gabe's psyche, this actually makes sense...
				}
				this.castSpell();
			}
			else if (this.controls.released.B){
				this.cleanSpell();
				this.spell_target = null;
			}
			if (this.spellshots.length){
				this.upkeepSpell();
			}
		}

		if (this._heap.oldAnimState != this.anim_state){
			this.frame_timer=0;
		}
		this.frame_timer++;
		this._heap.aniFrame = (this.frame_timer>>3) % this.anim_frames[this.anim_state].length;

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

		if (this.hurt){
			this.sprite.frame = 4;
		}
		//else if (this.flying){
		//	++this.flight_timer;
		//	this.flight_timer = this.flight_timer & 15;
		//	if (this.flight_timer & 8){
		//		++this.sprite.frame;
		//	}
		//}
		else if (this.anim_state===this.ANIM_STATES.fall && this.controls.held.down){
			this.sprite.frame = this.anim_frames[this.ANIM_STATES.dive][0];
		}

		this.sprite.frame = this.sprite.frame & 15;
		if (this.direction<0) this.sprite.frame+=16;


		// Enemy-style check HP and destroy
		// TODO: instead, throw an event
		if (!this.hurt && (this.hp<=0 || this.infection<=0) && this.control_state!=this.CONTROL_STATES.postfight){
			while (this.smoke.length>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();
		}
	}

