module vs.character.reimu;



private import std.math;
private import gedo.audio.sound;
private import gedo.video.ggl;
private import gedo.video.color;
private import gedo.video.texture;
private import gedo.video.polyline;
private import gedo.video.staticmesh;
private import gedo.video.skinnedmesh;
private import gedo.video.alphameshlist;
private import gedo.input.joystick;
private import gedo.system.task;
private import vs.action.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/reimu/body.gsa
	CHARGEHIT,
	CHARGESHOT,
	COUNTER,
	DASH0,
	DASH1,
	DASH2,
	DOWN,
	FALL0,
	FALL1,
	GUARD0,
	GUARD1,
	GUARD2,
	HIT0,
	HIT1,
	HIT2,
	HOMINGSHOT,
	JUMP,
	KNOCKBACK,
	KO,
	LAND,
	RISE,
	RUN0,
	RUN1,
	RUN2,
	SINGLESHOT,
	SPECIAL0,
	SPECIAL1,
	SPECIAL2,
	SPELL,
	STAND,
	//aiw end
	INVALID
}



private enum TextureID
{
	DEFAULT,
	ATTACK,
	DAMAGE,
	FUDA,
	NEEDLE,
	HOMINGBULLET0,
	HOMINGBULLET1,
	TAIL0,
	TAIL1,
}



public class ReimuFactory : CharacterFactory
{
public:
	this()
	{
		_textures[TextureID.DEFAULT] = Texture("$LIB/reimu/reimu_t.png");
		_textures[TextureID.ATTACK ] = Texture("$LIB/reimu/reimu_a.png");
		_textures[TextureID.DAMAGE ] = Texture("$LIB/reimu/reimu_d.png");
		_textures[TextureID.FUDA   ] = Texture("$LIB/reimu/fuda.png");
		_textures[TextureID.NEEDLE ] = Texture("$LIB/reimu/needle.png");
		_textures[TextureID.HOMINGBULLET0] = Texture("$LIB/reimu/homingbullet0.png");
		_textures[TextureID.HOMINGBULLET1] = Texture("$LIB/reimu/homingbullet1.png");
		_textures[TextureID.TAIL0] = Texture("$LIB/reimu/tail0.png");
		_textures[TextureID.TAIL1] = Texture("$LIB/reimu/tail1.png");

		_body = SkinnedMesh("$LIB/reimu/body.gsa");
		_spellMesh = SkinnedMesh("$LIB/reimu/spell.gsa");

		_boomerangMesh = StaticMesh("$LIB/reimu/boomerang.gsa");

		EffectProgram[2] programs;

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

		programs[0] = new Flash5(8.0, GGL_BlendMode.MIX, new Texture(pixels, 4, 1));
		programs[1] = new Ring(8.0, GGL_BlendMode.ADD, rgba(32, 255, 32, 255));

		_flashHoming = new CompositeEffect(programs);

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

		programs[0] = new Flash5(8.0, GGL_BlendMode.MIX, new Texture(pixels, 4, 1));
		programs[1] = new Ring(8.0, GGL_BlendMode.ADD, rgba(32, 32, 255, 255));

		_flashNeedle = new CompositeEffect(programs);
	}


	Character create(CharacterSystem sys)
	{
		return new Reimu(sys);
	}


private:
	Texture[TextureID.max+1] _textures;
	EffectProgram            _flashHoming;
	EffectProgram            _flashNeedle;
	SkinnedMesh              _body;
	SkinnedMesh              _spellMesh;
	StaticMesh               _boomerangMesh;



public class Reimu : Character
{
public:
	this(CharacterSystem sys)
	{
		super(_body, sys);

		{
			auto p = new Power(16, 1.5);

			foreach(inout b; _singleFudas)
			{
				b = new Fuda(p, _textures[TextureID.FUDA]);
			}

			p = new Power(6, 1.5);
			foreach(inout b; _chargeShots)
			{
				b = new Needle(p, _textures[TextureID.NEEDLE]);
			}

			auto effect = sys.sharedEffect(SharedEffectID.HITMARK);
			_fists[0] = new Fist(new Power( 8, 1.0), sys.sharedSound(SharedSoundID.HITM), effect, _timer);
			_fists[1] = new Fist(new Power( 8, 1.0), sys.sharedSound(SharedSoundID.HITM), effect, _timer);
			_fists[2] = new Fist(new Power(16, 2.5, cast(Vector)[0.0, 1.0, 1.0]), sys.sharedSound(SharedSoundID.HITH), effect, _timer);
			_fists[3] = new Fist(new Power( 8, 1.0), sys.sharedSound(SharedSoundID.HITM), effect, _timer);

			_chargeHit = new Fist(new Power(32, 2.5), sys.sharedSound(SharedSoundID.FINISH), effect, _timer, f2t(16));
			_spellHit = new Fist(new Power(64, 1.0, [0.0f, 0.5f, -1.0f]), sys.sharedSound(SharedSoundID.HITM), effect, _timer);

			p = new Power(6, 1.5);
			foreach(inout b; _homingShots[0])
			{
				b = new HomingBullet(p, _textures[TextureID.HOMINGBULLET0], _textures[TextureID.TAIL0]);
			}
			foreach(inout b; _homingShots[1])
			{
				b = new HomingBullet(p, _textures[TextureID.HOMINGBULLET1], _textures[TextureID.TAIL1]);
			}

			p = new Power(2, 0.5);
			foreach(inout b; _boomerangs)
			{
				b = new Boomerang(p);
			}

			_spell = new Spell(_spellMesh);

			_spell.getHoldPosition = &getHoldPosition;
		}
	}


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

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

		_body.draw(i, t, tex);
	}


	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(8))
			{
				if(auto b = cast(Bullet)findFreeTask(_singleFudas))
				{
					shot(b);
					Source().play(_system.sharedSound(SharedSoundID.SWINGL));
					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = b.position.current[];

						effectTask.start(_system.sharedEffect(SharedEffectID.FLASH4));
					}
				}
			}
			else if(t < f2t(8))
			{
				turnToTarget(.std.math.PI/16.0);
			}
			break;

		case Motion.CHARGESHOT:
			switch(t)
			{
			case f2t(8):
			case f2t(16):
			case f2t(24):
			case f2t(32):
				if(auto b = cast(Bullet)findFreeTask(_chargeShots))
				{
					shot(b);
					Source().play(_system.sharedSound(SharedSoundID.SHOTBEAM));

					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = b.position.current[];

						effectTask.direction.copyFrom(b.direction);

						effectTask.start(_flashNeedle);
					}
				}
				break;

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

		case Motion.HOMINGSHOT:
			if(t == f2t(9))
			{
				Vector r;

				direction.getRight(r);

				if(auto b = cast(Bullet)findFreeTask(_homingShots[0]))
				{
					shot(b, [4.0f, 0.0f, 2.75f]);

					b.direction.homing(r, PI, true);

					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = b.position.current[];

						effectTask.direction.copyFrom(b.direction);

						effectTask.start(_flashHoming);
					}
				}
				if(auto b = cast(Bullet)findFreeTask(_homingShots[1]))
				{
					shot(b, [-4.0f, 0.0f, 2.75f]);

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

					b.direction.homing(r, PI, true);

					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = b.position.current[];

						effectTask.direction.copyFrom(b.direction);

						effectTask.start(_flashHoming);
					}
				}
				Source().play(_system.sharedSound(SharedSoundID.SHOTBEAM));
			}
			break;

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

		case Motion.HIT2:
			if(t < f2t(7))
			{
				follow();
			}
			else if(t == f2t(7))
			{
				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(12) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT0, Motion.STAND);
			}
			break;

		case Motion.CHARGEHIT:
			if(t == f2t(6))
			{
				turnToTarget();
				if(auto b = cast(Bullet)findFreeTask(_boomerangs))
				{
					Source().play(_system.sharedSound(SharedSoundID.SWINGL));
					shot(b);
					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = b.position.current[];

						effectTask.start(_system.sharedEffect(SharedEffectID.FLASH4));
					}
				}
			}
			else if(t == f2t(27))
			{
				shotFist(_chargeHit);
			}
			else if(_chargeHit.parent && !_chargeHit.isCollided())
			{
				turnToTarget();

				direction.getFront(velocity);
				.gedo.math.vector.multiply(velocity, 2.0);
			}
			else
			{
				brake();
			}
			break;

		case Motion.SPECIAL1:
			if(_counts[Count.ACTION] == 0)
			{
				changeState(State.RECOVER);
			}
			else
			{
				brake();
				turnToTarget(.std.math.PI / 32.0);
			}
			break;

		case Motion.SPELL:
			turnToTarget();
			turnToHorizon();

			if(t < f2t(37))
			{
				velocity[] = 0.0;
			}

			switch(t)
			{
			case f2t(17):
				if(!_spell.parent)
				{
					shot(_spell, position.current, direction);
				}
				break;

			case f2t(37):
				velocity[2] = 2.0;
				break;

			case f2t(49):
				velocity[2] = 0.0;
				break;

			case f2t(53):
				if(!_spellHit.parent)
				{
					shotFist(_spellHit);
				}
				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;
			break;

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

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

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

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

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

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

			case Motion.SPECIAL1:
				motion[0] = Motion.SPECIAL2;
				break;
			}
			motion[1] = motion[0];
			break;

		case State.RUN:
			motion[0] = Motion.RUN0;
			motion[1] = 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.SPECIAL:
			_counts[Count.ACTION] = 60;
			motion[0] = Motion.SPECIAL0;
			motion[1] = Motion.SPECIAL1;
			Source().play(_system.sharedSound(SharedSoundID.SWINGL));
			break;

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

		case State.FOLLOW:
			motion[0] = Motion.DASH0;
			motion[1] = 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;
			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 onCollide(Bullet b, Character a, Vector c)
	{
		if(_timer.index == Motion.SPECIAL1 && b.type == BulletType.SHOT)
		{
			Vector bf;
			Vector f;

			b.direction.getFront(bf);

			direction.getFront(f);

			if(.gedo.math.vector.dot(f, bf) < 0.0)
			{
				b.stop();

				shot(b);

				_counts[Count.SAFETY] = 60;
				_counts[Count.ACTION] = 0;
				return;
			}
		}

		super.onCollide(b, a, c);
	}


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

		case Motion.SPELL:
			{
				Vector r;

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

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


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


private:
	static float[2][Movement.max+1] _accelerations;
	HomingBullet[4][2]              _homingShots;
	Boomerang[2]                    _boomerangs;
	Needle[16]                      _chargeShots;
	Fuda[4]                         _singleFudas;
	Fist[4]                         _fists;
	Spell                           _spell;
	Fist                            _chargeHit;
	Fist                            _spellHit;


	bool getHoldPosition(Vector p)
	{
		if(_timer.index == Motion.SPELL && _timer.time <= f2t(52))
		{
			direction.getFront(p);
			.gedo.math.vector.multiply(p, 4.0);
			.gedo.math.vector.add(p, position.current);

			return true;
		}

		return false;
	}
}



private class Fuda : Bullet
{
public:
	this(Power p, Texture t)
	{
		_texture = t;

		_power = p;
	}


	override void onUpdate()
	{
		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);

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


	override void draw()
	{
		GGL_pushMatrix();

		auto plane = StaticMesh.plane();

		translate(position.current);

		GGL_enterBillBoardMode();

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

		GGL_scale(2, 4, 1);

		plane.draw(_texture);

		GGL_popMatrix();
	}


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


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


	override Power power()
	{
		return _power;
	}


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


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


private:
	Texture _texture;
	Power   _power;
}



private class Needle : Bullet
{
public:
	this(Power p, Texture t)
	{
		_power    = p;
		_texture  = t;
		_polyLine = new PolyLine(2);

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


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


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


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


	override void onUpdate()
	{
		if(_time >= 128)
		{
			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 > 32.0)
			{
				.gedo.math.vector.multiply(d, 32.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, _texture, GGL_BlendMode.ADD);
		}
	}


	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 (cast(Character)parent).system.sharedEffect(SharedEffectID.HITMARK);
	}


private:
	PolyLine _polyLine;
	Texture  _texture;
	Power    _power;
}



private class HomingBullet : Bullet
{
public:
	this(Power p, Texture h, Texture t)
	{
		_head = h;
		_tail = t;

		_power = p;

		_polyLine = new PolyLine(12);

		_polyLine.count  = 12;
		_polyLine.radius = 2.0;
	}


	override void onStart()
	{
		foreach(inout point; _polyLine.points)
		{
			point[] = 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((cast(Character)parent).getBalance() > 0)
		{
			_time = LIFE_TIME;
		}
		else
		{
			auto t = _time&31;
			if(t == 31)
			{
				homing(.std.math.PI/4.0);
			}
			else
			{
				homing(.std.math.PI/128.0);
			}

			Vector v;

			direction.getFront(v);

			.gedo.math.vector.multiply(v, 4.0*(1.0 - t/32.0));

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

			foreach(i, inout p; _polyLine.points[0 .. $-1])
			{
				p[] = _polyLine.points[i+1][];
			}
			_polyLine.points[$-1][] = position.current[];
		}
	}


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


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


	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 (cast(Character)parent).system.sharedEffect(SharedEffectID.HITMARK);
	}


private:
	int LIFE_TIME = 256;

	PolyLine _polyLine;
	Texture  _head;
	Texture  _tail;
	Power    _power;


	void drawAlpha()
	{
		auto l = 4.0 + 1.0*(_time&3);

		GGL_setBlendMode(GGL_BlendMode.ADD);

		_polyLine.radius = l * 0.375;

		_tail.draw(_polyLine.vertices);

		translate(position.current);

		GGL_enterBillBoardMode();

		GGL_setBlendMode(GGL_BlendMode.ADD);

		_head.draw(l, l);
	}
}



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


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

		if((_time & 3) == 0)
		{
			clearDones();
		}

		if(_time < 24)
		{
			Vector v;

			direction.getFront(v);

			.gedo.math.vector.multiply(v, (24.0 - _time)*0.25);

			.gedo.math.vector.add(position.current, v);
		}
		else if(_time < 48)
		{
			if(_time == 24)
			{
				Vector v;

				direction.getFront(v);

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

				direction.homing(v, .std.math.PI);
			}

			.gedo.math.vector.lerp(position.current, (cast(Character)parent).position.current, (_time-24.0)*0.25);
		}
		else
		{
			stop();
		}
	}


	override void draw()
	{
		GGL_pushMatrix();
		scope(exit)
		{
			GGL_popMatrix();
		}

		direction.transform(position.current);

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

		_boomerangMesh.draw();
	}


	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:
	Power   _power;
}



private class Spell : Bullet
{
public:
	this(SkinnedMesh m)
	{
		_timer = new Timer(m);

		_power = new Power(6, 0.0);
	}


	override void onUpdate()
	{
		_timer.update();

		position.previous[] = position.current[];
		position.previous[2] += 42.0;

		if(_time > 48)
		{
			stop();
		}
		else if((_time & 7) == 0)
		{
			clearDones();
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(_timer.mesh, position.current, _timer.index, _timer.time, GGL_BlendMode.MIX, direction);
		}
	}


	override float radius()
	{
		return 10.0;
	}


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


	override Power power()
	{
		return _power;
	}


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


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


private:
	Power _power;
	Timer _timer;
}
}
