Enable ~/.hedgewars/Data (or platform equivalent) to override/extend pretty much everything in system Data dir. Obviously desyncing can occur, so this is at user's own risk. Should simplify map etc install. Needs testing.
authornemo
Sun, 12 Jun 2011 21:06:48 -0400
changeset 5238 46ddaf14509d
parent 5237 963d787a25c2
child 5239 f34f391a223b
Enable ~/.hedgewars/Data (or platform equivalent) to override/extend pretty much everything in system Data dir. Obviously desyncing can occur, so this is at user's own risk. Should simplify map etc install. Needs testing.
QTfrontend/SDLs.cpp
QTfrontend/chatwidget.cpp
QTfrontend/gamecfgwidget.cpp
QTfrontend/hats.cpp
QTfrontend/hwform.cpp
QTfrontend/main.cpp
QTfrontend/mapContainer.cpp
QTfrontend/namegen.cpp
QTfrontend/pageeditteam.cpp
QTfrontend/pageoptions.cpp
QTfrontend/pagetraining.cpp
hedgewars/ArgParsers.inc
hedgewars/hwengine.pas
hedgewars/uCommandHandlers.pas
hedgewars/uLand.pas
hedgewars/uLandObjects.pas
hedgewars/uScript.pas
hedgewars/uSound.pas
hedgewars/uStore.pas
hedgewars/uVariables.pas
--- a/QTfrontend/SDLs.cpp	Sun Jun 12 14:45:26 2011 -0400
+++ b/QTfrontend/SDLs.cpp	Sun Jun 12 21:06:48 2011 -0400
@@ -164,9 +164,12 @@
 void SDLInteraction::StartMusic()
 {
     SDLMusicInit();
+    QFile tmpfile;
 
+    tmpfile.setFileName(cfgdir->absolutePath() + "/Data/Music/main_theme.ogg");
+    if (!tmpfile.exists()) tmpfile.setFileName(datadir->absolutePath() + "/Music/main_theme.ogg");
     if (music == NULL) {
-        music = Mix_LoadMUS((datadir->absolutePath() + "/Music/main_theme.ogg").toLocal8Bit().constData());
+        music = Mix_LoadMUS(QFileInfo(tmpfile).absoluteFilePath().toLocal8Bit().constData());
 
     }
     Mix_VolumeMusic(MIX_MAX_VOLUME - 28);
--- a/QTfrontend/chatwidget.cpp	Sun Jun 12 14:45:26 2011 -0400
+++ b/QTfrontend/chatwidget.cpp	Sun Jun 12 21:06:48 2011 -0400
@@ -122,15 +122,14 @@
     this->sdli = sdli;
     this->notify = notify;
     if(notify && gameSettings->value("frontend/sound", true).toBool()) {
-       QDir tmpdir;
-
-       tmpdir.cd(datadir->absolutePath());
-       tmpdir.cd("Sounds/voices");
-       sdli->SDLMusicInit();
-       sound[0] = Mix_LoadWAV(QString(tmpdir.absolutePath() + "/Classic/Hello.ogg").toLocal8Bit().constData());
-       sound[1] = Mix_LoadWAV(QString(tmpdir.absolutePath() + "/Default/Hello.ogg").toLocal8Bit().constData());
-       sound[2] = Mix_LoadWAV(QString(tmpdir.absolutePath() + "/Mobster/Hello.ogg").toLocal8Bit().constData());
-       sound[3] = Mix_LoadWAV(QString(tmpdir.absolutePath() + "/Russian/Hello.ogg").toLocal8Bit().constData());
+        QFile tmpfile;
+        sdli->SDLMusicInit();
+        for(int i=0;i<4;i++) {
+            tmpfile.setFileName(cfgdir->absolutePath() + "/Data/Sounds/voices/Classic/Hello.ogg");
+            if (tmpfile.exists()) sound[i] = Mix_LoadWAV(QFileInfo(tmpfile).absoluteFilePath().toLocal8Bit().constData());
+            else sound[i] = Mix_LoadWAV(QString(datadir->absolutePath() + 
+                "/Sounds/voices/Classic/Hello.ogg").toLocal8Bit().constData());
+        }
     }
 
     mainLayout.setSpacing(1);
--- a/QTfrontend/gamecfgwidget.cpp	Sun Jun 12 14:45:26 2011 -0400
+++ b/QTfrontend/gamecfgwidget.cpp	Sun Jun 12 21:06:48 2011 -0400
@@ -61,7 +61,9 @@
         QString script = (*scriptList)[i].remove(".lua", Qt::CaseInsensitive);
         QList<QVariant> scriptInfo;
         scriptInfo.push_back(script);
-        QFile scriptCfgFile(QString("%1/Scripts/Multiplayer/%2.cfg").arg(datadir->absolutePath()).arg(script));
+        QFile scriptCfgFile;
+        scriptCfgFile.setFileName(QString("%1/Data/Scripts/Multiplayer/%2.cfg").arg(cfgdir->absolutePath()).arg(script));
+        if (!scriptCfgFile.exists()) scriptCfgFile.setFileName(QString("%1/Scripts/Multiplayer/%2.cfg").arg(datadir->absolutePath()).arg(script));
         if (scriptCfgFile.exists() && scriptCfgFile.open(QFile::ReadOnly)) {
             QString scheme;
             QString weapons;
--- a/QTfrontend/hats.cpp	Sun Jun 12 14:45:26 2011 -0400
+++ b/QTfrontend/hats.cpp	Sun Jun 12 21:06:48 2011 -0400
@@ -26,19 +26,46 @@
 HatsModel::HatsModel(QObject* parent) :
   QAbstractListModel(parent)
 {
-    QPixmap hhpix = QPixmap(datadir->absolutePath() + "/Graphics/Hedgehog/Idle.png").copy(0, 0, 32, 32);
+    QFile hhfile;
+    hhfile.setFileName(cfgdir->absolutePath() + "/Data/Graphics/Hedgehog/Idle.png");
+    if (!hhfile.exists()) hhfile.setFileName(datadir->absolutePath() + "/Graphics/Hedgehog/Idle.png");
+    QPixmap hhpix = QPixmap(QFileInfo(hhfile).absoluteFilePath()).copy(0, 0, 32, 32);
 
     QDir tmpdir;
-    tmpdir.cd(datadir->absolutePath());
+    tmpdir.cd(cfgdir->absolutePath());
+    tmpdir.cd("Data");
     tmpdir.cd("Graphics");
     tmpdir.cd("Hats");
 
     tmpdir.setFilter(QDir::Files);
 
+    QStringList userhatsList = tmpdir.entryList(QStringList("*.png"));
+    for (QStringList::Iterator it = userhatsList.begin(); it != userhatsList.end(); ++it )
+    {
+        QString str = QString(*it).replace(QRegExp("^(.*)\\.png"), "\\1");
+        QPixmap pix(cfgdir->absolutePath() + "/Data/Graphics/Hats/" + str + ".png");
+
+        QPixmap tmppix(32, 37);
+        tmppix.fill(QColor(Qt::transparent));
+
+        QPainter painter(&tmppix);
+        painter.drawPixmap(QPoint(0, 5), hhpix);
+        painter.drawPixmap(QPoint(0, 0), pix.copy(0, 0, 32, 32));
+        if(pix.width() > 32)
+            painter.drawPixmap(QPoint(0, 0), pix.copy(32, 0, 32, 32));
+        painter.end();
+
+        hats.append(qMakePair(str, QIcon(tmppix)));
+    }
+
+    tmpdir.cd(datadir->absolutePath());
+    tmpdir.cd("Graphics");
+    tmpdir.cd("Hats");
 
     QStringList hatsList = tmpdir.entryList(QStringList("*.png"));
     for (QStringList::Iterator it = hatsList.begin(); it != hatsList.end(); ++it )
     {
+        if (userhatsList.contains(*it,Qt::CaseInsensitive)) continue;
         QString str = (*it).replace(QRegExp("^(.*)\\.png"), "\\1");
         QPixmap pix(datadir->absolutePath() + "/Graphics/Hats/" + str + ".png");
 
--- a/QTfrontend/hwform.cpp	Sun Jun 12 14:45:26 2011 -0400
+++ b/QTfrontend/hwform.cpp	Sun Jun 12 21:06:48 2011 -0400
@@ -1201,13 +1201,23 @@
     ui.pageCampaign->CBSelect->clear();
 
     QDir tmpdir;
+    tmpdir.cd(cfgdir->absolutePath());
+    tmpdir.cd("Data/Missions/Campaign");
+    tmpdir.setFilter(QDir::Files);
+    QStringList userentries = tmpdir.entryList(QStringList("*#*.lua"));
+    //entries.sort();
+    for(int i = 0; (i < userentries.count()) && (i <= team.CampaignProgress); i++)
+        ui.pageCampaign->CBSelect->addItem(QString(userentries[i]).replace(QRegExp("^(\\d+)#(.+)\\.lua"), QComboBox::tr("Mission") + " \\1: \\2").replace("_", " "), QString(userentries[i]).replace(QRegExp("^(.*)\\.lua"), "\\1"));
+
     tmpdir.cd(datadir->absolutePath());
     tmpdir.cd("Missions/Campaign");
     tmpdir.setFilter(QDir::Files);
     QStringList entries = tmpdir.entryList(QStringList("*#*.lua"));
     //entries.sort();
-    for(int i = 0; (i < entries.count()) && (i <= team.CampaignProgress); i++)
+    for(int i = 0; (i < entries.count()) && (i <= team.CampaignProgress); i++) {
+        if (userentries.contains(entries[i])) continue; 
         ui.pageCampaign->CBSelect->addItem(QString(entries[i]).replace(QRegExp("^(\\d+)#(.+)\\.lua"), QComboBox::tr("Mission") + " \\1: \\2").replace("_", " "), QString(entries[i]).replace(QRegExp("^(.*)\\.lua"), "\\1"));
+    }
 }
 
 // used for --set-everything [screen width] [screen height] [color dept] [volume] [enable music] [enable sounds] [language file] [full screen] [show FPS] [alternate damage] [timer value] [reduced quality]
@@ -1240,8 +1250,8 @@
     registry_hkcr.setValue("Hedgewars.Save/Default", tr("Hedgewars Save File", "File Types"));
     registry_hkcr.setValue("Hedgewars.Demo/DefaultIcon/Default", "\"" + bindir->absolutePath().replace("/", "\\") + "\\hwdfile.ico\",0");
     registry_hkcr.setValue("Hedgewars.Save/DefaultIcon/Default", "\"" + bindir->absolutePath().replace("/", "\\") + "\\hwsfile.ico\",0");
-    registry_hkcr.setValue("Hedgewars.Demo/Shell/Open/Command/Default", "\"" + bindir->absolutePath().replace("/", "\\") + "\\hwengine.exe\" \"" + datadir->absolutePath().replace("/", "\\") + "\" \"%1\" --set-everything "+arguments);
-    registry_hkcr.setValue("Hedgewars.Save/Shell/Open/Command/Default", "\"" + bindir->absolutePath().replace("/", "\\") + "\\hwengine.exe\" \"" + datadir->absolutePath().replace("/", "\\") + "\" \"%1\" --set-everything "+arguments);
+    registry_hkcr.setValue("Hedgewars.Demo/Shell/Open/Command/Default", "\"" + bindir->absolutePath().replace("/", "\\") + "\\hwengine.exe\" \"" + cfgdir->absolutePath().replace("/","\\") + "\" \"" + datadir->absolutePath().replace("/", "\\") + "\" \"%1\" --set-everything "+arguments);
+    registry_hkcr.setValue("Hedgewars.Save/Shell/Open/Command/Default", "\"" + bindir->absolutePath().replace("/", "\\") + "\\hwengine.exe\" \"" + cfgdir->absolutePath().replace("/","\\") + "\" \"" + datadir->absolutePath().replace("/", "\\") + "\" \"%1\" --set-everything "+arguments);
 #elif defined __APPLE__
     success = false;
     // TODO; also enable button in pages.cpp and signal in hwform.cpp
@@ -1259,8 +1269,8 @@
     if (success) success = system(("update-mime-database "+QDir::home().absolutePath()+"/.local/share/mime").toLocal8Bit().constData())==0;
     if (success) success = system("xdg-mime default hwengine.desktop application/x-hedgewars-demo")==0;
     if (success) success = system("xdg-mime default hwengine.desktop application/x-hedgewars-save")==0;
-    // hack to add user's settings to hwengine. might be better at this point to read in the file, append it, and write it out to its new home
-    if (success) success = system(("sed -i 's/^\\(Exec=.*\\)/\\1 --set-everything "+arguments+"/' "+QDir::home().absolutePath()+"/.local/share/applications/hwengine.desktop").toLocal8Bit().constData())==0;
+    // hack to add user's settings to hwengine. might be better at this point to read in the file, append it, and write it out to its new home.  This assumes no spaces in the data dir path
+    if (success) success = system(("sed -i 's/^\\(Exec=.*\\) \\([^ ]* %f\\)/\\1 "+cfgdir->absolutePath().replace(" ","\\\\ ").replace("/","\\/")+" \\2 --set-everything "+arguments+"/' "+QDir::home().absolutePath()+"/.local/share/applications/hwengine.desktop").toLocal8Bit().constData())==0;
 #endif
     if (success) QMessageBox::information(0, "", QMessageBox::tr("All file associations have been set."));
     else QMessageBox::information(0, "", QMessageBox::tr("File association failed."));
--- a/QTfrontend/main.cpp	Sun Jun 12 14:45:26 2011 -0400
+++ b/QTfrontend/main.cpp	Sun Jun 12 21:06:48 2011 -0400
@@ -384,13 +384,19 @@
     }
 
     Themes = new QStringList();
+    QFile userthemesfile(cfgdir->absolutePath() + "/Data/Themes/themes.cfg");
+    if (userthemesfile.open(QIODevice::ReadOnly)) {
+        QTextStream stream(&userthemesfile);
+        while (!stream.atEnd()) Themes->append(stream.readLine());
+        userthemesfile.close();
+    }
     QFile themesfile(datadir->absolutePath() + "/Themes/themes.cfg");
+    QString str;
     if (themesfile.open(QIODevice::ReadOnly)) {
         QTextStream stream(&themesfile);
-        QString str;
-        while (!stream.atEnd())
-        {
-            Themes->append(stream.readLine());
+        while (!stream.atEnd()) {
+            str = stream.readLine();
+            if (!Themes->contains(str)) Themes->append(str);
         }
         themesfile.close();
     } else {
@@ -398,16 +404,29 @@
     }
 
     QDir tmpdir;
-    tmpdir.cd(datadir->absolutePath());
-    tmpdir.cd("Maps");
+    tmpdir.cd(cfgdir->absolutePath());
+    tmpdir.cd("Data/Maps");
     tmpdir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
     mapList = new QStringList(tmpdir.entryList(QStringList("*")));
 
     tmpdir.cd(datadir->absolutePath());
-    tmpdir.cd("Scripts/Multiplayer");
+    tmpdir.cd("Maps");
+    tmpdir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
+    QStringList tmplist = QStringList(tmpdir.entryList(QStringList("*")));
+    for (QStringList::Iterator it = tmplist.begin(); it != tmplist.end(); ++it)
+        if (!mapList->contains(*it,Qt::CaseInsensitive)) mapList->append(*it);
+ 
+    tmpdir.cd(cfgdir->absolutePath());
+    tmpdir.cd("Data/Scripts/Multiplayer");
     tmpdir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
     scriptList = new QStringList(tmpdir.entryList(QStringList("*.lua")));
 
+    tmpdir.cd(datadir->absolutePath());
+    tmpdir.cd("Scripts/Multiplayer");
+    tmpdir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
+    tmplist = QStringList(tmpdir.entryList(QStringList("*.lua")));
+    for (QStringList::Iterator it = tmplist.begin(); it != tmplist.end(); ++it)
+        if (!scriptList->contains(*it,Qt::CaseInsensitive)) scriptList->append(*it);
 
     QTranslator Translator;
     {
@@ -415,7 +434,10 @@
         QString cc = settings.value("misc/locale", "").toString();
         if(!cc.compare(""))
             cc = QLocale::system().name();
-        Translator.load(datadir->absolutePath() + "/Locale/hedgewars_" + cc);
+        QFile tmpfile;
+        tmpfile.setFileName(cfgdir->absolutePath() + "Data/Locale/hedgewars_" + cc);
+        if (!tmpfile.exists()) tmpfile.setFileName(datadir->absolutePath() + "Locale/hedgewars_" + cc);
+        Translator.load(QFileInfo(tmpfile).absoluteFilePath());
         app.installTranslator(&Translator);
     }
 
--- a/QTfrontend/mapContainer.cpp	Sun Jun 12 14:45:26 2011 -0400
+++ b/QTfrontend/mapContainer.cpp	Sun Jun 12 21:06:48 2011 -0400
@@ -85,16 +85,29 @@
 
     int missionindex = chooseMap->count();
     numMissions = 0;
+    QFile mapLuaFile;
+    QFile mapCfgFile;
     for (int i = 0; i < mapList->size(); ++i) {
         QString map = (*mapList)[i];
-        QFile mapCfgFile(
-                QString("%1/Maps/%2/map.cfg")
-                .arg(datadir->absolutePath())
+        mapCfgFile.setFileName(
+                QString("%1/Data/Maps/%2/map.cfg")
+                .arg(cfgdir->absolutePath())
                 .arg(map));
-        QFile mapLuaFile(
-                QString("%1/Maps/%2/map.lua")
-                .arg(datadir->absolutePath())
-                .arg(map));
+        if (mapCfgFile.exists()) {
+            mapLuaFile.setFileName(
+                    QString("%1/Data/Maps/%2/map.lua")
+                    .arg(cfgdir->absolutePath())
+                    .arg(map));
+        } else {
+            mapCfgFile.setFileName(
+                    QString("%1/Maps/%2/map.cfg")
+                    .arg(datadir->absolutePath())
+                    .arg(map));
+            mapLuaFile.setFileName(
+                    QString("%1/Maps/%2/map.lua")
+                    .arg(datadir->absolutePath())
+                    .arg(map));
+        }
 
         if (mapCfgFile.open(QFile::ReadOnly)) {
             QString theme;
@@ -190,10 +203,13 @@
     lwThemes = new QListWidget(mapWidget);
     lwThemes->setMinimumHeight(30);
     lwThemes->setFixedWidth(140);
+    QFile tmpfile;
     for (int i = 0; i < Themes->size(); ++i) {
         QListWidgetItem * lwi = new QListWidgetItem();
         lwi->setText(Themes->at(i));
-        lwi->setIcon(QIcon(QString("%1/Themes/%2/icon.png").arg(datadir->absolutePath()).arg(Themes->at(i))));
+        tmpfile.setFileName(QString("%1/Data/Themes/%2/icon.png").arg(cfgdir->absolutePath()).arg(Themes->at(i)));
+        if (tmpfile.exists()) lwi->setIcon(QIcon(QFileInfo(tmpfile).absoluteFilePath()));
+        else lwi->setIcon(QIcon(QString("%1/Themes/%2/icon.png").arg(datadir->absolutePath()).arg(Themes->at(i))));
         //lwi->setTextAlignment(Qt::AlignHCenter);
         lwThemes->addItem(lwi);
     }
@@ -373,7 +389,10 @@
     chooseMap->setItemData(1, mapInfo);
     mapInfo[0] = QString("+drawn+");
     chooseMap->setItemData(2, mapInfo);
-    gbThemes->setIcon(QIcon(QString("%1/Themes/%2/icon@2x.png").arg(datadir->absolutePath()).arg(theme)));
+    QFile tmpfile;
+    tmpfile.setFileName(QString("%1/Data/Themes/%2/icon@2x.png").arg(cfgdir->absolutePath()).arg(theme));
+    if (tmpfile.exists()) gbThemes->setIcon(QIcon(QFileInfo(tmpfile).absoluteFilePath()));
+    else gbThemes->setIcon(QIcon(QString("%1/Themes/%2/icon@2x.png").arg(datadir->absolutePath()).arg(theme)));
     emit themeChanged(theme);
 }
 
@@ -632,7 +651,10 @@
     default:
         QPixmap mapImage;
         qDebug() << "Map data" << curIndex << chooseMap->currentText() << chooseMap->itemData(curIndex);
-        if(!mapImage.load(datadir->absolutePath() + "/Maps/" + chooseMap->itemData(curIndex).toList()[0].toString() + "/preview.png")) {
+        QFile tmpfile;
+        tmpfile.setFileName(cfgdir->absolutePath() + "/Data//Maps/" + chooseMap->itemData(curIndex).toList()[0].toString() + "/preview.png");
+        if (!tmpfile.exists()) tmpfile.setFileName(datadir->absolutePath() + "/Maps/" + chooseMap->itemData(curIndex).toList()[0].toString() + "/preview.png");
+        if(!mapImage.load(QFileInfo(tmpfile).absoluteFilePath())) {
             imageButt->setIcon(QIcon());
             return;
         }
--- a/QTfrontend/namegen.cpp	Sun Jun 12 14:45:26 2011 -0400
+++ b/QTfrontend/namegen.cpp	Sun Jun 12 21:06:48 2011 -0400
@@ -88,21 +88,23 @@
 
 void HWNamegen::DictLoad(const QString filename, QStringList &list)
 {
-     list.clear();
+    list.clear();
 
-     QFile file(QString("%1/Names/%2.txt").arg(datadir->absolutePath()).arg(filename));
-     if (file.open(QIODevice::ReadOnly | QIODevice::Text))
-     {
+    QFile file;
+    file.setFileName(QString("%1/Data/Names/%2.txt").arg(cfgdir->absolutePath()).arg(filename));
+    if (!file.exists()) file.setFileName(QString("%1/Names/%2.txt").arg(datadir->absolutePath()).arg(filename));
+    if (file.open(QIODevice::ReadOnly | QIODevice::Text))
+    {
 
-         QTextStream in(&file);
-         while (!in.atEnd()) {
-             QString line = in.readLine();
-             if(line != QString(""))
-                 {list.append(line);}
-         }
-     }
+        QTextStream in(&file);
+        while (!in.atEnd()) {
+            QString line = in.readLine();
+            if(line != QString(""))
+                {list.append(line);}
+        }
+    }
 
-     if (list.size()==0)
+    if (list.size()==0)
          list.append(filename);
 
 }
@@ -110,21 +112,23 @@
 
 void HWNamegen::HatCfgLoad(const QString hatname, QStringList &list)
 {
-     list.clear();
+    list.clear();
 
-     QFile file(QString("%1/Names/%2.cfg").arg(datadir->absolutePath()).arg(hatname));
-     if (file.open(QIODevice::ReadOnly | QIODevice::Text))
-     {
+    QFile file;
+    file.setFileName(QString("%1/Data/Names/%2.cfg").arg(cfgdir->absolutePath()).arg(hatname));
+    if (!file.exists()) file.setFileName(QString("%1/Names/%2.cfg").arg(datadir->absolutePath()).arg(hatname));
+    if (file.open(QIODevice::ReadOnly | QIODevice::Text))
+    {
 
-         QTextStream in(&file);
-         while (!in.atEnd()) {
-             QString line = in.readLine();
-             if(line != QString(""))
-                 {list.append(line);}
-         }
-     }
+        QTextStream in(&file);
+        while (!in.atEnd()) {
+            QString line = in.readLine();
+            if(line != QString(""))
+                {list.append(line);}
+        }
+    }
 
-     if (list.size()==0)
+    if (list.size()==0)
          list.append(QString("generic"));
 
 }
@@ -132,37 +136,38 @@
 
 void HWNamegen::TypesLoad()
 {
+    QFile file;
+    file.setFileName(QString("%1/Data/Names/types.ini").arg(cfgdir->absolutePath()));
+    if (!file.exists()) file.setFileName(QString("%1/Names/types.ini").arg(datadir->absolutePath()));
+    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+        {TypesAvliable = FALSE; return;}
 
-     QFile file(QString("%1/Names/types.ini").arg(datadir->absolutePath()));
-     if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
-         {TypesAvliable = FALSE; return;}
-
-     int counter = 0; //counter starts with 0 (teamnames mode)
-     TypesTeamnames.append(QStringList());
-     TypesHatnames.append(QStringList());
+    int counter = 0; //counter starts with 0 (teamnames mode)
+    TypesTeamnames.append(QStringList());
+    TypesHatnames.append(QStringList());
 
-     QTextStream in(&file);
-     while (!in.atEnd()) {
-         QString line = in.readLine();
-         if (line == QString("#####")){
-             counter++; //toggle mode (teamnames || hats)
-             if ((counter%2) == 0){
-                 TypesTeamnames.append(QStringList());
-                 TypesHatnames.append(QStringList());
-             }
-         } else if ((line == QString("*****")) || (line == QString("*END*"))){
-             TypesAvliable = TRUE; return; // bye bye
-         } else {
-             if ((counter%2) == 0){ // even => teamnames mode
-                 TypesTeamnames[(counter/2)].append(line);
-             } else { // odd => hats mode
-                 TypesHatnames[((counter-1)/2)].append(line);
-             }
-         }
-//         Types.append(line);
-     }
-         TypesAvliable = TRUE;
-     return;
+    QTextStream in(&file);
+    while (!in.atEnd()) {
+        QString line = in.readLine();
+        if (line == QString("#####")){
+            counter++; //toggle mode (teamnames || hats)
+            if ((counter%2) == 0){
+                TypesTeamnames.append(QStringList());
+                TypesHatnames.append(QStringList());
+            }
+        } else if ((line == QString("*****")) || (line == QString("*END*"))){
+            TypesAvliable = TRUE; return; // bye bye
+        } else {
+            if ((counter%2) == 0){ // even => teamnames mode
+                TypesTeamnames[(counter/2)].append(line);
+            } else { // odd => hats mode
+                TypesHatnames[((counter-1)/2)].append(line);
+            }
+        }
+//        Types.append(line);
+    }
+        TypesAvliable = TRUE;
+    return;
 }
 
 
@@ -173,10 +178,17 @@
 
     //list all available Graves
     QDir tmpdir;
+    tmpdir.cd(cfgdir->absolutePath());
+    tmpdir.cd("Data/Graphics/Graves");
+    tmpdir.setFilter(QDir::Files);
+    Graves.append(tmpdir.entryList(QStringList("*.png")).replaceInStrings(QRegExp("^(.*)\\.png"), "\\1"));
+
     tmpdir.cd(datadir->absolutePath());
     tmpdir.cd("Graphics/Graves");
     tmpdir.setFilter(QDir::Files);
-    Graves.append(tmpdir.entryList(QStringList("*.png")).replaceInStrings(QRegExp("^(.*)\\.png"), "\\1"));
+    QStringList tmpList = tmpdir.entryList(QStringList("*.png")).replaceInStrings(QRegExp("^(.*)\\.png"), "\\1");
+    for (QStringList::Iterator it = tmpList.begin(); it != tmpList.end(); ++it) 
+        if (!Graves.contains(*it,Qt::CaseInsensitive)) Graves.append(*it);
 
     if(Graves.size()==0)
     {
--- a/QTfrontend/pageeditteam.cpp	Sun Jun 12 14:45:26 2011 -0400
+++ b/QTfrontend/pageeditteam.cpp	Sun Jun 12 21:06:48 2011 -0400
@@ -145,10 +145,19 @@
         CBVoicepack = new QComboBox(GBoxTeam);
         {
             QDir tmpdir;
+            tmpdir.cd(cfgdir->absolutePath());
+            tmpdir.cd("Data/Sounds/voices");
+            QStringList list = tmpdir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name);
+            CBVoicepack->addItems(list);
+
             tmpdir.cd(datadir->absolutePath());
             tmpdir.cd("Sounds/voices");
-            QStringList list = tmpdir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name);
-            CBVoicepack->addItems(list);
+            QStringList tmplist = tmpdir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name);
+            QStringList tmplist2;
+            for (QStringList::Iterator it = tmplist.begin(); it != tmplist.end(); ++it)
+                if (!list.contains(*it,Qt::CaseInsensitive)) tmplist2.append(*it);
+
+            CBVoicepack->addItems(tmplist2);
         }
         hbox->addWidget(CBVoicepack, 100);
         BtnTestSound = addButton(":/res/PlaySound.png", hbox, 1, true);
@@ -173,45 +182,103 @@
     vbox2->addWidget(GBoxFort);
 
     QDir tmpdir;
+    tmpdir.cd(cfgdir->absolutePath());
+    tmpdir.cd("Data/Forts");
+    tmpdir.setFilter(QDir::Files);
+
+    QStringList userforts = tmpdir.entryList(QStringList("*L.png")).replaceInStrings(QRegExp("^(.*)L\\.png"), "\\1");
+    CBFort->addItems(userforts);
+
+    tmpdir.cd("../Graphics/Graves");
+    QStringList userlist = tmpdir.entryList(QStringList("*.png"));
+    for (QStringList::Iterator it = userlist.begin(); it != userlist.end(); ++it )
+    {
+        QPixmap pix(cfgdir->absolutePath() + "/Data/Graphics/Graves/" + *it);
+        QIcon icon(pix.copy(0, 0, 32, 32));
+        CBGrave->addItem(icon, QString(*it).replace(QRegExp("^(.*)\\.png"), "\\1"));
+    }
+
     tmpdir.cd(datadir->absolutePath());
     tmpdir.cd("Forts");
     tmpdir.setFilter(QDir::Files);
 
+    QStringList tmplist = tmpdir.entryList(QStringList("*L.png")).replaceInStrings(QRegExp("^(.*)L\\.png"), "\\1");
+    QStringList dataforts;
+    for (QStringList::Iterator it = tmplist.begin(); it != tmplist.end(); ++it)
+        if (!userforts.contains(*it,Qt::CaseInsensitive)) dataforts.append(*it);
+
+    CBVoicepack->addItems(dataforts);
     connect(CBFort, SIGNAL(currentIndexChanged(const QString &)), this, SLOT(CBFort_activated(const QString &)));
-    CBFort->addItems(tmpdir.entryList(QStringList("*L.png")).replaceInStrings(QRegExp("^(.*)L\\.png"), "\\1"));
 
     tmpdir.cd("../Graphics/Graves");
-    QStringList list = tmpdir.entryList(QStringList("*.png"));
-    for (QStringList::Iterator it = list.begin(); it != list.end(); ++it )
+    QStringList datalist = tmpdir.entryList(QStringList("*.png"));
+    for (QStringList::Iterator it = datalist.begin(); it != datalist.end(); ++it )
     {
+        if (userlist.contains(*it,Qt::CaseInsensitive)) continue;
         QPixmap pix(datadir->absolutePath() + "/Graphics/Graves/" + *it);
         QIcon icon(pix.copy(0, 0, 32, 32));
         CBGrave->addItem(icon, (*it).replace(QRegExp("^(.*)\\.png"), "\\1"));
     }
 
-    tmpdir.cd(datadir->absolutePath());
-    tmpdir.cd("Graphics/Flags");
-    list = tmpdir.entryList(QStringList("*.png"));
-    
     // add the default flag
     CBFlag->addItem(QIcon(QPixmap(datadir->absolutePath() + "/Graphics/Flags/hedgewars.png").copy(0, 0, 22, 15)), "Hedgewars", "hedgewars");
+    CBFlag->insertSeparator(CBFlag->count());
+
+    tmpdir.cd(cfgdir->absolutePath());
+    tmpdir.cd("Data/Graphics/Flags");
+    userlist = tmpdir.entryList(QStringList("*.png"));
+    
+    // add all country flags
+    for (QStringList::Iterator it = userlist.begin(); it != userlist.end(); ++it )
+    {
+        QPixmap pix(cfgdir->absolutePath() + "/Data/Graphics/Flags/" + *it);
+        QIcon icon(pix.copy(0, 0, 22, 15));
+        if(it->compare("cpu.png") && it->compare("hedgewars.png") && (it->indexOf("cm_") == -1)) // skip cpu and hedgewars flags as well as all community flags
+        {
+            QString flag = QString(*it).replace(QRegExp("^(.*)\\.png"), "\\1");
+            CBFlag->addItem(icon, QString(flag).replace("_", " "), flag);
+        }
+    }
 
     CBFlag->insertSeparator(CBFlag->count());
+
+    // add all community flags
+    for (QStringList::Iterator it = userlist.begin(); it != userlist.end(); ++it )
+    {
+        QPixmap pix(cfgdir->absolutePath() + "/Data/Graphics/Flags/" + *it);
+        QIcon icon(pix.copy(0, 0, 22, 15));
+        if(it->indexOf("cm_") > -1) // skip non community flags this time
+        {
+            QString flag = QString(*it).replace(QRegExp("^(.*)\\.png"), "\\1");
+            CBFlag->addItem(icon, QString(flag).replace("cm_", QComboBox::tr("Community") + ": "), flag);
+        }
+    }
+
+    CBFlag->insertSeparator(CBFlag->count());
+
+    tmpdir.cd(datadir->absolutePath());
+    tmpdir.cd("Graphics/Flags");
+    datalist = tmpdir.entryList(QStringList("*.png"));
+    
     // add all country flags
-    for (QStringList::Iterator it = list.begin(); it != list.end(); ++it )
+    for (QStringList::Iterator it = datalist.begin(); it != datalist.end(); ++it )
     {
+        if (userlist.contains(*it,Qt::CaseInsensitive)) continue;
         QPixmap pix(datadir->absolutePath() + "/Graphics/Flags/" + *it);
         QIcon icon(pix.copy(0, 0, 22, 15));
         if(it->compare("cpu.png") && it->compare("hedgewars.png") && (it->indexOf("cm_") == -1)) // skip cpu and hedgewars flags as well as all community flags
         {
-            QString flag = (*it).replace(QRegExp("^(.*)\\.png"), "\\1");
+            QString flag = QString(*it).replace(QRegExp("^(.*)\\.png"), "\\1");
             CBFlag->addItem(icon, QString(flag).replace("_", " "), flag);
         }
     }
+
     CBFlag->insertSeparator(CBFlag->count());
+
     // add all community flags
-    for (QStringList::Iterator it = list.begin(); it != list.end(); ++it )
+    for (QStringList::Iterator it = datalist.begin(); it != datalist.end(); ++it )
     {
+        if (userlist.contains(*it,Qt::CaseInsensitive)) continue;
         QPixmap pix(datadir->absolutePath() + "/Graphics/Flags/" + *it);
         QIcon icon(pix.copy(0, 0, 22, 15));
         if(it->indexOf("cm_") > -1) // skip non community flags this time
@@ -272,7 +339,10 @@
 
 void PageEditTeam::CBFort_activated(const QString & fortname)
 {
-    QPixmap pix(datadir->absolutePath() + "/Forts/" + fortname + "L.png");
+    QFile tmp;
+    tmp.setFileName(cfgdir->absolutePath() + "/Data/Forts/" + fortname + "L.png");
+    if (!tmp.exists()) tmp.setFileName(datadir->absolutePath() + "/Forts/" + fortname + "L.png");
+    QPixmap pix(QFileInfo(tmp).absoluteFilePath());
     FortPreview->setPixmap(pix);
 }
 
@@ -282,9 +352,16 @@
     QDir tmpdir;
     mySdli->SDLMusicInit();
     
-    tmpdir.cd(datadir->absolutePath());
-    tmpdir.cd("Sounds/voices");
+    tmpdir.cd(cfgdir->absolutePath());
+    tmpdir.cd("Data/Sounds/voices");
     tmpdir.cd(CBVoicepack->currentText());
+    
+    if (!tmpdir.exists()) {
+        tmpdir.cd(datadir->absolutePath());
+        tmpdir.cd("Sounds/voices");
+        tmpdir.cd(CBVoicepack->currentText());
+    }
+
     QStringList list = tmpdir.entryList(QStringList() << "Illgetyou.ogg" << "Incoming.ogg" << "Stupid.ogg" << "Coward.ogg" << "Firstblood.ogg", QDir::Files);
     if (list.size()) {
         sound = Mix_LoadWAV(QString(tmpdir.absolutePath() + "/" + list[rand() % list.size()]).toLocal8Bit().constData());
--- a/QTfrontend/pageoptions.cpp	Sun Jun 12 14:45:26 2011 -0400
+++ b/QTfrontend/pageoptions.cpp	Sun Jun 12 21:06:48 2011 -0400
@@ -207,8 +207,8 @@
 
             CBLanguage = new QComboBox(groupMisc);
             QDir tmpdir;
-            tmpdir.cd(datadir->absolutePath());
-            tmpdir.cd("Locale");
+            tmpdir.cd(cfgdir->absolutePath());
+            tmpdir.cd("Data/Locale");
             tmpdir.setFilter(QDir::Files);
             QStringList locs = tmpdir.entryList(QStringList("hedgewars_*.qm"));
             CBLanguage->addItem(QComboBox::tr("(System default)"), QString(""));
@@ -218,6 +218,17 @@
                 CBLanguage->addItem(QLocale::languageToString(loc.language()) + " (" + QLocale::countryToString(loc.country()) + ")", loc.name());
             }
 
+            tmpdir.cd(datadir->absolutePath());
+            tmpdir.cd("Locale");
+            tmpdir.setFilter(QDir::Files);
+            QStringList tmplist = tmpdir.entryList(QStringList("hedgewars_*.qm"));
+            for(int i = 0; i < tmplist.count(); i++)
+            {
+                if (locs.contains(tmplist[i])) continue;
+                QLocale loc(tmplist[i].replace(QRegExp("hedgewars_(.*)\\.qm"), "\\1"));
+                CBLanguage->addItem(QLocale::languageToString(loc.language()) + " (" + QLocale::countryToString(loc.country()) + ")", loc.name());
+            }
+
             MiscLayout->addWidget(CBLanguage, 2, 1);
 
             CBAltDamage = new QCheckBox(groupMisc);
--- a/QTfrontend/pagetraining.cpp	Sun Jun 12 14:45:26 2011 -0400
+++ b/QTfrontend/pagetraining.cpp	Sun Jun 12 21:06:48 2011 -0400
@@ -35,10 +35,21 @@
     CBSelect = new QComboBox(this);
 
     QDir tmpdir;
+    tmpdir.cd(cfgdir->absolutePath());
+    tmpdir.cd("Data/Missions/Training");
+    tmpdir.setFilter(QDir::Files);
+    QStringList userlist = tmpdir.entryList(QStringList("*.lua")).replaceInStrings(QRegExp("^(.*)\\.lua"), "\\1");
+    CBSelect->addItems(userlist);
+
     tmpdir.cd(datadir->absolutePath());
     tmpdir.cd("Missions/Training");
     tmpdir.setFilter(QDir::Files);
-    CBSelect->addItems(tmpdir.entryList(QStringList("*.lua")).replaceInStrings(QRegExp("^(.*)\\.lua"), "\\1"));
+    QStringList tmplist = tmpdir.entryList(QStringList("*.lua")).replaceInStrings(QRegExp("^(.*)\\.lua"), "\\1");
+    QStringList datalist;
+    for (QStringList::Iterator it = tmplist.begin(); it != tmplist.end(); ++it)
+        if (!userlist.contains(*it,Qt::CaseInsensitive)) datalist.append(*it);
+    CBSelect->addItems(datalist);
+
     for(int i = 0; i < CBSelect->count(); i++)
     {
         CBSelect->setItemData(i, CBSelect->itemText(i));
--- a/hedgewars/ArgParsers.inc	Sun Jun 12 14:45:26 2011 -0400
+++ b/hedgewars/ArgParsers.inc	Sun Jun 12 21:06:48 2011 -0400
@@ -34,6 +34,7 @@
 procedure internalStartGameWithParameters();
 var tmp: LongInt;
 begin
+    UserPathPrefix:= ParamStr(1)+'/Data';
     val(ParamStr(2), cScreenWidth);
     val(ParamStr(3), cScreenHeight);
     val(ParamStr(4), cBits);
@@ -146,9 +147,10 @@
 var paramIndex: LongInt;
     wrongParameter: boolean;
 begin
-    PathPrefix:= ParamStr(1);
-    recordFileName:= ParamStr(2);
-    paramIndex:= 3;
+    UserPathPrefix:= ParamStr(1)+'/Data';
+    PathPrefix:= ParamStr(2);
+    recordFileName:= ParamStr(3);
+    paramIndex:= 4;
     wrongParameter:= false;
     while (paramIndex <= ParamCount) and not wrongParameter do
         begin
--- a/hedgewars/hwengine.pas	Sun Jun 12 14:45:26 2011 -0400
+++ b/hedgewars/hwengine.pas	Sun Jun 12 21:06:48 2011 -0400
@@ -214,6 +214,7 @@
     cFullScreen:= false;
     cTimerInterval:= 8;
     PathPrefix:= 'Data';
+    UserPathPrefix:= 'Data';
     cShowFPS:= {$IFDEF DEBUGFILE}true{$ELSE}false{$ENDIF};
     val(gameArgs[0], ipcPort);
     val(gameArgs[1], cScreenWidth);
@@ -234,10 +235,14 @@
 
     WriteLnToConsole('Hedgewars ' + cVersionString + ' engine (network protocol: ' + inttostr(cNetProtoVersion) + ')');
     AddFileLog('Prefix: "' + PathPrefix +'"');
+    AddFileLog('UserPrefix: "' + UserPathPrefix +'"');
     for i:= 0 to ParamCount do
         AddFileLog(inttostr(i) + ': ' + ParamStr(i));
 
     for p:= Succ(Low(TPathType)) to High(TPathType) do
+        if p <> ptMapCurrent then UserPathz[p]:= UserPathPrefix + '/' + Pathz[p];
+
+    for p:= Succ(Low(TPathType)) to High(TPathType) do
         if p <> ptMapCurrent then Pathz[p]:= PathPrefix + '/' + Pathz[p];
 
     WriteToConsole('Init SDL... ');
@@ -263,6 +268,7 @@
     InitKbdKeyTable();
     AddProgress();
 
+    LoadLocale(UserPathz[ptLocale] + '/en.txt');  // Do an initial load with english
     LoadLocale(Pathz[ptLocale] + '/en.txt');  // Do an initial load with english
     if (Length(cLocaleFName) > 6) then cLocale := Copy(cLocaleFName,1,5)
     else cLocale := Copy(cLocaleFName,1,2);
@@ -270,8 +276,12 @@
         begin
         // Try two letter locale first before trying specific locale overrides
         if (Length(cLocale) > 2) and (Copy(cLocale,1,2) <> 'en') then
-            LoadLocale(Pathz[ptLocale] + '/' + Copy(cLocale,1,2)+'.txt');
-        LoadLocale(Pathz[ptLocale] + '/' + cLocaleFName);
+            begin
+            LoadLocale(UserPathz[ptLocale] + '/' + Copy(cLocale,1,2)+'.txt');
+            LoadLocale(Pathz[ptLocale] + '/' + Copy(cLocale,1,2)+'.txt')
+            end;
+        LoadLocale(UserPathz[ptLocale] + '/' + cLocaleFName);
+        LoadLocale(Pathz[ptLocale] + '/' + cLocaleFName)
         end
     else cLocale := 'en';
 
@@ -430,7 +440,7 @@
 begin
     WriteLn('Wrong argument format: correct configurations is');
     WriteLn();
-    WriteLn('  hwengine <path to data folder> <path to replay file> [options]');
+    WriteLn('  hwengine <path to user hedgewars folder> <path to global data folder> <path to replay file> [options]');
     WriteLn();
     WriteLn('where [options] must be specified either as:');
     WriteLn(' --set-video [screen width] [screen height] [color dept]');
@@ -453,10 +463,10 @@
 
 procedure GetParams;
 begin
-    if (ParamCount < 2) then
+    if (ParamCount < 3) then
         GameType:= gmtSyntax
     else
-        if (ParamCount = 3) then
+        if (ParamCount = 3) and ((ParamStr(3) = '--stats-only') or (ParamStr(3) = 'landpreview')) then
             internalSetGameTypeLandPreviewFromParameters()
         else
             if (ParamCount = cDefaultParamNum) then
--- a/hedgewars/uCommandHandlers.pas	Sun Jun 12 14:45:26 2011 -0400
+++ b/hedgewars/uCommandHandlers.pas	Sun Jun 12 21:06:48 2011 -0400
@@ -447,6 +447,7 @@
 begin
 if isDeveloperMode then
 begin
+UserPathz[ptMapCurrent]:= UserPathz[ptMaps] + '/' + s;
 Pathz[ptMapCurrent]:= Pathz[ptMaps] + '/' + s;
 InitStepsFlags:= InitStepsFlags or cifMap
 end
@@ -456,6 +457,7 @@
 begin
 if isDeveloperMode then
 begin
+UserPathz[ptCurrTheme]:= UserPathz[ptThemes] + '/' + s;
 Pathz[ptCurrTheme]:= Pathz[ptThemes] + '/' + s;
 Theme:= s;
 InitStepsFlags:= InitStepsFlags or cifTheme
--- a/hedgewars/uLand.pas	Sun Jun 12 14:45:26 2011 -0400
+++ b/hedgewars/uLand.pas	Sun Jun 12 21:06:48 2011 -0400
@@ -283,7 +283,8 @@
     r, rr: TSDL_Rect;
     x, yd, yu: LongInt;
 begin
-    tmpsurf:= LoadImage(Pathz[ptCurrTheme] + '/LandTex', ifCritical or ifIgnoreCaps);
+    tmpsurf:= LoadImage(UserPathz[ptCurrTheme] + '/LandTex', ifIgnoreCaps);
+    if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[ptCurrTheme] + '/LandTex', ifCritical or ifIgnoreCaps);
     r.y:= 0;
     while r.y < LAND_HEIGHT do
     begin
@@ -298,9 +299,11 @@
     SDL_FreeSurface(tmpsurf);
 
     // freed in freeModule() below
-    LandBackSurface:= LoadImage(Pathz[ptCurrTheme] + '/LandBackTex', ifIgnoreCaps or ifTransparent);
+    LandBackSurface:= LoadImage(UserPathz[ptCurrTheme] + '/LandBackTex', ifIgnoreCaps or ifTransparent);
+    if LandBackSurface = nil then LandBackSurface:= LoadImage(Pathz[ptCurrTheme] + '/LandBackTex', ifIgnoreCaps or ifTransparent);
 
-    tmpsurf:= LoadImage(Pathz[ptCurrTheme] + '/Border', ifCritical or ifIgnoreCaps or ifTransparent);
+    tmpsurf:= LoadImage(UserPathz[ptCurrTheme] + '/Border', ifIgnoreCaps or ifTransparent);
+    if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[ptCurrTheme] + '/Border', ifCritical or ifIgnoreCaps or ifTransparent);
     for x:= 0 to LAND_WIDTH - 1 do
     begin
         yd:= LAND_HEIGHT - 1;
@@ -1104,21 +1107,18 @@
 
 WriteLnToConsole('Generating forts land...');
 
-tmpsurf:= LoadImage(Pathz[ptForts] + '/' + ClansArray[0]^.Teams[0]^.FortName + 'L', ifAlpha or ifCritical or ifTransparent or ifIgnoreCaps);
+tmpsurf:= LoadImage(UserPathz[ptForts] + '/' + ClansArray[0]^.Teams[0]^.FortName + 'L', ifAlpha or ifTransparent or ifIgnoreCaps);
+if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[ptForts] + '/' + ClansArray[0]^.Teams[0]^.FortName + 'L', ifAlpha or ifCritical or ifTransparent or ifIgnoreCaps);
 BlitImageAndGenerateCollisionInfo(leftX+150, LAND_HEIGHT - tmpsurf^.h, tmpsurf^.w, tmpsurf);
 SDL_FreeSurface(tmpsurf);
 
-tmpsurf:= LoadImage(Pathz[ptForts] + '/' + ClansArray[1]^.Teams[0]^.FortName + 'R', ifAlpha or ifCritical or ifTransparent or ifIgnoreCaps);
+tmpsurf:= LoadImage(UserPathz[ptForts] + '/' + ClansArray[1]^.Teams[0]^.FortName + 'R', ifAlpha or ifTransparent or ifIgnoreCaps);
+if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[ptForts] + '/' + ClansArray[1]^.Teams[0]^.FortName + 'R', ifAlpha or ifCritical or ifTransparent or ifIgnoreCaps);
 BlitImageAndGenerateCollisionInfo(rightX - 150 - tmpsurf^.w, LAND_HEIGHT - tmpsurf^.h, tmpsurf^.w, tmpsurf);
 SDL_FreeSurface(tmpsurf);
 end;
 
-// Hi unC0Rr.
-// This is a function that Tiy assures me would not be good for gameplay.
-// It allows the setting of arbitrary portions of landscape as indestructible, or regular, or even blank.
-// He said I could add it here only when I swore it would not impact gameplay.  Which, as far as I can tell, is true.
-// I would just like to play with it with my friends if you do not mind.
-// Can allow for amusing maps.
+// Loads Land[] from an image, allowing overriding standard collision
 procedure LoadMask(mapName: shortstring);
 var tmpsurf: PSDL_Surface;
     p: PLongwordArray;
@@ -1167,11 +1167,13 @@
 isMap:= true;
 WriteLnToConsole('Loading land from file...');
 AddProgress;
-tmpsurf:= LoadImage(Pathz[ptMapCurrent] + '/map', ifAlpha or ifTransparent or ifIgnoreCaps);
+tmpsurf:= LoadImage(UserPathz[ptMapCurrent] + '/map', ifAlpha or ifTransparent or ifIgnoreCaps);
+if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[ptMapCurrent] + '/map', ifAlpha or ifTransparent or ifIgnoreCaps);
 if tmpsurf = nil then
 begin
     mapName:= ExtractFileName(Pathz[ptMapCurrent]);
-    tmpsurf:= LoadImage(Pathz[ptMissionMaps] + '/' + mapName + '/map', ifAlpha or ifCritical or ifTransparent or ifIgnoreCaps);
+    tmpsurf:= LoadImage(UserPathz[ptMissionMaps] + '/' + mapName + '/map', ifAlpha or ifTransparent or ifIgnoreCaps);
+    if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[ptMissionMaps] + '/' + mapName + '/map', ifAlpha or ifCritical or ifTransparent or ifIgnoreCaps);
 end;
 TryDo((tmpsurf^.w <= LAND_WIDTH) and (tmpsurf^.h <= LAND_HEIGHT), 'Map dimensions too big!', true);
 
--- a/hedgewars/uLandObjects.pas	Sun Jun 12 14:45:26 2011 -0400
+++ b/hedgewars/uLandObjects.pas	Sun Jun 12 21:06:48 2011 -0400
@@ -196,7 +196,9 @@
 if x1 > 0 then
 begin
     bRes:= true;
-    tmpsurf:= LoadImage(Pathz[ptCurrTheme] + '/Girder', ifTransparent or ifIgnoreCaps);
+    tmpsurf:= LoadImage(UserPathz[ptCurrTheme] + '/Girder', ifTransparent or ifIgnoreCaps);
+    if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[ptCurrTheme] + '/Girder', ifTransparent or ifIgnoreCaps);
+    if tmpsurf = nil then tmpsurf:= LoadImage(UserPathz[ptGraphics] + '/Girder', ifTransparent or ifIgnoreCaps);
     if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[ptGraphics] + '/Girder', ifCritical or ifTransparent or ifIgnoreCaps);
 
     rr.x:= x1;
@@ -383,7 +385,8 @@
 
 AddProgress;
 
-s:= Pathz[ptCurrTheme] + '/' + cThemeCFGFilename;
+s:= UserPathz[ptCurrTheme] + '/' + cThemeCFGFilename;
+if not FileExists(s) then s:= Pathz[ptCurrTheme] + '/' + cThemeCFGFilename;
 WriteLnToConsole('Reading objects info...');
 Assign(f, s);
 {$I-}
@@ -469,7 +472,8 @@
         with ThemeObjects.objs[Pred(ThemeObjects.Count)] do
             begin
             i:= Pos(',', s);
-            Surf:= LoadImage(Pathz[ptCurrTheme] + '/' + Trim(Copy(s, 1, Pred(i))), ifCritical or ifTransparent or ifIgnoreCaps);
+            Surf:= LoadImage(UserPathz[ptCurrTheme] + '/' + Trim(Copy(s, 1, Pred(i))), ifTransparent or ifIgnoreCaps);
+            if Surf = nil then LoadImage(Pathz[ptCurrTheme] + '/' + Trim(Copy(s, 1, Pred(i))), ifCritical or ifTransparent or ifIgnoreCaps);
             Width:= Surf^.w;
             Height:= Surf^.h;
             Delete(s, 1, i);
@@ -525,7 +529,8 @@
         with SprayObjects.objs[Pred(SprayObjects.Count)] do
             begin
             i:= Pos(',', s);
-            Surf:= LoadImage(Pathz[ptCurrTheme] + '/' + Trim(Copy(s, 1, Pred(i))), ifCritical or ifTransparent or ifIgnoreCaps);
+            Surf:= LoadImage(UserPathz[ptCurrTheme] + '/' + Trim(Copy(s, 1, Pred(i))), ifTransparent or ifIgnoreCaps);
+            if Surf = nil then Surf:= LoadImage(Pathz[ptCurrTheme] + '/' + Trim(Copy(s, 1, Pred(i))), ifCritical or ifTransparent or ifIgnoreCaps);
             Width:= Surf^.w;
             Height:= Surf^.h;
             Delete(s, 1, i);
--- a/hedgewars/uScript.pas	Sun Jun 12 14:45:26 2011 -0400
+++ b/hedgewars/uScript.pas	Sun Jun 12 21:06:48 2011 -0400
@@ -1220,6 +1220,18 @@
     lc_getdatapath:= 1
 end;
 
+function lc_getuserdatapath(L : Plua_State) : LongInt; Cdecl;
+begin
+    if lua_gettop(L) <> 0 then
+        begin
+        LuaError('Lua: Wrong number of parameters passed to GetUserDataPath!');
+        lua_pushnil(L);
+        end
+    else
+        lua_pushstring(L, str2pchar(UserPathz[ptData]));
+    lc_getuserdatapath:= 1
+end;
+
 function lc_maphasborder(L : Plua_State) : LongInt; Cdecl;
 begin
     if lua_gettop(L) <> 0 then
@@ -1722,6 +1734,7 @@
 lua_register(luaState, 'GetRandom', @lc_getrandom);
 lua_register(luaState, 'SetWind', @lc_setwind);
 lua_register(luaState, 'GetDataPath', @lc_getdatapath);
+lua_register(luaState, 'GetUserDataPath', @lc_getuserdatapath);
 lua_register(luaState, 'MapHasBorder', @lc_maphasborder);
 lua_register(luaState, 'GetHogHat', @lc_gethoghat);
 lua_register(luaState, 'SetHogHat', @lc_sethoghat);
--- a/hedgewars/uSound.pas	Sun Jun 12 14:45:26 2011 -0400
+++ b/hedgewars/uSound.pas	Sun Jun 12 21:06:48 2011 -0400
@@ -106,13 +106,23 @@
     if cLocale <> 'en' then
         begin
         locName:= name+'_'+cLocale;
-        path:= Pathz[ptVoices] + '/' + locName;
+        path:= UserPathz[ptVoices] + '/' + locName;
         if DirectoryExists(path) then name:= locName
-        else if Length(cLocale) > 2 then
+        else
             begin
-            locName:= name+'_'+Copy(cLocale,1,2);
             path:= Pathz[ptVoices] + '/' + locName;
             if DirectoryExists(path) then name:= locName
+            else if Length(cLocale) > 2 then
+                begin
+                locName:= name+'_'+Copy(cLocale,1,2);
+                path:= UserPathz[ptVoices] + '/' + locName;
+                if DirectoryExists(path) then name:= locName
+                else
+                    begin
+                    path:= Pathz[ptVoices] + '/' + locName;
+                    if DirectoryExists(path) then name:= locName
+                    end
+                end
             end
         end;
 
@@ -209,7 +219,8 @@
                   sndMolotov, sndMortar, sndRideOfTheValkyries, sndYoohoo])
             and (Soundz[i].Path <> ptVoices) and (Soundz[i].FileName <> '') then
         begin
-            s:= Pathz[Soundz[i].Path] + '/' + Soundz[i].FileName;
+            s:= UserPathz[Soundz[i].Path] + '/' + Soundz[i].FileName;
+            if not FileExists(s) then s:= Pathz[Soundz[i].Path] + '/' + Soundz[i].FileName;
             WriteToConsole(msgLoading + s + ' ');
             defVoicepack^.chunks[i]:= Mix_LoadWAV_RW(SDL_RWFromFile(Str2PChar(s), 'rb'), 1);
             TryDo(defVoicepack^.chunks[i] <> nil, msgFailed, true);
@@ -247,7 +258,8 @@
     begin
         if (voicepack^.chunks[snd] = nil) and (Soundz[snd].Path = ptVoices) and (Soundz[snd].FileName <> '') then
         begin
-            s:= Pathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
+            s:= UserPathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
+            if not FileExists(s) then s:= Pathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
             WriteToConsole(msgLoading + s + ' ');
             voicepack^.chunks[snd]:= Mix_LoadWAV_RW(SDL_RWFromFile(Str2PChar(s), 'rb'), 1);
             if voicepack^.chunks[snd] = nil then
@@ -261,7 +273,8 @@
     begin
         if (defVoicepack^.chunks[snd] = nil) and (Soundz[snd].Path <> ptVoices) and (Soundz[snd].FileName <> '') then
         begin
-            s:= Pathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName;
+            s:= UserPathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName;
+            if not FileExists(s) then s:= Pathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName;
             WriteToConsole(msgLoading + s + ' ');
             defVoicepack^.chunks[snd]:= Mix_LoadWAV_RW(SDL_RWFromFile(Str2PChar(s), 'rb'), 1);
             TryDo(defVoicepack^.chunks[snd] <> nil, msgFailed, true);
@@ -300,7 +313,8 @@
     begin
         if (voicepack^.chunks[snd] = nil) and (Soundz[snd].Path = ptVoices) and (Soundz[snd].FileName <> '') then
         begin
-            s:= Pathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
+            s:= UserPathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
+            if not FileExists(s) then s:= Pathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
             WriteToConsole(msgLoading + s + ' ');
             voicepack^.chunks[snd]:= Mix_LoadWAV_RW(SDL_RWFromFile(Str2PChar(s), 'rb'), 1);
             if voicepack^.chunks[snd] = nil then
@@ -314,7 +328,8 @@
     begin
         if (defVoicepack^.chunks[snd] = nil) and (Soundz[snd].Path <> ptVoices) and (Soundz[snd].FileName <> '') then
         begin
-            s:= Pathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName;
+            s:= UserPathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName;
+            if not FileExists(s) then s:= Pathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName;
             WriteToConsole(msgLoading + s + ' ');
             defVoicepack^.chunks[snd]:= Mix_LoadWAV_RW(SDL_RWFromFile(Str2PChar(s), 'rb'), 1);
             TryDo(defVoicepack^.chunks[snd] <> nil, msgFailed, true);
@@ -360,7 +375,8 @@
     if (not isSoundEnabled) or (MusicFN = '') or (not isMusicEnabled) then
         exit;
 
-    s:= PathPrefix + '/Music/' + MusicFN;
+    s:= UserPathPrefix + '/Music/' + MusicFN;
+    if not FileExists(s) then s:= PathPrefix + '/Music/' + MusicFN;
     WriteToConsole(msgLoading + s + ' ');
 
     Mus:= Mix_LoadMUS(Str2PChar(s));
--- a/hedgewars/uStore.pas	Sun Jun 12 14:45:26 2011 -0400
+++ b/hedgewars/uStore.pas	Sun Jun 12 21:06:48 2011 -0400
@@ -82,7 +82,8 @@
     Color, i: Longword;
     s : shortstring;
 begin
-s:= Pathz[ptGraphics] + '/' + cCHFileName;
+s:= UserPathz[ptGraphics] + '/' + cCHFileName;
+if not FileExists(s+'.png') then s:= Pathz[ptGraphics] + '/' + cCHFileName;
 tmpsurf:= LoadImage(s, ifAlpha or ifCritical);
 
 for t:= 0 to Pred(TeamsCount) do
@@ -168,9 +169,10 @@
         else if Flag = 'cpu' then
             Flag:= 'hedgewars';
 
-        flagsurf:= LoadImage(Pathz[ptFlags] + '/' + Flag, ifNone);
-        if flagsurf = nil then
-            flagsurf:= LoadImage(Pathz[ptFlags] + '/hedgewars', ifNone);
+        flagsurf:= LoadImage(UserPathz[ptFlags] + '/' + Flag, ifNone);
+        if flagsurf = nil then flagsurf:= LoadImage(Pathz[ptFlags] + '/' + Flag, ifNone);
+        if flagsurf = nil then flagsurf:= LoadImage(UserPathz[ptFlags] + '/hedgewars', ifNone);
+        if flagsurf = nil then flagsurf:= LoadImage(Pathz[ptFlags] + '/hedgewars', ifNone);
         TryDo(flagsurf <> nil, 'Failed to load flag "' + Flag + '" as well as the default flag', true);
         copyToXY(flagsurf, texsurf, 2, 2);
         SDL_FreeSurface(flagsurf);
@@ -204,7 +206,8 @@
                         end
                     end;
         end;
-    MissionIcons:= LoadImage(Pathz[ptGraphics] + '/missions', ifCritical);
+    MissionIcons:= LoadImage(UserPathz[ptGraphics] + '/missions', ifNone);
+    if MissionIcons = nil then MissionIcons:= LoadImage(Pathz[ptGraphics] + '/missions', ifCritical);
     iconsurf:= SDL_CreateRGBSurface(SDL_SWSURFACE, 28, 28, 32, RMask, GMask, BMask, AMask);
     if iconsurf <> nil then
         begin
@@ -241,7 +244,9 @@
         with TeamsArray[t]^ do
             begin
             if GraveName = '' then GraveName:= 'Statue';
-            texsurf:= LoadImage(Pathz[ptGraves] + '/' + GraveName, ifTransparent);
+            texsurf:= LoadImage(UserPathz[ptGraves] + '/' + GraveName, ifTransparent);
+            if texsurf = nil then texsurf:= LoadImage(Pathz[ptGraves] + '/' + GraveName, ifTransparent);
+            if texsurf = nil then texsurf:= LoadImage(UserPathz[ptGraves] + '/Statue', ifTransparent);
             if texsurf = nil then texsurf:= LoadImage(Pathz[ptGraves] + '/Statue', ifCritical or ifTransparent);
             GraveTex:= Surface2Tex(texsurf, false);
             SDL_FreeSurface(texsurf)
@@ -258,7 +263,8 @@
 for fi:= Low(THWFont) to High(THWFont) do
     with Fontz[fi] do
         begin
-        s:= Pathz[ptFonts] + '/' + Name;
+        s:= UserPathz[ptFonts] + '/' + Name;
+        if not FileExists(s) then s:= Pathz[ptFonts] + '/' + Name;
         WriteToConsole(msgLoading + s + ' (' + inttostr(Height) + 'pt)... ');
         Handle:= TTF_OpenFont(Str2PChar(s), Height);
         SDLTry(Handle <> nil, true);
@@ -281,13 +287,20 @@
         begin
             if AltPath = ptNone then
                 if ii in [sprHorizontL, sprHorizontR, sprSkyL, sprSkyR] then // FIXME: hack
-                    tmpsurf:= LoadImage(Pathz[Path] + '/' + FileName, ifAlpha or ifTransparent)
+                    begin
+                    tmpsurf:= LoadImage(UserPathz[Path] + '/' + FileName, ifAlpha or ifTransparent);
+                    if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[Path] + '/' + FileName, ifAlpha or ifTransparent)
+                    end
                 else
-                    tmpsurf:= LoadImage(Pathz[Path] + '/' + FileName, ifAlpha or ifTransparent or ifCritical)
+                    begin
+                    tmpsurf:= LoadImage(UserPathz[Path] + '/' + FileName, ifAlpha or ifTransparent);
+                    if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[Path] + '/' + FileName, ifAlpha or ifTransparent or ifCritical)
+                    end
             else begin
-                tmpsurf:= LoadImage(Pathz[Path] + '/' + FileName, ifAlpha or ifTransparent);
-                if tmpsurf = nil then
-                    tmpsurf:= LoadImage(Pathz[AltPath] + '/' + FileName, ifAlpha or ifCritical or ifTransparent);
+                tmpsurf:= LoadImage(UserPathz[Path] + '/' + FileName, ifAlpha or ifTransparent);
+                if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[Path] + '/' + FileName, ifAlpha or ifTransparent);
+                if tmpsurf = nil then tmpsurf:= LoadImage(UserPathz[AltPath] + '/' + FileName, ifAlpha or ifTransparent);
+                if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[AltPath] + '/' + FileName, ifAlpha or ifCritical or ifTransparent);
                 end;
 
             if tmpsurf <> nil then
@@ -324,7 +337,8 @@
 
 AddProgress;
 
-tmpsurf:= LoadImage(Pathz[ptGraphics] + '/' + cHHFileName, ifAlpha or ifCritical or ifTransparent);
+tmpsurf:= LoadImage(UserPathz[ptGraphics] + '/' + cHHFileName, ifAlpha or ifTransparent);
+if tmpsurf = nil then tmpsurf:= LoadImage(Pathz[ptGraphics] + '/' + cHHFileName, ifAlpha or ifCritical or ifTransparent);
 HHTexture:= Surface2Tex(tmpsurf, false);
 SDL_FreeSurface(tmpsurf);
 
@@ -475,7 +489,8 @@
 procedure LoadHedgehogHat(HHGear: PGear; newHat: shortstring);
 var texsurf: PSDL_Surface;
 begin
-    texsurf:= LoadImage(Pathz[ptHats] + '/' + newHat, ifNone);
+    texsurf:= LoadImage(UserPathz[ptHats] + '/' + newHat, ifNone);
+    if texsurf = nil then texsurf:= LoadImage(Pathz[ptHats] + '/' + newHat, ifNone);
 
     // only do something if the hat could be loaded
     if texsurf <> nil then
@@ -667,7 +682,8 @@
     if Step = 0 then
     begin
         WriteToConsole(msgLoading + 'progress sprite: ');
-        texsurf:= LoadImage(Pathz[ptGraphics] + '/Progress', ifCritical or ifTransparent);
+        texsurf:= LoadImage(UserPathz[ptGraphics] + '/Progress', ifTransparent);
+        if texsurf = nil then texsurf:= LoadImage(Pathz[ptGraphics] + '/Progress', ifCritical or ifTransparent);
 
         ProgrTex:= Surface2Tex(texsurf, false);
 
@@ -906,9 +922,11 @@
 {$ENDIF}
     // load engine icon
 {$IFDEF DARWIN}
-    ico:= LoadImage(Pathz[ptGraphics] + '/hwengine_mac', ifIgnoreCaps);
+    ico:= LoadImage(UserPathz[ptGraphics] + '/hwengine_mac', ifIgnoreCaps);
+    if ico = nil then ico:= LoadImage(Pathz[ptGraphics] + '/hwengine_mac', ifIgnoreCaps);
 {$ELSE}
-    ico:= LoadImage(Pathz[ptGraphics] + '/hwengine', ifIgnoreCaps);
+    ico:= LoadImage(UserPathz[ptGraphics] + '/hwengine', ifIgnoreCaps);
+    if ico = nil then ico:= LoadImage(Pathz[ptGraphics] + '/hwengine', ifIgnoreCaps);
 {$ENDIF}
     if ico <> nil then
     begin
--- a/hedgewars/uVariables.pas	Sun Jun 12 14:45:26 2011 -0400
+++ b/hedgewars/uVariables.pas	Sun Jun 12 21:06:48 2011 -0400
@@ -37,6 +37,7 @@
     cInitVolume     : LongInt     = 100;
     cTimerInterval  : LongInt     = 8;
     PathPrefix      : shortstring = './';
+    UserPathPrefix  : shortstring = './';
     cShowFPS        : boolean     = false;
     cAltDamage      : boolean     = true;
     cReducedQuality : LongWord    = rqNone;
@@ -101,6 +102,7 @@
 
     // originally from uConsts
     Pathz: array[TPathType] of shortstring;
+    UserPathz: array[TPathType] of shortstring;
     CountTexz: array[1..Pred(AMMO_INFINITE)] of PTexture;
     LAND_WIDTH       : Word;
     LAND_HEIGHT      : Word;
@@ -2402,6 +2404,7 @@
     cInitVolume     := 100;
     cTimerInterval  := 8;
     PathPrefix      := './';
+    UserPathPrefix  := './';
     cShowFPS        := false;
     cAltDamage      := true;
     cReducedQuality := rqNone;