Huge Smaxx patch with some fixes by me:
authorunc0rr
Mon, 12 Oct 2009 16:44:30 +0000
changeset 2428 6800f8aa0184
parent 2427 241e3bb6a146
child 2429 f97bac89f0c5
Huge Smaxx patch with some fixes by me: - support for non power of two textures - modelview matrix calculation optimizations - health effect when picking up health crates or using vampirism - rc plane training time trial - fix visual gear drawing position (might require recheck) - gamepad support - reordered options in frontend - updated binding options (subcaptions, descriptions and localizable names) - cfg-dir parameter for frontend (allows teams/settings to sit on a usb flash drive for example) - icons for chatroom user list
QTfrontend/CMakeLists.txt
QTfrontend/SDLs.cpp
QTfrontend/SDLs.h
QTfrontend/binds.cpp
QTfrontend/binds.h
QTfrontend/chatwidget.cpp
QTfrontend/game.cpp
QTfrontend/hwconsts.cpp.in
QTfrontend/hwconsts.h
QTfrontend/hwform.cpp
QTfrontend/hwform.h
QTfrontend/main.cpp
QTfrontend/pages.cpp
QTfrontend/pages.h
QTfrontend/sdlkeys.h
QTfrontend/team.cpp
hedgewars/CCHandlers.inc
hedgewars/GSHandlers.inc
hedgewars/HHHandlers.inc
hedgewars/SDLh.pas
hedgewars/hwengine.pas
hedgewars/uConsole.pas
hedgewars/uConsts.pas
hedgewars/uGears.pas
hedgewars/uKeys.pas
hedgewars/uLocale.pas
hedgewars/uMisc.pas
hedgewars/uStore.pas
hedgewars/uVisualGears.pas
hedgewars/uWorld.pas
share/hedgewars/Data/Graphics/BigDigits.png
share/hedgewars/Data/Graphics/Frame.png
share/hedgewars/Data/Graphics/Health.png
share/hedgewars/Data/Locale/de.txt
share/hedgewars/Data/Locale/en.txt
share/hedgewars/Data/Trainings/003_RCPlane.txt
--- a/QTfrontend/CMakeLists.txt	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/CMakeLists.txt	Mon Oct 12 16:44:30 2009 +0000
@@ -5,8 +5,8 @@
 set(QT_USE_QTGUI TRUE)
 set(QT_USE_QTNETWORK TRUE)
 set(QT_USE_QTSVG TRUE)
-set(QT_USE_QTXML TRUE)
-# set(QT_USE_QTOPENGL TRUE)
+set(QT_USE_QTXML FALSE)
+set(QT_USE_QTOPENGL FALSE)
 set(QT_USE_QTMAIN TRUE)
 
 find_package(Qt4 REQUIRED)
--- a/QTfrontend/SDLs.cpp	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/SDLs.cpp	Mon Oct 12 16:44:30 2009 +0000
@@ -21,14 +21,26 @@
 #include "SDL.h"
 #include "hwconsts.h"
 
+#include <QApplication>
+
+extern char sdlkeys[1024][2][128];
+extern char xb360buttons[][128];
+extern char xb360dpad[128];
+extern char xbox360axes[][128];
+
 bool hardware;
 extern char *programname;
 
-SDLInteraction::SDLInteraction(bool hardware_snd)
+SDLInteraction::SDLInteraction()
 {
 	music = -1;
-	hardware = hardware_snd;
-	SDL_Init(SDL_INIT_VIDEO);
+	hardware = false;
+	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK);
+
+
+	if(SDL_NumJoysticks())
+		addGameControllerKeys();
+	SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
 }
 
 SDLInteraction::~SDLInteraction()
@@ -37,6 +49,11 @@
 	openal_close();
 }
 
+void SDLInteraction::setHardwareSound(bool hardware_snd)
+{
+	hardware = hardware_snd;
+}
+
 QStringList SDLInteraction::getResolutions() const
 {
 	QStringList result;
@@ -58,6 +75,87 @@
 	return result;
 }
 
+void SDLInteraction::addGameControllerKeys() const
+{
+	QStringList result;
+
+	int i = 0;
+	while(i < 1024 && sdlkeys[i][1][0] != '\0')
+		i++;
+
+	// Iterate through all game controllers
+	for(int jid = 0; jid < SDL_NumJoysticks(); jid++)
+	{
+		SDL_Joystick* joy = SDL_JoystickOpen(jid);
+		
+		// Retrieve the game controller's name and strip "Controller (...)" that's added by some drivers (English only)
+		QString joyname = QString(SDL_JoystickName(jid)).replace(QRegExp("^Controller \\((.*)\\)$"), "\\1");
+
+		// Connected Xbox 360 controller? Use specific button names then
+		// Might be interesting to add 'named' buttons for the most often used gamepads
+		bool isxb = joyname.contains("Xbox 360");
+
+		// This part of the string won't change for multiple keys/hats, so keep it
+		QString prefix = QString("%1 (%2): ").arg(joyname).arg(jid + 1);
+
+		// Register entries for missing axes not assigned to sticks of this joystick/gamepad
+		for(int aid = 0; aid < SDL_JoystickNumAxes(joy) && i < 1021; aid++)
+		{
+			// Axis 2 on xbox 360 is left/right trigger but those are used as buttons anyway so skip it
+			if(aid == 2 && isxb)
+				continue;
+
+			// Again store the part of the string not changing for multiple uses
+			QString axis = prefix + QApplication::translate("binds (keys)", "Axis") + QString(" %1 ").arg(aid + 1);
+			
+			// Entry for "Axis Up"
+			sprintf(sdlkeys[i][0], "j%da%du", jid, aid);
+			sprintf(sdlkeys[i++][1], "%s", ((isxb && aid < 5) ? (prefix + QApplication::translate("binds (keys)", xbox360axes[aid * 2])) : axis + QApplication::translate("binds (keys)", "(Up)")).toStdString().c_str());
+
+			// Entry for "Axis Down"
+			sprintf(sdlkeys[i][0], "j%da%dd", jid, aid);
+			sprintf(sdlkeys[i++][1], "%s", ((isxb && aid < 5) ? (prefix + QApplication::translate("binds (keys)", xbox360axes[aid * 2 + 1])) : axis + QApplication::translate("binds (keys)", "(Down)")).toStdString().c_str());
+		}
+
+		// Register entries for all coolie hats of this joystick/gamepad
+		for(int hid = 0; hid < SDL_JoystickNumHats(joy) && i < 1019; hid++)
+		{
+			// Again store the part of the string not changing for multiple uses
+			QString hat = prefix + (isxb ? (QApplication::translate("binds (keys)", xb360dpad) + QString(" ")) : QApplication::translate("binds (keys)", "Hat") + QString(" %1 ").arg(hid + 1));
+
+			// Entry for "Hat Up"
+			sprintf(sdlkeys[i][0], "j%dh%du", jid, hid);			
+			sprintf(sdlkeys[i++][1], "%s", (hat + QApplication::translate("binds (keys)", "(Up)")).toStdString().c_str());
+
+			// Entry for "Hat Down"
+			sprintf(sdlkeys[i][0], "j%dh%dd", jid, hid);			
+			sprintf(sdlkeys[i++][1], "%s", (hat + QApplication::translate("binds (keys)", "(Down)")).toStdString().c_str());
+
+			// Entry for "Hat Left"
+			sprintf(sdlkeys[i][0], "j%dh%dl", jid, hid);			
+			sprintf(sdlkeys[i++][1], "%s", (hat + QApplication::translate("binds (keys)", "(Left)")).toStdString().c_str());
+
+			// Entry for "Hat Right"
+			sprintf(sdlkeys[i][0], "j%dh%dr", jid, hid);			
+			sprintf(sdlkeys[i++][1], "%s", (hat + QApplication::translate("binds (keys)", "(Right)")).toStdString().c_str());
+		}
+		
+		// Register entries for all buttons of this joystick/gamepad
+		for(int bid = 0; bid < SDL_JoystickNumButtons(joy) && i < 1022; bid++)
+		{
+			// Buttons
+			sprintf(sdlkeys[i][0], "j%db%d", jid, bid);			
+			sprintf(sdlkeys[i++][1], "%s", (prefix + ((isxb && bid < 10) ? (QApplication::translate("binds (keys)", xb360buttons[bid]) + QString(" ")) : QApplication::translate("binds (keys)", "Button") + QString(" %1").arg(bid + 1))).toStdString().c_str());
+		}
+		// Close the game controller as we no longer need it
+		SDL_JoystickClose(joy);
+	}
+	
+	// Terminate the list
+	sdlkeys[i][0][0] = '\0';
+	sdlkeys[i][1][0] = '\0';
+}
+
 void SDLInteraction::StartMusic()
 {
 	OpenAL_Init();
--- a/QTfrontend/SDLs.h	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/SDLs.h	Mon Oct 12 16:44:30 2009 +0000
@@ -44,9 +44,11 @@
 	int music;
 
 public:
-	SDLInteraction(bool);
+	SDLInteraction();
 	~SDLInteraction();
+	void setHardwareSound(bool hardware_snd);
 	QStringList getResolutions() const;
+	void addGameControllerKeys() const;
 	void StartMusic();
 	void StopMusic();
 };
--- a/QTfrontend/binds.cpp	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/binds.cpp	Mon Oct 12 16:44:30 2009 +0000
@@ -20,43 +20,50 @@
 
 const BindAction cbinds[BINDS_NUMBER] =
 {
-	{"+up",	"up",	QT_TRANSLATE_NOOP("binds", "up"),	false},
-	{"+left",	"left",	QT_TRANSLATE_NOOP("binds", "left"),	false},
-	{"+right",	"right",	QT_TRANSLATE_NOOP("binds", "right"),	false},
-	{"+down",	"down",	QT_TRANSLATE_NOOP("binds", "down"),	false},
-	{"ljump",	"return",	QT_TRANSLATE_NOOP("binds", "jump"),	false},
-	{"hjump",	"backspace",	QT_TRANSLATE_NOOP("binds", "jump"),	false},
-	{"+attack",	"space",	QT_TRANSLATE_NOOP("binds", "attack"),	false},
-	{"+precise",	"left_shift",	QT_TRANSLATE_NOOP("binds", "precise aim"),	false},
-	{"put",	"mousel",	QT_TRANSLATE_NOOP("binds", "put"),	false},
-	{"switch",	"tab",	QT_TRANSLATE_NOOP("binds", "switch"),	false},
-	{"findhh",	"h",	QT_TRANSLATE_NOOP("binds", "find hedgehog"),	true},
-	{"ammomenu",	"mouser",	QT_TRANSLATE_NOOP("binds", "ammo menu"),	false},
-	{"slot 1",	"f1",	QT_TRANSLATE_NOOP("binds", "slot 1"),	false},
-	{"slot 2",	"f2",	QT_TRANSLATE_NOOP("binds", "slot 2"),	false},
-	{"slot 3",	"f3",	QT_TRANSLATE_NOOP("binds", "slot 3"),	false},
-	{"slot 4",	"f4",	QT_TRANSLATE_NOOP("binds", "slot 4"),	false},
-	{"slot 5",	"f5",	QT_TRANSLATE_NOOP("binds", "slot 5"),	false},
-	{"slot 6",	"f6",	QT_TRANSLATE_NOOP("binds", "slot 6"),	false},
-	{"slot 7",	"f7",	QT_TRANSLATE_NOOP("binds", "slot 7"),	false},
-	{"slot 8",	"f8",	QT_TRANSLATE_NOOP("binds", "slot 8"),	false},
-	{"slot 9",	"f9",	QT_TRANSLATE_NOOP("binds", "slot 9"),	true},
-	{"timer 1",	"1",	QT_TRANSLATE_NOOP("binds", "timer 1 sec"),	false},
-	{"timer 2",	"2",	QT_TRANSLATE_NOOP("binds", "timer 2 sec"),	false},
-	{"timer 3",	"3",	QT_TRANSLATE_NOOP("binds", "timer 3 sec"),	false},
-	{"timer 4",	"4",	QT_TRANSLATE_NOOP("binds", "timer 4 sec"),	false},
-	{"timer 5",	"5",	QT_TRANSLATE_NOOP("binds", "timer 5 sec"),	true},
-	{"chat",	"t",	QT_TRANSLATE_NOOP("binds", "chat"),	false},
-	{"history",	"`",	QT_TRANSLATE_NOOP("binds", "chat history"),	false},
-	{"pause",	"p",	QT_TRANSLATE_NOOP("binds", "pause"),	false},
-	{"confirm",	"y",	QT_TRANSLATE_NOOP("binds", "confirmation"),	false},
-	{"+voldown",	"9",	QT_TRANSLATE_NOOP("binds", "volume down"),	false},
-	{"+volup",	"0",	QT_TRANSLATE_NOOP("binds", "volume up"),	false},
-	{"zoomin",	"wheeldown",	QT_TRANSLATE_NOOP("binds", "zoom in"),	false},
-	{"zoomout",	"wheelup",	QT_TRANSLATE_NOOP("binds", "zoom out"),	false},
-	{"zoomreset",	"mousem",	QT_TRANSLATE_NOOP("binds", "reset zoom"),	false},
-	{"fullscr",	"f12",	QT_TRANSLATE_NOOP("binds", "change mode"),	false},
-	{"capture",	"c",	QT_TRANSLATE_NOOP("binds", "capture"),	false},
-	{"rotmask",	"delete",	QT_TRANSLATE_NOOP("binds", "hedgehogs\ninfo"),	false},
-	{"quit",	"escape",	QT_TRANSLATE_NOOP("binds", "quit"),	true}
+	{"+up",	"up",	QT_TRANSLATE_NOOP("binds", "up"),	QT_TRANSLATE_NOOP("binds (categories)", "Basic controls"), QT_TRANSLATE_NOOP("binds (descriptions)", "Move your hogs and aim:")},
+	{"+left",	"left",	QT_TRANSLATE_NOOP("binds", "left"),	NULL, NULL},
+	{"+right",	"right",	QT_TRANSLATE_NOOP("binds", "right"),	NULL, NULL},
+	{"+down",	"down",	QT_TRANSLATE_NOOP("binds", "down"),	NULL, NULL},
+	{"+precise",	"left_shift",	QT_TRANSLATE_NOOP("binds", "precise aim"),	NULL, NULL},
+	{"ljump",	"return",	QT_TRANSLATE_NOOP("binds", "long jump"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Traverse gaps and obstacles by jumping:")},
+	{"hjump",	"backspace",	QT_TRANSLATE_NOOP("binds", "high jump"),	NULL, NULL},
+	{"+attack",	"space",	QT_TRANSLATE_NOOP("binds", "attack"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Fire your selected weapon or trigger an utility item:")},
+	{"put",	"mousel",	QT_TRANSLATE_NOOP("binds", "put"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Pick a weapon or a target location under the cursor:")},
+	{"switch",	"tab",	QT_TRANSLATE_NOOP("binds", "switch"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Switch your currently active hog (if possible):")},
+	{"ammomenu",	"mouser",	QT_TRANSLATE_NOOP("binds", "ammo menu"),	QT_TRANSLATE_NOOP("binds (categories)", "Weapon controls"), QT_TRANSLATE_NOOP("binds (descriptions)", "Pick a weapon or utility item:")},
+	{"slot 1",	"f1",	QT_TRANSLATE_NOOP("binds", "slot 1"),	NULL, NULL},
+	{"slot 2",	"f2",	QT_TRANSLATE_NOOP("binds", "slot 2"),	NULL, NULL},
+	{"slot 3",	"f3",	QT_TRANSLATE_NOOP("binds", "slot 3"),	NULL, NULL},
+	{"slot 4",	"f4",	QT_TRANSLATE_NOOP("binds", "slot 4"),	NULL, NULL},
+	{"slot 5",	"f5",	QT_TRANSLATE_NOOP("binds", "slot 5"),	NULL, NULL},
+	{"slot 6",	"f6",	QT_TRANSLATE_NOOP("binds", "slot 6"),	NULL, NULL},
+	{"slot 7",	"f7",	QT_TRANSLATE_NOOP("binds", "slot 7"),	NULL, NULL},
+	{"slot 8",	"f8",	QT_TRANSLATE_NOOP("binds", "slot 8"),	NULL, NULL},
+	{"slot 9",	"f9",	QT_TRANSLATE_NOOP("binds", "slot 9"),	NULL, NULL},
+	{"timer 1",	"1",	QT_TRANSLATE_NOOP("binds", "timer 1 sec"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Set the timer on bombs and timed weapons:")},
+	{"timer 2",	"2",	QT_TRANSLATE_NOOP("binds", "timer 2 sec"),	NULL, NULL},
+	{"timer 3",	"3",	QT_TRANSLATE_NOOP("binds", "timer 3 sec"),	NULL, NULL},
+	{"timer 4",	"4",	QT_TRANSLATE_NOOP("binds", "timer 4 sec"),	NULL, NULL},
+	{"timer 5",	"5",	QT_TRANSLATE_NOOP("binds", "timer 5 sec"),	NULL, NULL},
+	{"findhh",	"h",	QT_TRANSLATE_NOOP("binds", "find hedgehog"),	QT_TRANSLATE_NOOP("binds (categories)", "Camera and cursor controls"), QT_TRANSLATE_NOOP("binds (descriptions)", "Move the camera to the active hog:")},
+	{"+cur_u",	"",	QT_TRANSLATE_NOOP("binds", "up"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Move the cursor or camera without using the mouse:")},
+	{"+cur_l",	"",	QT_TRANSLATE_NOOP("binds", "left"),	NULL, NULL},
+	{"+cur_r",	"",	QT_TRANSLATE_NOOP("binds", "right"),	NULL, NULL},
+	{"+cur_d",	"",	QT_TRANSLATE_NOOP("binds", "down"),	NULL, NULL},
+//	{"+cur_m",	"",	QT_TRANSLATE_NOOP("binds", "movement key modifier"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Specify a modifier key to move camera and cursor using your default hog movement keys:")},
+	{"zoomin",	"wheeldown",	QT_TRANSLATE_NOOP("binds", "zoom in"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Modify the camera's zoom level:")},
+	{"zoomout",	"wheelup",	QT_TRANSLATE_NOOP("binds", "zoom out"),	NULL, NULL},
+	{"zoomreset",	"mousem",	QT_TRANSLATE_NOOP("binds", "reset zoom"),	NULL, NULL},
+	{"chat",	"t",	QT_TRANSLATE_NOOP("binds", "chat"),	QT_TRANSLATE_NOOP("binds (categories)", "Other"), QT_TRANSLATE_NOOP("binds (descriptions)", "Talk to your team or all participants:")},
+	{"history",	"`",	QT_TRANSLATE_NOOP("binds", "chat history"),	NULL, NULL},
+	{"pause",	"p",	QT_TRANSLATE_NOOP("binds", "pause"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Pause, continue or leave your game:")},
+	{"quit",	"escape",	QT_TRANSLATE_NOOP("binds", "quit"),	NULL, NULL},
+	{"confirm",	"y",	QT_TRANSLATE_NOOP("binds", "confirmation"),	NULL, NULL},
+	{"+voldown",	"9",	QT_TRANSLATE_NOOP("binds", "volume down"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Modify the game's volume while playing:")},
+	{"+volup",	"0",	QT_TRANSLATE_NOOP("binds", "volume up"),	NULL, NULL},
+#ifndef _WIN32
+	{"fullscr",	"f12",	QT_TRANSLATE_NOOP("binds", "change mode"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Toggle fullscreen mode:")},
+#endif
+	{"capture",	"c",	QT_TRANSLATE_NOOP("binds", "capture"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Take a screenshot:")},
+	{"rotmask",	"delete",	QT_TRANSLATE_NOOP("binds", "hedgehogs\ninfo"),	NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Toggle labels above hedgehogs:")}
 };
--- a/QTfrontend/binds.h	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/binds.h	Mon Oct 12 16:44:30 2009 +0000
@@ -21,14 +21,19 @@
 
 #include <QString>
 
-#define BINDS_NUMBER 39
+#ifdef _WIN32
+#define BINDS_NUMBER 42
+#else
+#define BINDS_NUMBER 43
+#endif
 
 struct BindAction
 {
 	QString action;
 	QString strbind;
 	const char * name;
-	bool chwidget;
+	const char * category;
+	const char * description;
 };
 
 extern const BindAction cbinds[BINDS_NUMBER];
--- a/QTfrontend/chatwidget.cpp	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/chatwidget.cpp	Mon Oct 12 16:44:30 2009 +0000
@@ -107,6 +107,7 @@
 void HWChatWidget::nickAdded(const QString& nick)
 {
 	QListWidgetItem * item = new QListWidgetItem(nick);
+	item->setIcon(QIcon(":/res/hh_small.png"));
 	chatNicks->addItem(item);
 }
 
--- a/QTfrontend/game.cpp	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/game.cpp	Mon Oct 12 16:44:30 2009 +0000
@@ -141,7 +141,7 @@
 	HWProto::addStringListToBuffer(traincfg,
 			team1.TeamGameConfig(100));
 
-	QFile file(datadir->absolutePath() + "/Trainings/001_Shotgun.txt");
+	QFile file(datadir->absolutePath() + "/Trainings/003_RCPlane.txt");
 	if(!file.open(QFile::ReadOnly))
 	{
 		emit ErrorMessage(tr("Error reading training config file"));
--- a/QTfrontend/hwconsts.cpp.in	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/hwconsts.cpp.in	Mon Oct 12 16:44:30 2009 +0000
@@ -20,6 +20,7 @@
 
 QString * cProtoVer = new QString("${HEDGEWARS_PROTO_VER}");
 QString * cDataDir = new QString("${HEDGEWARS_DATADIR}");
+QString * cConfigDir = new QString("");
 QString * cVersionString = new QString("${HEDGEWARS_VERSION}");
 
 QDir * bindir = new QDir("${HEDGEWARS_BINDIR}");
--- a/QTfrontend/hwconsts.h	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/hwconsts.h	Mon Oct 12 16:44:30 2009 +0000
@@ -25,6 +25,7 @@
 extern QString * cProtoVer;
 extern QString * cVersionString;
 extern QString * cDataDir;
+extern QString * cConfigDir;
 
 extern QDir * bindir;
 extern QDir * cfgdir;
--- a/QTfrontend/hwform.cpp	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/hwform.cpp	Mon Oct 12 16:44:30 2009 +0000
@@ -77,9 +77,9 @@
 
 	CustomizePalettes();
         
-        sdli = new SDLInteraction(ui.pageOptions->CBHardwareSound->isChecked());
+    sdli.setHardwareSound(ui.pageOptions->CBHardwareSound->isChecked());
         
-	ui.pageOptions->CBResolution->addItems(sdli->getResolutions());
+	ui.pageOptions->CBResolution->addItems(sdli.getResolutions());
 
 	config = new GameUIConfig(this, cfgdir->absolutePath() + "/hedgewars.ini");
 
@@ -784,9 +784,11 @@
 				pRegisterServer->unregister();
 				pRegisterServer = 0;
 			}
+			setVisible(false);
 			break;
 		}
 		case gsFinished: {
+			setVisible(true);
 			GoBack();
 			Music(ui.pageOptions->CBEnableMusic->isChecked());
 			if (wBackground) wBackground->startAnimation();
@@ -795,6 +797,7 @@
 			break;
 		}
 		default: {
+			setVisible(true);
 			quint8 id = ui.Pages->currentIndex();
 			if (id == ID_PAGE_INGAME) {
 				GoBack();
@@ -889,9 +892,9 @@
 void HWForm::Music(bool checked)
 {
 	if (checked)
-		sdli->StartMusic();
+		sdli.StartMusic();
 	else
-		sdli->StopMusic();
+		sdli.StopMusic();
 }
 
 void HWForm::NetGameChangeStatus(bool isMaster)
--- a/QTfrontend/hwform.h	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/hwform.h	Mon Oct 12 16:44:30 2009 +0000
@@ -148,7 +148,7 @@
 	AmmoSchemeModel * ammoSchemeModel;
 	QStack<quint8> PagesStack;
 	QTime eggTimer;
-	SDLInteraction * sdli;
+	SDLInteraction sdli;
 	BGWidget * wBackground;
         
 #ifdef __APPLE__
--- a/QTfrontend/main.cpp	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/main.cpp	Mon Oct 12 16:44:30 2009 +0000
@@ -75,11 +75,15 @@
         *cDataDir = f.absoluteFilePath();
     }
 
+    if(parsedArgs.contains("config-dir")) {
+        QFileInfo f(parsedArgs["config-dir"]);
+        *cConfigDir = f.absoluteFilePath();
+    }
+
 	app.setStyle(new QPlastiqueStyle);
 
 	QDateTime now = QDateTime::currentDateTime();
-	QDateTime zero;
-	srand(now.secsTo(zero));
+	srand(now.toTime_t());
 	rand();
 
 	Q_INIT_RESOURCE(hedgewars);
@@ -288,23 +292,37 @@
 
 	bindir->cd("bin"); // workaround over NSIS installer
 
-	cfgdir->setPath(cfgdir->homePath());
-        
+	if(cConfigDir->length() == 0)
+		cfgdir->setPath(cfgdir->homePath());
+	else
+		cfgdir->setPath(*cConfigDir);
+
+	if(cConfigDir->length() == 0)
+	{
 #ifdef __APPLE__
-	if (checkForDir(cfgdir->absolutePath() + "/Library/Application Support/Hedgewars"))
-	{
-		checkForDir(cfgdir->absolutePath() + "/Library/Application Support/Hedgewars/Demos");
-		checkForDir(cfgdir->absolutePath() + "/Library/Application Support/Hedgewars/Saves");
+		if (checkForDir(cfgdir->absolutePath() + "/Library/Application Support/Hedgewars"))
+		{
+			checkForDir(cfgdir->absolutePath() + "/Library/Application Support/Hedgewars/Demos");
+			checkForDir(cfgdir->absolutePath() + "/Library/Application Support/Hedgewars/Saves");
+		}
+		cfgdir->cd("Library/Application Support/Hedgewars");
+#else
+		if (checkForDir(cfgdir->absolutePath() + "/.hedgewars"))
+		{
+			checkForDir(cfgdir->absolutePath() + "/.hedgewars/Demos");
+			checkForDir(cfgdir->absolutePath() + "/.hedgewars/Saves");
+		}
+		cfgdir->cd(".hedgewars");
+#endif
 	}
-	cfgdir->cd("Library/Application Support/Hedgewars");
-#else
-	if (checkForDir(cfgdir->absolutePath() + "/.hedgewars"))
+	else
 	{
-		checkForDir(cfgdir->absolutePath() + "/.hedgewars/Demos");
-		checkForDir(cfgdir->absolutePath() + "/.hedgewars/Saves");
+		if (checkForDir(cfgdir->absolutePath()))
+		{
+			checkForDir(cfgdir->absolutePath() + "/Demos");
+			checkForDir(cfgdir->absolutePath() + "/Saves");
+		}
 	}
-	cfgdir->cd(".hedgewars");
-#endif
 
 	datadir->cd(bindir->absolutePath());
 	datadir->cd(*cDataDir);
--- a/QTfrontend/pages.cpp	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/pages.cpp	Mon Oct 12 16:44:30 2009 +0000
@@ -243,39 +243,42 @@
 	BindsBox = new QToolBox(GBoxBinds);
 	BindsBox->setLineWidth(0);
 	GBBLayout->addWidget(BindsBox);
-	page_A = new QWidget(this);
-	BindsBox->addItem(page_A, QToolBox::tr("Actions"));
-	page_W = new QWidget(this);
-	BindsBox->addItem(page_W, QToolBox::tr("Weapons"));
-	page_WP = new QWidget(this);
-	BindsBox->addItem(page_WP, QToolBox::tr("Weapon properties"));
-	page_O = new QWidget(this);
-	BindsBox->addItem(page_O, QToolBox::tr("Other"));
 	page2Layout->addWidget(GBoxBinds, 0, 0);
 
-	QStringList binds;
-	for(int i = 0; strlen(sdlkeys[i][1]) > 0; i++)
-	{
-		binds << sdlkeys[i][1];
-	}
-
 	quint16 widind = 0, i = 0;
+	quint16 num = 0;
+	QWidget * curW = NULL;
+	QGridLayout * pagelayout = NULL;
+	QLabel* l = NULL;
 	while (i < BINDS_NUMBER) {
-		quint16 num = 0;
-		QWidget * curW = BindsBox->widget(widind);
-		QGridLayout * pagelayout = new QGridLayout(curW);
-		do {
-			LBind[i] = new QLabel(curW);
-			LBind[i]->setText(QApplication::translate("binds", cbinds[i].name));
-			LBind[i]->setAlignment(Qt::AlignRight);
-			pagelayout->addWidget(LBind[i], num, 0);
-			CBBind[i] = new QComboBox(curW);
-			CBBind[i]->addItems(binds);
-			pagelayout->addWidget(CBBind[i], num, 1);
-			num++;
-		} while (!cbinds[i++].chwidget);
-		pagelayout->addWidget(new QWidget(curW), num, 0, 1, 2);
-		widind++;
+		if(cbinds[i].category != NULL)
+		{
+			if(curW != NULL)
+			{
+				l = new QLabel(curW);
+				l->setText("");
+				pagelayout->addWidget(l, num++, 0, 1, 2);
+			}
+			curW = new QWidget(this);
+			BindsBox->addItem(curW, QApplication::translate("binds (categories)", cbinds[i].category));
+			pagelayout = new QGridLayout(curW);
+			num = 0;
+		}
+		if(cbinds[i].description != NULL)
+		{
+			l = new QLabel(curW);
+			l->setText((num > 0 ? QString("\n") : QString("")) + QApplication::translate("binds (descriptions)", cbinds[i].description));
+			pagelayout->addWidget(l, num++, 0, 1, 2);
+		}
+
+		l = new QLabel(curW);
+		l->setText(QApplication::translate("binds", cbinds[i].name));
+		l->setAlignment(Qt::AlignRight);
+		pagelayout->addWidget(l, num, 0);
+		CBBind[i] = new QComboBox(curW);
+		for(int j = 0; sdlkeys[j][1][0] != '\0'; j++)
+			CBBind[i]->addItem(QApplication::translate("binds (keys)", sdlkeys[j][1]).contains(": ") ? QApplication::translate("binds (keys)", sdlkeys[j][1]) : QApplication::translate("binds (keys)", "Keyboard") + QString(": ") + QApplication::translate("binds (keys)", sdlkeys[j][1]), sdlkeys[j][0]);
+		pagelayout->addWidget(CBBind[i++], num++, 1);
 	}
 }
 
@@ -398,6 +401,14 @@
             QVBoxLayout * GBAlayout = new QVBoxLayout(AGGroupBox);
             QHBoxLayout * GBAreslayout = new QHBoxLayout(0);
 
+            CBFrontendFullscreen = new QCheckBox(AGGroupBox);
+            CBFrontendFullscreen->setText(QCheckBox::tr("Frontend fullscreen"));
+            GBAlayout->addWidget(CBFrontendFullscreen);
+
+            CBFrontendEffects = new QCheckBox(AGGroupBox);
+            CBFrontendEffects->setText(QCheckBox::tr("Frontend effects (requires restart)"));
+            GBAlayout->addWidget(CBFrontendEffects);
+
             QLabel * resolution = new QLabel(AGGroupBox);
             resolution->setText(QLabel::tr("Resolution"));
             GBAreslayout->addWidget(resolution);
@@ -406,6 +417,10 @@
             GBAreslayout->addWidget(CBResolution);
             GBAlayout->addLayout(GBAreslayout);
 
+            CBFullscreen = new QCheckBox(AGGroupBox);
+            CBFullscreen->setText(QCheckBox::tr("Fullscreen"));
+            GBAlayout->addWidget(CBFullscreen);
+
             QHBoxLayout * GBAfpslayout = new QHBoxLayout(0);
             QLabel * maxfps = new QLabel(AGGroupBox);
             maxfps->setText(QLabel::tr("FPS limit"));
@@ -413,23 +428,11 @@
             GBAlayout->addLayout(GBAfpslayout);
 
             CBReduceQuality = new QCheckBox(AGGroupBox);
-            CBReduceQuality->setText(QCheckBox::tr("Reduce Quality"));
+            CBReduceQuality->setText(QCheckBox::tr("Reduced quality"));
             GBAlayout->addWidget(CBReduceQuality);
 
-            CBFrontendEffects = new QCheckBox(AGGroupBox);
-            CBFrontendEffects->setText(QCheckBox::tr("Frontend Effects (Requires Restart)"));
-            GBAlayout->addWidget(CBFrontendEffects);
-
-            CBFullscreen = new QCheckBox(AGGroupBox);
-            CBFullscreen->setText(QCheckBox::tr("Fullscreen"));
-            GBAlayout->addWidget(CBFullscreen);
-
-            CBFrontendFullscreen = new QCheckBox(AGGroupBox);
-            CBFrontendFullscreen->setText(QCheckBox::tr("Frontend fullscreen"));
-            GBAlayout->addWidget(CBFrontendFullscreen);
-
             CBHardwareSound = new QCheckBox(AGGroupBox);
-            CBHardwareSound->setText(QCheckBox::tr("Use hardware sound (if available; requires restart)"));
+            CBHardwareSound->setText(QCheckBox::tr("Hardware sound (if available; requires restart)"));
             //CBHardwareSound->setEnabled(openal_ready());
             GBAlayout->addWidget(CBHardwareSound);
 
@@ -776,7 +779,7 @@
 {
 	QGridLayout * pageLayout = new QGridLayout(this);
 
-	QHBoxLayout * newRoomLayout = new QHBoxLayout(this);
+	QHBoxLayout * newRoomLayout = new QHBoxLayout();
 	QLabel * roomNameLabel = new QLabel(this);
 	roomNameLabel->setText(tr("Room Name:"));
 	roomName = new QLineEdit(this);
--- a/QTfrontend/pages.h	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/pages.h	Mon Oct 12 16:44:30 2009 +0000
@@ -159,10 +159,6 @@
 	QComboBox *CBVoicepack;
 	QGroupBox *GBoxBinds;
 	QToolBox *BindsBox;
-	QWidget *page_A;
-	QWidget *page_W;
-	QWidget *page_WP;
-	QWidget *page_O;
 	QPushButton *BtnTeamDiscard;
 	QPushButton *BtnTeamSave;
 	QPushButton * BtnTestSound;
@@ -176,9 +172,6 @@
 public slots:
 	void CBFort_activated(const QString & gravename);
 
-private:
-	QLabel * LBind[BINDS_NUMBER];
-
 private slots:
 	void testSound();
 };
--- a/QTfrontend/sdlkeys.h	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/sdlkeys.h	Mon Oct 12 16:44:30 2009 +0000
@@ -16,135 +16,164 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  */
 
-char sdlkeys[][2][16] =
-{
-	{"mousel",	"mousel"},
-	{"mousem",	"mousem"},
-	{"mouser",	"mouser"},
-	{"wheelup",	"wheelup"},
-	{"wheeldown",	"wheeldown"},
-	{"backspace",	"backspace"},
-	{"tab",	"tab"},
-	{"clear",	"clear"},
-	{"return",	"return"},
-	{"pause",	"pause"},
-	{"escape",	"escape"},
-	{"space",	"space"},
-	{"!",	"!"},
-	{"\"",	"\""},
-	{"#",	"#"},
-	{"$",	"$"},
-	{"&",	"&"},
-	{"'",	"'"},
-	{"(",	"("},
-	{")",	")"},
-	{"*",	"*"},
-	{"+",	"+"},
-	{",",	","},
-	{"-",	"-"},
-	{".",	"."},
-	{"/",	"/"},
-	{"0",	"0"},
-	{"1",	"1"},
-	{"2",	"2"},
-	{"3",	"3"},
-	{"4",	"4"},
-	{"5",	"5"},
-	{"6",	"6"},
-	{"7",	"7"},
-	{"8",	"8"},
-	{"9",	"9"},
-	{":",	":"},
-	{";",	";"},
-	{"<",	"<"},
-	{"=",	"="},
-	{">",	">"},
-	{"?",	"?"},
-	{"@",	"@"},
-	{"[",	"["},
-	{"\\",	"\\"},
-	{"]",	"]"},
-	{"^",	"^"},
-	{"_",	"_"},
-	{"`",	"`"},
-	{"a",	"a"},
-	{"b",	"b"},
-	{"c",	"c"},
-	{"d",	"d"},
-	{"e",	"e"},
-	{"f",	"f"},
-	{"g",	"g"},
-	{"h",	"h"},
-	{"i",	"i"},
-	{"j",	"j"},
-	{"k",	"k"},
-	{"l",	"l"},
-	{"m",	"m"},
-	{"n",	"n"},
-	{"o",	"o"},
-	{"p",	"p"},
-	{"q",	"q"},
-	{"r",	"r"},
-	{"s",	"s"},
-	{"t",	"t"},
-	{"u",	"u"},
-	{"v",	"v"},
-	{"w",	"w"},
-	{"x",	"x"},
-	{"y",	"y"},
-	{"z",	"z"},
-	{"delete",	"delete"},
-	{"[0]",	"[0]"},
-	{"[1]",	"[1]"},
-	{"[2]",	"[2]"},
-	{"[3]",	"[3]"},
-	{"[4]",	"[4]"},
-	{"[5]",	"[5]"},
-	{"[6]",	"[6]"},
-	{"[7]",	"[7]"},
-	{"[8]",	"[8]"},
-	{"[9]",	"[9]"},
-	{"[.]",	"[.]"},
-	{"[/]",	"[/]"},
-	{"[*]",	"[*]"},
-	{"[-]",	"[-]"},
-	{"[+]",	"[+]"},
-	{"enter",	"enter"},
-	{"equals",	"equals"},
-	{"up",	"up"},
-	{"down",	"down"},
-	{"right",	"right"},
-	{"left",	"left"},
-	{"insert",	"insert"},
-	{"home",	"home"},
-	{"end",	"end"},
-	{"page up",	"page up"},
-	{"page down",	"page down"},
-	{"f1",	"f1"},
-	{"f2",	"f2"},
-	{"f3",	"f3"},
-	{"f4",	"f4"},
-	{"f5",	"f5"},
-	{"f6",	"f6"},
-	{"f7",	"f7"},
-	{"f8",	"f8"},
-	{"f9",	"f9"},
-	{"f10",	"f10"},
-	{"f11",	"f11"},
-	{"f12",	"f12"},
-	{"f13",	"f13"},
-	{"f14",	"f14"},
-	{"f15",	"f15"},
-	{"numlock",	"numlock"},
-	{"caps_lock",	"caps_lock"},
-	{"scroll_lock",	"scroll_lock"},
-	{"right_shift",	"right_shift"},
-	{"left_shift",	"left_shift"},
-	{"right_ctrl",	"right_ctrl"},
-	{"left_ctrl",	"left_ctrl"},
-	{"right_alt",	"right_alt"},
-	{"left_alt",	"left_alt"},
-	{"right_meta",	"right_meta"},
-	{"left_meta",	"left_meta"},
-	{"", ""}
+char sdlkeys[1024][2][128] = {
+	{"mousel", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Left button")},
+	{"mousem", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Middle button")},
+	{"mouser", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Right button")},
+	{"wheelup", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Wheel up")},
+	{"wheeldown", QT_TRANSLATE_NOOP("binds (keys)", "Mouse: Wheel down")},
+	{"backspace", QT_TRANSLATE_NOOP("binds (keys)", "Backspace")},
+	{"tab", QT_TRANSLATE_NOOP("binds (keys)", "Tab")},
+	{"clear", QT_TRANSLATE_NOOP("binds (keys)", "Clear")},
+	{"return", QT_TRANSLATE_NOOP("binds (keys)", "Return")},
+	{"pause", QT_TRANSLATE_NOOP("binds (keys)", "Pause")},
+	{"escape", QT_TRANSLATE_NOOP("binds (keys)", "Escape")},
+	{"space", QT_TRANSLATE_NOOP("binds (keys)", "Space")},
+	{"!", "!"},
+	{"\"", "\""},
+	{"#", "#"},
+	{"$", "$"},
+	{"&", "&"},
+	{"'", "'"},
+	{"(", "("},
+	{")", ")"},
+	{"*", "*"},
+	{"+", "+"},
+	{", ", ", "},
+	{"-", "-"},
+	{".", "."},
+	{"/", "/"},
+	{"0", "0"},
+	{"1", "1"},
+	{"2", "2"},
+	{"3", "3"},
+	{"4", "4"},
+	{"5", "5"},
+	{"6", "6"},
+	{"7", "7"},
+	{"8", "8"},
+	{"9", "9"},
+	{":", ":"},
+	{";", ";"},
+	{"<", "<"},
+	{"=", "="},
+	{">", ">"},
+	{"?", "?"},
+	{"@", "@"},
+	{"[", "["},
+	{"\\", "\\"},
+	{"]", "]"},
+	{"^", "^"},
+	{"_", "_"},
+	{"`", "`"},
+	{"a", "A"},
+	{"b", "B"},
+	{"c", "C"},
+	{"d", "D"},
+	{"e", "E"},
+	{"f", "F"},
+	{"g", "G"},
+	{"h", "H"},
+	{"i", "I"},
+	{"j", "J"},
+	{"k", "K"},
+	{"l", "L"},
+	{"m", "M"},
+	{"n", "N"},
+	{"o", "O"},
+	{"p", "P"},
+	{"q", "Q"},
+	{"r", "R"},
+	{"s", "S"},
+	{"t", "T"},
+	{"u", "U"},
+	{"v", "V"},
+	{"w", "W"},
+	{"x", "X"},
+	{"y", "Y"},
+	{"z", "Z"},
+	{"delete", QT_TRANSLATE_NOOP("binds (keys)", "Delete")},
+	{"[0]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 0")},
+	{"[1]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 1")},
+	{"[2]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 2")},
+	{"[3]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 3")},
+	{"[4]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 4")},
+	{"[5]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 5")},
+	{"[6]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 6")},
+	{"[7]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 7")},
+	{"[8]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 8")},
+	{"[9]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad 9")},
+	{"[.]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad .")},
+	{"[/]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad /")},
+	{"[*]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad *")},
+	{"[-]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad -")},
+	{"[+]", QT_TRANSLATE_NOOP("binds (keys)", "Numpad +")},
+	{"enter", QT_TRANSLATE_NOOP("binds (keys)", "Enter")},
+	{"equals", QT_TRANSLATE_NOOP("binds (keys)", "Equals")},
+	{"up", QT_TRANSLATE_NOOP("binds (keys)", "Up")},
+	{"down", QT_TRANSLATE_NOOP("binds (keys)", "Down")},
+	{"right", QT_TRANSLATE_NOOP("binds (keys)", "Right")},
+	{"left", QT_TRANSLATE_NOOP("binds (keys)", "Left")},
+	{"insert", QT_TRANSLATE_NOOP("binds (keys)", "Insert")},
+	{"home", QT_TRANSLATE_NOOP("binds (keys)", "Home")},
+	{"end", QT_TRANSLATE_NOOP("binds (keys)", "End")},
+	{"page up", QT_TRANSLATE_NOOP("binds (keys)", "Page up")},
+	{"page down", QT_TRANSLATE_NOOP("binds (keys)", "Page down")},
+	{"f1", "F1"},
+	{"f2", "F2"},
+	{"f3", "F3"},
+	{"f4", "F4"},
+	{"f5", "F5"},
+	{"f6", "F6"},
+	{"f7", "F7"},
+	{"f8", "F8"},
+	{"f9", "F9"},
+	{"f10", "F10"},
+	{"f11", "F11"},
+	{"f12", "F12"},
+	{"f13", "F13"},
+	{"f14", "F14"},
+	{"f15", "F15"},
+	{"numlock", QT_TRANSLATE_NOOP("binds (keys)", "Num lock")},
+	{"caps_lock", QT_TRANSLATE_NOOP("binds (keys)", "Caps lock")},
+	{"scroll_lock", QT_TRANSLATE_NOOP("binds (keys)", "Scroll lock")},
+	{"right_shift", QT_TRANSLATE_NOOP("binds (keys)", "Right shift")},
+	{"left_shift", QT_TRANSLATE_NOOP("binds (keys)", "Left shift")},
+	{"right_ctrl", QT_TRANSLATE_NOOP("binds (keys)", "Right ctrl")},
+	{"left_ctrl", QT_TRANSLATE_NOOP("binds (keys)", "Left ctrl")},
+	{"right_alt", QT_TRANSLATE_NOOP("binds (keys)", "Right alt")},
+	{"left_alt", QT_TRANSLATE_NOOP("binds (keys)", "Left alt")},
+	{"right_meta", QT_TRANSLATE_NOOP("binds (keys)", "Right meta")},
+	{"left_meta", QT_TRANSLATE_NOOP("binds (keys)", "Left meta")}
+	};
+
+// button name definitions for Microsoft's XBox360 controller
+// don't modify button order!
+char xb360buttons[][128] = {
+	QT_TRANSLATE_NOOP("binds (keys)", "A button"),
+	QT_TRANSLATE_NOOP("binds (keys)", "B button"),
+	QT_TRANSLATE_NOOP("binds (keys)", "X button"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Y button"),
+	QT_TRANSLATE_NOOP("binds (keys)", "LB button"),
+	QT_TRANSLATE_NOOP("binds (keys)", "RB button"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Back button"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Start button"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Left stick"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Right stick")
 };
+
+// axis name definitions for Microsoft's XBox360 controller
+// don't modify axis order!
+char xbox360axes[][128] = {
+	QT_TRANSLATE_NOOP("binds (keys)", "Left stick (Right)"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Left stick (Left)"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Left stick (Down)"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Left stick (Up)"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Left trigger"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Right trigger"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Right stick (Down)"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Right stick (Up)"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Right stick (Right)"),
+	QT_TRANSLATE_NOOP("binds (keys)", "Right stick (Left)"),
+};
+char xb360dpad[128] = QT_TRANSLATE_NOOP("binds (keys)", "DPad");
--- a/QTfrontend/team.cpp	Mon Oct 12 13:56:07 2009 +0000
+++ b/QTfrontend/team.cpp	Mon Oct 12 16:44:30 2009 +0000
@@ -214,7 +214,7 @@
 
 	for(int i = 0; i < BINDS_NUMBER; i++)
 	{
-		hwform->ui.pageEditTeam->CBBind[i]->setCurrentIndex(hwform->ui.pageEditTeam->CBBind[i]->findText(binds[i].strbind));
+		hwform->ui.pageEditTeam->CBBind[i]->setCurrentIndex(hwform->ui.pageEditTeam->CBBind[i]->findData(binds[i].strbind));
 	}
 }
 
@@ -233,7 +233,7 @@
 	Voicepack = hwform->ui.pageEditTeam->CBVoicepack->currentText();
 	for(int i = 0; i < BINDS_NUMBER; i++)
 	{
-		binds[i].strbind = hwform->ui.pageEditTeam->CBBind[i]->currentText();
+		binds[i].strbind = hwform->ui.pageEditTeam->CBBind[i]->itemData(hwform->ui.pageEditTeam->CBBind[i]->currentIndex()).toString();
 	}
 }
 
@@ -251,7 +251,8 @@
 
 	if (!m_isNetTeam)
 		for(int i = 0; i < BINDS_NUMBER; i++)
-			sl.push_back(QString("ebind " + binds[i].strbind + " " + binds[i].action));
+			if(!binds[i].strbind.isEmpty())
+				sl.push_back(QString("ebind " + binds[i].strbind + " " + binds[i].action));
 
 	for (int t = 0; t < numHedgehogs; t++)
 	{
--- a/hedgewars/CCHandlers.inc	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/CCHandlers.inc	Mon Oct 12 16:44:30 2009 +0000
@@ -184,6 +184,46 @@
          else CurrentTeam^.Binds[b]:= s
 end;
 
+procedure chCurU_p(var s: shortstring);
+begin
+CursorMovementY:= -1;
+end;
+
+procedure chCurU_m(var s: shortstring);
+begin
+CursorMovementY:= 0;
+end;
+
+procedure chCurD_p(var s: shortstring);
+begin
+CursorMovementY:= 1;
+end;
+
+procedure chCurD_m(var s: shortstring);
+begin
+CursorMovementY:= 0;
+end;
+
+procedure chCurL_p(var s: shortstring);
+begin
+CursorMovementX:= -1;
+end;
+
+procedure chCurL_m(var s: shortstring);
+begin
+CursorMovementX:= 0;
+end;
+
+procedure chCurR_p(var s: shortstring);
+begin
+CursorMovementX:= 1;
+end;
+
+procedure chCurR_m(var s: shortstring);
+begin
+CursorMovementX:= 0;
+end;
+
 procedure chLeft_p(var s: shortstring);
 begin
 if CheckNoTeamOrHH then exit;
@@ -576,10 +616,13 @@
 	end;
 
 SDL_WM_SetCaption('Hedgewars', nil);
-{$IFDEF DEBUGFILE}
-AddFileLog('Freeing old primary surface...');
-{$ENDIF}
-if SDLPrimSurface <> nil then SDL_FreeSurface(SDLPrimSurface);
+if SDLPrimSurface <> nil then
+	begin
+	{$IFDEF DEBUGFILE}
+	AddFileLog('Freeing old primary surface...');
+	{$ENDIF}
+	SDL_FreeSurface(SDLPrimSurface);
+	end;
 
 {$IFDEF DARWIN}
 //remove the topbar from Mac and iPhone
--- a/hedgewars/GSHandlers.inc	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/GSHandlers.inc	Mon Oct 12 16:44:30 2009 +0000
@@ -2130,7 +2130,9 @@
 begin
 AllInactive:= false;
 
-if Gear^.Timer > 0 then dec(Gear^.Timer);
+if ((TrainingFlags and tfRCPlane) = 0) and (Gear^.Timer > 0) then dec(Gear^.Timer);
+
+if ((TrainingFlags and tfRCPlane) <> 0) and ((TrainingFlags and tfTimeTrial) <> 0 ) and (TimeTrialStartTime = 0) then TimeTrialStartTime:= RealTicks;
 
 HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
 FollowGear:= Gear;
@@ -2172,37 +2174,87 @@
 Gear^.X:= Gear^.X + Gear^.dX;
 Gear^.Y:= Gear^.Y + Gear^.dY;
 
-if (GameTicks and $FF) = 0 then
-	if Gear^.Timer < 3500 then
-		AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtEvilTrace, 0, _0, _0, 0)
-	else
+if (TrainingFlags and tfRCPlane) = 0 then
+	begin
+	if (GameTicks and $FF) = 0 then
+		if Gear^.Timer < 3500 then
+			AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtEvilTrace, 0, _0, _0, 0)
+		else
+			AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0);
+
+	if ((HHGear^.Message and gm_Attack) <> 0) and (Gear^.Health <> 0) then
+		begin
+		HHGear^.Message := HHGear^.Message and not gm_Attack;
+		AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtAirBomb, 0, Gear^.dX * _0_5, Gear^.dY * _0_5, 0);
+		dec(Gear^.Health)
+		end;
+
+	if ((HHGear^.Message and gm_LJump) <> 0)
+		and ((Gear^.State and gsttmpFlag) = 0) then
+		begin
+		Gear^.State:= Gear^.State or gsttmpFlag;
+		PauseMusic;
+		playSound(sndRideOfTheValkyries, false, nil);
+		end;
+
+	// pickup bonuses
+	t:= CheckGearNear(Gear, gtCase, 36, 36);
+	if t <> nil then
+		PickUp(HHGear, t);
+	end
+else
+	begin
+	if (GameTicks and $FF) = 0 then
 		AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0);
 
-if ((HHGear^.Message and gm_Attack) <> 0) and (Gear^.Health <> 0) then
-	begin
-	HHGear^.Message := HHGear^.Message and not gm_Attack;
-	AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtAirBomb, 0, Gear^.dX * _0_5, Gear^.dY * _0_5, 0);
-	dec(Gear^.Health)
-    end;
+	// pickup targets
+	t:= CheckGearNear(Gear, gtTarget, 36, 36);
+	if t <> nil then
+		begin;
+		// TODO: Sound!
+		PlaySound(sndGraveImpact, false, nil);
+		DeleteGear(t);
+		TrainingTargetGear:= AddGear(0, 0, gtTarget, 0, _0, _0, 0);
+		FindPlace(TrainingTargetGear, false, 0, LAND_WIDTH);
+		inc(TurnTimeLeft, TrainingTimeInc);
+		
+		if TrainingTimeInc > TrainingTimeInM then
+			dec(TrainingTimeInc, TrainingTimeInD);
+		if TurnTimeLeft > TrainingTimeMax then TurnTimeLeft:= TrainingTimeMax;
+		end;
 
-if ((HHGear^.Message and gm_LJump) <> 0)
-	and ((Gear^.State and gsttmpFlag) = 0) then
-    begin
-    Gear^.State:= Gear^.State or gsttmpFlag;
-    PauseMusic;
-    playSound(sndRideOfTheValkyries, false, nil);
-    end;
+	if (TurnTimeLeft > 0) then 
+		dec(TurnTimeLeft)
+	else
+		begin
+		if ((TrainingFlags and tfTimeTrial) <>0) and (TimeTrialStopTime = 0) then TimeTrialStopTime:= RealTicks;
+		StopSound(sndRCPlane);
+		ResumeMusic;
+		doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 25, EXPLAutoSound);
+		for i:= 0 to 32 do
+			begin
+			dX:= AngleCos(i * 64) * _0_5 * (GetRandom + _1);
+			dY:= AngleSin(i * 64) * _0_5 * (GetRandom + _1);
+			AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtFlame, 0, dX, dY, 0);
+			AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtFlame, 0, dX, -dY, 0);
+			end;
+		DeleteGear(Gear);
+		AfterAttack;
+		TurnTimeLeft:= 0;
+		CurAmmoGear:= nil;
+		HHGear^.Message:= 0;
+		ParseCommand('/taunt '#1, true);
+		exit;
+		end;
 
-// pickup bonuses
-t:= CheckGearNear(Gear, gtCase, 36, 36);
-if t <> nil then
-	PickUp(HHGear, t);
-
+	end;
+		
 CheckCollision(Gear);
 
 if ((Gear^.State and gstCollision) <> 0)
 	or CheckGearDrowning(Gear) then
 	begin
+	if ((TrainingFlags and tfRCPlane) <> 0) and ((TrainingFlags and tfTimeTrial) <> 0 ) and (TimeTrialStopTime = 0) then TimeTrialStopTime:= RealTicks;
 	StopSound(sndRCPlane);
 	StopSound(sndRideOfTheValkyries);
 	ResumeMusic;
--- a/hedgewars/HHHandlers.inc	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/HHHandlers.inc	Mon Oct 12 16:44:30 2009 +0000
@@ -43,14 +43,14 @@
 			inc(i);
 			TryDo(i < 2, 'Engine bug: no ammo in current slot', true)
 			end;
-		until (Ammo^[slot, CurAmmo].Count > 0) and (Team^.Clan^.TurnNumber > Ammoz[Ammo^[slot, CurAmmo].AmmoType].SkipTurns)
+		until (Ammo^[slot, CurAmmo].Count > 0) and ((Team^.Clan^.TurnNumber > Ammoz[Ammo^[slot, CurAmmo].AmmoType].SkipTurns) or ((TrainingFlags and tfIgnoreDelays) <> 0))
 		end else
 		begin
 		i:= 0;
 		// check whether there is ammo in slot
 		while (i <= cMaxSlotAmmoIndex)
 		  and ((Ammo^[slot, i].Count = 0)
-		       or (Team^.Clan^.TurnNumber <= Ammoz[Ammo^[slot, i].AmmoType].SkipTurns)) do inc(i);
+		       or ((Team^.Clan^.TurnNumber <= Ammoz[Ammo^[slot, i].AmmoType].SkipTurns) and ((TrainingFlags and tfIgnoreDelays) = 0))) do inc(i);
 
 		if i <= cMaxSlotAmmoIndex then
 			begin
@@ -67,7 +67,7 @@
 begin
 weap:= TAmmoType(Gear^.MsgParam);
 
-if PHedgehog(Gear^.Hedgehog)^.Team^.Clan^.TurnNumber <= Ammoz[weap].SkipTurns then exit; // weapon is not activated yet
+if (PHedgehog(Gear^.Hedgehog)^.Team^.Clan^.TurnNumber <= Ammoz[weap].SkipTurns) and ((TrainingFlags and tfIgnoreDelays) = 0) then exit; // weapon is not activated yet
 
 Gear^.MsgParam:= Ammoz[weap].Slot;
 
@@ -164,7 +164,7 @@
                                  PlaySound(sndWhipCrack, false, nil)
                                  end;
                   amBaseballBat: begin
-								 CurAmmoGear:= AddGear(hwRound(X) + hwSign(dX) * 10, hwRound(Y), gtShover, 0, xx * _0_5, yy * _0_5, 0);
+								 CurAmmoGear:= AddGear(hwRound(X) + hwSign(dX) * 10, hwRound(Y), gtShover, gsttmpFlag, xx * _0_5, yy * _0_5, 0);
 								 PlaySound(sndBaseballBat, false, nil);
 								 end;
                     amParachute: CurAmmoGear:= AddGear(hwRound(X), hwRound(Y), gtParachute, 0, _0, _0, 0);
@@ -297,6 +297,7 @@
 procedure PickUp(HH, Gear: PGear);
 var s: shortstring;
     a: TAmmoType;
+	i: Integer;
 begin
 Gear^.Message:= gm_Destroy;
 case Gear^.Pos of
@@ -318,7 +319,14 @@
                     s:= '+' + s;
                     AddCaption(s, PHedgehog(HH^.Hedgehog)^.Team^.Clan^.Color, capgrpAmmoinfo);
                     RenderHealth(PHedgehog(HH^.Hedgehog)^);
-                    RecountTeamHealth(PHedgehog(HH^.Hedgehog)^.Team)
+                    RecountTeamHealth(PHedgehog(HH^.Hedgehog)^.Team);
+
+					i:= 0;
+					while i < Gear^.Health do
+						begin
+						AddVisualGear(hwRound(HH^.X), hwRound(HH^.Y), vgtHealth);
+						inc(i, 5);
+						end;
                     end;
      end
 end;
@@ -543,7 +551,22 @@
       end;
    CheckGearDrowning(Gear);
    if (Gear^.State and gstDrowning) <> 0 then isCursorVisible:= false
-   end
+   end;
+
+if (hwAbs(Gear^.dY) > _0) and (Gear^.FlightTime > 0) then
+    begin
+    inc(Gear^.FlightTime, 1);
+	if Gear^.FlightTime = 2000 then
+		begin
+		AddCaption(GetEventString(eidHomerun), $FFFFFF, capgrpMessage);
+		PlaySound(sndHomerun, false, nil)
+		end;
+	end
+else
+    begin
+    Gear^.FlightTime:= 0;
+	end;
+
 end;
 
 procedure doStepHedgehogDriven(Gear: PGear);
--- a/hedgewars/SDLh.pas	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/SDLh.pas	Mon Oct 12 16:44:30 2009 +0000
@@ -63,6 +63,7 @@
 	SDL_SRCALPHA    = $00010000;
 	SDL_INIT_VIDEO  = $00000020;
 	SDL_INIT_AUDIO  = $00000010;
+	SDL_INIT_JOYSTICK = $00000200;
 
 	SDL_APPINPUTFOCUS=$00000010;
 
@@ -96,8 +97,8 @@
 	SDL_NOEVENT     = 0;
 	SDL_KEYDOWN     = 2;
 	SDL_KEYUP       = 3;
-	SDL_QUITEV      = 12;
-	SDL_VIDEORESIZE = 16;
+	//SDL_QUITEV      = 12;
+	SDL_VIDEORESIZE = 16; // TODO: outdated? no longer in SDL 1.3?
 
 {$IFDEF SDL13}
         SDL_WINDOWEVENT = 1;
@@ -106,12 +107,22 @@
         SDL_MOUSEBUTTONDOWN = 6;
 	SDL_MOUSEBUTTONUP   = 7;
         SDL_MOUSEWHEEL = 8;  //different handling, should create SDL_MouseWheelEvent type
+	SDL_JOYAXIS = 10;
+	SDL_JOYHAT = 12;
+	SDL_JOYBUTTONDOWN = 13;
+	SDL_JOYBUTTONUP = 14;
+	SDL_QUITEV      = 15;
 {$ELSE}
         SDL_ACTIVEEVENT = 1;
        	SDL_MOUSEBUTTONDOWN = 5;
 	SDL_MOUSEBUTTONUP   = 6;
         SDL_BUTTON_WHEELDUP = 4;
 	SDL_BUTTON_WHEELDOWN = 5;
+	SDL_JOYAXIS = 7;
+	SDL_JOYHAT = 9;
+	SDL_JOYBUTTONDOWN = 10;
+	SDL_JOYBUTTONUP = 11;
+	SDL_QUITEV      = 12;
 {$ENDIF}
 {*end sdl_event binding*}
 
@@ -283,7 +294,28 @@
 			w, h: LongInt;
 			end;
 
-     PSDL_Event = ^TSDL_Event;
+     TSDL_JoyAxisEvent = record
+			type_: Byte;
+			which: Byte;
+			axis: Byte;
+			value: LongInt;
+			end;
+			
+     TSDL_JoyHatEvent = record
+			type_: Byte;
+			which: Byte;
+			hat: Byte;
+			value: Byte;
+			end;
+	
+     TSDL_JoyButtonEvent = record
+			type_: Byte;
+			which: Byte;
+			button: Byte;
+			state: Byte;
+			end;
+	 
+	 PSDL_Event = ^TSDL_Event;
      TSDL_Event = record
                   case Byte of
 {$IFDEF SDL13}
@@ -306,6 +338,10 @@
                        SDL_MOUSEBUTTONDOWN,
                        SDL_MOUSEBUTTONUP: (button: TSDL_MouseButtonEvent);
 {$ENDIF}
+						SDL_JOYAXIS: (jaxis: TSDL_JoyAxisEvent);
+						SDL_JOYHAT: (jhat: TSDL_JoyHatEvent);
+						SDL_JOYBUTTONDOWN,
+						SDL_JOYBUTTONUP: (jbutton: TSDL_JoyButtonEvent);
      end;
 
      PByteArray = ^TByteArray;
@@ -365,6 +401,7 @@
 function  SDL_GetKeyName(key: Longword): PChar; cdecl; external SDLLibName;
 procedure SDL_WarpMouse(x, y: Word); cdecl; external SDLLibName;
 
+procedure SDL_PumpEvents; cdecl; external SDLLibName;
 function  SDL_PollEvent(event: PSDL_Event): LongInt; cdecl; external SDLLibName;
 
 function  SDL_ShowCursor(toggle: LongInt): LongInt; cdecl; external SDLLibName;
@@ -490,6 +527,37 @@
 function  SDLNet_Read16(buf: pointer): Word;
 function  SDLNet_Read32(buf: pointer): LongWord;
 
+// Joystick/Controller support
+type PSDLJoystick = ^TSDLJoystick;
+     TSDLJoystick = record
+	                end;
+
+function SDL_NumJoysticks: LongInt; cdecl; external SDLLibName;
+function SDL_JoystickName(idx: LongInt): PChar; cdecl; external SDLLibName;
+function SDL_JoystickOpen(idx: LongInt): PSDLJoystick; cdecl; external SDLLibName;
+function SDL_JoystickOpened(idx: LongInt): LongInt; cdecl; external SDLLibName;
+function SDL_JoystickIndex(joy: PSDLJoystick): LongInt; cdecl; external SDLLibName;
+function SDL_JoystickNumAxes(joy: PSDLJoystick): LongInt; cdecl; external SDLLibName;
+function SDL_JoystickNumBalls(joy: PSDLJoystick): LongInt; cdecl; external SDLLibName;
+function SDL_JoystickNumHats(joy: PSDLJoystick): LongInt; cdecl; external SDLLibName;
+function SDL_JoystickNumButtons(joy: PSDLJoystick): LongInt; cdecl; external SDLLibName;
+procedure SDL_JoystickUpdate; cdecl; external SDLLibName;
+function SDL_JoystickEventState(state: LongInt): LongInt; cdecl; external SDLLibName;
+function SDL_JoystickGetAxis(joy: PSDLJoystick; axis: LongInt): Word; cdecl; external SDLLibName;
+const SDL_HAT_CENTERED  = $00;
+      SDL_HAT_UP        = $01;
+	  SDL_HAT_RIGHT     = $02;
+	  SDL_HAT_DOWN      = $04;
+	  SDL_HAT_LEFT      = $08;
+	  SDL_HAT_RIGHTUP   = SDL_HAT_RIGHT or SDL_HAT_UP;
+	  SDL_HAT_RIGHTDOWN = SDL_HAT_RIGHT or SDL_HAT_DOWN;
+	  SDL_HAT_LEFTUP    = SDL_HAT_LEFT or SDL_HAT_UP;
+	  SDL_HAT_LEFTDOWN  = SDL_HAT_LEFT or SDL_HAT_DOWN;
+function SDL_JoystickGetBall(joy: PSDLJoystick; ball: LongInt; dx: PInteger; dy: PInteger): Word; cdecl; external SDLLibName;
+function SDL_JoystickGetHat(joy: PSDLJoystick; hat: LongInt): Byte; cdecl; external SDLLibName;
+function SDL_JoystickGetButton(joy: PSDLJoystick; button: LongInt): Byte; cdecl; external SDLLibName;
+procedure SDL_JoystickClose(joy: PSDLJoystick); cdecl; external SDLLibName;
+
 implementation
 
 function SDL_MustLock(Surface: PSDL_Surface): Boolean;
--- a/hedgewars/hwengine.pas	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/hwengine.pas	Mon Oct 12 16:44:30 2009 +0000
@@ -89,7 +89,7 @@
 			AssignHHCoords;
 			AddMiscGears;
 			StoreLoad;
-            InitWorld;
+			InitWorld;
 			ResetKbd;
 			SoundLoad;
 			if GameType = gmtSave then
@@ -182,6 +182,10 @@
 		SDL_MOUSEBUTTONDOWN: if event.button.button = SDL_BUTTON_WHEELDOWN then uKeys.wheelDown:= true;
 		SDL_MOUSEBUTTONUP: if event.button.button = SDL_BUTTON_WHEELDUP then uKeys.wheelUp:= true;
 {$ENDIF}
+		SDL_JOYAXIS: ControllerAxisEvent(event.jaxis.which, event.jaxis.axis, event.jaxis.value);
+		SDL_JOYHAT: ControllerHatEvent(event.jhat.which, event.jhat.hat, event.jhat.value);
+		SDL_JOYBUTTONDOWN: ControllerButtonEvent(event.jbutton.which, event.jbutton.button, true);
+		SDL_JOYBUTTONUP: ControllerButtonEvent(event.jbutton.which, event.jbutton.button, false);
 		SDL_QUITEV: isTerminated:= true
 		end;
 CurrTime:= SDL_GetTicks;
@@ -388,7 +392,7 @@
 var s: shortstring;
 begin
 WriteToConsole('Init SDL... ');
-SDLTry(SDL_Init(SDL_INIT_VIDEO) >= 0, true);
+SDLTry(SDL_Init(SDL_INIT_VIDEO or SDL_INIT_JOYSTICK) >= 0, true);
 WriteLnToConsole(msgOK);
 
 SDL_EnableUNICODE(1);
@@ -399,6 +403,7 @@
 
 ShowMainWindow;
 
+ControllerInit; // has to happen before InitKbdKeyTable to map keys
 InitKbdKeyTable;
 
 if recordFileName = '' then InitIPC;
@@ -431,7 +436,8 @@
       'Some parameters not set (flags = ' + inttostr(InitStepsFlags) + ')',
       true);
 
-MainLoop
+MainLoop;
+ControllerClose
 end;
 
 /////////////////////////
--- a/hedgewars/uConsole.pas	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/uConsole.pas	Mon Oct 12 16:44:30 2009 +0000
@@ -244,6 +244,7 @@
 RegisterVariable('damagepct', vtLongInt, @cDamagePercent, false);
 RegisterVariable('landadds', vtLongInt, @cLandAdditions , false);
 RegisterVariable('gmflags' , vtLongInt, @GameFlags      , false);
+RegisterVariable('trflags' , vtLongInt, @TrainingFlags  , false);
 RegisterVariable('turntime', vtLongInt, @cHedgehogTurnTime, false);
 RegisterVariable('minestime', vtLongInt, @cMinesTime, false);
 RegisterVariable('fort'    , vtCommand, @chFort         , false);
@@ -297,6 +298,14 @@
 RegisterVariable('-voldown', vtCommand, @chVol_p        , true );
 RegisterVariable('findhh'  , vtCommand, @chFindhh       , true );
 RegisterVariable('pause'   , vtCommand, @chPause        , true );
+RegisterVariable('+cur_u'   , vtCommand, @chCurU_p      , true );
+RegisterVariable('-cur_u'   , vtCommand, @chCurU_m      , true );
+RegisterVariable('+cur_d'   , vtCommand, @chCurD_p      , true );
+RegisterVariable('-cur_d'   , vtCommand, @chCurD_m      , true );
+RegisterVariable('+cur_l'   , vtCommand, @chCurL_p      , true );
+RegisterVariable('-cur_l'   , vtCommand, @chCurL_m      , true );
+RegisterVariable('+cur_r'   , vtCommand, @chCurR_p      , true );
+RegisterVariable('-cur_r'   , vtCommand, @chCurR_m      , true );
 
 finalization
 FreeVariablesList
--- a/hedgewars/uConsts.pas	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/uConsts.pas	Mon Oct 12 16:44:30 2009 +0000
@@ -64,7 +64,7 @@
             sprSpeechCorner, sprSpeechEdge, sprSpeechTail,
             sprThoughtCorner, sprThoughtEdge, sprThoughtTail,
             sprShoutCorner, sprShoutEdge, sprShoutTail,
-            sprSniperRifle, sprBubbles, sprJetpack);
+            sprSniperRifle, sprBubbles, sprJetpack, sprHealth);
 
 	TGearType = (gtAmmo_Bomb, gtHedgehog, gtAmmo_Grenade, gtHealthTag, // 3
 			gtGrave, gtUFO, gtShotgunShot, gtPickHammer, gtRope, // 8
@@ -79,7 +79,7 @@
 
 	TVisualGearType = (vgtFlake, vgtCloud, vgtExplPart, vgtExplPart2, vgtFire,
 			vgtSmallDamageTag, vgtTeamHealthSorter, vgtSpeechBubble, vgtBubble,
-			vgtSteam);
+			vgtSteam, vgtHealth);
 
 	TGearsType = set of TGearType;
 
@@ -94,7 +94,8 @@
 			sndFirePunch3, sndFirePunch4, sndFirePunch5, sndFirePunch6,
 			sndMelon, sndHellish, sndYoohoo, sndRCPlane, sndWhipCrack,
 			sndRideOfTheValkyries, sndDenied, sndPlaced, sndBaseballBat,
-			sndVaporize, sndWarp, sndSuddenDeath, sndMortar, sndShutter);
+			sndVaporize, sndWarp, sndSuddenDeath, sndMortar, sndShutter,
+			sndHomerun);
 
 	TAmmoType  = (amNothing, amGrenade, amClusterBomb, amBazooka, amUFO, amShotgun, amPickHammer,
 			amSkip, amRope, amMine, amDEagle, amDynamite, amFirePunch, amWhip,
@@ -244,6 +245,13 @@
 
 	cSendEmptyPacketTime = 1000;
 
+	// Training Flags
+	tfNone         = $00000000;
+	tfTimeTrial    = $00000001;
+	tfRCPlane      = $00000002;
+	tfSpawnTargets = $00000004;
+	tfIgnoreDelays = $00000008;
+	
 	gfForts        = $00000001;
 	gfMultiWeapon  = $00000002;
 	gfSolidLand    = $00000004;
@@ -604,7 +612,9 @@
 			(FileName:    'Bubbles'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
 			Width:  16; Height: 16; imageWidth: 0; imageHeight: 0; saveSurf: false), // sprBubbles
 			(FileName:  'amJetpack'; Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
-			Width: 64; Height: 64; imageWidth: 0; imageHeight: 0; saveSurf: false) // sprJetpack
+			Width: 64; Height: 64; imageWidth: 0; imageHeight: 0; saveSurf: false), // sprJetpack
+			(FileName:  'Health'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+			Width: 16; Height: 16; imageWidth: 0; imageHeight: 0; saveSurf: false) // sprHealth
 			);
 
 	Wavez: array [TWave] of record
@@ -682,7 +692,8 @@
 			(FileName:                 'warp.ogg'; Path: ptSounds),// sndWarp
 			(FileName:          'suddendeath.ogg'; Path: ptSounds),// sndSuddenDeath
 			(FileName:               'mortar.ogg'; Path: ptSounds),// sndMortar
-			(FileName:         'shutterclick.ogg'; Path: ptSounds) // sndShutter
+			(FileName:         'shutterclick.ogg'; Path: ptSounds),// sndShutter			
+			(FileName:              'homerun.ogg'; Path: ptSounds) // sndHomerun
 			);
 
 	Ammoz: array [TAmmoType] of record
--- a/hedgewars/uGears.pas	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/uGears.pas	Mon Oct 12 16:44:30 2009 +0000
@@ -54,6 +54,7 @@
 			Z: Longword;
 			IntersectGear: PGear;
 			TriggerId: Longword;
+			FlightTime: Longword;
 			uid: Longword
 			end;
 
@@ -76,6 +77,7 @@
     SuddenDeathDmg: Boolean = false;
     SpeechType: Longword = 1;
     SpeechText: shortstring;
+	TrainingTargetGear: PGear = nil;
 
 implementation
 uses uWorld, uMisc, uStore, uConsole, uSound, uTeams, uRandom, uCollisions,
@@ -224,6 +226,7 @@
 Result^.CollisionIndex:= -1;
 Result^.Timer:= Timer;
 Result^.Z:= cUsualZ;
+Result^.FlightTime:= 0;
 Result^.uid:= Counter;
 
 if CurrentTeam <> nil then
@@ -697,7 +700,8 @@
 
 procedure ApplyDamage(Gear: PGear; Damage: Longword);
 var s: shortstring;
-    vampDmg, tmpDmg: Longword;
+    vampDmg, tmpDmg, i: Longword;
+	vg: PVisualGear;
 begin
 	if (Gear^.Kind = gtHedgehog) and (Damage>=1) then
     begin
@@ -718,6 +722,13 @@
                 AddCaption(s, CurrentHedgehog^.Team^.Clan^.Color, capgrpAmmoinfo);
                 RenderHealth(CurrentHedgehog^);
                 RecountTeamHealth(CurrentHedgehog^.Team);
+				i:= 0;
+				while i < vampDmg do
+					begin
+					vg:= AddVisualGear(hwRound(CurrentHedgehog^.Gear^.X), hwRound(CurrentHedgehog^.Gear^.Y), vgtHealth);
+					if vg <> nil then vg^.Frame:= 10;
+					inc(i, 5);
+					end;
                 end
             end;
         if ((GameFlags and gfKarma) <> 0) and
@@ -1249,13 +1260,17 @@
 
 if Gear^.Invulnerable then
     begin
-    DrawSprite(sprInvulnerable, sx - 24, sy - 24, 0);
+    glColor4f(1, 1, 1, 0.25 + abs(1 - ((RealTicks div 2) mod 1500) / 750));
+	DrawSprite(sprInvulnerable, sx - 24, sy - 24, 0);
+	glColor4f(1, 1, 1, 1);
     end;
 if cVampiric and
    (CurrentHedgehog^.Gear <> nil) and
    (CurrentHedgehog^.Gear = Gear) then
     begin
+    glColor4f(1, 1, 1, 0.25 + abs(1 - (RealTicks mod 1500) / 750));
     DrawSprite(sprVampiric, sx - 24, sy - 24, 0);
+	glColor4f(1, 1, 1, 1);
     end;
 end;
 
@@ -1402,11 +1417,14 @@
 	case Gear^.Kind of
        gtAmmo_Bomb: DrawRotated(sprBomb, hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, 0, Gear^.DirAngle);
 
-       gtRCPlane: if (Gear^.Tag = -1) then
+       gtRCPlane: begin
+                  if (Gear^.Tag = -1) then
                      DrawRotated(sprPlane, hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, -1,  DxDy2Angle(Gear^.dX, Gear^.dY) + 90)
                   else
                      DrawRotated(sprPlane, hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy,0,DxDy2Angle(Gear^.dY, Gear^.dX));
-
+                  if ((TrainingFlags and tfRCPlane) <> 0) and (TrainingTargetGear <> nil) and ((Gear^.State and gstDrowning) = 0) then
+					 DrawRotatedf(sprFinger, hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, GameTicks div 32 mod 16, 0, DxDy2Angle(Gear^.X - TrainingTargetGear^.X, TrainingTargetGear^.Y - Gear^.Y));
+                  end;
        gtBall: DrawRotatedf(sprBalls, hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, Gear^.Tag,0, DxDy2Angle(Gear^.dY, Gear^.dX));
 
        gtDrill: DrawRotated(sprDrill, hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, 0, DxDy2Angle(Gear^.dY, Gear^.dX));
@@ -1496,6 +1514,12 @@
 begin
 AddGear(0, 0, gtATStartGame, 0, _0, _0, 2000);
 
+if (TrainingFlags and tfSpawnTargets) <> 0 then
+	begin
+	TrainingTargetGear:= AddGear(0, 0, gtTarget, 0, _0, _0, 0);
+	FindPlace(TrainingTargetGear, false, 0, LAND_WIDTH);
+	end;
+
 if ((GameFlags and gfForts) = 0) and ((GameFlags and gfMines) <> 0) then
 	for i:= 0 to Pred(cLandAdditions) do
 		begin
@@ -1642,9 +1666,12 @@
 while i > 0 do
 	begin
 	dec(i);
-    Gear:= t^.ar[i];
-    if (Gear^.State and gstNoDamage) = 0 then
-        case Gear^.Kind of
+	Gear:= t^.ar[i];
+	if (Gear^.State and gstNoDamage) = 0 then
+		begin
+		if (Gear^.Kind = gtHedgehog) and (Ammo^.State and gsttmpFlag <> 0) then Gear^.FlightTime:= 1;
+		
+		case Gear^.Kind of
 			gtHedgehog,
 			gtMine,
 			gtTarget,
@@ -1675,6 +1702,7 @@
 					FollowGear:= Gear
 					end;
 		end
+		end;
 	end;
 if i <> 0 then SetAllToActive
 end;
@@ -1977,4 +2005,4 @@
 finalization
 FreeGearsList;
 
-end.
+end.
\ No newline at end of file
--- a/hedgewars/uKeys.pas	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/uKeys.pas	Mon Oct 12 16:44:30 2009 +0000
@@ -32,10 +32,27 @@
 procedure SetBinds(var binds: TBinds);
 procedure SetDefaultBinds;
 
-var KbdKeyPressed: boolean;
+procedure ControllerInit;
+procedure ControllerClose;
+procedure ControllerAxisEvent(joy, axis: Byte; value: Integer);
+procedure ControllerHatEvent(joy, hat, value: Byte);
+procedure ControllerButtonEvent(joy, button: Byte; pressed: Boolean);
+
+var hideAmmoMenu: boolean;
 	wheelUp: boolean = false;
 	wheelDown: boolean = false;
 
+	ControllerNumControllers: Integer;
+    ControllerEnabled: Integer;
+    ControllerNumAxes: array[0..5] of Integer;
+    //ControllerNumBalls: array[0..5] of Integer;
+	ControllerNumHats: array[0..5] of Integer;
+	ControllerNumButtons: array[0..5] of Integer;
+	ControllerAxes: array[0..5] of array[0..19] of Integer;
+	//ControllerBalls: array[0..5] of array[0..19] of array[0..1] of Integer;
+	ControllerHats: array[0..5] of array[0..19] of Byte;
+	ControllerButtons: array[0..5] of array[0..19] of Byte;
+
 implementation
 uses SDLh, uTeams, uConsole, uMisc, uStore;
 const KeyNumber = 1024;
@@ -53,17 +70,22 @@
 KeyNameToCode:= Result
 end;
 
+
 procedure ProcessKbd;
-var  i: LongInt;
-     s: shortstring;
+var  i, j, k: LongInt;
      pkbd: PByteArray;
      Trusted: boolean;
+     s: shortstring;
 begin
-KbdKeyPressed:= false;
+
+hideAmmoMenu:= false;
 Trusted:= (CurrentTeam <> nil)
           and (not CurrentTeam^.ExtDriven)
           and (CurrentHedgehog^.BotLevel = 0);
 
+// move cursor/camera
+// TODO: Scale on screen dimensions and/or axis value (game controller)?
+movecursor(5 * CursorMovementX, 5 * CursorMovementY);
 {$IFDEF SDL13}
 pkbd := SDL_GetKeyboardState(nil);
 i    := SDL_GetMouseState(0, nil, nil);
@@ -71,7 +93,6 @@
 pkbd := SDL_GetKeyState(nil);
 i    := SDL_GetMouseState(nil, nil);
 {$ENDIF}
-
 // mouse buttons
 {$IFDEF DARWIN}
 pkbd^[1]:= ((i and 1) and not (pkbd^[306] or pkbd^[305]));
@@ -88,11 +109,36 @@
 wheelUp:= false;
 wheelDown:= false;
 
+// Controller(s)
+k:= 500; // should we test k for hitting the limit? sounds rather unlikely to ever reach it
+for j:= 0 to Pred(ControllerNumControllers) do
+	begin
+	for i:= 0 to Pred(ControllerNumAxes[j]) do
+		begin
+		if ControllerAxes[j][i] > 20000 then pkbd^[k + 0]:= 1 else pkbd^[k + 0]:= 0;
+		if ControllerAxes[j][i] < -20000 then pkbd^[k + 1]:= 1 else pkbd^[k + 1]:= 0;
+		inc(k, 2);
+		end;
+	for i:= 0 to Pred(ControllerNumHats[j]) do
+		begin
+		pkbd^[k + 0]:= ControllerHats[j][i] and SDL_HAT_UP;
+		pkbd^[k + 1]:= ControllerHats[j][i] and SDL_HAT_RIGHT;
+		pkbd^[k + 2]:= ControllerHats[j][i] and SDL_HAT_DOWN;
+		pkbd^[k + 3]:= ControllerHats[j][i] and SDL_HAT_LEFT;
+		inc(k, 4);
+		end;
+	for i:= 0 to Pred(ControllerNumButtons[j]) do
+		begin
+		pkbd^[k]:= ControllerButtons[j][i];
+		inc(k, 1);
+		end;
+	end;
+
 // now process strokes
 for i:= 1 to cKeyMaxIndex do
 if CurrentBinds[i][0] <> #0 then
 	begin
-	if (i > 3) and (pkbd^[i] <> 0) then KbdKeyPressed:= true;
+	if (i > 3) and (pkbd^[i] <> 0) and not (hideAmmoMenu or (CurrentBinds[i] = 'put') or (CurrentBinds[i] = 'ammomenu') or (CurrentBinds[i] = '+cur_u') or (CurrentBinds[i] = '+cur_d') or (CurrentBinds[i] = '+cur_l') or (CurrentBinds[i] = '+cur_r')) then hideAmmoMenu:= true;
 	if (tkbd[i] = 0) and (pkbd^[i] <> 0) then ParseCommand(CurrentBinds[i], Trusted)
 	else if (CurrentBinds[i][1] = '+')
 			and (pkbd^[i] = 0)
@@ -103,11 +149,11 @@
 			ParseCommand(s, Trusted)
 			end;
 	tkbd[i]:= pkbd^[i]
-	end
+	end;
 end;
 
 procedure ResetKbd;
-var i, t: LongInt;
+var i, j, k, t: LongInt;
     pkbd: PByteArray;
 begin
 
@@ -118,12 +164,36 @@
 {$ENDIF}
 TryDo(i < cKeyMaxIndex, 'SDL keys number is more than expected (' + inttostr(i) + ')', true);
 
+k:= 500;
+for j:= 0 to Pred(ControllerNumControllers) do
+	begin
+	for i:= 0 to Pred(ControllerNumAxes[j]) do
+		begin
+		pkbd^[k + 0]:= 0;
+		pkbd^[k + 1]:= 0;
+		inc(k, 2);
+		end;
+	for i:= 0 to Pred(ControllerNumHats[j]) do
+		begin
+		pkbd^[k + 0]:= 0;
+		pkbd^[k + 1]:= 0;
+		pkbd^[k + 2]:= 0;
+		pkbd^[k + 3]:= 0;
+		inc(k, 4);
+		end;
+	for i:= 0 to Pred(ControllerNumButtons[j]) do
+		begin
+		pkbd^[k]:= 0;
+		inc(k, 1);
+		end;
+	end;
+
 for t:= 0 to Pred(i) do
     tkbd[i]:= pkbd^[i]
 end;
 
 procedure InitKbdKeyTable;
-var i, t: LongInt;
+var i, j, k, t: LongInt;
     s: string[15];
 begin
 KeyNames[1]:= 'mousel';
@@ -144,6 +214,31 @@
        end;
     end;
 
+// Controller(s)
+k:= 500;
+for j:= 0 to Pred(ControllerNumControllers) do
+	begin
+	for i:= 0 to Pred(ControllerNumAxes[j]) do
+		begin
+		keynames[k + 0]:= 'j' + inttostr(j) + 'a' + inttostr(i) + 'u';
+		keynames[k + 1]:= 'j' + inttostr(j) + 'a' + inttostr(i) + 'd';
+		inc(k, 2);
+		end;
+	for i:= 0 to Pred(ControllerNumHats[j]) do
+		begin
+		keynames[k + 0]:= 'j' + inttostr(j) + 'h' + inttostr(i) + 'u';
+		keynames[k + 1]:= 'j' + inttostr(j) + 'h' + inttostr(i) + 'r';
+		keynames[k + 2]:= 'j' + inttostr(j) + 'h' + inttostr(i) + 'd';
+		keynames[k + 3]:= 'j' + inttostr(j) + 'h' + inttostr(i) + 'l';
+		inc(k, 4);
+		end;
+	for i:= 0 to Pred(ControllerNumButtons[j]) do
+		begin
+		keynames[k]:= 'j' + inttostr(j) + 'b' + inttostr(i);
+		inc(k, 1);
+		end;
+	end;
+	
 DefaultBinds[ 27]:= 'quit';
 DefaultBinds[ 96]:= 'history';
 DefaultBinds[127]:= 'rotmask';
@@ -182,6 +277,87 @@
 tkbd[271]:= 1
 end;
 
+var Controller: array [0..5] of PSDLJoystick;
+	
+procedure ControllerInit;
+var i, j: Integer;
+begin
+ControllerEnabled:= 0;
+ControllerNumControllers:= SDL_NumJoysticks;
+
+if ControllerNumControllers > 6 then ControllerNumControllers:= 6;
+
+WriteLnToConsole('Number of game controllers: ' + inttostr(ControllerNumControllers));
+
+if ControllerNumControllers > 0 then
+	begin
+	for j:= 0 to pred(ControllerNumControllers) do
+		begin
+		WriteLnToConsole('Using game controller: ' + SDL_JoystickName(j));
+		Controller[j]:= SDL_JoystickOpen(j);
+		if Controller[j] = nil then
+			WriteLnToConsole('* Failed to open game controller!')
+		else
+			begin
+			ControllerNumAxes[j]:= SDL_JoystickNumAxes(Controller[j]);
+			//ControllerNumBalls[j]:= SDL_JoystickNumBalls(Controller[j]);
+			ControllerNumHats[j]:= SDL_JoystickNumHats(Controller[j]);
+			ControllerNumButtons[j]:= SDL_JoystickNumButtons(Controller[j]);
+			WriteLnToConsole('* Number of axes: ' + inttostr(ControllerNumAxes[j]));
+			//WriteLnToConsole('* Number of balls: ' + inttostr(ControllerNumBalls[j]));
+			WriteLnToConsole('* Number of hats: ' + inttostr(ControllerNumHats[j]));
+			WriteLnToConsole('* Number of buttons: ' + inttostr(ControllerNumButtons[j]));
+			ControllerEnabled:= 1;
+			
+			if ControllerNumAxes[j] > 20 then ControllerNumAxes[j]:= 20;
+			//if ControllerNumBalls[j] > 20 then ControllerNumBalls[j]:= 20;
+			if ControllerNumHats[j] > 20 then ControllerNumHats[j]:= 20;
+			if ControllerNumButtons[j] > 20 then ControllerNumButtons[j]:= 20;
+			
+			// reset all buttons/axes
+			for i:= 0 to pred(ControllerNumAxes[j]) do
+				ControllerAxes[j][i]:= 0;
+			(*for i:= 0 to pred(ControllerNumBalls[j]) do
+				begin
+				ControllerBalls[j][i][0]:= 0;
+				ControllerBalls[j][i][1]:= 0;
+				end;*)
+			for i:= 0 to pred(ControllerNumHats[j]) do
+				ControllerHats[j][i]:= SDL_HAT_CENTERED;
+			for i:= 0 to pred(ControllerNumButtons[j]) do
+				ControllerButtons[j][i]:= 0;
+			end;
+		end;
+	// enable event generation/controller updating
+	SDL_JoystickEventState(1);
+	end
+else	
+	WriteLnToConsole('Not using any game controller');
+end;
+
+procedure ControllerClose;
+var j: Integer;
+begin
+if ControllerEnabled > 0 then
+	for j:= 0 to pred(ControllerNumControllers) do
+		SDL_JoystickClose(Controller[j]);
+end;
+
+procedure ControllerAxisEvent(joy, axis: Byte; value: Integer);
+begin
+	ControllerAxes[joy][axis]:= value;
+end;
+
+procedure ControllerHatEvent(joy, hat, value: Byte);
+begin
+	ControllerHats[joy][hat]:= value;
+end;
+
+procedure ControllerButtonEvent(joy, button: Byte; pressed: Boolean);
+begin
+	if pressed then ControllerButtons[joy][button]:= 1 else ControllerButtons[joy][button]:= 0;
+end;
+
 initialization
 
 end.
--- a/hedgewars/uLocale.pas	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/uLocale.pas	Mon Oct 12 16:44:30 2009 +0000
@@ -32,7 +32,8 @@
 			sidConfirm, sidSuddenDeath, sidRemaining, sidFuel, sidSync);
 
 	TEventId = (eidDied, eidDrowned, eidRoundStart, eidRoundWin, eidRoundDraw,
-			eidNewHealthPack, eidNewAmmoPack, eidNewUtilityPack, eidTurnSkipped, eidHurtSelf);
+			eidNewHealthPack, eidNewAmmoPack, eidNewUtilityPack, eidTurnSkipped, eidHurtSelf,
+			eidHomerun);
 
 const MAX_EVENT_STRINGS = 100;
 var trammo: array[TAmmoStrId] of string;
--- a/hedgewars/uMisc.pas	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/uMisc.pas	Mon Oct 12 16:44:30 2009 +0000
@@ -43,6 +43,7 @@
 	GameState     : TGameState = Low(TGameState);
 	GameType      : TGameType = gmtLocal;
 	GameFlags     : Longword = 0;
+	TrainingFlags : Longword = 0;
 	TurnTimeLeft  : Longword = 0;
 	cSuddenDTurns : LongInt = 15;
 	cDamagePercent : LongInt = 100;
@@ -71,7 +72,14 @@
 	cAltDamage       : boolean = true;
 
 	GameTicks     : LongWord = 0;
+	TrainingTimeInc: Longword = 10000;
+	TrainingTimeInD: Longword = 250;
+	TrainingTimeInM: Longword = 5000;
+	TrainingTimeMax: Longword = 90000;
 
+	TimeTrialStartTime: Longword = 0;
+	TimeTrialStopTime: Longword = 0;
+	
 	cSkyColor     : Longword = 0;
 	cWhiteColor   : Longword = $FFFFFFFF;
 	cColorNearBlack       : Longword = $FF000010;
@@ -102,6 +110,8 @@
 var
 	cSendCursorPosTime   : LongWord = 50;
 	ShowCrosshair : boolean;
+	CursorMovementX : Integer = 0;
+	CursorMovementY : Integer = 0;
 	cDrownSpeed,
 	cMaxWindSpeed,
 	cWindSpeed,
@@ -127,6 +137,7 @@
 
 var WaterColorArray: array[0..3] of HwColor4f;
 
+procedure movecursor(dx, dy: Integer);
 function hwSign(r: hwFloat): LongInt;
 function Min(a, b: LongInt): LongInt;
 function Max(a, b: LongInt): LongInt;
@@ -169,6 +180,20 @@
 var f: textfile;
 {$ENDIF}
 
+procedure movecursor(dx, dy: Integer);
+var x, y: LongInt;
+begin
+if (dx = 0) and (dy = 0) then exit;
+{$IFDEF SDL13}
+SDL_GetMouseState(0, @x, @y);
+{$ELSE}
+SDL_GetMouseState(@x, @y);
+{$ENDIF}
+Inc(x, dx);
+Inc(y, dy);
+SDL_WarpMouse(x, y);
+end;
+
 function hwSign(r: hwFloat): LongInt;
 begin
 // yes, we have negative zero for a reason
@@ -388,7 +413,7 @@
 if SDL_MustLock(surf) then
    SDLTry(SDL_LockSurface(surf) >= 0, true);
 
-if not (isPowerOf2(Surf^.w) and isPowerOf2(Surf^.h)) then
+if (not SupportNPOTT) and (not (isPowerOf2(Surf^.w) and isPowerOf2(Surf^.h))) then
 	begin
 	tw:= toPowerOf2(Surf^.w);
 	th:= toPowerOf2(Surf^.h);
--- a/hedgewars/uStore.pas	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/uStore.pas	Mon Oct 12 16:44:30 2009 +0000
@@ -22,7 +22,7 @@
 {$IFDEF GLES11}
 	gles11,
 {$ELSE}
-	GL,
+	GL, GLext,
 {$ENDIF}
 uFloat;
 {$INCLUDE options.inc}
@@ -64,6 +64,7 @@
    SyncTexture,
    ConfirmTexture: PTexture;
    cScaleFactor: GLfloat = 2.0;
+   SupportNPOTT: Boolean = false;
 
 implementation
 uses uMisc, uConsole, uLand, uLocale, uWorld;
@@ -347,7 +348,7 @@
     _l, _r, _t, _b: real;
     VertexBuffer, TextureBuffer: array [0..3] of TVertex2f;
 begin
-if SourceTexture^.h = 0 then exit;
+if (SourceTexture^.h = 0) or (SourceTexture^.w = 0) then exit;
 rr.x:= X;
 rr.y:= Y;
 rr.w:= r^.w;
@@ -967,32 +968,56 @@
 LoadImage:= tmpsurf //Result
 end;
 
+function glLoadExtension(extension : string) : boolean;
+begin
+	glLoadExtension:= glext_LoadExtension(extension);
+	if not glLoadExtension then
+		WriteLnToConsole('OpenGL: "' + extension + '" failed to load')
+	else
+		WriteLnToConsole('OpenGL: "' + extension + '" loaded');
+end;
+
 procedure SetupOpenGL;
 begin
-SetScale(2.0);
-
-glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-glMatrixMode(GL_MODELVIEW);
-
 glGetIntegerv(GL_MAX_TEXTURE_SIZE, @MaxTextureSize);
 {$IFDEF DEBUGFILE}
 AddFileLog('GL_MAX_TEXTURE_SIZE: ' + inttostr(MaxTextureSize));
 {$ENDIF}
 
+{$IFNDEF GLES11}
+SupportNPOTT:= glLoadExtension('GL_ARB_texture_non_power_of_two');
+{$ENDIF}
+
+// set view port to whole window
+glViewport(0, 0, cScreenWidth, cScreenHeight);
+
+glMatrixMode(GL_MODELVIEW);
+// prepare default translation/scaling
+glLoadIdentity;
+glScalef(2.0 / cScreenWidth, -2.0 / cScreenHeight, 1.0);
+glTranslatef(0, -cScreenHeight / 2, 0);
+
+// enable alpha blending
+glEnable(GL_BLEND);
+glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 end;
 
 procedure SetScale(f: GLfloat);
 begin
-cScaleFactor:= f;
+// leave immediately if scale factor didn't change
+if f = cScaleFactor then exit;
 
-glLoadIdentity;
-glViewport(0, 0, cScreenWidth, cScreenHeight);
-glScalef(f / cScreenWidth, -f / cScreenHeight, 1.0);
-//glTranslatef(-cScreenWidth / 2, -cScreenHeight / 2, 0);
-glTranslatef(0, -cScreenHeight / 2, 0);
+if f = 2.0 then // default scaling
+	glPopMatrix // "return" to default scaling
+else // other scaling
+	begin
+	glPushMatrix; // save default scaling
+	glLoadIdentity;
+	glScalef(f / cScreenWidth, -f / cScreenHeight, 1.0);
+	glTranslatef(0, -cScreenHeight / 2, 0);
+	end;
 
-// glTranslatex(320, 0, 0);
-// glRotatef(90.0, 0.0, 0.0, 1.0);
+cScaleFactor:= f;
 end;
 
 ////////////////////////////////////////////////////////////////////////////////
--- a/hedgewars/uVisualGears.pas	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/uVisualGears.pas	Mon Oct 12 16:44:30 2009 +0000
@@ -173,6 +173,17 @@
 		dec(Gear^.FrameTicks, Steps)
 end;
 
+procedure doStepHealth(Gear: PVisualGear; Steps: Longword);
+begin
+Gear^.X:= Gear^.X + Gear^.dX * Steps;
+Gear^.Y:= Gear^.Y - Gear^.dY * Steps;
+
+if Gear^.FrameTicks <= Steps then
+	DeleteVisualGear(Gear)
+else
+	dec(Gear^.FrameTicks, Steps);
+end;
+
 procedure doStepSteam(Gear: PVisualGear; Steps: Longword);
 begin
 	Gear^.X:= Gear^.X + (cWindSpeed * 100 + Gear^.dX) * Steps;
@@ -319,7 +330,8 @@
 			@doStepTeamHealthSorter,
 			@doStepSpeechBubble,
 			@doStepBubble,
-			@doStepSteam
+			@doStepSteam,
+			@doStepHealth
 		);
 
 function  AddVisualGear(X, Y: LongInt; Kind: TVisualGearType): PVisualGear;
@@ -395,25 +407,28 @@
 				Result^.FrameTicks:= 1100
 				end;
 	vgtBubble: begin
-				t:= random(1024);
-				sp:= _0_001 * (random(85) + 95);
 				dx.isNegative:= random(2) = 0;
 				dx.QWordValue:= random(100000000);
-				dy:= sp;
+				dy:= _0_001 * (random(85) + 95);
 				dy.isNegative:= false;
 				FrameTicks:= 250 + random(1751);
 				Frame:= random(5)
 				end;
 	vgtSteam: begin
-				t:= random(1024);
-				sp:= _0_001 * (random(95) + 70);
 				dx.isNegative:= random(2) = 0;
 				dx.QWordValue:= random(100000000);
-				dy:= sp;
+				dy:= _0_001 * (random(85) + 95);
 				dy.isNegative:= false;
 				Frame:= 7 - random(3);
 				FrameTicks:= cExplFrameTicks * 2;
 				end;
+	vgtHealth: begin
+				dx:= _0_001 * random(45);
+				dx.isNegative:= random(2) = 0;
+				dy:= _0_001 * (random(20) + 25);
+				Frame:= 0;
+				FrameTicks:= random(750) + 1250;
+				end;
 		end;
 
 if VisualGearsList <> nil then
@@ -472,11 +487,19 @@
 		begin
         if not cReducedQuality then
             case Gear^.Kind of
-                vgtExplPart: DrawSprite(sprExplPart, hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, 7 - Gear^.Frame);
-                vgtExplPart2: DrawSprite(sprExplPart2, hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, 7 - Gear^.Frame);
-                vgtFire: DrawSprite(sprFlame, hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, (RealTicks div 64 + Gear^.Frame) mod 8);
-				vgtBubble: DrawSprite(sprBubbles, hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, Gear^.Frame);//(RealTicks div 64 + Gear^.Frame) mod 8);
-				vgtSteam: DrawSprite(sprExplPart, hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, 7 - Gear^.Frame);
+                vgtExplPart: DrawSprite(sprExplPart, hwRound(Gear^.X) + WorldDx - 16, hwRound(Gear^.Y) + WorldDy - 16, 7 - Gear^.Frame);
+                vgtExplPart2: DrawSprite(sprExplPart2, hwRound(Gear^.X) + WorldDx - 16, hwRound(Gear^.Y) + WorldDy - 16, 7 - Gear^.Frame);
+                vgtFire: DrawSprite(sprFlame, hwRound(Gear^.X) + WorldDx - 8, hwRound(Gear^.Y) + WorldDy, (RealTicks div 64 + Gear^.Frame) mod 8);
+				vgtBubble: DrawSprite(sprBubbles, hwRound(Gear^.X) + WorldDx - 8, hwRound(Gear^.Y) + WorldDy - 8, Gear^.Frame);//(RealTicks div 64 + Gear^.Frame) mod 8);
+				vgtSteam: DrawSprite(sprExplPart, hwRound(Gear^.X) + WorldDx - 16, hwRound(Gear^.Y) + WorldDy - 16, 7 - Gear^.Frame);
+				vgtHealth:  begin
+							case Gear^.Frame div 10 of
+								0:glColor4f(0, 1, 0, Gear^.FrameTicks / 1000);
+								1:glColor4f(1, 0, 0, Gear^.FrameTicks / 1000);
+							end;
+							DrawSprite(sprHealth, hwRound(Gear^.X) + WorldDx - 8, hwRound(Gear^.Y) + WorldDy - 8, 0);
+							glColor4f(1, 1, 1, 1);
+							end;
             end;
         case Gear^.Kind of
             vgtSmallDamageTag: DrawCentered(hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, Gear^.Tex);
--- a/hedgewars/uWorld.pas	Mon Oct 12 13:56:07 2009 +0000
+++ b/hedgewars/uWorld.pas	Mon Oct 12 16:44:30 2009 +0000
@@ -83,7 +83,7 @@
 var x, y, i, t, l, g: LongInt;
     Slot, Pos: LongInt;
 begin
-if (TurnTimeLeft = 0) or (((CurAmmoGear = nil) or ((CurAmmoGear^.Ammo^.Propz and ammoprop_AltAttack) = 0)) and KbdKeyPressed) then bShowAmmoMenu:= false;
+if (TurnTimeLeft = 0) or (((CurAmmoGear = nil) or ((CurAmmoGear^.Ammo^.Propz and ammoprop_AltAttack) = 0)) and hideAmmoMenu) then bShowAmmoMenu:= false;
 if bShowAmmoMenu then
    begin
    FollowGear:= nil;
@@ -138,6 +138,8 @@
 					begin
 					l:= Ammoz[Ammo^[i, t].AmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber;
 
+					if (TrainingFlags and tfIgnoreDelays) <> 0 then l:= -1;
+
 					if l >= 0 then
 						begin
 						DrawSprite(sprAMAmmosBW, x + g * 33 + 35, y + 1, LongInt(Ammo^[i, t].AmmoType)-1);
@@ -168,7 +170,7 @@
 		if Ammo^[Slot, Pos].Count < AMMO_INFINITE then
 			DrawTexture(cScreenWidth div 2 + AMxShift - 35, cScreenHeight - 68, CountTexz[Ammo^[Slot, Pos].Count]);
 
-		if bSelected and (Ammoz[Ammo^[Slot, Pos].AmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber < 0) then
+		if bSelected and (((TrainingFlags and tfIgnoreDelays) <> 0) or (Ammoz[Ammo^[Slot, Pos].AmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber < 0)) then
 			begin
 			bShowAmmoMenu:= false;
 			SetWeapon(Ammo^[Slot, Pos].AmmoType);
@@ -416,8 +418,53 @@
    DrawSprite(sprFrame, -cScreenWidth div 2 + t - 4, cScreenHeight - 48, 0);
    end;
 
+// Timetrial
+if ((TrainingFlags and tfTimeTrial) <> 0) and (TimeTrialStartTime > 0) then
+	begin
+	if TimeTrialStopTime = 0 then i:= RealTicks - TimeTrialStartTime else i:= TimeTrialStopTime - TimeTrialStartTime;
+	t:= 272;
+	// right frame
+	DrawSprite(sprFrame, -cScreenWidth div 2 + t, 8, 1);
+    dec(t, 32);
+	// 1 ms
+    DrawSprite(sprBigDigit, -cScreenWidth div 2 + t, 8, i mod 10); 
+    dec(t, 32);
+	i:= i div 10;
+	// 10 ms
+    DrawSprite(sprBigDigit, -cScreenWidth div 2 + t, 8, i mod 10);
+    dec(t, 32);
+	i:= i div 10;
+	// 100 ms
+    DrawSprite(sprBigDigit, -cScreenWidth div 2 + t, 8, i mod 10);
+	dec(t, 16);
+	// Point
+	DrawSprite(sprBigDigit, -cScreenWidth div 2 + t, 8, 11);
+    dec(t, 32);
+	i:= i div 10;
+	// 1 s
+    DrawSprite(sprBigDigit, -cScreenWidth div 2 + t, 8, i mod 10);
+    dec(t, 32);
+	i:= i div 10;
+	// 10s
+    DrawSprite(sprBigDigit, -cScreenWidth div 2 + t, 8, i mod 6);
+	dec(t, 16);
+	// Point
+	DrawSprite(sprBigDigit, -cScreenWidth div 2 + t, 8, 10);
+    dec(t, 32);
+	i:= i div 6;
+	// 1 m
+    DrawSprite(sprBigDigit, -cScreenWidth div 2 + t, 8, i mod 10);
+    dec(t, 32);
+	i:= i div 10;
+	// 10 m
+    DrawSprite(sprBigDigit, -cScreenWidth div 2 + t, 8, i mod 10);
+	// left frame
+	DrawSprite(sprFrame, -cScreenWidth div 2 + t - 4, 8, 0);
+	end;
+   
 // Captions
-i:= 8;
+if ((TrainingFlags and tfTimeTrial) <> 0) and (TimeTrialStartTime > 0) then i:= 48 else i:= 8;
+
 for grp:= Low(TCapGroup) to High(TCapGroup) do
     with Captions[grp] do
          if Tex <> nil then
@@ -561,11 +608,8 @@
 
 procedure MoveCamera;
 const PrevSentPointTime: LongWord = 0;
-var EdgesDist, cw, wdy: LongInt;
+var EdgesDist,  wdy: LongInt;
 begin
-
-cw:= round(cScreenWidth / cScaleFactor);
-
 if (not (CurrentTeam^.ExtDriven and isCursorVisible)) and cHasFocus then
 	begin
 {$IFDEF SDL13}
Binary file share/hedgewars/Data/Graphics/BigDigits.png has changed
Binary file share/hedgewars/Data/Graphics/Frame.png has changed
Binary file share/hedgewars/Data/Graphics/Health.png has changed
--- a/share/hedgewars/Data/Locale/de.txt	Mon Oct 12 13:56:07 2009 +0000
+++ b/share/hedgewars/Data/Locale/de.txt	Mon Oct 12 16:44:30 2009 +0000
@@ -7,9 +7,9 @@
 00:04=Schrotflinte
 00:05=Presslufthammer
 00:06=Runde überspringen
-00:07=Seil
+00:07=Enterhaken
 00:08=Mine
-00:09=DEagle
+00:09=Desert Eagle
 00:10=Dynamit
 00:11=Baseballschläger
 00:12=Feuerfaust
@@ -31,7 +31,7 @@
 00:28=Bohrkopfrakete
 00:29=Ballpistole
 00:30=Napalm
-00:31=RC Flugzeug
+00:31=RC-Flugzeug
 00:32=Schwerkraft verringern
 00:33=Zusatzschaden
 00:34=Unverwundbarkeit
@@ -39,17 +39,18 @@
 00:36=Laservisier
 00:37=Vampirismus
 00:38=Scharfschützengewehr
-00:39=Flying Saucer
+00:39=Fliegende Untertasse
 
 01:00=Auf in die Schlacht!
 01:01=Unentschieden
 01:02=%1 gewinnt!
-01:03=Lautstärke %1%
+01:03=Lautstärke: %1 %
 01:04=Pausiert
 01:05=Wirklich beenden (Z/Esc)?
 01:06=Sudden Death!
-01:07=Remaining
-01:08=Fuel
+01:07=verbleibend
+01:08=Treibstoff
+01:09=Synchronisiere ...
 
 ; Event messages
 ; Hog (%1) died
@@ -57,9 +58,19 @@
 02:00=%1 steht wohl nicht mehr auf!
 02:00=%1 hat's nicht geschafft!
 02:00=%1 stellt sich tot!
-02:00=%1 scheint schon bessere Tage gesehen zu haben!
+02:00=%1 hat schon bessere Tage gesehen!
 02:00=%1 sieht tote Igel!
 02:00=%1 hat ins Gras gebissen!
+02:00=%1 ist Futter für die Würmer!
+02:00=Das war's wohl, %1!
+02:00=%1 hat's hinter sich!
+02:00=%1 kann schon das Licht sehen!
+02:00=Für %1 gehen alle Lichter aus!
+02:00=%1 kommt wieder!
+02:00=%1 ist urlaubsreif!
+02:00=%1 trifft seine Ahnen.
+02:00=%1 war nicht hartnäckig genug!
+
 ; Hog (%1) drowned
 02:01=%1 geht auf Tauchstation!
 02:01=%1 sucht nach der Titanic!
@@ -68,30 +79,72 @@
 02:01=%1 hat sich nass gemacht!
 02:01=%1 versagt beim Seepferdchen!
 02:01=%1 ist ein Opfer der Gezeiten!
+02:01=%1 findet Nemo!
+02:01=%1 taucht unter ...
+02:01=Atlantis wartet, %1!
+02:01=%1 nimmt ein entspannendes Bad.
+02:01=%1 hat nie das Seepferdchen gemacht!
+02:01=%1 ist Fischfutter!
+02:01=%1 im Rausch der Tiefe!
+02:01=Was für ein feuchtes Erlebnis!
+02:01=%1 geht unter die Perlentaucher!
+02:01=Igel über Bord!
+02:01=%1 verlässt das sinkende Schiff.
+02:01=%1 überschätzt seinen Auftrieb!
+02:01=%1 erliegt dem Sog der Tiefe!
+02:01=%1 geht der Sache auf den Grund!
+
 ; Round starts
 02:02=Auf in die Schlacht!
 02:02=Geladen und entsichert!
 02:02=Jetzt geht's rund!
 02:02=Los geht's!
 02:02=Alles angetreten!
+
 ; Round ends (win; unused atm)
 02:03=...
+
 ; Round ends (draw; unused atm)
 02:04=...
+
 ; New health crate
 02:05=Alles Gute kommt von oben!
-02:05=Der Arzt hat's verschrieben ...
+02:05=Linderung in Sicht!
+02:05=Das kommt gelegen!
+02:05=Jemand denkt an euch!
+02:05=Mit Liebe verpackt?
+
 ; New ammo crate
 02:06=Nachschub!
 02:06=Zeit zum Nachladen!
+02:06=Was wohl darin ist?
+02:06=Bringt das die Wende?
+02:06=Tod aus der Luft!
+
 ; New utility crate
 02:07=Nützliches?
 02:07=Tooltime!
+02:07=Was wohl darin ist?
+02:07=Lieferung frei Haus!
+02:07=Mehr als nur eine Kiste!
+
 ; Hog (%1) skips his turn
 02:08=%1 ist so ein Langeweiler ...
+02:08=%1 denkt weiter nach ...
+02:08=Das war öde, %1!
+02:08=%1 führt etwas im Schilde!
+02:08=%1 setzt aus.
+02:08=%1 ist ein Drückeberger.
+02:08=%1 überdenkt die Situation.
+02:08=%1 kann sich nicht entscheiden.
+
 ; Hog (%1) hurts himself only
 02:09=%1 sollte besser Zielen üben!
 02:09=%1 scheint sich zu hassen.
 02:09=%1 steht auf der falschen Seite!
 02:09=%1 lebt gefährlich!
 
+; Hog (%1) shot an home run (using the bat and another hog)
+02:10=Home Run!
+02:10=Ein Vogel, ein Flugzeug, ...
+02:10=Der verleiht Flügel!
\ No newline at end of file
--- a/share/hedgewars/Data/Locale/en.txt	Mon Oct 12 13:56:07 2009 +0000
+++ b/share/hedgewars/Data/Locale/en.txt	Mon Oct 12 16:44:30 2009 +0000
@@ -343,3 +343,7 @@
 02:09=I'm sure nobody saw that %1
 02:09=%1 needs to review his field manual
 02:09=%1's weapon clearly malfunctioned
+; Hog shot an home run (using the bat and another hog)
+02:10=Home Run!
+02:10=A bird, a plane, ...
+02:10=That one is out!
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/hedgewars/Data/Trainings/003_RCPlane.txt	Mon Oct 12 16:44:30 2009 +0000
@@ -0,0 +1,12 @@
+seed 1
+$gmflags 268435458
+$trflags 15
+$turntime 30000
+$casefreq 0
+$landadds 0
+$delay 0
+map EarthRise
+theme EarthRise
+ammstore 000000000000000000000000000000100000000000000000000000000000000000000000000000
+addtrig C7 1 1
+addtrig F2147483649 2 1
\ No newline at end of file