module vs.action.character;



private import std.math;
private import std.random;
private import gedo.audio.sound;
private import gedo.video.ggl;
private import gedo.video.rect;
private import gedo.video.color;
private import gedo.video.texture;
private import gedo.video.skinnedmesh;
private import gedo.video.alphameshlist;
private import gedo.math.collision;
private import gedo.math.direction;
private import gedo.input.joystick;
private import vs.action.inputdevice;
private import vs.action.visualtask;
private import vs.action.effect;
private import vs.action.button;
private import vs.action.bullet;
private import vs.action.field;



private const bool isTraining = false;



private enum KOSchedule
{
	KNOCKBACK = 56,
	FADEOUT   = 64,
	WAIT      = 120,
	TOTAL     = KNOCKBACK + FADEOUT + WAIT
}



private enum MeterIcon
{
	LIFE,
	FIST,
	SHOT,
	SPELL,
}



private class Command
{
public:
	enum
	{
		NONE,
		HIT,
		CHARGEHIT,
		SINGLESHOT,
		CHARGESHOT,
		SPECIAL,
		SPELL,
	}


	this()
	{
		_spell      = new Meter(64);
		_chargeHit  = new Meter(32);
		_chargeShot = new Meter(32);
	}


	bool isReceived(int id, uint mask = ~0)
	{
		if(_id == id && mask & (1<<id))
		{
			_id = NONE;

			return true;
		}

		return false;
	}


	void update(Button[] buttons)
	{
		int id = NONE;

		if(buttons[ActionButton.SPECIAL].isPushed(0))
		{
			id = SPECIAL;
		}
		else if(buttons[ActionButton.HIT].isPushed(0))
		{
			id = HIT;
		}
		else if(buttons[ActionButton.SHOT].isPushed(0))
		{
			id = SINGLESHOT;
		}

		if(buttons[ActionButton.SPECIAL].isPushed())
		{
			_spell.add(1);
		}
		else
		{
			if(_spell.isMax())
			{
				id = SPELL;
			}
			_spell.add(-1);
		}

		if(buttons[ActionButton.HIT].isPushed())
		{
			_chargeHit.add(1);
		}
		else
		{
			if(_chargeHit.isMax())
			{
				id = CHARGEHIT;
			}
			_chargeHit.add(-1);
		}

		if(buttons[ActionButton.SHOT].isPushed())
		{
			_chargeShot.add(1);
		}
		else
		{
			if(_chargeShot.isMax())
			{
				id = CHARGESHOT;
			}
			_chargeShot.add(-1);
		}

		if(id != NONE)
		{
			_id    = id;
			_count = 16;
		}
		else if(_count > 0)
		{
			--_count;
			if(_count == 0)
			{
				_id = NONE;
			}
		}
	}


	Meter spell()
	{
		return _spell;
	}


	Meter chargeShot()
	{
		return _chargeShot;
	}


	Meter chargeHit()
	{
		return _chargeHit;
	}


private:
	Meter _spell;
	Meter _chargeShot;
	Meter _chargeHit;
	int   _id;
	int   _count;
}



public enum SharedEffectID
{
	HITMARK,
	GUARDMARK,
	LANDMARK,
	DASHMARK,
	SETSPELLMARK,
	FLASH4,
}



public enum SharedSoundID
{
	FINISH,
	JUMP,
	HITL,
	HITM,
	HITH,
	CUT,
	SWINGL,
	SWINGH,
	SHOTBEAM,
	SHOTL,
	SHOTM,
}



public interface CharacterSystem
{
	AlphaMeshList alphaMeshList();
	EffectProgram sharedEffect(SharedEffectID id);
	StaticMesh    shadow();
	EffectTask    getEffectTask();
	Sound         sharedSound(SharedSoundID id);
	Field         field();
	bool          isReady();
}



public interface CharacterFactory
{
	Character create(CharacterSystem sys);
}



public class Character : VisualTask
{
public:
	public enum Movement
	{
		RUN,
		STEP,
		FOLLOW,
		JUMP,
		FALL,
	}



	public enum Count
	{
		PAUSE,
		ACTION,
		SAFETY,
	}



	public enum State
	{
		STAND,
		RECOVER,
		RUN,
		GUARD,
		STEP,
		JUMP,
		FALL,
		LAND,
		FOLLOW,
		HIT,
		CHARGEHIT,
		HOMINGSHOT,
		SINGLESHOT,
		CHARGESHOT,
		SPECIAL,
		SPELL,
		KNOCKBACK,
		DOWN,
		KO,
		RESSURECT,
	}



	private enum VoiceID
	{
		INVALID,
		GUARD,
		STEP,
		JUMP,
		LAND,
		FOLLOW,
		HIT,
		CHARGEHIT,
		HOMINGSHOT,
		SINGLESHOT,
		CHARGESHOT,
		SPECIAL,
		SPELL,
		KNOCKBACK,
		DOWN,
		KO,
		RESSURECT,
	}



	Vector velocity;


	this(SkinnedMesh mesh, CharacterSystem sys)
	{
		velocity[] = 0.0;

		_life  = new Meter(MAX_BALANCE*3);

		_command = new Command();

		_inputDevice = new AI(this);

		_timer = new Timer(mesh);

		_system = sys;

		_voiceSource = new Source();

		loadVoices();
	}


	static this()
	{
		_accelerations[Movement.RUN][0]     = 1.0 / 32.0;
		_accelerations[Movement.RUN][1]     = 0.5;
		_accelerations[Movement.FALL][0]    = 1.0 / 64.0;
		_accelerations[Movement.FALL][1]    = 0.5;
		_accelerations[Movement.JUMP][0]    = sqrt(2.0*-G*48.0);
		_accelerations[Movement.JUMP][1]    = _accelerations[Movement.JUMP][0];
		_accelerations[Movement.STEP][0]    = 0.75;
		_accelerations[Movement.STEP][1]    = 0.75;
		_accelerations[Movement.FOLLOW][0]  = 1.25;
		_accelerations[Movement.FOLLOW][1]  = 1.25;
	}


	static void getDefaultAccelerations(float[2][] as)
	{
		foreach(i, inout a; as)
		{
			a[] = _accelerations[i][];
		}
	}


	void inputDevice(ActionInputDevice dev)
	{
		_inputDevice = dev;
	}


	void cplevel(uint level)
	{
		_inputDevice.cplevel = level;
	}


	float groundPosition()
	{
		return -4.5;
	}


	float radius()
	{
		return 2.0;
	}


	bool isFlyingPosition()
	{
		return !_isLand;
	}


	uint index()
	{
		if(parent && parent.parent)
		{
			uint i = 0;
			foreach(p; parent.parent)
			{
				if(p !is parent)
				{
					i = 16;
				}
				break;
			}

			foreach(c; parent)
			{
				if(c is this)
				{
					break;
				}
				++i;
			}

			return i;
		}

		return 0;
	}


	void move(float x, float y)
	{
		position.current[0] = x;
		position.current[1] = y;
	}


	void move(Vector p)
	{
		bool land = false;

		if(getBalance() != 0)
		{
			foreach(i, a; p)
			{
				if((a - position.current[i])*velocity[i] < 0.0)
				{
					velocity[i] = -velocity[i];
				}
			}
			if(p[2] <= position.current[2])
			{
				_isLand = false;
			}
		}
		else if(p[2] > position.current[2] && velocity[2] < 0.0)
		{
			velocity[2] = 0.0;
			if(!_isLand)
			{
				switch(state)
				{
				case State.RESSURECT:
					_life.toMax();
				case State.FALL:
					_dashCount  = 2;
					_isLand     = true;
					land        = true;
					break;

				case State.SINGLESHOT:
					disableBarrier();
					break;

				default:
					onLand();
					break;
				}
			}
		}
		else
		{
			_isLand = false;
		}

		position.current[] = p[];
		if(land)
		{
			changeState(State.LAND);
		}
	}


	void move()
	{
		if(state == State.KNOCKBACK)
		{
			if(_getHoldPosition)
			{
				if(_getHoldPosition(position.current))
				{
					velocity[] = 0.0;

					_timer.rewind();

					return;
				}
			}
		}

		_getHoldPosition = null;

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


	void ressurect(float xi, float yi)
	{
		position.current[0] = xi;
		position.current[1] = yi;
		position.current[2] = 180.0;

		velocity[] = 0.0;

		position.previous[] = position.current[];

		changeState(State.RESSURECT);
	}


	Character target()
	{
		return _target;
	}


	Meter life()
	{
		return _life;
	}


	void getEyeBasic(Vector p, Vector v, out float lz)
	{
		p[] = position.current[];
		//lz  = -groundPosition();
		lz  = 4.0;

		getTargetDirection(v);
	}


	void getEye(Vector p, Vector v, out float lz)
	{
		p[] = position.current[];
		//lz  = -groundPosition();
		lz  = 4.0;

		getEyeVector(v);
	}


	void getEyeVector(Vector v)
	{
		getTargetDirection(v);
	}


	void getTargetDirection(Vector v)
	{
		if(_target)
		{
			.gedo.math.vector.subtract(v, _target.position.current, position.current);

			.gedo.math.vector.normalize(v);
		}
		else
		{
			direction.getFront(v);
		}
	}


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

		if((cast(BulletSystem)parent.parent).isRunning(this))
		{
			_inputDevice.update();

			auto buttons = _inputDevice.buttons;
			auto axis    = _inputDevice.axis;

			updateCounts();

			updateTarget(buttons);

			if(_system.isReady)
			{
				static Button[ActionButton.max+1] dummyButtons;
				if(!dummyButtons[0])
				{
					foreach(inout b; dummyButtons)
					{
						b = new Button();
					}
				}

				buttons = dummyButtons;

				axis.length      = 0.0;
				axis.direction[] = Direction.FRONT[];
			}

			_command.update(buttons);

			if(_counts[Count.PAUSE] == 0)
			{
				_timer.update();

				updateState(axis.length, axis.direction, buttons);

				onUpdate(axis.length, axis.direction, buttons);
			}
		}
	}


	override void draw()
	{
		if(state == State.KO && _counts[Count.ACTION] < KOSchedule.WAIT)
		{
			return;
		}

		Vector p = position.current[];

		if((_counts[Count.PAUSE] & 2) && (getBalance() != 0))
		{
			p[2] += _counts[Count.PAUSE] / 16.0;
		}

		GGL_pushMatrix();

		direction.transform(p);

		if(state == State.KO && _counts[Count.ACTION] < KOSchedule.WAIT + KOSchedule.FADEOUT)
		{
			auto k = (_counts[Count.ACTION] - KOSchedule.WAIT)/cast(float)KOSchedule.FADEOUT;

			GGL_scale(k, k, k);
		}

		onDraw();

		GGL_popMatrix();

		drawShadow();
	}


	void push(Character c)
	{
		if(!_isGhost && !c._isGhost)
		{
			antiPassing(position.current, radius, position.previous, c.position.current, c.radius, c.position.previous);
		}
	}


	void drawLife3D()
	{
		GGL_enableDepthBuffer(0);
		scope(exit)
		{
			GGL_enableDepthBuffer(1);
		}

		Vector p = position.current[];

		p[2] += -groundPosition;

		auto color = YELLOW;
		if(_life.rate <= 0.5)
		{
			color = RED;
		}

		_life.draw(p, cast(Vector2D)[2.0, 0.5], color, BLACK);
	}


	void drawLife2D()
	{
		if(!_meterIcons[0])
		{
			_meterIcons[MeterIcon.LIFE] = Texture("$LIB/action/lifeicon.png");
			_meterIcons[MeterIcon.FIST] = Texture("$LIB/action/fisticon.png");
			_meterIcons[MeterIcon.SHOT] = Texture("$LIB/action/shoticon.png");
			_meterIcons[MeterIcon.SPELL] = Texture("$LIB/action/spellicon.png");
		}

		const Vector2D SIZEL = [256.0f, 8.0f];
		const Vector2D SIZES = [128.0f, 8.0f];
		const Vector2D INTERVAL = [40.0f, 40.0f];

		Vector p = [-320.0f + SIZEL[0]*0.5f + INTERVAL[0], -240+INTERVAL[1]*0.5, 0.0];

		auto color = GREEN;
		if(_life.rate <= 0.5)
		{
			color = RED;
		}
		_life.draw(p, SIZEL, color, WHITE);
		GGL_pushMatrix();
		GGL_translate(-320.0 + 16.0 + 4.0, p[1], 0.0);
		_meterIcons[MeterIcon.LIFE].draw();
		GGL_popMatrix();

		p[0] = -320.0 + SIZES[0]*0.5 + INTERVAL[0];
		p[1] += INTERVAL[1];
		_command.chargeShot.draw(p, SIZES, BLUE, WHITE);
		GGL_pushMatrix();
		GGL_translate(-320.0 + 16.0 + 4.0, p[1], 0.0);
		_meterIcons[MeterIcon.SHOT].draw();
		GGL_popMatrix();

		p[1] += INTERVAL[1];
		_command.chargeHit.draw(p, SIZES, rgba(0, 255, 255, 255), WHITE);
		GGL_pushMatrix();
		GGL_translate(-320.0 + 16.0 + 4.0, p[1], 0.0);
		_meterIcons[MeterIcon.FIST].draw();
		GGL_popMatrix();

		if(color == RED)
		{
			p[1] += INTERVAL[1];
			_command.spell.draw(p, SIZES, rgba(255, 0, 255, 255), WHITE);
			GGL_pushMatrix();
			GGL_translate(-320.0 + 16.0 + 4.0, p[1], 0.0);
			_meterIcons[MeterIcon.SPELL].draw();
			GGL_popMatrix();
		}
	}


	void attack(Character c)
	{
		// collision between vs.bullet and character
		foreach(task; this)
		{
			auto b = cast(Bullet)task;

			if(!b.isCollided(c.index))
			{
				Vector center;

				if(isColliding(c.position.current, c.radius, b.points, b.radius, center))
				{
					b.collide(c.index);
					b.onCollide(center);

					c.onCollide(b, this, center);
				}
			}
		}
	}


	State state()
	{
		return _state;
	}


	bool hasNewBullets()
	{
		foreach(b; this)
		{
			if((cast(Bullet)b).time == 0)
			{
				return true;
			}
		}

		return false;
	}


	bool isPaused()
	{
		return (_counts[Count.PAUSE] != 0);
	}


	bool isSafety()
	{
		return (_counts[Count.SAFETY] != 0);
	}


	Timer timer()
	{
		return _timer;
	}


	uint getBalance()
	{
		return _balance;
	}


	CharacterSystem system()
	{
		return _system;
	}


	uint scoreAttack()
	{
		return _scoreAttack;
	}


	uint scoreDamage()
	{
		return _scoreDamage;
	}


	uint scoreCombo()
	{
		return _scoreCombo;
	}


	uint scoreTotal()
	{
		return _scoreTotal;
	}


	void scoreTotal(uint a)
	{
		_scoreTotal = a;
	}


	uint timeTotal()
	{
		return _timeTotal;
	}


	void timeTotal(uint a)
	{
		_timeTotal = a;
	}


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


protected:
	const float G = -9.8 * 9.0 / 1.5 / 60.0 / 60.0;

	uint[Count.max+1] _counts;
	ActionInputDevice _inputDevice;
	GetHoldPosition   _getHoldPosition;
	CharacterSystem   _system;
	Character         _target;
	Command           _command;
	Timer             _timer;
	Meter             _life;
	State             _state;
	uint              _dashCount;
	uint              _scoreAttack;
	uint              _scoreDamage;
	uint              _scoreCombo;
	uint              _scoreTotal;
	uint              _timeTotal;
	bool              _isLand;
	bool              _isBarrier;
	bool              _isGhost;
	int               _balance;


	void pause(uint t)
	{
		if(_counts[Count.PAUSE] < t)
		{
			_counts[Count.PAUSE] = t;
		}
	}


	bool isHitRange()
	{
		Vector d = target.velocity[];
		.gedo.math.vector.multiply(d, 8.0);
		.gedo.math.vector.add(d, target.position.current);
		.gedo.math.vector.subtract(d, position.current);

		auto l = square(d);
		if(l > 144.0)
		{
			return false;
		}

		return true;
	}


	void shot(Bullet bullet)
	{
		add(bullet);

		Vector ps;

		direction.getFront(ps);

		.gedo.math.vector.multiply(ps, 2.0);
		.gedo.math.vector.add(ps, position.current);

		bullet.start(ps, direction, &_target.position);
	}


	void shotFist(Bullet bullet)
	{
		add(bullet);

		bullet.start([0.0f, 5.0f, 0.0f], direction, &_target.position);
	}


	void shot(Bullet bullet, Vector p)
	{
		add(bullet);

		Vector a;

		direction.toVector(p[0], p[1], p[2], a);

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

		bullet.start(a, direction, &_target.position);
	}


	void shotFist(Bullet bullet, Vector p)
	{
		add(bullet);

		bullet.start(p, direction, &_target.position);
	}


	void shot(Bullet bullet, Vector p, Direction d)
	{
		add(bullet);

		bullet.start(p, d, &_target.position);
	}


	void shot(Bullet bullet, Vector p, Direction d, Character c)
	{
		add(bullet);

		bullet.start(p, d, &c.position);
	}


	void shot(Bullet bullet, Vector p, Position* t)
	{
		add(bullet);

		bullet.start(p, direction, t);
	}


	void turnToTarget(float speed = .std.math.PI, float range = -1.0)
	{
		Vector dn;

		getTargetDirection(dn);

		if(range > -1.0)
		{
			Vector f;

			direction.getFront(f);

			if(.gedo.math.vector.dot(dn, f) <= range)
			{
				return;
			}
		}

		direction.homing(dn, speed);
	}


	void turnToHorizon()
	{
		direction.restoreUp();
	}


	void onCollide(Bullet collidedBullet, Character bulletOwner, Vector collisionPoint)
	{
		bool isDefended = false;

		if(_counts[Count.SAFETY] != 0)
		{
			isDefended = true;
		}
		else
		{
			switch(state)
			{
			case State.GUARD:
				switch(collidedBullet.type)
				{
				case BulletType.HIT:
					isDefended = true;

					changeState(State.HIT);

					_counts[Count.SAFETY] = 60;
					pause(30);
					bulletOwner.pause(46);

					{
						Vector bf;

						collidedBullet.direction.getFront(bf);

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

						direction.homing(bf, .std.math.PI);
					}
					break;

				case BulletType.SPELL:
					isDefended = true;
					break;

				default:
					break;
				}
				break;

			case State.STEP:
				if(isBarrierEnable())
				{
					switch(collidedBullet.type)
					{
					case BulletType.SHOT:
					case BulletType.SPELL:
						{
							Vector bf;
							Vector f;

							collidedBullet.direction.getFront(bf);

							direction.getFront(f);

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

								isDefended = true;
							}
						}
						break;

					default:
						break;
					}
				}
				break;

			case State.FOLLOW:
				switch(collidedBullet.type)
				{
				case BulletType.SHOT:
					isDefended = true;
					_counts[Count.SAFETY] = 60;
					changeState(State.STAND);
					break;

				default:
					break;
				}
				break;

			default:
				break;
			}
		}

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

				effectTask.start(_system.sharedEffect(SharedEffectID.GUARDMARK));
			}
			return;
		}

		auto preRate = _life.rate();

		int damage = collidedBullet.power.damage;

		_life.add(-damage);

		_scoreDamage += damage;
		bulletOwner._scoreAttack += damage;

		if(collidedBullet.type == BulletType.HIT)
		{
			auto hitStop = damage;
			if(hitStop > 32)
			{
				hitStop = 32;
			}
			pause(hitStop);
			bulletOwner.pause(hitStop);
		}
		else if(collidedBullet.power.damage >= 32)
		{
			pause(32);
			collidedBullet.pause(32);
		}

		auto  balance = getBalance();

		float knockBackVelocity;

		if(_life.current > 0)
		{
			balance += damage;

			if(balance < MAX_BALANCE)
			{
				knockBackVelocity = collidedBullet.power.velocity;

				changeState(State.KNOCKBACK);
				if(knockBackVelocity >= 2.0)
				{
					_isGhost = true;
				}

				Source().play(collidedBullet.hitSE);
			}
			else
			{
				balance = MAX_BALANCE;

				knockBackVelocity = 1.5;

				changeState(State.DOWN);

				Source().play(_system.sharedSound(SharedSoundID.FINISH));

				bulletOwner._scoreCombo = 1;
			}

			setBalance(balance);
		}
		else
		{
			knockBackVelocity = 2.0;

			changeState(State.KO);

			_counts[Count.ACTION] = KOSchedule.TOTAL;

			Source().play(_system.sharedSound(SharedSoundID.FINISH));
		}

		if(_getHoldPosition is null)
		{
			Vector vb;

			collidedBullet.getKnockBackDirection(vb);

			if(collidedBullet.getHoldPosition && collidedBullet.getHoldPosition(position.current))
			{
				velocity[] = 0.0;
				_getHoldPosition = collidedBullet.getHoldPosition;
			}
			else
			{
				.gedo.math.vector.multiply(velocity, vb, knockBackVelocity);
			}

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

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

		if(collidedBullet.onCollideOwner)
		{
			collidedBullet.onCollideOwner(this, collisionPoint);
		}

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

			effectTask.direction.copyFrom(direction);

			effectTask.start(collidedBullet.hitEffect);
		}

		if(preRate > 0.5 && _life.rate <= 0.5)
		{
			if(auto effectTask = _system.getEffectTask())
			{
				effectTask.position.current[] = position.current[];

				effectTask.direction.copyFrom(direction);

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


	bool isBarrierEnable()
	{
		return _isBarrier;
	}


	void disableBarrier()
	{
		_isBarrier = false;
	}


	void setBalance(uint balance)
	{
		_balance = balance;
	}


	void step(Vector axis)
	{
		if(_dashCount > 0)
		{
			_counts[Count.ACTION] = 32;

			--_dashCount;

			direction.homing(axis, .std.math.PI);

			changeState(State.STEP);
		}
	}


	void jump()
	{
		auto v = acceleration(Movement.JUMP);

		_jumpVelocity[0] = velocity[0];
		_jumpVelocity[1] = velocity[1];
		_jumpVelocity[2] = v[0];

		changeState(State.JUMP);
	}


	bool isAttackCommandReceived(uint mask = ~0)
	{
		if(target)
		{
			if(_command.isReceived(Command.SPELL, mask) && (_life.rate <= 0.5 || isTraining))
			{
				changeState(State.SPELL);
			}
			else if(_command.isReceived(Command.SPECIAL, mask))
			{
				changeState(State.SPECIAL);
			}
			else if(_command.isReceived(Command.CHARGESHOT, mask))
			{
				changeState(State.CHARGESHOT);
			}
			else if(_command.isReceived(Command.SINGLESHOT, mask))
			{
				switch(state)
				{
				case State.GUARD:
					changeState(State.HOMINGSHOT);
					break;

				default:
					changeState(State.SINGLESHOT);
					break;
				}
			}
			else if(_command.isReceived(Command.CHARGEHIT, mask))
			{
				_counts[Count.ACTION] = 30;

				changeState(State.CHARGEHIT);
			}
			else if(_command.isReceived(Command.HIT, mask))
			{
				switch(state)
				{
				case State.GUARD:
					changeState(State.HIT);
					break;

				default:
					_counts[Count.ACTION] = 30;

					changeState(State.FOLLOW);
					break;
				}
			}
			else
			{
				return false;
			}

			turnToTarget();
			return true;
		}

		return false;
	}


	void brake()
	{
		.gedo.math.vector.lerp(velocity, [0.0, 0.0, 0.0], 1.0/16.0);
		if(!isFlyingPosition)
		{
			velocity[2] = G;
		}
	}


	void setVelocity(Movement m, uint dim=3)
	{
		Vector vn;

		auto a = acceleration(m);

		direction.getFront(vn);

		.gedo.math.vector.multiply(vn, a[1]);
		if(dim != 3)
		{
			vn[2] = velocity[2];
		}

		.gedo.math.vector.lerp(velocity, vn, a[0]);
	}


	void setVelocity(Movement m, Vector axis)
	{
		Vector vn = axis[];

		auto a = acceleration(m);

		.gedo.math.vector.multiply(vn, a[1]);

		vn[2] = velocity[2];

		.gedo.math.vector.lerp(velocity, vn, a[0]);
	}


	void changeState(State s)
	{
		onChangeState(state, s);

		_state     = s;
		_isGhost   = false;
		_isBarrier = true;

		switch(s)
		{
		case State.LAND:
			if(auto effectTask = _system.getEffectTask())
			{
				effectTask.position.current[] = position.current[];

				effectTask.position.current[2] += groundPosition + 0.1;

				effectTask.direction.homing(Direction.UP, PI);

				effectTask.start(_system.sharedEffect(SharedEffectID.LANDMARK));
			}
			break;

		case State.STEP:
			if(auto effectTask = _system.getEffectTask())
			{
				effectTask.position.current[] = position.current[];

				effectTask.direction.copyFrom(direction);

				effectTask.start(_system.sharedEffect(SharedEffectID.DASHMARK));
			}
			break;

		case State.STAND:
		case State.RESSURECT:
			setBalance(0);
			break;

		default:
			break;
		}
		playVoice(s);
	}


	void follow()
	{
		turnToTarget(PI, 0.0);

		setVelocity(Movement.FOLLOW);
	}


	void follow(float k)
	{
		turnToTarget(PI, 0.0);

		direction.toVector(0.0, acceleration(Movement.FOLLOW)[1]*k, 0.0, velocity);
	}


	bool through(float l, float x = 1.0)
	{
		auto result = false;

		if(target)
		{
			Vector a = target.position.current[];

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

			{
				Vector at = a[];

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

				direction.homing(at, PI, true);
			}
			Vector e;

			direction.getRight(e);
			if(x < 0.0)
			{
				.gedo.math.vector.invert(e);
			}

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

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

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

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

			result = (la < l*1.25);

			direction.homing(e, PI, true);
		}

		setVelocity(Movement.FOLLOW);

		return result;
	}


	void onLand()
	{
	}


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


	abstract void onDraw()
	{
	}


	abstract void onUpdate(float axisLength, Vector axisDirection, Button[] buttons)
	{
	}


	abstract void onChangeState(State from, State to)
	{
	}


private:
	static const uint MAX_BALANCE = 64;

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

	Texture[MeterIcon.max+1] _meterIcons;
	Sound[][VoiceID.max+1]   _voices;
	Source                   _voiceSource;
	Vector                   _jumpVelocity;
	Vector                   _shadowPosition;
	uint                     _voiceCount;


	void updateCounts()
	{
		foreach(inout c; _counts)
		{
			if(c > 0)
			{
				--c;
			}
		}
	}


	void updateTarget(Button[] buttons)
	{
		if(_target is null || buttons[ActionButton.SEARCH].isPushed(0))
		{
			foreach(t; parent.next)
			{
				if(t !is _target)
				{
					_target = cast(Character)t;
					break;
				}
			}
		}
	}


	void updateState(float axisLength, Vector axisDirection, Button[] buttons)
	{
		bool isFalling = true;

		switch(state)
		{
		case State.STAND:
			if(!isAttackCommandReceived())
			{
				if(isFlyingPosition())
				{
					changeState(State.FALL);
				}
				else if(axisLength > 0.0)
				{
					changeState(State.RUN);
				}
				else if(buttons[ActionButton.DEFEND].isPushed)
				{
					changeState(State.GUARD);
				}
				else
				{
					brake();
				}
				turnToHorizon();
			}
			break;

		case State.RUN:
			if(!isAttackCommandReceived())
			{
				if(isFlyingPosition())
				{
					changeState(State.FALL);
				}
				else if(axisLength == 0.0)
				{
					changeState(State.RECOVER);
				}
				else if(buttons[ActionButton.DEFEND].isPushed(16))
				{
					jump();
				}
				else
				{
					Vector f;

					direction.getFront(f);

					if(.gedo.math.vector.dot(axisDirection, f) >= SQRT1_2)
					{
						setVelocity(Movement.RUN, axisDirection);
					}
					else
					{
						brake();
					}
					direction.homing(axisDirection, .std.math.PI / 60.0);
				}
			}
			break;

		case State.FALL:
			if(!isAttackCommandReceived())
			{
				if(axisLength > 0.0)
				{
					if(buttons[ActionButton.DEFEND].isPushed(16))
					{
						step(axisDirection);
						break;
					}
				}
				else if(buttons[ActionButton.DEFEND].isPushed)
				{
					changeState(State.GUARD);
					break;
				}
				auto vy = velocity[2];

				if(vy < 0 && vy - G >= 0)
				{
					changeState(State.FALL);
				}

				if(axisLength > 0.0)
				{
					setVelocity(Movement.FALL, axisDirection);
				}

				turnToHorizon();
			}
			break;

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

		case State.LAND:
			if(!isAttackCommandReceived())
			{
				if(_timer.isLooped)
				{
					changeState(State.STAND);
				}
			}
			velocity[] = 0.0;
			break;

		case State.GUARD:
			if(_timer.isLooped && !buttons[ActionButton.DEFEND].isPushed)
			{
				changeState(State.RECOVER);
			}
			else if(!isAttackCommandReceived((1<<Command.HIT)|(1<<Command.SINGLESHOT)))
			{
				turnToTarget(.std.math.PI / 60.0);
				isFalling = false;
				brake();
			}
			break;

		case State.STEP:
			if(!isAttackCommandReceived())
			{
				isFalling = false;
				if(_timer.isLooped())
				{
					if(axisLength == 0.0 || _counts[Count.ACTION] == 0)
					{
						changeState(State.RECOVER);
						break;
					}
				}
				direction.homing(axisDirection, .std.math.PI / 120.0);
				setVelocity(Movement.STEP);
			}
			break;

		case State.JUMP:
			if(_timer.isLooped)
			{
				velocity[] = _jumpVelocity[];

				changeState(State.FALL);

				Source().play(_system.sharedSound(SharedSoundID.JUMP));
			}
			else
			{
				brake();
			}
			break;

		case State.FOLLOW:
			isFalling = false;
			if(_counts[Count.ACTION] == 0 || isHitRange() || isAttackCommandReceived(1<<Command.SINGLESHOT))
			{
				changeState(State.HIT);
			}
			else
			{
				follow();
			}
			break;

		case State.HIT:
			isFalling = false;
			if(_timer.isLooped)
			{
				changeState(State.STAND);
			}
			break;

		case State.CHARGEHIT:
			isFalling = false;
			break;

		case State.SINGLESHOT:
			if(_timer.isLooped)
			{
				changeState(State.STAND);
			}
			else if(!isBarrierEnable())
			{
				isFalling = false;
				brake();
			}
			break;

		case State.CHARGESHOT:
		case State.HOMINGSHOT:
			if(_timer.isLooped)
			{
				changeState(State.STAND);
			}
			else
			{
				isFalling = false;
				brake();
			}
			break;

		case State.SPECIAL:
		case State.SPELL:
			break;

		case State.KNOCKBACK:
			if(_timer.isLooped)
			{
				_counts[Count.SAFETY] = 60;
				_dashCount = 2;
				changeState(State.STAND);
			}
			else if(!_isGhost)
			{
				isFalling = false;
				brake();
			}
			break;

		case State.DOWN:
			if(_timer.isLooped)
			{
				_counts[Count.SAFETY] = 90;
				_dashCount = 2;
				changeState(State.STAND);
			}
			else
			{
				_counts[Count.SAFETY] = _timer.left + 1;
				if(_counts[Count.SAFETY] <= 4)
				{
					brake();
				}
			}
			break;

		case State.KO:
			isFalling = false;
			_counts[Count.SAFETY] = 60;
			if(_counts[Count.ACTION] == 0)
			{
				ressurect(0, 0);
			}
			else if(_counts[Count.ACTION] <= KOSchedule.WAIT + KOSchedule.FADEOUT)
			{
				velocity[] = 0.0;
			}
			break;

		case State.RESSURECT:
			_counts[Count.SAFETY] = 60;
			turnToTarget(.std.math.PI/16.0);
			isFalling = false;

			velocity[2] = -1.5;
			break;
		}

		if(isFalling || !isFlyingPosition())
		{
			velocity[2] += G;

			if(velocity[2] < G*60.0)
			{
				velocity[2] = G * 60.0;
			}
		}
	}


	void drawShadow()
	{
		Vector shadowPosition;

		shadowPosition[0] = position.current[0];
		shadowPosition[1] = position.current[1];
		if(isFlyingPosition)
		{
			shadowPosition[2] = system.field.height(position.current, radius) + 0.1;
		}
		else
		{
			shadowPosition[2] = position.current[2] + groundPosition() + 0.1;
		}

		GGL_pushMatrix();
		GGL_enableAlphaBlend(1);
		GGL_lockDepthBuffer(1);
		scope(exit)
		{
			GGL_popMatrix();
			GGL_enableAlphaBlend(0);
			GGL_lockDepthBuffer(0);
		}

		translate(shadowPosition);

		auto s = radius * 1.5;

		GGL_scale(s, s, 1.0);

		system.shadow().draw();
	}


	void playVoice(State s)
	{
		const VoiceID[State.max+1] TABLE = [
			State.STAND: VoiceID.INVALID,
			State.RECOVER: VoiceID.INVALID,
			State.RUN: VoiceID.INVALID,
			State.GUARD: VoiceID.GUARD,
			State.STEP: VoiceID.STEP,
			State.JUMP: VoiceID.JUMP,
			State.FALL: VoiceID.INVALID,
			State.LAND: VoiceID.LAND,
			State.FOLLOW: VoiceID.FOLLOW,
			State.HIT: VoiceID.HIT,
			State.CHARGEHIT: VoiceID.CHARGEHIT,
			State.HOMINGSHOT: VoiceID.HOMINGSHOT,
			State.SINGLESHOT: VoiceID.SINGLESHOT,
			State.CHARGESHOT: VoiceID.CHARGESHOT,
			State.SPECIAL: VoiceID.SPECIAL,
			State.SPELL: VoiceID.SPELL,
			State.KNOCKBACK: VoiceID.KNOCKBACK,
			State.DOWN: VoiceID.DOWN,
			State.KO: VoiceID.KO,
			State.RESSURECT: VoiceID.RESSURECT
		];

		playVoice(TABLE[s]);
	}


	void playVoice(VoiceID id)
	{
		auto a = _voices[id];
		if(a.length > 0)
		{
			auto voice = a[_voiceCount % $];
			++_voiceCount;

			_voiceSource.stop();
			_voiceSource.play(voice);
		}
	}


	void loadVoices()
	{
		string[VoiceID.max+1] baseNames;

		baseNames[VoiceID.INVALID] = "invalid";
		baseNames[VoiceID.GUARD] = "guard";
		baseNames[VoiceID.STEP] = "step";
		baseNames[VoiceID.JUMP] = "jump";
		baseNames[VoiceID.LAND] = "land";
		baseNames[VoiceID.FOLLOW] = "follow";
		baseNames[VoiceID.HIT] = "hit";
		baseNames[VoiceID.CHARGEHIT] = "chargehit";
		baseNames[VoiceID.HOMINGSHOT] = "homingshot";
		baseNames[VoiceID.SINGLESHOT] = "singleshot";
		baseNames[VoiceID.CHARGESHOT] = "chargeshot";
		baseNames[VoiceID.SPECIAL] = "special";
		baseNames[VoiceID.SPELL] = "spell";
		baseNames[VoiceID.KNOCKBACK] = "knockback";
		baseNames[VoiceID.DOWN] = "down";
		baseNames[VoiceID.KO] = "ko";
		baseNames[VoiceID.RESSURECT] = "ressurect";
		if(.gedo.file.path.exist(voiceDir()))
		{
			foreach(filename; .gedo.file.path.listdir(voiceDir()))
			{
				foreach(id, baseName; baseNames)
				{
					if(isVoiceName(filename, baseName))
					{
						_voices[id] ~= Sound(.gedo.file.path.join(voiceDir(), filename));
						break;
					}
				}
			}
			_voices[VoiceID.INVALID] = null;
		}
	}


	bool isVoiceName(string filename, string basename)
	{
		if(filename.length > basename.length)
		{
			foreach(i, c; basename)
			{
				if(filename[i] != c)
				{
					return false;
				}
			}

			auto c = filename[basename.length];
			if(c >= '0' && c <= '9')
			{
				return true;
			}
		}

		return false;
	}
}



public class Meter
{
public:
	this(int a = 128)
	{
		_current = _max = a;
	}


	void add(int a)
	{
		_current += a;
		if(_current > _max)
		{
			_current = _max;
		}
		else if(_current < 0)
		{
			_current = 0;
		}
	}


	int max()
	{
		return _max;
	}


	void max(int m)
	{
		_current = _max = m;
	}


	int current()
	{
		return _current;
	}


	float rate()
	{
		return cast(float)_current / cast(float)_max;
	}


	bool isMax()
	{
		return (_current == _max);
	}


	void toMax()
	{
		_current = _max;
	}


	void draw(Vector p, Vector2D s, Color ci, Color co)
	{
		GGL_pushMatrix();

		translate(p);

		GGL_enterBillBoardMode();

		Rect(0.0, 0.0, s[0], s[1]).frame(s[1]*0.25, co);

		auto r = rate();
		// r = 1.0, x = 0.0
		// r = 0.0, x = -s[0] * 0.5
		Rect(s[0]*0.5*(r - 1.0), 0.0, s[0]*r, s[1]).fill(ci);

		GGL_popMatrix();
	}


private:
	int _current;
	int _max;
}



private class AI : ActionInputDevice
{
private:
	struct Event
	{
		enum ID
		{
			NONE,
			SHOT,
			HIT,
			GUARD,
			NEAR,
		}

		ID  id;
		int time;
	}


	enum State
	{
		NORMAL,
		GUARD,
		WALK_F,
		WALK_R,
		WALK_B,
	}


public:
	this(Character owner)
	{
		_owner = owner;

		foreach(inout button; _buttons)
		{
			button = new Button();
		}
	}


	override void cplevel(uint level)
	{
		_level = level;
	}


	override void update()
	{
		static if(isTraining)
		{
			return;
		}
		bool[ActionButton.max+1] buffer;

		_axis.length      = 0.0;
		_axis.direction[] = Direction.FRONT[];

		if(auto target = _owner.target)
		{
			Vector f;
			Vector r;
			float  l;

			getPositions(l, f, r);

			updateEvent(l);

			if(_owner._command.chargeShot.isMax)
			{
				buffer[ActionButton.SHOT] = true;
			}

			if(_owner._command.chargeHit.isMax)
			{
				buffer[ActionButton.HIT] = true;
			}

			if(_owner._command.spell.isMax)
			{
				buffer[ActionButton.SPECIAL] = true;
			}

			if(_event.time == 0 && _event.id != Event.ID.NONE)
			{
				onEvent(l, f, r, buffer);
			}
			else if(_time == 0)
			{
				_time = 128;

				onTimer(l, f, r, buffer);
			}
			else
			{
				onIdle(l, f, r, buffer);
				--_time;
			}
		}

		foreach(i, b; buffer)
		{
			_buttons[i].update(b);
		}
	}


private:
	Character _owner;
	State     _state;
	Event     _event;
	float     _l;
	uint      _targetState;
	uint      _level;
	int       _time;


	static const float SHORT_RANGE = 1600.0;


	void getPositions(out float l, Vector f, Vector r)
	{
		Vector d;
		.gedo.math.vector.subtract(d, _owner.target.position.current, _owner.position.current);

		l = square(d);

		f[0] = d[0];
		f[1] = d[1];
		f[2] = 0.0;

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

		cross(r, Direction.UP, f);

		.gedo.math.vector.normalize(r);
	}


	void updateEvent(float l)
	{
		if(_event.time == 0)
		{
			_event.id = Event.ID.NONE;

			if(_l >= SHORT_RANGE && l < SHORT_RANGE)
			{
				_event.id = Event.ID.NEAR;
			}
			_l = l;

			checkTarget(_owner.target);

			if(_level < 2)
			{
				if(_owner.target.target is _owner && _owner.target.hasNewBullets())
				{
					_event.id = Event.ID.SHOT;
				}
			}
			else
			{
				foreach(task; _owner.parent.next)
				{
					auto target = cast(Character)task;
					if(target.target is _owner && target.hasNewBullets())
					{
						_event.id = Event.ID.SHOT;
					}
				}
			}

			if(_event.id != Event.ID.NONE)
			{
				_event.time = 40 - _level*10;
			}
		}
		else
		{
			--_event.time;
		}
	}


	void checkTarget(Character target)
	{
		if(target.state != _targetState)
		{
			switch(target.state)
			{
			case Character.State.FOLLOW:
				if(target.target is _owner)
				{
					_event.id = Event.ID.HIT;
				}
				break;

			case Character.State.GUARD:
				_event.id = Event.ID.GUARD;
				break;

			default:
				break;
			}

			_targetState = target.state;
		}
	}


	void onEvent(float l, Vector f, Vector r, bool[] buffer)
	{
		if(_level == 0)
		{
			return;
		}

		switch(_event.id)
		{
		case Event.ID.SHOT:
			if(_level >= 2 && l < SHORT_RANGE)
			{
				buffer[ActionButton.HIT] = true;
			}
			else if(_level >= 2 && l < 4.0 * SHORT_RANGE)
			{
				_axis.length = 1.0;
				_axis.direction[] = f[];

				buffer[ActionButton.DEFEND] = true;
			}
			else
			{
				_axis.length = 1.0;
				if(dot(_owner.position.current, r) > 0)
				{
					.gedo.math.vector.invert(_axis.direction, r);
				}
				else
				{
					_axis.direction[] = r[];
				}

				buffer[ActionButton.DEFEND] = true;
			}
			break;

		case Event.ID.HIT:
			if(random() <= _level*64)
			{
				buffer[ActionButton.DEFEND] = true;
				_state = State.GUARD;
			}
			break;

		case Event.ID.GUARD:
			if(random() <= _level*64)
			{
				buffer[ActionButton.SHOT] = true;
			}
			break;

		case Event.ID.NEAR:
			if(_level < 3)
			{
				buffer[ActionButton.HIT] = true;
			}
			else
			{
				auto rnd = random();
				if(rnd < 128)
				{
					buffer[ActionButton.HIT] = true;
				}
				else if(rnd < 224)
				{
					buffer[ActionButton.SHOT] = true;
				}
			}
			break;
		}
	}


	void onTimer(float l, Vector f, Vector r, bool[] buffer)
	{
		auto rnd = random();
		if(_level > 0)
		{
			Vector[3] positions;

			positions[0][] = _owner.position.current[];
			positions[1][] = _owner.target.position.current[];

			if(_owner._system.field.isSinked(positions[0], positions[1], positions[2]))
			{
				return;
			}

			if(l < SHORT_RANGE)
			{
				if(rnd < 192)
				{
					buffer[ActionButton.HIT] = !buffer[ActionButton.HIT];
				}
				else if(rnd < 224)
				{
					buffer[ActionButton.SHOT] = !buffer[ActionButton.SHOT];
				}
				else
				{
					buffer[ActionButton.SPECIAL] = !buffer[ActionButton.SPECIAL];
				}
			}
			else
			{
				if(rnd < 192)
				{
					buffer[ActionButton.SHOT] = !buffer[ActionButton.SHOT];
				}
				else if(rnd < 240)
				{
					buffer[ActionButton.DEFEND] = true;
				}
				else
				{
					buffer[ActionButton.SPECIAL] = !buffer[ActionButton.SPECIAL];
				}
			}
		}
		else
		{
			if(rnd < 64)
			{
				buffer[ActionButton.HIT] = true;
				_state = State.WALK_F;
			}
			else if(rnd < 192)
			{
				buffer[ActionButton.SHOT] = true;
				_state = State.WALK_R;
			}
			else if(rnd < 224)
			{
				buffer[ActionButton.DEFEND] = true;
				_state = State.WALK_B;
			}
			else
			{
				buffer[ActionButton.SPECIAL] = true;
				_state = State.NORMAL;
			}
		}
	}


	void onIdle(float l, Vector f, Vector r, bool[] buffer)
	{
		auto target = _owner.target;

		{
			auto anotherTarget = cast(Character)target.next;
			if(anotherTarget.target is _owner && anotherTarget.hasNewBullets)
			{
				buffer[ActionButton.SEARCH] = true;
			}
			else if(_level >= 3 && target._counts[Character.Count.SAFETY] != 0)
			{
				buffer[ActionButton.SEARCH] = true;
			}
		}

		if(_level > 0)
		{
			bool isFree = true;
			switch(_level)
			{
			case 1:
				isFree = false;
				break;

			case 2:
				if(target.target is _owner)
				{
					isFree = false;
				}
				break;

			default:
				foreach(task; _owner.target.parent)
				{
					target = cast(Character)task;
					if(target.target is _owner)
					{
						isFree = false;
						break;
					}
				}
				break;
			}
			if(isFree)
			{
				buffer[ActionButton.SHOT] = true;
				switch(_owner.state)
				{
				case Character.State.SINGLESHOT:
					buffer[ActionButton.SPECIAL] = true;
					buffer[ActionButton.HIT] = true;
					break;

				case Character.State.SPECIAL:
				case Character.State.SPELL:
					buffer[ActionButton.SPECIAL] = false;
					break;

				default:
					buffer[ActionButton.SPECIAL] = buttons[ActionButton.SPECIAL].isPushed;
					buffer[ActionButton.HIT] = buttons[ActionButton.HIT].isPushed;
					break;
				}
			}

			switch(_state)
			{
			case State.NORMAL:
				switch(_owner.state)
				{
				case Character.State.HIT:
				case Character.State.CHARGEHIT:
					if(_owner.timer.left >= 20 && _owner.timer.time&2)
					{
						buffer[ActionButton.HIT] = true;
					}
					else
					{
						buffer[ActionButton.HIT] = false;
					}
					break;

				case Character.State.RUN:
				case Character.State.FALL:
				case Character.State.STAND:
					_axis.length      = 1.0;
					_axis.direction[] = f[];
					if(!_owner.isFlyingPosition)
					{
						if(random() < 128)
						{
							.gedo.math.vector.invert(_axis.direction, r);
						}
						else
						{
							_axis.direction[] = r[];
						}
						buffer[ActionButton.DEFEND] = true;
					}
					else
					{
						if(_owner._dashCount > 0 && _owner.velocity[2] <= -0.25 / 16.0)
						{
							auto friend = cast(Character)_owner.next;

							Vector toFriend;
							.gedo.math.vector.subtract(toFriend, friend.position.current, _owner.position.current);
							.gedo.math.vector.normalize(toFriend);
							_axis.direction[] = toFriend[];
							buffer[ActionButton.DEFEND] = true;
						}
					}
					break;

				case Character.State.STEP:
					_axis.length      = 1.0;
					_axis.direction[] = f[];
					break;

				case Character.State.KNOCKBACK:
				case Character.State.DOWN:
					{
						auto friend = cast(Character)_owner.next;
						if(_owner.target is friend.target)
						{
							buffer[ActionButton.SEARCH] = true;
						}
					}
					break;

				case Character.State.GUARD:
					buffer[ActionButton.SHOT] = !buffer[ActionButton.SHOT];
					break;

				default:
					break;
				}
				break;

			case State.GUARD:
				_axis.length = 0.0;
				buffer[ActionButton.DEFEND] = true;

				if(_level < 2)
				{
					switch(_owner.target.state)
					{
					case Character.State.HIT:
					case Character.State.FOLLOW:
						break;

					default:
						_state = State.NORMAL;
						break;
					}
				}
				else
				{
					_state = State.NORMAL;
					foreach(task; _owner.target.parent)
					{
						target = cast(Character)task;
						switch(target.state)
						{
						case Character.State.HIT:
						case Character.State.FOLLOW:
							_state = State.GUARD;
							break;

						default:
							break;
						}
					}
				}
				break;

			default:
				break;
			}
		}
		else
		{
			switch(_state)
			{
			case State.WALK_F:
				_axis.length      = 1.0;
				_axis.direction[] = f[];
				break;

			case State.WALK_B:
				_axis.length      = 1.0;
				.gedo.math.vector.invert(_axis.direction, f);
				break;

			case State.WALK_R:
				_axis.length      = 1.0;
				_axis.direction[] = r[];
				break;

			default:
				_axis.length      = 0.0;
				_axis.direction[] = f[];
				break;
			}
		}
	}


	uint random()
	{
		//return cast(int)((_owner.x + _owner.y) * 8.0) & r;
		return (.std.random.rand() & 0xFF0000)>>16;
	}
}
