module vs.scene.characterselect;



private import std.math;
private import gedo.gui.label;
private import gedo.gui.cursor;
private import gedo.gui.component;
private import gedo.audio.sound;
private import gedo.audio.music;
private import gedo.video.ggl;
private import gedo.video.rect;
private import gedo.video.color;
private import gedo.video.texture;
private import gedo.video.staticmesh;
private import gedo.input.joystick;
private import gedo.system.scene;
private import vs.scene.action;
private import vs.action.field;
private import vs.action.character;
private import vs.character.reimu;
private import vs.character.frandle;
private import vs.character.kanako;
private import vs.character.moko;
private import vs.character.aki;
private import vs.character.oku;
private import vs.character.inusakuya;



private const float[2] CANVAS_SIZE = [640, 480];
private const float[2] MENUITEM_SIZE = [64, 128];
private const float[2] SELECTEDITEM_SIZE = [128, 256];



private enum OperatorID
{
	P1,
	P2,
	CP,
}



public enum CharacterID
{
	REIMU,
	FRANDLE,
	KANAKO,
	MOKO,
	AKI,
	OKU,
	INUSAKUYA,
}



alias Scene delegate(CharacterSelect) OnQuit;



public class CharacterSelect : Scene
{
public:
	this(OnQuit dg, Joystick[] joysticks, Player2is p2is, bool selectAll = true)
	{
		_onQuit = dg;

		_wallPaper = new WallPaper();

		foreach(i, inout item; _menuItems[0..$-1])
		{
			item = new GenericMenuItem(cast(CharacterID)i);
		}
		_menuItems[$-1] = new AutoMenuItem();

		{
			float x = -MENUITEM_SIZE[0]*0.5 - ((_menuItems.length-1)>>1)*MENUITEM_SIZE[0];
			foreach(item; _menuItems)
			{
				item.move(x, -CANVAS_SIZE[1]*0.5 + CANVAS_SIZE[1]/6.0);

				x += MENUITEM_SIZE[0];
			}
		}

		{
			StaticMesh[2][2] buffer;

			buffer[0][0] = StaticMesh("$LIB/characterselect/p1.gsa");
			switch(p2is)
			{
			case Player2is.NONE:
				buffer[0][1] = StaticMesh("$LIB/characterselect/cp.gsa");
				buffer[1][0] = buffer[0][1];
				buffer[1][1] = buffer[0][1];
				break;

			case Player2is.FRIEND:
				buffer[0][1] = StaticMesh("$LIB/characterselect/p2.gsa");
				buffer[1][0] = StaticMesh("$LIB/characterselect/cp.gsa");
				buffer[1][1] = buffer[1][0];
				break;

			case Player2is.ENEMY:
				buffer[0][1] = StaticMesh("$LIB/characterselect/cp.gsa");
				buffer[1][0] = StaticMesh("$LIB/characterselect/p2.gsa");
				buffer[1][1] = buffer[0][1];
				break;
			}

			float x = -CANVAS_SIZE[0]*0.5 + CANVAS_SIZE[0]/8.0;
			foreach(it, inout t; _selectedItems)
			{
				foreach(ip, inout p; t)
				{
					p = new SelectedItem(x, CANVAS_SIZE[1]*0.5 - CANVAS_SIZE[1]/3.0, buffer[it][ip]);

					x += CANVAS_SIZE[0]/4.0;
				}
			}
		}

		switch(p2is)
		{
		case Player2is.NONE:
			{
				SelectedItem[4] buffer = [
					_selectedItems[0][0],
					_selectedItems[0][1],
					_selectedItems[1][0],
					_selectedItems[1][1]
				];

				if(selectAll)
				{
					_cursors[0] = new Cursor(joysticks[0], _menuItems, buffer);
				}
				else
				{
					_cursors[0] = new Cursor(joysticks[0], _menuItems, buffer[0..1]);
				}
			}
			break;

		case Player2is.FRIEND:
			{
				SelectedItem[3] buffer = [
					_selectedItems[0][0],
					_selectedItems[1][0],
					_selectedItems[1][1]
				];

				if(selectAll)
				{
					_cursors[0] = new Cursor(joysticks[0], _menuItems, buffer);
				}
				else
				{
					_cursors[0] = new Cursor(joysticks[0], _menuItems, buffer[0..1]);
				}

				buffer[0] = _selectedItems[0][1];
				_cursors[1] = new Cursor(joysticks[1], _menuItems, buffer[0 .. 1]);
			}
			break;

		case Player2is.ENEMY:
			{
				SelectedItem[2] buffer = [
					_selectedItems[0][0],
					_selectedItems[0][1]
				];

				_cursors[0] = new Cursor(joysticks[0], _menuItems, buffer);

				buffer[0] = _selectedItems[1][0];
				buffer[1] = _selectedItems[1][1];
				_cursors[1] = new Cursor(joysticks[1], _menuItems, buffer);
			}
			break;
		}
		if(_cursors[1])
		{
			_cursors[1].color = GREEN;
		}

		_player2is = p2is;

		_go       = new ImageLabel(Texture("$LIB/characterselect/go.png", false));
		_back     = new ImageLabel(Texture("$LIB/characterselect/back.png", false));
		_yesno[0] = new ImageLabel(Texture("$LIB/characterselect/yes.png", false));
		_yesno[1] = new ImageLabel(Texture("$LIB/characterselect/no.png", false));

		_guiCursor = new .gedo.gui.cursor.Cursor();
		_guiCursor.joystick = _cursors[0].joystick;

		_back.position[] = 0.0;
		_yesno[0].position[0] = 0.0;
		_yesno[0].position[1] = -48.0;
		_yesno[1].position[0] = 0.0;
		_yesno[1].position[1] = -96.0;

		_yesno[0].cbClick = &onClickYes;
		_yesno[1].cbClick = &onClickNo;

		_back.add(_yesno[0]);
		_back.add(_yesno[1]);

		{
			float l = _back.position[0] - _back.size[0]*0.5 - 4.0;
			float r = _back.position[0] + _back.size[0]*0.5 + 4.0;
			float b = _yesno[1].position[1] - _yesno[1].size[1]*0.5 - 4.0;
			float t = _back.position[1] + _back.size[1]*0.5 + 4.0;

			_rect = new Rect((l+r)*0.5, (b+t)*0.5, r-l, t-b);
		}

		_bgm = new Music("$LIB/characterselect/bgm.txt");

		_bgm.play();
	}


	override void update()
	{
		foreach(i; _menuItems)
		{
			i.update();
		}

		auto n = 0;
		foreach(t; _selectedItems)
		{
			foreach(p; t)
			{
				p.update();
				if(p.item)
				{
					++n;
				}
			}
		}

		_wallPaper.update();


		if(_guiCursor.focus)
		{
			_guiCursor.focus.root.update();
			_guiCursor.update();
		}
		else
		{
			if(isSelectionDone)
			{
				if(!_ready)
				{
					_go.position[] = 0.0;
					_go.slideIn();
					if(!_go.parent)
					{
						_wallPaper.add(_go);
					}

					_ready = true;
				}
				else if(_cursors[0].joystick.buttons[0].isPushed(0))
				{
					_nextScene = _onQuit(this);
				}
			}
			else
			{
				if(_ready)
				{
					_go.slideOut();
					_ready = false;
				}
				else if(_go.parent && !_go.isAnimating)
				{
					_wallPaper.remove(_go);
				}
				else if(n == 0 && _cursors[0].joystick.buttons[1].isPushed(0))
				{
					_guiCursor.focus = _yesno[1];

					_guiCursor.focus.parent.slideIn();

					foreach(c; _guiCursor.focus.parent)
					{
						c.slideIn();
					}
				}
			}

			_cursors[0].update();
			if(_cursors[1])
			{
				_cursors[1].update();
			}
		}
	}


	override void draw()
	{
		GGL_enableDepthBuffer(0);

		GGL_enableAlphaBlend(1);

		GGL_enter2DMode(CANVAS_SIZE[0], CANVAS_SIZE[1]);

		_wallPaper.draw();

		foreach(i; _menuItems)
		{
			i.draw();
		}

		Color[2] teamColors;

		teamColors[0] = rgba(255, 0, 0, 128);
		teamColors[1] = rgba(0, 255, 0, 128);
		foreach(it, t; _selectedItems)
		{
			foreach(ip, p; t)
			{
				Color c = rgba(128, 128, 128, 128);

				if(it == 0 && ip == 0)
				{
					c = rgba(255, 0, 0, 128);
				}
				else if(_player2is == Player2is.FRIEND && it == 0 && ip == 1)
				{
					c = rgba(0, 255, 0, 128);
				}
				else if(_player2is == Player2is.ENEMY && it == 1 && ip == 0)
				{
					c = rgba(0, 255, 0, 128);
				}

				p.draw(teamColors[it], c);
			}
		}
		_cursors[0].draw();
		if(_cursors[1])
		{
			_cursors[1].draw();
		}

		if(_guiCursor.focus)
		{
			_rect.fill(rgba(0, 0, 0, 128));
			_guiCursor.focus.root.drawRecursive();
			_guiCursor.draw();
		}
		else if(_go.parent)
		{
			Rect(_go.position, _go.size).fill(rgba(0, 0, 0, 128));
			_go.draw();
		}
	}


	CharacterID id(int t, int p)
	{
		return _selectedItems[t][p].item.id;
	}


private:
	CharacterMenuItem[CharacterID.max+2] _menuItems;
	gedo.gui.cursor.Cursor               _guiCursor;
	SelectedItem[2][2]                   _selectedItems;
	ImageLabel[2]                        _yesno;
	ImageLabel                           _go;
	ImageLabel                           _back;
	Player2is                            _player2is;
	WallPaper                            _wallPaper;
	Cursor[2]                            _cursors;
	OnQuit                               _onQuit;
	Music                                _bgm;
	Rect                                 _rect;
	bool                                 _ready;


	void onClickYes()
	{
		_nextScene = _onQuit(null);
	}


	void onClickNo()
	{
		_guiCursor.focus = null;
	}


	bool isSelectionDone()
	{
		if(!_cursors[0].isDone)
		{
			return false;
		}
		else if(_cursors[1] && !_cursors[1].isDone)
		{
			return false;
		}

		return true;
	}
}



private class WallPaper : Component
{
public:
	this()
	{
		_mesh = StaticMesh("$LIB/characterselect/text.gsa");
		_ball = StaticMesh("$LIB/characterselect/ball.gsa");
	}


	override void onUpdate()
	{
		_time = (_time + 1) & 511;
	}


	override void onDraw()
	{
		GGL_clearScreen(0.0, 0.5, 1.0);

		auto t = _time & 127;
		auto s = false;
		for(float x=-512.0+t;x<512.0;x+=128.0)
		{
			for(float y=-512.0+t;y<512.0;y+=128.0)
			{
				if(s)
				{
					GGL_pushMatrix();

					GGL_translate(x, y, 0);

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

					GGL_scale(16, 16, 1);

					_ball.draw(rgba(255, 255, 255, 64));

					GGL_popMatrix();
				}
				s = !s;
			}
			s = !s;
		}

		uint c=0;
		for(float x=-(CANVAS_SIZE[0]+MENUITEM_SIZE[0])*0.5;x<CANVAS_SIZE[0]*0.5;x+=MENUITEM_SIZE[0])
		{
			Rect(x, -CANVAS_SIZE[1]*0.5 + CANVAS_SIZE[1]/6.0, MENUITEM_SIZE[0], MENUITEM_SIZE[1]).fill(rgba(c, c, c, 128));

			c ^= 255;
		}

		float y = -CANVAS_SIZE[1]*0.5 + CANVAS_SIZE[1]/3.0;

		Rect(0, y, CANVAS_SIZE[0], CANVAS_SIZE[1]/3.0 - MENUITEM_SIZE[1]).fill(rgba(0, 0, 0, 192));

		for(float x = _time - 512; x < 512; x += 512)
		{
			GGL_pushMatrix();

			GGL_translate(x, y, 0);

			GGL_scale(16, 16, 1);

			_mesh.draw(rgba(255, 255, 255, 192));

			GGL_popMatrix();
		}
	}


private:
	StaticMesh _mesh;
	StaticMesh _ball;
	int        _time;
}



private class CharacterMenuItem
{
public:
	this()
	{
		_texture = createTexture();
		_factory = null;
	}


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


	void update()
	{
	}


	void draw()
	{
		auto plane = StaticMesh.plane;

		GGL_pushMatrix();

		GGL_translate(_position[0], _position[1], 0);

		GGL_scale(MENUITEM_SIZE[0], MENUITEM_SIZE[1], 1.0);

		plane.draw(_texture);

		GGL_popMatrix();
	}


	float x()
	{
		return _position[0];
	}


	float y()
	{
		return _position[1];
	}


	Texture texture()
	{
		return _texture;
	}


	abstract Texture createTexture()
	{
		return null;
	}


	abstract CharacterID id()
	{
		return CharacterID.REIMU;
	}


protected:
	CharacterFactory _factory;
	Texture          _texture;
	float[2]          _position;
}



private class GenericMenuItem : CharacterMenuItem
{
public:
	this(CharacterID i)
	{
		_id = i;
		super();
	}


	override Texture createTexture()
	{
		return createIcon(_id);
	}


	override CharacterID id()
	{
		return _id;
	}


private:
	CharacterID _id;
}



private class AutoMenuItem : CharacterMenuItem
{
public:
	override Texture createTexture()
	{
		return Texture("$LIB/characterselect/auto.png", false);
	}


	override CharacterID id()
	{
		return autoSelect();
	}
}



private class SelectedItem
{
public:
	this(float x, float y, StaticMesh o)
	{
		_position[0] = x;
		_position[1] = y;

		_operator = o;

		_time = 0;
	}


	void update()
	{
		if(_time > 0)
		{
			--_time;
		}
	}


	void draw(Color c0, Color c1)
	{
		GGL_pushMatrix();

		if(_time == 0)
		{
			GGL_translate(_position[0], _position[1], 0);
		}
		else
		{
			auto t = _time >> 1;

			if(t & 1)
			{
				GGL_translate(_position[0], _position[1] + t, 0);
			}
			else
			{
				GGL_translate(_position[0], _position[1] - t, 0);
			}
		}

		Rect(0, 0, SELECTEDITEM_SIZE[0], SELECTEDITEM_SIZE[1]).fill(c0);

		GGL_pushMatrix();

		GGL_scale(SELECTEDITEM_SIZE[0]*0.5, SELECTEDITEM_SIZE[0]*0.5, 1.0);

		_operator.draw(c1);

		GGL_popMatrix();

		if(_item)
		{
			Rect(0, 0, SELECTEDITEM_SIZE[0], SELECTEDITEM_SIZE[1]).draw(_item.texture, WHITE);
		}

		GGL_popMatrix();
	}


	float[] position()
	{
		return _position;
	}


	CharacterMenuItem item()
	{
		return _item;
	}


	void select(CharacterMenuItem i)
	{
		_item = i;

		_time = 16;
	}


	void deselect()
	{
		_item = null;
	}


private:
	CharacterMenuItem _item;
	StaticMesh        _operator;
	float[2]           _position;
	uint              _time;
}



private class Cursor
{
public:
	this(Joystick joystick, CharacterMenuItem[] menuitems, SelectedItem[] selectedItems)
	{
		_joystick = joystick;

		_position = 0;

		_menuItems = menuitems;

		_selectedItems = selectedItems.dup;

		_color = RED;

		_sounds[0] = Sound("$LIB/cursor_move.ogg");
		_sounds[1] = Sound("$LIB/cursor_click.ogg");
		_sounds[2] = Sound("$LIB/cursor_back.ogg");
	}


	void color(Color c)
	{
		_color = c;
	}


	void update()
	{
		if(_joystick.buttons[0].isPushed(0))
		{
			if(_selectPosition < _selectedItems.length)
			{
				_selectedItems[_selectPosition].select(_menuItems[_position]);

				++_selectPosition;

				Source().play(_sounds[1]);
			}
		}
		else if(_joystick.buttons[1].isPushed(0))
		{
			if(_selectPosition > 0)
			{
				--_selectPosition;

				_selectedItems[_selectPosition].deselect();

				Source().play(_sounds[2]);
			}
		}

		if(_joystick.axis.asButtons()[Axis.RIGHT].isRepeatPushed())
		{
			++_position;

			if(_position >= _menuItems.length)
			{
				_position = 0;
			}

			Source().play(_sounds[0]);
		}
		else if(_joystick.axis.asButtons()[Axis.LEFT].isRepeatPushed())
		{
			if(_position > 0)
			{
				--_position;
			}
			else
			{
				_position = _menuItems.length - 1;
			}

			Source().play(_sounds[0]);
		}

		++_time;
	}


	void draw()
	{
		float k = 0.5 + 0.5 * .std.math.sin((_time & 63) / 64.0 * .std.math.PI * 2.0);

		auto focus = _menuItems[_position];

		auto c = setA(_color, cast(ubyte)(255 * k));

		Rect(focus.x, focus.y, MENUITEM_SIZE[0], MENUITEM_SIZE[1]).frame(4.0, c);

		if(_selectPosition < _selectedItems.length)
		{
			auto p = _selectedItems[_selectPosition].position;

			Rect(p[0], p[1], SELECTEDITEM_SIZE[0], SELECTEDITEM_SIZE[1]).frame(4.0, _color);
			Rect(p[0], p[1], SELECTEDITEM_SIZE[0], SELECTEDITEM_SIZE[1]).draw(focus.texture, rgba(255, 255, 255, 128));
		}
	}


	Joystick joystick()
	{
		return _joystick;
	}


	bool isDone()
	{
		return (_selectPosition >= _selectedItems.length);
	}


private:
	CharacterMenuItem[] _menuItems;
	SelectedItem[]      _selectedItems;
	Joystick            _joystick;
	Sound[3]            _sounds;
	Color               _color;
	uint                _position;
	uint                _selectPosition;
	uint                _time;
}



public Texture createIcon(CharacterID id)
{
	string[CharacterID.max+1] paths;

	if(paths[0].length == 0)
	{
		paths[CharacterID.REIMU]   = "$LIB/reimu/icon.png";
		paths[CharacterID.FRANDLE] = "$LIB/frandle/icon.png";
		paths[CharacterID.KANAKO]  = "$LIB/kanako/icon.png";
		paths[CharacterID.MOKO]    = "$LIB/moko/icon.png";
		paths[CharacterID.AKI]     = "$LIB/aki/icon.png";
		paths[CharacterID.OKU]     = "$LIB/oku/icon.png";
		paths[CharacterID.INUSAKUYA] = "$LIB/inusakuya/inusakuya_charasele.png";
	}

	return Texture(paths[id], false);
}



public CharacterFactory createFactory(CharacterID id)
{
	CharacterFactory factory;

	switch(id)
	{
	case CharacterID.REIMU:
		factory = new ReimuFactory();
		break;

	case CharacterID.FRANDLE:
		factory = new FrandleFactory();
		break;

	case CharacterID.KANAKO:
		factory = new KanakoFactory();
		break;

	case CharacterID.MOKO:
		factory = new MokoFactory();
		break;

	case CharacterID.AKI:
		factory = new ShizuFactory();
		break;

	case CharacterID.OKU:
		factory = new OkuFactory();
		break;

	case CharacterID.INUSAKUYA:
		factory = new InusakuyaFactory();
		break;
	}

	return factory;
}



public Music createBGM(CharacterID id)
{
	string[CharacterID.max+1] paths;

	if(paths[0].length == 0)
	{
		paths[CharacterID.REIMU]   = "$LIB/reimu/bgm.txt";
		paths[CharacterID.FRANDLE] = "$LIB/frandle/bgm.txt";
		paths[CharacterID.KANAKO]  = "$LIB/kanako/bgm.txt";
		paths[CharacterID.MOKO]    = "$LIB/moko/bgm.txt";
		paths[CharacterID.AKI]     = "$LIB/aki/bgm.txt";
		paths[CharacterID.OKU]     = "$LIB/oku/bgm.txt";
		paths[CharacterID.INUSAKUYA] = "$LIB/inusakuya/bgm.txt";
	}

	return new Music(paths[id]);
}



private enum FieldID
{
	KYOUKOKU,
	RANDOM,
	CHIKURIN,
	CHITEI,
	AKI,
	CASTLE,
	NEOHAKUREI,
	NEOMORIYA,
}



public Field createField(CharacterID cid)
{
	static int counter = 0;

	FieldID fid;
	switch(cid)
	{
	case CharacterID.OKU:
		fid = FieldID.CHITEI;
		break;

	case CharacterID.MOKO:
		fid = FieldID.CHIKURIN;
		break;

	case CharacterID.AKI:
		fid = FieldID.AKI;
		break;

	case CharacterID.INUSAKUYA:
		fid = FieldID.CASTLE;
		break;

	case CharacterID.FRANDLE:
		fid = FieldID.RANDOM;
		break;

	case CharacterID.REIMU:
		fid = FieldID.NEOHAKUREI;
		break;

	case CharacterID.KANAKO:
		fid = FieldID.NEOMORIYA;
		break;

	default:
		fid = cast(FieldID)(counter % (FieldID.max+1));
		++counter;
		break;
	}

	Field field;

	string[32] meshes;
	switch(fid)
	{
	case FieldID.KYOUKOKU:
		meshes[0] = "$LIB/field/kyoukoku/top.gsa";
		meshes[1] = "$LIB/field/kyoukoku/middle.gsa";
		meshes[2] = "$LIB/field/kyoukoku/bottom.gsa";

		field = new Field(meshes[0..3], "$LIB/field/kyoukoku/field.zmap");
		field.skybox = new SkyBox([StaticMesh("$LIB/field/chikurin/bamboosky.gsa")]);
		break;

	case FieldID.CHIKURIN:
		meshes[0] = "$LIB/field/chikurin/bambooforest0.gsa";
		meshes[1] = "$LIB/field/chikurin/bambooforest1.gsa";
		meshes[2] = "$LIB/field/chikurin/bambooforest2.gsa";
		meshes[3] = "$LIB/field/chikurin/bambooforest3.gsa";

		field = new Field(meshes[0..4], "$LIB/field/chikurin/bambooforest.zmap");
		field.skybox = new SkyBox([StaticMesh("$LIB/field/chikurin/bamboosky.gsa")]);
		break;

	case FieldID.CHITEI:
		meshes[0] = "$LIB/field/chitei/yuka.gsa";
		meshes[1] = "$LIB/field/chitei/kabe.gsa";
		meshes[2] = "$LIB/field/chitei/soko.gsa";

		field = new Field(meshes[0..3], "$LIB/field/chitei/map.zmap");
		field.skybox = new SkyBox([StaticMesh("$LIB/field/chitei/skybox.gsa")]);
		break;

	case FieldID.AKI:
		meshes[0] = "$LIB/field/aki/ine.gsa";
		meshes[1] = "$LIB/field/aki/ine_kage.gsa";
		meshes[2] = "$LIB/field/aki/iwa.gsa";
		meshes[3] = "$LIB/field/aki/iwa_kage.gsa";
		meshes[4] = "$LIB/field/aki/kusa.gsa";
		meshes[5] = "$LIB/field/aki/kusa_kage.gsa";
		meshes[6] = "$LIB/field/aki/momiji.gsa";
		meshes[7] = "$LIB/field/aki/momiji_kage.gsa";
		meshes[8] = "$LIB/field/aki/tsuchi.gsa";

		field = new Field(meshes[0..9], "$LIB/field/aki/map.zmap");
		field.skybox = new SkyBox([StaticMesh("$LIB/field/aki/skybox.gsa")]);
		break;

	case FieldID.CASTLE:
		meshes[0] = "$LIB/field/castle/carpet.gsa";
		meshes[1] = "$LIB/field/castle/door.gsa";
		meshes[2] = "$LIB/field/castle/floor.gsa";
		meshes[3] = "$LIB/field/castle/wall.gsa";

		field = new Field(meshes[0..4], "$LIB/field/castle/map.zmap");
		field.skybox = new SkyBox([StaticMesh("$LIB/field/castle/skybox.gsa")]);
		break;

	case FieldID.NEOHAKUREI:
		meshes[0] = "$LIB/field/neohakurei/crater.gsa";
		meshes[1] = "$LIB/field/neohakurei/crater_huti.gsa";
		meshes[2] = "$LIB/field/neohakurei/kanameisi.gsa";
		meshes[3] = "$LIB/field/neohakurei/mori1.gsa";
		meshes[4] = "$LIB/field/neohakurei/mori2.gsa";
		meshes[5] = "$LIB/field/neohakurei/mori_huti.gsa";
		meshes[6] = "$LIB/field/neohakurei/sandou.gsa";
		meshes[7] = "$LIB/field/neohakurei/tuti.gsa";
		meshes[8] = "$LIB/field/neohakurei/zinzya.gsa";
		meshes[9] = "$LIB/field/neohakurei/kaidan.gsa";

		field = new Field(meshes[0..10], "$LIB/field/neohakurei/neohakurei.zmap");
		field.skybox = new SkyBox([StaticMesh("$LIB/field/neohakurei/soto_yoko.gsa")]);
		break;

	case FieldID.NEOMORIYA:
		meshes[0] = "$LIB/field/neomoriya/crater.gsa";
		meshes[1] = "$LIB/field/neomoriya/crater_huti.gsa";
		meshes[2] = "$LIB/field/neomoriya/haiden_houkai.gsa";
		meshes[3] = "$LIB/field/neomoriya/haiden_mado.gsa";
		meshes[4] = "$LIB/field/neomoriya/haiden_syoumen.gsa";
		meshes[5] = "$LIB/field/neomoriya/honden.gsa";
		meshes[6] = "$LIB/field/neomoriya/honden_houkai.gsa";
		meshes[7] = "$LIB/field/neomoriya/honden_sandou.gsa";
		meshes[8] = "$LIB/field/neomoriya/kaidan.gsa";
		meshes[9] = "$LIB/field/neomoriya/ki.gsa";
		meshes[10] = "$LIB/field/neomoriya/ki_mawari.gsa";
		meshes[11] = "$LIB/field/neomoriya/onbasira.gsa";
		meshes[12] = "$LIB/field/neomoriya/oreta_onbasira.gsa";
		meshes[13] = "$LIB/field/neomoriya/sandou.gsa";
		meshes[14] = "$LIB/field/neomoriya/tityuu.gsa";
		meshes[15] = "$LIB/field/neomoriya/tourou.gsa";
		meshes[16] = "$LIB/field/neomoriya/tuti.gsa";
		meshes[17] = "$LIB/field/neomoriya/yoko_onbasira1.gsa";

		field = new Field(meshes[0..18], "$LIB/field/neomoriya/neomoriya.zmap");
		field.skybox = new SkyBox([StaticMesh("$LIB/field/neomoriya/soto_yoko.gsa")]);
		break;

	case FieldID.RANDOM:
		{
			Field.MeshSource[2] sources;

			sources[0].texturePathV = "$LIB/field/random/tile_dark.png";
			sources[0].texturePathH = "$LIB/field/random/tile.png";
			sources[0].bottom       = 0.0;
			sources[0].top          = 20.0;
			sources[1].texturePathV = "$LIB/field/random/brick.png";
			sources[1].texturePathH = "$LIB/field/random/brick_dark.png";
			sources[1].bottom       = 20.0;
			sources[1].top          = 120.0;

			field = new Field(sources);
		}
		field.skybox = new SkyBox([StaticMesh("$LIB/field/random/skybox.gsa")]);
		break;
	}

	return field;
}



public CharacterID autoSelect()
{
	static uint[CharacterID.max+1] counts;

	uint minValue = counts[0];
	uint minCount = 1;

	foreach(count; counts[1..$])
	{
		if(count == minValue)
		{
			++minCount;
		}
		else if(count < minValue)
		{
			minValue = count;
			minCount = 1;
		}
	}

	auto a = .std.random.rand() % minCount;
	foreach(i, count; counts)
	{
		if(count == minValue)
		{
			if(a == 0)
			{
				counts[i] += 1;

				return cast(CharacterID)i;
			}
			else
			{
				--a;
			}
		}
	}

	assert(0);
}
