module vs.character.frandle;



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.trajectory;
private import gedo.video.staticmesh;
private import gedo.video.skinnedmesh;
private import gedo.video.alphameshlist;
private import gedo.video.alphameshlist;
private import gedo.input.joystick;
private import gedo.system.task;
private import vs.action.task;
private import vs.effect.program;
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/frandle/body.gsa
	ATTACKRUN0,
	ATTACKRUN1,
	ATTACKRUN2,
	CHARGEHIT,
	CHARGEHIT2,
	CHARGESHOT,
	COUNTER,
	DASH0,
	DASH1,
	DASH2,
	DOWN,
	FALL0,
	FALL1,
	GUARD0,
	GUARD1,
	GUARD2,
	HIT0,
	HIT1,
	HIT2,
	HOMINGSHOT,
	JUMP,
	KNOCKBACK,
	KO,
	LAND,
	RISE,
	RUN0,
	RUN1,
	RUN2,
	SINGLESHOT,
	SPECIAL,
	SPELL,
	STAND,
	//aiw end
	INVALID
}



private enum TextureID
{
	DEFAULT,
	ATTACK,
	DAMAGE,
	BEAM,
	BEAM_HEAD,
	BEAM_BIT,
	BOOST_BIT,
	TRAJECTORY,
	PARTICLE,
}



private enum TrajectoryID
{
	//tiw begin ../../../lib/vs/frandle/fran.trj
	TRAJECTORY_HIT2_9_13,
	TRAJECTORY_HIT1_4_7,
	TRAJECTORY_COUNTER_5_9,
	TRAJECTORY_HIT0_3_9,
	//tiw end
}



private enum SoundID
{
	BURST,
	DISCONNECT,
}



public class FrandleFactory : CharacterFactory
{
public:
	this()
	{
		_textures[TextureID.DEFAULT] = Texture("$LIB/frandle/fran_t.png");
		_textures[TextureID.ATTACK ] = Texture("$LIB/frandle/fran_a.png");
		_textures[TextureID.DAMAGE ] = Texture("$LIB/frandle/fran_d.png");
		_textures[TextureID.BEAM   ] = Texture("$LIB/frandle/beam.png");
		_textures[TextureID.BEAM_HEAD] = Texture("$LIB/frandle/beamhead.png");
		_textures[TextureID.BEAM_BIT] = Texture("$LIB/frandle/beambit.png");
		_textures[TextureID.BOOST_BIT] = Texture("$LIB/frandle/boostbit.png");
		_textures[TextureID.PARTICLE] = Texture("$LIB/frandle/particle.png");

		Color[16] pixels;
		auto k = 255.0 / (pixels.length - 1);
		foreach(i, inout c; pixels)
		{
			c = rgba(255, 0, 0, cast(ubyte)(i*k));
		}

		_textures[TextureID.TRAJECTORY] = new Texture(pixels, pixels.length, 1);

		_trajectories = loadTrajectories("$LIB/frandle/fran.trj");

		_body = SkinnedMesh("$LIB/frandle/body.gsa");
		_ringRoughPurple = new SkinnedMeshEffect(SkinnedMesh("$LIB/frandle/ringroughpurple.gsa"));

		_featherMesh[0][0] = SkinnedMesh("$LIB/frandle/feather0r.gsa");
		_featherMesh[0][1] = SkinnedMesh("$LIB/frandle/feather0l.gsa");
		_featherMesh[1][0] = SkinnedMesh("$LIB/frandle/feather1r.gsa");
		_featherMesh[1][1] = SkinnedMesh("$LIB/frandle/feather1l.gsa");
		_featherMesh[2][0] = SkinnedMesh("$LIB/frandle/feather2r.gsa");
		_featherMesh[2][1] = SkinnedMesh("$LIB/frandle/feather2l.gsa");
		_featherMesh[3][0] = SkinnedMesh("$LIB/frandle/feather3r.gsa");
		_featherMesh[3][1] = SkinnedMesh("$LIB/frandle/feather3l.gsa");

		_burstTextures[0] = Texture("$LIB/frandle/beamburst0.png");
		_burstTextures[1] = Texture("$LIB/frandle/beamburst1.png");
		_burstTextures[2] = Texture("$LIB/frandle/beamburst2.png");
		_burstTextures[3] = Texture("$LIB/frandle/beamburst3.png");

		_sounds[SoundID.DISCONNECT] = Sound("$LIB/frandle/disconnect.ogg");
		_sounds[SoundID.BURST] = Sound("$LIB/frandle/burst.ogg");

		EffectProgram[3] programs;
		programs[0] = new Flash5();
		programs[1] = new Ring(8.0, GGL_BlendMode.ADD, rgba(255, 64, 16, 255));

		_flashMain = new CompositeEffect(programs[0..2]);

		pixels[0] = WHITE;
		pixels[1] = rgba(255, 128, 128, 255);
		pixels[2] = rgba(255, 32, 32, 255);
		pixels[3] = setA(pixels[2], 0);

		programs[0] = new Flash5(32.0, GGL_BlendMode.ADD, new Texture(pixels[0..4], 4, 1));
		programs[1] = new Ring(16.0, GGL_BlendMode.ADD, rgba(255, 64, 16, 255));
		_flashMegaBeam = new CompositeEffect(programs[0..2]);

		programs[0] = new Explosion(8.0);
		programs[1] = new Ring(16.0, GGL_BlendMode.ADD, rgba(255, 64, 16, 255));

		pixels[0] = WHITE;
		pixels[1] = rgba(255, 255, 64, 255);
		pixels[2] = rgba(255, 192, 32, 255);
		pixels[3] = setA(pixels[2], 0);

		programs[2] = new Flash5(8.0, GGL_BlendMode.ADD, new Texture(pixels[0..4], 4, 1));

		_explosion = new CompositeEffect(programs);
	}


	Character create(CharacterSystem sys)
	{
		return new Frandle(this, sys);
	}


private:
	Texture[TextureID.max+1] _textures;
	Sound[SoundID.max+1]     _sounds;
	SkinnedMeshEffect        _ringRoughPurple;
	SkinnedMesh[2][4]        _featherMesh;
	EffectProgram            _flashMain;
	EffectProgram            _flashMegaBeam;
	EffectProgram            _explosion;
	Trajectory[]             _trajectories;
	SkinnedMesh              _body;
	Texture[4]               _burstTextures;


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

		_accelerations[Movement.RUN][1]     *= 0.75;
		_accelerations[Movement.FALL][1]    *= 0.75;
		_accelerations[Movement.STEP][0]    *= 1.125;
		_accelerations[Movement.STEP][1]    *= 1.125;
		_accelerations[Movement.FOLLOW][0]  *= 1.125;
		_accelerations[Movement.FOLLOW][1]  *= 1.125;
	}


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

		foreach(i, pair; f._featherMesh)
		{
			foreach(j, feather; pair)
			{
				_feather[i][j] = new Feather(feather, this);
			}
		}

		{
			auto power = new Power(20, 1.0);

			foreach(inout b; _singleBeams)
			{
				b = new Beam(power, _textures[TextureID.BEAM], _textures[TextureID.BEAM_HEAD]);
			}

			power = new Power(2, 0.5);
			foreach(inout b; _bitBeams)
			{
				b = new Beam(power, _textures[TextureID.BEAM_BIT], null);
			}

			_chargeHit2 = new MegaBeam(new Power(24, 2.5));

			auto sound = sys.sharedSound(SharedSoundID.CUT);
			auto effect = sys.sharedEffect(SharedEffectID.HITMARK);

			_fists[0] = new Fist(new Power( 9, 1.0                    ), sound, effect, _timer);
			_fists[1] = new Fist(new Power( 9, 1.8, [0.0f, 1.0f, 1.0f]), sound, effect, _timer);
			_fists[2] = new Fist(new Power(14, 2.5, [0.0f, 0.2f,-1.0f]), sound, effect, _timer);
			_fists[3] = new Fist(new Power( 9, 1.0                    ), sound, effect, _timer);

			_chargeHit = new Fist(new Power(8, 1.0), sound, effect, _timer, f2t(16));
			_chargeHit.onCollideOwner = &explode;
		}

		foreach(inout particle; _particles)
		{
			particle = new Particle(_textures[TextureID.PARTICLE], system);
		}

		{
			auto power0 = new Power(4, 0.5);
			auto power1 = new Power(4, 2.5);
			foreach(inout burst; _fullBursts)
			{
				burst = new FullBurst(this, power0, power1, f._burstTextures);
			}
		}

		{
			auto power0 = new Power(3, 0.5);
			auto power1 = new Power(6, 2.5);
			foreach(inout burst; _gigaBursts)
			{
				burst = new FullBurst(this, power0, power1, f._burstTextures);
			}
		}

		foreach(inout clone; _clones)
		{
			clone.position[] = 0.0;
			clone.direction  = new Direction();
		}

		_hitExplosion = new ChargeHit(new Power(2, 2.5));
	}


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

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

		_body.draw(i, t, tex);

		Trajectory trj = null;
		switch(i)
		{
		case Motion.HIT0:
			trj = _trajectories[TrajectoryID.TRAJECTORY_HIT0_3_9];
			break;

		case Motion.HIT1:
			trj = _trajectories[TrajectoryID.TRAJECTORY_HIT1_4_7];
			break;

		case Motion.HIT2:
			trj = _trajectories[TrajectoryID.TRAJECTORY_HIT2_9_13];
			break;

		case Motion.COUNTER:
			trj = _trajectories[TrajectoryID.TRAJECTORY_COUNTER_5_9];
			break;

		default:
			break;
		}
		if(trj)
		{
			auto wt = _textures[TextureID.TRAJECTORY].width;
			auto ht = _textures[TextureID.TRAJECTORY].height;

			auto verts = trj.getVertices(t, _trjVertices, wt, ht);

			if(verts.length >= 4)
			{
				if(auto am = system.alphaMeshList().get())
				{
					am.set(verts, _textures[TextureID.TRAJECTORY], position.current, direction, GGL_BlendMode.MIX);
				}
			}
		}

		// draw connected feathers
		foreach(df; _feather)
		{
			foreach(f; df)
			{
				f.draw(i, t);
			}
		}
	}


	override void draw()
	{
		super.draw();

		// draw disconnected feathers
		foreach(df; _feather)
		{
			foreach(f; df)
			{
				f.draw();
			}
		}

		if(_timer.index == Motion.SPELL)
		{
			if(_clones[0].position != position.current)
			{
				foreach(clone; _clones)
				{
					GGL_pushMatrix();

					clone.direction.transform(clone.position);

					_timer.draw();

					GGL_popMatrix();
				}
			}
		}
	}


	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.DASH0:
		case Motion.DASH1:
			if(auto particle = cast(Particle)findFreeTask(_particles))
			{
				Vector b;

				direction.getFront(b);

				.gedo.math.vector.invert(b);

				Vector r;

				direction.getRight(r);

				.gedo.math.vector.multiply(r, .std.math.sin(position.current[0] + position.current[1] + t));

				.gedo.math.vector.add(b, r);

				Vector u;

				direction.getUp(u);

				.gedo.math.vector.multiply(u, .std.math.sin(.gedo.math.vector.square(position.current) + t));

				.gedo.math.vector.add(b, u);

				Vector p;

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

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

				particle.start(p, b);

				parent.parent.add(particle);
			}
			break;

		case Motion.SINGLESHOT:
			if(t <= f2t(6))
			{
				turnToTarget(.std.math.PI/8.0);

				if(t == f2t(6))
				{
					if(auto b = cast(Bullet)findFreeTask(_singleBeams))
					{
						Source().play(_system.sharedSound(SharedSoundID.SHOTBEAM));
						shot(b);
						if(auto effectTask = _system.getEffectTask())
						{
							effectTask.position.current[] = b.position.current[];

							effectTask.direction.copyFrom(b.direction);

							effectTask.start(_flashMain);
						}
					}
				}
			}
			break;

		case Motion.CHARGESHOT:
			switch(t)
			{
			case f2t(1):
				setBalance(0);
				foreach(j, df; _feather)
				{
					df[0].disconnect(2*j+0);
					df[1].disconnect(2*j+1);
				}

				if(auto task = system.getEffectTask())
				{
					task.position.current[] = position.current[];

					task.start(_ringRoughPurple);
					Source().play(_sounds[SoundID.DISCONNECT]);
				}
				break;

			case f2t(21):
				if(auto b = cast(Bullet)findFreeTask(_fullBursts))
				{
					Source().play(_sounds[SoundID.BURST]);
					shot(b);
				}
				break;

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

		case Motion.HOMINGSHOT:
			if(t == f2t(13))
			{
				Vector back;

				direction.getFront(back);

				.gedo.math.vector.invert(back);

				Vector right;

				direction.getRight(right);

				setBalance(0);
				foreach(j, df; _feather)
				{
					float angle = j * .std.math.PI / 4.0 + .std.math.PI / 8.0;

					float c = cos(angle);
					float s = sin(angle);

					Vector v;
					.gedo.math.vector.blend(v, c, back, s, right);

					df[0].disconnect(v);

					Vector r = right[];
					.gedo.math.vector.multiply(r, -2.0 * s);

					.gedo.math.vector.add(v, r);

					df[1].disconnect(v);
				}

				if(auto task = system.getEffectTask())
				{
					task.position.current[] = position.current[];

					task.start(_ringRoughPurple);
					Source().play(_sounds[SoundID.DISCONNECT]);
				}
			}
			break;

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

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

		case Motion.HIT2:
			if(t < f2t(13))
			{
				follow();
			}
			else if(t == f2t(13))
			{
				velocity[] = 0.0;
				shotFist(_fists[2]);
			}
			break;

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

		case Motion.CHARGEHIT:
			if(t < f2t(9))
			{
				direction.getUp(velocity);
				.gedo.math.vector.multiply(velocity, 2.0);
			}
			else if(t < f2t(13))
			{
				velocity[] = 0.0;
			}
			else if(t == f2t(13))
			{
				shotFist(_chargeHit);
			}
			else if(t < f2t(29))
			{
				turnToTarget();

				direction.getFront(velocity);
				.gedo.math.vector.multiply(velocity, 3.0);
			}
			else
			{
				velocity[] = 0.0;
			}
			break;

		case Motion.CHARGEHIT2:
			if(t == f2t(2))
			{
				turnToHorizon();
			}
			else if(t >= f2t(5) && t < f2t(20))
			{
				velocity[0] = velocity[1] = 0.0;

				auto k = (t - f2t(5))*1.0/f2t(15);

				velocity[2] = 3.0*(1.0 - k);
			}
			else if(t >= f2t(20))
			{
				turnToTarget(.std.math.PI/16.0);

				brake();
			}

			if(t == f2t(30))
			{
				if(!_chargeHit2.parent)
				{
					shot(_chargeHit2);
					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = _chargeHit2.position.current[];

						effectTask.direction.copyFrom(_chargeHit2.direction);

						effectTask.start(_flashMegaBeam);
					}
					direction.toVector(0.0, -2.0, 0.0, velocity);
				}
			}
			break;

		case Motion.SPECIAL:
			brake();
			switch(t)
			{
			case f2t(1):
				setBalance(0);
				foreach(int j, df; _feather)
				{
					df[0].disconnect(_target, j);
					df[1].disconnect(cast(Character)_target.next, -j);
				}
				break;

			default:
				if(_timer.isLooped)
				{
					changeState(State.STAND);
				}
				break;
			}
			break;

		case Motion.SPELL:
			brake();
			switch(t)
			{
			case f2t(17):
				setBalance(0);
				foreach(j, df; _feather)
				{
					df[0].disconnect(2*j+0);
					df[1].disconnect(2*j+1);
				}

				if(auto task = system.getEffectTask())
				{
					task.position.current[] = position.current[];

					task.start(_ringRoughPurple);

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

			case f2t(37):
				if(auto b = cast(Bullet)findFreeTask(_gigaBursts))
				{
					Source().play(_sounds[SoundID.BURST]);
					shot(b);
				}
				foreach(clone; _clones)
				{
					if(auto b = cast(Bullet)findFreeTask(_gigaBursts))
					{
						Vector p = clone.position[];

						Vector f;
						clone.direction.getFront(f);

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

						.gedo.math.vector.add(p, f);

						shot(b, p, clone.direction);
					}
				}
				break;

			default:
				if(t < 16)
				{
					double[3] c;
					double[3] s;

					auto k = 0.5;

					c[0] = .std.math.cos(0.0)*k*t;
					s[0] = .std.math.sin(0.0)*k*t;
					c[1] = .std.math.cos(.std.math.PI*2.0/3.0)*k*t;
					s[1] = .std.math.sin(.std.math.PI*2.0/3.0)*k*t;
					c[2] = .std.math.cos(.std.math.PI*2.0*2.0/3.0)*k*t;
					s[2] = .std.math.sin(.std.math.PI*2.0*2.0/3.0)*k*t;

					Vector uo;
					direction.getUp(uo);

					Vector ro;
					direction.getRight(ro);

					foreach(j, inout clone; _clones)
					{
						Vector u;

						.gedo.math.vector.multiply(u, uo, c[j]);

						Vector r;

						.gedo.math.vector.multiply(r, ro, s[j]);

						clone.position[] = position.current[];
						.gedo.math.vector.add(clone.position, u);
						.gedo.math.vector.add(clone.position, r);
					}
				}
				else if(t >= f2t(70))
				{
					foreach(inout clone; _clones)
					{
						.gedo.math.vector.lerp(clone.position, position.current, 0.5);
					}
				}

				if(t < f2t(2))
				{
					turnToTarget();

					foreach(inout clone; _clones)
					{
						clone.direction.copyFrom(direction);
					}
				}
				else if(t < f2t(20))
				{
					turnToTarget(.std.math.PI/512.0);

					foreach(inout clone; _clones)
					{
						Vector d;

						.gedo.math.vector.subtract(d, _target.position.current, clone.position);
						.gedo.math.vector.normalize(d);

						clone.direction.homing(d, .std.math.PI/512.0);
					}
				}

				if(_timer.isLooped)
				{
					changeState(State.STAND);
				}
				break;
			}
			break;

		default:
			break;
		}

		foreach(df; _feather)
		{
			foreach(f; df)
			{
				f.update();
			}
		}
	}


	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:
			case Motion.FALL0:
				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[0] = Motion.RUN1;
			break;

		case State.STEP:
			motion[0] = Motion.DASH0;
			motion[1] = 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.FOLLOW:
			motion[0] = Motion.ATTACKRUN0;
			motion[1] = Motion.ATTACKRUN1;
			break;

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

		case State.SPELL:
			motion[0] = Motion.SPELL;
			motion[1] = Motion.SPELL;
			foreach(inout clone; _clones)
			{
				clone.position[] = position.current[];
				clone.direction.copyFrom(direction);
			}
			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)
	{
		void fromFront()
		{
			Vector r;

			direction.getRight(r);
			direction.getFront(d);
			.gedo.math.vector.invert(d);
			.gedo.math.vector.add(d, r);
			.gedo.math.vector.normalize(d);
		}

		switch(_timer.index)
		{
		case Motion.HIT0:
		case Motion.HIT1:
		case Motion.HIT2:
		case Motion.COUNTER:
			direction.getRight(d);
			return;

		case Motion.CHARGESHOT:
			if(_timer.time < f2t(25))
			{
				fromFront();
				return;
			}
			break;

		case Motion.SPELL:
			if(_timer.time < f2t(40))
			{
				fromFront();
				return;
			}
			break;

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


	void shotBitBeam(Vector p, Direction d, Character c)
	{
		if(auto b = cast(Bullet)findFreeTask(_bitBeams))
		{
			_system.sharedSound(SharedSoundID.SHOTBEAM);
			shot(b, p, d, c);
		}
	}


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


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

	//Trajectory[TrajectoryID.max+1]  _trajectories;
	//Texture[TextureID.max+1]        _textures;
	//Sound[SoundID.max+1]            _sounds;
	//SkinnedMeshEffect               _ringRoughPurple;
	GGL_Vertex[32]                  _trjVertices;
	Feather[2][4]                   _feather;
	//EffectProgram                   _flashMain;
	Particle[32]                    _particles;
	FullBurst[1]                    _fullBursts;
	FullBurst[4]                    _gigaBursts;
	//SkinnedMesh                     _mesh;
	ChargeHit                       _hitExplosion;
	MegaBeam                        _chargeHit2;
	Beam[8]                         _bitBeams;
	Beam[4]                         _singleBeams;
	Fist[4]                         _fists;
	Fist                            _chargeHit;

	struct Clone
	{
		Direction direction;
		Vector    position;
	}

	Clone[3] _clones;


	void explode(VisualTask collisionObject, Vector collisionPosition)
	{
		if(_hitExplosion.parent is null)
		{
			shot(_hitExplosion, collisionObject.position.current, &collisionObject.position);
		}

		_timer.set(Motion.CHARGEHIT2, Motion.STAND);
	}
}



private class Beam : Bullet
{
public:
	this(Power p, Texture t1, Texture t2)
	{
		_se          = Sound("$LIB/frandle/beamhit.ogg");
		_power       = p;
		_polyLine    = new PolyLine(2);
		_textures[0] = t1;
		_textures[1] = t2;

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


	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 >= 128)
		{
			.gedo.math.vector.lerp(_polyLine.points[0], _polyLine.points[1], 4.0);
			if(_polyLine.points[1] == _polyLine.points[0])
			{
				stop();
			}
		}
		else
		{
			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.points[length-1][] = position.current[];

			Vector d;

			.gedo.math.vector.subtract(d, _polyLine.points[0], _polyLine.points[1]);

			auto l = .gedo.math.vector.magnitude(d);
			if(l > 128.0)
			{
				.gedo.math.vector.multiply(d, 128.0/l);

				.gedo.math.vector.add(_polyLine.points[0], _polyLine.points[1], d);
			}

			if((cast(Character)parent).system.field.isSinked(position.current))
			{
				_time = 128;
			}
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(_polyLine.vertices, _textures[0], GGL_BlendMode.ADD);
		}
		if(_textures[1])
		{
			if(auto am = (cast(Character)parent).system.alphaMeshList().get())
			{
				auto l = _polyLine.radius * 2.25;
				am.set(_textures[1], l, l, position.current, GGL_BlendMode.ADD);
			}
		}
	}


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


	override Power power()
	{
		return _power;
	}


	override Sound hitSE()
	{
		return _se;
	}


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


private:
	Texture[2] _textures;
	PolyLine   _polyLine;
	Power      _power;
	Sound      _se;
}



private class MegaBeam : Bullet
{
public:
	this(Power p)
	{
		_power = p;
		_polyLine    = new PolyLine(2);

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


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


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


	override void onUpdate()
	{
		if(_time >= 128)
		{
			.gedo.math.vector.lerp(_polyLine.points[0], _polyLine.points[1], 4.0);
			if(_polyLine.points[1] == _polyLine.points[0])
			{
				stop();
			}
		}
		else
		{
			if(_time == 0)
			{
				Source().play((cast(Character)parent).system.sharedSound(SharedSoundID.SHOTM));
			}
			if(!isCollided())
			{
				homing(.std.math.PI / 64.0);
			}

			Vector v;

			direction.getFront(v);

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

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

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

			Vector d;

			.gedo.math.vector.subtract(d, _polyLine.points[0], _polyLine.points[1]);

			auto l = .gedo.math.vector.magnitude(d);
			if(l > 128.0)
			{
				.gedo.math.vector.multiply(d, 128.0/l);

				.gedo.math.vector.add(_polyLine.points[0], _polyLine.points[1], d);
			}

			if((cast(Character)parent).system.field.isSinked(position.current))
			{
				_time = 128;
			}
		}
	}


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

		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			auto l = _polyLine.radius * 2.5;
			am.set(_textures[TextureID.BEAM_HEAD], l, l, position.current, GGL_BlendMode.ADD);
		}
	}


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


	override Power power()
	{
		return _power;
	}


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


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


private:
	PolyLine _polyLine;
	Power    _power;
}



private class Feather
{
public:
	Vector position;


	this(SkinnedMesh m, Frandle c)
	{
		_master = c;
		phase   = 0;
		_mesh   = m;

		_direction = new Direction();

		_polyLine  = new PolyLine(8);

		_polyLine.count  = 8;
		_polyLine.radius = 0.75;
	}


	void draw(uint i, uint t)
	{
		if(_phase == 0)
		{
			_mesh.draw(i, t);
		}
	}


	void draw()
	{
		if(_phase)
		{
			GGL_pushMatrix();

			direction.transform(position);

			(cast(StaticMesh)_mesh).draw();

			GGL_popMatrix();

			if(auto am = _master.system.alphaMeshList().get())
			{
				am.set(_polyLine.vertices, _textures[TextureID.BOOST_BIT], GGL_BlendMode.ADD);
			}
		}
	}


	void disconnect(Vector d)
	{
		if(_phase == 0 && _master.target)
		{
			position[] = _master.position.current[];
			foreach(inout p; _polyLine.points)
			{
				p[] = position[];
			}

			direction.homing(d, .std.math.PI, true);

			.gedo.math.vector.multiply(_goal, d, 16.0);

			_target = _master.target;

			phase = 1;
		}
	}


	void disconnect(Character c, int j)
	{
		if(_phase == 0)
		{
			_target = c;

			Vector r;

			_master.direction.getRight(r);
			.gedo.math.vector.multiply(r, j*4.0);

			Vector d;
			.gedo.math.vector.subtract(d, c.position.current, _master.position.current);
			.gedo.math.vector.normalize(d);

			direction.homing(d, .std.math.PI, true);

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

			.gedo.math.vector.add(position, _master.position.current, r);
			.gedo.math.vector.add(_goal, position, d);

			foreach(inout p; _polyLine.points)
			{
				p[] = position[];
			}
			phase = 5;
		}
	}


	void disconnect(int j)
	{
		auto i = j>>1;

		auto angle = .std.math.PI * (2*i + 1)/8.0 - .std.math.PI_2;

		auto c = .std.math.cos(angle);
		auto s = .std.math.sin(angle);

		if(j & 1)
		{
			c = -c;
		}

		Vector r;
		_master.direction.getRight(r);
		.gedo.math.vector.multiply(r, c);

		Vector u;
		_master.direction.getUp(u);
		.gedo.math.vector.multiply(u, s);

		.gedo.math.vector.add(_goal, r, u);

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

		phase = 6;
	}


	void phase(int p)
	{
		_phase = p;
	}


	void update()
	{
		++_time;

		switch(_phase)
		{
		case 0:
			return;

		case 1:
			{
				Vector goal;
				.gedo.math.vector.add(goal, _master.position.current, _goal);

				.gedo.math.vector.lerp(position, goal, 1.0);

				if(position == goal)
				{
					++_phase;
				}
			}
			break;

		case 2:
			{
				Vector goal;
				.gedo.math.vector.add(goal, _target.position.current, _goal);
				.gedo.math.vector.lerp(position, goal, 3.0);
				if(_master.system.field.isSinked(position))
				{
					phase = 4;
					return;
				}

				if(position == goal)
				{
					++_phase;
					_time = 0;

					Vector d;
					.gedo.math.vector.subtract(d, _target.position.current, position);
					.gedo.math.vector.normalize(d);
				}
			}
			break;

		case 3:
			{
				Vector d;
				.gedo.math.vector.subtract(d, _target.position.current, position);

				auto l = .gedo.math.vector.magnitude(d);

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

				direction.homing(d, .std.math.PI, true);
				if(_time <= 128)
				{
					if(_time >= 64)
					{
						if(!_master.system.field.isSinked(position))
						{
							_master.shotBitBeam(position, direction, _target);
							_time = 128;
						}
					}

					Vector u;
					direction.getUp(u);

					Vector f;
					direction.getFront(f);

					.gedo.math.vector.multiply(u, l*.std.math.sin(.std.math.PI/12.0));
					.gedo.math.vector.multiply(f, l*(1.0 - .std.math.cos(.std.math.PI/12.0)));

					.gedo.math.vector.add(position, f);
					.gedo.math.vector.add(position, u);
				}
				else if(_time >= 160)
				{
					phase = 4;
				}
			}
			break;

		case 4:
			_goal[] = _master.position.current[];

			.gedo.math.vector.lerp(position, _goal, 3.0);

			if(position == _goal)
			{
				phase = 0;
				return;
			}
			break;

		case 5:
			if(_master.timer.index == Motion.SPECIAL)
			{
				.gedo.math.vector.lerp(position, _goal, 1.0);

				Vector d;
				.gedo.math.vector.subtract(d, _target.position.current, position);
				.gedo.math.vector.normalize(d);

				direction.homing(d, .std.math.PI, true);

				switch(_master.timer.time)
				{
				case f2t(13):
					_master.shotBitBeam(position, direction, _target);
					break;

				case f2t(21):
					phase = 4;
					break;

				default:
					break;
				}
			}
			else
			{
				phase = 4;
			}
			break;

		case 6:
			{
				Vector goal;

				.gedo.math.vector.multiply(goal, _goal, 8.0);

				Vector b;
				_master.direction.getFront(b);
				.gedo.math.vector.invert(b);

				.gedo.math.vector.add(goal, b);
				.gedo.math.vector.add(goal, _master.position.current);

				.gedo.math.vector.lerp(position, goal, 1.0);
				if(position == goal)
				{
					phase = 7;
					_time = 0;
				}
			}
			break;

		case 7:
			if(_time <= 8)
			{
				Vector f;
				_master.direction.getFront(f);

				.gedo.math.vector.add(position, f);
			}
			else
			{
				phase = 8;
			}
			break;

		case 8:
			{
				Vector goal;

				.gedo.math.vector.multiply(goal, _goal, 4.0);

				Vector f;
				_master.direction.getFront(f);
				.gedo.math.vector.multiply(f, 2.0);

				.gedo.math.vector.add(goal, f);
				.gedo.math.vector.add(goal, _master.position.current);

				.gedo.math.vector.lerp(position, goal, 2.0);

				if(_master.timer.index != Motion.CHARGESHOT)
				{
					phase = 4;
				}
			}
			break;
		}

		if(_master.getBalance() != 0)
		{
			phase = 4;
		}

		foreach(i, inout p; _polyLine.points[0 .. $-1])
		{
			p[] = _polyLine.points[i+1][];
		}
		_polyLine.points[$-1][] = position[];
		if(_time & 4)
		{
			_polyLine.radius = 0.25;
		}
		else
		{
			_polyLine.radius = 2.0;
		}
	}


	Direction direction()
	{
		return _direction;
	}


	Character target()
	{
		return _target;
	}


	void connect()
	{
		phase = 4;
	}


private:
	SkinnedMesh _mesh;
	Direction   _direction;
	Character   _target;
	PolyLine    _polyLine;
	Frandle     _master;
	Vector      _goal;
	uint        _phase;
	uint        _time;
}



private class Particle : ActionTask
{
public:
	this(Texture t, CharacterSystem sys)
	{
		_texture = t;
		_system  = sys;
	}


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

		.gedo.math.vector.multiply(_velocity, v, 1.0 / .gedo.math.vector.magnitude(v));

		_time = 0;
	}


	override void update()
	{
		//.gedo.math.vector.lerp(_velocity, [0.0f, 0.0f, 0.0f], 1.0/32.0);

		.gedo.math.vector.add(_position, _velocity);

		++_time;
		if(_time >= 32)
		{
			stop();
		}
	}


	override void draw()
	{
		if(auto am = _system.alphaMeshList().get())
		{
			am.set(_texture, 1.5, 1.5, _position, GGL_BlendMode.ADD);
		}
	}


private:
	CharacterSystem _system;
	Texture _texture;
	Vector  _position;
	Vector  _velocity;
	int     _time;
}



public class SkinnedMeshEffect : EffectProgram
{
public:
	this(SkinnedMesh mesh)
	{
		_mesh = mesh;
	}


	void update(EffectTask machine)
	{
		if(machine.time >= _mesh.animationLength(0))
		{
			machine.stop();
		}
	}


	void draw(EffectTask machine)
	{
		if(auto am = machine.list().get())
		{
			am.set(_mesh, machine.position.current, 0, machine.time, GGL_BlendMode.ADD);
		}
	}


	void drawAlpha(EffectTask machine)
	{
	}


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


private:
	SkinnedMesh _mesh;
}



private class FullBurst : Bullet
{
public:
	this(Frandle master, Power p0, Power p1, Texture[] t)
	{
		_se         = Sound("$LIB/frandle/beamhit.ogg");
		_power0     = p0;
		_power1     = p1;
		_master     = master;
		_textures[] = t[];

		foreach(inout polyLine; _polyLines)
		{
			polyLine = new PolyLine(2);
			polyLine.count  = 2;
			polyLine.radius = 0.75;
		}
	}


	override float radius()
	{
		return 4.0;
	}


	override void onStart()
	{
		_tail[] = position.current[];

		Vector f;
		direction.getFront(f);
		.gedo.math.vector.multiply(f, 256.0);

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

		if((cast(Character)parent).system.field.isSinked(position.previous, position.current, f))
		{
			position.current[] = f[];
		}

		for(auto i=0; i<4; ++i)
		{
			auto angle = .std.math.PI * (2*i + 1)/8.0 - .std.math.PI_2;

			auto c = .std.math.cos(angle) * 4.0;
			auto s = .std.math.sin(angle) * 4.0;

			Vector u;

			direction.getUp(u);
			.gedo.math.vector.multiply(u, s);

			Vector r;

			direction.getRight(r);
			.gedo.math.vector.multiply(r, c);

			Vector g;
			.gedo.math.vector.add(g, r, u);

			auto polyLine = _polyLines[2*i+0];

			polyLine.points[0][] = position.previous[];
			polyLine.points[1][] = position.current[];

			.gedo.math.vector.add(polyLine.points[0], position.previous, g);
			.gedo.math.vector.add(polyLine.points[1], position.current, g);

			polyLine = _polyLines[2*i+1];

			polyLine.points[0][] = position.previous[];
			polyLine.points[1][] = position.current[];

			.gedo.math.vector.subtract(polyLine.points[0], position.previous, g);
			.gedo.math.vector.subtract(polyLine.points[1], position.current, g);
		}
	}


	override void onUpdate()
	{
		switch(_master.timer.index)
		{
		case Motion.CHARGESHOT:
		case Motion.SPELL:
			if(_time <= 64)
			{
				position.previous[] = _tail[];

				if(_time <= 48)
				{
					if((_time & 7) == 0)
					{
						clearDones();
					}
				}

				auto r = 0.5;
				if(_time & 2)
				{
					r = 1.0;
				}
				foreach(polyLine; _polyLines)
				{
					polyLine.radius = r;
				}

				return;
			}
			break;

		default:
			break;
		}

		stop();
	}


	override void draw()
	{
		foreach(i, polyLine; _polyLines)
		{
			if(auto am = (cast(Character)parent).system.alphaMeshList().get())
			{
				auto texture = _textures[i>>1];

				am.set(polyLine.vertices, texture, GGL_BlendMode.ADD);
			}
		}
	}


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


	override Power power()
	{
		if(_time > 40)
		{
			return _power1;
		}
		else
		{
			return _power0;
		}
	}


	override Sound hitSE()
	{
		return _se;
	}


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


private:
	PolyLine[8] _polyLines;
	Texture[4]  _textures;
	Frandle     _master;
	Vector      _tail;
	Power       _power0;
	Power       _power1;
	Sound       _se;
}



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


	override float radius()
	{
		return 1.0;
	}


	override void onStart()
	{
		fillDones();
	}


	override void onUpdate()
	{
		if(_time >= LIFE_TIME)
		{
			stop();
		}
		else
		{
			position.current[] = _target.current[];

			if(_time >= 16 && (_time & 7) == 7)
			{
				clearDones();
			}
		}
	}


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


	override Power power()
	{
		return _power;
	}


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


	override EffectProgram hitEffect()
	{
		return _explosion;
	}


private:
	const int LIFE_TIME = 80;

	Power _power;
}
}
