module vs.character.aki;



private import std.math;
private import std.cstream;
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 import vs.effect.program;



private enum Motion
{
	//aiw begin ../../../lib/vs/aki/shizu.gsa
	CHARGEHIT,
	CHARGESHOT,
	COUNTER,
	DASH1,
	DASH2,
	DOWN,
	FALL0,
	FALL1,
	GUARD0,
	GUARD1,
	GUARD2,
	HIT0,
	HIT1,
	HIT2,
	HOMINGSHOT,
	JUMP,
	KNOCKBACK,
	KO,
	LAND,
	RISE,
	RUN0,
	RUN1,
	RUN2,
	SINGLESHOT,
	SPECIAL_A0,
	SPECIAL_A1,
	SPECIAL_A2,
	SPECIAL_L,
	SPELL,
	STAND,
	//aiw end
	INVALID
}



private enum MinoMotion
{
	//aiw begin ../../../lib/vs/aki/mino.gsa
	CHARGEHIT,
	CHARGESHOT,
	HIT2,
	HOMINGSHOT0,
	HOMINGSHOT1,
	SPECIAL_A,
	SPECIAL_L,
	SPELL,
	STAND,
	//aiw end
	INVALID
}



private enum TextureID
{
	DEFAULT,
	ATTACK,
	DAMAGE,
	BUDOU,
	IMO,
	IMOFIRE,
	SYMBOL,
	PARTICLE,
	TRAJECTORY,
	TAIL,
}



private enum SoundID
{
	HIT0,
	HIT2,
	CUT,
	JUMP,
	HEADING,
	IMOHIT,
	BUDOUHIT,
	MOMIJISHOOT,
	BUDOUSHOOT,
	MINOSHOOT,
	POSING,
	SPELLCHARGE,
	SPELLSHOT,
	CHARGEHIT,
}



private enum FistID
{
	COUNTER,
	HIT0,
	HIT1,
	HIT2,
	CHARGEHIT,
}



public class ShizuFactory : CharacterFactory
{
public:
	this()
	{
		_textures[TextureID.DEFAULT] = Texture("$LIB/aki/shizu_n.png");
		_textures[TextureID.ATTACK ] = Texture("$LIB/aki/shizu_a.png");
		_textures[TextureID.DAMAGE ] = Texture("$LIB/aki/shizu_d.png");
		_textures[TextureID.BUDOU  ] = Texture("$LIB/aki/budou.png");
		_textures[TextureID.IMO    ] = Texture("$LIB/aki/imo.png");
		_textures[TextureID.IMOFIRE] = Texture("$LIB/aki/imofire.png");
		_textures[TextureID.SYMBOL ] = Texture("$LIB/aki/symbol.png");
		_textures[TextureID.PARTICLE] = Texture("$LIB/aki/particle.png");
		_textures[TextureID.TAIL] = Texture("$LIB/aki/tail.png");

		Color[4] pixels;
		pixels[0] = setA(WHITE, 128);
		pixels[1] = rgba(192, 192, 255, 128);
		pixels[2] = setA(BLUE, 128);
		pixels[3] = setA(pixels[2], 0);
		_textures[TextureID.TRAJECTORY] = new Texture(pixels, pixels.length, 1);

		_shizu = SkinnedMesh("$LIB/aki/shizu.gsa");
		_mino  = SkinnedMesh("$LIB/aki/mino.gsa");
		_momiji = StaticMesh("$LIB/aki/momiji.gsa");
		_rays[0] = StaticMesh("$LIB/aki/ray0.gsa");
		_rays[1] = StaticMesh("$LIB/aki/ray1.gsa");
		_circles[0] = StaticMesh("$LIB/aki/circle0.gsa");
		_circles[1] = StaticMesh("$LIB/aki/circle1.gsa");

		EffectProgram[2] programs;

		programs[0] = new Ray(2.0, GGL_BlendMode.ADD, WHITE);
		programs[1] = new Ring(8.0, GGL_BlendMode.ADD, WHITE);

		_chargeHitFlash = new CompositeEffect(programs);
	}


	override Character create(CharacterSystem sys)
	{
		_sounds[SoundID.CUT]  = sys.sharedSound(SharedSoundID.CUT);
		_sounds[SoundID.HIT0] = sys.sharedSound(SharedSoundID.HITM);
		_sounds[SoundID.HIT2] = sys.sharedSound(SharedSoundID.HITH);
		_sounds[SoundID.JUMP] = Sound("$LIB/aki/jump.ogg");
		_sounds[SoundID.HEADING] = Sound("$LIB/aki/heading.ogg");
		_sounds[SoundID.IMOHIT] = Sound("$LIB/aki/imohit.ogg");
		_sounds[SoundID.BUDOUHIT] = sys.sharedSound(SharedSoundID.HITH);
		_sounds[SoundID.MOMIJISHOOT] = sys.sharedSound(SharedSoundID.SWINGL);
		_sounds[SoundID.BUDOUSHOOT] = sys.sharedSound(SharedSoundID.SHOTL);
		_sounds[SoundID.MINOSHOOT] = sys.sharedSound(SharedSoundID.SWINGH);
		_sounds[SoundID.POSING] = Sound("$LIB/aki/posing.ogg");
		_sounds[SoundID.SPELLCHARGE] = Sound("$LIB/aki/spellcharge.ogg");
		_sounds[SoundID.SPELLSHOT] = Sound("$LIB/aki/spellshot.ogg");
		_sounds[SoundID.CHARGEHIT] = Sound("$LIB/aki/chargehit.ogg");

		return new Shizu(sys);
	}


private:
	Texture[TextureID.max+1] _textures;
	Sound[SoundID.max+1]     _sounds;
	EffectProgram            _chargeHitFlash;
	StaticMesh[2]            _rays;
	StaticMesh[2]            _circles;
	SkinnedMesh              _shizu;
	SkinnedMesh              _mino;
	StaticMesh               _momiji;



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

		_accelerations[Movement.RUN][1]     *= 0.875;
		_accelerations[Movement.FALL][1]    *= 0.875;
	}


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

		_minoTimer = new Timer(_mino);
		_minoTimer.set(MinoMotion.STAND);

		_fists[FistID.COUNTER] = new Fist(new Power( 8, 0.0, [0.0f, 1.0f, 0.0f]), _sounds[SoundID.HIT0], sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.HIT0]    = new Fist(new Power( 8, 1.0, [0.0f, 1.0f, 0.0f]), _sounds[SoundID.HIT0], sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.HIT1]    = new Fist(new Power( 8, 1.0, [0.0f, 1.0f, 0.0f]), _sounds[SoundID.HIT0], sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.HIT2]    = new Fist(new Power(16, 2.5, [0.0f, 1.0f, 0.0f]), _sounds[SoundID.HIT2], sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.CHARGEHIT] = new Fist(new Power(50, 2.5, [0.0f, -1.0f, 2.0f]), sys.sharedSound(SharedSoundID.FINISH), sys.sharedEffect(SharedEffectID.HITMARK), _timer, f2t(15));

		auto power = new Power(16, 1.5);
		foreach(inout b; _singleShots)
		{
			b = new Momiji(power);
		}

		power = new Power(6, 1.5);
		foreach(inout b; _chargeShots)
		{
			b = new Momiji(power);
		}

		power = new Power(3, 1.0);
		foreach(inout b; _budous)
		{
			b = new Budou(power);
		}

		power = new Power(32, 1.0);
		foreach(inout b; _imos)
		{
			b = new Imo(power);
		}

		power = new Power(12, 1.0);
		_homingShot = new HomingMino(power, _minoTimer);

		power = new Power(80, 2.5);
		_spell = new Spell(power);

		_spark = new Spark(_system);

		foreach(inout s; _budouSparks)
		{
			s = new BudouSpark(_system);
		}

		foreach(i, inout vertex; _vertices)
		{
			vertex.texCoord[0] = (i>>1) * 1.0/((_vertices.length>>1)-1);
			vertex.texCoord[1] = 1.0 * (i&1);
		}
	}


	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(t >= f2t(22) && t < f2t(24))
				{
					tex = Texture.white;
				}
				break;

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

		_shizu.draw(i, t, tex);
		if(i == Motion.SPECIAL_L && t >= f2t(35) && t < f2t(45))
		{
			if(auto am = _system.alphaMeshList().get())
			{
				am.set(position.current, &drawImo);
			}
		}
		else if(i == Motion.SPELL)
		{
			if(t >= f2t(22) && t < f2t(27))
			{
				if(auto am = _system.alphaMeshList().get())
				{
					am.set(position.current, &drawParticle);
				}
			}
			else if(t >= f2t(35) && t < f2t(53))
			{
				if(auto am = _system.alphaMeshList().get())
				{
					am.set(position.current, &drawSpell);
				}
			}
		}
		else if(i == Motion.CHARGEHIT && t >= f2t(5) && t < f2t(25))
		{
			if(auto am = _system.alphaMeshList().get())
			{
				getTrajectoryPosition(_vertices[0].position, _vertices[1].position);
				am.set(_vertices, _textures[TextureID.TRAJECTORY], GGL_BlendMode.ADD);
			}
		}

		switch(_minoTimer.index)
		{
		case MinoMotion.STAND:
		case MinoMotion.HOMINGSHOT0:
		case MinoMotion.HOMINGSHOT1:
			break;

		default:
			switch(i)
			{
			case Motion.SPECIAL_L:
				if(t < f2t(23))
				{
					GGL_translate(0.0, -0.25*(cast(float)t - cast(float)f2t(23)), _minoPosition[2] - position.current[2]);
				}
				else
				{
					GGL_translate(0.0, 0.0, _minoPosition[2] - position.current[2]);
				}
				break;

			case Motion.SPECIAL_A0:
				GGL_translate(0.0, -1.0 * _timer.left, 0.0);
				break;

			case Motion.SPECIAL_A2:
				GGL_translate(0.0, 1.0 * t, 0.0);
				break;

			case Motion.CHARGESHOT:
				GGL_translate(4.0, 0.0, 0.0);
				break;

			default:
				break;
			}

			_minoTimer.draw();
			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 == f2t(7))
			{
				if(auto b = cast(Bullet)findFreeTask(_singleShots))
				{
					shot(b);
				}
			}
			else if(t < f2t(7))
			{
				turnToTarget(.std.math.PI/4.0);
			}
			break;

		case Motion.CHARGESHOT:
			switch(t)
			{
			case f2t(7):
			case f2t(23):
				if(auto b = cast(Bullet)findFreeTask(_chargeShots))
				{
					shot(b);
				}
				break;

			case f2t(13):
			case f2t(17):
			case f2t(21):
			case f2t(25):
				if(auto b = cast(Bullet)findFreeTask(_budous))
				{
					shot(b, [4.0f, 2.0f, 0.0f]);
					budouSpark(b.position.current);
				}
				break;

			default:
				break;
			}
			if(t <= f2t(23))
			{
				turnToTarget(.std.math.PI / 240.0);
			}
			break;

		case Motion.HOMINGSHOT:
			if(t == f2t(7))
			{
				if(!_homingShot.parent)
				{
					shot(_homingShot);
				}
			}
			break;

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

		case Motion.HIT0:
			if(t < f2t(7))
			{
				follow();
			}
			else if(t == f2t(7))
			{
				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))
			{
				shotFist(_fists[FistID.HIT1]);
				velocity[] = 0.0;
			}
			else if(t >= f2t(11) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT2, Motion.STAND);
				_minoTimer.set(MinoMotion.HIT2, MinoMotion.STAND);
			}
			break;

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

		case Motion.CHARGEHIT:
			if(t == f2t(5))
			{
				shotFist(_fists[FistID.CHARGEHIT], [5.0f, 0.0f, 0.0f]);

				Source().play(_sounds[SoundID.CHARGEHIT]);

				getTrajectoryPosition(_vertices[2].position, _vertices[3].position);
				foreach(a, inout vertex; _vertices[4 .. $])
				{
					vertex.position[] = _vertices[a+2].position[];
				}
			}
			else if(t > f2t(5) && t < f2t(25))
			{
				if(t == f2t(9))
				{
					if(auto effectTask = _system.getEffectTask())
					{
						direction.toVector(5.0, 0.0, 0.0, effectTask.position.current);

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

						effectTask.direction.copyFrom(direction);

						effectTask.start(_chargeHitFlash);
					}
				}

				for(auto a=_vertices.length-1; a>3; --a)
				{
					_vertices[a].position[] = _vertices[a-2].position[];
				}
				getTrajectoryPosition(_vertices[2].position, _vertices[3].position);
			}

			if(_fists[FistID.CHARGEHIT].parent)
			{

				if(target && (!_fists[FistID.CHARGEHIT].isCollided()))
				{
					Vector a = target.position.current[];

					.gedo.math.vector.subtract(a, position.current);

					Vector e;

					turnToTarget();
					direction.getRight(e);

					auto la = .gedo.math.vector.magnitude(a);
					if(la > 5.0)
					{
						auto sint = 5.0 / la;

						.gedo.math.vector.multiply(e, -5.0/sqrt(1 - sint*sint));

						.gedo.math.vector.add(e, a);

						.gedo.math.vector.normalize(e);
					}
					else
					{
						.gedo.math.vector.invert(e);
					}

					direction.homing(e, PI);
				}

				setVelocity(Movement.FOLLOW);
			}
			else
			{
				brake();
			}
			break;

		case Motion.SPECIAL_L:
			if(t == f2t(3))
			{
				turnToHorizon();
				velocity[] = [0.0f, 0.0f, 1.0f];
			}
			else if(t > f2t(3) && t < f2t(23))
			{
				velocity[2] = 1.0 - 2.0*(t - f2t(3))/f2t(20);
			}
			else if(t >= f2t(23) && t < f2t(45))
			{
				if(t == f2t(23))
				{
					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = position.current[];

						effectTask.start(_system.sharedEffect(SharedEffectID.HITMARK));
					}

					Source().play(_sounds[SoundID.JUMP]);
				}
				velocity[2] = 3.0 - 3.0*(t - f2t(23))/f2t(22);
			}
			else if(t == f2t(45))
			{
				turnToTarget();
				if(auto b = cast(Bullet)findFreeTask(_imos))
				{
					shot(b);
					pause(16);
					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = b.position.current[];

						effectTask.start(_system.sharedEffect(SharedEffectID.HITMARK));
					}

					Source().play(_sounds[SoundID.HEADING]);
				}
			}
			break;

		case Motion.SPECIAL_A0:
			direction.homing(axisDirection, .std.math.PI / 120.0);
			velocity[] = 0.0;
			break;

		case Motion.SPECIAL_A1:
			if(axisLength == 0.0 || _counts[Count.ACTION] == 0)
			{
				_timer.set(Motion.SPECIAL_A2, Motion.STAND);
			}
			direction.homing(axisDirection, .std.math.PI / 120.0);
			setVelocity(Movement.STEP);
			break;

		case Motion.SPELL:
			brake();
			turnToTarget(.std.math.PI/16.0);
			if(t == f2t(53))
			{
				if(!_spell.parent)
				{
					shot(_spell);
					spark(_spell.position.current);
				}
			}
			else if(t == f2t(35))
			{
				Source().play(_sounds[SoundID.SPELLCHARGE]);
			}
			else if(t == f2t(22))
			{
				Source().play(_sounds[SoundID.POSING]);
			}
			break;

		case Motion.KNOCKBACK:
		case Motion.DOWN:
		case Motion.KO:
			_minoTimer.set(MinoMotion.STAND);
			break;

		default:
			break;
		}

		_minoTimer.update();
		switch(_minoTimer.index)
		{
		case MinoMotion.SPECIAL_A:
			switch(i)
			{
			case Motion.SPECIAL_A0:
			case Motion.SPECIAL_A1:
			case Motion.SPECIAL_A2:
				break;

			default:
				_minoTimer.set(MinoMotion.STAND);
				break;
			}
			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:
			_specialCount = 1;
			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;
			_minoTimer.set(MinoMotion.CHARGESHOT, MinoMotion.STAND);
			break;

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

		case State.SPECIAL:
			if(isFlyingPosition())
			{
				motion[0] = Motion.SPECIAL_A0;
				motion[1] = Motion.SPECIAL_A1;

				if(_specialCount > 0)
				{
					_counts[Count.ACTION] = 180;
					_specialCount = 0;
				}
				else
				{
					_counts[Count.ACTION] = 30;
				}

				_minoTimer.set(MinoMotion.SPECIAL_A);
			}
			else
			{
				motion[0] = Motion.SPECIAL_L;
				motion[1] = Motion.STAND;

				_minoTimer.set(MinoMotion.SPECIAL_L, MinoMotion.STAND);
				_minoPosition[] = position.current[];
			}
			break;

		case State.SPELL:
			motion[0] = Motion.SPELL;
			motion[1] = Motion.STAND;
			_minoTimer.set(MinoMotion.SPELL, MinoMotion.STAND);
			break;

		case State.FOLLOW:
			motion[0] = Motion.DASH1;
			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.CHARGEHIT;
			motion[1] = Motion.STAND;
			_minoTimer.set(MinoMotion.CHARGEHIT, MinoMotion.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.COUNTER:
			direction.getRight(d);
			return;

		case Motion.CHARGEHIT:
			direction.toVector(SQRT1_2, SQRT1_2, 0.0, d);
			return;

		case Motion.SPECIAL_L:
			if(_timer.time <= f2t(23))
			{
				direction.toVector(0.0, SQRT1_2, -SQRT1_2, d);
			}
			else if(_timer.time <= f2t(43))
			{
				direction.getRight(d);
			}
			else
			{
				break;
			}
			return;

		case Motion.SPELL:
			if(_timer.time < f2t(48))
			{
				direction.toVector(SQRT1_2, -SQRT1_2, 0.0, d);
				return;
			}
			break;

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


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


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

	Bullet[FistID.max+1] _fists;
	GGL_Vertex[16]       _vertices;
	BudouSpark[2]        _budouSparks;
	HomingMino           _homingShot;
	Momiji[2]            _singleShots;
	Momiji[4]            _chargeShots;
	Budou[8]             _budous;
	Imo[1]               _imos;
	Vector               _minoPosition;
	Timer                _minoTimer;
	Spell                _spell;
	Spark                _spark;
	int                  _specialCount;


	void getTrajectoryPosition(Vector p0, Vector p1)
	{
		direction.toVector(3.0, -0.8, 1.1, p0);
		direction.toVector(7.0, -0.8, 1.1, p1);
		.gedo.math.vector.add(p0, position.current);
		.gedo.math.vector.add(p1, position.current);
	}


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

		auto k = (_timer.time - f2t(35))/cast(float)f2t(10);

		GGL_translate(0.0, 2.0 + 8.0*(1.0 - k), -8.0*(1.0 - k));

		GGL_enterBillBoardMode();

		GGL_rotate(PI * 16.0 * k, 0, 0, 1);

		GGL_setBlendMode(GGL_BlendMode.MIX);

		_textures[TextureID.IMO].draw(4.0, 4.0);
	}


	void drawParticle()
	{
		auto k = (_timer.time - f2t(22))/cast(float)f2t(5);

		direction.transform(position.current);

		GGL_enterBillBoardMode();

		GGL_setBlendMode(GGL_BlendMode.ADD);

		GGL_pushMatrix();

		auto t = _timer.time;

		GGL_rotate(t * .std.math.PI / 4.0, 0, 0, 1);

		auto s = 1.0 + sin(PI_2*k);

		GGL_scale(s, s, s);

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

		_rays[0].draw(color);

		GGL_popMatrix();

		auto l = 0.5 + 8.0*k;
		GGL_pushMatrix();
		GGL_translate(l, l, 0.0);
		_textures[TextureID.PARTICLE].draw(4.0, 4.0, color);
		GGL_popMatrix();
		GGL_pushMatrix();
		GGL_translate(-l, -l, 0.0);
		_textures[TextureID.PARTICLE].draw(4.0, 4.0, color);
		GGL_popMatrix();

		l *= 1.5;
		GGL_pushMatrix();
		GGL_translate(l, -l, 0.0);
		_textures[TextureID.PARTICLE].draw(4.0, 4.0, color);
		GGL_popMatrix();
		GGL_pushMatrix();
		GGL_translate(-l, l, 0.0);
		_textures[TextureID.PARTICLE].draw(4.0, 4.0, color);
		GGL_popMatrix();
	}


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

		GGL_translate(0.0, -2.0, 0.0);

		GGL_enterBillBoardMode();

		GGL_setBlendMode(GGL_BlendMode.ADD);

		auto t = _timer.time;

		GGL_rotate(t * .std.math.PI / 4.0, 0, 0, 1);

		auto s = 0.5 + (t & 3)*0.125;

		GGL_scale(s, s, s);

		_circles[1].draw();
		_rays[1].draw();

		_circles[0].draw();
		_rays[0].draw();
	}


	void spark(Vector p)
	{
		if(!_spark.parent)
		{
			_spark.start(p);
			parent.parent.add(_spark);
		}
	}


	void budouSpark(Vector p)
	{
		if(auto s = cast(BudouSpark)findFreeTask(_budouSparks))
		{
			s.start(p);
			parent.parent.add(s);
		}
	}
}



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

		_polyLine = new PolyLine(2);
		_polyLine.count  = 2;
		_polyLine.radius = 1.5;
	}


	override float radius()
	{
		return 2.0;
	}


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


	override void onUpdate()
	{
		if(_time >= LIFE_TIME)
		{
			stop();
		}
		else if((cast(Character)parent).system.field.isSinked(position.current))
		{
			_time = LIFE_TIME;
		}
		else
		{
			if(_time == 0)
			{
				Source().play(_sounds[SoundID.MOMIJISHOOT]);
				if(auto effectTask = (cast(Character)parent).system.getEffectTask())
				{
					effectTask.position.current[] = position.current[];

					effectTask.start((cast(Character)parent).system.sharedEffect(SharedEffectID.FLASH4));
				}
			}
			homing(.std.math.PI/1024.0);

			Vector v;

			direction.getFront(v);

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

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

			_polyLine.record(position.current, 64.0);
		}
	}


	override void draw()
	{
		GGL_pushMatrix();

		direction.transform(position.current);

		GGL_setBlendMode(GGL_BlendMode.MIX);

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

		_momiji.draw();

		GGL_popMatrix();

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


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


	override Power power()
	{
		return _power;
	}


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


	override EffectProgram hitEffect()
	{
		return (cast(Character)parent).system.sharedEffect(SharedEffectID.HITMARK);
	}


private:
	static const LIFE_TIME = 128;

	Power _power;
	PolyLine _polyLine;
}



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


	override float radius()
	{
		return 1.0;
	}


	override void onUpdate()
	{
		if(_time >= LIFE_TIME)
		{
			stop();
		}
		else if((cast(Character)parent).system.field.isSinked(position.current))
		{
			_time = LIFE_TIME;
		}
		else
		{
			homing(.std.math.PI/2048.0);

			Vector v;

			direction.getFront(v);

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

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

			if(_time == 0)
			{
				Source().play(_sounds[SoundID.BUDOUSHOOT]);
			}
		}
	}


	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.BUDOUHIT];
	}


	override EffectProgram hitEffect()
	{
		return (cast(Character)parent).system.sharedEffect(SharedEffectID.HITMARK);
	}


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


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.BUDOU].draw(radius*2.0, radius*2.0);
	}
}



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


	override float radius()
	{
		return 2.0;
	}


	override void onUpdate()
	{
		if(_time >= LIFE_TIME)
		{
			stop();
		}
		else if((cast(Character)parent).system.field.isSinked(position.current))
		{
			_time = LIFE_TIME;
		}
		else if(!(cast(Character)parent).isPaused())
		{
			Vector v;

			direction.getFront(v);

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

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


	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.IMOHIT];
	}


	override EffectProgram hitEffect()
	{
		return (cast(Character)parent).system.sharedEffect(SharedEffectID.HITMARK);
	}


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


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 / 4.0, 0, 0, 1);

		auto s = 4.0 * radius + (_time & 3);

		_textures[TextureID.IMOFIRE].draw(s, s);

		s = radius*2.0;

		_textures[TextureID.IMO].draw(s, s);
	}
}



private class HomingMino : Bullet
{
public:
	this(Power p, Timer t)
	{
		_power = p;

		_timer = t;

		onCollideOwner = &hold;
		getHoldPosition = &holdPosition;
	}


	override void onStart()
	{
		_timer.set(MinoMotion.HOMINGSHOT0);
	}


	override void onUpdate()
	{
		switch(_timer.index)
		{
		case MinoMotion.HOMINGSHOT0:
			if(_time >= LIFETIME)
			{
				stop();
			}
			else if((cast(Character)parent).system.field.isSinked(position.current))
			{
				stop();
			}
			else if((cast(Character)parent).getBalance() != 0)
			{
				stop();
			}
			else
			{
				if(_time == 0)
				{
					Source().play(_sounds[SoundID.MINOSHOOT]);
				}

				homing(.std.math.PI/64.0);

				Vector v;

				direction.getFront(v);

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

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

		case MinoMotion.HOMINGSHOT1:
			if(_time >= LIFETIME || _holdTarget.getBalance() >= 20)
			{
				stop();
			}
			break;

		default:
			stop();
			break;
		}
	}


	override void draw()
	{
		GGL_pushMatrix();

		direction.transform(position.current);

		if(_timer.index == MinoMotion.HOMINGSHOT0)
		{
			GGL_rotate(_time * .std.math.PI / 16.0, 0, 1, 0);
		}

		_timer.draw();

		GGL_popMatrix();
	}


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


	override Power power()
	{
		return _power;
	}


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


	override EffectProgram hitEffect()
	{
		return (cast(Character)parent).system.sharedEffect(SharedEffectID.HITMARK);
	}


private:
	const int LIFETIME = 192;

	Character _holdTarget;
	Power     _power;
	Timer     _timer;


	void hold(VisualTask t, Vector p)
	{
		_timer.set(MinoMotion.HOMINGSHOT1);

		_time = 0;

		_holdTarget = cast(Character)t;
	}


	bool holdPosition(Vector p)
	{
		if(parent)
		{
			p[] = position.current[];
			return true;
		}

		return false;
	}
}



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


	override float radius()
	{
		return 8.0;
	}


	override void onUpdate()
	{
		if(_time >= LIFE_TIME)
		{
			stop();
		}
		else if((cast(Character)parent).system.field.isSinked(position.current))
		{
			_time = LIFE_TIME;
		}
		else
		{
			if(_time == 0)
			{
				Source().play(_sounds[SoundID.SPELLSHOT]);
			}

			Vector v;

			direction.getFront(v);

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

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


	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.IMOHIT];
	}


	override EffectProgram hitEffect()
	{
		return (cast(Character)parent).system.sharedEffect(SharedEffectID.HITMARK);
	}


private:
	static const LIFE_TIME = 128;

	Power _power;


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

		GGL_enterBillBoardMode();

		GGL_setBlendMode(GGL_BlendMode.MIX);

		GGL_pushMatrix();

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

		auto s = 1.0 + (_time & 3)*0.25;

		GGL_scale(s, s, s);

		_circles[1].draw();
		_rays[1].draw();

		_circles[0].draw();
		_rays[0].draw();

		GGL_popMatrix();

		_textures[TextureID.SYMBOL].draw(16.0, 16.0);
	}
}



public class Spark : 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()
	{
		auto k = _time/12.0;

		translate(_position);

		GGL_enterBillBoardMode();

		GGL_setBlendMode(GGL_BlendMode.ADD);

		GGL_pushMatrix();

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

		auto s = 1.0 + sin(PI_2*k);

		GGL_scale(s, s, s);

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

		_circles[1].draw(color);
		_rays[1].draw(color);

		GGL_popMatrix();

		auto l = 1.0 + 16.0*k;
		GGL_pushMatrix();
		GGL_translate(l, l, 0.0);
		_textures[TextureID.PARTICLE].draw(8.0, 8.0, color);
		GGL_popMatrix();
		GGL_pushMatrix();
		GGL_translate(-l, -l, 0.0);
		_textures[TextureID.PARTICLE].draw(8.0, 8.0, color);
		GGL_popMatrix();

		l *= 1.5;
		GGL_pushMatrix();
		GGL_translate(l, -l, 0.0);
		_textures[TextureID.PARTICLE].draw(8.0, 8.0, color);
		GGL_popMatrix();
		GGL_pushMatrix();
		GGL_translate(-l, l, 0.0);
		_textures[TextureID.PARTICLE].draw(8.0, 8.0, color);
		GGL_popMatrix();
	}
}



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


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

		_time = -1;
	}


	override void update()
	{
		++_time;

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


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


private:
	const int LIFE_TIME = 8;

	CharacterSystem _system;
	Vector          _position;
	int             _time;


	void draw2()
	{
		translate(_position);

		GGL_enterBillBoardMode();

		GGL_setBlendMode(GGL_BlendMode.ADD);

		GGL_rotate(_time*PI/64.0, 0, 0, 1);

		auto s = 4.0 + 4.0*sin(_time*PI_2/LIFE_TIME);

		_textures[TextureID.IMOFIRE].draw(s, s);
	}
}
}
