# HG changeset patch # User nemo # Date 1346346139 14400 # Node ID bc7b1d228a2c109776be914b04995f7addc779b3 # Parent 7ee3191347136ce9cbfb057ff9a4a165310905cf# Parent e1e112687fd6b7f9bcaa8627e54546050132cc89 Checking merge against latest trunk diff -r 7ee319134713 -r bc7b1d228a2c INSTALL --- a/INSTALL Thu Aug 30 12:47:41 2012 -0400 +++ b/INSTALL Thu Aug 30 13:02:19 2012 -0400 @@ -18,9 +18,9 @@ $ cmake . or $ cmake -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="install_prefix" \ --DDATA_INSTALL_DIR="data_dir" . +-DDATA_INSTALL_DIR="data_dir" -DNOSERVER=1 . -add -DWITH_SERVER=1 to compile net server; if you have Qt installed but it is +add -DNOSERVER=0 to compile net server; if you have Qt installed but it is not found you can set it up with -DQT_QMAKE_EXECUTABLE="path_to_qmake" 2. Compile: diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/CMakeLists.txt --- a/QTfrontend/CMakeLists.txt Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/CMakeLists.txt Thu Aug 30 13:02:19 2012 -0400 @@ -28,6 +28,7 @@ # Configure for SDL find_package(SDL REQUIRED) find_package(SDL_mixer REQUIRED) +find_package(FFMPEG) include_directories(.) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/model) @@ -39,6 +40,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/util) include_directories(${SDL_INCLUDE_DIR}) include_directories(${SDLMIXER_INCLUDE_DIR}) +include_directories(${FFMPEG_INCLUDE_DIR}) include_directories(${CMAKE_SOURCE_DIR}/misc/quazip) if(UNIX) # HACK: in freebsd cannot find iconv.h included via SDL.h @@ -71,6 +73,10 @@ file(GLOB_RECURSE UIcpp ui/*.cpp) file(GLOB UtilCpp util/*.cpp) +if((NOT NO_VIDEOREC) AND "${FFMPEG_FOUND}") + add_definitions(-DVIDEOREC) +endif() + set(hwfr_src ${ModelCpp} ${NetCpp} @@ -172,6 +178,7 @@ ${QT_LIBRARIES} ${SDL_LIBRARY} ${SDLMIXER_LIBRARY} + ${FFMPEG_LIBRARIES} ${HW_LINK_LIBS} ) diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/binds.cpp --- a/QTfrontend/binds.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/binds.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -65,5 +65,6 @@ {"mute", "8", QT_TRANSLATE_NOOP("binds", "mute audio"), NULL, NULL}, {"fullscr", "f12", QT_TRANSLATE_NOOP("binds", "change mode"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Toggle fullscreen mode:")}, {"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:")} + {"rotmask", "delete", QT_TRANSLATE_NOOP("binds", "hedgehogs\ninfo"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Toggle labels above hedgehogs:")}, + {"record", "r", QT_TRANSLATE_NOOP("binds", "record"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Record video:")} }; diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/binds.h --- a/QTfrontend/binds.h Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/binds.h Thu Aug 30 13:02:19 2012 -0400 @@ -21,7 +21,7 @@ #include -#define BINDS_NUMBER 45 +#define BINDS_NUMBER 46 struct BindAction { diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/game.cpp --- a/QTfrontend/game.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/game.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -53,20 +53,20 @@ { switch (gameType) { - case gtSave: - if (gameState == gsInterrupted || gameState == gsHalted) - emit HaveRecord(false, demo); - else if (gameState == gsFinished) - emit HaveRecord(true, demo); - break; case gtDemo: + // for video recording we need demo anyway + emit HaveRecord(rtNeither, demo); break; case gtNet: - emit HaveRecord(true, demo); + emit HaveRecord(rtDemo, demo); break; default: - if (gameState == gsInterrupted || gameState == gsHalted) emit HaveRecord(false, demo); - else if (gameState == gsFinished) emit HaveRecord(true, demo); + if (gameState == gsInterrupted || gameState == gsHalted) + emit HaveRecord(rtSave, demo); + else if (gameState == gsFinished) + emit HaveRecord(rtDemo, demo); + else + emit HaveRecord(rtNeither, demo); } SetGameState(gsStopped); } diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/game.h --- a/QTfrontend/game.h Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/game.h Thu Aug 30 13:02:19 2012 -0400 @@ -40,6 +40,13 @@ gsHalted = 6 }; +enum RecordType +{ + rtDemo, + rtSave, + rtNeither, +}; + bool checkForDir(const QString & dir); class HWGame : public TCPBase @@ -70,7 +77,7 @@ void SendTeamMessage(const QString & msg); void GameStateChanged(GameState gameState); void GameStats(char type, const QString & info); - void HaveRecord(bool isDemo, const QByteArray & record); + void HaveRecord(RecordType type, const QByteArray & record); void ErrorMessage(const QString &); void CampStateChanged(int); diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/gameuiconfig.cpp --- a/QTfrontend/gameuiconfig.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/gameuiconfig.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -22,14 +22,17 @@ #include #include #include +#include #include "gameuiconfig.h" #include "hwform.h" #include "pageoptions.h" +#include "pagevideos.h" #include "pagenetserver.h" #include "hwconsts.h" #include "fpsedit.h" #include "HWApplication.h" +#include "DataManager.h" GameUIConfig::GameUIConfig(HWForm * FormWidgets, const QString & fileName) : QSettings(fileName, QSettings::IniFormat) @@ -42,6 +45,7 @@ resizeToConfigValues(); reloadValues(); + reloadVideosValues(); } void GameUIConfig::reloadValues(void) @@ -108,6 +112,35 @@ depth = HWApplication::desktop()->depth(); if (depth < 16) depth = 16; else if (depth > 16) depth = 32; + + { // load colors + QStandardItemModel * model = DataManager::instance().colorsModel(); + for(int i = model->rowCount() - 1; i >= 0; --i) + model->item(i)->setData(QColor(value(QString("colors/color%1").arg(i), model->item(i)->data().value()).value())); + } +} + +void GameUIConfig::reloadVideosValues(void) +{ + Form->ui.pageVideos->framerateBox->setValue(value("videorec/fps",25).toUInt()); + bool useGameRes = value("videorec/usegameres",true).toBool(); + if (useGameRes) + { + QRect res = vid_Resolution(); + Form->ui.pageVideos->widthEdit->setText(QString::number(res.width())); + Form->ui.pageVideos->heightEdit->setText(QString::number(res.height())); + } + else + { + Form->ui.pageVideos->widthEdit->setText(value("videorec/width","800").toString()); + Form->ui.pageVideos->heightEdit->setText(value("videorec/height","600").toString()); + } + Form->ui.pageVideos->checkUseGameRes->setChecked(useGameRes); + Form->ui.pageVideos->checkRecordAudio->setChecked(value("videorec/audio",true).toBool()); + if (!Form->ui.pageVideos->tryCodecs(value("videorec/format","no").toString(), + value("videorec/videocodec","no").toString(), + value("videorec/audiocodec","no").toString())) + Form->ui.pageVideos->setDefaultCodecs(); } QStringList GameUIConfig::GetTeamsList() @@ -182,6 +215,28 @@ #ifdef SPARKLE_ENABLED setValue("misc/autoUpdate", isAutoUpdateEnabled()); #endif + + { // save colors + QStandardItemModel * model = DataManager::instance().colorsModel(); + for(int i = model->rowCount() - 1; i >= 0; --i) + setValue(QString("colors/color%1").arg(i), model->item(i)->data()); + } + + Form->gameSettings->sync(); +} + +void GameUIConfig::SaveVideosOptions() +{ + QRect res = rec_Resolution(); + setValue("videorec/format", AVFormat()); + setValue("videorec/videocodec", videoCodec()); + setValue("videorec/audiocodec", audioCodec()); + setValue("videorec/fps", rec_Framerate()); + setValue("videorec/width", res.width()); + setValue("videorec/height", res.height()); + setValue("videorec/usegameres", Form->ui.pageVideos->checkUseGameRes->isChecked()); + setValue("videorec/audio", recordAudio()); + Form->gameSettings->sync(); } @@ -380,3 +435,38 @@ { return Form->ui.pageOptions->volumeBox->value() * 128 / 100; } + +QString GameUIConfig::AVFormat() +{ + return Form->ui.pageVideos->format(); +} + +QString GameUIConfig::videoCodec() +{ + return Form->ui.pageVideos->videoCodec(); +} + +QString GameUIConfig::audioCodec() +{ + return Form->ui.pageVideos->audioCodec(); +} + +QRect GameUIConfig::rec_Resolution() +{ + if (Form->ui.pageVideos->checkUseGameRes->isChecked()) + return vid_Resolution(); + QRect res(0,0,0,0); + res.setWidth(Form->ui.pageVideos->widthEdit->text().toUInt()); + res.setHeight(Form->ui.pageVideos->heightEdit->text().toUInt()); + return res; +} + +int GameUIConfig::rec_Framerate() +{ + return Form->ui.pageVideos->framerateBox->value(); +} + +bool GameUIConfig::recordAudio() +{ + return Form->ui.pageVideos->checkRecordAudio->isChecked(); +} diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/gameuiconfig.h --- a/QTfrontend/gameuiconfig.h Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/gameuiconfig.h Thu Aug 30 13:02:19 2012 -0400 @@ -59,18 +59,27 @@ void resizeToConfigValues(); quint32 stereoMode() const; + QString AVFormat(); + QString videoCodec(); + QString audioCodec(); + QRect rec_Resolution(); + int rec_Framerate(); + bool recordAudio(); + #ifdef __APPLE__ #ifdef SPARKLE_ENABLED bool isAutoUpdateEnabled(); #endif #endif - void reloadValues(void); + void reloadValues(); + void reloadVideosValues(); signals: void frontendFullscreen(bool value); public slots: void SaveOptions(); + void SaveVideosOptions(); void updNetNick(); private: bool netPasswordIsValid(); diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/hwform.cpp --- a/QTfrontend/hwform.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/hwform.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -77,6 +77,7 @@ #include "pagegamestats.h" #include "pageplayrecord.h" #include "pagedata.h" +#include "pagevideos.h" #include "hwconsts.h" #include "newnetclient.h" #include "gamecfgwidget.h" @@ -91,6 +92,7 @@ #include "drawmapwidget.h" #include "mouseoverfilter.h" #include "roomslistmodel.h" +#include "recorder.h" #include "DataManager.h" @@ -141,6 +143,8 @@ config = new GameUIConfig(this, cfgdir->absolutePath() + "/hedgewars.ini"); + ui.pageVideos->init(config); + #ifdef __APPLE__ panel = new M3Panel; @@ -200,12 +204,17 @@ connect(ui.pageNetGame, SIGNAL(DLCClicked()), pageSwitchMapper, SLOT(map())); pageSwitchMapper->setMapping(ui.pageNetGame, ID_PAGE_DATADOWNLOAD); +#ifdef VIDEOREC + connect(ui.pageMain->BtnVideos, SIGNAL(clicked()), pageSwitchMapper, SLOT(map())); + pageSwitchMapper->setMapping(ui.pageMain->BtnVideos, ID_PAGE_VIDEOS); +#endif + //connect(ui.pageMain->BtnExit, SIGNAL(pressed()), this, SLOT(btnExitPressed())); //connect(ui.pageMain->BtnExit, SIGNAL(clicked()), this, SLOT(btnExitClicked())); connect(ui.pageFeedback->BtnSend, SIGNAL(clicked()), this, SLOT(SendFeedback())); - connect(ui.pageEditTeam, SIGNAL(teamEdited()), this, SLOT(AfterTeamEdit())); + connect(ui.pageEditTeam, SIGNAL(goBack()), this, SLOT(AfterTeamEdit())); connect(ui.pageMultiplayer->BtnStartMPGame, SIGNAL(clicked()), this, SLOT(StartMPGame())); connect(ui.pageMultiplayer->teamsSelect, SIGNAL(setEnabledGameStart(bool)), @@ -291,6 +300,7 @@ connect(ui.pageConnecting, SIGNAL(cancelConnection()), this, SLOT(GoBack())); + connect(ui.pageVideos, SIGNAL(goBack()), config, SLOT(SaveVideosOptions())); ammoSchemeModel = new AmmoSchemeModel(this, cfgdir->absolutePath() + "/schemes.ini"); ui.pageScheme->setModel(ammoSchemeModel); @@ -515,6 +525,11 @@ GoToPage(ID_PAGE_SCHEME); } +void HWForm::GoToVideos() +{ + GoToPage(ID_PAGE_VIDEOS); +} + void HWForm::OnPageShown(quint8 id, quint8 lastid) { #ifdef USE_XFIRE @@ -606,6 +621,11 @@ config->reloadValues(); } + if (id == ID_PAGE_VIDEOS ) + { + config->reloadVideosValues(); + } + // load and save ignore/friends lists if (lastid == ID_PAGE_NETGAME) // leaving a room ui.pageNetGame->pChatWidget->saveLists(ui.pageOptions->editNetNick->text()); @@ -707,6 +727,8 @@ int curid = ui.Pages->currentIndex(); if (curid == ID_PAGE_MAIN) { + if (!ui.pageVideos->tryQuit(this)) + return; stopAnim = true; exit(); } @@ -881,7 +903,7 @@ void HWForm::AfterTeamEdit() { UpdateTeamsLists(); - GoBack(); + //GoBack(); } @@ -1361,7 +1383,7 @@ connect(game, SIGNAL(GameStateChanged(GameState)), this, SLOT(GameStateChanged(GameState))); connect(game, SIGNAL(GameStats(char, const QString &)), ui.pageGameStats, SLOT(GameStats(char, const QString &))); connect(game, SIGNAL(ErrorMessage(const QString &)), this, SLOT(ShowErrorMessage(const QString &)), Qt::QueuedConnection); - connect(game, SIGNAL(HaveRecord(bool, const QByteArray &)), this, SLOT(GetRecord(bool, const QByteArray &))); + connect(game, SIGNAL(HaveRecord(RecordType, const QByteArray &)), this, SLOT(GetRecord(RecordType, const QByteArray &))); m_lastDemo = QByteArray(); } @@ -1372,43 +1394,47 @@ msg); } -void HWForm::GetRecord(bool isDemo, const QByteArray & record) +void HWForm::GetRecord(RecordType type, const QByteArray & record) { - QString filename; - QByteArray demo = record; - QString recordFileName = - config->appendDateTimeToRecordName() ? - QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm") : - "LastRound"; + if (type != rtNeither) + { + QString filename; + QByteArray demo = record; + QString recordFileName = + config->appendDateTimeToRecordName() ? + QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm") : + "LastRound"; - QStringList versionParts = cVersionString->split('-'); - if ( (versionParts.size() == 2) && (!versionParts[1].isEmpty()) && (versionParts[1].contains(':')) ) - recordFileName = recordFileName + "_" + versionParts[1].replace(':','-'); + QStringList versionParts = cVersionString->split('-'); + if ( (versionParts.size() == 2) && (!versionParts[1].isEmpty()) && (versionParts[1].contains(':')) ) + recordFileName = recordFileName + "_" + versionParts[1].replace(':','-'); - if (isDemo) - { - demo.replace(QByteArray("\x02TL"), QByteArray("\x02TD")); - demo.replace(QByteArray("\x02TN"), QByteArray("\x02TD")); - demo.replace(QByteArray("\x02TS"), QByteArray("\x02TD")); - filename = cfgdir->absolutePath() + "/Demos/" + recordFileName + "." + *cProtoVer + ".hwd"; - m_lastDemo = demo; - } - else - { - demo.replace(QByteArray("\x02TL"), QByteArray("\x02TS")); - demo.replace(QByteArray("\x02TN"), QByteArray("\x02TS")); - filename = cfgdir->absolutePath() + "/Saves/" + recordFileName + "." + *cProtoVer + ".hws"; + if (type == rtDemo) + { + demo.replace(QByteArray("\x02TL"), QByteArray("\x02TD")); + demo.replace(QByteArray("\x02TN"), QByteArray("\x02TD")); + demo.replace(QByteArray("\x02TS"), QByteArray("\x02TD")); + filename = cfgdir->absolutePath() + "/Demos/" + recordFileName + "." + *cProtoVer + ".hwd"; + m_lastDemo = demo; + } + else + { + demo.replace(QByteArray("\x02TL"), QByteArray("\x02TS")); + demo.replace(QByteArray("\x02TN"), QByteArray("\x02TS")); + filename = cfgdir->absolutePath() + "/Saves/" + recordFileName + "." + *cProtoVer + ".hws"; + } + + QFile demofile(filename); + if (!demofile.open(QIODevice::WriteOnly)) + ShowErrorMessage(tr("Cannot save record to file %1").arg(filename)); + else + { + demofile.write(demo); + demofile.close(); + } } - - QFile demofile(filename); - if (!demofile.open(QIODevice::WriteOnly)) - { - ShowErrorMessage(tr("Cannot save record to file %1").arg(filename)); - return ; - } - demofile.write(demo); - demofile.close(); + ui.pageVideos->startEncoding(record); } void HWForm::startTraining(const QString & scriptName) @@ -1455,6 +1481,7 @@ xfire_free(); #endif config->SaveOptions(); + config->SaveVideosOptions(); event->accept(); } diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/hwform.h --- a/QTfrontend/hwform.h Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/hwform.h Thu Aug 30 13:02:19 2012 -0400 @@ -67,6 +67,7 @@ void exit(); void setButtonDescription(QString desc); void backDescription(); + void GoToVideos(); private slots: void GoToSaves(); @@ -114,7 +115,7 @@ void GameStateChanged(GameState gameState); void ForcedDisconnect(const QString & reason); void ShowErrorMessage(const QString &); - void GetRecord(bool isDemo, const QByteArray & record); + void GetRecord(RecordType type, const QByteArray & record); void CreateNetGame(); void UpdateWeapons(); void onFrontendFullscreen(bool value); @@ -177,6 +178,7 @@ ID_PAGE_DRAWMAP , ID_PAGE_DATADOWNLOAD , ID_PAGE_FEEDBACK , + ID_PAGE_VIDEOS, MAX_PAGE }; QPointer game; diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/main.cpp --- a/QTfrontend/main.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/main.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -197,6 +197,8 @@ checkForDir(cfgdir->absolutePath() + "/Screenshots"); checkForDir(cfgdir->absolutePath() + "/Teams"); checkForDir(cfgdir->absolutePath() + "/Logs"); + checkForDir(cfgdir->absolutePath() + "/Videos"); + checkForDir(cfgdir->absolutePath() + "/VideoTemp"); } datadir->cd(bindir->absolutePath()); diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/model/ammoSchemeModel.h --- a/QTfrontend/model/ammoSchemeModel.h Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/model/ammoSchemeModel.h Thu Aug 30 13:02:19 2012 -0400 @@ -47,8 +47,8 @@ public slots: void Save(); - signals: - void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight); +// signals: +// void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight); protected: QList< QList > schemes; diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/net/newnetclient.cpp --- a/QTfrontend/net/newnetclient.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/net/newnetclient.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -384,38 +384,6 @@ return; } - if (lst[0] == "ADD_TEAM") - { - if(lst.size() != 24) - { - qWarning("Net: Bad ADDTEAM message"); - return; - } - QStringList tmp = lst; - tmp.removeFirst(); - emit AddNetTeam(tmp); - return; - } - - if (lst[0] == "REMOVE_TEAM") - { - if(lst.size() != 2) - { - qWarning("Net: Bad REMOVETEAM message"); - return; - } - emit RemoveNetTeam(HWTeam(lst[1])); - return; - } - - if(lst[0] == "ROOMABANDONED") - { - netClientState = InLobby; - askRoomsList(); - emit LeftRoom(tr("Room destroyed")); - return; - } - if(lst[0] == "KICKED") { netClientState = InLobby; @@ -424,31 +392,6 @@ return; } - if(lst[0] == "JOINED") - { - if(lst.size() < 2) - { - qWarning("Net: Bad JOINED message"); - return; - } - - for(int i = 1; i < lst.size(); ++i) - { - if (lst[i] == mynick) - { - netClientState = InRoom; - emit EnteredGame(); - emit roomMaster(isChief); - if (isChief) - emit configAsked(); - } - - emit nickAdded(lst[i], isChief && (lst[i] != mynick)); - emit chatStringFromNet(tr("%1 *** %2 has joined the room").arg('\x03').arg(lst[i])); - } - return; - } - if(lst[0] == "LOBBY:JOINED") { if(lst.size() < 2) @@ -472,21 +415,6 @@ return; } - if(lst[0] == "LEFT") - { - if(lst.size() < 2) - { - qWarning("Net: Bad LEFT message"); - return; - } - emit nickRemoved(lst[1]); - if (lst.size() < 3) - emit chatStringFromNet(tr("%1 *** %2 has left").arg('\x03').arg(lst[1])); - else - emit chatStringFromNet(tr("%1 *** %2 has left (%3)").arg('\x03').arg(lst[1], lst[2])); - return; - } - if(lst[0] == "ROOM" && lst.size() == 10 && lst[1] == "ADD") { QStringList tmp = lst; @@ -529,13 +457,6 @@ return; } - if (lst[0] == "RUN_GAME") - { - netClientState = InGame; - emit AskForRunGame(); - return; - } - if (lst[0] == "ASKPASSWORD") { emit AskForPassword(mynick); @@ -563,76 +484,6 @@ return; } - if (lst[0] == "TEAM_ACCEPTED") - { - if (lst.size() != 2) - { - qWarning("Net: Bad TEAM_ACCEPTED message"); - return; - } - emit TeamAccepted(lst[1]); - return; - } - - - if (lst[0] == "CFG") - { - if(lst.size() < 3) - { - qWarning("Net: Bad CFG message"); - return; - } - QStringList tmp = lst; - tmp.removeFirst(); - tmp.removeFirst(); - if (lst[1] == "SCHEME") - emit netSchemeConfig(tmp); - else - emit paramChanged(lst[1], tmp); - return; - } - - if (lst[0] == "HH_NUM") - { - if (lst.size() != 3) - { - qWarning("Net: Bad TEAM_ACCEPTED message"); - return; - } - HWTeam tmptm(lst[1]); - tmptm.setNumHedgehogs(lst[2].toUInt()); - emit hhnumChanged(tmptm); - return; - } - - if (lst[0] == "TEAM_COLOR") - { - if (lst.size() != 3) - { - qWarning("Net: Bad TEAM_COLOR message"); - return; - } - HWTeam tmptm(lst[1]); - tmptm.setColor(lst[2].toInt()); - emit teamColorChanged(tmptm); - return; - } - - if (lst[0] == "EM") - { - if(lst.size() < 2) - { - qWarning("Net: Bad EM message"); - return; - } - for(int i = 1; i < lst.size(); ++i) - { - QByteArray em = QByteArray::fromBase64(lst[i].toAscii()); - emit FromNet(em); - } - return; - } - if (lst[0] == "BYE") { if (lst.size() < 2) @@ -650,26 +501,192 @@ return; } - if (lst[0] == "ADMIN_ACCESS") { emit adminAccess(true); return; } - if (lst[0] == "ROOM_CONTROL_ACCESS") + if(netClientState == InLobby && lst[0] == "JOINED") { - if (lst.size() < 2) + if(lst.size() < 2 || lst[1] != mynick) { - qWarning("Net: Bad ROOM_CONTROL_ACCESS message"); + qWarning("Net: Bad JOINED message"); return; } - isChief = (lst[1] != "0"); - emit roomMaster(isChief); + + for(int i = 1; i < lst.size(); ++i) + { + if (lst[i] == mynick) + { + netClientState = InRoom; + emit EnteredGame(); + emit roomMaster(isChief); + if (isChief) + emit configAsked(); + } + + emit nickAdded(lst[i], isChief && (lst[i] != mynick)); + emit chatStringFromNet(tr("%1 *** %2 has joined the room").arg('\x03').arg(lst[i])); + } return; } - qWarning() << "Net: Unknown message:" << lst; + if(netClientState == InRoom || netClientState == InGame) + { + if (lst[0] == "EM") + { + if(lst.size() < 2) + { + qWarning("Net: Bad EM message"); + return; + } + for(int i = 1; i < lst.size(); ++i) + { + QByteArray em = QByteArray::fromBase64(lst[i].toAscii()); + emit FromNet(em); + } + return; + } + + if (lst[0] == "ADD_TEAM") + { + if(lst.size() != 24) + { + qWarning("Net: Bad ADDTEAM message"); + return; + } + QStringList tmp = lst; + tmp.removeFirst(); + emit AddNetTeam(tmp); + return; + } + + if (lst[0] == "REMOVE_TEAM") + { + if(lst.size() != 2) + { + qWarning("Net: Bad REMOVETEAM message"); + return; + } + emit RemoveNetTeam(HWTeam(lst[1])); + return; + } + + if(lst[0] == "ROOMABANDONED") + { + netClientState = InLobby; + askRoomsList(); + emit LeftRoom(tr("Room destroyed")); + return; + } + + if (lst[0] == "RUN_GAME") + { + netClientState = InGame; + emit AskForRunGame(); + return; + } + + if (lst[0] == "TEAM_ACCEPTED") + { + if (lst.size() != 2) + { + qWarning("Net: Bad TEAM_ACCEPTED message"); + return; + } + emit TeamAccepted(lst[1]); + return; + } + + if (lst[0] == "CFG") + { + if(lst.size() < 3) + { + qWarning("Net: Bad CFG message"); + return; + } + QStringList tmp = lst; + tmp.removeFirst(); + tmp.removeFirst(); + if (lst[1] == "SCHEME") + emit netSchemeConfig(tmp); + else + emit paramChanged(lst[1], tmp); + return; + } + + if (lst[0] == "HH_NUM") + { + if (lst.size() != 3) + { + qWarning("Net: Bad TEAM_ACCEPTED message"); + return; + } + HWTeam tmptm(lst[1]); + tmptm.setNumHedgehogs(lst[2].toUInt()); + emit hhnumChanged(tmptm); + return; + } + + if (lst[0] == "TEAM_COLOR") + { + if (lst.size() != 3) + { + qWarning("Net: Bad TEAM_COLOR message"); + return; + } + HWTeam tmptm(lst[1]); + tmptm.setColor(lst[2].toInt()); + emit teamColorChanged(tmptm); + return; + } + + if(lst[0] == "JOINED") + { + if(lst.size() < 2) + { + qWarning("Net: Bad JOINED message"); + return; + } + + for(int i = 1; i < lst.size(); ++i) + { + emit nickAdded(lst[i], isChief && (lst[i] != mynick)); + emit chatStringFromNet(tr("%1 *** %2 has joined the room").arg('\x03').arg(lst[i])); + } + return; + } + + if(lst[0] == "LEFT") + { + if(lst.size() < 2) + { + qWarning("Net: Bad LEFT message"); + return; + } + emit nickRemoved(lst[1]); + if (lst.size() < 3) + emit chatStringFromNet(tr("%1 *** %2 has left").arg('\x03').arg(lst[1])); + else + emit chatStringFromNet(tr("%1 *** %2 has left (%3)").arg('\x03').arg(lst[1], lst[2])); + return; + } + + if (lst[0] == "ROOM_CONTROL_ACCESS") + { + if (lst.size() < 2) + { + qWarning("Net: Bad ROOM_CONTROL_ACCESS message"); + return; + } + isChief = (lst[1] != "0"); + emit roomMaster(isChief); + return; + } + } + + qWarning() << "Net: Unknown message or wrong state:" << lst; } void HWNewNet::onHedgehogsNumChanged(const HWTeam& team) diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/net/recorder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/QTfrontend/net/recorder.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,129 @@ +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +//#include + +#include "recorder.h" +#include "gameuiconfig.h" +#include "hwconsts.h" +#include "game.h" +#include "libav_iteraction.h" + +// Encoding is memory expensive process, so we need to limit maximum number +// of simultaneous encoders. +static const int maxRecorders = 3; +static int numRecorders = 0; + +static QList queue; + +HWRecorder::HWRecorder(GameUIConfig * config, const QString &prefix) : + TCPBase(false) +{ + this->config = config; + this->prefix = prefix; + finished = false; + name = prefix + "." + LibavIteraction::instance().getExtension(config->AVFormat()); +} + +HWRecorder::~HWRecorder() +{ + emit encodingFinished(finished); + if (queue.empty()) + numRecorders--; + else + queue.takeFirst()->Start(); +} + +void HWRecorder::onClientDisconnect() +{ +} + +void HWRecorder::onClientRead() +{ + quint8 msglen; + quint32 bufsize; + while (!readbuffer.isEmpty() && ((bufsize = readbuffer.size()) > 0) && + ((msglen = readbuffer.data()[0]) < bufsize)) + { + QByteArray msg = readbuffer.left(msglen + 1); + readbuffer.remove(0, msglen + 1); + switch (msg.at(1)) + { + case '?': + SendIPC("!"); + break; + case 'p': + emit onProgress((quint8(msg.at(2))*256.0 + quint8(msg.at(3)))*0.0001); + break; + case 'v': + finished = true; + break; + } + } +} + +void HWRecorder::EncodeVideo(const QByteArray & record) +{ + toSendBuf = record; + toSendBuf.replace(QByteArray("\x02TD"), QByteArray("\x02TV")); + toSendBuf.replace(QByteArray("\x02TL"), QByteArray("\x02TV")); + toSendBuf.replace(QByteArray("\x02TN"), QByteArray("\x02TV")); + toSendBuf.replace(QByteArray("\x02TS"), QByteArray("\x02TV")); + + if (numRecorders < maxRecorders) + { + numRecorders++; + Start(); // run engine + } + else + queue.push_back(this); +} + +QStringList HWRecorder::getArguments() +{ + QStringList arguments; + QRect resolution = config->rec_Resolution(); + arguments << cfgdir->absolutePath(); + arguments << QString::number(resolution.width()); + arguments << QString::number(resolution.height()); + arguments << "32"; // bpp + arguments << QString("%1").arg(ipc_port); + arguments << "0"; // fullscreen + arguments << "0"; // sound + arguments << "0"; // music + arguments << "0"; // sound volume + arguments << QString::number(config->timerInterval()); + arguments << datadir->absolutePath(); + arguments << (config->isShowFPSEnabled() ? "1" : "0"); + arguments << (config->isAltDamageEnabled() ? "1" : "0"); + arguments << config->netNick().toUtf8().toBase64(); + arguments << QString::number(config->translateQuality()); + arguments << QString::number(config->stereoMode()); + arguments << HWGame::tr("en.txt"); + arguments << QString::number(config->rec_Framerate()); // framerate numerator + arguments << "1"; // framerate denominator + arguments << prefix; + arguments << config->AVFormat(); + arguments << config->videoCodec(); + arguments << "5"; // video quality + arguments << (config->recordAudio()? config->audioCodec() : "no"); + + return arguments; +} diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/net/recorder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/QTfrontend/net/recorder.h Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,58 @@ +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef RECORDER_H +#define RECORDER_H + +#include +#include + +#include "tcpBase.h" + +class GameUIConfig; +class VideoItem; + +class HWRecorder : public TCPBase +{ + Q_OBJECT + public: + HWRecorder(GameUIConfig * config, const QString & prefix); + virtual ~HWRecorder(); + + void EncodeVideo(const QByteArray & record); + + VideoItem * item; // used by pagevideos + QString name; + QString prefix; + + protected: + // virtuals from TCPBase + virtual QStringList getArguments(); + virtual void onClientRead(); + virtual void onClientDisconnect(); + + signals: + void onProgress(float progress); // 0 < progress < 1 + void encodingFinished(bool success); + + private: + bool finished; + GameUIConfig * config; +}; + +#endif // RECORDER_H diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/net/tcpBase.cpp --- a/QTfrontend/net/tcpBase.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/net/tcpBase.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -31,6 +31,8 @@ TCPBase::~TCPBase() { + if (IPCSocket) + IPCSocket->deleteLater(); } TCPBase::TCPBase(bool demoMode) : @@ -65,6 +67,9 @@ connect(IPCSocket, SIGNAL(disconnected()), this, SLOT(ClientDisconnect())); connect(IPCSocket, SIGNAL(readyRead()), this, SLOT(ClientRead())); SendToClientFirst(); + + if(srvsList.size()==1) srvsList.pop_front(); + emit isReadyNow(); } void TCPBase::RealStart() @@ -88,8 +93,8 @@ disconnect(IPCSocket, SIGNAL(readyRead()), this, SLOT(ClientRead())); onClientDisconnect(); - if(srvsList.size()==1) srvsList.pop_front(); - emit isReadyNow(); + /* if(srvsList.size()==1) srvsList.pop_front(); + emit isReadyNow();*/ IPCSocket->deleteLater(); deleteLater(); } @@ -107,6 +112,8 @@ QMessageBox::critical(0, tr("Error"), tr("Unable to run engine: %1 (") .arg(error) + bindir->absolutePath() + "/hwengine)"); + + ClientDisconnect(); } void TCPBase::tcpServerReady() diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/net/tcpBase.h diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/res/css/birthday.css --- a/QTfrontend/res/css/birthday.css Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/res/css/birthday.css Thu Aug 30 13:02:19 2012 -0400 @@ -33,7 +33,7 @@ a { color:#c8c8ff; } QLineEdit, QListWidget, QTableView, QTextBrowser, QSpinBox, QComboBox, -QComboBox QAbstractItemView, QMenu::item { +QComboBox QAbstractItemView, QPlainTextEdit, QMenu::item { background-color: rgba(13, 5, 68, 70%); } @@ -42,7 +42,7 @@ } QPushButton, QListWidget, QTableView, QLineEdit, QHeaderView, -QTextBrowser, QSpinBox, QToolBox, QComboBox, +QTextBrowser, QSpinBox, QToolBox, QComboBox, QPlainTextEdit, QComboBox QAbstractItemView, IconedGroupBox, .QGroupBox, GameCFGWidget, TeamSelWidget, SelWeaponWidget, QTabWidget::pane, QTabBar::tab { @@ -57,14 +57,14 @@ } QLineEdit, QListWidget,QTableView, QTextBrowser, -QSpinBox, QToolBox { +QSpinBox, QToolBox, QPlainTextEdit { border-radius: 10px; } QLineEdit, QLabel, QHeaderView, QListWidget, QTableView, QSpinBox, QToolBox::tab, QComboBox, QComboBox QAbstractItemView, IconedGroupBox, .QGroupBox, GameCFGWidget, TeamSelWidget, -SelWeaponWidget, QCheckBox, QRadioButton, QPushButton { +SelWeaponWidget, QCheckBox, QRadioButton, QPushButton, QPlainTextEdit { font: bold 13px; } SelWeaponWidget QTabWidget::pane, SelWeaponWidget QTabBar::tab:selected { diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/res/css/christmas.css --- a/QTfrontend/res/css/christmas.css Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/res/css/christmas.css Thu Aug 30 13:02:19 2012 -0400 @@ -33,7 +33,7 @@ a { color:#c8c8ff; } QLineEdit, QListWidget, QTableView, QTextBrowser, QSpinBox, QComboBox, -QComboBox QAbstractItemView, QMenu::item { +QComboBox QAbstractItemView, QPlainTextEdit, QMenu::item { background-color: rgba(13, 5, 68, 70%); } @@ -42,7 +42,7 @@ } QPushButton, QListWidget, QTableView, QLineEdit, QHeaderView, -QTextBrowser, QSpinBox, QToolBox, QComboBox, +QTextBrowser, QSpinBox, QToolBox, QComboBox, QPlainTextEdit, QComboBox QAbstractItemView, IconedGroupBox, .QGroupBox, GameCFGWidget, TeamSelWidget, SelWeaponWidget, QTabWidget::pane, QTabBar::tab { @@ -57,14 +57,14 @@ } QLineEdit, QListWidget,QTableView, QTextBrowser, -QSpinBox, QToolBox { +QSpinBox, QToolBox, QPlainTextEdit { border-radius: 10px; } QLineEdit, QLabel, QHeaderView, QListWidget, QTableView, QSpinBox, QToolBox::tab, QComboBox, QComboBox QAbstractItemView, IconedGroupBox, .QGroupBox, GameCFGWidget, TeamSelWidget, -SelWeaponWidget, QCheckBox, QRadioButton, QPushButton { +SelWeaponWidget, QCheckBox, QRadioButton, QPushButton, QPlainTextEdit { font: bold 13px; } SelWeaponWidget QTabWidget::pane, SelWeaponWidget QTabBar::tab:selected { diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/res/css/easter.css --- a/QTfrontend/res/css/easter.css Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/res/css/easter.css Thu Aug 30 13:02:19 2012 -0400 @@ -33,7 +33,7 @@ a { color:#c8c8ff; } QLineEdit, QListWidget, QTableView, QTextBrowser, QSpinBox, QComboBox, -QComboBox QAbstractItemView, QMenu::item { +QComboBox QAbstractItemView, QPlainTextEdit, QMenu::item { background-color: rgba(13, 5, 68, 70%); } @@ -42,7 +42,7 @@ } QPushButton, QListWidget, QTableView, QLineEdit, QHeaderView, -QTextBrowser, QSpinBox, QToolBox, QComboBox, +QTextBrowser, QSpinBox, QToolBox, QComboBox, QPlainTextEdit, QComboBox QAbstractItemView, IconedGroupBox, .QGroupBox, GameCFGWidget, TeamSelWidget, SelWeaponWidget, QTabWidget::pane, QTabBar::tab { @@ -57,14 +57,14 @@ } QLineEdit, QListWidget,QTableView, QTextBrowser, -QSpinBox, QToolBox { +QSpinBox, QToolBox, QPlainTextEdit { border-radius: 10px; } QLineEdit, QLabel, QHeaderView, QListWidget, QTableView, QSpinBox, QToolBox::tab, QComboBox, QComboBox QAbstractItemView, IconedGroupBox, .QGroupBox, GameCFGWidget, TeamSelWidget, -SelWeaponWidget, QCheckBox, QRadioButton, QPushButton { +SelWeaponWidget, QCheckBox, QRadioButton, QPushButton, QPlainTextEdit { font: bold 13px; } SelWeaponWidget QTabWidget::pane, SelWeaponWidget QTabBar::tab:selected { diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/res/css/qt.css --- a/QTfrontend/res/css/qt.css Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/res/css/qt.css Thu Aug 30 13:02:19 2012 -0400 @@ -33,7 +33,7 @@ a { color:#c8c8ff; } QLineEdit, QListWidget, QTableView, QTextBrowser, QSpinBox, QComboBox, -QComboBox QAbstractItemView, QMenu::item { +QComboBox QAbstractItemView, QPlainTextEdit, QMenu::item { background-color: rgba(13, 5, 68, 70%); } @@ -42,7 +42,7 @@ } QPushButton, QListWidget, QTableView, QLineEdit, QHeaderView, -QTextBrowser, QSpinBox, QToolBox, QComboBox, +QTextBrowser, QSpinBox, QToolBox, QComboBox, QPlainTextEdit, QComboBox QAbstractItemView, IconedGroupBox, .QGroupBox, GameCFGWidget, TeamSelWidget, SelWeaponWidget, QTabWidget::pane, QTabBar::tab { @@ -57,14 +57,14 @@ } QLineEdit, QListWidget,QTableView, QTextBrowser, -QSpinBox, QToolBox { +QSpinBox, QToolBox, QPlainTextEdit { border-radius: 10px; } QLineEdit, QLabel, QHeaderView, QListWidget, QTableView, QSpinBox, QToolBox::tab, QComboBox, QComboBox QAbstractItemView, IconedGroupBox, .QGroupBox, GameCFGWidget, TeamSelWidget, -SelWeaponWidget, QCheckBox, QRadioButton, QPushButton { +SelWeaponWidget, QCheckBox, QRadioButton, QPushButton, QPlainTextEdit { font: bold 13px; } SelWeaponWidget QTabWidget::pane, SelWeaponWidget QTabBar::tab:selected { diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/dialog/ask_quit.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/QTfrontend/ui/dialog/ask_quit.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,79 @@ +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include + +#include "hwform.h" +#include "ask_quit.h" +#include "pagevideos.h" + +HWAskQuitDialog::HWAskQuitDialog(QWidget* parent, HWForm * form) : QDialog(parent) +{ + this->form = form; + + setWindowTitle(tr("Do yot really want to quit?")); + + QVBoxLayout * layout = new QVBoxLayout(this); + + QLabel * lbLabel = new QLabel(this); + lbLabel->setText(QLabel::tr("There are videos that are currently being processed.\n" + "Exiting now will abort them.\n" + "Do yot really want to quit?")); + layout->addWidget(lbLabel); + + lbList = new QLabel(this); + layout->addWidget(lbList); + updateList(); + + QDialogButtonBox* dbbButtons = new QDialogButtonBox(this); + QPushButton * pbYes = dbbButtons->addButton(QDialogButtonBox::Yes); + QPushButton * pbNo = dbbButtons->addButton(QDialogButtonBox::No); + QPushButton * pbMore = dbbButtons->addButton(QPushButton::tr("More info"), QDialogButtonBox::HelpRole); + layout->addWidget(dbbButtons); + + connect(pbYes, SIGNAL(clicked()), this, SLOT(accept())); + connect(pbNo, SIGNAL(clicked()), this, SLOT(reject())); + connect(pbMore, SIGNAL(clicked()), this, SLOT(goToPageVideos())); + + // update list periodically + QTimer * timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(updateList())); + timer->start(200); +} + +void HWAskQuitDialog::goToPageVideos() +{ + reject(); + form->GoToVideos(); +} + +void HWAskQuitDialog::updateList() +{ + QString text = form->ui.pageVideos->getVideosInProgress(); + if (text.isEmpty()) + { + // automatically exit when everything is finished + accept(); + return; + } + lbList->setText(text); +} diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/dialog/ask_quit.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/QTfrontend/ui/dialog/ask_quit.h Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,45 @@ +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef ASK_QUIT_H +#define ASK_QUIT_H + +#include + +class QLabel; +class HWForm; +class PageVideos; + +class HWAskQuitDialog : public QDialog +{ + Q_OBJECT + + public: + HWAskQuitDialog(QWidget* parent, HWForm *form); + + private slots: + void goToPageVideos(); + void updateList(); + + private: + HWForm * form; + QLabel * lbList; +}; + + +#endif // INPUT_PASSWORD_H diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/dialog/upload_video.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/QTfrontend/ui/dialog/upload_video.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,297 @@ +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "upload_video.h" +#include "hwconsts.h" + +// User-agent string used in http requests. +// Don't make it a global varibale - crash on linux because of cVersionString +#define USER_AGENT ("Hedgewars-QtFrontend/" + *cVersionString).toAscii() + +// This is developer key obtained from http://code.google.com/apis/youtube/dashboard/ +// If you are reusing this code outside Hedgewars, don't use this developer key, +// obtain you own at http://code.google.com/apis/youtube/dashboard/ +static const QByteArray devKey = "AI39si5pKjxR0XgNIlmrEFF-LyYD31rps4g2O5dZTxLgD0fvJ2rHxrMrNFY8FYTZrzeI3VlaFVQLKfFnSBugvdZmy8vFzRDefQ"; + +HWUploadVideoDialog::HWUploadVideoDialog(QWidget* parent, const QString &filename, QNetworkAccessManager* netManager) : QDialog(parent) +{ + this->filename = filename; + this->netManager = netManager; + + setWindowTitle(tr("Upload video")); + + // Google requires us to display this, see https://developers.google.com/youtube/terms + QString GoogleNotice = + "

By clicking 'upload,' you certify that you own all rights to the content or that " + "you are authorized by the owner to make the content publicly available on YouTube, " + "and that it otherwise complies with the YouTube Terms of Service located at " + "http://www.youtube.com/t/terms.

"; + + // youtube doesn't understand this characters, even when they are properly escaped + // (either with CDATA or with < or >) + QRegExp rx("[^<>]*"); + + int row = 0; + + QGridLayout * layout = new QGridLayout(this); + layout->setColumnStretch(0, 1); + layout->setColumnStretch(1, 2); + + QLabel * lbLabel = new QLabel(this); + lbLabel->setWordWrap(true); + lbLabel->setText(QLabel::tr( + "Please provide either the YouTube account name " + "or the email address associated with the Google Account.")); + layout->addWidget(lbLabel, row++, 0, 1, 2); + + lbLabel = new QLabel(this); + lbLabel->setText(QLabel::tr("Account name (or email): ")); + layout->addWidget(lbLabel, row, 0); + + leAccount = new QLineEdit(this); + layout->addWidget(leAccount, row++, 1); + + lbLabel = new QLabel(this); + lbLabel->setText(QLabel::tr("Password: ")); + layout->addWidget(lbLabel, row, 0); + + lePassword = new QLineEdit(this); + lePassword->setEchoMode(QLineEdit::Password); + layout->addWidget(lePassword, row++, 1); + + cbSave = new QCheckBox(this); + cbSave->setText(QCheckBox::tr("Save account name and password")); + layout->addWidget(cbSave, row++, 0, 1, 2); + + QFrame * hr = new QFrame(this); + hr->setFrameStyle(QFrame::HLine); + hr->setLineWidth(3); + hr->setFixedHeight(10); + layout->addWidget(hr, row++, 0, 1, 2); + + lbLabel = new QLabel(this); + lbLabel->setText(QLabel::tr("Video title: ")); + layout->addWidget(lbLabel, row, 0); + + leTitle = new QLineEdit(this); + leTitle->setText(filename); + leTitle->setValidator(new QRegExpValidator(rx, leTitle)); + layout->addWidget(leTitle, row++, 1); + + lbLabel = new QLabel(this); + lbLabel->setText(QLabel::tr("Video description: ")); + layout->addWidget(lbLabel, row++, 0, 1, 2); + + teDescription = new QPlainTextEdit(this); + layout->addWidget(teDescription, row++, 0, 1, 2); + + lbLabel = new QLabel(this); + lbLabel->setText(QLabel::tr("Tags (comma separated): ")); + layout->addWidget(lbLabel, row, 0); + + leTags = new QLineEdit(this); + leTags->setText("hedgewars"); + leTags->setMaxLength(500); + leTags->setValidator(new QRegExpValidator(rx, leTags)); + layout->addWidget(leTags, row++, 1); + + cbPrivate = new QCheckBox(this); + cbPrivate->setText(QCheckBox::tr("Video is private")); + layout->addWidget(cbPrivate, row++, 0, 1, 2); + + hr = new QFrame(this); + hr->setFrameStyle(QFrame::HLine); + hr->setLineWidth(3); + hr->setFixedHeight(10); + layout->addWidget(hr, row++, 0, 1, 2); + + lbLabel = new QLabel(this); + lbLabel->setWordWrap(true); + lbLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse); + lbLabel->setTextFormat(Qt::RichText); + lbLabel->setOpenExternalLinks(true); + lbLabel->setText(GoogleNotice); + layout->addWidget(lbLabel, row++, 0, 1, 2); + + QDialogButtonBox* dbbButtons = new QDialogButtonBox(this); + btnUpload = dbbButtons->addButton(tr("Upload"), QDialogButtonBox::ActionRole); + QPushButton * pbCancel = dbbButtons->addButton(QDialogButtonBox::Cancel); + layout->addWidget(dbbButtons, row++, 0, 1, 2); + + /* hr = new QFrame(this); + hr->setFrameStyle(QFrame::HLine); + hr->setLineWidth(3); + hr->setFixedHeight(10); + layout->addWidget(hr, row++, 0, 1, 2);*/ + + connect(btnUpload, SIGNAL(clicked()), this, SLOT(upload())); + connect(pbCancel, SIGNAL(clicked()), this, SLOT(reject())); +} + +void HWUploadVideoDialog::showEvent(QShowEvent * event) +{ + QDialog::showEvent(event); + + // set width to the same value as height (otherwise dialog has too small width) + QSize s = size(); + QPoint p = pos(); + resize(s.height(), s.height()); + move(p.x() - (s.height() - s.width())/2, p.y()); +} + +void HWUploadVideoDialog::setEditable(bool editable) +{ + leTitle->setEnabled(editable); + leAccount->setEnabled(editable); + lePassword->setEnabled(editable); + btnUpload->setEnabled(editable); +} + +void HWUploadVideoDialog::upload() +{ + setEditable(false); + + // Documentation is at https://developers.google.com/youtube/2.0/developers_guide_protocol_clientlogin#ClientLogin_Authentication + QNetworkRequest request; + request.setUrl(QUrl("https://www.google.com/accounts/ClientLogin")); + request.setRawHeader("User-Agent", USER_AGENT); + request.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); + + QString account(QUrl::toPercentEncoding(leAccount->text())); + QString pass(QUrl::toPercentEncoding(lePassword->text())); + QByteArray data = QString("Email=%1&Passwd=%2&service=youtube&source=Hedgewars").arg(account).arg(pass).toAscii(); + + QNetworkReply *reply = netManager->post(request, data); + connect(reply, SIGNAL(finished()), this, SLOT(authFinished())); +} + +static QString XmlEscape(const QString& str) +{ + QString str2 = str; + // youtube doesn't understand this characters, even when they are properly escaped + // (either with CDATA or with < >) + str2.replace('<', ' ').replace('>', ' '); + return "", "]]]]>") + "]]>"; +} + +void HWUploadVideoDialog::authFinished() +{ + QNetworkReply *reply = (QNetworkReply*)sender(); + reply->deleteLater(); + + int HttpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + QByteArray answer = reply->readAll(); + QString authToken = ""; + QList lines = answer.split('\n'); + foreach (const QByteArray& line, lines) + { + QString str(line); + if (!str.startsWith("Auth=", Qt::CaseInsensitive)) + continue; + str.remove(0, 5); + authToken = str; + break; + } + if (authToken.isEmpty()) + { + QString errorStr = QMessageBox::tr("Error while authenticating at google.com:\n"); + if (HttpCode == 403) + errorStr += QMessageBox::tr("Login or password is incorrect"); + else + errorStr += reply->errorString(); + QMessageBox::warning(this, QMessageBox::tr("Error"), errorStr); + setEditable(true); + return; + } + + QByteArray auth = ("GoogleLogin auth=" + authToken).toAscii(); + + // We have authenticated, now we can send metadata and start upload + // Documentation is here: https://developers.google.com/youtube/2.0/developers_guide_protocol_resumable_uploads#Resumable_uploads + QByteArray body = + "" + "" + "" + // "" + "Games" + "" + "" + + XmlEscape(leTitle->text()).toUtf8() + + "" + "" + + XmlEscape(teDescription->toPlainText()).toUtf8() + + "" + "" + + XmlEscape(leTags->text()).toUtf8() + + "" + + (cbPrivate->isChecked()? "" : "") + + "" + ""; + + QNetworkRequest request; + request.setUrl(QUrl("http://uploads.gdata.youtube.com/resumable/feeds/api/users/default/uploads")); + request.setRawHeader("User-Agent", USER_AGENT); + request.setRawHeader("Authorization", auth); + request.setRawHeader("GData-Version", "2"); + request.setRawHeader("X-GData-Key", "key=" + devKey); + request.setRawHeader("Slug", filename.toUtf8()); + request.setRawHeader("Content-Type", "application/atom+xml; charset=UTF-8"); + + reply = netManager->post(request, body); + connect(reply, SIGNAL(finished()), this, SLOT(startUpload())); +} + +void HWUploadVideoDialog::startUpload() +{ + QNetworkReply *reply = (QNetworkReply*)sender(); + reply->deleteLater(); + + location = QString::fromAscii(reply->rawHeader("Location")); + if (location.isEmpty()) + { + QString errorStr = QMessageBox::tr("Error while sending metadata to youtube.com:\n"); + errorStr += reply->errorString(); + QMessageBox::warning(this, QMessageBox::tr("Error"), errorStr); + setEditable(true); + return; + } + + accept(); +} diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/dialog/upload_video.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/QTfrontend/ui/dialog/upload_video.h Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,65 @@ +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef UPLOAD_VIDEO_H +#define UPLOAD_VIDEO_H + +#include + +class QLineEdit; +class QCheckBox; +class QPlainTextEdit; +class QLabel; +class QNetworkAccessManager; + +class HWUploadVideoDialog : public QDialog +{ + Q_OBJECT + public: + HWUploadVideoDialog(QWidget* parent, const QString& filename, QNetworkAccessManager* netManager); + + QLineEdit* leAccount; + QLineEdit* lePassword; + QCheckBox* cbSave; + + QLineEdit* leTitle; + QPlainTextEdit* teDescription; + QLineEdit* leTags; + QCheckBox* cbPrivate; + + QPushButton* btnUpload; + + QString location; + + private: + QNetworkAccessManager* netManager; + QString filename; + + void setEditable(bool editable); + + protected: + // virtual from QWidget + void showEvent(QShowEvent * event); + + private slots: + void upload(); + void authFinished(); + void startUpload(); +}; + +#endif // UPLOAD_VIDEO_H diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/page/pageeditteam.cpp --- a/QTfrontend/ui/page/pageeditteam.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/ui/page/pageeditteam.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -289,6 +289,9 @@ int idx = list.indexOf("cpu.png"); if (idx >= 0) list.removeAt(idx); + idx = list.indexOf("cpu_plain.png"); + if (idx >= 0) + list.removeAt(idx); idx = list.indexOf("hedgewars.png"); if (idx >= 0) list.removeAt(idx); @@ -469,5 +472,4 @@ void PageEditTeam::saveTeam() { data().saveToFile(); - emit teamEdited(); } diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/page/pageeditteam.h --- a/QTfrontend/ui/page/pageeditteam.h Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/ui/page/pageeditteam.h Thu Aug 30 13:02:19 2012 -0400 @@ -40,9 +40,6 @@ void editTeam(const QString & name, const QString & playerHash); void deleteTeam(const QString & name); - signals: - void teamEdited(); - public slots: void CBFort_activated(const QString & gravename); diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/page/pagemain.cpp --- a/QTfrontend/ui/page/pagemain.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/ui/page/pagemain.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -85,9 +85,15 @@ bottomLayout->setStretch(0,1); btnBack->setWhatsThis(tr("Exit game")); - - BtnSetup = addButton(":/res/Settings.png", bottomLayout, 1, true); + +#ifdef VIDEOREC + BtnVideos = addButton(":/res/Record.png", bottomLayout, 1, true); + BtnVideos->setWhatsThis(tr("Manage videos recorded from game")); +#endif + + BtnSetup = addButton(":/res/Settings.png", bottomLayout, 2, true); BtnSetup->setWhatsThis(tr("Edit game preferences")); + return bottomLayout; } diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/page/pagemain.h --- a/QTfrontend/ui/page/pagemain.h Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/ui/page/pagemain.h Thu Aug 30 13:02:19 2012 -0400 @@ -34,6 +34,7 @@ QPushButton * BtnFeedback; QPushButton * BtnInfo; QPushButton * BtnDataDownload; + QPushButton * BtnVideos; QLabel * mainNote; private: diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/page/pageoptions.cpp --- a/QTfrontend/ui/page/pageoptions.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/ui/page/pageoptions.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -28,368 +28,397 @@ #include #include #include +#include +#include +#include #include "pageoptions.h" #include "hwconsts.h" #include "fpsedit.h" #include "igbox.h" +#include "DataManager.h" // TODO cleanup QLayout * PageOptions::bodyLayoutDefinition() { - QGridLayout * pageLayout = new QGridLayout(); - pageLayout->setColumnStretch(0, 100); - pageLayout->setColumnStretch(1, 100); - pageLayout->setColumnStretch(2, 100); - pageLayout->setRowStretch(0, 0); - //pageLayout->setRowStretch(1, 100); - pageLayout->setRowStretch(2, 0); - pageLayout->setContentsMargins(7, 7, 7, 0); - pageLayout->setSpacing(0); + QVBoxLayout * pageLayout = new QVBoxLayout(); + QTabWidget * tabs = new QTabWidget(this); + pageLayout->addWidget(tabs); + QWidget * page1 = new QWidget(this); + QWidget * page2 = new QWidget(this); + tabs->addTab(page1, tr("General")); + tabs->addTab(page2, tr("Advanced")); + + { // page 1 + QGridLayout * page1Layout = new QGridLayout(page1); + //gbTBLayout->setMargin(0); + page1Layout->setSpacing(0); + page1Layout->setAlignment(Qt::AlignTop | Qt::AlignLeft); - QGroupBox * gbTwoBoxes = new QGroupBox(this); - pageLayout->addWidget(gbTwoBoxes, 0, 0, 1, 3); - QGridLayout * gbTBLayout = new QGridLayout(gbTwoBoxes); - gbTBLayout->setMargin(0); - gbTBLayout->setSpacing(0); - gbTBLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft); - - QPixmap pmNew(":/res/new.png"); - QPixmap pmEdit(":/res/edit.png"); - QPixmap pmDelete(":/res/delete.png"); + QPixmap pmNew(":/res/new.png"); + QPixmap pmEdit(":/res/edit.png"); + QPixmap pmDelete(":/res/delete.png"); - { - teamsBox = new IconedGroupBox(this); - //teamsBox->setContentTopPadding(0); - //teamsBox->setAttribute(Qt::WA_PaintOnScreen, true); - teamsBox->setIcon(QIcon(":/res/teamicon.png")); - teamsBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - teamsBox->setTitle(QGroupBox::tr("Teams")); + { + teamsBox = new IconedGroupBox(this); + //teamsBox->setContentTopPadding(0); + //teamsBox->setAttribute(Qt::WA_PaintOnScreen, true); + teamsBox->setIcon(QIcon(":/res/teamicon.png")); + teamsBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + teamsBox->setTitle(QGroupBox::tr("Teams")); - QGridLayout * GBTlayout = new QGridLayout(teamsBox); + QGridLayout * GBTlayout = new QGridLayout(teamsBox); - CBTeamName = new QComboBox(teamsBox); - GBTlayout->addWidget(CBTeamName, 0, 0); + CBTeamName = new QComboBox(teamsBox); + GBTlayout->addWidget(CBTeamName, 0, 0); - BtnNewTeam = new QPushButton(teamsBox); - BtnNewTeam->setToolTip(tr("New team")); - BtnNewTeam->setIconSize(pmNew.size()); - BtnNewTeam->setIcon(pmNew); - BtnNewTeam->setMaximumWidth(pmNew.width() + 6); - connect(BtnNewTeam, SIGNAL(clicked()), this, SIGNAL(newTeamRequested())); - GBTlayout->addWidget(BtnNewTeam, 0, 1); + BtnNewTeam = new QPushButton(teamsBox); + BtnNewTeam->setToolTip(tr("New team")); + BtnNewTeam->setIconSize(pmNew.size()); + BtnNewTeam->setIcon(pmNew); + BtnNewTeam->setMaximumWidth(pmNew.width() + 6); + connect(BtnNewTeam, SIGNAL(clicked()), this, SIGNAL(newTeamRequested())); + GBTlayout->addWidget(BtnNewTeam, 0, 1); - BtnEditTeam = new QPushButton(teamsBox); - BtnEditTeam->setToolTip(tr("Edit team")); - BtnEditTeam->setIconSize(pmEdit.size()); - BtnEditTeam->setIcon(pmEdit); - BtnEditTeam->setMaximumWidth(pmEdit.width() + 6); - connect(BtnEditTeam, SIGNAL(clicked()), this, SLOT(requestEditSelectedTeam())); - GBTlayout->addWidget(BtnEditTeam, 0, 2); + BtnEditTeam = new QPushButton(teamsBox); + BtnEditTeam->setToolTip(tr("Edit team")); + BtnEditTeam->setIconSize(pmEdit.size()); + BtnEditTeam->setIcon(pmEdit); + BtnEditTeam->setMaximumWidth(pmEdit.width() + 6); + connect(BtnEditTeam, SIGNAL(clicked()), this, SLOT(requestEditSelectedTeam())); + GBTlayout->addWidget(BtnEditTeam, 0, 2); - BtnDeleteTeam = new QPushButton(teamsBox); - BtnDeleteTeam->setToolTip(tr("Delete team")); - BtnDeleteTeam->setIconSize(pmDelete.size()); - BtnDeleteTeam->setIcon(pmDelete); - BtnDeleteTeam->setMaximumWidth(pmDelete.width() + 6); - connect(BtnDeleteTeam, SIGNAL(clicked()), this, SLOT(requestDeleteSelectedTeam())); - GBTlayout->addWidget(BtnDeleteTeam, 0, 3); - - LblNoEditTeam = new QLabel(teamsBox); - LblNoEditTeam->setText(tr("You can't edit teams from team selection. Go back to main menu to add, edit or delete teams.")); - LblNoEditTeam->setWordWrap(true); - LblNoEditTeam->setVisible(false); - GBTlayout->addWidget(LblNoEditTeam, 0, 0); + BtnDeleteTeam = new QPushButton(teamsBox); + BtnDeleteTeam->setToolTip(tr("Delete team")); + BtnDeleteTeam->setIconSize(pmDelete.size()); + BtnDeleteTeam->setIcon(pmDelete); + BtnDeleteTeam->setMaximumWidth(pmDelete.width() + 6); + connect(BtnDeleteTeam, SIGNAL(clicked()), this, SLOT(requestDeleteSelectedTeam())); + GBTlayout->addWidget(BtnDeleteTeam, 0, 3); - gbTBLayout->addWidget(teamsBox, 0, 0); - } - - { - IconedGroupBox* groupWeapons = new IconedGroupBox(this); + LblNoEditTeam = new QLabel(teamsBox); + LblNoEditTeam->setText(tr("You can't edit teams from team selection. Go back to main menu to add, edit or delete teams.")); + LblNoEditTeam->setWordWrap(true); + LblNoEditTeam->setVisible(false); + GBTlayout->addWidget(LblNoEditTeam, 0, 0); - //groupWeapons->setContentTopPadding(0); - //groupWeapons->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - groupWeapons->setIcon(QIcon(":/res/weaponsicon.png")); - groupWeapons->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - groupWeapons->setTitle(QGroupBox::tr("Schemes and Weapons")); - QGridLayout * WeaponsLayout = new QGridLayout(groupWeapons); + page1Layout->addWidget(teamsBox, 0, 0); + } - QLabel* SchemeLabel = new QLabel(groupWeapons); - SchemeLabel->setText(QLabel::tr("Game scheme")); - WeaponsLayout->addWidget(SchemeLabel, 1, 0); + { + IconedGroupBox* groupWeapons = new IconedGroupBox(this); - SchemesName = new QComboBox(groupWeapons); - WeaponsLayout->addWidget(SchemesName, 1, 1); + //groupWeapons->setContentTopPadding(0); + //groupWeapons->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + groupWeapons->setIcon(QIcon(":/res/weaponsicon.png")); + groupWeapons->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + groupWeapons->setTitle(QGroupBox::tr("Schemes and Weapons")); + QGridLayout * WeaponsLayout = new QGridLayout(groupWeapons); - SchemeNew = new QPushButton(groupWeapons); - SchemeNew->setWhatsThis(tr("New scheme")); - SchemeNew->setIconSize(pmNew.size()); - SchemeNew->setIcon(pmNew); - SchemeNew->setMaximumWidth(pmNew.width() + 6); - WeaponsLayout->addWidget(SchemeNew, 1, 2); + QLabel* SchemeLabel = new QLabel(groupWeapons); + SchemeLabel->setText(QLabel::tr("Game scheme")); + WeaponsLayout->addWidget(SchemeLabel, 1, 0); - SchemeEdit = new QPushButton(groupWeapons); - SchemeEdit->setWhatsThis(tr("Edit scheme")); - SchemeEdit->setIconSize(pmEdit.size()); - SchemeEdit->setIcon(pmEdit); - SchemeEdit->setMaximumWidth(pmEdit.width() + 6); - WeaponsLayout->addWidget(SchemeEdit, 1, 3); + SchemesName = new QComboBox(groupWeapons); + WeaponsLayout->addWidget(SchemesName, 1, 1); - SchemeDelete = new QPushButton(groupWeapons); - SchemeDelete->setWhatsThis(tr("Delete scheme")); - SchemeDelete->setIconSize(pmDelete.size()); - SchemeDelete->setIcon(pmDelete); - SchemeDelete->setMaximumWidth(pmDelete.width() + 6); - WeaponsLayout->addWidget(SchemeDelete, 1, 4); + SchemeNew = new QPushButton(groupWeapons); + SchemeNew->setWhatsThis(tr("New scheme")); + SchemeNew->setIconSize(pmNew.size()); + SchemeNew->setIcon(pmNew); + SchemeNew->setMaximumWidth(pmNew.width() + 6); + WeaponsLayout->addWidget(SchemeNew, 1, 2); - QLabel* WeaponLabel = new QLabel(groupWeapons); - WeaponLabel->setText(QLabel::tr("Weapons")); - WeaponsLayout->addWidget(WeaponLabel, 2, 0); - - WeaponsName = new QComboBox(groupWeapons); - WeaponsLayout->addWidget(WeaponsName, 2, 1); - - WeaponNew = new QPushButton(groupWeapons); - WeaponNew->setWhatsThis(tr("New weapon set")); - WeaponNew->setIconSize(pmNew.size()); - WeaponNew->setIcon(pmNew); - WeaponNew->setMaximumWidth(pmNew.width() + 6); - WeaponsLayout->addWidget(WeaponNew, 2, 2); + SchemeEdit = new QPushButton(groupWeapons); + SchemeEdit->setWhatsThis(tr("Edit scheme")); + SchemeEdit->setIconSize(pmEdit.size()); + SchemeEdit->setIcon(pmEdit); + SchemeEdit->setMaximumWidth(pmEdit.width() + 6); + WeaponsLayout->addWidget(SchemeEdit, 1, 3); - WeaponEdit = new QPushButton(groupWeapons); - WeaponEdit->setWhatsThis(tr("Edit weapon set")); - WeaponEdit->setIconSize(pmEdit.size()); - WeaponEdit->setIcon(pmEdit); - WeaponEdit->setMaximumWidth(pmEdit.width() + 6); - WeaponsLayout->addWidget(WeaponEdit, 2, 3); + SchemeDelete = new QPushButton(groupWeapons); + SchemeDelete->setWhatsThis(tr("Delete scheme")); + SchemeDelete->setIconSize(pmDelete.size()); + SchemeDelete->setIcon(pmDelete); + SchemeDelete->setMaximumWidth(pmDelete.width() + 6); + WeaponsLayout->addWidget(SchemeDelete, 1, 4); - WeaponDelete = new QPushButton(groupWeapons); - WeaponDelete->setWhatsThis(tr("Delete weapon set")); - WeaponDelete->setIconSize(pmDelete.size()); - WeaponDelete->setIcon(pmDelete); - WeaponDelete->setMaximumWidth(pmDelete.width() + 6); - WeaponsLayout->addWidget(WeaponDelete, 2, 4); + QLabel* WeaponLabel = new QLabel(groupWeapons); + WeaponLabel->setText(QLabel::tr("Weapons")); + WeaponsLayout->addWidget(WeaponLabel, 2, 0); - WeaponTooltip = new QCheckBox(this); - WeaponTooltip->setText(QCheckBox::tr("Show ammo menu tooltips")); - WeaponsLayout->addWidget(WeaponTooltip, 3, 0, 1, 4); - - gbTBLayout->addWidget(groupWeapons, 1, 0); - } + WeaponsName = new QComboBox(groupWeapons); + WeaponsLayout->addWidget(WeaponsName, 2, 1); - { - IconedGroupBox* groupMisc = new IconedGroupBox(this); - //groupMisc->setContentTopPadding(0); - groupMisc->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); - groupMisc->setIcon(QIcon(":/res/miscicon.png")); - //groupMisc->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - groupMisc->setTitle(QGroupBox::tr("Misc")); - QGridLayout * MiscLayout = new QGridLayout(groupMisc); + WeaponNew = new QPushButton(groupWeapons); + WeaponNew->setWhatsThis(tr("New weapon set")); + WeaponNew->setIconSize(pmNew.size()); + WeaponNew->setIcon(pmNew); + WeaponNew->setMaximumWidth(pmNew.width() + 6); + WeaponsLayout->addWidget(WeaponNew, 2, 2); - // Label for "Language" - QLabel *labelLanguage = new QLabel(groupMisc); - labelLanguage->setText(QLabel::tr("Locale") + " *"); - MiscLayout->addWidget(labelLanguage, 0, 0); + WeaponEdit = new QPushButton(groupWeapons); + WeaponEdit->setWhatsThis(tr("Edit weapon set")); + WeaponEdit->setIconSize(pmEdit.size()); + WeaponEdit->setIcon(pmEdit); + WeaponEdit->setMaximumWidth(pmEdit.width() + 6); + WeaponsLayout->addWidget(WeaponEdit, 2, 3); - // List of installed languages - CBLanguage = new QComboBox(groupMisc); - QDir tmpdir; - 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("")); - for(int i = 0; i < locs.count(); i++) - { - QLocale loc(locs[i].replace(QRegExp("hedgewars_(.*)\\.qm"), "\\1")); - CBLanguage->addItem(QLocale::languageToString(loc.language()) + " (" + QLocale::countryToString(loc.country()) + ")", loc.name()); + WeaponDelete = new QPushButton(groupWeapons); + WeaponDelete->setWhatsThis(tr("Delete weapon set")); + WeaponDelete->setIconSize(pmDelete.size()); + WeaponDelete->setIcon(pmDelete); + WeaponDelete->setMaximumWidth(pmDelete.width() + 6); + WeaponsLayout->addWidget(WeaponDelete, 2, 4); + + WeaponTooltip = new QCheckBox(this); + WeaponTooltip->setText(QCheckBox::tr("Show ammo menu tooltips")); + WeaponsLayout->addWidget(WeaponTooltip, 3, 0, 1, 4); + + page1Layout->addWidget(groupWeapons, 1, 0); } - 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()); + IconedGroupBox* groupMisc = new IconedGroupBox(this); + //groupMisc->setContentTopPadding(0); + groupMisc->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); + groupMisc->setIcon(QIcon(":/res/miscicon.png")); + //groupMisc->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + groupMisc->setTitle(QGroupBox::tr("Misc")); + QGridLayout * MiscLayout = new QGridLayout(groupMisc); + + // Label for "Language" + QLabel *labelLanguage = new QLabel(groupMisc); + labelLanguage->setText(QLabel::tr("Locale") + " *"); + MiscLayout->addWidget(labelLanguage, 0, 0); + + // List of installed languages + CBLanguage = new QComboBox(groupMisc); + QDir tmpdir; + 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("")); + for(int i = 0; i < locs.count(); i++) + { + QLocale loc(locs[i].replace(QRegExp("hedgewars_(.*)\\.qm"), "\\1")); + 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, 0, 1); + + // Label and field for net nick + labelNN = new QLabel(groupMisc); + labelNN->setText(QLabel::tr("Nickname")); + MiscLayout->addWidget(labelNN, 1, 0); + + editNetNick = new QLineEdit(groupMisc); + editNetNick->setMaxLength(20); + editNetNick->setText(QLineEdit::tr("anonymous")); + MiscLayout->addWidget(editNetNick, 1, 1); + + // checkbox and field for password + CBSavePassword = new QCheckBox(groupMisc); + CBSavePassword->setText(QCheckBox::tr("Save password")); + MiscLayout->addWidget(CBSavePassword, 2, 0); + + editNetPassword = new QLineEdit(groupMisc); + editNetPassword->setEchoMode(QLineEdit::Password); + MiscLayout->addWidget(editNetPassword, 2, 1); + + CBNameWithDate = new QCheckBox(groupMisc); + CBNameWithDate->setText(QCheckBox::tr("Append date and time to record file name")); + MiscLayout->addWidget(CBNameWithDate, 5, 0, 1, 2); + + BtnAssociateFiles = new QPushButton(groupMisc); + BtnAssociateFiles->setText(QPushButton::tr("Associate file extensions")); + BtnAssociateFiles->setVisible(!custom_data && !custom_config); + MiscLayout->addWidget(BtnAssociateFiles, 6, 0, 1, 2); + + #ifdef __APPLE__ + #ifdef SPARKLE_ENABLED + CBAutoUpdate = new QCheckBox(groupMisc); + CBAutoUpdate->setText(QCheckBox::tr("Check for updates at startup")); + MiscLayout->addWidget(CBAutoUpdate, 7, 0, 1, 3); + #endif + #endif + page1Layout->addWidget(groupMisc, 2, 0); } - MiscLayout->addWidget(CBLanguage, 0, 1); + { + AGGroupBox = new IconedGroupBox(this); + //AGGroupBox->setContentTopPadding(0); + AGGroupBox->setIcon(QIcon(":/res/graphicsicon.png")); + //AGGroupBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + AGGroupBox->setTitle(QGroupBox::tr("Audio/Graphic options")); + + QVBoxLayout * GBAlayout = new QVBoxLayout(AGGroupBox); + QHBoxLayout * GBAreslayout = new QHBoxLayout(0); + QHBoxLayout * GBAstereolayout = new QHBoxLayout(0); + QHBoxLayout * GBAqualayout = new QHBoxLayout(0); - // Label and field for net nick - labelNN = new QLabel(groupMisc); - labelNN->setText(QLabel::tr("Nickname")); - MiscLayout->addWidget(labelNN, 1, 0); + CBFrontendFullscreen = new QCheckBox(AGGroupBox); + CBFrontendFullscreen->setText(QCheckBox::tr("Frontend fullscreen")); + GBAlayout->addWidget(CBFrontendFullscreen); + + CBFrontendEffects = new QCheckBox(AGGroupBox); + CBFrontendEffects->setText(QCheckBox::tr("Frontend effects")); + GBAlayout->addWidget(CBFrontendEffects); + + CBEnableFrontendSound = new QCheckBox(AGGroupBox); + CBEnableFrontendSound->setText(QCheckBox::tr("Enable frontend sounds")); + GBAlayout->addWidget(CBEnableFrontendSound); + + CBEnableFrontendMusic = new QCheckBox(AGGroupBox); + CBEnableFrontendMusic->setText(QCheckBox::tr("Enable frontend music")); + GBAlayout->addWidget(CBEnableFrontendMusic); - editNetNick = new QLineEdit(groupMisc); - editNetNick->setMaxLength(20); - editNetNick->setText(QLineEdit::tr("anonymous")); - MiscLayout->addWidget(editNetNick, 1, 1); + QFrame * hr = new QFrame(AGGroupBox); + hr->setFrameStyle(QFrame::HLine); + hr->setLineWidth(3); + hr->setFixedHeight(10); + GBAlayout->addWidget(hr); + + QLabel * resolution = new QLabel(AGGroupBox); + resolution->setText(QLabel::tr("Resolution")); + GBAreslayout->addWidget(resolution); + + CBResolution = new QComboBox(AGGroupBox); + GBAreslayout->addWidget(CBResolution); + GBAlayout->addLayout(GBAreslayout); - // checkbox and field for password - CBSavePassword = new QCheckBox(groupMisc); - CBSavePassword->setText(QCheckBox::tr("Save password")); - MiscLayout->addWidget(CBSavePassword, 2, 0); + CBFullscreen = new QCheckBox(AGGroupBox); + CBFullscreen->setText(QCheckBox::tr("Fullscreen")); + GBAreslayout->addWidget(CBFullscreen); + + QLabel * quality = new QLabel(AGGroupBox); + quality->setText(QLabel::tr("Quality")); + quality->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + GBAqualayout->addWidget(quality); + + SLQuality = new QSlider(Qt::Horizontal, AGGroupBox); + SLQuality->setTickPosition(QSlider::TicksBelow); + SLQuality->setMaximum(5); + SLQuality->setMinimum(0); + SLQuality->setFixedWidth(150); + GBAqualayout->addWidget(SLQuality); + GBAlayout->addLayout(GBAqualayout); + + QLabel * stereo = new QLabel(AGGroupBox); + stereo->setText(QLabel::tr("Stereo rendering")); + GBAstereolayout->addWidget(stereo); - editNetPassword = new QLineEdit(groupMisc); - editNetPassword->setEchoMode(QLineEdit::Password); - MiscLayout->addWidget(editNetPassword, 2, 1); + CBStereoMode = new QComboBox(AGGroupBox); + CBStereoMode->addItem(QComboBox::tr("Disabled")); + CBStereoMode->addItem(QComboBox::tr("Red/Cyan")); + CBStereoMode->addItem(QComboBox::tr("Cyan/Red")); + CBStereoMode->addItem(QComboBox::tr("Red/Blue")); + CBStereoMode->addItem(QComboBox::tr("Blue/Red")); + CBStereoMode->addItem(QComboBox::tr("Red/Green")); + CBStereoMode->addItem(QComboBox::tr("Green/Red")); + CBStereoMode->addItem(QComboBox::tr("Side-by-side")); + CBStereoMode->addItem(QComboBox::tr("Top-Bottom")); + CBStereoMode->addItem(QComboBox::tr("Wiggle")); + CBStereoMode->addItem(QComboBox::tr("Red/Cyan grayscale")); + CBStereoMode->addItem(QComboBox::tr("Cyan/Red grayscale")); + CBStereoMode->addItem(QComboBox::tr("Red/Blue grayscale")); + CBStereoMode->addItem(QComboBox::tr("Blue/Red grayscale")); + CBStereoMode->addItem(QComboBox::tr("Red/Green grayscale")); + CBStereoMode->addItem(QComboBox::tr("Green/Red grayscale")); - CBNameWithDate = new QCheckBox(groupMisc); - CBNameWithDate->setText(QCheckBox::tr("Append date and time to record file name")); - MiscLayout->addWidget(CBNameWithDate, 5, 0, 1, 2); + GBAstereolayout->addWidget(CBStereoMode); + GBAlayout->addLayout(GBAstereolayout); + + QHBoxLayout * GBAfpslayout = new QHBoxLayout(0); + QLabel * maxfps = new QLabel(AGGroupBox); + maxfps->setText(QLabel::tr("FPS limit")); + GBAfpslayout->addWidget(maxfps); + GBAlayout->addLayout(GBAfpslayout); + fpsedit = new FPSEdit(AGGroupBox); + GBAfpslayout->addWidget(fpsedit); + + CBShowFPS = new QCheckBox(AGGroupBox); + CBShowFPS->setText(QCheckBox::tr("Show FPS")); + GBAfpslayout->addWidget(CBShowFPS); - BtnAssociateFiles = new QPushButton(groupMisc); - BtnAssociateFiles->setText(QPushButton::tr("Associate file extensions")); - BtnAssociateFiles->setVisible(!custom_data && !custom_config); - MiscLayout->addWidget(BtnAssociateFiles, 6, 0, 1, 2); + hr = new QFrame(AGGroupBox); + hr->setFrameStyle(QFrame::HLine); + hr->setLineWidth(3); + hr->setFixedHeight(10); + GBAlayout->addWidget(hr); + + QGridLayout * GBAvollayout = new QGridLayout(); + QLabel * vol = new QLabel(AGGroupBox); + vol->setText(QLabel::tr("Initial sound volume")); + GBAvollayout->addWidget(vol, 0, 0, 1, 2); + GBAlayout->addLayout(GBAvollayout); + volumeBox = new QSpinBox(AGGroupBox); + volumeBox->setRange(0, 100); + volumeBox->setSingleStep(5); + GBAvollayout->addWidget(volumeBox, 0, 2); -#ifdef __APPLE__ -#ifdef SPARKLE_ENABLED - CBAutoUpdate = new QCheckBox(groupMisc); - CBAutoUpdate->setText(QCheckBox::tr("Check for updates at startup")); - MiscLayout->addWidget(CBAutoUpdate, 7, 0, 1, 3); -#endif -#endif - gbTBLayout->addWidget(groupMisc, 2, 0); + CBEnableSound = new QCheckBox(AGGroupBox); + CBEnableSound->setText(QCheckBox::tr("Enable sound")); + GBAvollayout->addWidget(CBEnableSound, 1, 0, 1, 1); + + CBEnableMusic = new QCheckBox(AGGroupBox); + CBEnableMusic->setText(QCheckBox::tr("Enable music")); + GBAvollayout->addWidget(CBEnableMusic, 1, 1, 1, 2); + + GBAvollayout->setSizeConstraint(QLayout::SetMinimumSize); + + hr = new QFrame(AGGroupBox); + hr->setFrameStyle(QFrame::HLine); + hr->setLineWidth(3); + hr->setFixedHeight(10); + GBAlayout->addWidget(hr); + + CBAltDamage = new QCheckBox(AGGroupBox); + CBAltDamage->setText(QCheckBox::tr("Alternative damage show")); + GBAlayout->addWidget(CBAltDamage); + + page1Layout->addWidget(AGGroupBox, 0, 1, 3, 1); + } } - { - AGGroupBox = new IconedGroupBox(this); - //AGGroupBox->setContentTopPadding(0); - AGGroupBox->setIcon(QIcon(":/res/graphicsicon.png")); - //AGGroupBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); - AGGroupBox->setTitle(QGroupBox::tr("Audio/Graphic options")); - - QVBoxLayout * GBAlayout = new QVBoxLayout(AGGroupBox); - QHBoxLayout * GBAreslayout = new QHBoxLayout(0); - QHBoxLayout * GBAstereolayout = new QHBoxLayout(0); - QHBoxLayout * GBAqualayout = 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")); - GBAlayout->addWidget(CBFrontendEffects); - - CBEnableFrontendSound = new QCheckBox(AGGroupBox); - CBEnableFrontendSound->setText(QCheckBox::tr("Enable frontend sounds")); - GBAlayout->addWidget(CBEnableFrontendSound); - - CBEnableFrontendMusic = new QCheckBox(AGGroupBox); - CBEnableFrontendMusic->setText(QCheckBox::tr("Enable frontend music")); - GBAlayout->addWidget(CBEnableFrontendMusic); + { // page 2 + QGridLayout * page2Layout = new QGridLayout(page2); - QFrame * hr = new QFrame(AGGroupBox); - hr->setFrameStyle(QFrame::HLine); - hr->setLineWidth(3); - hr->setFixedHeight(10); - GBAlayout->addWidget(hr); - - QLabel * resolution = new QLabel(AGGroupBox); - resolution->setText(QLabel::tr("Resolution")); - GBAreslayout->addWidget(resolution); - - CBResolution = new QComboBox(AGGroupBox); - GBAreslayout->addWidget(CBResolution); - GBAlayout->addLayout(GBAreslayout); + IconedGroupBox * gbColors = new IconedGroupBox(this); + //gbColors->setIcon(QIcon(":/res/teamicon.png")); + gbColors->setTitle(QGroupBox::tr("Custom colors")); + page2Layout->addWidget(gbColors, 0, 0, 1, 3); + QVBoxLayout * gbCLayout = new QVBoxLayout(gbColors); - CBFullscreen = new QCheckBox(AGGroupBox); - CBFullscreen->setText(QCheckBox::tr("Fullscreen")); - GBAreslayout->addWidget(CBFullscreen); - - QLabel * quality = new QLabel(AGGroupBox); - quality->setText(QLabel::tr("Quality")); - quality->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - GBAqualayout->addWidget(quality); + QSignalMapper * mapper = new QSignalMapper(this); - SLQuality = new QSlider(Qt::Horizontal, AGGroupBox); - SLQuality->setTickPosition(QSlider::TicksBelow); - SLQuality->setMaximum(5); - SLQuality->setMinimum(0); - SLQuality->setFixedWidth(150); - GBAqualayout->addWidget(SLQuality); - GBAlayout->addLayout(GBAqualayout); - - QLabel * stereo = new QLabel(AGGroupBox); - stereo->setText(QLabel::tr("Stereo rendering")); - GBAstereolayout->addWidget(stereo); + QStandardItemModel * model = DataManager::instance().colorsModel(); - CBStereoMode = new QComboBox(AGGroupBox); - CBStereoMode->addItem(QComboBox::tr("Disabled")); - CBStereoMode->addItem(QComboBox::tr("Red/Cyan")); - CBStereoMode->addItem(QComboBox::tr("Cyan/Red")); - CBStereoMode->addItem(QComboBox::tr("Red/Blue")); - CBStereoMode->addItem(QComboBox::tr("Blue/Red")); - CBStereoMode->addItem(QComboBox::tr("Red/Green")); - CBStereoMode->addItem(QComboBox::tr("Green/Red")); - CBStereoMode->addItem(QComboBox::tr("Side-by-side")); - CBStereoMode->addItem(QComboBox::tr("Top-Bottom")); - CBStereoMode->addItem(QComboBox::tr("Wiggle")); - CBStereoMode->addItem(QComboBox::tr("Red/Cyan grayscale")); - CBStereoMode->addItem(QComboBox::tr("Cyan/Red grayscale")); - CBStereoMode->addItem(QComboBox::tr("Red/Blue grayscale")); - CBStereoMode->addItem(QComboBox::tr("Blue/Red grayscale")); - CBStereoMode->addItem(QComboBox::tr("Red/Green grayscale")); - CBStereoMode->addItem(QComboBox::tr("Green/Red grayscale")); - - GBAstereolayout->addWidget(CBStereoMode); - GBAlayout->addLayout(GBAstereolayout); - - QHBoxLayout * GBAfpslayout = new QHBoxLayout(0); - QLabel * maxfps = new QLabel(AGGroupBox); - maxfps->setText(QLabel::tr("FPS limit")); - GBAfpslayout->addWidget(maxfps); - GBAlayout->addLayout(GBAfpslayout); - fpsedit = new FPSEdit(AGGroupBox); - GBAfpslayout->addWidget(fpsedit); - - CBShowFPS = new QCheckBox(AGGroupBox); - CBShowFPS->setText(QCheckBox::tr("Show FPS")); - GBAfpslayout->addWidget(CBShowFPS); + connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(onColorModelDataChanged(QModelIndex,QModelIndex))); + for(int i = 0; i < model->rowCount(); ++i) + { + QPushButton * btn = new QPushButton(this); + gbCLayout->addWidget(btn); + btn->setStyleSheet(QString("background: %1").arg(model->item(i)->data().value().name())); + m_colorButtons.append(btn); + connect(btn, SIGNAL(clicked()), mapper, SLOT(map())); + mapper->setMapping(btn, i); + } - hr = new QFrame(AGGroupBox); - hr->setFrameStyle(QFrame::HLine); - hr->setLineWidth(3); - hr->setFixedHeight(10); - GBAlayout->addWidget(hr); - - QGridLayout * GBAvollayout = new QGridLayout(); - QLabel * vol = new QLabel(AGGroupBox); - vol->setText(QLabel::tr("Initial sound volume")); - GBAvollayout->addWidget(vol, 0, 0, 1, 2); - GBAlayout->addLayout(GBAvollayout); - volumeBox = new QSpinBox(AGGroupBox); - volumeBox->setRange(0, 100); - volumeBox->setSingleStep(5); - GBAvollayout->addWidget(volumeBox, 0, 2); - - CBEnableSound = new QCheckBox(AGGroupBox); - CBEnableSound->setText(QCheckBox::tr("Enable sound")); - GBAvollayout->addWidget(CBEnableSound, 1, 0, 1, 1); - - CBEnableMusic = new QCheckBox(AGGroupBox); - CBEnableMusic->setText(QCheckBox::tr("Enable music")); - GBAvollayout->addWidget(CBEnableMusic, 1, 1, 1, 2); - - GBAvollayout->setSizeConstraint(QLayout::SetMinimumSize); - - hr = new QFrame(AGGroupBox); - hr->setFrameStyle(QFrame::HLine); - hr->setLineWidth(3); - hr->setFixedHeight(10); - GBAlayout->addWidget(hr); - - CBAltDamage = new QCheckBox(AGGroupBox); - CBAltDamage->setText(QCheckBox::tr("Alternative damage show")); - GBAlayout->addWidget(CBAltDamage); - - gbTBLayout->addWidget(AGGroupBox, 0, 1, 3, 1); + connect(mapper, SIGNAL(mapped(int)), this, SLOT(colorButtonClicked(int))); } previousQuality = this->SLQuality->value(); @@ -499,3 +528,25 @@ CBTeamName->setVisible(enabled); LblNoEditTeam->setVisible(!enabled); } + +void PageOptions::colorButtonClicked(int i) +{ + if(i < 0 || i >= m_colorButtons.size()) + return; + + QPalette p = m_colorButtons[i]->palette(); + QColor c = QColorDialog::getColor(p.color(QPalette::Button)); + + if(c.isValid()) + { + DataManager::instance().colorsModel()->item(i)->setData(c); + m_colorButtons[i]->setStyleSheet(QString("background: %1").arg(c.name())); + } +} + +void PageOptions::onColorModelDataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight) +{ + QStandardItemModel * model = DataManager::instance().colorsModel(); + + m_colorButtons[topLeft.row()]->setStyleSheet(QString("background: %1").arg(model->item(topLeft.row())->data().value().name())); +} diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/page/pageoptions.h --- a/QTfrontend/ui/page/pageoptions.h Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/ui/page/pageoptions.h Thu Aug 30 13:02:19 2012 -0400 @@ -23,6 +23,7 @@ class FPSEdit; class IconedGroupBox; +class QSignalMapper; class PageOptions : public AbstractPage { @@ -91,6 +92,7 @@ QPushButton *BtnNewTeam; QPushButton *BtnEditTeam; QPushButton *BtnDeleteTeam; + QList m_colorButtons; private slots: void forceFullscreen(int index); @@ -101,6 +103,8 @@ void requestEditSelectedTeam(); void requestDeleteSelectedTeam(); void savePwdChanged(int state); + void colorButtonClicked(int i); + void onColorModelDataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight); }; #endif diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/page/pagevideos.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/QTfrontend/ui/page/pagevideos.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,1136 @@ +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hwconsts.h" +#include "pagevideos.h" +#include "igbox.h" +#include "libav_iteraction.h" +#include "gameuiconfig.h" +#include "recorder.h" +#include "ask_quit.h" +#include "upload_video.h" + +static const QSize ThumbnailSize(350, 350*3/5); + +// columns in table with list of video files +enum VideosColumns +{ + vcName, + vcSize, + vcProgress, // either encoding or uploading + + vcNumColumns, +}; + +// this class is used for items in first column in file-table +class VideoItem : public QTableWidgetItem +{ + // note: QTableWidgetItem is not Q_OBJECT + + public: + VideoItem(const QString& name); + ~VideoItem(); + + QString name; + QString prefix; // original filename without extension + QString desc; // description (duration, resolution, etc...) + QString uploadUrl; // http://youtu.be/??????? + HWRecorder * pRecorder; // non NULL if file is being encoded + QNetworkReply * pUploading; // non NULL if file is being uploaded + bool seen; // used when updating directory + float lastSizeUpdate; + float progress; + + bool ready() + { return !pRecorder; } + + QString path() + { return cfgdir->absoluteFilePath("Videos/" + name); } +}; + +VideoItem::VideoItem(const QString& name) + : QTableWidgetItem(name, UserType) +{ + this->name = name; + pRecorder = NULL; + pUploading = NULL; + lastSizeUpdate = 0; + progress = 0; +} + +VideoItem::~VideoItem() +{} + +QLayout * PageVideos::bodyLayoutDefinition() +{ + QGridLayout * pPageLayout = new QGridLayout(); + pPageLayout->setColumnStretch(0, 1); + pPageLayout->setColumnStretch(1, 2); + pPageLayout->setRowStretch(0, 1); + pPageLayout->setRowStretch(1, 1); + + // options + { + IconedGroupBox* pOptionsGroup = new IconedGroupBox(this); + pOptionsGroup->setIcon(QIcon(":/res/Settings.png")); // FIXME + pOptionsGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + pOptionsGroup->setTitle(QGroupBox::tr("Video recording options")); + QGridLayout * pOptLayout = new QGridLayout(pOptionsGroup); + + // label for format + QLabel *labelFormat = new QLabel(pOptionsGroup); + labelFormat->setText(QLabel::tr("Format")); + pOptLayout->addWidget(labelFormat, 0, 0); + + // list of supported formats + comboAVFormats = new QComboBox(pOptionsGroup); + pOptLayout->addWidget(comboAVFormats, 0, 1, 1, 4); + LibavIteraction::instance().fillFormats(comboAVFormats); + + // separator + QFrame * hr = new QFrame(pOptionsGroup); + hr->setFrameStyle(QFrame::HLine); + hr->setLineWidth(3); + hr->setFixedHeight(10); + pOptLayout->addWidget(hr, 1, 0, 1, 5); + + // label for audio codec + QLabel *labelACodec = new QLabel(pOptionsGroup); + labelACodec->setText(QLabel::tr("Audio codec")); + pOptLayout->addWidget(labelACodec, 2, 0); + + // list of supported audio codecs + comboAudioCodecs = new QComboBox(pOptionsGroup); + pOptLayout->addWidget(comboAudioCodecs, 2, 1, 1, 3); + + // checkbox 'record audio' + checkRecordAudio = new QCheckBox(pOptionsGroup); + checkRecordAudio->setText(QCheckBox::tr("Record audio")); + pOptLayout->addWidget(checkRecordAudio, 2, 4); + + // separator + hr = new QFrame(pOptionsGroup); + hr->setFrameStyle(QFrame::HLine); + hr->setLineWidth(3); + hr->setFixedHeight(10); + pOptLayout->addWidget(hr, 3, 0, 1, 5); + + // label for video codec + QLabel *labelVCodec = new QLabel(pOptionsGroup); + labelVCodec->setText(QLabel::tr("Video codec")); + pOptLayout->addWidget(labelVCodec, 4, 0); + + // list of supported video codecs + comboVideoCodecs = new QComboBox(pOptionsGroup); + pOptLayout->addWidget(comboVideoCodecs, 4, 1, 1, 4); + + // label for resolution + QLabel *labelRes = new QLabel(pOptionsGroup); + labelRes->setText(QLabel::tr("Resolution")); + pOptLayout->addWidget(labelRes, 5, 0); + + // width + widthEdit = new QLineEdit(pOptionsGroup); + widthEdit->setValidator(new QIntValidator(this)); + pOptLayout->addWidget(widthEdit, 5, 1); + + // x + QLabel *labelX = new QLabel(pOptionsGroup); + labelX->setText("X"); + pOptLayout->addWidget(labelX, 5, 2); + + // height + heightEdit = new QLineEdit(pOptionsGroup); + heightEdit->setValidator(new QIntValidator(pOptionsGroup)); + pOptLayout->addWidget(heightEdit, 5, 3); + + // checkbox 'use game resolution' + checkUseGameRes = new QCheckBox(pOptionsGroup); + checkUseGameRes->setText(QCheckBox::tr("Use game resolution")); + pOptLayout->addWidget(checkUseGameRes, 5, 4); + + // label for framerate + QLabel *labelFramerate = new QLabel(pOptionsGroup); + labelFramerate->setText(QLabel::tr("Framerate")); + pOptLayout->addWidget(labelFramerate, 6, 0); + + // framerate + framerateBox = new QSpinBox(pOptionsGroup); + framerateBox->setRange(1, 200); + framerateBox->setSingleStep(1); + pOptLayout->addWidget(framerateBox, 6, 1); + + // button 'set default options' + btnDefaults = new QPushButton(pOptionsGroup); + btnDefaults->setText(QPushButton::tr("Set default options")); + pOptLayout->addWidget(btnDefaults, 7, 0, 1, 5); + + pPageLayout->addWidget(pOptionsGroup, 1, 0); + } + + // list of videos + { + IconedGroupBox* pTableGroup = new IconedGroupBox(this); + pTableGroup->setIcon(QIcon(":/res/graphicsicon.png")); // FIXME + pTableGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + pTableGroup->setTitle(QGroupBox::tr("Videos")); + + QStringList columns; + columns << tr("Name"); + columns << tr("Size"); + columns << ""; + + filesTable = new QTableWidget(pTableGroup); + filesTable->setColumnCount(vcNumColumns); + filesTable->setHorizontalHeaderLabels(columns); + filesTable->setSelectionBehavior(QAbstractItemView::SelectRows); + filesTable->setSelectionMode(QAbstractItemView::SingleSelection); + filesTable->setEditTriggers(QAbstractItemView::SelectedClicked); + filesTable->verticalHeader()->hide(); + filesTable->setMinimumWidth(400); + + QHeaderView * header = filesTable->horizontalHeader(); + header->setResizeMode(vcName, QHeaderView::ResizeToContents); + header->setResizeMode(vcSize, QHeaderView::Fixed); + header->resizeSection(vcSize, 100); + header->setStretchLastSection(true); + + btnOpenDir = new QPushButton(QPushButton::tr("Open videos directory"), pTableGroup); + + QVBoxLayout *box = new QVBoxLayout(pTableGroup); + box->addWidget(filesTable); + box->addWidget(btnOpenDir); + + pPageLayout->addWidget(pTableGroup, 0, 1, 2, 1); + } + + // description + { + IconedGroupBox* pDescGroup = new IconedGroupBox(this); + pDescGroup->setIcon(QIcon(":/res/graphicsicon.png")); // FIXME + pDescGroup->setTitle(QGroupBox::tr("Description")); + + QVBoxLayout* pDescLayout = new QVBoxLayout(pDescGroup); + QHBoxLayout* pTopDescLayout = new QHBoxLayout(0); // picture and text + QHBoxLayout* pBottomDescLayout = new QHBoxLayout(0); // buttons + + // label with thumbnail picture + labelThumbnail = new QLabel(pDescGroup); + labelThumbnail->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + labelThumbnail->setMaximumSize(ThumbnailSize); + labelThumbnail->setStyleSheet( + "QFrame {" + "border: solid;" + "border-width: 3px;" + "border-color: #ffcc00;" + "border-radius: 4px;" + "}" ); + clearThumbnail(); + pTopDescLayout->addWidget(labelThumbnail, 2); + + // label with file description + labelDesc = new QLabel(pDescGroup); + labelDesc->setAlignment(Qt::AlignLeft | Qt::AlignTop); + labelDesc->setTextInteractionFlags(Qt::TextSelectableByMouse | + Qt::TextSelectableByKeyboard | + Qt::LinksAccessibleByMouse | + Qt::LinksAccessibleByKeyboard); + labelDesc->setTextFormat(Qt::RichText); + labelDesc->setOpenExternalLinks(true); + pTopDescLayout->addWidget(labelDesc, 1); + + // buttons: play and delete + btnPlay = new QPushButton(QPushButton::tr("Play"), pDescGroup); + btnPlay->setEnabled(false); + pBottomDescLayout->addWidget(btnPlay); + btnDelete = new QPushButton(QPushButton::tr("Delete"), pDescGroup); + btnDelete->setEnabled(false); + pBottomDescLayout->addWidget(btnDelete); + btnToYouTube = new QPushButton(QPushButton::tr("Upload to YouTube"), pDescGroup); + btnToYouTube->setEnabled(false); + pBottomDescLayout->addWidget(btnToYouTube); + + pDescLayout->addStretch(1); + pDescLayout->addLayout(pTopDescLayout, 0); + pDescLayout->addStretch(1); + pDescLayout->addLayout(pBottomDescLayout, 0); + + pPageLayout->addWidget(pDescGroup, 0, 0); + } + + return pPageLayout; +} + +QLayout * PageVideos::footerLayoutDefinition() +{ + return NULL; +} + +void PageVideos::connectSignals() +{ + connect(checkUseGameRes, SIGNAL(stateChanged(int)), this, SLOT(changeUseGameRes(int))); + connect(checkRecordAudio, SIGNAL(stateChanged(int)), this, SLOT(changeRecordAudio(int))); + connect(comboAVFormats, SIGNAL(currentIndexChanged(int)), this, SLOT(changeAVFormat(int))); + connect(btnDefaults, SIGNAL(clicked()), this, SLOT(setDefaultOptions())); + connect(filesTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(cellDoubleClicked(int, int))); + connect(filesTable, SIGNAL(cellChanged(int,int)), this, SLOT(cellChanged(int, int))); + connect(filesTable, SIGNAL(currentCellChanged(int,int,int,int)), this, SLOT(currentCellChanged(int,int,int,int))); + connect(btnPlay, SIGNAL(clicked()), this, SLOT(playSelectedFile())); + connect(btnDelete, SIGNAL(clicked()), this, SLOT(deleteSelectedFiles())); + connect(btnToYouTube, SIGNAL(clicked()), this, SLOT(uploadToYouTube())); + connect(btnOpenDir, SIGNAL(clicked()), this, SLOT(openVideosDirectory())); +} + +PageVideos::PageVideos(QWidget* parent) : AbstractPage(parent), + config(0), netManager(0) +{ + nameChangedFromCode = false; + numRecorders = 0; + numUploads = 0; + initPage(); +} + +void PageVideos::init(GameUIConfig * config) +{ + this->config = config; + + QString path = cfgdir->absolutePath() + "/Videos"; + QFileSystemWatcher * pWatcher = new QFileSystemWatcher(this); + pWatcher->addPath(path); + connect(pWatcher, SIGNAL(directoryChanged(const QString &)), this, SLOT(updateFileList(const QString &))); + updateFileList(path); + + startEncoding(); // this is for videos recorded from demos which were executed directly (without frontend) +} + +// user changed file format, we need to update list of codecs +void PageVideos::changeAVFormat(int index) +{ + // remember selected codecs + QString prevVCodec = videoCodec(); + QString prevACodec = audioCodec(); + + // clear lists of codecs + comboVideoCodecs->clear(); + comboAudioCodecs->clear(); + + // get list of codecs for specified format + LibavIteraction::instance().fillCodecs(comboAVFormats->itemData(index).toString(), comboVideoCodecs, comboAudioCodecs); + + // disable audio if there is no audio codec + if (comboAudioCodecs->count() == 0) + { + checkRecordAudio->setChecked(false); + checkRecordAudio->setEnabled(false); + } + else + checkRecordAudio->setEnabled(true); + + // restore selected codecs if possible + int iVCodec = comboVideoCodecs->findData(prevVCodec); + if (iVCodec != -1) + comboVideoCodecs->setCurrentIndex(iVCodec); + int iACodec = comboAudioCodecs->findData(prevACodec); + if (iACodec != -1) + comboAudioCodecs->setCurrentIndex(iACodec); +} + +// user switched checkbox 'use game resolution' +void PageVideos::changeUseGameRes(int state) +{ + if (state && config) + { + // set resolution to game resolution + QRect resolution = config->vid_Resolution(); + widthEdit->setText(QString::number(resolution.width())); + heightEdit->setText(QString::number(resolution.height())); + } + widthEdit->setEnabled(!state); + heightEdit->setEnabled(!state); +} + +// user switched checkbox 'record audio' +void PageVideos::changeRecordAudio(int state) +{ + comboAudioCodecs->setEnabled(!!state); +} + +void PageVideos::setDefaultCodecs() +{ + if (tryCodecs("mp4", "libx264", "libmp3lame")) + return; + if (tryCodecs("mp4", "libx264", "libfaac")) + return; + if (tryCodecs("mp4", "libx264", "libvo_aacenc")) + return; + if (tryCodecs("mp4", "libx264", "aac")) + return; + if (tryCodecs("mp4", "libx264", "mp2")) + return; + if (tryCodecs("avi", "libxvid", "libmp3lame")) + return; + if (tryCodecs("avi", "libxvid", "ac3_fixed")) + return; + if (tryCodecs("avi", "libxvid", "mp2")) + return; + if (tryCodecs("avi", "mpeg4", "libmp3lame")) + return; + if (tryCodecs("avi", "mpeg4", "ac3_fixed")) + return; + if (tryCodecs("avi", "mpeg4", "mp2")) + return; + + // this shouldn't happen, just in case + if (tryCodecs("ogg", "libtheora", "libvorbis")) + return; + tryCodecs("ogg", "libtheora", "flac"); +} + +void PageVideos::setDefaultOptions() +{ + framerateBox->setValue(25); + checkRecordAudio->setChecked(true); + checkUseGameRes->setChecked(true); + setDefaultCodecs(); +} + +bool PageVideos::tryCodecs(const QString & format, const QString & vcodec, const QString & acodec) +{ + // first we should change format + int iFormat = comboAVFormats->findData(format); + if (iFormat == -1) + return false; + comboAVFormats->setCurrentIndex(iFormat); + // format was changed, so lists of codecs were automatically updated to codecs supported by this format + + // try to find video codec + int iVCodec = comboVideoCodecs->findData(vcodec); + if (iVCodec == -1) + return false; + comboVideoCodecs->setCurrentIndex(iVCodec); + + // try to find audio codec + int iACodec = comboAudioCodecs->findData(acodec); + if (iACodec == -1 && checkRecordAudio->isChecked()) + return false; + if (iACodec != -1) + comboAudioCodecs->setCurrentIndex(iACodec); + + return true; +} + +// get file size as string +static QString FileSizeStr(const QString & path) +{ + quint64 size = QFileInfo(path).size(); + + quint64 KiB = 1024; + quint64 MiB = 1024*KiB; + quint64 GiB = 1024*MiB; + QString sizeStr; + if (size >= GiB) + return QString("%1 GiB").arg(QString::number(float(size)/GiB, 'f', 2)); + if (size >= MiB) + return QString("%1 MiB").arg(QString::number(float(size)/MiB, 'f', 2)); + if (size >= KiB) + return QString("%1 KiB").arg(QString::number(float(size)/KiB, 'f', 2)); + return PageVideos::tr("%1 bytes").arg(QString::number(size)); +} + +// set file size in file list in specified row +void PageVideos::updateSize(int row) +{ + VideoItem * item = nameItem(row); + QString path = item->ready()? item->path() : cfgdir->absoluteFilePath("VideoTemp/" + item->pRecorder->name); + filesTable->item(row, vcSize)->setText(FileSizeStr(path)); +} + +// There is a button 'Open videos dir', so it is possible that user will open +// this dir and rename/delete some files there, so we should handle this. +void PageVideos::updateFileList(const QString & path) +{ + // mark all files as non seen + int numRows = filesTable->rowCount(); + for (int i = 0; i < numRows; i++) + nameItem(i)->seen = false; + + QStringList files = QDir(path).entryList(QDir::Files); + foreach (const QString & name, files) + { + int row = -1; + foreach (QTableWidgetItem * item, filesTable->findItems(name, Qt::MatchExactly)) + { + if (item->type() != QTableWidgetItem::UserType || !((VideoItem*)item)->ready()) + continue; + row = item->row(); + break; + } + if (row == -1) + row = appendRow(name); + VideoItem * item = nameItem(row); + item->seen = true; + item->desc = ""; + updateSize(row); + } + + // remove all non seen files + for (int i = 0; i < filesTable->rowCount();) + { + VideoItem * item = nameItem(i); + if (item->ready() && !item->seen) + filesTable->removeRow(i); + else + i++; + } +} + +void PageVideos::addRecorder(HWRecorder* pRecorder) +{ + int row = appendRow(pRecorder->name); + VideoItem * item = nameItem(row); + item->pRecorder = pRecorder; + pRecorder->item = item; + + // add progress bar + QProgressBar * progressBar = new QProgressBar(filesTable); + progressBar->setMinimum(0); + progressBar->setMaximum(10000); + progressBar->setValue(0); + connect(pRecorder, SIGNAL(onProgress(float)), this, SLOT(updateProgress(float))); + connect(pRecorder, SIGNAL(encodingFinished(bool)), this, SLOT(encodingFinished(bool))); + filesTable->setCellWidget(row, vcProgress, progressBar); + + numRecorders++; +} + +void PageVideos::setProgress(int row, VideoItem* item, float value) +{ + QProgressBar * progressBar = (QProgressBar*)filesTable->cellWidget(row, vcProgress); + progressBar->setValue(value*10000); + progressBar->setFormat(QString("%1%").arg(value*100, 0, 'f', 2)); + item->progress = value; +} + +void PageVideos::updateProgress(float value) +{ + HWRecorder * pRecorder = (HWRecorder*)sender(); + VideoItem * item = pRecorder->item; + int row = filesTable->row(item); + + // update file size every percent + if (value - item->lastSizeUpdate > 0.01) + { + updateSize(row); + item->lastSizeUpdate = value; + } + + setProgress(row, item, value); +} + +void PageVideos::encodingFinished(bool success) +{ + numRecorders--; + + HWRecorder * pRecorder = (HWRecorder*)sender(); + VideoItem * item = (VideoItem*)pRecorder->item; + int row = filesTable->row(item); + + if (success) + { + // move file to destination + success = cfgdir->rename("VideoTemp/" + pRecorder->name, "Videos/" + item->name); + if (!success) + { + // unable to rename for some reason (maybe user entered incorrect name); + // try to use temp name instead. + success = cfgdir->rename("VideoTemp/" + pRecorder->name, "Videos/" + pRecorder->name); + if (success) + setName(item, pRecorder->name); + } + } + + if (!success) + { + filesTable->removeRow(row); + return; + } + + filesTable->setCellWidget(row, vcProgress, NULL); // remove progress bar + item->pRecorder = NULL; + updateSize(row); + updateDescription(); +} + +void PageVideos::cellDoubleClicked(int row, int column) +{ + play(row); +} + +void PageVideos::cellChanged(int row, int column) +{ + // user can only edit name + if (column != vcName || nameChangedFromCode) + return; + + // user has edited filename, so we should rename the file + VideoItem * item = nameItem(row); + QString oldName = item->name; + QString newName = item->text(); + if (!newName.contains('.')) // user forgot an extension + { + // restore old extension + int pt = oldName.lastIndexOf('.'); + if (pt != -1) + { + newName += oldName.right(oldName.length() - pt); + setName(item, newName); + } + } +#ifdef Q_WS_WIN + // there is a bug in qt, QDir::rename() doesn't fail on such names but damages files + if (newName.contains(QRegExp("[\"*:<>?\/|]"))) + { + setName(item, oldName); + return; + } +#endif + if (item->ready() && !cfgdir->rename("Videos/" + oldName, "Videos/" + newName)) + { + // unable to rename for some reason (maybe user entered incorrect name), + // therefore restore old name in cell + setName(item, oldName); + return; + } + item->name = newName; + updateDescription(); +} + +void PageVideos::setName(VideoItem * item, const QString & newName) +{ + nameChangedFromCode = true; + item->setText(newName); + nameChangedFromCode = false; + item->name = newName; +} + +int PageVideos::appendRow(const QString & name) +{ + int row = filesTable->rowCount(); + filesTable->setRowCount(row+1); + + // add 'name' item + QTableWidgetItem * item = new VideoItem(name); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + nameChangedFromCode = true; + filesTable->setItem(row, vcName, item); + nameChangedFromCode = false; + + // add 'size' item + item = new QTableWidgetItem(); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + item->setTextAlignment(Qt::AlignRight); + filesTable->setItem(row, vcSize, item); + + // add 'progress' item + item = new QTableWidgetItem(); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + filesTable->setItem(row, vcProgress, item); + + return row; +} + +VideoItem* PageVideos::nameItem(int row) +{ + return (VideoItem*)filesTable->item(row, vcName); +} + +void PageVideos::clearThumbnail() +{ + // add empty (transparent) image for proper sizing + QPixmap pic(ThumbnailSize); + pic.fill(QColor(0,0,0,0)); + labelThumbnail->setPixmap(pic); +} + +void PageVideos::updateDescription() +{ + VideoItem * item = nameItem(filesTable->currentRow()); + if (!item) + { + // nothing is selected => clear description and return + labelDesc->clear(); + clearThumbnail(); + btnPlay->setEnabled(false); + btnDelete->setEnabled(false); + btnToYouTube->setEnabled(false); + return; + } + + btnPlay->setEnabled(item->ready()); + btnToYouTube->setEnabled(item->ready()); + btnDelete->setEnabled(true); + btnDelete->setText(item->ready()? QPushButton::tr("Delete") : QPushButton::tr("Cancel")); + btnToYouTube->setText(item->pUploading? QPushButton::tr("Cancel uploading") : QPushButton::tr("Upload to YouTube")); + + // construct string with desctiption of this file to display it + QString desc = item->name + "\n\n"; + + if (!item->ready()) + desc += tr("(in progress...)"); + else + { + QString path = item->path(); + desc += tr("Date: ") + QFileInfo(path).created().toString(Qt::DefaultLocaleLongDate) + '\n'; + desc += tr("Size: ") + FileSizeStr(path) + '\n'; + if (item->desc.isEmpty()) + { + // Extract description from file; + // It will contain duration, resolution, etc and also comment added by hwengine. + item->desc = LibavIteraction::instance().getFileInfo(path); + + // extract prefix (original name) from description (it is enclosed in prefix[???]prefix) + int prefixBegin = item->desc.indexOf("prefix["); + int prefixEnd = item->desc.indexOf("]prefix"); + if (prefixBegin != -1 && prefixEnd != -1) + { + item->prefix = item->desc.mid(prefixBegin + 7, prefixEnd - (prefixBegin + 7)); + item->desc.remove(prefixBegin, prefixEnd + 7 - prefixBegin); + } + } + desc += item->desc + '\n'; + } + + if (item->prefix.isEmpty()) + { + // try to extract prefix from file name instead + if (item->ready()) + item->prefix = item->name; + else + item->prefix = item->pRecorder->name; + + // remove extension + int pt = item->prefix.lastIndexOf('.'); + if (pt != -1) + item->prefix.truncate(pt); + } + + if (item->ready() && item->uploadUrl.isEmpty()) + { + // try to load url from file + QFile * file = new QFile(cfgdir->absoluteFilePath("VideoTemp/" + item->prefix + "-url.txt"), this); + if (!file->open(QIODevice::ReadOnly)) + item->uploadUrl = "no"; + else + { + QByteArray data = file->readAll(); + file->close(); + item->uploadUrl = QString::fromUtf8(data.data()); + } + } + if (item->uploadUrl != "no") + desc += QString("%1").arg(item->uploadUrl); + desc.replace("\n", "
"); + + labelDesc->setText(desc); + + if (!item->prefix.isEmpty()) + { + QString thumbName = cfgdir->absoluteFilePath("VideoTemp/" + item->prefix); + QPixmap pic; + if (pic.load(thumbName + ".png") || pic.load(thumbName + ".bmp")) + { + if (pic.height()*ThumbnailSize.width() > pic.width()*ThumbnailSize.height()) + pic = pic.scaledToWidth(ThumbnailSize.width()); + else + pic = pic.scaledToHeight(ThumbnailSize.height()); + labelThumbnail->setPixmap(pic); + } + else + clearThumbnail(); + } +} + +// user selected another cell, so we should change description +void PageVideos::currentCellChanged(int row, int column, int previousRow, int previousColumn) +{ + updateDescription(); +} + +// open video file in external media player +void PageVideos::play(int row) +{ + VideoItem * item = nameItem(row); + if (item && item->ready()) + QDesktopServices::openUrl(QUrl("file:///" + QDir::toNativeSeparators(item->path()))); +} + +void PageVideos::playSelectedFile() +{ + int index = filesTable->currentRow(); + if (index != -1) + play(index); +} + +void PageVideos::deleteSelectedFiles() +{ + int index = filesTable->currentRow(); + if (index == -1) + return; + + VideoItem * item = nameItem(index); + if (!item) + return; + + // ask user if (s)he is serious + if (QMessageBox::question(this, + tr("Are you sure?"), + tr("Do you really want do remove %1?").arg(item->name), + QMessageBox::Yes | QMessageBox::No) + != QMessageBox::Yes) + return; + + // remove + if (!item->ready()) + item->pRecorder->deleteLater(); + else + cfgdir->remove("Videos/" + item->name); + +// this code is for removing several files when multiple selection is enabled +#if 0 + QList items = filesTable->selectedItems(); + int num = items.size() / vcNumColumns; + if (num == 0) + return; + + // ask user if (s)he is serious + if (QMessageBox::question(this, + tr("Are you sure?"), + tr("Do you really want do remove %1 file(s)?").arg(num), + QMessageBox::Yes | QMessageBox::No) + != QMessageBox::Yes) + return; + + // remove + foreach (QTableWidgetItem * witem, items) + { + if (witem->type() != QTableWidgetItem::UserType) + continue; + VideoItem * item = (VideoItem*)witem; + if (!item->ready()) + item->pRecorder->deleteLater(); + else + cfgdir->remove("Videos/" + item->name); + } +#endif +} + +void PageVideos::keyPressEvent(QKeyEvent * pEvent) +{ + if (filesTable->hasFocus()) + { + if (pEvent->key() == Qt::Key_Delete) + { + deleteSelectedFiles(); + return; + } + if (pEvent->key() == Qt::Key_Enter) // doesn't work + { + playSelectedFile(); + return; + } + } + AbstractPage::keyPressEvent(pEvent); +} + +void PageVideos::openVideosDirectory() +{ + QString path = QDir::toNativeSeparators(cfgdir->absolutePath() + "/Videos"); + QDesktopServices::openUrl(QUrl("file:///" + path)); +} + +// clear VideoTemp directory (except for thumbnails and upload links) +void PageVideos::clearTemp() +{ + QDir temp(cfgdir->absolutePath() + "/VideoTemp"); + QStringList files = temp.entryList(QDir::Files); + foreach (const QString& file, files) + { + if (!file.endsWith(".bmp") && !file.endsWith(".png") && !file.endsWith("-url.txt")) + temp.remove(file); + } +} + +bool PageVideos::tryQuit(HWForm * form) +{ + bool quit = true; + if (numRecorders != 0 || numUploads != 0) + { + // ask user what to do - abort or wait + HWAskQuitDialog * askd = new HWAskQuitDialog(this, form); + askd->deleteLater(); + quit = askd->exec(); + } + if (quit) + clearTemp(); + return quit; +} + +// returns multi-line string with list of videos in progress +/* it will look like this: +foo.avi (15.21% - encoding) +bar.avi (18.21% - uploading) +*/ +QString PageVideos::getVideosInProgress() +{ + QString list = ""; + int count = filesTable->rowCount(); + for (int i = 0; i < count; i++) + { + VideoItem * item = nameItem(i); + QString process; + if (!item->ready()) + process = tr("encoding"); + else if (item->pUploading) + process = tr("uploading"); + else + continue; + float progress = 100*item->progress; + if (progress > 99.99) + progress = 99.99; // displaying 100% may be confusing + list += item->name + " (" + QString::number(progress, 'f', 2) + "% - " + process + ")\n"; + } + return list; +} + +void PageVideos::startEncoding(const QByteArray & record) +{ + QDir videoTempDir(cfgdir->absolutePath() + "/VideoTemp/"); + QStringList files = videoTempDir.entryList(QStringList("*.txtout"), QDir::Files); + foreach (const QString & str, files) + { + QString prefix = str; + prefix.chop(7); // remove ".txtout" + videoTempDir.rename(prefix + ".txtout", prefix + ".txtin"); // rename this file to not open it twice + + HWRecorder* pRecorder = new HWRecorder(config, prefix); + + if (!record.isEmpty()) + pRecorder->EncodeVideo(record); + else + { + // this is for videos recorded from demos which were executed directly (without frontend) + QFile demofile(videoTempDir.absoluteFilePath(prefix + ".hwd")); + if (!demofile.open(QIODevice::ReadOnly)) + continue; + QByteArray demo = demofile.readAll(); + if (demo.isEmpty()) + continue; + pRecorder->EncodeVideo(demo); + } + addRecorder(pRecorder); + } +} + +VideoItem * PageVideos::itemFromReply(QNetworkReply* reply, int & row) +{ + VideoItem * item = NULL; + int count = filesTable->rowCount(); + // find corresponding item (maybe there is a better way to implement this?) + for (int i = 0; i < count; i++) + { + item = nameItem(i); + if (item->pUploading == reply) + { + row = i; + break; + } + } + return item; +} + +void PageVideos::uploadProgress(qint64 bytesSent, qint64 bytesTotal) +{ + QNetworkReply* reply = (QNetworkReply*)sender(); + int row; + VideoItem * item = itemFromReply(reply, row); + setProgress(row, item, bytesSent*1.0/bytesTotal); +} + +void PageVideos::uploadFinished() +{ + QNetworkReply* reply = (QNetworkReply*)sender(); + reply->deleteLater(); + + int row; + VideoItem * item = itemFromReply(reply, row); + if (!item) + return; + + item->pUploading = NULL; + + // extract video id from reply + QString videoid; + QXmlStreamReader xml(reply); + while (!xml.atEnd()) + { + xml.readNext(); + if (xml.qualifiedName() == "yt:videoid") + { + videoid = xml.readElementText(); + break; + } + } + + if (!videoid.isEmpty()) + { + item->uploadUrl = "http://youtu.be/" + videoid; + updateDescription(); + + // save url in file + QFile * file = new QFile(cfgdir->absoluteFilePath("VideoTemp/" + item->prefix + "-url.txt"), this); + if (file->open(QIODevice::WriteOnly)) + { + file->write(item->uploadUrl.toUtf8()); + file->close(); + } + } + + filesTable->setCellWidget(row, vcProgress, NULL); // remove progress bar + numUploads--; +} + +// this will protect saved youtube password from those who cannot read source code +static QString protectPass(QString str) +{ + QByteArray array = str.toUtf8(); + for (int i = 0; i < array.size(); i++) + array[i] = array[i] ^ 0xC4 ^ i; + array = array.toBase64(); + return QString::fromAscii(array.data()); +} + +static QString unprotectPass(QString str) +{ + QByteArray array = QByteArray::fromBase64(str.toAscii()); + for (int i = 0; i < array.size(); i++) + array[i] = array[i] ^ 0xC4 ^ i; + return QString::fromUtf8(array); +} + +void PageVideos::uploadToYouTube() +{ + int row = filesTable->currentRow(); + VideoItem * item = nameItem(row); + + if (item->pUploading) + { + if (QMessageBox::question(this, + tr("Are you sure?"), + tr("Do you really want do cancel uploading %1?").arg(item->name), + QMessageBox::Yes | QMessageBox::No) + != QMessageBox::Yes) + return; + item->pUploading->deleteLater(); + filesTable->setCellWidget(row, vcProgress, NULL); // remove progress bar + numUploads--; + return; + } + + if (!netManager) + netManager = new QNetworkAccessManager(this); + + HWUploadVideoDialog* dlg = new HWUploadVideoDialog(this, item->name, netManager); + dlg->deleteLater(); + if (config->value("youtube/save").toBool()) + { + dlg->cbSave->setChecked(true); + dlg->leAccount->setText(config->value("youtube/name").toString()); + dlg->lePassword->setText(unprotectPass(config->value("youtube/pswd").toString())); + } + + bool result = dlg->exec(); + + if (dlg->cbSave->isChecked()) + { + config->setValue("youtube/save", true); + config->setValue("youtube/name", dlg->leAccount->text()); + config->setValue("youtube/pswd", protectPass(dlg->lePassword->text())); + } + else + { + config->setValue("youtube/save", false); + config->setValue("youtube/name", ""); + config->setValue("youtube/pswd", ""); + } + + if (!result) + return; + + QNetworkRequest request(QUrl(dlg->location)); + request.setRawHeader("Content-Type", "application/octet-stream"); + + QFile * file = new QFile(item->path(), this); + if (!file->open(QIODevice::ReadOnly)) + return; + + // add progress bar + QProgressBar * progressBar = new QProgressBar(filesTable); + progressBar->setMinimum(0); + progressBar->setMaximum(10000); + progressBar->setValue(0); + // make it different from progress-bar used during encoding (use blue color) + progressBar->setStyleSheet("* {color: #00ccff; selection-background-color: #00ccff;}" ); + filesTable->setCellWidget(row, vcProgress, progressBar); + + QNetworkReply* reply = netManager->put(request, file); + file->setParent(reply); // automatically close file when needed + item->pUploading = reply; + connect(reply, SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64))); + connect(reply, SIGNAL(finished()), this, SLOT(uploadFinished())); + numUploads++; + + updateDescription(); +} diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/page/pagevideos.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/QTfrontend/ui/page/pagevideos.h Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,125 @@ +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + + +#ifndef PAGE_VIDEOS_H +#define PAGE_VIDEOS_H + +#include "AbstractPage.h" + +class QNetworkAccessManager; +class QNetworkReply; +class GameUIConfig; +class HWRecorder; +class VideoItem; +class HWForm; + +class PageVideos : public AbstractPage +{ + Q_OBJECT + + public: + PageVideos(QWidget* parent = 0); + + QSpinBox *framerateBox; + QLineEdit *widthEdit; + QLineEdit *heightEdit; + QCheckBox *checkUseGameRes; + QCheckBox *checkRecordAudio; + + QString format() + { return comboAVFormats->itemData(comboAVFormats->currentIndex()).toString(); } + + QString videoCodec() + { return comboVideoCodecs->itemData(comboVideoCodecs->currentIndex()).toString(); } + + QString audioCodec() + { return comboAudioCodecs->itemData(comboAudioCodecs->currentIndex()).toString(); } + + void setDefaultCodecs(); + bool tryCodecs(const QString & format, const QString & vcodec, const QString & acodec); + void addRecorder(HWRecorder* pRecorder); + bool tryQuit(HWForm *form); + QString getVideosInProgress(); // get multi-line string with list of videos in progress + void startEncoding(const QByteArray & record = QByteArray()); + void init(GameUIConfig * config); + + private: + // virtuals from AbstractPage + QLayout * bodyLayoutDefinition(); + QLayout * footerLayoutDefinition(); + void connectSignals(); + + // virtual from QWidget + void keyPressEvent(QKeyEvent * pEvent); + + void setName(VideoItem * item, const QString & newName); + void updateSize(int row); + int appendRow(const QString & name); + VideoItem* nameItem(int row); + void play(int row); + void updateDescription(); + void clearTemp(); + void clearThumbnail(); + void setProgress(int row, VideoItem* item, float value); + VideoItem * itemFromReply(QNetworkReply* reply, int & row); + + GameUIConfig * config; + QNetworkAccessManager* netManager; + + // options group + QComboBox *comboAVFormats; + QComboBox *comboVideoCodecs; + QComboBox *comboAudioCodecs; + QPushButton *btnDefaults; + + // file list group + QTableWidget *filesTable; + QPushButton *btnOpenDir; + + // description group + QPushButton *btnPlay, *btnDelete, *btnToYouTube; + QLabel *labelDesc; + QLabel *labelThumbnail; + + // this flag is used to distinguish if cell was changed from code or by user + // (in signal cellChanged) + bool nameChangedFromCode; + + int numRecorders, numUploads; + + private slots: + void changeAVFormat(int index); + void changeUseGameRes(int state); + void changeRecordAudio(int state); + void setDefaultOptions(); + void encodingFinished(bool success); + void updateProgress(float value); + void cellDoubleClicked(int row, int column); + void cellChanged(int row, int column); + void currentCellChanged(int row, int column, int previousRow, int previousColumn); + void playSelectedFile(); + void deleteSelectedFiles(); + void openVideosDirectory(); + void updateFileList(const QString & path); + void uploadToYouTube(); + void uploadProgress(qint64 bytesSent, qint64 bytesTotal); + void uploadFinished(); +}; + +#endif // PAGE_VIDEOS_H diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/widget/chatwidget.cpp --- a/QTfrontend/ui/widget/chatwidget.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/ui/widget/chatwidget.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -922,7 +922,7 @@ if(b) { chatNicks->insertAction(0, acKick); -// chatNicks->insertAction(0, acBan); + chatNicks->insertAction(0, acBan); } } diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui/widget/teamselect.cpp --- a/QTfrontend/ui/widget/teamselect.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/ui/widget/teamselect.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -135,6 +135,11 @@ curPlayingTeams.erase(itPlay); break; } + else + { + qWarning() << QString("removeNetTeam: team '%1' was actually a local team!").arg(team.name()); + break; + } } emit setEnabledGameStart(curPlayingTeams.size()>1); } diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui_hwform.cpp --- a/QTfrontend/ui_hwform.cpp Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/ui_hwform.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -46,6 +46,7 @@ #include "pagegamestats.h" #include "pageplayrecord.h" #include "pagedata.h" +#include "pagevideos.h" #include "hwconsts.h" void Ui_HWForm::setupUi(HWForm *HWForm) @@ -145,4 +146,7 @@ pageFeedback = new PageFeedback(); Pages->addWidget(pageFeedback); + + pageVideos = new PageVideos(); + Pages->addWidget(pageVideos); } diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/ui_hwform.h --- a/QTfrontend/ui_hwform.h Thu Aug 30 12:47:41 2012 -0400 +++ b/QTfrontend/ui_hwform.h Thu Aug 30 13:02:19 2012 -0400 @@ -43,6 +43,7 @@ class PageAdmin; class PageNetType; class PageDrawMap; +class PageVideos; class QStackedLayout; class QFont; class QWidget; @@ -78,6 +79,7 @@ PageNetType *pageNetType; PageCampaign *pageCampaign; PageDrawMap *pageDrawMap; + PageVideos *pageVideos; QStackedLayout *Pages; QFont *font14; diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/util/libav_iteraction.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/QTfrontend/util/libav_iteraction.cpp Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,363 @@ +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "libav_iteraction.h" + +#if VIDEOREC +#define __STDC_CONSTANT_MACROS +extern "C" +{ +#include "libavformat/avformat.h" +} + +#include +#include +#include +#include + +#include "HWApplication.h" + +struct Codec +{ + CodecID id; + bool isAudio; + QString shortName; // used for identification + QString longName; // used for displaying to user + bool isRecomended; +}; + +struct Format +{ + QString shortName; + QString longName; + bool isRecomended; + QString extension; + QVector codecs; +}; + +QList codecs; +QMap formats; + +// test if given format supports given codec +bool FormatQueryCodec(AVOutputFormat *ofmt, enum CodecID codec_id) +{ +#if LIBAVFORMAT_VERSION_MAJOR >= 54 + return avformat_query_codec(ofmt, codec_id, FF_COMPLIANCE_NORMAL) == 1; +#else + if (ofmt->codec_tag) + return !!av_codec_get_tag(ofmt->codec_tag, codec_id); + return codec_id == ofmt->video_codec || codec_id == ofmt->audio_codec; +#endif +} + +LibavIteraction::LibavIteraction() +{ + // initialize libav and register all codecs and formats + av_register_all(); + + // get list of all codecs + AVCodec* pCodec = NULL; + while (pCodec = av_codec_next(pCodec)) + { +#if LIBAVCODEC_VERSION_MAJOR >= 54 + if (!av_codec_is_encoder(pCodec)) +#else + if (!pCodec->encode) +#endif + continue; + + if (pCodec->type != AVMEDIA_TYPE_VIDEO && pCodec->type != AVMEDIA_TYPE_AUDIO) + continue; + + // this encoders seems to be buggy + if (strcmp(pCodec->name, "rv10") == 0 || strcmp(pCodec->name, "rv20") == 0) + continue; + + // doesn't support stereo sound + if (strcmp(pCodec->name, "real_144") == 0) + continue; + + if (!pCodec->long_name || strlen(pCodec->long_name) == 0) + continue; + + if (pCodec->type == AVMEDIA_TYPE_VIDEO) + { + if (pCodec->supported_framerates != NULL) + continue; + + // check if codec supports yuv 4:2:0 format + if (!pCodec->pix_fmts) + continue; + bool yuv420Supported = false; + for (const PixelFormat* pfmt = pCodec->pix_fmts; *pfmt != -1; pfmt++) + if (*pfmt == PIX_FMT_YUV420P) + { + yuv420Supported = true; + break; + } + if (!yuv420Supported) + continue; + } + if (pCodec->type == AVMEDIA_TYPE_AUDIO) + { + // check if codec supports signed 16-bit format + if (!pCodec->sample_fmts) + continue; + bool s16Supported = false; + for (const AVSampleFormat* pfmt = pCodec->sample_fmts; *pfmt != -1; pfmt++) + if (*pfmt == AV_SAMPLE_FMT_S16) + { + s16Supported = true; + break; + } + if (!s16Supported) + continue; + } + // add codec to list of codecs + codecs.push_back(Codec()); + Codec & codec = codecs.back(); + codec.id = pCodec->id; + codec.isAudio = pCodec->type == AVMEDIA_TYPE_AUDIO; + codec.shortName = pCodec->name; + codec.longName = pCodec->long_name; + + codec.isRecomended = false; + if (strcmp(pCodec->name, "libx264") == 0) + { + codec.longName = "H.264/MPEG-4 Part 10 AVC (x264)"; + codec.isRecomended = true; + } + else if (strcmp(pCodec->name, "libxvid") == 0) + { + codec.longName = "MPEG-4 Part 2 (Xvid)"; + codec.isRecomended = true; + } + else if (strcmp(pCodec->name, "libmp3lame") == 0) + { + codec.longName = "MP3 (MPEG audio layer 3) (LAME)"; + codec.isRecomended = true; + } + else + codec.longName = pCodec->long_name; + + if (strcmp(pCodec->name, "mpeg4") == 0 || strcmp(pCodec->name, "ac3_fixed") == 0) + codec.isRecomended = true; + + // FIXME: remove next line + //codec.longName += QString(" (%1)").arg(codec.shortName); + } + + // get list of all formats + AVOutputFormat* pFormat = NULL; + while (pFormat = av_oformat_next(pFormat)) + { + if (!pFormat->extensions) + continue; + + // skip some strange formats to not confuse users + if (strstr(pFormat->long_name, "raw")) + continue; + + Format format; + bool hasVideoCodec = false; + for (QList::iterator codec = codecs.begin(); codec != codecs.end(); ++codec) + { + if (!FormatQueryCodec(pFormat, codec->id)) + continue; + format.codecs.push_back(&*codec); + if (!codec->isAudio) + hasVideoCodec = true; + } + if (!hasVideoCodec) + continue; + + QString ext(pFormat->extensions); + ext.truncate(strcspn(pFormat->extensions, ",")); + format.extension = ext; + format.shortName = pFormat->name; + format.longName = QString("%1 (*.%2)").arg(pFormat->long_name).arg(ext); + + // FIXME: remove next line + //format.longName += QString(" (%1)").arg(format.shortName); + + format.isRecomended = strcmp(pFormat->name, "mp4") == 0 || strcmp(pFormat->name, "avi") == 0; + + formats[pFormat->name] = format; + } +} + +void LibavIteraction::fillFormats(QComboBox * pFormats) +{ + // first insert recomended formats + foreach(const Format & format, formats) + if (format.isRecomended) + pFormats->addItem(format.longName, format.shortName); + + // remember where to place separator between recomended and other formats + int sep = pFormats->count(); + + // insert remaining formats + foreach(const Format & format, formats) + if (!format.isRecomended) + pFormats->addItem(format.longName, format.shortName); + + // insert separator if necessary + if (sep != 0 && sep != pFormats->count()) + pFormats->insertSeparator(sep); +} + +void LibavIteraction::fillCodecs(const QString & fmt, QComboBox * pVCodecs, QComboBox * pACodecs) +{ + Format & format = formats[fmt]; + + // first insert recomended codecs + foreach(Codec * codec, format.codecs) + { + if (codec->isRecomended) + { + if (codec->isAudio) + pACodecs->addItem(codec->longName, codec->shortName); + else + pVCodecs->addItem(codec->longName, codec->shortName); + } + } + + // remember where to place separators between recomended and other codecs + int vsep = pVCodecs->count(); + int asep = pACodecs->count(); + + // insert remaining codecs + foreach(Codec * codec, format.codecs) + { + if (!codec->isRecomended) + { + if (codec->isAudio) + pACodecs->addItem(codec->longName, codec->shortName); + else + pVCodecs->addItem(codec->longName, codec->shortName); + } + } + + // insert separators if necessary + if (vsep != 0 && vsep != pVCodecs->count()) + pVCodecs->insertSeparator(vsep); + if (asep != 0 && asep != pACodecs->count()) + pACodecs->insertSeparator(asep); +} + +QString LibavIteraction::getExtension(const QString & format) +{ + return formats[format].extension; +} + +QString tr(QString a) +{ + return a; +} + +// get information abaout file (duration, resolution etc) in multiline string +QString LibavIteraction::getFileInfo(const QString & filepath) +{ + AVFormatContext* pContext = NULL; + QByteArray utf8path = filepath.toUtf8(); + if (avformat_open_input(&pContext, utf8path.data(), NULL, NULL) < 0) + return ""; +#if LIBAFORMAT_VERSION_MAJOR < 54 + if (av_find_stream_info(pContext) < 0) +#else + if (avformat_find_stream_info(pContext, NULL) < 0) +#endif + return ""; + + int s = float(pContext->duration)/AV_TIME_BASE; + QString desc = QString(tr("Duration: %1m %2s\n")).arg(s/60).arg(s%60); + for (int i = 0; i < (int)pContext->nb_streams; i++) + { + AVStream* pStream = pContext->streams[i]; + if (!pStream) + continue; + AVCodecContext* pCodec = pContext->streams[i]->codec; + if (!pCodec) + continue; + + if (pCodec->codec_type == AVMEDIA_TYPE_VIDEO) + { + desc += QString(tr("Video: %1x%2, ")).arg(pCodec->width).arg(pCodec->height); + if (pStream->avg_frame_rate.den) + { + float fps = float(pStream->avg_frame_rate.num)/pStream->avg_frame_rate.den; + desc += QString(tr("%1 fps, ")).arg(fps, 0, 'f', 2); + } + } + else if (pCodec->codec_type == AVMEDIA_TYPE_AUDIO) + desc += tr("Audio: "); + else + continue; + AVCodec* pDecoder = avcodec_find_decoder(pCodec->codec_id); + desc += pDecoder? pDecoder->name : "unknown"; + desc += "\n"; + } + AVDictionaryEntry* pComment = av_dict_get(pContext->metadata, "comment", NULL, 0); + if (pComment) + desc += QString("\n") + pComment->value; +#if LIBAFORMAT_VERSION_MAJOR < 54 + av_close_input_file(pContext); +#else + avformat_close_input(&pContext); +#endif + return desc; +} + +#else +LibavIteraction::LibavIteraction() +{ + +} + +void LibavIteraction::fillFormats(QComboBox * pFormats) +{ + Q_UNUSED(pFormats); +} + +void LibavIteraction::fillCodecs(const QString & format, QComboBox * pVCodecs, QComboBox * pACodecs) +{ + Q_UNUSED(format); + Q_UNUSED(pVCodecs); + Q_UNUSED(pACodecs); +} + +QString LibavIteraction::getExtension(const QString & format) +{ + Q_UNUSED(format); + + return QString(); +} + +QString LibavIteraction::getFileInfo(const QString & filepath) +{ + Q_UNUSED(filepath); + + return QString(); +} +#endif + +LibavIteraction & LibavIteraction::instance() +{ + static LibavIteraction instance; + return instance; +} diff -r 7ee319134713 -r bc7b1d228a2c QTfrontend/util/libav_iteraction.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/QTfrontend/util/libav_iteraction.h Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,49 @@ +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef LIBAV_ITERACTION +#define LIBAV_ITERACTION + +#include + +/** + * @brief Class for interacting with ffmpeg/libav libraries + * + * @see singleton pattern + */ +class LibavIteraction +{ + LibavIteraction(); + +public: + + static LibavIteraction & instance(); + + // fill combo box with known file formats + void fillFormats(QComboBox * pFormats); + + // fill combo boxes with known codecs for given formats + void fillCodecs(const QString & format, QComboBox * pVCodecs, QComboBox * pACodecs); + + QString getExtension(const QString & format); + + // get information about file (duration, resolution etc) in multiline string + QString getFileInfo(const QString & filepath); +}; + +#endif // LIBAV_ITERACTION diff -r 7ee319134713 -r bc7b1d228a2c cmake_modules/FindFFMPEG.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmake_modules/FindFFMPEG.cmake Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,79 @@ +# - Try to find ffmpeg libraries (libavcodec, libavformat and libavutil) +# Once done this will define +# +# FFMPEG_FOUND - system has ffmpeg or libav +# FFMPEG_INCLUDE_DIR - the ffmpeg include directory +# FFMPEG_LIBRARIES - Link these to use ffmpeg +# FFMPEG_LIBAVCODEC +# FFMPEG_LIBAVFORMAT +# FFMPEG_LIBAVUTIL +# +# Copyright (c) 2008 Andreas Schneider +# Modified for other libraries by Lasse Kärkkäinen +# Modified for Hedgewars by Stepik777 +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# + +if (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) + # in cache already + set(FFMPEG_FOUND TRUE) +else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(_FFMPEG_AVCODEC libavcodec) + pkg_check_modules(_FFMPEG_AVFORMAT libavformat) + pkg_check_modules(_FFMPEG_AVUTIL libavutil) + endif (PKG_CONFIG_FOUND) + + find_path(FFMPEG_AVCODEC_INCLUDE_DIR + NAMES libavcodec/avcodec.h + PATHS ${_FFMPEG_AVCODEC_INCLUDE_DIRS} /usr/include /usr/local/include /opt/local/include /sw/include + PATH_SUFFIXES ffmpeg libav + ) + + find_library(FFMPEG_LIBAVCODEC + NAMES avcodec + PATHS ${_FFMPEG_AVCODEC_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib + ) + + find_library(FFMPEG_LIBAVFORMAT + NAMES avformat + PATHS ${_FFMPEG_AVFORMAT_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib + ) + + find_library(FFMPEG_LIBAVUTIL + NAMES avutil + PATHS ${_FFMPEG_AVUTIL_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib + ) + + if (FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVFORMAT) + set(FFMPEG_FOUND TRUE) + endif() + + if (FFMPEG_FOUND) + set(FFMPEG_INCLUDE_DIR ${FFMPEG_AVCODEC_INCLUDE_DIR}) + + set(FFMPEG_LIBRARIES + ${FFMPEG_LIBAVCODEC} + ${FFMPEG_LIBAVFORMAT} + ${FFMPEG_LIBAVUTIL} + ) + + endif (FFMPEG_FOUND) + + if (FFMPEG_FOUND) + if (NOT FFMPEG_FIND_QUIETLY) + message(STATUS "Found FFMPEG or Libav: ${FFMPEG_LIBRARIES}, ${FFMPEG_INCLUDE_DIR}") + endif (NOT FFMPEG_FIND_QUIETLY) + else (FFMPEG_FOUND) + if (FFMPEG_FIND_REQUIRED) + message(FATAL_ERROR "Could not find libavcodec or libavformat or libavutil") + endif (FFMPEG_FIND_REQUIRED) + endif (FFMPEG_FOUND) + +endif (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) + diff -r 7ee319134713 -r bc7b1d228a2c gameServer/Actions.hs --- a/gameServer/Actions.hs Thu Aug 30 12:47:41 2012 -0400 +++ b/gameServer/Actions.hs Thu Aug 30 13:02:19 2012 -0400 @@ -146,9 +146,14 @@ io $ infoM "Clients" (show ci ++ " quits: " ++ B.unpack msg) - processAction $ AnswerClients [chan] ["BYE", msg] when loggedIn $ processAction $ AnswerClients clientsChans ["LOBBY:LEFT", clNick, msg] + mapM processAction + [ + AnswerClients [chan] ["BYE", msg] + , ModifyClient (\c -> c{logonPassed = False}) -- this will effectively hide client from others while he isn't deleted from list + ] + s <- get put $! s{removedClients = ci `Set.insert` removedClients s} @@ -216,7 +221,7 @@ chans <- othersChans if master then - if gameProgress && playersNum > 1 then + if playersNum > 1 then mapM_ processAction [ChangeMaster, NoticeMessage AdminLeft, RemoveClientTeams ci, AnswerClients chans ["LEFT", clNick, msg]] else processAction RemoveRoom @@ -225,7 +230,7 @@ -- when not removing room ready <- client's isReady - when (not master || (gameProgress && playersNum > 1)) . io $ do + when (not master || playersNum > 1) . io $ do modifyRoom rnc (\r -> r{ playersIn = playersIn r - 1, readyPlayers = if ready then readyPlayers r - 1 else readyPlayers r @@ -418,11 +423,22 @@ processAction JoinLobby = do chan <- client's sendChan clientNick <- client's nick - (lobbyNicks, clientsChans) <- liftM (unzip . Prelude.map (nick &&& sendChan) . Prelude.filter logonPassed) $! allClientsS - mapM_ processAction $ - AnswerClients clientsChans ["LOBBY:JOINED", clientNick] - : AnswerClients [chan] ("LOBBY:JOINED" : clientNick : lobbyNicks) - : [ModifyClient (\cl -> cl{logonPassed = True}), SendServerMessage] + isAuthenticated <- liftM (not . B.null) $ client's webPassword + isAdmin <- client's isAdministrator + loggedInClients <- liftM (Prelude.filter logonPassed) $! allClientsS + let (lobbyNicks, clientsChans) = unzip . L.map (nick &&& sendChan) $ loggedInClients + let authenticatedNicks = L.map nick . L.filter (not . B.null . webPassword) $ loggedInClients + let adminsNicks = L.map nick . L.filter isAdministrator $ loggedInClients + let clFlags = B.concat . L.concat $ [["u" | isAuthenticated], ["a" | isAdmin]] + mapM_ processAction . concat $ [ + [AnswerClients clientsChans ["LOBBY:JOINED", clientNick]] + , [AnswerClients [chan] ("LOBBY:JOINED" : clientNick : lobbyNicks)] + , [AnswerClients [chan] ("CLIENT_FLAGS" : "+u" : authenticatedNicks) | not $ null authenticatedNicks] + , [AnswerClients [chan] ("CLIENT_FLAGS" : "+a" : adminsNicks) | not $ null adminsNicks] + , [AnswerClients (chan : clientsChans) ["CLIENT_FLAGS", B.concat["+" , clFlags], clientNick] | not $ B.null clFlags] + , [ModifyClient (\cl -> cl{logonPassed = True})] + , [SendServerMessage] + ] processAction (KickClient kickId) = do @@ -453,9 +469,9 @@ processAction BanList = do ch <- client's sendChan - bans <- gets (bans . serverInfo) + bans <- gets (B.pack . unlines . map show . bans . serverInfo) processAction $ - AnswerClients [ch] ["BANLIST", B.pack $ show bans] + AnswerClients [ch] ["BANLIST", bans] @@ -522,9 +538,11 @@ where kickTimeouted rnc ci = do pq <- io $ client'sM rnc pingsQueue ci - when (pq > 0) $ + when (pq > 0) $ do withStateT (\as -> as{clientIndex = Just ci}) $ processAction (ByeClient "Ping timeout") +-- when (pq > 1) $ +-- processAction $ DeleteClient ci -- smth went wrong with client io threads, issue DeleteClient here processAction StatsAction = do diff -r 7ee319134713 -r bc7b1d228a2c gameServer/CoreTypes.hs --- a/gameServer/CoreTypes.hs Thu Aug 30 12:47:41 2012 -0400 +++ b/gameServer/CoreTypes.hs Thu Aug 30 13:02:19 2012 -0400 @@ -94,6 +94,7 @@ readyPlayers :: !Int, isRestrictedJoins :: Bool, isRestrictedTeams :: Bool, + roomBansList :: [B.ByteString], mapParams :: Map.Map B.ByteString B.ByteString, params :: Map.Map B.ByteString [B.ByteString] } @@ -111,6 +112,7 @@ 0 False False + [] ( Map.fromList $ Prelude.zipWith (,) ["MAP", "MAPGEN", "MAZE_SIZE", "SEED", "TEMPLATE"] diff -r 7ee319134713 -r bc7b1d228a2c gameServer/HWProtoInRoomState.hs --- a/gameServer/HWProtoInRoomState.hs Thu Aug 30 12:47:41 2012 -0400 +++ b/gameServer/HWProtoInRoomState.hs Thu Aug 30 13:02:19 2012 -0400 @@ -278,6 +278,14 @@ where engineMsg cl = toEngineMsg $ B.concat ["b", nick cl, "(team): ", msg, "\x20\x20"] +handleCmd_inRoom ["BAN", banNick] = do + (_, rnc) <- ask + maybeClientId <- clientByNick banNick + let banId = fromJust maybeClientId + master <- liftM isMaster thisClient + return [ModifyRoom (\r -> r{roomBansList = (host $ rnc `client` banId) : roomBansList r}) | master && isJust maybeClientId] + + handleCmd_inRoom ["LIST"] = return [] -- for old clients (<= 0.9.17) handleCmd_inRoom (s:_) = return [ProtocolError $ "Incorrect command '" `B.append` s `B.append` "' (state: in room)"] diff -r 7ee319134713 -r bc7b1d228a2c gameServer/HWProtoLobbyState.hs --- a/gameServer/HWProtoLobbyState.hs Thu Aug 30 12:47:41 2012 -0400 +++ b/gameServer/HWProtoLobbyState.hs Thu Aug 30 13:02:19 2012 -0400 @@ -70,11 +70,14 @@ let jRoomClients = map (client irnc) $ roomClients irnc jRI let nicks = map nick jRoomClients let chans = map sendChan (cl : jRoomClients) + let isBanned = host cl `elem` roomBansList jRoom return $ if isNothing maybeRI || not sameProto then [Warning "No such room"] else if isRestrictedJoins jRoom then [Warning "Joining restricted"] + else if isBanned then + [Warning "You are banned in this room"] else if roomPassword /= password jRoom then [NoticeMessage WrongPassword] else @@ -183,7 +186,7 @@ handleCmd_lobby ["RESTART_SERVER"] = do cl <- thisClient - return [RestartServer] + return [RestartServer | isAdministrator cl] handleCmd_lobby _ = return [ProtocolError "Incorrect command (state: in lobby)"] diff -r 7ee319134713 -r bc7b1d228a2c gameServer/ServerCore.hs --- a/gameServer/ServerCore.hs Thu Aug 30 12:47:41 2012 -0400 +++ b/gameServer/ServerCore.hs Thu Aug 30 13:02:19 2012 -0400 @@ -43,7 +43,7 @@ ClientMessage (ci, cmd) -> do liftIO $ debugM "Clients" $ show ci ++ ": " ++ show cmd - + removed <- gets removedClients unless (ci `Set.member` removed) $ do modify (\s -> s{clientIndex = Just ci}) @@ -75,8 +75,6 @@ (fromJust $ serverSocket si) (coreChan si) - return () - _ <- forkIO $ timerLoop 0 $ coreChan si startDBConnection si diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/ArgParsers.inc --- a/hedgewars/ArgParsers.inc Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/ArgParsers.inc Thu Aug 30 13:02:19 2012 -0400 @@ -63,6 +63,21 @@ cLocaleFName:= ParamStr(17); end; +{$IFDEF USE_VIDEO_RECORDING} +procedure internalStartVideoRecordingWithParameters(); +begin + internalStartGameWithParameters(); + GameType:= gmtRecord; + cVideoFramerateNum:= StrToInt(ParamStr(18)); + cVideoFramerateDen:= StrToInt(ParamStr(19)); + RecPrefix:= ParamStr(20); + cAVFormat:= ParamStr(21); + cVideoCodec:= ParamStr(22); + cVideoQuality:= StrToInt(ParamStr(23)); + cAudioCodec:= ParamStr(24); +end; +{$ENDIF} + procedure setVideo(screenWidth: LongInt; screenHeight: LongInt; bitsStr: LongInt); begin cScreenWidth:= screenWidth; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/CMakeLists.txt --- a/hedgewars/CMakeLists.txt Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/CMakeLists.txt Thu Aug 30 13:02:19 2012 -0400 @@ -3,6 +3,7 @@ find_package(SDL_net) find_package(SDL_ttf) find_package(SDL_mixer) +find_package(FFMPEG) include(${CMAKE_MODULE_PATH}/FindSDL_Extras.cmake) @@ -33,7 +34,9 @@ uGame.pas uGears.pas uGearsHandlers.pas + uGearsHandlersRope.pas uGearsRender.pas + uGearsUtils.pas uIO.pas uInputHandler.pas uLand.pas @@ -60,6 +63,7 @@ uTypes.pas uUtils.pas uVariables.pas + uVideoRec.pas uVisualGears.pas uWorld.pas GSHandlers.inc @@ -183,9 +187,31 @@ message(STATUS "PNG screenshots disabled per user request, using BMP format") endif() +if(NOT NO_VIDEOREC) + if(${FFMPEG_FOUND}) + message(STATUS "Compiling with video recording") + include_directories(${FFMPEG_INCLUDE_DIR}) + set(pascal_flags "-dUSE_VIDEO_RECORDING" ${pascal_flags}) + set(LIBRARY_OUTPUT_PATH ${EXECUTABLE_OUTPUT_PATH}) + IF (WIN32) + # there are some problems with linking our avwrapper as static lib, so link it as shared + add_library(avwrapper SHARED avwrapper.c) + target_link_libraries(avwrapper ${FFMPEG_LIBRARIES}) + install(PROGRAMS "${EXECUTABLE_OUTPUT_PATH}/${CMAKE_SHARED_LIBRARY_PREFIX}avwrapper${CMAKE_SHARED_LIBRARY_SUFFIX}" DESTINATION ${target_dir}) + ELSE() + add_library(avwrapper STATIC avwrapper.c) + set(pascal_flags "-k${FFMPEG_LIBAVCODEC}" "-k${FFMPEG_LIBAVFORMAT}" "-k${FFMPEG_LIBAVUTIL}" ${pascal_flags}) + # set(pascal_flags "-k${LIBRARY_OUTPUT_PATH}/${CMAKE_STATIC_LIBRARY_PREFIX}avwrapper${CMAKE_STATIC_LIBRARY_SUFFIX}" ${pascal_flags}) + ENDIF() + else() + message(STATUS "FFMPEG library not found, video recording will be disabled") + endif() +else() + message(STATUS "Video recording disabled by user") +endif() + set(fpc_flags ${noexecstack_flags} ${pascal_flags} ${hwengine_project}) - IF(NOT APPLE) #here is the command for standard executables or for shared library add_custom_command(OUTPUT "${EXECUTABLE_OUTPUT_PATH}/${engine_output_name}${CMAKE_EXECUTABLE_SUFFIX}" @@ -225,10 +251,9 @@ #this command is a workaround to some inlining issues present in older # FreePascal versions and fixed in 2.6, That version is mandatory on OSX, # hence the command is not needed there -if(NOT APPLE) - add_custom_target(ENGINECLEAN COMMAND ${CMAKE_BUILD_TOOL} "clean" "${PROJECT_BINARY_DIR}" "${hedgewars_SOURCE_DIR}/hedgewars") - add_dependencies(${engine_output_name} ENGINECLEAN) -endif() +# if(NOT APPLE) +# add_custom_target(ENGINECLEAN COMMAND ${CMAKE_BUILD_TOOL} "clean" "${PROJECT_BINARY_DIR}" "${hedgewars_SOURCE_DIR}/hedgewars") +# add_dependencies(${engine_output_name} ENGINECLEAN) +# endif() install(PROGRAMS "${EXECUTABLE_OUTPUT_PATH}/${engine_output_name}${CMAKE_EXECUTABLE_SUFFIX}" DESTINATION ${target_dir}) - diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/GSHandlers.inc --- a/hedgewars/GSHandlers.inc Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/GSHandlers.inc Thu Aug 30 13:02:19 2012 -0400 @@ -140,28 +140,6 @@ ScriptCall('onHogRestore', HH^.Gear^.Uid) end; -//////////////////////////////////////////////////////////////////////////////// -procedure CheckCollision(Gear: PGear); inline; -begin - if TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) - or (TestCollisionYwithGear(Gear, hwSign(Gear^.dY)) <> 0) then - Gear^.State := Gear^.State or gstCollision - else - Gear^.State := Gear^.State and (not gstCollision) -end; - -procedure CheckCollisionWithLand(Gear: PGear); inline; -begin - if TestCollisionX(Gear, hwSign(Gear^.dX)) - or TestCollisionY(Gear, hwSign(Gear^.dY)) then - Gear^.State := Gear^.State or gstCollision - else - Gear^.State := Gear^.State and (not gstCollision) -end; - - -//////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// procedure doStepDrowningGear(Gear: PGear); @@ -191,11 +169,24 @@ collV, collH: LongInt; land: word; begin - // clip velocity at 1.9 - over 1 per pixel, but really shouldn't cause many actual problems. - if Gear^.dX.QWordValue > 8160437862 then - Gear^.dX.QWordValue:= 8160437862; - if Gear^.dY.QWordValue > 8160437862 then - Gear^.dY.QWordValue:= 8160437862; + // clip velocity at 2 - over 1 per pixel, but really shouldn't cause many actual problems. +{$IFNDEF WEB} + if Gear^.dX.Round > 2 then + Gear^.dX.QWordValue:= 8589934592; + if Gear^.dY.Round > 2 then + Gear^.dY.QWordValue:= 8589934592; +{$ELSE} + if Gear^.dX.Round > 2 then + begin + Gear^.dX.Round:= 2; + Gear^.dX.Frac:= 0 + end; + if Gear^.dY.QWordValue > 2 then + begin + Gear^.dY.Round:= 2; + Gear^.dY.Frac:= 0 + end; +{$ENDIF} Gear^.State := Gear^.State and (not gstCollision); collV := 0; collH := 0; @@ -541,7 +532,7 @@ kick:= hwRound((hwAbs(Gear^.dX)+hwAbs(Gear^.dY)) * _20); Gear^.dY.isNegative:= not Gear^.dY.isNegative; Gear^.dX.isNegative:= not Gear^.dX.isNegative; - AmmoShove(Gear, 1, kick); + AmmoShove(Gear, 0, kick); for i:= 15 + kick div 10 downto 0 do begin particle := AddVisualGear(hwRound(Gear^.X) + Random(25), hwRound(Gear^.Y) + Random(25), vgtDust); @@ -610,11 +601,10 @@ // move back to cloud layer if yy > cWaterLine then move:= true - else if ((yy and LAND_HEIGHT_MASK) <> 0) - or (xx > LAND_WIDTH + 512) or (xx < -512) then + else if (xx > snowRight) or (xx < snowLeft) then move:=true // Solid pixel encountered - else if ((xx and LAND_WIDTH_MASK) = 0) and (Land[yy, xx] <> 0) then + else if ((yy and LAND_HEIGHT_MASK) = 0) and ((xx and LAND_WIDTH_MASK) = 0) and (Land[yy, xx] <> 0) then begin lf:= Land[yy, xx] and (lfObject or lfBasic or lfIndestructible); // If there's room below keep falling @@ -724,8 +714,8 @@ exit end; Gear^.Pos:= 0; - Gear^.X:= int2hwFloat(GetRandom(LAND_WIDTH+1024)-512); - Gear^.Y:= int2hwFloat(750+(GetRandom(50)-25)); + Gear^.X:= int2hwFloat(GetRandom(snowRight-snowLeft)+snowLeft); + Gear^.Y:= int2hwFloat(LAND_HEIGHT-1300+(GetRandom(50)-25)); Gear^.State:= Gear^.State or gstInvisible; end end; @@ -924,7 +914,13 @@ Gear^.State := Gear^.State or gstAnimation end; exit - end + end else + if(Gear^.Hedgehog^.Gear = nil) or ((Gear^.Hedgehog^.Gear^.State and gstMoving) <> 0) then + begin + DeleteGear(Gear); + AfterAttack; + exit + end else inc(Gear^.Timer); @@ -1401,436 +1397,6 @@ end; //////////////////////////////////////////////////////////////////////////////// - -procedure doStepRope(Gear: PGear); -forward; - -procedure doStepRopeAfterAttack(Gear: PGear); -var - HHGear: PGear; -begin - HHGear := Gear^.Hedgehog^.Gear; - if ((HHGear^.State and gstHHDriven) = 0) - or (CheckGearDrowning(HHGear)) - or (TestCollisionYwithGear(HHGear, 1) <> 0) then - begin - DeleteGear(Gear); - isCursorVisible := false; - ApplyAmmoChanges(HHGear^.Hedgehog^); - exit - end; - - HedgehogChAngle(HHGear); - - if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then - SetLittle(HHGear^.dX); - - if HHGear^.dY.isNegative and (TestCollisionYwithGear(HHGear, -1) <> 0) then - HHGear^.dY := _0; - HHGear^.X := HHGear^.X + HHGear^.dX; - HHGear^.Y := HHGear^.Y + HHGear^.dY; - HHGear^.dY := HHGear^.dY + cGravity; - - if (GameFlags and gfMoreWind) <> 0 then - HHGear^.dX := HHGear^.dX + cWindSpeed / HHGear^.Density; - - if (Gear^.Message and gmAttack) <> 0 then - begin - Gear^.X := HHGear^.X; - Gear^.Y := HHGear^.Y; - - ApplyAngleBounds(Gear^.Hedgehog^, amRope); - - Gear^.dX := SignAs(AngleSin(HHGear^.Angle), HHGear^.dX); - Gear^.dY := -AngleCos(HHGear^.Angle); - Gear^.Friction := _4_5 * cRopePercent; - Gear^.Elasticity := _0; - Gear^.State := Gear^.State and (not gsttmpflag); - Gear^.doStep := @doStepRope; - end -end; - -procedure RopeDeleteMe(Gear, HHGear: PGear); -begin - with HHGear^ do - begin - Message := Message and (not gmAttack); - State := (State or gstMoving) and (not gstWinner); - end; - DeleteGear(Gear) -end; - -procedure RopeWaitCollision(Gear, HHGear: PGear); -begin - with HHGear^ do - begin - Message := Message and (not gmAttack); - State := State or gstMoving; - end; - RopePoints.Count := 0; - Gear^.Elasticity := _0; - Gear^.doStep := @doStepRopeAfterAttack -end; - -procedure doStepRopeWork(Gear: PGear); -var - HHGear: PGear; - len, tx, ty, nx, ny, ropeDx, ropeDy, mdX, mdY: hwFloat; - lx, ly, cd: LongInt; - haveCollision, - haveDivided: boolean; - -begin - HHGear := Gear^.Hedgehog^.Gear; - - if ((HHGear^.State and gstHHDriven) = 0) - or (CheckGearDrowning(HHGear)) or (Gear^.PortalCounter <> 0) then - begin - PlaySound(sndRopeRelease); - RopeDeleteMe(Gear, HHGear); - exit - end; - - if (Gear^.Message and gmLeft <> 0) and (not TestCollisionXwithGear(HHGear, -1)) then - HHGear^.dX := HHGear^.dX - _0_0002; - - if (Gear^.Message and gmRight <> 0) and (not TestCollisionXwithGear(HHGear, 1)) then - HHGear^.dX := HHGear^.dX + _0_0002; - - // vector between hedgehog and rope attaching point - ropeDx := HHGear^.X - Gear^.X; - ropeDy := HHGear^.Y - Gear^.Y; - - if TestCollisionYwithGear(HHGear, 1) = 0 then - begin - - // depending on the rope vector we know which X-side to check for collision - // in order to find out if the hog can still be moved by gravity - if ropeDx.isNegative = RopeDy.IsNegative then - cd:= -1 - else - cd:= 1; - - // apply gravity if there is no obstacle - if not TestCollisionXwithGear(HHGear, cd) then - HHGear^.dY := HHGear^.dY + cGravity; - - if (GameFlags and gfMoreWind) <> 0 then - // apply wind if there's no obstacle - if not TestCollisionXwithGear(HHGear, hwSign(cWindSpeed)) then - HHGear^.dX := HHGear^.dX + cWindSpeed / HHGear^.Density; - end; - - mdX := ropeDx + HHGear^.dX; - mdY := ropeDy + HHGear^.dY; - len := _1 / Distance(mdX, mdY); - // rope vector plus hedgehog direction vector normalized - mdX := mdX * len; - mdY := mdY * len; - - // for visual purposes only - Gear^.dX := mdX; - Gear^.dY := mdY; - - ///// - tx := HHGear^.X; - ty := HHGear^.Y; - - if ((Gear^.Message and gmDown) <> 0) and (Gear^.Elasticity < Gear^.Friction) then - if not (TestCollisionXwithGear(HHGear, hwSign(ropeDx)) - or (TestCollisionYwithGear(HHGear, hwSign(ropeDy)) <> 0)) then - Gear^.Elasticity := Gear^.Elasticity + _0_3; - - if ((Gear^.Message and gmUp) <> 0) and (Gear^.Elasticity > _30) then - if not (TestCollisionXwithGear(HHGear, -hwSign(ropeDx)) - or (TestCollisionYwithGear(HHGear, -hwSign(ropeDy)) <> 0)) then - Gear^.Elasticity := Gear^.Elasticity - _0_3; - - HHGear^.X := Gear^.X + mdX * Gear^.Elasticity; - HHGear^.Y := Gear^.Y + mdY * Gear^.Elasticity; - - HHGear^.dX := HHGear^.X - tx; - HHGear^.dY := HHGear^.Y - ty; - //// - - - haveDivided := false; - // check whether rope needs dividing - - len := Gear^.Elasticity - _5; - nx := Gear^.X + mdX * len; - ny := Gear^.Y + mdY * len; - tx := mdX * _0_3; // should be the same as increase step - ty := mdY * _0_3; - - while len > _3 do - begin - lx := hwRound(nx); - ly := hwRound(ny); - if ((ly and LAND_HEIGHT_MASK) = 0) and ((lx and LAND_WIDTH_MASK) = 0) and ((Land[ly, lx] and $FF00) <> 0) then - begin - ny := _1 / Distance(ropeDx, ropeDy); - // old rope pos - nx := ropeDx * ny; - ny := ropeDy * ny; - - with RopePoints.ar[RopePoints.Count] do - begin - X := Gear^.X; - Y := Gear^.Y; - if RopePoints.Count = 0 then - RopePoints.HookAngle := DxDy2Angle(Gear^.dY, Gear^.dX); - b := (nx * HHGear^.dY) > (ny * HHGear^.dX); - dLen := len - end; - - with RopePoints.rounded[RopePoints.Count] do - begin - X := hwRound(Gear^.X); - Y := hwRound(Gear^.Y); - end; - - Gear^.X := Gear^.X + nx * len; - Gear^.Y := Gear^.Y + ny * len; - inc(RopePoints.Count); - TryDo(RopePoints.Count <= MAXROPEPOINTS, 'Rope points overflow', true); - Gear^.Elasticity := Gear^.Elasticity - len; - Gear^.Friction := Gear^.Friction - len; - haveDivided := true; - break - end; - nx := nx - tx; - ny := ny - ty; - - // len := len - _0_3 // should be the same as increase step - len.QWordValue := len.QWordValue - _0_3.QWordValue; - end; - - if not haveDivided then - if RopePoints.Count > 0 then // check whether the last dividing point could be removed - begin - tx := RopePoints.ar[Pred(RopePoints.Count)].X; - ty := RopePoints.ar[Pred(RopePoints.Count)].Y; - mdX := tx - Gear^.X; - mdY := ty - Gear^.Y; - if RopePoints.ar[Pred(RopePoints.Count)].b xor (mdX * (ty - HHGear^.Y) > (tx - HHGear^.X) * mdY) then - begin - dec(RopePoints.Count); - Gear^.X := RopePoints.ar[RopePoints.Count].X; - Gear^.Y := RopePoints.ar[RopePoints.Count].Y; - Gear^.Elasticity := Gear^.Elasticity + RopePoints.ar[RopePoints.Count].dLen; - Gear^.Friction := Gear^.Friction + RopePoints.ar[RopePoints.Count].dLen; - - // restore hog position - len := _1 / Distance(mdX, mdY); - mdX := mdX * len; - mdY := mdY * len; - - HHGear^.X := Gear^.X - mdX * Gear^.Elasticity; - HHGear^.Y := Gear^.Y - mdY * Gear^.Elasticity; - end - end; - - haveCollision := false; - if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then - begin - HHGear^.dX := -_0_6 * HHGear^.dX; - haveCollision := true - end; - if TestCollisionYwithGear(HHGear, hwSign(HHGear^.dY)) <> 0 then - begin - HHGear^.dY := -_0_6 * HHGear^.dY; - haveCollision := true - end; - - if haveCollision and (Gear^.Message and (gmLeft or gmRight) <> 0) and (Gear^.Message and (gmUp or gmDown) <> 0) then - begin - HHGear^.dX := SignAs(hwAbs(HHGear^.dX) + _0_2, HHGear^.dX); - HHGear^.dY := SignAs(hwAbs(HHGear^.dY) + _0_2, HHGear^.dY) - end; - - len := hwSqr(HHGear^.dX) + hwSqr(HHGear^.dY); - if len > _0_64 then - begin - len := _0_8 / hwSqrt(len); - HHGear^.dX := HHGear^.dX * len; - HHGear^.dY := HHGear^.dY * len; - end; - - haveCollision:= ((hwRound(Gear^.Y) and LAND_HEIGHT_MASK) = 0) and ((hwRound(Gear^.X) and LAND_WIDTH_MASK) = 0) and ((Land[hwRound(Gear^.Y), hwRound(Gear^.X)]) <> 0); - - if not haveCollision then - begin - // backup gear location - tx:= Gear^.X; - ty:= Gear^.Y; - - if RopePoints.Count > 0 then - begin - // set gear location to the remote end of the rope, the attachment point - Gear^.X:= RopePoints.ar[0].X; - Gear^.Y:= RopePoints.ar[0].Y; - end; - - CheckCollision(Gear); - // if we haven't found any collision yet then check the other side too - if (Gear^.State and gstCollision) = 0 then - begin - Gear^.dX.isNegative:= not Gear^.dX.isNegative; - Gear^.dY.isNegative:= not Gear^.dY.isNegative; - CheckCollision(Gear); - Gear^.dX.isNegative:= not Gear^.dX.isNegative; - Gear^.dY.isNegative:= not Gear^.dY.isNegative; - end; - - haveCollision:= (Gear^.State and gstCollision) <> 0; - - // restore gear location - Gear^.X:= tx; - Gear^.Y:= ty; - end; - - // if the attack key is pressed, lose rope contact as well - if (Gear^.Message and gmAttack) <> 0 then - haveCollision:= false; - - if not haveCollision then - begin - if (Gear^.State and gsttmpFlag) <> 0 then - begin - PlaySound(sndRopeRelease); - if Gear^.Hedgehog^.CurAmmoType <> amParachute then - RopeWaitCollision(Gear, HHGear) - else - RopeDeleteMe(Gear, HHGear) - end - end - else - if (Gear^.State and gsttmpFlag) = 0 then - Gear^.State := Gear^.State or gsttmpFlag; -end; - -procedure RopeRemoveFromAmmo(Gear, HHGear: PGear); -begin - if (Gear^.State and gstAttacked) = 0 then - begin - OnUsedAmmo(HHGear^.Hedgehog^); - Gear^.State := Gear^.State or gstAttacked - end; - ApplyAmmoChanges(HHGear^.Hedgehog^) -end; - -procedure doStepRopeAttach(Gear: PGear); -var - HHGear: PGear; - tx, ty, tt: hwFloat; -begin - Gear^.X := Gear^.X - Gear^.dX; - Gear^.Y := Gear^.Y - Gear^.dY; - Gear^.Elasticity := Gear^.Elasticity + _1; - - HHGear := Gear^.Hedgehog^.Gear; - DeleteCI(HHGear); - - if (HHGear^.State and gstMoving) <> 0 then - begin - if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then - SetLittle(HHGear^.dX); - if HHGear^.dY.isNegative and (TestCollisionYwithGear(HHGear, -1) <> 0) then - HHGear^.dY := _0; - - HHGear^.X := HHGear^.X + HHGear^.dX; - Gear^.X := Gear^.X + HHGear^.dX; - - if TestCollisionYwithGear(HHGear, 1) <> 0 then - begin - CheckHHDamage(HHGear); - HHGear^.dY := _0 - //HHGear^.State:= HHGear^.State and (not (gstHHJumping or gstHHHJump)); - end - else - begin - HHGear^.Y := HHGear^.Y + HHGear^.dY; - Gear^.Y := Gear^.Y + HHGear^.dY; - HHGear^.dY := HHGear^.dY + cGravity; - if (GameFlags and gfMoreWind) <> 0 then - HHGear^.dX := HHGear^.dX + cWindSpeed / HHGear^.Density - end; - - tt := Gear^.Elasticity; - tx := _0; - ty := _0; - while tt > _20 do - begin - if ((hwRound(Gear^.Y+ty) and LAND_HEIGHT_MASK) = 0) and ((hwRound(Gear^.X+tx) and LAND_WIDTH_MASK) = 0) and ((Land[hwRound(Gear^.Y+ty), hwRound(Gear^.X+tx)] and $FF00) <> 0) then - begin - Gear^.X := Gear^.X + tx; - Gear^.Y := Gear^.Y + ty; - Gear^.Elasticity := tt; - Gear^.doStep := @doStepRopeWork; - PlaySound(sndRopeAttach); - with HHGear^ do - begin - State := State and (not (gstAttacking or gstHHJumping or gstHHHJump)); - Message := Message and (not gmAttack) - end; - - RopeRemoveFromAmmo(Gear, HHGear); - - tt := _0; - exit - end; - tx := tx + Gear^.dX + Gear^.dX; - ty := ty + Gear^.dY + Gear^.dY; - tt := tt - _2; - end; - end; - - CheckCollision(Gear); - - if (Gear^.State and gstCollision) <> 0 then - if Gear^.Elasticity < _10 then - Gear^.Elasticity := _10000 - else - begin - Gear^.doStep := @doStepRopeWork; - PlaySound(sndRopeAttach); - with HHGear^ do - begin - State := State and (not (gstAttacking or gstHHJumping or gstHHHJump)); - Message := Message and (not gmAttack) - end; - - RopeRemoveFromAmmo(Gear, HHGear); - - exit - end; - - if (Gear^.Elasticity > Gear^.Friction) - or ((Gear^.Message and gmAttack) = 0) - or ((HHGear^.State and gstHHDriven) = 0) - or (HHGear^.Damage > 0) then - begin - with Gear^.Hedgehog^.Gear^ do - begin - State := State and (not gstAttacking); - Message := Message and (not gmAttack) - end; - DeleteGear(Gear); - exit; - end; - if CheckGearDrowning(HHGear) then DeleteGear(Gear) -end; - -procedure doStepRope(Gear: PGear); -begin - Gear^.dX := - Gear^.dX; - Gear^.dY := - Gear^.dY; - Gear^.doStep := @doStepRopeAttach; - PlaySound(sndRopeShot) -end; - -//////////////////////////////////////////////////////////////////////////////// procedure doStepMine(Gear: PGear); var vg: PVisualGear; begin @@ -1930,7 +1496,7 @@ or TestCollisionXwithGear(Gear, -2) or (TestCollisionYwithGear(Gear, 2) <> 0) then begin - if (hwAbs(Gear^.dX) > _0) or (hwAbs(Gear^.dY) > _0) then + if (not isZero(Gear^.dX)) or (not isZero(Gear^.dY)) then begin PlaySound(sndRopeAttach); Gear^.dX:= _0; @@ -2406,7 +1972,7 @@ //DrawExplosion(gX, gY, 4); if ((GameTicks and $7) = 0) and (Random(2) = 0) then - for i:= 1 to Random(2)+1 do + for i:= Random(2) downto 0 do AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke); if Gear^.Health > 0 then @@ -2420,7 +1986,7 @@ begin DrawExplosion(gX, gY, 4); - for i:= 0 to Random(3) do + for i:= Random(3) downto 0 do AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke); end; @@ -2438,20 +2004,12 @@ if not sticky then begin if ((GameTicks and $3) = 0) and (Random(1) = 0) then - begin - for i:= 1 to Random(2)+1 do - begin + for i:= Random(2) downto 0 do AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke); - end; - end; end else - begin - for i:= 0 to Random(3) do - begin + for i:= Random(3) downto 0 do AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke); - end; - end; DeleteGear(Gear) end; @@ -3007,7 +2565,6 @@ //////////////////////////////////////////////////////////////////////////////// const cakeh = 27; - cakeDmg = 75; var CakePoints: array[0..Pred(cakeh)] of record x, y: hwFloat; @@ -3091,6 +2648,19 @@ if Gear^.Tag < 7 then exit; + dec(Gear^.Health); + Gear^.Timer := Gear^.Health*10; + if Gear^.Health mod 100 = 0 then + Gear^.PortalCounter:= 0; + // This is not seconds, but at least it is *some* feedback + if (Gear^.Health = 0) or ((Gear^.Message and gmAttack) <> 0) then + begin + FollowGear := Gear; + Gear^.RenderTimer := false; + Gear^.doStep := @doStepCakeDown; + exit + end; + cakeStep(Gear); if Gear^.Tag = 0 then @@ -3102,18 +2672,6 @@ CakePoints[CakeI].y := Gear^.Y; Gear^.DirAngle := DxDy2Angle(tdx, tdy); end; - - dec(Gear^.Health); - Gear^.Timer := Gear^.Health*10; - if Gear^.Health mod 100 = 0 then - Gear^.PortalCounter:= 0; - // This is not seconds, but at least it is *some* feedback - if (Gear^.Health = 0) or ((Gear^.Message and gmAttack) <> 0) then - begin - FollowGear := Gear; - Gear^.RenderTimer := false; - Gear^.doStep := @doStepCakeDown - end end; procedure doStepCakeUp(Gear: PGear); @@ -3693,10 +3251,6 @@ Gear^.X := HHGear^.X; Gear^.Y := HHGear^.Y; - // For some reason I need to reapply followgear here, something else grabs it otherwise. - // This is probably not needed anymore - if not CurrentTeam^.ExtDriven then - FollowGear := HHGear; if not isUnderWater and hasBorder and ((HHGear^.X < _0) or (hwRound(HHGear^.X) > LAND_WIDTH)) then @@ -4048,7 +3602,7 @@ break; // don't port portals or other gear that wouldn't make sense - if (iterator^.Kind in [gtPortal, gtRope]) + if (iterator^.Kind in [gtPortal, gtRope, gtAirAttack]) or (iterator^.PortalCounter > 32) then continue; @@ -4163,7 +3717,7 @@ // calc gear offset in portal normal vector direction noffs:= (nx * ox + ny * oy); - if isBullet and (hwRound(hwAbs(noffs)) >= Gear^.Radius) then + if isBullet and (noffs.Round >= Gear^.Radius) then continue; // avoid gravity related loops of not really moving gear @@ -4330,23 +3884,6 @@ iterator^.Friction := iterator^.Y; end; - // This jiggles gears, to ensure a portal connection just placed under a gear takes effect. - iterator:= GearsList; - while iterator <> nil do - begin - if (iterator^.Kind <> gtPortal) and ((iterator^.Hedgehog <> CurrentHedgehog) - or ((iterator^.Message and gmAllStoppable) = 0)) then - begin - iterator^.Active:= true; - if iterator^.dY.QWordValue = _0.QWordValue then - iterator^.dY.isNegative:= false; - iterator^.State:= iterator^.State or gstMoving; - DeleteCI(iterator); - //inc(iterator^.dY.QWordValue,10); - end; - iterator:= iterator^.NextGear - end; - if Gear^.Health > 1 then dec(Gear^.Health); end; @@ -4398,7 +3935,7 @@ Gear^.State := Gear^.State and (not gstMoving); if (Land[y, x] and lfBouncy <> 0) - or not CalcSlopeTangent(Gear, x, y, tx, ty, 255) + or (not CalcSlopeTangent(Gear, x, y, tx, ty, 255)) or (DistanceI(tx,ty) < _12) then // reject shots at too irregular terrain begin loadNewPortalBall(Gear, true); @@ -4475,7 +4012,7 @@ iterator := GearsList; while iterator <> nil do - begin + begin if (iterator^.Kind = gtPortal) then if (iterator <> newPortal) and (iterator^.Timer > 0) and (iterator^.Hedgehog = CurrentHedgehog) then begin @@ -4493,7 +4030,27 @@ end; iterator^.PortalCounter:= 0; iterator := iterator^.NextGear - end; + end; + + if newPortal^.LinkedGear <> nil then + begin + // This jiggles gears, to ensure a portal connection just placed under a gear takes effect. + iterator:= GearsList; + while iterator <> nil do + begin + if not (iterator^.Kind in [gtPortal, gtAirAttack]) and ((iterator^.Hedgehog <> CurrentHedgehog) + or ((iterator^.Message and gmAllStoppable) = 0)) then + begin + iterator^.Active:= true; + if iterator^.dY.QWordValue = _0.QWordValue then + iterator^.dY.isNegative:= false; + iterator^.State:= iterator^.State or gstMoving; + DeleteCI(iterator); + //inc(iterator^.dY.QWordValue,10); + end; + iterator:= iterator^.NextGear + end + end end; newPortal^.State := newPortal^.State and (not gstCollision); newPortal^.State := newPortal^.State or gstMoving; @@ -4796,7 +4353,7 @@ Gear^.Timer:= Gear^.Tag end; - if (Gear^.Health = 0) or (HHGear^.Damage <> 0) then + if (Gear^.Health = 0) or ((HHGear^.State and gstHHDriven) = 0) then begin DeleteGear(Gear); AfterAttack @@ -4872,7 +4429,7 @@ Gear^.Timer:= Gear^.Tag end; - if (Gear^.Health = 0) or (HHGear^.Damage <> 0) or ((HHGear^.Message and gmAttack) <> 0) then + if (Gear^.Health = 0) or ((HHGear^.State and gstHHDriven) = 0) or ((HHGear^.Message and gmAttack) <> 0) then begin HHGear^.Message:= HHGear^.Message and (not gmAttack); DeleteGear(Gear); @@ -5597,3 +5154,84 @@ doStepFallingGear(Gear); end; + +procedure doStepCreeper(Gear: PGear); +var hogs: PGearArrayS; + HHGear: PGear; + tdX: hwFloat; + dir: LongInt; +begin +doStepFallingGear(Gear); +if Gear^.Timer > 0 then dec(Gear^.Timer); +// creeper sleep phase +if (Gear^.Hedgehog = nil) and (Gear^.Timer > 0) then exit; + +if Gear^.Hedgehog <> nil then HHGear:= Gear^.Hedgehog^.Gear +else HHGear:= nil; + +// creeper boom phase +if (Gear^.State and gstTmpFlag <> 0) then + begin + if (Gear^.Timer = 0) then + begin + doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 300, CurrentHedgehog, EXPLAutoSound); + DeleteGear(Gear) + end; + // ssssss he essssscaped + if (Gear^.Timer > 250) and ((HHGear = nil) or + (((abs(HHGear^.X.Round-Gear^.X.Round) + abs(HHGear^.Y.Round-Gear^.Y.Round) + 2) > 180) and + (Distance(HHGear^.X-Gear^.X,HHGear^.Y-Gear^.Y) > _180))) then + begin + Gear^.State:= Gear^.State and (not gstTmpFlag); + Gear^.Timer:= 0 + end; + exit + end; + +// Search out a new target, as target seek time has expired, target is dead, target is out of range, or we didn't have a target +if (HHGear = nil) or (Gear^.Timer = 0) or + (((abs(HHGear^.X.Round-Gear^.X.Round) + abs(HHGear^.Y.Round-Gear^.Y.Round) + 2) > Gear^.Angle) and + (Distance(HHGear^.X-Gear^.X,HHGear^.Y-Gear^.Y) > int2hwFloat(Gear^.Angle))) + then + begin + hogs := GearsNear(Gear^.X, Gear^.Y, gtHedgehog, Gear^.Angle); + if hogs.size > 1 then + Gear^.Hedgehog:= hogs.ar^[GetRandom(hogs.size)]^.Hedgehog + else if hogs.size = 1 then Gear^.Hedgehog:= hogs.ar^[0]^.Hedgehog + else Gear^.Hedgehog:= nil; + if Gear^.Hedgehog <> nil then Gear^.Timer:= 5000; + exit + end; + +// we have a target. move the creeper. +if HHGear <> nil then + begin + // GOTCHA + if ((abs(HHGear^.X.Round-Gear^.X.Round) + abs(HHGear^.Y.Round-Gear^.Y.Round) + 2) < 50) and + (Distance(HHGear^.X-Gear^.X,HHGear^.Y-Gear^.Y) < _50) then + begin + // hisssssssssss + Gear^.State:= Gear^.State or gstTmpFlag; + Gear^.Timer:= 1500; + exit + end; + if (Gear^.State and gstMoving <> 0) then + begin + Gear^.dY:= _0; + Gear^.dX:= _0; + end + else if (GameTicks and $FF = 0) then + begin + tdX:= HHGear^.X-Gear^.X; + dir:= hwSign(tdX); + if not TestCollisionX(Gear, dir) then + Gear^.X:= Gear^.X + signAs(_1,tdX); + if TestCollisionXwithXYShift(Gear, signAs(_10,tdX), 0, dir) then + begin + Gear^.dX:= SignAs(_0_15, tdX); + Gear^.dY:= -_0_3; + Gear^.State:= Gear^.State or gstMoving + end + end; + end; +end; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/SDLh.pas --- a/hedgewars/SDLh.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/SDLh.pas Thu Aug 30 13:02:19 2012 -0400 @@ -410,12 +410,10 @@ PSDL_Color = ^TSDL_Color; TSDL_Color = record - case Byte of - 0: ( r: Byte; - g: Byte; - b: Byte; - unused: Byte; ); - 1: ( value: LongWord; ); + r: Byte; + g: Byte; + b: Byte; + unused: Byte; end; @@ -829,6 +827,8 @@ TMixMusic = record end; + TPostMix = procedure(udata: pointer; stream: PByte; len: LongInt); cdecl; + {* SDL_net *} TIPAddress = record host: LongWord; @@ -959,6 +959,9 @@ function SDL_GL_SetAttribute(attr: TSDL_GLattr; value: LongInt): LongInt; cdecl; external SDLLibName; procedure SDL_GL_SwapBuffers; cdecl; external SDLLibName; +procedure SDL_LockAudio; cdecl; external SDLLibName; +procedure SDL_UnlockAudio; cdecl; external SDLLibName; + function SDL_NumJoysticks: LongInt; cdecl; external SDLLibName; function SDL_JoystickName(idx: LongInt): PChar; cdecl; external SDLLibName; function SDL_JoystickOpen(idx: LongInt): PSDL_Joystick; cdecl; external SDLLibName; @@ -1010,6 +1013,7 @@ function Mix_OpenAudio(frequency: LongInt; format: Word; channels: LongInt; chunksize: LongInt): LongInt; cdecl; external SDL_MixerLibName; procedure Mix_CloseAudio; cdecl; external SDL_MixerLibName; +function Mix_QuerySpec(frequency: PLongInt; format: PWord; channels: PLongInt): LongInt; cdecl; external SDL_MixerLibName; function Mix_Volume(channel: LongInt; volume: LongInt): LongInt; cdecl; external SDL_MixerLibName; function Mix_SetDistance(channel: LongInt; distance: Byte): LongInt; cdecl; external SDL_MixerLibName; @@ -1037,6 +1041,8 @@ function Mix_FadeInChannelTimed(channel: LongInt; chunk: PMixChunk; loops: LongInt; fadems: LongInt; ticks: LongInt): LongInt; cdecl; external SDL_MixerLibName; function Mix_FadeOutChannel(channel: LongInt; fadems: LongInt): LongInt; cdecl; external SDL_MixerLibName; +procedure Mix_SetPostMix( mix_func: TPostMix; arg: pointer); cdecl; external SDL_MixerLibName; + (* SDL_image *) function IMG_Init(flags: LongInt): LongInt; {$IFDEF SDL_IMAGE_NEWER}cdecl; external SDL_ImageLibName;{$ENDIF} procedure IMG_Quit; {$IFDEF SDL_IMAGE_NEWER}cdecl; external SDL_ImageLibName;{$ENDIF} diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/avwrapper.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hedgewars/avwrapper.c Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,515 @@ +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include "libavformat/avformat.h" + +#ifndef AVIO_FLAG_WRITE +#define AVIO_FLAG_WRITE AVIO_WRONLY +#endif + +static AVFormatContext* g_pContainer; +static AVOutputFormat* g_pFormat; +static AVStream* g_pAStream; +static AVStream* g_pVStream; +static AVFrame* g_pAFrame; +static AVFrame* g_pVFrame; +static AVCodec* g_pACodec; +static AVCodec* g_pVCodec; +static AVCodecContext* g_pAudio; +static AVCodecContext* g_pVideo; + +static int g_Width, g_Height; +static uint32_t g_Frequency, g_Channels; +static int g_VQuality; +static AVRational g_Framerate; + +static FILE* g_pSoundFile; +static int16_t* g_pSamples; +static int g_NumSamples; + +/* +Initially I wrote code for latest ffmpeg, but on Linux (Ubuntu) +only older version is available from repository. That's why you see here +all of this #if LIBAVCODEC_VERSION_MAJOR < 54. +Actually, it may be possible to remove code for newer version +and use only code for older version. +*/ + +#if LIBAVCODEC_VERSION_MAJOR < 54 +#define OUTBUFFER_SIZE 200000 +static uint8_t g_OutBuffer[OUTBUFFER_SIZE]; +#endif + +// pointer to function from hwengine (uUtils.pas) +static void (*AddFileLogRaw)(const char* pString); + +static void FatalError(const char* pFmt, ...) +{ + const char Buffer[1024]; + va_list VaArgs; + + va_start(VaArgs, pFmt); + vsnprintf(Buffer, 1024, pFmt, VaArgs); + va_end(VaArgs); + + AddFileLogRaw("Error in av-wrapper: "); + AddFileLogRaw(Buffer); + AddFileLogRaw("\n"); + exit(1); +} + +// Function to be called from libav for logging. +// Note: libav can call LogCallback from different threads +// (there is mutex in AddFileLogRaw). +static void LogCallback(void* p, int Level, const char* pFmt, va_list VaArgs) +{ + const char Buffer[1024]; + + vsnprintf(Buffer, 1024, pFmt, VaArgs); + AddFileLogRaw(Buffer); +} + +static void Log(const char* pFmt, ...) +{ + const char Buffer[1024]; + va_list VaArgs; + + va_start(VaArgs, pFmt); + vsnprintf(Buffer, 1024, pFmt, VaArgs); + va_end(VaArgs); + + AddFileLogRaw(Buffer); +} + +static void AddAudioStream() +{ +#if LIBAVFORMAT_VERSION_MAJOR >= 54 + g_pAStream = avformat_new_stream(g_pContainer, g_pACodec); +#else + g_pAStream = av_new_stream(g_pContainer, 1); +#endif + if(!g_pAStream) + { + Log("Could not allocate audio stream\n"); + return; + } + g_pAStream->id = 1; + + g_pAudio = g_pAStream->codec; + + avcodec_get_context_defaults3(g_pAudio, g_pACodec); + g_pAudio->codec_id = g_pACodec->id; + + // put parameters + g_pAudio->sample_fmt = AV_SAMPLE_FMT_S16; + g_pAudio->sample_rate = g_Frequency; + g_pAudio->channels = g_Channels; + + // set quality + g_pAudio->bit_rate = 160000; + + // for codecs that support variable bitrate use it, it should be better + g_pAudio->flags |= CODEC_FLAG_QSCALE; + g_pAudio->global_quality = 1*FF_QP2LAMBDA; + + // some formats want stream headers to be separate + if (g_pFormat->flags & AVFMT_GLOBALHEADER) + g_pAudio->flags |= CODEC_FLAG_GLOBAL_HEADER; + + // open it +#if LIBAVCODEC_VERSION_MAJOR >= 53 + if (avcodec_open2(g_pAudio, g_pACodec, NULL) < 0) +#else + if (avcodec_open(g_pAudio, g_pACodec) < 0) +#endif + { + Log("Could not open audio codec %s\n", g_pACodec->long_name); + return; + } + +#if LIBAVCODEC_VERSION_MAJOR >= 54 + if (g_pACodec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE) +#else + if (g_pAudio->frame_size == 0) +#endif + g_NumSamples = 4096; + else + g_NumSamples = g_pAudio->frame_size; + g_pSamples = (int16_t*)av_malloc(g_NumSamples*g_Channels*sizeof(int16_t)); + g_pAFrame = avcodec_alloc_frame(); + if (!g_pAFrame) + { + Log("Could not allocate frame\n"); + return; + } +} + +// returns non-zero if there is more sound +static int WriteAudioFrame() +{ + if (!g_pAStream) + return 0; + + AVPacket Packet = { 0 }; + av_init_packet(&Packet); + + int NumSamples = fread(g_pSamples, 2*g_Channels, g_NumSamples, g_pSoundFile); + +#if LIBAVCODEC_VERSION_MAJOR >= 54 + AVFrame* pFrame = NULL; + if (NumSamples > 0) + { + g_pAFrame->nb_samples = NumSamples; + avcodec_fill_audio_frame(g_pAFrame, g_Channels, AV_SAMPLE_FMT_S16, + (uint8_t*)g_pSamples, NumSamples*2*g_Channels, 1); + pFrame = g_pAFrame; + } + // when NumSamples == 0 we still need to call encode_audio2 to flush + int got_packet; + if (avcodec_encode_audio2(g_pAudio, &Packet, pFrame, &got_packet) != 0) + FatalError("avcodec_encode_audio2 failed"); + if (!got_packet) + return 0; +#else + if (NumSamples == 0) + return 0; + int BufferSize = OUTBUFFER_SIZE; + if (g_pAudio->frame_size == 0) + BufferSize = NumSamples*g_Channels*2; + Packet.size = avcodec_encode_audio(g_pAudio, g_OutBuffer, BufferSize, g_pSamples); + if (Packet.size == 0) + return 1; + if (g_pAudio->coded_frame && g_pAudio->coded_frame->pts != AV_NOPTS_VALUE) + Packet.pts = av_rescale_q(g_pAudio->coded_frame->pts, g_pAudio->time_base, g_pAStream->time_base); + Packet.flags |= AV_PKT_FLAG_KEY; + Packet.data = g_OutBuffer; +#endif + + // Write the compressed frame to the media file. + Packet.stream_index = g_pAStream->index; + if (av_interleaved_write_frame(g_pContainer, &Packet) != 0) + FatalError("Error while writing audio frame"); + return 1; +} + +// add a video output stream +static void AddVideoStream() +{ +#if LIBAVFORMAT_VERSION_MAJOR >= 54 + g_pVStream = avformat_new_stream(g_pContainer, g_pVCodec); +#else + g_pVStream = av_new_stream(g_pContainer, 0); +#endif + if (!g_pVStream) + FatalError("Could not allocate video stream"); + + g_pVideo = g_pVStream->codec; + + avcodec_get_context_defaults3(g_pVideo, g_pVCodec); + g_pVideo->codec_id = g_pVCodec->id; + + // put parameters + // resolution must be a multiple of two + g_pVideo->width = g_Width & ~1; // make even (dimensions should be even) + g_pVideo->height = g_Height & ~1; // make even + /* time base: this is the fundamental unit of time (in seconds) in terms + of which frame timestamps are represented. for fixed-fps content, + timebase should be 1/framerate and timestamp increments should be + identically 1. */ + g_pVideo->time_base.den = g_Framerate.num; + g_pVideo->time_base.num = g_Framerate.den; + //g_pVideo->gop_size = 12; /* emit one intra frame every twelve frames at most */ + g_pVideo->pix_fmt = PIX_FMT_YUV420P; + + // set quality + if (g_VQuality > 100) + g_pVideo->bit_rate = g_VQuality; + else + { + g_pVideo->flags |= CODEC_FLAG_QSCALE; + g_pVideo->global_quality = g_VQuality*FF_QP2LAMBDA; + } + + // some formats want stream headers to be separate + if (g_pFormat->flags & AVFMT_GLOBALHEADER) + g_pVideo->flags |= CODEC_FLAG_GLOBAL_HEADER; + +#if LIBAVCODEC_VERSION_MAJOR < 54 + // for some versions of ffmpeg x264 options must be set explicitly + if (strcmp(g_pVCodec->name, "libx264") == 0) + { + g_pVideo->coder_type = FF_CODER_TYPE_AC; + g_pVideo->flags |= CODEC_FLAG_LOOP_FILTER; + g_pVideo->crf = 23; + g_pVideo->thread_count = 3; + g_pVideo->me_cmp = FF_CMP_CHROMA; + g_pVideo->partitions = X264_PART_I8X8 | X264_PART_I4X4 | X264_PART_P8X8 | X264_PART_B8X8; + g_pVideo->me_method = ME_HEX; + g_pVideo->me_subpel_quality = 7; + g_pVideo->me_range = 16; + g_pVideo->gop_size = 250; + g_pVideo->keyint_min = 25; + g_pVideo->scenechange_threshold = 40; + g_pVideo->i_quant_factor = 0.71; + g_pVideo->b_frame_strategy = 1; + g_pVideo->qcompress = 0.6; + g_pVideo->qmin = 10; + g_pVideo->qmax = 51; + g_pVideo->max_qdiff = 4; + g_pVideo->max_b_frames = 3; + g_pVideo->refs = 3; + g_pVideo->directpred = 1; + g_pVideo->trellis = 1; + g_pVideo->flags2 = CODEC_FLAG2_BPYRAMID | CODEC_FLAG2_MIXED_REFS | CODEC_FLAG2_WPRED | CODEC_FLAG2_8X8DCT | CODEC_FLAG2_FASTPSKIP; + g_pVideo->weighted_p_pred = 2; + } +#endif + + // open the codec +#if LIBAVCODEC_VERSION_MAJOR >= 53 + AVDictionary* pDict = NULL; + if (strcmp(g_pVCodec->name, "libx264") == 0) + av_dict_set(&pDict, "preset", "medium", 0); + + if (avcodec_open2(g_pVideo, g_pVCodec, &pDict) < 0) +#else + if (avcodec_open(g_pVideo, g_pVCodec) < 0) +#endif + FatalError("Could not open video codec %s", g_pVCodec->long_name); + + g_pVFrame = avcodec_alloc_frame(); + if (!g_pVFrame) + FatalError("Could not allocate frame"); + + g_pVFrame->linesize[0] = g_Width; + g_pVFrame->linesize[1] = g_Width/2; + g_pVFrame->linesize[2] = g_Width/2; + g_pVFrame->linesize[3] = 0; +} + +static int WriteFrame(AVFrame* pFrame) +{ + double AudioTime, VideoTime; + + // write interleaved audio frame + if (g_pAStream) + { + VideoTime = (double)g_pVStream->pts.val*g_pVStream->time_base.num/g_pVStream->time_base.den; + do + AudioTime = (double)g_pAStream->pts.val*g_pAStream->time_base.num/g_pAStream->time_base.den; + while (AudioTime < VideoTime && WriteAudioFrame()); + } + + if (!g_pVStream) + return 0; + + AVPacket Packet; + av_init_packet(&Packet); + Packet.data = NULL; + Packet.size = 0; + + g_pVFrame->pts++; + if (g_pFormat->flags & AVFMT_RAWPICTURE) + { + /* raw video case. The API will change slightly in the near + future for that. */ + Packet.flags |= AV_PKT_FLAG_KEY; + Packet.stream_index = g_pVStream->index; + Packet.data = (uint8_t*)pFrame; + Packet.size = sizeof(AVPicture); + + if (av_interleaved_write_frame(g_pContainer, &Packet) != 0) + FatalError("Error while writing video frame"); + return 0; + } + else + { +#if LIBAVCODEC_VERSION_MAJOR >= 54 + int got_packet; + if (avcodec_encode_video2(g_pVideo, &Packet, pFrame, &got_packet) < 0) + FatalError("avcodec_encode_video2 failed"); + if (!got_packet) + return 0; + + if (Packet.pts != AV_NOPTS_VALUE) + Packet.pts = av_rescale_q(Packet.pts, g_pVideo->time_base, g_pVStream->time_base); + if (Packet.dts != AV_NOPTS_VALUE) + Packet.dts = av_rescale_q(Packet.dts, g_pVideo->time_base, g_pVStream->time_base); +#else + Packet.size = avcodec_encode_video(g_pVideo, g_OutBuffer, OUTBUFFER_SIZE, pFrame); + if (Packet.size < 0) + FatalError("avcodec_encode_video failed"); + if (Packet.size == 0) + return 0; + + if( g_pVideo->coded_frame->pts != AV_NOPTS_VALUE) + Packet.pts = av_rescale_q(g_pVideo->coded_frame->pts, g_pVideo->time_base, g_pVStream->time_base); + if( g_pVideo->coded_frame->key_frame ) + Packet.flags |= AV_PKT_FLAG_KEY; + Packet.data = g_OutBuffer; +#endif + // write the compressed frame in the media file + Packet.stream_index = g_pVStream->index; + if (av_interleaved_write_frame(g_pContainer, &Packet) != 0) + FatalError("Error while writing video frame"); + + return 1; + } +} + +void AVWrapper_WriteFrame(uint8_t* pY, uint8_t* pCb, uint8_t* pCr) +{ + g_pVFrame->data[0] = pY; + g_pVFrame->data[1] = pCb; + g_pVFrame->data[2] = pCr; + WriteFrame(g_pVFrame); +} + +void AVWrapper_Init( + void (*pAddFileLogRaw)(const char*), + const char* pFilename, + const char* pDesc, + const char* pSoundFile, + const char* pFormatName, + const char* pVCodecName, + const char* pACodecName, + int Width, int Height, + int FramerateNum, int FramerateDen, + int VQuality) +{ + AddFileLogRaw = pAddFileLogRaw; + av_log_set_callback( &LogCallback ); + + g_Width = Width; + g_Height = Height; + g_Framerate.num = FramerateNum; + g_Framerate.den = FramerateDen; + g_VQuality = VQuality; + + // initialize libav and register all codecs and formats + av_register_all(); + + // find format + g_pFormat = av_guess_format(pFormatName, NULL, NULL); + if (!g_pFormat) + FatalError("Format \"%s\" was not found", pFormatName); + + // allocate the output media context + g_pContainer = avformat_alloc_context(); + if (!g_pContainer) + FatalError("Could not allocate output context"); + + g_pContainer->oformat = g_pFormat; + + // store description of file + av_dict_set(&g_pContainer->metadata, "comment", pDesc, 0); + + // append extesnion to filename + char ext[16]; + strncpy(ext, g_pFormat->extensions, 16); + ext[15] = 0; + ext[strcspn(ext,",")] = 0; + snprintf(g_pContainer->filename, sizeof(g_pContainer->filename), "%s.%s", pFilename, ext); + + // find codecs + g_pVCodec = avcodec_find_encoder_by_name(pVCodecName); + g_pACodec = avcodec_find_encoder_by_name(pACodecName); + + // add audio and video stream to container + g_pVStream = NULL; + g_pAStream = NULL; + + if (g_pVCodec) + AddVideoStream(); + else + Log("Video codec \"%s\" was not found; video will be ignored.\n", pVCodecName); + + if (g_pACodec) + { + g_pSoundFile = fopen(pSoundFile, "rb"); + if (g_pSoundFile) + { + fread(&g_Frequency, 4, 1, g_pSoundFile); + fread(&g_Channels, 4, 1, g_pSoundFile); + AddAudioStream(); + } + else + Log("Could not open %s\n", pSoundFile); + } + else + Log("Audio codec \"%s\" was not found; audio will be ignored.\n", pACodecName); + + if (!g_pAStream && !g_pVStream) + FatalError("No video, no audio, aborting..."); + + // write format info to log + av_dump_format(g_pContainer, 0, g_pContainer->filename, 1); + + // open the output file, if needed + if (!(g_pFormat->flags & AVFMT_NOFILE)) + { + if (avio_open(&g_pContainer->pb, g_pContainer->filename, AVIO_FLAG_WRITE) < 0) + FatalError("Could not open output file (%s)", g_pContainer->filename); + } + + // write the stream header, if any + avformat_write_header(g_pContainer, NULL); + + g_pVFrame->pts = -1; +} + +void AVWrapper_Close() +{ + // output buffered frames + if (g_pVCodec->capabilities & CODEC_CAP_DELAY) + while( WriteFrame(NULL) ); + // output any remaining audio + while( WriteAudioFrame() ); + + // write the trailer, if any. + av_write_trailer(g_pContainer); + + // close the output file + if (!(g_pFormat->flags & AVFMT_NOFILE)) + avio_close(g_pContainer->pb); + + // free everything + if (g_pVStream) + { + avcodec_close(g_pVideo); + av_free(g_pVideo); + av_free(g_pVStream); + av_free(g_pVFrame); + } + if (g_pAStream) + { + avcodec_close(g_pAudio); + av_free(g_pAudio); + av_free(g_pAStream); + av_free(g_pAFrame); + av_free(g_pSamples); + fclose(g_pSoundFile); + } + + av_free(g_pContainer); +} diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/hwengine.pas --- a/hedgewars/hwengine.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/hwengine.pas Thu Aug 30 13:02:19 2012 -0400 @@ -30,9 +30,11 @@ {$ENDIF} uses SDLh, uMisc, uConsole, uGame, uConsts, uLand, uAmmos, uVisualGears, uGears, uStore, uWorld, uInputHandler, uSound, - uScript, uTeams, uStats, uIO, uLocale, uChat, uAI, uAIMisc, uRandom, uLandTexture, uCollisions, + uScript, uTeams, uStats, uIO, uLocale, uChat, uAI, uAIMisc, uLandTexture, uCollisions, SysUtils, uTypes, uVariables, uCommands, uUtils, uCaptions, uDebug, uCommandHandlers, uLandPainted - {$IFDEF SDL13}, uTouch{$ENDIF}{$IFDEF ANDROID}, GLUnit{$ENDIF}; + {$IFDEF USE_VIDEO_RECORDING}, uVideoRec {$ENDIF} + {$IFDEF SDL13}, uTouch{$ENDIF}{$IFDEF ANDROID}, GLUnit{$ENDIF}, uAILandMarks; + {$IFDEF HWLIBRARY} procedure initEverything(complete:boolean); @@ -57,6 +59,9 @@ gsLandGen: begin GenMap; + uLandTexture.initModule; + UpdateLandTexture(0, LAND_WIDTH, 0, LAND_HEIGHT, false); + uAILandMarks.initModule; ParseCommand('sendlanddigest', true); GameState:= gsStart; end; @@ -81,7 +86,7 @@ end; gsConfirm, gsGame: begin - DrawWorld(Lag); // never place between ProcessKbd and DoGameTick - bugs due to /put cmd and isCursorVisible + DrawWorld(Lag); DoGameTick(Lag); ProcessVisualGears(Lag); end; @@ -101,18 +106,27 @@ SwapBuffers; +{$IFDEF USE_VIDEO_RECORDING} + if flagPrerecording then + SaveCameraPosition; +{$ENDIF} + if flagMakeCapture then begin flagMakeCapture:= false; {$IFDEF PAS2C} - s:= 'hw'; + s:= '/Screenshots/hw'; {$ELSE} - s:= 'hw_' + FormatDateTime('YYYY-MM-DD_HH-mm-ss', Now()) + inttostr(GameTicks); + s:= '/Screenshots/hw_' + FormatDateTime('YYYY-MM-DD_HH-mm-ss', Now()) + inttostr(GameTicks); {$ENDIF} + // flash playSound(sndShutter); - - if MakeScreenshot(s) then + ScreenFade:= sfFromWhite; + ScreenFadeValue:= sfMax; + ScreenFadeSpeed:= 5; + + if MakeScreenshot(s, 1) then WriteLnToConsole('Screenshot saved: ' + s) else begin @@ -261,6 +275,39 @@ end; end; +{$IFDEF USE_VIDEO_RECORDING} +procedure RecorderMainLoop; +var oldGameTicks, oldRealTicks, newGameTicks, newRealTicks: LongInt; +begin + if not BeginVideoRecording() then + exit; + DoTimer(0); // gsLandGen -> gsStart + DoTimer(0); // gsStart -> gsGame + + if not LoadNextCameraPosition(newRealTicks, newGameTicks) then + exit; + fastScrolling:= true; + DoGameTick(newGameTicks); + fastScrolling:= false; + oldRealTicks:= 0; + oldGameTicks:= newGameTicks; + + while LoadNextCameraPosition(newRealTicks, newGameTicks) do + begin + IPCCheckSock(); + DoGameTick(newGameTicks - oldGameTicks); + if GameState = gsExit then + break; + ProcessVisualGears(newRealTicks - oldRealTicks); + DrawWorld(newRealTicks - oldRealTicks); + EncodeFrame(); + oldRealTicks:= newRealTicks; + oldGameTicks:= newGameTicks; + end; + StopVideoRecording(); +end; +{$ENDIF} + /////////////// procedure Game{$IFDEF HWLIBRARY}(gameArgs: PPChar); cdecl; export{$ENDIF}; var p: TPathType; @@ -327,11 +374,18 @@ SDLTry(TTF_Init() <> -1, true); WriteLnToConsole(msgOK); - // show main window - if cFullScreen then - ParseCommand('fullscr 1', true) +{$IFDEF USE_VIDEO_RECORDING} + if GameType = gmtRecord then + InitOffscreenOpenGL() else - ParseCommand('fullscr 0', true); +{$ENDIF} + begin + // show main window + if cFullScreen then + ParseCommand('fullscr 1', true) + else + ParseCommand('fullscr 0', true); + end; ControllerInit(); // has to happen before InitKbdKeyTable to map keys InitKbdKeyTable(); @@ -368,12 +422,22 @@ InitTeams(); AssignStores(); + + if GameType = gmtRecord then + SetSound(false); + InitSound(); isDeveloperMode:= false; TryDo(InitStepsFlags = cifAllInited, 'Some parameters not set (flags = ' + inttostr(InitStepsFlags) + ')', true); ParseCommand('rotmask', true); - MainLoop(); + +{$IFDEF USE_VIDEO_RECORDING} + if GameType = gmtRecord then + RecorderMainLoop() + else +{$ENDIF} + MainLoop(); // clean up all the memory allocated freeEverything(true); @@ -412,9 +476,7 @@ //uLandGraphics does not need initialization //uLandObjects does not need initialization //uLandTemplates does not need initialization - uLandTexture.initModule; //uLocale does not need initialization - uRandom.initModule; uScript.initModule; uSound.initModule; uStats.initModule; @@ -432,6 +494,7 @@ begin WriteLnToConsole('Freeing resources...'); uAI.freeModule; + uAILandMarks.freeModule; uAIMisc.freeModule; //stub uCaptions.freeModule; uWorld.freeModule; @@ -441,7 +504,7 @@ uStats.freeModule; //stub uSound.freeModule; uScript.freeModule; - uRandom.freeModule; //stub + //uRandom does not need to be freed //uLocale does not need to be freed //uLandTemplates does not need to be freed uLandTexture.freeModule; @@ -456,6 +519,7 @@ //uAIAmmoTests does not need to be freed //uAIActions does not need to be freed uStore.freeModule; +{$IFDEF USE_VIDEO_RECORDING}uVideoRec.freeModule;{$ENDIF} end; uIO.freeModule; @@ -529,11 +593,14 @@ else if (ParamCount = 3) and ((ParamStr(3) = '--stats-only') or (ParamStr(3) = 'landpreview')) then internalSetGameTypeLandPreviewFromParameters() + else if ParamCount = cDefaultParamNum then + internalStartGameWithParameters() +{$IFDEF USE_VIDEO_RECORDING} + else if ParamCount = cVideorecParamNum then + internalStartVideoRecordingWithParameters() +{$ENDIF} else - if (ParamCount = cDefaultParamNum) then - internalStartGameWithParameters() - else - playReplayFileWithParameters(); + playReplayFileWithParameters(); end; //////////////////////////////////////////////////////////////////////////////// diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/options.inc diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/pas2cSystem.pas --- a/hedgewars/pas2cSystem.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/pas2cSystem.pas Thu Aug 30 13:02:19 2012 -0400 @@ -38,6 +38,7 @@ PPChar = ^Pchar; PByte = ^Byte; + PWord = ^Word; PLongInt = ^LongInt; PLongWord = ^LongWord; PInteger = ^Integer; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uAI.pas --- a/hedgewars/uAI.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uAI.pas Thu Aug 30 13:02:19 2012 -0400 @@ -31,7 +31,7 @@ implementation uses uConsts, SDLh, uAIMisc, uAIAmmoTests, uAIActions, uAmmos, SysUtils{$IFNDEF USE_SDLTHREADS} {$IFDEF UNIX}, cthreads{$ENDIF} {$ENDIF}, uTypes, - uVariables, uCommands, uUtils, uDebug; + uVariables, uCommands, uUtils, uDebug, uAILandMarks; var BestActions: TActions; CanUseAmmo: array [TAmmoType] of boolean; @@ -178,9 +178,12 @@ begin AddAction(BestActions, aia_attack, aim_push, 350 + random(200), 0, 0); AddAction(BestActions, aia_attack, aim_release, 1, 0, 0); - - AddAction(BestActions, aia_Down, aim_push, 100 + random(150), 0, 0); - AddAction(BestActions, aia_Down, aim_release, 32, 0, 0); + + if abs(ap.Angle) > 32 then + begin + AddAction(BestActions, aia_Down, aim_push, 100 + random(150), 0, 0); + AddAction(BestActions, aia_Down, aim_release, 32, 0, 0); + end; AddAction(BestActions, aia_waitAngle, ap.Angle, 250, 0, 0); AddAction(BestActions, aia_attack, aim_push, 1, 0, 0); @@ -191,7 +194,14 @@ AddAction(BestActions, aia_attack, aim_push, 650 + random(300), 0, 0); AddAction(BestActions, aia_attack, aim_release, ap.Power, 0, 0); end; - + + if (Ammoz[a].Ammo.Propz and ammoprop_Track) <> 0 then + begin + AddAction(BestActions, aia_waitAmmoXY, 0, 12, ap.ExplX, ap.ExplY); + AddAction(BestActions, aia_attack, aim_push, 1, 0, 0); + AddAction(BestActions, aia_attack, aim_release, 7, 0, 0); + end; + if ap.ExplR > 0 then AddAction(BestActions, aia_AwareExpl, ap.ExplR, 10, ap.ExplX, ap.ExplY); end @@ -205,7 +215,7 @@ end; procedure Walk(Me: PGear; var Actions: TActions); -const FallPixForBranching = cHHRadius * 2 + 8; +const FallPixForBranching = cHHRadius; var ticks, maxticks, steps, tmp: Longword; BaseRate, BestRate, Rate: integer; @@ -267,7 +277,9 @@ break; if (BotLevel < 5) and (GoInfo.JumpType = jmpHJump) then // hjump support - if Push(ticks, Actions, AltMe, Me^.Message) then + // check if we could go backwards and maybe ljump over a gap after this hjump + if Push(ticks, Actions, AltMe, Me^.Message xor 3) then + begin with Stack.States[Pred(Stack.Count)] do begin if Me^.dX.isNegative then @@ -283,11 +295,20 @@ else AddAction(MadeActions, aia_LookRight, 0, 200, 0, 0); end; + // but first check walking forward + Push(ticks, Stack.States[Pred(Stack.Count)].MadeActions, AltMe, Me^.Message) + end; if (BotLevel < 3) and (GoInfo.JumpType = jmpLJump) then // ljump support begin - // push current position so we proceed from it after checking jump opportunities + // at final check where we go after jump walking backward + if Push(ticks, Actions, AltMe, Me^.Message xor 3) then + with Stack.States[Pred(Stack.Count)] do + AddAction(MadeActions, aia_LJump, 0, 305 + random(50), 0, 0); + + // push current position so we proceed from it after checking jump+forward walk opportunities if CanGo then Push(ticks, Actions, Me^, Me^.Message); - // first check where we go after jump + + // first check where we go after jump walking forward if Push(ticks, Actions, AltMe, Me^.Message) then with Stack.States[Pred(Stack.Count)] do AddAction(MadeActions, aia_LJump, 0, 305 + random(50), 0, 0); @@ -310,8 +331,16 @@ end else if Rate < BestRate then break; + if ((Me^.State and gstAttacked) = 0) and ((steps mod 4) = 0) then + begin + if (steps > 4) and checkMark(hwRound(Me^.X), hwRound(Me^.Y), markWasHere) then + break; + addMark(hwRound(Me^.X), hwRound(Me^.Y), markWasHere); + TestAmmos(Actions, Me, true); + end; + if GoInfo.FallPix >= FallPixForBranching then Push(ticks, Actions, Me^, Me^.Message xor 3); // aia_Left xor 3 = aia_Right end {while}; @@ -408,7 +437,7 @@ end end; -PGear(Me)^.State:= PGear(Me)^.State and not gstHHThinking; +PGear(Me)^.State:= PGear(Me)^.State and (not gstHHThinking); Think:= 0; InterlockedDecrement(hasThread) end; @@ -419,7 +448,9 @@ or isInMultiShoot then exit; -//DeleteCI(Me); // this might break demo +//DeleteCI(Me); // this will break demo/netplay +clearAllMarks; + Me^.State:= Me^.State or gstHHThinking; Me^.Message:= 0; @@ -476,12 +507,11 @@ end else begin - (* - if not scoreShown then + {if not scoreShown then begin if BestActions.Score > 0 then ParseCommand('/say Expected score = ' + inttostr(BestActions.Score div 1024), true); scoreShown:= true - end;*) + end;} ProcessAction(BestActions, Gear) end else if ((GameTicks - StartTicks) > cMaxAIThinkTime) diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uAIActions.pas --- a/hedgewars/uAIActions.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uAIActions.pas Thu Aug 30 13:02:19 2012 -0400 @@ -44,6 +44,7 @@ aia_Wait = $8009; aia_Put = $800A; aia_waitAngle = $800B; + aia_waitAmmoXY = $800C; aim_push = $8000; aim_release = $8001; @@ -115,19 +116,19 @@ procedure AddAction(var Actions: TActions; Action: Longword; Param: LongInt; TimeDelta: Longword; X, Y: LongInt); begin -with Actions do - begin - actions[Count].Action:= Action; - actions[Count].Param:= Param; - actions[Count].X:= X; - actions[Count].Y:= Y; - if Count > 0 then - actions[Count].Time:= TimeDelta - else - actions[Count].Time:= GameTicks + TimeDelta; - inc(Count); - TryDo(Count < MAXACTIONS, 'AI: actions overflow', true); - end +if Actions.Count < MAXACTIONS then + with Actions do + begin + actions[Count].Action:= Action; + actions[Count].Param:= Param; + actions[Count].X:= X; + actions[Count].Y:= Y; + if Count > 0 then + actions[Count].Time:= TimeDelta + else + actions[Count].Time:= GameTicks + TimeDelta; + inc(Count); + end end; procedure CheckHang(Me: PGear); @@ -234,6 +235,10 @@ aia_waitAngle: if Me^.Angle <> Abs(Param) then exit; + + aia_waitAmmoXY: + if (CurAmmoGear <> nil) and ((hwRound(CurAmmoGear^.X) <> X) or (hwRound(CurAmmoGear^.Y) <> Y)) then exit; + end else begin diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uAIAmmoTests.pas --- a/hedgewars/uAIAmmoTests.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uAIAmmoTests.pas Thu Aug 30 13:02:19 2012 -0400 @@ -47,6 +47,7 @@ function TestBaseballBat(Me: PGear; Targ: TPoint; Level: LongInt; var ap: TAttackParams): LongInt; function TestFirePunch(Me: PGear; Targ: TPoint; Level: LongInt; var ap: TAttackParams): LongInt; function TestWhip(Me: PGear; Targ: TPoint; Level: LongInt; var ap: TAttackParams): LongInt; +function TestKamikaze(Me: PGear; Targ: TPoint; Level: LongInt; var ap: TAttackParams): LongInt; function TestAirAttack(Me: PGear; Targ: TPoint; Level: LongInt; var ap: TAttackParams): LongInt; function TestTeleport(Me: PGear; Targ: TPoint; Level: LongInt; var ap: TAttackParams): LongInt; function TestHammer(Me: PGear; Targ: TPoint; Level: LongInt; var ap: TAttackParams): LongInt; @@ -84,8 +85,8 @@ //(proc: @TestTeleport; flags: amtest_OnTurn), // amTeleport (proc: nil; flags: 0), // amSwitch (proc: @TestMortar; flags: 0), // amMortar - (proc: nil; flags: 0), // amKamikaze - (proc: @TestCake; flags: 0), // amCake + (proc: @TestKamikaze; flags: 0), // amKamikaze + (proc: @TestCake; flags: amtest_OnTurn or amtest_NoTarget), // amCake (proc: nil; flags: 0), // amSeduction (proc: @TestWatermelon; flags: 0), // amWatermelon (proc: nil; flags: 0), // amHellishBomb @@ -122,7 +123,7 @@ const BadTurn = Low(LongInt) div 4; implementation -uses uAIMisc, uVariables, uUtils; +uses uAIMisc, uVariables, uUtils, uGearsHandlers, uCollisions; function Metric(x1, y1, x2, y2: LongInt): LongInt; inline; begin @@ -167,7 +168,7 @@ EX:= trunc(x); EY:= trunc(y); - if Me^.Hedgehog^.BotLevel = 1 then + if Level = 1 then value:= RateExplosion(Me, EX, EY, 101, afTrackFall or afErasesLand) else value:= RateExplosion(Me, EX, EY, 101); if value = 0 then @@ -330,7 +331,7 @@ EX:= trunc(x); EY:= trunc(y); if t < 50 then - if Me^.Hedgehog^.BotLevel = 1 then + if Level = 1 then Score:= RateExplosion(Me, EX, EY, 101, afTrackFall or afErasesLand) else Score:= RateExplosion(Me, EX, EY, 101) else @@ -361,12 +362,12 @@ t: LongInt; begin valueResult:= BadTurn; -TestTime:= 0; +TestTime:= 500; ap.ExplR:= 0; meX:= hwFloat2Float(Me^.X); meY:= hwFloat2Float(Me^.Y); repeat - inc(TestTime, 1000); + inc(TestTime, 900); // Try to overshoot slightly, seems to pay slightly better dividends in terms of hitting cluster if meX 3 then exit(BadTurn); +if Level > 3 then exit(BadTurn); dmgMod:= 0.01 * hwFloat2Float(cDamageModifier) * cDamagePercent; Level:= Level; // avoid compiler hint ap.ExplR:= 0; ap.Time:= 0; ap.Power:= 1; + x:= hwFloat2Float(Me^.X); y:= hwFloat2Float(Me^.Y); + if Abs(trunc(x) - Targ.X) + Abs(trunc(y) - Targ.Y) < 40 then - begin - TestDesertEagle:= BadTurn; exit(BadTurn); - end; + t:= 2 / sqrt(sqr(Targ.X - x)+sqr(Targ.Y-y)); Vx:= (Targ.X - x) * t; Vy:= (Targ.Y - y) * t; @@ -650,7 +651,7 @@ d: Longword; fallDmg, valueResult: LongInt; begin -if Me^.Hedgehog^.BotLevel > 3 then exit(BadTurn); +if Level > 3 then exit(BadTurn); dmgMod:= 0.01 * hwFloat2Float(cDamageModifier) * cDamagePercent; Level:= Level; // avoid compiler hint ap.ExplR:= 0; @@ -700,28 +701,28 @@ x, y, trackFall: LongInt; dx, dy: real; begin - if Me^.Hedgehog^.BotLevel < 3 then trackFall:= afTrackFall + if Level < 3 then trackFall:= afTrackFall else trackFall:= 0; - Level:= Level; // avoid compiler hint + ap.ExplR:= 0; ap.Time:= 0; ap.Power:= 1; x:= hwRound(Me^.X); y:= hwRound(Me^.Y); - a:= 0; + a:= cMaxAngle div 2; valueResult:= 0; - while a <= cMaxAngle div 2 do + while a >= 0 do begin dx:= sin(a / cMaxAngle * pi) * 0.5; dy:= cos(a / cMaxAngle * pi) * 0.5; - v1:= RateShove(Me, x - 10, y - , 33, 30, 115 + v1:= RateShove(Me, x - 10, y + 2 + , 32, 30, 115 , -dx, -dy, trackFall); - v2:= RateShove(Me, x + 10, y - , 33, 30, 115 + v2:= RateShove(Me, x + 10, y + 2 + , 32, 30, 115 , dx, -dy, trackFall); if (v1 > valueResult) or (v2 > valueResult) then if (v2 > v1) @@ -736,7 +737,7 @@ valueResult:= v1 end; - a:= a + 15 + random(cMaxAngle div 16) + a:= a - 15 - random(cMaxAngle div 16) end; if valueResult <= 0 then @@ -749,24 +750,24 @@ var valueResult, v1, v2, i: LongInt; x, y, trackFall: LongInt; begin - if Me^.Hedgehog^.BotLevel = 1 then trackFall:= afTrackFall + if Level = 1 then trackFall:= afTrackFall else trackFall:= 0; - Level:= Level; // avoid compiler hint + ap.ExplR:= 0; ap.Time:= 0; ap.Power:= 1; x:= hwRound(Me^.X); - y:= hwRound(Me^.Y); + y:= hwRound(Me^.Y) + 4; v1:= 0; for i:= 0 to 8 do begin - v1:= v1 + RateShove(Me, x - 10, y - 10 * i - , 18, 30, 40 + v1:= v1 + RateShove(Me, x - 5, y - 10 * i + , 19, 30, 40 , -0.45, -0.9, trackFall or afSetSkip); end; - v1:= v1 + RateShove(Me, x - 10, y - 90 - , 18, 30, 40 + v1:= v1 + RateShove(Me, x - 5, y - 90 + , 19, 30, 40 , -0.45, -0.9, trackFall); @@ -774,12 +775,12 @@ v2:= 0; for i:= 0 to 8 do begin - v2:= v2 + RateShove(Me, x + 10, y - 10 * i - , 18, 30, 40 + v2:= v2 + RateShove(Me, x + 5, y - 10 * i + , 19, 30, 40 , 0.45, -0.9, trackFall or afSetSkip); end; - v2:= v2 + RateShove(Me, x + 10, y - 90 - , 18, 30, 40 + v2:= v2 + RateShove(Me, x + 5, y - 90 + , 19, 30, 40 , 0.45, -0.9, trackFall); if (v2 > v1) @@ -805,9 +806,9 @@ var valueResult, v1, v2: LongInt; x, y, trackFall: LongInt; begin - if Me^.Hedgehog^.BotLevel = 1 then trackFall:= afTrackFall + if Level = 1 then trackFall:= afTrackFall else trackFall:= 0; - Level:= Level; // avoid compiler hint + ap.ExplR:= 0; ap.Time:= 0; ap.Power:= 1; @@ -818,19 +819,19 @@ {first RateShove checks farthermost of two whip's AmmoShove attacks to encourage distant attacks (damaged hog is excluded from view of second RateShove call)} - v1:= RateShove(Me, x - 15, y + v1:= RateShove(Me, x - 13, y , 30, 30, 25 , -1, -0.8, trackFall or afSetSkip); v1:= v1 + - RateShove(Me, x, y + RateShove(Me, x - 2, y , 30, 30, 25 , -1, -0.8, trackFall); // now try opposite direction - v2:= RateShove(Me, x + 15, y + v2:= RateShove(Me, x + 13, y , 30, 30, 25 , 1, -0.8, trackFall or afSetSkip); v2:= v2 + - RateShove(Me, x, y + RateShove(Me, x + 2, y , 30, 30, 25 , 1, -0.8, trackFall); @@ -854,6 +855,90 @@ TestWhip:= valueResult; end; +function TestKamikaze(Me: PGear; Targ: TPoint; Level: LongInt; var ap: TAttackParams): LongInt; +const step = 8; +var valueResult, i, v, tx: LongInt; + trackFall: LongInt; + t, d, x, y, dx, dy, cx: real; +begin + ap.ExplR:= 0; + ap.Time:= 0; + ap.Power:= 1; + + if Level = 1 then + trackFall:= afTrackFall + else if Level = 2 then + trackFall:= 0 + else + exit(BadTurn); + + valueResult:= 0; + v:= 0; + + x:= hwFloat2Float(Me^.X); + y:= hwFloat2Float(Me^.Y); + d:= sqrt(sqr(Targ.X - x) + sqr(Targ.Y - y)); + if d < 10 then + begin + dx:= 0; + dy:= 8; + ap.Angle:= 2048 + end + else + begin + t:= step / d; + dx:= (Targ.X - x) * t; + dy:= (Targ.Y - y) * t; + + ap.Angle:= DxDy2AttackAnglef(dx, -dy) + end; + + if dx >= 0 then cx:= 0.45 else cx:= -0.45; + + for i:= 0 to 512 div step - 2 do + begin + valueResult:= valueResult + + RateShove(Me, trunc(x), trunc(y) + , 30, 30, 25 + , cx, -0.9, trackFall or afSetSkip); + + x:= x + dx; + y:= y + dy; + end; + if dx = 0 then + begin + x:= hwFloat2Float(Me^.X); + y:= hwFloat2Float(Me^.Y); + tx:= trunc(x); + v:= RateShove(Me, tx, trunc(y) + , 30, 30, 25 + , -cx, -0.9, trackFall); + for i:= 1 to 512 div step - 2 do + begin + y:= y + dy; + v:= v + + RateShove(Me, tx, trunc(y) + , 30, 30, 25 + , -cx, -0.9, trackFall or afSetSkip); + end + end; + if v > valueResult then + begin + ap.Angle:= -2048; + valueResult:= v + end; + + v:= RateShove(Me, trunc(x), trunc(y) + , 30, 30, 25 + , cx, -0.9, trackFall); + valueResult:= valueResult + v - KillScore * friendlyfactor div 100 * 1024; + + if v < 65536 then + inc(valueResult, RateExplosion(Me, trunc(x), trunc(y), 30)); + + TestKamikaze:= valueResult; +end; + function TestHammer(Me: PGear; Targ: TPoint; Level: LongInt; var ap: TAttackParams): LongInt; var rate: LongInt; begin @@ -983,9 +1068,24 @@ end; -function checkCakeWalk(Gear: PGear): LongInt; +procedure checkCakeWalk(Me, Gear: PGear; var ap: TAttackParams); +var i: Longword; + v: LongInt; begin -checkCakeWalk:= BadTurn +while (not TestColl(hwRound(Gear^.X), hwRound(Gear^.Y), 6)) and (Gear^.Y.Round < LAND_HEIGHT) do + Gear^.Y:= Gear^.Y + _1; + +for i:= 0 to 2040 do + begin + cakeStep(Gear); + v:= RateExplosion(Me, hwRound(Gear^.X), hwRound(Gear^.Y), cakeDmg * 2, afTrackFall); + if v > ap.Power then + begin + ap.ExplX:= hwRound(Gear^.X); + ap.ExplY:= hwRound(Gear^.Y); + ap.Power:= v + end + end; end; function TestCake(Me: PGear; Targ: TPoint; Level: LongInt; var ap: TAttackParams): LongInt; @@ -993,26 +1093,33 @@ x, y, trackFall: LongInt; cake: TGear; begin - Level:= Level; // avoid compiler hint + if (Level > 2) then + exit(BadTurn); ap.ExplR:= 0; ap.Time:= 0; - ap.Power:= 1; + ap.Power:= BadTurn; // use it as max score value in checkCakeWalk + FillChar(cake, sizeof(cake), 0); cake.Radius:= 7; + cake.CollisionMask:= $FF7F; // check left direction cake.Angle:= 3; cake.dX.isNegative:= true; cake.X:= Me^.X - _3; cake.Y:= Me^.Y; - v1:= checkCakeWalk(@cake); + checkCakeWalk(Me, @cake, ap); + v1:= ap.Power; // now try opposite direction cake.Angle:= 1; cake.dX.isNegative:= false; cake.X:= Me^.X + _3; cake.Y:= Me^.Y; - v2:= checkCakeWalk(@cake); + checkCakeWalk(Me, @cake, ap); + v2:= ap.Power; + + ap.Power:= 1; if (v2 > v1) then begin diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uAILandMarks.pas --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hedgewars/uAILandMarks.pas Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,71 @@ +unit uAILandMarks; + +interface +const markWasHere = $01; + +procedure addMark(X, Y: LongInt; mark: byte); +function checkMark(X, Y: LongInt; mark: byte) : boolean; +procedure clearAllMarks; +procedure clearMarks(mark: byte); + +procedure initModule; +procedure freeModule; + +implementation +uses uVariables; + +const gr = 2; + +var marks: array of array of byte; + WIDTH, HEIGHT: Longword; + +procedure addMark(X, Y: LongInt; mark: byte); +begin + if((X and LAND_WIDTH_MASK) = 0) and ((Y and LAND_HEIGHT_MASK) = 0) then + begin + X:= X shr gr; + Y:= Y shr gr; + marks[Y, X]:= marks[Y, X] or mark + end +end; + +function checkMark(X, Y: LongInt; mark: byte) : boolean; +begin + checkMark:= ((X and LAND_WIDTH_MASK) = 0) + and ((Y and LAND_HEIGHT_MASK) = 0) + and ((marks[Y shr gr, X shr gr] and mark) <> 0) +end; + +procedure clearAllMarks; +var + Y, X: Longword; +begin + for Y:= 0 to Pred(HEIGHT) do + for X:= 0 to Pred(WIDTH) do + marks[Y, X]:= 0 +end; + +procedure clearMarks(mark: byte); +var + Y, X: Longword; +begin + for Y:= 0 to Pred(HEIGHT) do + for X:= 0 to Pred(WIDTH) do + marks[Y, X]:= marks[Y, X] and (not mark) +end; + + +procedure initModule; +begin + WIDTH:= LAND_WIDTH shr gr; + HEIGHT:= LAND_HEIGHT shr gr; + + SetLength(marks, HEIGHT, WIDTH); +end; + +procedure freeModule; +begin + SetLength(marks, 0, 0); +end; + +end. diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uAIMisc.pas --- a/hedgewars/uAIMisc.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uAIMisc.pas Thu Aug 30 13:02:19 2012 -0400 @@ -86,12 +86,13 @@ ar: array[0..Pred(MAXBONUS div 8)] of TBonus; // don't use too many end; +const KillScore = 200; +var friendlyfactor: LongInt = 300; + implementation uses uCollisions, uVariables, uUtils, uDebug, uLandTexture; -const KillScore = 200; - -var friendlyfactor: LongInt = 300; +var KnownExplosion: record X, Y, Radius: LongInt end = (X: 0; Y: 0; Radius: 0); @@ -170,7 +171,7 @@ begin case Gear^.Kind of gtCase: - AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 33, 25); + AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y) + 3, 37, 25); gtFlame: if (Gear^.State and gsttmpFlag) <> 0 then AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 20, -50); @@ -371,13 +372,13 @@ x:= x + dX; y:= y + dY; dY:= dY + cGravityf; -(* - if ((trunc(y) and LAND_HEIGHT_MASK) = 0) and ((trunc(x) and LAND_WIDTH_MASK) = 0) then + +{ if ((trunc(y) and LAND_HEIGHT_MASK) = 0) and ((trunc(x) and LAND_WIDTH_MASK) = 0) then begin LandPixels[trunc(y), trunc(x)]:= v; UpdateLandTexture(trunc(X), 1, trunc(Y), 1, true); - end; -*) + end;} + // consider adding dX/dY calc here for fall damage if TestCollExcludingObjects(trunc(x), trunc(y), cHHRadius) then @@ -439,21 +440,21 @@ end; if fallDmg < 0 then // drowning. score healthier hogs higher, since their death is more likely to benefit the AI if Score > 0 then - inc(rate, KillScore + Score div 10) // Add a bit of a bonus for bigger hog drownings + inc(rate, (KillScore + Score div 10) * 1024) // Add a bit of a bonus for bigger hog drownings else - dec(rate, KillScore * friendlyfactor div 100 - Score div 10) // and more of a punishment for drowning bigger friendly hogs + dec(rate, (KillScore * friendlyfactor div 100 - Score div 10) * 1024) // and more of a punishment for drowning bigger friendly hogs else if (dmg+fallDmg) >= abs(Score) then if Score > 0 then - inc(rate, KillScore) + inc(rate, KillScore * 1024 + (dmg + fallDmg)) // tiny bonus for dealing more damage than needed to kill else - dec(rate, KillScore * friendlyfactor div 100) + dec(rate, KillScore * friendlyfactor div 100 * 1024) else if Score > 0 then - inc(rate, dmg+fallDmg) - else dec(rate, (dmg+fallDmg) * friendlyfactor div 100) + inc(rate, (dmg + fallDmg) * 1024) + else dec(rate, (dmg + fallDmg) * friendlyfactor div 100 * 1024) end; end; -RateExplosion:= rate * 1024; +RateExplosion:= rate; end; function RateShove(Me: PGear; x, y, r, power, kick: LongInt; gdX, gdY: real; Flags: LongWord): LongInt; @@ -624,6 +625,12 @@ end; repeat + {if ((hwRound(Gear^.Y) and LAND_HEIGHT_MASK) = 0) and ((hwRound(Gear^.X) and LAND_WIDTH_MASK) = 0) then + begin + LandPixels[hwRound(Gear^.Y), hwRound(Gear^.X)]:= Gear^.Hedgehog^.Team^.Clan^.Color; + UpdateLandTexture(hwRound(Gear^.X), 1, hwRound(Gear^.Y), 1, true); + end;} + if not (hwRound(Gear^.Y) + cHHRadius < cWaterLine) then exit(false); if (Gear^.State and gstMoving) <> 0 then @@ -645,7 +652,7 @@ Gear^.Y:= Gear^.Y + Gear^.dY; if (not Gear^.dY.isNegative) and (TestCollisionYwithGear(Gear, 1) <> 0) then begin - Gear^.State:= Gear^.State and not (gstMoving or gstHHJumping); + Gear^.State:= Gear^.State and (not (gstMoving or gstHHJumping)); Gear^.dY:= _0; case JumpType of jmpHJump: @@ -673,6 +680,7 @@ var pX, pY, tY: LongInt; begin HHGo:= false; +Gear^.CollisionMask:= $FF7F; AltGear^:= Gear^; GoInfo.Ticks:= 0; @@ -680,6 +688,12 @@ GoInfo.JumpType:= jmpNone; tY:= hwRound(Gear^.Y); repeat + {if ((hwRound(Gear^.Y) and LAND_HEIGHT_MASK) = 0) and ((hwRound(Gear^.X) and LAND_WIDTH_MASK) = 0) then + begin + LandPixels[hwRound(Gear^.Y), hwRound(Gear^.X)]:= random($FFFFFFFF);//Gear^.Hedgehog^.Team^.Clan^.Color; + UpdateLandTexture(hwRound(Gear^.X), 1, hwRound(Gear^.Y), 1, true); + end;} + pX:= hwRound(Gear^.X); pY:= hwRound(Gear^.Y); if pY + cHHRadius >= cWaterLine then @@ -696,7 +710,7 @@ Gear^.dY:= Gear^.dY + cGravity; if Gear^.dY > _0_4 then begin - Goinfo.FallPix:= 0; + GoInfo.FallPix:= 0; // try ljump instead of fall with damage HHJump(AltGear, jmpLJump, GoInfo); if AltGear^.Hedgehog^.BotLevel < 4 then @@ -709,7 +723,7 @@ if TestCollisionYwithGear(Gear, 1) <> 0 then begin inc(GoInfo.Ticks, 410); - Gear^.State:= Gear^.State and not (gstMoving or gstHHJumping); + Gear^.State:= Gear^.State and (not (gstMoving or gstHHJumping)); Gear^.dY:= _0; // try ljump instead of fall HHJump(AltGear, jmpLJump, GoInfo); diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uAmmos.pas --- a/hedgewars/uAmmos.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uAmmos.pas Thu Aug 30 13:02:19 2012 -0400 @@ -374,7 +374,7 @@ with CurWeapon^ do begin s:= trammo[Ammoz[AmmoType].NameId]; - if (Count <> AMMO_INFINITE) and not (Hedgehog.Team^.ExtDriven or (Hedgehog.BotLevel > 0)) then + if (Count <> AMMO_INFINITE) and (not (Hedgehog.Team^.ExtDriven or (Hedgehog.BotLevel > 0))) then s:= s + ' (' + IntToStr(Count) + ')'; if (Propz and ammoprop_Timerable) <> 0 then s:= s + ', ' + IntToStr(Timer div 1000) + ' ' + trammo[sidSeconds]; @@ -386,7 +386,7 @@ end else begin - if Gear <> nil then Gear^.State:= Gear^.State and not gstHHChooseTarget; + if Gear <> nil then Gear^.State:= Gear^.State and (not gstHHChooseTarget); isCursorVisible:= false end; end diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uCollisions.pas --- a/hedgewars/uCollisions.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uCollisions.pas Thu Aug 30 13:02:19 2012 -0400 @@ -82,7 +82,7 @@ X:= hwRound(Gear^.X); Y:= hwRound(Gear^.Y); Radius:= Gear^.Radius; - ChangeRoundInLand(X, Y, Radius - 1, true, Gear = CurrentHedgehog^.Gear); + ChangeRoundInLand(X, Y, Radius - 1, true, (Gear = CurrentHedgehog^.Gear) or (Gear^.Kind = gtCase)); cGear:= Gear end; Gear^.CollisionIndex:= Count; @@ -103,7 +103,7 @@ if Gear^.CollisionIndex >= 0 then begin with cinfos[Gear^.CollisionIndex] do - ChangeRoundInLand(X, Y, Radius - 1, false, Gear = CurrentHedgehog^.Gear); + ChangeRoundInLand(X, Y, Radius - 1, false, (Gear = CurrentHedgehog^.Gear) or (Gear^.Kind = gtCase)); cinfos[Gear^.CollisionIndex]:= cinfos[Pred(Count)]; cinfos[Gear^.CollisionIndex].cGear^.CollisionIndex:= Gear^.CollisionIndex; Gear^.CollisionIndex:= -1; @@ -138,7 +138,7 @@ var x, y, i: LongInt; begin // Special case to emulate the old intersect gear clearing, but with a bit of slop for pixel overlap -if (Gear^.CollisionMask = $FF7F) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) and +if (Gear^.CollisionMask = $FF7F) and (Gear^.Kind <> gtHedgehog) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) and ((hwRound(Gear^.Hedgehog^.Gear^.X) + Gear^.Hedgehog^.Gear^.Radius + 4 < hwRound(Gear^.X) - Gear^.Radius) or (hwRound(Gear^.Hedgehog^.Gear^.X) - Gear^.Hedgehog^.Gear^.Radius - 4 > hwRound(Gear^.X) + Gear^.Radius)) then Gear^.CollisionMask:= $FFFF; @@ -168,7 +168,7 @@ var x, y, i: LongInt; begin // Special case to emulate the old intersect gear clearing, but with a bit of slop for pixel overlap -if (Gear^.CollisionMask = $FF7F) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) and +if (Gear^.CollisionMask = $FF7F) and (Gear^.Kind <> gtHedgehog) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) and ((hwRound(Gear^.Hedgehog^.Gear^.Y) + Gear^.Hedgehog^.Gear^.Radius + 4 < hwRound(Gear^.Y) - Gear^.Radius) or (hwRound(Gear^.Hedgehog^.Gear^.Y) - Gear^.Hedgehog^.Gear^.Radius - 4 > hwRound(Gear^.Y) + Gear^.Radius)) then Gear^.CollisionMask:= $FFFF; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uCommandHandlers.pas diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uCommands.pas --- a/hedgewars/uCommands.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uCommands.pas Thu Aug 30 13:02:19 2012 -0400 @@ -93,9 +93,12 @@ begin if t^.Name = CmdStr then begin - if t^.Rand then CheckSum:= CheckSum xor LongWord(SDLNet_Read32(@CmdStr)) xor LongWord(s[0]) xor GameTicks; if TrustedSource or t^.Trusted then + begin + if t^.Rand and (not CheckNoTeamOrHH) then + CheckSum:= CheckSum xor LongWord(SDLNet_Read32(@CmdStr)) xor LongWord(s[0]) xor GameTicks; t^.Handler(s); + end; exit end else diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uConsts.pas --- a/hedgewars/uConsts.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uConsts.pas Thu Aug 30 13:02:19 2012 -0400 @@ -28,6 +28,7 @@ const sfMax = 1000; cDefaultParamNum = 17; + cVideorecParamNum = cDefaultParamNum + 7; // message constants errmsgCreateSurface = 'Error creating SDL surface'; @@ -146,6 +147,7 @@ cBarrelHealth = 60; cShotgunRadius = 22; cBlowTorchC = 6; + cakeDmg = 75; cKeyMaxIndex = 1023; cKbdMaxIndex = 65536;//need more room for the modifier keys @@ -244,6 +246,7 @@ gmRemoveFromList = $00004000; gmAddToList = $00008000; + gmDelete = $00010000; gmAllStoppable = gmLeft or gmRight or gmUp or gmDown or gmAttack or gmPrecise; cMaxSlotIndex = 9; @@ -266,6 +269,7 @@ ammoprop_NeedUpDown = $00008000;//Used by TouchInterface to show or hide up/down widgets ammoprop_OscAim = $00010000; ammoprop_NoMoveAfter = $00020000; + ammoprop_Track = $00040000; ammoprop_NoRoundEnd = $10000000; AMMO_INFINITE = 100; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uFloat.pas --- a/hedgewars/uFloat.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uFloat.pas Thu Aug 30 13:02:19 2012 -0400 @@ -119,6 +119,7 @@ _0_005: hwFloat = (isNegative: false; QWordValue: 21474836); _0_008: hwFloat = (isNegative: false; QWordValue: 34359738); _0_01: hwFloat = (isNegative: false; QWordValue: 42949673); + _0_0128: hwFloat = (isNegative: false; QWordValue: 54975581); _0_02: hwFloat = (isNegative: false; QWordValue: 85899345); _0_03: hwFloat = (isNegative: false; QWordValue: 128849018); _0_07: hwFloat = (isNegative: false; QWordValue: 300647710); @@ -149,14 +150,18 @@ _0: hwFloat = (isNegative: false; QWordValue: 0); _1: hwFloat = (isNegative: false; QWordValue: 4294967296); _1_5: hwFloat = (isNegative: false; QWordValue: 4294967296 * 3 div 2); + _1_6: hwFloat = (isNegative: false; QWordValue: 4294967296 * 8 div 5); _1_9: hwFloat = (isNegative: false; QWordValue: 8160437862); _2: hwFloat = (isNegative: false; QWordValue: 4294967296 * 2); + _2_4: hwFloat = (isNegative: false; QWordValue: 4294967296 * 12 div 5); _3: hwFloat = (isNegative: false; QWordValue: 4294967296 * 3); _PI: hwFloat = (isNegative: false; QWordValue: 13493037704); _4: hwFloat = (isNegative: false; QWordValue: 4294967296 * 4); _4_5: hwFloat = (isNegative: false; QWordValue: 4294967296 * 9 div 2); _5: hwFloat = (isNegative: false; QWordValue: 4294967296 * 5); _6: hwFloat = (isNegative: false; QWordValue: 4294967296 * 6); + _6_4: hwFloat = (isNegative: false; QWordValue: 3435973837 * 8); + _7: hwFloat = (isNegative: false; QWordValue: 4294967296 * 7); _10: hwFloat = (isNegative: false; QWordValue: 4294967296 * 10); _12: hwFloat = (isNegative: false; QWordValue: 4294967296 * 12); _16: hwFloat = (isNegative: false; QWordValue: 4294967296 * 16); @@ -165,6 +170,8 @@ _25: hwFloat = (isNegative: false; QWordValue: 4294967296 * 25); _30: hwFloat = (isNegative: false; QWordValue: 4294967296 * 30); _40: hwFloat = (isNegative: false; QWordValue: 4294967296 * 40); + _41: hwFloat = (isNegative: false; QWordValue: 4294967296 * 41); + _49: hwFloat = (isNegative: false; QWordValue: 4294967296 * 49); _50: hwFloat = (isNegative: false; QWordValue: 4294967296 * 50); _70: hwFloat = (isNegative: false; QWordValue: 4294967296 * 70); _90: hwFloat = (isNegative: false; QWordValue: 4294967296 * 90); @@ -196,20 +203,21 @@ {$IFDEF FPC} -function int2hwFloat (const i: LongInt) : hwFloat; +function int2hwFloat (const i: LongInt) : hwFloat; inline; begin int2hwFloat.isNegative:= i < 0; int2hwFloat.Round:= abs(i); int2hwFloat.Frac:= 0 end; -function hwFloat2Float (const i: hwFloat) : extended; +function hwFloat2Float (const i: hwFloat) : extended; inline; begin -hwFloat2Float:= i.QWordValue / $100000000; +hwFloat2Float:= i.Frac / $100000000 + i.Round; if i.isNegative then hwFloat2Float:= -hwFloat2Float; end; +{$IFNDEF WEB} operator = (const z1, z2: hwFloat) z : boolean; inline; begin z:= (z1.isNegative = z2.isNegative) and (z1.QWordValue = z2.QWordValue); @@ -222,7 +230,7 @@ end; {$ENDIF} -operator + (const z1, z2: hwFloat) z : hwFloat; +operator + (const z1, z2: hwFloat) z : hwFloat; inline; begin if z1.isNegative = z2.isNegative then begin @@ -242,7 +250,7 @@ end end; -operator - (const z1, z2: hwFloat) z : hwFloat; +operator - (const z1, z2: hwFloat) z : hwFloat; inline; begin if z1.isNegative = z2.isNegative then if z1.QWordValue > z2.QWordValue then @@ -262,27 +270,150 @@ end end; -operator - (const z1: hwFloat) z : hwFloat; +function isZero(const z: hwFloat): boolean; inline; +begin +isZero := z.QWordValue = 0; +end; + +operator < (const z1, z2: hwFloat) b : boolean; inline; +begin +if z1.isNegative xor z2.isNegative then + b:= z1.isNegative +else + if z1.QWordValue = z2.QWordValue then + b:= false + else + b:= not((z1.QWordValue = z2.QWordValue) or ((z2.QWordValue < z1.QWordValue) <> z1.isNegative)) +end; + +operator > (const z1, z2: hwFloat) b : boolean; inline; +begin +if z1.isNegative xor z2.isNegative then + b:= z2.isNegative +else + if z1.QWordValue = z2.QWordValue then + b:= false + else + b:= (z1.QWordValue > z2.QWordValue) <> z2.isNegative +end; +{$ENDIF} +{$IFDEF WEB} +(* + Mostly to be kind to JS as of 2012-08-27 where there is no int64/uint64. This may change though. +*) +operator = (const z1, z2: hwFloat) z : boolean; inline; +begin + z:= (z1.isNegative = z2.isNegative) and (z1.Frac = z2.Frac) and (z1.Round = z2.Round); +end; + +operator <> (const z1, z2: hwFloat) z : boolean; inline; +begin + z:= (z1.isNegative <> z2.isNegative) or (z1.Frac <> z2.Frac) or (z1.Round <> z2.Round); +end; + +operator + (const z1, z2: hwFloat) z : hwFloat; inline; +begin +if z1.isNegative = z2.isNegative then + begin + z:= z1; + z.Frac:= z.Frac + z2.Frac; + z.Round:= z.Round + z2.Round; + if z.Frac z2.Round) or ((z1.Round = z2.Round) and (z1.Frac > z2.Frac)) then + begin + z.isNegative:= z1.isNegative; + z.Round:= z1.Round - z2.Round; + z.Frac:= z1.Frac - z2.Frac; + if z2.Frac > z1.Frac then dec(z.Round) + end + else + begin + z.isNegative:= z2.isNegative; + z.Round:= z2.Round - z1.Round; + z.Frac:= z2.Frac-z1.Frac; + if z2.Frac < z1.Frac then dec(z.Round) + end +end; + +operator - (const z1, z2: hwFloat) z : hwFloat; inline; +begin +if z1.isNegative = z2.isNegative then + if (z1.Round > z2.Round) or ((z1.Round = z2.Round) and (z1.Frac > z2.Frac)) then + begin + z.isNegative:= z1.isNegative; + z.Round:= z1.Round - z2.Round; + z.Frac:= z1.Frac-z2.Frac; + if z2.Frac > z1.Frac then dec(z.Round) + end + else + begin + z.isNegative:= not z2.isNegative; + z.Round:= z2.Round - z1.Round; + z.Frac:= z2.Frac-z1.Frac; + if z2.Frac < z1.Frac then dec(z.Round) + end +else + begin + z:= z1; + z.Frac:= z.Frac + z2.Frac; + z.Round:= z.Round + z2.Round; + if z.Frac z1.isNegative +end; + +operator > (const z1, z2: hwFloat) b : boolean; inline; +begin +if z1.isNegative xor z2.isNegative then + b:= z2.isNegative +else +(* + if z1.QWordValue = z2.QWordValue then + b:= false + else*) + b:= ((z1.Round > z2.Round) or ((z1.Round = z2.Round) and (z1.Frac > z2.Frac))) <> z1.isNegative +end; + +function isZero(const z: hwFloat): boolean; inline; +begin +isZero := z.Round = 0 and z.Frac = 0; +end; +{$ENDIF} + +operator - (const z1: hwFloat) z : hwFloat; inline; begin z:= z1; z.isNegative:= not z.isNegative end; -operator * (const z1, z2: hwFloat) z : hwFloat; +operator * (const z1, z2: hwFloat) z : hwFloat; inline; begin z.isNegative:= z1.isNegative xor z2.isNegative; z.QWordValue:= QWord(z1.Round) * z2.Frac + QWord(z1.Frac) * z2.Round + ((QWord(z1.Frac) * z2.Frac) shr 32); z.Round:= z.Round + QWord(z1.Round) * z2.Round; end; -operator * (const z1: hwFloat; const z2: LongInt) z : hwFloat; +operator * (const z1: hwFloat; const z2: LongInt) z : hwFloat; inline; begin z.isNegative:= z1.isNegative xor (z2 < 0); z.QWordValue:= z1.QWordValue * abs(z2) end; -operator / (const z1: hwFloat; z2: hwFloat) z : hwFloat; +operator / (const z1: hwFloat; z2: hwFloat) z : hwFloat; inline; var t: hwFloat; begin z.isNegative:= z1.isNegative xor z2.isNegative; @@ -304,34 +435,12 @@ end end; -operator / (const z1: hwFloat; const z2: LongInt) z : hwFloat; +operator / (const z1: hwFloat; const z2: LongInt) z : hwFloat; inline; begin z.isNegative:= z1.isNegative xor (z2 < 0); z.QWordValue:= z1.QWordValue div abs(z2) end; -operator < (const z1, z2: hwFloat) b : boolean; -begin -if z1.isNegative xor z2.isNegative then - b:= z1.isNegative -else - if z1.QWordValue = z2.QWordValue then - b:= false - else - b:= (z1.QWordValue < z2.QWordValue) xor z1.isNegative -end; - -operator > (const z1, z2: hwFloat) b : boolean; -begin -if z1.isNegative xor z2.isNegative then - b:= z2.isNegative -else - if z1.QWordValue = z2.QWordValue then - b:= false - else - b:= (z1.QWordValue > z2.QWordValue) xor z2.isNegative -end; - function cstr(const z: hwFloat): shortstring; var tmpstr: shortstring; begin @@ -360,7 +469,7 @@ hwAbs.isNegative:= false end; -function hwSqr(const t: hwFloat): hwFloat; +function hwSqr(const t: hwFloat): hwFloat; inline; begin hwSqr.isNegative:= false; hwSqr.QWordValue:= ((QWord(t.Round) * t.Round) shl 32) + QWord(t.Round) * t.Frac * 2 + ((QWord(t.Frac) * t.Frac) shr 32); @@ -369,7 +478,7 @@ function hwPow(const t: hwFloat;p: LongWord): hwFloat; begin hwPow:= t; -if p mod 2 = 0 then hwPow.isNegative:= t.isNegative; +if p mod 2 = 0 then hwPow.isNegative:= false; while p > 0 do begin @@ -461,11 +570,6 @@ else AngleCos.QWordValue:= SinTable[Angle - 1024] end; - -function isZero(const z: hwFloat): boolean; inline; -begin -isZero := z.QWordValue = 0; -end; {$ENDIF} end. diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uGame.pas --- a/hedgewars/uGame.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uGame.pas Thu Aug 30 13:02:19 2012 -0400 @@ -39,17 +39,27 @@ isInLag:= false; SendKeepAliveMessage(Lag) end; -if Lag > 100 then - Lag:= 100 -else if (GameType = gmtSave) or (fastUntilLag and (GameType = gmtNet)) then - Lag:= 2500; +if GameType <> gmtRecord then + begin + if Lag > 100 then + Lag:= 100 + else if (GameType = gmtSave) or (fastUntilLag and (GameType = gmtNet)) then + Lag:= 2500; -if (GameType = gmtDemo) then - if isSpeed then - Lag:= Lag * 10 - else - if cOnlyStats then - Lag:= High(LongInt); + if (GameType = gmtDemo) then + if isSpeed then + begin + i:= RealTicks-SpeedStart; + if i < 2000 then Lag:= Lag*5 + else if i < 4000 then Lag:= Lag*10 + else if i < 6000 then Lag:= Lag*20 + else if i < 8000 then Lag:= Lag*40 + else Lag:= Lag*80; + end + else + if cOnlyStats then + Lag:= High(LongInt); + end; PlayNextVoice; i:= 1; while (GameState <> gsExit) and (i <= Lag) do @@ -71,7 +81,7 @@ AddVisualGear(0, 0, vgtTeamHealthSorter); break; end; - gmtDemo: begin + gmtDemo, gmtRecord: begin GameState:= gsExit; exit end; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uGears.pas --- a/hedgewars/uGears.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uGears.pas Thu Aug 30 13:02:19 2012 -0400 @@ -59,7 +59,7 @@ uses uStore, uSound, uTeams, uRandom, uCollisions, uIO, uLandGraphics, uLocale, uAI, uAmmos, uStats, uVisualGears, uScript, GLunit, uMobile, uVariables, uCommands, uUtils, uTextures, uRenderUtils, uGearsRender, uCaptions, uDebug, uLandTexture, - uGearsHedgehog, uGearsUtils, uGearsList, uGearsHandlers; + uGearsHedgehog, uGearsUtils, uGearsList, uGearsHandlers, uGearsHandlersRope; var skipFlag: boolean; @@ -77,6 +77,7 @@ stAfterDelay, stChWin, stWater, stChWin2, stHealth, stSpawn, stNTurn); upd: Longword; + snowLeft,snowRight: LongInt; //SDMusic: shortstring; // For better maintainability the step handlers of gears are stored in @@ -206,23 +207,28 @@ curHandledGear:= t; t:= curHandledGear^.NextGear; - if curHandledGear^.Message and gmRemoveFromList <> 0 then - begin - RemoveGearFromList(curHandledGear); - // since I can't think of any good reason this would ever be separate from a remove from list, going to keep it inside this block - if curHandledGear^.Message and gmAddToList <> 0 then InsertGearToList(curHandledGear); - curHandledGear^.Message:= curHandledGear^.Message and not (gmRemoveFromList or gmAddToList) - end; - if curHandledGear^.Active then + if curHandledGear^.Message and gmDelete <> 0 then + DeleteGear(curHandledGear) + else begin - if curHandledGear^.RenderTimer and (curHandledGear^.Timer > 500) and ((curHandledGear^.Timer mod 1000) = 0) then + if curHandledGear^.Message and gmRemoveFromList <> 0 then + begin + RemoveGearFromList(curHandledGear); + // since I can't think of any good reason this would ever be separate from a remove from list, going to keep it inside this block + if curHandledGear^.Message and gmAddToList <> 0 then InsertGearToList(curHandledGear); + curHandledGear^.Message:= curHandledGear^.Message and (not (gmRemoveFromList or gmAddToList)) + end; + if curHandledGear^.Active then begin - FreeTexture(curHandledGear^.Tex); - curHandledGear^.Tex:= RenderStringTex(inttostr(curHandledGear^.Timer div 1000), cWhiteColor, fntSmall); - end; - curHandledGear^.doStep(curHandledGear); - // might be useful later - //ScriptCall('onGearStep', Gear^.uid); + if curHandledGear^.RenderTimer and (curHandledGear^.Timer > 500) and ((curHandledGear^.Timer mod 1000) = 0) then + begin + FreeTexture(curHandledGear^.Tex); + curHandledGear^.Tex:= RenderStringTex(inttostr(curHandledGear^.Timer div 1000), cWhiteColor, fntSmall); + end; + curHandledGear^.doStep(curHandledGear); + // might be useful later + //ScriptCall('onGearStep', Gear^.uid); + end end end; curHandledGear:= nil; @@ -634,18 +640,21 @@ if (GameFlags and gfArtillery) <> 0 then cArtillery:= true; - -for i:= 0 to GetRandom(10)+30 do - begin rx:= GetRandom(rightX-leftX)+leftX; +for i:= GetRandom(10)+30 downto 0 do + begin + rx:= GetRandom(rightX-leftX)+leftX; ry:= GetRandom(LAND_HEIGHT-topY)+topY; rdx:= _90-(GetRandomf*_360); rdy:= _90-(GetRandomf*_360); AddGear(rx, ry, gtGenericFaller, gstInvisible, rdx, rdy, $FFFFFFFF); end; +snowRight:= max(LAND_WIDTH,4096)+512; +snowLeft:= -(snowRight-LAND_WIDTH); + if not hasBorder and ((Theme = 'Snow') or (Theme = 'Christmas')) then - for i:= 0 to Pred(vobCount*2) do - AddGear(GetRandom(LAND_WIDTH+1024)-512, LAND_HEIGHT - GetRandom(LAND_HEIGHT div 2), gtFlake, 0, _0, _0, 0); + for i:= vobCount * max(LAND_WIDTH,4096) div 2048 downto 1 do + AddGear(GetRandom(snowRight-snowLeft)+snowLeft, LAND_HEIGHT-1300+GetRandom(750), gtFlake, 0, _0, _0, 0); end; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uGearsHandlers.pas --- a/hedgewars/uGearsHandlers.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uGearsHandlers.pas Thu Aug 30 13:02:19 2012 -0400 @@ -52,8 +52,8 @@ dA := hwSign(Gear^.dX); xx := dirs[Gear^.Angle].x; yy := dirs[Gear^.Angle].y; - xxn := dirs[(LongInt(Gear^.Angle) + 4 + dA) mod 4].x; - yyn := dirs[(LongInt(Gear^.Angle) + 4 + dA) mod 4].y; + xxn := dirs[(Gear^.Angle + dA) and 3].x; + yyn := dirs[(Gear^.Angle + dA) and 3].y; if (xx = 0) then if TestCollisionYwithGear(Gear, yy) <> 0 then diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uGearsHandlersRope.pas --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hedgewars/uGearsHandlersRope.pas Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,589 @@ +(* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + *) + +{$INCLUDE "options.inc"} +unit uGearsHandlersRope; +interface + +uses uTypes; + +procedure doStepRope(Gear: PGear); + +implementation +uses uConsts, uFloat, uCollisions, uVariables, uGearsList, uSound, uGearsUtils, + uAmmos, uDebug, uUtils, uGearsHedgehog, uGearsRender; + +procedure doStepRopeAfterAttack(Gear: PGear); +var + HHGear: PGear; +begin + HHGear := Gear^.Hedgehog^.Gear; + if ((HHGear^.State and gstHHDriven) = 0) + or (CheckGearDrowning(HHGear)) + or (TestCollisionYwithGear(HHGear, 1) <> 0) then + begin + DeleteGear(Gear); + isCursorVisible := false; + ApplyAmmoChanges(HHGear^.Hedgehog^); + exit + end; + + HedgehogChAngle(HHGear); + + if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then + SetLittle(HHGear^.dX); + + if HHGear^.dY.isNegative and (TestCollisionYwithGear(HHGear, -1) <> 0) then + HHGear^.dY := _0; + HHGear^.X := HHGear^.X + HHGear^.dX; + HHGear^.Y := HHGear^.Y + HHGear^.dY; + HHGear^.dY := HHGear^.dY + cGravity; + + if (GameFlags and gfMoreWind) <> 0 then + HHGear^.dX := HHGear^.dX + cWindSpeed / HHGear^.Density; + + if (Gear^.Message and gmAttack) <> 0 then + begin + Gear^.X := HHGear^.X; + Gear^.Y := HHGear^.Y; + + ApplyAngleBounds(Gear^.Hedgehog^, amRope); + + Gear^.dX := SignAs(AngleSin(HHGear^.Angle), HHGear^.dX); + Gear^.dY := -AngleCos(HHGear^.Angle); + Gear^.Friction := _4_5 * cRopePercent; + Gear^.Elasticity := _0; + Gear^.State := Gear^.State and (not gsttmpflag); + Gear^.doStep := @doStepRope; + end +end; + +procedure unstickHog(Gear, HHGear: PGear); +var i: LongInt; + stuck: Boolean; +begin + if (TestCollisionYwithGear(HHGear, 1) <> 0) and (TestCollisionYwithGear(HHGear, -1) = 0) then + begin + i:= 1; + repeat + begin + inc(i); + stuck:= TestCollisionYwithGear(HHGear, 1) <> 0; + if stuck then HHGear^.Y:= HHGear^.Y-_1 + end + until (i = 8) or (not stuck); + HHGear^.Y:= HHGear^.Y+_1; + // experiment in simulating something the shoppa players apparently expect + if Gear^.Message and gmDown <> 0 then + begin + //HHGear^.dY:= HHGear^.dY / 16; + //HHGear^.dY.QWordValue:= 0; + HHGear^.dY:= -_0_1; + HHGear^.dX:= HHGear^.dX * _1_5; + end; + if Gear^.Message and gmRight <> 0 then + HHGear^.dX.isNegative:= false + else if Gear^.Message and gmLeft <> 0 then + HHGear^.dX.isNegative:= true + end + else if (TestCollisionYwithGear(HHGear, -1) <> 0) and (TestCollisionYwithGear(HHGear, 1) = 0) then + begin + i:= 1; + repeat + begin + inc(i); + stuck:= TestCollisionYwithGear(HHGear, -1) <> 0; + if stuck then HHGear^.Y:= HHGear^.Y+_1 + end + until (i = 8) or (not stuck); + HHGear^.Y:= HHGear^.Y-_1; + if Gear^.Message and gmDown <> 0 then + begin + //HHGear^.dY:= HHGear^.dY / 16; + //HHGear^.dY.QWordValue:= 0; + HHGear^.dY:= _0_1; + HHGear^.dX:= HHGear^.dX * _1_5; + end; + if Gear^.Message and gmRight <> 0 then + HHGear^.dX.isNegative:= true + else if Gear^.Message and gmLeft <> 0 then + HHGear^.dX.isNegative:= false + end; + if TestCollisionXwithGear(HHGear, 1) and (not TestCollisionXwithGear(HHGear, -1)) then + begin + i:= 1; + repeat + begin + inc(i); + stuck:= TestCollisionXwithGear(HHGear, 1); + if stuck then HHGear^.X:= HHGear^.X-_1 + end + until (i = 8) or (not stuck); + HHGear^.X:= HHGear^.X+_1; + if Gear^.Message and gmDown <> 0 then + begin + //HHGear^.dX:= HHGear^.dX / 16; + //HHGear^.dX.QWordValue:= 0; + HHGear^.dX:= -_0_1; + HHGear^.dY:= HHGear^.dY * _1_5; + end; + if Gear^.Message and gmRight <> 0 then + HHGear^.dY.isNegative:= true + else if Gear^.Message and gmLeft <> 0 then + HHGear^.dY.isNegative:= false + end + else if TestCollisionXwithGear(HHGear, -1) and (not TestCollisionXwithGear(HHGear, 1)) then + begin + i:= 1; + repeat + begin + inc(i); + stuck:= TestCollisionXwithGear(HHGear, -1); + if stuck then HHGear^.X:= HHGear^.X+_1 + end + until (i = 8) or (not stuck); + HHGear^.X:= HHGear^.X-_1; + if Gear^.Message and gmDown <> 0 then + begin + //HHGear^.dX:= HHGear^.dX / 16; + //HHGear^.dX.QWordValue:= 0; + HHGear^.dX:= _0_1; + HHGear^.dY:= HHGear^.dY * _1_5; + end; + if Gear^.Message and gmRight <> 0 then + HHGear^.dY.isNegative:= false + else if Gear^.Message and gmLeft <> 0 then + HHGear^.dY.isNegative:= true + end +end; + +procedure RopeDeleteMe(Gear, HHGear: PGear); +begin + PlaySound(sndRopeRelease); + HHGear^.dX.QWordValue:= HHGear^.dX.QWordValue div Gear^.stepFreq; + HHGear^.dY.QWordValue:= HHGear^.dY.QWordValue div Gear^.stepFreq; + with HHGear^ do + begin + Message := Message and (not gmAttack); + State := (State or gstMoving) and (not gstWinner); + end; + unstickHog(Gear, HHGear); + DeleteGear(Gear) +end; + +procedure RopeWaitCollision(Gear, HHGear: PGear); +begin + PlaySound(sndRopeRelease); + with HHGear^ do + begin + Message := Message and (not gmAttack); + State := State or gstMoving; + end; + unstickHog(Gear, HHGear); + RopePoints.Count := 0; + Gear^.Elasticity := _0; + Gear^.doStep := @doStepRopeAfterAttack; + HHGear^.dX.QWordValue:= HHGear^.dX.QWordValue div Gear^.stepFreq; + HHGear^.dY.QWordValue:= HHGear^.dY.QWordValue div Gear^.stepFreq; + Gear^.stepFreq := 1 +end; + +procedure doStepRopeWork(Gear: PGear); +var + HHGear: PGear; + len, tx, ty, nx, ny, ropeDx, ropeDy, mdX, mdY, t: hwFloat; + lx, ly, cd, i: LongInt; + haveCollision, + haveDivided: boolean; + +begin + if GameTicks mod 8 <> 0 then exit; + + HHGear := Gear^.Hedgehog^.Gear; + haveCollision:= false; + if (Gear^.Message and gmLeft <> 0) and (not TestCollisionXwithGear(HHGear, -1)) then + HHGear^.dX := HHGear^.dX - _0_0128 + else haveCollision:= true; + + if (Gear^.Message and gmRight <> 0) and (not TestCollisionXwithGear(HHGear, 1)) then + HHGear^.dX := HHGear^.dX + _0_0128 + else haveCollision:= true; + + + if ((HHGear^.State and gstHHDriven) = 0) + or (CheckGearDrowning(HHGear)) or (Gear^.PortalCounter <> 0) then + begin + RopeDeleteMe(Gear, HHGear); + exit + end; + + // vector between hedgehog and rope attaching point + ropeDx := HHGear^.X - Gear^.X; + ropeDy := HHGear^.Y - Gear^.Y; + + if TestCollisionYwithGear(HHGear, 1) = 0 then + begin + + // depending on the rope vector we know which X-side to check for collision + // in order to find out if the hog can still be moved by gravity + if ropeDx.isNegative = RopeDy.IsNegative then + cd:= -1 + else + cd:= 1; + + // apply gravity if there is no obstacle + if not TestCollisionXwithGear(HHGear, cd) then + HHGear^.dY := HHGear^.dY + cGravity * 64; + + if (GameFlags and gfMoreWind) <> 0 then + // apply wind if there's no obstacle + if not TestCollisionXwithGear(HHGear, hwSign(cWindSpeed)) then + HHGear^.dX := HHGear^.dX + cWindSpeed * 64 / HHGear^.Density; + end + else haveCollision:= true; + + if ((Gear^.Message and gmDown) <> 0) and (Gear^.Elasticity < Gear^.Friction) then + if not (TestCollisionXwithGear(HHGear, hwSign(ropeDx)) + or (TestCollisionYwithGear(HHGear, hwSign(ropeDy)) <> 0)) then + Gear^.Elasticity := Gear^.Elasticity + _2_4 + else haveCollision:= true; + + if ((Gear^.Message and gmUp) <> 0) and (Gear^.Elasticity > _30) then + if not (TestCollisionXwithGear(HHGear, -hwSign(ropeDx)) + or (TestCollisionYwithGear(HHGear, -hwSign(ropeDy)) <> 0)) then + Gear^.Elasticity := Gear^.Elasticity - _2_4 + else haveCollision:= true; + +(* +I am not so sure this is useful. Disabling + if haveCollision then + begin + if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) and not TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then + HHGear^.dX.isNegative:= not HHGear^.dX.isNegative; + if (TestCollisionYwithGear(HHGear, hwSign(HHGear^.dY)) <> 0) and (TestCollisionYwithGear(HHGear, -hwSign(HHGear^.dY)) = 0) then + HHGear^.dY.isNegative:= not HHGear^.dY.isNegative; + end; +*) + + mdX := ropeDx + HHGear^.dX; + mdY := ropeDy + HHGear^.dY; + len := _1 / Distance(mdX, mdY); + // rope vector plus hedgehog direction vector normalized + mdX := mdX * len; + mdY := mdY * len; + + // for visual purposes only + Gear^.dX := mdX; + Gear^.dY := mdY; + + ///// + tx := HHGear^.X; + ty := HHGear^.Y; + + HHGear^.X := Gear^.X + mdX * Gear^.Elasticity; + HHGear^.Y := Gear^.Y + mdY * Gear^.Elasticity; + + HHGear^.dX := HHGear^.X - tx; + HHGear^.dY := HHGear^.Y - ty; + //// + + + haveDivided := false; + // check whether rope needs dividing + + len := Gear^.Elasticity - _5; + nx := Gear^.X + mdX * len; + ny := Gear^.Y + mdY * len; + tx := mdX * _2_4; // should be the same as increase step + ty := mdY * _2_4; + + while len > _3 do + begin + lx := hwRound(nx); + ly := hwRound(ny); + if ((ly and LAND_HEIGHT_MASK) = 0) and ((lx and LAND_WIDTH_MASK) = 0) and ((Land[ly, lx] and $FF00) <> 0) then + begin + ny := _1 / Distance(ropeDx, ropeDy); + // old rope pos + nx := ropeDx * ny; + ny := ropeDy * ny; + + with RopePoints.ar[RopePoints.Count] do + begin + X := Gear^.X; + Y := Gear^.Y; + if RopePoints.Count = 0 then + RopePoints.HookAngle := DxDy2Angle(Gear^.dY, Gear^.dX); + b := (nx * HHGear^.dY) > (ny * HHGear^.dX); + dLen := len + end; + + with RopePoints.rounded[RopePoints.Count] do + begin + X := hwRound(Gear^.X); + Y := hwRound(Gear^.Y); + end; + + Gear^.X := Gear^.X + nx * len; + Gear^.Y := Gear^.Y + ny * len; + inc(RopePoints.Count); + TryDo(RopePoints.Count <= MAXROPEPOINTS, 'Rope points overflow', true); + Gear^.Elasticity := Gear^.Elasticity - len; + Gear^.Friction := Gear^.Friction - len; + haveDivided := true; + break + end; + nx := nx - tx; + ny := ny - ty; + + // len := len - _2_4 // should be the same as increase step + len.QWordValue := len.QWordValue - _2_4.QWordValue; + end; + + if not haveDivided then + if RopePoints.Count > 0 then // check whether the last dividing point could be removed + begin + tx := RopePoints.ar[Pred(RopePoints.Count)].X; + ty := RopePoints.ar[Pred(RopePoints.Count)].Y; + mdX := tx - Gear^.X; + mdY := ty - Gear^.Y; + if RopePoints.ar[Pred(RopePoints.Count)].b xor (mdX * (ty - HHGear^.Y) > (tx - HHGear^.X) * mdY) then + begin + dec(RopePoints.Count); + Gear^.X := RopePoints.ar[RopePoints.Count].X; + Gear^.Y := RopePoints.ar[RopePoints.Count].Y; + Gear^.Elasticity := Gear^.Elasticity + RopePoints.ar[RopePoints.Count].dLen; + Gear^.Friction := Gear^.Friction + RopePoints.ar[RopePoints.Count].dLen; + + // restore hog position + len := _1 / Distance(mdX, mdY); + mdX := mdX * len; + mdY := mdY * len; + + HHGear^.X := Gear^.X - mdX * Gear^.Elasticity; + HHGear^.Y := Gear^.Y - mdY * Gear^.Elasticity; + end + end; + + haveCollision := false; + if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then + begin + HHGear^.dX := -_0_6 * HHGear^.dX; + haveCollision := true + end; + if TestCollisionYwithGear(HHGear, hwSign(HHGear^.dY)) <> 0 then + begin + HHGear^.dY := -_0_6 * HHGear^.dY; + haveCollision := true + end; + + if haveCollision and (Gear^.Message and (gmLeft or gmRight) <> 0) and (Gear^.Message and (gmUp or gmDown) <> 0) then + begin + HHGear^.dX := SignAs(hwAbs(HHGear^.dX) + _1_6, HHGear^.dX); + HHGear^.dY := SignAs(hwAbs(HHGear^.dY) + _1_6, HHGear^.dY) + end; + + len := hwSqr(HHGear^.dX) + hwSqr(HHGear^.dY); + if len > _49 then + begin + len := _7 / hwSqrt(len); + HHGear^.dX := HHGear^.dX * len; + HHGear^.dY := HHGear^.dY * len; + end; + + haveCollision:= ((hwRound(Gear^.Y) and LAND_HEIGHT_MASK) = 0) and ((hwRound(Gear^.X) and LAND_WIDTH_MASK) = 0) and ((Land[hwRound(Gear^.Y), hwRound(Gear^.X)]) <> 0); + + if not haveCollision then + begin + // backup gear location + tx:= Gear^.X; + ty:= Gear^.Y; + + if RopePoints.Count > 0 then + begin + // set gear location to the remote end of the rope, the attachment point + Gear^.X:= RopePoints.ar[0].X; + Gear^.Y:= RopePoints.ar[0].Y; + end; + + CheckCollision(Gear); + // if we haven't found any collision yet then check the other side too + if (Gear^.State and gstCollision) = 0 then + begin + Gear^.dX.isNegative:= not Gear^.dX.isNegative; + Gear^.dY.isNegative:= not Gear^.dY.isNegative; + CheckCollision(Gear); + Gear^.dX.isNegative:= not Gear^.dX.isNegative; + Gear^.dY.isNegative:= not Gear^.dY.isNegative; + end; + + haveCollision:= (Gear^.State and gstCollision) <> 0; + + // restore gear location + Gear^.X:= tx; + Gear^.Y:= ty; + end; + + // if the attack key is pressed, lose rope contact as well + if (Gear^.Message and gmAttack) <> 0 then + haveCollision:= false; + + if not haveCollision then + begin + if (Gear^.State and gsttmpFlag) <> 0 then + begin + if Gear^.Hedgehog^.CurAmmoType <> amParachute then + RopeWaitCollision(Gear, HHGear) + else + RopeDeleteMe(Gear, HHGear) + end + end + else + if (Gear^.State and gsttmpFlag) = 0 then + Gear^.State := Gear^.State or gsttmpFlag; +end; + +procedure RopeRemoveFromAmmo(Gear, HHGear: PGear); +begin + if (Gear^.State and gstAttacked) = 0 then + begin + OnUsedAmmo(HHGear^.Hedgehog^); + Gear^.State := Gear^.State or gstAttacked + end; + ApplyAmmoChanges(HHGear^.Hedgehog^) +end; + +procedure doStepRopeAttach(Gear: PGear); +var + HHGear: PGear; + tx, ty, tt: hwFloat; +begin + Gear^.X := Gear^.X - Gear^.dX; + Gear^.Y := Gear^.Y - Gear^.dY; + Gear^.Elasticity := Gear^.Elasticity + _1; + + HHGear := Gear^.Hedgehog^.Gear; + DeleteCI(HHGear); + + if (HHGear^.State and gstMoving) <> 0 then + begin + if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then + SetLittle(HHGear^.dX); + if HHGear^.dY.isNegative and (TestCollisionYwithGear(HHGear, -1) <> 0) then + HHGear^.dY := _0; + + HHGear^.X := HHGear^.X + HHGear^.dX; + Gear^.X := Gear^.X + HHGear^.dX; + + if TestCollisionYwithGear(HHGear, 1) <> 0 then + begin + CheckHHDamage(HHGear); + HHGear^.dY := _0 + //HHGear^.State:= HHGear^.State and (not (gstHHJumping or gstHHHJump)); + end + else + begin + HHGear^.Y := HHGear^.Y + HHGear^.dY; + Gear^.Y := Gear^.Y + HHGear^.dY; + HHGear^.dY := HHGear^.dY + cGravity; + if (GameFlags and gfMoreWind) <> 0 then + HHGear^.dX := HHGear^.dX + cWindSpeed / HHGear^.Density + end; + + tt := Gear^.Elasticity; + tx := _0; + ty := _0; + while tt > _20 do + begin + if ((hwRound(Gear^.Y+ty) and LAND_HEIGHT_MASK) = 0) and ((hwRound(Gear^.X+tx) and LAND_WIDTH_MASK) = 0) and ((Land[hwRound(Gear^.Y+ty), hwRound(Gear^.X+tx)] and $FF00) <> 0) then + begin + Gear^.X := Gear^.X + tx; + Gear^.Y := Gear^.Y + ty; + Gear^.Elasticity := tt; + Gear^.doStep := @doStepRopeWork; + Gear^.stepFreq:= 8; + PlaySound(sndRopeAttach); + with HHGear^ do + begin + dX.QWordValue:= dX.QWordValue shl 3; + dY.QWordValue:= dY.QWordValue shl 3; + State := State and (not (gstAttacking or gstHHJumping or gstHHHJump)); + Message := Message and (not gmAttack) + end; + + RopeRemoveFromAmmo(Gear, HHGear); + + tt := _0; + exit + end; + tx := tx + Gear^.dX + Gear^.dX; + ty := ty + Gear^.dY + Gear^.dY; + tt := tt - _2; + end; + end; + + if Gear^.Elasticity < _20 then Gear^.CollisionMask:= $FF00 + else Gear^.CollisionMask:= $FF7F; + CheckCollision(Gear); + + if (Gear^.State and gstCollision) <> 0 then + if Gear^.Elasticity < _10 then + Gear^.Elasticity := _10000 + else + begin + Gear^.doStep := @doStepRopeWork; + Gear^.stepFreq:= 8; + PlaySound(sndRopeAttach); + with HHGear^ do + begin + dX.QWordValue:= dX.QWordValue shl 3; + dY.QWordValue:= dY.QWordValue shl 3; + State := State and (not (gstAttacking or gstHHJumping or gstHHHJump)); + Message := Message and (not gmAttack) + end; + + RopeRemoveFromAmmo(Gear, HHGear); + + exit + end; + + if (Gear^.Elasticity > Gear^.Friction) + or ((Gear^.Message and gmAttack) = 0) + or ((HHGear^.State and gstHHDriven) = 0) + or (HHGear^.Damage > 0) then + begin + with Gear^.Hedgehog^.Gear^ do + begin + State := State and (not gstAttacking); + Message := Message and (not gmAttack) + end; + DeleteGear(Gear); + exit; + end; + if CheckGearDrowning(HHGear) then DeleteGear(Gear) +end; + +procedure doStepRope(Gear: PGear); +begin + Gear^.dX := - Gear^.dX; + Gear^.dY := - Gear^.dY; + Gear^.doStep := @doStepRopeAttach; + PlaySound(sndRopeShot) +end; + +end. diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uGearsHedgehog.pas --- a/hedgewars/uGearsHedgehog.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uGearsHedgehog.pas Thu Aug 30 13:02:19 2012 -0400 @@ -234,9 +234,9 @@ and ((Gear^.Message and gmLJump) <> 0) and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) then begin - newDx:= dX / _2; - newDy:= dY / _2; - altUse:= true; + newDx:= dX / CurAmmoGear^.stepFreq; + newDy:= dY / CurAmmoGear^.stepFreq; + altUse:= true end else begin @@ -260,10 +260,7 @@ amPickHammer: newGear:= AddGear(hwRound(lx), hwRound(ly) + cHHRadius, gtPickHammer, 0, _0, _0, 0); amSkip: ParseCommand('/skip', true); amRope: newGear:= AddGear(hwRound(lx), hwRound(ly), gtRope, 0, xx, yy, 0); - amMine: if altUse then - newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtMine, gstWait, newDx, newDy, 3000) - else - newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtMine, gstWait, SignAs(_0_02, dX), _0, 3000); + amMine: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtMine, gstWait, SignAs(_0_02, dX), _0, 3000); amSMine: newGear:= AddGear(hwRound(lx), hwRound(ly), gtSMine, 0, xx*Power/cPowerDivisor, yy*Power/cPowerDivisor, 0); amDEagle: newGear:= AddGear(hwRound(lx + xx * cHHRadius), hwRound(ly + yy * cHHRadius), gtDEagleShot, 0, xx * _0_5, yy * _0_5, 0); amSineGun: newGear:= AddGear(hwRound(lx + xx * cHHRadius), hwRound(ly + yy * cHHRadius), gtSineGunShot, 0, xx * _0_5, yy * _0_5, 0); @@ -360,6 +357,11 @@ amTardis: newGear:= AddGear(hwRound(X), hwRound(Y), gtTardis, 0, _0, _0, 5000); amIceGun: newGear:= AddGear(hwRound(X), hwRound(Y), gtIceGun, 0, _0, _0, 0); end; + if altUse then + begin + newGear^.dX:= newDx / newGear^.Density; + newGear^.dY:= newDY / newGear^.Density + end; case CurAmmoType of amGrenade, amMolotov, @@ -455,11 +457,13 @@ procedure AfterAttack; var s: shortstring; a: TAmmoType; + HHGear: PGear; begin -with CurrentHedgehog^.Gear^, CurrentHedgehog^ do +with CurrentHedgehog^ do begin + HHGear:= Gear; a:= CurAmmoType; - State:= State and (not gstAttacking); + if HHGear <> nil then HHGear^.State:= HHGear^.State and (not gstAttacking); if (Ammoz[a].Ammo.Propz and ammoprop_Effect) = 0 then begin Inc(MultiShootAttacks); @@ -484,8 +488,8 @@ TagTurnTimeLeft:= TurnTimeLeft; TurnTimeLeft:=(Ammoz[a].TimeAfterTurn * cGetAwayTime) div 100; end; - if ((Ammoz[a].Ammo.Propz and ammoprop_NoRoundEnd) = 0) then - State:= State or gstAttacked; + if ((Ammoz[a].Ammo.Propz and ammoprop_NoRoundEnd) = 0) and (HHGear <> nil) then + HHGear^.State:= HHGear^.State or gstAttacked; if (Ammoz[a].Ammo.Propz and ammoprop_NoRoundEnd) <> 0 then ApplyAmmoChanges(CurrentHedgehog^) end; @@ -570,7 +574,6 @@ var s: shortstring; vga: PVisualGear; begin - PlaySound(sndShotgunReload); if cnt <> 0 then AddAmmo(HH, ammo, cnt) else AddAmmo(HH, ammo); @@ -612,6 +615,7 @@ case Gear^.Pos of posCaseUtility, posCaseAmmo: begin + PlaySound(sndShotgunReload); if Gear^.AmmoType <> amNothing then begin AddPickup(HH^.Hedgehog^, Gear^.AmmoType, Gear^.Power, hwRound(Gear^.X), hwRound(Gear^.Y)); @@ -634,7 +638,7 @@ end; gi := gi^.NextGear end; - ag:= AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtAddAmmo, gstInvisible, _0, _0, GetRandom(200)+100); + ag:= AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtAddAmmo, gstInvisible, _0, _0, GetRandom(125)+25); ag^.Pos:= Gear^.Pos; ag^.Power:= Gear^.Power end; @@ -853,7 +857,7 @@ Gear^.State:= Gear^.State and (not gstMoving); exit end; -isFalling:= (Gear^.dY.isNegative) or not TestCollisionYKick(Gear, 1); +isFalling:= (Gear^.dY.isNegative) or (not TestCollisionYKick(Gear, 1)); if isFalling then begin if (Gear^.dY.isNegative) and TestCollisionYKick(Gear, -1) then @@ -983,7 +987,7 @@ begin Gear^.State:= Gear^.State and (not gstWinner); Gear^.State:= Gear^.State and (not gstMoving); - while (TestCollisionYWithGear(Gear,1) = 0) and not CheckGearDrowning(Gear) do + while (TestCollisionYWithGear(Gear,1) = 0) and (not CheckGearDrowning(Gear)) do Gear^.Y:= Gear^.Y+_1; SetLittle(Gear^.dX); Gear^.dY:= _0 @@ -1010,8 +1014,8 @@ if (CurrentHedgehog^.Gear = Gear) then isCursorVisible:= false end; - -if (hwAbs(Gear^.dY) > _0) and (Gear^.FlightTime > 0) and ((GameFlags and gfLowGravity) = 0) then +// IMO this should trigger homerun based on leftX/rightX + someval instead - that is 'knocking it out of the park' +if (not isZero(Gear^.dY)) and (Gear^.FlightTime > 0) and ((GameFlags and gfLowGravity) = 0) then begin inc(Gear^.FlightTime); if Gear^.FlightTime = 3000 then diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uGearsList.pas --- a/hedgewars/uGearsList.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uGearsList.pas Thu Aug 30 13:02:19 2012 -0400 @@ -105,6 +105,7 @@ // Define ammo association, if any. gear^.AmmoType:= GearKindAmmoTypeMap[Kind]; gear^.CollisionMask:= $FFFF; +gear^.stepFreq:= 1; if CurrentHedgehog <> nil then gear^.Hedgehog:= CurrentHedgehog; @@ -230,7 +231,7 @@ gear^.Radius:= 2; gear^.Elasticity:= _0_55; gear^.Friction:= _0_995; - gear^.Density:= _0_9; + gear^.Density:= _1; if cMinesTime < 0 then gear^.Timer:= getrandom(51)*100 else @@ -242,7 +243,7 @@ gear^.Radius:= 2; gear^.Elasticity:= _0_55; gear^.Friction:= _0_995; - gear^.Density:= _0_9; + gear^.Density:= _1_6; gear^.Timer:= 500; end; gtCase: begin diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uGearsRender.pas --- a/hedgewars/uGearsRender.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uGearsRender.pas Thu Aug 30 13:02:19 2012 -0400 @@ -376,26 +376,26 @@ hAngle:= 0; i:= -1 end; - if ((Gear^.State and gstWinner) = 0) then - begin - DrawHedgehog(ox, oy, - i, - 1, - 0, - DxDy2Angle(CurAmmoGear^.dY, CurAmmoGear^.dX) + dAngle); - with HH^ do - if (HatTex <> nil) then - begin - DrawTextureRotatedF(HatTex, 1.0, -1.0, -6.0, ox, oy, 0, i, 32, 32, - i*DxDy2Angle(CurAmmoGear^.dY, CurAmmoGear^.dX) + hAngle); - if HatTex^.w > 64 then + if ((Gear^.State and gstWinner) = 0) then + begin + DrawHedgehog(ox, oy, + i, + 1, + 0, + DxDy2Angle(CurAmmoGear^.dY, CurAmmoGear^.dX) + dAngle); + with HH^ do + if (HatTex <> nil) then begin - Tint(HH^.Team^.Clan^.Color shl 8 or $FF); - DrawTextureRotatedF(HatTex, 1.0, -1.0, -6.0, ox, oy, 32, i, 32, 32, + DrawTextureRotatedF(HatTex, 1.0, -1.0, -6.0, ox, oy, 0, i, 32, 32, i*DxDy2Angle(CurAmmoGear^.dY, CurAmmoGear^.dX) + hAngle); - Tint($FF, $FF, $FF, $FF) + if HatTex^.w > 64 then + begin + Tint(HH^.Team^.Clan^.Color shl 8 or $FF); + DrawTextureRotatedF(HatTex, 1.0, -1.0, -6.0, ox, oy, 32, i, 32, 32, + i*DxDy2Angle(CurAmmoGear^.dY, CurAmmoGear^.dX) + hAngle); + Tint($FF, $FF, $FF, $FF) + end end - end end; DrawAltWeapon(Gear, ox, oy); defaultPos:= false diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uGearsUtils.pas --- a/hedgewars/uGearsUtils.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uGearsUtils.pas Thu Aug 30 13:02:19 2012 -0400 @@ -38,6 +38,8 @@ function CheckGearNear(Gear: PGear; Kind: TGearType; rX, rY: LongInt): PGear; function CheckGearDrowning(Gear: PGear): boolean; +procedure CheckCollision(Gear: PGear); inline; +procedure CheckCollisionWithLand(Gear: PGear); inline; var doStepHandlers: array[TGearType] of TGearStepProcedure; @@ -106,7 +108,7 @@ // Run the calcs only once we know we have a type that will need damage tdX:= Gear^.X-fX; tdY:= Gear^.Y-fY; - if hwRound(hwAbs(tdX)+hwAbs(tdY)) < dmgBase then + if (tdX.Round + tdY.Round + 2) < dmgBase then dmg:= dmgBase - hwRound(Distance(tdX, tdY)); if dmg > 1 then begin @@ -140,7 +142,7 @@ // Run the calcs only once we know we have a type that will need damage tdX:= Gear^.X-fX; tdY:= Gear^.Y-fY; - if hwRound(hwAbs(tdX)+hwAbs(tdY)) < dmgBase then + if (tdX.Round + tdY.Round + 2) < dmgBase then dmg:= dmgBase - hwRound(Distance(tdX, tdY)); if dmg > 1 then begin @@ -239,9 +241,7 @@ end; end else if Gear^.Kind <> gtStructure then // not gtHedgehog nor gtStructure - begin Gear^.Hedgehog:= AttackerHog; - end; inc(Gear^.Damage, Damage); ScriptCall('onGearDamage', Gear^.UID, Damage); @@ -319,7 +319,8 @@ var dAngle: real; begin - dAngle := (Gear^.dX.QWordValue + Gear^.dY.QWordValue) / $80000000; +// Frac/Round to be kind to JS as of 2012-08-27 where there is yet no int64/uint64 + dAngle := (Gear^.dX.Round + Gear^.dY.Round) / 2 + (Gear^.dX.Frac+Gear^.dY.Frac) / $80000000; if not Gear^.dX.isNegative then Gear^.DirAngle := Gear^.DirAngle + dAngle else @@ -420,10 +421,9 @@ Scale:= hwFloat2Float(Gear^.Density / _3 * Gear^.dY); if Scale > 1 then Scale:= power(Scale,0.3333) else Scale:= Scale + ((1-Scale) / 2); - if Scale > 1 then Timer:= round(max(Scale,3)) + if Scale > 1 then Timer:= round(min(Scale*0.0005/cGravityf,4)) else Timer:= 1; // Low Gravity - Timer:=round(0.0005/cGravityf); FrameTicks:= FrameTicks*Timer; end; @@ -604,7 +604,7 @@ inc(cnt) end; - inc(y, 45) + inc(y, 10) end; if cnt > 0 then @@ -664,4 +664,22 @@ CheckGearNear:= nil end; +procedure CheckCollision(Gear: PGear); inline; +begin + if TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) + or (TestCollisionYwithGear(Gear, hwSign(Gear^.dY)) <> 0) then + Gear^.State := Gear^.State or gstCollision + else + Gear^.State := Gear^.State and (not gstCollision) +end; + +procedure CheckCollisionWithLand(Gear: PGear); inline; +begin + if TestCollisionX(Gear, hwSign(Gear^.dX)) + or TestCollisionY(Gear, hwSign(Gear^.dY)) then + Gear^.State := Gear^.State or gstCollision + else + Gear^.State := Gear^.State and (not gstCollision) +end; + end. diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uIO.pas --- a/hedgewars/uIO.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uIO.pas Thu Aug 30 13:02:19 2012 -0400 @@ -126,6 +126,7 @@ 'D': GameType:= gmtDemo; 'N': GameType:= gmtNet; 'S': GameType:= gmtSave; + 'V': GameType:= gmtRecord; else OutError(errmsgIncorrectUse + ' IPC "T" :' + s[2], true) end; 'V': begin if s[2] = '.' then @@ -406,7 +407,7 @@ TargetPoint.Y:= putY end; AddFileLog('put: ' + inttostr(TargetPoint.X) + ', ' + inttostr(TargetPoint.Y)); - State:= State and not gstHHChooseTarget; + State:= State and (not gstHHChooseTarget); if (Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AttackingPut) <> 0 then Message:= Message or (gmAttack and InputMask); end diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uInputHandler.pas --- a/hedgewars/uInputHandler.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uInputHandler.pas Thu Aug 30 13:02:19 2012 -0400 @@ -25,8 +25,9 @@ procedure initModule; procedure freeModule; -function KeyNameToCode(name: shortstring; Modifier: shortstring = ''): LongInt; -procedure MaskModifier(var code: LongInt; modifier: LongWord); +function KeyNameToCode(name: shortstring): LongInt; inline; +function KeyNameToCode(name: shortstring; Modifier: shortstring): LongInt; +//procedure MaskModifier(var code: LongInt; modifier: LongWord); procedure MaskModifier(Modifier: shortstring; var code: LongInt); procedure ProcessMouse(event: TSDL_MouseButtonEvent; ButtonDown: boolean); procedure ProcessKey(event: TSDL_KeyboardEvent); inline; @@ -60,6 +61,11 @@ KeyNames: array [0..cKeyMaxIndex] of string[15]; CurrentBinds: TBinds; +function KeyNameToCode(name: shortstring): LongInt; inline; +begin + KeyNameToCode:= KeyNameToCode(name, ''); +end; + function KeyNameToCode(name: shortstring; Modifier: shortstring): LongInt; var code: LongInt; begin @@ -70,7 +76,7 @@ MaskModifier(Modifier, code); KeyNameToCode:= code; end; - +(* procedure MaskModifier(var code: LongInt; Modifier: LongWord); begin if(Modifier and KMOD_LSHIFT) <> 0 then code:= code or LSHIFT; @@ -80,7 +86,7 @@ if(Modifier and KMOD_LCTRL) <> 0 then code:= code or LCTRL; if(Modifier and KMOD_RCTRL) <> 0 then code:= code or LCTRL; end; - +*) procedure MaskModifier(Modifier: shortstring; var code: LongInt); var mod_ : shortstring; ModifierCount, i: LongInt; @@ -133,7 +139,7 @@ if CurrentBinds[code][0] <> #0 then begin - if (code > 3) and KeyDown and not ((CurrentBinds[code] = 'put') or (CurrentBinds[code] = 'ammomenu') or (CurrentBinds[code] = '+cur_u') or (CurrentBinds[code] = '+cur_d') or (CurrentBinds[code] = '+cur_l') or (CurrentBinds[code] = '+cur_r')) then hideAmmoMenu:= true; + if (code > 3) and KeyDown and (not ((CurrentBinds[code] = 'put')) or (CurrentBinds[code] = 'ammomenu') or (CurrentBinds[code] = '+cur_u') or (CurrentBinds[code] = '+cur_d') or (CurrentBinds[code] = '+cur_l') or (CurrentBinds[code] = '+cur_r')) then hideAmmoMenu:= true; if KeyDown then begin @@ -250,6 +256,7 @@ DefaultBinds[KeyNameToCode(_S'9')]:= '+voldown'; DefaultBinds[KeyNameToCode(_S'8')]:= 'mute'; DefaultBinds[KeyNameToCode(_S'c')]:= 'capture'; +DefaultBinds[KeyNameToCode(_S'r')]:= 'record'; DefaultBinds[KeyNameToCode(_S'h')]:= 'findhh'; DefaultBinds[KeyNameToCode(_S'p')]:= 'pause'; DefaultBinds[KeyNameToCode(_S's')]:= '+speedup'; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uLand.pas --- a/hedgewars/uLand.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uLand.pas Thu Aug 30 13:02:19 2012 -0400 @@ -20,7 +20,7 @@ unit uLand; interface -uses SDLh, uLandTemplates, uFloat, uConsts, GLunit, uTypes; +uses SDLh, uLandTemplates, uFloat, uConsts, GLunit, uTypes, uAILandMarks; procedure initModule; procedure freeModule; @@ -35,6 +35,28 @@ var digest: shortstring; +procedure ResizeLand(width, height: LongWord); +var potW, potH: LongWord; +begin +potW:= toPowerOf2(width); +potH:= toPowerOf2(height); +if (potW <> LAND_WIDTH) or (potH <> LAND_HEIGHT) then + begin + LAND_WIDTH:= potW; + LAND_HEIGHT:= potH; + LAND_WIDTH_MASK:= not(LAND_WIDTH-1); + LAND_HEIGHT_MASK:= not(LAND_HEIGHT-1); + cWaterLine:= LAND_HEIGHT; + if (cReducedQuality and rqBlurryLand) = 0 then + SetLength(LandPixels, LAND_HEIGHT, LAND_WIDTH) + else + SetLength(LandPixels, LAND_HEIGHT div 2, LAND_WIDTH div 2); + + SetLength(Land, LAND_HEIGHT, LAND_WIDTH); + SetLength(LandDirty, (LAND_HEIGHT div 32), (LAND_WIDTH div 32)); + end; +end; + procedure ColorizeLand(Surface: PSDL_Surface); var tmpsurf: PSDL_Surface; r, rr: TSDL_Rect; @@ -181,6 +203,7 @@ i: Longword; y, x: Longword; begin + ResizeLand(Template.TemplateWidth, Template.TemplateHeight); for y:= 0 to LAND_HEIGHT - 1 do for x:= 0 to LAND_WIDTH - 1 do Land[y, x]:= lfBasic; @@ -237,6 +260,7 @@ procedure GenDrawnMap; begin + ResizeLand(4096, 2048); uLandPainted.Draw; MaxHedgehogs:= 48; @@ -260,6 +284,11 @@ 3: SelectTemplate:= LargeTemplates[getrandom(Succ(High(LargeTemplates)))]; 4: SelectTemplate:= CavernTemplates[getrandom(Succ(High(CavernTemplates)))]; 5: SelectTemplate:= WackyTemplates[getrandom(Succ(High(WackyTemplates)))]; +// For lua only! + 6: begin + SelectTemplate:= min(LuaTemplateNumber,High(EdgeTemplates)); + GetRandom(2) // burn 1 + end; end; WriteLnToConsole('Selected template #'+inttostr(SelectTemplate)+' using filter #'+inttostr(cTemplateFilter)); @@ -299,7 +328,7 @@ WriteLnToConsole('Generating land...'); case cMapGen of 0: GenBlank(EdgeTemplates[SelectTemplate]); - 1: GenMaze; + 1: begin ResizeLand(4096,2048); GenMaze; end; 2: GenDrawnMap; else OutError('Unknown mapgen', true); @@ -489,7 +518,10 @@ 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); +// (bare) Sanity check. Considering possible LongInt comparisons as well as just how much system memoery it would take +TryDo((tmpsurf^.w < $40000000) and (tmpsurf^.h < $40000000) and (tmpsurf^.w * tmpsurf^.h < 6*1024*1024*1024), 'Map dimensions too big!', true); + +ResizeLand(tmpsurf^.w, tmpsurf^.h); // unC0Rr - should this be passed from the GUI? I am not sure which layer does what s:= UserPathz[ptMapCurrent] + '/map.cfg'; @@ -581,7 +613,7 @@ if Land[y, x] <> 0 then begin inc(c); - if c > 200 then // avoid accidental triggering + if c > 1000 then // avoid accidental triggering begin hasBorder:= true; break; @@ -676,24 +708,35 @@ LandPixels[y,x]:= w or (LandPixels[y div 2, x div 2] and AMask) end end; - -UpdateLandTexture(0, LAND_WIDTH, 0, LAND_HEIGHT, false); end; procedure GenPreview(out Preview: TPreview); -var x, y, xx, yy, t, bit, cbit, lh, lw: LongInt; +var rh, rw, ox, oy, x, y, xx, yy, t, bit, cbit, lh, lw: LongInt; begin WriteLnToConsole('Generating preview...'); case cMapGen of 0: GenBlank(EdgeTemplates[SelectTemplate]); - 1: GenMaze; + 1: begin ResizeLand(4096,2048); GenMaze; end; 2: GenDrawnMap; else OutError('Unknown mapgen', true); end; - lh:= LAND_HEIGHT div 128; - lw:= LAND_WIDTH div 32; + // strict scaling needed here since preview assumes a rectangle + rh:= max(LAND_HEIGHT,2048); + rw:= max(LAND_WIDTH,4096); + ox:= 0; + if rw < rh*2 then + begin + rw:= rh*2; + end; + if rh < rw div 2 then rh:= rw * 2; + + ox:= (rw-LAND_WIDTH) div 2; + oy:= rh-LAND_HEIGHT; + + lh:= rh div 128; + lw:= rw div 32; for y:= 0 to 127 do for x:= 0 to 31 do begin @@ -704,7 +747,8 @@ cbit:= bit * 8; for yy:= y * lh to y * lh + 7 do for xx:= x * lw + cbit to x * lw + cbit + 7 do - if Land[yy, xx] <> 0 then + if ((yy-oy) and LAND_HEIGHT_MASK = 0) and ((xx-ox) and LAND_WIDTH_MASK = 0) + and (Land[yy-oy, xx-ox] <> 0) then inc(t); if t > 8 then Preview[y, x]:= Preview[y, x] or ($80 shr bit); @@ -741,7 +785,9 @@ LandBackSurface:= nil; digest:= ''; - + LAND_WIDTH:= 0; + LAND_HEIGHT:= 0; +(* if (cReducedQuality and rqBlurryLand) = 0 then SetLength(LandPixels, LAND_HEIGHT, LAND_WIDTH) else @@ -749,6 +795,7 @@ SetLength(Land, LAND_HEIGHT, LAND_WIDTH); SetLength(LandDirty, (LAND_HEIGHT div 32), (LAND_WIDTH div 32)); +*) end; procedure freeModule; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uLandGraphics.pas --- a/hedgewars/uLandGraphics.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uLandGraphics.pas Thu Aug 30 13:02:19 2012 -0400 @@ -59,7 +59,7 @@ begin addBgColor:= NewColor; exit - end; + end; // Get colors oRed := (OldColor shr RShift); oGreen := (OldColor shr GShift); @@ -72,7 +72,7 @@ // Mix colors nRed := min(255,((nRed*nAlpha) div 255) + ((oRed*oAlpha*byte(255-nAlpha)) div 65025)); nGreen := min(255,((nGreen*nAlpha) div 255) + ((oGreen*oAlpha*byte(255-nAlpha)) div 65025)); - nBlue := min(255,((nBlue*nAlpha) div 255) + ((oBlue*oAlpha*byte(255-nAlpha)) div 65025)); + nBlue := min(255,((nBlue*nAlpha) div 255) + ((oBlue*oAlpha*byte(255-nAlpha)) div 65025)); nAlpha := min(255, oAlpha + nAlpha); addBgColor := (nAlpha shl AShift) or (nRed shl RShift) or (nGreen shl GShift) or (nBlue shl BShift); @@ -106,25 +106,25 @@ begin if ((y + dy) and LAND_HEIGHT_MASK) = 0 then for i:= Max(x - dx, 0) to Min(x + dx, LAND_WIDTH - 1) do - if isCurrent then + if isCurrent then Land[y + dy, i]:= Land[y + dy, i] and $FF7F else if Land[y + dy, i] and $007F > 0 then Land[y + dy, i]:= (Land[y + dy, i] and $FF80) or ((Land[y + dy, i] and $7F) - 1); if ((y - dy) and LAND_HEIGHT_MASK) = 0 then for i:= Max(x - dx, 0) to Min(x + dx, LAND_WIDTH - 1) do - if isCurrent then + if isCurrent then Land[y - dy, i]:= Land[y - dy, i] and $FF7F else if Land[y - dy, i] and $007F > 0 then Land[y - dy, i]:= (Land[y - dy, i] and $FF80) or ((Land[y - dy, i] and $7F) - 1); if ((y + dx) and LAND_HEIGHT_MASK) = 0 then for i:= Max(x - dy, 0) to Min(x + dy, LAND_WIDTH - 1) do - if isCurrent then + if isCurrent then Land[y + dx, i]:= Land[y + dx, i] and $FF7F else if Land[y + dx, i] and $007F > 0 then Land[y + dx, i]:= (Land[y + dx, i] and $FF80) or ((Land[y + dx, i] and $7F) - 1); if ((y - dx) and LAND_HEIGHT_MASK) = 0 then for i:= Max(x - dy, 0) to Min(x + dy, LAND_WIDTH - 1) do - if isCurrent then + if isCurrent then Land[y - dx, i]:= Land[y - dx, i] and $FF7F else if Land[y - dx, i] and $007F > 0 then Land[y - dx, i]:= (Land[y - dx, i] and $FF80) or ((Land[y - dx, i] and $7F) - 1) @@ -133,27 +133,27 @@ begin if ((y + dy) and LAND_HEIGHT_MASK) = 0 then for i:= Max(x - dx, 0) to Min(x + dx, LAND_WIDTH - 1) do - if isCurrent then + if isCurrent then Land[y + dy, i]:= Land[y + dy, i] or $80 else if Land[y + dy, i] and $007F < 127 then Land[y + dy, i]:= (Land[y + dy, i] and $FF80) or ((Land[y + dy, i] and $7F) + 1); - if ((y - dy) and LAND_HEIGHT_MASK) = 0 then - for i:= Max(x - dx, 0) to Min(x + dx, LAND_WIDTH - 1) do - if isCurrent then - Land[y - dy, i]:= Land[y - dy, i] or $80 - else if Land[y - dy, i] and $007F < 127 then + if ((y - dy) and LAND_HEIGHT_MASK) = 0 then + for i:= Max(x - dx, 0) to Min(x + dx, LAND_WIDTH - 1) do + if isCurrent then + Land[y - dy, i]:= Land[y - dy, i] or $80 + else if Land[y - dy, i] and $007F < 127 then Land[y - dy, i]:= (Land[y - dy, i] and $FF80) or ((Land[y - dy, i] and $7F) + 1); - if ((y + dx) and LAND_HEIGHT_MASK) = 0 then - for i:= Max(x - dy, 0) to Min(x + dy, LAND_WIDTH - 1) do - if isCurrent then - Land[y + dx, i]:= Land[y + dx, i] or $80 - else if Land[y + dx, i] and $007F < 127 then + if ((y + dx) and LAND_HEIGHT_MASK) = 0 then + for i:= Max(x - dy, 0) to Min(x + dy, LAND_WIDTH - 1) do + if isCurrent then + Land[y + dx, i]:= Land[y + dx, i] or $80 + else if Land[y + dx, i] and $007F < 127 then Land[y + dx, i]:= (Land[y + dx, i] and $FF80) or ((Land[y + dx, i] and $7F) + 1); - if ((y - dx) and LAND_HEIGHT_MASK) = 0 then - for i:= Max(x - dy, 0) to Min(x + dy, LAND_WIDTH - 1) do - if isCurrent then - Land[y - dx, i]:= Land[y - dx, i] or $80 - else if Land[y - dx, i] and $007F < 127 then + if ((y - dx) and LAND_HEIGHT_MASK) = 0 then + for i:= Max(x - dy, 0) to Min(x + dy, LAND_WIDTH - 1) do + if isCurrent then + Land[y - dx, i]:= Land[y - dx, i] or $80 + else if Land[y - dx, i] and $007F < 127 then Land[y - dx, i]:= (Land[y - dx, i] and $FF80) or ((Land[y - dx, i] and $7F) + 1) end end; @@ -266,7 +266,7 @@ inc(cnt); LandPixels[by, bx]:= LandBackPixel(i, t) end - else if ((Land[t, i] and lfObject) <> 0) or (((LandPixels[by,bx] and AMask) shr AShift) < 255) then + else if ((Land[t, i] and lfObject) <> 0) or (((LandPixels[by,bx] and AMask) shr AShift) < 255) then LandPixels[by, bx]:= 0 end; @@ -288,7 +288,7 @@ inc(cnt); LandPixels[by, bx]:= LandBackPixel(i, t) end - else if ((Land[t, i] and lfObject) <> 0) or (((LandPixels[by,bx] and AMask) shr AShift) < 255) then + else if ((Land[t, i] and lfObject) <> 0) or (((LandPixels[by,bx] and AMask) shr AShift) < 255) then LandPixels[by, bx]:= 0 end; @@ -310,7 +310,7 @@ inc(cnt); LandPixels[by, bx]:= LandBackPixel(i, t) end - else if ((Land[t, i] and lfObject) <> 0) or (((LandPixels[by,bx] and AMask) shr AShift) < 255) then + else if ((Land[t, i] and lfObject) <> 0) or (((LandPixels[by,bx] and AMask) shr AShift) < 255) then LandPixels[by, bx]:= 0 end; t:= y - dx; @@ -331,7 +331,7 @@ inc(cnt); LandPixels[by, bx]:= LandBackPixel(i, t) end - else if ((Land[t, i] and lfObject) <> 0) or (((LandPixels[by,bx] and AMask) shr AShift) < 255) then + else if ((Land[t, i] and lfObject) <> 0) or (((LandPixels[by,bx] and AMask) shr AShift) < 255) then LandPixels[by, bx]:= 0 end; FillLandCircleLinesBG:= cnt; @@ -504,7 +504,7 @@ end; if ((Land[ty, tx] and lfBasic) <> 0) and (((LandPixels[by,bx] and AMask) shr AShift) = 255) and (not disableLandBack) then LandPixels[by, bx]:= LandBackPixel(tx, ty) - else if ((Land[ty, tx] and lfObject) <> 0) or (((LandPixels[by,bx] and AMask) shr AShift) < 255) then + else if ((Land[ty, tx] and lfObject) <> 0) or (((LandPixels[by,bx] and AMask) shr AShift) < 255) then LandPixels[by, bx]:= 0 end end; @@ -567,7 +567,7 @@ and ((tx and LAND_WIDTH_MASK) = 0) and (((Land[ty, tx] and lfBasic) <> 0) or ((Land[ty, tx] and lfObject) <> 0)) then begin - if despeckle then + if despeckle then begin Land[ty, tx]:= Land[ty, tx] or lfDamaged; LandDirty[ty div 32, tx div 32]:= 1 @@ -837,7 +837,7 @@ procedure Smooth(X, Y: LongInt); begin // a bit of AA for explosions -if (Land[Y, X] = 0) and (Y > LongInt(topY) + 1) and +if (Land[Y, X] = 0) and (Y > LongInt(topY) + 1) and (Y < LAND_HEIGHT-2) and (X > LongInt(leftX) + 1) and (X < LongInt(rightX) - 1) then begin if ((((Land[y, x-1] and lfDamaged) <> 0) and (((Land[y+1,x] and lfDamaged) <> 0)) or ((Land[y-1,x] and lfDamaged) <> 0)) diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uLandObjects.pas --- a/hedgewars/uLandObjects.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uLandObjects.pas Thu Aug 30 13:02:19 2012 -0400 @@ -501,7 +501,7 @@ c2.g:= t; c2.b:= t end; - ExplosionBorderColor:= c2.value or AMask; + ExplosionBorderColor:= (c2.r shl RShift) or (c2.g shl GShift) or (c2.b shl BShift) or AMask; end else if key = 'water-top' then begin @@ -554,7 +554,7 @@ SetMusicName(Trim(s)) else if key = 'clouds' then begin - cCloudsNumber:= Word(StrToInt(Trim(s))) * cScreenSpace div LAND_WIDTH; + cCloudsNumber:= Word(StrToInt(Trim(s))) * cScreenSpace div 4096; cSDCloudsNumber:= cCloudsNumber end else if key = 'object' then @@ -700,7 +700,7 @@ else if key = 'sd-water-opacity' then SDWaterOpacity:= StrToInt(Trim(s)) else if key = 'sd-clouds' then - cSDCloudsNumber:= Word(StrToInt(Trim(s))) * cScreenSpace div LAND_WIDTH + cSDCloudsNumber:= Word(StrToInt(Trim(s))) * cScreenSpace div 4096 else if key = 'sd-flakes' then begin i:= Pos(',', s); diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uLandPainted.pas --- a/hedgewars/uLandPainted.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uLandPainted.pas Thu Aug 30 13:02:19 2012 -0400 @@ -58,9 +58,9 @@ rec.X:= SDLNet_Read16(@rec.X); rec.Y:= SDLNet_Read16(@rec.Y); if rec.X < -318 then rec.X:= -318; - if rec.X > LAND_WIDTH+318 then rec.X:= LAND_WIDTH+318; + if rec.X > 4096+318 then rec.X:= 4096+318; if rec.Y < -318 then rec.Y:= -318; - if rec.Y > LAND_HEIGHT+318 then rec.Y:= LAND_HEIGHT+318; + if rec.Y > 2048+318 then rec.Y:= 2048+318; new(pe); if pointsListLast = nil then diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uLandTemplates.pas --- a/hedgewars/uLandTemplates.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uLandTemplates.pas Thu Aug 30 13:02:19 2012 -0400 @@ -1457,92 +1457,92 @@ // Many islands const Template41Points: array[0..86] of TSDL_Rect = ( - (x: 95; y: 500; w: 1; h: 1), - (x: 100; y: 275; w: 25; h: 100), - (x: 325; y: 275; w: 25; h: 100), - (x: 330; y: 500; w: 1; h: 1), + (x: 95; y: 500; w: 26; h: 26), + (x: 100; y: 275; w: 50; h: 125), + (x: 325; y: 275; w: 50; h: 125), + (x: 330; y: 500; w: 26; h: 26), (x: NTPX; y: 0; w: 1; h: 1), - (x: 725; y: 125; w: 1; h: 1), - (x: 725; y: 25; w: 5; h: 25), - (x: 825; y: 35; w: 5; h: 10), - (x: 825; y: 135; w: 1; h: 1), + (x: 725; y: 125; w: 26; h: 26), + (x: 725; y: 25; w: 30; h: 50), + (x: 825; y: 35; w: 30; h: 35), + (x: 825; y: 135; w: 26; h: 26), (x: NTPX; y: 0; w: 1; h: 1), - (x: 1150; y: 550; w: 25; h: 50), - (x: 1250; y: 300; w: 25; h: 50), - (x: 1350; y: 300; w: 25; h: 50), - (x: 1400; y: 575; w: 25; h: 50), + (x: 1150; y: 550; w: 50; h: 75), + (x: 1250; y: 300; w: 50; h: 75), + (x: 1350; y: 300; w: 50; h: 75), + (x: 1400; y: 575; w: 50; h: 75), (x: NTPX; y: 0; w: 1; h: 1), - (x: 525; y:1050; w: 50; h: 50), - (x: 700; y: 800; w: 100; h: 150), - (x: 950; y: 900; w: 100; h: 150), - (x: 1100; y:1100; w: 50; h: 50), + (x: 525; y:1050; w: 75; h: 75), + (x: 700; y: 800; w: 125; h: 175), + (x: 950; y: 900; w: 125; h: 175), + (x: 1100; y:1100; w: 75; h: 75), (x: NTPX; y: 0; w: 1; h: 1), - (x: 175; y:1500; w: 1; h: 1), - (x: 210; y:1400; w: 5; h: 25), - (x: 240; y:1400; w: 5; h: 25), - (x: 275; y:1510; w: 1; h: 1), + (x: 175; y:1500; w: 26; h: 26), + (x: 210; y:1400; w: 30; h: 50), + (x: 240; y:1400; w: 30; h: 50), + (x: 275; y:1510; w: 26; h: 26), (x: NTPX; y: 0; w: 1; h: 1), - (x: 450; y:1800; w: 100; h: 100), - (x: 600; y:1750; w: 100; h: 100), - (x: 750; y:1750; w: 100; h: 100), - (x: 950; y:1850; w: 100; h: 100), + (x: 450; y:1800; w: 125; h: 125), + (x: 600; y:1750; w: 125; h: 125), + (x: 750; y:1750; w: 125; h: 125), + (x: 950; y:1850; w: 125; h: 125), (x: NTPX; y: 0; w: 1; h: 1), - (x: 1075; y:1450; w: 1; h: 1), - (x: 1110; y:1300; w: 5; h: 25), - (x: 1140; y:1300; w: 5; h: 25), - (x: 1175; y:1430; w: 1; h: 1), + (x: 1075; y:1450; w: 26; h: 26), + (x: 1110; y:1300; w: 30; h: 50), + (x: 1140; y:1300; w: 30; h: 50), + (x: 1175; y:1430; w: 26; h: 26), (x: NTPX; y: 0; w: 1; h: 1), - (x: 1600; y:1250; w: 25; h: 100), - (x: 1700; y:1150; w: 25; h: 100), - (x: 1850; y: 500; w: 50; h: 100), - (x: 1950; y: 550; w: 50; h: 150), - (x: 2250; y:1150; w: 25; h: 100), - (x: 2350; y:1250; w: 25; h: 100), + (x: 1600; y:1250; w: 50; h: 125), + (x: 1700; y:1150; w: 50; h: 125), + (x: 1850; y: 500; w: 75; h: 125), + (x: 1950; y: 550; w: 75; h: 175), + (x: 2250; y:1150; w: 50; h: 125), + (x: 2350; y:1250; w: 50; h: 125), (x: NTPX; y: 0; w: 1; h: 1), - (x: 1750; y:2010; w: 1; h: 1), - (x: 1900; y:1870; w: 50; h: 50), - (x: 2050; y:1870; w: 50; h: 50), - (x: 2175; y:2010; w: 1; h: 1), + (x: 1750; y:2010; w: 26; h: 26), + (x: 1900; y:1870; w: 75; h: 75), + (x: 2050; y:1870; w: 75; h: 75), + (x: 2175; y:2010; w: 26; h: 26), (x: NTPX; y: 0; w: 1; h: 1), - (x: 2500; y:1700; w: 1; h: 1), - (x: 2575; y:1500; w: 10; h: 50), - (x: 2650; y:1500; w: 10; h: 50), - (x: 2700; y:1690; w: 1; h: 1), + (x: 2500; y:1700; w: 26; h: 26), + (x: 2575; y:1500; w: 35; h: 75), + (x: 2650; y:1500; w: 35; h: 75), + (x: 2700; y:1690; w: 26; h: 26), (x: NTPX; y: 0; w: 1; h: 1), - (x: 2000; y: 125; w: 1; h: 1), - (x: 2050; y: 50; w: 25; h: 25), - (x: 2100; y: 50; w: 25; h: 25), - (x: 2150; y: 150; w: 1; h: 1), + (x: 2000; y: 125; w: 26; h: 26), + (x: 2050; y: 50; w: 50; h: 50), + (x: 2100; y: 50; w: 50; h: 50), + (x: 2150; y: 150; w: 26; h: 26), (x: NTPX; y: 0; w: 1; h: 1), - (x: 2600; y: 250; w: 25; h: 100), - (x: 2750; y: 400; w: 50; h: 50), - (x: 2900; y: 525; w: 50; h: 50), - (x: 3150; y: 550; w: 50; h: 100), + (x: 2600; y: 250; w: 50; h: 125), + (x: 2750; y: 400; w: 75; h: 75), + (x: 2900; y: 525; w: 75; h: 75), + (x: 3150; y: 550; w: 75; h: 125), (x: NTPX; y: 0; w: 1; h: 1), - (x: 2800; y:1150; w: 1; h: 1), - (x: 2840; y: 950; w: 25; h: 25), - (x: 2880; y: 950; w: 25; h: 25), - (x: 2900; y:1150; w: 1; h: 1), + (x: 2800; y:1150; w: 26; h: 26), + (x: 2840; y: 950; w: 50; h: 50), + (x: 2880; y: 950; w: 50; h: 50), + (x: 2900; y:1150; w: 26; h: 26), (x: NTPX; y: 0; w: 1; h: 1), - (x: 3075; y:1985; w: 1; h: 1), - (x: 3325; y:1700; w: 50; h: 100), - (x: 3475; y:1700; w: 50; h: 100), - (x: 3625; y:1985; w: 1; h: 1), + (x: 3075; y:1985; w: 26; h: 26), + (x: 3325; y:1700; w: 75; h: 125), + (x: 3475; y:1700; w: 75; h: 125), + (x: 3625; y:1985; w: 26; h: 26), (x: NTPX; y: 0; w: 1; h: 1), - (x: 3200; y:1450; w: 1; h: 1), - (x: 3240; y:1350; w: 25; h: 25), - (x: 3280; y:1350; w: 25; h: 25), - (x: 3300; y:1450; w: 1; h: 1), + (x: 3200; y:1450; w: 26; h: 26), + (x: 3240; y:1350; w: 50; h: 50), + (x: 3280; y:1350; w: 50; h: 50), + (x: 3300; y:1450; w: 26; h: 26), (x: NTPX; y: 0; w: 1; h: 1), - (x: 3500; y:1050; w: 25; h: 50), - (x: 3650; y: 600; w: 50; h: 100), - (x: 3800; y: 600; w: 50; h: 100), - (x: 3900; y:1000; w: 25; h: 50), + (x: 3500; y:1050; w: 50; h: 75), + (x: 3650; y: 600; w: 75; h: 125), + (x: 3800; y: 600; w: 75; h: 125), + (x: 3900; y:1000; w: 50; h: 75), (x: NTPX; y: 0; w: 1; h: 1), - (x: 3800; y: 200; w: 25; h: 50), - (x: 3875; y: 100; w: 50; h: 50), - (x: 3925; y: 50; w: 50; h: 25), - (x: 4050; y: 125; w: 25; h: 50), + (x: 3800; y: 200; w: 50; h: 75), + (x: 3875; y: 100; w: 75; h: 75), + (x: 3925; y: 50; w: 75; h: 50), + (x: 4050; y: 125; w: 50; h: 75), (x: NTPX; y: 0; w: 1; h: 1) ); Template41FPoints: array[0..0] of TPoint = @@ -1571,9 +1571,191 @@ ( (X: 512; Y: 0) ); +// Many islands +const Template43Points: array[0..173] of TSDL_Rect = + ( + (x: 95; y: 500; w: 26; h: 26), + (x: 100; y: 275; w: 50; h: 125), + (x: 325; y: 275; w: 50; h: 125), + (x: 330; y: 500; w: 26; h: 26), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 725; y: 125; w: 26; h: 26), + (x: 725; y: 25; w: 30; h: 50), + (x: 825; y: 35; w: 30; h: 35), + (x: 825; y: 135; w: 26; h: 26), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 1150; y: 550; w: 50; h: 75), + (x: 1250; y: 300; w: 50; h: 75), + (x: 1350; y: 300; w: 50; h: 75), + (x: 1400; y: 575; w: 50; h: 75), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 525; y:1050; w: 75; h: 75), + (x: 700; y: 800; w: 125; h: 175), + (x: 950; y: 900; w: 125; h: 175), + (x: 1100; y:1100; w: 75; h: 75), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 175; y:1500; w: 26; h: 26), + (x: 210; y:1400; w: 30; h: 50), + (x: 240; y:1400; w: 30; h: 50), + (x: 275; y:1510; w: 26; h: 26), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 450; y:1800; w: 125; h: 125), + (x: 600; y:1750; w: 125; h: 125), + (x: 750; y:1750; w: 125; h: 125), + (x: 950; y:1850; w: 125; h: 125), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 1075; y:1450; w: 26; h: 26), + (x: 1110; y:1300; w: 30; h: 50), + (x: 1140; y:1300; w: 30; h: 50), + (x: 1175; y:1430; w: 26; h: 26), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 1600; y:1250; w: 50; h: 125), + (x: 1700; y:1150; w: 50; h: 125), + (x: 1850; y: 500; w: 75; h: 125), + (x: 1950; y: 550; w: 75; h: 175), + (x: 2250; y:1150; w: 50; h: 125), + (x: 2350; y:1250; w: 50; h: 125), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 1750; y:2010; w: 26; h: 26), + (x: 1900; y:1870; w: 75; h: 75), + (x: 2050; y:1870; w: 75; h: 75), + (x: 2175; y:2010; w: 26; h: 26), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 2500; y:1700; w: 26; h: 26), + (x: 2575; y:1500; w: 35; h: 75), + (x: 2650; y:1500; w: 35; h: 75), + (x: 2700; y:1690; w: 26; h: 26), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 2000; y: 125; w: 26; h: 26), + (x: 2050; y: 50; w: 50; h: 50), + (x: 2100; y: 50; w: 50; h: 50), + (x: 2150; y: 150; w: 26; h: 26), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 2600; y: 250; w: 50; h: 125), + (x: 2750; y: 400; w: 75; h: 75), + (x: 2900; y: 525; w: 75; h: 75), + (x: 3150; y: 550; w: 75; h: 125), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 2800; y:1150; w: 26; h: 26), + (x: 2840; y: 950; w: 50; h: 50), + (x: 2880; y: 950; w: 50; h: 50), + (x: 2900; y:1150; w: 26; h: 26), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 3075; y:1985; w: 26; h: 26), + (x: 3325; y:1700; w: 75; h: 125), + (x: 3475; y:1700; w: 75; h: 125), + (x: 3625; y:1985; w: 26; h: 26), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 3200; y:1450; w: 26; h: 26), + (x: 3240; y:1350; w: 50; h: 50), + (x: 3280; y:1350; w: 50; h: 50), + (x: 3300; y:1450; w: 26; h: 26), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 3500; y:1050; w: 50; h: 75), + (x: 3650; y: 600; w: 75; h: 125), + (x: 3800; y: 600; w: 75; h: 125), + (x: 3900; y:1000; w: 50; h: 75), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 3800; y: 200; w: 50; h: 75), + (x: 3875; y: 100; w: 75; h: 75), + (x: 3925; y: 50; w: 75; h: 50), + (x: 4050; y: 125; w: 50; h: 75), + (x: NTPX; y: 0; w: 1; h: 1), + (x: 95; y:2548; w: 26; h: 26), + (x: 100; y:2323; w: 50; h: 125), + (x: 325; y:2323; w: 50; h: 125), + (x: 330; y:2548; w: 26; h: 26), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 725; y:2173; w: 26; h: 26), + (x: 725; y:2073; w: 30; h: 50), + (x: 825; y:2083; w: 30; h: 35), + (x: 825; y:2183; w: 26; h: 26), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 1150; y:2598; w: 50; h: 75), + (x: 1250; y:2348; w: 50; h: 75), + (x: 1350; y:2348; w: 50; h: 75), + (x: 1400; y:2623; w: 50; h: 75), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 525; y:3098; w: 75; h: 75), + (x: 700; y:2848; w: 125; h: 175), + (x: 950; y:2948; w: 125; h: 175), + (x: 1100; y:3148; w: 75; h: 75), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 175; y:3548; w: 26; h: 26), + (x: 210; y:3448; w: 30; h: 50), + (x: 240; y:3448; w: 30; h: 50), + (x: 275; y:3558; w: 26; h: 26), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 450; y:3848; w: 125; h: 125), + (x: 600; y:3798; w: 125; h: 125), + (x: 750; y:3798; w: 125; h: 125), + (x: 950; y:3898; w: 125; h: 125), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 1075; y:3498; w: 26; h: 26), + (x: 1110; y:3348; w: 30; h: 50), + (x: 1140; y:3348; w: 30; h: 50), + (x: 1175; y:3478; w: 26; h: 26), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 1600; y:3298; w: 50; h: 125), + (x: 1700; y:3198; w: 50; h: 125), + (x: 1850; y:2548; w: 75; h: 125), + (x: 1950; y:2598; w: 75; h: 175), + (x: 2250; y:3198; w: 50; h: 125), + (x: 2350; y:3298; w: 50; h: 125), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 1750; y:4058; w: 26; h: 26), + (x: 1900; y:3918; w: 75; h: 75), + (x: 2050; y:3918; w: 75; h: 75), + (x: 2175; y:4058; w: 26; h: 26), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 2500; y:3748; w: 26; h: 26), + (x: 2575; y:3548; w: 35; h: 75), + (x: 2650; y:3548; w: 35; h: 75), + (x: 2700; y:3738; w: 26; h: 26), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 2000; y:2173; w: 26; h: 26), + (x: 2050; y:2098; w: 50; h: 50), + (x: 2100; y:2098; w: 50; h: 50), + (x: 2150; y:2198; w: 26; h: 26), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 2600; y:2298; w: 50; h: 125), + (x: 2750; y:2448; w: 75; h: 75), + (x: 2900; y:2573; w: 75; h: 75), + (x: 3150; y:2598; w: 75; h: 125), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 2800; y:3198; w: 26; h: 26), + (x: 2840; y:2998; w: 50; h: 50), + (x: 2880; y:2998; w: 50; h: 50), + (x: 2900; y:3198; w: 26; h: 26), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 3075; y:4033; w: 26; h: 26), + (x: 3325; y:3748; w: 75; h: 125), + (x: 3475; y:3748; w: 75; h: 125), + (x: 3625; y:4033; w: 26; h: 26), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 3200; y:3498; w: 26; h: 26), + (x: 3240; y:3398; w: 50; h: 50), + (x: 3280; y:3398; w: 50; h: 50), + (x: 3300; y:3498; w: 26; h: 26), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 3500; y:3098; w: 50; h: 75), + (x: 3650; y:2648; w: 75; h: 125), + (x: 3800; y:2648; w: 75; h: 125), + (x: 3900; y:3048; w: 50; h: 75), + (x: NTPX; y:2048; w: 1; h: 1), + (x: 3800; y:2248; w: 50; h: 75), + (x: 3875; y:2148; w: 75; h: 75), + (x: 3925; y:2098; w: 75; h: 50), + (x: 4050; y:2173; w: 50; h: 75), + (x: NTPX; y:2048; w: 1; h: 1) + ); + Template43FPoints: array[0..0] of TPoint = + ( + (X: 4095; Y: 0) + ); //////////////////////////////////////////////////////////////////////// -var EdgeTemplates: array[0..42] of TEdgeTemplate = +var EdgeTemplates: array[0..43] of TEdgeTemplate = ( (BasePoints: @Template0Points; BasePointsCount: Succ(High(Template0Points)); @@ -2030,8 +2212,8 @@ BasePointsCount: Succ(High(Template41Points)); FillPoints: @Template41FPoints; FillPointsCount: Succ(High(Template41FPoints)); - BezierizeCount: 3; - RandPassesCount: 5; + BezierizeCount: 2; + RandPassesCount: 9; TemplateHeight: 2048; TemplateWidth: 4096; canMirror: true; canFlip: true; isNegative: false; canInvert: false; hasGirders: true; @@ -2047,19 +2229,30 @@ canMirror: true; canFlip: false; isNegative: false; canInvert: false; hasGirders: false; MaxHedgeHogs: 8; + ), + (BasePoints: @Template43Points; + BasePointsCount: Succ(High(Template43Points)); + FillPoints: @Template43FPoints; + FillPointsCount: Succ(High(Template43FPoints)); + BezierizeCount: 2; + RandPassesCount: 9; + TemplateHeight: 4096; TemplateWidth: 4096; + canMirror: true; canFlip: true; isNegative: false; canInvert: false; + hasGirders: true; + MaxHedgeHogs: 48; ) ); const SmallTemplates: array[0..2] of Longword = ( 39, 40, 42 ); const MediumTemplates: array[0..17] of Longword = ( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 ); -const LargeTemplates: array[0..19] of Longword = +const LargeTemplates: array[0..20] of Longword = ( 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32, 33, 34, 35, 37, 38 + 28, 29, 30, 31, 32, 33, 34, 35, 37, 38, 43 ); const CavernTemplates: array[0..4] of Longword = (36, 2, 3, 21, 29); //const WackyTemplates: array[0..4] of Longword = (37, 38, 39, 40, 41); -const WackyTemplates: array[0..2] of Longword = (37, 38, 41); +const WackyTemplates: array[0..3] of Longword = (37, 38, 41, 43); implementation diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uMisc.pas --- a/hedgewars/uMisc.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uMisc.pas Thu Aug 30 13:02:19 2012 -0400 @@ -28,7 +28,7 @@ procedure movecursor(dx, dy: LongInt); function doSurfaceConversion(tmpsurf: PSDL_Surface): PSDL_Surface; -function MakeScreenshot(filename: shortstring): boolean; +function MakeScreenshot(filename: shortstring; k: LongInt): boolean; function GetTeamStatString(p: PTeam): shortstring; {$IFDEF SDL13} function SDL_RectMake(x, y, width, height: LongInt): TSDL_Rect; inline; @@ -186,19 +186,48 @@ {$ENDIF} // no PNG_SCREENSHOTS +{$IFDEF USE_VIDEO_RECORDING} +// make image k times smaller (useful for saving thumbnails) +procedure ReduceImage(img: PByte; width, height, k: LongInt); +var i, j, i0, j0, w, h, r, g, b: LongInt; +begin + w:= width div k; + h:= height div k; + + // rescale inplace + if k <> 1 then + begin + for i:= 0 to h-1 do + for j:= 0 to w-1 do + begin + r:= 0; + g:= 0; + b:= 0; + for i0:= 0 to k-1 do + for j0:= 0 to k-1 do + begin + r+= img[4*(width*(i*k+i0) + j*k+j0)+0]; + g+= img[4*(width*(i*k+i0) + j*k+j0)+1]; + b+= img[4*(width*(i*k+i0) + j*k+j0)+2]; + end; + img[4*(w*i + j)+0]:= r div (k*k); + img[4*(w*i + j)+1]:= g div (k*k); + img[4*(w*i + j)+2]:= b div (k*k); + img[4*(w*i + j)+3]:= 255; + end; + end; +end; +{$ENDIF} + // captures and saves the screen. returns true on success. -function MakeScreenshot(filename: shortstring): Boolean; +// saved image will be k times smaller than original (useful for saving thumbnails). +function MakeScreenshot(filename: shortstring; k: LongInt): Boolean; var p: Pointer; size: QWord; image: PScreenshot; format: GLenum; ext: string[4]; begin -// flash -ScreenFade:= sfFromWhite; -ScreenFadeValue:= sfMax; -ScreenFadeSpeed:= 5; - {$IFDEF PNG_SCREENSHOTS} format:= GL_RGBA; ext:= '.png'; @@ -218,14 +247,18 @@ exit; end; -// read pixel from the front buffer +// read pixels from the front buffer glReadPixels(0, 0, cScreenWidth, cScreenHeight, format, GL_UNSIGNED_BYTE, p); +{$IFDEF USE_VIDEO_RECORDING} +ReduceImage(p, cScreenWidth, cScreenHeight, k); +{$ENDIF} + // allocate and fill structure that will be passed to new thread New(image); // will be disposed in SaveScreenshot() -image^.filename:= UserPathPrefix + '/Screenshots/' + filename + ext; -image^.width:= cScreenWidth; -image^.height:= cScreenHeight; +image^.filename:= UserPathPrefix + filename + ext; +image^.width:= cScreenWidth div k; +image^.height:= cScreenHeight div k; image^.size:= size; image^.buffer:= p; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uMobile.pas --- a/hedgewars/uMobile.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uMobile.pas Thu Aug 30 13:02:19 2012 -0400 @@ -28,7 +28,7 @@ interface function isPhone: Boolean; inline; -function getScreenDPI: Single; inline; +function getScreenDPI: Double; inline; procedure performRumble; inline; procedure GameLoading; inline; @@ -36,7 +36,7 @@ procedure SaveLoadingEnded; inline; implementation -uses uVariables, uConsole; +uses uVariables, uConsole, SDLh; // add here any external call that you need {$IFDEF IPHONEOS} @@ -48,10 +48,6 @@ procedure AudioServicesPlaySystemSound(num: LongInt); cdecl; external; {$ENDIF} -{$IFDEF ANDROID} -function Android_JNI_getDensity(): Single; cdecl; external; -{$ENDIF} - // this function is just to determine whether we are running on a limited screen device function isPhone: Boolean; inline; begin @@ -66,10 +62,11 @@ {$ENDIF} end; -function getScreenDPI: Single; inline; +function getScreenDPI: Double; inline; begin {$IFDEF ANDROID} - getScreenDPI:= Android_JNI_getDensity(); +// getScreenDPI:= Android_JNI_getDensity(); + getScreenDPI:= 1; {$ELSE} getScreenDPI:= 1; {$ENDIF} diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uRandom.pas --- a/hedgewars/uRandom.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uRandom.pas Thu Aug 30 13:02:19 2012 -0400 @@ -30,9 +30,6 @@ interface uses uFloat; -procedure initModule; -procedure freeModule; - procedure SetRandomSeed(Seed: shortstring); // Sets the seed that should be used for generating pseudo-random values. function GetRandomf: hwFloat; overload; // Returns a pseudo-random hwFloat. function GetRandom(m: LongWord): LongWord; overload; inline; // Returns a positive pseudo-random integer smaller than m. @@ -99,15 +96,4 @@ rndSign:= num end; -procedure initModule; -begin - n:= 54; - FillChar(cirbuf, 64*sizeof(Longword), 0); -end; - -procedure freeModule; -begin - -end; - end. diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uRenderUtils.pas --- a/hedgewars/uRenderUtils.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uRenderUtils.pas Thu Aug 30 13:02:19 2012 -0400 @@ -92,7 +92,7 @@ textRect.y:= Y; textRect.w:= w; textRect.h:= h; - DrawRoundRect(@finalRect, cWhiteColor, endian(cNearBlackColorChannels.value), Surface, true); + DrawRoundRect(@finalRect, cWhiteColor, cNearBlackColor, Surface, true); clr.r:= (Color shr 16) and $FF; clr.g:= (Color shr 8) and $FF; clr.b:= Color and $FF; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uScript.pas --- a/hedgewars/uScript.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uScript.pas Thu Aug 30 13:02:19 2012 -0400 @@ -413,7 +413,7 @@ begin gear:= GearByUID(lua_tointeger(L, 1)); if gear <> nil then - DeleteGear(gear); + gear^.Message:= gear^.Message or gmDelete; end; lc_deletegear:= 0 end; @@ -745,7 +745,7 @@ TryDo(texsurf <> nil, errmsgCreateSurface, true); TryDo(SDL_SetColorKey(texsurf, SDL_SRCCOLORKEY, 0) = 0, errmsgTransparentSet, true); - DrawRoundRect(@r, cWhiteColor, cNearBlackColorChannels.value, texsurf, true); + DrawRoundRect(@r, cWhiteColor, cNearBlackColor, texsurf, true); rr:= r; inc(rr.x, 2); dec(rr.w, 4); inc(rr.y, 2); dec(rr.h, 4); DrawRoundRect(@rr, clan^.Color, clan^.Color, texsurf, false); @@ -981,10 +981,13 @@ if (gear <> nil) and (gear^.Kind = gtHedgehog) and (gear^.Hedgehog <> nil) and (CurrentHedgehog <> nil) then begin prevgear := CurrentHedgehog^.Gear; - prevgear^.Active := false; - prevgear^.State:= prevgear^.State and not gstHHDriven; - prevgear^.Z := cHHZ; - prevgear^.Message:= prevgear^.Message or gmRemoveFromList or gmAddToList; + if prevgear <> nil then + begin + prevgear^.Active := false; + prevgear^.State:= prevgear^.State and (not gstHHDriven); + prevgear^.Z := cHHZ; + prevgear^.Message:= prevgear^.Message or gmRemoveFromList or gmAddToList; + end; SwitchCurrentHedgehog(gear^.Hedgehog); CurrentTeam:= CurrentHedgehog^.Team; @@ -1808,6 +1811,7 @@ ScriptSetInteger('GameFlags', GameFlags); ScriptSetString('Seed', cSeed); ScriptSetInteger('TemplateFilter', cTemplateFilter); +ScriptSetInteger('TemplateNumber', LuaTemplateNumber); ScriptSetInteger('MapGen', cMapGen); ScriptSetInteger('ScreenHeight', cScreenHeight); ScriptSetInteger('ScreenWidth', cScreenWidth); @@ -1836,6 +1840,7 @@ // pop game variables ParseCommand('seed ' + ScriptGetString('Seed'), true); cTemplateFilter := ScriptGetInteger('TemplateFilter'); +LuaTemplateNumber:= ScriptGetInteger('TemplateNumber'); cMapGen := ScriptGetInteger('MapGen'); GameFlags := ScriptGetInteger('GameFlags'); cHedgehogTurnTime:= ScriptGetInteger('TurnTime'); diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uSound.pas --- a/hedgewars/uSound.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uSound.pas Thu Aug 30 13:02:19 2012 -0400 @@ -587,11 +587,8 @@ RegisterVariable('mute' , @chMute , true ); MusicFN:=''; - isMusicEnabled:= true; - isSoundEnabled:= true; isAudioMuted:= false; isSEBackup:= isSoundEnabled; - cInitVolume:= 100; Volume:= 0; defVoicepack:= AskForVoicepack('Default'); @@ -615,6 +612,11 @@ begin if isSoundEnabled then ReleaseSound(true); + // koda still needs to fix this properly. when he rearranged things, he made these variables get + // reset after argparsers picks them up + isMusicEnabled:= true; + isSoundEnabled:= true; + cInitVolume:= 100; end; end. diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uStore.pas --- a/hedgewars/uStore.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uStore.pas Thu Aug 30 13:02:19 2012 -0400 @@ -40,13 +40,17 @@ procedure ShowWeaponTooltip(x, y: LongInt); procedure FreeWeaponTooltip; procedure MakeCrossHairs; +{$IFDEF USE_VIDEO_RECORDING} +procedure InitOffscreenOpenGL; +{$ENDIF} procedure WarpMouse(x, y: Word); inline; procedure SwapBuffers; inline; implementation uses uMisc, uConsole, uMobile, uVariables, uUtils, uTextures, uRender, uRenderUtils, uCommands, - uDebug{$IFDEF USE_CONTEXT_RESTORE}, uWorld{$ENDIF}; + uDebug{$IFDEF USE_CONTEXT_RESTORE}, uWorld{$ENDIF} + {$IF NOT DEFINED(SDL13) AND DEFINED(USE_VIDEO_RECORDING)}, glut {$ENDIF}; //type TGPUVendor = (gvUnknown, gvNVIDIA, gvATI, gvIntel, gvApple); @@ -131,10 +135,11 @@ procedure WriteNames(Font: THWFont); var t: LongInt; - i: LongInt; + i, maxLevel: LongInt; r, rr: TSDL_Rect; drY: LongInt; texsurf, flagsurf, iconsurf: PSDL_Surface; + foundBot: boolean; begin r.x:= 0; r.y:= 0; @@ -151,7 +156,7 @@ TryDo(texsurf <> nil, errmsgCreateSurface, true); TryDo(SDL_SetColorKey(texsurf, SDL_SRCCOLORKEY, 0) = 0, errmsgTransparentSet, true); - DrawRoundRect(@r, cWhiteColor, cNearBlackColorChannels.value, texsurf, true); + DrawRoundRect(@r, cWhiteColor, cNearBlackColor, texsurf, true); rr:= r; inc(rr.x, 2); dec(rr.w, 4); inc(rr.y, 2); dec(rr.h, 4); DrawRoundRect(@rr, Clan^.Color, Clan^.Color, texsurf, false); @@ -172,11 +177,28 @@ DrawRoundRect(@r, cWhiteColor, cNearBlackColor, texsurf, true); // overwrite flag for cpu teams and keep players from using it - if (Hedgehogs[0].Gear <> nil) and (Hedgehogs[0].BotLevel > 0) then - if Flag = 'hedgewars' then - Flag:= 'cpu' - else if Flag = 'cpu' then - Flag:= 'hedgewars'; + foundBot:= false; + maxLevel:= -1; + for i:= 0 to cMaxHHIndex do + with Hedgehogs[i] do + if (Gear <> nil) and (BotLevel > 0) then + begin + foundBot:= true; + // initially was going to do the highest botlevel of the team, but for now, just apply if entire team has same bot level + if maxLevel = -1 then maxLevel:= BotLevel + else if (maxLevel > 0) and (maxLevel <> BotLevel) then maxLevel:= 0; + //if (maxLevel > 0) and (BotLevel < maxLevel) then maxLevel:= BotLevel + end + else if Gear <> nil then maxLevel:= 0; + + if foundBot then + begin + // disabled the plain flag - I think it looks ok even w/ full bars obscuring CPU + //if (maxLevel > 0) and (maxLevel < 3) then Flag:= 'cpu_plain' else + Flag:= 'cpu' + end + else if (Flag = 'cpu') or (Flag = 'cpu_plain') then + Flag:= 'hedgewars'; flagsurf:= LoadImage(UserPathz[ptFlags] + '/' + Flag, ifNone); if flagsurf = nil then @@ -186,16 +208,27 @@ 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); + + case maxLevel of + 1: copyToXY(SpritesData[sprBotlevels].Surface, flagsurf, 0, 0); + 2: copyToXYFromRect(SpritesData[sprBotlevels].Surface, flagsurf, 5, 2, 17, 13, 5, 2); + 3: copyToXYFromRect(SpritesData[sprBotlevels].Surface, flagsurf, 9, 5, 13, 10, 9, 5); + 4: copyToXYFromRect(SpritesData[sprBotlevels].Surface, flagsurf, 13, 9, 9, 6, 13, 9); + 5: copyToXYFromRect(SpritesData[sprBotlevels].Surface, flagsurf, 17, 11, 5, 4, 17, 11) + end; + copyToXY(flagsurf, texsurf, 2, 2); SDL_FreeSurface(flagsurf); flagsurf:= nil; + // restore black border pixels inside the flag PLongwordArray(texsurf^.pixels)^[32 * 2 + 2]:= cNearBlackColor; PLongwordArray(texsurf^.pixels)^[32 * 2 + 23]:= cNearBlackColor; PLongwordArray(texsurf^.pixels)^[32 * 16 + 2]:= cNearBlackColor; PLongwordArray(texsurf^.pixels)^[32 * 16 + 23]:= cNearBlackColor; + FlagTex:= Surface2Tex(texsurf, false); SDL_FreeSurface(texsurf); texsurf:= nil; @@ -204,7 +237,7 @@ dec(drY, r.h + 2); DrawHealthY:= drY; - for i:= 0 to 7 do + for i:= 0 to cMaxHHIndex do with Hedgehogs[i] do if Gear <> nil then begin @@ -294,7 +327,6 @@ WriteLnToConsole(msgOK) end; -WriteNames(fnt16); MakeCrossHairs; LoadGraves; if not reload then @@ -391,6 +423,8 @@ Surface:= nil end; +WriteNames(fnt16); + if not reload then AddProgress; @@ -438,6 +472,31 @@ IMG_Quit(); end; +{$IF NOT DEFINED(S3D_DISABLED) OR DEFINED(USE_VIDEO_RECORDING)} +procedure CreateFramebuffer(var frame, depth, tex: GLuint); +begin + glGenFramebuffersEXT(1, @frame); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frame); + glGenRenderbuffersEXT(1, @depth); + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, cScreenWidth, cScreenHeight); + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth); + glGenTextures(1, @tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, cScreenWidth, cScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex, 0); +end; + +procedure DeleteFramebuffer(var frame, depth, tex: GLuint); +begin + glDeleteTextures(1, @tex); + glDeleteRenderbuffersEXT(1, @depth); + glDeleteFramebuffersEXT(1, @frame); +end; +{$ENDIF} + procedure StoreRelease(reload: boolean); var ii: TSprite; ai: TAmmoType; @@ -511,15 +570,15 @@ end; end; end; +{$IFDEF USE_VIDEO_RECORDING} + if defaultFrame <> 0 then + DeleteFramebuffer(defaultFrame, depthv, texv); +{$ENDIF} {$IFNDEF S3D_DISABLED} if (cStereoMode = smHorizontal) or (cStereoMode = smVertical) or (cStereoMode = smAFR) then begin - glDeleteTextures(1, @texl); - glDeleteRenderbuffersEXT(1, @depthl); - glDeleteFramebuffersEXT(1, @framel); - glDeleteTextures(1, @texr); - glDeleteRenderbuffersEXT(1, @depthr); - glDeleteFramebuffersEXT(1, @framer) + DeleteFramebuffer(framel, depthl, texl); + DeleteFramebuffer(framer, depthr, texr); end {$ENDIF} end; @@ -628,6 +687,7 @@ procedure SetupOpenGL; //var vendor: shortstring = ''; var buf: array[byte] of char; + AuxBufNum: LongInt; begin buf[0]:= char(0); // avoid compiler hint AddFileLog('Setting up OpenGL (using driver: ' + shortstring(SDL_VideoDriverName(buf, sizeof(buf))) + ')'); @@ -673,14 +733,43 @@ {$ENDIF} //SupportNPOTT:= glLoadExtension('GL_ARB_texture_non_power_of_two'); *) + glGetIntegerv(GL_AUX_BUFFERS, @AuxBufNum); // everyone love debugging AddFileLog('OpenGL-- Renderer: ' + shortstring(pchar(glGetString(GL_RENDERER)))); AddFileLog(' |----- Vendor: ' + shortstring(pchar(glGetString(GL_VENDOR)))); AddFileLog(' |----- Version: ' + shortstring(pchar(glGetString(GL_VERSION)))); AddFileLog(' |----- Texture Size: ' + inttostr(MaxTextureSize)); - AddFileLog(' \----- Extensions: ' + shortstring(pchar(glGetString(GL_EXTENSIONS)))); - //TODO: don't have the Extensions line trimmed but slipt it into multiple lines + AddFileLog(' |----- Number of auxilary buffers: ' + inttostr(AuxBufNum)); + AddFileLog(' \----- Extensions: '); + AddFileLogRaw(glGetString(GL_EXTENSIONS)); + AddFileLog(''); + //TODO: slipt Extensions line into multiple lines + + defaultFrame:= 0; +{$IFDEF USE_VIDEO_RECORDING} + if GameType = gmtRecord then + begin + if glLoadExtension('GL_EXT_framebuffer_object') then + begin + CreateFramebuffer(defaultFrame, depthv, texv); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, defaultFrame); + AddFileLog('Using framebuffer for video recording.'); + end + else if AuxBufNum > 0 then + begin + glDrawBuffer(GL_AUX0); + glReadBuffer(GL_AUX0); + AddFileLog('Using auxilary buffer for video recording.'); + end + else + begin + glDrawBuffer(GL_BACK); + glReadBuffer(GL_BACK); + AddFileLog('Warning: off-screen rendering is not supported; using back buffer but it may not work.'); + end; + end; +{$ENDIF} {$IFNDEF S3D_DISABLED} if (cStereoMode = smHorizontal) or (cStereoMode = smVertical) or (cStereoMode = smAFR) then @@ -688,36 +777,11 @@ // prepare left and right frame buffers and associated textures if glLoadExtension('GL_EXT_framebuffer_object') then begin - // left - glGenFramebuffersEXT(1, @framel); - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framel); - glGenRenderbuffersEXT(1, @depthl); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthl); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, cScreenWidth, cScreenHeight); - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthl); - glGenTextures(1, @texl); - glBindTexture(GL_TEXTURE_2D, texl); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, cScreenWidth, cScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texl, 0); - - // right - glGenFramebuffersEXT(1, @framer); - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framer); - glGenRenderbuffersEXT(1, @depthr); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthr); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, cScreenWidth, cScreenHeight); - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthr); - glGenTextures(1, @texr); - glBindTexture(GL_TEXTURE_2D, texr); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, cScreenWidth, cScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texr, 0); + CreateFramebuffer(framel, depthl, texl); + CreateFramebuffer(framer, depthr, texr); // reset - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0) + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, defaultFrame) end else cStereoMode:= smNone; @@ -991,6 +1055,34 @@ WeaponTooltipTex:= nil end; +{$IFDEF USE_VIDEO_RECORDING} +{$IFDEF SDL13} +procedure InitOffscreenOpenGL; +begin + // create hidden window + SDLwindow:= SDL_CreateWindow('hedgewars (you don''t see this)', + SDL_WINDOWPOS_CENTERED_MASK, SDL_WINDOWPOS_CENTERED_MASK, + cScreenWidth, cScreenHeight, + SDL_WINDOW_HIDDEN or SDL_WINDOW_OPENGL); + SDLTry(SDLwindow <> nil, true); + SetupOpenGL(); +end; +{$ELSE} +procedure InitOffscreenOpenGL; +var ArgCount: LongInt; + PrgName: pchar; +begin + ArgCount:= 1; + PrgName:= 'hwengine'; + glutInit(@ArgCount, @PrgName); + glutInitWindowSize(cScreenWidth, cScreenHeight); + glutCreateWindow('hedgewars (you don''t see this)'); // we don't need a window, but if this function is not called then OpenGL will not be initialized + glutHideWindow(); + SetupOpenGL(); +end; +{$ENDIF} // SDL13 +{$ENDIF} // USE_VIDEO_RECORDING + procedure chFullScr(var s: shortstring); var flags: Longword = 0; reinit: boolean = false; @@ -1171,6 +1263,8 @@ procedure SwapBuffers; inline; begin + if GameType = gmtRecord then + exit; {$IFDEF SDL13} SDL_GL_SwapWindow(SDLwindow); {$ELSE} diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uTeams.pas --- a/hedgewars/uTeams.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uTeams.pas Thu Aug 30 13:02:19 2012 -0400 @@ -473,7 +473,7 @@ begin Gear^.Invulnerable:= false; Gear^.Damage:= Gear^.Health; - Gear^.State:= (Gear^.State or gstHHGone) and not gstHHDriven + Gear^.State:= (Gear^.State or gstHHGone) and (not gstHHDriven) end end end; @@ -531,7 +531,7 @@ AddTeam(Color); CurrentTeam^.TeamName:= ts; CurrentTeam^.PlayerHash:= s; - if GameType in [gmtDemo, gmtSave] then + if GameType in [gmtDemo, gmtSave, gmtRecord] then CurrentTeam^.ExtDriven:= true; CurrentTeam^.voicepack:= AskForVoicepack('Default') diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uTypes.pas --- a/hedgewars/uTypes.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uTypes.pas Thu Aug 30 13:02:19 2012 -0400 @@ -39,7 +39,7 @@ TGameState = (gsLandGen, gsStart, gsGame, gsChat, gsConfirm, gsExit, gsSuspend); // Game types that help determining what the engine is actually supposed to do - TGameType = (gmtLocal, gmtDemo, gmtNet, gmtSave, gmtLandPreview, gmtSyntax); + TGameType = (gmtLocal, gmtDemo, gmtNet, gmtSave, gmtLandPreview, gmtSyntax, gmtRecord); // Different files are stored in different folders, this enumeration is used to tell which folder to use TPathType = (ptNone, ptData, ptGraphics, ptThemes, ptCurrTheme, ptTeams, ptMaps, @@ -86,7 +86,7 @@ sprHandResurrector, sprCross, sprAirDrill, sprNapalmBomb, sprBulletHit, sprSnowball, sprHandSnowball, sprSnow, sprSDFlake, sprSDWater, sprSDCloud, sprSDSplash, sprSDDroplet, sprTardis, - sprSlider + sprSlider, sprBotlevels ); // Gears that interact with other Gears and/or Land @@ -235,6 +235,7 @@ Kind: TGearType; Pos: Longword; doStep: TGearStepProcedure; + stepFreq: Longword; Radius: LongInt; Angle, Power : Longword; DirAngle: real; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uUtils.pas --- a/hedgewars/uUtils.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uUtils.pas Thu Aug 30 13:02:19 2012 -0400 @@ -43,7 +43,7 @@ function StrToInt(s: shortstring): LongInt; function FloatToStr(n: hwFloat): shortstring; -function DxDy2Angle(const _dY, _dX: hwFloat): GLfloat; +function DxDy2Angle(const _dY, _dX: hwFloat): GLfloat; inline; function DxDy2Angle32(const _dY, _dX: hwFloat): LongInt; function DxDy2AttackAngle(const _dY, _dX: hwFloat): LongInt; function DxDy2AttackAnglef(const _dY, _dX: extended): LongInt; @@ -61,6 +61,7 @@ function CheckCJKFont(s: ansistring; font: THWFont): THWFont; procedure AddFileLog(s: shortstring); +procedure AddFileLogRaw(s: pchar); cdecl; function CheckNoTeamOrHH: boolean; inline; @@ -81,6 +82,9 @@ {$IFDEF DEBUGFILE} var f: textfile; +{$IFDEF USE_VIDEO_RECORDING} + logMutex: TRTLCriticalSection; // mutex for debug file +{$ENDIF} {$ENDIF} var CharArray: array[byte] of Char; @@ -182,15 +186,11 @@ end; -function DxDy2Angle(const _dY, _dX: hwFloat): GLfloat; +function DxDy2Angle(const _dY, _dX: hwFloat): GLfloat; inline; var dY, dX: Extended; begin -dY:= _dY.QWordValue / $100000000; -if _dY.isNegative then - dY:= - dY; -dX:= _dX.QWordValue / $100000000; -if _dX.isNegative then - dX:= - dX; +dY:= hwFloat2Float(_dY); +dX:= hwFloat2Float(_dX); DxDy2Angle:= arctan2(dY, dX) * 180 / pi end; @@ -198,12 +198,8 @@ const _16divPI: Extended = 16/pi; var dY, dX: Extended; begin -dY:= _dY.QWordValue / $100000000; -if _dY.isNegative then - dY:= - dY; -dX:= _dX.QWordValue / $100000000; -if _dX.isNegative then - dX:= - dX; +dY:= hwFloat2Float(_dY); +dX:= hwFloat2Float(_dX); DxDy2Angle32:= trunc(arctan2(dY, dX) * _16divPI) and $1f end; @@ -211,12 +207,8 @@ const MaxAngleDivPI: Extended = cMaxAngle/pi; var dY, dX: Extended; begin -dY:= _dY.QWordValue / $100000000; -if _dY.isNegative then - dY:= - dY; -dX:= _dX.QWordValue / $100000000; -if _dX.isNegative then - dX:= - dX; +dY:= hwFloat2Float(_dY); +dX:= hwFloat2Float(_dX); DxDy2AttackAngle:= trunc(arctan2(dY, dX) * MaxAngleDivPI) end; @@ -303,11 +295,31 @@ begin s:= s; {$IFDEF DEBUGFILE} +{$IFDEF USE_VIDEO_RECORDING} +EnterCriticalSection(logMutex); +{$ENDIF} writeln(f, inttostr(GameTicks) + ': ' + s); -flush(f) +flush(f); +{$IFDEF USE_VIDEO_RECORDING} +LeaveCriticalSection(logMutex); +{$ENDIF} {$ENDIF} end; +procedure AddFileLogRaw(s: pchar); cdecl; +begin +s:= s; +{$IFDEF DEBUGFILE} +{$IFDEF USE_VIDEO_RECORDING} +EnterCriticalSection(logMutex); +{$ENDIF} +write(f, s); +flush(f); +{$IFDEF USE_VIDEO_RECORDING} +LeaveCriticalSection(logMutex); +{$ENDIF} +{$ENDIF} +end; function CheckCJKFont(s: ansistring; font: THWFont): THWFont; var l, i : LongInt; @@ -397,9 +409,17 @@ begin {$IFDEF DEBUGFILE} if isGame then - logfileBase:= 'game' + begin + if GameType = gmtRecord then + logfileBase:= 'rec' + else + logfileBase:= 'game'; + end else logfileBase:= 'preview'; +{$IFDEF USE_VIDEO_RECORDING} + InitCriticalSection(logMutex); +{$ENDIF} {$I-} {$IFDEF MOBILE} {$IFDEF IPHONEOS} Assign(f,'../Documents/hw-' + logfileBase + '.log'); {$ENDIF} @@ -436,6 +456,9 @@ writeln(f, 'halt at ' + inttostr(GameTicks) + ' ticks. TurnTimeLeft = ' + inttostr(TurnTimeLeft)); flush(f); close(f); +{$IFDEF USE_VIDEO_RECORDING} + DoneCriticalSection(logMutex); +{$ENDIF} {$ENDIF} end; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uVariables.pas --- a/hedgewars/uVariables.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uVariables.pas Thu Aug 30 13:02:19 2012 -0400 @@ -52,6 +52,15 @@ cReadyDelay : Longword = 5000; cStereoMode : TStereoMode = smNone; cOnlyStats : boolean = False; +{$IFDEF USE_VIDEO_RECORDING} + RecPrefix : shortstring; + cAVFormat : shortstring; + cVideoCodec : shortstring; + cVideoFramerateNum : LongInt; + cVideoFramerateDen : LongInt; + cVideoQuality : LongInt; + cAudioCodec : shortstring; +{$ENDIF} ////////////////////////// cMapName : shortstring = ''; @@ -60,8 +69,10 @@ isPaused : boolean; isInMultiShoot : boolean; isSpeed : boolean; + SpeedStart : LongWord; fastUntilLag : boolean; + fastScrolling : boolean; autoCameraOn : boolean; CheckSum : LongWord; @@ -185,6 +196,8 @@ hiddenHedgehogs : array [0..cMaxHHs] of PHedgehog; hiddenHedgehogsNumber : longint; + LuaTemplateNumber : LongWord; + VoiceList : array[0..7] of TVoice = ( ( snd: sndNone; voicepack: nil), ( snd: sndNone; voicepack: nil), @@ -646,7 +659,9 @@ (FileName: 'TARDIS'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil; Width: 48; Height: 79; imageWidth: 0; imageHeight: 0; saveSurf: false; priority: tpHighest; getDimensions: false; getImageDimensions: true),// sprTardis (FileName: 'slider'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil; - Width: 3; Height: 17; imageWidth: 3; imageHeight: 17; saveSurf: false; priority: tpLow; getDimensions: false; getImageDimensions: false) // sprSlider + Width: 3; Height: 17; imageWidth: 3; imageHeight: 17; saveSurf: false; priority: tpLow; getDimensions: false; getImageDimensions: false), // sprSlider + (FileName: 'botlevels'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil; + Width: 22; Height: 15; imageWidth: 22; imageHeight: 15; saveSurf: true; priority: tpLow; getDimensions: false; getImageDimensions: false) // sprBotlevels ); const @@ -1448,7 +1463,8 @@ NumberInCase: 1; Ammo: (Propz: ammoprop_ForwMsgs or ammoprop_NoCrosshair or - ammoprop_DontHold; + ammoprop_DontHold or + ammoprop_Track; Count: 1; NumPerTurn: 0; Timer: 0; @@ -2446,6 +2462,10 @@ framel, framer, depthl, depthr: GLuint; texl, texr: GLuint; + // video recorder framebuffer and texture + defaultFrame, depthv: GLuint; + texv: GLuint; + VisualGearLayers: array[0..6] of PVisualGear; lastVisualGearByUID: PVisualGear; vobFrameTicks, vobFramesCount, vobCount: Longword; @@ -2571,7 +2591,7 @@ cExplosives := 2; GameState := Low(TGameState); - GameType := gmtLocal; +// GameType := gmtLocal; zoom := cDefaultZoomLevel; ZoomValue := cDefaultZoomLevel; WeaponTooltipTex:= nil; @@ -2586,7 +2606,9 @@ isPaused := false; isInMultiShoot := false; isSpeed := false; + SpeedStart := 0; fastUntilLag := false; + fastScrolling := false; autoCameraOn := true; cScriptName := ''; cSeed := ''; @@ -2621,6 +2643,8 @@ SDWaterOpacity:= $80; LuaGoals:= ''; + + LuaTemplateNumber:= 0; end; procedure freeModule; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uVideoRec.pas --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hedgewars/uVideoRec.pas Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,369 @@ +(* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + *) + + +{$INCLUDE "options.inc"} + +unit uVideoRec; + +{$IFNDEF USE_VIDEO_RECORDING} +interface +implementation +end. +{$ELSE} + +{$IFNDEF WIN32} + {$LINKLIB ../bin/libavwrapper.a} +{$ENDIF} + +interface + +var flagPrerecording: boolean = false; + +function BeginVideoRecording: Boolean; +function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean; +procedure EncodeFrame; +procedure StopVideoRecording; + +procedure BeginPreRecording; +procedure StopPreRecording; +procedure SaveCameraPosition; + +procedure freeModule; + +implementation + +uses uVariables, uUtils, GLunit, SDLh, SysUtils, uIO, uMisc, uTypes; + +type TAddFileLogRaw = procedure (s: pchar); cdecl; + +procedure AVWrapper_Init( + AddLog: TAddFileLogRaw; + filename, desc, soundFile, format, vcodec, acodec: PChar; + width, height, framerateNum, framerateDen, vquality: LongInt); cdecl; external {$IFDEF WIN32}'libavwrapper.dll'{$ENDIF}; +procedure AVWrapper_Close; cdecl; external {$IFDEF WIN32}'libavwrapper.dll'{$ENDIF}; +procedure AVWrapper_WriteFrame( pY, pCb, pCr: PByte ); cdecl; external {$IFDEF WIN32}'libavwrapper.dll'{$ENDIF}; + +type TFrame = record + realTicks: LongWord; + gameTicks: LongWord; + CamX, CamY: LongInt; + zoom: single; + end; + +var YCbCr_Planes: array[0..2] of PByte; + RGB_Buffer: PByte; + cameraFile: File of TFrame; + audioFile: File; + numPixels: LongWord; + startTime, numFrames, curTime, progress, maxProgress: LongWord; + soundFilePath: shortstring; + thumbnailSaved : Boolean; + +function BeginVideoRecording: Boolean; +var filename, desc: shortstring; +begin + AddFileLog('BeginVideoRecording'); + +{$IOCHECKS OFF} + // open file with prerecorded camera positions + filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtin'; + Assign(cameraFile, filename); + Reset(cameraFile); + maxProgress:= FileSize(cameraFile); + if IOResult <> 0 then + begin + AddFileLog('Error: Could not read from ' + filename); + exit(false); + end; +{$IOCHECKS ON} + + // store some description in output file + desc:= ''; + if UserNick <> '' then + desc+= 'Player: ' + UserNick + #10; + if recordFileName <> '' then + desc+= 'Record: ' + recordFileName + #10; + if cMapName <> '' then + desc+= 'Map: ' + cMapName + #10; + if Theme <> '' then + desc+= 'Theme: ' + Theme + #10; + desc+= 'prefix[' + RecPrefix + ']prefix'; + desc+= #0; + + filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + #0; + soundFilePath:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.sw' + #0; + cAVFormat+= #0; + cAudioCodec+= #0; + cVideoCodec+= #0; + AVWrapper_Init(@AddFileLogRaw, @filename[1], @desc[1], @soundFilePath[1], @cAVFormat[1], @cVideoCodec[1], @cAudioCodec[1], + cScreenWidth, cScreenHeight, cVideoFramerateNum, cVideoFramerateDen, cVideoQuality); + + numPixels:= cScreenWidth*cScreenHeight; + YCbCr_Planes[0]:= GetMem(numPixels); + YCbCr_Planes[1]:= GetMem(numPixels div 4); + YCbCr_Planes[2]:= GetMem(numPixels div 4); + + if (YCbCr_Planes[0] = nil) or (YCbCr_Planes[1] = nil) or (YCbCr_Planes[2] = nil) then + begin + AddFileLog('Error: Could not allocate memory for video recording (YCbCr buffer).'); + exit(false); + end; + + RGB_Buffer:= GetMem(4*numPixels); + if RGB_Buffer = nil then + begin + AddFileLog('Error: Could not allocate memory for video recording (RGB buffer).'); + exit(false); + end; + + curTime:= 0; + numFrames:= 0; + progress:= 0; + BeginVideoRecording:= true; +end; + +procedure StopVideoRecording; +begin + AddFileLog('StopVideoRecording'); + FreeMem(YCbCr_Planes[0], numPixels); + FreeMem(YCbCr_Planes[1], numPixels div 4); + FreeMem(YCbCr_Planes[2], numPixels div 4); + FreeMem(RGB_Buffer, 4*numPixels); + Close(cameraFile); + AVWrapper_Close(); + Erase(cameraFile); + DeleteFile(soundFilePath); + SendIPC(_S'v'); // inform frontend that we finished +end; + +function pixel(x, y, color: LongInt): LongInt; +begin + pixel:= RGB_Buffer[(cScreenHeight-y-1)*cScreenWidth*4 + x*4 + color]; +end; + +procedure EncodeFrame; +var x, y, r, g, b: LongInt; + s: shortstring; +begin + // read pixels from OpenGL + glReadPixels(0, 0, cScreenWidth, cScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, RGB_Buffer); + + // convert to YCbCr 4:2:0 format + // Y + for y := 0 to cScreenHeight-1 do + for x := 0 to cScreenWidth-1 do + YCbCr_Planes[0][y*cScreenWidth + x]:= Byte(16 + ((16828*pixel(x,y,0) + 33038*pixel(x,y,1) + 6416*pixel(x,y,2)) shr 16)); + + // Cb and Cr + for y := 0 to cScreenHeight div 2 - 1 do + for x := 0 to cScreenWidth div 2 - 1 do + begin + r:= pixel(2*x,2*y,0) + pixel(2*x+1,2*y,0) + pixel(2*x,2*y+1,0) + pixel(2*x+1,2*y+1,0); + g:= pixel(2*x,2*y,1) + pixel(2*x+1,2*y,1) + pixel(2*x,2*y+1,1) + pixel(2*x+1,2*y+1,1); + b:= pixel(2*x,2*y,2) + pixel(2*x+1,2*y,2) + pixel(2*x,2*y+1,2) + pixel(2*x+1,2*y+1,2); + YCbCr_Planes[1][y*(cScreenWidth div 2) + x]:= Byte(128 + ((-2428*r - 4768*g + 7196*b) shr 16)); + YCbCr_Planes[2][y*(cScreenWidth div 2) + x]:= Byte(128 + (( 7196*r - 6026*g - 1170*b) shr 16)); + end; + + AVWrapper_WriteFrame(YCbCr_Planes[0], YCbCr_Planes[1], YCbCr_Planes[2]); + + // inform frontend that we have encoded new frame + s[0]:= #3; + s[1]:= 'p'; // p for progress + SDLNet_Write16(progress*10000 div maxProgress, @s[2]); + SendIPC(s); + inc(numFrames); +end; + +function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean; +var frame: TFrame; +begin + // we need to skip or duplicate frames to match target framerate + while Int64(curTime)*cVideoFramerateNum <= Int64(numFrames)*cVideoFramerateDen*1000 do + begin + {$IOCHECKS OFF} + if eof(cameraFile) then + exit(false); + BlockRead(cameraFile, frame, 1); + {$IOCHECKS ON} + curTime:= frame.realTicks; + WorldDx:= frame.CamX; + WorldDy:= frame.CamY + cScreenHeight div 2; + zoom:= frame.zoom*cScreenWidth; + ZoomValue:= zoom; + inc(progress); + newRealTicks:= frame.realTicks; + newGameTicks:= frame.gameTicks; + end; + LoadNextCameraPosition:= true; +end; + +// Callback which records sound. +// This procedure may be called from different thread. +procedure RecordPostMix(udata: pointer; stream: PByte; len: LongInt); cdecl; +begin + udata:= udata; // avoid warning +{$IOCHECKS OFF} + BlockWrite(audioFile, stream^, len); +{$IOCHECKS ON} +end; + +procedure SaveThumbnail; +var thumbpath: shortstring; + k: LongInt; +begin + thumbpath:= '/VideoTemp/' + RecPrefix; + AddFileLog('Saving thumbnail ' + thumbpath); + k:= max(max(cScreenWidth, cScreenHeight) div 400, 1); // here 400 is minimum size of thumbnail + MakeScreenshot(thumbpath, k); + thumbnailSaved:= true; +end; + +// copy file (free pascal doesn't have copy file function) +procedure CopyFile(src, dest: shortstring); +var inF, outF: file; + buffer: array[0..1023] of byte; + result: LongInt; +begin +{$IOCHECKS OFF} + result:= 0; // avoid compiler hint + + Assign(inF, src); + Reset(inF, 1); + if IOResult <> 0 then + begin + AddFileLog('Error: Could not read from ' + src); + exit; + end; + + Assign(outF, dest); + Rewrite(outF, 1); + if IOResult <> 0 then + begin + AddFileLog('Error: Could not write to ' + dest); + exit; + end; + + repeat + BlockRead(inF, buffer, 1024, result); + BlockWrite(outF, buffer, result); + until result < 1024; +{$IOCHECKS ON} +end; + +procedure BeginPreRecording; +var format: word; + filename: shortstring; + frequency, channels: LongInt; +begin + AddFileLog('BeginPreRecording'); + + thumbnailSaved:= false; + RecPrefix:= 'hw-' + FormatDateTime('YYYY-MM-DD_HH-mm-ss-z', Now()); + + // If this video is recorded from demo executed directly (without frontend) + // then we need to copy demo so that frontend will be able to find it later. + if recordFileName <> '' then + begin + if GameType <> gmtDemo then // this is save and game demo is not recording, abort + exit; + CopyFile(recordFileName, UserPathPrefix + '/VideoTemp/' + RecPrefix + '.hwd'); + end; + + Mix_QuerySpec(@frequency, @format, @channels); + AddFileLog('sound: frequency = ' + IntToStr(frequency) + ', format = ' + IntToStr(format) + ', channels = ' + IntToStr(channels)); + if format <> $8010 then + begin + // TODO: support any audio format + AddFileLog('Error: Unexpected audio format ' + IntToStr(format)); + exit; + end; + +{$IOCHECKS OFF} + // create sound file + filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.sw'; + Assign(audioFile, filename); + Rewrite(audioFile, 1); + if IOResult <> 0 then + begin + AddFileLog('Error: Could not write to ' + filename); + exit; + end; + + // create file with camera positions + filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtout'; + Assign(cameraFile, filename); + Rewrite(cameraFile); + if IOResult <> 0 then + begin + AddFileLog('Error: Could not write to ' + filename); + exit; + end; + + // save audio parameters in sound file + BlockWrite(audioFile, frequency, 4); + BlockWrite(audioFile, channels, 4); +{$IOCHECKS ON} + + // register callback for actual audio recording + Mix_SetPostMix(@RecordPostMix, nil); + + startTime:= SDL_GetTicks(); + flagPrerecording:= true; +end; + +procedure StopPreRecording; +begin + AddFileLog('StopPreRecording'); + flagPrerecording:= false; + + // call SDL_LockAudio because RecordPostMix may be executing right now + SDL_LockAudio(); + Close(audioFile); + Close(cameraFile); + Mix_SetPostMix(nil, nil); + SDL_UnlockAudio(); + + if not thumbnailSaved then + SaveThumbnail(); +end; + +procedure SaveCameraPosition; +var frame: TFrame; +begin + if (not thumbnailSaved) and (ScreenFade = sfNone) then + SaveThumbnail(); + + frame.realTicks:= SDL_GetTicks() - startTime; + frame.gameTicks:= GameTicks; + frame.CamX:= WorldDx; + frame.CamY:= WorldDy - cScreenHeight div 2; + frame.zoom:= zoom/cScreenWidth; + BlockWrite(cameraFile, frame, 1); +end; + +procedure freeModule; +begin + if flagPrerecording then + StopPreRecording(); +end; + +end. + +{$ENDIF} // USE_VIDEO_RECORDING diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uVisualGears.pas --- a/hedgewars/uVisualGears.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uVisualGears.pas Thu Aug 30 13:02:19 2012 -0400 @@ -135,7 +135,7 @@ sp: real; begin AddVisualGear:= nil; -if ((GameType = gmtSave) or (fastUntilLag and (GameType = gmtNet))) and // we are scrolling now +if ((GameType = gmtSave) or (fastUntilLag and (GameType = gmtNet)) or fastScrolling) and // we are scrolling now ((Kind <> vgtCloud) and (not Critical)) then exit; @@ -284,8 +284,9 @@ begin dx:= 0.005 * (random(15) + 10); dy:= 0.001 * (random(40) + 20); - if random(2) = 0 then - dx := -dx; + if random(2) = 0 then dx := -dx; + if random(2) = 0 then Tag:= 1 + else Tag:= -1; Frame:= 7 - random(2); FrameTicks:= random(20) + 15; end; @@ -295,6 +296,8 @@ dy:= 0; FrameTicks:= 740; Frame:= 19; + Scale:= 0.75; + Timer:= 1; end; vgtDroplet: begin @@ -640,9 +643,9 @@ vgtSmoke: DrawTextureF(SpritesData[sprSmoke].Texture, Gear^.scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, 7 - Gear^.Frame, 1, SpritesData[sprSmoke].Width, SpritesData[sprSmoke].Height); vgtSmokeWhite: DrawSprite(sprSmokeWhite, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame); vgtDust: if Gear^.State = 1 then - DrawSpriteRotatedF(sprSnowDust, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame, 1, Gear^.Angle) + DrawSpriteRotatedF(sprSnowDust, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame, Gear^.Tag, Gear^.Angle) else - DrawSprite(sprDust, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame); + DrawSpriteRotatedF(sprDust, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame, Gear^.Tag, Gear^.Angle); vgtFire: if (Gear^.State and gstTmpFlag) = 0 then DrawSprite(sprFlame, round(Gear^.X) + WorldDx - 8, round(Gear^.Y) + WorldDy, (RealTicks shr 6 + Gear^.Frame) mod 8) else @@ -956,10 +959,10 @@ exit; if hasBorder or ((Theme <> 'Snow') and (Theme <> 'Christmas')) then - for i:= 0 to Pred(vobCount * cScreenSpace div LAND_WIDTH) do + for i:= 0 to Pred(vobCount * cScreenSpace div 4096) do AddVisualGear(cLeftScreenBorder + random(cScreenSpace), random(1024+200) - 100 + LAND_HEIGHT, vgtFlake) else - for i:= 0 to Pred((vobCount * cScreenSpace div LAND_WIDTH) div 3) do + for i:= 0 to Pred((vobCount * cScreenSpace div 4096) div 3) do AddVisualGear(cLeftScreenBorder + random(cScreenSpace), random(1024+200) - 100 + LAND_HEIGHT, vgtFlake); end; diff -r 7ee319134713 -r bc7b1d228a2c hedgewars/uWorld.pas --- a/hedgewars/uWorld.pas Thu Aug 30 12:47:41 2012 -0400 +++ b/hedgewars/uWorld.pas Thu Aug 30 13:02:19 2012 -0400 @@ -60,7 +60,8 @@ uCaptions, uCursor, uCommands, - uMobile + uMobile, + uVideoRec ; var cWaveWidth, cWaveHeight: LongInt; @@ -80,6 +81,7 @@ stereoDepth: GLfloat; isFirstFrame: boolean; AMAnimType: LongInt; + recTexture: PTexture; const cStereo_Sky = 0.0500; cStereo_Horizon = 0.0250; @@ -377,6 +379,8 @@ timeTexture:= nil; FreeTexture(missionTex); missionTex:= nil; + FreeTexture(recTexture); + recTexture:= nil; end; function GetAmmoMenuTexture(Ammo: PHHAmmo): PTexture; @@ -954,7 +958,7 @@ //glPushMatrix; //glScalef(1.0, 1.0, 1.0); - if not isPaused then + if (not isPaused) and (GameType <> gmtRecord) then MoveCamera; if cStereoMode = smNone then @@ -985,7 +989,7 @@ DrawWorldStereo(0, rmRightEye); // detatch drawing from fbs - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, defaultFrame); glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); SetScale(cDefaultZoomLevel); @@ -1329,7 +1333,7 @@ r.w:= 3; DrawTextureFromRect(TeamHealthBarWidth + 16, cScreenHeight + DrawHealthY + smallScreenOffset, @r, HealthTex); - if not highlight and not hasGone and (TeamHealth > 1) then + if not highlight and (not hasGone) and (TeamHealth > 1) then for i:= 0 to cMaxHHIndex do if Hedgehogs[i].Gear <> nil then begin @@ -1581,6 +1585,33 @@ end end; +{$IFDEF USE_VIDEO_RECORDING} +// during video prerecording draw red blinking circle and text 'rec' +if flagPrerecording then + begin + if recTexture = nil then + begin + s:= 'rec'; + tmpSurface:= TTF_RenderUTF8_Blended(Fontz[fntBig].Handle, Str2PChar(s), cWhiteColorChannels); + tmpSurface:= doSurfaceConversion(tmpSurface); + FreeTexture(recTexture); + recTexture:= Surface2Tex(tmpSurface, false); + SDL_FreeSurface(tmpSurface) + end; + DrawTexture( -(cScreenWidth shr 1) + 50, 20, recTexture); + + // draw red circle + glDisable(GL_TEXTURE_2D); + Tint($FF, $00, $00, Byte(Round(127*(1 + sin(SDL_GetTicks()*0.007))))); + glBegin(GL_POLYGON); + for i:= 0 to 20 do + glVertex2f(-(cScreenWidth shr 1) + 30 + sin(i*2*Pi/20)*10, 35 + cos(i*2*Pi/20)*10); + glEnd(); + Tint($FF, $FF, $FF, $FF); + glEnable(GL_TEXTURE_2D); + end; +{$ENDIF} + SetScale(zoom); // Cursor @@ -1614,10 +1645,7 @@ uCursor.updatePosition(); {$ENDIF} z:= round(200/zoom); -if not PlacingHogs and (FollowGear <> nil) and (not isCursorVisible) and (not bShowAmmoMenu) and (not fastUntilLag) then - if (not autoCameraOn) then - FollowGear:= nil - else +if not PlacingHogs and (FollowGear <> nil) and (not isCursorVisible) and (not bShowAmmoMenu) and (not fastUntilLag) and autoCameraOn then if ((abs(CursorPoint.X - prevPoint.X) + abs(CursorPoint.Y - prevpoint.Y)) > 4) then begin FollowGear:= nil; @@ -1764,8 +1792,14 @@ if (not cHasFocus) and (GameState <> gsConfirm) then ParseCommand('quit', true); -if not cHasFocus then DampenAudio() -else UndampenAudio(); +{$IFDEF USE_VIDEO_RECORDING} +// do not change volume during prerecording as it will affect sound in video file +if not flagPrerecording then +{$ENDIF} + begin + if not cHasFocus then DampenAudio() + else UndampenAudio(); + end; end; procedure SetUtilityWidgetState(ammoType: TAmmoType); @@ -1822,6 +1856,7 @@ procedure initModule; begin fpsTexture:= nil; + recTexture:= nil; FollowGear:= nil; WindBarWidth:= 0; bShowAmmoMenu:= false; @@ -1853,7 +1888,9 @@ FreeTexture(timeTexture); timeTexture:= nil; FreeTexture(missionTex); - missionTex:= nil + missionTex:= nil; + FreeTexture(recTexture); + recTexture:= nil; end; end. diff -r 7ee319134713 -r bc7b1d228a2c project_files/hedgewars.pro --- a/project_files/hedgewars.pro Thu Aug 30 12:47:41 2012 -0400 +++ b/project_files/hedgewars.pro Thu Aug 30 13:02:19 2012 -0400 @@ -104,7 +104,12 @@ ../QTfrontend/ui/dialog/input_password.h \ ../QTfrontend/ui/widget/colorwidget.h \ ../QTfrontend/model/HatModel.h \ - ../QTfrontend/model/GameStyleModel.h + ../QTfrontend/model/GameStyleModel.h \ + ../QTfrontend/util/libav_iteraction.h \ + ../QTfrontend/ui/page/pagevideos.h \ + ../QTfrontend/net/recorder.h \ + ../QTfrontend/ui/dialog/ask_quit.h \ + ../QTfrontend/ui/dialog/upload_video.h SOURCES += ../QTfrontend/model/ammoSchemeModel.cpp \ ../QTfrontend/model/MapModel.cpp \ @@ -186,7 +191,12 @@ ../QTfrontend/ui/dialog/input_password.cpp \ ../QTfrontend/ui/widget/colorwidget.cpp \ ../QTfrontend/model/HatModel.cpp \ - ../QTfrontend/model/GameStyleModel.cpp + ../QTfrontend/model/GameStyleModel.cpp \ + ../QTfrontend/util/libav_iteraction.cpp \ + ../QTfrontend/ui/page/pagevideos.cpp \ + ../QTfrontend/net/recorder.cpp \ + ../QTfrontend/ui/dialog/ask_quit.cpp \ + ../QTfrontend/ui/dialog/upload_video.cpp win32 { SOURCES += ../QTfrontend/xfire.cpp diff -r 7ee319134713 -r bc7b1d228a2c share/hedgewars/Data/Graphics/Flags/cpu_plain.png Binary file share/hedgewars/Data/Graphics/Flags/cpu_plain.png has changed diff -r 7ee319134713 -r bc7b1d228a2c share/hedgewars/Data/Graphics/Graves/dragonball.png Binary file share/hedgewars/Data/Graphics/Graves/dragonball.png has changed diff -r 7ee319134713 -r bc7b1d228a2c share/hedgewars/Data/Graphics/Graves/dragonball.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Graves/dragonball.svg Thu Aug 30 13:02:19 2012 -0400 @@ -0,0 +1,606 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 7ee319134713 -r bc7b1d228a2c share/hedgewars/Data/Graphics/botlevels.png Binary file share/hedgewars/Data/Graphics/botlevels.png has changed diff -r 7ee319134713 -r bc7b1d228a2c share/hedgewars/Data/Locale/en.txt --- a/share/hedgewars/Data/Locale/en.txt Thu Aug 30 12:47:41 2012 -0400 +++ b/share/hedgewars/Data/Locale/en.txt Thu Aug 30 13:02:19 2012 -0400 @@ -492,7 +492,7 @@ 04:36=Well, sometimes you're just too bad in aiming. Get|some assistance using modern day technology.|Attack: Activate 04:37=Don't fear the daylight. It will just last one turn|but will enable you to absorb the damage you do to|other hogs.|Attack: Activate 04:38=The sniper rifle can be the most devastating weapon|in your whole arsenal, however it's very ineffective|at close quarters. The damage dealt increases with|the distance to its target.|Attack: Shoot (twice) -04:39=Fly to other parts of the map using the flying|saucer. This hard to master utility is able to|take you to almost any position on the battlefield.|Attack: Activate|Up/Left/Right: Apply force in one direction|Long Jump: Drop grenades or similar weapons +04:39=Fly to other parts of the map using the flying|saucer. This hard to master utility can|take you to almost any position on the battlefield.|Attack: Activate|Up/Left/Right: Apply force in one direction|Long Jump: Drop grenades or similar weapons 04:40=Set some ground on fire using this bottle filled|with (soon to be) burning liquid.|Attack: Hold to shoot with more power 04:41=The evidence nature might even top the flying|saucer. Birdy can carry your hog around and|drop eggs on your enemies!|Be quick, as using Birdy eats into your turn|time!|Attack: Activate and drop eggs|Up/Left/Right: Flap in one direction 04:42=This portable portal device is capable|of instantly transporting you, your enemies,|or your weaponry between two points on the|terrain.|Use it wisely and your campaign will be a...|HUGE SUCCESS!|Attack: Shoot a portal|Switch: Cycle portal colours @@ -506,7 +506,7 @@ 04:50=Is someone hiding underground?|Dig them out with a drill strike!|Timer controls how far it will dig. 04:51=Get in a free shot by hurling a ball of mud.|Stings a bit, and knocks hogs back. 04:52=UNUSED -04:53=Go on an adventure through time and space,|while leaving your comrades to fight on alone.|Be prepared to return at any time,|or for Sudden Death or if they are all defeated.|Disclaimer. Does not function in Sudden Death,|if you are alone, or if you are a King. +04:53=Take a trip through time and space,|while leaving your comrades to fight on alone.|Be prepared to return at any time,|or for Sudden Death or if they are all defeated.|Disclaimer. Does not function in Sudden Death,|if you are alone, or if you are a King. 04:54=INCOMPLETE 04:55=Spray a stream of sticky flakes.|Build bridges, bury enemies, seal off tunnels.|Be careful you don't get any on you! diff -r 7ee319134713 -r bc7b1d228a2c share/hedgewars/Data/Locale/hedgewars_zh_CN.ts --- a/share/hedgewars/Data/Locale/hedgewars_zh_CN.ts Thu Aug 30 12:47:41 2012 -0400 +++ b/share/hedgewars/Data/Locale/hedgewars_zh_CN.ts Thu Aug 30 13:02:19 2012 -0400 @@ -2,325 +2,240 @@ - AmmoSchemeModel - - new - 新建 - - - copy of - 副本 - - - - DrawMapWidget - - File error - 文件错误 - - - Cannot open file '%1' for writing - 无法打开文件 '%1' 写入 - 无法打开要写入的文件 '%1' - - - Cannot read file '%1' - 无法读取文件 '%1' - - - FreqSpinBox + Never 从不 + Every %1 turn - 每 %1 个回合 + 每隔 %1 回合 GameCFGWidget + Error 错误 + Illegal ammo scheme 无法使用此弹药设置 + Edit schemes 修改游戏设置 - - Edit weapons - 修改武器 - - - When this option is enabled selecting a game scheme will auto-select a weapon - 使用此项则游戏框架自动选择武器配备 - - - Game Options - 游戏选项 - - HWChatWidget - - %1 *** %2 has been removed from your ignore list - %1 *** %2 已经从您的忽略列表中移除 - - - %1 *** %2 has been added to your ignore list - %1 *** %2 已经添加到您的忽略列表中 - - - %1 *** %2 has been removed from your friends list - %1 *** %2 已经从您的朋友列表中移除 - + GameUIConfig - %1 *** %2 has been added to your friends list - %1 *** %2 已经添加到您的朋友列表中 - - - %1 has been removed from your ignore list - %1 已从您的忽略列表中移除 - - - %1 has been added to your ignore list - %1 已被添加到您的忽略列表中 - - - %1 has been removed from your friends list - %1 已从您的好友列表中移除 + + Error + 错误 - %1 has been added to your friends list - %1 已加入您的好友列表 - - - Stylesheet imported from %1 - 已从 %1 导入样式表 - - - Enter %1 if you want to use the current StyleSheet in future, enter %2 to reset! - 键入 %1 以使用当前的样式表,键入 %2 以重置! - - - Couldn't read %1 - 无法读取 %1 + + Cannot create directory %1 + 不能创建路径 - StyleSheet discarded - 已丢弃样式表 - - - StyleSheet saved to %1 - 样式表已保存到 %1 + + Quit + 退出 - Failed to save StyleSheet to %1 - 保存样式表到 %1 失败 - - - %1 is not a valid command! - %1 不是一个有效的命令! - - - Kicking %1 ... - 正在踢出 %1 ... + + Cannot save options to file %1 + 不能把选项保存到 %1 HWForm + Error 错误 + + Please, select demo from the list above + 请选择一个DEMO + + + OK 确认 + + Please, select server from the list above + 请选择一个服务器 + + + + Please, select record from the list above + 请选择一个记录 + + + Cannot save record to file %1 无法录入文件 %1 + Unable to start the server 开启服务端出现错误 + new - - - - Please select record from the list above - 请选择一个记录 - - - DefaultTeam - 默认队伍 - - - Hedgewars Demo File - File Types - 刺猬大作战回放文件 - - - Hedgewars Save File - File Types - 刺猬大作战存档文件 - - - Demo name - 回放录像名称 - - - Demo name: - 回放录像名称: - - - Game aborted - 游戏被中断 - - - Password - 密码 - - - Your nickname %1 is -registered on Hedgewars.org -Please provide your password below -or pick another nickname in game config: - 您的昵称%1 -在Hedgewars.org已注册 -请输入您的密码 -或从游戏配置中选择另一个昵称: - - - No password supplied. - 没有填写密码. - - - Nickname - 昵称 - - - Some one already uses - your nickname %1 -on the server. -Please pick another nickname: - 您的昵称 %1 -以被其他人 -在服务器上使用 -请选择另一个昵称: - - - No nickname supplied. - 没有填写昵称。 + HWGame + + Error + 错误 + + + + Unable to start the server: %1. + 开启服务端出现错误: %1. + + + en.txt zh_CN.txt + + Cannot save demo to file %1 + 不能把demo保存为 %1 + + + + Quit + 退出 + + + Cannot open demofile %1 DEMO %1 打不开 + + + Unable to run engine: %1 ( + 引擎无法启动: %1 ( + + + + Error reading training config file + 训练设置文件无法读取 + HWMapContainer + Map 地图 + Themes 主题 + Filter 过滤 + All 全部 + Small 小型 + Medium 中型 + Large 大型 + Cavern 洞穴 + Wacky - 险峻 - - - Type - 类型 + 曲折 + + + HWNet - Small tunnels - 小型隧道 - - - Medium tunnels - 中型隧道 + + Error + 错误 - Large tunnels - 大型隧道 - - - Small floating islands - 小型漂浮岛屿 + + The host was not found. Please check the host name and port settings. + 未发现主机。请检查主机名和端口设置。 - Medium floating islands - 中型漂浮岛屿 + + Connection refused + 连接被拒绝 + + + + HWNetServer + + + Error + 错误 - Large floating islands - 大型漂浮岛屿 - - - Seed - 作种 - - - Set - 设定 + + Unable to start the server: %1. + 无法启动服务端: %1. HWNetServersModel + Title - 名称 + 标题 + IP IP + Port 端口 @@ -328,177 +243,109 @@ HWNewNet - The host was not found. Please check the host name and port settings. - 没找到主机。请检查主机名和端口设置。 + + Error + 错误 + + The host was not found. Please check the host name and port settings. + 错误没找到这个主机。请检查主机名和端口设置。 + + + Connection refused 连接被拒绝 + + *** %1 joined + *** %1 加入 + + + + *** %1 left + *** %1 离开 + + + + *** %1 left (%2) + *** %1 离开 (%2) + + + Quit reason: 退出原因: + Room destroyed 房间损坏 + You got kicked 被踢出 + Password - 密码 - - - Your nickname %1 is -registered on Hedgewars.org -Please provide your password -or pick another nickname: - 您的昵称%1 -在Hedgewars.org已注册 -请输入您的密码 -或选择另一个昵称: - - - %1 *** %2 has joined the room - %1 *** %2 进入这个房间了 - - - %1 *** %2 has joined - %1 *** %2 加入了 + 密码 - %1 *** %2 has left (%3) - %1 *** %2 离开了(%3) - - - %1 *** %2 has left - %1 *** %2 离开了 - - - Your nickname %1 is -registered on Hedgewars.org -Please provide your password below -or pick another nickname in game config: - 您的昵称%1 -在Hedgewars.org已注册 -请输入您的密码 -或从游戏配置中选择另一个昵称: - - - Nickname - 昵称 - - - User quit - 用户退出 + + Enter your password: + 输入你的密码: KB + SDL_ttf returned error while rendering text, most propably it is related to the bug in freetype2. It's recommended to update your freetype lib. - 渲染文字时SDL_ttf 返回错误,可能有关freetype2的bug。建议升级 freetype。 + SDL_ttf 返回错误-渲染文字失败,可能有关freetype2的bug。建议升级 freetype。 PageAdmin + Server message: - 服务器信息: - - - Set message - 设定信息 - - - Clear Accounts Cache - 清空账户缓存 - - - Fetch data - 获取数据 + 服务器信息: - Server message for latest version: - 最新版本的服务器信息: - - - Server message for previous versions: - 之前版本的服务器信息: - - - Latest version protocol number: - 最新版本的通讯协议号码: - - - MOTD preview: - MOTD预览: - - - Set data - 设定数据 + + Set message + 设定信息 PageConnecting + Connecting... 连接中... - - Cancel - 取消 - - - - PageDrawMap - - Undo - 取消 - - - Clear - 清除 - - - Load - 读取 - - - Save - 保存 - - - Load drawn map - 读取已经绘制的地图 - - - Drawn Maps (*.hwmap);;All files (*.*) - 绘制的地图 (*.hwmap);;全部文件 (*.*) - - - Save drawn map - 保存绘制的地图 - - - Drawn Maps - 绘制的地图 - - - All files - 全部文件 - PageEditTeam + + Discard + 中止 + + + + Save + 保存 + + + General 常规 + Advanced 进阶 @@ -506,393 +353,87 @@ PageGameStats + <p>The best shot award was won by <b>%1</b> with <b>%2</b> pts.</p> - <p>最佳射手奖给与 <b>%1</b>:伤害 <b>%2</b>点。</p> - - - <p>The best killer is <b>%1</b> with <b>%2</b> kills in a turn.</p> - - <p>最佳杀手是 <b>%1</b>单回合击杀刺猬数:<b>%2</b></p> - + <p>最佳射手是<b>%1</b>。伤害 <b>%2</b>点。</p> - <p>A total of <b>%1</b> hedgehog(s) were killed during this round.</p> - - <p>本轮总共有<b>%1</b>只刺猬被击杀</p> - - - - Details - 细节 - - - Health graph - 健康值图形 - - - Ranking - 排名 - - - The best shot award was won by <b>%1</b> with <b>%2</b> pts. - 最佳射手<b>%1</b>制造了<b>%2</b>点伤害。 - - - The best killer is <b>%1</b> with <b>%2</b> kills in a turn. + + <p>The best killer is <b>%1</b> with <b>%2</b> kills in a turn.</p> - 最佳杀手 <b>%1</b> 完成了单回合<b>%2</b>次击杀。 + <p>最佳杀手<b>%1</b>:却敌<b>%2</b></p> - A total of <b>%1</b> hedgehog(s) were killed during this round. - - 总共 <b>%1</b> 只刺猬在本轮失去生命。 - - - - (%1 kill) + + <p>A total of <b>%1</b> hedgehog(s) were killed during this round.</p> - (%1 击杀) - - - - (%1 kills) - (%1 击杀) - - - <b>%1</b> thought it's good to shoot his own hedgehogs with <b>%2</b> pts. - - <b>%1</b>以为给自己的刺猬造成 <b>%2</b> 点创伤是小意思。 + <p>有<b>%1</b>个刺猬在此局失去生命。</p> - - <b>%1</b> killed <b>%2</b> of his own hedgehogs. - - <b>%1</b> 整垮了 <b>%2</b> 只自己的刺猬。 - - - - <b>%1</b> was scared and skipped turn <b>%2</b> times. - - <b>%1</b> 受惊了,共计 <b>%2</b> 次放弃。 - - - - - PageInGame - - In game... - - PageMain - Local Game (Play a game on a single computer) - 单机游戏(在一台电脑上) - - - Network Game (Play a game across a network) - 网络游戏(通过网络) - - - Simply pick the same color as a friend to play together as a team. Each of you will still control his or her own hedgehogs but they'll win or lose together. - Tips - 点击同色作为同一组的友军。控制权不分享,但是共同胜利/失败。 - - - Some weapons might do only low damage but they can be a lot more devastating in the right situation. Try to use the Desert Eagle to knock multiple hedgehogs into the water. - Tips - 有些武器可能威力低下但是有毁灭性的效果。比如沙漠之鹰能把多个刺猬打入水中。 - - - If you're unsure what to do and don't want to waste ammo, skip one round. But don't let too much time pass as there will be Sudden Death! - Tips - 加入不确定怎么做,不要浪费弹药,跳过此回合。但是注意突然时间! - - - Want to save ropse? Release the rope in mid air and then shoot again. As long as you don't touch the ground you'll reuse your rope without wasting ammo! - Tips - 保存绳子?在半空释放然后再次射出。只要不接触地面停止就可以继续使用同一根不会浪费! - - - If you'd like to keep others from using your preferred nickname on the official server, register an account at http://www.hedgewars.org/. - Tips - 如果您确定好了一个昵称不想让别人使用,那么在 http://www.hedgewars.org/. 注册一个帐号吧。 - - - You're bored of default gameplay? Try one of the missions - they'll offer different gameplay depending on the one you picked. - Tips - 厌倦了默认的玩法?试试任务吧——每个任务都有不同的玩法。 - - - By default the game will always record the last game played as a demo. Select 'Local Game' and pick the 'Demos' button on the lower right corner to play or manage them. - Tips - 默认情况下游戏记录最后的游戏作为Demo,选择单机游戏——然后Demo——然后点击右下角开始回放或者整理。 - - - Hedgewars is Open Source and Freeware we create in our spare time. If you've got problems, ask on our forums but please don't expect 24/7 support! - Tips - 刺猬大作战是一个开放源代码的免费软件,它充分利用了我们的业余时间。如果您有问题,到论坛来吧,不过7×24小时支持不可能! - - - Hedgewars is Open Source and Freeware we create in our spare time. If you like it, help us with a small donation or contribute your own work! - Tips - 刺猬大作战是一个开放源代码的免费软件,它充分利用了我们的业余时间。如果您喜欢它,我们接受您的捐赠/感谢! - - - Hedgewars is Open Source and Freeware we create in our spare time. Share it with your family and friends as you like! - Tips - 刺猬大作战是一个开放源代码的免费软件,它充分利用了我们的业余时间。与他人分享它吧! - - - From time to time there will be official tournaments. Upcoming events will be announced at http://www.hedgewars.org/ some days in advance. - Tips - 官方的竞赛一直存在。临近时去 http://www.hedgewars.org/ 即可看到。 - - - Hedgewars is available in many languages. If the translation in your language seems to be missing or outdated, feel free to contact us! - Tips - Hedgewars 被翻译成多种语言,中文是刺猬大作战——同样为翻译名。如果您的语言翻译有什么缺失/过时/遗漏或任何问题,来联系我们吧! - - - Hedgewars can be run on lots of different operating systems including Microsoft Windows, Mac OS X and Linux. - Tips - 刺猬大作战可以运行的操作系统包括:GNU/Linux、Mac OS X、MicroSoft Windows。 + + Multiplayer + 多人游戏 - Always remember you're able to set up your own games in local and network/online play. You're not restricted to the 'Simple Game' option. - Tips - 您可以建立自己的网络游戏/局域网游戏。不仅限于 "简单游戏" 选项。 - - - Create an account on http://www.hedgewars.org/ to keep others from using your most favourite nickname while playing on the official server. - Tips - 在官方服务器 http://www.hedgewars.org/ 建立自己的帐号——就能一直使用最喜欢的昵称 - - - While playing you should give yourself a short break at least once an hour. - Tips - 最好玩一个小时就休息一下,如果你要继续用电脑。 - - - If your graphics card isn't able to provide hardware accelerated OpenGL, try to enable the low quality mode to improve performance. - Tips - 假如你的显卡不能提供OpenGL硬件加速,试着用降低效果的方式运行。 - - - We're open to suggestions and constructive feedback. If you don't like something or got a great idea, let us know! - Tips - 我们接受意见和建设性反馈。假如您有好电子或者不喜欢的东西,告诉我们! - - - Especially while playing online be polite and always remember there might be some minors playing with or against you as well! - Tips - 特别是网络游戏,请有礼貌记住对方也和您一样是人! - - - Special game modes such as 'Vampirism' or 'Karma' allow you to develop completely new tactics. Try them in a custom game! - Tips - 特别游戏模式“吸血“、”因果报应“需要全新的战术。现在自定义游戏里试试! + + Single Player + 单人游戏 - The Windows version of Hedgewars supports Xfire. Make sure to add Hedgwars to its game list so your friends can see you playing. - Tips - Windows版本的刺猬大作战支持Xfire。添加它到游戏列表里让您的朋友看到。 - - - You should never install Hedgewars on computers you don't own (school, university, work, etc.). Please ask the responsible person instead! - Tips - 您不应该在不属于您的计算机上安装刺猬大作战——比如学校/工作场所等。您应当向计算机的负责人咨询! - - - Hedgewars can be perfect for short games during breaks. Just ensure you don't add too many hedgehogs or use an huge map. Reducing time and health might help as well. - Tips - 刺猬大作战适合短时间休息,不需要太多刺猬挤在大地图上。 + + Net game + 网络游戏 - No hedgehogs were harmed in making this game. - Tips - 在制作这款游戏时没有任何刺猬受到伤害。 - - - Connect one or more gamepads before launching the game to be able to assign their controls to your teams. - Tips - 在运行游戏前连接游戏板 - - - Hedgewars is Open Source and Freeware we create in our spare time. If someone sold you the game, you should try get a refund! - Tips - 刺猬大作战是开放源代码的免费软件,用我们的闲暇时间创造。如果有人卖给你,那么应该把钱拿回来! - - - Create an account on %1 to keep others from using your most favourite nickname while playing on the official server. - Tips - 在 %1 建立一个帐号阻止其他人使用你喜欢的名称在官方服务器游戏。 - - - There are three different jumps available. Tap [high jump] twice to do a very high/backwards jump. - Tips - 三种跳跃方式。点击[高跳]两次做出跳跃高度极限的后空翻。 + + Saved games + 存档 - Afraid of falling off a cliff? Hold down [precise] to turn [left] or [right] without actually moving. - Tips - 害怕掉下悬崖?按住[精确]后再点击[左][右]就会只转身,不移动位置。 - - - Some weapons require special strategies or just lots of training, so don't give up on a particular tool if you miss an enemy once. - Tips - 有些武器需要特殊策略或者仅仅是大量的练习,假如你一次失去准星,也不要放弃。 - - - Most weapons won't work once they touch the water. The Homing Bee as well as the Cake are exceptions to this. - Tips - 多数武器不会在接触水之后生效。归巢的蜜蜂和蛋糕是例外。 - - - The Old Limbuger only causes a small explosion. However the wind affected smelly cloud can poison lots of hogs at once. - Tips - 老干酪发射器造成小规模爆炸,然后产生随风移动的有毒云雾——能一次毒害多个刺猬。 - - - The Piano Strike is the most damaging air strike. You'll lose the hedgehog performing it, so there's a huge downside as well. - Tips - 钢琴攻击是最大威力的空袭。弹奏钢琴的刺猬会跟着钢琴返回天堂。 - - - The Homing Bee can be tricky to use. It's turn radius depends on it's velocity, so try to not use full power. - Tips - 归巢的蜜蜂有些技巧。它的回转半径和初速有关,最好不用全力发射。 - - - Sticky Mines are a perfect tool to create small chain reactions knocking enemy hedgehogs into dire situations ... or water. - Tips - 黏着地雷是创造小范围连锁反应的绝佳武器。 + + Demos + Demo - The Hammer is most effective when used on bridges or girders. Hit hogs will just break through the ground. - Tips - 锤是桥梁上/分界处最佳武器之一,只是刚刚好把刺猬打陷——如果没底就没办法了。 - - - If you're stuck behind an enemy hedgehog, use the Hammer to free yourself without getting damaged by an explosion. - Tips - 假如对方刺猬把你堵住了,一锤打下去让自己轻松些。 - - - The Cake's maximum walking distance depends on the ground it has to pass. Use [attack] to detonate it early. - Tips - 蛋糕的最大行走距离取决于地表。也可按下[攻击键]激活起爆。 - - - The Flame Thrower is a weapon but it can be used for tunnel digging as well. - Tips - 火焰喷射器是一种武器,也是一种开路机器。 - - - Use the Incinerating Grenade to temporary keep hedgehogs from passing terrain such as tunnels or platforms. - Tips - 燃烧瓶可以短时阻止刺猬通过特定区域(比如通道或平台) - - - Want to know who's behind the game? Click on the Hedgewars logo in the main menu to see the credits. - Tips - 想要知道谁是此游戏的幕后人员?点击主菜单的Hedgewars Logo就可以看到贡献者名单。 - - - Like hedgewars? Become a fan on %1 or join our group at %2. You could follow us on %3 as well! - Tips - 喜欢刺猬大作战(hedgewars)?那么加入我们 %1 或者 %2.。你可以在 %3 跟随我们! - - - You can find your Hedgewars configuration files under "My Documents\Hedgewars". Create backups or take the files with you, but don't edit them by hand. - Tips - 你可以在( 我的文档\Hedgewars)里找到设置文件。可以创建备份,但不要手动修改。 + + Setup + 设置 - You can find your Hedgewars configuration files under "Hedgewars" in your home directory. Create backups or take the files with you, but don't edit them by hand. - Tips - 你可以在家目录找到 .hedgewars。可以创建备份,但不要手动修改。 - - - Connect one or more gamepads before starting the game to be able to assign their controls to your teams. - Tips - 在游戏开始前连接游戏手柄才能用它们操控你的队伍。 - - - If your graphics card isn't able to provide hardware accelerated OpenGL, try to update the associated drivers. - Tips - 加入你的显卡不能使用OpenGL硬件加速,请升级相应驱动。 - - - Like Hedgewars? Become a fan on %1 or join our group at %2. You could follow us on %3 as well! - Tips - 喜欢刺猬大作战(Hedgewars)吗?加入 %2 ,成为 %1 粉丝,也可以在 %3 跟随我们! - - - Feel free to draw your own graves, hats, flags or even maps and themes! But note that you'll have to share them somewhere to use them online. - Tips - 欢迎你自己绘制墓碑,帽子(头饰),旗帜或者地图,主题!但是记住,如果要在网上使用,你的必须把它们分享出来。 - - - Really want to wear a specific hat? Donate to us and receive an exclusive hat of your choice! - Tips - 非常想要一个帽子?捐赠的话就给你! - - - Keep your video card drivers up to date to avoid issues playing the game. - Tips - 保持显卡驱动最新避免可能的麻烦。 + + About + 关于 - You're able to associate Hedgewars related files (savegames and demo recordings) with the game to launch them right from your favorite file or internet browser. - Tips - 你可以关联刺猬大作战的相关文件(比如存档和回放)到本游戏以便在网络或文件浏览器中直接打开这些文件。 - - - Want to save ropes? Release the rope in mid air and then shoot again. As long as you don't touch the ground you'll reuse your rope without wasting ammo! - Tips - 想要节省绳子?放开绳子之后再次发射,只要你不曾脱离绳子接触接触地面就可以继续使用同一根! - - - Like Hedgewars? Become a fan on %1 or follow us on %2! - Tips - 喜欢刺猬大作战Hedgewars吗?来 %1 或者 %2 追随我们吧! - - - You can find your Hedgewars configuration files under "Library/Application Support/Hedgewars" in your home directory. Create backups or take the files with you, but don't edit them by hand. - Tips - 在家目录的"Library/Application Support/Hedgewars"找到刺猬的配置文件。备份随你,但是不要手动编辑。 + + Exit + 退出 - You can find your Hedgewars configuration files under ".hedgewars" in your home directory. Create backups or take the files with you, but don't edit them by hand. - Tips - 在家目录的".hedgewars"找到刺猬的配置文件。备份随你,但是不要手动编辑。 - - - The Windows version of Hedgewars supports Xfire. Make sure to add Hedgewars to its game list so your friends can see you playing. - Tips - Windows版本的刺猬大作战支持Xfire。您可以添加刺猬大作战到它的游戏列表里让您的朋友看到。 + + Local Game (Play a game on a single computer) + 本地游戏(在一台电脑上) - Use the Molotov or Flame Thrower to temporary keep hedgehogs from passing terrain such as tunnels or platforms. - Tips - 使用燃烧瓶或火焰喷射器可以短时阻止刺猬通过特定区域(比如隧道或平台). - - - The Homing Bee can be tricky to use. Its turn radius depends on its velocity, so try to not use full power. - Tips - 使用归巢的蜜蜂有些技巧,它的回转半径和发射速度有关,所以最好不要全力发射。 - - - Downloadable Content - 可下载内容 + + Network Game (Play a game across a network) + 网络游戏(通过网络) PageMultiplayer + + Back + 返回 + + + Start 开始 @@ -900,40 +441,43 @@ PageNet + + Local + 本地 + + + + Internet + Internet + + + Error 错误 - Please select server from the list above + + Please, select server from the list above 请选择一个服务器 PageNetGame + Control - 房间管理 - - - Error - 错误 - - - Please enter room name - 请键入房间名 - - - OK - 确定 + Ctrl PageNetType + LAN game 局域网游戏 + Official server 官方服务器 @@ -941,925 +485,681 @@ PageOptions + New team 新队伍 + Edit team 修改队伍设定 - Delete team - 删除队伍 - - - New weapon scheme - 新武器配置 - - - Edit weapon scheme - 修改武器配置 - - - Delete weapon scheme - 删除武器配置 - - - You can't edit teams from team selection. Go back to main menu to add, edit or delete teams. - 您不能在队伍选择界面修改队伍。请返回主页面进行添加、修改、删除队伍等操作。 + + Save + 保存 - New scheme - 新框架 - - - Edit scheme - 修改框架 + + Back + 返回 - Delete scheme - 删除框架 + + Weapons set + 新武器设定 - New weapon set - 新武器配置 - - - Edit weapon set - 修改武器配置 - - - Delete weapon set - 删除武器配置 + + Edit + 修改当前武器设定 PagePlayDemo + Error 错误 + + Please, select record from the list + 请从列表选择记录 + + + OK 确认 + Rename dialog 重命名对话框 + Enter new file name: 输入新的文件名: + Cannot rename to 不能改变名字 + Cannot delete file 不能删除文件 - - Please select record from the list - 请从列表选择记录 - PageRoomsList + Create 建立 + Join 加入 + Refresh 刷新 + Error 错误 + + Please, enter room name + 请键入房间名 + + + OK 确认 - Admin features - 管理员功能 - - - Room Name: - 房间名: - - - This game is in lobby. -You may join and start playing once the game starts. - 游戏正在大厅中。 -您可以加入等待游戏开始。 - - - This game is in progress. -You may join and spectate now but you'll have to wait for the game to end to start playing. - 游戏正在进行中。 -您可以加入观战但必须等游戏结束才能参与游戏。 - - - %1 is the host. He may adjust settings and start the game. - %1 是房主,他可以调整设置、开始游戏。 - - - Random Map - 随机地图 - - - Games may be played on precreated or randomized maps. - 游戏可以在预先创建或者随机产生的地图上进行。 - - - The Game Scheme defines general options and preferences like Round Time, Sudden Death or Vampirism. - 游戏设置包括一般选项例如回合时间,突然死亡或吸血模式。 - - - The Weapon Scheme defines available weapons and their ammunition count. - 武器配置包括可以选用的武器和弹药数量。 - - - There are %1 clients connected to this room. - - 有 %1 个客户端连接到这个房间。 - - - - There are %1 teams participating in this room. - - 有 %1 个队伍加入这个房间。 - - - - Please enter room name - 请键入房间名 - - - Please select room from the list + + Please, select room from the list 请从列表选中房间 - Random Maze - 随机迷宫 - - - State: - 游戏状态 - - - Rules: - 规则 - - - Weapons: - 武器 - - - Search: - 搜索 - - - Clear - 清除 - - - Warning - 警告 - - - The game you are trying to join has started. -Do you still want to join the room? - 你要加入的游戏已经开始了。 -还要进入房间吗? - - - %1 players online - - %1 个玩家在线 - + + Admin features + 管理员功能 PageScheme + New - 新模式 - - - Delete - 删除 - - - Defend your fort and destroy the opponents, two team colours max! - 保卫你的城堡,破坏对手的,努力吧! - - - Teams will start on opposite sides of the terrain, two team colours max! - 队伍开始在对手的地盘,努力! - - - Land can not be destroyed! - 地面无法破坏! - - - Add an indestructable border around the terrain - 添加不可毁坏地边界 - - - Lower gravity - 低重力 - - - Assisted aiming with laser sight - 激光瞄准辅助 - - - All hogs have a personal forcefield - 每个刺猬都有一个力场 - - - Enable random mines - 开启随机地雷 - - - Gain 80% of the damage you do back in health - 伤害的80%变成自身力量 - - - Share your opponents pain, share their damage - 分担你的对手的疼痛 - - - Your hogs are unable to move, put your artillery skills to the test - 你的刺猬不能移动,检验你射击技巧的时候到了 - - - Random - 随机 - - - Seconds - 秒钟 + 新游戏 - Order of play is random instead of in room order. - 出场顺序是随机的而不是按照房间顺序。 - - - Play with a King. If he dies, your side dies. - 国王不能死。 - - - Take turns placing your hedgehogs before the start of play. - 在开局前轮流手动放置刺猬。 - - - Ammo is shared between all teams that share a colour. - 同色队伍共享所有弹药。 - - - Disable girders when generating random maps. - 禁止随机生成地图时使用梁。 - - - Disable land objects when generating random maps. - 禁止随机生成地图时使用地面物体。 - - - AI respawns on death. - AI 死亡再生。 - - - Attacking does not end your turn. - 攻击不会结束当前回合。 - - - Weapons are reset to starting values each turn. - 在每回合武器将自动重置到开始设定。 - - - Each hedgehog has its own ammo. It does not share with the team. - 每个刺猬都有独立的弹药,而非团队分享。 - - - All (living) hedgehogs are fully restored at the end of turn - 所有活着的刺猬在回合结束时完全恢复健康 - - - You will not have to worry about wind anymore. - 不用担心风的影响了。 - - - Wind will affect almost everything. - 风无所不在。 - - - Copy - 备份 - - - Teams in each clan take successive turns sharing their turn time. - 在同一集团中的队伍在共用的回合时间里使用连续的回合。 - - - Add an indestructible border around the terrain - 添加不可毁坏地边界 - - - Add an indestructible border along the bottom - 在底部添加一个不可毁坏的边界 + + Delete + 删除 PageSelectWeapon + + Back + 返回 + + + Default 默认 + Delete 删除 - New - 新模式 + + Save + 保存 + + + + PageSimpleGame + + + Back + 返回 - Copy - 备份 + + Simple Game + 简单游戏 PageSinglePlayer - Simple Game (a quick game against the computer, settings are chosen for you) - 快速游戏 (使用预设对抗电脑) + + Simple Game + 简单游戏 + + + + Training + 训练 + + + + Multiplayer + 多人游戏 + + Saved games + 游戏存档 + + + + Demos + Demo + + + + Simple Game (a quick game against the computer, settings are chosen for you) + 快速游戏 (对抗电脑,固定设置) + + + Multiplayer (play a hotseat game against your friends, or AI teams) 多人游戏 (热坐对抗朋友或AI) + Training Mode (Practice your skills in a range of training missions). IN DEVELOPMENT - 训练模式 (一系列训练任务)。开发中 + 训练模式 (一系列训练任务)。开发中 + Demos (Watch recorded demos) Demo (观看记录的Demo) + Load (Load a previously saved game) 读取 (读取之前保存的游戏) - - Campaign Mode (...). IN DEVELOPMENT - 战役模式 ——开发中 - - - Campaign Mode (...) - 战役模式 (...) - - - Training Mode (Practice your skills in a range of training missions) - 训练模式(在一系列训练任务中练习你的技能) - - - - PageTraining - - No description available - 没有可用描述 - - - Select a mission! - 选择一个任务! - QAction + Kick - 踢出 + + Start 开始 + Restrict Joins 限制参与 + Restrict Team Additions - 限制增加团队 + 限制团队插件 + Info 信息 + Ban 屏蔽 - - Follow - 跟随 - - - Ignore - 忽略 - - - Add friend - 添加朋友 - - - Unignore - 取消忽略 - - - Remove friend - 移除朋友 - - - Update - 更新 - QCheckBox - Check for updates at startup - 启动时检查程序升级 + + Enable sound + 开启音效 - Enable sound - 开启游戏音效 - - + Fullscreen 游戏全屏幕 + + Forts mode + 城堡模式 + + + Show FPS 显示帧率 (FPS) + Alternative damage show 另一种伤害显示方式 + Enable music - 开启游戏音乐 + 开启音乐 + Frontend fullscreen - 前端界面全屏幕 - - - Append date and time to record file name - 记录名称中包含具体时间和日期 + 界面全屏幕 - Reduced quality - 降低显示效果 - - - Show ammo menu tooltips - 显示武器菜单提示 + + Divide teams + 分组 - Enable frontend sounds - 开启前端界面音效 + + Append date and time to record file name + 记录名称中包含具体时间日期 - Enable frontend music - 开启前端界面音乐 + + Solid land + 固实地面 - Frontend effects - 前端界面效果 + + Reduce Quality + 降低质量 QComboBox + generated map... 生成地图... + Human 玩家 - Level - Lv 级别 - - - (System default) - (系统默认) - - - generated maze... - 生成迷宫... - - - Mission - 任务 - - - Community - 社区 - - - Any - 任意 - - - In lobby - 大厅中 + + Level 5 + Lv 5 - In progress - 进行中 - - - Default - 默认 - - - Pro mode - 高手模式 - - - Shoppa - 绳子党 - - - Basketball - 篮球 - - - Minefield - 雷区 - - - Barrel mayhem - 炼狱场 - - - Tunnel hogs - 刺猬洞 - - - Crazy - 疯狂刺猬 + + Level 4 + Lv 4 - hand drawn map... - 手绘地图... - - - Disabled - 禁用 - - - Red/Cyan - 红/青 - - - Cyan/Red - 青/红 - - - Red/Blue - 红/蓝 - - - Blue/Red - 蓝/红 - - - Red/Green - 红/绿 - - - Green/Red - 绿/红 + + Level 3 + Lv 3 - Side-by-side - 横向排列 - - - Top-Bottom - 自顶向下 - - - Wiggle - - - - Red/Cyan grayscale - 红/青 灰度 + + Level 2 + Lv 2 - Cyan/Red grayscale - 青/红 灰度 - - - Red/Blue grayscale - 红/蓝 灰度 + + Level 1 + Lv 1 - Blue/Red grayscale - 蓝/红 灰度 - - - Red/Green grayscale - 红/绿 灰度 - - - Green/Red grayscale - 绿/红 灰度 + + Level + Lv 级别 QGroupBox + Team Members 成员 + + Team + 队伍 + + + Fort 城堡模式 + Key binds 键位绑定 + Teams 队伍 + Audio/Graphic options 音频/视频选项 + + Net nick + 昵称 + + + + Net options + 网络选项 + + + + Landscape + 地形 + + + + Game scheme + 游戏设置 + + + Playing teams 玩家队伍 + + Team level + 队伍级别 + + + Net game - 局域网络游戏 + 网络游戏 - Weapons - 武器 + + Servers list + 服务器列表 + + Weapons + 武器 + + + + Scheme options + 游戏设定 + + + Game Modifiers 游戏修改 + Basic Settings 基本设置 - - Team Settings - 队伍设定 - - - Misc - 杂项 - - - Schemes and Weapons - 游戏框架和武器配置 - QLabel + Net nick 网络游戏昵称 + + Server address + 服务器地址 + + + + <div align="center"><h1>Hedgewars</h1><h3>Version 0.8</h3><p><a href="http://www.hedgewars.org/">http://www.hedgewars.org/</a></p><br>This program is distributed under the GNU General Public License</div> + <div align="center"><h1>刺猬大作战</h1><h3>0.8</h3><p><a href="http://www.hedgewars.org/">http://www.hedgewars.org/</a></p><br>This program is distributed under the GNU General Public License</div> + + + + <h2>Developers:</h2><p>Andrey Korotaev &lt;<a href="mailto:unC0Rr@gmail.com">unC0Rr@gmail.com</a>&gt;<br>Igor Ulyanov &lt;<a href="mailto:iulyanov@gmail.com">iulyanov@gmail.com</a>&gt;</p><h2>Translations:</h2>english: Andrey Korotaev &lt;<a href="mailto:unC0Rr@gmail.com">unC0Rr@gmail.com</a>&gt;<br>russian: Andrey Korotaev &lt;<a href="mailto:unC0Rr@gmail.com">unC0Rr@gmail.com</a>&gt; + <h2>Developers:</h2><p>Andrey Korotaev &lt;<a href="mailto:unC0Rr@gmail.com">unC0Rr@gmail.com</a>&gt;<br>Igor Ulyanov &lt;<a href="mailto:iulyanov@gmail.com">iulyanov@gmail.com</a>&gt;</p><h2>Translations:</h2>english: Andrey Korotaev &lt;<a href="mailto:unC0Rr@gmail.com">unC0Rr@gmail.com</a>&gt;<br>russian: Andrey Korotaev &lt;<a href="mailto:unC0Rr@gmail.com">unC0Rr@gmail.com</a>&gt; + + + + difficulty: + 难度: + + + + <h3>Version 0.8</h3> + <h3>版本 0.8</h3> + + + This program is distributed under the GNU General Public License - This program is distributed under the GNU General Public License + This program is distributed under the GNU General Public License + + + + <h2>Translations:</h2> + <h2>翻译:</h2> + + + + <h2>Developers:</h2> + <h2>开发者:</h2> + + <h2>Translations:</h2><p> + <h2>翻译:</h2><p> + + + + <h2>Special thanks:</h2><p> + <h2>特别感谢:</h2><p> + + + + <h3>Version 0.8.1</h3> + <h3>版本 0.8.1</h3> + + + + <h2></h2><p></p> + <h2></h2><p></p> + + + + Turn time + 回合时间 + + + + Initial health + 初始生命值 + + + + <p>The best shot award was won by <b>%1</b> with <b>%2</b> pts.</p> + <p>射击冠军<b>%1</b> with <b>%2</b> .</p> + + + + <p>A total of <b>%1</b> Hedgehog(s) were killed during this round.</p> + <p>阵亡<b>%1</b> </p> + + + + <h3>Version 0.9</h3> + <h3>版本0.9</h3> + + + Resolution 分辨率 + FPS limit FPS 上限 + Developers: 开发者: + Art: 艺术: + Translations: 翻译: + Special thanks: 特别感谢: + Server name: 服务器名: + Server port: 服务器端口: + Host: 主机: + Port: 端口: + Weapons 武器 + + <h3>Version 0.9.2</h3> + <h3>版本0.9.2</h3> + + + Version 版本 + + <p>The best shot award was won by <b>%1</b> with <b>%2</b> kills.</p> + <p>最佳射手<b>%1</b>取得的战果 <b>%2</b></p> + + + Sounds: 声音: + Initial sound volume 初始音量 + Damage Modifier - 伤害修正值 + 伤害修改 + Turn Time 回合时间 + Initial Health 初始生命值 + Sudden Death Timeout 死亡模式倒计时 + + Case Probability + 箱子掉落几率 + + + Scheme Name: 设置名称: + Crate Drops 箱子降落 + Game scheme 游戏设置 - - Mines Time - 布雷时间 - - - Mines - 地雷 - - - % Dud Mines - % 地雷故障 - - - Name - 名称 - - - Type - 类型 - - - Grave - 墓碑 - - - Flag - 旗帜 - - - Voice - 声音 - - - Locale - Locale - - - Restart game to apply - 需要重新启动游戏方可应用 - - - Explosives - 爆炸物 - - - Tip: - 提示: - - - This development build is 'work in progress' and may not be compatible with other versions of the game. Some features might be broken or incomplete. Use at your own risk! - 当前运行的为开发版本,不同其他版本兼容。功能或许损坏、不完整。请自行承担风险! - - - Quality - 图像质量 - - - Sudden Death Water Rise - 死亡模式水位上涨 - - - Sudden Death Health Decrease - 死亡模式健康降低 - - - % Rope Length - % 绳长 - - - % Health Crates - % 生命箱 - - - Health in Crates - 生命箱的值数 - - - Gameplay - 游戏 - - - Stereo rendering - 立体渲染 - - - Style - 样式 - - - Scheme - 模式 - - - Password - 密码 - - - % Get Away Time - % 脱身时间 - - - This program is distributed under the GNU General Public License v2 - 本程序在GNU通用许可证协议第二版(GNU GPLv2)下发布 - QLineEdit + unnamed 无名 - - hedgehog %1 - 刺猬 %1 - QMainWindow + + -= by unC0Rr =- + -= by unC0Rr =- + + + + Hedgewars + 刺猬大作战 + + + Hedgewars %1 刺猬大作战 %1 @@ -1867,10 +1167,12 @@ QMessageBox + Error 错误 + Failed to open data directory: %1 Please check your installation @@ -1879,233 +1181,292 @@ 请检查 + Network 网络 + Connection to server is lost - 与服务器的连接丢失 + 服务器连接丢失 + Weapons 武器 + Can not delete default weapon set - 不能删除默认武器设定 + 不能删除默认武器设定 + Really delete this weapon set? 真的删除这个武器设定吗? + Can not edit default weapon set - 不能更改默认的武器设定 - - - Can not overwrite default weapon set '%1'! - 不能覆盖默认的武器配置 '%1'! - - - All file associations have been set. - 所有相关文件已经设定。 - - - Teams - 队伍 - - - Really delete this team? - 真的要删除队伍? - - - Schemes - 游戏框架 - - - Can not delete default scheme '%1'! - 无法删除默认游戏框架 '%1'! - - - File association failed. - 文件关联失败。 - - - Really delete this game scheme? - 真的删除此游戏框架? - - - Can not delete default weapon set '%1'! - 无法删除武器配置%1'! + 不能更改默认的武器设定 QObject + Error 错误 + Cannot create directory %1 无法创建路径 %1 - OK - 确认 + + Quit + 退出 - Nickname - 昵称 - - - Please enter your nickname - 请输入您的昵称 + + OK + 确认 QPushButton + + Single Player + 单人游戏 + + + + Multiplayer + 多人游戏 + + + + Net game + 网络游戏 + + + + Demos + Demo + + + Setup 设置 + + Exit + 退出 + + + + Back + 返回 + + + + Simple Game + 简单游戏 + + + + Discard + 中止 + + + + Save + 保存 + + + Play demo 播放 demo + + New team + 新队伍 + + + + Edit team + 编辑队伍 + + + Connect 连接 + + Disconnect + 失去连接 + + + + Join + 加入 + + + + Create + 创建 + + + + Add Team + 添加队伍 + + + Go! 上场! + Start 开始 + + About + 关于 + + + Start server 开始服务端 + Update 更新 + + Waiting + 等待中 + + + Load 读取 + + Weapons scheme + 武器设定 + + + + Training + 训练 + + + Specify 指定 + default 默认 + Rename 重命名 + OK 确定 + Cancel 取消 + Delete 删除 - Ready - 准备好了 + + Join official server + 加入官方服务器 - Random Team - 随机分配队伍 - - - Associate file extensions - 相关文件扩展 - - - more - 更多 + + Ready + 准备好了 QTableWidget - Room Name - 房间名 + + Room name + 房间名称 - C - 人数限制 - - - T - 时间限制 + + Players number + 玩家数量 - Owner - 创建者 + + Round in progress + 回合数 + + + + QToolBox + + + Actions + 行动 - Map - 地图 - - - Rules - 规则 - - + Weapons 武器 - - - SelWeaponWidget - Weapon set - 武器设置 - - - Probabilities - 空中支援几率 + + Weapon properties + 武器选项 - Ammo in boxes - 弹药箱 - - - Delays - 延迟回合数 - - - new - - - - copy of - 副本 + + Other + 其他 TCPBase + Error 错误 + Unable to start the server: %1. 无法开始服务端: %1. + Unable to run engine: %1 ( 无法运行引擎: %1 ( @@ -2113,678 +1474,362 @@ ToggleButtonWidget + Fort Mode 城堡模式 + Divide Teams 团体行动 + Solid Land 固实地面 + Add Border 添加边界 + Low Gravity 低重力 + Laser Sight 激光瞄准 + Invulnerable 刀枪不入 + Add Mines - 布置地雷 - - - Vampirism - 吸血鬼 - - - Karma - 因果报应 - - - Artillery - 射术 - - - Random Order - 随机顺序 - - - King - 国王模式 - - - Place Hedgehogs - 手动放置刺猬 - - - Clan Shares Ammo - 团队共享弹药 - - - Disable Girders - 禁止梁 - - - Disable Land Objects - 禁止地面物件 - - - AI Survival Mode - AI生存模式 - - - Unlimited Attacks - 无限攻击 - - - Reset Weapons - 重置武器 - - - Per Hedgehog Ammo - 每个刺猬的弹药 - - - Reset Health - 重置生命值 - - - Disable Wind - 禁止风力作用 - - - More Wind - 让风来地更猛烈吧 - - - Tag Team - 为队伍添加标签 - - - Add Bottom Border - 添加底部边界 + 布置地雷 binds + up + left + right + down + + jump + + + + attack 攻击 + put + switch 切换 + slot 1 slot 1 + slot 2 slot 2 + slot 3 slot 3 + slot 4 slot 4 + slot 5 slot 5 + slot 6 slot 6 + slot 7 slot 7 + slot 8 slot 8 + timer 1 sec 定时1秒 + timer 2 sec 定时2秒 + timer 3 sec 定时3秒 + timer 4 sec 定时4秒 + timer 5 sec 定时5秒 + capture - 截取 + 夺取 + quit 退出 + find hedgehog - 寻找刺猬 + 找到 刺猬 + ammo menu 弹药菜单 + volume down 降低音量 + volume up 提高音量 + change mode 改变模式 + pause 暂停 + slot 9 slot 9 + hedgehogs info 刺猬大作战 信息 + chat 聊天 + chat history 聊天记录 + confirmation 确认 + precise aim 练习瞄准 - - zoom in - 放大 - - - zoom out - 缩小 - - - reset zoom - 重置 - - - long jump - 远跳 - - - high jump - 高跳 - - - slot 10 - slot 10 - - - - binds (categories) - - Basic controls - 基本控制 - - - Weapon controls - 武器控制 - - - Camera and cursor controls - 镜头和光标控制 - - - Other - 其他 - - - - binds (descriptions) - - Move your hogs and aim: - 移动您的刺猬同时瞄准: - - - Traverse gaps and obstacles by jumping: - 使用跳跃越过沟渠、障碍: - - - Fire your selected weapon or trigger an utility item: - 使用选择的武器开火、使用物品: - - - Pick a weapon or a target location under the cursor: - 选取一个武器或者瞄准光标下的地点: - - - Switch your currently active hog (if possible): - 切换到您当前活动的刺猬(如果可用): - - - Pick a weapon or utility item: - 选择一个武器或物品: - - - Set the timer on bombs and timed weapons: - 设置定时炸弹等武器起爆时间: - - - Move the camera to the active hog: - 移动镜头到选中的刺猬: - - - Move the cursor or camera without using the mouse: - 不用鼠标移动光标或镜头: - - - Modify the camera's zoom level: - 调整镜头放大倍数: - - - Talk to your team or all participants: - 同队友或全部参与者对话: - - - Pause, continue or leave your game: - 暂停、继续或离开游戏: - - - Modify the game's volume while playing: - 调整游戏时音量: - - - Toggle fullscreen mode: - 全屏模式: - - - Take a screenshot: - 截图: - - - Toggle labels above hedgehogs: - 切换刺猬头顶标签的显示方式: - - binds (keys) - - Axis - Axis轴 - + teams - (Up) - (上) - - - (Down) - (下) - - - Hat - 帽子 + + Hedgehogs + 刺猬 - (Left) - (左) - - - (Right) - (右) - - - Button - 按键 - - - Keyboard - 键盘 - - - Mouse: Left button - 鼠标:左键 + + hedgehog 1 + 刺猬 1号 - Mouse: Middle button - 鼠标:中键 - - - Mouse: Right button - 鼠标:右键 - - - Mouse: Wheel up - 鼠标滚轮:向上 - - - Mouse: Wheel down - 鼠标滚轮:向下 + + hedgehog 2 + 刺猬 2号 - Backspace - 退格键 - - - Tab - Tab + + hedgehog 3 + 刺猬 3号 - Clear - Num Lock / Clear + + hedgehog 4 + 刺猬 4号 - Return - 回车 - - - Pause - 暂停键 - - - Escape - ESC键(退出键) + + hedgehog 5 + 刺猬 5号 - Space - 空格键 - - - Delete - Del(删除键) - - - Numpad 0 - 小键盘0 - - - Numpad 1 - 小键盘1 + + hedgehog 6 + 刺猬 6号 - Numpad 2 - 小键盘2 - - - Numpad 3 - 小键盘3 + + hedgehog 7 + 刺猬 7号 - Numpad 4 - 小键盘4 - - - Numpad 5 - 小键盘5 - - - Numpad 6 - 小键盘6 + + hedgehog 8 + 刺猬 8号 - Numpad 7 - 小键盘7 - - - Numpad 8 - 小键盘8 - - - Numpad 9 - 小键盘9 - - - Numpad . - 小键盘. - - - Numpad / - 小键盘/ + + Goddess + 女神 - Numpad * - 小键盘* - - - Numpad - - 小键盘- + + Isis + 艾希丝 - Numpad + - 小键盘+ + + Astarte + 阿斯德尔特 - Enter - 回车键 - - - Equals - 等于 - - - Up - + + Diana + 黛安娜 - Down - - - - Right - - - - Left - - - - Insert - 插入键 + + Aphrodite + 阿弗罗狄特 - Home - Home键 - - - End - End键 - - - Page up - 向上翻页键 - - - Page down - 向下翻页键 - - - Num lock - 小键盘数字锁 + + Hecate + 赫卡特 - Caps lock - 大小写切换键 - - - Scroll lock - Scroll Lock键 - - - Right shift - 右Shift键 - - - Left shift - 左Shift键 + + Demeter + 得墨忒耳 - Right ctrl - 右Ctrl键 - - - Left ctrl - 左Ctrl键 + + Kali + 迦梨 - Right alt - 右Alt键 + + Inanna + 維納斯 - Left alt - 左Alt键 - - - Right meta - 右meta键 - - - Left meta - 左meta键 + + Fruits + 水果 - A button - A 键 - - - B button - B 键 - - - X button - X 键 - - - Y button - Y 键 + + Banana + 香蕉 - LB button - LB 键 - - - RB button - RB 键 + + Apple + 苹果 - Back button - 返回键 - - - Start button - 开始键 - - - Left stick - 左摇杆 - - - Right stick - 右摇杆 + + Orange + 橙子 - Left stick (Right) - 右(左摇杆) - - - Left stick (Left) - 左(左摇杆) + + Lemon + 柠檬 - Left stick (Down) - 下(左摇杆) - - - Left stick (Up) - 上(左摇杆) - - - Left trigger - 左制动 + + Pineapple + 菠萝 - Right trigger - 右制动 - - - Right stick (Down) - 下(右摇杆) + + Mango + 芒果 - Right stick (Up) - 上(右摇杆) + + Peach + 桃子 - Right stick (Right) - 右(右摇杆) - - - Right stick (Left) - 左(右摇杆) - - - DPad - DPad板 + + Plum + 梅子 diff -r 7ee319134713 -r bc7b1d228a2c share/hedgewars/Data/Locale/zh_CN.txt --- a/share/hedgewars/Data/Locale/zh_CN.txt Thu Aug 30 12:47:41 2012 -0400 +++ b/share/hedgewars/Data/Locale/zh_CN.txt Thu Aug 30 13:02:19 2012 -0400 @@ -1,825 +1,47 @@ ; Simplified Chinese locale 00:00=手榴弹 -00:01=集束炸弹 -00:02=火箭筒 -00:03=归巢的蜜蜂 -00:04=霰弹枪 -00:05=大锤 -00:06=跳过回合 +00:01=子母炸弹 +00:02=火箭炮 +00:03=UFO +00:04=散弹枪 +00:05=气锤 +00:06=掠过 00:07=绳索 00:08=地雷 00:09=沙漠之鹰 00:10=炸药 00:11=球棒 -00:12=Shoryuken +00:12=升龙拳 00:13=秒 00:14=降落伞 00:15=空袭 -00:16=地雷空袭 +00:16=地雷袭击 00:17=喷灯 -00:18=钢梁 +00:18=钢板 00:19=传送 -00:20=切换刺猬 +00:20=切换 00:21=迫击炮 00:22=鞭子 00:23=神风特工队 00:24=蛋糕 -00:25=引诱 +00:25=吸引 00:26=西瓜炸弹 00:27=地狱礼花 -00:28=钻头火箭 +00:28=钻地火箭 00:29=弹珠炮 -00:30=汽油弹空袭 -00:31=遥控轰炸机 +00:30=燃烧弹 +00:31=轰炸机 00:32=低重力 -00:33=增强伤害 -00:34=无敌 +00:33=附加伤害 +00:34=刀枪不入 00:35=加时 00:36=激光瞄准 -00:37=吸血 -00:38=狙击枪 -00:39=UFO -00:40=燃烧瓶 -00:41=鸟儿 -00:42=传送器 -00:43=飞来的钢琴 -00:44=毒奶酪 -00:45=正弦能量炮 -00:46=火焰喷射器 -00:47=固定地雷 -00:48=大锤 -00:49=复苏 -00:50=电钻空袭 -00:51=土块 -01:00=开战! -01:01=平局 -01:02= %1 胜利! +01:00=战斗啦! +01:01=平手 +01:02= %1 胜! 01:03=音量 %1% 01:04=暂停 -01:05=确定要退出? (是Y/否Esc) -01:06=死亡模式! -01:07=%1 剩余 -01:08=燃料 -01:09=同步中... -01:10=使用本工具不会结束回合! -01:11=您还不能用它! -01:12=死亡模式前最后一回合! -01:13=%1 回合倒计时! -01:14=预备上, %1! - -; Event messages -; Hog (%1) died -; 02:00=%1 has kicked the bucket! -02:00=%1 翘辫子了! -; 02:00=%1 has seen the light! -02:00=%1 目睹圣光降临! -; 02:00=%1 never saw that coming! -02:00=%1 无法瞑目! -; 02:00=%1 waves goodbye! -02:00=%1 向大家挥手道别。 -; 02:00=%1 has gone to a better place! -02:00=%1 去了极乐世界! -; 02:00=%1 meets his maker! -02:00=%1 去见造物主了! -; 02:00=%1 can hang on no longer! -02:00=%1 再也受不了了! -; 02:00=%1 has done his duty! -02:00=%1 完成了他的使命! -; 02:00=%1 makes the ultimate sacrifice! -02:00=%1 做了最大的牺牲! -; 02:00=%1 departs this mortal coil! -02:00=%1 摆脱了躯壳的束缚! -; 02:00=%1 makes like a tree and leaves! -02:00=%1 叶落归根。 -; 02:00=%1 has timed out! -02:00=%1 大限已至。 -; 02:00=%1 says peace out! -02:00=%1 悄然离场了。 -; 02:00=%1 will be fondly remembered! -02:00=%1 永远活在我们心中! -; 02:00=%1 has an aneurysm! -02:00=%1 不治而亡。 -; 02:00=%1 leaves behind a wife and child -02:00=%1 留下一家孤儿寡母 -; 02:00=%1 has launched his last bazooka -02:00=%1 发射了最后一发火箭弹 -; 02:00=%1 has tossed his last grenade -02:00=%1 扔出了最后一枚手榴弹 -; 02:00=%1 has baked his last cake -02:00=%1 烘烤了最后一块蛋糕 -; 02:00=%1 has swung on his last rope -02:00=%1 最后一次甩出了绳索 -; 02:00=%1 has called his last airstrike -02:00=%1 最后一次呼叫空袭 -; 02:00=%1 has pumped his last shotgun -02:00=%1 最后一次抽出了霰弹枪 -; 02:00=%1 has thrown his last melon -02:00=%1 最后一次扔出了西瓜炸弹 -; 02:00=%1 has drawn his last deagle -02:00=%1 最后一次拔出了沙漠之鹰 -; 02:00=%1 took one shot too many -02:00=%1 挨了太多枪子了 -; 02:00=%1 could really have used a health crate -02:00=%1 真该用下医疗包的 -; 02:00=%1 has gone to play a better game -02:00=%1 去玩更有意思的游戏去了 -; 02:00=%1 has ragequit life -02:00=%1 拔网线了! -; 02:00=%1 fails -02:00=%1 失败了 -; 02:00=Poor poor %1... -02:00=可怜的 %1... -; 02:00=%1 prefers wormux -02:00=%1 更喜欢 Warmux -; 02:00=%1 has been blocking shots with his face -02:00=%1 勇于面对子弹,结果相当惨烈 -; 02:00=%1 is a hero amongst me...err..hogs -02:00=%1 是位英雄……额……在刺猬当中 -; 02:00=%1 finds his place in Valhalla -02:00=%1 在勇者纪念碑上找到了自己的位置 -; 02:00=%1 has left the building -02:00=%1 离开了这间屋子 -; 02:00=%1 goes the way of the dinosaurs -02:00=%1 步上了恐龙的道路 -; 02:00=%1 brings hedgehogs one step closer to extinction -02:00=%1 让刺猬向物种灭绝更近了一步 -; 02:00=%1 brings a tear to my eye -02:00=%1 带走了我一滴眼泪 -; 02:00=%1 is an ex-hog -02:00=%1 生前是一只刺猬 -; 02:00=%1 is pushing up the daisies -02:00=%1 被菊花簇拥 -; 02:00=%1 has ceased to be -02:00=%1 被删除了 -; 02:00=Say goodbye to %1 -02:00=对 %1 说再见 -; 02:00=No hope left for %1 -02:00=%1 没有希望了 -; 02:00=%1 faces the final curtain -02:00=%1 面容被落下的帷幕遮住了 -; 02:00=Smoke 'em if you got 'em, %1 -02:00=%1 抓紧时间实现你最后的愿望吧 -; 02:00=%1 suffers a Spontaneous Massive Existence Failure -02:00=%1 遭遇了自发性大规模故障 -; 02:00=%1 has passed on -02:00=%1 走了 -; 02:00=%1 is stone dead -02:00=%1 永垂不朽 -; 02:00=%1 is no more -02:00=%1 不在了 -; 02:00=%1 has expired -02:00=%1 已故 -; 02:00=Bereft of life, %1 rests in peace -02:00=%1 安详地躺着 -; 02:00=%1 joins the choir invisible -02:00=%1 加入了隐形唱诗班 -; 02:00=Farewell %1, we hardly knew ye! -02:00=%1, 永别了,我们还没熟悉你呢! -; 02:00=%1 had a low tolerance for being shot -02:00=%1 抗打击能力不足 -; 02:00=%1 could have used an extra life -02:00=%1 本该有第二条命的 -; 02:00=Is there a doctor in the house? -02:00=这里有医生吗? - -; Hog (%1) drowned -; 02:01=%1 plays submarine! -02:01=%1 去玩潜水艇了! -; 02:01=%1 mimics the Titanic! -02:01=%1 学泰坦尼克去了! -; 02:01=%1 swims like a stone! -02:01=%1 石沉大海! -;02:01=%1 checks out the deep end -02:01=%1 说要去检查深水区 -;02:01=%1 goes glug glug glug -02:01=%1 :“咕噜咕噜咕噜……” -;02:01=%1 goes splash -02:01=%1 栽入水花里 -;02:01=%1 forgot his armbands -02:01=%1 忘记了戴臂章 -;02:01=%1 really should have taken swimming lessons -02:01=%1 真的该去学游泳的 -;02:01=%1 left his surfboard at home -02:01=%1 把救生圈忘家了 -;02:01=%1 is washed up -02:01=%1 被冲走了 -;02:01=%1 is one soggy hog -02:01=%1 脑子进水了 -;02:01=%1 forgot to bring his life jacket -02:01=%1 忘记带救生衣了 -;02:01=%1 goes splish splash splish -02:01=%1 实现了水上飘,身后一片水花荡漾 -;02:01=%1 is sleeping with the fishes -02:01=%1 将会和鱼睡在一起 -;02:01=%1 thinks the water physics suck in this game -02:01=%1 认为这游戏关于落水的设定糟糕透了 -;02:01=%1 looks thirsty -02:01=%1 看样子很渴 -;02:01=the sea claims %1 -02:01=大海吞没了 %1 -;02:01=%1 is lost at sea -02:01=%1 在海上迷失了 -;02:01=%1 should have brought his scuba gear -02:01=%1 应该要带潜水工具的 -;02:01=%1 gets a burial at sea -02:01=%1 享受到了海葬待遇 -;02:01=%1 has that sinking feeling -02:01=%1 觉得自己在下沉 -;02:01=%1 is practicing his backstroke -02:01=%1 终于能实践自己的游泳理论了 -;02:01=%1 goes in search of the Titanic -02:01=%1 去泰坦尼克号寻宝了 -;02:01=%1 is not Jesus -02:01=很遗憾 %1 不是耶稣 -;02:01=%1 is finding Nemo -02:01=%1 正在寻找Nemo -;02:01=%1 springs a leak -02:01=%1 钻入了一个水洼 -;02:01=You've gotta wonder how many hogs are down there -02:01=你会知道海底还会有多少同伴的 -;02:01=%1 makes the ocean slightly higher -02:01=%1 让海平面高了那么一点, 就一点 -;02:01=%1 didn't enlist in the Navy -02:01=很明显 %1 没在海军服役过 -;02:01=%1 is doing his impersonation of a dead fish -02:01=%1 其实是在模仿死鱼啦 -;02:01=At least you didn't go down the toilet, %1 -02:01=还好 %1 你不是掉进了厕所 -;02:01=Sonic couldn't swim and neither can %1 -02:01=索尼克不能游泳, %1 也一样 -;02:01=%1 wants to play Ecco the dolphin -02:01=%1 想玩海底漫步 -;02:01=%1 has gone to visit Aquaria -02:01=%1 去水族馆报到了 -;02:01=%1 has found the lost city of Atlantis -02:01=%1 找到了传说中的亚特兰蒂斯城 -;02:01=%1 aims for the lead role in Bioshock 3 -02:01=%1 立志在生化奇兵3中担任头号角色 -;02:01=Your doggy paddle could use a little work, %1 -02:01=你的狗刨式会有点用的, %1 -;02:01=%1 should have brought a jet ski -02:01=%1 竟然没带摩托艇 -;02:01=%1 doesn't like watersports -02:01=%1 不喜欢水上运动 -;02:01=%1 is forever blowing bubbles -02:01=%1 学会了绝技: 神风吹泡泡 -;02:01=%1 is short of a raft -02:01=%1 需要一个救生艇 -;02:01=%1 thinks salt water is good for the skin -02:01=%1 认为盐水对皮肤有好处 -;02:01=%1 gets salt water in his wounds -02:01=%1 的伤口沾上了盐水 -;02:01=%1 has walked the plank -02:01=%1 错过了那块木板 -;02:01=%1 has a bath -02:01=%1 洗澡去了 -;02:01=%1 is wet wet wet -02:01=%1 全身都是水,水,水…… -;02:01=%1 gets his quills wet -02:01=%1 把刺全弄湿了 -;02:01=It's Davy Jones' locker for %1 -02:01=深海阎王正在等待 %1 - -; Round starts -; 02:02=Let's fight! -02:02=开战吧! -; 02:02=Armed and ready! -02:02=武装准备! -;02:02=Let's get ready to rumble! -02:02=准备对轰! -;02:02=Let's get it on! -02:02=为胜利而战吧! -;02:02=Let's get this party started -02:02=让我们开始这个派对吧 -;02:02=Last hog standing wins -02:02=胜利属于最后一个生还者 -;02:02=Let's go! -02:02=出发吧! -;02:02=Let's rock! -02:02=一起摇滚吧! -;02:02=Let's jam! -;02:02=It's beginning... -02:02=开始了... -;02:02=This is the start of something big -02:02=这是一个伟大的开始 -;02:02=Welcome to Hedgewars -02:02=欢迎来到刺猬大作战 -;02:02=Welcome to the front lines -02:02=欢迎来到前线 -;02:02=Crush your enemies! -02:02=目标:粉碎你的敌人! -;02:02=May the best hog win -02:02=祝愿胜利属于最厉害的刺猬! -;02:02=Victory or death -02:02=胜利或死亡 -;02:02=To the victor goes the spoils -02:02=战利品只属于胜利者 -;02:02=Losing is not an option -02:02=字典里面应该没有"输"这个字的 -;02:02=Cry havoc! Let loose the hogs of war! -02:02=放声喊吧! 这是刺猬的战争! -;02:02=Hedgewars, brought to you by Hedgewars.org -02:02=欢迎来到刺猬大作战, Hedgewars.org 为你呈现 -02:02=GL HF -;02:02=Just count yourself lucky you're not up against Tiyuri -02:02=你看你多幸运不是在对战 Tiyuri -;02:02=Just count yourself lucky you're not up against unC0Rr -02:02=你看你多幸运不是在对战 unC0Rr -;02:02=Just count yourself lucky you're not up against Nemo -02:02=你看你多幸运不是在对战 Nemo -;02:02=Just count yourself lucky you're not up against Smaxx -02:02=你看你多幸运不是在对战 Smaxx -;02:02=Just count yourself lucky you're not up against Jessor -02:02=你看你多幸运不是在对战 Jessor -;02:02=Give it your all! -02:02=展现你的一切吧! -;02:02=The losers do the cleaning up! -02:02=输的要罚扫厕所! -;02:02=Let the fight of the millenium begin -02:02=宇宙之战开始了 -;02:02=Let the fight of the century begin -02:02=世纪之战开始了 -;02:02=Let the fight of the decade begin -02:02=正义之战开始了 -;02:02=Let the fight of the year begin -02:02=年度争霸战开始了 -;02:02=Let the fight of the month begin -02:02=本月之星争霸战开始了 -;02:02=Let the fight of the week begin -02:02=每周擂主争霸战开始了 -;02:02=Let the fight of the day begin -02:02=本日最强入围赛开始了 -;02:02=Let the fight of the hour begin -02:02=我们能战一小时! -;02:02=Do your best! -02:02=诸君努力! -;02:02=Destroy the enemy! -02:02=目标: 摧毁敌人 -;02:02=Good luck -02:02=祝你好运 -;02:02=Have fun~ -02:02=开心玩~ -;02:02=Fight the good fight -02:02=漂亮的战斗 -;02:02=Fight dirty -02:02=不择手段 -;02:02=Fight with honour -02:02=满载荣誉而战 -;02:02=Don't give up -02:02=教练告诉你: 别放弃 -;02:02=Never surrender -02:02=永不屈服! -;02:02=Rock 'em and sock 'em! -02:02=蹂虐对手 -;02:02=Let the fragfest begin! -02:02=积分赛开始! -;02:02=I hope you're ready for a tussle! -02:02=你准备好恶战了么? -;02:02=Go Go Go! -02:02=冲冲冲! -;02:02=Hedgehogs advance! -02:02=刺猬向前冲! -;02:02=Bring it to them! -02:02=炸飞他们! -;02:02=Have no fear! -02:02=无所畏惧! -;02:02=Be brave and conquer -02:02=敢于征服! - -; Round ends (win; unused atm) -02:03=回合结束(胜利) - -; Round ends (draw; unused atm) -02:04=回合结束(平局) - -; New health crate -;02:05=Incoming aid! -02:05=医疗包! -;02:05=Medic! -02:05=急救包! -;02:05=First aid from the skies! -02:05=救援物资空运来了! -;02:05=A health pack for you -02:05=你的医疗包到了 -;02:05=Good health.. in box form! -02:05=生命就在那箱子里! -;02:05=The doctor calls -02:05=医生的紧急呼叫 -;02:05=Fresh band-aids! -02:05=新鲜创可贴! -;02:05=This will make you feel better -02:05=吃了这个感觉会好些的... -;02:05=A Hi-Potion! Whoops wrong game -02:05=兴奋剂!呃。。。走错地方了 -;02:05=A pick-me-up! -02:05=万金油! -;02:05=Grab it -02:05=捉住它 -;02:05=A healthy snack -02:05=健康食品 -;02:05=A remedy to pain -02:05=止痛饼来了 -;02:05=Correct Dosage: as many as you can find! -02:05=使用方法: 吃得越多越好 -;02:05=Urgent delivery -02:05=紧急物资 -;02:05=Supplies! -02:05=补给! - -; New ammo crate -; 02:06=More weapons! -02:06=武器! -;02:06=Reinforcements! -02:06=增援! -;02:06=Lock and load! -02:06=准备! -;02:06=I wonder what weapon is in there? -02:06=我要的那个会在的吧... -;02:06=Supplies! -02:06=补给! -;02:06=What could be inside? -02:06=里面会有啥呢? -;02:06=Christmas comes early in Hedgewars -02:06=刺猬大作战每天都是圣诞节 -;02:06=A present! -02:06=礼物送到! -;02:06=Special delivery! -02:06=特快专递! -;02:06=It was a nightmare getting this through customs -02:06=本局的噩梦来了 -;02:06=Destructive toys from the heavens -02:06=玩具从天堂掉下来了 -;02:06=Warning! Contents Volatile -02:06=警告! 内含危险物品 -;02:06=Pick it up or blow it up, choice is yours -02:06=拿走或打爆, 随你 -;02:06=Goodies! -02:06=好玩意儿! -;02:06=Mmmmm Ammo -02:06=弹药!!!! -;02:06=A box of destructive power -02:06=潘朵拉之盒 -;02:06=Airmail! -02:06=天降之物! -;02:06=Whatever's in that box, it ain't pizza -02:06=无论里面是啥, 那肯定不会是软妹子 -;02:06=Get it! -02:06=拿走它! -;02:06=Weapon drop incoming -02:06=武器掉下来了! -;02:06=Don't let the enemy grab that! -02:06=别让敌人拿了! -;02:06=Shiny new toys! -02:06=新玩具! -;02:06=A mysterious box! -02:06=神秘的箱子! - -; New utility crate -; 02:07=Tooltime! -02:07=工具箱! -;02:07=This could come in handy... -02:07=这可能派上用场 -;02:07=Utilities! -02:07=工具! -;02:07=Utilise this box -02:07=工具在这里! -;02:07=Watch out below -02:07=快看这里! -;02:07=More utilities! -02:07=更多选择更多欢笑, 尽在工具包 -;02:07=Tools for you! -02:07=一堆工具, 送给你! -;02:07=This should be good! -02:07=这看见起来蛮好... -;02:07=Use this wisely -02:07=使用这个才是明智的选择 -;02:07=Ooo this box is heavy -02:07=好重...好重... -;02:07=You might need this -02:07=会有用的 - -; Hog (%1) skips his turn -; 02:08=%1 is sooo boring... -02:08=%1 太无聊了... -;02:08=%1 couldn't be bothered -02:08=%1 不想被打扰! -;02:08=%1 is one lazy hog -02:08=%1 太懒了 -;02:08=%1 is thoughtless -02:08=%1 太轻率了 -;02:08=%1 gave up -02:08=%1 放弃了 -;02:08=You snooze you lose, %1 -02:08=不认真你就输了, %1 -;02:08=%1 shamelessly skips -02:08=%1 无耻的跳过了本回合 -;02:08=%1 is really lazy -02:08=%1 真的太懒了 -;02:08=%1 needs a little more motivation -02:08=%1 没有动力了 -;02:08=%1 is a pacifist -02:08=%1 是和平主义者 -;02:08=%1 has a breather -02:08=%1 需要喘息一下 -;02:08=%1 has a rest -02:08=%1 需要休息 -;02:08=%1 chills out -02:08=%1 发冷了 -;02:08=%1 has no faith in his own abilities -02:08=%1 做啥都没信心了 -;02:08=%1 decides to do nothing at all -02:08=%1 决定啥都不做 -;02:08=%1 lets the enemy destroy itself -02:08=%1 认为敌人会自杀的 -;02:08=%1 would be terrible at parties -02:08=%1 将会陷入可怕的事件中 -;02:08=%1 hides out -02:08=%1 说:“你看不到我,你看不到我……” -;02:08=%1 has decided to pass on this opportunity -02:08=%1 已经决定放弃这个机会 -;02:08=%1 decides the best thing he can do is...nothing -02:08=%1 决定他现在最应该做的是......坐着不动 -;02:08=%1 is a big wuss -02:08=%1 大笨蛋! -;02:08=Buck Buck Buck, %1 is a chicken -02:08=%1 是小鸡鸡 -;02:08=%1 is looking a little yellow -02:08=%1 看来有点印堂发黑 -;02:08=%1 is a coward! -02:08=%1 是懦夫! -;02:08=%1 is waiting for sudden death -02:08=%1 在等待突然死亡模式 -;02:08=%1 is not the fighting type -02:08=%1 不是战斗系的 -;02:08=%1 is reconsidering his purpose in life -02:08=%1 正在重新寻找他的人生 -;02:08=%1 was never much of a good shot anyway -02:08=%1 从来没一次打准的 -;02:08=%1 didn't want to join the army in the first place -02:08=%1 不想参军 -;02:08=Stop wasting our time, %1 -02:08=别浪费时间了! %1 -;02:08=I'm dissapointed in you, %1 -02:08=我对你失望了, %1 -;02:08=Come on, you can do better than that %1 -02:08=%1 明明就能做的更好的 -;02:08=%1's will has broken -02:08=%1 会被打飞的 -;02:08=%1 apparently has better things to do -02:08=%1 显然有更好的事情等着做 -;02:08=%1 is scared stiff -02:08=%1 怕刺激 -;02:08=%1 has fallen asleep -02:08=%1 睡着了 - -; Hog (%1) hurts himself only -; 02:09=%1 should practice aiming! -02:09=%1 该练练瞄准了! -; 02:09=%1 seems to hate himself. -02:09=%1 似乎看自己很不爽。 -; 02:09=%1 is standing on the wrong side! -02:09=%1 在表演乌龙! -; 02:09=%1 makes like an emo -02:09=%1 以为自己无敌 -; 02:09=%1 was holding his weapon the wrong way around -02:09=%1 好像把武器拿错方向了 -;02:09=%1 is a little sadistic -02:09=%1 有点施虐狂 -;02:09=%1 is a masochist -02:09=%1 是受虐狂 -;02:09=%1 has no instinct of self-preservation -02:09=%1 根本不会自我保护 -;02:09=%1 messed up -02:09=%1 乱套了 -;02:09=%1 screwed up -02:09=%1 搞砸了 -;02:09=That was a poor shot, %1 -02:09=%1 这一发真渣 -;02:09=%1 is a little too careless with dangerous weapons -02:09=%1 太不小心用那些危险的玩意了 -;02:09=%1 should consider a change of career -02:09=%1 正在考虑转职 -;02:09=Worst. Shot. Ever! -02:09=更差! 最差! 非常差! -;02:09=No no no %1, you shoot at the ENEMY! -02:09=No no no %1, 你要打敌人! -;02:09=%1 should only be destroying the enemy -02:09=%1 应该消灭敌人才对 -;02:09=%1 moves one step closer to suicide -02:09=%1 正在走向自杀 -;02:09=%1 aids the enemy -02:09=%1 帮助敌人 -;02:09=That was stupid %1 -02:09= %1 是笨蛋 -;02:09=%1 lives by the mantra of "no pain, no gain" -02:09=%1 贯彻“不付出,何收获“的原则 -;02:09=%1 is confused -02:09=%1 思维混乱了 -;02:09=%1 hurt itself in its confusion -02:09=%1 在混乱中攻击自己 -;02:09=%1 has a knack for embarrassing himself -02:09=%1 正在为自己尴尬 -;02:09=%1 is a klutz! -02:09=%1 就是一个笨蛋! -;02:09=%1 is clumsy -02:09=%1 笨手笨脚的 -;02:09=%1 shows the enemy what he's capable of -02:09=%1 展示了自己的能力 -;02:09=%1 can't be expected to be perfect all the time -02:09=%1 不能每次都完美 -;02:09=Don't worry %1, pobody's nerfect -02:09=不用担心 %1 , 人都不是完美的 -;02:09=%1 totally did that on purpose -02:09=%1 这么做真的是有目的 -;02:09=I won't tell anyone if you don't, %1 -02:09=我不会把 %1 的事情到处说的 -;02:09=How embarrassing! -02:09=何等的失态! -;02:09=I'm sure nobody saw that %1 -02:09=保证,决没人看到 %1 做什么 -;02:09=%1 needs to review his field manual -02:09=%1 需要复习说明书 -;02:09=%1's weapon clearly malfunctioned -02:09=%1 的武器很明显坏了 - -; Hog shot an home run (using the bat and another hog) -; 02:10=Home Run! -02:10=全垒打! -; 02:10=A bird, a plane, ... -02:10=一只鸟,一架飞机,... -; 02:10=That one is out! -02:10=那一位出界了! - -; Hog (%1) has to leave (team is gone) -02:11=%1 必须上床了 -02:11=%1 玩的过火了,休息一下 -02:11=发射!这位已经被送出去 -02:11=%1 必须走了 - -; Weapon Categories -03:00=定时手雷 -03:01=定时手雷 -03:02=弹道武器 -03:03=制导武器 -03:04=枪 (多发子弹) -03:05=钻孔工具 -03:06=动作 -03:07=移动工具 -03:08=接近式炸弹 -03:09=枪 (多发子弹) -03:10=BOOM! -03:11=咚! -03:12=武术 -03:13=未使用 -03:14=移动工具 -03:15=空投打击 -03:16=空投打击 -03:17=打洞工具 -03:18=工具 -03:19=移动工具 -03:20=动作 -03:21=弹道武器 -03:22=叫我主人! -03:23=武术 (真的!) -03:24=蛋糕不是谎言! -03:25=化妆的诱惑 -03:26=果汁手雷 -03:27=烫手手雷 -03:28=弹道武器 -03:29=弹道武器 -03:30=空投打击 -03:31=遥控飞机(不是玩具!) -03:32=临时效果 -03:33=临时效果 -03:34=临时效果 -03:35=临时效果 -03:36=临时效果 -03:37=临时效果 -03:38=枪 (多发子弹) -03:39=移动工具 -03:40=燃烧弹 -;03:41=Huge fan of Squawks -03:41=粉丝的呼喊 -;03:42=I'm making a note here... -03:42=我将在此做一记录... -; the misspelled "Beethoven" is intentional (-> to beat) -;03:43=Performing Beathoven's deadly sonata -03:43=正在演奏贝揍芬的死亡奏鸣曲 -;03:44=Best before: 1923 -03:44=此日期前最佳:1923 -;03:45=The power of science -03:45=科学的力量 -;03:46=Hot Hot Hot! -03:46=烫烫烫! -;03:47=Stick these somewhere useful! -03:47= 呆在有利的地方! -;03:48=It's Hammer time! -03:48=大锤威武! -;03:49=Does what you guess -03:49=尽情猜想 -;03:50=Moles fan -03:50=地道战 - -; Weapon Descriptions (use | as line breaks) -04:00=使用简单的手榴弹攻击敌人.|定时器倒数到0就会爆炸.|1-5: 设定定时器|攻击键: 按住蓄力. -04:01=使用集束手雷攻击敌人.|定时器倒数到0就会爆炸并裂开成几块.|1-5: 设定定时器|攻击键: 按住蓄力. -04:02=使用弹道导弹攻击敌人.|受风力影响.|攻击键: 按住蓄力. -04:03=发射一个制导导弹攻击所选目标.|如果要精确打击就不要使用全力发射.|光标: 选定目标|攻击键: 按住蓄力. -04:04=霰弹枪有两排子弹.|因为是霰弹枪所以不一定要对准敌人.|攻击键: 开枪 (两发) -04:05=向地底出发! 使用他就能在地面|打个洞, 就能去其他地方.|攻击键: 开始/停止打洞 -04:06=闷了? 没法打? 保存体力? 没问题!|跳过这回合就可以了, 懦夫!|攻击键: 跳过回合 -04:07=用绳索就可以去很远的地方.|还能空降到别的刺猬身上丢手榴弹呢.|攻击键: 发射/收回绳索|长跳键: 发射手榴弹或其他武器 -04:08=你能用地雷阻止敌人靠近.|还能静悄悄的放在敌人脚下.|一定要在爆炸前逃离到安全的地方!|攻击键: 把地雷放在你的脚下 -04:09=自我感觉准头不行? |沙漠之鹰有4颗子弹呢.|攻击键: 开枪 (四发) -04:10=使用强力炸药就是一个明智的选择.|这是最经典的轰炸方式.|攻击键: 把炸药放在你的脚下 -04:11=把敌人打飞, 飞出地图或者飞进水里.|或者把地雷打过去?|攻击键: 敲打你面前的所有东西 -04:12=这就是武术的威力!|致命的气功!|攻击键: 使用升龙拳 -04:13=UNUSED -04:14=有恐高症? 拿降落伞吧.|他能慢慢的安全的把你带到地面.|攻击键: 展开降落伞 -04:15=呼叫一架飞机轰炸你的敌人.|左/右方向键: 决定攻击方向|光标: 选定目标 -04:16=呼叫一架飞机投下大量地雷.|左/右方向键: 决定攻击方向|光标: 选定目标 -04:17=需要个安全的地方? 使用喷灯为你挖掘一条安全的隧道!|攻击键: 开始/停止挖掘 -04:18=喷灯还不够?还要个更安全的地方?|建造若干条大梁挡住吧.|左/右方向键: 选择梁的方向|光标: 建造 -04:19=适当的时候撤退是比所有的攻击|更安全的选择|光标: 选择传送目标 -04:20=可以让你更换当前使用的刺猬.|攻击键: 启动切换功能 -04:21=用炮弹发射器发射一个手榴弹样|的东西. 在爆炸之后会裂开成小块|攻击键: 全力发射 -04:22=这不只是女王才用的东西!|这鞭子能解决很多问题, 比如说那些|喜欢站在悬崖边上的小屁孩.|攻击键: 鞭打你面前的一切东西 -04:23=自杀式炸弹袭击向来好用!|用你的一条命攻击直线上的一切东西并爆炸.|攻击键: 启动自杀性攻击 -04:24=生日快乐! 嗱, 放下这个蛋糕, 他|就会走到敌人身边然后爆炸.| 而且能贴着地形走.|攻击键: 让蛋糕开始/结束走路 -04:25=使用美人计让敌人向着你这个方向跳|(比如跳进海里).|攻击键: 使用本工具诱惑敌人 -04:26=把这个多汁的西瓜扔向敌人!| 一旦定时器倒数完, 就会|炸成几块更强力的炸弹.|攻击键: 按住蓄力. -04:27=让地狱的礼花在敌人头上绽放!|这真的是危险品, 使用时候记得原理|爆炸之后还会燃烧好一阵子|攻击键: 按住蓄力. -04:28=本火箭在发射后将会钻到地里|一旦燃料用完或者打穿地面就会爆炸.|攻击键: 按住蓄力. -04:29=还记得小时候玩的玻璃球么?|不过这个是炸弹版. 发射大量的小玻|璃球然后爆炸|攻击键: 全力发射|上/下方向键: 发射过程中更换方向 -04:30=呼叫一架飞机空投燃烧弹.|用得好的话会造成巨大伤害.|左/右方向键: 决定攻击方向|光标: 选定目标 -04:31=啊哈, 遥控飞机除了能帮你|收集物品之外. 还能空投炸弹.|攻击键: 飞机起飞/投放炸弹|长跳键: 战场之神|上/下方向键: 控制方向 -04:32=低重力装置能影响更多东西!| 除了跳得更远之外还能让|敌人飞得更远.|攻击键: 激活 -04:33=有时候致命打击还是不够过瘾.|攻击键: 激活 -04:34=你打不到我!|攻击键: 激活 -04:35=时间流逝得很快, 你也知道|刺猬腿短.|攻击键: 激活 -04:36=好吧, 你最后还是承认自己眼神不好.|高科技还是能帮你不少的.|攻击键: 激活 -04:37=不用害怕白天.|这只能本回合有效, 可以把造成的伤害变|成自己的血量 .|攻击键: 激活 -04:38=你也知道狙击枪的威力,|能打比较远的地方.|攻击键: 射击 (2发子弹) -04:39=驾驶飞碟可以飞到地图上的任何角落.|不过这个东西连发明者都认为很难用.|攻击键: 激活|上/左/右方向键: 向某方向飞|前跳:攻击敌人 -04:40=把地面填满汽油然后....|攻击键: 按住蓄力. -;04:41=The evidence nature might even top the flying|saucer. Birdy can carry your hog around and|drop eggs on your enemies!|Be quick, as using Birdy eats into your turn|time!|Attack: Activate and drop eggs|Up/Left/Right: Flap in one direction -04:41=自然的力量胜过飞盘的证据。|鸟儿可以携带刺猬并在敌人头上下蛋!|要快!使用鸟儿会消耗回合时间!|攻击键: 激活鸟儿和下蛋|上/左/右方向键: 向某方向飞 -;04:42=This portable portal device is capable|of instantly transporting you, your enemies,|or your weaponry between two points on the|terrain.|Use it wisely and your campaign will be a...|HUGE SUCCESS!|Attack: Shoot a portal|Switch: Cycle portal colours -04:42=移动传送装置|迅速传输自己或者敌人或者|你的武器,直接连接|地表的两个不同位置。|如果用的聪明那你的战斗将是一场……|巨大的胜利!|攻击键: 发射一个传送点|切换键: 改变颜色 -;04:43=Make your musical debut an explosive success!|Drop a piano from the heavens, but beware...|someone needs to play it, and that may cost you|your life!|Cursor: Select target region|F1-F9: Play the piano -04:43=音乐细胞的迸发!|钢琴从天堂降落,带|着演奏者最终回归天堂|光标: 选择目标区域|F1-F9:演奏钢琴 -04:44=这不是奶酪!而是生化武器!|爆炸只有一次,带来的毒害是深远的!|1-5: 设定定时器|攻击键: 按住蓄力 -;04:45=All those physics classes have finally |paid off, launch a devastating Sine |wave at your foes. |Watch out, this weapon packs quite a kick. (This weapon is incomplete)|Attack: Shoot -04:45=全部物理阶级最终|转化为正弦波动|留心,力是相对的|攻击键: 发射 -;04:46=Cover your foes with sizzling liquid flame.|Heartwarming!|Attack: Activate|Up/Down: Continue aiming|Left/Right: Modify spitting power -04:46= 用满腔的火焰虐待你的对手吧。|攻击键: 激活|上/下方向键: 改变攻击方向|左/右方向键: 调整喷射距离 -;04:47=Double the fun with two spiky, sneaky, sticky mines.|Set up a chain reaction or defend yourself (or both!)|Attack: Hold to shoot with more power (twice) -04:47=两次机会双重乐趣,隐蔽且黏着的地雷。|利用脑力造成连锁反应!|攻击键: 按住蓄力(两发) -;04:48=Why should the moles get all the abuse?|Wacking a hog can be just as fun! A good|blow from this hammer will shave off one|third of a hog's health and plunge them|underground.|Attack: Activate -04:48=痛扁刺猬:用力一锤|将使中者镶入地表,削减它健康的1/3.|攻击键: 打 -;04:49=Resurrect your friends!|But beware that this also resurrects your foes.|Attack: Keep attack pressed to resurrect slowly|Up: Accelerate resurrection -04:49=复苏|注意,一视同仁|使用: 按住使用键|上: 提高速率 - -; Game goal strings -;05:01=The following rules apply -05:01= 将应用以下规则 -;05:02=Forts: Defend your fortress; vanquish your enemies! -05:02= 城堡: 守住你的城堡; 削平你的敌人! -;05:03=Low Gravity: Watch your step -05:03= 低重力: 注意脚步 -;05:04=Invulnerability: Hogs are (almost) invulnerable -05:04=无敌: 刺猬不受伤害 -;05:05=Vampirism: Hogs will be healed for the damage dealt -05:05=吸血: 敌人失去的就是我的 -;05:06=Karma: Hogs will be damaged for the damage dealt -05:06=因果效应: 伤害有多少,自己都知道 -;05:07=Protect the King: Don't let your king die!|Place the King: Pick a protected starting point for your King -05:07=保护国王: 国王不能死!|放置国王: 为国王选择安全的起始地点 -;05:08=Place Hedgehogs: Place your hogs before the game starts -05:08=选择起始点: 游戏开始前手动放置刺猬 -;05:09=Artillery: Hogs can't walk to change position -05:09=远程打击: 不许动! -;05:10=Indestructible Terrain: Most weapons won't destroy terrain -05:10=无损地表: 多数武器无法改变地形 -;05:11=Shared Ammo: All teams of the same color share their ammunition -05:11=共享装备: 同色的刺猬共享它们的装备 -;05:12=Mine Timers: Mines will detonate after %1 second(s) -05:12=地雷定时器: %1 秒起爆 -;05:13=Mine Timers: Mines will detonate instantly -05:13=地雷定时器: 立即起爆 -;05:14=Mine Timers: Mines will detonate after 0 - 3 seconds -05:14=地雷定时器: 0-3 秒起爆 -;05:15=Damage Modifier: All weapons will do %1% damage -05:15=伤害修正: 武器伤害使用 %1% 修正值 -;05:16=Health of all hogs is reset on end of turn -05:16=所有活着的刺猬回合结尾时彻底恢复健康 -;05:17=AI hogs respawn on death -05:17=AI刺猬即时复活 -;05:18=Unlimited Attacks -05:18=无限攻击法则 -;05:19=Weapons are reset on end of turn -05:19=武器在回合结束时重置 -;05:20=Weapons are not shared between hogs -05:20=刺猬的武器无法分享 +01:05=退出 (Y/Esc)? +01:06=出现紧急情况! diff -r 7ee319134713 -r bc7b1d228a2c tools/PascalParser.hs --- a/tools/PascalParser.hs Thu Aug 30 12:47:41 2012 -0400 +++ b/tools/PascalParser.hs Thu Aug 30 13:02:19 2012 -0400 @@ -19,7 +19,7 @@ pascalUnit = do comments - u <- choice [program, unit, systemUnit] + u <- choice [program, unit, systemUnit, redoUnit] comments return u @@ -270,12 +270,12 @@ char ';' comments forward <- liftM isJust $ optionMaybe (try (string "forward;") >> comments) - many functionDecorator + inline <- liftM (any (== "inline;")) $ many functionDecorator b <- if isImpl && (not forward) then liftM Just functionBody else return Nothing - return $ [OperatorDeclaration i rid ret vs b] + return $ [OperatorDeclaration i rid inline ret vs b] funcDecl = do @@ -295,21 +295,24 @@ char ';' comments forward <- liftM isJust $ optionMaybe (try (string "forward;") >> comments) - many functionDecorator + inline <- liftM (any (== "inline;")) $ many functionDecorator b <- if isImpl && (not forward) then liftM Just functionBody else return Nothing - return $ [FunctionDeclaration i ret vs b] + return $ [FunctionDeclaration i inline ret vs b] - functionDecorator = choice [ - try $ string "inline;" - , try $ caseInsensitiveString "cdecl;" - , try $ string "overload;" - , try $ string "export;" - , try $ string "varargs;" - , try (string "external") >> comments >> iD >> optional (string "name" >> comments >> stringLiteral pas)>> string ";" - ] >> comments + functionDecorator = do + d <- choice [ + try $ string "inline;" + , try $ caseInsensitiveString "cdecl;" + , try $ string "overload;" + , try $ string "export;" + , try $ string "varargs;" + , try (string "external") >> comments >> iD >> optional (string "name" >> comments >> stringLiteral pas)>> string ";" + ] + comments + return d program = do @@ -348,36 +351,46 @@ comments return $ Implementation u (TypesAndVars tv) -expression = buildExpressionParser table term "expression" +expression = do + buildExpressionParser table term "expression" where term = comments >> choice [ builtInFunction expression >>= \(n, e) -> return $ BuiltInFunCall e (SimpleReference (Identifier n BTUnknown)) , try (parens pas $ expression >>= \e -> notFollowedBy (comments >> char '.') >> return e) , brackets pas (commaSep pas iD) >>= return . SetExpression - , try $ natural pas >>= \i -> notFollowedBy (char '.') >> (return . NumberLiteral . show) i + , try $ integer pas >>= \i -> notFollowedBy (char '.') >> (return . NumberLiteral . show) i , float pas >>= return . FloatLiteral . show - , natural pas >>= return . NumberLiteral . show + , try $ integer pas >>= return . NumberLiteral . show , try (string "_S" >> stringLiteral pas) >>= return . StringLiteral , try (string "_P" >> stringLiteral pas) >>= return . PCharLiteral , stringLiteral pas >>= return . strOrChar , try (string "#$") >> many hexDigit >>= \c -> comments >> return (HexCharCode c) , char '#' >> many digit >>= \c -> comments >> return (CharCode c) , char '$' >> many hexDigit >>= \h -> comments >> return (HexNumber h) - , char '-' >> expression >>= return . PrefixOp "-" + --, char '-' >> expression >>= return . PrefixOp "-" + , char '-' >> reference >>= return . PrefixOp "-" . Reference + , try $ string "not" >> error "unexpected not in term" , try $ string "nil" >> return Null - , try $ string "not" >> expression >>= return . PrefixOp "not" , reference >>= return . Reference ] "simple expression" - table = [ + table = [ + [ Prefix (try (string "not") >> return (PrefixOp "not")) + , Prefix (try (char '-') >> return (PrefixOp "-"))] + , [ Infix (char '*' >> return (BinOp "*")) AssocLeft , Infix (char '/' >> return (BinOp "/")) AssocLeft , Infix (try (string "div") >> return (BinOp "div")) AssocLeft , Infix (try (string "mod") >> return (BinOp "mod")) AssocLeft , Infix (try (string "in") >> return (BinOp "in")) AssocNone + , Infix (try $ string "and" >> return (BinOp "and")) AssocLeft + , Infix (try $ string "shl" >> return (BinOp "shl")) AssocLeft + , Infix (try $ string "shr" >> return (BinOp "shr")) AssocLeft ] , [ Infix (char '+' >> return (BinOp "+")) AssocLeft , Infix (char '-' >> return (BinOp "-")) AssocLeft + , Infix (try $ string "or" >> return (BinOp "or")) AssocLeft + , Infix (try $ string "xor" >> return (BinOp "xor")) AssocLeft ] , [ Infix (try (string "<>") >> return (BinOp "<>")) AssocNone , Infix (try (string "<=") >> return (BinOp "<=")) AssocNone @@ -385,13 +398,13 @@ , Infix (char '<' >> return (BinOp "<")) AssocNone , Infix (char '>' >> return (BinOp ">")) AssocNone ] - , [ Infix (try $ string "shl" >> return (BinOp "shl")) AssocNone - , Infix (try $ string "shr" >> return (BinOp "shr")) AssocNone + {-, [ Infix (try $ string "shl" >> return (BinOp "shl")) AssocNone + , Infix (try $ string "shr" >> return (BinOp "shr")) AssocNone ] - , [ Infix (try $ string "and" >> return (BinOp "and")) AssocLeft - , Infix (try $ string "or" >> return (BinOp "or")) AssocLeft + , [ + Infix (try $ string "or" >> return (BinOp "or")) AssocLeft , Infix (try $ string "xor" >> return (BinOp "xor")) AssocLeft - ] + ]-} , [ Infix (char '=' >> return (BinOp "=")) AssocNone ] @@ -415,7 +428,7 @@ , switchCase , withBlock , forCycle - , (try $ reference >>= \r -> string ":=" >> return r) >>= \r -> expression >>= return . Assignment r + , (try $ reference >>= \r -> string ":=" >> return r) >>= \r -> comments >> expression >>= return . Assignment r , builtInFunction expression >>= \(n, e) -> return $ BuiltInFunctionCall e (SimpleReference (Identifier n BTUnknown)) , procCall , char ';' >> comments >> return NOP @@ -480,7 +493,12 @@ comments e1 <- expression comments - choice [string "to", string "downto"] + up <- liftM (== Just "to") $ + optionMaybe $ choice [ + try $ string "to" + , try $ string "downto" + ] + --choice [string "to", string "downto"] comments e2 <- expression comments @@ -488,7 +506,7 @@ comments p <- phrase comments - return $ ForCycle i e1 e2 p + return $ ForCycle i e1 e2 p up switchCase = do try $ string "case" @@ -573,14 +591,20 @@ table = [ [ Prefix (char '-' >> return (InitPrefixOp "-")) + ,Prefix (try (string "not") >> return (InitPrefixOp "not")) ] , [ Infix (char '*' >> return (InitBinOp "*")) AssocLeft , Infix (char '/' >> return (InitBinOp "/")) AssocLeft , Infix (try (string "div") >> return (InitBinOp "div")) AssocLeft , Infix (try (string "mod") >> return (InitBinOp "mod")) AssocLeft + , Infix (try $ string "and" >> return (InitBinOp "and")) AssocLeft + , Infix (try $ string "shl" >> return (InitBinOp "shl")) AssocNone + , Infix (try $ string "shr" >> return (InitBinOp "shr")) AssocNone ] , [ Infix (char '+' >> return (InitBinOp "+")) AssocLeft , Infix (char '-' >> return (InitBinOp "-")) AssocLeft + , Infix (try $ string "or" >> return (InitBinOp "or")) AssocLeft + , Infix (try $ string "xor" >> return (InitBinOp "xor")) AssocLeft ] , [ Infix (try (string "<>") >> return (InitBinOp "<>")) AssocNone , Infix (try (string "<=") >> return (InitBinOp "<=")) AssocNone @@ -589,14 +613,14 @@ , Infix (char '>' >> return (InitBinOp ">")) AssocNone , Infix (char '=' >> return (InitBinOp "=")) AssocNone ] - , [ Infix (try $ string "and" >> return (InitBinOp "and")) AssocLeft + {--, [ Infix (try $ string "and" >> return (InitBinOp "and")) AssocLeft , Infix (try $ string "or" >> return (InitBinOp "or")) AssocLeft , Infix (try $ string "xor" >> return (InitBinOp "xor")) AssocLeft ] , [ Infix (try $ string "shl" >> return (InitBinOp "shl")) AssocNone , Infix (try $ string "shr" >> return (InitBinOp "shr")) AssocNone - ] - , [Prefix (try (string "not") >> return (InitPrefixOp "not"))] + ]--} + --, [Prefix (try (string "not") >> return (InitPrefixOp "not"))] ] itypeCast = do @@ -621,3 +645,14 @@ string "var" v <- varsDecl True return $ System (t ++ v) + +redoUnit = do + string "redo;" + comments + string "type" + comments + t <- typesDecl + string "var" + v <- varsDecl True + return $ Redo (t ++ v) + diff -r 7ee319134713 -r bc7b1d228a2c tools/PascalPreprocessor.hs --- a/tools/PascalPreprocessor.hs Thu Aug 30 12:47:41 2012 -0400 +++ b/tools/PascalPreprocessor.hs Thu Aug 30 13:02:19 2012 -0400 @@ -18,6 +18,8 @@ initDefines = Map.fromList [ ("FPC", "") , ("PAS2C", "") + , ("ENDIAN_LITTLE", "") + , ("S3D_DISABLED", "") ] preprocess :: String -> IO String diff -r 7ee319134713 -r bc7b1d228a2c tools/PascalUnitSyntaxTree.hs --- a/tools/PascalUnitSyntaxTree.hs Thu Aug 30 12:47:41 2012 -0400 +++ b/tools/PascalUnitSyntaxTree.hs Thu Aug 30 13:02:19 2012 -0400 @@ -7,6 +7,7 @@ Program Identifier Implementation Phrase | Unit Identifier Interface Implementation (Maybe Initialize) (Maybe Finalize) | System [TypeVarDeclaration] + | Redo [TypeVarDeclaration] deriving Show data Interface = Interface Uses TypesAndVars deriving Show @@ -18,8 +19,8 @@ deriving Show data TypeVarDeclaration = TypeDeclaration Identifier TypeDecl | VarDeclaration Bool Bool ([Identifier], TypeDecl) (Maybe InitExpression) - | FunctionDeclaration Identifier TypeDecl [TypeVarDeclaration] (Maybe (TypesAndVars, Phrase)) - | OperatorDeclaration String Identifier TypeDecl [TypeVarDeclaration] (Maybe (TypesAndVars, Phrase)) + | FunctionDeclaration Identifier Bool TypeDecl [TypeVarDeclaration] (Maybe (TypesAndVars, Phrase)) + | OperatorDeclaration String Identifier Bool TypeDecl [TypeVarDeclaration] (Maybe (TypesAndVars, Phrase)) deriving Show data TypeDecl = SimpleType Identifier | RangeType Range @@ -48,7 +49,7 @@ | IfThenElse Expression Phrase (Maybe Phrase) | WhileCycle Expression Phrase | RepeatCycle Expression [Phrase] - | ForCycle Identifier Expression Expression Phrase + | ForCycle Identifier Expression Expression Phrase Bool -- The last Boolean indicates wether it's up or down counting | WithBlock Reference Phrase | Phrases [Phrase] | SwitchCase Expression [([InitExpression], Phrase)] (Maybe [Phrase]) diff -r 7ee319134713 -r bc7b1d228a2c tools/pas2c.hs --- a/tools/pas2c.hs Thu Aug 30 12:47:41 2012 -0400 +++ b/tools/pas2c.hs Thu Aug 30 13:02:19 2012 -0400 @@ -17,24 +17,32 @@ import Data.List (find) import Numeric -import PascalParser +import PascalParser(pascalUnit) import PascalUnitSyntaxTree data InsertOption = IOInsert + | IOInsertWithType Doc | IOLookup | IOLookupLast | IOLookupFunction Int | IODeferred -type Record = (String, BaseType) +data Record = Record + { + lcaseId :: String, + baseType :: BaseType, + typeDecl :: Doc + } + deriving Show type Records = Map.Map String [Record] data RenderState = RenderState { currentScope :: Records, lastIdentifier :: String, lastType :: BaseType, + lastIdTypeDecl :: Doc, stringConsts :: [(String, String)], uniqCounter :: Int, toMangle :: Set.Set String, @@ -43,7 +51,9 @@ namespaces :: Map.Map String Records } -emptyState = RenderState Map.empty "" BTUnknown [] 0 Set.empty "" "" +rec2Records = map (\(a, b) -> Record a b empty) + +emptyState = RenderState Map.empty "" BTUnknown empty [] 0 Set.empty "" "" getUniq :: State RenderState Int getUniq = do @@ -71,13 +81,14 @@ escapeChar :: Char -> ShowS escapeChar '"' s = "\\\"" ++ s +escapeChar '\\' s = "\\\\" ++ s escapeChar a s = a : s strInit :: String -> Doc strInit a = text "STRINIT" <> parens (doubleQuotes (text $ escapeStr a)) renderStringConsts :: State RenderState Doc -renderStringConsts = liftM (vcat . map (\(a, b) -> text "const string255" <+> (text a) <+> text "=" <+> strInit b <> semi)) +renderStringConsts = liftM (vcat . map (\(a, b) -> text "static const string255" <+> (text a) <+> text "=" <+> strInit b <> semi)) $ gets stringConsts docToLower :: Doc -> Doc @@ -132,10 +143,16 @@ where f = do checkDuplicateFunDecls tvs - mapM_ (tvar2C True) tvs + mapM_ (tvar2C True False True False) tvs + toNamespace nss (Redo tvs) = -- functions that are re-implemented, add prefix to all of them + currentScope $ execState f (emptyState nss){currentUnit = "fpcrtl_"} + where + f = do + checkDuplicateFunDecls tvs + mapM_ (tvar2C True False True False) tvs toNamespace _ (Program {}) = Map.empty toNamespace nss (Unit (Identifier i _) interface _ _ _) = - currentScope $ execState (interface2C interface) (emptyState nss){currentUnit = map toLower i ++ "_"} + currentScope $ execState (interface2C interface True) (emptyState nss){currentUnit = map toLower i ++ "_"} withState' :: (RenderState -> RenderState) -> State RenderState a -> State RenderState a @@ -149,65 +166,72 @@ }) return a -withLastIdNamespace :: State RenderState Doc -> State RenderState Doc withLastIdNamespace f = do li <- gets lastIdentifier nss <- gets namespaces withState' (\st -> st{currentScope = fromMaybe Map.empty $ Map.lookup li (namespaces st)}) f -withRecordNamespace :: String -> [(String, BaseType)] -> State RenderState Doc -> State RenderState Doc +withRecordNamespace :: String -> [Record] -> State RenderState Doc -> State RenderState Doc withRecordNamespace _ [] = error "withRecordNamespace: empty record" withRecordNamespace prefix recs = withState' f where f st = st{currentScope = Map.unionWith un records (currentScope st), currentUnit = ""} - records = Map.fromList $ map (\(a, b) -> (map toLower a, [(prefix ++ a, b)])) recs + records = Map.fromList $ map (\(Record a b d) -> (map toLower a, [Record (prefix ++ a) b d])) recs un [a] b = a : b toCFiles :: Map.Map String Records -> (String, PascalUnit) -> IO () toCFiles _ (_, System _) = return () +toCFiles _ (_, Redo _) = return () toCFiles ns p@(fn, pu) = do hPutStrLn stdout $ "Rendering '" ++ fn ++ "'..." toCFiles' p where - toCFiles' (fn, p@(Program {})) = writeFile (fn ++ ".c") $ (render2C initialState . pascal2C) p + toCFiles' (fn, p@(Program {})) = writeFile (fn ++ ".c") $ "#include \"fpcrtl.h\"\n" ++ (render2C initialState . pascal2C) p toCFiles' (fn, (Unit unitId@(Identifier i _) interface implementation _ _)) = do - let (a, s) = runState (id2C IOInsert (setBaseType BTUnit unitId) >> interface2C interface) initialState{currentUnit = map toLower i ++ "_"} + let (a, s) = runState (id2C IOInsert (setBaseType BTUnit unitId) >> interface2C interface True) initialState{currentUnit = map toLower i ++ "_"} + (a', s') = runState (id2C IOInsert (setBaseType BTUnit unitId) >> interface2C interface False) initialState{currentUnit = map toLower i ++ "_"} writeFile (fn ++ ".h") $ "#pragma once\n\n#include \"pas2c.h\"\n\n" ++ (render (a $+$ text "")) - writeFile (fn ++ ".c") $ "#include \"" ++ fn ++ ".h\"\n" ++ (render2C s . implementation2C) implementation + writeFile (fn ++ ".c") $ "#include \"fpcrtl.h\"\n\n#include \"" ++ fn ++ ".h\"\n" ++ render (a' $+$ text "") ++ (render2C s . implementation2C) implementation initialState = emptyState ns render2C :: RenderState -> State RenderState Doc -> String render2C a = render . ($+$ empty) . flip evalState a + usesFiles :: PascalUnit -> [String] -usesFiles (Program _ (Implementation uses _) _) = "pas2cSystem" : uses2List uses -usesFiles (Unit _ (Interface uses1 _) (Implementation uses2 _) _ _) = "pas2cSystem" : uses2List uses1 ++ uses2List uses2 +usesFiles (Program _ (Implementation uses _) _) = ["pas2cSystem", "pas2cRedo"] ++ uses2List uses +usesFiles (Unit _ (Interface uses1 _) (Implementation uses2 _) _ _) = ["pas2cSystem", "pas2cRedo"] ++ uses2List uses1 ++ uses2List uses2 usesFiles (System {}) = [] - +usesFiles (Redo {}) = [] pascal2C :: PascalUnit -> State RenderState Doc pascal2C (Unit _ interface implementation init fin) = - liftM2 ($+$) (interface2C interface) (implementation2C implementation) + liftM2 ($+$) (interface2C interface True) (implementation2C implementation) pascal2C (Program _ implementation mainFunction) = do impl <- implementation2C implementation - [main] <- tvar2C True - (FunctionDeclaration (Identifier "main" BTInt) (SimpleType $ Identifier "int" BTInt) [] (Just (TypesAndVars [], mainFunction))) + [main] <- tvar2C True False True True (FunctionDeclaration (Identifier "main" BTInt) False (SimpleType $ Identifier "int" BTInt) [VarDeclaration False False ([Identifier "argc" BTInt], SimpleType (Identifier "Integer" BTInt)) Nothing, VarDeclaration False False ([Identifier "argv" BTUnknown], SimpleType (Identifier "PPChar" BTUnknown)) Nothing] (Just (TypesAndVars [], mainFunction))) return $ impl $+$ main - -interface2C :: Interface -> State RenderState Doc -interface2C (Interface uses tvars) = do +-- the second bool indicates whether do normal interface translation or generate variable declarations +-- that will be inserted into implementation files +interface2C :: Interface -> Bool -> State RenderState Doc +interface2C (Interface uses tvars) True = do u <- uses2C uses - tv <- typesAndVars2C True tvars + tv <- typesAndVars2C True True True tvars r <- renderStringConsts return (u $+$ r $+$ tv) +interface2C (Interface uses tvars) False = do + u <- uses2C uses + tv <- typesAndVars2C True False False tvars + r <- renderStringConsts + return tv implementation2C :: Implementation -> State RenderState Doc implementation2C (Implementation uses tvars) = do u <- uses2C uses - tv <- typesAndVars2C True tvars + tv <- typesAndVars2C True False True tvars r <- renderStringConsts return (u $+$ r $+$ tv) @@ -217,20 +241,25 @@ where initMap = Map.empty --initMap = Map.fromList [("reset", 2)] - ins (FunctionDeclaration (Identifier i _) _ _ _) m = Map.insertWith (+) (map toLower i) 1 m + ins (FunctionDeclaration (Identifier i _) _ _ _ _) m = Map.insertWith (+) (map toLower i) 1 m ins _ m = m -typesAndVars2C :: Bool -> TypesAndVars -> State RenderState Doc -typesAndVars2C b (TypesAndVars ts) = do +-- the second bool indicates whether declare variable as extern or not +-- the third bool indicates whether include types or not + +typesAndVars2C :: Bool -> Bool -> Bool -> TypesAndVars -> State RenderState Doc +typesAndVars2C b externVar includeType(TypesAndVars ts) = do checkDuplicateFunDecls ts - liftM (vcat . map (<> semi) . concat) $ mapM (tvar2C b) ts + liftM (vcat . map (<> semi) . concat) $ mapM (tvar2C b externVar includeType False) ts setBaseType :: BaseType -> Identifier -> Identifier setBaseType bt (Identifier i _) = Identifier i bt uses2C :: Uses -> State RenderState Doc uses2C uses@(Uses unitIds) = do + mapM_ injectNamespace (Identifier "pas2cSystem" undefined : unitIds) + mapM_ injectNamespace (Identifier "pas2cRedo" undefined : unitIds) mapM_ (id2C IOInsert . setBaseType BTUnit) unitIds return $ vcat . map (\i -> text $ "#include \"" ++ i ++ ".h\"") $ uses2List uses where @@ -242,8 +271,11 @@ uses2List (Uses ids) = map (\(Identifier i _) -> i) ids +setLastIdValues vv = (\s -> s{lastType = baseType vv, lastIdentifier = lcaseId vv, lastIdTypeDecl = typeDecl vv}) + id2C :: InsertOption -> Identifier -> State RenderState Doc -id2C IOInsert (Identifier i t) = do +id2C IOInsert i = id2C (IOInsertWithType empty) i +id2C (IOInsertWithType d) (Identifier i t) = do ns <- gets currentScope tom <- gets (Set.member n . toMangle) cu <- gets currentUnit @@ -252,10 +284,11 @@ (BTFunction _ _ _, _) -> (cu ++ i, t) (BTVarParam t', _) -> ('(' : '*' : i ++ ")" , t') _ -> (i, t) - modify (\s -> s{currentScope = Map.insertWith (++) n [(i', t')] (currentScope s), lastIdentifier = n}) + modify (\s -> s{currentScope = Map.insertWith (++) n [Record i' t' d] (currentScope s), lastIdentifier = n}) return $ text i' where n = map toLower i + id2C IOLookup i = id2CLookup head i id2C IOLookupLast i = id2CLookup last i id2C (IOLookupFunction params) (Identifier i t) = do @@ -266,9 +299,9 @@ error $ "Not defined: '" ++ i' ++ "'\n" ++ show lt ++ "\nwith num of params = " ++ show params ++ "\n" ++ show v else let vv = fromMaybe (head $ fromJust v) . find checkParam $ fromJust v in - modify (\s -> s{lastType = snd vv, lastIdentifier = fst vv}) >> (return . text . fst $ vv) + modify (setLastIdValues vv) >> (return . text . lcaseId $ vv) where - checkParam (_, BTFunction _ p _) = p == params + checkParam (Record _ (BTFunction _ p _) _) = p == params checkParam _ = False id2C IODeferred (Identifier i t) = do let i' = map toLower i @@ -276,40 +309,44 @@ if (isNothing v) then modify (\s -> s{lastType = BTUnknown, lastIdentifier = i}) >> return (text i) else - let vv = head $ fromJust v in modify (\s -> s{lastType = snd vv, lastIdentifier = fst vv}) >> (return . text . fst $ vv) + let vv = head $ fromJust v in modify (setLastIdValues vv) >> (return . text . lcaseId $ vv) id2CLookup :: ([Record] -> Record) -> Identifier -> State RenderState Doc -id2CLookup f (Identifier i _) = do +id2CLookup f (Identifier i t) = do let i' = map toLower i v <- gets $ Map.lookup i' . currentScope lt <- gets lastType if isNothing v then error $ "Not defined: '" ++ i' ++ "'\n" ++ show lt else - let vv = f $ fromJust v in modify (\s -> s{lastType = snd vv, lastIdentifier = fst vv}) >> (return . text . fst $ vv) + let vv = f $ fromJust v in modify (setLastIdValues vv) >> (return . text . lcaseId $ vv) id2CTyped :: TypeDecl -> Identifier -> State RenderState Doc -id2CTyped t (Identifier i _) = do +id2CTyped = id2CTyped2 Nothing + +id2CTyped2 :: Maybe Doc -> TypeDecl -> Identifier -> State RenderState Doc +id2CTyped2 md t (Identifier i _) = do tb <- resolveType t case (t, tb) of (_, BTUnknown) -> do error $ "id2CTyped: type BTUnknown for " ++ show i ++ "\ntype: " ++ show t (SimpleType {}, BTRecord _ r) -> do ts <- type2C t - id2C IOInsert (Identifier i (BTRecord (render $ ts empty) r)) + id2C (IOInsertWithType $ ts empty) (Identifier i (BTRecord (render $ ts empty) r)) (_, BTRecord _ r) -> do ts <- type2C t - id2C IOInsert (Identifier i (BTRecord i r)) - _ -> id2C IOInsert (Identifier i tb) - + id2C (IOInsertWithType $ ts empty) (Identifier i (BTRecord i r)) + _ -> case md of + Nothing -> id2C IOInsert (Identifier i tb) + Just ts -> id2C (IOInsertWithType ts) (Identifier i tb) resolveType :: TypeDecl -> State RenderState BaseType resolveType st@(SimpleType (Identifier i _)) = do let i' = map toLower i v <- gets $ Map.lookup i' . currentScope - if isJust v then return . snd . head $ fromJust v else return $ f i' + if isJust v then return . baseType . head $ fromJust v else return $ f i' where f "integer" = BTInt f "pointer" = BTPointerTo BTVoid @@ -352,7 +389,7 @@ resolve s (BTUnresolved t) = do v <- gets $ Map.lookup t . currentScope if isJust v then - resolve s . snd . head . fromJust $ v + resolve s . baseType . head . fromJust $ v else error $ "Unknown type " ++ show t ++ "\n" ++ s resolve _ t = return t @@ -363,7 +400,7 @@ error $ "Dereferencing from non-pointer type " ++ show t ++ "\n" ++ s -functionParams2C params = liftM (hcat . punctuate comma . concat) $ mapM (tvar2C False) params +functionParams2C params = liftM (hcat . punctuate comma . concat) $ mapM (tvar2C False False True True) params numberOfDeclarations :: [TypeVarDeclaration] -> Int numberOfDeclarations = sum . map cnt @@ -392,20 +429,21 @@ ps = zip ['a'..] (toIsVarList params) fun2C :: Bool -> String -> TypeVarDeclaration -> State RenderState [Doc] -fun2C _ _ (FunctionDeclaration name returnType params Nothing) = do +fun2C _ _ (FunctionDeclaration name inline returnType params Nothing) = do t <- type2C returnType t'<- gets lastType p <- withState' id $ functionParams2C params n <- liftM render . id2C IOInsert $ setBaseType (BTFunction hasVars (numberOfDeclarations params) t') name + let decor = if inline then text "inline" else empty if hasVars then - return [funWithVarsToDefine n params $+$ t empty <+> text (n ++ "__vars") <> parens p] + return [funWithVarsToDefine n params $+$ decor <+> t empty <+> text (n ++ "__vars") <> parens p] else - return [t empty <+> text n <> parens p] + return [decor <+> t empty <+> text n <> parens p] where hasVars = hasPassByReference params -fun2C True rv (FunctionDeclaration name@(Identifier i _) returnType params (Just (tvars, phrase))) = do +fun2C True rv (FunctionDeclaration name@(Identifier i _) inline returnType params (Just (tvars, phrase))) = do let res = docToLower $ text rv <> text "_result" t <- type2C returnType t'<- gets lastType @@ -418,16 +456,20 @@ VoidType -> True _ -> False - (p, ph) <- withState' (\st -> st{currentScope = Map.insertWith un (map toLower rv) [(render res, t')] $ currentScope st + (p, ph) <- withState' (\st -> st{currentScope = Map.insertWith un (map toLower rv) [Record (render res) t' empty] $ currentScope st , currentFunctionResult = if isVoid then [] else render res}) $ do p <- functionParams2C params - ph <- liftM2 ($+$) (typesAndVars2C False tvars) (phrase2C' phrase) + ph <- liftM2 ($+$) (typesAndVars2C False False True tvars) (phrase2C' phrase) return (p, ph) let phrasesBlock = if isVoid then ph else t empty <+> res <> semi $+$ ph $+$ text "return" <+> res <> semi - - return [(if notDeclared && hasVars then funWithVarsToDefine n params else empty) $+$ - t empty <+> text (if hasVars then n ++ "__vars" else n) <> parens p + let define = if hasVars then text "#ifndef" <+> text n $+$ funWithVarsToDefine n params $+$ text "#endif" else empty + let decor = if inline then text "inline" else empty + return [ + define + $+$ + --(if notDeclared && hasVars then funWithVarsToDefine n params else empty) $+$ + decor <+> t empty <+> text (if hasVars then n ++ "__vars" else n) <> parens p $+$ text "{" $+$ @@ -440,42 +482,74 @@ un [a] b = a : b hasVars = hasPassByReference params -fun2C False _ (FunctionDeclaration (Identifier name _) _ _ _) = error $ "nested functions not allowed: " ++ name +fun2C False _ (FunctionDeclaration (Identifier name _) _ _ _ _) = error $ "nested functions not allowed: " ++ name fun2C _ tv _ = error $ "fun2C: I don't render " ++ show tv -tvar2C :: Bool -> TypeVarDeclaration -> State RenderState [Doc] -tvar2C b f@(FunctionDeclaration (Identifier name _) _ _ _) = - fun2C b name f -tvar2C _ td@(TypeDeclaration i' t) = do +-- the second bool indicates whether declare variable as extern or not +-- the third bool indicates whether include types or not +-- the fourth bool indicates whether ignore initialization or not (basically for dynamic arrays since we cannot do initialization in function params) +tvar2C :: Bool -> Bool -> Bool -> Bool -> TypeVarDeclaration -> State RenderState [Doc] +tvar2C b _ includeType _ f@(FunctionDeclaration (Identifier name _) _ _ _ _) = do + t <- fun2C b name f + if includeType then return t else return [] +tvar2C _ _ includeType _ td@(TypeDeclaration i' t) = do i <- id2CTyped t i' tp <- type2C t - return [text "typedef" <+> tp i] + return $ if includeType then [text "typedef" <+> tp i] else [] -tvar2C _ (VarDeclaration True _ (ids, t) Nothing) = do +tvar2C _ _ _ _ (VarDeclaration True _ (ids, t) Nothing) = do t' <- liftM ((empty <+>) . ) $ type2C t - liftM (map(\i -> t' i)) $ mapM (id2CTyped (VarParamType t)) ids + liftM (map(\i -> t' i)) $ mapM (id2CTyped2 (Just $ t' empty) (VarParamType t)) ids -tvar2C _ (VarDeclaration _ isConst (ids, t) mInitExpr) = do - t' <- liftM (((if isConst then text "const" else empty) <+>) . ) $ type2C t +tvar2C _ externVar includeType ignoreInit (VarDeclaration _ isConst (ids, t) mInitExpr) = do + t' <- liftM (((if isConst then text "static const" else if externVar + then text "extern" + else empty) + <+>) . ) $ type2C t ie <- initExpr mInitExpr lt <- gets lastType case (isConst, lt, ids, mInitExpr) of (True, BTInt, [i], Just _) -> do i' <- id2CTyped t i - return [text "enum" <> braces (i' <+> ie)] + return $ if includeType then [text "enum" <> braces (i' <+> ie)] else [] (True, BTFloat, [i], Just e) -> do i' <- id2CTyped t i ie <- initExpr2C e - return [text "#define" <+> i' <+> parens ie <> text "\n"] + return $ if includeType then [text "#define" <+> i' <+> parens ie <> text "\n"] else [] (_, BTFunction{}, _, Nothing) -> liftM (map(\i -> t' i)) $ mapM (id2CTyped t) ids - _ -> liftM (map(\i -> t' i <+> ie)) $ mapM (id2CTyped t) ids + (_, BTArray r _ _, [i], _) -> do + i' <- id2CTyped t i + ie' <- return $ case (r, mInitExpr, ignoreInit) of + (RangeInfinite, Nothing, False) -> text "= NULL" -- force dynamic array to be initialized as NULL if not initialized at all + (_, _, _) -> ie + result <- liftM (map(\i -> varDeclDecision isConst includeType (t' i) ie')) $ mapM (id2CTyped t) ids + case (r, ignoreInit) of + (RangeInfinite, False) -> + -- if the array is dynamic, add dimension info to it + return $ [dimDecl] ++ result + where + arrayDimStr = show $ arrayDimension t + arrayDimInitExp = text ("={" ++ ".dim = " ++ arrayDimStr ++ ", .a = {0, 0, 0, 0}}") + dimDecl = varDeclDecision isConst includeType (text "fpcrtl_dimension_t" <+> i' <> text "_dimension_info") arrayDimInitExp + + (_, _) -> return result + + _ -> liftM (map(\i -> varDeclDecision isConst includeType (t' i) ie)) $ mapM (id2CTyped2 (Just $ t' empty) t) ids where initExpr Nothing = return $ empty initExpr (Just e) = liftM (text "=" <+>) (initExpr2C e) + varDeclDecision True True varStr expStr = varStr <+> expStr + varDeclDecision False True varStr expStr = if externVar then varStr else varStr <+> expStr + varDeclDecision False False varStr expStr = varStr <+> expStr + varDeclDecision True False varStr expStr = empty + arrayDimension a = case a of + ArrayDecl Nothing t -> let a = arrayDimension t in if a > 3 then error "Dynamic array with dimension > 4 is not supported." else 1 + arrayDimension t + ArrayDecl _ _ -> error "Mixed dynamic array and static array are not supported." + _ -> 0 -tvar2C f (OperatorDeclaration op (Identifier i _) ret params body) = do +tvar2C f _ _ _ (OperatorDeclaration op (Identifier i _) inline ret params body) = do r <- op2CTyped op (extractTypes params) - fun2C f i (FunctionDeclaration r ret params body) + fun2C f i (FunctionDeclaration r inline ret params body) op2CTyped :: String -> [TypeDecl] -> State RenderState Identifier @@ -489,6 +563,7 @@ "-" -> "sub" "*" -> "mul" "/" -> "div" + "/(float)" -> "div" "=" -> "eq" "<" -> "lt" ">" -> "gt" @@ -591,7 +666,7 @@ _ -> return $ \a -> i' <+> text "*" <+> a type2C' (PointerTo t) = liftM (\t a -> t (parens $ text "*" <> a)) $ type2C t type2C' (RecordType tvs union) = do - t <- withState' f $ mapM (tvar2C False) tvs + t <- withState' f $ mapM (tvar2C False False True False) tvs u <- unions return $ \i -> text "struct __" <> i <+> lbrace $+$ nest 4 ((vcat . map (<> semi) . concat $ t) $$ u) $+$ rbrace <+> i where @@ -602,7 +677,7 @@ structs <- mapM struct2C a return $ text "union" $+$ braces (nest 4 $ vcat structs) <> semi struct2C tvs = do - t <- withState' f $ mapM (tvar2C False) tvs + t <- withState' f $ mapM (tvar2C False False True False) tvs return $ text "struct" $+$ braces (nest 4 (vcat . map (<> semi) . concat $ t)) <> semi type2C' (RangeType r) = return (text "int" <+>) type2C' (Sequence ids) = do @@ -615,7 +690,7 @@ t' <- type2C t lt <- gets lastType ft <- case lt of - BTFunction {} -> type2C (PointerTo t) + -- BTFunction {} -> type2C (PointerTo t) _ -> return t' r' <- initExpr2C (InitRange r) return $ \i -> ft i <> brackets r' @@ -675,15 +750,26 @@ e <- expr2C expr return $ r <+> text "=" <+> e <> semi _ -> error $ "Assignment to string from " ++ show lt - (BTArray _ _ _, _) -> phrase2C $ - ProcCall (FunCall - [ - Reference $ Address ref - , Reference $ Address $ RefExpression expr - , Reference $ FunCall [expr] (SimpleReference (Identifier "sizeof" BTUnknown)) - ] - (SimpleReference (Identifier "memcpy" BTUnknown)) - ) [] + (BTArray _ _ _, _) -> do + case expr of + Reference er -> do + exprRef <- ref2C er + exprT <- gets lastType + case exprT of + BTArray RangeInfinite _ _ -> + return $ text "FIXME: assign a dynamic array to an array" + BTArray _ _ _ -> phrase2C $ + ProcCall (FunCall + [ + Reference $ ref + , Reference $ RefExpression expr + , Reference $ FunCall [expr] (SimpleReference (Identifier "sizeof" BTUnknown)) + ] + (SimpleReference (Identifier "memcpy" BTUnknown)) + ) [] + _ -> return $ text "FIXME: assign a non-specific value to an array" + + _ -> return $ text "FIXME: dynamic array assignment 2" _ -> do e <- expr2C expr return $ r <+> text "=" <+> e <> semi @@ -704,7 +790,7 @@ ph <- phrase2C p return $ vcat (map (\i -> text "case" <+> i <> colon) . concat $ ies) <> nest 4 (ph $+$ text "break;") - dflt | isNothing mphrase = return [] + dflt | isNothing mphrase = return [text "default: break;"] -- avoid compiler warning | otherwise = do ph <- mapM phrase2C $ fromJust mphrase return [text "default:" <+> nest 4 (vcat ph)] @@ -713,18 +799,27 @@ r <- ref2C ref t <- gets lastType case t of - (BTRecord _ rs) -> withRecordNamespace (render r ++ ".") rs $ phrase2C $ wrapPhrase p + (BTRecord _ rs) -> withRecordNamespace (render r ++ ".") (rec2Records rs) $ phrase2C $ wrapPhrase p a -> do error $ "'with' block referencing non-record type " ++ show a ++ "\n" ++ show wb -phrase2C (ForCycle i' e1' e2' p) = do +phrase2C (ForCycle i' e1' e2' p up) = do i <- id2C IOLookup i' + iType <- gets lastIdTypeDecl e1 <- expr2C e1' e2 <- expr2C e2' - ph <- phrase2C (wrapPhrase p) - return $ - text "for" <> (parens . hsep . punctuate (char ';') $ [i <+> text "=" <+> e1, i <+> text "<=" <+> e2, text "++" <> i]) + let inc = if up then "inc" else "dec" + let add = if up then "+ 1" else "- 1" + let iEnd = i <> text "__end__" + ph <- phrase2C . appendPhrase (BuiltInFunctionCall [Reference $ SimpleReference i'] (SimpleReference (Identifier inc BTUnknown))) $ wrapPhrase p + return . braces $ + i <+> text "=" <+> e1 <> semi $$ - ph + iType <+> iEnd <+> text "=" <+> e2 <> semi + $$ + text "if" <+> (parens $ i <+> text "<=" <+> iEnd) <+> text "do" <+> ph <+> + text "while" <> parens (i <+> text "!=" <+> iEnd <+> text add) <> semi + where + appendPhrase p (Phrases ps) = Phrases $ ps ++ [p] phrase2C (RepeatCycle e' p') = do e <- expr2C e' p <- phrase2C (Phrases p') @@ -777,12 +872,23 @@ case expr2 of SetExpression set -> do ids <- mapM (id2C IOLookup) set + modify(\s -> s{lastType = BTBool}) return . parens . hcat . punctuate (text " || ") . map (\i -> parens $ e1 <+> text "==" <+> i) $ ids _ -> error "'in' against not set expression" (o, _, _) | o `elem` boolOps -> do modify(\s -> s{lastType = BTBool}) return $ parens e1 <+> text o <+> parens e2 - | otherwise -> return $ parens e1 <+> text o <+> parens e2 + | otherwise -> do + o' <- return $ case o of + "/(float)" -> text "/(float)" -- pascal returns real value + _ -> text o + e1' <- return $ case (o, t1, t2) of + ("-", BTInt, BTInt) -> parens $ text "(int64_t)" <+> parens e1 + _ -> parens e1 + e2' <- return $ case (o, t1, t2) of + ("-", BTInt, BTInt) -> parens $ text "(int64_t)" <+> parens e2 + _ -> parens e2 + return $ e1' <+> o' <+> e2' where boolOps = ["==", "!=", "<", ">", "<=", ">="] expr2C (NumberLiteral s) = do @@ -806,7 +912,12 @@ BTRecord t _ -> do i <- op2CTyped op [SimpleType (Identifier t undefined)] ref2C $ FunCall [expr] (SimpleReference i) - _ -> return $ text (op2C op) <> e + BTBool -> do + o <- return $ case op of + "not" -> text "!" + _ -> text (op2C op) + return $ o <> parens e + _ -> return $ text (op2C op) <> parens e expr2C Null = return $ text "NULL" expr2C (CharCode a) = do modify(\s -> s{lastType = BTChar}) @@ -835,13 +946,13 @@ _ -> error $ "BuiltInFunCall 'high' from " ++ show e ++ "\ntype: " ++ show lt expr2C (BuiltInFunCall [e] (SimpleReference (Identifier "ord" _))) = liftM parens $ expr2C e expr2C (BuiltInFunCall [e] (SimpleReference (Identifier "succ" _))) = liftM (<> text " + 1") $ expr2C e -expr2C (BuiltInFunCall [e] (SimpleReference (Identifier "pred" _))) = liftM (<> text " - 1") $ expr2C e +expr2C (BuiltInFunCall [e] (SimpleReference (Identifier "pred" _))) = liftM (<> text " - (int64_t)1") $ expr2C e expr2C (BuiltInFunCall [e] (SimpleReference (Identifier "length" _))) = do e' <- expr2C e lt <- gets lastType modify (\s -> s{lastType = BTInt}) case lt of - BTString -> return $ text "Length" <> parens e' + BTString -> return $ text "fpcrtl_Length" <> parens e' BTArray RangeInfinite _ _ -> error $ "length() called on variable size array " ++ show e' BTArray (RangeFromTo _ n) _ _ -> initExpr2C (BuiltInFunction "succ" [n]) _ -> error $ "length() called on " ++ show lt @@ -864,7 +975,7 @@ case t of BTFunction _ _ rt -> do modify(\s -> s{lastType = rt}) - return $ i <> parens empty + return $ i <> parens empty --xymeng: removed parens _ -> return $ i ref2CF r@(RecordField (SimpleReference _) (SimpleReference _)) = do i <- ref2C r @@ -907,7 +1018,7 @@ r1 <- ref2C ref1 t <- fromPointer (show ref1) =<< gets lastType r2 <- case t of - BTRecord _ rs -> withRecordNamespace "" rs $ ref2C ref2 + BTRecord _ rs -> withRecordNamespace "" (rec2Records rs) $ ref2C ref2 BTUnit -> error "What??" a -> error $ "dereferencing from " ++ show a ++ "\n" ++ show rf return $ @@ -917,7 +1028,7 @@ t <- gets lastType case t of BTRecord _ rs -> do - r2 <- withRecordNamespace "" rs $ ref2C ref2 + r2 <- withRecordNamespace "" (rec2Records rs) $ ref2C ref2 return $ r1 <> text "." <> r2 BTUnit -> withLastIdNamespace $ ref2C ref2 a -> error $ "dereferencing from " ++ show a ++ "\n" ++ show rf @@ -962,7 +1073,7 @@ op2C :: String -> String op2C "or" = "|" op2C "and" = "&" -op2C "not" = "!" +op2C "not" = "~" op2C "xor" = "^" op2C "div" = "/" op2C "mod" = "%" @@ -970,5 +1081,6 @@ op2C "shr" = ">>" op2C "<>" = "!=" op2C "=" = "==" +op2C "/" = "/(float)" op2C a = a