module vs.character.moko;



private import std.math;
private import gedo.audio.sound;
private import gedo.video.ggl;
private import gedo.video.color;
private import gedo.video.texture;
private import gedo.video.polyline;
private import gedo.video.staticmesh;
private import gedo.video.skinnedmesh;
private import gedo.video.alphameshlist;
private import gedo.input.joystick;
private import gedo.system.task;
private import vs.action.task;
private import vs.action.bullet;
private import vs.action.button;
private import vs.action.character;
private import vs.bullet.fist;



private enum Motion
{
	//aiw begin ../../../lib/vs/moko/body.gsa
	CHARGEHIT0,
	CHARGEHIT1,
	CHARGESHOT,
	COUNTER,
	DASH1,
	DASH2,
	DOWN,
	FALL0,
	FALL1,
	FOLLOW,
	GUARD0,
	GUARD1,
	GUARD2,
	HIT0,
	HIT1,
	HIT2,
	HIT3,
	HOMINGSHOT,
	JUMP,
	KNOCKBACK,
	KO,
	LAND,
	RISE,
	RUN0,
	RUN1,
	RUN2,
	SINGLESHOT,
	SPECIAL,
	SPELL0,
	SPELL1,
	SPELL2,
	STAND,
	//aiw end
	INVALID
}



private enum TextureID
{
	DEFAULT,
	ATTACK,
	DAMAGE,
	FIREBALL,
	FIRERING,
	BOMBBALL,
	REDBIND,
	EXPLOSION,
}



private enum SoundID
{
	HIT0,
	HIT3,
	HEATUP,
	EXPLOSION,
}



private enum FistID
{
	COUNTER,
	HIT0,
	HIT1,
	HIT2,
	HIT3,
	CHARGEHIT0,
	CHARGEHIT1,
	SPELL0,
}



public class MokoFactory : CharacterFactory
{
public:
	this()
	{
		_textures[TextureID.DEFAULT] = Texture("$LIB/moko/moko_n.png");
		_textures[TextureID.ATTACK ] = Texture("$LIB/moko/moko_a.png");
		_textures[TextureID.DAMAGE ] = Texture("$LIB/moko/moko_d.png");
		_textures[TextureID.FIREBALL] = Texture("$LIB/moko/fireball.png");
		_textures[TextureID.FIRERING] = Texture("$LIB/moko/firering.png");
		_textures[TextureID.BOMBBALL] = Texture("$LIB/moko/chargeshot.png");
		_textures[TextureID.REDBIND] = Texture("$LIB/moko/redbind.png");
		_textures[TextureID.EXPLOSION] = Texture("$LIB/moko/explosion.png");

		_body = SkinnedMesh("$LIB/moko/body.gsa");
		_wing = SkinnedMesh("$LIB/moko/firewing.gsa");
		_ring = SkinnedMesh("$LIB/moko/firehitring.gsa");
		_ray  = StaticMesh("$LIB/moko/fireray.gsa");
		_shine      = StaticMesh("$LIB/moko/shine.gsa");
		_heatupRay  = StaticMesh("$LIB/moko/heatupray.gsa");
		_homingShot = StaticMesh("$LIB/moko/homingshot.gsa");
		_homingShotAura = StaticMesh("$LIB/moko/homingshotaura.gsa");

		_fireHitEffect = new FireHitEffect();
	}


	override Character create(CharacterSystem sys)
	{
		_sounds[SoundID.HIT0] = sys.sharedSound(SharedSoundID.HITM);
		_sounds[SoundID.HIT3] = sys.sharedSound(SharedSoundID.HITH);
		_sounds[SoundID.HEATUP] = Sound("$LIB/moko/heatup.ogg");
		_sounds[SoundID.EXPLOSION] = Sound("$LIB/moko/explosion.ogg");

		return new Moko(sys);
	}


private:
	Texture[TextureID.max+1] _textures;
	Sound[SoundID.max+1]     _sounds;
	EffectProgram            _fireHitEffect;
	SkinnedMesh              _body;
	SkinnedMesh              _wing;
	SkinnedMesh              _ring;
	StaticMesh               _ray;
	StaticMesh               _shine;
	StaticMesh               _heatupRay;
	StaticMesh               _homingShot;
	StaticMesh               _homingShotAura;



class Moko : Character
{
public:
	static this()
	{
		getDefaultAccelerations(_accelerations);

		_accelerations[Movement.RUN][1]     *= 1.25;
		_accelerations[Movement.FALL][1]    *= 1.25;
		_accelerations[Movement.STEP][0]    *= 1.25;
		_accelerations[Movement.STEP][1]    *= 1.25;
		_accelerations[Movement.FOLLOW][0]  *= 1.5;
		_accelerations[Movement.FOLLOW][1]  *= 1.5;
	}


	this(CharacterSystem sys)
	{
		super(_body, sys);

		auto ve = _fireHitEffect;

		_fists[FistID.COUNTER]    = new Fist(new Power( 3, 0.0, [0.0f, 1.0f, 0.0f]), _sounds[SoundID.HIT0], ve, _timer);
		_fists[FistID.HIT0]       = new Fist(new Power( 9, 1.0, [0.0f, 1.0f, 0.0f]), _sounds[SoundID.HIT0], ve, _timer);
		_fists[FistID.HIT1]       = new Fist(new Power( 5, 0.0, [0.0f, 1.0f, 0.0f]), _sounds[SoundID.HIT0], ve, _timer);
		_fists[FistID.HIT2]       = new Fist(new Power( 3, 0.0, [0.0f, 1.0f, 0.0f]), _sounds[SoundID.HIT0], ve, _timer);
		_fists[FistID.HIT3]       = new Fist(new Power(12, 2.5, [0.0f, 1.0f, 0.0f]), _sounds[SoundID.HIT3], ve, _timer);
		_fists[FistID.CHARGEHIT0] = new Fist(new Power(16, 0.0, [0.0f, 1.0f, 0.0f]), _sounds[SoundID.HIT0], ve, _timer, f2t(20));
		_fists[FistID.CHARGEHIT0].onCollideOwner = &startChargeHit1;
		_fists[FistID.CHARGEHIT1] = new Fist(new Power(32, 2.5, [0.0f, 0.5f, 2.0f]), sys.sharedSound(SharedSoundID.FINISH), ve, _timer);
		_fists[FistID.SPELL0]     = new Fist(new Power(16, 0.0, [0.0f, 0.5f, 2.0f]), _sounds[SoundID.HIT0], ve, _timer);
		_fists[FistID.SPELL0].getHoldPosition = &getHoldPosition;
		_fists[FistID.SPELL0].onCollideOwner  = &startSpell1;

		auto power = new Power(20, 1.0);
		foreach(inout b; _fireBalls)
		{
			b = new FireBall(power);
		}

		power = new Power(4, 1.0);
		foreach(inout b; _bombBalls)
		{
			b = new BombBall(power);
		}

		power = new Power(3, 1.0);
		foreach(inout b; _homingShots)
		{
			b = new HomingShot(power);
		}

		power = new Power(0, 0.0);
		_redBind = new RedBind(power);
		_redBind.onCollideOwner = &shotRedBind2;

		power = new Power(1, 0.0);
		_redBind2 = new RedBind2(power);

		power = new Power(72, 0.0);
		_spell = new Spell(power);

		_fireSpark = new FireSpark(sys);
	}


	override float[] acceleration(Movement m)
	{
		return _accelerations[m];
	}


	override void onDraw()
	{
		uint t;
		uint i;

		_timer.get(i, t);

		Texture tex = null;
		if(_counts[Count.SAFETY] & 2)
		{
			tex = Texture.white;
		}
		else
		{
			switch(state)
			{
			case State.KNOCKBACK:
			case State.DOWN:
			case State.KO:
				tex = _textures[TextureID.DAMAGE];
				break;

			case State.SINGLESHOT:
			case State.CHARGESHOT:
			case State.HOMINGSHOT:
			case State.CHARGEHIT:
			case State.HIT:
			case State.SPECIAL:
				tex = _textures[TextureID.ATTACK];
				break;

			case State.SPELL:
				tex = _textures[TextureID.ATTACK];
				if(i == Motion.SPELL1)
				{
					if(_counts[Count.ACTION] <= 60)
					{
						tex = Texture.white;
					}
					else if(_counts[Count.ACTION] <= 120)
					{
						if(t & 7)
						{
							tex = Texture.white;
						}
					}
					else
					{
						if(t & 4)
						{
							tex = Texture.white;
						}
					}
				}
				break;

			default:
				tex = _textures[TextureID.DEFAULT];
				break;
			}
		}

		if(i == Motion.CHARGEHIT1 && t >= f2t(9) && t < f2t(9) + 16)
		{
			_body.draw(i, t, Texture.white, rgba(255, 255 - (t - f2t(9))*15, 0, 255));
		}
		else
		{
			_body.draw(i, t, tex);
		}
		switch(i)
		{
		case Motion.CHARGESHOT:
		case Motion.CHARGEHIT1:
		case Motion.SPELL0:
		case Motion.SPELL1:
		case Motion.SPELL2:
			if(auto am = _system.alphaMeshList().get())
			{
				am.set(position.current, &drawWing);
			}
			break;

		default:
			break;
		}
	}


	override void onUpdate(float axisLength, Vector axisDirection, Button[] buttons)
	{
		uint i, t;

		_timer.get(i, t);

		switch(i)
		{
		case Motion.STAND:
			if(state != State.STAND)
			{
				changeState(State.STAND);
			}
			break;

		case Motion.SINGLESHOT:
			if(t == 1)
			{
				Vector back;
				Vector up;

				direction.getFront(back);
				.gedo.math.vector.multiply(back, -0.25);

				direction.getUp(up);
				.gedo.math.vector.multiply(up, 0.5);

				.gedo.math.vector.add(velocity, back, up);

				Source().play(_system.sharedSound(SharedSoundID.SWINGH));
			}
			else if(t < f2t(8))
			{
				turnToTarget(.std.math.PI/16.0);
			}
			else if(t == f2t(8))
			{
				if(auto b = cast(Bullet)findFreeTask(_fireBalls))
				{
					shot(b);
					if(!_fireSpark.parent)
					{
						_fireSpark.start(b.position.current);
						parent.parent.add(_fireSpark);
					}
				}
			}
			break;

		case Motion.CHARGESHOT:
			if(t < f2t(13))
			{
				turnToTarget(.std.math.PI / 240.0);
			}
			else
			{
				const Vector[4] shotPositions = [[2.0f, 0.0f, 6.0f], [4.0f, 0.0f, 0.0f], [6.0f, 0.0f, 6.0f], [8.0f, 0.0f, 0.0f]];

				auto index = -1;
				switch(t)
				{
				case f2t(13):
					index = 0;
					break;

				case f2t(15):
					index = 1;
					break;

				case f2t(17):
					index = 2;
					break;

				case f2t(19):
					index = 3;
					break;

				default:
					break;
				}

				if(index >= 0)
				{
					if(auto b = cast(Bullet)findFreeTask(_bombBalls))
					{
						shot(b, shotPositions[index]);
						Source().play(_system.sharedSound(SharedSoundID.SHOTL));
					}
					if(auto b = cast(Bullet)findFreeTask(_bombBalls))
					{
						Vector a = shotPositions[index][];
						a[0] = -a[0];
						shot(b, a);
					}
				}
			}
			break;

		case Motion.HOMINGSHOT:
			if(t == f2t(30))
			{
				if(!_fireSpark.parent)
				{
					Vector a;
					direction.getFront(a);
					.gedo.math.vector.multiply(a, 5.0);
					.gedo.math.vector.add(a, position.current);

					_fireSpark.start(a);
					parent.parent.add(_fireSpark);
				}
				for(auto angle=0.0; angle < .std.math.PI*2.0; angle += .std.math.PI/3.0)
				{
					if(auto b = cast(Bullet)findFreeTask(_homingShots))
					{
						shot(b);

						Vector a;

						direction.toVector(.std.math.cos(angle), 1.0, .std.math.sin(angle), a);

						.gedo.math.vector.normalize(a);

						b.direction.homing(a, .std.math.PI);
					}
				}
				Source().play(_system.sharedSound(SharedSoundID.SWINGH));
			}
			break;

		case Motion.COUNTER:
			if(t < f2t(5))
			{
				follow();
			}
			else if(t == f2t(5) || t == f2t(11) || t == f2t(17))
			{
				velocity[] = 0.0;
				shotFist(_fists[FistID.COUNTER]);
			}
			else if(t >= f2t(23) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT0, Motion.STAND);
			}
			break;

		case Motion.HIT0:
			if(t < f2t(5))
			{
				follow();
			}
			else if(t == f2t(5))
			{
				shotFist(_fists[FistID.HIT0]);
				velocity[] = 0.0;
			}
			else if(t >= f2t(11) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT1, Motion.STAND);
			}
			break;

		case Motion.HIT1:
			if(t < f2t(7))
			{
				follow();
			}
			else if(t == f2t(7) || t == f2t(13))
			{
				shotFist(_fists[FistID.HIT1]);
				velocity[] = 0.0;
			}
			else if(t >= f2t(19) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT2, Motion.STAND);
			}
			break;

		case Motion.HIT2:
			if(t < f2t(6))
			{
				follow();
			}
			else if(t == f2t(6) || t == f2t(11) || t == f2t(16))
			{
				shotFist(_fists[FistID.HIT2]);
				velocity[] = 0.0;
			}
			else if(t >= f2t(22) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT3, Motion.STAND);
			}
			break;

		case Motion.HIT3:
			if(t < f2t(7))
			{
				follow();
			}
			else if(t == f2t(7))
			{
				shotFist(_fists[FistID.HIT3]);
				velocity[] = 0.0;
			}
			break;

		case Motion.CHARGEHIT0:
			if(t <= f2t(22))
			{
				follow();
				if(t == f2t(3))
				{
					shotFist(_fists[FistID.CHARGEHIT0]);
				}
			}
			else
			{
				brake();
			}
			break;

		case Motion.CHARGEHIT1:
			if(t == f2t(9))
			{
				if(!_fireSpark.parent)
				{
					Vector p;
					direction.toVector(-0.5, -3.5, 0.5, p);

					.gedo.math.vector.add(p, position.current);

					_fireSpark.start(p);
					parent.parent.add(_fireSpark);
				}
			}
			else if(t == f2t(21))
			{
				velocity[] = 0.0;
				shotFist(_fists[FistID.CHARGEHIT1]);
			}
			if(t == f2t(18))
			{
				follow();
			}
			else
			{
				brake();
			}
			break;

		case Motion.SPECIAL:
			if(t == f2t(7) && _redBind.parent is null)
			{
				turnToTarget();

				shot(_redBind, [0.0f, 5.0f, 0.0f]);
				if(!_fireSpark.parent)
				{
					_fireSpark.start(_redBind.position.current);
					parent.parent.add(_fireSpark);
				}
				Source().play(_system.sharedSound(SharedSoundID.SWINGL));
			}
			else
			{
				brake();
			}
			break;

		case Motion.SPELL0:
			if(t <= f2t(4))
			{
				follow();
			}
			else
			{
				brake();

				if(t == f2t(13))
				{
					shotFist(_fists[FistID.SPELL0]);
					if(!_fireSpark.parent)
					{
						_fireSpark.start(position.current);
						parent.parent.add(_fireSpark);
					}
				}
			}
			break;

		case Motion.SPELL1:
			if(_counts[Count.ACTION] == 0)
			{
				_timer.set(Motion.SPELL2, Motion.STAND);
				if(!_spell.parent)
				{
					_life.add(1 - _life.current);
					shot(_spell, [0.0f, 0.0f, 0.0f]);
					Source().play(_sounds[SoundID.EXPLOSION]);
				}
			}
			else
			{
				brake();
				if(_counts[Count.ACTION] == 150)
				{
					Source().play(_sounds[SoundID.HEATUP]);
				}
			}
			break;

		case Motion.SPELL2:
			brake();
			_counts[Count.SAFETY] = _timer.left + 60;
			break;

		default:
			break;
		}
	}


	override void onChangeState(State from, State to)
	{
		Motion[2] motion;

		motion[1] = Motion.INVALID;

		switch(to)
		{
		case State.RESSURECT:
			motion[0] = Motion.FALL0;
			motion[1] = Motion.FALL1;
			break;

		case State.STAND:
			motion[0] = Motion.STAND;
			break;

		case State.FALL:
			if(velocity[2] > 0.0)
			{
				motion[0] = Motion.RISE;
			}
			else
			{
				motion[0] = Motion.FALL0;
				motion[1] = Motion.FALL1;
			}
			break;

		case State.RECOVER:
			switch(_timer.index)
			{
			default:
			case Motion.GUARD1:
				motion[0] = Motion.GUARD2;
				break;

			case Motion.RUN1:
				motion[0] = Motion.RUN2;
				break;

			case Motion.FALL1:
				motion[0] = Motion.LAND;
				break;

			case Motion.DASH1:
				motion[0] = Motion.DASH2;
				break;
			}
			motion[1] = motion[0];
			break;

		case State.RUN:
			motion[0] = Motion.RUN0;
			motion[1] = Motion.RUN1;
			break;

		case State.STEP:
			motion[0] = Motion.DASH1;
			break;

		case State.JUMP:
			motion[0] = Motion.JUMP;
			motion[1] = Motion.STAND;
			break;

		case State.LAND:
			motion[0] = Motion.LAND;
			motion[1] = Motion.STAND;
			break;

		case State.GUARD:
			motion[0] = Motion.GUARD0;
			motion[1] = Motion.GUARD1;
			break;

		case State.SINGLESHOT:
			motion[0] = Motion.SINGLESHOT;
			motion[1] = Motion.STAND;
			break;

		case State.CHARGESHOT:
			motion[0] = Motion.CHARGESHOT;
			motion[1] = Motion.STAND;
			break;

		case State.HOMINGSHOT:
			motion[0] = Motion.HOMINGSHOT;
			motion[1] = Motion.STAND;
			break;

		case State.SPECIAL:
			motion[0] = Motion.SPECIAL;
			motion[1] = Motion.STAND;
			break;

		case State.SPELL:
			motion[0] = Motion.SPELL0;
			motion[1] = Motion.STAND;
			break;

		case State.FOLLOW:
			motion[0] = Motion.FOLLOW;
			break;

		case State.HIT:
			if(from == State.GUARD)
			{
				motion[0] = Motion.COUNTER;
				motion[1] = Motion.STAND;
			}
			else
			{
				motion[0] = Motion.HIT0;
				motion[1] = Motion.STAND;
			}
			break;

		case State.CHARGEHIT:
			motion[0] = Motion.CHARGEHIT0;
			motion[1] = Motion.STAND;
			break;

		case State.KNOCKBACK:
			motion[0] = Motion.KNOCKBACK;
			motion[1] = Motion.STAND;
			break;

		case State.DOWN:
			motion[0] = Motion.DOWN;
			motion[1] = Motion.STAND;
			break;

		case State.KO:
			motion[0] = Motion.KO;
			break;
		}

		if(motion[1] == Motion.INVALID)
		{
			_timer.set(motion[0]);
		}
		else
		{
			_timer.set(motion[0], motion[1]);
		}
	}


	override void getEyeVector(Vector d)
	{
		switch(_timer.index)
		{
		case Motion.HIT0:
		case Motion.HIT1:
		case Motion.HIT2:
		case Motion.HIT3:
		case Motion.COUNTER:
		case Motion.CHARGEHIT0:
			direction.getRight(d);
			break;

		case Motion.CHARGEHIT1:
			direction.getRight(d);
			.gedo.math.vector.invert(d);
			{
				Vector a;
				direction.getUp(a);
				.gedo.math.vector.add(d, a);
			}
			.gedo.math.vector.normalize(d);
			break;

		case Motion.SPELL1:
			direction.getRight(d);
			break;

		default:
			super.getEyeVector(d);
			return;
		}
	}


	override string voiceDir()
	{
		return "$LIB/moko/voice";
	}


private:
	static float[2][Movement.max+1] _accelerations;

	Bullet[FistID.max+1] _fists;
	HomingShot[12]       _homingShots;
	BombBall[16]         _bombBalls;
	FireBall[4]          _fireBalls;
	FireSpark            _fireSpark;
	RedBind2             _redBind2;
	RedBind              _redBind;
	Spell                _spell;


	void startChargeHit1(VisualTask t, Vector p)
	{
		_timer.set(Motion.CHARGEHIT1, Motion.STAND);
	}


	void shotRedBind2(VisualTask collisionObject, Vector collisionPosition)
	{
		if(_redBind2.parent is null && (cast(Character)collisionObject).getBalance() == 0)
		{
			shot(_redBind2, collisionObject.position.current, &collisionObject.position);
		}
	}


	void startSpell1(VisualTask t, Vector p)
	{
		_counts[Count.ACTION] = 180;
		_timer.set(Motion.SPELL1);
	}


	void drawWing()
	{
		direction.transform(position.current);

		GGL_setBlendMode(GGL_BlendMode.ADD);

		uint index, time;
		_timer.get(index, time);

		auto color = rgba(255, 255, 80 * (time & 3), 192);

		_wing.draw(index, time, _wing.texture, color);

		switch(index)
		{
		case Motion.SPELL1:
			{
				auto k = (180 - _counts[Count.ACTION])/180.0;
				GGL_enterBillBoardMode();

				GGL_rotate(std.math.PI*k, 0, 0, 1);

				GGL_scale(8.0*k, 8.0*k, 1.0);

				auto j = cast(int)(255*k);
				color = rgba(j, j, j, 255);

				_heatupRay.draw(_heatupRay.texture, color);
			}
			break;

		default:
			break;
		}
	}


	bool getHoldPosition(Vector p)
	{
		uint index, time;

		_timer.get(index, time);

		switch(index)
		{
		case Motion.SPELL0:
		case Motion.SPELL1:
			direction.toVector(0.0f, 0.0f, 4.0f, p);
			.gedo.math.vector.add(p, position.current);
			return true;

		default:
			break;
		}

		return false;
	}
}



class FireBall : Bullet
{
public:
	this(Power p)
	{
		_power = p;
	}


	override float radius()
	{
		return 2.0;
	}


	override void onUpdate()
	{
		homing(.std.math.PI/1024.0);

		Vector v;

		direction.getFront(v);

		.gedo.math.vector.multiply(v, 2.0);

		.gedo.math.vector.add(position.current, v);

		if(_time >= LIFE_TIME)
		{
			stop();
		}
		else if((cast(Character)parent).system.field.isSinked(position.current))
		{
			_time = LIFE_TIME;
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(position.current, &draw2);
		}
	}


	override BulletType type()
	{
		return BulletType.SHOT;
	}


	override Power power()
	{
		return _power;
	}


	override void onCollide(Vector center)
	{
		_time = LIFE_TIME;
	}


	override Sound hitSE()
	{
		return _sounds[SoundID.HIT0];
	}


	override EffectProgram hitEffect()
	{
		return _fireHitEffect;
	}


private:
	static const LIFE_TIME = 128;

	Power _power;


	void draw2()
	{
		translate(position.current);

		GGL_enterBillBoardMode();

		GGL_setBlendMode(GGL_BlendMode.MIX);

		GGL_rotate(_time * .std.math.PI / 3.0, 0, 0, 1);

		_textures[TextureID.FIREBALL].draw(4.0, 4.0);

		auto k = 4.5 + (_time & 7);
		GGL_setBlendMode(GGL_BlendMode.ADD);
		_textures[TextureID.FIRERING].draw(k, k);
	}
}



class BombBall : Bullet
{
public:
	this(Power p)
	{
		_power = p;
	}


	override float radius()
	{
		if(_time < LIFE_TIME)
		{
			return 1.0;
		}
		else
		{
			return 8.0;
		}
	}


	override void onUpdate()
	{
		if(_time < LIFE_TIME)
		{
			homing(.std.math.PI/1024.0);

			Vector v;

			direction.getFront(v);

			.gedo.math.vector.multiply(v, 2.0 * (LIFE_TIME - _time) / LIFE_TIME);

			.gedo.math.vector.add(position.current, v);

			if((cast(Character)parent).system.field.isSinked(position.current))
			{
				_time = LIFE_TIME;
			}
		}
		else if(_time >= LIFE_TIME + _ring.animationLength(0))
		{
			stop();
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(position.current, &draw2);
		}
	}


	override BulletType type()
	{
		return BulletType.SHOT;
	}


	override Power power()
	{
		return _power;
	}


	override void onCollide(Vector center)
	{
		if(_time < LIFE_TIME)
		{
			_time = LIFE_TIME;
			clearDones();
		}
	}


	override Sound hitSE()
	{
		return _sounds[SoundID.HIT0];
	}


	override EffectProgram hitEffect()
	{
		return _fireHitEffect;
	}


private:
	static const LIFE_TIME = 128;

	Power _power;


	void draw2()
	{
		translate(position.current);

		GGL_enterBillBoardMode();

		GGL_setBlendMode(GGL_BlendMode.MIX);

		if(_time < LIFE_TIME)
		{
			GGL_rotate(_time * .std.math.PI / 3.0, 0, 0, 1);

			auto scale = radius * 2.0;
			_textures[TextureID.BOMBBALL].draw(scale, scale);

			auto k = 4.5 + (_time & 7);
			GGL_setBlendMode(GGL_BlendMode.ADD);
			_textures[TextureID.FIRERING].draw(k, k);
		}
		else
		{
			_ring.draw(0, _time - LIFE_TIME);
		}
	}
}



public class FireHitEffect : EffectProgram
{
public:
	void update(EffectTask task)
	{
		if(task.time >= _ring.animationLength(0))
		{
			task.stop();
		}
	}


	void draw(EffectTask task)
	{
		if(auto am = task.list.get())
		{
			am.set(task.position.current, &task.drawAlpha);
		}
	}


	void drawAlpha(EffectTask task)
	{
		translate(task.position.current);

		GGL_enterBillBoardMode();

		GGL_setBlendMode(GGL_BlendMode.ADD);

		if(task.time < _ring.animationLength(0))
		{
			_ring.draw(0, task.time);
		}

		if(task.time < 6)
		{
			auto k = (1.0 + (task.time & 1)) * 2.0;
			GGL_scale(k, k, k);

			GGL_rotate(((task.time>>1)&1) * .std.math.PI, 0, 0, 1);

			_ray.draw();
		}
	}


	bool isLiving(EffectTask task)
	{
		return (task.time < _ring.animationLength(0));
	}
}



public class FireSpark : ActionTask
{
public:
	this(CharacterSystem sys)
	{
		_system = sys;
	}


	void start(Vector p)
	{
		_position[] = p[];

		_time = -1;
	}


	override void update()
	{
		++_time;

		if(_time >= 12)
		{
			stop();
		}
	}


	override void draw()
	{
		if(auto am = _system.alphaMeshList().get())
		{
			am.set(_position, &draw2);
		}
	}


private:
	CharacterSystem _system;
	Vector _position;
	int    _time;


	void draw2()
	{
		translate(_position);

		GGL_enterBillBoardMode();

		auto k = (_time + 1.0) * 2.0;
		GGL_setBlendMode(GGL_BlendMode.ADD);
		_textures[TextureID.FIRERING].draw(k, k);

		if(_time < 6)
		{
			k = 1.0 + (_time & 1);
			GGL_scale(k, k, k);

			GGL_rotate(((_time>>1)&1) * .std.math.PI, 0, 0, 1);

			_ray.draw();
		}
	}
}



class HomingShot : Bullet
{
public:
	this(Power p)
	{
		_power = p;
	}


	override float radius()
	{
		return 2.0;
	}


	override void onUpdate()
	{
		auto lv = 0.5;
		if(_time >= 32)
		{
			homing(.std.math.PI/16.0);
			lv = 0.5 + (_time - 32) * (4.5 / (LIFE_TIME - 32));
		}

		Vector v;

		direction.getFront(v);

		.gedo.math.vector.multiply(v, lv);

		.gedo.math.vector.add(position.current, v);

		if(_time >= LIFE_TIME)
		{
			stop();
		}
		else if((cast(Character)parent).system.field.isSinked(position.current))
		{
			_time = LIFE_TIME;
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(position.current, &draw2);
		}

		GGL_pushMatrix();

		direction.transform(position.current);

		GGL_rotate(_time * .std.math.PI / 16.0, 0, 1, 0);

		_homingShot.draw();

		GGL_popMatrix();
	}


	override BulletType type()
	{
		return BulletType.SHOT;
	}


	override Power power()
	{
		return _power;
	}


	override void onCollide(Vector center)
	{
		_time = LIFE_TIME;
	}


	override Sound hitSE()
	{
		return _sounds[SoundID.HIT0];
	}


	override EffectProgram hitEffect()
	{
		return _fireHitEffect;
	}


private:
	static const LIFE_TIME = 128;

	Power _power;


	void draw2()
	{
		direction.transform(position.current);

		GGL_setBlendMode(GGL_BlendMode.ADD);

		GGL_rotate(_time * .std.math.PI / 16.0, 0, 1, 0);

		auto scale = 1.0 + (_time & 3)/3.0;

		GGL_scale(scale, scale, scale);

		_homingShotAura.draw();
	}
}



private class RedBind : Bullet
{
public:
	this(Power p)
	{
		_power = p;

		_polyLine = new PolyLine(2);

		_polyLine.count  = 2;
		_polyLine.radius = 1.0;
	}


	override float radius()
	{
		return _polyLine.radius;
	}


	override Vector[] points()
	{
		return _polyLine.points[length-1 .. length];
	}


	override void onStart()
	{
		foreach(inout point; _polyLine.points)
		{
			point[] = position.current[];
		}
	}


	override void onUpdate()
	{
		if(_time < 64)
		{
			Vector v;

			direction.getFront(v);

			.gedo.math.vector.multiply(v, 32.0 * .std.math.sin(.std.math.PI / 64.0 * _time));

			.gedo.math.vector.add(position.current, _polyLine.points[0], v);

			_polyLine.points[length-1][] = position.current[];

			_polyLine.radius = 1.0 + (_time & 3)/3.0;
		}
		else
		{
			stop();
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(_polyLine.vertices, _textures[TextureID.REDBIND], GGL_BlendMode.ADD);
		}
	}


	override BulletType type()
	{
		return BulletType.SHOT;
	}


	override Power power()
	{
		return _power;
	}


	override Sound hitSE()
	{
		return _sounds[SoundID.HIT0];
	}


	override EffectProgram hitEffect()
	{
		return _fireHitEffect;
	}


private:
	PolyLine _polyLine;
	Power    _power;
}



private class RedBind2 : Bullet
{
public:
	this(Power p)
	{
		_power = p;

		_polyLine = new PolyLine(4);

		_polyLine.count  = 4;
		_polyLine.radius = 1.0;
	}


	override float radius()
	{
		return _polyLine.radius;
	}


	override void onUpdate()
	{
		if(_time <= 64)
		{
			position.current[] = _target.current[];

			foreach(i, inout point; _polyLine.points)
			{
				point[0] = position.current[0] + 8.0 * .std.math.cos((_time + i)*.std.math.PI/4.0);
				point[1] = position.current[1] + 8.0 * .std.math.sin((_time + i)*.std.math.PI/4.0);
				point[2] = position.current[2];
			}
			if((_time & 15) == 0)
			{
				clearDones();
			}

			_polyLine.radius = 1.0 + (_time&3)/3.0;
		}
		else
		{
			stop();
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(_polyLine.vertices, _textures[TextureID.REDBIND], GGL_BlendMode.ADD);
		}
	}


	override BulletType type()
	{
		return BulletType.SHOT;
	}


	override Power power()
	{
		return _power;
	}


	override Sound hitSE()
	{
		return _sounds[SoundID.HIT0];
	}


	override EffectProgram hitEffect()
	{
		return _fireHitEffect;
	}


private:
	PolyLine _polyLine;
	Power    _power;
}



class Spell : Bullet
{
public:
	this(Power p)
	{
		_power = p;
	}


	override float radius()
	{
		if(_time <= 32)
		{
			return 2.0 * _time;
		}
		else
		{
			return 64.0;
		}
	}


	override void onUpdate()
	{
		if(_time >= 48)
		{
			stop();
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(position.current, &draw2);
		}
	}


	override BulletType type()
	{
		return BulletType.SHOT;
	}


	override Power power()
	{
		return _power;
	}


	override Sound hitSE()
	{
		return _sounds[SoundID.HIT0];
	}


	override EffectProgram hitEffect()
	{
		return _fireHitEffect;
	}


private:
	static const LIFE_TIME = 128;

	Power _power;


	void draw2()
	{
		translate(position.current);

		GGL_enterBillBoardMode();

		if(_time >= 16)
		{
			auto k = (_time - 15)/32.0;
			GGL_setBlendMode(GGL_BlendMode.MIX);
			for(auto i=0; i<6; ++i)
			{
				GGL_pushMatrix();

				if(i != 0)
				{
					float x = 32.0*cos(.std.math.PI*2.0/5.0*i);
					float y = 32.0*sin(.std.math.PI*2.0/5.0*i);

					GGL_translate(x, y, 0.0);
				}

				auto scale = 64.0 * .std.math.sin(k * .std.math.PI_2);
				auto color = WHITE;
				if(_time > 44)
				{
					color = setA(color, (48 - _time)*60);
				}

				_textures[TextureID.EXPLOSION].draw(scale, scale, color);

				GGL_popMatrix();
			}
		}

		if(_time < 32)
		{
			GGL_pushMatrix();

			GGL_setBlendMode(GGL_BlendMode.ADD);

			auto k = _time / 32.0;
			auto scale = 256.0 * k;

			GGL_scale(scale, scale, scale);

			auto color = WHITE;
			if(_time >= 24)
			{
				auto j = (32 - _time)*255/8;

				color = rgba(255, 255, 255, j);
			}

			_shine.draw(_shine.texture, color);

			GGL_popMatrix();
		}
	}
}
}
