module vs.character.inusakuya;



private import std.math;
private import std.cstream;
private import std.c.stdlib;
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.particle;
private import gedo.video.trajectory;
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/inusakuya/body.gsa
	ATTACKRUN1,
	ATTACKRUN2,
	CHARGEHIT0,
	CHARGEHIT1,
	CHARGEHIT2,
	CHARGEHIT3,
	CHARGEHIT4,
	CHARGESHOT,
	COUNTER0,
	COUNTER1,
	COUNTER2,
	DASH0,
	DASH1,
	DASH2,
	DOWN,
	FALL0,
	FALL1,
	GUARD0,
	GUARD1,
	GUARD2,
	HIT0,
	HIT1,
	HIT2,
	HIT3,
	HIT4,
	HIT5,
	HOMINGSHOT0,
	HOMINGSHOT1,
	JUMP,
	KNOCKBACK,
	KO,
	LAND,
	RISE,
	RUN0,
	RUN1,
	RUN2,
	SINGLESHOT,
	SPECIAL,
	SPELL,
	STAND0,
	STAND1,
	//aiw end
	INVALID
}



private enum TextureID
{
	DEFAULT,
	ATTACK,
	DAMAGE,
	SPECIAL,
	SPELL,
	TAIL,
	CHARGESHOT,
}



private enum SoundID
{
	COUNTER2,
	SPECIAL,
	COUNTDOWN,
	SPELL,
}



private enum FistID
{
	COUNTER0,
	COUNTER1,
	HIT0,
	HIT1,
	HIT2A,
	HIT2B,
	HIT4,
	HIT5A,
	HIT5B,
	CHARGEHIT0,
	CHARGEHIT1,
	CHARGEHIT2,
	CHARGEHIT4A,
	CHARGEHIT4B,
}



private enum TrajectoryID
{
	//tiw begin ../../../lib/vs/inusakuya/inusakuya.trj
	TRAJECTORY_HIT0_3_7,
	TRAJECTORY_SPECIALHIT0_1_5,
	TRAJECTORY_CHARGEHIT0_2_7,
	TRAJECTORY_CHARGEHIT1_3_110,
	TRAJECTORY_SPECIALHIT1_5_9,
	TRAJECTORY_HIT1_6_10,
	//tiw end
}



public class InusakuyaFactory : CharacterFactory
{
public:
	this()
	{
		_textures[TextureID.DEFAULT] = Texture("$LIB/inusakuya/inusakuya_t.png");
		_textures[TextureID.ATTACK ] = Texture("$LIB/inusakuya/inusakuya_a.png");
		_textures[TextureID.DAMAGE ] = Texture("$LIB/inusakuya/inusakuya_d.png");
		_textures[TextureID.SPECIAL] = Texture("$LIB/inusakuya/inusakuya_k.png");
		_textures[TextureID.SPELL  ] = Texture("$LIB/inusakuya/inusakuya_sc.png");

		Color[256] pixels;

		for(auto x = 0; x < 16; ++x)
		{
			int maxy = -(x - 8)*(x - 8)/8 + 16;

			auto maxa = 255.0;
			if(x & 2)
			{
				maxa = 64.0;
				maxy *= 0.5;
			}

			for(auto y = 0; y < 16; ++y)
			{
				int alpha = 0;
				if(y < maxy)
				{
					auto k = cast(double)y / maxy;
					k = maxa*(1.0 - k*k);

					alpha = cast(int)k;
				}
				pixels[(15-y)*16 + x] = rgba(32, 128, 255, alpha);
			}
		}

		_textures[TextureID.TAIL] = new Texture(pixels, 16, 16);

		_body = SkinnedMesh("$LIB/inusakuya/body.gsa");
		_knife = SkinnedMesh("$LIB/inusakuya/knife.gsa");
		_knifethrowing = StaticMesh("$LIB/inusakuya/knifethrowing.gsa");
		_obon = SkinnedMesh("$LIB/inusakuya/cube.gsa");
		_meet = SkinnedMesh("$LIB/inusakuya/inu_meet_0.gsa");
		_specialFlash[0] = SkinnedMesh("$LIB/inusakuya/inu_hikari_0.gsa");
		_specialFlash[1] = SkinnedMesh("$LIB/inusakuya/inu_cure_0.gsa");
		_specialFlash[2] = SkinnedMesh("$LIB/inusakuya/inu_cure_1.gsa");
		_specialFlash[3] = SkinnedMesh("$LIB/inusakuya/inu_cure_2.gsa");
		_slashes[0] = SkinnedMesh("$LIB/inusakuya/slash_0.gsa");
		_slashes[1] = SkinnedMesh("$LIB/inusakuya/slash_1.gsa");
		_slashes[2] = SkinnedMesh("$LIB/inusakuya/slash_2.gsa");
		_slashes[3] = SkinnedMesh("$LIB/inusakuya/slash_3.gsa");
		_world = SkinnedMesh("$LIB/inusakuya/world_.gsa");

		pixels[0] = pixels[1] = WHITE;
		pixels[2] = rgba(64, 128, 255, 255);
		pixels[3] = setA(pixels[2], 0);
		_chargeShotRoot = StaticMesh.createCircle(1.0, new Texture(pixels[0..4], 4, 1));

		pixels[0] = pixels[7] = rgba(64, 128, 255, 0);
		pixels[1] = pixels[6] = rgba(64, 128, 255, 255);
		pixels[2] = pixels[5] = WHITE;
		pixels[3] = pixels[4] = WHITE;
		_textures[TextureID.CHARGESHOT] = new Texture(pixels[0..8], 8, 1);
	}


	override Character create(CharacterSystem sys)
	{
		_sounds[SoundID.COUNTER2] = Sound("$LIB/inusakuya/counter.ogg");
		_sounds[SoundID.SPECIAL] = Sound("$LIB/inusakuya/special.ogg");
		_sounds[SoundID.SPELL] = Sound("$LIB/inusakuya/spell.ogg");
		_sounds[SoundID.COUNTDOWN] = Sound("$LIB/inusakuya/countdown.ogg");

		return new Inusakuya(sys);
	}


private:
	Texture[TextureID.max+1] _textures;
	Sound[SoundID.max+1]     _sounds;
	SkinnedMesh[4]           _specialFlash;
	SkinnedMesh[4]           _slashes;
	SkinnedMesh              _body;
	SkinnedMesh              _obon;
	SkinnedMesh              _knife;
	SkinnedMesh              _meet;
	SkinnedMesh              _world;
	StaticMesh               _chargeShotRoot;
	StaticMesh               _knifethrowing;



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

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


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

		_fists[FistID.COUNTER0] = new Fist(new Power(10, 1.0), sys.sharedSound(SharedSoundID.CUT), sys.sharedEffect(SharedEffectID.HITMARK), _timer, f2t(22));
		_fists[FistID.COUNTER1] = new Fist(new Power(10, 2.5), sys.sharedSound(SharedSoundID.CUT), sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.HIT0] = new Fist(new Power(2, 0.0), sys.sharedSound(SharedSoundID.CUT), sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.HIT1] = new Fist(new Power(2, 1.0), sys.sharedSound(SharedSoundID.CUT), sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.HIT2A] = new Fist(new Power(2, 0.0), sys.sharedSound(SharedSoundID.CUT), sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.HIT2B] = new Fist(new Power(2, 1.0), sys.sharedSound(SharedSoundID.HITH), sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.HIT2B].onCollideOwner = &startHit3;
		_fists[FistID.HIT4] = new Fist(new Power(2, 0.0), sys.sharedSound(SharedSoundID.HITM), sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.HIT5A] = new Fist(new Power(2, 1.0), sys.sharedSound(SharedSoundID.HITM), sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.HIT5B] = new Fist(new Power(2, 2.5), sys.sharedSound(SharedSoundID.HITH), sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.CHARGEHIT0] = new Fist(new Power(4, 1.0), sys.sharedSound(SharedSoundID.CUT), sys.sharedEffect(SharedEffectID.HITMARK), _timer, f2t(28));
		_fists[FistID.CHARGEHIT0].onCollideOwner = &startChargeHit1;
		_fists[FistID.CHARGEHIT1] = new Fist(new Power(4, 1.0), sys.sharedSound(SharedSoundID.CUT), sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.CHARGEHIT2] = new Fist(new Power(1, 0.0), sys.sharedSound(SharedSoundID.CUT), sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.CHARGEHIT4A] = new Fist(new Power(1, 0.7, [0.0f, 0.25f, 0.5f]), sys.sharedSound(SharedSoundID.CUT), sys.sharedEffect(SharedEffectID.HITMARK), _timer);
		_fists[FistID.CHARGEHIT4B] = new Fist(new Power(20, 2.5), sys.sharedSound(SharedSoundID.FINISH), sys.sharedEffect(SharedEffectID.HITMARK), _timer);

		auto power = new Power(2, 1.0);
		foreach(inout knife; _counterKnifes)
		{
			knife = new CounterKnife(power);
		}

		power = new Power(1, 1.0);
		foreach(inout knife; _homingKnifes)
		{
			knife = new HomingKnife(power);
		}

		power = new Power(16, 1.0);
		foreach(inout knife; _shotKnifes)
		{
			knife = new ShotKnife(power);
		}

		power = new Power(1, 0.5);
		_chargeBeam = new ChargeBeam(power, new Power(2, 2.5));
	}


	override float groundPosition()
	{
		return -2.0;
	}


	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.CHARGEHIT:
				tex = _textures[TextureID.ATTACK];
				break;

			case State.HOMINGSHOT:
				if(t >= f2t(53) && t < f2t(89))
				{
					tex = _textures[TextureID.DAMAGE];
				}
				else
				{
					tex = _textures[TextureID.ATTACK];
				}
				break;

			case State.SPECIAL:
				tex = _textures[TextureID.SPECIAL];
				break;

			case State.SPELL:
				if(t >= f2t(10) && t < f2t(60))
				{
					tex = _textures[TextureID.SPELL];
				}
				else
				{
					tex = _textures[TextureID.ATTACK];
				}
				break;

			case State.HIT:
				if(i == Motion.HIT3)
				{
					tex = _textures[TextureID.DAMAGE];
				}
				else
				{
					tex = _textures[TextureID.ATTACK];
				}
				break;

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

		_body.draw(i, t, tex);
		_knife.draw(i, t, tex);
		switch(i)
		{
		case Motion.RUN1:
			_obon.draw(0, t);
			break;

		case Motion.SPECIAL:
			if(t < _meet.animationLength(0))
			{
				_meet.draw(0, t);
			}
			foreach(flash; _specialFlash)
			{
				if(t < flash.animationLength(0))
				{
					if(auto am = system.alphaMeshList().get())
					{
						am.set(flash, position.current, 0u, t, GGL_BlendMode.MIX);
					}
				}
			}
			break;

		case Motion.CHARGEHIT4:
			foreach(slash; _slashes)
			{
				if(t < slash.animationLength(0))
				{
					if(auto am = system.alphaMeshList().get())
					{
						am.set(slash, position.current, 0u, t, GGL_BlendMode.MIX, direction);
					}
				}
			}
			break;

		case Motion.SPELL:
			if(t < _world.animationLength(0))
			{
				if(auto am = system.alphaMeshList().get())
				{
					am.set(_world, position.current, 0u, t, GGL_BlendMode.MIX);
				}
			}
			break;

		default:
			break;
		}
	}


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

		_timer.get(i, t);

		if(_spellCount > 0)
		{
			--_spellCount;
			if(_spellCount == 60*5)
			{
				(cast(BulletSystem)parent.parent).resume();
			}
		}

		switch(i)
		{
		case Motion.STAND0:
			if(state != State.STAND)
			{
				changeState(State.STAND);
			}
			else if(_timer.isLooped)
			{
				_timer.set(Motion.STAND1);
			}
			break;

		case Motion.SINGLESHOT:
			if(t == f2t(6))
			{
				if(auto b = cast(Bullet)findFreeTask(_shotKnifes))
				{
					shot(b);
				}
			}
			else if(t < f2t(6))
			{
				turnToTarget(.std.math.PI/4.0);
			}
			break;

		case Motion.HOMINGSHOT0:
			switch(t)
			{
			case f2t(8):
			case f2t(21):
			case f2t(30):
				if(auto b = cast(Bullet)findFreeTask(_homingKnifes))
				{
					shot(b);
				}
				break;

			default:
				if(t >= f2t(34) && _command.isReceived(Command.SINGLESHOT))
				{
					_timer.set(Motion.HOMINGSHOT1, Motion.STAND0);
				}
				break;
			}
			break;

		case Motion.HOMINGSHOT1:
			switch(t)
			{
			case f2t(6):
			case f2t(12):
			case f2t(18):
			case f2t(24):
			case f2t(30):
			case f2t(36):
			case f2t(42):
			case f2t(48):
				if(auto b = cast(Bullet)findFreeTask(_homingKnifes))
				{
					shot(b);
				}
				break;

			default:
				break;
			}
			break;

		case Motion.CHARGESHOT:
			if(t == f2t(28))
			{
				if(!_chargeBeam.parent)
				{
					shot(_chargeBeam);
				}
			}
			else if(t < f2t(24))
			{
				turnToTarget(.std.math.PI/64.0);
			}

			if(t >= f2t(28) && _chargeBeam.parent)
			{
				Vector p;
				Vector v;

				_inputDevice.getLocalAxis(v);
				if(abs(v[0]) < 0.5)
				{
					v[0] = 0.0;
				}
				if(abs(v[1]) < 0.5)
				{
					v[1] = 0.0;
				}
				if(v[0] != 0.0 || v[1] != 0.0)
				{
					direction.toVector(v[0], 0.0, v[1], v);
					.gedo.math.vector.normalize(v);
					direction.homing(v, PI/2048.0);
				}

				direction.toVector(0.0, 4.5, 0.5, p);
				.gedo.math.vector.add(p, position.current);

				direction.toVector(0.0, 16.0, 0.0, v);
				_chargeBeam.add(p, v);
			}
			break;

		case Motion.COUNTER0:
			if(t == f2t(9))
			{
				shotFist(_fists[FistID.COUNTER0]);
			}
			else if(t >= f2t(17) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.COUNTER1, Motion.STAND0);
			}
			else if(_fists[FistID.COUNTER0].parent)
			{
				follow();
			}
			else
			{
				brake();
			}
			break;

		case Motion.COUNTER1:
			if(t < f2t(8))
			{
				follow();
			}
			else if(t == f2t(8))
			{
				velocity[] = 0.0;
				shotFist(_fists[FistID.COUNTER1]);
			}
			else if(t >= f2t(14) && _fists[FistID.COUNTER1].isCollided && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.COUNTER2, Motion.STAND0);
			}
			break;

		case Motion.COUNTER2:
			if(t == f2t(5))
			{
				Source().play(_sounds[SoundID.COUNTER2]);
				foreach(ik, knife; _counterKnifes)
				{
					if(!knife.parent)
					{
						Vector p = target.position.current[];

						auto angle = (2.0 * PI / _counterKnifes.length) * ik;
						p[0] += 16.0 * cos(angle);
						p[1] += 16.0 * sin(angle);

						shot(knife, p, direction);
					}
				}
			}
			brake();
			break;

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

		case Motion.HIT1:
			if(t == f2t(6) || t == f2t(13) || t == f2t(19) || t == f2t(25) || t == f2t(31))
			{
				shotFist(_fists[FistID.HIT1]);
			}
			else if(t >= f2t(36) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT2, Motion.STAND0);
			}
			else if(t < f2t(30))
			{
				follow();
			}
			else if(t < f2t(48))
			{
				direction.toVector(0.0, _accelerations[Movement.FOLLOW][1]*(f2t(48) - t)/(f2t(48) - f2t(30)), 0.0, velocity);
			}
			else
			{
				brake();
			}
			break;

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

		case Motion.HIT3:
			if(t >= f2t(8) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT4, Motion.STAND0);
			}
			break;

		case Motion.HIT4:
			if(t == f2t(9) || t == f2t(17))
			{
				velocity[] = 0.0;
				shotFist(_fists[FistID.HIT4]);
			}
			else if(t >= f2t(21) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT5, Motion.STAND0);
			}
			else if(t < f2t(9))
			{
				follow();
			}
			break;

		case Motion.HIT5:
			if(t == f2t(5) || t == f2t(13) || t == f2t(21))
			{
				shotFist(_fists[FistID.HIT5A]);
			}
			else if(t == f2t(29))
			{
				shotFist(_fists[FistID.HIT5B]);
			}
			if(t == f2t(1) || t == f2t(9) || t == f2t(17) || t == f2t(25))
			{
				follow();
			}
			else
			{
				brake();
			}
			break;

		case Motion.CHARGEHIT0:
			if(t == f2t(4))
			{
				shotFist(_fists[FistID.CHARGEHIT0], [-5.0f, 0.0f, 0.0f]);
			}
			if(_fists[FistID.CHARGEHIT0].parent && !_fists[FistID.CHARGEHIT0].isCollided)
			{
				through(5.0, -1.0);
			}
			break;

		case Motion.CHARGEHIT1:
			if(t == f2t(3))
			{
				shotFist(_fists[FistID.CHARGEHIT1]);
			}
			else if(t < f2t(3))
			{
				through(5.0, -1.0);
			}
			else if(t >= f2t(7) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.CHARGEHIT2);
				_counts[Count.ACTION] = 120;
			}
			break;

		case Motion.CHARGEHIT2:
			if(_counts[Count.ACTION] == 0)
			{
				_timer.set(Motion.CHARGEHIT3, Motion.STAND0);
			}
			else if(_command.isReceived(Command.HIT))
			{
				_counts[Count.ACTION] = 60;
				_timer.set(Motion.ATTACKRUN1);
				turnToTarget();
			}
			else
			{
				if((t&7) == 0)
				{
					shotFist(_fists[FistID.CHARGEHIT2], [-5.0f, 0.0f, 0.0f]);
				}
				through(5.0, -1.0);
			}
			break;

		case Motion.CHARGEHIT3:
			brake();
			break;

		case Motion.ATTACKRUN1:
			if(state == State.CHARGEHIT)
			{
				if(isHitRange || _counts[Count.ACTION] == 0)
				{
					_timer.set(Motion.CHARGEHIT4, Motion.STAND0);
				}
				else
				{
					follow();
				}
			}
			break;

		case Motion.CHARGEHIT4:
			if(t < f2t(4))
			{
				follow();
			}
			else if(t >= f2t(4) && t < f2t(19))
			{
				direction.toVector(0.0, 0.25, 0.5, velocity);
				if(t&1)
				{
					shotFist(_fists[FistID.CHARGEHIT4A]);
				}
			}
			else if(t == f2t(19))
			{
				shotFist(_fists[FistID.CHARGEHIT4B]);
			}
			break;

		case Motion.SPECIAL:
			if(t == f2t(86))
			{
				_life.add(_life.max>>(2+_specialCount));
				++_specialCount;

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

		case Motion.SPELL:
			brake();
			if(t == _world.animationLength(0) && _spellCount == 0)
			{
				Source().play(_sounds[SoundID.SPELL]);
				(cast(BulletSystem)parent.parent).pause(this);
				_spellCount = 60 * 10;
			}
			else
			{
				switch(t)
				{
				case f2t(5):
				case f2t(25):
				case f2t(45):
					Source().play(_sounds[SoundID.COUNTDOWN]);
					break;

				default:
					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;
			_specialCount = 0;
			break;

		case State.STAND:
			motion[0] = Motion.STAND0;
			motion[1] = Motion.STAND1;
			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.STAND0;
			break;

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

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

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

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

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

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

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

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

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

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

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

		case State.DOWN:
			motion[0] = Motion.DOWN;
			motion[1] = Motion.STAND0;
			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.HIT4:
		case Motion.HIT5:
		case Motion.COUNTER0:
		case Motion.CHARGEHIT0:
		case Motion.CHARGEHIT1:
		case Motion.CHARGEHIT2:
		case Motion.CHARGEHIT3:
		case Motion.CHARGEHIT4:
			direction.getRight(d);
			return;

		case Motion.SPECIAL:
		case Motion.SPELL:
			direction.toVector(0.0, -1.0, 0.0, d);
			return;

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


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


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

	Bullet[FistID.max+1] _fists;
	CounterKnife[8]      _counterKnifes;
	HomingKnife[16]      _homingKnifes;
	ShotKnife[2]         _shotKnifes;
	ChargeBeam           _chargeBeam;
	uint                 _specialCount;
	uint                 _spellCount;


	void startHit3(VisualTask t, Vector p)
	{
		_timer.set(Motion.HIT3, Motion.STAND0);
	}


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



class CounterKnife : 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[];
		}
		homing(PI);
	}


	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)
			{
				if(auto effectTask = (cast(Character)parent).system.getEffectTask())
				{
					effectTask.position.current[] = position.current[];

					effectTask.start((cast(Character)parent).system.sharedEffect(SharedEffectID.FLASH4));
				}
			}
			if(!isCollided)
			{
				homing(.std.math.PI/64.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);

		_knifethrowing.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 (cast(Character)parent).system.sharedSound(SharedSoundID.CUT);
	}


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


private:
	static const LIFE_TIME = 128;

	Power _power;
	PolyLine _polyLine;
}



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

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


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


	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((cast(Character)parent).system.sharedSound(SharedSoundID.SWINGL));
				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, 4.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);

		_knifethrowing.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 (cast(Character)parent).system.sharedSound(SharedSoundID.CUT);
	}


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


private:
	static const LIFE_TIME = 128;

	Power _power;
	PolyLine _polyLine;
}



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

		_polyLine = new PolyLine(16);
		_polyLine.count  = 16;
		_polyLine.radius = 1.0;
	}


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


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


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

					effectTask.start((cast(Character)parent).system.sharedEffect(SharedEffectID.FLASH4));
				}
			}
			if(!isCollided)
			{
				switch(_time & 15)
				{
				case 0:
					homing(.std.math.PI);
					{
						Vector v;
						direction.toVector(1.0, 0.0, 0.0, v);

						direction.homing(v, PI/64.0, true);
					}
					break;

				case 8:
					homing(.std.math.PI);
					{
						Vector v;
						direction.toVector(-1.0, 0.0, 0.0, v);

						direction.homing(v, PI/64.0, true);
					}
					break;

				default:
					break;
				}
			}

			Vector v;

			direction.getFront(v);

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

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

			_polyLine.record(position.current);
		}
	}


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

		_knifethrowing.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 (cast(Character)parent).system.sharedSound(SharedSoundID.CUT);
	}


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


private:
	static const LIFE_TIME = 256;

	Power _power;
	PolyLine _polyLine;
}



private class ChargeBeam : MultijointBeam
{
public:
	this(Power p0, Power p1)
	{
		_power[0] = p0;
		_power[1] = p1;

		super(16);
	}


	override void onUpdate()
	{
		if(_time >= LIFE_TIME)
		{
			stop();
		}
		else if((cast(Character)parent).getBalance() != 0)
		{
			stop();
		}
		else
		{
			super.onUpdate();
			if((_time & 7) == 0 || _time == LIFE_TIME-1)
			{
				clearDones();
			}

			polyLine.radius = 2.0 + (_time&3)/2.0;
		}
	}


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


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


	override Power power()
	{
		if(_time == LIFE_TIME - 1)
		{
			return _power[1];
		}
		else
		{
			return _power[0];
		}
	}


	override Sound hitSE()
	{
		return (cast(Character)parent).system.sharedSound(SharedSoundID.HITH);
	}


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


private:
	const int LIFE_TIME = 180;

	Power[2] _power;


	void drawAlpha()
	{
		if(polyLine.count >= 2)
		{
			GGL_setBlendMode(GGL_BlendMode.MIX);

			_textures[TextureID.CHARGESHOT].draw(polyLine.vertices);

			GGL_setBlendMode(GGL_BlendMode.ADD);
			translate(polyLine.points[0]);
			GGL_enterBillBoardMode();
			auto k = polyLine.radius * 2.0;
			GGL_scale(k, k, k);
			_chargeShotRoot.draw();
		}
	}
}
}



public class MultijointBeam : Bullet
{
public:
	this(uint c)
	{
		_polyLine = new PolyLine(c);

		_joints.length = c;

		_pointer = 0;
	}


	PolyLine polyLine()
	{
		return _polyLine;
	}


	void add(Vector p, Vector v)
	{
		_joints[_pointer][] = v[];

		_pointer = (_pointer + 1) & (_polyLine.maxCount-1);

		if(_polyLine.count < _polyLine.maxCount)
		{
			_polyLine.count += 1;
		}
		for(auto i=_polyLine.count-1; i>0; --i)
		{
			_polyLine.points[i][] = _polyLine.points[i-1][];
		}
		_polyLine.points[0][] = p[];
	}


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


	override Vector[] points()
	{
		if(_polyLine.count >= 2)
		{
			return _polyLine.points;
		}
		else
		{
			return super.points();
		}
	}


	override void onStart()
	{
		_polyLine.count = 0;
		_pointer        = 0;
	}


	override void onUpdate()
	{
		foreach(i, inout point; _polyLine.points)
		{
			position.previous[] = point[];
			Vector* joint = &_joints[(_pointer - i - 1)&(_polyLine.maxCount - 1)];
			point[0] += (*joint)[0];
			point[1] += (*joint)[1];
			point[2] += (*joint)[2];

			Vector f;
			if((cast(Character)parent).system.field.isSinked(position.previous, point, f))
			{
				point[]    = f[];
				(*joint)[] = 0.0;

				_polyLine.count = i + 1;
				break;
			}
		}
	}


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


private:
	PolyLine _polyLine;
	Vector[] _joints;
	uint     _pointer;
}
