"use strict";
/* Malfose. Perfectly reasonable, level-headed, fabulous dark dragon-god advocating problem-solving through cathartic destruction. */
var GO_Malfose = function(){
	this.sprite = new Sprite(GO_Malfose.spriteSheet,"Malfose");
	this.sprite.collision_flags = 
		//DEFINE_SPRITE_COLLISION_FLAGS.GROUND |
		DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_ACTIVE |
		DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_PASSIVE;
	this.sprite.setBoundingBox(10,0,21,31);
	this.sprite.game_object = this; // no longer a generic GO

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

	this.horizontal_move_index=0;
	this.vertical_move_index=0;
	
	this.anim_state = this.ANIM_STATES.neutral;
	
	this.frame_timer=0;

	this.control_state = this.CONTROL_STATES.prefight;

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

	this.casting_cooldown = 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.spell_counter = 0;
	//this.spell_sequence = 0;
	this.spellshots = [];

	this.AI = {
		phase: 0, // 0 off, 1 phase1, 2 phase2, 3 phase 3 (2 main)
		sequence: -1,
		timer: 0,
		delay: 0,

		base_pos: {x:0,y:0},
	};

	this.hp = 255;
	//this.infection = 10;
	this.smoke = [];

	this.spikes = [[],[],[]];
	this.sp_ring = 0;
	this.sp_index = 0;
	this.sp_target = {x:0,y:0};
	this.sp_angle = 0;
	this.sp_timer = 0;

	this.angle = 0;

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

GO_Malfose.prototype = new GameObject(
	new Sprite(
		GO_Malfose.spriteSheet,
		"Malfose"
	),
	"Malfose"
	);

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

	// SimpleMove (fly) controls
	GO_Malfose.prototype.leftward_move = [0,-1,-2];
	GO_Malfose.prototype.rightward_move = [0,1,2];
	GO_Malfose.prototype.upward_move = GO_Malfose.prototype.leftward_move;
	GO_Malfose.prototype.downward_move = GO_Malfose.prototype.rightward_move;


	GO_Malfose.prototype.ANIM_STATES = {
		"neutral" : 0,
		"expository" : 1,
		"cast" : 2, // or accusatory
		"focus" : 3,
		"hurt" : 4,
		"escape" : 5,
	};
	GO_Malfose.prototype.anim_frames = [
		[0,1,2,3], //"neutral" : 
		[4,5,6,7], //"expository" : 
		[8,9,10,11], //"accusitory" : 
		[12], //"focus" : 
		[13], //"hurt" : 
		[14,15], //"escape" : 
	];

	GO_Malfose.prototype.CONTROL_STATES = {
		"prefight" : 0,
		"fight" : 1,
		"postfight" : 2,
		"settled" : 3,
	};
	GO_Malfose.prototype.RING_SIZES = [
		8,16,32
	];

	GO_Malfose.prototype.PHASE1 = [
		{
			spell: "SPRAY",
			side: -1,
			time: 100, // 6 rounds @ 15 ticks plus pad
		},
		{
			spell: "SPRAY",
			side: 1,
			time: 100, // 6 rounds @ 15 ticks plus pad
		},
		{
			spell: "LIGHTNING",
			side: -1,
			time: 200, // 24 blocks x 16px @ ~2px/frame plus pad
		},
		{
			spell: "WAVE",
			side: 1,
			time: 150, // full 5 seconds, but harder pattern to dodge
		},
		{
			spell: "WAVE",
			side: -1,
			time: 150, // full 5 seconds, but harder pattern to dodge
		},
		{
			spell: "LIGHTNING",
			side: 1,
			time: 200, // 24 blocks x 16px @ ~2px/frame plus pad
		},
	];

	GO_Malfose.prototype.SPELLS = {
		"NONE" : 0,
		"LIGHTNING" : 1,
		"WAVE" : 2,
		"SPRAY" : 3,
	};

	GO_Malfose.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: 0 // discharge state length
		},
		{ // WAVE
			spell_cooldown: 1, // 1 bullet every 1 ticks
			casting_cooldown: 0 // 
		},
		{ // SPRAY
			spell_cooldown: 15, // N bullets twice a second
			casting_cooldown: 0 // 
		},
	];

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

	GO_Malfose.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;
		//}

		// debug to test these before the relevant cutscenes exist
		//if (this.control_state===this.CONTROL_STATES.prefight) this.startFight();
		//if (this.control_state===this.CONTROL_STATES.postfight) this.controls.press(this.controls,"A");

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

		if (this.control_state===this.CONTROL_STATES.settled) return "Ending";
	}

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

		switch (this.spell){
			case this.SPELLS.NONE:
				// nop
			break;
			case this.SPELLS.LIGHTNING:
				
				this._heap.lit = new GO_Lightning(this.direction<<3,0);
				this._heap.lit.sprite.x = (this.direction>0) ?
					this.sprite.x+23 :
					this.sprite.x+1;
				this._heap.lit.sprite.y = this.sprite.y+8;
				
				this._heap.lit.sprite.y += [0,1,2,1,0,-1,-2,-1][this.spell_counter&7];
				
				this._heap.lit.addToRoom(this.sprite.map_room);
				this.manager.add(this._heap.lit);

				++this.spell_counter;

				this._heap.lit = null;
			break;
			case this.SPELLS.WAVE:
				// goal here is a sinusoidally oscillating stream
				// across a ~90 degree arc.
				// So our time factor needs to go into an initial
				// sinusoid lookup, then _that_ needs to be mapped
				// to the proper angle range, and finally a second
				// lookup yields our launch angle.
				
				//this._heap.a = SIN[(this.spell_counter<<5)&0xff]; // count*8*4 for 1 cycle every 256/32 calls, or 256/8 frames @ 4 frame increments
				this._heap.a = SIN[(this.spell_counter<<3)&0xff];
				// a in range -127 to 127
				// since our trig tables are in 256, need to reduce this to 25%
				this._heap.a = ((this._heap.a>>2)+256)&0xff; // normalize to positive while we're here
				// now do the final lookups for dx & dy
				// again reducing results from 0-127 down to, say, 0-32
				this._heap.dx = (COS[this._heap.a]>>2)*this.direction;
				this._heap.dy = SIN[this._heap.a]>>2;
				
				++this.spell_counter;

				this._heap.lit = new GO_Bullet(this._heap.dx,this._heap.dy);
				this._heap.lit.sprite.x = (this.direction>0) ?
					this.sprite.x+23 :
					this.sprite.x+1;
				this._heap.lit.sprite.y = this.sprite.y+8;
				
				
				this._heap.lit.addToRoom(this.sprite.map_room);
				this.manager.add(this._heap.lit);

				this._heap.lit = null;
			break;
			case this.SPELLS.SPRAY:
				// goal here is a burst of a handful of bullets,
				// let's say 7, distributed across a 90 degree arc.
				// possibly vary the angle a bit per burst
				
				for (this._heap.i=-3; this._heap.i<=3; ++this._heap.i){
					this._heap.a = this._heap.i<<3;
					if ((this.spell_counter&3) == 1) this._heap.a += 4;
					else if ((this.spell_counter&3) == 3) this._heap.a -= 4;

					this._heap.a = (this._heap.a+256)&0xff;

					this._heap.dx = (COS[this._heap.a]>>1)*this.direction;
					this._heap.dy = SIN[this._heap.a]>>1;

					this._heap.lit = new GO_Bullet(this._heap.dx,this._heap.dy);
					this._heap.lit.sprite.x = (this.direction>0) ?
						this.sprite.x+23 :
						this.sprite.x+1;
					this._heap.lit.sprite.y = this.sprite.y+8;
					
					
					this._heap.lit.addToRoom(this.sprite.map_room);
					this.manager.add(this._heap.lit);
				}

				++this.spell_counter;

				this._heap.lit = null;
			break;
		}
		this._heap.i = this._heap.a = this._heap.dx = this._heap.dy = null;

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

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

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

		this.spell_cooldown = 0;
	}

	GO_Malfose.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_Malfose.prototype.startFight = function(){
		SDS.bgmName = null;
		SDS.bgm.stop();

		if (GLOBAL_DIARY.check(5,"RAVI_KILL")){
			SDS.switchBgm("Buildup",true);
			SDS.queueBgm("Malforce",true);
		}
		else{
			SDS.queueBgm("Teamwork",true);
		}
		

		this.control_state = this.CONTROL_STATES.fight;
		this.AI.phase = 1;
		//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_Malfose.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;

		for (this.sp_ring=0; this.sp_ring<this.spikes.length; ++this.sp_ring){
			for(this.sp_index=0; this.sp_index<this.spikes[this.sp_ring].length; ++this.sp_index){
				if (this.spikes[this.sp_ring][this.sp_index]){
					if (this.spikes[this.sp_ring][this.sp_index].hp>0){
						this._heap.smoke = new GO_Smoke(GO_Smoke.SPEEDS.APPEAR,true,10);
						this._heap.smoke.launch_dir.x = 0;
						this._heap.smoke.launch_dir.y = 0;
						this._heap.smoke.spawnOnSprite(this.spikes[this.sp_ring][this.sp_index].sprite,this.manager);
					}
					this.spikes[this.sp_ring][this.sp_index].clearFromRoom(this.sprite.map_room);
					this.manager.remove(this.spikes[this.sp_ring][this.sp_index]);
					this.spikes[this.sp_ring][this.sp_index]=null;
				}
			}
		}
		this._heap.smoke = null;
	};

	GO_Malfose.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_Malfose.prototype.damage = function(source,points){
		this.hurt = true;
		//this.hurt_timer = 16;
		this.anim_state = this.ANIM_STATES.hurt;

		if (source instanceof GO_DarkWave){
			// start game with 32 chi; have not less than 72 on max-tears.
			// Kickwaves should hit for rather less, both for balance
			// and because "Malfose can mitigate his own power"
			points = points>>2;
		}
		else if ((source instanceof GO_InterceptOrbit) && this.hp>points+1){
			// glitter-induced goddess rash.
			// not lethal, but uncomfortable.
			++points;
		}

		this.hurt_timer = Math.min(points,16);

		if (this.AI.phase==1){
			this.AI.sequence += points;
			this.AI.sequence %= this.PHASE1.length;
		}

		// staged postbattle state
		if (this.hp<=0 || 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._heap.poof = new GO_Smoke(GO_Smoke.SPEEDS.VENT,true,0);
			this._heap.poof.launch_dir = {x:-1,y:-1};
			this._heap.poof.spawnOnSprite(this.sprite,this.manager);
			this._heap.poof = new GO_Smoke(GO_Smoke.SPEEDS.VENT,true,0);
			this._heap.poof.launch_dir = {x:1,y:-1};
			this._heap.poof.spawnOnSprite(this.sprite,this.manager);
			this._heap.poof = new GO_Smoke(GO_Smoke.SPEEDS.VENT,true,0);
			this._heap.poof.launch_dir = {x:1,y:1};
			this._heap.poof.spawnOnSprite(this.sprite,this.manager);
			this._heap.poof = new GO_Smoke(GO_Smoke.SPEEDS.VENT,true,0);
			this._heap.poof.launch_dir = {x:-1,y:1};
			this._heap.poof.spawnOnSprite(this.sprite,this.manager);
			this._heap.poof = new GO_Poof();
			this._heap.poof.spawnOnSprite(this.sprite,this.manager);
			this._heap.poof = null;
		}

		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){});
				//}
				if (this.AI.phase!=3) this.hp = 1;
			}

			return (this.hp<=0);
		}
		else{
			this.hurt_timer = 2;
			return false;
		}
	};
	GO_Malfose.prototype._inflictVal = {hp:1, chi:0};
	GO_Malfose.prototype.inflict = function(){
		return this._inflictVal;
	};

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

	GO_Malfose.prototype.smokeBurst = function(){
		this._heap.smoke = new GO_Smoke(GO_Smoke.SPEEDS.APPEAR,true,10);
		this._heap.smoke.launch_dir.x = 0;
		this._heap.smoke.launch_dir.y = 0;
		this._heap.smoke.spawnOnSprite(this.sprite,this.manager);
		this._heap.smoke.sprite.y += 8;
		//this.smoke.push(this._heap.smoke);
		this._heap.smoke = new GO_Smoke(GO_Smoke.SPEEDS.APPEAR,true,10);
		this._heap.smoke.launch_dir.x = 0;
		this._heap.smoke.launch_dir.y = 0;
		this._heap.smoke.spawnOnSprite(this.sprite,this.manager);
		this._heap.smoke.sprite.x -= 4;
		this._heap.smoke.y -= 4;
		//this.smoke.push(this._heap.smoke);
		this._heap.smoke = new GO_Smoke(GO_Smoke.SPEEDS.APPEAR,true,10);
		this._heap.smoke.launch_dir.x = 0;
		this._heap.smoke.launch_dir.y = 0;
		this._heap.smoke.spawnOnSprite(this.sprite,this.manager);
		this._heap.smoke.sprite.x += 4;
		this._heap.smoke.y -= 4;
		//this.smoke.push(this._heap.smoke);
		this._heap.smoke = new GO_Smoke(GO_Smoke.SPEEDS.APPEAR,true,10);
		this._heap.smoke.launch_dir.x = 0;
		this._heap.smoke.launch_dir.y = 0;
		this._heap.smoke.spawnOnSprite(this.sprite,this.manager);
		this._heap.smoke.sprite.y -= 8;
		//this.smoke.push(this._heap.smoke);

		this._heap.smoke = null;
	};

	GO_Malfose.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");

			// brute-force nav to bottom platform
			if (this.sprite.x<(25<<4)) ++this.sprite.x;
			if (this.sprite.x>(25<<4)) --this.sprite.x;
			if (this.sprite.y<(25<<4)) ++this.sprite.y;
			if (this.sprite.y>(25<<4)) --this.sprite.y;

			if (this.sprite.x == (25<<4) && this.sprite.y == (25<<4)){
				this.control_state = this.CONTROL_STATES.settled;
			}
			return;
		} 
		if (this.control_state === this.CONTROL_STATES.settled){
			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;

		// TODO: stuff.
		switch(this.AI.phase){
			case 1:
				// phase 1: arc around the perimiter, switching sides,
				// firing a spell in a [fixed sequence +/- current HP]
				// until HP 1 (capped)
				// (HP logic handled in damage function)
				if (this.AI.timer<=0 && !this.hurt){
					++this.AI.sequence;
					this.AI.sequence%=this.PHASE1.length;
					this.AI.timer = this.PHASE1[this.AI.sequence].time;
					//this.AI.delay = 45;
					//this.AI.delay = (45 * this.hp)>>8; // less downtime over time; assuming 255~=256 max hp
					//this.AI.delay = (64 * this.hp)>>8;
					this.AI.delay = this.hp>>2;
					this.spell = this.SPELLS[this.PHASE1[this.AI.sequence].spell];
					
					// spawn smoke burst at old location
					this.smokeBurst();

					this.sprite.x = (25<<4) + ((this.PHASE1[this.AI.sequence].side*16)<<4);
					this._heap.cx = this.sprite.x + this.sprite.center.x;
					this.sprite.y = 16<<4;
					this._heap.cy = this.sprite.y + this.sprite.center.y;
					if (this.spell==this.SPELLS.LIGHTNING){
						if (this._heap.mcy < this._heap.cy){
							this.sprite.y -= 11<<4;
							this.controls.press(this.controls,"down");
						}
						else{
							this.sprite.y += 11<<4;
							this.controls.press(this.controls,"up");
						}
						this._heap.cy = this.sprite.y + this.sprite.center.y;
						this.AI.timer -= this.AI.delay; // avoid overmovement due to delay
					}

					this.direction = (this._heap.mcx < this._heap.cx) ? -1 : 1;

					// spawn smoke burst at new location
					this.smokeBurst();

					//this.controls.press(this.controls,"B");
				}

				if (this.AI.delay>0){
					--this.AI.delay;
					if (this.AI.delay<=0){
						this.controls.press(this.controls,"B");
						//if (this.spell==this.SPELLS.LIGHTNING){
						//	if (this._heap.mcy < 16<<4){
						//		this.controls.press(this.controls,"down");
						//	}
						//	else{
						//		this.controls.press(this.controls,"up");
						//	}
						//}

					}
				}
				else --this.AI.timer;

				if (this.hp<=1 && !this.hurt){
					this.hp = 1;
					++this.AI.phase;
					this.AI.timer = 0;
					this.interactions &= ~DEFINE_GAMEOBJECT_INTERACTIONS.DAMAGE;

					if (SDS.bgmName!="Buildup" && SDS.bgmName!="Malforce"){
						SDS.bgm.stop();// should mean .queue hits the !is_playing clause and does a normal play
						SDS.switchBgm("Buildup",true);
						SDS.queueBgm("Malforce",true);
					}

					// spawn smoke burst at old location
					this.smokeBurst();

					this._CircleBasePos(0,this.AI.base_pos);
					this.sprite.x = this.AI.base_pos.x - this.sprite.center.x;//25<<4;
					this.sprite.y = this.AI.base_pos.y - this.sprite.center.y;//16<<4;

					// spawn smoke burst at new location
					this.smokeBurst();

					// be sure tracking vars are set up for phase 2
					this.sp_ring = this.spikes.length-1;
					this.sp_index = 0;
					this.sp_target.x = 0;
					this.sp_target.y = 0;
					this.sp_angle = 0;
					this.sp_timer = 0;
				}

				if (this.AI.timer<=0){
					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");
				}

			break;
			case 2:
				// phase 2: summon 3 layers of spike ring;

				// spawn spike if needed
				if (!this.spikes[this.sp_ring][this.sp_index]){
					//this.sp_angle = this.sp_index*256/this.RING_SIZES[this.sp_ring];
					//this.sp_timer = 8;
					//this._heap.sin = SIN[this.sp_angle];
					//this._heap.cos = COS[this.sp_angle];
					//this._heap.offx = ((this._heap.sin*((this.sp_ring+1)*12))>>7); // (trig/128.0)*((ring+1)*12)
					//this._heap.offy = -((this._heap.cos*((this.sp_ring+1)*12))>>7);
					//this.sp_target.x = this.sprite.x + this.sprite.center.x + this._heap.offx;
					//this.sp_target.y = this.sprite.y + this.sprite.center.y + this._heap.offy;

					this.sp_timer = 8;
					this.sp_angle = this._RingElPos(0,this.sp_ring,this.sp_index,this.sp_target,this.AI.base_pos);

					this._heap.spike = new GO_MalfoseSpike();
					this._heap.spike.angle = this.sp_angle;
					this._heap.spike.addToRoom(this.sprite.map_room);
					this.manager.add(this._heap.spike);
					this.spikes[this.sp_ring][this.sp_index] = this._heap.spike;
					this._heap.spike = null;
				}

				// advance timer, move spike towards position
				--this.sp_timer;
				this._heap.spike = this.spikes[this.sp_ring][this.sp_index];
				this._heap.offx = -((this._heap.sin*this.sp_timer)>>2); // (trig/128.0)*timer*32 = trig*timer/4
				this._heap.offy = ((this._heap.cos*this.sp_timer)>>2);
				this._heap.sin = SIN[this.sp_angle];
				this._heap.cos = COS[this.sp_angle];
				this._heap.spike.sprite.x = this.sp_target.x + this._heap.offx - this._heap.spike.sprite.center.x;
				this._heap.spike.sprite.y = this.sp_target.y + this._heap.offy - this._heap.spike.sprite.center.y;
				if (this.sp_timer==7){ // HACK
					this._heap.smoke = new GO_Smoke(GO_Smoke.SPEEDS.APPEAR,true,10);
					this._heap.smoke.launch_dir.x = 0;
					this._heap.smoke.launch_dir.y = 0;
					this._heap.smoke.spawnOnSprite(this._heap.spike.sprite,this.manager);
					this._heap.smoke = null;
				}
				this._heap.spike = null;

				// advance state
				if (this.sp_timer<=0){
					++this.sp_index;
					if (this.sp_index>=this.RING_SIZES[this.sp_ring]){
						--this.sp_ring;
						this.sp_index=0;

						if (this.sp_ring<0){
							++this.AI.phase;
							this.AI.timer = 0;
							this.interactions |= DEFINE_GAMEOBJECT_INTERACTIONS.DAMAGE;
						}
					}
				}

				
				
				
				//this.endFight();
			break;
			case 3:
				// phase 3: spin spike rings
				// gyrate out from center to a radius 
				// of the full SIN/COS fidelity.

				
				// restore spikes 1 at a time from outside in
				// by anticipating future position/angle
				// and animating in to that point per phase 2
				// before turning it over to main rotation logic

				++this.AI.timer;
				this.AI.timer &=0xff;

				this._CircleBasePos(this.AI.timer,this.AI.base_pos);
				this.sprite.x = this.AI.base_pos.x - this.sprite.center.x;
				this.sprite.y = this.AI.base_pos.y - this.sprite.center.y;

				for (this._heap.ring=0; this._heap.ring<this.spikes.length; ++this._heap.ring){
					for(this._heap.i=0; this._heap.i<this.spikes[this._heap.ring].length; ++this._heap.i){
						if (this.sp_timer>0 && this._heap.ring==this.sp_ring && this._heap.i==this.sp_index){
							// skip handling this spike in normal motion- it's incoming.
							// that said, clean/purge/reset if it was destroyed
							if (this.spikes[this.sp_ring][this.sp_index].hp<=0){
								this.spikes[this.sp_ring][this.sp_index] = null;
								this.sp_timer=0;
							}
						}
						else if (this.spikes[this._heap.ring][this._heap.i]){
							
							if (this.spikes[this._heap.ring][this._heap.i].hp>0){
								this.spikes[this._heap.ring][this._heap.i].angle = this._RingElPos(
									this.AI.timer,
									this._heap.ring,
									this._heap.i,
									this.spikes[this._heap.ring][this._heap.i].sprite,
									this.AI.base_pos
								);
								this.spikes[this._heap.ring][this._heap.i].sprite.x -= this.spikes[this._heap.ring][this._heap.i].sprite.center.x;
								this.spikes[this._heap.ring][this._heap.i].sprite.y -= this.spikes[this._heap.ring][this._heap.i].sprite.center.y;
							}
							else{
								this.spikes[this._heap.ring][this._heap.i] = null;
							}
						}
						else if (this.sp_timer<=0 && this.AI.delay<=0){
							// spawn spike if needed
							this.sp_timer = 128;
							this.AI.delay = 96;
							this.sp_ring = this._heap.ring;
							this.sp_index = this._heap.i;
							this.sp_angle = this._RingElPos(
								(this.AI.timer + this.sp_timer)&0xff, // where we should be at convergence
								this.sp_ring,
								this.sp_index,
								this.sp_target,
								//this.AI.base_pos // noooo... in this case we want the future base pos to be calculated
							);

							this._heap.spike = new GO_MalfoseSpike();
							this._heap.spike.angle = this.sp_angle;
							this._heap.spike.addToRoom(this.sprite.map_room);
							this.manager.add(this._heap.spike);
							this.spikes[this.sp_ring][this.sp_index] = this._heap.spike;
							this._heap.spike = null;
						}
					}
				}

				
				if (this.sp_timer>0 && this.spikes[this.sp_ring][this.sp_index]){
					// advance timer, move spike towards position
					--this.sp_timer;
					this._heap.spike = this.spikes[this.sp_ring][this.sp_index];
					this._heap.offx = -((this._heap.sin*this.sp_timer)>>5); // (trig/128.0)*timer*4 = trig*timer/32
					this._heap.offy = ((this._heap.cos*this.sp_timer)>>5);
					this._heap.sin = SIN[this.sp_angle];
					this._heap.cos = COS[this.sp_angle];
					this._heap.spike.sprite.x = this.sp_target.x + this._heap.offx - this._heap.spike.sprite.center.x;
					this._heap.spike.sprite.y = this.sp_target.y + this._heap.offy - this._heap.spike.sprite.center.y;
					if (this.sp_timer==127){ // HACK
						this._heap.smoke = new GO_Smoke(GO_Smoke.SPEEDS.APPEAR,true,10);
						this._heap.smoke.launch_dir.x = 0;
						this._heap.smoke.launch_dir.y = 0;
						this._heap.smoke.spawnOnSprite(this._heap.spike.sprite,this.manager);
						this._heap.smoke = null;
					}
					this._heap.spike = null;
				}
				if (this.sp_timer<=0 && this.AI.delay>0) --this.AI.delay;

				
				//this.endFight();
			break;
		}

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

	GO_Malfose.prototype._CircleBasePos = function(t,out){
		// circular motion modulo 256 from sin/cos tables
		// x
		// radial motion modulo 128, in 32-frame quarters:
		// positive, center, negative, center
		// It is... completely serendipitous that this produces
		// a "bouncing off the platforms" effect.
		// I honestly expected to have him just kinda loop
		// all over the place through solid walls
		out.x=26<<4;
		out.y=17<<4;

		this._heap.rmod = t&127;
		if (this._heap.rmod<32){
			this._heap.r = this._heap.rmod;
		}
		else if (this._heap.rmod<96){
			this._heap.r = 32 - (this._heap.rmod - 32);
		}
		else{
			this._heap.r = -32 + (this._heap.rmod - 96);
		}
		this._heap.r = this._heap.r<<2;

		out.x += (this._heap.r * SIN[t&255])>>7;
		out.y += (this._heap.r * COS[t&255])>>7;
		
	}
	GO_Malfose.prototype._RingElPos = function(t,ring,idx,out,base){
		if (base){
			out.x = base.x;
			out.y = base.y;
		}
		else{
			this._CircleBasePos(t,out);
		}

		this._heap.angle = idx*256/this.RING_SIZES[ring];
		switch(ring){
			case 0:
				this._heap.angle += t<<2;
			break;
			case 1:
				this._heap.angle -= t*3;
			break;
			case 2:
				this._heap.angle += t<<1;
			break;
		}
		this._heap.angle &= 255;
		if (this._heap.angle<0) this._heap.angle += 256;

		out.x += ((SIN[this._heap.angle]*((ring+1)*12))>>7); // (trig/128.0)*((ring+1)*12)
		out.y -= ((COS[this._heap.angle]*((ring+1)*12))>>7);

		return this._heap.angle;
	}

	GO_Malfose.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{
			if (this.controls.held.B){
				this.casting_cooldown = this.SPELL_PARAMS[this.spell].casting_cooldown;
				this.anim_state = this.ANIM_STATES.cast;
				
			}
			else if (this.casting_cooldown>0){
				--this.casting_cooldown;
				//this.anim_state = this.ANIM_STATES.neutral;
			}
			else if (this.controls.held.A){
				this.anim_state = this.ANIM_STATES.expository;
			}
			else{
				this.anim_state = this.ANIM_STATES.neutral;
			}
			
			this.SimpleMove();
			if (this.controls.held.left) this.direction=-1;
			if (this.controls.held.right) this.direction=1;
			
			
			
			// actual spells logic
			if (this.spell_cooldown>0) --this.spell_cooldown;
			if (this.controls.pressed.B){
				this.spell_counter = 0;
				//this.spell = this.SPELLS[["LIGHTNING","WAVE","SPRAY"][(this.spell_sequence+this.hp)%3]];
			}
			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; // this is literally just a safety fallback
				}
				this.castSpell();
			}
			else if (this.controls.released.B){
				this.cleanSpell();
				this.spell_target = null;
				//++this.spell_sequence;
			}
			if (this.spellshots.length){
				this.upkeepSpell();
			}
		}

		if(this.control_state==this.CONTROL_STATES.settled && this.controls.held.A){
			this.anim_state = this.ANIM_STATES.escape;
			this.sprite.constraints.y = DEFINE_SPRITE_MOVE_CONSTRAINT.NONE; // otherwise we wrap and won't detect room escape
			this.sprite.move(0,-4,  this._heap.spriteMoveRes);
			if (this.sprite.isTouchingTerrain(DEFINE_MAP_TERRAIN_TYPES.GROUND)){
				this._heap.poof = new GO_Poof("FX_fallcrash");
				this._heap.poof.spawnOnSprite(this.sprite,this.manager);
				this._heap.poof.sprite.y -= 8;
				this._heap.poof.sprite.x += [0,-4,0,4][this.frame_timer&3];
				this._heap.poof = null;
			}
			if (this.sprite.y + this.sprite.bottom_right.y <= 0){
				this.manager.remove(this);
				this.clearFromRoom(this.sprite.map_room);
			}
		}

		if (this._heap.oldAnimState != this.anim_state){
			this.frame_timer=0;
		}
		this.frame_timer++;

		// normal case...
		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];

		// and an UGLY number of one-off exceptions.
		// If the final boss is not a one-time use exceptional entity,
		// then this game has longer legs than I ever expected.
		if (this.AI.phase>1){
			this.sprite.frame = 12;
		}
		if (this.AI.phase==2 || this.control_state == this.CONTROL_STATES.postfight){
			this.sprite.visible = !!(this.frame_timer & 1);
		}
		else{
			this.sprite.visible = true;
		}
		if (this.hurt || (this.control_state>=this.CONTROL_STATES.postfight && this.anim_state != this.ANIM_STATES.escape)){
			this.sprite.frame = 13;
		}
		if (this.anim_state == this.ANIM_STATES.escape){
			this._heap.aniFrame = this.frame_timer % this.anim_frames[this.anim_state].length;
			this.sprite.frame = this.anim_frames[this.anim_state][this._heap.aniFrame];
		}

		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.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();
		}
	}

