From 9e4cef015d2e2b748b5898a8b793b77a1fe32619 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Dec 2022 19:12:44 +0200 Subject: [PATCH 001/197] Initial test version of selectable translations for launcher. --- launcher/CMakeLists.txt | 6 +- launcher/lobby/lobby_moc.ui | 4 +- launcher/lobby/lobbyroomrequest_moc.ui | 17 +- launcher/main.cpp | 5 + launcher/mainwindow_moc.cpp | 51 +- launcher/mainwindow_moc.h | 4 +- launcher/mainwindow_moc.ui | 315 +++++--- launcher/modManager/cmodlistview_moc.ui | 12 +- launcher/settingsView/csettingsview_moc.cpp | 18 +- launcher/settingsView/csettingsview_moc.h | 2 - launcher/settingsView/csettingsview_moc.ui | 839 ++++++++++---------- launcher/translation/launcher_uk.ts | 536 +++++++++++++ 12 files changed, 1210 insertions(+), 599 deletions(-) create mode 100644 launcher/translation/launcher_uk.ts diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 5764c38a1..319b1ea06 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -87,6 +87,10 @@ else() qt5_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS}) endif() +find_package(Qt5LinguistTools) +set(launcher_TS translation/launcher_pl.ts translation/launcher_ru.ts translation/launcher_uk.ts) +qt5_add_translation( QM_FILES ${launcher_TS} ) + if(WIN32) set(launcher_ICON VCMI_launcher.rc) endif() @@ -94,7 +98,7 @@ endif() if(BUILD_SINGLE_APP) add_library(vcmilauncher STATIC ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS}) else() - add_executable(vcmilauncher WIN32 ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS} ${launcher_ICON}) + add_executable(vcmilauncher WIN32 ${QM_FILES} ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS} ${launcher_ICON}) endif() if(WIN32) diff --git a/launcher/lobby/lobby_moc.ui b/launcher/lobby/lobby_moc.ui index 44d410903..e859f7f7c 100644 --- a/launcher/lobby/lobby_moc.ui +++ b/launcher/lobby/lobby_moc.ui @@ -11,7 +11,7 @@ - Form + @@ -52,7 +52,7 @@ - 127.0.0.1:5002 + 127.0.0.1:5002 diff --git a/launcher/lobby/lobbyroomrequest_moc.ui b/launcher/lobby/lobbyroomrequest_moc.ui index b9ef935fe..a05741970 100644 --- a/launcher/lobby/lobbyroomrequest_moc.ui +++ b/launcher/lobby/lobbyroomrequest_moc.ui @@ -51,39 +51,42 @@ 0 + + 2 + - 2 + 2 - 3 + 3 - 4 + 4 - 5 + 5 - 6 + 6 - 7 + 7 - 8 + 8 diff --git a/launcher/main.cpp b/launcher/main.cpp index 7180cb1d9..8cb856c22 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -32,6 +32,11 @@ int main(int argc, char * argv[]) { #endif QApplication vcmilauncher(argc, argv); + + //QTranslator translator; + //translator.load("./launcher_uk.qm"); + //vcmilauncher.installTranslator(&translator); + MainWindow mainWindow; mainWindow.show(); result = vcmilauncher.exec(); diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 5b71b9043..38951556a 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -68,41 +68,13 @@ MainWindow::MainWindow(QWidget * parent) move(position); } - //set default margins - - auto width = ui->startGameTitle->fontMetrics().boundingRect(ui->startGameTitle->text()).width(); - if(ui->startGameButton->iconSize().width() < width) - { - ui->startGameButton->setIconSize(QSize(width, width)); - } - auto tab_icon_size = ui->tabSelectList->iconSize(); - if(tab_icon_size.width() < width) - { - ui->tabSelectList->setIconSize(QSize(width, width + tab_icon_size.height() - tab_icon_size.width())); - ui->tabSelectList->setGridSize(QSize(width, width)); - // 4 is a dirty hack to make it look right - ui->tabSelectList->setMaximumWidth(width + 4); - } ui->tabListWidget->setCurrentIndex(0); ui->settingsView->isExtraResolutionsModEnabled = ui->modlistView->isExtraResolutionsModEnabled(); ui->settingsView->setDisplayList(); connect(ui->modlistView, &CModListView::extraResolutionsEnabledChanged, ui->settingsView, &CSettingsView::fillValidResolutions); - - connect(ui->tabSelectList, &QListWidget::currentRowChanged, [this](int i) { -#ifdef Q_OS_IOS - if(auto widget = qApp->focusWidget()) - widget->clearFocus(); -#endif - ui->tabListWidget->setCurrentIndex(i); - }); -#ifdef Q_OS_IOS - QScroller::grabGesture(ui->tabSelectList, QScroller::LeftMouseButtonGesture); - ui->tabSelectList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); -#endif - if(settings["launcher"]["updateOnStartup"].Bool()) UpdateDialog::showUpdateDialog(false); } @@ -122,12 +94,25 @@ void MainWindow::on_startGameButton_clicked() startGame({}); } -void MainWindow::on_tabSelectList_currentRowChanged(int currentRow) -{ - ui->startGameButton->setEnabled(currentRow != TabRows::LOBBY); -} - const CModList & MainWindow::getModList() const { return ui->modlistView->getModList(); } + +void MainWindow::on_modslistButton_clicked() +{ + ui->startGameButton->setEnabled(true); + ui->tabListWidget->setCurrentIndex(TabRows::MODS); +} + +void MainWindow::on_settingsButton_clicked() +{ + ui->startGameButton->setEnabled(true); + ui->tabListWidget->setCurrentIndex(TabRows::SETTINGS); +} + +void MainWindow::on_lobbyButton_clicked() +{ + ui->startGameButton->setEnabled(false); + ui->tabListWidget->setCurrentIndex(TabRows::LOBBY); +} diff --git a/launcher/mainwindow_moc.h b/launcher/mainwindow_moc.h index 0603790de..189f3f4f3 100644 --- a/launcher/mainwindow_moc.h +++ b/launcher/mainwindow_moc.h @@ -46,5 +46,7 @@ public slots: void on_startGameButton_clicked(); private slots: - void on_tabSelectList_currentRowChanged(int currentRow); + void on_modslistButton_clicked(); + void on_settingsButton_clicked(); + void on_lobbyButton_clicked(); }; diff --git a/launcher/mainwindow_moc.ui b/launcher/mainwindow_moc.ui index 01918642a..76f8cbfe1 100644 --- a/launcher/mainwindow_moc.ui +++ b/launcher/mainwindow_moc.ui @@ -31,153 +31,129 @@ - - + + - - 89 + + 1 0 - 89 - 89 + 0 + 0 - - Qt::ScrollBarAlwaysOff + + Settings - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContents - - - QAbstractItemView::NoEditTriggers - - - false - - - QAbstractItemView::NoDragDrop - - - QAbstractItemView::SelectItems + + + icons:menu-settings.pngicons:menu-settings.png - 89 - 89 + 60 + 60 - - QListView::Static - - - QListView::TopToBottom - - - false - - - QListView::Adjust - - - - 100 - 100 - - - - QListView::IconMode - - + true - + false - - - Mods - - - - icons:menu-mods.pngicons:menu-mods.png - - - - - Settings - - - - icons:menu-settings.pngicons:menu-settings.png - - - - - Lobby - - - - icons:menu-lobby.pngicons:menu-lobby.png - - + + true + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + Qt::Vertical + + + + 80 + 0 + + + + - + + + + 1 + 0 + + + + + 0 + 0 + + + + Lobby + + + + icons:menu-lobby.pngicons:menu-lobby.png + + + + 60 + 60 + + + + true + + + false + + + true + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + + + + 1 + 0 + + + + + 0 + 0 + + + 75 true - Start game - - - Qt::AlignCenter - - - - - - - true - - - - 0 - 0 - - - - 0 - - - - - - - - - - - 89 - 0 - - - - - 89 - 89 - - - - Play + @@ -198,6 +174,104 @@ Qt::ToolButtonIconOnly + + false + + + + + + + + 75 + true + + + + Start game + + + Qt::AlignCenter + + + + + + + true + + + + 10 + 0 + + + + 0 + + + + + + + + + + + 1 + 0 + + + + + 0 + 0 + + + + Mods + + + + icons:menu-mods.pngicons:menu-mods.png + + + + 60 + 60 + + + + true + + + true + + + true + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + + + QFrame::Plain + + + 1 + + + 0 + + + Qt::Vertical + @@ -225,7 +299,6 @@ - tabSelectList startGameButton diff --git a/launcher/modManager/cmodlistview_moc.ui b/launcher/modManager/cmodlistview_moc.ui index 50542e75d..5e546e279 100644 --- a/launcher/modManager/cmodlistview_moc.ui +++ b/launcher/modManager/cmodlistview_moc.ui @@ -6,12 +6,12 @@ 0 0 - 596 + 680 342 - Form + @@ -252,11 +252,11 @@ true - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:11pt;"><br /></p></body></html> +</style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:11pt;"><br /></p></body></html> true diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index fbae12bec..f19164771 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -72,12 +72,10 @@ void CSettingsView::loadSettings() #ifdef Q_OS_IOS ui->comboBoxFullScreen->setCurrentIndex(true); - ui->checkBoxFullScreen->setChecked(false); - for (auto widget : std::initializer_list{ui->comboBoxFullScreen, ui->checkBoxFullScreen}) - widget->setDisabled(true); + ui->comboBoxFullScreen->setDisabled(true); #else ui->comboBoxFullScreen->setCurrentIndex(settings["video"]["fullscreen"].Bool()); - ui->checkBoxFullScreen->setChecked(settings["video"]["realFullscreen"].Bool()); + //ui->checkBoxFullScreen->setChecked(settings["video"]["realFullscreen"].Bool()); #endif ui->comboBoxFriendlyAI->setCurrentText(QString::fromStdString(settings["server"]["friendlyAI"].String())); @@ -192,14 +190,10 @@ void CSettingsView::on_comboBoxResolution_currentTextChanged(const QString & arg void CSettingsView::on_comboBoxFullScreen_currentIndexChanged(int index) { - Settings node = settings.write["video"]["fullscreen"]; - node->Bool() = index; -} - -void CSettingsView::on_checkBoxFullScreen_stateChanged(int state) -{ - Settings node = settings.write["video"]["realFullscreen"]; - node->Bool() = state; + Settings nodeFullscreen = settings.write["video"]["fullscreen"]; + Settings nodeRealFullscreen = settings.write["video"]["realFullscreen"]; + nodeFullscreen->Bool() = index != 0; + nodeFullscreen->Bool() = index == 2; } void CSettingsView::on_comboBoxAutoCheck_currentIndexChanged(int index) diff --git a/launcher/settingsView/csettingsview_moc.h b/launcher/settingsView/csettingsview_moc.h index fba5bd2c6..6ca30a741 100644 --- a/launcher/settingsView/csettingsview_moc.h +++ b/launcher/settingsView/csettingsview_moc.h @@ -32,8 +32,6 @@ public slots: void fillValidResolutions(bool isExtraResolutionsModEnabled); private slots: - void on_checkBoxFullScreen_stateChanged(int state); - void on_comboBoxResolution_currentTextChanged(const QString & arg1); void on_comboBoxFullScreen_currentIndexChanged(int index); diff --git a/launcher/settingsView/csettingsview_moc.ui b/launcher/settingsView/csettingsview_moc.ui index a9c12aadb..27568bafa 100644 --- a/launcher/settingsView/csettingsview_moc.ui +++ b/launcher/settingsView/csettingsview_moc.ui @@ -11,7 +11,7 @@ - Form + @@ -26,47 +26,67 @@ 0 - - - - - true - + + + + false - AI on the map + Change - - - - - true - - + + - Video + Open - - + + - Show intro + User data directory - - - - 1024 + + + + Qt::Horizontal - - 65535 + + QSizePolicy::Fixed - - 3030 + + + 8 + 20 + + + + + + + + 1 + + + + Off + + + + + On + + + + + + + + Open @@ -79,28 +99,7 @@ - /usr/share/vcmi - - - - - - - Heroes III character set - - - - - - - Real fullscreen mode - - - - - - - Resolution + /usr/share/vcmi @@ -111,22 +110,63 @@ - - - - false + + + + BattleAI - - - 150 - 0 - + + + BattleAI + + + + + StupidAI + + + + + + + + 1024 + + + 65535 + + + 3030 + + + + + + + + 75 + true + - /home/user/.vcmi + Repositories - - true + + + + + + + + + Check for updates + + + + + + + Neutral AI @@ -145,12 +185,25 @@ On + + + Real + + - + + + + Open + + + + + 75 true @@ -159,210 +212,8 @@ - - - - QPlainTextEdit::NoWrap - - - http://downloads.vcmi.eu/Mods/repository.json - - - - - - - Open - - - - - - - Player AI - - - - - - - - true - - - - Repositories - - - - - - - - VCAI - - - - - Nullkiller - - - - - - - - Display index - - - - - - - 1 - - - - Off - - - - - On - - - - - - - - - BattleAI - - - - - StupidAI - - - - - - - - false - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 8 - 20 - - - - - - - - Open - - - - - - - - true - - - - Launcher Settings - - - - - - - Extra data directory - - - - - - - - 0 - 22 - - - - Enemy AI - - - - - - - false - - - Change - - - - - - - - - - Neutral AI - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 56 - 8 - - - - - - + + false @@ -373,158 +224,21 @@ - /home/user/.vcmi + /home/user/.vcmi true - - - - 1 - - - - Off - - - - - On - - - - - - - - 1 - - - - Off - - - - - On - - - - - - - - false - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 8 - - - - - - - - - true - true - false - - + + - AI in the battlefield + Player AI - - - - Check repositories on startup - - - - - - - - 0 - 22 - - - - Friendly AI - - - - - - - Log files directory - - - - - - - - true - - - - Data Directories - - - - - - - User data directory - - - - - - - Open - - - - - - - Network port - - - - + @@ -558,34 +272,331 @@ - + + + + + 0 + 22 + + + + Friendly AI + + + + + + + Resolution + + + + + + + + 75 + true + + + + AI on the map + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 8 + + + + + + + + Autosave - - + + - Check for updates + Display index - + + + + false + + + + 150 + 0 + + + + /home/user/.vcmi + + + true + + + + + + + Check repositories on startup + + + + + + + Network port + + + + + + + VCAI + + + + VCAI + + + + + Nullkiller + + + + + + + + false + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + + 75 + true + + + + Data Directories + + + + + + + + 75 + true + + + + Video + + + + + + + Heroes III character set + + + + + + + false + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + Extra data directory + + + + + + + 1 + + + + Off + + + + + On + + + + + + + + Log files directory + + + + + + + QPlainTextEdit::NoWrap + + + http://downloads.vcmi.eu/Mods/repository.json + + + + + + + Show intro + + + + + + + + 75 + true + + + + Launcher Settings + + + + Build version - + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 56 + 8 + + + + + + + + + 0 + 22 + + + + Enemy AI + + + + + + + 1 + + + + Off + + + + + On + + + + + + + + + + 75 + true + true + false + + + + AI in the battlefield + + + diff --git a/launcher/translation/launcher_uk.ts b/launcher/translation/launcher_uk.ts new file mode 100644 index 000000000..17ea2be7a --- /dev/null +++ b/launcher/translation/launcher_uk.ts @@ -0,0 +1,536 @@ + + + + + CModListView + + + Filter + Фільтр + + + + All mods + Усі модифікації + + + + Downloadable + Усі доступні + + + + Installed + Встановлені + + + + Updatable + Доступні оновлення + + + + Active + Активні + + + + Inactive + Неактивні + + + + Download && refresh repositories + Завантажити та оновити репозиторії + + + + + Description + Опис + + + + Changelog + Зміни + + + + Screenshots + Знімки + + + + Show details + Показати подробиці + + + + Uninstall + Видалити + + + + Enable + Активувати + + + + Disable + Деактивувати + + + + Update + Оновити + + + + Install + Встановити + + + + %p% (%v KB out of %m KB) + %p% (%v КБ з %m КБ) + + + + Abort + Відмінити + + + + Mod name + Назва модифікації + + + + Installed version + Встановлена версія + + + + Latest version + Найновіша версія + + + + Download size + Розмір для завантаження + + + + Authors + Автори + + + + License + Ліцензія + + + + Home + Домашня сторінка + + + + + Compatibility + Сумісність + + + + + Required VCMI version + Необхідна версія VCMI + + + + Supported VCMI version + Підтримувана версія VCMI + + + + Supported VCMI versions + Підтримувані версії VCMI + + + + Required mods + Необхідні модифікації + + + + Conflicting mods + Конфліктуючі модифікації + + + + This mod can not be installed or enabled because following dependencies are not present + Цю модифікацію не можна встановити чи активувати, оскільки відсутні наступні залежності + + + + This mod can not be enabled because following mods are incompatible with this mod + Цю модифікацію не можна ввімкнути, оскільки наступні модифікації несумісні з цією модифікацією + + + + This mod can not be disabled because it is required to run following mods + Цю модифікацію не можна відключити, оскільки вона необхідна для запуску наступних модифікацій + + + + This mod can not be uninstalled or updated because it is required to run following mods + Цю модифікацію не можна видалити або оновити, оскільки вона необхідна для запуску наступних модифікацій + + + + This is submod and it can not be installed or uninstalled separately from parent mod + Це вкладена модифікація, і її не можна встановити або видалити окремо від батьківської модифікації + + + + Notes + Примітки + + + + Screenshot %1 + Знімок екрану %1 + + + + CSettingsView + + + Change + Змінити + + + + + + Open + Відкрити + + + + User data directory + Тека даних користувача + + + + + + + Off + Вимкнено + + + + + + + On + Увімкнено + + + + Fullscreen + Повноекранний режим + + + + AI in the battlefield + Штучний інтелект на полі бою + + + + Repositories + Репозиторії + + + + Check for updates + Оновити зараз + + + + Neutral AI + Нейтральний ШІ + + + + Real + Повний + + + + General + Загальні налаштування + + + + Player AI + ШІ гравців + + + + Central European (Windows 1250) + Центральноєвропейська (Windows 1250) + + + + Cyrillic script (Windows 1251) + Кирилиця (Windows 1251) + + + + Western European (Windows 1252) + Західноєвропейська (Windows 1252) + + + + Simplified Chinese (GBK) + Спрощена китайська (GBK) + + + + Simplified Chinese (GB2312) + Спрощена китайська (GB2312) + + + + Korean (Windows 949) + Корейська (Windows 949) + + + + Friendly AI + Дружній ШІ + + + + Resolution + Роздільна здатність + + + + AI on the map + Штучний інтелект на карті пригод + + + + Autosave + Автозбереження + + + + Display index + Дісплей + + + + Check repositories on startup + Перевірка репозиторіїв при запуску + + + + Network port + Мережевий порт + + + + Data Directories + Теки даних гри + + + + Video + Графіка + + + + Heroes III character set + Кодування Heroes III + + + + Extra data directory + Додаткова тека даних + + + + Log files directory + Тека файлів журналу + + + + Show intro + Вступні відео + + + + Launcher Settings + Налаштування лаунчера + + + + Build version + Версія збірки + + + + Enemy AI + Ворожий ШІ + + + + ImageViewer + + + Image Viewer + Перегляд зображень + + + + Lobby + + + Connect + Підключитися + + + + Username + Ім'я користувача + + + + Server + Сервер + + + + Session + Сесія + + + + Players + Гравці + + + + New room + Створити кімнату + + + + Join room + Приєднатися до кімнати + + + + Ready + Готовність! + + + + Mods mismatch + Модифікації, що не збігаються + + + + Leave + Вийти з кімнати + + + + Kick player + Виключити гравця + + + + Players in the room + Гравці у кімнаті + + + + LobbyRoomRequest + + + Room settings + Налаштування кімнати + + + + Room name + Назва кімнати + + + + Maximum players + Максимум гравців + + + + Password (optional) + Пароль (за бажанням) + + + + MainWindow + + + VCMI Launcher + VCMI Launcher + + + + Mods + Модифікації + + + + Settings + Налаштування + + + + Lobby + Лобі + + + + Start game + Грати! + + + + Play + Грати! + + + + UpdateDialog + + + You have latest version + У вас встановлена остання версія + + + + Close + Закрити + + + + Check updates on startup + Перевіряти наявність оновлень при запуску + + + From 1696db8a3c6617462541a95d94d144c8b97d21b6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 25 Dec 2022 13:19:16 +0200 Subject: [PATCH 002/197] Launcher translation upgrade, still WIP: - launcher can be re-translated without restart - mod info (name/description/etc) can have localized versions --- Mods/vcmi/mod.json | 7 + launcher/CMakeLists.txt | 3 +- launcher/lobby/lobby_moc.cpp | 8 + launcher/lobby/lobby_moc.h | 1 + launcher/lobby/lobbyroomrequest_moc.cpp | 8 + launcher/lobby/lobbyroomrequest_moc.h | 1 + launcher/main.cpp | 4 - launcher/mainwindow_moc.cpp | 21 + launcher/mainwindow_moc.h | 3 + launcher/mainwindow_moc.ui | 225 +++--- launcher/modManager/cmodlist.cpp | 29 +- launcher/modManager/cmodlistmodel_moc.cpp | 24 +- launcher/modManager/cmodlistview_moc.cpp | 13 +- launcher/modManager/cmodlistview_moc.h | 1 + launcher/modManager/imageviewer_moc.cpp | 8 + launcher/modManager/imageviewer_moc.h | 1 + launcher/settingsView/csettingsview_moc.cpp | 27 + launcher/settingsView/csettingsview_moc.h | 3 + launcher/settingsView/csettingsview_moc.ui | 725 ++++++++++---------- launcher/translation/launcher_uk.ts | 554 +++++++++++++-- 20 files changed, 1153 insertions(+), 513 deletions(-) diff --git a/Mods/vcmi/mod.json b/Mods/vcmi/mod.json index 5c07c3150..2c212544f 100644 --- a/Mods/vcmi/mod.json +++ b/Mods/vcmi/mod.json @@ -1,6 +1,13 @@ { "name" : "VCMI essential files", "description" : "Essential files required for VCMI to run correctly", + + "translation_uk" : { + "name" : "VCMI - ключові файли", + "description" : "Ключові файли необхідні для повноцінної роботи VCMI", + "author" : "Команда VCMI", + "modType" : "Графіка", + }, "version" : "1.0", "author" : "VCMI Team", diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 319b1ea06..acfa79e0e 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -88,7 +88,7 @@ else() endif() find_package(Qt5LinguistTools) -set(launcher_TS translation/launcher_pl.ts translation/launcher_ru.ts translation/launcher_uk.ts) +set(launcher_TS translation/launcher_en.ts translation/launcher_pl.ts translation/launcher_ru.ts translation/launcher_uk.ts) qt5_add_translation( QM_FILES ${launcher_TS} ) if(WIN32) @@ -160,4 +160,5 @@ else() install(FILES "eu.vcmi.VCMI.metainfo.xml" DESTINATION share/metainfo) endif() endif() + install(DIRECTORY icons DESTINATION ${ICONS_DESTINATION}) diff --git a/launcher/lobby/lobby_moc.cpp b/launcher/lobby/lobby_moc.cpp index ca3218c3b..5dc57b79d 100644 --- a/launcher/lobby/lobby_moc.cpp +++ b/launcher/lobby/lobby_moc.cpp @@ -35,6 +35,14 @@ Lobby::Lobby(QWidget *parent) : ui->kickButton->setVisible(false); } +void Lobby::changeEvent(QEvent *event) +{ + if ( event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } +} + Lobby::~Lobby() { delete ui; diff --git a/launcher/lobby/lobby_moc.h b/launcher/lobby/lobby_moc.h index 364500223..4fdce76e0 100644 --- a/launcher/lobby/lobby_moc.h +++ b/launcher/lobby/lobby_moc.h @@ -19,6 +19,7 @@ class Lobby : public QWidget { Q_OBJECT + void changeEvent(QEvent *event) override; public: explicit Lobby(QWidget *parent = nullptr); ~Lobby(); diff --git a/launcher/lobby/lobbyroomrequest_moc.cpp b/launcher/lobby/lobbyroomrequest_moc.cpp index ec42e182d..d8e10e524 100644 --- a/launcher/lobby/lobbyroomrequest_moc.cpp +++ b/launcher/lobby/lobbyroomrequest_moc.cpp @@ -27,6 +27,14 @@ LobbyRoomRequest::LobbyRoomRequest(SocketLobby & socket, const QString & room, c show(); } +void LobbyRoomRequest::changeEvent(QEvent *event) +{ + if ( event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } +} + LobbyRoomRequest::~LobbyRoomRequest() { delete ui; diff --git a/launcher/lobby/lobbyroomrequest_moc.h b/launcher/lobby/lobbyroomrequest_moc.h index 4595c5ef5..eff7161e4 100644 --- a/launcher/lobby/lobbyroomrequest_moc.h +++ b/launcher/lobby/lobbyroomrequest_moc.h @@ -21,6 +21,7 @@ class LobbyRoomRequest : public QDialog { Q_OBJECT + void changeEvent(QEvent *event) override; public: explicit LobbyRoomRequest(SocketLobby & socket, const QString & room, const QMap & mods, QWidget *parent = nullptr); ~LobbyRoomRequest(); diff --git a/launcher/main.cpp b/launcher/main.cpp index 8cb856c22..acc5f0cde 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -33,10 +33,6 @@ int main(int argc, char * argv[]) #endif QApplication vcmilauncher(argc, argv); - //QTranslator translator; - //translator.load("./launcher_uk.qm"); - //vcmilauncher.installTranslator(&translator); - MainWindow mainWindow; mainWindow.show(); result = vcmilauncher.exec(); diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 38951556a..5ec14e8a2 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -79,6 +79,14 @@ MainWindow::MainWindow(QWidget * parent) UpdateDialog::showUpdateDialog(false); } +void MainWindow::changeEvent(QEvent *event) +{ + if ( event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } +} + MainWindow::~MainWindow() { //save window settings @@ -116,3 +124,16 @@ void MainWindow::on_lobbyButton_clicked() ui->startGameButton->setEnabled(false); ui->tabListWidget->setCurrentIndex(TabRows::LOBBY); } + +void MainWindow::updateTranslation() +{ + std::string languageCode = settings["general"]["language"].String(); + + QString translationFile = "./launcher_" + QString::fromStdString(languageCode) + ".qm"; + + qApp->removeTranslator(&translator); + if (!translator.load(translationFile)) + logGlobal->error("Failed to load translation"); + if (!qApp->installTranslator(&translator)) + logGlobal->error("Failed to install translator"); +} diff --git a/launcher/mainwindow_moc.h b/launcher/mainwindow_moc.h index 189f3f4f3..1ac2a85a8 100644 --- a/launcher/mainwindow_moc.h +++ b/launcher/mainwindow_moc.h @@ -26,6 +26,7 @@ class MainWindow : public QMainWindow { Q_OBJECT + QTranslator translator; private: Ui::MainWindow * ui; void load(); @@ -35,12 +36,14 @@ private: MODS = 0, SETTINGS = 1, LOBBY = 2 }; + void changeEvent(QEvent *event) override; public: explicit MainWindow(QWidget * parent = 0); ~MainWindow(); const CModList & getModList() const; + void updateTranslation(); public slots: void on_startGameButton_clicked(); diff --git a/launcher/mainwindow_moc.ui b/launcher/mainwindow_moc.ui index 76f8cbfe1..e54677155 100644 --- a/launcher/mainwindow_moc.ui +++ b/launcher/mainwindow_moc.ui @@ -31,6 +31,22 @@ + + + + QFrame::Plain + + + 1 + + + 0 + + + Qt::Vertical + + + @@ -75,64 +91,42 @@ - - - - Qt::Vertical - - - - 80 - 0 - - - - - - - - - 1 - 0 - - - - - 0 - 0 - + + + + + 75 + true + - Lobby + Start game - - - icons:menu-lobby.pngicons:menu-lobby.png - - - - 60 - 60 - - - - true - - - false - - - true - - - Qt::ToolButtonTextUnderIcon - - - true + + Qt::AlignCenter - + + + + true + + + + 10 + 0 + + + + 0 + + + + + + + @@ -179,39 +173,48 @@ - - - - - 75 - true - - - - Start game - - - Qt::AlignCenter - - - - - - - true - + + - - 10 + + 1 0 - - 0 + + + 0 + 0 + + + + Lobby + + + + icons:menu-lobby.pngicons:menu-lobby.png + + + + 60 + 60 + + + + true + + + false + + + true + + + Qt::ToolButtonTextUnderIcon + + + true - - - @@ -258,20 +261,60 @@ - - - - QFrame::Plain - - - 1 - - - 0 - + + Qt::Vertical + + + 80 + 0 + + + + + + + + + 1 + 0 + + + + + 0 + 0 + + + + + 75 + true + + + + Editor + + + + 60 + 60 + + + + false + + + false + + + Qt::ToolButtonIconOnly + + + false + diff --git a/launcher/modManager/cmodlist.cpp b/launcher/modManager/cmodlist.cpp index e08c99ce4..0146c983c 100644 --- a/launcher/modManager/cmodlist.cpp +++ b/launcher/modManager/cmodlist.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "cmodlist.h" +#include "../lib/CConfigHandler.h" #include "../../lib/JsonNode.h" #include "../../lib/filesystem/CFileInputStream.h" #include "../../lib/GameConstants.h" @@ -157,23 +158,35 @@ QString CModEntry::getName() const QVariant CModEntry::getValue(QString value) const { + QString lang = QString::fromStdString(settings["general"]["language"].String()); + QString langValue = "translation_" + lang; + + // Priorities + // 1) data from newest version + // 2) data from preferred language + + bool useRepositoryData = repository.contains(value); + if(repository.contains(value) && localData.contains(value)) { // value is present in both repo and locally installed. Select one from latest version QString installedVer = localData["installedVersion"].toString(); QString availableVer = repository["latestVersion"].toString(); - if(compareVersions(installedVer, availableVer)) - return repository[value]; - else - return localData[value]; + useRepositoryData = compareVersions(installedVer, availableVer); } - if(repository.contains(value)) - return repository[value]; + auto & storage = useRepositoryData ? repository : localData; - if(localData.contains(value)) - return localData[value]; + if (storage.contains(langValue)) + { + auto langStorage = storage[langValue].toMap(); + if (langStorage.contains(value)) + return langStorage[value]; + } + + if (storage.contains(value)) + return storage[value]; return QVariant(); } diff --git a/launcher/modManager/cmodlistmodel_moc.cpp b/launcher/modManager/cmodlistmodel_moc.cpp index fb2d3f1e0..59e325241 100644 --- a/launcher/modManager/cmodlistmodel_moc.cpp +++ b/launcher/modManager/cmodlistmodel_moc.cpp @@ -25,16 +25,6 @@ static const QString names[ModFields::COUNT] = "author" }; -static const QString header[ModFields::COUNT] = -{ - "Name", - "", // status icon - "", // status icon - "Type", - "Version", - "Size", - "Author" -}; } namespace ModStatus @@ -155,13 +145,25 @@ Qt::ItemFlags CModListModel::flags(const QModelIndex &) const QVariant CModListModel::headerData(int section, Qt::Orientation orientation, int role) const { + static const QString header[ModFields::COUNT] = + { + QT_TR_NOOP("Name"), + QT_TR_NOOP(""), // status icon + QT_TR_NOOP(""), // status icon + QT_TR_NOOP("Type"), + QT_TR_NOOP("Version"), + QT_TR_NOOP("Size"), + QT_TR_NOOP("Author") + }; + if(role == Qt::DisplayRole && orientation == Qt::Horizontal) - return ModFields::header[section]; + return QCoreApplication::translate("ModFields", header[section].toStdString().c_str()); return QVariant(); } void CModListModel::reloadRepositories() { + //emit headerDataChanged(Qt::Horizontal, 0, -1 ); beginResetModel(); endResetModel(); } diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index 00f115633..f977d3bac 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -34,6 +34,15 @@ void CModListView::setupModModel() this, &CModListView::extraResolutionsEnabledChanged); } +void CModListView::changeEvent(QEvent *event) +{ + if ( event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + modModel->reloadRepositories(); + } +} + void CModListView::setupFilterModel() { filterModel = new CModFilterModel(modModel, this); @@ -227,8 +236,8 @@ QString CModListView::genModInfoText(CModEntry & mod) QString textTemplate = prefix + "

%2

"; QString listTemplate = "

%1: %2

"; QString noteTemplate = "

%1

"; - QString compatibleString = prefix + "Mod is compatible

"; - QString incompatibleString = redPrefix + "Mod is incompatible

"; + QString compatibleString = prefix + tr("Mod is compatible") + "

"; + QString incompatibleString = redPrefix + tr("Mod is incompatible") + "

"; QString supportedVersions = redPrefix + "%2 %3 %4

"; QString result; diff --git a/launcher/modManager/cmodlistview_moc.h b/launcher/modManager/cmodlistview_moc.h index 688a79d05..dec1c9e8e 100644 --- a/launcher/modManager/cmodlistview_moc.h +++ b/launcher/modManager/cmodlistview_moc.h @@ -64,6 +64,7 @@ class CModListView : public QWidget QString genChangelogText(CModEntry & mod); QString genModInfoText(CModEntry & mod); + void changeEvent(QEvent *event) override; signals: void extraResolutionsEnabledChanged(bool enabled); diff --git a/launcher/modManager/imageviewer_moc.cpp b/launcher/modManager/imageviewer_moc.cpp index 8022250a9..107419073 100644 --- a/launcher/modManager/imageviewer_moc.cpp +++ b/launcher/modManager/imageviewer_moc.cpp @@ -20,6 +20,14 @@ ImageViewer::ImageViewer(QWidget * parent) ui->setupUi(this); } +void ImageViewer::changeEvent(QEvent *event) +{ + if ( event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } +} + ImageViewer::~ImageViewer() { delete ui; diff --git a/launcher/modManager/imageviewer_moc.h b/launcher/modManager/imageviewer_moc.h index 6e26efee2..771a4e9a4 100644 --- a/launcher/modManager/imageviewer_moc.h +++ b/launcher/modManager/imageviewer_moc.h @@ -21,6 +21,7 @@ class ImageViewer : public QDialog { Q_OBJECT + void changeEvent(QEvent *event) override; public: explicit ImageViewer(QWidget * parent = 0); ~ImageViewer(); diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index f19164771..b06b2f95e 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -11,6 +11,8 @@ #include "csettingsview_moc.h" #include "ui_csettingsview_moc.h" +#include "mainwindow_moc.h" + #include "../jsonutils.h" #include "../launcherdirs.h" #include "../updatedialog_moc.h" @@ -44,6 +46,15 @@ static const std::string knownEncodingsList[] = //TODO: remove hardcode "CP949" // extension of EUC-KR. }; +/// List of tags of languages that can be selected from Launcher (and have translation for Launcher) +static const std::string languageTagList[] = +{ + "en", // english + "pl", // polish + "ru", // russian + "uk", // ukrainian +}; + void CSettingsView::setDisplayList() { QStringList list; @@ -301,3 +312,19 @@ void CSettingsView::on_updatesButton_clicked() UpdateDialog::showUpdateDialog(true); } + +void CSettingsView::on_comboBoxLanguage_currentIndexChanged(int index) +{ + Settings node = settings.write["general"]["language"]; + node->String() = languageTagList[index]; + + dynamic_cast(qApp->activeWindow())->updateTranslation(); +} + +void CSettingsView::changeEvent(QEvent *event) +{ + if ( event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } +} diff --git a/launcher/settingsView/csettingsview_moc.h b/launcher/settingsView/csettingsview_moc.h index 6ca30a741..1d54b3ffa 100644 --- a/launcher/settingsView/csettingsview_moc.h +++ b/launcher/settingsView/csettingsview_moc.h @@ -25,6 +25,7 @@ public: void loadSettings(); void setDisplayList(); + void changeEvent(QEvent *event) override; bool isExtraResolutionsModEnabled{}; @@ -68,6 +69,8 @@ private slots: void on_updatesButton_clicked(); + void on_comboBoxLanguage_currentIndexChanged(int index); + private: Ui::CSettingsView * ui; diff --git a/launcher/settingsView/csettingsview_moc.ui b/launcher/settingsView/csettingsview_moc.ui index 27568bafa..20eb49c43 100644 --- a/launcher/settingsView/csettingsview_moc.ui +++ b/launcher/settingsView/csettingsview_moc.ui @@ -26,67 +26,35 @@ 0 - - + + false - - Change - - - - - - - Open - - - - - - - User data directory - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - + - 8 - 20 + 150 + 0 - - - - - - 1 + + /home/user/.vcmi + + + true - - - Off - - - - - On - - - - + + + + + 75 + true + + - Open + Launcher Settings @@ -103,6 +71,47 @@ + + + + false + + + Change + + + + + + + 1 + + + + Off + + + + + On + + + + + + + + Open + + + + + + + Heroes III character set + + + @@ -110,37 +119,57 @@ - - - - BattleAI + + + + 1 - BattleAI + Off - StupidAI + On - - - - 1024 - - - 65535 - - - 3030 + + + + Log files directory - + + + + false + + + + 150 + 0 + + + + /home/user/.vcmi + + + true + + + + + + + Autosave + + + + @@ -153,20 +182,123 @@ - - - - + Check for updates - - + + + + + - Neutral AI + Check repositories on startup + + + + + + + Open + + + + + + + Player AI + + + + + + + Display index + + + + + + + + 75 + true + true + false + + + + AI in the battlefield + + + + + + + QPlainTextEdit::NoWrap + + + http://downloads.vcmi.eu/Mods/repository.json + + + + + + + + 75 + true + + + + General + + + + + + + Show intro + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 8 + + + + + + + + Build version + + + + + + + Extra data directory + + + + + + + Resolution @@ -192,15 +324,25 @@ - - - - Open + + + + 1 + + + Off + + + + + On + + - - + + 75 @@ -208,37 +350,137 @@ - General + AI on the map - - - - false + + + + VCAI - + + + VCAI + + + + + Nullkiller + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + - 150 - 0 + 8 + 20 + + + + + + + - /home/user/.vcmi - - - true + Open - - + + + + + 75 + true + + - Player AI + Video - + + + + + 75 + true + + + + Data Directories + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 56 + 8 + + + + + + + + User data directory + + + + + + + Network port + + + + + + + 1024 + + + 65535 + + + 3030 + + + + + + + VCMI Language + + + + + + + + + + + @@ -272,127 +514,39 @@ - - - - - 0 - 22 - - - - Friendly AI - - - - - - - Resolution - - - - - - - - 75 - true - - - - AI on the map - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 8 - - - - - - - - - - - Autosave - - - - - - - Display index - - - - - - - false - - - - 150 - 0 - - - - /home/user/.vcmi - - - true - - - - - - - Check repositories on startup - - - - - - - Network port - - - - - - - VCAI - + + - VCAI + English - Nullkiller + Polska (Polish) + + + + + Русский (Russian) + + + + + Українська (Ukrainian) - - - - false + + + + Neutral AI + + + + BattleAI @@ -408,36 +562,16 @@ - - - - - 75 - true - + + + + + 0 + 22 + - Data Directories - - - - - - - - 75 - true - - - - Video - - - - - - - Heroes III character set + Friendly AI @@ -461,91 +595,27 @@ - - - - Extra data directory + + + + false - - - - - - 1 + + BattleAI - Off + BattleAI - On + StupidAI - - - - Log files directory - - - - - - - QPlainTextEdit::NoWrap - - - http://downloads.vcmi.eu/Mods/repository.json - - - - - - - Show intro - - - - - - - - 75 - true - - - - Launcher Settings - - - - - - - Build version - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 56 - 8 - - - - - + @@ -558,45 +628,6 @@ - - - - 1 - - - - Off - - - - - On - - - - - - - - - - - - - - - - 75 - true - true - false - - - - AI in the battlefield - - - diff --git a/launcher/translation/launcher_uk.ts b/launcher/translation/launcher_uk.ts index 17ea2be7a..84f955f8c 100644 --- a/launcher/translation/launcher_uk.ts +++ b/launcher/translation/launcher_uk.ts @@ -1,389 +1,750 @@ + + CModListModel + + + + ModFields + + + + + + Name + Назва + + + + + Type + Тип + + + + + Version + Версія + + + + + Size + Розмір + + + + + Author + Автори + + CModListView + Filter Фільтр + All mods Усі модифікації + Downloadable Усі доступні + Installed Встановлені + Updatable Доступні оновлення + Active Активні + Inactive Неактивні + Download && refresh repositories - Завантажити та оновити репозиторії + Оновити репозиторії - + + Description Опис + Changelog Зміни + Screenshots Знімки + Show details Показати подробиці + Uninstall Видалити + Enable Активувати + Disable Деактивувати + Update Оновити + Install Встановити + %p% (%v KB out of %m KB) %p% (%v КБ з %m КБ) + Abort Відмінити + + + + + + + + + + Mod name Назва модифікації + + + + + + + + + + Installed version Встановлена версія + + + + + + + + + + Latest version Найновіша версія + + + + + + + + + + Download size Розмір для завантаження + + + + + + + + + + Authors Автори + + + + + + + + + + License Ліцензія + + + + + + + + + + Home Домашня сторінка + + + + + + + + + + + + + + + + + + + + Compatibility Сумісність + + + + + + + + + + + + + + + + + + + + Required VCMI version Необхідна версія VCMI + + + + + + + + + + Supported VCMI version Підтримувана версія VCMI + + + + + + + + + + Supported VCMI versions Підтримувані версії VCMI + + + + + + + + + + Required mods Необхідні модифікації + + + + + + + + + + Conflicting mods Конфліктуючі модифікації + + + + + + + + + + This mod can not be installed or enabled because following dependencies are not present Цю модифікацію не можна встановити чи активувати, оскільки відсутні наступні залежності + + + + + + + + + + This mod can not be enabled because following mods are incompatible with this mod Цю модифікацію не можна ввімкнути, оскільки наступні модифікації несумісні з цією модифікацією + + + + + + + + + + This mod can not be disabled because it is required to run following mods Цю модифікацію не можна відключити, оскільки вона необхідна для запуску наступних модифікацій + + + + + + + + + + This mod can not be uninstalled or updated because it is required to run following mods Цю модифікацію не можна видалити або оновити, оскільки вона необхідна для запуску наступних модифікацій + + + + + + + + + + This is submod and it can not be installed or uninstalled separately from parent mod Це вкладена модифікація, і її не можна встановити або видалити окремо від батьківської модифікації + + + + + + + + + + Notes Примітки + + + + + + + + + + Screenshot %1 Знімок екрану %1 + + + + + + + Mod is compatible + Модифікація сумісна + + + + + + + + Mod is incompatible + Модифікація несумісна + CSettingsView - + + Change Змінити - - - + + + + Open Відкрити - + + User data directory Тека даних користувача - - - - + + + + + Off Вимкнено - - - - + + + + + On Увімкнено - + + Fullscreen Повноекранний режим - + + AI in the battlefield Штучний інтелект на полі бою - + + Repositories Репозиторії - + + Check for updates Оновити зараз - + + Neutral AI Нейтральний ШІ - + + Real Повний - + + General Загальні налаштування - + + Player AI ШІ гравців - + + + VCMI Language + Мова VCMI + + + + Central European (Windows 1250) Центральноєвропейська (Windows 1250) - + + Cyrillic script (Windows 1251) Кирилиця (Windows 1251) - + + Western European (Windows 1252) Західноєвропейська (Windows 1252) - + + Simplified Chinese (GBK) Спрощена китайська (GBK) - + + Simplified Chinese (GB2312) Спрощена китайська (GB2312) - + + Korean (Windows 949) Корейська (Windows 949) - + + + English + English (Англійська) + + + + + Polska (Polish) + Polska (Польська) + + + + + Русский (Russian) + Русский (Російська) + + + + + Українська (Ukrainian) + Українська + + + + Friendly AI Дружній ШІ - + + Resolution Роздільна здатність - + + AI on the map Штучний інтелект на карті пригод - + + Autosave Автозбереження - + + Display index Дісплей - + + Check repositories on startup Перевірка репозиторіїв при запуску - + + Network port Мережевий порт - + + Data Directories Теки даних гри - + + Video Графіка - + + Heroes III character set Кодування Heroes III - + + Extra data directory Додаткова тека даних - + + Log files directory Тека файлів журналу - + + Show intro Вступні відео - + + Launcher Settings Налаштування лаунчера - + + Build version Версія збірки - + + Enemy AI Ворожий ШІ @@ -391,6 +752,7 @@ ImageViewer + Image Viewer Перегляд зображень @@ -399,61 +761,73 @@ Lobby + Connect Підключитися + Username Ім'я користувача + Server Сервер + Session Сесія + Players Гравці + New room Створити кімнату + Join room Приєднатися до кімнати + Ready Готовність! + Mods mismatch Модифікації, що не збігаються + Leave Вийти з кімнати + Kick player Виключити гравця + Players in the room Гравці у кімнаті @@ -462,21 +836,25 @@ LobbyRoomRequest + Room settings Налаштування кімнати + Room name Назва кімнати + Maximum players Максимум гравців + Password (optional) Пароль (за бажанням) @@ -485,27 +863,44 @@ MainWindow + VCMI Launcher VCMI Launcher - + + Mods Модифікації - + + + Editor + Редактор + + + + + Map Editor + Редактор мап + + + + Settings Налаштування - + + Lobby Лобі - + + Start game Грати! @@ -515,19 +910,80 @@ Грати! + + ModFields + + + + + + + + + Name + Назва + + + + + + + + + + Type + Тип + + + + + + + + + + Version + Версія + + + + + + + + + + Size + Розмір + + + + + + + + + + Author + Автори + + UpdateDialog + You have latest version У вас встановлена остання версія + Close Закрити + Check updates on startup Перевіряти наявність оновлень при запуску From a481961c57623c41e8e8f0e82cd85128fc4bb417 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 25 Dec 2022 21:41:45 +0200 Subject: [PATCH 003/197] Added dummy files for WIP translations --- launcher/translation/english.ts | 992 ++++++++++++++++++ launcher/translation/polish.ts | 992 ++++++++++++++++++ launcher/translation/russian.ts | 992 ++++++++++++++++++ .../{launcher_uk.ts => ukrainian.ts} | 0 4 files changed, 2976 insertions(+) create mode 100644 launcher/translation/english.ts create mode 100644 launcher/translation/polish.ts create mode 100644 launcher/translation/russian.ts rename launcher/translation/{launcher_uk.ts => ukrainian.ts} (100%) diff --git a/launcher/translation/english.ts b/launcher/translation/english.ts new file mode 100644 index 000000000..bd2e8fe44 --- /dev/null +++ b/launcher/translation/english.ts @@ -0,0 +1,992 @@ + + + + + CModListModel + + + + ModFields + + + + + + Name + + + + + + Type + + + + + + Version + + + + + + Size + + + + + + Author + + + + + CModListView + + + + Filter + + + + + + All mods + + + + + + Downloadable + + + + + + Installed + + + + + + Updatable + + + + + + Active + + + + + + Inactive + + + + + + Download && refresh repositories + + + + + + + Description + + + + + + Changelog + + + + + + Screenshots + + + + + + Show details + + + + + + Uninstall + + + + + + Enable + + + + + + Disable + + + + + + Update + + + + + + Install + + + + + + %p% (%v KB out of %m KB) + + + + + + Abort + + + + + + + + + + + + + + + Mod name + + + + + + + + + + + + + + + Installed version + + + + + + + + + + + + + + + Latest version + + + + + + + + + + + + + + + Download size + + + + + + + + + + + + + + + Authors + + + + + + + + + + + + + + + License + + + + + + + + + + + + + + + Home + + + + + + + + + + + + + + + + + + + + + + + + + + Compatibility + + + + + + + + + + + + + + + + + + + + + + + + + + Required VCMI version + + + + + + + + + + + + + + + Supported VCMI version + + + + + + + + + + + + + + + Supported VCMI versions + + + + + + + + + + + + + + + Required mods + + + + + + + + + + + + + + + Conflicting mods + + + + + + + + + + + + + + + This mod can not be installed or enabled because following dependencies are not present + + + + + + + + + + + + + + + This mod can not be enabled because following mods are incompatible with this mod + + + + + + + + + + + + + + + This mod can not be disabled because it is required to run following mods + + + + + + + + + + + + + + + This mod can not be uninstalled or updated because it is required to run following mods + + + + + + + + + + + + + + + This is submod and it can not be installed or uninstalled separately from parent mod + + + + + + + + + + + + + + + Notes + + + + + + + + + + + + + + + Screenshot %1 + + + + + + + + + Mod is compatible + + + + + + + + + Mod is incompatible + + + + + CSettingsView + + + + Change + + + + + + + + Open + + + + + + User data directory + + + + + + + + + Off + + + + + + + + + On + + + + + + Fullscreen + + + + + + Repositories + + + + + + Check for updates + + + + + + Neutral AI + + + + + + Real + + + + + + General + + + + + + Player AI + + + + + + VCMI Language + + + + + + Central European (Windows 1250) + + + + + + Cyrillic script (Windows 1251) + + + + + + Western European (Windows 1252) + + + + + + Simplified Chinese (GBK) + + + + + + Simplified Chinese (GB2312) + + + + + + Korean (Windows 949) + + + + + + English + + + + + + Polska (Polish) + + + + + + Русский (Russian) + + + + + + Українська (Ukrainian) + + + + + + Friendly AI + + + + + + Resolution + + + + + + AI on the map + + + + + + Autosave + + + + + + Display index + + + + + + Check repositories on startup + + + + + + Network port + + + + + + Data Directories + + + + + + Video + + + + + + Heroes III character set + + + + + + Extra data directory + + + + + + Log files directory + + + + + + Show intro + + + + + + Launcher Settings + + + + + + Build version + + + + + + Enemy AI + + + + + + AI in the battlefield + + + + + ImageViewer + + + + Image Viewer + + + + + Lobby + + + + Connect + + + + + + Username + + + + + + Server + + + + + + Session + + + + + + Players + + + + + + New room + + + + + + Join room + + + + + + Ready + + + + + + Mods mismatch + + + + + + Leave + + + + + + Kick player + + + + + + Players in the room + + + + + LobbyRoomRequest + + + + Room settings + + + + + + Room name + + + + + + Maximum players + + + + + + Password (optional) + + + + + MainWindow + + + + VCMI Launcher + + + + + + Settings + + + + + + Start game + + + + + + Lobby + + + + + + Mods + + + + + + Editor + + + + + + Map Editor + + + + + Play + + + + + ModFields + + + + + + + + + Name + + + + + + + + + + + Type + + + + + + + + + + + Version + + + + + + + + + + + Size + + + + + + + + + + + Author + + + + + UpdateDialog + + + + You have latest version + + + + + + Close + + + + + + Check updates on startup + + + + diff --git a/launcher/translation/polish.ts b/launcher/translation/polish.ts new file mode 100644 index 000000000..723608b37 --- /dev/null +++ b/launcher/translation/polish.ts @@ -0,0 +1,992 @@ + + + + + CModListModel + + + + ModFields + + + + + + Name + + + + + + Type + + + + + + Version + + + + + + Size + + + + + + Author + + + + + CModListView + + + + Filter + + + + + + All mods + + + + + + Downloadable + + + + + + Installed + + + + + + Updatable + + + + + + Active + + + + + + Inactive + + + + + + Download && refresh repositories + + + + + + + Description + + + + + + Changelog + + + + + + Screenshots + + + + + + Show details + + + + + + Uninstall + + + + + + Enable + + + + + + Disable + + + + + + Update + + + + + + Install + + + + + + %p% (%v KB out of %m KB) + + + + + + Abort + + + + + + + + + + + + + + + Mod name + + + + + + + + + + + + + + + Installed version + + + + + + + + + + + + + + + Latest version + + + + + + + + + + + + + + + Download size + + + + + + + + + + + + + + + Authors + + + + + + + + + + + + + + + License + + + + + + + + + + + + + + + Home + + + + + + + + + + + + + + + + + + + + + + + + + + Compatibility + + + + + + + + + + + + + + + + + + + + + + + + + + Required VCMI version + + + + + + + + + + + + + + + Supported VCMI version + + + + + + + + + + + + + + + Supported VCMI versions + + + + + + + + + + + + + + + Required mods + + + + + + + + + + + + + + + Conflicting mods + + + + + + + + + + + + + + + This mod can not be installed or enabled because following dependencies are not present + + + + + + + + + + + + + + + This mod can not be enabled because following mods are incompatible with this mod + + + + + + + + + + + + + + + This mod can not be disabled because it is required to run following mods + + + + + + + + + + + + + + + This mod can not be uninstalled or updated because it is required to run following mods + + + + + + + + + + + + + + + This is submod and it can not be installed or uninstalled separately from parent mod + + + + + + + + + + + + + + + Notes + + + + + + + + + + + + + + + Screenshot %1 + + + + + + + + + Mod is compatible + + + + + + + + + Mod is incompatible + + + + + CSettingsView + + + + Change + + + + + + + + Open + + + + + + User data directory + + + + + + + + + Off + + + + + + + + + On + + + + + + Fullscreen + + + + + + Repositories + + + + + + Check for updates + + + + + + Neutral AI + + + + + + Real + + + + + + General + + + + + + Player AI + + + + + + VCMI Language + + + + + + Central European (Windows 1250) + + + + + + Cyrillic script (Windows 1251) + + + + + + Western European (Windows 1252) + + + + + + Simplified Chinese (GBK) + + + + + + Simplified Chinese (GB2312) + + + + + + Korean (Windows 949) + + + + + + English + English (Angielski) + + + + + Polska (Polish) + Polska + + + + + Русский (Russian) + Русский (Rosyjski) + + + + + Українська (Ukrainian) + Українська (Ukraiński) + + + + + Friendly AI + + + + + + Resolution + + + + + + AI on the map + + + + + + Autosave + + + + + + Display index + + + + + + Check repositories on startup + + + + + + Network port + + + + + + Data Directories + + + + + + Video + + + + + + Heroes III character set + + + + + + Extra data directory + + + + + + Log files directory + + + + + + Show intro + + + + + + Launcher Settings + + + + + + Build version + + + + + + Enemy AI + + + + + + AI in the battlefield + + + + + ImageViewer + + + + Image Viewer + + + + + Lobby + + + + Connect + + + + + + Username + + + + + + Server + + + + + + Session + + + + + + Players + + + + + + New room + + + + + + Join room + + + + + + Ready + + + + + + Mods mismatch + + + + + + Leave + + + + + + Kick player + + + + + + Players in the room + + + + + LobbyRoomRequest + + + + Room settings + + + + + + Room name + + + + + + Maximum players + + + + + + Password (optional) + + + + + MainWindow + + + + VCMI Launcher + + + + + + Settings + + + + + + Start game + + + + + + Lobby + + + + + + Mods + + + + + + Editor + + + + + + Map Editor + + + + + Play + + + + + ModFields + + + + + + + + + Name + + + + + + + + + + + Type + + + + + + + + + + + Version + + + + + + + + + + + Size + + + + + + + + + + + Author + + + + + UpdateDialog + + + + You have latest version + + + + + + Close + + + + + + Check updates on startup + + + + diff --git a/launcher/translation/russian.ts b/launcher/translation/russian.ts new file mode 100644 index 000000000..8402c54ca --- /dev/null +++ b/launcher/translation/russian.ts @@ -0,0 +1,992 @@ + + + + + CModListModel + + + + ModFields + + + + + + Name + + + + + + Type + + + + + + Version + + + + + + Size + + + + + + Author + + + + + CModListView + + + + Filter + + + + + + All mods + + + + + + Downloadable + + + + + + Installed + + + + + + Updatable + + + + + + Active + + + + + + Inactive + + + + + + Download && refresh repositories + + + + + + + Description + + + + + + Changelog + + + + + + Screenshots + + + + + + Show details + + + + + + Uninstall + + + + + + Enable + + + + + + Disable + + + + + + Update + + + + + + Install + + + + + + %p% (%v KB out of %m KB) + + + + + + Abort + + + + + + + + + + + + + + + Mod name + + + + + + + + + + + + + + + Installed version + + + + + + + + + + + + + + + Latest version + + + + + + + + + + + + + + + Download size + + + + + + + + + + + + + + + Authors + + + + + + + + + + + + + + + License + + + + + + + + + + + + + + + Home + + + + + + + + + + + + + + + + + + + + + + + + + + Compatibility + + + + + + + + + + + + + + + + + + + + + + + + + + Required VCMI version + + + + + + + + + + + + + + + Supported VCMI version + + + + + + + + + + + + + + + Supported VCMI versions + + + + + + + + + + + + + + + Required mods + + + + + + + + + + + + + + + Conflicting mods + + + + + + + + + + + + + + + This mod can not be installed or enabled because following dependencies are not present + + + + + + + + + + + + + + + This mod can not be enabled because following mods are incompatible with this mod + + + + + + + + + + + + + + + This mod can not be disabled because it is required to run following mods + + + + + + + + + + + + + + + This mod can not be uninstalled or updated because it is required to run following mods + + + + + + + + + + + + + + + This is submod and it can not be installed or uninstalled separately from parent mod + + + + + + + + + + + + + + + Notes + + + + + + + + + + + + + + + Screenshot %1 + + + + + + + + + Mod is compatible + + + + + + + + + Mod is incompatible + + + + + CSettingsView + + + + Change + + + + + + + + Open + + + + + + User data directory + + + + + + + + + Off + + + + + + + + + On + + + + + + Fullscreen + + + + + + Repositories + + + + + + Check for updates + + + + + + Neutral AI + + + + + + Real + + + + + + General + + + + + + Player AI + + + + + + VCMI Language + + + + + + Central European (Windows 1250) + + + + + + Cyrillic script (Windows 1251) + + + + + + Western European (Windows 1252) + + + + + + Simplified Chinese (GBK) + + + + + + Simplified Chinese (GB2312) + + + + + + Korean (Windows 949) + + + + + + English + English (Английский) + + + + + Polska (Polish) + Polska (Польский) + + + + + Русский (Russian) + Русский + + + + + Українська (Ukrainian) + Українська (Украинский) + + + + + Friendly AI + + + + + + Resolution + + + + + + AI on the map + + + + + + Autosave + + + + + + Display index + + + + + + Check repositories on startup + + + + + + Network port + + + + + + Data Directories + + + + + + Video + + + + + + Heroes III character set + + + + + + Extra data directory + + + + + + Log files directory + + + + + + Show intro + + + + + + Launcher Settings + + + + + + Build version + + + + + + Enemy AI + + + + + + AI in the battlefield + + + + + ImageViewer + + + + Image Viewer + + + + + Lobby + + + + Connect + + + + + + Username + + + + + + Server + + + + + + Session + + + + + + Players + + + + + + New room + + + + + + Join room + + + + + + Ready + + + + + + Mods mismatch + + + + + + Leave + + + + + + Kick player + + + + + + Players in the room + + + + + LobbyRoomRequest + + + + Room settings + + + + + + Room name + + + + + + Maximum players + + + + + + Password (optional) + + + + + MainWindow + + + + VCMI Launcher + + + + + + Settings + + + + + + Start game + + + + + + Lobby + + + + + + Mods + + + + + + Editor + + + + + + Map Editor + + + + + Play + + + + + ModFields + + + + + + + + + Name + + + + + + + + + + + Type + + + + + + + + + + + Version + + + + + + + + + + + Size + + + + + + + + + + + Author + + + + + UpdateDialog + + + + You have latest version + + + + + + Close + + + + + + Check updates on startup + + + + diff --git a/launcher/translation/launcher_uk.ts b/launcher/translation/ukrainian.ts similarity index 100% rename from launcher/translation/launcher_uk.ts rename to launcher/translation/ukrainian.ts From a0c78e8ecbdfbd8b298c1da44c22ce64396f1c9b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 25 Dec 2022 21:58:20 +0200 Subject: [PATCH 004/197] Added german translation by Laserlicht --- Mods/vcmi/mod.json | 9 +- launcher/settingsView/csettingsview_moc.cpp | 9 +- launcher/settingsView/csettingsview_moc.ui | 5 + launcher/translation/german.ts | 992 ++++++++++++++++++++ 4 files changed, 1010 insertions(+), 5 deletions(-) create mode 100644 launcher/translation/german.ts diff --git a/Mods/vcmi/mod.json b/Mods/vcmi/mod.json index 2c212544f..a78ad2887 100644 --- a/Mods/vcmi/mod.json +++ b/Mods/vcmi/mod.json @@ -2,7 +2,14 @@ "name" : "VCMI essential files", "description" : "Essential files required for VCMI to run correctly", - "translation_uk" : { + "german" : { + "name" : "VCMI - grundlegende Dateien", + "description" : "Grundlegende Dateien, die für die korrekte Ausführung von VCMI erforderlich sind", + "author" : "VCMI-Team", + "modType" : "Grafik", + }, + + "ukrainian" : { "name" : "VCMI - ключові файли", "description" : "Ключові файли необхідні для повноцінної роботи VCMI", "author" : "Команда VCMI", diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index b06b2f95e..74d98a02f 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -49,10 +49,11 @@ static const std::string knownEncodingsList[] = //TODO: remove hardcode /// List of tags of languages that can be selected from Launcher (and have translation for Launcher) static const std::string languageTagList[] = { - "en", // english - "pl", // polish - "ru", // russian - "uk", // ukrainian + "english", + "german", + "polish", + "russian", + "ukrainian", }; void CSettingsView::setDisplayList() diff --git a/launcher/settingsView/csettingsview_moc.ui b/launcher/settingsView/csettingsview_moc.ui index 20eb49c43..016576c32 100644 --- a/launcher/settingsView/csettingsview_moc.ui +++ b/launcher/settingsView/csettingsview_moc.ui @@ -521,6 +521,11 @@ English + + + Deutsch (German) + + Polska (Polish) diff --git a/launcher/translation/german.ts b/launcher/translation/german.ts new file mode 100644 index 000000000..0dd3c5ffe --- /dev/null +++ b/launcher/translation/german.ts @@ -0,0 +1,992 @@ + + + + + CModListModel + + + + ModFields + + + + + + Name + Name + + + + + Type + Typ + + + + + Version + Version + + + + + Size + Größe + + + + + Author + Autor + + + + CModListView + + + + Filter + Filter + + + + + All mods + Alle Mods + + + + + Downloadable + Herunterladbar + + + + + Installed + Installiert + + + + + Updatable + Aktualisierbar + + + + + Active + Aktiv + + + + + Inactive + Inaktiv + + + + + Download && refresh repositories + Repositories herunterladen && aktualisieren + + + + + + Description + Beschreibung + + + + + Changelog + Änderungslog + + + + + Screenshots + Screenshots + + + + + Show details + Details anzeigen + + + + + Uninstall + Deinstallieren + + + + + Enable + Aktivieren + + + + + Disable + Deaktivieren + + + + + Update + Aktualisieren + + + + + Install + Installieren + + + + + %p% (%v KB out of %m KB) + %p% (%v КB von %m КB) + + + + + Abort + Abbrechen + + + + + + + + + + + + + + Mod name + Mod-Name + + + + + + + + + + + + + + Installed version + Installierte Version + + + + + + + + + + + + + + Latest version + Letzte Version + + + + + + + + + + + + + + Download size + Downloadgröße + + + + + + + + + + + + + + Authors + Autoren + + + + + + + + + + + + + + License + Lizenz + + + + + + + + + + + + + + Home + Home + + + + + + + + + + + + + + + + + + + + + + + + + Compatibility + Kompatibilität + + + + + + + + + + + + + + + + + + + + + + + + + Required VCMI version + Benötigte VCMI Version + + + + + + + + + + + + + + Supported VCMI version + Unterstützte VCMI Version + + + + + + + + + + + + + + Supported VCMI versions + Unterstützte VCMI Versionen + + + + + + + + + + + + + + Required mods + Benötigte Mods + + + + + + + + + + + + + + Conflicting mods + Mods mit Konflikt + + + + + + + + + + + + + + This mod can not be installed or enabled because following dependencies are not present + Diese Mod kann nicht installiert oder aktiviert werden, da die folgenden Abhängigkeiten nicht vorhanden sind + + + + + + + + + + + + + + This mod can not be enabled because following mods are incompatible with this mod + Diese Mod kann nicht aktiviert werden, da folgende Mods nicht mit dieser Mod kompatibel sind + + + + + + + + + + + + + + This mod can not be disabled because it is required to run following mods + Diese Mod kann nicht deaktiviert werden, da sie zum Ausführen der folgenden Mods erforderlich ist + + + + + + + + + + + + + + This mod can not be uninstalled or updated because it is required to run following mods + Diese Mod kann nicht deinstalliert oder aktualisiert werden, da sie für die folgenden Mods erforderlich ist + + + + + + + + + + + + + + This is submod and it can not be installed or uninstalled separately from parent mod + Dies ist eine Submod und kann nicht separat von der Hauptmod installiert oder deinstalliert werden + + + + + + + + + + + + + + Notes + Anmerkungen + + + + + + + + + + + + + + Screenshot %1 + Screenshot %1 + + + + + + + + Mod is compatible + Mod ist kompatibel + + + + + + + + Mod is incompatible + Mod ist inkompatibel + + + + CSettingsView + + + + Change + Ändern + + + + + + + Open + Öffnen + + + + + User data directory + Verzeichnis der Benutzerdaten + + + + + + + + Off + Aus + + + + + + + + On + An + + + + + Fullscreen + Vollbild + + + + + AI in the battlefield + KI auf dem Schlachtfeld + + + + + Repositories + Repositories + + + + + Check for updates + Nach Aktualisierungen suchen + + + + + Neutral AI + Neutrale KI + + + + + Real + Vollständig + + + + + General + Allgemein + + + + + Player AI + Spieler-KI + + + + + VCMI Language + VCMI-Sprache + + + + + Central European (Windows 1250) + Mitteleuropäisch (Windows 1250) + + + + + Cyrillic script (Windows 1251) + Kyrillische Schrift (Windows 1251) + + + + + Western European (Windows 1252) + Westeuropäisch (Windows 1252) + + + + + Simplified Chinese (GBK) + Vereinfachtes Chinesisch (GBK) + + + + + Simplified Chinese (GB2312) + Vereinfachtes Chinesisch (GB2312) + + + + + Korean (Windows 949) + Koreanisch (Windows 949) + + + + + English + English (Englisch) + + + + + Polska (Polish) + Polska (Polnisch) + + + + + Русский (Russian) + Русский (Russisch) + + + + + Українська (Ukrainian) + Українська (Ukrainisch) + + + + + Friendly AI + Freundliche KI + + + + + Resolution + Auflösung + + + + + AI on the map + KI auf der Karte + + + + + Autosave + Autospeichern + + + + + Display index + Anzeige-Index + + + + + Check repositories on startup + Repositories beim Start prüfen + + + + + Network port + Netzwerk-Port + + + + + Data Directories + Daten-Verzeichnisse + + + + + Video + Video + + + + + Heroes III character set + Heroes III Zeichensatz + + + + + Extra data directory + Zusätzliches Daten-Verzeichnis + + + + + Log files directory + Verzeichnis der Log-Dateien + + + + + Show intro + Intro anzeigen + + + + + Launcher Settings + Launcher-Einstellungen + + + + + Build version + Version des Builds + + + + + Enemy AI + Feindliche KI + + + + ImageViewer + + + + Image Viewer + Bildbetrachter + + + + Lobby + + + + Connect + Verbinden + + + + + Username + Benutzername + + + + + Server + Server + + + + + Session + Sitzung + + + + + Players + Spieler + + + + + New room + Neuer Raum + + + + + Join room + Raum beitreten + + + + + Ready + Bereit + + + + + Mods mismatch + Mods stimmen nicht überein + + + + + Leave + Verlassen + + + + + Kick player + Spieler kicken + + + + + Players in the room + Spieler im Raum + + + + LobbyRoomRequest + + + + Room settings + Raumeinstellungen + + + + + Room name + Raumname + + + + + Maximum players + Maximale Spieler + + + + + Password (optional) + Passwort (optional) + + + + MainWindow + + + + VCMI Launcher + VCMI Launcher + + + + + Mods + Mods + + + + + Editor + Editor + + + + + Map Editor + Karteneditor + + + + + Settings + Einstellungen + + + + + Lobby + Lobby + + + + + Start game + Spiel starten + + + + Play + Spielen + + + + ModFields + + + + + + + + + Name + Name + + + + + + + + + + Type + Typ + + + + + + + + + + Version + Version + + + + + + + + + + Size + Größe + + + + + + + + + + Author + Autor + + + + UpdateDialog + + + + You have latest version + Sie haben die neueste Version + + + + + Close + Beenden + + + + + Check updates on startup + Nach Aktualisierungen beim Starten prüfen + + + From b012cd949037510bbbc6fceddf91f559d490f316 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 25 Dec 2022 22:18:14 +0200 Subject: [PATCH 005/197] Compiled launcher translations are now in launcher/translations/ dir --- CMakeLists.txt | 4 ++-- launcher/CMakeLists.txt | 22 +++++++++++++++------- launcher/mainwindow_moc.cpp | 2 +- launcher/modManager/cmodlist.cpp | 3 +-- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 95fe102a4..759095988 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -316,8 +316,8 @@ find_package(TBB REQUIRED) if(ENABLE_LAUNCHER OR ENABLE_EDITOR) # Widgets finds its own dependencies (QtGui and QtCore). - find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network) - find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network) + find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network LinguistTools) + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network LinguistTools) endif() if(ENABLE_LUA) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index acfa79e0e..6883e4e9a 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -62,6 +62,13 @@ set(launcher_FORMS lobby/lobbyroomrequest_moc.ui ) +set(launcher_TS + translation/english.ts + translation/german.ts + translation/polish.ts + translation/russian.ts + translation/ukrainian.ts) + if(APPLE_IOS) list(APPEND launcher_SRCS ios/main.m @@ -83,13 +90,13 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) if(TARGET Qt6::Core) qt_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS}) + qt_add_translation( launcher_QM ${launcher_TS} ) else() + set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/translations) qt5_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS}) + qt5_add_translation( launcher_QM ${launcher_TS} ) endif() -find_package(Qt5LinguistTools) -set(launcher_TS translation/launcher_en.ts translation/launcher_pl.ts translation/launcher_ru.ts translation/launcher_uk.ts) -qt5_add_translation( QM_FILES ${launcher_TS} ) if(WIN32) set(launcher_ICON VCMI_launcher.rc) @@ -98,7 +105,7 @@ endif() if(BUILD_SINGLE_APP) add_library(vcmilauncher STATIC ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS}) else() - add_executable(vcmilauncher WIN32 ${QM_FILES} ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS} ${launcher_ICON}) + add_executable(vcmilauncher WIN32 ${launcher_QM} ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS} ${launcher_ICON}) endif() if(WIN32) @@ -130,7 +137,7 @@ vcmi_set_output_dir(vcmilauncher "") enable_pch(vcmilauncher) if(APPLE_IOS) - set(ICONS_DESTINATION ${DATA_DIR}) + set(RESOURCES_DESTINATION ${DATA_DIR}) # workaround https://github.com/conan-io/conan-center-index/issues/13332 if(USING_CONAN) @@ -144,7 +151,7 @@ if(APPLE_IOS) ) endif() else() - set(ICONS_DESTINATION ${DATA_DIR}/launcher) + set(RESOURCES_DESTINATION ${DATA_DIR}/launcher) # Copy to build directory for easier debugging add_custom_command(TARGET vcmilauncher POST_BUILD @@ -161,4 +168,5 @@ else() endif() endif() -install(DIRECTORY icons DESTINATION ${ICONS_DESTINATION}) +install(DIRECTORY icons DESTINATION ${RESOURCES_DESTINATION}) +install(DIRECTORY ts DESTINATION ${RESOURCES_DESTINATION}) diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 5ec14e8a2..a88c3845f 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -129,7 +129,7 @@ void MainWindow::updateTranslation() { std::string languageCode = settings["general"]["language"].String(); - QString translationFile = "./launcher_" + QString::fromStdString(languageCode) + ".qm"; + QString translationFile = "./launcher/translations/" + QString::fromStdString(languageCode) + ".qm"; qApp->removeTranslator(&translator); if (!translator.load(translationFile)) diff --git a/launcher/modManager/cmodlist.cpp b/launcher/modManager/cmodlist.cpp index 0146c983c..fbee206c8 100644 --- a/launcher/modManager/cmodlist.cpp +++ b/launcher/modManager/cmodlist.cpp @@ -158,8 +158,7 @@ QString CModEntry::getName() const QVariant CModEntry::getValue(QString value) const { - QString lang = QString::fromStdString(settings["general"]["language"].String()); - QString langValue = "translation_" + lang; + QString langValue = QString::fromStdString(settings["general"]["language"].String()); // Priorities // 1) data from newest version From 4cd91ffbed839e22dafcac78c89edf8bdd674fa3 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 25 Dec 2022 23:06:22 +0200 Subject: [PATCH 006/197] Save & load language selection --- config/schemas/settings.json | 6 +++++- launcher/mainwindow_moc.cpp | 1 + launcher/settingsView/csettingsview_moc.cpp | 8 +++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 9baa638c6..34a721a61 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -17,7 +17,7 @@ "type" : "object", "default": {}, "additionalProperties" : false, - "required" : [ "playerName", "showfps", "music", "sound", "encoding", "swipe", "saveRandomMaps", "saveFrequency", "notifications", "extraDump" ], + "required" : [ "playerName", "showfps", "music", "sound", "encoding", "language", "swipe", "saveRandomMaps", "saveFrequency", "notifications", "extraDump" ], "properties" : { "playerName" : { "type":"string", @@ -51,6 +51,10 @@ "type":"string", "default" : "Maps/Arrogance" }, + "language" : { + "type":"string", + "default" : "english" + }, "lastSave" : { "type":"string", "default" : "NEWGAME" diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index a88c3845f..35591185e 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -51,6 +51,7 @@ MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent), ui(new Ui::MainWindow) { load(); // load FS before UI + updateTranslation(); // load translation ui->setupUi(this); diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 74d98a02f..73befdcad 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -114,6 +114,11 @@ void CSettingsView::loadSettings() if(encodingIndex < ui->comboBoxEncoding->count()) ui->comboBoxEncoding->setCurrentIndex((int)encodingIndex); ui->comboBoxAutoSave->setCurrentIndex(settings["general"]["saveFrequency"].Integer() > 0 ? 1 : 0); + + std::string language = settings["general"]["language"].String(); + size_t languageIndex = boost::range::find(languageTagList, language) - languageTagList; + if(languageIndex < ui->comboBoxLanguage->count()) + ui->comboBoxLanguage->setCurrentIndex((int)languageIndex); } void CSettingsView::fillValidResolutions(bool isExtraResolutionsModEnabled) @@ -319,7 +324,8 @@ void CSettingsView::on_comboBoxLanguage_currentIndexChanged(int index) Settings node = settings.write["general"]["language"]; node->String() = languageTagList[index]; - dynamic_cast(qApp->activeWindow())->updateTranslation(); + if ( qApp->activeWindow() && dynamic_cast(qApp->activeWindow()) ) + dynamic_cast(qApp->activeWindow())->updateTranslation(); } void CSettingsView::changeEvent(QEvent *event) From b38752bde1d65183570386a3f4b4655b70ae5957 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 25 Dec 2022 23:08:39 +0200 Subject: [PATCH 007/197] Editor can be started from Launcher (disabled for ios) --- launcher/main.cpp | 5 +++++ launcher/main.h | 1 + launcher/mainwindow_moc.cpp | 9 +++++++++ launcher/mainwindow_moc.h | 1 + lib/VCMIDirs.cpp | 4 ++++ lib/VCMIDirs.h | 7 +++++-- 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/launcher/main.cpp b/launcher/main.cpp index acc5f0cde..68064f70b 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -64,6 +64,11 @@ void startGame(const QStringList & args) #endif } +void startEditor(const QStringList & args) +{ + startExecutable(pathToQString(VCMIDirs::get().editorPath()), args); +} + #ifndef Q_OS_IOS void startExecutable(QString name, const QStringList & args) { diff --git a/launcher/main.h b/launcher/main.h index 371d68e7f..ee7f1ea3f 100644 --- a/launcher/main.h +++ b/launcher/main.h @@ -10,6 +10,7 @@ #pragma once void startGame(const QStringList & args); +void startEditor(const QStringList & args); #ifdef VCMI_IOS extern "C" void launchGame(int argc, char * argv[]); diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 35591185e..f9221cc1a 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -69,6 +69,10 @@ MainWindow::MainWindow(QWidget * parent) move(position); } +#ifdef Q_OS_IOS + ui->startEditorButton->hide(); +#endif + ui->tabListWidget->setCurrentIndex(0); ui->settingsView->isExtraResolutionsModEnabled = ui->modlistView->isExtraResolutionsModEnabled(); @@ -103,6 +107,11 @@ void MainWindow::on_startGameButton_clicked() startGame({}); } +void MainWindow::on_startEditorButton_clicked() +{ + startEditor({}); +} + const CModList & MainWindow::getModList() const { return ui->modlistView->getModList(); diff --git a/launcher/mainwindow_moc.h b/launcher/mainwindow_moc.h index 1ac2a85a8..9165431db 100644 --- a/launcher/mainwindow_moc.h +++ b/launcher/mainwindow_moc.h @@ -52,4 +52,5 @@ private slots: void on_modslistButton_clicked(); void on_settingsButton_clicked(); void on_lobbyButton_clicked(); + void on_startEditorButton_clicked(); }; diff --git a/lib/VCMIDirs.cpp b/lib/VCMIDirs.cpp index f56a6549b..a6031be1d 100644 --- a/lib/VCMIDirs.cpp +++ b/lib/VCMIDirs.cpp @@ -158,6 +158,7 @@ class VCMIDirsWIN32 final : public IVCMIDirs std::vector dataPaths() const override; bfs::path clientPath() const override; + bfs::path editorPath() const override; bfs::path serverPath() const override; bfs::path libraryPath() const override; @@ -348,6 +349,7 @@ std::vector VCMIDirsWIN32::dataPaths() const } bfs::path VCMIDirsWIN32::clientPath() const { return binaryPath() / "VCMI_client.exe"; } +bfs::path VCMIDirsWIN32::editorPath() const { return binaryPath() / "VCMI_editor.exe"; } bfs::path VCMIDirsWIN32::serverPath() const { return binaryPath() / "VCMI_server.exe"; } bfs::path VCMIDirsWIN32::libraryPath() const { return "."; } @@ -359,6 +361,7 @@ class IVCMIDirsUNIX : public IVCMIDirs { public: bfs::path clientPath() const override; + bfs::path editorPath() const override; bfs::path serverPath() const override; virtual bool developmentMode() const; @@ -371,6 +374,7 @@ bool IVCMIDirsUNIX::developmentMode() const } bfs::path IVCMIDirsUNIX::clientPath() const { return binaryPath() / "vcmiclient"; } +bfs::path IVCMIDirsUNIX::editorPath() const { return binaryPath() / "vcmieditor"; } bfs::path IVCMIDirsUNIX::serverPath() const { return binaryPath() / "vcmiserver"; } #ifdef VCMI_APPLE diff --git a/lib/VCMIDirs.h b/lib/VCMIDirs.h index 50b385f5d..33d38af73 100644 --- a/lib/VCMIDirs.h +++ b/lib/VCMIDirs.h @@ -35,10 +35,13 @@ public: // Paths to global system-wide data directories. First items have higher priority virtual std::vector dataPaths() const = 0; - // Full path to client executable, including server name (e.g. /usr/bin/vcmiclient) + // Full path to client executable, including name (e.g. /usr/bin/vcmiclient) virtual boost::filesystem::path clientPath() const = 0; - // Full path to server executable, including server name (e.g. /usr/bin/vcmiserver) + // Full path to editor executable, including name (e.g. /usr/bin/vcmieditor) + virtual boost::filesystem::path editorPath() const = 0; + + // Full path to server executable, including name (e.g. /usr/bin/vcmiserver) virtual boost::filesystem::path serverPath() const = 0; // Path where vcmi libraries can be found (in AI and Scripting subdirectories) From 20dec34484af1b907f112e14af1962b3967e02ba Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 25 Dec 2022 23:09:15 +0200 Subject: [PATCH 008/197] Attempt to make translations optional (MXE...) --- CMakeLists.txt | 9 +++++++-- launcher/CMakeLists.txt | 14 ++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 759095988..81debe3c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF) option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF) option(ENABLE_LAUNCHER "Enable compilation of launcher" ON) option(ENABLE_EDITOR "Enable compilation of map editor" ON) +option(ENABLE_TRANSLATIONS "Enable generation of translations for launcher and editor" ON) if(APPLE_IOS) set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix") set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen") @@ -316,8 +317,12 @@ find_package(TBB REQUIRED) if(ENABLE_LAUNCHER OR ENABLE_EDITOR) # Widgets finds its own dependencies (QtGui and QtCore). - find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network LinguistTools) - find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network LinguistTools) + find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network OPTIONAL_COMPONENTS LinguistTools) + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network OPTIONAL_COMPONENTS LinguistTools) + + if (NOT DEFINED Qt5LinguistTools_DIR) + set(ENABLE_TRANSLATIONS OFF) + endif() endif() if(ENABLE_LUA) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 6883e4e9a..7f9610f5d 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -90,11 +90,15 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) if(TARGET Qt6::Core) qt_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS}) - qt_add_translation( launcher_QM ${launcher_TS} ) + if (ENABLE_TRANSLATIONS) + qt_add_translation( launcher_QM ${launcher_TS} ) + endif() else() - set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/translations) qt5_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS}) - qt5_add_translation( launcher_QM ${launcher_TS} ) + if (ENABLE_TRANSLATIONS) + set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/translations) + qt5_add_translation( launcher_QM ${launcher_TS} ) + endif() endif() @@ -169,4 +173,6 @@ else() endif() install(DIRECTORY icons DESTINATION ${RESOURCES_DESTINATION}) -install(DIRECTORY ts DESTINATION ${RESOURCES_DESTINATION}) +if (ENABLE_TRANSLATIONS) + install(DIRECTORY translation DESTINATION ${RESOURCES_DESTINATION}) +endif() From 82f13701118130fcd89ca9104f8e6fdfd8f0c100 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 25 Dec 2022 23:23:44 +0200 Subject: [PATCH 009/197] Linguist tools are now optional --- CMakeLists.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f65ffa24..587e02029 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -329,10 +329,12 @@ endif() if(ENABLE_LAUNCHER OR ENABLE_EDITOR) # Widgets finds its own dependencies (QtGui and QtCore). - find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network OPTIONAL_COMPONENTS LinguistTools) - find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network OPTIONAL_COMPONENTS LinguistTools) + find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network ) + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network ) - if (NOT DEFINED Qt5LinguistTools_DIR) + find_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools ) + find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools) + if (NOT Qt5LinguistTools_DIR) set(ENABLE_TRANSLATIONS OFF) endif() endif() From 9fb426edc1dfed7a40968d890c0b4ad2124be6bd Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 25 Dec 2022 23:42:06 +0200 Subject: [PATCH 010/197] Do not use translator without translator tools --- CMakeLists.txt | 3 +++ launcher/mainwindow_moc.cpp | 2 ++ launcher/mainwindow_moc.h | 2 ++ 3 files changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 587e02029..d9945a1ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,6 +337,9 @@ if(ENABLE_LAUNCHER OR ENABLE_EDITOR) if (NOT Qt5LinguistTools_DIR) set(ENABLE_TRANSLATIONS OFF) endif() + if (ENABLE_TRANSLATIONS) + add_definitions(-DENABLE_QT_TRANSLATIONS) + endif() endif() if(ENABLE_NULLKILLER_AI) diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index f9221cc1a..d1a86d1d7 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -137,6 +137,7 @@ void MainWindow::on_lobbyButton_clicked() void MainWindow::updateTranslation() { +#ifdef ENABLE_QT_TRANSLATIONS std::string languageCode = settings["general"]["language"].String(); QString translationFile = "./launcher/translations/" + QString::fromStdString(languageCode) + ".qm"; @@ -146,4 +147,5 @@ void MainWindow::updateTranslation() logGlobal->error("Failed to load translation"); if (!qApp->installTranslator(&translator)) logGlobal->error("Failed to install translator"); +#endif } diff --git a/launcher/mainwindow_moc.h b/launcher/mainwindow_moc.h index 9165431db..f6733c504 100644 --- a/launcher/mainwindow_moc.h +++ b/launcher/mainwindow_moc.h @@ -26,7 +26,9 @@ class MainWindow : public QMainWindow { Q_OBJECT +#ifdef ENABLE_QT_TRANSLATIONS QTranslator translator; +#endif private: Ui::MainWindow * ui; void load(); From 9d2704adc37736ed6039125620e84d4d6442d33c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Dec 2022 00:03:00 +0200 Subject: [PATCH 011/197] Updated mod.json schema to include localization support --- config/schemas/mod.json | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/config/schemas/mod.json b/config/schemas/mod.json index 012c3f762..f77fdea91 100644 --- a/config/schemas/mod.json +++ b/config/schemas/mod.json @@ -4,7 +4,41 @@ "title" : "VCMI mod file format", "description" : "Format used to define main mod file (mod.json) in VCMI", "required" : [ "name", "description", "version", "author", "contact", "modType" ], + "definitions" : { + "localizeable" : { + "type":"object", + "additionalProperties" : false, + "required" : [ "name", "description", "author", "modType" ], + "properties":{ + "name": { + "type":"string", + "description": "Short name of your mod. No more than 2-3 words" + }, + "description": { + "type":"string", + "description": "More lengthy description of mod. No hard limit" + }, + "modType" : { + "type":"string", + "description": "Type of mod, e.g. Town, Artifacts, Graphical." + }, + "author" : { + "type":"string", + "description": "Author of the mod. Can be nickname, real name or name of team" + }, + "changelog" : { + "type":"object", + "description": "List of changes/new features in each version", + "additionalProperties" : { + "type" : "array", + "items" : { "type":"string" } + } + } + } + } + }, + "additionalProperties" : false, "properties":{ "name": { @@ -78,6 +112,26 @@ "type":"boolean", "description": "If set to true, mod will not be enabled automatically on install" }, + + "english" : { + "$ref" : "#/definitions/localizeable" + }, + + "german" : { + "$ref" : "#/definitions/localizeable" + }, + + "polish" : { + "$ref" : "#/definitions/localizeable" + }, + + "russian" : { + "$ref" : "#/definitions/localizeable" + }, + + "ukrainian" : { + "$ref" : "#/definitions/localizeable" + }, "artifacts": { "type":"array", From 1f0ad8e149eaddd7962a7e592add078ab1a0762e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Dec 2022 00:10:31 +0200 Subject: [PATCH 012/197] Regenerated translations --- launcher/translation/english.ts | 44 +++++++++++++++++++++++++++---- launcher/translation/german.ts | 44 +++++++++++++++++++++++++++---- launcher/translation/polish.ts | 44 +++++++++++++++++++++++++++---- launcher/translation/russian.ts | 44 +++++++++++++++++++++++++++---- launcher/translation/ukrainian.ts | 44 +++++++++++++++++++++++++++---- 5 files changed, 195 insertions(+), 25 deletions(-) diff --git a/launcher/translation/english.ts b/launcher/translation/english.ts index bd2e8fe44..e4d00b8b5 100644 --- a/launcher/translation/english.ts +++ b/launcher/translation/english.ts @@ -10,30 +10,35 @@ + Name + Type + Version + Size + Author @@ -169,6 +174,7 @@ + Mod name @@ -184,6 +190,7 @@ + Installed version @@ -199,6 +206,7 @@ + Latest version @@ -214,6 +222,7 @@ + Download size @@ -229,6 +238,7 @@ + Authors @@ -244,6 +254,7 @@ + License @@ -259,6 +270,7 @@ + Home @@ -285,6 +297,8 @@ + + Compatibility @@ -311,6 +325,8 @@ + + Required VCMI version @@ -326,6 +342,7 @@ + Supported VCMI version @@ -341,6 +358,7 @@ + Supported VCMI versions @@ -356,6 +374,7 @@ + Required mods @@ -371,6 +390,7 @@ + Conflicting mods @@ -386,6 +406,7 @@ + This mod can not be installed or enabled because following dependencies are not present @@ -401,6 +422,7 @@ + This mod can not be enabled because following mods are incompatible with this mod @@ -416,6 +438,7 @@ + This mod can not be disabled because it is required to run following mods @@ -431,6 +454,7 @@ + This mod can not be uninstalled or updated because it is required to run following mods @@ -446,6 +470,7 @@ + This is submod and it can not be installed or uninstalled separately from parent mod @@ -461,6 +486,7 @@ + Notes @@ -476,6 +502,7 @@ + Screenshot %1 @@ -485,6 +512,7 @@ + Mod is compatible @@ -494,6 +522,7 @@ + Mod is incompatible @@ -557,7 +586,7 @@ - + Neutral AI @@ -630,24 +659,29 @@ + Deutsch (German) + + + + Polska (Polish) - + Русский (Russian) - + Українська (Ukrainian) - + Friendly AI @@ -737,7 +771,7 @@ - + Enemy AI diff --git a/launcher/translation/german.ts b/launcher/translation/german.ts index 0dd3c5ffe..8e7ded6c3 100644 --- a/launcher/translation/german.ts +++ b/launcher/translation/german.ts @@ -10,30 +10,35 @@ + Name Name + Type Typ + Version Version + Size Größe + Author @@ -169,6 +174,7 @@ + Mod name Mod-Name @@ -184,6 +190,7 @@ + Installed version Installierte Version @@ -199,6 +206,7 @@ + Latest version Letzte Version @@ -214,6 +222,7 @@ + Download size Downloadgröße @@ -229,6 +238,7 @@ + Authors Autoren @@ -244,6 +254,7 @@ + License Lizenz @@ -259,6 +270,7 @@ + Home Home @@ -285,6 +297,8 @@ + + Compatibility Kompatibilität @@ -311,6 +325,8 @@ + + Required VCMI version Benötigte VCMI Version @@ -326,6 +342,7 @@ + Supported VCMI version Unterstützte VCMI Version @@ -341,6 +358,7 @@ + Supported VCMI versions Unterstützte VCMI Versionen @@ -356,6 +374,7 @@ + Required mods Benötigte Mods @@ -371,6 +390,7 @@ + Conflicting mods Mods mit Konflikt @@ -386,6 +406,7 @@ + This mod can not be installed or enabled because following dependencies are not present Diese Mod kann nicht installiert oder aktiviert werden, da die folgenden Abhängigkeiten nicht vorhanden sind @@ -401,6 +422,7 @@ + This mod can not be enabled because following mods are incompatible with this mod Diese Mod kann nicht aktiviert werden, da folgende Mods nicht mit dieser Mod kompatibel sind @@ -416,6 +438,7 @@ + This mod can not be disabled because it is required to run following mods Diese Mod kann nicht deaktiviert werden, da sie zum Ausführen der folgenden Mods erforderlich ist @@ -431,6 +454,7 @@ + This mod can not be uninstalled or updated because it is required to run following mods Diese Mod kann nicht deinstalliert oder aktualisiert werden, da sie für die folgenden Mods erforderlich ist @@ -446,6 +470,7 @@ + This is submod and it can not be installed or uninstalled separately from parent mod Dies ist eine Submod und kann nicht separat von der Hauptmod installiert oder deinstalliert werden @@ -461,6 +486,7 @@ + Notes Anmerkungen @@ -476,6 +502,7 @@ + Screenshot %1 Screenshot %1 @@ -485,6 +512,7 @@ + Mod is compatible Mod ist kompatibel @@ -494,6 +522,7 @@ + Mod is incompatible Mod ist inkompatibel @@ -563,7 +592,7 @@ Nach Aktualisierungen suchen - + Neutral AI Neutrale KI @@ -636,24 +665,29 @@ + Deutsch (German) + Deutsch + + + Polska (Polish) Polska (Polnisch) - + Русский (Russian) Русский (Russisch) - + Українська (Ukrainian) Українська (Ukrainisch) - + Friendly AI Freundliche KI @@ -743,7 +777,7 @@ Version des Builds - + Enemy AI Feindliche KI diff --git a/launcher/translation/polish.ts b/launcher/translation/polish.ts index 723608b37..2ef17dd5e 100644 --- a/launcher/translation/polish.ts +++ b/launcher/translation/polish.ts @@ -10,30 +10,35 @@ + Name + Type + Version + Size + Author @@ -169,6 +174,7 @@ + Mod name @@ -184,6 +190,7 @@ + Installed version @@ -199,6 +206,7 @@ + Latest version @@ -214,6 +222,7 @@ + Download size @@ -229,6 +238,7 @@ + Authors @@ -244,6 +254,7 @@ + License @@ -259,6 +270,7 @@ + Home @@ -285,6 +297,8 @@ + + Compatibility @@ -311,6 +325,8 @@ + + Required VCMI version @@ -326,6 +342,7 @@ + Supported VCMI version @@ -341,6 +358,7 @@ + Supported VCMI versions @@ -356,6 +374,7 @@ + Required mods @@ -371,6 +390,7 @@ + Conflicting mods @@ -386,6 +406,7 @@ + This mod can not be installed or enabled because following dependencies are not present @@ -401,6 +422,7 @@ + This mod can not be enabled because following mods are incompatible with this mod @@ -416,6 +438,7 @@ + This mod can not be disabled because it is required to run following mods @@ -431,6 +454,7 @@ + This mod can not be uninstalled or updated because it is required to run following mods @@ -446,6 +470,7 @@ + This is submod and it can not be installed or uninstalled separately from parent mod @@ -461,6 +486,7 @@ + Notes @@ -476,6 +502,7 @@ + Screenshot %1 @@ -485,6 +512,7 @@ + Mod is compatible @@ -494,6 +522,7 @@ + Mod is incompatible @@ -557,7 +586,7 @@ - + Neutral AI @@ -630,24 +659,29 @@ + Deutsch (German) + + + + Polska (Polish) Polska - + Русский (Russian) Русский (Rosyjski) - + Українська (Ukrainian) Українська (Ukraiński) - + Friendly AI @@ -737,7 +771,7 @@ - + Enemy AI diff --git a/launcher/translation/russian.ts b/launcher/translation/russian.ts index 8402c54ca..5bb6e94cd 100644 --- a/launcher/translation/russian.ts +++ b/launcher/translation/russian.ts @@ -10,30 +10,35 @@ + Name + Type + Version + Size + Author @@ -169,6 +174,7 @@ + Mod name @@ -184,6 +190,7 @@ + Installed version @@ -199,6 +206,7 @@ + Latest version @@ -214,6 +222,7 @@ + Download size @@ -229,6 +238,7 @@ + Authors @@ -244,6 +254,7 @@ + License @@ -259,6 +270,7 @@ + Home @@ -285,6 +297,8 @@ + + Compatibility @@ -311,6 +325,8 @@ + + Required VCMI version @@ -326,6 +342,7 @@ + Supported VCMI version @@ -341,6 +358,7 @@ + Supported VCMI versions @@ -356,6 +374,7 @@ + Required mods @@ -371,6 +390,7 @@ + Conflicting mods @@ -386,6 +406,7 @@ + This mod can not be installed or enabled because following dependencies are not present @@ -401,6 +422,7 @@ + This mod can not be enabled because following mods are incompatible with this mod @@ -416,6 +438,7 @@ + This mod can not be disabled because it is required to run following mods @@ -431,6 +454,7 @@ + This mod can not be uninstalled or updated because it is required to run following mods @@ -446,6 +470,7 @@ + This is submod and it can not be installed or uninstalled separately from parent mod @@ -461,6 +486,7 @@ + Notes @@ -476,6 +502,7 @@ + Screenshot %1 @@ -485,6 +512,7 @@ + Mod is compatible @@ -494,6 +522,7 @@ + Mod is incompatible @@ -557,7 +586,7 @@ - + Neutral AI @@ -630,24 +659,29 @@ + Deutsch (German) + Deutsch (Немецкий) + + + Polska (Polish) Polska (Польский) - + Русский (Russian) Русский - + Українська (Ukrainian) Українська (Украинский) - + Friendly AI @@ -737,7 +771,7 @@ - + Enemy AI diff --git a/launcher/translation/ukrainian.ts b/launcher/translation/ukrainian.ts index 84f955f8c..d9fdff7ce 100644 --- a/launcher/translation/ukrainian.ts +++ b/launcher/translation/ukrainian.ts @@ -10,30 +10,35 @@ + Name Назва + Type Тип + Version Версія + Size Розмір + Author @@ -169,6 +174,7 @@ + Mod name Назва модифікації @@ -184,6 +190,7 @@ + Installed version Встановлена версія @@ -199,6 +206,7 @@ + Latest version Найновіша версія @@ -214,6 +222,7 @@ + Download size Розмір для завантаження @@ -229,6 +238,7 @@ + Authors Автори @@ -244,6 +254,7 @@ + License Ліцензія @@ -259,6 +270,7 @@ + Home Домашня сторінка @@ -285,6 +297,8 @@ + + Compatibility Сумісність @@ -311,6 +325,8 @@ + + Required VCMI version Необхідна версія VCMI @@ -326,6 +342,7 @@ + Supported VCMI version Підтримувана версія VCMI @@ -341,6 +358,7 @@ + Supported VCMI versions Підтримувані версії VCMI @@ -356,6 +374,7 @@ + Required mods Необхідні модифікації @@ -371,6 +390,7 @@ + Conflicting mods Конфліктуючі модифікації @@ -386,6 +406,7 @@ + This mod can not be installed or enabled because following dependencies are not present Цю модифікацію не можна встановити чи активувати, оскільки відсутні наступні залежності @@ -401,6 +422,7 @@ + This mod can not be enabled because following mods are incompatible with this mod Цю модифікацію не можна ввімкнути, оскільки наступні модифікації несумісні з цією модифікацією @@ -416,6 +438,7 @@ + This mod can not be disabled because it is required to run following mods Цю модифікацію не можна відключити, оскільки вона необхідна для запуску наступних модифікацій @@ -431,6 +454,7 @@ + This mod can not be uninstalled or updated because it is required to run following mods Цю модифікацію не можна видалити або оновити, оскільки вона необхідна для запуску наступних модифікацій @@ -446,6 +470,7 @@ + This is submod and it can not be installed or uninstalled separately from parent mod Це вкладена модифікація, і її не можна встановити або видалити окремо від батьківської модифікації @@ -461,6 +486,7 @@ + Notes Примітки @@ -476,6 +502,7 @@ + Screenshot %1 Знімок екрану %1 @@ -485,6 +512,7 @@ + Mod is compatible Модифікація сумісна @@ -494,6 +522,7 @@ + Mod is incompatible Модифікація несумісна @@ -563,7 +592,7 @@ Оновити зараз - + Neutral AI Нейтральний ШІ @@ -636,24 +665,29 @@ + Deutsch (German) + Deutsch (Німецька) + + + Polska (Polish) Polska (Польська) - + Русский (Russian) Русский (Російська) - + Українська (Ukrainian) Українська - + Friendly AI Дружній ШІ @@ -743,7 +777,7 @@ Версія збірки - + Enemy AI Ворожий ШІ From e5cb322eb0209996b6455872a568f10661505ad9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Dec 2022 00:16:16 +0200 Subject: [PATCH 013/197] Add qt5 translation tools into Linux CI --- CI/linux/before_install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/linux/before_install.sh b/CI/linux/before_install.sh index ce49cc637..1b4d7fa3e 100644 --- a/CI/linux/before_install.sh +++ b/CI/linux/before_install.sh @@ -5,7 +5,7 @@ sudo apt-get update # Dependencies sudo apt-get install libboost-all-dev sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev -sudo apt-get install qtbase5-dev +sudo apt-get install qtbase5-dev sudo apt-get install ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev # Optional dependencies -sudo apt-get install libminizip-dev libfuzzylite-dev +sudo apt-get install libminizip-dev libfuzzylite-dev qttools5-dev From 5dea5fcd859e8565b924118d930a60175747cc9b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Dec 2022 00:22:20 +0200 Subject: [PATCH 014/197] Fix ios build --- launcher/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/main.cpp b/launcher/main.cpp index 68064f70b..21fee7634 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -66,7 +66,9 @@ void startGame(const QStringList & args) void startEditor(const QStringList & args) { +#ifndef Q_OS_IOS startExecutable(pathToQString(VCMIDirs::get().editorPath()), args); +#endif } #ifndef Q_OS_IOS From 1963f3c7f193d92f4d9dcb63c60aea817b21a264 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Dec 2022 13:12:22 +0200 Subject: [PATCH 015/197] MXE update test --- CI/mxe/before_install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/mxe/before_install.sh b/CI/mxe/before_install.sh index 102df868d..1f5d667ef 100644 --- a/CI/mxe/before_install.sh +++ b/CI/mxe/before_install.sh @@ -5,8 +5,8 @@ sudo add-apt-repository 'deb http://security.ubuntu.com/ubuntu bionic-security m sudo apt-get install -qq nsis ninja-build libssl1.0.0 # MXE repository was too slow for Travis far too often -wget -nv https://github.com/vcmi/vcmi-deps-mxe/releases/download/2021-02-20/mxe-i686-w64-mingw32.shared-2021-01-22.tar -tar -xvf mxe-i686-w64-mingw32.shared-2021-01-22.tar +wget -nv https://github.com/vcmi/vcmi-deps-mxe/releases/download/2022-12-26/mxe-i686-w64-mingw32.shared-2022-12-26.tar +tar -xvf mxe-i686-w64-mingw32.shared-2022-12-26.tar sudo dpkg -i mxe-*.deb sudo apt-get install -f --yes From e9c08612adab99d3345ebff18ca55e6c27bd23e7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Dec 2022 13:57:55 +0200 Subject: [PATCH 016/197] Added MXE update instructions --- CI/mxe/before_install.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CI/mxe/before_install.sh b/CI/mxe/before_install.sh index 1f5d667ef..d1a3b96d8 100644 --- a/CI/mxe/before_install.sh +++ b/CI/mxe/before_install.sh @@ -1,5 +1,14 @@ #!/bin/sh +# steps to upgrade MXE dependencies: +# 1) Use Debian/Ubuntu system or install one (virtual machines will work too) +# 2) update following script to include any new dependencies +# You can also run it to upgrade existing ones, but don't expect much +# MXE repository only provides ancient versions for the sake of "stability" +# https://github.com/vcmi/vcmi-deps-mxe/blob/master/mirror-mxe.sh +# 3) make release in vcmi-deps-mxe repository using resulting tar archive +# 4) update paths to tar archive in this script + # Install nsis for installer creation sudo add-apt-repository 'deb http://security.ubuntu.com/ubuntu bionic-security main' sudo apt-get install -qq nsis ninja-build libssl1.0.0 From bbb1d36bce9dad4d3e31601813fcac3e6349aa1b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 27 Dec 2022 12:28:05 +0200 Subject: [PATCH 017/197] Fix formatting --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d9945a1ca..4a91dedb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -334,10 +334,10 @@ if(ENABLE_LAUNCHER OR ENABLE_EDITOR) find_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools ) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools) - if (NOT Qt5LinguistTools_DIR) + if(NOT Qt5LinguistTools_DIR) set(ENABLE_TRANSLATIONS OFF) endif() - if (ENABLE_TRANSLATIONS) + if(ENABLE_TRANSLATIONS) add_definitions(-DENABLE_QT_TRANSLATIONS) endif() endif() From b6f13ea50c067f8e0f3b0bf5bfc5b78e5d7dc208 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 27 Dec 2022 12:28:40 +0200 Subject: [PATCH 018/197] Fix formatting --- launcher/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 93797558a..8aac6b418 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -90,12 +90,12 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) if(TARGET Qt6::Core) qt_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS}) - if (ENABLE_TRANSLATIONS) + if(ENABLE_TRANSLATIONS) qt_add_translation( launcher_QM ${launcher_TS} ) endif() else() qt5_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS}) - if (ENABLE_TRANSLATIONS) + if(ENABLE_TRANSLATIONS) set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/translations) qt5_add_translation( launcher_QM ${launcher_TS} ) endif() @@ -173,6 +173,6 @@ else() endif() install(DIRECTORY icons DESTINATION ${RESOURCES_DESTINATION}) -if (ENABLE_TRANSLATIONS) +if(ENABLE_TRANSLATIONS) install(DIRECTORY translation DESTINATION ${RESOURCES_DESTINATION}) endif() From 954ea9d5a9a86b79caa15ba9a17ac0c9d01dd3ac Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 28 Dec 2022 01:14:10 +0400 Subject: [PATCH 019/197] Implement lasso brush --- mapeditor/icons/brush-3.png | Bin 0 -> 1485 bytes mapeditor/mainwindow.cpp | 16 ++++++++++ mapeditor/mainwindow.h | 2 ++ mapeditor/mainwindow.ui | 8 +++-- mapeditor/mapview.cpp | 58 ++++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 mapeditor/icons/brush-3.png diff --git a/mapeditor/icons/brush-3.png b/mapeditor/icons/brush-3.png new file mode 100644 index 0000000000000000000000000000000000000000..19800c069ae5ce13875e18b5b7a04665709f4358 GIT binary patch literal 1485 zcmV;;1v2`HP)Hg1+lHrgWSWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6 zwD^Ni=!>T7nL9I?X}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8re zhoBb*p;u8ID_yBf0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J z`jH<$>RKN5V(7OqK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYv zwjAKwmYb0gKL(K8-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z> z!FI&AHCpoWI|RUqx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVT zrI(b06~u#xf1yS}_UGdMvD``!0~u->P=lA4?YN`hilQ z|3tHka)7T{2CGqwjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^ z7T9R1gAN8V6s;5)ieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2b zW$~+pTw@bIek?ZvKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L z_AC5qq~L$#SMj%U$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6= zb6>{xYV#Ue-+LB$7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re z4r3qYr~6#KE>;1F`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+ z5K}u-6REM(K@W$srgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5 zh^QEb$V`rCQ-|7ZS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX z2i^rZ^Mu;6+rb@?NPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV z0id6JRRdfL?*IS*IAvH#W=%~1DgXcg2mk?xX#fNO00031000^Q000000-yo_1ONa4 z0RR91AfN*P1ONa40RR91AOHXW0FO>U$p8QWD@jB_R9Fe^R9g zAT3pYI%z6R9NRJ4N_jtT0&fBjCva@g|2iBY|Hm1&S|o^H!X35N_JlLN&p4`yPv{WP zxM^mND6Y)LTY$;vh(;RLy8@1{K)iGwB-wL!H|EoBqk+Y~FkmGhK#RnuV48s@-eYg1 z*{E0Wp4+>(7yNSqqUpIj*$epxU?s*RhPVg6wLl2427|gOj#qG}IQdimJ<)(zA{lQn z@Vt~NE(H4v4`{t?&5Rw1W@l&sdsydr>~oEujQ22%RiEs>J)L-2h)BeY%BmDShe%GN z78Y?9Mv;z7NX*-5Q;5_0Zq57^;MD&L#;b81qn|xQkYMjE6R{Z9F9{=2 nbS81lKs2|d|ETzu4I+{sT-1)tsZ|@Y00000NkvXXu0mjfTsXM{ literal 0 HcmV?d00001 diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index b8408b0f5..4bdbcc98e 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -839,6 +839,22 @@ void MainWindow::on_toolArea_clicked(bool checked) ui->tabWidget->setCurrentIndex(0); } +void MainWindow::on_toolLasso_clicked(bool checked) +{ + ui->toolBrush->setChecked(false); + ui->toolBrush2->setChecked(false); + ui->toolBrush4->setChecked(false); + ui->toolArea->setChecked(false); + //ui->toolLasso->setChecked(false); + + if(checked) + ui->mapView->selectionTool = MapView::SelectionTool::Lasso; + else + ui->mapView->selectionTool = MapView::SelectionTool::None; + + ui->tabWidget->setCurrentIndex(0); +} + void MainWindow::on_actionErase_triggered() { on_toolErase_clicked(); diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index ccd410aa8..d367525bd 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -87,6 +87,8 @@ private slots: void on_toolBrush2_clicked(bool checked); void on_toolBrush4_clicked(bool checked); + + void on_toolLasso_clicked(bool checked); void on_inspectorWidget_itemChanged(QTableWidgetItem *item); diff --git a/mapeditor/mainwindow.ui b/mapeditor/mainwindow.ui index 766114052..6e7522956 100644 --- a/mapeditor/mainwindow.ui +++ b/mapeditor/mainwindow.ui @@ -642,7 +642,7 @@ - false + true @@ -663,7 +663,11 @@ - O + + + + + icons:brush-3.pngicons:brush-3.png true diff --git a/mapeditor/mapview.cpp b/mapeditor/mapview.cpp index e24a990cb..6d91481ae 100644 --- a/mapeditor/mapview.cpp +++ b/mapeditor/mapview.cpp @@ -155,6 +155,14 @@ void MapView::mouseMoveEvent(QMouseEvent *mouseEvent) } sc->selectionTerrainView.draw(); break; + + case MapView::SelectionTool::Lasso: + if(mouseEvent->buttons() == Qt::LeftButton) + { + sc->selectionTerrainView.select(tile); + sc->selectionTerrainView.draw(); + } + break; case MapView::SelectionTool::None: if(mouseEvent->buttons() & Qt::RightButton) @@ -247,6 +255,7 @@ void MapView::mousePressEvent(QMouseEvent *event) break; case MapView::SelectionTool::Area: + case MapView::SelectionTool::Lasso: if(event->button() == Qt::RightButton) break; @@ -329,6 +338,55 @@ void MapView::mouseReleaseEvent(QMouseEvent *event) switch(selectionTool) { + case MapView::SelectionTool::Lasso: { + if(event->button() == Qt::RightButton) + break; + + //key: y position of tile + //value.first: x position of left tile + //value.second: x postiion of right tile + std::map> selectionRangeMapX, selectionRangeMapY; + for(auto & t : sc->selectionTerrainView.selection()) + { + auto pairIter = selectionRangeMapX.find(t.y); + if(pairIter == selectionRangeMapX.end()) + selectionRangeMapX[t.y] = std::make_pair(t.x, t.x); + else + { + pairIter->second.first = std::min(pairIter->second.first, t.x); + pairIter->second.second = std::max(pairIter->second.second, t.x); + } + + pairIter = selectionRangeMapY.find(t.x); + if(pairIter == selectionRangeMapY.end()) + selectionRangeMapY[t.x] = std::make_pair(t.y, t.y); + else + { + pairIter->second.first = std::min(pairIter->second.first, t.y); + pairIter->second.second = std::max(pairIter->second.second, t.y); + } + } + + std::set selectionByX, selectionByY; + std::vector finalSelection; + for(auto & selectionRange : selectionRangeMapX) + { + for(int i = selectionRange.second.first; i < selectionRange.second.second; ++i) + selectionByX.insert(int3(i, selectionRange.first, sc->level)); + } + for(auto & selectionRange : selectionRangeMapY) + { + for(int i = selectionRange.second.first; i < selectionRange.second.second; ++i) + selectionByY.insert(int3(selectionRange.first, i, sc->level)); + } + std::set_intersection(selectionByX.begin(), selectionByX.end(), selectionByY.begin(), selectionByY.end(), std::back_inserter(finalSelection)); + for(auto & lassoTile : finalSelection) + sc->selectionTerrainView.select(lassoTile); + + sc->selectionTerrainView.draw(); + break; + } + case MapView::SelectionTool::None: if(event->button() == Qt::RightButton) break; From 2687b7af7f5acaa13f3cda9aa770565c4a4b701f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 12:28:59 +0200 Subject: [PATCH 020/197] Increased default width of lobby room creation window --- launcher/lobby/lobbyroomrequest_moc.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/lobby/lobbyroomrequest_moc.ui b/launcher/lobby/lobbyroomrequest_moc.ui index a05741970..5b8db9b7b 100644 --- a/launcher/lobby/lobbyroomrequest_moc.ui +++ b/launcher/lobby/lobbyroomrequest_moc.ui @@ -9,7 +9,7 @@ 0 0 - 193 + 227 188 From 60f25f7a0c7582bd3381089c30e69efb92e6e286 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 12:29:26 +0200 Subject: [PATCH 021/197] Added Polish translation by dydzio --- launcher/translation/polish.ts | 276 +++++++++++++++------------------ 1 file changed, 121 insertions(+), 155 deletions(-) diff --git a/launcher/translation/polish.ts b/launcher/translation/polish.ts index 2ef17dd5e..713da4449 100644 --- a/launcher/translation/polish.ts +++ b/launcher/translation/polish.ts @@ -7,42 +7,37 @@ ModFields - + Pola modów - Name - + Nazwa - Type - + Typ - Version - + Wersja - Size - + Rosmiar - Author - + Autor @@ -51,116 +46,116 @@ Filter - + Filtruj All mods - + Wszystkie mody Downloadable - + Dostępny do pobrania Installed - + Zainstalowany Updatable - + Można zaktualizować Active - + Aktywny Inactive - + Nieaktywny Download && refresh repositories - + Pobierz i odśwież repozytoria Description - + Opis Changelog - + Lista zmian Screenshots - + Zrzuty ekranu Show details - + Pokaż szczegóły Uninstall - + Odinstaluj Enable - + Włącz Disable - + Wyłącz Update - + Zaktualizuj Install - + Zainstaluj %p% (%v KB out of %m KB) - + %p% (%v KB z %m KB) Abort - + Przerwij @@ -174,9 +169,8 @@ - Mod name - + Nazwa moda @@ -190,9 +184,8 @@ - Installed version - + Zainstalowana wersja @@ -206,9 +199,8 @@ - Latest version - + Najnowsza wersja @@ -222,9 +214,8 @@ - Download size - + Rozmiar pobierania @@ -238,9 +229,8 @@ - Authors - + Autorzy @@ -254,9 +244,8 @@ - License - + Licencja @@ -270,9 +259,8 @@ - Home - + Źródło @@ -297,10 +285,8 @@ - - Compatibility - + Kompatybilność @@ -325,10 +311,8 @@ - - Required VCMI version - + Wymagana wersja VCMI @@ -342,9 +326,8 @@ - Supported VCMI version - + Wspierana wersja VCMI @@ -358,9 +341,8 @@ - Supported VCMI versions - + Wspierane wersje VCMI @@ -374,9 +356,8 @@ - Required mods - + Wymagane mody @@ -390,9 +371,8 @@ - Conflicting mods - + Konfliktujące mody @@ -406,9 +386,8 @@ - This mod can not be installed or enabled because following dependencies are not present - + Ten mod nie może zostać zainstalowany lub włączony ponieważ następujące zależności nie zostały spełnione @@ -422,9 +401,8 @@ - This mod can not be enabled because following mods are incompatible with this mod - + Ten mod nie może zostać włączony ponieważ następujące mody są z nim niekompatybilne @@ -438,9 +416,8 @@ - This mod can not be disabled because it is required to run following mods - + Ten mod nie może zostać wyłączony ponieważ jest wymagany by do uruchomienia następujących modów @@ -454,9 +431,8 @@ - This mod can not be uninstalled or updated because it is required to run following mods - + Ten mod nie może zostać odinstalowany lub zaktualizowany ponieważ jest wymagany do uruchomienia następujących modów @@ -470,9 +446,8 @@ - This is submod and it can not be installed or uninstalled separately from parent mod - + To jest moduł składowy innego moda i nie może być zainstalowany lub odinstalowany oddzielnie od moda nadrzędnego @@ -486,9 +461,8 @@ - Notes - + Uwagi @@ -502,9 +476,8 @@ - Screenshot %1 - + Zrzut ekranu %1 @@ -512,9 +485,8 @@ - Mod is compatible - + Mod jest kompatybilny @@ -522,9 +494,8 @@ - Mod is incompatible - + Mod jest niekompatybilny @@ -533,7 +504,7 @@ Change - + Zmień @@ -541,13 +512,13 @@ Open - + Otwórz User data directory - + Katalog danych użytkownika @@ -556,7 +527,7 @@ Off - + Włączony @@ -565,91 +536,91 @@ On - + Wyłączony Fullscreen - + Pełny ekran Repositories - + Repozytoria Check for updates - + Sprawdź czy są aktualizacje - + Neutral AI - + AI jednostek neutralnych Real - + Prawdziwy General - + Ogólne Player AI - + AI graczy VCMI Language - + Język VCMI Central European (Windows 1250) - + Środkowoeuropejski (Windows 1250) Cyrillic script (Windows 1251) - + Cyrylica (Windows 1251) Western European (Windows 1252) - + Wschodnioeuropejski (Windows 1252) Simplified Chinese (GBK) - + Uproszczony chiński (GBK) Simplified Chinese (GB2312) - + Uproszczony chiński (GB2312) Korean (Windows 949) - + Koreański (Windows 949) @@ -659,128 +630,123 @@ - Deutsch (German) - - - - Polska (Polish) Polska - + Русский (Russian) Русский (Rosyjski) - + Українська (Ukrainian) Українська (Ukraiński) - + Friendly AI - + AI sojuszników Resolution - + Rozdzielczość AI on the map - + AI na mapie przygody Autosave - + Autozapis Display index - + Numer wyświetlacza Check repositories on startup - + Sprawdź repozytoria przy starcie Network port - + Port sieciowy Data Directories - + Katalogi z danymi Video - + Obraz Heroes III character set - + Zestaw znaków w grze Extra data directory - + Katalog danych dodatkowych Log files directory - + Katalog logów Show intro - + Pokaż intro Launcher Settings - + Ustawienia Launchera Build version - + Wersja programu - + Enemy AI - + AI wrogich graczy AI in the battlefield - + AI na polu bitwy @@ -789,7 +755,7 @@ Image Viewer - + Wyświetlacz obrazków @@ -798,73 +764,73 @@ Connect - + Połącz Username - + Nazwa użytkownika Server - + Serwer Session - + Sesja Players - + Gracze New room - + Nowy pokój Join room - + Dołącz Ready - + Zgłoś gotowość Mods mismatch - + Niezgodność modów Leave - + Wyjdź Kick player - + Wyrzuć gracza Players in the room - + Gracze w pokoju @@ -873,25 +839,25 @@ Room settings - + Ustawienia pokoju Room name - + Nazwa pokoju Maximum players - + Maks. ilość graczy Password (optional) - + Hasło (opcjonalnie) @@ -900,48 +866,48 @@ VCMI Launcher - + VCMI Launcher (program startowy) Settings - + Ustawienia Start game - + Uruchom grę Lobby - + Lobby Mods - + Mody Editor - + Edytor Map Editor - + Edytor map Play - + Graj @@ -955,7 +921,7 @@ Name - + Nazwa @@ -966,7 +932,7 @@ Type - + Rodzaj @@ -977,7 +943,7 @@ Version - + Wersja @@ -988,7 +954,7 @@ Size - + Rozmiar @@ -999,7 +965,7 @@ Author - + Autor @@ -1008,19 +974,19 @@ You have latest version - + Posiadasz obecnie aktualną wersję Close - + Zamknij Check updates on startup - + Sprawdź aktualizacje przy uruchomieniu From 42ecf05c47cca48bc0e1c3bd355dd00f925abcc8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 12:40:39 +0200 Subject: [PATCH 022/197] Regenerated translations --- launcher/translation/english.ts | 422 ----------------------------- launcher/translation/german.ts | 422 ----------------------------- launcher/translation/polish.ts | 408 +--------------------------- launcher/translation/russian.ts | 422 ----------------------------- launcher/translation/ukrainian.ts | 426 +----------------------------- 5 files changed, 12 insertions(+), 2088 deletions(-) diff --git a/launcher/translation/english.ts b/launcher/translation/english.ts index e4d00b8b5..4dbc6d084 100644 --- a/launcher/translation/english.ts +++ b/launcher/translation/english.ts @@ -4,42 +4,26 @@ CModListModel - - - ModFields - - - - - Name - - Type - - Version - - Size - - Author @@ -48,480 +32,209 @@ CModListView - Filter - All mods - Downloadable - Installed - Updatable - Active - Inactive - Download && refresh repositories - Description - Changelog - Screenshots - Show details - Uninstall - Enable - Disable - Update - Install - %p% (%v KB out of %m KB) - Abort - - - - - - - - - - - Mod name - - - - - - - - - - - Installed version - - - - - - - - - - - Latest version - - - - - - - - - - - Download size - - - - - - - - - - - Authors - - - - - - - - - - - License - - - - - - - - - - - Home - - - - - - - - - - - - - - - - - - - - - - Compatibility - - - - - - - - - - - - - - - - - - - - - - Required VCMI version - - - - - - - - - - - Supported VCMI version - - - - - - - - - - - Supported VCMI versions - - - - - - - - - - - Required mods - - - - - - - - - - - Conflicting mods - - - - - - - - - - - This mod can not be installed or enabled because following dependencies are not present - - - - - - - - - - - This mod can not be enabled because following mods are incompatible with this mod - - - - - - - - - - - This mod can not be disabled because it is required to run following mods - - - - - - - - - - - This mod can not be uninstalled or updated because it is required to run following mods - - - - - - - - - - - This is submod and it can not be installed or uninstalled separately from parent mod - - - - - - - - - - - Notes - - - - - - - - - - - Screenshot %1 - - - - - Mod is compatible - - - - - Mod is incompatible @@ -530,7 +243,6 @@ CSettingsView - Change @@ -539,12 +251,10 @@ - Open - User data directory @@ -554,7 +264,6 @@ - Off @@ -563,96 +272,80 @@ - On - Fullscreen - Repositories - Check for updates - Neutral AI - Real - General - Player AI - VCMI Language - Central European (Windows 1250) - Cyrillic script (Windows 1251) - Western European (Windows 1252) - Simplified Chinese (GBK) - Simplified Chinese (GB2312) - Korean (Windows 949) - English @@ -664,120 +357,100 @@ - Polska (Polish) - Русский (Russian) - Українська (Ukrainian) - Friendly AI - Resolution - AI on the map - Autosave - Display index - Check repositories on startup - Network port - Data Directories - Video - Heroes III character set - Extra data directory - Log files directory - Show intro - Launcher Settings - Build version - Enemy AI - AI in the battlefield @@ -786,7 +459,6 @@ ImageViewer - Image Viewer @@ -795,73 +467,61 @@ Lobby - Connect - Username - Server - Session - Players - New room - Join room - Ready - Mods mismatch - Leave - Kick player - Players in the room @@ -870,25 +530,21 @@ LobbyRoomRequest - Room settings - Room name - Maximum players - Password (optional) @@ -897,127 +553,49 @@ MainWindow - VCMI Launcher - Settings - Start game - Lobby - Mods - Editor - - - - Map Editor - - - - - Play - - - - - ModFields - - - - - - - - - Name - - - - - - - - - - - Type - - - - - - - - - - - Version - - - - - - - - - - - Size - - - - - - - - - - - Author - - UpdateDialog - You have latest version - Close - Check updates on startup diff --git a/launcher/translation/german.ts b/launcher/translation/german.ts index 8e7ded6c3..fa27590e4 100644 --- a/launcher/translation/german.ts +++ b/launcher/translation/german.ts @@ -4,42 +4,26 @@ CModListModel - - - ModFields - - - - - Name Name - - Type Typ - - Version Version - - Size Größe - - Author Autor @@ -48,480 +32,209 @@ CModListView - Filter Filter - All mods Alle Mods - Downloadable Herunterladbar - Installed Installiert - Updatable Aktualisierbar - Active Aktiv - Inactive Inaktiv - Download && refresh repositories Repositories herunterladen && aktualisieren - Description Beschreibung - Changelog Änderungslog - Screenshots Screenshots - Show details Details anzeigen - Uninstall Deinstallieren - Enable Aktivieren - Disable Deaktivieren - Update Aktualisieren - Install Installieren - %p% (%v KB out of %m KB) %p% (%v КB von %m КB) - Abort Abbrechen - - - - - - - - - - - Mod name Mod-Name - - - - - - - - - - - Installed version Installierte Version - - - - - - - - - - - Latest version Letzte Version - - - - - - - - - - - Download size Downloadgröße - - - - - - - - - - - Authors Autoren - - - - - - - - - - - License Lizenz - - - - - - - - - - - Home Home - - - - - - - - - - - - - - - - - - - - - - Compatibility Kompatibilität - - - - - - - - - - - - - - - - - - - - - - Required VCMI version Benötigte VCMI Version - - - - - - - - - - - Supported VCMI version Unterstützte VCMI Version - - - - - - - - - - - Supported VCMI versions Unterstützte VCMI Versionen - - - - - - - - - - - Required mods Benötigte Mods - - - - - - - - - - - Conflicting mods Mods mit Konflikt - - - - - - - - - - - This mod can not be installed or enabled because following dependencies are not present Diese Mod kann nicht installiert oder aktiviert werden, da die folgenden Abhängigkeiten nicht vorhanden sind - - - - - - - - - - - This mod can not be enabled because following mods are incompatible with this mod Diese Mod kann nicht aktiviert werden, da folgende Mods nicht mit dieser Mod kompatibel sind - - - - - - - - - - - This mod can not be disabled because it is required to run following mods Diese Mod kann nicht deaktiviert werden, da sie zum Ausführen der folgenden Mods erforderlich ist - - - - - - - - - - - This mod can not be uninstalled or updated because it is required to run following mods Diese Mod kann nicht deinstalliert oder aktualisiert werden, da sie für die folgenden Mods erforderlich ist - - - - - - - - - - - This is submod and it can not be installed or uninstalled separately from parent mod Dies ist eine Submod und kann nicht separat von der Hauptmod installiert oder deinstalliert werden - - - - - - - - - - - Notes Anmerkungen - - - - - - - - - - - Screenshot %1 Screenshot %1 - - - - - Mod is compatible Mod ist kompatibel - - - - - Mod is incompatible Mod ist inkompatibel @@ -530,7 +243,6 @@ CSettingsView - Change Ändern @@ -539,12 +251,10 @@ - Open Öffnen - User data directory Verzeichnis der Benutzerdaten @@ -554,7 +264,6 @@ - Off Aus @@ -563,102 +272,85 @@ - On An - Fullscreen Vollbild - AI in the battlefield KI auf dem Schlachtfeld - Repositories Repositories - Check for updates Nach Aktualisierungen suchen - Neutral AI Neutrale KI - Real Vollständig - General Allgemein - Player AI Spieler-KI - VCMI Language VCMI-Sprache - Central European (Windows 1250) Mitteleuropäisch (Windows 1250) - Cyrillic script (Windows 1251) Kyrillische Schrift (Windows 1251) - Western European (Windows 1252) Westeuropäisch (Windows 1252) - Simplified Chinese (GBK) Vereinfachtes Chinesisch (GBK) - Simplified Chinese (GB2312) Vereinfachtes Chinesisch (GB2312) - Korean (Windows 949) Koreanisch (Windows 949) - English English (Englisch) @@ -670,115 +362,96 @@ - Polska (Polish) Polska (Polnisch) - Русский (Russian) Русский (Russisch) - Українська (Ukrainian) Українська (Ukrainisch) - Friendly AI Freundliche KI - Resolution Auflösung - AI on the map KI auf der Karte - Autosave Autospeichern - Display index Anzeige-Index - Check repositories on startup Repositories beim Start prüfen - Network port Netzwerk-Port - Data Directories Daten-Verzeichnisse - Video Video - Heroes III character set Heroes III Zeichensatz - Extra data directory Zusätzliches Daten-Verzeichnis - Log files directory Verzeichnis der Log-Dateien - Show intro Intro anzeigen - Launcher Settings Launcher-Einstellungen - Build version Version des Builds - Enemy AI Feindliche KI @@ -786,7 +459,6 @@ ImageViewer - Image Viewer Bildbetrachter @@ -795,73 +467,61 @@ Lobby - Connect Verbinden - Username Benutzername - Server Server - Session Sitzung - Players Spieler - New room Neuer Raum - Join room Raum beitreten - Ready Bereit - Mods mismatch Mods stimmen nicht überein - Leave Verlassen - Kick player Spieler kicken - Players in the room Spieler im Raum @@ -870,25 +530,21 @@ LobbyRoomRequest - Room settings Raumeinstellungen - Room name Raumname - Maximum players Maximale Spieler - Password (optional) Passwort (optional) @@ -897,127 +553,49 @@ MainWindow - VCMI Launcher VCMI Launcher - Mods Mods - Editor Editor - - - Map Editor - Karteneditor - - - Settings Einstellungen - Lobby Lobby - Start game Spiel starten - - - Play - Spielen - - - - ModFields - - - - - - - - - Name - Name - - - - - - - - - - Type - Typ - - - - - - - - - - Version - Version - - - - - - - - - - Size - Größe - - - - - - - - - - Author - Autor - UpdateDialog - You have latest version Sie haben die neueste Version - Close Beenden - Check updates on startup Nach Aktualisierungen beim Starten prüfen diff --git a/launcher/translation/polish.ts b/launcher/translation/polish.ts index 713da4449..4fedabb7b 100644 --- a/launcher/translation/polish.ts +++ b/launcher/translation/polish.ts @@ -4,37 +4,26 @@ CModListModel - - - ModFields - Pola modów - - - Name Nazwa - Type Typ - Version Wersja - Size Rosmiar - Author Autor @@ -43,456 +32,209 @@ CModListView - Filter Filtruj - All mods Wszystkie mody - Downloadable Dostępny do pobrania - Installed Zainstalowany - Updatable Można zaktualizować - Active Aktywny - Inactive Nieaktywny - Download && refresh repositories Pobierz i odśwież repozytoria - Description Opis - Changelog Lista zmian - Screenshots Zrzuty ekranu - Show details Pokaż szczegóły - Uninstall Odinstaluj - Enable Włącz - Disable Wyłącz - Update Zaktualizuj - Install Zainstaluj - %p% (%v KB out of %m KB) %p% (%v KB z %m KB) - Abort Przerwij - - - - - - - - - - Mod name Nazwa moda - - - - - - - - - - Installed version Zainstalowana wersja - - - - - - - - - - Latest version Najnowsza wersja - - - - - - - - - - Download size Rozmiar pobierania - - - - - - - - - - Authors Autorzy - - - - - - - - - - License Licencja - - - - - - - - - - Home Źródło - - - - - - - - - - - - - - - - - - - - Compatibility Kompatybilność - - - - - - - - - - - - - - - - - - - - Required VCMI version Wymagana wersja VCMI - - - - - - - - - - Supported VCMI version Wspierana wersja VCMI - - - - - - - - - - Supported VCMI versions Wspierane wersje VCMI - - - - - - - - - - Required mods Wymagane mody - - - - - - - - - - Conflicting mods Konfliktujące mody - - - - - - - - - - This mod can not be installed or enabled because following dependencies are not present Ten mod nie może zostać zainstalowany lub włączony ponieważ następujące zależności nie zostały spełnione - - - - - - - - - - This mod can not be enabled because following mods are incompatible with this mod Ten mod nie może zostać włączony ponieważ następujące mody są z nim niekompatybilne - - - - - - - - - - This mod can not be disabled because it is required to run following mods Ten mod nie może zostać wyłączony ponieważ jest wymagany by do uruchomienia następujących modów - - - - - - - - - - This mod can not be uninstalled or updated because it is required to run following mods Ten mod nie może zostać odinstalowany lub zaktualizowany ponieważ jest wymagany do uruchomienia następujących modów - - - - - - - - - - This is submod and it can not be installed or uninstalled separately from parent mod To jest moduł składowy innego moda i nie może być zainstalowany lub odinstalowany oddzielnie od moda nadrzędnego - - - - - - - - - - Notes Uwagi - - - - - - - - - - Screenshot %1 Zrzut ekranu %1 - - - - Mod is compatible Mod jest kompatybilny - - - - Mod is incompatible Mod jest niekompatybilny @@ -501,7 +243,6 @@ CSettingsView - Change Zmień @@ -510,12 +251,10 @@ - Open Otwórz - User data directory Katalog danych użytkownika @@ -525,7 +264,6 @@ - Off Włączony @@ -534,216 +272,185 @@ - On Wyłączony - Fullscreen Pełny ekran - Repositories Repozytoria - Check for updates Sprawdź czy są aktualizacje - - + Neutral AI AI jednostek neutralnych - Real Prawdziwy - General Ogólne - Player AI AI graczy - VCMI Language Język VCMI - Central European (Windows 1250) Środkowoeuropejski (Windows 1250) - Cyrillic script (Windows 1251) Cyrylica (Windows 1251) - Western European (Windows 1252) Wschodnioeuropejski (Windows 1252) - Simplified Chinese (GBK) Uproszczony chiński (GBK) - Simplified Chinese (GB2312) Uproszczony chiński (GB2312) - Korean (Windows 949) Koreański (Windows 949) - English English (Angielski) - + Deutsch (German) + Deutsch (Niemiecki) + + + Polska (Polish) Polska - - + Русский (Russian) Русский (Rosyjski) - - + Українська (Ukrainian) Українська (Ukraiński) - - + Friendly AI AI sojuszników - Resolution Rozdzielczość - AI on the map AI na mapie przygody - Autosave Autozapis - Display index Numer wyświetlacza - Check repositories on startup Sprawdź repozytoria przy starcie - Network port Port sieciowy - Data Directories Katalogi z danymi - Video Obraz - Heroes III character set Zestaw znaków w grze - Extra data directory Katalog danych dodatkowych - Log files directory Katalog logów - Show intro Pokaż intro - Launcher Settings Ustawienia Launchera - Build version Wersja programu - - + Enemy AI AI wrogich graczy - AI in the battlefield AI na polu bitwy @@ -752,7 +459,6 @@ ImageViewer - Image Viewer Wyświetlacz obrazków @@ -761,73 +467,61 @@ Lobby - Connect Połącz - Username Nazwa użytkownika - Server Serwer - Session Sesja - Players Gracze - New room Nowy pokój - Join room Dołącz - Ready Zgłoś gotowość - Mods mismatch Niezgodność modów - Leave Wyjdź - Kick player Wyrzuć gracza - Players in the room Gracze w pokoju @@ -836,25 +530,21 @@ LobbyRoomRequest - Room settings Ustawienia pokoju - Room name Nazwa pokoju - Maximum players Maks. ilość graczy - Password (optional) Hasło (opcjonalnie) @@ -863,127 +553,49 @@ MainWindow - VCMI Launcher VCMI Launcher (program startowy) - Settings Ustawienia - Start game Uruchom grę - Lobby Lobby - Mods Mody - Editor Edytor - - - - Map Editor - Edytor map - - - - Play - Graj - - - - ModFields - - - - - - - - - Name - Nazwa - - - - - - - - - - Type - Rodzaj - - - - - - - - - - Version - Wersja - - - - - - - - - - Size - Rozmiar - - - - - - - - - - Author - Autor - UpdateDialog - You have latest version Posiadasz obecnie aktualną wersję - Close Zamknij - Check updates on startup Sprawdź aktualizacje przy uruchomieniu diff --git a/launcher/translation/russian.ts b/launcher/translation/russian.ts index 5bb6e94cd..e855f9981 100644 --- a/launcher/translation/russian.ts +++ b/launcher/translation/russian.ts @@ -4,42 +4,26 @@ CModListModel - - - ModFields - - - - - Name - - Type - - Version - - Size - - Author @@ -48,480 +32,209 @@ CModListView - Filter - All mods - Downloadable - Installed - Updatable - Active - Inactive - Download && refresh repositories - Description - Changelog - Screenshots - Show details - Uninstall - Enable - Disable - Update - Install - %p% (%v KB out of %m KB) - Abort - - - - - - - - - - - Mod name - - - - - - - - - - - Installed version - - - - - - - - - - - Latest version - - - - - - - - - - - Download size - - - - - - - - - - - Authors - - - - - - - - - - - License - - - - - - - - - - - Home - - - - - - - - - - - - - - - - - - - - - - Compatibility - - - - - - - - - - - - - - - - - - - - - - Required VCMI version - - - - - - - - - - - Supported VCMI version - - - - - - - - - - - Supported VCMI versions - - - - - - - - - - - Required mods - - - - - - - - - - - Conflicting mods - - - - - - - - - - - This mod can not be installed or enabled because following dependencies are not present - - - - - - - - - - - This mod can not be enabled because following mods are incompatible with this mod - - - - - - - - - - - This mod can not be disabled because it is required to run following mods - - - - - - - - - - - This mod can not be uninstalled or updated because it is required to run following mods - - - - - - - - - - - This is submod and it can not be installed or uninstalled separately from parent mod - - - - - - - - - - - Notes - - - - - - - - - - - Screenshot %1 - - - - - Mod is compatible - - - - - Mod is incompatible @@ -530,7 +243,6 @@ CSettingsView - Change @@ -539,12 +251,10 @@ - Open - User data directory @@ -554,7 +264,6 @@ - Off @@ -563,96 +272,80 @@ - On - Fullscreen - Repositories - Check for updates - Neutral AI - Real - General - Player AI - VCMI Language - Central European (Windows 1250) - Cyrillic script (Windows 1251) - Western European (Windows 1252) - Simplified Chinese (GBK) - Simplified Chinese (GB2312) - Korean (Windows 949) - English English (Английский) @@ -664,120 +357,100 @@ - Polska (Polish) Polska (Польский) - Русский (Russian) Русский - Українська (Ukrainian) Українська (Украинский) - Friendly AI - Resolution - AI on the map - Autosave - Display index - Check repositories on startup - Network port - Data Directories - Video - Heroes III character set - Extra data directory - Log files directory - Show intro - Launcher Settings - Build version - Enemy AI - AI in the battlefield @@ -786,7 +459,6 @@ ImageViewer - Image Viewer @@ -795,73 +467,61 @@ Lobby - Connect - Username - Server - Session - Players - New room - Join room - Ready - Mods mismatch - Leave - Kick player - Players in the room @@ -870,25 +530,21 @@ LobbyRoomRequest - Room settings - Room name - Maximum players - Password (optional) @@ -897,127 +553,49 @@ MainWindow - VCMI Launcher - Settings - Start game - Lobby - Mods - Editor - - - - Map Editor - - - - - Play - - - - - ModFields - - - - - - - - - Name - - - - - - - - - - - Type - - - - - - - - - - - Version - - - - - - - - - - - Size - - - - - - - - - - - Author - - UpdateDialog - You have latest version - Close - Check updates on startup diff --git a/launcher/translation/ukrainian.ts b/launcher/translation/ukrainian.ts index d9fdff7ce..0ceb04e55 100644 --- a/launcher/translation/ukrainian.ts +++ b/launcher/translation/ukrainian.ts @@ -4,42 +4,26 @@ CModListModel - - - ModFields - - - - - Name Назва - - Type Тип - - Version Версія - - Size Розмір - - Author Автори @@ -48,480 +32,209 @@ CModListView - Filter Фільтр - All mods Усі модифікації - Downloadable Усі доступні - Installed Встановлені - Updatable Доступні оновлення - Active Активні - Inactive Неактивні - Download && refresh repositories Оновити репозиторії - Description Опис - Changelog Зміни - Screenshots Знімки - Show details Показати подробиці - Uninstall Видалити - Enable Активувати - Disable Деактивувати - Update Оновити - Install Встановити - %p% (%v KB out of %m KB) %p% (%v КБ з %m КБ) - Abort Відмінити - - - - - - - - - - - Mod name Назва модифікації - - - - - - - - - - - Installed version Встановлена версія - - - - - - - - - - - Latest version Найновіша версія - - - - - - - - - - - Download size Розмір для завантаження - - - - - - - - - - - Authors Автори - - - - - - - - - - - License Ліцензія - - - - - - - - - - - Home Домашня сторінка - - - - - - - - - - - - - - - - - - - - - - Compatibility Сумісність - - - - - - - - - - - - - - - - - - - - - - Required VCMI version Необхідна версія VCMI - - - - - - - - - - - Supported VCMI version Підтримувана версія VCMI - - - - - - - - - - - Supported VCMI versions Підтримувані версії VCMI - - - - - - - - - - - Required mods Необхідні модифікації - - - - - - - - - - - Conflicting mods Конфліктуючі модифікації - - - - - - - - - - - This mod can not be installed or enabled because following dependencies are not present Цю модифікацію не можна встановити чи активувати, оскільки відсутні наступні залежності - - - - - - - - - - - This mod can not be enabled because following mods are incompatible with this mod Цю модифікацію не можна ввімкнути, оскільки наступні модифікації несумісні з цією модифікацією - - - - - - - - - - - This mod can not be disabled because it is required to run following mods Цю модифікацію не можна відключити, оскільки вона необхідна для запуску наступних модифікацій - - - - - - - - - - - This mod can not be uninstalled or updated because it is required to run following mods Цю модифікацію не можна видалити або оновити, оскільки вона необхідна для запуску наступних модифікацій - - - - - - - - - - - This is submod and it can not be installed or uninstalled separately from parent mod Це вкладена модифікація, і її не можна встановити або видалити окремо від батьківської модифікації - - - - - - - - - - - Notes Примітки - - - - - - - - - - - Screenshot %1 Знімок екрану %1 - - - - - Mod is compatible Модифікація сумісна - - - - - Mod is incompatible Модифікація несумісна @@ -530,7 +243,6 @@ CSettingsView - Change Змінити @@ -539,12 +251,10 @@ - Open Відкрити - User data directory Тека даних користувача @@ -554,7 +264,6 @@ - Off Вимкнено @@ -563,102 +272,85 @@ - On Увімкнено - Fullscreen Повноекранний режим - AI in the battlefield Штучний інтелект на полі бою - Repositories Репозиторії - Check for updates Оновити зараз - Neutral AI Нейтральний ШІ - Real Повний - General Загальні налаштування - Player AI ШІ гравців - VCMI Language Мова VCMI - Central European (Windows 1250) Центральноєвропейська (Windows 1250) - Cyrillic script (Windows 1251) Кирилиця (Windows 1251) - Western European (Windows 1252) Західноєвропейська (Windows 1252) - Simplified Chinese (GBK) Спрощена китайська (GBK) - Simplified Chinese (GB2312) Спрощена китайська (GB2312) - Korean (Windows 949) Корейська (Windows 949) - English English (Англійська) @@ -670,115 +362,96 @@ - Polska (Polish) Polska (Польська) - Русский (Russian) Русский (Російська) - Українська (Ukrainian) Українська - Friendly AI Дружній ШІ - Resolution Роздільна здатність - AI on the map Штучний інтелект на карті пригод - Autosave Автозбереження - Display index Дісплей - Check repositories on startup Перевірка репозиторіїв при запуску - Network port Мережевий порт - Data Directories Теки даних гри - Video Графіка - Heroes III character set Кодування Heroes III - Extra data directory Додаткова тека даних - Log files directory Тека файлів журналу - Show intro Вступні відео - Launcher Settings Налаштування лаунчера - Build version Версія збірки - Enemy AI Ворожий ШІ @@ -786,7 +459,6 @@ ImageViewer - Image Viewer Перегляд зображень @@ -795,73 +467,61 @@ Lobby - Connect Підключитися - Username Ім'я користувача - Server Сервер - Session Сесія - Players Гравці - New room Створити кімнату - Join room Приєднатися до кімнати - Ready - Готовність! + Готовність - Mods mismatch Модифікації, що не збігаються - Leave Вийти з кімнати - Kick player Виключити гравця - Players in the room Гравці у кімнаті @@ -870,25 +530,21 @@ LobbyRoomRequest - Room settings Налаштування кімнати - Room name Назва кімнати - Maximum players Максимум гравців - Password (optional) Пароль (за бажанням) @@ -897,127 +553,49 @@ MainWindow - VCMI Launcher VCMI Launcher - Mods Модифікації - Editor Редактор - - - Map Editor - Редактор мап - - - Settings Налаштування - Lobby Лобі - Start game - Грати! - - - - Play - Грати! - - - - ModFields - - - - - - - - - Name - Назва - - - - - - - - - - Type - Тип - - - - - - - - - - Version - Версія - - - - - - - - - - Size - Розмір - - - - - - - - - - Author - Автори + Грати UpdateDialog - You have latest version У вас встановлена остання версія - Close Закрити - Check updates on startup Перевіряти наявність оновлень при запуску From 858fb97f42d75d6e383fa0330bb0ea95350b62c2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 17:56:22 +0200 Subject: [PATCH 023/197] Added -DENABLED_LAUNCHER and -DENABLED_EDITOR defines --- CMakeLists.txt | 2 ++ launcher/main.cpp | 16 ++++++++-------- launcher/mainwindow_moc.cpp | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a91dedb0..cbc87f20c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -474,9 +474,11 @@ if(NOT TARGET minizip::minizip) endif() if(ENABLE_LAUNCHER) + add_definitions(-DENABLE_LAUNCHER) add_subdirectory(launcher) endif() if(ENABLE_EDITOR) + add_definitions(-DENABLE_EDITOR) add_subdirectory(mapeditor) endif() add_subdirectory(client) diff --git a/launcher/main.cpp b/launcher/main.cpp index 21fee7634..dcd1f4cd8 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -63,14 +63,14 @@ void startGame(const QStringList & args) startExecutable(pathToQString(VCMIDirs::get().clientPath()), args); #endif } - -void startEditor(const QStringList & args) -{ -#ifndef Q_OS_IOS - startExecutable(pathToQString(VCMIDirs::get().editorPath()), args); -#endif -} - + +void startEditor(const QStringList & args) +{ +#ifdef ENABLE_EDITOR + startExecutable(pathToQString(VCMIDirs::get().mapEditorPath()), args); +#endif +} + #ifndef Q_OS_IOS void startExecutable(QString name, const QStringList & args) { diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index d1a86d1d7..32a5b305d 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -69,7 +69,7 @@ MainWindow::MainWindow(QWidget * parent) move(position); } -#ifdef Q_OS_IOS +#ifndef ENABLE_EDITOR ui->startEditorButton->hide(); #endif From f9575d66906a043e1aefd421671fb9d1bfb29ab7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 17:57:03 +0200 Subject: [PATCH 024/197] Added languages validation to settings schema --- config/schemas/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 34a721a61..dc52f17f4 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -53,6 +53,7 @@ }, "language" : { "type":"string", + "enum" : [ "english", "german", "polish", "russian", "ukrainian" ], "default" : "english" }, "lastSave" : { From cd6a894417b750055beafc36565c6aedf4e38e6a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 17:58:39 +0200 Subject: [PATCH 025/197] Call method from parent class after processing event --- launcher/lobby/lobby_moc.cpp | 1 + launcher/lobby/lobbyroomrequest_moc.cpp | 3 ++- launcher/mainwindow_moc.cpp | 1 + launcher/modManager/cmodlistview_moc.cpp | 1 + launcher/modManager/imageviewer_moc.cpp | 1 + launcher/settingsView/csettingsview_moc.cpp | 1 + 6 files changed, 7 insertions(+), 1 deletion(-) diff --git a/launcher/lobby/lobby_moc.cpp b/launcher/lobby/lobby_moc.cpp index 5dc57b79d..0122d6801 100644 --- a/launcher/lobby/lobby_moc.cpp +++ b/launcher/lobby/lobby_moc.cpp @@ -41,6 +41,7 @@ void Lobby::changeEvent(QEvent *event) { ui->retranslateUi(this); } + QWidget::changeEvent(event); } Lobby::~Lobby() diff --git a/launcher/lobby/lobbyroomrequest_moc.cpp b/launcher/lobby/lobbyroomrequest_moc.cpp index d8e10e524..f8feed9cd 100644 --- a/launcher/lobby/lobbyroomrequest_moc.cpp +++ b/launcher/lobby/lobbyroomrequest_moc.cpp @@ -29,10 +29,11 @@ LobbyRoomRequest::LobbyRoomRequest(SocketLobby & socket, const QString & room, c void LobbyRoomRequest::changeEvent(QEvent *event) { - if ( event->type() == QEvent::LanguageChange) + if (event->type() == QEvent::LanguageChange) { ui->retranslateUi(this); } + QDialog::changeEvent(event); } LobbyRoomRequest::~LobbyRoomRequest() diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 32a5b305d..52c464d05 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -90,6 +90,7 @@ void MainWindow::changeEvent(QEvent *event) { ui->retranslateUi(this); } + QMainWindow::changeEvent(event); } MainWindow::~MainWindow() diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index f977d3bac..28b547c72 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -41,6 +41,7 @@ void CModListView::changeEvent(QEvent *event) ui->retranslateUi(this); modModel->reloadRepositories(); } + QWidget::changeEvent(event); } void CModListView::setupFilterModel() diff --git a/launcher/modManager/imageviewer_moc.cpp b/launcher/modManager/imageviewer_moc.cpp index 107419073..e8a9b5814 100644 --- a/launcher/modManager/imageviewer_moc.cpp +++ b/launcher/modManager/imageviewer_moc.cpp @@ -26,6 +26,7 @@ void ImageViewer::changeEvent(QEvent *event) { ui->retranslateUi(this); } + QDialog::changeEvent(event); } ImageViewer::~ImageViewer() diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 73befdcad..a13b076eb 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -334,4 +334,5 @@ void CSettingsView::changeEvent(QEvent *event) { ui->retranslateUi(this); } + QWidget::changeEvent(event); } From 5f4a99843506d255b621f233fe31539e3a39a63d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 17:59:16 +0200 Subject: [PATCH 026/197] editorPath -> mapEditorPath --- launcher/main.cpp | 16 ++++++++-------- lib/VCMIDirs.cpp | 8 ++++---- lib/VCMIDirs.h | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/launcher/main.cpp b/launcher/main.cpp index dcd1f4cd8..e6b866724 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -63,14 +63,14 @@ void startGame(const QStringList & args) startExecutable(pathToQString(VCMIDirs::get().clientPath()), args); #endif } - -void startEditor(const QStringList & args) -{ -#ifdef ENABLE_EDITOR - startExecutable(pathToQString(VCMIDirs::get().mapEditorPath()), args); -#endif -} - + +void startEditor(const QStringList & args) +{ +#ifdef ENABLE_EDITOR + startExecutable(pathToQString(VCMIDirs::get().mapEditorPath()), args); +#endif +} + #ifndef Q_OS_IOS void startExecutable(QString name, const QStringList & args) { diff --git a/lib/VCMIDirs.cpp b/lib/VCMIDirs.cpp index a6031be1d..4a2501ee6 100644 --- a/lib/VCMIDirs.cpp +++ b/lib/VCMIDirs.cpp @@ -158,7 +158,7 @@ class VCMIDirsWIN32 final : public IVCMIDirs std::vector dataPaths() const override; bfs::path clientPath() const override; - bfs::path editorPath() const override; + bfs::path mapEditorPath() const override; bfs::path serverPath() const override; bfs::path libraryPath() const override; @@ -349,7 +349,7 @@ std::vector VCMIDirsWIN32::dataPaths() const } bfs::path VCMIDirsWIN32::clientPath() const { return binaryPath() / "VCMI_client.exe"; } -bfs::path VCMIDirsWIN32::editorPath() const { return binaryPath() / "VCMI_editor.exe"; } +bfs::path VCMIDirsWIN32::mapEditorPath() const { return binaryPath() / "VCMI_editor.exe"; } bfs::path VCMIDirsWIN32::serverPath() const { return binaryPath() / "VCMI_server.exe"; } bfs::path VCMIDirsWIN32::libraryPath() const { return "."; } @@ -361,7 +361,7 @@ class IVCMIDirsUNIX : public IVCMIDirs { public: bfs::path clientPath() const override; - bfs::path editorPath() const override; + bfs::path mapEditorPath() const override; bfs::path serverPath() const override; virtual bool developmentMode() const; @@ -374,7 +374,7 @@ bool IVCMIDirsUNIX::developmentMode() const } bfs::path IVCMIDirsUNIX::clientPath() const { return binaryPath() / "vcmiclient"; } -bfs::path IVCMIDirsUNIX::editorPath() const { return binaryPath() / "vcmieditor"; } +bfs::path IVCMIDirsUNIX::mapEditorPath() const { return binaryPath() / "vcmieditor"; } bfs::path IVCMIDirsUNIX::serverPath() const { return binaryPath() / "vcmiserver"; } #ifdef VCMI_APPLE diff --git a/lib/VCMIDirs.h b/lib/VCMIDirs.h index 33d38af73..bf35b4c0b 100644 --- a/lib/VCMIDirs.h +++ b/lib/VCMIDirs.h @@ -39,7 +39,7 @@ public: virtual boost::filesystem::path clientPath() const = 0; // Full path to editor executable, including name (e.g. /usr/bin/vcmieditor) - virtual boost::filesystem::path editorPath() const = 0; + virtual boost::filesystem::path mapEditorPath() const = 0; // Full path to server executable, including name (e.g. /usr/bin/vcmiserver) virtual boost::filesystem::path serverPath() const = 0; From df3d62c5bc62fe8e1d2c2c156d9a51f2ef1dba51 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 18:04:27 +0200 Subject: [PATCH 027/197] Formatting fix --- launcher/modManager/cmodlistmodel_moc.cpp | 1 - launcher/settingsView/csettingsview_moc.cpp | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/launcher/modManager/cmodlistmodel_moc.cpp b/launcher/modManager/cmodlistmodel_moc.cpp index 59e325241..7e9622f26 100644 --- a/launcher/modManager/cmodlistmodel_moc.cpp +++ b/launcher/modManager/cmodlistmodel_moc.cpp @@ -163,7 +163,6 @@ QVariant CModListModel::headerData(int section, Qt::Orientation orientation, int void CModListModel::reloadRepositories() { - //emit headerDataChanged(Qt::Horizontal, 0, -1 ); beginResetModel(); endResetModel(); } diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index a13b076eb..58723bdad 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -87,7 +87,6 @@ void CSettingsView::loadSettings() ui->comboBoxFullScreen->setDisabled(true); #else ui->comboBoxFullScreen->setCurrentIndex(settings["video"]["fullscreen"].Bool()); - //ui->checkBoxFullScreen->setChecked(settings["video"]["realFullscreen"].Bool()); #endif ui->comboBoxFriendlyAI->setCurrentText(QString::fromStdString(settings["server"]["friendlyAI"].String())); @@ -209,8 +208,8 @@ void CSettingsView::on_comboBoxFullScreen_currentIndexChanged(int index) { Settings nodeFullscreen = settings.write["video"]["fullscreen"]; Settings nodeRealFullscreen = settings.write["video"]["realFullscreen"]; - nodeFullscreen->Bool() = index != 0; - nodeFullscreen->Bool() = index == 2; + nodeFullscreen->Bool() = (index != 0); + nodeRealFullscreen->Bool() = (index == 2); } void CSettingsView::on_comboBoxAutoCheck_currentIndexChanged(int index) From d11abdcdef0e04f435d1915da380e14586edeb7f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 22:19:45 +0200 Subject: [PATCH 028/197] Updated icons with larger versions for scaling --- launcher/icons/menu-game.png | Bin 5482 -> 47461 bytes launcher/icons/menu-lobby.png | Bin 7352 -> 46771 bytes launcher/icons/menu-mods.png | Bin 2986 -> 17265 bytes launcher/icons/menu-settings.png | Bin 4474 -> 27531 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/launcher/icons/menu-game.png b/launcher/icons/menu-game.png index 5f632e2b71bab48cd3c01d300a8146267c7c193a..6d2c83cbed60ab1e550173f29db3aeafdef73c3a 100644 GIT binary patch literal 47461 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 +/// Class functionality to be used after extracting original H3 resources and before moving those resources to SoD Mod +/// Splits def files containing individual images, so that faction resources are independent. (TwCrPort, CPRSMALL, FlagPort, ITPA, ITPt, Un32 and Un44) +/// (town creature portraits, hero army creature portraits, adventure map dwellings, small town icons, big town icons, +/// hero speciality small icons, hero speciality large icons) +/// Converts all PCX images to PNG +/// class ResourceConverter { @@ -44,14 +44,15 @@ public: private: - /** Converts all .pcx from extractedFolder/Images into .png */ + // Converts all .pcx from extractedFolder/Images into .png static void doConvertPcxToPng(const bfs::path & sourceFolder, bool deleteOriginals); - /** splits a .def file into individual images and converts the output to PNG format */ + // splits a .def file into individual images and converts the output to PNG format static void splitDefFile(const std::string & fileName, const bfs::path & sourceFolder, bool deleteOriginals); - /** Splits the given .def files into individual images. - * For each .def file, the resulting images will be output in the same folder, in a subfolder (named just like the .def file) - */ + /// + /// Splits the given .def files into individual images. + /// For each .def file, the resulting images will be output in the same folder, in a subfolder (named just like the .def file) + /// static void splitDefFiles(const std::vector & defFileNames, const bfs::path & sourceFolder, bool deleteOriginals); }; From f5a7f5173f41038988f28e62664fade33e74dc69 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 18:14:10 +0200 Subject: [PATCH 157/197] Cursor type is now configurable in Launcher --- config/schemas/settings.json | 9 +++---- launcher/settingsView/csettingsview_moc.cpp | 20 ++++++++++++++++ launcher/settingsView/csettingsview_moc.h | 2 ++ launcher/settingsView/csettingsview_moc.ui | 26 +++++++++++++++++++++ 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/config/schemas/settings.json b/config/schemas/settings.json index bbe6133dc..d3472c3ab 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -82,7 +82,7 @@ "type" : "object", "additionalProperties" : false, "default": {}, - "required" : [ "screenRes", "bitsPerPixel", "fullscreen", "realFullscreen", "softwareCursor", "spellbookAnimation", "driver", "showIntro", "displayIndex" ], + "required" : [ "screenRes", "bitsPerPixel", "fullscreen", "realFullscreen", "cursor", "spellbookAnimation", "driver", "showIntro", "displayIndex" ], "properties" : { "screenRes" : { "type" : "object", @@ -106,9 +106,10 @@ "type" : "boolean", "default" : false }, - "softwareCursor" : { - "type" : "boolean", - "default" : false + "cursor" : { + "type" : "string", + "enum" : [ "auto", "hardware", "software" ], + "default" : "auto" }, "showIntro" : { "type" : "boolean", diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 43dc7e2a5..902992081 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -58,6 +58,13 @@ static const std::string languageTagList[] = "ukrainian", }; +static const std::string cursorTypesList[] = +{ + "auto", + "hardware", + "software" +}; + void CSettingsView::setDisplayList() { QStringList list; @@ -89,6 +96,8 @@ void CSettingsView::loadSettings() ui->comboBoxFullScreen->setDisabled(true); #else ui->comboBoxFullScreen->setCurrentIndex(settings["video"]["fullscreen"].Bool()); + if (settings["video"]["realFullscreen"].Bool()) + ui->comboBoxFullScreen->setCurrentIndex(2); #endif ui->comboBoxFriendlyAI->setCurrentText(QString::fromStdString(settings["server"]["friendlyAI"].String())); @@ -120,6 +129,10 @@ void CSettingsView::loadSettings() size_t languageIndex = boost::range::find(languageTagList, language) - languageTagList; if(languageIndex < ui->comboBoxLanguage->count()) ui->comboBoxLanguage->setCurrentIndex((int)languageIndex); + + std::string cursorType = settings["video"]["cursor"].String(); + size_t cursorTypeIndex = boost::range::find(cursorTypesList, cursorType) - cursorTypesList; + ui->comboBoxCursorType->setCurrentIndex((int)cursorTypeIndex); } void CSettingsView::fillValidResolutions(bool isExtraResolutionsModEnabled) @@ -339,3 +352,10 @@ void CSettingsView::changeEvent(QEvent *event) } QWidget::changeEvent(event); } + +void CSettingsView::on_comboBoxCursorType_currentIndexChanged(int index) +{ + Settings node = settings.write["video"]["cursor"]; + node->String() = cursorTypesList[index]; +} + diff --git a/launcher/settingsView/csettingsview_moc.h b/launcher/settingsView/csettingsview_moc.h index 1d54b3ffa..987143924 100644 --- a/launcher/settingsView/csettingsview_moc.h +++ b/launcher/settingsView/csettingsview_moc.h @@ -71,6 +71,8 @@ private slots: void on_comboBoxLanguage_currentIndexChanged(int index); + void on_comboBoxCursorType_currentIndexChanged(int index); + private: Ui::CSettingsView * ui; diff --git a/launcher/settingsView/csettingsview_moc.ui b/launcher/settingsView/csettingsview_moc.ui index 232526a26..73c7226f8 100644 --- a/launcher/settingsView/csettingsview_moc.ui +++ b/launcher/settingsView/csettingsview_moc.ui @@ -638,6 +638,32 @@

YN)JPev4QM9hVAz=wR+*y6h+NM*P3#`H=o=OkisRz5Aq+=- zX!=9E_kgb)kPE8e!fPTk+J?ikYw+Rc9r)zY1a0z*=Tf`K$@t36F00b*CQ2l~ug6xA zvppJSOCVF4L_L&TYdWO0Wc!x+0Mh+5*JaLKy5C5pvp8a+9gjyA+b6rE!2`?@zS0$j z_CFy~1mY+fvnPJ>elMqfdmfZ@oQ?`|0){jcN*E*Q&sqTCC77H6(ol#2v>V-(*Ng7z zOQ+LPxp{pV`+wzVKyU@=uS!hEN_PtricuM5S-LbK!lG1@ikxHA1t>=i?S zZd$PQv`$-L*h>53IkoD|Mu@6o9aXZ3@6C-cD7i!y$JajJL}e9gE`X#^ZqGXH6a;IB z;yov5ul7f16SkGPziaDgPqKgzCh;bmT&yGUkKy<4y)zf4yX=d?i~w(<-QR_S-E|0M zU29oNf(s)@4KF;r`ojpu#1xYmNGYp{euW%i>Fnoavzp!g_8{WaW$H&rngBS6&yaTL zFy3I7?`8H|R`2)n`DsdH22v~*Gzdhbx%g-XFh~e#0cdu_vp985 zqIMdk3_#`2f7yII6Yckw``0epdhdm60Lu`qRFw%p`F+(4fDXezbizq+u(#KE2opn^hb)yf)wg;`UZLjaZ^_@NFgcKtm zAhAXz2STL`K);dU&|8wX2QTe^m$rS*;^GO%w6)!7B}XRl2>Pmlho*~+q6SZnK7)@R zegNYYvh^0ddZChcH!F2 zF5J0skDZgQYER5KLJe-lQhmlc7wvPS9zwGjvNT=L8jF5&+|gw-$D5cVF5NE>BJ@`g9y<^2FQ+UJ~O9mivRAe6}VhMQA(ENPmJ8YlHo z*vYe1hM-JyS`SiGT2m7s(vYD}AYvwvXmcmGdyH~PjWp^=G!2v#Ggeoh_eIG(^sKd7 z2g2rr+j_~w=NNrz_mXtPfBV8`2(Ct>$wZX)th)*`P?=MUrJ7tNIOOcN()%sM8JaO| zxO33K=lBk4D3g@w($bG{ji{vg>G2nE-aF-awUJ@$@7;v!y9dx}cG4DK^k28rgqzoV z=!R$buQR>hj9*X;w=RwidPe7!g#;;^fyn*=nI#4>FiNbk0hCVvjiC6%a`Ac*!|~M8 zqE>O)5d5Fp2e29g5TUME1F~1w(omMcZ)I>uQU{3vbX(2(%{>Wgoz2=T-bK1<;Sz_) z=r_Dxe7t3vwQcX#wU^_+e*I0q6QTr6&TxT8xalJ#&XC)gf^eDht zWdxmFXC@OiTkNEK9#nu>5_2cEsS&nRMOezy zK*f1*5vLwpLB1!YElX!AFJS;CZDHYh(J}L@HGs>8{r+sJz>6S^+)!P9HvTSd>B;54lhK5FOJ8H8s5Kyb9@O8 z;2v`EmdQbPaNlj4D#tfqlTU2&jCS&rnK^{IYfhEWO-dV$7PQ-47~#RT5MAp*zOKEB zt)Du;QmwF!(w}k(x@gx^LCS4>4(fd{1h?9eiHe?Ywr5O*WT_KyX(mGD^IGB$%sRIW zQP&I|&6xP<%R==~q9b`W#&B(GgBdaHw}<;py#~$)`!E>Zg@=zn)@zMZ?zGq7*1;?A zt-HI(+#lofJjLh5{Z4Kgem^NKwJ<||_%^!UXz*3wUJXoWnK~L#hF=P2JSbH`;{5lOFW?XI$_!cj#A+ z>gX)F^5+FZ{3vzBCa(Jy9(wwzk==c741srn4E>x^W$;@kXp#(2dOs`dcOMyC2krVD z-1m0s*bR|+?rd(u!S)66)cY`-RqZ#kNE~|uwIYJ^*5i9LJ~9YToKQ_EuN5&Rce6bc z36N6c?SIJkn$RiCx|O+a3SFjzSvLp&MJ7P?dRZqx&16P?*{`em08~YYj;@dK`kwvL zJAeGg3sM7K-gPH3bPWy`hD z2JOR#58j8Pvt!dl)F}uSPA#(a4LGD~3!apmSMe-H39F5E;vye(_ACaz%6FU*h5HnJ z0F>LW(|f-G-FC-Fttq%wG9&>#)N&}QLZ7AY(08BbRsFnzRe#^7_1=NA{yrS{){#+Z z3Eb_>U}vojn_UkX!Bf`ONLrWfck?#hr=@d5oFpO7(@9X%sA9hb`Ea_-d{AqIv%w|| zMs;4lPB?|lP8&@HBwCo70{w7Rh0hV6mp5Q0S&hhvA+|H+GH{B`H{;2e*W8kI4h51H-p>z+pQbB9yDuF;EA3mWlwYQ zG$N1ElG;gm1DH$(aykv&EWrTPSv8%G(e4^3hzBB`^|T29G7a0YDvHu2QQIg5ufwC` zb$E0*gWdq8037q@D27Wl^v9>qP@+D8@pO_~TO!cJX|hpFE^#+k7J9viwBijMBQI;A zWm{bdku{vm9monPP1$Hv*h4S!pcjjgiZ8bqh`tL8n9C|oz|!YsP!6(ZI;dsr!STf| zeE1OH^Fy8UaN`;(hPS9fGG+(ZcqB~l%oZ@ zwNjF)-xQnRsY_26Fz?y z!#{uUE=u~NRK+6(zy%(7k#v;tEKhKpeUi*&i@C7UX=qHsT$!rxdU$j2)}{c})2inj z&SP5UJkRE7o>bLGg*}wpUo1zSx7+jOz-1z2)&A0|@_9*%3D~@fQ39XzTJXVx&*8lf z{=jdK?tK@wHg3SpYg_o4r@E@JV!ubx6ef|0=Nk<`=UqgA`wgu?LD_!$-0}@I5M=nL z{Wg5`@EP3yt&hyiPvrV;)BPZPRnk7dU2B6yneg! z%8fg5qB^)n4eh0OJ1p}co+%&YZt>WZohnXuu4GHf9S&_TTP~cf5u<5--gkstOXcL-mG%8Y`@d-)f%e)0gn9{JbL;W?_syqh1YK%;AhsV$9MOu z!U$`aS+k!F#J|*5i9q;SJgQBt-~?hSP?f>_N|Rs9R#%s>GMsuackan!#Tmz@`jh8Zk%<)9XJyZE&4?xSmLi|@% zJ+DclRW)93V!qm+Vzrj3dC%vcT&U$Zzw-N?0Ze6m0Ij#(tJrUM{3bHVfK!b*m!I6s zQ|kDu->Vwm?w3}{6{N}hMJJ#s4zhDQ)(l|RS*ipa+}g7Ba(w_w!dLRTY9^qBtZAVr z5I;Tme>J=JQ@pmyZqyDtexug#0>7aZKjoPX6>}TPm3o|#>sd$zX|#a4E#Q_iyYk-$ zQ)*02D;u0L+6r4-JmNN&w7uUf(GeKMPOAo;Mm?)JuSjrFj#@m=fQnMmO)WOL0-nhi zNF4*F5uWQOz$^6iO;!OVT}AS@k@%bb6*K@<{k+1$TZw=xCL8re`aSwR2q;1@H8mol zlz{Di7Y!e+jxXA8cYGi4Y;mfwH6bWfn9cx*xMKYI{c;(QYbPk=-g!h zI3AtPK$z}(W$WdJRn_uDps$PpR20z8nE@__KOL+8hS&eZ>zXE1zW!6{1nh43VYB7= zc>0xDC0l{zs$@D=o&)P}8Guof=hg63IX9UYBw%YuPKk;!xV<2+RkC+y4PlO@$?PkTC+~ga=ib z;Z~MOBIfhru95*(^}NFDBmGXqM`1i$eoVj9-voK!&0f!ce{R3$j$ieDXX7($TM%&Q zP=j}RTpvOy#N5WaVtgo?0LAw4Ce<7qrqJv4&P@8RyG#kr-&YIHNAdjpC1?O;;lMHz zz-?%F0=n>hH|O}^GD<$=oGU3!Z}mzOfF***Q2_G(19xze#s?tMs>Av38%QS#+6dhu zoU-9O)7y?)522Lb+uhZjT`T|uOPX1VFjNUTq8!YF((%oEX0?Rl1F&@ky3)-YLC^Nb zHq1+iQt_bFS9D@ldS2TmTN&IuKizYUs3Z;47A)9rPZPOm2c{*Z-EVKferG@)R+v1bVv*Iv+sZ-zVbIRkPECmw4Gd%J!SQ8Gy&(B7H!b%_iEXb=cb6q#E5^6ggw|}yX#l47 z!2V665GkV!7-uCBKThRrD&2&HtmE$`UvwL~HrTiD96o@;^Lon7qp#{PV4H`kIg`)s zH|;6yErn+7i+_==?e^Ed$)F#$Ium^hk%EI&+ovo$ny(FD+kBq3~e!oPz+H0sS%`)Jmi z$XH1tOz{1g`MxSK0Z57_sY*%!4s_WA(QF_CATwa^8n+m?8UTZNaClHuXUnKXUU8FI zCLJaKA|C}Xcu>N#X~vGV>FMe@WpD_GvpAPPl%^8{EW(sq_#%hmr5M1H=dJQ|XZtO= z5J#DPs@!kfQ%Wl)%`DYgYLwAO@5TI?vi)YuPFmv67=YXDccuM;d=+5P1hA*!=Eq0-`56Vbg!*=4UY8zX{3kbt+DQN+?1Vv$`G^EDYqsU-wpdU!zP!@Ql;K!4?fe;0Gbau zkEVUD;HdHeEO(0Kr=-B{^xm=W_+nH@rc&8`J*UjlC-6Ij?6-~~DX z<$^h6YpC~xq1+1JxBIQQSZEz?9^7H<1A5W#?J(i@=rhIr*59|lE;6T82uJ#tr);1qmbW#v@IM@N+mVA*;V^D6$*Gyo_lNo87qSH%F(?iIBl5Y!B`Q49RZ*fyKU zKr*$hM5-WaSl+SM4V(4XZf=CF##;eyvVXAKt*^I-o|LCBONIud!KwEL-5`Sg)@*8N z!l9rPVK&nM!dA#czq7Lg6w+mQCwjjYf`|r-qX1H;3s*a}3YdeUmYSh~(Ekljw>M#JBeHezE8$gHkVE+Jm7d=z*iC3K%z)+*Mz=HmU6*gtwCeFK2 z)3PEPn^XCA@9YLNRHcNxi9R^4c0~~*5vySwqJz{tr7><_r1z3d{ zs3p(qbP+d_IJ3dY?^0t#A41XXaM6zEtJ3`jaRl8~0JrzoU}s~^F167oWo|Pu$jX;Dc4|FRHEe}=TIc?4$ZBxx@!Fad{28)RQ-plI{ z)}KK%J?2WwTycyS;%BM(YIR?LD}@59o>!V&4ik3Gb@gGUTIXE-di^FtxcN!1biV=4 z@R=ks?jh`Mgl2#-uE;qT5moJX;IHw$6>OVoKWURK(`X7a_lhu}biqIhc>r!c2E9&C zPL2!oVM_~@gCnV|Lvd*$(ABl@FVzHaX`rYDzyd8GR}(Ocr|PVK7N49?r|X^TVNp1??Ul+=6 zBF(iy`Fqfx?8C({W*`na0GZ8<$Fbc`H2^XPwzjv~8K_ZSQCfvv6q^@r$M87@ASvuN0u(RHSD7ufQ?+d)oAWI~V{pImsvAkEOL(3A#<4fK)Z4@7r1t0v@9D++&>MGgtPglh+DDoL zWhQD655oXTqDE4cH=jT<*$ zI2dY26@3ov>&S)i7A0zNMQ*%}@p9@SwJVP%d??-Yil-ODIqB*e^49LR#819uiiuNh zeUcCW<`IlW5AkmgAdd5hKmo1|J#VdcjaB7EeE}{V2CVV{5cZ;{nI}0YiQ&wm89Vbn z{rp4dw7bx4w;-PF;N050-vO%blquMvh)h5Pb_`4Q8=~2D+}CY1jqA_V78$nZ>1fFKXAZ6wJmCV|7~-PVmWND(j>QEm+HWPckob4u z@U#t&pG}b9^RN@dAfFz6!DEthH>lOQ4kVy1-73RdT0wO*o5(PU4HQ_ihuXs?`QQ^E!x`N3a!Y}jVNYF zC{rc^Hww`0cC+Rncvz_M=lU8U+4N1@u@=-?)RgFSZhdu1X~O8nI{uum8&TTV0a8$_ zuqj6&2%UF48O?hI3&=E}90a1YOR;7GL|B3wp!AUm^eIo0$5HI*mRVe#J>>}!9W}I! zxEfM-xhX#>tKtEGlMbAJUQ@Q~>98xUB^Tu28zhY>ndix~&r92>1AL#5o?){y!nq$V z-|uJ*iGK`#_~SdJ?a)!k=n6ok8GsI#n%)UgR?mN#%*Eqms{dSJ*e@i*DJZxS=Ek$P z%TVc8)wZEzhU&}G0G4s8q9))L?+{whtE?|2u^No}bd^S;EndLQ>vv)$I*O2ebe(v? zYs(;%_#S>{95s-sJe2Q$`lr~hdE+;teNE90DmyQl@c z_ujj@7oC?tE$qzc|01mnlc{ZAlqd{Zkwi!k10>om?CtErD>v_;ME__W5f`1 z*x$PWw+~*Uy~JHzyx$T!$N*|^c)Vht1)K+IBHEEEQ$UZZC`NA03)?Xq4bP_;tdjNo z4;B*t3K~EK1f?#Vtza~|6xHGunKlxJumS;F9Rsc4S*$Dz%R||{5N>rU3BvlFCb_L z#+bKgK`wf#36HVIhU&km-oI8Ct!7)0c5!iW9_{Sxgtn)fyHYQ|{4)Ib$3KC$-uh7* z5s*eQ5GwGkTQ}J>7z_rSrdtnN8g@Ia4G=(POS4Y+{A&=oV7*)upgI?z z*d9-X^<(g2DBmLUce!9$4&{00~X2q=B6ZGO_f=kWr3Pxp!}#S6&(Mh}82o}!Gn zPP@wt0QYIKySp3y>}Nk?FFz&xH1P_X6dSLZgdjiF2CVH!sv>Ct)PDTkci#mn_2vYk zK=-P*w!g;MM=QhM#rxi35R5pcuvV=5({Xrfs&NkCf^FhXa{}Ggx=C2khkdzMUO5Z^ zZTe(t`X5~27-x80uvio91+Oz>@i;C{pd4K!kOSliyC?}m?I*C=hbGL(#Yj%)i!?)V zkvLkE3as34p`2p?9dqgi4*Ghh)j?)z>-Do*{i^+Dh!^}UeTTm5GJxX!rSQ`F4&{d4 zmLxLefk2F_KaQsc`%gLE|8o=OR!$xOr*tk_ubhs*SObu-N)w(dXam6uBK|6eU{(Ks z#HdVT^Kaswd&Nvaix-gE*RsTBODn@9ueb21ZHR6ha&~q`IPD6FS8Q%>G0}7TFYY%_ z#Nith*$nT`CIFEZK+%Bv_wQ>o8Tt-NoH&}8gP_A&rx#<`=x(EQ{}wWW9h{IB1`7Ik z#|y@vm-Lg-XOB6??J0slYj7`n+lz4Z5S%m#H9B!7v8c8L|1H+>ew(;r>nJ2q8$l+( zKgAxL54Pa+yoYl>hgi-sFNUd)5Y`K2TcL~U^Q6?`S^%!@T+o)g6y}3VqBUMX>Y-n- z-_}qU-uxPhg8DzF(R9{IIF4std~Dnv`-~Q`tfg@goz6ka(N5TEIk17fExS5*(N|&tB>rkjCpweAM|ryZT|E(LjNtCv;WWDcLvFIUFn_s zUUyH9n1GxU2@oJfa!JVqMNv}i%1Y&O*m9L!yj+}u>QDKl5JYMk|nQQ z(n^e?BuW;8nL!GoD3DwdOaMqk5=0tckS2C|*YDi)&b{xx?w*+jFj`K7m)-rQ`}GU= zoco>cq%ooZyj*^*-=lFA8K+Zg^`A2jkZaQFUk?Si(SfvRh*koXtmTHLj(P((xbD0l zX*aO_wZ$0fwKBNx88b7C;&kwqf1LDfFg{XJEc4p zXGOUw3+A@9!tkOl=Vej6(q7NT_Cd{`$ zd3wqq0Bp~NWv^Yo_CVh-Y}>R=bhPbk?-sG@lI@L*Uwh8t&tz#V+&2W>V&zBSfAifp zjhj*1vB+1TPaM5h7?7mZwrGlo--wzg6Gs(YNPJw3bKMnsC z%4=XgF|wHJb1#)l#~8h^jjQ;U>5NsH*PFi-0n{)7sx^?nyiovI^#Zao1*rn4Kvul~ zRVkhuA3G-(Fxkcg2Owg;u^EF=`{i;uI(X>N=<*dShGZomT@ffy8Hs^V2qt+0e=(5p z0NBqB-GLY>I6giB{r&xjC}Jg0fNB{VS4@fFd!Vly{o2bHuZrPxhp^GXsbk-q2L{Fz z#BY?!g&5+WfFsA=6On?xaAN&hS0!o*lfSuWZxTDcB3x?==0r3cO`B$OO9BLl&0E<-@R$&olJR$M&qXuU)%#9Zv*4 zv=o3O8=3i=K?gV#XMiXG6~)@MYb6TE)z_P%9zNC^EOdxU#5$F} zo`9fqSD1 z>Jw-h7_%huPYZurK7=LRZP3$J5D7x)Z8g^<(2RVdMDQBr1b>2p7ec-JfuG4MqSbYC zGEapyk+~Ru7QfSs{a3?6O2ME~$FSr=E~vl%_%nE}0v*QRCGcO*>oMiUnkGuX2xa_x zyy5w41$x!x0Gi4JG@Ao(i9x925uj0m6_<#^dI5~JN4eZ!wY_~AmW~kFZb`m7-qd)) zw=auV<$(x48*5sO)hH{HQOu0-@$vGTZ@zKj6QB6Ria@dc97v>EkVLEDYN#Ux^<%|~ z70}buBg%%aS+iOM2_1X82hN{9p;ip(u6p-)k{;eJYTy@Qp}|C>L1=4RCO(VFIvkqB z0f_VO6#K^Cz}L9SwYSP{z*>Gc4E2&SBdQaTZnMQ@u@Ol`ap|29g){8l1v*qZR&5Z3 zi^MgrbwBVk_*oe%!5f$~T$L$n&i>0n0Te9+5SjfC)ZKrGE?u#IVGc&IDOq&p_<5oL zimog*GJnlnX(XaKs+&dv0RFRpg$_K zv{wfTg|MUjWPAZ9of{Ufvhl?Owz|TCLKK^6k?J5^CFjSVe)^dYZoBQa6--6JF;iqC zIZUQ<_L&NV(a;iCj?J4l!^zc6)OsRx}w(D=FtlNB)TxTe9+yAioqD7?Ms#oJnXb znu@aC7((~Mz@k>Dbw8@n3eiwiG+T9gV*5A%{EXaxPprh|wtpVvAi^%ib^@w2%i{eP z3O(`uTWan<;w#1EK*BT@{*qJqr%2$x9tE)Chw9JYWU*KR{e3q;ch|;RCO{)7fM0C{ zP%k_0P43vUw3QE08E$K;lq*Nb6JU1xB|=M{Gwm} z@|VZI_O-87Iy&+>Q-+(mYPckzsC}GN4`Q%w+ctRh)mKfMss5#_;q3bdp*%fpRf+XI zh6g_p-(=76&)p4R&VORM07p;k^3q|t2B8l6;UZ8+H`@7*b5-<_uTeh4RSX3&sfr=504-B-&(4@f4rWjUexYCH*o{u z)ue*}G9h{9{8f*ZmVP1pSkb6K1<*7TfMvT<*xVMUV!ulO>ItVrRJ9NJ} zH40++l{e@mym0s5f8Rgav17-Yoazju2|;b=Ne)1_8&j;X3Jk*MAO7%%*aX7Vm^*dg zZ8-bEArqap_ro(#s#Kdxr|?;_JL-We|Hj4rvSz*(1eQeAfr_H!<~$>n&(Gn|=tPWS zBFBT^8mum>&Al=7hKY}Vcu2Ji94Ue@HJbUr{kL`Xii*zT6BFWma_T-es*MF;ba<AdUll-1$@W^4vrE(MS!tXUobtqgvpwCkBU>|gI2Ng7N#I;yv$88A6HQ3OqX@g!SK z*uf@PBT))mx22Asw_bOhSs8GSclIuV_U=C8Nm$(13CsF1{5NcZ5~!^R>aYAEEa~ll z;qEpS>Z>~t1?Kn#EEBLdG?9c)3gf*9@ShfX^JD&-(Sscx(j>w3nl|&hMaG2Fu-k7>|?c z#vmNq9*?}fSohv@&nG^0=chK(m0YimG$PPCf8`oNX#9&`{2UG(IAGjGrw{Fikt6%e zdqFtF&r`(;j7?3$c(EMYlO+MAo8w{in*~^YtD_|^MrS;5C1j?jAh&$_T(9DgZa5mL zNJ!a!D^hj+BbX{xVZ2lp&r{{9bv``h!;%q&7-ZBEzrc}WwbO#j@$QPMT^T;tGYwGS zalZw%|2$S-W+7Z`>9tfO25+GPzy!YOsVPwvX*wQN4`~Y|7!2b-T3cGCPn{qL6^t|6`9me(Lnd$fe=op>FhV1tMZhiHBPK1EmCj*uDDy>(;In zrNgJDB{FR7=z_i_tKh<^4^%hmq^*~zFeBJD8G^6@Wx zb2ZO17jtoDp0n54Yp)+HI4hKRl}Z(`RgD+M&2ew^)|8xK)duIs>^r+1Jtko}$8Ao? z3eR`k^QD$4Iz(2S+<8fjA`{xYH=%IU@mYmYUG?L63j8U-=1tWH6F?pJ-1i=3oZ=UZ zptjgaGBWuzx}TER>Bid9dpkW^N-X~l;+R_vc@?PK7x+QwR-owi93 z8$%`ehRWcdiX(wojyw8AZhzt1AOWahTo9#2OjU z?^9^!)CaTa7c2^ju?fUkx;cw>Ul`CE<5=Ygwb0Cbn;I=n16^V*Jv?1-ceRV2!PSTH zcIVk_4zVT2qaS3`3p31-QP9E~=!Vc&NtUOmFT+4%Q9B?`m50_R@nWvG#98T|wLuH% z3iBa3Sg5Q>=Xj$vMr>9Y%IK)FF|X=0#JV8#>pzvq!YHGk^0i5MjzfAZUNkh+0!83g zB^Ngs(6w2KMDFXuBZ&O*X!O@8N>=)J;KLH;d+OUve)4LEFGIqFY+Qk!N>EyG#W zKv*L@eY zC>TLgl$Do+K7gGzD=6yvt`hrx7FTPd$;>dyc-%l711>)Q-WCy(RkvzxPXOP8XOH*W zCakt#^1wwXp1Y-?vY0UL&fgdMu7A1`a++%#*%;-Y|BkuxmkXwll!jaLf`o;I#s$;O z)Csc}{dlK{?8eL-n!j4%=i<=Xr^CT8cx%kWq+VXZURUvvCjU%8t}w|*Wsw*D7LMg| z^Y3EwdpsCpsLGc#alT;Zd5dW#Rw}nO$0unzj?&vrYzPb%riCl;iC?)|JjN-T<x>_v)OUitiK@gjl0JukjyNH8ZV2C|4B%q|f-211_3;!F3q?B| z(olh|btGRZ7KluwC>plK*`cwyaKHN`c}2^a9!n+wY>cjqxNq*l?2cz4OgaW$3p-QG zpIo*%{*GoU^&i?^6fg2Q;TpoIdPBqF0=E!SCw%4t_=38nwM04XG>8H&{A{GA;_Zpb z;P2qI_efEeVj+#HfKf}lkD{p&Xb#5j-qJ(CklZ$GSkjK_XIf~C5#m)-{*_s3(eLP~ zriBITRq;a%l8$$S6y3{NVER!9Cb{9HLJ{}%Bz|@!y3>Y@xksb^TZizoj^9!x)JyYu zYT>5%#EnGGndQ(D%ryDPPU}|U=|6y)nB83b@5UGlj_IhpoQP8StteUeN>w5Z&D6Y3pV z#s>WwWqhLp_H0Dq2LTfTB+;hDC<8>7h+_&vEkXhYl;7y1S3H)JB_JL}MWaZl6Wa)#(y|h0pt&qDlyCIF%=Sj#zJDo&wu@jQM0m26Bx@4TEf2P6oTLLXAiVV z8St(j5su+cNc19}_akjl6|V*cKa49{)%jZq_OB?JSpV`2g z`2fzcW?T4gY~RgvgSSG|iUnSm2&EOcx|zt|w(`_p%9|lpOJYqEO`ksSd|v_xu9IP{ zmb_N%F6^v`e0P;>^mi!4`Hfw{;Idzml$1P%i8aLw9u17|$X9UYQ8L(UF%KC}^K%93 z0#ww$L%?yx9kt#sMuPyl%<0)#tmjR{({Ekm-;m=?k<8QOWHSMX{-c%l+%mOwDWR~> zn?ozeK59d>KC6AQiB2lURMI+XIr{^)17pkMcjB~I=%jZ-J`y7!h&%Ta^xdA)%lJGg zPn90@EK`4aoceFd+pDc+x%{xv*9;w(WnY+^b8ZQ#Q{?wmei1Lv?MMLoIu4_ z?y{vo#>zpjDRuGdkMsit`d*)8Msa=<**IL`%kffi#+33C%6WEhQ0{%ag*UGm_Fvls zIX{n~*2VSs#w-6eJs`iuT*&)6@9zL+-1bT&?Q+K*WUIC~9uo}{Kvc=}$Ti()yXfN` z%5WFsz}LCll)>T(C7w)153Jc76DTBZvtGj`kP-L6+S7@Nca|SHO8twA0dN`oVLiuOHL7~Fq4ATvD_P{jKO}LJCbw&$d zq#e4djhVD2RXa8fUpAbXhYnNg02RxHhDu%DtrYeSueI!3Yz}WM(B-={2MDX z{hVfNaWb9sj9g62VaR^*?)}>OIngEv9M?A+_LZtbkj`p%GU+u({()Hw>YOa|&O03g z{|18f=m;6CJy4m|st-wHw*j8{!iYDRY3h|-L+vDr#4y!_OYkVXbK2jCxC3^<&olq& zZ)wcUVBORgfWM7f-87ejuy-C4)?$cYWEOQ|sV zPcb*Ggccv+q=2{3;64&-eywNmz8D;g(ZZ)$vDmQf;PsK1UE#}Kzjt?+&U;?#ad}zP z`UWJVT6&G`4iu!z-Cnr+OEgNa@BK;6EX! zT_YBYfU=YfKB3Z{9;J3^rJ?p}$Vw@m?iM?YHDve4=pBA1Ev5dyy1NM$b4+{P{6RvzkD z19aZx<>z4wrzm`Uf=^=&bAa}?8G%se81s%%j6&lXPc_H5xr&bQs=rHjM*|^0y52D- zMLg=v77k!&ooziINC640kfBV--p7Fjs6?tK5NE!Rkt_Q@KX$wjLX2TRS&Jk9tmR-; ze{pT--fTTdG=iN966p&fcovzK<#&H{=v~#c>~Z1u+4(Lm=B>*WojIL?-ccvjbt13K z%g|Y43&~fJDB{uSg&wGXMgy}`FoBTE9KiV?=D34%&GjdaOs{4dQXlqOg;v~qpU(Di zb!j>|L~4h@)Ly2~BvG9NIo0g8sg4`7?{8^4lqA05!#zDM;(La_*OvX|e#=t->E)eM0ceB3EZm-|JK?I z@E)8}UqlD2Jk$#>b!y+-5c(|7^y;~izX+HS1zZ)0Zsz>5w)s-&VqXoo>cZ#1se-W< zeFelHz6LeB0NU@l`Ysm=Foc8mZyn`jahcmcR;9cm!U~IgTc+0NP_pXgQOq>gYG0}a zD0!4v$cXfv*`*0vzeyI8#pKL1VO~N!Pid4APWn@zqdQWsNoTGBvB46#NzfcW#O*0m zDA|c-g9;j0oM4>%dF_8&6f)}n`EsS-xhHxY_B}5HRY@jGJV}?`;&+4!4f^@>r?tnJ zh>}!9*%kx-jYi#l)%{1=?`M&0Ozu)4gR*=Kq)491@;IfeAG!n>wR;!y%&MshCvy*X zD_u5#3(TDf#Am!{ZPX%WcZ%7!w1^*mz#I&ew>#?vwK!p&amUy%V$`;Y!NBZT6t&G@ zZC5B>3(EK=Lq@};D8oo=B~OLHTpy4e(^O?e+<1LtI{Z2PfEU9rD(b!EMa~RptCZNz zzy^7j=Z4W)XBwa2U5j2B^Evu+FogEB$h`j@9Kw6%8kV1z;i7F9_+rd~`R>W6;JP^ZQ)3x_z>n0qFkiodme-j6TQ6woU?bJ@4 z%|x&H(+-_Qb@O4=9R2bD+VrP1a?{xAl?7muVoC_wW!tzQ6pNXrWm6pLlvx~yiK0OW z_z0lhDmPw&8d*#YbHN9F7>#x_nB28jTe&XeVb1!#XHIe*m`^Qlx+q)6^R>BdAWU0N<3>U@roI&;t1Cd&a`z`NG0+;2-c_d_lgLd-6X& z8&skXICq|uWa&Em)uPK4=X*L+qyPGGyQoxnSo;^8dg8USZ78Nga;Q`m zAF}{zD*-bl{nsV&Igcr)elT5skJIjp@3nd)K?lwU8=_Pm7n%i&i{c~OV&Y!05(0r)%LE)oUjBMYTaU1n=I!hJyiNRT zEpGvjvV8pP`mZJ}4IQ1N>x5!~d^L6pOUi=eDV{x(?iZt)7?8W9s1B&66u4Jnb=%s{ zwz|7Kpz(6sAl+^eYWrEif#$H)$|4^bQIMrhsiKBhw;7AZdU1+=+4$3}SMmq^oVw@DjV#>-U}Gfd&rx4K8J>pg)wJI^`{S*<*JsjjZU_ENzn zL4$w#kIdYzqmXL~UQ+>JuA#oA;WG9$$!l{N0l*nM($4KG+m31PQf+P4%~keP;A`+% z>+9f$-z=EO1pP?R>vHUAa%N=H$;tPp##p}FORC61%*V97!H{AAAGypd^)!^V)sb++ zmAP*!imH{0eCo6)TRW!YJD@aQzs_AdTA zBFLcfS6izD70I+Yw+^jwyHk|)=Zu4#@82z;Gtsud&nA&Sn@?t+ zg%KS~zjh^+lsWx;^ZXz0QoMu=Xy~o(1?z{qpC46@zbgQAaI2p6f+6y&Mw|Rl>8e4p zVX=#>)q=dq{m%)y+U?HtTN#1cqqWU z^ue4beVifqdTrRtBm}?iQ5INatn-Up*#U1==)|nyF{)>R-45YwtOdYN3b{}RqkTxG z23^hOjJTyOVv7JjOYVQ|{`}c+HtS&vApGPHsQ7Ss!idAs>SuIakreSE zio`a>jh~SHGJv0&-x!5~KG8EaAbhqQ*tcmsu$f`3@GV5j9EK4WRP-Wgs)=cO5-=QW z>||Z^3B_LM@1a?)Q#=$83i)F6K=bYP=coel+U$|%x~um|GM_fMI{H%>HtdA@Q;Ja1 zReEQM-=0wXODfVfEx@1q|JsQ0c6skN3hpkQR@%K@jAZDIyq6wpW^@5vY z$KBrE-a`qCKF|R1lDaRY(;Q~`XS@Y^3$B6&f)Dsp9|~yDivtgf4^-^~G&i$alddvl zEPGbi@z`+V`o%cVb}PAzMQlo784=(^Yk0KrvhTsHHkVFPCDX`2N#D?m|^@IYy=kgY)ksahGkBJXyN=N6CPILaX56df;Zd{PUNNaGHZRDmC;fyn>fIUUvYl$h}d z@V&~kw0R!ZJw6AZhUX3b__|o!_EXo1#QQX}|C}{N7#_eJyY$na{h6urSZ#l=CnP+2b?EUAt(X+-#M z$)tT$F9_KBR7HT3r7LAg0im^^hVV5@$e(xIn; z43I^5=}N3Qvacd$TEyLOm^JGYWIyC;88_gnFCb~Rf}X{GFyZ17jFtR-Wd-YVCT~kZ z-H!!`cE59!)7?{$N^*31Lq^+KpK3>MlFlybo1`Jqk+{GeOmy z$j4LS5_iC{2_|u%9%MspvrgoPoCesxC|HDi1o#p`(eE-@;;&}I60QgW?v`(M#r5dW zBZ;o0b?jYZwoJ4VzQ^Ys0}pj__;ZieS=N2hB=@PFPvjtw7D>eI5iU*<<{sfU{LA97 zoDuW7xiF(5H!8-?1F>;Z=&4qMMrf^`P>gaO zGxY=$c7qP2#M;<3DlnjuDER&=aw^2kW?pZ`>s6$_{SQ5z2xeodz=u8al>=H#oJXSZ zBk7;7(FvdlwRLShF4d9;k6!@dmfRf4xaj3l%L3 z0z)LO{o7tYXUCU!_NkbCyvl++1O=72K@~(d1I2Z5=)69MpyjaP)X@fy-v-OEbz&QS z)^#j_)G~RJ^@85T@#;C`GljAI;E^FnlL&si*(Q`apO&l4`|Oib`zUa1`+Fw%<2d8H z??{g;BbzV}GS!B`Fpw6tc(um4;2S2SGm`+ZG2kLKLJOM~gP6|ubmQZv*aGikRR133bBFL)g@Awan4cZW~GJsXpq4eX@c1pcebxWca=(ddq zK&-8sq+d%YdEO%YBxiy?WjiqVXQBNyr1qqUJFLEqFxo{XO>IB(L~>!*fu&AykhejM zWS=OE*g(JHv8t?@L`E2I2(ELZwMdbvTa7u-ek9Q{@JipD^Yf~`;A@N=yb0dK)q8VeA|JUYajOQzDjA1qT${$pz_PFSKA?X&f@afyx(m!u!9_Q1&QGksQ(Dv z2<}(!Rzj@by-WLjGk)jSI^RA(R0NL^Et12PP{0&@^-Dko0~ZJB4`Ono3<+s7569uS zb5W0=8Y2e?3_(^mM9cofDM_4M%Sne^E7;&jV1C*+h>;a@?i9jF!m)g~ZQ~Q+v&eT8 zb&B~K`YnFuUAx`tKM2gLVS$A@KvCYV!(|?n+b|DK_u#Z2tsn(n+6qJ$<#B{_`3Y$t zn!a3pXD&tG(5%dD8$IwM^Uq%u)EsD%AzsMp+;X@6QDaSt;Br)GECO`0UkIzd2ludr zDbU{Pgww=7cOwNfgo4~aMzyDhndYKxt}BlZ-63>8RZ$w238*9Tcceq@b+fXzO7FdR zMjP>CBVQg>o9D9RnFHnXKMN!JYoskN{%%`;&KzV|4oux|GSm(`;s^y3r9D?D%I*B; zy;|-OgWfcM95*c}V$fZ(N;2IGd5JH&%rWvrk8}f@=VCo=4#N;g5+Z<_9FNnEP^)Ic zASz}W;S`Fi2p88gyi2MBl}O*7^!5Brubu#9fQK7dgiuPL9{|Q!kQ>9S`GCgU`wUyS zQ{=Af_?PDMoDY~=FvPF+clZ&Vr;fsA&l)NBJ@1@5KE3=>-b}mMO01kFGpd8L*}Qyr z^j(P|VEbg8fx-RTc^PrA^W9YEs!}Nq(i_yQEz=%@w6l-_ib&2dX_|zj{4G=Cj=b-b7uPTKE>i}1Q%8=R;$QEteg$) zbtfstbl;I+n&6=<=|b|?a~@AvB{GPJdt9b%fzELpUYm9`0heAfc$0xL2D;ZUKY~#* zpiz^63MGqv2y7EG1HZwn1%CXO zyK;MQbGFTSVxU)$``<+U<#28}z(fKU_fa(2e{g1jLDzzIyy8G5}akGy3I%M{tz zT?lArsuJTsRO0+C6RR@I9Zx*!K?`3?e&6_^GBu;hvHrLO=m@6mKa^!{d=RT)5lZ-2 zM8=BF=DVEI?$ zQVd2Bo2=MHCULM73*u`e2c7XM#p7$2LHBZS!cSg03E!Qe0!i0i&UteWPrXo`8W&_o z1f^y+#MIQ(?ZI1I3yLN)0*-l_s*L7$+gaGehCDYtXx_+mEIm(bBUAm1F<`$BW9iSNhAKWvuBD87A(ZA z94w~L3Xl5m_n5IoUgIKHsF3@Pk=5F$FTg~=!g zy%LmdQ0h`olQD{qpy4oT0!iQZFdRwwpYN1;3NUc6YVmFeJNyPILJChcjJ$Ano)-D- z#U*Y?kS?se`Jq`Id<+2zmH?W=!U-^TSz($BJH>^Py{Sz|{t|HK_|l0m*$H>VVF6F) z%~;}Sja^T^EbWSvF366&jK;-m5KuySaad$u~pqIAX}*NE!jI-7_K(VS13#wrSre3lO67U_#Lu5$z!7;!qXLR9Tz zu|*`RGJlfMpBWG%i~dCm9eBQKw*NYR*O_8;{5e$u{bQ@p5F#vz>3i`X;m-64pmbYt z{HY(=(6>s<`6m^lU}k`mabW&z_N7{dNgA1BK`74C8gkuNFST!?eIi%taQL~`8;f(- zxRBLYka$i~x6t5|KX)Ow|H_-1JY}(~#KPjWUjarUI!ME&p6@t~2RZd!QX6)8`27o1 zOA2o)!eAR}pL;f8TM}y1g;#30%DpV7b+AJcqo#YD+}V3mL1W3CA#IgZx7yk8j#}6> z`17QEQBBBw!ODa5qyP49a&n3100H+JLj&-k^eHTI3P(zf@#`(ykdh8iKH=E2rr!KH zNwNESqrQ_&->~B}sqd^Ht+DfG0b zY=@K^Ro;j(7Jf-dMB4l$U}^D>eObquDmD*CwOo!!F7Ql&DA@v^Ko6{EMiTqNo)UVg z4%`vS3fQW;$LIW0hkj3JwU=V=qd4~E={n4R?a!F2!v;gqcNm+tA<5DFNVH%4Ziz9*Rj$r$KKbdtVf#AdK$OU<>q%kNB*Ui;??t}PRmM3r%lE7a~5h!w63&D{X!|DZfRpl@Z7%4h7mhtn2b9tliakV&G zaVZ!Y-+ksg-*a8)RY3*c_lTHVduD+cx2_+Yf7$t1e=Ga(izww<#Cw1EmCwopo$wU; z$vEVanpmN5Z_R6Qa-w(9wR@wGGht}GVed)hx>lB00Gj6RI_$N&DD_;_;{PF@==fri z{eBExvBf34FId@8+9bZJ2-QymA$Z`rIPiuy7<_gi$&c&IiBkQ%W_6kMhW+p8q6T8I zpC=&{N(hsCNVCb8eD|AYI&;*&Il4s17l@a{Y0%6Lr^NQ=>A}u?0P~fcBD~oOCU4rFYe`WwBMMICS~KOXH#b6&0wm?u2O2 z<=Edb-_||e$7RjK){+Ql0Hy-e)KpA5i2U)Lk%3>>rE|*Zwx#Lqi;W3Qj%8BS@X%7A-XxrQ&KlFbwPRR6|bL0c^J>%`N$o48C!UG z5GMrrf)o`MBb;*{l;v}9e4d{{X`JiL+HmyiO&GcU>+x97)F-V_I1>PO=w5j zmjp!?$lUIw9s2WSZb<~DI#{Shd{vFAq5`=EPQpd0(sQ3vScyN}mnKTzEM-ce*+cSh znWKj2;n)*QdW8sx9H-?b4kU~0DeMg-NaPebO)ht$LGycXp{-_ZJu=Si@&1OA%p_uW zT>#dX)mwjt0#yV(1T{j(OM=6T-Wu_XU*@M?<#8a>WR|a^ zX=)PL-mcGh-|S7NW+9cd0v^)fBk)$ti|VDq8I%Dt<^PUw!g3Rf;#;}@-BIXsD5Em} zKtcC=;2mr6z<)K>diJ2IBHq%8vMBN@BuUs$jtH(?if5}5j6nX?fk+Vat|5>kd&E+M zWKaRpV)^XZt0HxE&y7;8*h=#6P=(J-j3B(*oVyr>m`wQ<&_WWkQ1tx%&L@vXkTg13U-?@OxIYP>Qh ziRx||h<%ZmAbBx6(!pH2+Omr02k>POpe+-<@+VzdNqh5THeY1|o6W;Wp%ZMP(41FO zW!R>q9gx;0ZTPFZgtmK+p;f8QF`Y-aHjmgMGy-Pf?iC4p#<|JCS7o9yA#1)Ie!*=v<^=T=*Q1Un5&n;7SDAmSuE;qX0GUxTW0)<#^M+j6{*Y6^*wFkZB z6Hmr_t_pr}NI&oGX3BmHn$X%_aeLy*zj;3f&cnToo^F8KiA<6^%6*$E|0yHPJRsOr z%iQ>GUQK_2{2*!Qxh=r@rV<0M+xyCB>z_?m*_PG$a`z_;{E zEk_9FuQ933r&oES2&b$ zTXrV|tozcN&YICt!i%*yV)Tl*nm%z&)N$C4w|^CrdNNyv2Lc|5gqc2i9KSd2pm$n1 znffPy6{<_`$b&O1|4GKeP9n5=qU-#5(jd?gvp7OrdfVrG@Cm7+qte%)0PN53EIE{tZ5hsIkN)DR8 z3xzn*%MmBR_w$u#w&UPxseYL3i~oSKTxA&1^w%%eSJpVbQv4jCyuRl7E5(=iOhmRG z09={rB*X|lel#DMjXOa&?`Ya9*h^%pUGrtSl~9|R++AWSr4pis!xiG8&#YoKL6BCXK>Zj-b!Bp`ot5T!o@KSO^hSP+rhuN9UQ{&qwF@=|5708>U&IgVN_up zgOV3a=OCPIiMwCFH~2cxFmF^vhEnm}GT$dvJVMNIe%r6~GkmK%7^s!UXY9a{u+Z?0 ztiIAIX3EH=u(my+90#xnlvE84Hw5XjY2=7l9?&@IYv$-)rb$EI=f=;0K_jsDiQ4x{ z>Bgv#djh-n`9^&Y52g%{R-cP2DsuCUydOK?G7Jn)>2ffz8%~aF60kx!=Ve<{q{k z`{4laI6F}Ib)Xz$KHG0U|Iz>+2>>YVHhpb>J^Qt<>Tk$)kZi&=fUsBz{Ovj~nq_bX zf^+eqixrMU1PAx`Y+bDMOVoIbwFKMdox`IU1HRB5qq&1tj1a5EWN1b}D3=5V(EQ}V z)32(leDimPv-n<4uJMLV!Ls_zQE0xj{>@Zg{kVq}3G}jBujrMNUh&0DGS?FuTo#O> zy{51S7m;>-4AX`n|I@;Sk)1+aa<(MiC@@X>$?7gq?k<6tZsR37EEcyCkB#?M+_eL# zxzssbCTgp{ToUEGt@ZP_1xwSY`DR04c$>@*SWiK91g1drwJ=|1$vMMD|WQRnfy35 zhKS+HwBeLoEDb6AU4#??EE*TxkSNq$_M|fbRTcKsY^J>UkHn8n}9KzGWp#(lA>>CZ9Iv{^`5r_iUtyH3`fNP> zTI!xLx)7gBR8^6742hJUQ53tR3Lc>AmRn41qZfVc`R_E{@p;ao8d)xZeix2+(8T%Y zgI>8!X`_HV_<_r}M5{DiAO@Wh;2BNXi-(~u@O@Z}{Ko9Z*S)_$h)c(wjSO0O2=ne>Fu2y&k`JLidCofnk7sE|Vg6Y!TQr<##;rWK5U(Gw$5uf7R)F zXIo#)vTl6HuDiAZSEI+7Az*=HR@E}NP{hPzcdZJCfly{k^VWi zwDhB{E9hKKn_!iC?xF|(v0wIjhF)5ARdV|9G}gGtlPJoD49~}9aJ`%yKz|w(B}<#| z(q-`WLr9toVhdtX1?z_VV$G_!qgPc`#WC~Sjf_0o{%V>w`PmRm&(XA*nw$&;X|n*R zWTQ*g-&=AE$2xk3teo{e(g^4De-!uHdJ_PWSwDUd5yq!`e2ozAZ%4oX{-{@YxLRB|TC(f*`}?E3 z!L{Az-R0;KPW6O9?h($}iD=JC>B$9_SI^`3;TgkunKnQYifiH@(++f?xCfem-HL2$ zcDlhO2Pn#piMc}&@cL1aceq6gh~==ZY{AG-gFVn0yncNu zuWhMqBm>MpAoKcoDL8wE*&T2z7U1tM=6~nkb0pZ&F0_1qczCerBTf={@Q$K#CZM%= zJg9N;jBY?vCy=ov5d)PhhlPDl&=%|k;&W^5@BH?Bx(Axb)M0TGOQA#t7(*x(B@-Dw zY@&F_i1@qI)1&8K5Tx(RnvOBP5SCazU&A_Adiese%G4BICy|tPMY+m4)~8-9maTv| z!>oAWD8ub@Bq&`JceBRKsM}=UndmM35AY}{aAECfUbXcXeK|aX6@4#fgYPvm55en< zxZJXAW4rRSfXn{_;Wj^vWJ5=m>KCz7m6<*CUa4300!5ge$h>(_r3ldRsCYB6_|OL> z%yOT!ZIa^3e$fZYG~Vz@$#r>#XBc4L;hv+xLRd2CFb2-B4DyP8179hYQbqZB*eQ$t zAm0QWcT;wZd*N|845=`HW+JSAl?}Y$XWfaUu8Q!n8 z{j-wyK8Z-yZJIile!*yEB{W^t1JDP%TP=Zhs7asfrLg&`5>@Xj| z=uxWvK|r1Yos-QkRYu%3PM?dkCipmdX(vm4jq;- z4XhO4A0S8;SQ0bwT;njwaP!j}C!qkKtOwLs6Fxb9`oh%QY|c%cWwlY&_304@5}5L2 z&@=pVvxyM?elJMJTystisEyY^Omm9G`sGp9$JCafLna%QfVGBAr`OSr10ZhwX8^;d jH=`gV+5dM-uXMvuHA?}ZbT63&JY6aZFXiiHEyDf>J4Ll@ literal 5482 zcmV-w6_x6VP)GS83^n*l=_iXn*A(zVu@Bn{Ac)&joJOJ`sHw8hU`(`9NztShkDUHiV z`M<&lj#cDiwDK#BT}6u1cyX-hlF!{t2XN-yNK276A$gIA;Rw;iO=@ z>V0BT=43TY4v_=f7zz9Y|+s%s8g_?MMJMx~ZbV|LvD{zSLS>UF{5q$CFpD zT?=<~Tn?W-dp6AL#W@f}Ax>c&tPzkj2Tv_=)t92qoM<3@Ce4P~b#q3&oJlV)m+R{4 z>@Cg9+>H$l?m(d0o5^I2)XoI}b+qBte}47RhaY~pDV0uv zlF7j1k8jmx1OHvWem!)3csShG*B1^3Z^V0gdm_C(y)i6H@-|d18FYOePK4Y!({p zf7m=SG7|6Z>21cxgS^jqOMthzxjB$XCLs}zvj!1GEY1NU2_CP<@9}v2E83RNYE@AL z0cmV(ESyXw(_^D!#3=Oj^@nLjv|2f6Z~r1lFzUbke!soCs>-3_cluveW;a(=RXM4j z1q&+OW|PTW;rDx0Rb}H)6a}(s7P5Fvr_&Ifj6en%&oWX$RTWkb`w*A6wp1YiBn7AW zgv<*7b}u?LrCQ8p)=BXvr~So2(UMnbKw0f-F`K+Ls|CDncNKw#g8-41F!D@HedpWX zVqnd*q~;hyDh^s@?d3pcmWax41cRV(##G)9tD{gIt(?Y@%V~Y+6E!t8mA(@zz(lpD zyE}sG-=SdRI16Axk|ar2OWI!%f9C}DOQ3%Fz;VXe0ClLC?BrBFIL^2}SjO-y^Ev=3 z0;3YR445ouQmdaUAVDN_CDQD(WjLUr>%+Q!Qf>zTrro=HdLqblO)8ZJE6%9RW&<-y z#AGzeX5Q=dg2UkeQqYn_B!eoO*nqX!Q<%8Yx->FZOmp8O2uYG81Um<__cGY_R4fMZ z={TTH0Gq{<_bf`4+CMO;Tdwr&06=Rcj1mw=B?D1pSe9kRYi{{%_R?i~y69Q3fIbAB#K~71t0g}l{CZ@p|2?5$8lmX25LB!` zg{x11L?Q|zQxpOd=i!iM2t@o|W-&7+$jHz&5b%LXmY~dLWn!%}DdIAt?~HKS={i9~ zicKe=3@1ZmI!OZn&2xyG-1wa+!Eh`VOUL8!7}qG8!g zOin?duAYrgFB=@X9_IG{_5dJP7H5e$FB20J)QE$$Iw>PIvnF)D68Ta^OlQzsrc&rP zAQ1H1gSvsFAt%#dC6IIm*dQg&5Pp{=gATy&l2Jd*))h-ANh%|y8=0C07wR99F8yBT zm1{KizC!yyuV9Fy7C}!cRN=2!q-pdlThFRwhEhQUEtO~_!lWTZ4pbt}mWkJ2fjYp+ z7$+cT(2|s!1%Ux!WLzxxotqPcC|ZGXXE`&|df6l@1k_o{QXbzA;8VXe{sA7$# z(IqC6r=kv!<}x8D0VFXg?nP?Spy>vg4N{v`QOt3Xm1Y^wlLoOE7!IHjfbj?_KaZP9 zr(QM|ybB|VuTPVa=sbwI~k#Nvsa#9}AVN`@ZpRbYX(b|@9kSP)MKu&*lmib(cGi?AsSC|x8 zrt$eCGnsMbV39Pqy*}i_#QgxxG?KD4nPt41@Lrbr3j`23Pr5HbGoC=jjL|SyELJTB zK?e#$^`I6FvI%5-Vf~^+ja-6xV_HcEAv`oR6vpsWTNgYsOrsVsapnZW)T{W{x+ z))<+rXmpb4hD0KXAjBbpHk>5E=5VkUC3J$wowlG0Z8Bw<7f9vI|7U)I)+R~DL?Qz& zUlphfc!B-v!SI_iukz47$@$C!K`uy;>WPV$ke)D+@Ib4Jj*&b zeX#?&`mZxESpoh}X0otm&BM^PT+2&*_~AkL&-dQ1!LQFCt)n8OUve;1Lnf*S06h!y z#Mmd7H`&E~5Lf>c7Hny;VjR$g0TBfhZeqUs;HRM9Z~R6JIAsk0YMGet{rEDwUu0IW zegoEb$+|SC7|l&VFeO6w(J%9!p}LQJ|Lf4!RF6_1vl+hDHwb6mJXTci>p%Jq3y$b> z{__ja^}$&;)^X##MjSs)$FB^jxtl6MJ=q8*bstFCSHL99<_5-s!4Qg&TPV0s>k^eQ zL{cimvsyzI^%cBd41h)GXIDy^m7yJVoB47)uLB0|8mm{2n~iya`Q ze}e0K08+vWh)$is9Q32E7=YdnUjWtoGA!PTl9QWNmU^2rx&+%MJ%PZ3+wErACPJWe zy)=xDj6g_JqT6Q2a{g9X*fR1-w*ii0y z8EUt-jGlh|2uw}<1jTXyHT4cOJv(rg{|U_%0f43rpTRRJqNoCnMIg+R+8%w&_$?B2H@1b=YaZN^~bB>;(vWU(>K5tj6w&{+w8}_fThi* zrlO=zRMc0>>^SmtoH*Ckn97+ab8!NhJ=OZX*H2||u)(o6AQFCq0Yq2&P32$viw~gX zn~$(~-GthkGY*?`=Q6#OmpNk*=Wh7zRIKGIPdp%y(~4->e|pxd-X*v>J72u23`;K1K!DZz{K- zxUE^e8r&X_=8>Uo((luibOS|a3>e6~d2VCfw+GdVnOgJxE_Ge5PQBdhUb>2SEpb zZEn`pK2JMd?1}=BK5^oc;b)(Hw#tTzkQ}3R>()W%=VxH>ayxu65QLUxe~n7AuBh#X zy5~_Lz6q^WKFHBJrqDWraXm(b1%1o_P*@Ho3woRRh2PDjV z556!u?7`4jzIgG%_Oh}vQAQ<4PROgT{tKM{@EzzK8;9|!M1D%d;PQGMR;cxq17+7~ zXD5?R!O#ST#OS{1nIMW7$!tanB13yC(L<5W@=Z=OE&|J8 z2yDC(%jjs|$q!ce7I%tF8v#Q5h^W2PoSJVYXbMFWB+Dzoi@t}x$(l^1hOc#X9p*Z# z%W&pEEO79OPOsc*UqC6a45DETb?)1@Z_z`mR@SG;-lMa#VnrLAJJ|rS&TV;j8@Q`X)9`t<(MK^T#(BaOfo_u00i}i89&CN@p z|MEr1#HX}*n@MZK%z*{^F0idoJ_}qJgq+9{pPDpCAdw{COiQRJnX>c;U@>~04g`pD ziKGKCMDN5lgM9Xre7y5Mpmj&*1bHWvb34EPjo)0Qw0C$s1UJUU*!H;HUk$RwrX8o@ z$xD_q5avO=9z~brXem4!1nFip|-XLrK<)OEvkcwiAYaJM+Z&7xtkq+xbFb) z}y`BfAh(iT`-D@Jn;FXYg6&v*SB$z8n& z8dk1_ZBIYV_7>@A4Fylfjvnneefl&dK2F`u?B5drIQkTYUD)%hy{9BuQt2E4ZG@89 zYg^t5Nvj)jbe`ZQGhd3VP7hQpT?tP<@i;qa7Duy3hb|^2!s(qmcb?;R|E^~LULKbx zU2y#B)oa5SE`HH2i8AVe3_DciaM)qllEpA>^MEQ_N-_%t(qMA9!BO7=YuBz}hpgy; z3OQO+(MkCE&wh4xaBy&lo4(lH4*Z?~V9*60|NDRJIfptRLT*lq9hXT#MTH+)nwP<} z*#(Mhy@>_jRe__U=0(NwPwlptKa|a0DG`Td|@QvJM|in;f7>7RaHbFd#6pQ!RE-%PYVUsE0>4 zZ2%9t)zdNZE5Z;&2dlfQEB4Y~{`e?Yd_8w+_valB-{Ex#F?_B^MiD_sitNjEDQu@>Pno*wf)!KzkL7GPd_8(e|<03 z{=8#>+gu7s1cBD)fe#KIvZF&(wV=Y+O3I5uUXdN8mDue1ecn7K8DUgOdP6EGKXmxW zDP*3)+ynf@hx9$0w0r&57|$cny9SRUJ^iCUe{rkV?OIB@fSG#EXx=P~V$rPY^vTKp zIrY1@-zBY2JFQo7ywQ6)-uv;|oZ)pF%F2A>xj*^thT4VI57F!vgCG_I!eV_;j(&Xn z_(#Wne+k=o3$#~XqWAh$9N~VwH)sHXLglActz6NxZtX)G%qEkcco29FpjaYA7#SJs z`RLg3bNvH@r0m}2N-ug}-b1>dFHZ91;N&T^r;(aAZg`{#{em{T&049Q=NGu56O_ng zbok7tpPj#Yt(#2hdGamzG57fWVE(=XU;x3)rC|}JtdSZWWo6dNO21RD*VjK7R#i35 zL)+8byNupTh4%*mpac-aG@WOoH@~V3C(#CYN{izA*!^bwD+izi7-E4LR{jt#VhaEN g^7I4#dEf!~Ul4czSFS?m724=Z@0SP$a~o#sdI=P+3V{8vsC$w;%uq>+xdjT5JUX^*+k- zvbtWNgQ=j$HUOM+okF(}Tnuu=1t0(pKmsrTG63vm^YQf{zia+QvJDp#87H}W|>pmHEVNcG;;UT1YMn`Q??Ub-83%$eUAb4^r5ikA)`(86tx~cMJ zR)k;;@cK4-;Zg6xwH z{PYt0Y2#5C>ia#1a5hA6*5%m&eNI(h)sg9K^WE<5&em;#oqL1F8uEOx8}lKrv|V`k zR@!m7r6fBd^Kl$Am8czUJ^y$dr?%;$i^7k5E-J5dw$(n~ht_QLT7Me&ZN_#GX{7j} zI4!lBc$n_BPtIza4372caw*o6BV1<{qE?eu_j->Bm%7YX*4!?KYu_0J91}r^_;5z) z`Iei-#Ypc|FvHd+586gulh_Fh#ZVob-d)dS#ho$mrD-x%L~wNO{%Lurdg$0i?z`;Y zbccD?sA#EA&~C&k0fRC9bpQ8|A{mcxZSL>8Ei)aLF|&<+TTE1%SzR3BOJHmTq->4h zZO_p^X=)}`A{aK3uU#}zsRaYcIX0-CK$~QB^mTs7R3v7ApKUFW4u_EPwU^E8SVkz@ zS!9XiXr8Vs2Zbty>c@6JE-{Mxwv8&$I7QZJCGM1;g}qom-|EMP%S`PgR(nkhJ^uvu zpiQZNb!aT+om?BCG4XG?M}4BKw4K65*x}!8$lq=Wb`VLe)i7J()9^b{J%4enK0>}q z!M>&MG5wmuN#$i9Z5q3sYn1rRYu#4~n?bpKf^9In8M(M)m5)##q#O zagpvj3JNQArx()PvEdXMd&%<;b0`py?@j1Iz9CqkmJ2grSaAz)9ve=tGA|qhfM^M>PDeG zLzw(ETuE0)QbhFP^rHOwq;gMF9L%r2;3k?mn{4`6OH)<3yB@h>r!4+?M#H`C9UU2r z1I7t9hxk`hD$V_r$BBKGEobhd3 zO`n~eu-X$hA3BGW1(Bv>UTY$nE;mU>7+*iV+T_h38jVKrcP~`{k>1Pa%ZZ#6)Y310 zLLt;%l+@{a-pj9qe%WmLVW|>@gkXl_y$BD-*Yt6)icHWDO#Ca5s4Q%5WMy`6)Mh?% z7cT8ql1Xk|sq-==k2^WBwg$PUniGjdP4gxPBwM8$wWPxC1(fDl=~BGa{Z42{Ikq$v zX&~A%)@be=YWJO_xCV75_=ER#C>y41v1au(rPf#4?N;Qm(6t&d{}JbTrT)-=AClGh zYH1fw@#i(xoY%G`wo$ZRBm$wdd>cKo{DZ&D3?`yK*R_b(b`9|lJEJfCi*Cg|_KZC= z?G)|e^UpX8Om=1t4)5;#jSIGWug6(x(qz!5wIZmUNu|;oYU(HaD03!KRLFWC+KFa= z(Ub|6a&RBtq7OoA0`{^$W(xB+53iTLNE#e3*nyotX%vPjgX=YAMldmmm}I4YGD^q8l%K+GXvgyN+9NU}EC*e@)=qR% z(^Gq?7aG?Qo<#JH56=R`d+HV6lc#5N zFRMp`fb2gy8R9-fKpF%g6VHu=*9`$T(uNa;z<~eti2B^rE3?+G$w5H<7 z0GfvEpFx4oz$Bc0=jriS4?FP>m&`xZj@Ac-2&twms%GXF;|$t2pUjnYdX>0SD#m9v zB$Yc`+0lUr!-_}-O59)b zh)x#ziZH|X3PF*GHTF2^6ZS1Os~H5lyY%y@M*3#-!)WDNLGQ44kg9k4Q2`FTCMzP! z{@Ob5O0()24Gmi73G>QswEB=qhMs`ap-u1pvJ3H*NezY+CW&p?Da96LviBFZiAXk` z$WS?v-%c^qAq2=qZ~*JJIx9uJ?}qZnyR7HQZV)e5_zeM>v!4{GWd@Icsags4R_E^8 zLPG9PsAFb)ITYn96e6n(E3#8S$X)scjhyliJdt@HL-<*9z;Q(mqD4;lla&l1J<)Xr{%+Qjr7HEd?9R{x>iiqhSF8sYpI=n&P6(?{z80QQ zMG=>#tQdSlQlEey62KRdPJW`5&6g^WV9U;^>x1I>~sS(G` zq+&l&O_1>hBmcJF56;lvBYsE^Sn>PXNA={4;WV9vhcxalFOrl+1&wHV3cwDF(*HIa zs@FIW&&nFjhKQ0lbNC)JDu6B5YYBWJI*x7lPGr>pbu1G8oPNchm`Vb0K{Out`znwV zBwMJ=K#jZJYxlX!gNChlr+o?KT=1K}nkz}tlxESxQ;<#D> z!Za^WZ5>b7chlGE&-S(%2f6IO(VtBl5CsU;m(q{@XA+detD zr~<;O6U%=VJRQcj*|+U!Rrz;(;24IFy=za2iFu;L!h%pP_>~HM-_w1;=X3YRa5$oN zCW2c^6!a=SF6N;_S>kHS=)t!e;_SCT51eAB?zK|`)SJ@W@@6(P7UzGPg3ZtD7bl|B zO&t6x0Z!Q=WX}_K$@iGQV@p!ST>I}%nYWJvfTG^o@D=etnRPLt=e zdy82eqK-ti5i=`CA-b%E2{O|O&+t^8E%2H(VkM~;&{E>!oq0&EfpbfP4OW& zAe#L7s*_zK<5{Ss0=SVM$>Ci1*oARSA`#1z2}dTR9r*D9!oUhT>&+Mn zvKGX|{LhOw5v}mN4mSY0Cazw$ejH!`M*(+ z-^=c8D$7K|x6ScHnrQ9=krB|_AE&2H!m9ci3V;V;UE|ise!|pW%Xbb^1zYukv$Yr_ z)3}0tqp_$Ue=BjP)WElHS;*@o5BFAZ4!?N;15N-hNA2;=}K|%iP?fsQN znjdQVu3)dJ(sm4G!Wr5#S_%HHAS|j>hO(-P-sOM(TcX6r(%DI&IW)19xLPkmmBU3V zXulg=w+eTygq0|i6k1))H za-LSMR(&T&%Gv%4I#7k2tgmkhckoyiyoVngsBAnglysn_%jClr#%GVhN)2aUE0m;p}l3 z;;XEaijN{pbJ!YjoqjEuiwiFjdwL=<3lpXd&UxodEOnf=_r-ihKybzx(?U;x3Akhd z#3&=^Gp(x^(_jgV!?icTC4}EKMSL?>UKbD^LqQE}Z<)z)oi969oiZe3h0rt4e?z95p+e zm+#_3kq>=I0~eo9L*;N_9d7>|49rJ^4wYCY8+iqA9Vv0@?RVdHoaQB9)A`;?kcMsG zPyjS@t+>AR*Jk#%ASx`jCN^y&RxC!)zkd3YB&R@83q8ifDX?poucou$fSLD;AbL7c z5PZ4`kP`WcOOYA0M-%G~xZqH;Ff^Jcxc@$*1~P{JTigi?{!Eevfiqt$%>X%qjEP7A z>i@JD_;`n-t@W<0t*KnL?nx*Z!m85tN~;TMK_(S~J2v{QC0Guz)5R4;d2cg$<=iJN+mr+rA5iM7A!*%TStNc~SNA~#B5$@^S3 z;3i(mLpI1yL)YZ1$Skx8KbG)yO_2#NiSyb44qd=KK?IHi*^Yfh-}E3=wZ9uSI3PP0 zv?CGVKvYfP8T8!?Of32NJsWn-lrRiB1!m|2->=ccv2Z2me5FL&l*dO=nr$DaF9-g> zAQ5ku*;cPxaYpXCHT_inHp>j%UbprD7xIZ_NDAW!r*|w5SIjlnJ!uvaw6rt8*s~!0 z=7fcNT3lDNW=uY|*U48EEg&X_ze(P?JK3d%_ea}}#@RG4BSf=R9!ObPKk+EV1~Wf<~pbiu8q=K!d8}EWqj)$z;JAxE-ZgbcJzs#mq0-c zmZ+x-c+i^>oJ8^TBK$&l3oMUIO#L~q|5#2@?y0&tmBI0m)>p~!?FrWlln*A$EED6j zAjj>Lq&MgA5HqP}bzl%oGa$^e59{RHZDvs+B_(L3jK^tWOclbA(@sI=bxq>~!xXmmuhW4Z~P>}XOn{skQ(UD#BPBoBKdmPIc2IpCbnjm;BUJIL= z;5+*rO-3#o7Z~`RCcKYcI(_dr+v{Ime85&S4=Mbk@);>Sg$;y(>|H{S$+2BYA`z!h z?G>sQyd-04gk~wOSC@xN@)KEbhRxHx(&C&v{#X4QXBKx?9t~fIdG?s+M|hfVU{9wo z8>#Q(rsjF7SmzDKre*)y)zvykHCG2aEukr_tSAR+`c)k~>V#jOK%>|{KCvB3AL)3* z9q)ubdD@;D^mA{N1;LLO#_#~s8XGlp&~MGHPaDPUFe-yg?$q)2*)l1h-;UVRP-mKx zvyu`1eW`#+^>$f06yo}ofW@}GAR0qmknv!rv}2~;mKAnsgK3bYQpv!QpqID49;U~F z!Dqe6!Lr7YUn^?r$tcwf^JD#sVEZ+^{fUJsbudd>`YMRWZ`Cq8Fbc?mPD|T^+O)}V zBu?P`*hKVFr|&Q?NI-IH8xq&6W0!a1Q_G=0=3nrOYJe?OPX!98*4QI`@azxmtOqIP6SMo!y7L zU`p24F^ns^IgV!ISCjZ}OZQdq?w+lzrTPp1%SXeu6G3Zz)53J} z<+;g!JwTwy{+{_BNb)*|95r}R$kUk!&iu%w+48A}aTB~b!$x9&Wb6YX|1&oHvYxIo z$fo^_3lsn6*S53zSKlV9lB8=JzsdNUJh@jW;d$*_8$BAPsoHo)BpqTbWEYgJ=s%LH z1WAPX>C*%<-EVsT<6mo;&y%g6Bj4~)0t?>twyGNm0m z^M^M}H9Za5!;hL2k(P*fsKI1$C9>Unt_93Fxm`r*Xt%J45>k$nHaY&A>9aECB$Ls! zBLs(WmV&v(((SaKm?CBY|GhAUoy z#qJHi!DZPZud6pdtoW?W0u9j)`!Fc%m>B6itR6uUrFZ&kK5oXhb4bhc$He3YF`$;@Oy6`}?CjBHAP?@r34w_R4`wO6*W^}??2@^x5O>KbU? zuv2$P1g#AIV%GDpV$=}IoBLW*$}W0cOrzh`T{E*b>vlv`Xq_@wOJsl}I~2i>S{LzY zq5q*47v#Da>-&9S1uL%i*?>|w7K6N5@XGk7KQy@gn??)R@|Q*Kb0n2>lZrfbeepvR z&yAP<5S^!(0dvY)2*CR)Ee*@=2b z@MYBZ$bD*jAvCl37D4?tUh-@Xw<~Brc?!HkHvudxeE6m=Jz_^_zia!S8+`gYNn>ta zJH+8%>`5N1spIv=S7pKgrc)-|#;A11h3oOzwyW?_CwVgG0JVa4+=r9*%4YWV^_zd8 zZRmC?JVy*=|7k(ww8YN#%Qk1dZ}^^mWayL>VmDP>ZI2hCMXeQ}SdZ6mW1+tZsRWGc zn;9m?@9H0nu==xTTWuVR!|aw`A^1$DtyJ1|e8QAeGY0$sL zKjVf_^%d5@0wc7JLi=T6&TigGm>{wvK24oW7yY)X`|+n0x^+9c$%CCY1UNF;fU zW8+tb23IKL+~o$gUo~F@(C{UNos4z$%_*9b0V+12E3$)&1EE&~^f94Yyhol>g#Qjb zcI=BgI}wpgpbzBzlb&2FZlkfs&38wy+P6eBt{#tz@GeiD%W^s-Ifc9qjf`KpA$)=9 zgM<9Tj|{=PH+UHVEw2pMGs=9%24^}MQLul3<)&n z;LdW4uBF%g+W6-s*dQr_1h%ds)ox@6F;D`e{&bXr5-ed|PVrJML`{AgN;wrx$w^Tc z6@|*HC4_KQ3>a*g1#I_(0SCGJTJRM@csAUS+(rI?E5slt?=i^BbMfyJ`>zs;Mdl5K+M{X;(H{ZW^%krE+U4~7k1gC2@ zJQ<$2f**RmQ?#>e@Y>mxYSp*ENlxIq+Z^lLA301Te&tsY zYT!2tyupzv0{_Lw|Dry$zIMWb_VrzOKK^e!e@n{cUUcEqn*2?q(CG-FencvXRlYxg-py{i3;15*AwG|Uc#+@V z=>==**fDPzor!;?Q*|48c&}bt8^IEwr#BbC0R9XrSHOW_4EBrPcN;B7$JCBYxl}T^ z*wDl(b?*13uzuq_GgjEey`HvFtLu@wc=W z!v!D23=l7dA_)#SWo$jhfyJ9mh}8CJRlbXhSckTBQ62L@zYeM`BJPoA*x3HAC)sr+ zsQ6r>4z^q=WdCo$!HUSjF#va5i6U1E#{Xh8lfE#R(TN`}< z%UY{Q&EJ3z$5G2Eu(w}o)Jm&+J;8@)ZtzfEOL!Om$}MjTY99d1fEgtWb38xJJDr~= z!b0{)sIc^=Y^K+~|3~Ld*l9Qyzn}!0diWX(7q0-MB~rR=`K+hGT0BZ05juT$-?0S$ zB1ZWVL|US!ODK>9R$%L0pi=i`1mIRB6Vkxg!dn-I-rU$~r;$`*UAzFi^W| zx$tMny-ZT{=!WV~7iC5iCG({ktC)Wd_)cCJ32AWHW$nu(2}*$Np+a;-yg%a@Wg662 zUsEpoB088yPnPlOpH=(zvAVOzD#D@8iE4!|TT2X-Df+Die}?|1fdp`Pu05vQ^m6S; zvB*aTUoM@J%3+Xf=d6WxmeKp2XttxsK;@E$!dN*NGIe^LuUO0qZ%V@?-nX5E9s6t7 z*kfQw_anBKCzd1SW{>(hCtlXndiDEL>wUwWnb6WB1@!rS{3Tw^q7UE@D@uL1HuP-7 zgbAWbj%b=1IsP{uQ$}P@Ssg>3fF&uM8E6H82e~iGR~ryoAanxTK8UG4^6vqHuN0&Q zS4&}%L;o2<>APmyxGNr-3$fsg^Ih?oZ!9gj>Ydhy6xuXKFURxPcITrgW#Z;PIEwYR zmo#Z~4CtUzvy4I=H#>vuSaInSWehMZ#uIkgpYSPZjxtCJ*}}5l5}l8f%--(R9qQkc zfqsK``0`suA*~g->{{5;s9p4Jss-v_E=h>#6nbIdX=OX=I-teBF6S12TTyRQHPT1 zEuPE_zLa>dt&vM+adGBEP~Om8&8^S&j8nk5&h8_Ao_Blj1^uRmUnf5-HQhX{(zl_A z^cqmCU}@#RM*`bo+3Ri^Sd3S$-+v*z>@qfK+V{alZ;61+P&vKi*MDeDODjhsmNsQT zV6X-&sO5PW_(;11&ZV;FErgHcTi8~6Q~f)5ftQp%E9ET!RoSR6{)H@iZSugfEm{M3 z{rsL1Bm(?O^XXEI%UC#XJ(fIG#`Kd8%2H;4GxywjPcMh{JmD7&pYBcec?m>ScEu12 zVH;D~XA{Lu{A9${S;I$erY0%Ce|RT4!}v!mrWu#qr-_gsYlu$zhYOk%RjSRmsnjMr zv>A6^upo4bolt?oW*k4(ae^rBy*xq4q`tc*YHixJ!H~tM$_p`|lKRCM0?y9B8J*~E+uBiRgV81Q?huB9>f zM^RP~PxiUj^y%KCWOgA8y?~g7-4lT$CvsuXw?Ehfk-HBk2M4bIpbwh{Xr+==T7?s; zz6$ZXYReV<(gS9=QV>Bb(d*$tt8#<%4;q&L*+!Y#f<-<(%Mk6qLqvcH&i0fNN*|OQ zbhqyb9;jq2Ni?}X4jsbP;5z>>QbZN=NT<8_sOyz;&5W5&oYLk_xuk*yBFK71~C!JcH2DA4O zr#10=2Z^Ks8eKd5k+yzZnF;?YuogbUu{yv`G6E-DwLm9H-^@a@vZ1Y$5UJSV3c|+S zI6Gv|D~dC#FdDvj2_cWEyj<4cOWO;kKAmTkb!?wBASV{K`L{{Z-Q4KR4~9MP6AC-& z8bgY7in$+v2JwX8#1}8O#1P*GZNqV60{JbJTv9YI_fJ;+*oyRKuOC7pTW{&PXJ1J8 zA4q&Z!OKxk)!)EIcza93c17|%us=L6bpRvUoeV{pUyBmOU(pXzcrl-lfsC<1`x6gf&!i;|>*j^~Sp2BzM&g}8<{WDkF zl2xHavYozh(mqEWB$UK9o!MF7J;d|E<^BMN>(7AMa&v7wtxcaHh884e(ogwM5^F-y zXqnqL!hUKQp;6Tu$&-FuCdWV7k-=re`*wCBUlVvXT-GQeOw>;%y7@3TouNOO$Kq%b zv`Q-Q(zlhw*E}#4vm}DdT&LInc`l>+nX_*@W1;~2lVhx$KaXLtk0Hni(D{lgx|^D^ zKxL!m+aD_S#fIxIiv9#FlsSDipD&LLNjnRs`|tXAeku6U(L0mfsrP7j`ZM&)27^?v zSM&hGwir9|k;&TysSPFM39w0#GJ#xizIIEy1$>KwO~Pt1)6P?;w+RxAN*mZfU}I04{!?CX-fwhpk&&(xMyk1gh5l6bFtat&B>+V8=l=sWy7h zz(YZPyN}Zh)hw?(1L~Ysa_^4BuZ+H19;SY6 zFMS!@yyC@H6vL|X4D#&09K_IL_V-K-+u zEju1o@EkV6=lQ*lF>n$(cmGzC)lHB7ZVu{is=PQ{zZck*Ih#|E7v|YdxaUk?5Kjg# zh8MN5cJ2lG>U^nfoM~1csjx8fleIv}WBmdONbv-4U~!j}diPp6VG>T=H3bb%KG-z7 z@VBpMV3opfY`aFCX6R`q8oh2#J@{#{*r-k~ns6mtd}uVYvzfH(aB@PQnTdn5i@!*~?=Tb&dAl!_)xQE(3XB3D`&-pbb06%mDok z9U_gKXr6W*YPYw#8*lN|!FFuEd;6HLjE{EWO6S7jJ!f(o+AGC|CbC=3SItQ3ruQ|0 z5b~I(*+#N6K&vvqN;<|~0>7=uRTmNR5X$@D@g8KjETai7&cG=R@iP4iGX^IAK5Omb zaSy;C+0bG~5fOFTUO+AW3;o?_NyU0PH}Kk&?2C}4%eyH0oHHeWzG{G!9m23r;8^rM z#4k=6xL;kx?{e-9msA67TR!d}Umh&52o6H%`TZa1H}*RPxAf8hH42BbXua%F1M3CU z@1<$Gl>lL~9|R8T-4m_3tSSsC3E%AFa(&?2iaP!j`~TuhQr9ZrFYZRc{<(nZO7_E6 zaE)Ss7OiK!U^S|kifS$6Mwd0DvYyp4jDdWqAiuQ%R|Zl&GOo-)o&_CdbmGFZsKQe7 zpi0akmd%byvyn-3-TA5%H1o(|dTsn5eb4uWiEtBFW~3sJeT?;IxZ%@(6&|c0=f76J zp8D-k{EYf&=rFzAH4^b?F+k#H0s&+)nb^o8kRK*&B?geX9Upy&-`wmLSKFm0!gd}W zj;OXg*6%i0w<(YLMKx0;+{DtnQj~%c67IJtU%jg~jeQW;%ub&oThTr>IZ1;_s{D!w zmaRF*m+-0Vi~8?nY#{pT-NC{{l$`I~g^Ii>f~Sf!^d5Ni#w%dKPa;A$8TIrtUxMf8 z-;7yCLnA|Z9f#1#U+Md$S+$>?4HMzSS&flheTx0Le?}5vgSarFehP-5|HuHz;`tw< zVN?sJuZ!WE2$z$PjT1oJf)1_aYtoT)4kRnT4R~(WmLYZFm2Q+Dm1Qg&ojG zS^6!zCZ2BPdQOoX6HL+ZjI;A7GcZRDD#uuN!Q)ZZU`!&up9=SipR< zXntGlA$JqHO~Sjg*?8w~IjseBJni1?2e|AEJem;B%dVj9*(<2hMhtA0`9LqIfE8ws zESQEQ>wh)=A=+FWts8CmaIhOc;k2M1K`W^~*Ie+#giCNV>Px&0pBHitXVc$U2t4M- z2$1pV@|Za`;-NCQv-T9d9LcIVL46#?o{uV3e47?7f*Y9ih6Pf>sXlc;jPT(*$AM_{ zLz%tM^&k^+@!MdFhaFp`&V)2|3{#epY<-75vv(&_9Ac-8x0z(qCX}L7~IGw&{q5jDk8gMXR2ia5Sna* zS@{PVm1qiiIoO~a0>t-TQ;h~Sym7-k5W-a|8e?@}|3#z8H|IA0vsQpDUwM=_55+1_ z6{}FKN!rnI=uq0=MjP;gOZ`7*3yNz=DcD^$4NKoa!6vvo5g2k)r+8xX+nggrSH3V=>0Cnbyl?D*L4lT7K`=V z0zxx6Z8b!9l}p3Vy+z`RublzB2GVCIe&0J?elOF^FSfA3;`<)$40jE`$yA}*2iZb} zIYWX~?*mJ4e*X>9i-b{5-VM0clNvX z-Ga^HF2h;eDqE$+S;5OuPIOZ7XrB{K?k_`&5TIO#`!RYt!yHW1e+#5OMjVF?l8g9w zpD8?%hO*3vKx8lzunp?Kmqe>#uDw_{HNrSKbV8%sv`lDQ?e4C)1q7!0sRRGJ zYYvds9{6n^smv;qF+D`lvus2=_1xa}$&sRgJqvz(d-u67;IRpP0sYB z96>8fn1(XPO~Nv=Ng#Q0uQPe+6r@VyRAk(L<((UdJ?3@vFQM>59s%$i zCW1{8E?ode8_*SWtZD|F#WZknSNc^{R4k9j`20FtnJJm-w7a&jwR)G~n~$m0g6`a* znQPWCo0Tc@h9^8&g)?wvJo-Tsp~ZDVzh2!bQYn>^oD}$QIgtXfrjY8o28=t;>u7$h zPXBtyndmQ01{EbVY>)7rD^k*Tz*XY!aaPl}r@kA^Af}PJo=x%};I-1{A3{Ief#J)f z{U_LCf~+b-T$O7(liQM+g+EFE+iXeO@6jWqT@pOFHPj;!re)xp`(k%Z_6#{|-{yap zh>skl&*~`Y*xil=ipNLZ)Ra~~E_h;M%)0a22!tE{6?tHj3eoKYeiYz{dEyg)LSK>o zAQ+DVK9_!587EgLuzPPzGWc$A*N(Wq2=PP}1k@kv^uE>t228j=N6{m+$goY%z6 z0`J3va9vJ+G{9z}@7%b5SxBj$I2y&}WD9KVwOdhvi=#tbw$<-ek-sfTC{I@Zwde(4 zNv8cAt(-`j(%eu(EdGtvS%&C(+6c_jl9Xf?GG+cOT3LP?8U+--Wq1z@1rI0Uho5;N z`f+ElJ6V8$2*q!dTHlR>rs;Iw!z*}Lz+OqDXyH0opbq3D-vufO zb0C&N4z;20MRSLIBbrFKlV@JN+Bh%R&T=|b9|0)Vsd_Qq3! z{@x!Fx9(MV*q`K-&XiO!858l9?;77E1#}RyrC>?Nzrp(XurJz9GChuSb-WN7ewzw| z{sulkJ%`3W-0Dc_Scb;69$st;l-yH(f*ae7`6-mlKXX|_er-|;eEN0e45NQ(0z7os zkS!7b(&1OtzEsRBzE8tB;8da7#Dznw)djvZJI#g9F8|VTz4%*K^W&r6!-u{Bc?Pz% zKcs+Udu8%_fo`TZz%BSKHX9<2s0-TPWpg35d~(v2?XwZ~29PF1cYs^EL= zKA?I`2d=UJ_!wB=Bo=klmCH~mRsBRtu7^i(0arHbRdAY-KhYR9#!rw}{eMJ;8lXz9 z%ZjEbN-yibMibDjnHjU{A%GDmy(y9;vk^#sTjW@%l3p^|QvQ^h+V;sO{oMM(G+)!C zzXSXsA}i8Xw!q%<(@Lq|_~bh0BHlg;ZF&Dm0u_VuEY8Oj;vBPPTmj%sW|4~D!It0l zU6dg*6?rA$Zdz*K998BJ%M%VRcoET;WKSCF?bWXAy+B41xD#k@&m4RoD7Lafe294X zk$saCU+TIN@4O_vvz=n^AsE#VaH7s08e(V~jh~?Ge@vgrhMWIFI%Gu`CHM}RPX`;Z zn_kgHV&l^1cyEd%u}`U+>8oly8Ii#}`3G*`o3C77nj491cHqi$h(2s+NEc^Xy<$ zCi)CTj@(E4fn&-zj-%mc5cu)hm8k|1A1*N9MMlmx%9K`r4nE=dPIYq50|6uHfqFxP zCvfk%-hTL;KARY|G>ST12i_}3Tk#o9yX_q9!+oXBxHH7WS+WAbcdksaH9ww!Ta^N( z;{&?p*;p958SC3ENnz9qdRW7lH0)1d)q((Z!&Uv{%&Hw`yI^v{1LBL6`^*^r#$j%@ zD!3%$#pSCpK>pwLGNtibP+x6eahG$~n?vTsY44jUYl{ zZIWFI<4-Rlg_mh6V?^?0GGuC;!v@!R6|nH~cN@D{kDv7p=?g+olST zJ0*3V4-csMKwko={|P55{S_0SL8`wHp$@!?DNB`fqf4|O4Byl&?amo>a0eql0<&JJ zK>qT7IQLV~t6$KY(&)RkU{f+%$l#w_+xyOAzto19vrm9d(X>Y~mUNCX6C$ZPdmQsZ zfQnouBaldKS2GU_FA0{3XZsb1Z^86b#Spswea77>gsc%FgjW(KBtjGv>l%u(JAG?i z`afGN6G=M}0+cxN7%(F-)koa^!`G46(MBOj9w@?6z)0A^Ne3#uE^(JO)^iEp*!IuN zn(MlSnI$>LqJ?Pfm{NO7CaG@jlZQ>_CT63?PoHIoxs5}F?Wig$Vh^0tf0%GKu>utv z_i5>$5t7$58&C38>fE~Akn4d`Klg~jXVd_JeTOmyLWkG-3F0ypXnE;U%0h>@0Qm}P z_pY~Wux!i|zIb-HVvG??c;z0ybx?|C>;d@X*|Md7(E7m8+iN|9l;nQ7ORs7D3lo6rnUQ?lo=iu*vTr+O6m&d)mIriS#z~tnr zN;aVgT}~HXYQvVkye%8Fg%C?ghOXS*Ve^W$wy-_|8Hm>>v2?(SbvuEkG15#5KX2UE zuSbs9e{b$BE35(VDpI6Cw7-HAV!#Q7{g8pKf2yYgP6Hok-hLt-u-BjR@sYK;VQ*g< zIZLWM{%$MNma+|T_cz_c5ePOUCwcve9QJp$_0`oy?jJFW7G9rnl6zjgzvT;p{s5o$%Hj=MUc*HLP2d{{wR1hCJgk8kytmYS5j=Q9&s!i(9%BEkbQFW=sO2(& znr48Qqz}3bv?xO;K@t}`xSU)SIVCl5pK%K9?6sHB@T6WVT5iWb4|o$N$nzg+Seo?W ziXVEuGXavp*X~}&WmWLzV=0+;p%>dZZU`!eZ5c1`zT|yf#8wpTWeHB1SQJ_{rVCB$<9gRec4fZif1uj_1@2*vZ>WM~@B zPDYxiXB~Sy+RClWS+aBlh+qRzT-24!W77Dq5ky2j5}R+*qXw;e-xj&t(v)r9I~sfb z3pg|Azvg^7A>!={+uFo|rRiRw4xU%OW9&A$5AYUtt{LxpSi2Ju<<)ad{Mt)x`g4Q@ zpi|_IW8~EsfrGyYkrN4fE(fC7T%UgjaOSel*fAg2R506i3yGm7?C{DEt8J)O&E3Hg z-#n7Qs}Ebk%{sEsb)!BF-GbF{QK|g~#tJ)LIf}~gWSF#W z*XVimu!2JB8j|_$TlnvK4Pf&6aN{(HiY;KOojv+Y^}Xr_20NJEYG;(0Cs^^(Fq_4P z!Wj!UaiUanF(heNswS@4dudsd5WI`5#g)nC$1$@4b;tm(FEuGh=?!zk%Ydn^Lazq6feqaQ?G2z!$PpMFx1 zO=Syb1gVy#iciKX3GEuuSHHt$01(muL_#G9t*eYi=LteS<^MuY9HYT;*|deDUE})| z<76u+=8b!|a_8)!RvejpX;PRC=>2AqrD9&MJ0y+wSdVN!y4By^bR?M%qgn&O=HTuA zOi3lxDfY4Gle9mEHSgp3^ZyuNV2B~>m|7I4f{gCDv{VyZhVE%bw+fR&9r+swWZqTRQT5rn=T)kl_PppFb?v6^lqVvnTE_{b1BNaBIUG@0nDQD~nhpY@NJ_o?K>}7@&4QVt6hot>)S`S%Fq2ux1>|##X9Jh$69C_pc+84|ucH z!JC4>--iYX6c$q50nP$SMKsU`)l)KTcC@@e631ucL=r;x_v|~a+B8Bt88Pk!-tgpZ zZ6ZU|fqEV|EA9;e>Bz(yp6$NHnaS2QOVTIf9eS*7rVUs7w8rX~AbWDz$giF$0cg9* z?#1JUzRrFS%g=n}AUnJV1U-FsI|}hl9XKF3*te<1neXZDr&t<)hy2J-8lY)Cx$9oE zpu^kz4i~`;ESuedq>vrbw^JoLRPESht%pU+U+hKR5OC$>(O>NRa3S=W_7Kd5mZHix z=(Cw~2;zq=(!l{DWGsAn$FM+=SdYbYrh(HQ zYw=$WkdtnGL0^cFK-?v7rTA}Jf60oq=LuToxYg%VmTi;+n)MHe$AZwo;)l)k2foI_ zRy!~a)6IXTyrUg*d+!-#;vpR1^!nWb8ZRS|ub(R&dFf=SBE;`jN>0p2W!Xlgd-=gc z+=r9^(>}Hv{O8_eQ#mixsHTOi54pqgg66vkc>-iivt)Yw+)zIGOhYkGSE_gx9LPv~BXXgieU?OOQOsj~q5S1(v#Qayq<8Z@=-0uXtEN z?k&C+5c}9NMWjKfHu+NGYg6A#X-`FK^e5}=DkYr4!74#eyA438^Vdegnf}UtzgzZw zror%~AS1r&Z3;gA;L_;V72m&=UX)MCPE^ZyXO8{t!_N?BiMy9n5!plD)j=4Guc{Oc z5p3G*CpY5*qRS2DezqX|m@=P3h{5bl^Ju+%gEuD|2gDaO?MsSt+bU!C3FB&I5BK70 zK!h=O1b-10`_Gy}y`XC+yRNGCZL8k`x?P|R$;toY=q%i#e7-)syDTBy-BL<-x02HN zr4gl(?${+HL|SR3Te>6{q(r)#Md|Klci;WJf5LM;GuN3p=l2XkKB>7nx=o_ zs<`~t0%D?wtS2j);rg7&tc$)x?|x;n8B(#L%w+|dQ1s?$J0OVUL;mf+k;gE9+`;vr z+wNUo4`(~mgtD#oWs9gO1G_7Rf5n&Tdpey@Do6059XJ=_dd7dU9$-BR_)qU{u*yHb zwqveOM%Vki(rAaMocw9}W!!Zzea?z;&Usj72iZ9$4p&z=7`^RZvT2d(UV0-o1CA=Z ziG$(g1Rk1z;ouC$?#M!H3vmS!8sIT40y~`seK_oqiAhOON3UknS64@Lk&_BJQ&0a^ z-1D#T+zb>hG~}-NRew$=(^i`(&_<~PMm}-vK4Qz_M&-I0viwAE;%h)yqa&!i6IEJJ zFuazBI5P3r8LeBr6=p&?Sx{%C*y$IPQjto?7aFnlX`WsRni8y7N6INMXIxp^zHKkX zQV0LLgjN9arD*NTtPPY27!CapWV&{@-Jw84e8SaA*bc_U(-SZYF<}H zmPTU%s#d8$0m>L>KJC3^Pd?uPI(V(Ckh`dL`qwznQIJ&2cb!0xz~zHoJx)!V7ZGr% zPxAgqfxm=h(SG1*l~g5qXueZCKQPfMK^)#jbo$c;wv_`&9NmwG+@A>Ft(xR1m@DHG zH0z}SOr>chmXg}<3KI&6sysFv#9jhtL3fE_7+zHT+OFmnpIhlAy-4#=GLOKi>GjVG zsMgjqR#FaNE<^Ufk^VFXAaH64Sk4fxf8r91WH7)3&@(PIP@Rc$Pr{CN5KZ|pm?3Mg zK{@|LmV^^oj=&cHQHbvqUEr_>*ZZ4XBgllIH7LRo$d#WQj3*GzV%Y%1%b#M71ljMH zoEe4ndjhVDdW{&o2KdDSj8meB? zS}EL$V3%_23d#v$`s&gECmk(d&L(Al=zbE;O%c=Qn|iZeo^tPQ>>7?MIB066e16?W zeZcfOAZXf3`0Vfxe!!T@I{lRna221qwiv1S9xJN+;?$1m(R`ij-%f|S(n=z)saay5 zV=>a8vYcYw%f?2RK9ub|knt?8i(fP55{soUbxKklw7j|!y;8H%w`5mHp%#4H;hGR(Z13#)Lp6;x z(nA95PEY{VE4i6?!~bI_BLAw((6IOk)SBPct+CFYXG3p6NXH6`@IrUieS;K z!ShQ4keDW3oy>+xZxVVJA7_0$*detc*8drVqNHDL&60Cq$Bv`QkUmv<2gn;DwN-3y zs$eW9tl_`Y!@5h{ZLpdbZ2FIR;c6Aoofrfaja(1D2KIb2~xra$-1IUkZV-F(>JIC-*M}27#kc8cQX5=&i3Jja8Eb3 z{`pW*OD}T=_SF60AocoryE*u7{S_c9X9~muv{beMwRcdKCk049MfdMm!*?>7i?Z1F zwSwV)z~pr+E%o2lg({>uFh@DKbp5vPhIMl&_}}F^ zZT1n~{x_u3UTg7O6t!3Zl)2and|;3rWzo5YMchHy!8T+X?BE{Z9Kg$Gz-2HqUh3ZK z4|nfO!x3fh0NxlB{d0zIek6sG_ZZ(LME_WeN{c4-?@>%blpvP{q5 z!Kaqz!6$9=wY?Lv#@6xta0sn|cNMFrlPuDXs+iGc%pMSU4!@5!|110!K^~F)-P|zs zK!h^gm`&uTw@U_Vyt2$K9qF1Gm*E>MjHex6niV@jvnpHh;>(@^OlFgSu-=IxPuKpt zYsv7~lsgRJze4~R%k{OVmCXLpQ{O(e4i<5$;L zHE^=hrmh(X6u$jB2S#33#SA<3=fU2d+OLN7$+6(N8}y8y-*AMdJ7Zfm6CS0HVPM3I z$T9WAyjauRxxm=*mCde%AtcG}mT$yMP)EOTk%#6Xb0CcGC=kXw-Ji$n@xMc#XuKun}jxi^9NW9zs3nZSdX8mIok?UWVx^TpgV!)%E z$SVfd4_AR7<^}5j?=B9BLIQrQx&T+(KVWpu2C@()Ngm(l^+G}}Nj|m7OV@=@ucLg8 zQDcw%!@xbjH_KyszD@}NC8dKl6+jfAe9_U?o-oT$LvC`Ox)0kLA}S5+1IkxcDP344 zX!F(^oULZD%<_$8-2<+s@=U;I6$$Eqb|kD@E_$N;YvQl|phTjAO0GuU>h_22(>!qV zCRJ%&_UQ8a>JU_o+uF~A4Kcs`Sf3q2&nNX&I4dZdNnW;m+HhjCh3@9<=?!Dgmn(ox z;6u0rXZS%fk+eA?WXv@%qvw?MD{mO`9w@i*%#TRFPWb6hYQ(CY)$=5j33<4-Hh<>E z4NsA!`!(##<_E+P#Af%Tn*Oz!mDRxBq{ONQ15^osj-J+HOPHk&Jzde`-phc3XdY$F z%{(W3SMJsdu7RU-6-;44#MX$Dw6lp(<}VYOs8X0Q_{;#^jOZrV;LCmU-zPW z3J1{7;Yk%=RjcxCtWMH1%8FJdYSA%uYAs%y9G7mlxxSbf%01yOfvs<3uzeoVw)NJL z4(iDM`|Zx|HQf&!Kn`;l6S|Qd1WxQqC3@J!Q+s!{+yANpim%aW@n9I29VWXRWfRF@ zT1CUW4q5(tJM8eFWn*&kRAqsH{kVac=wLe^I^V%HOM27DX14XAvdyWntk8BW4giDS zn5MH>8LBeu68XXPv|Ke*D~f(9HCF|7411c!$dp)f=LE}E3V~|Tby@(&hHR{=^4DEF z?2aqjobEkb@}OyN$QrRg7I4(hWdp6RsK;ux=wOZ&%-KEB2nS@ z&ac1nIY_%rCo_{X$md!z4wvf;TaUrZrWJ^ZEcxORGS5o({m%x$laPM=Zu%GgZDJ9X zV=zxG&0?`aoS609EmOlY1vds)6caj(kqtOFa2du=hCE(v@$BqSYLuha!5f#4dT@&* z8-Ze=&Mw~S8fb##3gdRYlgy34=!|BH?Epg=J+g#D$fn+qs+jA8bzy&THY%&aUQ*+J zOx?MF^F!{9?&ho^F@G(0YTg$1-Cboci7G~#tOrui(m8)lkPX5A@ZXMRZdmCW|A2x@ zI+LrLLUG6bBH3BCC&YxXzDP7G`X$qmSc^qZ@c~ERk{-Tv(BCltyOp8&!wO-q{*An= zO;It84P)LhQy&qbJcc0$VqxwP_Tc~$2hNAeLyx7!<88byF+V40*}e~KP90NtS378G zNl;l?boIXUDkG5lRKE!?tZs|SyCHdG6V`pEg>RkPl#VwcE_XGV$M4Do`gy;Tx0X8b z_*hM<`h>_pVlQXEwU2m~x=$Z=*$96gldrZW-nC;d1oY`8+7QPL*~{E51on&o%T>RW zUy;SRX92U>EaYOoM+2k|L3Jx3g;3;DCvo&MA8vQWYzsl)JJFSvmT)3>)%?KkDw-D= z^MKCSe{h&5SlC-r>~2I8^~V6RV2Q}Mr`<&5w7M0tkG>LkKTU&wL)2Hz161Sq>xkp) z>+6yGmYu^X--&JWg;mehIJdZ+jhF1n`Om%{UKL3?CSuk#v+t$At$X%gESp=+h6cY7lohwJu3Va~dL@>f z3D_N7ZmIydH z{Sr_9U%AT5O1|sF%?$;Yj=;Vg@OZqF` z;XYR`a%n&jd@80(_|uP5>0D@L0I>5c?7)9!_S*t9gKH@}Gu{(sD|D8y!^lI8M^d#abDMklH)-t3#@( zD=M6eZ92Vd4A;qk;BT6*F&(2gfnPwVMi;>OJ6h|fB&KOfa~CYsg9d7(3fX~W+7T?h zZ~A@g?l+k9yg&O0a?cUp`;)X@H`$Ih`=H9mv<}nc^x&48d8>kn{>`dBTHDtM?2I4;wBN@k?5SwwSe`Uq}ZoN~AH= zqbUX6w%FciRo|>|!1B0Syq~T_J*cBTeS;iUBXf$;Vx5AnMy*N?vN&*x!s{ED7TC)* zBw7eabh4*l?SQdNS9x9a_w9RE+=^|Z&q))17X>{FLe*b`*4ujDK$D*2dqTNCw;Uf} zIKu1Ib3fd!)-fy`?Z4v4;B{O=9l?&pji+tJIB_eMBD*`RGNfXwXNABG6O*vS=D-LvjNQ3UEK zkLY50Z83QoH9Z13q&t|Jo#rA*nffbO95}FXaR6}wCBr}|y2Oty%`COU>$|W!v<4l{{ZAfb&-`Aj{W^SVsleCd}l1id`O$tY}aq;~PjZLncg7fm(-fL99MSp=rF$!u(^| zgxF}%6W?IjZk?3q5dT<(L1bo*ITnJUf|#^C zNw1)Pu4z4oyuiczDPZG@M}G+fnh$~$v&#fN$xLVAGu>V{**@+NcHzV-0t8ef9hMG$ z;qalTgQW_e;DU4rf-=tOtt;W44eHcI64DLtL}iS-AC}V=av78k1< zzsb8*!5d}(K_L1Ddy8Cs&_wx~ptL;1#l#D03bb3JK>6G}`8w0jhJFT1+IIB>(T>=sJAK$AJ7Yf10Z&YST$`#!! zkbarkJp388r;hRYQb(=qLfSf|$!fc=A98Xc)vCLSG3okk_Cf4tr&$ym`!v=jG5P+P zXIV4Km45$tRz*K@aZfx{Sv**aU0a{%{pY&3KFeD={byQer*wCI09oc@WTc2r@@}~e zt3UXQE}Z3Lnpq644hWw4+3o+B21NsGj7Sv5RD^*$0Fhsgwfc{MCij!Uw#(;D$()+Y z+jIEQXl5GE9mcqDdN@_k3yMs@<>H{6kr3?cUj|8T4RP!W2#H~GfV-?W)-7`;4Vi?W zK%WXV&3)VsjvZG&C{H3P`oaqHSnIfZef z@6VIH@hU$**e-V;P9KFyJ#MlhxGfMdW~w@zN(FVUN*ZPgeC}VAgJVz{%P5g(iVYV# zlBQWzJp+bl<_3T9A79F^Pv`AieeiWV`RH<_&?F!oGq?S9 zw-`BdD>~~*LhOJo#sw;`CA>uV8reoE$rU^GP0nB_0U8@Mi{mO7;~3f~Wgx&0#iE zxrZ6J{N`e#dyMvsF-`&MP@sLD-})jVJC@1*`Pb85hPcCTde_1Ii>mNcWU86@6JZ~D z-7HiCFXAOG_JQZI(=VAnUN-Q%6T8fB)`C5#)#I@Ii(H7!w_?clxctNcEIm=|RUwI; zWj|<#(;-EJgIgapJZx#+7oqa!Bb^KD7I^n?s*dSzKGXhJiBj!`n}OxyX?^;pJS<{Y zDX*=hU2!mQ(hM3O{{h!HcxIv#W6fORGVb{HzAZ5KByXb`7ztdz%@dF8JY230xYm+h zq_T-{w3VzdCXq^Ttzic1nj5^vi^kcwb{s#xuyDGbQ}mgQv1zUUD6+HJmO5>vvy|rBz%pAwOK3QlBAl)1qR}Tti zO;HlcD)`nU{lA1KhZqQ``@-YvOU%SRXLqBVa342gn`_0?ke!Sf3BMgyN-7f)+_QN& z4pwNugsh1TeBRgq@y|1eCDEO+fJe0MnZhB~S^Byk10dxy%Qb@wne5A7i>rlmf<@KE z7u4CBP8*Xp;Z{jCo|bAFJI*|ra{(eX_0+LuuU%$Wdc=2j+JrRYQoIl2D{thjf9Gt# zL^@zfb8v%7jKdcQ?)9h0^_+37H5oy&@^nf{5^J*;708*Fz0J*f=|~UEr$S)z6(?Fx zSAUADz?agG2AYb?c{4_4w9TA(t{7z>mQn~50j+RkoPv~W&c`XWe=xl)fUJ4P-;4t} z=9CvUqFI}v>nbXo6*8%!Xw4ikQ^?xaJ;$0UE7KHb>TgzqM0xaEr=L}nT(QV;1DCK| z9_=sIAJ{jE>qbH>eZ*fA8{K9mTo7ZG8Z?P!bxpvMlYw4=Ecjoo$A^6d?zSzK>@}9B z=?uOk;hxpq5dXbp49LE5E+>A!?Vc{Ylj$L*MSjXNm&j59;DHIxq#N;;doOn3BLG=63t!OF$+0(|3uOi_2rtn>_@({XbpYPQHBb`I3@*)x<6$E1EYY5eG zH3rBs^Rh0Jk3L<>-n9Jmw(+;ZPOrzGeVy=}mr zl$S~(!0O9t@Acw)IY5>wd-C`XdvIVm4>P);(C;}5^+5n~Tkv~GL@ zWWg|R&)~=TXCKjh@c?a>1Q%ogQ4{VAg9pxkpeH4fFrhh^&_4gdpCzP=2XepnvT^LV z8Yi0iM`q49a*ELuY^e zo!k3g2+8?5Ne?Sj3V{E?Y;Cv3zZ=Wk-;D&DaY(iiJ%wy2zM*5wkL%$*8;qOEk2JAp zjtj-G_kkk)AO7)RaZ>Jwv4pRmPzP4SjA23F!?&xn>%z`&?k}YS_9f&o- zJyH&2k(eQoIDfpLycBMewW1v(GwX|T3pc73fe;++p!ale3OzohZ0I7cf8#Ojoe8XQXASXLf1>d#H>TnC#fE3t)Ed4=%8wzOni7joG+%lg+=PYDWYwJ z1+i@&P>@aAbtHaWa{s!RLX9r_@Lra8%X`DJ&O4B?FNK+&AdFure@w}7fmuj%_`f}j z4x}uU|o)=rw0`SiJ$4-m9$I!S%&U! zx2oqegY&x(B1%2{MTo$T-mod|rBDaA6o8IfkDSit(?8tq%ScXc5M?CDl*blTX?i+ch1ICC~FFGe;P6~b9M?acFS)p!AcM{I=gS80a8^NTvAKR z#?H;LefHBJ=~+)eN6LY@3onf^ zva#}+Y6jCglnOP=M}{yLREBe&npxITY?Le>l+Svk9T*8BW$1H}V( zZZfFL3d*aqxg(AWY)jtrp4Z0d9qn$OoO{kEqL-kbzD3p8XOcs`kMTG12`fZLJ!7?P zX{jF6KNKx@<-5L6ZeY6!Llyb$p|qiI34O#=wrvew{ne+H_4vfa^MfsA z5>JaxZ6?e{&+bN65bw#t%8mFCnu7@L3?v256Uz|csY3;p`dQTFQC-KJZ0KDphJ9|sfIGufvb;M;)wCa1w~hTU^8 z66yt(=~xeMhzbYP^JIh-tH{0WEsSHKyX}XUBD4$6&Xpb>r<5Sr9hRB_@opYEY5d5} zXGv_>E_0VkiJvkNtO|}cWC~;Kg@sw6d^30l!L6IjNj4i)HieREY@?#x@% zOoyM+w(XB`zF*FeDx8f-x;Geill(8P?}@ZPVyBvt$6wCr$2<8G7Z_)b z5mrgj#*_L~J2<^T%>{An_*#6d=HP&IRbZDoh$bGtro)*k2lpfE-A8Ow{4zYs6nztXN?RNmkl0N$tlg~DyNq8 zHMNz#C2E`3)fS>s$kgHy{HQ^lN~3W3?x{k6yB9qi7Txy-u=+fOdHw0{tq@HnTV3jJ z7IP2LvjN3D3fiV7Y;4N}JwsKHX>Gb56mm41iFqNDg~@u(o1iebej;||(=4>uM)m4n zm0pj#6e!#)5XeETL5aE4Q)^?bc(&Kpp$o*6xwvzBqdiWi&;>p-alIWme~>`(MP8MC zi_W2~+;2aAvP;Xaj_X3b0u>_D_??{XN&1q$E6ADDu{@?ZJD{m%5rPekuMjKO8{WTEljNfSuzzU1`^3KfEA~12|_9rU!D9oMN z#Ol1YX4+cLge;zw_E~@wiVFqPwydqdt&nsl8DCK4q)Jxj5WM^SVsXs#tl@v*JUG@R zFr-P4m}@jkTY=X4rp2V7x{3h!{4%Kl=rktl2|k$00W>|-4rV(7*0hXFPyIZV1nUoI z%%_8)c|n8BG0KmvEXI}t8~%TtNj;c3dK*ch{-d2&nlJOY^C%qi_yfn~O1mx^{!oO{ zpT7#~>ak8)Bl@v+=*=tv!St>sD>Bu-`>=;H8=&k z=r^)kzg<5h7wXyPMih2f4f|Q%1eHnydAX=3v(pL92mj_- zG;w1;1&Lf@I_8F11bXMv%o zJBQH8=J8#{Y1>zFCcHcJ9kA4ZSh0_4k{qwd7x4_bKrbOHfuZ*(3f$sxI^D+e0QzA` z$;kt)=p&-}6_Ui>%KXl^7&T8E`H0mOP9(dW&)?iBpYxy=yHoazNw7QHV)P00b>)7`nv_`wOqy=Nw`cxIwG%`6_Z2#-L@52S#dRJ6L=RA zc8+a2Mz;6$bcl0-Usmg9|HZ#1?wS3@NUoOBL8&8n$5*n>JwCHP&K{1Wp`i>vNXR=g zScO|(ZoaBY<9tN7#>k$&1|k;V=dJep)@1R_w1mr*@?y;km)Vqw+NSYfL@u_6pGO4c zdBZ^*&(Ex$0(I>j1&i!{ zXH!^c^_{K5s{ejG+(>3>rj=FM)w(s2vv4Id;yp2Oyf~e3Y~JApf{w~)e=%^9J;a#* zSx`QD?9KiLKt0r?II=MbmXgk{_kQH4n7vvK>lC?rUnAc4QvuI5pbAOIK)w)=ABSo+#G&}yZq8ZBSL-JKyO?_$U!4inp-yni-X2T%&4H| zWvs9Bmjg|1f@eQpf7*ThuW?4d2}60=;ezs+DQ&p&dG1P0;REQD3%#xys{6VS!1oLh zp!sR^IIaVaLO{ABwzM?pk`d!-8(oWgAHuvyze{?jQ1yGzdomwTwSw=qzq~fZYNIF? zE6bnIEN}6egWn-59b+#Zv{v%q@Gf(+l9$bqVL;;60 z@ak|_taTHA3H$tre@A}+@~~4j0d{UmefNXyV-y(Ii6*_hwpX0E4m5wr@s>VJT0eYP zj^#zv7qw=kBZQ30v4S5tOVhdLa(juo4k!Qg%j|qnw`qPSv1$>FLgf8+GX!~9Aup`& zKQ-W4+wEZZxb626)OJ^!Qo0s{%l2QOjmBk|@xwORAZIpJW=SwV;uQT(*O9l+eo zqIV#^C`eY70{{TC%3+Bs%H5$g0Eq<;7{tDC5!JgdKQ?u3CYAMn;(5G(we&;Y>**dK ze3EoCNZsNi+EZpk4MVO>Smo+ddt5PbabwxuJW(4vWY{!g8-h2)vR&0c`8N$2hy!=Y z#K#T}Alj~drm>BkuWt*}Q3F&}(3hvuAR1 z?+m=Nn}M_0p!&VOGpBp(@6-z^#y?R{xLCsh8W1WGT$-PO*@xPXz@RPawEvWB<%5W{ zCgiK7d>tJ)UFx6QC%<_iCWy2P+vMyuAjr{0vfbp7ogerLx3}n?#psZj2yoSV!hJnm z+u=l93qX4sFd|#-?j)ig913P-1+LuOzQjqLAo09v?$c*(g zy#&D)ujVl%LLWe~c33b#p30K(x?r*FH~zFUJ4azwgfCyT3#o$Mf#!o4T&Ei+bo7;O zlccWoM14r(za&vKO1z@Sv?*d8_h=Bc`Iqx?Nk_M+{RP6i0$(Ms9nf(fa#F<4b3H^k5(wU$StyruG!9)DD%(nqw}4?T4^OdCPpY6!f;sPP(L$tsen5H}mql zLD200+P}od8e1`VBQK0R1SMgghO`rHMYF;IFs`NeB1a&mFgX{;tHEi|o{VS^Avwk3 z{K9a*aiF4Z;AF5nmsxLj1So&{zX*XO&13oU-c6hk%lgyazkO_cgr8%5ZUuV=$m?1H z(vkr-j5+U^v$Yfz#ur{)iBB5Ax<6v@ebvP7DVS z6*GOAnO3h%To_;zrB_K4Ja1bdtDCz-Vl#d=blHI4L>{}}U?HaJ?T6?R>tfDd6S`fk z;>(0V8y|;{VpdcZ###20x86+bo~K@}e#e~JIqbt&@7QC-^$J?|GxgWv5$;YpX~@LM z7_Z}xINB;;hZ5GX4`EU+b;(=|%~c?qGG9)_RXze%1 zfSHL(v^#vC-}?H|7)w_ip+ZHLCcz5FIe(P9b*3!*DI_r2L}A#&jZ0hFCnN>U%yBzoWjIZ|8#wQNFc1)_)X zpN5XQnmc-zXOg7h!9gCBs^4jBv2BgnI>m@y9k(jSgt(@Y;z=BY7G{)Y7YlAJwKVi>N78Vz#$)7 zvZ=>98CuTMub&M6LYN@Hob)wdC0MwmZIw5-W+(cU8-qX;{MVKT! z{GDp-+%(ei#YsS)T<_T})Hcf-P?v3;1T*q_G*G6s4KQ;UG-rDMN;w?h0#E{pcb{xK z?mG*YNFk`f>keZ`VK+!6cy;|hm)i5Bw7&R%5@>$u>lg*TsHuXMY^^#}PSZB0%Fo}k zAX>i$}$Y;XYg47_qR$^@lE6nlh20ZOFvi4)&9T9w1Iv754q)(Fh= zkVE=bkCEjaA`S@&qCl&=86etExs{0n?Y?Z6mHl$enS#qik9<6p4IZ7|M%fK;IOmEk{6gr6|bNQl2=&Lwd+Z1l@h<*K{-zV z3#yG}GeAnXXKmkfo|v(~%9avok@qv(zuVgqsTCsSl7+ZA-{BGDyIYqcl+->Z#|N*h zRb~6lU3o6GQc-{H`U0-D%CoMNcX?}JuW_y#egrxYGm{e7s;1y?Z{%-J`{V5s1O}gREq%V&&E5C121oZNIhd z?5A`Uhr05UdNX}@?C-04V4HL-R3iwra`m!z^)QFdW5wK>w?Hm0{pQ?|vf@qe%>6F+ zJkSu+^Kf14ktuRB5>Ktz`z^or*yvPJMM?BM>tue_6@CYQEl~U2#Z=-aD~2fmTMicViP&6Qq)F&DaCjF=<-50q|M`prie;uCa>H{`rQyr!Ape zHJ`okf1{(6W55})FB!dTK>HxZ%WQD$`E!=eqwKLg$4WtK(zd*l(7i?CZhs%>bZo+f z&4_ruN;^uQc5d2WuTGT&xbbC8DmO;cLSU;+0&J`1Cr>{GqR%Tw2xVsjzokt$D-c=I zPjv&@)zOR+pe>$a!}Oz4keT86z$5Bt8vZ`c9cn${joEM+d33)Em(6g1PZK4J)cvHVs>?wm_ON0U zT8q=;TmwHn4c;q|X9!ay0W{})c)&5jS}&5~?3pxO(T!YzpG(!y-NrNZ*b1Ycxw>3M z1GvjLR$=8cgZPXRg0D4$;t}$qqC@K@kJj!VB2pIk71w=m8^~>J=sGY-;!QT2TaSvh z7+$|7XPvyIN9)cZkG)PjwWY5x$`u|=`U&d~oR8MlRO8*g{2(Is$XV#LLs1kzGvA5m zzs3Av({t&Vg%PqYBZjuEK#jJhQq6oFRysQE#e`P4ohp+W~VkN5nit1FpA2_@#=<9{EAMhs~N%UeLf;VRE0ru!4 zU<#~lxJ4C|BdJ8gbJjz|Pdoo8EHK9$PKNL`G~JN zAZSX`!##=Pcd|E7`rYp|K`^kXB$VChS82Po6uugj=>b(qqfRVTb#E$M3pYgGT0j&V z8Xh}buO2i$*wxP1BZT>eLMMA%A}Zs`0iV(>bOxU*JN)0>CJZA{+2u|U4THG#mflE7 z7!OWm^}dktdt&uEBKjlzEIBW~s4cxO@_#E_m>RP4cKZN@X6O%V9@VPh6ZQ(lIANkg zE!=2iSIgwr5OWM0VzkztTH%OJKnyQ*rP^EqV-h@IfbgdN{@=$U+5UYWthBU$3PRbN zdWpC*&$x=Py5|8=!~yGuR0HNGtMOV4*Q}x!fgC`$=j{O(s}gdkvCXDmx*vtYj*mCO z-5i~q%gg1UyN9|=0O}2Yk-dU&Gd2uh3s$%1+83nU2#Z?YbGo1)ufbSEq@2Sx8P45( zuK1m-NJe3Tdp6KZn^nn2`%h{dz>z&K)ZF|pHH*s6p3x`VZtpFTZ9##v#%zF^V+w-k zh5UFV$B2dmZ{k>=GiDx$id8?0-@F!1d(5TiExxnH*xZ@`Q1 z6hll2mI-qKYu8<+O?H!^$Mv@FylXlGppkLIG{puqX9etRN7>h%6`0iBfCivXD}w`mLi@*)|BA0J2d27URaZSPIabYI9=s+zjTO2}d{aRJ^M-6bv5vj32ib{hrhh z_)*oe48AXAR^PvRdH1bXgY~1ktPkn%yJ+?J3*>#Mo}H!EmzMIfQ?faxk22_cE^Eec zuk$VUDT+ZPu$QzaK|E~FYQYzo2A!CLhH-!_0vCAqmegk+b@ww+wkuc)gSfmjf8Gpq_mpyob+j4ide#SqEwK~5$_@2#T}IR( z2`=AYS@e7FHm~@cC*f6t&Qh)5QX#nUnx4GW7x*EG0Q5r~Vgq}Zq+(Hc#gxVZp|-1d z+42PqDC9Zj8vr@M-7DsA-x<8<%hEPczjJK3$xr4b?IQa~x zkZYDdIAmuzNN_efBDARq4~!OPs<7DgA2a{Yy!|+xdq3pK`5D|dH2^v+Bk*& zr#s4_orz2$`-+eWBE#&{yDQQd%**Iw9Xnr%C{y2LhTc*GkDFiAq%qw+l;PVK;NZA_g5I&essI=K1Gq zR>?vYLIRzh)i3an6j72`sZ8jiYx!ib{Rci~91_mBb8^cGY3u9*G$*%ioVINJMeQKoihK5&R} zTPpbtM~Z1&4vN6w8hhf65Y%=DXdf^mOqLw}fdc7BpdI@+yKBv{B8i3kqR+x=Z!eA| zmo7W?a}RwC#9ROQ7%G^CY)8)0HnItM&W**t+z!bPO!&G>W_Z?8vb<>xF2(z$p zOJtWO1GLaDehh83#)dsN1CaGLfc#~?^>u3z{6WIm82xF72a&r>gn;xdlG6rS2m77+ zpFiJL{)VcW)!hN{V{~h{2yiD4V2XfMEmDu(xiCHll7Er49k|%7t#R(?`1yh1)zz^o z{{~&mgx-Mdz0S2=cSC^Q{V!lp9I6 zOR6|#`m3TADG*1Lg_TR(f1^YbZ=K$*(EJ}nFgxtP*2=rQ~#7u@yi6j)1jE$ z+{r=fSGYOdfGHeD5mqp|)9!NX^A}Mo8+B8f8S=e*Viqg?pOYW?`t9B8N?ubkqDiE8 z4B^QaeN$oF4fL-6X`}XHm)U|K;4rIkHsccXq1(il1!G2WFA}iAhK1-_&PY=cV%%+W zlJm$QW)+unh+wj+0!%sD`(|rfKYwVnGY}T@7mIa|cw_vt$$M6aG2kCa(@zhvFZ){v z=;8jO>K{a25}c(w+x$i>a;l7<)kPY$L2tAHOSk7|1>LoE0O8_JA5wcx=xTo5xDfr; z?BgeQr%xRWwrXJ*YbH^CjefIJI>7;J_dZ74NXzs;dvA?TqoX{ax;`_)gn3O{NsLS5 zXl8Z76{`Hx^Pqn6_7F7wbT*%HynUsIl)L|+F4Ox<0~an6!YCi&94q?FeXsG$z*mza zB@tSzy3*=C?jJ;4Z8ouLuJg0_6Y@E(B2sd~_>LqI-9#4*VxA+b^d{-)l0KaF-|ru# z>;)$6wWYUQ%8*CJC6TMVqAeEOtEM(G$I4(^SK3obc8;{Af5>Q?3n z_m;P*VJgJW(QKX5Zok&Qyaa~4A9w|rr7yvx$F3v73N}zXOT6u(>D7&eKIbRR0-c5~ z5&oRSuRi`+D<>5d_mS-t0z1JznkEUqAv|2~4}O#Df9m*p&+*UVO-x4#hVh6u@SaWj z8juFIEFTE02QC1O%e^ig+gz9wNrQ95ECWE)=7>6(McId;4B3RPfr7 z2xfM4;38-mg$jN;S%qB57?Y~c-OyQF)m%ov1WL z9`oz(j$6~kY5ZrvZ<@Qmw;gu60|@8K%G49 zI#!3(%a6Sy;brevZ~FQ3bKE(Na0_pu0zrT#9`rAX@pD(wjmr>s_Jq_JxwCBLPWzB` zU0L6X`p$e|JWnG^zwbi015J+QwY}@Iub8#po5$dBY49@`f3Q{ra)&d%X?dW}@S3S7 zhT0NRb3{H1%Ur-W_$Bxpdvvsld&Fx@`?ccrD1O5gHW~CwbO)2H*Oo8@Mp|My_&*7?I&QFD7B04oo68UgsuPP}O; zUHI9;Q&t((gX1R4zQxzV^7$J0gE(cM>UWkT@JH%DNpW1d?&Ix8wKb7LT-PzEI5#3% zh~Rs-z()*hKU2+5=Fpq;tM>;OS6_u#6c~cYee*sNd8+%^-54Ev}SuE^?#TRJ_%_WP| zzLdJK?xje8j;D>u2Syh|qQ^B&7Si!B%XX(!<(r>a*#N`%Pewfmc-`_iU9A$l0MTHv zc>E|`*VvKRfC%`=@bDL_%vb_L_Xv{oC0N_wD3DcuT8 zKpGi6U}OLNfA7f-cCsD3_w(HMeO;f6U^V#nEl;@aOZV7272PNQY3R^?u8(S3_sQq7 zWYPZu%-E9Noi-dDm>^a8z_Gr`dwPR+C-%b-@qG z5cdc8aV{|~(L$##1k`s30mE4NdH*6I70A%K&fMMIHB(2(-4)WpoE+rjC`qX^>Kvd?$ry2Sp_qIQ<{h)@7uU)88h zRBn=ZRXs?&>h}j`%(rBR)lpy5tMp}~$3TzWe(|Isg_LH5Tvd)fT>nE>wq0`xAu*&& zh7&dNo+$Ky{&`!n0d$AKVQFVxHa?7kfxP&zx*^Ve?a83!Ic?*n)N5y|oicIU$>{QO z!7oB0O}PKnfIjenICPr@w3Yl{OPvMxH^Q0);Lv#vywowzepTH*R;d359u&$ks0c2AWHWdoagYZ{e7cJeX8RZd~=rcpb0xv`hj>r0sYp@dT| z5pQ)S86-MoThZpW{r97UlWbeVhW{Eu*>8J1l{71&n`pv8lT;HRJWCOE=Y+PG^u%tk zFlx|7K$C~CI+?g~kYN7j$!58yrn`{b$Bq2=vyRN9rO@2}_5S{ClJA@4G|^)up-U_B zDp;o7JHy-*SA=*WBA%M7;~9oq-#54InoLRd55v~kdoPrM&q?cFpzh!=xbq9`shx$_ zz_MiS_~BsU;TM)q9af>J%+4QFs{B)yp?_0(sUFqehDIjq2&>C_d=CR`?vW~#$;e1W z47_>n5o2}4q%WVFGIWB=*je=>fN-C)?Q2+TzLNplsxZS=GMulx3bKt*RX4BS{iyA4 zX#$JZ(V}^x5#)xwy51l~y&$#926_bX2H)B9u~HiraH-*TkcNm(6W!e*D{65yEFsmL%dB;}^>3GQK6ODZ)mz}0ExN2+7`S+yue6I`fNF=Jt zcfDCEQyX|wF5i3E8pe%9sS?_>W5Pc!4#1spUs(?Ns8QAs=LT>%dE7``$?*dy_UyXD z=i=;FV|ix|C7eI}pnuF76&h#yH9c8`YNgT zKWAr9X(^*eu*_I8x@sk8;MVVuYrE5ySu#{R3$oO5_JLu4#PIWcR?9W9hiy#TqY4nu z&O*+n_nO8-utbn*{4k32(tE5l%JasxxmLB7%nEc;z(G)+Y6GJkn( znCkFI*F#E7La?tV!+T=k%k({*!0p_p$i;+d=Mt8vr5G@W1qUJx&>Ggz66iqbAd3vk zBZ`WzpdyhrEdXK)d2SVMzL-DNW4|jn{uv%#HI*Pq_p@HpS1{f@W*wuy&BZ1tYt*woxYO4`u#mbphJ`>~H0`=ei90)A z>&rCB-4|gq@Qv#)cX{wswAjGlg+cLtBrn4;Be?NJ$l}^Exa(5UgqyLN2Am<9`ESjXeVqLw!bsk@G|;9f7MuHn1e)u-^!>j=B%cI) zmVvP8r$_XO}LsOztITSXI%=~2=uZtH8#-JU&A(LSrDf45ExwE(_1r|hX=ZmV(|Q)uMb5|a{zXD%l8fLI%L&< zT$Qwlf+R|qFXHW65&^IQu*jke!o=gQ_TZbe9D?Y_>3|*nDi1~Z#`xEy1QZiC_&s&Z zdy?v{%i0j#XurutHx3YBU5G{;#ku`39A_7}|JOk1K8>h7&2NDcU72^bovr;Z0*D;~ zePwA*WgR_S#3SbANKZMODa5uIj7?^X`J&qYW+-U5NqYJfUWct6(B3v#20jzsQ(-8? z!JWi!MdjRsVRG=zi(5&h3BDOiy@iQo*qlB$K0tWqd8Ocwt8mb#+%2uMhNQQI_=9hK zvk1y{-&m-C%r>PX*vy<5e}X&g{Tn#rA}x}Z{zO&#BsVwrwAJ&M$r@=;Dg$DCzo5Wk zAULD}{=+B#C+b;=c+go*!xpLK^jxI3f%va3+M;}|{P{)S?97--3s#5#%5(V8fW%QC zRwDEdmd>QgZ&)y>o%>l}pmJ_5`&_es5CPaRRv-MZM4%|5dm?Udg+#^iZqDB=1)1)5 zivvs?AmIjVX`km6Xgh!5;3`+)>{*tJ=l!R@IupJ-q(q0t2q37~JW9X3a*5Gc*qg7| zvhdmets7jiyHUL!TGXlTT1So-Fm1z-e@H31UIq=lhFeh>T5J zkn17;57vxpIa$XL3(nAe8JTm<#vvZBnfnuwwWbN=;T6sMN?}DLJ+(7Gid$ZUThG!C z{Z9EuGC}Yk9T+%(y?qV}4Wl(dD)xzF4ljL}i8;?1hb2mx&L(b{u@`^&RXz^$7WDl_ z*M7VSl^DGf9I^-B`p$m+&zD26u1C-T{^!QVl$)KRo1J|^heO!v z@xb`M~p`GwwfkJl#AWgy=MNOMCCgIEdaDt(bh!Pms|_)!H%p z&d#W<@jzu&$++RgyX>bBKjF)AY{Y%LJn-8mDNo-yx#g-^r(BrU+#Ioc?cZEpU2XG6 z6e`gpt~jFXW#8W%1*xd2sTJPz;xF)1s>HicKd?7s-}yoz5QY5p&-LZMo3+kW+^ZPw zdj2O&#;z|1pXHkB8X!)R8F8Y_w3L;el<05+^-po3EH%d;3aXv65Ab% z#koJuVX$|@#kZ`L_a@93H45hkv${{u1lSQyb!^X66a;<;@g-;p=q!Qm^3iXr4#V`} z)B){DJc(B-e>Vf=o;ZaV-I37Avwn!GlD-4esKKrsZ7so+gBD!x{B&1w1rA-AigGY#{=&-sc*3yH$(MYwjrwjVxPs)r6=@ZP z@A~myFDKPz#D4|1GfeciQwLa|UDOi`nKGGKbMpN}G*Pp=`^DG1sJK5qHS5d!_>!HY zh9fyG$t0&M!;9r>yvBR9qsY^0bw*HfXE6I~eD~<&w%7F^Mj~TW^e7Jt7?n@-Wg?*0 zNBrw(C~xKurt;orIPKC1Qm>AxEhzc+uFaFP=Dm)xD@N9dY~JC9`gB|o8wbY^EN&Ga z_K--uLFa;42LPifbc-u06t9kw;k%?I#vi9o>z$>ZbtXD)je1pnk*OIGps(eZ${_Km zK7T%p?22ivFcdV+QiaeJcN|v>L9gr924brp=Mwv|@56>gU^!V0wBDA|+n#kAUcS^z z?4>q`|CF@-kyz_zS$0kDS0tuNG%V)YKePEBS!0v3!NL4zz3FW;JvIkN>36d2t(d{a zsElSOrB-qWH+S-?poeDR;<#b6-0qvJdLu~GQ-NV4l>97SrcI{6NaR{wT3vk{LO|cs zR%Qqn_d%D#K!jWWF+~#o+zY>|eCrmP{46DEH)8awT0kxL7L3`WNX~LEdX$2|-#8CnrLw2B_ zO@r3;GwsWY-~vsZBwUgyNMdjPFVHhhi@M%FZ8xhXEjkL z{ahntMr#AlWLlqk-wA9l<3;3e41WL%r3j;TlwRsSP$()vVjbmMYRCKI5fF zZj9(bLfo*cNgZwjQBNKhH-IC20ZoYE>U87_T4!H1++;c)?7`*JhQZLgKig)R!F?5i>pz(bTi|HYap)S+&!)RC?-k^g%BrLB4(meTgm zExnMVXR_6JnOt7gUInz+VBs7cpN9H1RV>v|vM~$wr0ThZn#xsewZWj>-R$1+7}Y#$ zAH`n?Fg}G3WHHaB8xKufV$C)z|D@9W$WT2G82YE)3Wu+&SB*a>-`@>+x>VytgP*)7 zn-1J!VbLa12tIYSx+Ln7L0}&U0`>z0ZTxwnSLiy;8C@x@y2_RBQBZ(KDujfzvhi~o zLj5+fu+u(CH>}Q*kw+G}?(0*axSe^63dt9pz(OKy(G{s<*-psHU9b45YRu}`n0I$U zH9}&L3vrQl{P#VHbi*~j`s%wAN{l!IBIrC}1)J+i=WotCV@6!%mlf|av?%z@;RV?7 zA2*K&?fz;%rJr4EfSp^~`ZOihi?G)kgA!D_N(tpT&*dm(w+A4`+&N2~ZAZ}1p8GZ- z*nvw6e!!GaUyN$#9GqXj&XGkBhHg=bY`|)mAUIWsL-eqyYe5Ul#4E%~JdvK7a3_D` zKO4IX9XnG-O>2w1IFyvBS8+(qP1a|(SbYMi=cV(-j+1CvC64%k6m8;^Fl&_v2fq|g zT|{g|+Y{PXSB)>k`oDd#uD+2lQucYtBznZpf5+JWfYu?U;YaO6z)8i-yy4H#SJ&rD zy>So1OdrCS&2hC)@NQ!$K?aA(qKB!@Fmjk%JcNlHweZQwO`EKp0fKs|1@?Xjbn-cV z*{ko|RYOP6?witzt4n>JQkKcM@&8LoHcfj^wgu7r{@-lk*+Tsfkhp<5()z5Itd23YZP~zSwX_)o(yG*xteZMFj5<&^gzjE^0uvFtknuG{pMcfh# zz@_-Ck})Wf%CC?TeJPzwM+kA-7#s}#S9!g|HH$M_{Mb>HT(#d;A`raTk1!H3m2%oY zbUB~BG%sUt1NHdSO)T4<;n~{N|)cC4!BPPa_bZaxTw=A5_ULUWnr+0 z?OWiOJrT%16F|)O-kcfC+rz{19E~i_urp?u=W$xwmD@3Kd)Y5O&9=5VjC#sXuYGVN z$@YB3tJeVUU(J_6W#3v zS#6(alY4!NnI;t4+-^zs^P&!RAXp-e`k`s`1tOGzdeGfFKV&EI24gK~n(6ik{wn$qz=G)0|Ju zXEy!7m(YfNv`~Fo#D|D!8b^op&B_m- z-fS!D;>SEK&bq#hy$=rKq$Zd#%GdI?x^E#?aw?5zS%=lcE>oXA% zCK{Q%MxPv`9cAyyw8;LF>scNlS~l4kRVF@Dq&(fcP)-t6ykekkEbIEw5{Jri>Lm_c z52yC)c$o*9O{F`3>8Wii_ThwtLxMOUf6MY}T#OD~wFfSOEMm_;+x^z$r8+;=>M{6F z6}r;^GtzQU!hi0-2-YZ7;W`3V#LD;1)ywWUj;KPKuAql{!B(mkjIr&jzcCZH-w+jL z2 z*6s@1LNXpt73~62@qkG@VQG}jn35#`3e==S20skD&VL0V37LVGJe@HYF$3|Ks&jO# z#Rp;ogqS+Hxaz7ufojQbBTiWV^!iDRUsFAs{m6=t22~CUj5Od6RkHH&JW9bzs>pxu z<%wSm+x74a1yJOESe+8t=xVP{Ch2@e6bum3lRo{$wJsZ7Jk^5LupIoVe$+BXf@!|G ztE{3-lWxYBsiEn~Jl9m^z+qU(o%&Gi^X0le`7@Kb2V5pBC`13inBH{--A%JQXb)!f zny+vz2*Y*d`iBD}+Ze#|O7vR2yxC$7YzE=c-tFak^V>=WLBJ;M(S|$Ki)?^*Ba}8T zy}yWj4m`BRhbVA#l5mNeSeJ%^<#}$`w4Q~zrJs=3ZfCw_I8u1OxhZN#?JZtcUq%0E z>2_D%!Hg(7NXUZj8KR4S`{xM*1rq~W)A1ZczO$$XzDLQw@1hsNi`cw!x9MynZTS>C z1M!|e(UOXiW~mHHPd?}tf2jZQYjCrcRALj6lP)7h@7QH{(}*v9ESo)O#3lk>(IkBu zHt53$38xMc03Do;vQ@+9%<%p@P&dHO<`KYmrfxtFXamF3l}0WjAb2S~!mE4srp4|; zR27emXv4enn}noSZcx9O2%Pqhm6LzHb*@AM4Y=2q|7~qw#pLK)hr0ICcw={EdI9|3 zk{_IMNGjtaqomOnx&MOFBg$DZFb4MTR^bnZ>ob|2EjwW-u)%)(Ik5}`&odm~8lZG* zB2QV_XiRKvC*^kB(DEmh#-OytBK^U^&g%|Bj z$_%05&3|%9j%8I9oX8u4QHOF(SKs5^rh@Sifz@*WH!Jek7x2H3M-3nh@eX?ifXzx^ zVxokcAB6nUsjg=9(wc=u}abb{LWA4@B%LbJ#-{>2k^bjyh&rz=VtiJXkdsu5c^w! zFp1%3e=Ns3nbf~BCeSQ5H{l{;av3w|!VIb%5h%%6$$M(Zll2^#hq5T>Q=go>6-wqk ztsMsq=$)h<9!Iv7sdF}66m34B1{QH{W!jinm(EFH7QmAeOGoN4UN%f?Av)JrT z?ys0FN}6gW`cuD|(od}vv%zq+&c}xM4`2+opF$B!b_%|E#P{_K3l91xCFq6s_H8;8 zim=wZ`7o39II1w0j=<}7Wk4C4*h4`N3i{zWJ3DS7c!Me#2c<0dA4X)dF8e~uo@|4g z>%ys8&?u#hUrXuMlBlA`L}eTzVBLT}?Wv^=am(Kz3dW9`YZETn*Wd4$n>Vlfyl%`I zCu-#nAx5;#-x4bUVK}l&RD2m}eOaMjTem!DRWEWhLX% zag2T|3z3iHcC=Q}#G7CcwNFLaSxGv3erHRt5Swo_ZP>>HZ+~Vv+d$?IXDy*gWQei{BD`ru<=YZoq((m%8E^T$m?eNd#JFy}W;3(v2! zAswh`>v&4cYslt%#-Q!taIt#+?Q50UaIORh0K8p=EmzJFT5dZ9<)a`=tUmDU)|J9X52gj zKhwokvH{EkBkw<7%OhwV!V(aL4ded)oJ)(#VYmiN=#5o6P+i@xLc&bnESg?i(vbDv zJta9(zOc^8o>$Gk#NJSjwm2v}s_H4{?j>H4ohNRvyrM+udyuZ&09R!+ME$iuGk*_V zWHgBOV7uS;;qLsCn1N2O_W;XBU~7g9`h6HC*XQ*@Vt3ss_2$Y>r~i$g1P8_O>K|mS zm$zGS>Ymc8DlwB9HzEvGcn%|qoh>w3r*ur=r!1QMRJ%B@R5#oc>{`h!pPO)EBnUhw zM3%$TZZ?RFkw*4iL8)WE{`9guxQ&(qI@ex1t-ntaxBiD6aFCo5)w};m=%78UCH!QA{~R}QSs=Bo4V0sfdCn7_jzWT?s} zj?9ZwQ$6qx!g*ddw%1Jl{>n|G@5L68^G2b^7bD}$V)4f89cMI^l%yFUI}N0!7i#w@ zYmD{7s*FdF@Zlqss7E4qoDq?2@BGGmD2_Umwmz=yX;WhPaGqeEi9D2iHK4akiAkvH z4JPK{0|@=0391Z~LEI&~Dg;4{5@ANR`W(#t4p&0dZzswv8}lPvWyGLtmq35>fo}-y z6uQLhjyR%~pbZr`VAk0F>*eOPb|^Y8l7$tR(2wfQSL8t)@e{Gu{Z#pOo|W3V9KKu^ zl3Ci2HU25=WzYqA^JV1d?oH`9)e^y|JGb_Mm~2~|!5dlrb<#VfnW!{!`i+w+3v+>v zyQ22g@+na)p9AGyqW za;|8K7Slu9Up+YHKsoutpg?{G8X1{yC|CCR$pFEKkneFHT5AgI zdeV}inPdMsr#^fqou+SWZDCMzU4Jy0hY({Wsr`N420%@EK6@9p4tg4?&mXSeVx9~6 zdT_11JkJ#l{%A)w6EitXjNPJ5atimEg>q`@t(D(4Mk zo`VciaF%f;H}t>5C^Lr06Cd#mvvV}EY#VfK8}szQMX-pZMh_fkc55{p!<1Bbd#WP( zeBYW-6VTdAqxoIeOy{MNZrQjDO1YQh>OLRbzF{Ew+-iOS2^+ z3m<4+O{?e+YM-roliljZDH35n+=zBbv(jM*`-H6xZ0SNT1^)TaNyFwMg(`ui@`UEy zDDCOUp((M}r_RHhVcNNwW}C;5a333614ma^ikS}wo*(}mM5v>nNBo=wBVtMnEg>*3 z{4zrS2sA>SsSAf9EuPT?>5$u`{;=5J|E6sBnoRlHYM>MPtt!IkphWQMD3R2NC-WyC z?Bdch&Wj-;1@J%keJ9QcDaf6eDMB@0yM>CZJ9W?f8q%6mR^CH1aI0c>_qUT;QJ&cr zr8b%o`O%GUC`Zcy1MssThrk={5`akc&EkixA~rp^32ykjC_n#^@J>-3fRS2_g*XuX z`wVnXdAKK^@8)@hf8|uOKdCX#;4ztjxT?Uz-^C%=^y+mF1->oi{t3z^K8>88pB?fM z$a3L(YVgd{<-fm=9EcW3g8!cP-aQ+&Af2L6Jq-IP`^Ay8{9cIiHb<~?)HG&Io_p}y zj}M<}zwG>B8SkME?MUvr;y)73GaPWIlaI25NJf+u*uQBqOu zQ%;J3zdYMyZGC^r9%zwVhW;E2?fFMDc%-Apc$+oH8ID-K|H*yk#epU)F`6Zq+{2N+ zTDQNcMN3KlF_DB%%a?V__Joc5&>q=kyNL%&Qq#wXrPB_;pgv zO>^#+cE2=Qe3-kDU#*V8hS?ms_2^MH8!^Y7{mKWtK$fB{W50luO z>S`>2M6jSeP}4($yG;V*k{D3zZbP#6)wF8{kg#hMRdq!O8QX0jL#W`jXwuHDt4;~z zX=5wOFJee2Jn23A^<@$MvOrc=_CLhK6(aK;zh&w-_n*-csBUUJl*votR_vNT-`mZ! z$V&5!sJ{xQ6m~}z==O>Cau{n5!~XeFkus1qEYBx|WLn^Xs;i{(O=WN>O=;NHkQ-LL z{NK3vKp*OTtp)+7c&AE-*p4nSTG2G6Vee2r;*cnahRwgvSR!5fEk|fhm!$?-!!4?g z^x4@(C--;k49&$6UJv0UDnDDAS@?)HjY(w5hQ7{o&Q73(3+q zGud?jw)J#%LYc^%YGo7%zgBSr{R~_UyAU}0Fm(wew%ZV{2UQ(v=|1cPg)vheBtSZ@ zSc4uUuKAXtAAi{;5M}oEe*79#D2qa@>DU44flr5oFZ%yFQp)@`z5L{mICK&`q;k3P zXBt!4pca3$sNE#hKh1b}~RSV(Zd zjpcv6_uptW9?6q-+aT*v`CBc?;isj_a-K^wx?6ETXtiPzH09x&HgR#qm?REG&byTk zRkz+nPc&9h`Y#if|3n|s>SYq7tSLi8Ho!o6^NE(Cky{gxXm^@gfB=L*zO< zZYyhYS+yarkE)#DF2`2as`C-^7E#0jP$saq2x-9I;`-CBFr&c0K(hnQAZ-N>8pWiq z{KXHr{pZiLw>Ez-t7H0DwMpU}EL#S-VV*9cyCF+-?*`@|aMr}IOFdED=U+6toi}Ff zd=(~0RAi6q?K=b0(=f_rC5E)hsC))w$w^uMoL~TQXB$qkEpJmiGv{x=oe;P$R=ds zcN>O_x3RuwL$*Y#RK%OEG8~9UBqZBzg(a`)u^s4`KKtft^8!L7+|C))3i ze4`&nmL?c48&$izv&QA$t`aBHP!Pdy5OA>>@H_mc-0$GOax-Rcv=25jYztz%lrXWb zT%l_!YWNl%pK7?xls8ehlVFhp%wPRm(nW%mSJ1V2jJ~h<5%sLS7k8oUUt=ysj2{<~t_zpm3jx`{OL zDCiO{xX%a-pKHAqB?#$*O6UQJQZxAm^WUrZn~RI8gP{Nh3@BR*d55z|HRRr9W&_8b z@A85{2Vo{F|DmFVeD)`Gvx|lvDy#KcJlk~EI!QSlboQPwhk!dYJ)B>TKi{;9 zRW*W1^#@hNsOOBJI!D?s&PbD-f?pDccQ;mAdXdiG_OI{dzqrls$Z{47?ekAv$AnoW zx*Z%;zI1+;Ta^Bf1e}xb#~b^UC&m78v<;+34*-XoUj1@N#}Ha6?>l^Ss2TxZPHG21 zhsjeY=?w!)B9I1Z21Wp<$3)#9uHidb;ckOJsY5PJ1?<%27Nqc5WX1fq!kF(#}%X_JmB4d^{^VwFe-oNqlr zb7r=N;GJCjvOhlxZp6qbYzVBtx#NrHE238gX=U?tn*@vYL)=5vqYBkLB3CkQ9;qny zs)tE1#TQYL5MyTk`$1{P&x2|!?+2)x6I{tU4JX%{ z)UfBB5N-GB$FpY1u1@0p?le05FNc>S^7fn-da8((^fc&)QHjC0z{uL=gF}%%Pf?HMwfe= z+5eKTn(n#q48H!g8s0L(OtwQ;;zogS6)P;&EWZ&~z1q$}?MzGOWzxEeqOOepD^;bw zWA^x|1(A7uF&XbjPgJ!x8K=1awx<`eV(h_-fA05f4Q9Y?0N%13KlKnCQ`CbXfr{PvwMEhF9n=Th6mwjUxTB6n!aF31uBwV=xmqMR8RS0^=D=!i0)ehV}df z6(w_ofh>qtCQEDXswXdz@;D>742Ge(h$J#5PdKQ@kW!%Rr`FIN|Q%hFTsHiPX0 zk9xdBZr)T+BSme#-*P)+I6NR{%mbwyA8I+m65%xlLOh@)p_Rv2#SI;~-s;oQCp4k#Sf~V8$djg)TTvk^sG&5FpF)UXq4H6r zW*IfXTlk2t@j=IsYL;fpuK%K#`>YSL*;o8*p6h4G{$u1_t{|=fx3|(bMvBud^ukfN zm5;?aVe-pj9&9BP#SweSkxU#fY3vvA{l)vc!4h@B59Iz6e?E1)m}SR{fry*vjaErx z@OS^M@r@J8QW3o&^I{g%^hc#CE16pdVO$2(NI;DdovC)TvdP!9FC3zx>9!wLGp&;%u@`N7n z7B-la=RJn)REy-pae8;d+BMT*b=*<-Ikzou?KE0dFyjHoPBN*2rr#-C2JEPTWG zxRt$Sp@>WM3qJQ#QL96!-!p_gDUKmgGh1VB{zh7LoeO~w4vZvqQzDd;>2BT$-$MMb z2IN%~BuJ(S-z=R1jQesPj(b!Y(e&nn)fcasHb+@m!!rZjp!A5v{88X0zPv}VZM-UL z0dJ~)W}4^Ki#so<_B6LK*10X$sR#T`vSlf`57IzZcCZ&WNRq=Yi-HOwrz>O3xz}F( z9T+gjev$dBWmGmT8Ws2LH|qF!0lB~b=FffJg1ad-|LC@7g^*j1xjn!sk85s{1S0s* zcTZiC1TZZdTkYgcRIqBF{XKM% zXXtE=-E75Bame-g0jE35`qV^1jE=<=r&*^z^12N|x`( zTjR+jhDBU;x%oDpuksz%lfNG3jFz)BEq>Ed>Q*V#6?+GTCMQG1s2+_Zyz~dlMid15 z*zqGiY!HN!T76RmNe07S3Z%A-m{#)QaQ>lno^dQN z{egZsUC`)zW{e`VH*faqpIlu70~ZgOr3I$MIXRLjbAxJm5^yR>5!BeAfFtos^aCDV zxB6g}A|t4?RNN)i#oxteCa-Tj5pYf$E-wRl4|Sa?$}_|<=^^&v+lv0%h7K${4atOe zE92%q@IV6az~(~A*dfLfQ$4z1{xdyH`jtHoig9B2e1124g(Unh#Mmt)j!+2q4ktQ8 zk!@S&?G#E6Ilek+@fdT~S64UC#5m1{!+Ew(-h4%!lr*k;s}bpvO>w=Kv2YS7s;q4* z4^lEvE&Qxl^38Spz^PN(n|whF&?0jmGbtL&nQ#6kuLnT<*mhKaRIFcZE-THCX;*RJ zet{MaKVHvT_nqb|j0ulF6!4lKnH5$hBvVAudjg=usiU&@T6|sle^~E z3JtiRsZ0=9l9hWI_EjE&=sk45o=4#|fCV%=<(IREJPVflmXX{IUU(~dIOzg81Ptsp zt!=$7>??-4h~q_mPH*l-$X;GxLtZ)ZX@T;Jf(LnIAkkl^Giwz4 zL|_Xp9KiS@sy~sezQF%wwQfnV8pRwd|t2k2g73^ zErC`Rup|N-z;X`giFr=g&?kfsJ_?u?6Czdx$hhvm?|I($A3%8Yv<j{PALg~m AWB>pF literal 7352 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 diff --git a/launcher/icons/menu-settings.png b/launcher/icons/menu-settings.png index 4cce5d91ea81b0b051dfd7736f7beeda24380e0d..8dec624929c307ad7ab39231beb02dba4f9bb3bc 100644 GIT binary patch literal 27531 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 literal 4474 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 M07*qoM6N<$g8#!*Hvj+t From 0e6750743490a71fc9ef1d0a31052755b554f14a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 22:20:04 +0200 Subject: [PATCH 029/197] Fix editor button visibility --- CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cbc87f20c..90f491f6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,14 @@ set(CMAKE_XCODE_ATTRIBUTE_MARKETING_VERSION ${APP_SHORT_VERSION}) set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO) set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] YES) +if(ENABLE_LAUNCHER) + add_definitions(-DENABLE_LAUNCHER) +endif() + +if(ENABLE_EDITOR) + add_definitions(-DENABLE_EDITOR) +endif() + if(ENABLE_SINGLE_APP_BUILD) add_definitions(-DSINGLE_PROCESS_APP=1) endif() @@ -474,11 +482,9 @@ if(NOT TARGET minizip::minizip) endif() if(ENABLE_LAUNCHER) - add_definitions(-DENABLE_LAUNCHER) add_subdirectory(launcher) endif() if(ENABLE_EDITOR) - add_definitions(-DENABLE_EDITOR) add_subdirectory(mapeditor) endif() add_subdirectory(client) From e6a83ad53b2b50d24455c9bda67dc6f68feb078d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 22:20:27 +0200 Subject: [PATCH 030/197] Attempt to improve layout on mobile devices --- launcher/mainwindow_moc.ui | 504 +++++++++++++++++-------------------- 1 file changed, 232 insertions(+), 272 deletions(-) diff --git a/launcher/mainwindow_moc.ui b/launcher/mainwindow_moc.ui index e54677155..1d4d5050d 100644 --- a/launcher/mainwindow_moc.ui +++ b/launcher/mainwindow_moc.ui @@ -7,7 +7,7 @@ 0 0 800 - 480 + 508 @@ -30,84 +30,238 @@ - - - - - QFrame::Plain - - - 1 - - - 0 - - - Qt::Vertical - - + + + + + + + + 1 + 10 + + + + + 0 + 0 + + + + Mods + + + + icons:menu-mods.pngicons:menu-mods.png + + + + 60 + 60 + + + + true + + + true + + + true + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + + + + 1 + 10 + + + + + 0 + 0 + + + + Settings + + + + icons:menu-settings.pngicons:menu-settings.png + + + + 60 + 60 + + + + true + + + false + + + true + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + + + + 1 + 10 + + + + + 0 + 0 + + + + Lobby + + + + icons:menu-lobby.pngicons:menu-lobby.png + + + + 60 + 60 + + + + true + + + false + + + true + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + + + + 1 + 5 + + + + + 0 + 0 + + + + + 75 + true + + + + Editor + + + + icons:menu-editor.pngicons:menu-editor.png + + + + 30 + 30 + + + + false + + + false + + + Qt::ToolButtonTextUnderIcon + + + false + + + + + + + + 1 + 10 + + + + + 0 + 0 + + + + + 75 + true + + + + Start game + + + + icons:menu-game.pngicons:menu-game.png + + + + 60 + 60 + + + + false + + + false + + + Qt::ToolButtonTextUnderIcon + + + false + + + + - - - - - 1 - 0 - - - - - 0 - 0 - - - - Settings - - - - icons:menu-settings.pngicons:menu-settings.png - - - - 60 - 60 - - - - true - - - false - - - true - - - Qt::ToolButtonTextUnderIcon - - - true - - - - - - - - 75 - true - - - - Start game - - - Qt::AlignCenter - - - - + true @@ -126,197 +280,6 @@ - - - - - 1 - 0 - - - - - 0 - 0 - - - - - 75 - true - - - - - - - - icons:menu-game.pngicons:menu-game.png - - - - 60 - 60 - - - - false - - - false - - - Qt::ToolButtonIconOnly - - - false - - - - - - - - 1 - 0 - - - - - 0 - 0 - - - - Lobby - - - - icons:menu-lobby.pngicons:menu-lobby.png - - - - 60 - 60 - - - - true - - - false - - - true - - - Qt::ToolButtonTextUnderIcon - - - true - - - - - - - - 1 - 0 - - - - - 0 - 0 - - - - Mods - - - - icons:menu-mods.pngicons:menu-mods.png - - - - 60 - 60 - - - - true - - - true - - - true - - - Qt::ToolButtonTextUnderIcon - - - true - - - - - - - Qt::Vertical - - - - 80 - 0 - - - - - - - - - 1 - 0 - - - - - 0 - 0 - - - - - 75 - true - - - - Editor - - - - 60 - 60 - - - - false - - - false - - - Qt::ToolButtonIconOnly - - - false - - - @@ -341,9 +304,6 @@ 1 - - startGameButton - From 4f4a89e15d942cf4570aad4277fd11984dbcb0da Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 22:22:05 +0200 Subject: [PATCH 031/197] Fix formatting --- launcher/modManager/cmodlist.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/modManager/cmodlist.cpp b/launcher/modManager/cmodlist.cpp index fbee206c8..cd74d55b8 100644 --- a/launcher/modManager/cmodlist.cpp +++ b/launcher/modManager/cmodlist.cpp @@ -177,14 +177,14 @@ QVariant CModEntry::getValue(QString value) const auto & storage = useRepositoryData ? repository : localData; - if (storage.contains(langValue)) + if(storage.contains(langValue)) { auto langStorage = storage[langValue].toMap(); if (langStorage.contains(value)) return langStorage[value]; } - if (storage.contains(value)) + if(storage.contains(value)) return storage[value]; return QVariant(); From 3704102e1d6993911607dbdf689ef4495f0d4982 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 22:22:43 +0200 Subject: [PATCH 032/197] Editor -> Map Editor string --- launcher/mainwindow_moc.ui | 2 +- launcher/translation/english.ts | 68 +++++++++++++++---------------- launcher/translation/german.ts | 66 ++++++++++++++++-------------- launcher/translation/polish.ts | 66 ++++++++++++++++-------------- launcher/translation/russian.ts | 68 +++++++++++++++---------------- launcher/translation/ukrainian.ts | 66 ++++++++++++++++-------------- 6 files changed, 174 insertions(+), 162 deletions(-) diff --git a/launcher/mainwindow_moc.ui b/launcher/mainwindow_moc.ui index 1d4d5050d..fe98a8abf 100644 --- a/launcher/mainwindow_moc.ui +++ b/launcher/mainwindow_moc.ui @@ -186,7 +186,7 @@ - Editor + Map Editor diff --git a/launcher/translation/english.ts b/launcher/translation/english.ts index 4dbc6d084..3360d5869 100644 --- a/launcher/translation/english.ts +++ b/launcher/translation/english.ts @@ -73,7 +73,7 @@ - + Description @@ -128,114 +128,114 @@ - + Mod name - + Installed version - + Latest version - + Download size - + Authors - + License - + Home - - + + Compatibility - - + + Required VCMI version - + Supported VCMI version - + Supported VCMI versions - + Required mods - + Conflicting mods - + This mod can not be installed or enabled because following dependencies are not present - + This mod can not be enabled because following mods are incompatible with this mod - + This mod can not be disabled because it is required to run following mods - + This mod can not be uninstalled or updated because it is required to run following mods - + This is submod and it can not be installed or uninstalled separately from parent mod - + Notes - + Screenshot %1 - + Mod is compatible - + Mod is incompatible @@ -558,30 +558,30 @@ - + Settings - + + Map Editor + + + + Start game - + Lobby - + Mods - - - Editor - - UpdateDialog diff --git a/launcher/translation/german.ts b/launcher/translation/german.ts index fa27590e4..48358d7d1 100644 --- a/launcher/translation/german.ts +++ b/launcher/translation/german.ts @@ -73,7 +73,7 @@ - + Description Beschreibung @@ -128,114 +128,114 @@ Abbrechen - + Mod name Mod-Name - + Installed version Installierte Version - + Latest version Letzte Version - + Download size Downloadgröße - + Authors Autoren - + License Lizenz - + Home Home - - + + Compatibility Kompatibilität - - + + Required VCMI version Benötigte VCMI Version - + Supported VCMI version Unterstützte VCMI Version - + Supported VCMI versions Unterstützte VCMI Versionen - + Required mods Benötigte Mods - + Conflicting mods Mods mit Konflikt - + This mod can not be installed or enabled because following dependencies are not present Diese Mod kann nicht installiert oder aktiviert werden, da die folgenden Abhängigkeiten nicht vorhanden sind - + This mod can not be enabled because following mods are incompatible with this mod Diese Mod kann nicht aktiviert werden, da folgende Mods nicht mit dieser Mod kompatibel sind - + This mod can not be disabled because it is required to run following mods Diese Mod kann nicht deaktiviert werden, da sie zum Ausführen der folgenden Mods erforderlich ist - + This mod can not be uninstalled or updated because it is required to run following mods Diese Mod kann nicht deinstalliert oder aktualisiert werden, da sie für die folgenden Mods erforderlich ist - + This is submod and it can not be installed or uninstalled separately from parent mod Dies ist eine Submod und kann nicht separat von der Hauptmod installiert oder deinstalliert werden - + Notes Anmerkungen - + Screenshot %1 Screenshot %1 - + Mod is compatible Mod ist kompatibel - + Mod is incompatible Mod ist inkompatibel @@ -558,27 +558,31 @@ VCMI Launcher - + Mods Mods - Editor - Editor + Editor - + Settings Einstellungen - + Lobby Lobby - + + Map Editor + + + + Start game Spiel starten diff --git a/launcher/translation/polish.ts b/launcher/translation/polish.ts index 4fedabb7b..53fe63f23 100644 --- a/launcher/translation/polish.ts +++ b/launcher/translation/polish.ts @@ -73,7 +73,7 @@ - + Description Opis @@ -128,114 +128,114 @@ Przerwij - + Mod name Nazwa moda - + Installed version Zainstalowana wersja - + Latest version Najnowsza wersja - + Download size Rozmiar pobierania - + Authors Autorzy - + License Licencja - + Home Źródło - - + + Compatibility Kompatybilność - - + + Required VCMI version Wymagana wersja VCMI - + Supported VCMI version Wspierana wersja VCMI - + Supported VCMI versions Wspierane wersje VCMI - + Required mods Wymagane mody - + Conflicting mods Konfliktujące mody - + This mod can not be installed or enabled because following dependencies are not present Ten mod nie może zostać zainstalowany lub włączony ponieważ następujące zależności nie zostały spełnione - + This mod can not be enabled because following mods are incompatible with this mod Ten mod nie może zostać włączony ponieważ następujące mody są z nim niekompatybilne - + This mod can not be disabled because it is required to run following mods Ten mod nie może zostać wyłączony ponieważ jest wymagany by do uruchomienia następujących modów - + This mod can not be uninstalled or updated because it is required to run following mods Ten mod nie może zostać odinstalowany lub zaktualizowany ponieważ jest wymagany do uruchomienia następujących modów - + This is submod and it can not be installed or uninstalled separately from parent mod To jest moduł składowy innego moda i nie może być zainstalowany lub odinstalowany oddzielnie od moda nadrzędnego - + Notes Uwagi - + Screenshot %1 Zrzut ekranu %1 - + Mod is compatible Mod jest kompatybilny - + Mod is incompatible Mod jest niekompatybilny @@ -558,29 +558,33 @@ VCMI Launcher (program startowy) - + Settings Ustawienia - + + Map Editor + + + + Start game Uruchom grę - + Lobby Lobby - + Mods Mody - Editor - Edytor + Edytor diff --git a/launcher/translation/russian.ts b/launcher/translation/russian.ts index e855f9981..9860912e4 100644 --- a/launcher/translation/russian.ts +++ b/launcher/translation/russian.ts @@ -73,7 +73,7 @@ - + Description @@ -128,114 +128,114 @@ - + Mod name - + Installed version - + Latest version - + Download size - + Authors - + License - + Home - - + + Compatibility - - + + Required VCMI version - + Supported VCMI version - + Supported VCMI versions - + Required mods - + Conflicting mods - + This mod can not be installed or enabled because following dependencies are not present - + This mod can not be enabled because following mods are incompatible with this mod - + This mod can not be disabled because it is required to run following mods - + This mod can not be uninstalled or updated because it is required to run following mods - + This is submod and it can not be installed or uninstalled separately from parent mod - + Notes - + Screenshot %1 - + Mod is compatible - + Mod is incompatible @@ -558,30 +558,30 @@ - + Settings - + + Map Editor + + + + Start game - + Lobby - + Mods - - - Editor - - UpdateDialog diff --git a/launcher/translation/ukrainian.ts b/launcher/translation/ukrainian.ts index 0ceb04e55..b93221b03 100644 --- a/launcher/translation/ukrainian.ts +++ b/launcher/translation/ukrainian.ts @@ -73,7 +73,7 @@ - + Description Опис @@ -128,114 +128,114 @@ Відмінити - + Mod name Назва модифікації - + Installed version Встановлена версія - + Latest version Найновіша версія - + Download size Розмір для завантаження - + Authors Автори - + License Ліцензія - + Home Домашня сторінка - - + + Compatibility Сумісність - - + + Required VCMI version Необхідна версія VCMI - + Supported VCMI version Підтримувана версія VCMI - + Supported VCMI versions Підтримувані версії VCMI - + Required mods Необхідні модифікації - + Conflicting mods Конфліктуючі модифікації - + This mod can not be installed or enabled because following dependencies are not present Цю модифікацію не можна встановити чи активувати, оскільки відсутні наступні залежності - + This mod can not be enabled because following mods are incompatible with this mod Цю модифікацію не можна ввімкнути, оскільки наступні модифікації несумісні з цією модифікацією - + This mod can not be disabled because it is required to run following mods Цю модифікацію не можна відключити, оскільки вона необхідна для запуску наступних модифікацій - + This mod can not be uninstalled or updated because it is required to run following mods Цю модифікацію не можна видалити або оновити, оскільки вона необхідна для запуску наступних модифікацій - + This is submod and it can not be installed or uninstalled separately from parent mod Це вкладена модифікація, і її не можна встановити або видалити окремо від батьківської модифікації - + Notes Примітки - + Screenshot %1 Знімок екрану %1 - + Mod is compatible Модифікація сумісна - + Mod is incompatible Модифікація несумісна @@ -558,27 +558,31 @@ VCMI Launcher - + Mods Модифікації - Editor - Редактор + Редактор - + Settings Налаштування - + Lobby Лобі - + + Map Editor + + + + Start game Грати From d3a4a38694ff6f5dc14c0d175b2761b2747b10fc Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Dec 2022 22:45:47 +0200 Subject: [PATCH 033/197] Add editor icon to launcher --- launcher/icons/menu-editor.png | Bin 0 -> 58984 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 launcher/icons/menu-editor.png diff --git a/launcher/icons/menu-editor.png b/launcher/icons/menu-editor.png new file mode 100644 index 0000000000000000000000000000000000000000..c8aa1c09509aa03edeb1fa48bd864c878502328b GIT binary patch literal 58984 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 LNkvXXu0mjfqLvOu literal 0 HcmV?d00001 From b1aaa09b8b4508733d07fc2c3aa6477d8840a691 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Dec 2022 16:37:03 +0200 Subject: [PATCH 034/197] Fix search paths for translation --- launcher/CMakeLists.txt | 2 +- launcher/mainwindow_moc.cpp | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 8aac6b418..3d6a38ec3 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -96,7 +96,7 @@ if(TARGET Qt6::Core) else() qt5_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS}) if(ENABLE_TRANSLATIONS) - set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/translations) + set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/translation) qt5_add_translation( launcher_QM ${launcher_TS} ) endif() endif() diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 52c464d05..dc36f1f17 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -139,14 +139,25 @@ void MainWindow::on_lobbyButton_clicked() void MainWindow::updateTranslation() { #ifdef ENABLE_QT_TRANSLATIONS - std::string languageCode = settings["general"]["language"].String(); + std::string translationFile = settings["general"]["language"].String()+ ".qm"; - QString translationFile = "./launcher/translations/" + QString::fromStdString(languageCode) + ".qm"; + QVector searchPaths; + + for(auto const & string : VCMIDirs::get().dataPaths()) + searchPaths.push_back(pathToQString(string / "launcher" / "translation" / translationFile)); + searchPaths.push_back(pathToQString(VCMIDirs::get().userDataPath() / "launcher" / "translation" / translationFile)); + + for(auto const & string : boost::adaptors::reverse(searchPaths)) + { + if (translator.load(string)) + { + if (!qApp->installTranslator(&translator)) + logGlobal->error("Failed to install translator"); + return; + } + } + + logGlobal->error("Failed to find translation"); - qApp->removeTranslator(&translator); - if (!translator.load(translationFile)) - logGlobal->error("Failed to load translation"); - if (!qApp->installTranslator(&translator)) - logGlobal->error("Failed to install translator"); #endif } From 8010bff866af8ab9040df831778ffc03c0f93f4b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Dec 2022 16:37:38 +0200 Subject: [PATCH 035/197] Formatting --- launcher/lobby/lobby_moc.cpp | 2 +- launcher/lobby/lobbyroomrequest_moc.cpp | 2 +- launcher/mainwindow_moc.cpp | 2 +- launcher/modManager/cmodlistview_moc.cpp | 2 +- launcher/modManager/imageviewer_moc.cpp | 2 +- launcher/settingsView/csettingsview_moc.cpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/launcher/lobby/lobby_moc.cpp b/launcher/lobby/lobby_moc.cpp index 0122d6801..09068debd 100644 --- a/launcher/lobby/lobby_moc.cpp +++ b/launcher/lobby/lobby_moc.cpp @@ -37,7 +37,7 @@ Lobby::Lobby(QWidget *parent) : void Lobby::changeEvent(QEvent *event) { - if ( event->type() == QEvent::LanguageChange) + if(event->type() == QEvent::LanguageChange) { ui->retranslateUi(this); } diff --git a/launcher/lobby/lobbyroomrequest_moc.cpp b/launcher/lobby/lobbyroomrequest_moc.cpp index f8feed9cd..62c2496db 100644 --- a/launcher/lobby/lobbyroomrequest_moc.cpp +++ b/launcher/lobby/lobbyroomrequest_moc.cpp @@ -29,7 +29,7 @@ LobbyRoomRequest::LobbyRoomRequest(SocketLobby & socket, const QString & room, c void LobbyRoomRequest::changeEvent(QEvent *event) { - if (event->type() == QEvent::LanguageChange) + if(event->type() == QEvent::LanguageChange) { ui->retranslateUi(this); } diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index dc36f1f17..885c56170 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -86,7 +86,7 @@ MainWindow::MainWindow(QWidget * parent) void MainWindow::changeEvent(QEvent *event) { - if ( event->type() == QEvent::LanguageChange) + if(event->type() == QEvent::LanguageChange) { ui->retranslateUi(this); } diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index 28b547c72..dc68caed8 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -36,7 +36,7 @@ void CModListView::setupModModel() void CModListView::changeEvent(QEvent *event) { - if ( event->type() == QEvent::LanguageChange) + if(event->type() == QEvent::LanguageChange) { ui->retranslateUi(this); modModel->reloadRepositories(); diff --git a/launcher/modManager/imageviewer_moc.cpp b/launcher/modManager/imageviewer_moc.cpp index e8a9b5814..afd46b134 100644 --- a/launcher/modManager/imageviewer_moc.cpp +++ b/launcher/modManager/imageviewer_moc.cpp @@ -22,7 +22,7 @@ ImageViewer::ImageViewer(QWidget * parent) void ImageViewer::changeEvent(QEvent *event) { - if ( event->type() == QEvent::LanguageChange) + if(event->type() == QEvent::LanguageChange) { ui->retranslateUi(this); } diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 58723bdad..fb7318be1 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -329,7 +329,7 @@ void CSettingsView::on_comboBoxLanguage_currentIndexChanged(int index) void CSettingsView::changeEvent(QEvent *event) { - if ( event->type() == QEvent::LanguageChange) + if(event->type() == QEvent::LanguageChange) { ui->retranslateUi(this); } From 78ae6d6ed090d22aabe3d3341c3f1f0bc57819da Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Dec 2022 16:38:43 +0200 Subject: [PATCH 036/197] Made scaling of left panel limited, within well-defined bounds --- launcher/mainwindow_moc.cpp | 25 +++++++++++++++++++++ launcher/mainwindow_moc.h | 1 + launcher/mainwindow_moc.ui | 45 ++++++++++++++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 885c56170..2e50c5686 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -47,6 +47,29 @@ void MainWindow::load() settings.init(); } +void MainWindow::computeSidePanelSizes() +{ + QVector widgets = { + ui->modslistButton, + ui->settingsButton, + ui->lobbyButton, + ui->startEditorButton, + ui->startGameButton + }; + + for(auto & widget : widgets) + { + QFontMetrics metrics(widget->font()); + QSize iconSize = widget->iconSize(); + + // this is minimal space that is needed for our button to avoid text clipping + int buttonHeight = iconSize.height() + metrics.height() + 4; + + widget->setMinimumHeight(buttonHeight); + widget->setMaximumHeight(buttonHeight * 1.2); + } +} + MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent), ui(new Ui::MainWindow) { @@ -73,6 +96,8 @@ MainWindow::MainWindow(QWidget * parent) ui->startEditorButton->hide(); #endif + computeSidePanelSizes(); + ui->tabListWidget->setCurrentIndex(0); ui->settingsView->isExtraResolutionsModEnabled = ui->modlistView->isExtraResolutionsModEnabled(); diff --git a/launcher/mainwindow_moc.h b/launcher/mainwindow_moc.h index f6733c504..4a287ea79 100644 --- a/launcher/mainwindow_moc.h +++ b/launcher/mainwindow_moc.h @@ -46,6 +46,7 @@ public: const CModList & getModList() const; void updateTranslation(); + void computeSidePanelSizes(); public slots: void on_startGameButton_clicked(); diff --git a/launcher/mainwindow_moc.ui b/launcher/mainwindow_moc.ui index fe98a8abf..9665d131f 100644 --- a/launcher/mainwindow_moc.ui +++ b/launcher/mainwindow_moc.ui @@ -7,7 +7,7 @@ 0 0 800 - 508 + 410 @@ -47,6 +47,12 @@ 0 + + + 16777215 + 16777215 + + Mods @@ -91,6 +97,12 @@ 0 + + + 16777215 + 16777215 + + Settings @@ -135,6 +147,12 @@ 0 + + + 16777215 + 16777215 + + Lobby @@ -165,6 +183,19 @@ + + + + Qt::Vertical + + + + 100 + 0 + + + + @@ -179,6 +210,12 @@ 0 + + + 16777215 + 16777215 + + 75 @@ -226,6 +263,12 @@ 0 + + + 16777215 + 16777215 + + 75 From b1c008179d68455d89f4f471e1500832d521937d Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 6 Jan 2023 17:52:18 +0100 Subject: [PATCH 037/197] Extract client commands handling to separate class + minor fixes --- client/CMT.cpp | 443 +-------------------------- client/CMT.h | 1 - client/CMakeLists.txt | 2 + client/ClientCommandManager.cpp | 510 ++++++++++++++++++++++++++++++++ client/ClientCommandManager.h | 27 ++ 5 files changed, 542 insertions(+), 441 deletions(-) create mode 100644 client/ClientCommandManager.cpp create mode 100644 client/ClientCommandManager.h diff --git a/client/CMT.cpp b/client/CMT.cpp index 7385d7b19..40fbbcedf 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -13,8 +13,6 @@ #include -#include - #include "gui/SDL_Extensions.h" #include "CGameInfo.h" #include "mapHandler.h" @@ -33,32 +31,26 @@ #include "../lib/CBuildingHandler.h" #include "CVideoHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CCreatureHandler.h" #include "../lib/spells/CSpellHandler.h" #include "CMusicHandler.h" #include "../lib/CGeneralTextHandler.h" #include "Graphics.h" #include "Client.h" -#include "../lib/CConfigHandler.h" #include "../lib/serializer/BinaryDeserializer.h" #include "../lib/serializer/BinarySerializer.h" -#include "../lib/VCMI_Lib.h" #include "../lib/VCMIDirs.h" #include "../lib/NetPacks.h" #include "CMessage.h" #include "../lib/CModHandler.h" -#include "../lib/ScriptHandler.h" #include "../lib/CTownHandler.h" -#include "../lib/CArtHandler.h" -#include "../lib/GameConstants.h" #include "gui/CGuiHandler.h" #include "../lib/logging/CBasicLogConfigurator.h" -#include "../lib/StringConstants.h" #include "../lib/CPlayerState.h" #include "gui/CAnimation.h" #include "../lib/serializer/Connection.h" #include "CServerHandler.h" #include "gui/NotificationHandler.h" +#include "ClientCommandManager.h" #include @@ -71,7 +63,7 @@ #ifdef VCMI_ANDROID #include "lib/CAndroidVMHelper.h" #endif -#include "../lib/UnlockGuard.h" + #include "CMT.h" #if __MINGW32__ @@ -247,7 +239,7 @@ int main(int argc, char * argv[]) std::cout.flags(std::ios::unitbuf); #ifndef VCMI_IOS console = new CConsoleHandler(); - *console->cb = processCommand; + *console->cb = ClientCommandManager::processCommand; console->start(); #endif @@ -579,435 +571,6 @@ void printInfoAboutIntObject(const CIntObject *obj, int level) printInfoAboutIntObject(child, level+1); } -void removeGUI() -{ - // CClient::endGame - GH.curInt = nullptr; - if(GH.topInt()) - GH.topInt()->deactivate(); - GH.listInt.clear(); - GH.objsToBlit.clear(); - GH.statusbar = nullptr; - logGlobal->info("Removed GUI."); - - LOCPLINT = nullptr; -} - -#ifndef VCMI_IOS -void processCommand(const std::string &message) -{ - std::istringstream readed; - readed.str(message); - std::string cn; //command name - readed >> cn; - -// Check mantis issue 2292 for details -// if(LOCPLINT && LOCPLINT->cingconsole) -// LOCPLINT->cingconsole->print(message); - - if(message==std::string("die, fool")) - { - exit(EXIT_SUCCESS); - } - else if(cn==std::string("activate")) - { - int what; - readed >> what; - switch (what) - { - case 0: - GH.topInt()->activate(); - break; - case 1: - adventureInt->activate(); - break; - case 2: - LOCPLINT->castleInt->activate(); - break; - } - } - else if(cn=="redraw") - { - GH.totalRedraw(); - } - else if(cn=="screen") - { - std::cout << "Screenbuf points to "; - - if(screenBuf == screen) - logGlobal->error("screen"); - else if(screenBuf == screen2) - logGlobal->error("screen2"); - else - logGlobal->error("?!?"); - - SDL_SaveBMP(screen, "Screen_c.bmp"); - SDL_SaveBMP(screen2, "Screen2_c.bmp"); - } - else if(cn=="save") - { - if(!CSH->client) - { - std::cout << "Game in not active"; - return; - } - std::string fname; - readed >> fname; - CSH->client->save(fname); - } -// else if(cn=="load") -// { -// // TODO: this code should end the running game and manage to call startGame instead -// std::string fname; -// readed >> fname; -// CSH->client->loadGame(fname); -// } - else if(message=="convert txt") - { - std::cout << "Command accepted.\t"; - - const bfs::path outPath = - VCMIDirs::get().userExtractedPath(); - - bfs::create_directories(outPath); - - auto extractVector = [=](const std::vector & source, const std::string & name) - { - JsonNode data(JsonNode::JsonType::DATA_VECTOR); - size_t index = 0; - for(auto & line : source) - { - JsonNode lineNode(JsonNode::JsonType::DATA_STRUCT); - lineNode["text"].String() = line; - lineNode["index"].Integer() = index++; - data.Vector().push_back(lineNode); - } - - const bfs::path filePath = outPath / (name + ".json"); - bfs::ofstream file(filePath); - file << data.toJson(); - }; - - extractVector(VLC->generaltexth->allTexts, "generalTexts"); - extractVector(VLC->generaltexth->jktexts, "jkTexts"); - extractVector(VLC->generaltexth->arraytxt, "arrayTexts"); - - std::cout << "\rExtracting done :)\n"; - std::cout << " Extracted files can be found in " << outPath << " directory\n"; - } - else if(message=="get config") - { - std::cout << "Command accepted.\t"; - - const bfs::path outPath = - VCMIDirs::get().userExtractedPath() / "configuration"; - - bfs::create_directories(outPath); - - const std::vector contentNames = {"heroClasses", "artifacts", "creatures", "factions", "objects", "heroes", "spells", "skills"}; - - for(auto contentName : contentNames) - { - auto & content = (*VLC->modh->content)[contentName]; - - auto contentOutPath = outPath / contentName; - bfs::create_directories(contentOutPath); - - for(auto & iter : content.modData) - { - const JsonNode & modData = iter.second.modData; - - for(auto & nameAndObject : modData.Struct()) - { - const JsonNode & object = nameAndObject.second; - - std::string name = CModHandler::normalizeIdentifier(object.meta, CModHandler::scopeBuiltin(), nameAndObject.first); - - boost::algorithm::replace_all(name,":","_"); - - const bfs::path filePath = contentOutPath / (name + ".json"); - bfs::ofstream file(filePath); - file << object.toJson(); - } - } - } - - std::cout << "\rExtracting done :)\n"; - std::cout << " Extracted files can be found in " << outPath << " directory\n"; - } -#if SCRIPTING_ENABLED - else if(message=="get scripts") - { - std::cout << "Command accepted.\t"; - - const bfs::path outPath = - VCMIDirs::get().userExtractedPath() / "scripts"; - - bfs::create_directories(outPath); - - for(auto & kv : VLC->scriptHandler->objects) - { - std::string name = kv.first; - boost::algorithm::replace_all(name,":","_"); - - const scripting::ScriptImpl * script = kv.second.get(); - bfs::path filePath = outPath / (name + ".lua"); - bfs::ofstream file(filePath); - file << script->getSource(); - } - std::cout << "\rExtracting done :)\n"; - std::cout << " Extracted files can be found in " << outPath << " directory\n"; - } -#endif - else if(message=="get txt") - { - std::cout << "Command accepted.\t"; - - const bfs::path outPath = - VCMIDirs::get().userExtractedPath(); - - auto list = CResourceHandler::get()->getFilteredFiles([](const ResourceID & ident) - { - return ident.getType() == EResType::TEXT && boost::algorithm::starts_with(ident.getName(), "DATA/"); - }); - - for (auto & filename : list) - { - const bfs::path filePath = outPath / (filename.getName() + ".TXT"); - - bfs::create_directories(filePath.parent_path()); - - bfs::ofstream file(filePath); - auto text = CResourceHandler::get()->load(filename)->readAll(); - - file.write((char*)text.first.get(), text.second); - } - - std::cout << "\rExtracting done :)\n"; - std::cout << " Extracted files can be found in " << outPath << " directory\n"; - } - else if(cn=="crash") - { - int *ptr = nullptr; - *ptr = 666; - //disaster! - } - else if(cn == "mp" && adventureInt) - { - if(const CGHeroInstance *h = dynamic_cast(adventureInt->selection)) - std::cout << h->movement << "; max: " << h->maxMovePoints(true) << "/" << h->maxMovePoints(false) << std::endl; - } - else if(cn == "bonuses") - { - bool jsonFormat = (message == "bonuses json"); - auto format = [jsonFormat](const BonusList & b) -> std::string - { - if(jsonFormat) - return b.toJsonNode().toJson(true); - std::ostringstream ss; - ss << b; - return ss.str(); - }; - std::cout << "Bonuses of " << adventureInt->selection->getObjectName() << std::endl - << format(adventureInt->selection->getBonusList()) << std::endl; - - std::cout << "\nInherited bonuses:\n"; - TCNodes parents; - adventureInt->selection->getParents(parents); - for(const CBonusSystemNode *parent : parents) - { - std::cout << "\nBonuses from " << typeid(*parent).name() << std::endl << format(*parent->getAllBonuses(Selector::all, Selector::all)) << std::endl; - } - } - else if(cn == "not dialog") - { - LOCPLINT->showingDialog->setn(false); - } - else if(cn == "gui") - { - for(auto & child : GH.listInt) - { - const auto childPtr = child.get(); - if(const CIntObject * obj = dynamic_cast(childPtr)) - printInfoAboutIntObject(obj, 0); - else - std::cout << typeid(childPtr).name() << std::endl; - } - } - else if(cn=="tell") - { - std::string what; - int id1, id2; - readed >> what >> id1 >> id2; - if(what == "hs") - { - for(const CGHeroInstance *h : LOCPLINT->cb->getHeroesInfo()) - if(h->type->ID.getNum() == id1) - if(const CArtifactInstance *a = h->getArt(ArtifactPosition(id2))) - std::cout << a->nodeName(); - } - } - else if (cn == "set") - { - std::string what, value; - readed >> what; - - Settings conf = settings.write["session"][what]; - - readed >> value; - - if (value == "on") - { - conf->Bool() = true; - logGlobal->info("Option %s enabled!", what); - } - else if (value == "off") - { - conf->Bool() = false; - logGlobal->info("Option %s disabled!", what); - } - } - else if(cn == "unlock") - { - std::string mxname; - readed >> mxname; - if(mxname == "pim" && LOCPLINT) - LOCPLINT->pim->unlock(); - } - else if(cn == "def2bmp") - { - std::string URI; - readed >> URI; - std::unique_ptr anim = std::make_unique(URI); - anim->preload(); - anim->exportBitmaps(VCMIDirs::get().userExtractedPath()); - } - else if(cn == "extract") - { - std::string URI; - readed >> URI; - - if (CResourceHandler::get()->existsResource(ResourceID(URI))) - { - const bfs::path outPath = VCMIDirs::get().userExtractedPath() / URI; - - auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll(); - - bfs::create_directories(outPath.parent_path()); - bfs::ofstream outFile(outPath, bfs::ofstream::binary); - outFile.write((char*)data.first.get(), data.second); - } - else - logGlobal->error("File not found!"); - } - else if(cn == "setBattleAI") - { - std::string fname; - readed >> fname; - std::cout << "Will try loading that AI to see if it is correct name...\n"; - try - { - if(auto ai = CDynLibHandler::getNewBattleAI(fname)) //test that given AI is indeed available... heavy but it is easy to make a typo and break the game - { - Settings neutralAI = settings.write["server"]["neutralAI"]; - neutralAI->String() = fname; - std::cout << "Setting changed, from now the battle ai will be " << fname << "!\n"; - } - } - catch(std::exception &e) - { - logGlobal->warn("Failed opening %s: %s", fname, e.what()); - logGlobal->warn("Setting not changes, AI not found or invalid!"); - } - } - - auto giveTurn = [&](PlayerColor player) - { - YourTurn yt; - yt.player = player; - yt.daysWithoutCastle = CSH->client->getPlayerState(player)->daysWithoutCastle; - yt.applyCl(CSH->client); - }; - - Settings session = settings.write["session"]; - if(cn == "autoskip") - { - session["autoSkip"].Bool() = !session["autoSkip"].Bool(); - } - else if(cn == "gosolo") - { - boost::unique_lock un(*CPlayerInterface::pim); - if(!CSH->client) - { - std::cout << "Game in not active"; - return; - } - PlayerColor color; - if(session["aiSolo"].Bool()) - { - for(auto & elem : CSH->client->gameState()->players) - { - if(elem.second.human) - CSH->client->installNewPlayerInterface(std::make_shared(elem.first), elem.first); - } - } - else - { - color = LOCPLINT->playerID; - removeGUI(); - for(auto & elem : CSH->client->gameState()->players) - { - if(elem.second.human) - { - auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(elem.first), false); - logNetwork->info("Player %s will be lead by %s", elem.first, AiToGive); - CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first); - } - } - GH.totalRedraw(); - giveTurn(color); - } - session["aiSolo"].Bool() = !session["aiSolo"].Bool(); - } - else if(cn == "controlai") - { - std::string colorName; - readed >> colorName; - boost::to_lower(colorName); - - boost::unique_lock un(*CPlayerInterface::pim); - if(!CSH->client) - { - std::cout << "Game in not active"; - return; - } - PlayerColor color; - if(LOCPLINT) - color = LOCPLINT->playerID; - for(auto & elem : CSH->client->gameState()->players) - { - if(elem.second.human || (colorName.length() && - elem.first.getNum() != vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, colorName))) - { - continue; - } - - removeGUI(); - CSH->client->installNewPlayerInterface(std::make_shared(elem.first), elem.first); - } - GH.totalRedraw(); - if(color != PlayerColor::NEUTRAL) - giveTurn(color); - } - // Check mantis issue 2292 for details -/* else if(client && client->serv && client->serv->connected && LOCPLINT) //send to server - { - boost::unique_lock un(*CPlayerInterface::pim); - LOCPLINT->cb->sendMessage(message); - }*/ -} -#endif - //plays intro, ends when intro is over or button has been pressed (handles events) void playIntro() { diff --git a/client/CMT.h b/client/CMT.h index c06a684b6..e3378edfe 100644 --- a/client/CMT.h +++ b/client/CMT.h @@ -19,5 +19,4 @@ extern SDL_Surface *screen; // main screen surface extern SDL_Surface *screen2; // and hlp surface (used to store not-active interfaces layer) extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed -void removeGUI(); void handleQuit(bool ask = true); diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 8c84ecd07..81dafb61d 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -81,6 +81,7 @@ set(client_SRCS NetPacksClient.cpp NetPacksLobbyClient.cpp SDLRWwrapper.cpp + ClientCommandManager.cpp ) set(client_HEADERS @@ -165,6 +166,7 @@ set(client_HEADERS mapHandler.h resource.h SDLRWwrapper.h + ClientCommandManager.h ) if(APPLE_IOS) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp new file mode 100644 index 000000000..678df709a --- /dev/null +++ b/client/ClientCommandManager.cpp @@ -0,0 +1,510 @@ +/* + * ClientCommandManager.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "ClientCommandManager.h" + +#include "Client.h" +#include "CPlayerInterface.h" +#include "CServerHandler.h" +#include "gui/CGuiHandler.h" +#include "../lib/NetPacks.h" +#include "../lib/CConfigHandler.h" +#include "../lib/CGameState.h" +#include "../lib/CPlayerState.h" +#include "../lib/StringConstants.h" +#include "gui/CAnimation.h" +#include "windows/CAdvmapInterface.h" +#include "windows/CCastleInterface.h" +#include "../CCallback.h" +#include "../lib/CGeneralTextHandler.h" +#include "../lib/CHeroHandler.h" +#include "../lib/CModHandler.h" +#include "../lib/VCMIDirs.h" + +#ifdef SCRIPTING_ENABLED +#include "../lib/ScriptHandler.h" +#endif + + +void ClientCommandManager::handleGoSolo() +{ + Settings session = settings.write["session"]; + + boost::unique_lock un(*CPlayerInterface::pim); + if(!CSH->client) + { + std::cout << "Game is not in playing state"; + return; + } + PlayerColor color; + if(session["aiSolo"].Bool()) + { + for(auto & elem : CSH->client->gameState()->players) + { + if(elem.second.human) + CSH->client->installNewPlayerInterface(std::make_shared(elem.first), elem.first); + } + } + else + { + color = LOCPLINT->playerID; + removeGUI(); + for(auto & elem : CSH->client->gameState()->players) + { + if(elem.second.human) + { + auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(elem.first), false); + logNetwork->info("Player %s will be lead by %s", elem.first, AiToGive); + CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first); + } + } + GH.totalRedraw(); + giveTurn(color); + } + session["aiSolo"].Bool() = !session["aiSolo"].Bool(); +} + +void ClientCommandManager::handleControlAi(const std::string &colorName) +{ + boost::unique_lock un(*CPlayerInterface::pim); + if(!CSH->client) + { + std::cout << "Game is not in playing state"; + return; + } + PlayerColor color; + if(LOCPLINT) + color = LOCPLINT->playerID; + for(auto & elem : CSH->client->gameState()->players) + { + if(elem.second.human || (colorName.length() && + elem.first.getNum() != vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, colorName))) + { + continue; + } + + removeGUI(); + CSH->client->installNewPlayerInterface(std::make_shared(elem.first), elem.first); + } + GH.totalRedraw(); + if(color != PlayerColor::NEUTRAL) + giveTurn(color); +} + +#ifndef VCMI_IOS +void ClientCommandManager::processCommand(const std::string &message) +{ + std::istringstream readed; + readed.str(message); + std::string commandName; + readed >> commandName; + +// Check mantis issue 2292 for details +// if(LOCPLINT && LOCPLINT->cingconsole) +// LOCPLINT->cingconsole->print(message); + + if(message==std::string("die, fool")) + { + exit(EXIT_SUCCESS); + } + else if(commandName == std::string("activate")) + { + int what; + readed >> what; + switch (what) + { + case 0: + GH.topInt()->activate(); + break; + case 1: + adventureInt->activate(); + break; + case 2: + LOCPLINT->castleInt->activate(); + break; + default: + logGlobal->error("Wrong argument specified!"); + } + } + else if(commandName == "redraw") + { + GH.totalRedraw(); + } + else if(commandName == "screen") + { + std::cout << "Screenbuf points to "; + + if(screenBuf == screen) + logGlobal->error("screen"); + else if(screenBuf == screen2) + logGlobal->error("screen2"); + else + logGlobal->error("?!?"); + + SDL_SaveBMP(screen, "Screen_c.bmp"); + SDL_SaveBMP(screen2, "Screen2_c.bmp"); + } + else if(commandName == "save") + { + if(!CSH->client) + { + std::cout << "Game is not in playing state"; + return; + } + std::string fname; + readed >> fname; + CSH->client->save(fname); + } +// else if(commandName=="load") +// { +// // TODO: this code should end the running game and manage to call startGame instead +// std::string fname; +// readed >> fname; +// CSH->client->loadGame(fname); +// } + else if(message=="convert txt") + { + //TODO: to be replaced with "VLC->generaltexth->dumpAllTexts();" due to https://github.com/vcmi/vcmi/pull/1329 merge: + + std::cout << "Command accepted.\t"; + + const boost::filesystem::path outPath = + VCMIDirs::get().userExtractedPath(); + + boost::filesystem::create_directories(outPath); + + auto extractVector = [=](const std::vector & source, const std::string & name) + { + JsonNode data(JsonNode::JsonType::DATA_VECTOR); + int64_t index = 0; + for(auto & line : source) + { + JsonNode lineNode(JsonNode::JsonType::DATA_STRUCT); + lineNode["text"].String() = line; + lineNode["index"].Integer() = index++; + data.Vector().push_back(lineNode); + } + + const boost::filesystem::path filePath = outPath / (name + ".json"); + boost::filesystem::ofstream file(filePath); + file << data.toJson(); + }; + + extractVector(VLC->generaltexth->allTexts, "generalTexts"); + extractVector(VLC->generaltexth->jktexts, "jkTexts"); + extractVector(VLC->generaltexth->arraytxt, "arrayTexts"); + + std::cout << "\rExtracting done :)\n"; + std::cout << " Extracted files can be found in " << outPath << " directory\n"; + } + else if(message=="get config") + { + std::cout << "Command accepted.\t"; + + const boost::filesystem::path outPath = + VCMIDirs::get().userExtractedPath() / "configuration"; + + boost::filesystem::create_directories(outPath); + + const std::vector contentNames = {"heroClasses", "artifacts", "creatures", "factions", "objects", "heroes", "spells", "skills"}; + + for(auto contentName : contentNames) + { + auto & content = (*VLC->modh->content)[contentName]; + + auto contentOutPath = outPath / contentName; + boost::filesystem::create_directories(contentOutPath); + + for(auto & iter : content.modData) + { + const JsonNode & modData = iter.second.modData; + + for(auto & nameAndObject : modData.Struct()) + { + const JsonNode & object = nameAndObject.second; + + std::string name = CModHandler::normalizeIdentifier(object.meta, CModHandler::scopeBuiltin(), nameAndObject.first); + + boost::algorithm::replace_all(name,":","_"); + + const boost::filesystem::path filePath = contentOutPath / (name + ".json"); + boost::filesystem::ofstream file(filePath); + file << object.toJson(); + } + } + } + + std::cout << "\rExtracting done :)\n"; + std::cout << " Extracted files can be found in " << outPath << " directory\n"; + } +#if SCRIPTING_ENABLED + else if(message=="get scripts") + { + std::cout << "Command accepted.\t"; + + const boost::filesystem::path outPath = + VCMIDirs::get().userExtractedPath() / "scripts"; + + boost::filesystem::create_directories(outPath); + + for(auto & kv : VLC->scriptHandler->objects) + { + std::string name = kv.first; + boost::algorithm::replace_all(name,":","_"); + + const scripting::ScriptImpl * script = kv.second.get(); + boost::filesystem::path filePath = outPath / (name + ".lua"); + boost::filesystem::ofstream file(filePath); + file << script->getSource(); + } + std::cout << "\rExtracting done :)\n"; + std::cout << " Extracted files can be found in " << outPath << " directory\n"; + } +#endif + else if(message=="get txt") + { + std::cout << "Command accepted.\t"; + + const boost::filesystem::path outPath = + VCMIDirs::get().userExtractedPath(); + + auto list = + CResourceHandler::get()->getFilteredFiles([](const ResourceID & ident) + { + return ident.getType() == EResType::TEXT && boost::algorithm::starts_with(ident.getName(), "DATA/"); + }); + + for (auto & filename : list) + { + const boost::filesystem::path filePath = outPath / (filename.getName() + ".TXT"); + + boost::filesystem::create_directories(filePath.parent_path()); + + boost::filesystem::ofstream file(filePath); + auto text = CResourceHandler::get()->load(filename)->readAll(); + + file.write((char*)text.first.get(), text.second); + } + + std::cout << "\rExtracting done :)\n"; + std::cout << " Extracted files can be found in " << outPath << " directory\n"; + } + else if(commandName == "crash") + { + int *ptr = nullptr; + *ptr = 666; + //disaster! + } + else if(commandName == "mp" && adventureInt) + { + if(const CGHeroInstance *h = dynamic_cast(adventureInt->selection)) + std::cout << h->movement << "; max: " << h->maxMovePoints(true) << "/" << h->maxMovePoints(false) << std::endl; + } + else if(commandName == "bonuses") + { + bool jsonFormat = (message == "bonuses json"); + auto format = [jsonFormat](const BonusList & b) -> std::string + { + if(jsonFormat) + return b.toJsonNode().toJson(true); + std::ostringstream ss; + ss << b; + return ss.str(); + }; + std::cout << "Bonuses of " << adventureInt->selection->getObjectName() << std::endl + << format(adventureInt->selection->getBonusList()) << std::endl; + + std::cout << "\nInherited bonuses:\n"; + TCNodes parents; + adventureInt->selection->getParents(parents); + for(const CBonusSystemNode *parent : parents) + { + std::cout << "\nBonuses from " << typeid(*parent).name() << std::endl << format(*parent->getAllBonuses(Selector::all, Selector::all)) << std::endl; + } + } + else if(commandName == "not dialog") + { + LOCPLINT->showingDialog->setn(false); + } + else if(commandName == "gui") + { + for(auto & child : GH.listInt) + { + const auto childPtr = child.get(); + if(const CIntObject * obj = dynamic_cast(childPtr)) + printInfoAboutInterfaceObject(obj, 0); + else + std::cout << typeid(childPtr).name() << std::endl; + } + } + else if(commandName == "tell") + { + std::string what; + int id1, id2; + readed >> what >> id1 >> id2; + if(what == "hs") + { + for(const CGHeroInstance *h : LOCPLINT->cb->getHeroesInfo()) + if(h->type->ID.getNum() == id1) + if(const CArtifactInstance *a = h->getArt(ArtifactPosition(id2))) + std::cout << a->nodeName(); + } + } + else if (commandName == "set") + { + std::string what, value; + readed >> what; + + Settings config = settings.write["session"][what]; + + readed >> value; + + if (value == "on") + { + config->Bool() = true; + logGlobal->info("Option %s enabled!", what); + } + else if (value == "off") + { + config->Bool() = false; + logGlobal->info("Option %s disabled!", what); + } + } + else if(commandName == "unlock") + { + std::string mxname; + readed >> mxname; + if(mxname == "pim" && LOCPLINT) + LOCPLINT->pim->unlock(); + } + else if(commandName == "def2bmp") + { + std::string URI; + readed >> URI; + std::unique_ptr anim = std::make_unique(URI); + anim->preload(); + anim->exportBitmaps(VCMIDirs::get().userExtractedPath()); + } + else if(commandName == "extract") + { + std::string URI; + readed >> URI; + + if (CResourceHandler::get()->existsResource(ResourceID(URI))) + { + const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / URI; + + auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll(); + + boost::filesystem::create_directories(outPath.parent_path()); + boost::filesystem::ofstream outFile(outPath, boost::filesystem::ofstream::binary); + outFile.write((char*)data.first.get(), data.second); + } + else + logGlobal->error("File not found!"); + } + else if(commandName == "setBattleAI") + { + std::string fname; + readed >> fname; + std::cout << "Will try loading that AI to see if it is correct name...\n"; + try + { + if(auto ai = CDynLibHandler::getNewBattleAI(fname)) //test that given AI is indeed available... heavy but it is easy to make a typo and break the game + { + Settings neutralAI = settings.write["server"]["neutralAI"]; + neutralAI->String() = fname; + std::cout << "Setting changed, from now the battle ai will be " << fname << "!\n"; + } + } + catch(std::exception &e) + { + logGlobal->warn("Failed opening %s: %s", fname, e.what()); + logGlobal->warn("Setting not changed, AI not found or invalid!"); + } + } + + Settings session = settings.write["session"]; + if(commandName == "autoskip") + { + session["autoSkip"].Bool() = !session["autoSkip"].Bool(); + } + else if(commandName == "gosolo") + { + ClientCommandManager::handleGoSolo(); + } + else if(commandName == "controlai") + { + std::string colorName; + readed >> colorName; + boost::to_lower(colorName); + + ClientCommandManager::handleControlAi(colorName); + } + // Check mantis issue 2292 for details +/* else if(client && client->serv && client->serv->connected && LOCPLINT) //send to server + { + boost::unique_lock un(*CPlayerInterface::pim); + LOCPLINT->cb->sendMessage(message); + }*/ +} +#endif + +void ClientCommandManager::giveTurn(const PlayerColor &colorIdentifier) +{ + YourTurn yt; + yt.player = colorIdentifier; + yt.daysWithoutCastle = CSH->client->getPlayerState(colorIdentifier)->daysWithoutCastle; + yt.applyCl(CSH->client); +} + +void ClientCommandManager::removeGUI() +{ + // CClient::endGame + GH.curInt = nullptr; + if(GH.topInt()) + GH.topInt()->deactivate(); + GH.listInt.clear(); + GH.objsToBlit.clear(); + GH.statusbar = nullptr; + logGlobal->info("Removed GUI."); + + LOCPLINT = nullptr; +} + +void ClientCommandManager::printInfoAboutInterfaceObject(const CIntObject *obj, int level) +{ + std::stringstream sbuffer; + sbuffer << std::string(level, '\t'); + + sbuffer << typeid(*obj).name() << " *** "; + if (obj->active) + { +#define PRINT(check, text) if (obj->active & CIntObject::check) sbuffer << text + PRINT(LCLICK, 'L'); + PRINT(RCLICK, 'R'); + PRINT(HOVER, 'H'); + PRINT(MOVE, 'M'); + PRINT(KEYBOARD, 'K'); + PRINT(TIME, 'T'); + PRINT(GENERAL, 'A'); + PRINT(WHEEL, 'W'); + PRINT(DOUBLECLICK, 'D'); +#undef PRINT + } + else + sbuffer << "inactive"; + sbuffer << " at " << obj->pos.x <<"x"<< obj->pos.y; + sbuffer << " (" << obj->pos.w <<"x"<< obj->pos.h << ")"; + logGlobal->info(sbuffer.str()); + + for(const CIntObject *child : obj->children) + printInfoAboutInterfaceObject(child, level+1); +} \ No newline at end of file diff --git a/client/ClientCommandManager.h b/client/ClientCommandManager.h new file mode 100644 index 000000000..e117664b8 --- /dev/null +++ b/client/ClientCommandManager.h @@ -0,0 +1,27 @@ +/* + * ClientCommandManager.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +class PlayerColor; +class CIntObject; + +class ClientCommandManager +{ + static void giveTurn(const PlayerColor &color); + static void removeGUI(); + static void printInfoAboutInterfaceObject(const CIntObject *obj, int level); +public: +#ifndef VCMI_IOS + static void processCommand(const std::string &message); +#endif + static void handleGoSolo(); + static void handleControlAi(const std::string &colorName); +}; From 622e012fcffdcee0b9e70f5cc922b32d1975b0e8 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 6 Jan 2023 21:01:00 +0100 Subject: [PATCH 038/197] Allow ingame console to use clientside commands and print output --- client/ClientCommandManager.cpp | 117 ++++++++++++++++--------- client/ClientCommandManager.h | 11 ++- client/widgets/AdventureMapClasses.cpp | 8 +- client/widgets/AdventureMapClasses.h | 2 +- lib/CConsoleHandler.cpp | 6 +- lib/CConsoleHandler.h | 2 +- 6 files changed, 95 insertions(+), 51 deletions(-) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 678df709a..ed8bc537b 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -32,6 +32,7 @@ #include "../lib/ScriptHandler.h" #endif +bool ClientCommandManager::currentCallFromIngameConsole; void ClientCommandManager::handleGoSolo() { @@ -40,7 +41,7 @@ void ClientCommandManager::handleGoSolo() boost::unique_lock un(*CPlayerInterface::pim); if(!CSH->client) { - std::cout << "Game is not in playing state"; + printCommandMessage("Game is not in playing state"); return; } PlayerColor color; @@ -61,7 +62,7 @@ void ClientCommandManager::handleGoSolo() if(elem.second.human) { auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(elem.first), false); - logNetwork->info("Player %s will be lead by %s", elem.first, AiToGive); + printCommandMessage("Player " + elem.first.getStr() + " will be lead by " + AiToGive, ELogLevel::INFO); CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first); } } @@ -76,7 +77,7 @@ void ClientCommandManager::handleControlAi(const std::string &colorName) boost::unique_lock un(*CPlayerInterface::pim); if(!CSH->client) { - std::cout << "Game is not in playing state"; + printCommandMessage("Game is not in playing state"); return; } PlayerColor color; @@ -98,18 +99,19 @@ void ClientCommandManager::handleControlAi(const std::string &colorName) giveTurn(color); } -#ifndef VCMI_IOS -void ClientCommandManager::processCommand(const std::string &message) +void ClientCommandManager::processCommand(const std::string &message, bool calledFromIngameConsole) { std::istringstream readed; readed.str(message); std::string commandName; readed >> commandName; + currentCallFromIngameConsole = calledFromIngameConsole; // Check mantis issue 2292 for details // if(LOCPLINT && LOCPLINT->cingconsole) // LOCPLINT->cingconsole->print(message); + if(message==std::string("die, fool")) { exit(EXIT_SUCCESS); @@ -130,7 +132,7 @@ void ClientCommandManager::processCommand(const std::string &message) LOCPLINT->castleInt->activate(); break; default: - logGlobal->error("Wrong argument specified!"); + printCommandMessage("Wrong argument specified!", ELogLevel::ERROR); } } else if(commandName == "redraw") @@ -139,14 +141,14 @@ void ClientCommandManager::processCommand(const std::string &message) } else if(commandName == "screen") { - std::cout << "Screenbuf points to "; + printCommandMessage("Screenbuf points to "); if(screenBuf == screen) - logGlobal->error("screen"); + printCommandMessage("screen", ELogLevel::ERROR); else if(screenBuf == screen2) - logGlobal->error("screen2"); + printCommandMessage("screen2", ELogLevel::ERROR); else - logGlobal->error("?!?"); + printCommandMessage("?!?", ELogLevel::ERROR); SDL_SaveBMP(screen, "Screen_c.bmp"); SDL_SaveBMP(screen2, "Screen2_c.bmp"); @@ -155,7 +157,7 @@ void ClientCommandManager::processCommand(const std::string &message) { if(!CSH->client) { - std::cout << "Game is not in playing state"; + printCommandMessage("Game is not in playing state"); return; } std::string fname; @@ -173,7 +175,7 @@ void ClientCommandManager::processCommand(const std::string &message) { //TODO: to be replaced with "VLC->generaltexth->dumpAllTexts();" due to https://github.com/vcmi/vcmi/pull/1329 merge: - std::cout << "Command accepted.\t"; + printCommandMessage("Command accepted.\t"); const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath(); @@ -201,12 +203,12 @@ void ClientCommandManager::processCommand(const std::string &message) extractVector(VLC->generaltexth->jktexts, "jkTexts"); extractVector(VLC->generaltexth->arraytxt, "arrayTexts"); - std::cout << "\rExtracting done :)\n"; - std::cout << " Extracted files can be found in " << outPath << " directory\n"; + printCommandMessage("\rExtracting done :)\n"); + printCommandMessage("Extracted files can be found in" + outPath.string() + " directory\n"); } else if(message=="get config") { - std::cout << "Command accepted.\t"; + printCommandMessage("Command accepted.\t"); const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "configuration"; @@ -241,13 +243,13 @@ void ClientCommandManager::processCommand(const std::string &message) } } - std::cout << "\rExtracting done :)\n"; - std::cout << " Extracted files can be found in " << outPath << " directory\n"; + printCommandMessage("\rExtracting done :)\n"); + printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n"); } #if SCRIPTING_ENABLED else if(message=="get scripts") { - std::cout << "Command accepted.\t"; + printCommandMessage("Command accepted.\t"); const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "scripts"; @@ -264,13 +266,13 @@ void ClientCommandManager::processCommand(const std::string &message) boost::filesystem::ofstream file(filePath); file << script->getSource(); } - std::cout << "\rExtracting done :)\n"; - std::cout << " Extracted files can be found in " << outPath << " directory\n"; + printCommandMessage("\rExtracting done :)\n"); + printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n"); } #endif else if(message=="get txt") { - std::cout << "Command accepted.\t"; + printCommandMessage("Command accepted.\t"); const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath(); @@ -293,8 +295,8 @@ void ClientCommandManager::processCommand(const std::string &message) file.write((char*)text.first.get(), text.second); } - std::cout << "\rExtracting done :)\n"; - std::cout << " Extracted files can be found in " << outPath << " directory\n"; + printCommandMessage("\rExtracting done :)\n"); + printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n"); } else if(commandName == "crash") { @@ -305,7 +307,7 @@ void ClientCommandManager::processCommand(const std::string &message) else if(commandName == "mp" && adventureInt) { if(const CGHeroInstance *h = dynamic_cast(adventureInt->selection)) - std::cout << h->movement << "; max: " << h->maxMovePoints(true) << "/" << h->maxMovePoints(false) << std::endl; + printCommandMessage(std::to_string(h->movement) + "; max: " + std::to_string(h->maxMovePoints(true)) + "/" + std::to_string(h->maxMovePoints(false)) + "\n"); } else if(commandName == "bonuses") { @@ -318,15 +320,15 @@ void ClientCommandManager::processCommand(const std::string &message) ss << b; return ss.str(); }; - std::cout << "Bonuses of " << adventureInt->selection->getObjectName() << std::endl - << format(adventureInt->selection->getBonusList()) << std::endl; + printCommandMessage("Bonuses of " + adventureInt->selection->getObjectName() + "\n"); + printCommandMessage(format(adventureInt->selection->getBonusList()) + "\n"); - std::cout << "\nInherited bonuses:\n"; + printCommandMessage("\nInherited bonuses:\n"); TCNodes parents; adventureInt->selection->getParents(parents); for(const CBonusSystemNode *parent : parents) { - std::cout << "\nBonuses from " << typeid(*parent).name() << std::endl << format(*parent->getAllBonuses(Selector::all, Selector::all)) << std::endl; + printCommandMessage(std::string("\nBonuses from ") + typeid(*parent).name() + "\n" + format(*parent->getAllBonuses(Selector::all, Selector::all)) + "\n"); } } else if(commandName == "not dialog") @@ -341,7 +343,7 @@ void ClientCommandManager::processCommand(const std::string &message) if(const CIntObject * obj = dynamic_cast(childPtr)) printInfoAboutInterfaceObject(obj, 0); else - std::cout << typeid(childPtr).name() << std::endl; + printCommandMessage(std::string(typeid(childPtr).name()) + "\n"); } } else if(commandName == "tell") @@ -354,7 +356,7 @@ void ClientCommandManager::processCommand(const std::string &message) for(const CGHeroInstance *h : LOCPLINT->cb->getHeroesInfo()) if(h->type->ID.getNum() == id1) if(const CArtifactInstance *a = h->getArt(ArtifactPosition(id2))) - std::cout << a->nodeName(); + printCommandMessage(a->nodeName()); } } else if (commandName == "set") @@ -369,12 +371,12 @@ void ClientCommandManager::processCommand(const std::string &message) if (value == "on") { config->Bool() = true; - logGlobal->info("Option %s enabled!", what); + printCommandMessage("Option " + what + " enabled!", ELogLevel::INFO); } else if (value == "off") { config->Bool() = false; - logGlobal->info("Option %s disabled!", what); + printCommandMessage("Option " + what + " disabled!", ELogLevel::INFO); } } else if(commandName == "unlock") @@ -408,26 +410,26 @@ void ClientCommandManager::processCommand(const std::string &message) outFile.write((char*)data.first.get(), data.second); } else - logGlobal->error("File not found!"); + printCommandMessage("File not found!", ELogLevel::ERROR); } else if(commandName == "setBattleAI") { std::string fname; readed >> fname; - std::cout << "Will try loading that AI to see if it is correct name...\n"; + printCommandMessage("Will try loading that AI to see if it is correct name...\n"); try { if(auto ai = CDynLibHandler::getNewBattleAI(fname)) //test that given AI is indeed available... heavy but it is easy to make a typo and break the game { Settings neutralAI = settings.write["server"]["neutralAI"]; neutralAI->String() = fname; - std::cout << "Setting changed, from now the battle ai will be " << fname << "!\n"; + printCommandMessage("Setting changed, from now the battle ai will be " + fname + "!\n"); } } catch(std::exception &e) { - logGlobal->warn("Failed opening %s: %s", fname, e.what()); - logGlobal->warn("Setting not changed, AI not found or invalid!"); + printCommandMessage("Failed opening " + fname + ": " + e.what(), ELogLevel::WARN); + printCommandMessage("Setting not changed, AI not found or invalid!", ELogLevel::WARN); } } @@ -455,7 +457,6 @@ void ClientCommandManager::processCommand(const std::string &message) LOCPLINT->cb->sendMessage(message); }*/ } -#endif void ClientCommandManager::giveTurn(const PlayerColor &colorIdentifier) { @@ -474,7 +475,7 @@ void ClientCommandManager::removeGUI() GH.listInt.clear(); GH.objsToBlit.clear(); GH.statusbar = nullptr; - logGlobal->info("Removed GUI."); + printCommandMessage("Removed GUI.", ELogLevel::INFO); LOCPLINT = nullptr; } @@ -503,8 +504,44 @@ void ClientCommandManager::printInfoAboutInterfaceObject(const CIntObject *obj, sbuffer << "inactive"; sbuffer << " at " << obj->pos.x <<"x"<< obj->pos.y; sbuffer << " (" << obj->pos.w <<"x"<< obj->pos.h << ")"; - logGlobal->info(sbuffer.str()); + printCommandMessage(sbuffer.str(), ELogLevel::INFO); for(const CIntObject *child : obj->children) printInfoAboutInterfaceObject(child, level+1); +} + +void ClientCommandManager::printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType) +{ + switch(messageType) + { + case ELogLevel::NOT_SET: + std::cout << commandMessage; + break; + case ELogLevel::TRACE: + logGlobal->trace(commandMessage); + break; + case ELogLevel::DEBUG: + logGlobal->debug(commandMessage); + break; + case ELogLevel::INFO: + logGlobal->info(commandMessage); + break; + case ELogLevel::WARN: + logGlobal->warn(commandMessage); + break; + case ELogLevel::ERROR: + logGlobal->error(commandMessage); + break; + default: + std::cout << commandMessage; + break; + } + + if(currentCallFromIngameConsole) + { + if(LOCPLINT && LOCPLINT->cingconsole) + { + LOCPLINT->cingconsole->print(commandMessage); + } + } } \ No newline at end of file diff --git a/client/ClientCommandManager.h b/client/ClientCommandManager.h index e117664b8..01dae3774 100644 --- a/client/ClientCommandManager.h +++ b/client/ClientCommandManager.h @@ -15,13 +15,16 @@ class CIntObject; class ClientCommandManager { + static bool currentCallFromIngameConsole; + static void giveTurn(const PlayerColor &color); static void removeGUI(); static void printInfoAboutInterfaceObject(const CIntObject *obj, int level); -public: -#ifndef VCMI_IOS - static void processCommand(const std::string &message); -#endif + static void printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType = ELogLevel::NOT_SET); static void handleGoSolo(); static void handleControlAi(const std::string &colorName); + +public: + ClientCommandManager() = delete; + static void processCommand(const std::string &message, bool calledFromIngameConsole); }; diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index 93b4900f6..8155019ed 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -48,6 +48,7 @@ #include "../../lib/mapping/CMap.h" #include "../../lib/NetPacksBase.h" #include "../../lib/StringConstants.h" +#include "ClientCommandManager.h" CList::CListItem::CListItem(CList * Parent) : CIntObject(LCLICK | RCLICK | HOVER), @@ -1144,15 +1145,18 @@ void CInGameConsole::startEnteringText() GH.statusbar->setEnteredText(enteredText); } -void CInGameConsole::endEnteringText(bool printEnteredText) +void CInGameConsole::endEnteringText(bool processEnteredText) { captureAllKeys = false; prevEntDisp = -1; - if(printEnteredText) + if(processEnteredText) { std::string txt = enteredText.substr(0, enteredText.size()-1); LOCPLINT->cb->sendMessage(txt, LOCPLINT->getSelection()); previouslyEntered.push_back(txt); + + boost::thread clientCommandThread(ClientCommandManager::processCommand, txt, true); + clientCommandThread.join(); } enteredText.clear(); diff --git a/client/widgets/AdventureMapClasses.h b/client/widgets/AdventureMapClasses.h index 1c7ef5b50..e86d26e72 100644 --- a/client/widgets/AdventureMapClasses.h +++ b/client/widgets/AdventureMapClasses.h @@ -424,7 +424,7 @@ public: void textEdited(const SDL_TextEditingEvent & event) override; void startEnteringText(); - void endEnteringText(bool printEnteredText); + void endEnteringText(bool processEnteredText); void refreshEnteredText(); CInGameConsole(); diff --git a/lib/CConsoleHandler.cpp b/lib/CConsoleHandler.cpp index 0ae62d4e6..4b61a9074 100644 --- a/lib/CConsoleHandler.cpp +++ b/lib/CConsoleHandler.cpp @@ -230,7 +230,7 @@ int CConsoleHandler::run() { if ( getline(std::cin, buffer).good() ) if ( cb && *cb ) - (*cb)(buffer); + (*cb)(buffer, false); } else boost::this_thread::sleep(boost::posix_time::millisec(100)); @@ -239,7 +239,7 @@ int CConsoleHandler::run() #else std::getline(std::cin, buffer); if ( cb && *cb ) - (*cb)(buffer); + (*cb)(buffer, false); #endif } return -1; @@ -263,7 +263,7 @@ CConsoleHandler::CConsoleHandler() : thread(nullptr) #else defColor = "\x1b[0m"; #endif - cb = new std::function; + cb = new std::function; } CConsoleHandler::~CConsoleHandler() { diff --git a/lib/CConsoleHandler.h b/lib/CConsoleHandler.h index 4ac139260..cafbd838c 100644 --- a/lib/CConsoleHandler.h +++ b/lib/CConsoleHandler.h @@ -76,7 +76,7 @@ public: #endif } - std::function *cb; //function to be called when message is received + std::function *cb; //function to be called when message is received private: int run(); From 4157feb2f3e78f3d12bb33eda52eaba2b256775b Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 6 Jan 2023 21:20:46 +0100 Subject: [PATCH 039/197] Small cleanup --- client/CMT.cpp | 30 -------------------------- client/widgets/AdventureMapClasses.cpp | 1 + 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index 40fbbcedf..8cf16809e 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -541,36 +541,6 @@ int main(int argc, char * argv[]) return 0; } -void printInfoAboutIntObject(const CIntObject *obj, int level) -{ - std::stringstream sbuffer; - sbuffer << std::string(level, '\t'); - - sbuffer << typeid(*obj).name() << " *** "; - if (obj->active) - { -#define PRINT(check, text) if (obj->active & CIntObject::check) sbuffer << text - PRINT(LCLICK, 'L'); - PRINT(RCLICK, 'R'); - PRINT(HOVER, 'H'); - PRINT(MOVE, 'M'); - PRINT(KEYBOARD, 'K'); - PRINT(TIME, 'T'); - PRINT(GENERAL, 'A'); - PRINT(WHEEL, 'W'); - PRINT(DOUBLECLICK, 'D'); -#undef PRINT - } - else - sbuffer << "inactive"; - sbuffer << " at " << obj->pos.x <<"x"<< obj->pos.y; - sbuffer << " (" << obj->pos.w <<"x"<< obj->pos.h << ")"; - logGlobal->info(sbuffer.str()); - - for(const CIntObject *child : obj->children) - printInfoAboutIntObject(child, level+1); -} - //plays intro, ends when intro is over or button has been pressed (handles events) void playIntro() { diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index 8155019ed..eb328541f 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -1155,6 +1155,7 @@ void CInGameConsole::endEnteringText(bool processEnteredText) LOCPLINT->cb->sendMessage(txt, LOCPLINT->getSelection()); previouslyEntered.push_back(txt); + //some commands like gosolo don't work when executed from GUI thread boost::thread clientCommandThread(ClientCommandManager::processCommand, txt, true); clientCommandThread.join(); } From 7708885cf94e5a4bbff9f2a4dd7215b5498a1bcf Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 6 Jan 2023 22:53:10 +0100 Subject: [PATCH 040/197] Use / as command prefix --- client/widgets/AdventureMapClasses.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index eb328541f..422b28a3c 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -1155,9 +1155,12 @@ void CInGameConsole::endEnteringText(bool processEnteredText) LOCPLINT->cb->sendMessage(txt, LOCPLINT->getSelection()); previouslyEntered.push_back(txt); - //some commands like gosolo don't work when executed from GUI thread - boost::thread clientCommandThread(ClientCommandManager::processCommand, txt, true); - clientCommandThread.join(); + if(txt.at(0) == '/') + { + //some commands like gosolo don't work when executed from GUI thread + boost::thread clientCommandThread(ClientCommandManager::processCommand, txt.substr(1), true); + clientCommandThread.join(); + } } enteredText.clear(); From ce4c1ca48b40da5845e4ec9f824661be84406859 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 6 Jan 2023 23:37:57 +0100 Subject: [PATCH 041/197] Include StdInc.h --- client/ClientCommandManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index ed8bc537b..f159a4bfb 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -7,6 +7,7 @@ * Full text of license available in license.txt file, in main folder * */ +#include "StdInc.h" #include "ClientCommandManager.h" From 9f9798d3a4377d8021ee686af48d1f442190644b Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Mon, 2 Jan 2023 14:11:02 +0200 Subject: [PATCH 042/197] Update android JNI bindings --- client/Client.cpp | 8 ++++++- client/widgets/TextControls.cpp | 37 --------------------------------- client/widgets/TextControls.h | 3 --- lib/CAndroidVMHelper.cpp | 5 ----- lib/CAndroidVMHelper.h | 2 -- 5 files changed, 7 insertions(+), 48 deletions(-) diff --git a/client/Client.cpp b/client/Client.cpp index 5ee3c6cb0..ded4b4d82 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -770,8 +770,14 @@ void CClient::reinitScripting() #endif } - #ifdef VCMI_ANDROID +extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_clientSetupJNI(JNIEnv * env, jobject cls) +{ + logNetwork->info("Received clientSetupJNI"); + + CAndroidVMHelper::cacheVM(env); +} + extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jobject cls) { logNetwork->info("Received server closed signal"); diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index 3fdf070e3..7dff297a8 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -488,9 +488,6 @@ CKeyboardFocusListener::CKeyboardFocusListener(CTextInput * textInput) void CKeyboardFocusListener::focusGot() { CSDL_Ext::startTextInput(&textInput->pos); -#ifdef VCMI_ANDROID - textInput->notifyAndroidTextInputChanged(textInput->text); -#endif usageIndex++; } @@ -552,9 +549,6 @@ void CTextInput::keyPressed(const SDL_KeyboardEvent & key) { redraw(); cb(text); -#ifdef VCMI_ANDROID - notifyAndroidTextInputChanged(text); -#endif } } @@ -563,10 +557,6 @@ void CTextInput::setText(const std::string & nText, bool callCb) CLabel::setText(nText); if(callCb) cb(text); - -#ifdef VCMI_ANDROID - notifyAndroidTextInputChanged(text); -#endif } bool CTextInput::captureThisEvent(const SDL_KeyboardEvent & key) @@ -592,10 +582,6 @@ void CTextInput::textInputed(const SDL_TextInputEvent & event) cb(text); } newText.clear(); - -#ifdef VCMI_ANDROID - notifyAndroidTextInputChanged(text); -#endif } void CTextInput::textEdited(const SDL_TextEditingEvent & event) @@ -606,11 +592,6 @@ void CTextInput::textEdited(const SDL_TextEditingEvent & event) newText = event.text; redraw(); cb(text + newText); - -#ifdef VCMI_ANDROID - auto editedText = text + newText; - notifyAndroidTextInputChanged(editedText); -#endif } void CTextInput::filenameFilter(std::string & text, const std::string &) @@ -657,24 +638,6 @@ void CTextInput::numberFilter(std::string & text, const std::string & oldText, i } } -#ifdef VCMI_ANDROID -void CTextInput::notifyAndroidTextInputChanged(std::string & text) -{ - if(!focus) - return; - - auto fun = [&text](JNIEnv * env, jclass cls, jmethodID method) - { - auto jtext = env->NewStringUTF(text.c_str()); - env->CallStaticVoidMethod(cls, method, jtext); - env->DeleteLocalRef(jtext); - }; - CAndroidVMHelper vmHelper; - vmHelper.callCustomMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "notifyTextInputChanged", - "(Ljava/lang/String;)V", fun, true); -} -#endif //VCMI_ANDROID - CFocusable::CFocusable() :CFocusable(std::make_shared()) { diff --git a/client/widgets/TextControls.h b/client/widgets/TextControls.h index 335d9c1c4..b4ba5c037 100644 --- a/client/widgets/TextControls.h +++ b/client/widgets/TextControls.h @@ -198,9 +198,6 @@ class CTextInput : public CLabel, public CFocusable protected: std::string visibleText() override; -#ifdef VCMI_ANDROID - void notifyAndroidTextInputChanged(std::string & text); -#endif public: CFunctionList cb; CFunctionList filters; diff --git a/lib/CAndroidVMHelper.cpp b/lib/CAndroidVMHelper.cpp index 075bf71dd..6ac49e164 100644 --- a/lib/CAndroidVMHelper.cpp +++ b/lib/CAndroidVMHelper.cpp @@ -21,11 +21,6 @@ void CAndroidVMHelper::cacheVM(JNIEnv * env) env->GetJavaVM(&vmCache); } -void CAndroidVMHelper::cacheVM(JavaVM * vm) -{ - vmCache = vm; -} - CAndroidVMHelper::CAndroidVMHelper() { auto res = vmCache->GetEnv((void **) &envPtr, JNI_VERSION_1_1); diff --git a/lib/CAndroidVMHelper.h b/lib/CAndroidVMHelper.h index 48e77d1ae..b46272d48 100644 --- a/lib/CAndroidVMHelper.h +++ b/lib/CAndroidVMHelper.h @@ -42,8 +42,6 @@ public: static void cacheVM(JNIEnv * env); - static void cacheVM(JavaVM * vm); - static constexpr const char * NATIVE_METHODS_DEFAULT_CLASS = "eu/vcmi/vcmi/NativeMethods"; }; From e78105c95e9ecb473d114f240c1cd051bfec5d30 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sat, 7 Jan 2023 12:47:00 +0100 Subject: [PATCH 043/197] Try Ivan's fix for iOS compilation --- client/ClientCommandManager.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ClientCommandManager.h b/client/ClientCommandManager.h index 01dae3774..c17a2da8f 100644 --- a/client/ClientCommandManager.h +++ b/client/ClientCommandManager.h @@ -10,7 +10,9 @@ #pragma once +VCMI_LIB_NAMESPACE_BEGIN class PlayerColor; +VCMI_LIB_NAMESPACE_END class CIntObject; class ClientCommandManager From 67f7e5d042da26ff83f31ac810ffa8af3bb29337 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 7 Jan 2023 16:15:45 +0200 Subject: [PATCH 044/197] Apply review suggestions --- config/schemas/mod.json | 12 ++++++------ launcher/settingsView/csettingsview_moc.cpp | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/config/schemas/mod.json b/config/schemas/mod.json index f77fdea91..ca74f80ef 100644 --- a/config/schemas/mod.json +++ b/config/schemas/mod.json @@ -5,7 +5,7 @@ "description" : "Format used to define main mod file (mod.json) in VCMI", "required" : [ "name", "description", "version", "author", "contact", "modType" ], "definitions" : { - "localizeable" : { + "localizable" : { "type":"object", "additionalProperties" : false, "required" : [ "name", "description", "author", "modType" ], @@ -114,23 +114,23 @@ }, "english" : { - "$ref" : "#/definitions/localizeable" + "$ref" : "#/definitions/localizable" }, "german" : { - "$ref" : "#/definitions/localizeable" + "$ref" : "#/definitions/localizable" }, "polish" : { - "$ref" : "#/definitions/localizeable" + "$ref" : "#/definitions/localizable" }, "russian" : { - "$ref" : "#/definitions/localizeable" + "$ref" : "#/definitions/localizable" }, "ukrainian" : { - "$ref" : "#/definitions/localizeable" + "$ref" : "#/definitions/localizable" }, "artifacts": { diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index fb7318be1..8ac81f65e 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -323,8 +323,8 @@ void CSettingsView::on_comboBoxLanguage_currentIndexChanged(int index) Settings node = settings.write["general"]["language"]; node->String() = languageTagList[index]; - if ( qApp->activeWindow() && dynamic_cast(qApp->activeWindow()) ) - dynamic_cast(qApp->activeWindow())->updateTranslation(); + if ( auto mainWindow = dynamic_cast(qApp->activeWindow()) ) + mainWindow->updateTranslation(); } void CSettingsView::changeEvent(QEvent *event) From 16d06827d64eea383df3d4fe82276dca69214083 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sat, 7 Jan 2023 15:42:34 +0100 Subject: [PATCH 045/197] Change thread join to detach --- client/widgets/AdventureMapClasses.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index 422b28a3c..c5d8ad36b 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -1159,7 +1159,7 @@ void CInGameConsole::endEnteringText(bool processEnteredText) { //some commands like gosolo don't work when executed from GUI thread boost::thread clientCommandThread(ClientCommandManager::processCommand, txt.substr(1), true); - clientCommandThread.join(); + clientCommandThread.detach(); } } enteredText.clear(); From b1427f695b9d4528ff9c111d487a7013fd66ce13 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 7 Jan 2023 18:01:39 +0200 Subject: [PATCH 046/197] Fix MXE --- CI/linux/before_install.sh | 2 +- CMakeLists.txt | 20 ++++++++++++++------ launcher/mainwindow_moc.cpp | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CI/linux/before_install.sh b/CI/linux/before_install.sh index 1b4d7fa3e..8b0c75d59 100644 --- a/CI/linux/before_install.sh +++ b/CI/linux/before_install.sh @@ -5,7 +5,7 @@ sudo apt-get update # Dependencies sudo apt-get install libboost-all-dev sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev -sudo apt-get install qtbase5-dev +sudo apt-get install qtbase5-dev sudo apt-get install ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev # Optional dependencies sudo apt-get install libminizip-dev libfuzzylite-dev qttools5-dev diff --git a/CMakeLists.txt b/CMakeLists.txt index 90f491f6e..042aab89d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,14 +128,22 @@ else() endif(ENABLE_GITVERSION) # Precompiled header configuration -if(ENABLE_PCH AND NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0") +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0 ) + set(ENABLE_PCH OFF) # broken +endif() + +if( ${CMAKE_VERSION} VERSION_LESS "3.16.0") + set(ENABLE_PCH OFF) #not supported +endif() + +if(ENABLE_PCH) macro(enable_pch name) target_precompile_headers(${name} PRIVATE $<$:>) endmacro(enable_pch) -else(ENABLE_PCH AND NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0") +else() macro(enable_pch ignore) endmacro(enable_pch) -endif(ENABLE_PCH AND NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0") +endif() ############################################ # Documentation section # @@ -337,12 +345,12 @@ endif() if(ENABLE_LAUNCHER OR ENABLE_EDITOR) # Widgets finds its own dependencies (QtGui and QtCore). - find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network ) - find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network ) + find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network) + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network) find_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools ) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools) - if(NOT Qt5LinguistTools_DIR) + if(NOT Qt${QT_VERSION_MAJOR}LinguistTools_DIR) set(ENABLE_TRANSLATIONS OFF) endif() if(ENABLE_TRANSLATIONS) diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 2e50c5686..0839561d5 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -164,7 +164,7 @@ void MainWindow::on_lobbyButton_clicked() void MainWindow::updateTranslation() { #ifdef ENABLE_QT_TRANSLATIONS - std::string translationFile = settings["general"]["language"].String()+ ".qm"; + std::string translationFile = settings["general"]["language"].String() + ".qm"; QVector searchPaths; From e28250b89982e1f876c044c82afb7d0371650ffb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 7 Jan 2023 18:26:02 +0200 Subject: [PATCH 047/197] Add missing include? --- launcher/mainwindow_moc.h | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/mainwindow_moc.h b/launcher/mainwindow_moc.h index 4a287ea79..b7505635d 100644 --- a/launcher/mainwindow_moc.h +++ b/launcher/mainwindow_moc.h @@ -10,6 +10,7 @@ #pragma once #include #include +#include namespace Ui { From 58fe0e6bd96025e25b7918a16199216cfbc636e6 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 8 Jan 2023 20:30:57 +0100 Subject: [PATCH 048/197] Apply command review suggestions + minor cleanup --- client/ClientCommandManager.cpp | 46 +++++++++++--------------- client/ClientCommandManager.h | 2 +- client/widgets/AdventureMapClasses.cpp | 3 +- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index f159a4bfb..3f3e55599 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -102,17 +102,12 @@ void ClientCommandManager::handleControlAi(const std::string &colorName) void ClientCommandManager::processCommand(const std::string &message, bool calledFromIngameConsole) { - std::istringstream readed; - readed.str(message); + std::istringstream singleWordBuffer; + singleWordBuffer.str(message); std::string commandName; - readed >> commandName; + singleWordBuffer >> commandName; currentCallFromIngameConsole = calledFromIngameConsole; -// Check mantis issue 2292 for details -// if(LOCPLINT && LOCPLINT->cingconsole) -// LOCPLINT->cingconsole->print(message); - - if(message==std::string("die, fool")) { exit(EXIT_SUCCESS); @@ -120,7 +115,7 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle else if(commandName == std::string("activate")) { int what; - readed >> what; + singleWordBuffer >> what; switch (what) { case 0: @@ -162,14 +157,14 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle return; } std::string fname; - readed >> fname; + singleWordBuffer >> fname; CSH->client->save(fname); } // else if(commandName=="load") // { // // TODO: this code should end the running game and manage to call startGame instead // std::string fname; -// readed >> fname; +// singleWordBuffer >> fname; // CSH->client->loadGame(fname); // } else if(message=="convert txt") @@ -351,7 +346,7 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle { std::string what; int id1, id2; - readed >> what >> id1 >> id2; + singleWordBuffer >> what >> id1 >> id2; if(what == "hs") { for(const CGHeroInstance *h : LOCPLINT->cb->getHeroesInfo()) @@ -363,11 +358,11 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle else if (commandName == "set") { std::string what, value; - readed >> what; + singleWordBuffer >> what; Settings config = settings.write["session"][what]; - readed >> value; + singleWordBuffer >> value; if (value == "on") { @@ -383,14 +378,14 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle else if(commandName == "unlock") { std::string mxname; - readed >> mxname; + singleWordBuffer >> mxname; if(mxname == "pim" && LOCPLINT) LOCPLINT->pim->unlock(); } else if(commandName == "def2bmp") { std::string URI; - readed >> URI; + singleWordBuffer >> URI; std::unique_ptr anim = std::make_unique(URI); anim->preload(); anim->exportBitmaps(VCMIDirs::get().userExtractedPath()); @@ -398,7 +393,7 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle else if(commandName == "extract") { std::string URI; - readed >> URI; + singleWordBuffer >> URI; if (CResourceHandler::get()->existsResource(ResourceID(URI))) { @@ -416,7 +411,7 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle else if(commandName == "setBattleAI") { std::string fname; - readed >> fname; + singleWordBuffer >> fname; printCommandMessage("Will try loading that AI to see if it is correct name...\n"); try { @@ -433,10 +428,9 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle printCommandMessage("Setting not changed, AI not found or invalid!", ELogLevel::WARN); } } - - Settings session = settings.write["session"]; - if(commandName == "autoskip") + else if(commandName == "autoskip") { + Settings session = settings.write["session"]; session["autoSkip"].Bool() = !session["autoSkip"].Bool(); } else if(commandName == "gosolo") @@ -446,17 +440,15 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle else if(commandName == "controlai") { std::string colorName; - readed >> colorName; + singleWordBuffer >> colorName; boost::to_lower(colorName); ClientCommandManager::handleControlAi(colorName); } - // Check mantis issue 2292 for details -/* else if(client && client->serv && client->serv->connected && LOCPLINT) //send to server + else { - boost::unique_lock un(*CPlayerInterface::pim); - LOCPLINT->cb->sendMessage(message); - }*/ + printCommandMessage("Command not found :(", ELogLevel::ERROR); + } } void ClientCommandManager::giveTurn(const PlayerColor &colorIdentifier) diff --git a/client/ClientCommandManager.h b/client/ClientCommandManager.h index c17a2da8f..51295e640 100644 --- a/client/ClientCommandManager.h +++ b/client/ClientCommandManager.h @@ -15,7 +15,7 @@ class PlayerColor; VCMI_LIB_NAMESPACE_END class CIntObject; -class ClientCommandManager +class ClientCommandManager //take mantis #2292 issue about account if thinking about handling cheats from command-line { static bool currentCallFromIngameConsole; diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index c5d8ad36b..8c4098b5d 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -1152,7 +1152,6 @@ void CInGameConsole::endEnteringText(bool processEnteredText) if(processEnteredText) { std::string txt = enteredText.substr(0, enteredText.size()-1); - LOCPLINT->cb->sendMessage(txt, LOCPLINT->getSelection()); previouslyEntered.push_back(txt); if(txt.at(0) == '/') @@ -1161,6 +1160,8 @@ void CInGameConsole::endEnteringText(bool processEnteredText) boost::thread clientCommandThread(ClientCommandManager::processCommand, txt.substr(1), true); clientCommandThread.detach(); } + else + LOCPLINT->cb->sendMessage(txt, LOCPLINT->getSelection()); } enteredText.clear(); From 6b7ce798d0182443e797c4c4a0a8f262c193b44c Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Wed, 28 Dec 2022 21:58:32 +0200 Subject: [PATCH 049/197] artifactTransitionPos created --- client/widgets/CArtifactHolder.cpp | 4 ++-- lib/CArtHandler.cpp | 15 ++++++++++++--- lib/CArtHandler.h | 1 + lib/GameConstants.h | 1 + server/CGameHandler.cpp | 12 ++++++------ 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp index 5b4690ed7..f3a7411fa 100644 --- a/client/widgets/CArtifactHolder.cpp +++ b/client/widgets/CArtifactHolder.cpp @@ -740,11 +740,11 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation &src, const Artifact } else if(commonInfo->dst == src) //the dest artifact was moved -> we are picking it { - assert(dst.slot >= GameConstants::BACKPACK_START); + assert(ArtifactUtils::isSlotBackpack(dst.slot)); commonInfo->reset(); CArtifactsOfHero::ArtPlacePtr ap; - for(CArtifactsOfHero *aoh : commonInfo->participants) + for(CArtifactsOfHero * aoh : commonInfo->participants) { if(dst.isHolder(aoh->curHero)) { diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 0e0851639..79bc0d618 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -824,7 +824,12 @@ bool CArtifactInstance::canBePutAt(const ArtifactLocation & al, bool assumeDestR bool CArtifactInstance::canBePutAt(const CArtifactSet *artSet, ArtifactPosition slot, bool assumeDestRemoved) const { - if(slot >= GameConstants::BACKPACK_START) + if(slot == ArtifactPosition::TRANSITION_POS) + { + return true; + } + + if(ArtifactUtils::isSlotBackpack(slot)) { if(artType->isBig()) return false; @@ -851,7 +856,7 @@ void CArtifactInstance::putAt(ArtifactLocation al) assert(canBePutAt(al)); al.getHolderArtSet()->setNewArtSlot(al.slot, this, false); - if(!ArtifactUtils::isSlotBackpack(al.slot)) + if(!ArtifactUtils::isSlotBackpack(al.slot) && (al.slot != ArtifactPosition::TRANSITION_POS)) al.getHolderNode()->attachTo(*this); } @@ -1329,6 +1334,8 @@ const CCombinedArtifactInstance *CArtifactSet::getAssemblyByConstituent(Artifact const ArtSlotInfo * CArtifactSet::getSlot(ArtifactPosition pos) const { + if(pos == ArtifactPosition::TRANSITION_POS) + return &artifactTransitionPos; if(vstd::contains(artifactsWorn, pos)) return &artifactsWorn.at(pos); if(pos >= ArtifactPosition::AFTER_LAST ) @@ -1355,7 +1362,9 @@ ArtSlotInfo & CArtifactSet::retrieveNewArtSlot(ArtifactPosition slot) { assert(!vstd::contains(artifactsWorn, slot)); - if (!ArtifactUtils::isSlotBackpack(slot)) + if(slot == ArtifactPosition::TRANSITION_POS) + return artifactTransitionPos; + if(!ArtifactUtils::isSlotBackpack(slot)) return artifactsWorn[slot]; ArtSlotInfo newSlot; diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index 70a8bc5c7..cf9c99a06 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -317,6 +317,7 @@ class DLL_LINKAGE CArtifactSet public: std::vector artifactsInBackpack; //hero's artifacts from bag std::map artifactsWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 + ArtSlotInfo artifactTransitionPos; // Used as transition position for manual artifact exchange ArtSlotInfo & retrieveNewArtSlot(ArtifactPosition slot); void setNewArtSlot(ArtifactPosition slot, CArtifactInstance *art, bool locked); diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 068fbfd90..244162e67 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -983,6 +983,7 @@ class ArtifactPosition public: enum EArtifactPosition { + TRANSITION_POS = -3, FIRST_AVAILABLE = -2, PRE_FIRST = -1, //sometimes used as error, sometimes as first free in backpack HEAD, SHOULDERS, NECK, RIGHT_HAND, LEFT_HAND, TORSO, //5 diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 81ecb186f..6d9c553a9 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3907,17 +3907,17 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat if (src.slot == ArtifactPosition::MACH4 || dst.slot == ArtifactPosition::MACH4) COMPLAIN_RET("Cannot move catapult!"); - if (dst.slot >= GameConstants::BACKPACK_START) + if(ArtifactUtils::isSlotBackpack(dst.slot)) vstd::amin(dst.slot, ArtifactPosition(GameConstants::BACKPACK_START + (si32)dst.getHolderArtSet()->artifactsInBackpack.size())); - if (src.slot == dst.slot && src.artHolder == dst.artHolder) + if(src.slot == dst.slot && src.artHolder == dst.artHolder) COMPLAIN_RET("Won't move artifact: Dest same as source!"); - if (dst.slot < GameConstants::BACKPACK_START && destArtifact) //moving art to another slot + // Check if dst slot is occupied + if(!ArtifactUtils::isSlotBackpack(dst.slot) && destArtifact) { - //old artifact must be removed first - moveArtifact(dst, ArtifactLocation(dst.artHolder, ArtifactPosition( - (si32)dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START))); + // Previous artifact must be removed first + moveArtifact(dst, ArtifactLocation(dst.artHolder, ArtifactPosition::TRANSITION_POS)); } auto hero = boost::get>(dst.artHolder); if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->id, dst.slot)) From c706b4d4190a55d90d4808f991600f483f8e4236 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Wed, 28 Dec 2022 23:56:21 +0200 Subject: [PATCH 050/197] CArtifactsOfHero::artifactMoved updated --- client/widgets/CArtifactHolder.cpp | 77 ++++++++++++------------------ 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp index f3a7411fa..2b9200820 100644 --- a/client/widgets/CArtifactHolder.cpp +++ b/client/widgets/CArtifactHolder.cpp @@ -716,31 +716,40 @@ void CArtifactsOfHero::realizeCurrentTransaction() ArtifactLocation(commonInfo->dst.AOH->curHero, commonInfo->dst.slotID)); } -void CArtifactsOfHero::artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst) +void CArtifactsOfHero::artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) { bool isCurHeroSrc = src.isHolder(curHero), isCurHeroDst = dst.isHolder(curHero); - if(isCurHeroSrc && src.slot >= GameConstants::BACKPACK_START) + if(isCurHeroSrc && ArtifactUtils::isSlotBackpack(src.slot)) updateSlot(src.slot); - if(isCurHeroDst && dst.slot >= GameConstants::BACKPACK_START) + if(isCurHeroDst && ArtifactUtils::isSlotBackpack(dst.slot)) updateSlot(dst.slot); - if(isCurHeroSrc || isCurHeroDst) //we need to update all slots, artifact might be combined and affect more slots + // We need to update all slots, artifact might be combined and affect more slots + if(isCurHeroSrc || isCurHeroDst) updateWornSlots(false); - if (!src.isHolder(curHero) && !isCurHeroDst) + if(!src.isHolder(curHero) && !isCurHeroDst) return; - if(commonInfo->src == src) //artifact was taken from us + // When moving one artifact onto another it leads to two art movements: dst->TRANSITION_POS; src->dst + // however after first movement we pick the art from TRANSITION_POS and the second movement coming when + // we have a different artifact may look surprising... but it's valid. + + // Artifact was taken from us + if(commonInfo->src == src) { - assert(commonInfo->dst == dst //expected movement from slot ot slot - || dst.slot == dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START //artifact moved back to backpack (eg. to make place for art we are moving) + // Expected movement from slot ot slot + assert(commonInfo->dst == dst + // Artifact moved back to backpack (eg. to make place for art we are moving) + || dst.slot == dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START || dst.getHolderArtSet()->bearerType() != ArtBearer::HERO); commonInfo->reset(); unmarkSlots(); } - else if(commonInfo->dst == src) //the dest artifact was moved -> we are picking it + // The dest artifact was moved -> we are picking it + else if(commonInfo->dst == src) { - assert(ArtifactUtils::isSlotBackpack(dst.slot)); + assert(dst.slot == ArtifactPosition::TRANSITION_POS); commonInfo->reset(); CArtifactsOfHero::ArtPlacePtr ap; @@ -749,52 +758,28 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation &src, const Artifact if(dst.isHolder(aoh->curHero)) { commonInfo->src.AOH = aoh; - if((ap = aoh->getArtPlace(dst.slot)))//getArtPlace may return null - break; + break; } } - if(ap) - { - ap->select(); - } - else - { - commonInfo->src.art = dst.getArt(); - commonInfo->src.slotID = dst.slot; - assert(commonInfo->src.AOH); - CCS->curh->dragAndDropCursor(std::make_unique("artifact", dst.getArt()->artType->getIconIndex())); - markPossibleSlots(dst.getArt()); - } - } - else if(src.slot >= GameConstants::BACKPACK_START && - src.slot < commonInfo->src.slotID && - src.isHolder(commonInfo->src.AOH->curHero)) //artifact taken from before currently picked one - { - //int fixedSlot = src.hero->getArtPos(commonInfo->src.art); - vstd::advance(commonInfo->src.slotID, -1); - assert(commonInfo->src.valid()); - } - else - { - //when moving one artifact onto another it leads to two art movements: dst->backapck; src->dst - // however after first movement we pick the art from backpack and the second movement coming when - // we have a different artifact may look surprising... but it's valid. + commonInfo->src.art = dst.getArt(); + commonInfo->src.slotID = dst.slot; + assert(commonInfo->src.AOH); + CCS->curh->dragAndDropCursor(make_unique("artifact", dst.getArt()->artType->getIconIndex())); + markPossibleSlots(dst.getArt()); } updateParentWindow(); int shift = 0; -// if(dst.slot >= Arts::BACKPACK_START && dst.slot - Arts::BACKPACK_START < backpackPos) -// shift++; -// - if(src.slot < GameConstants::BACKPACK_START && dst.slot - GameConstants::BACKPACK_START < backpackPos) + if(!ArtifactUtils::isSlotBackpack(src.slot) && dst.slot - GameConstants::BACKPACK_START < backpackPos) shift++; - if(dst.slot < GameConstants::BACKPACK_START && src.slot - GameConstants::BACKPACK_START < backpackPos) + if(!ArtifactUtils::isSlotBackpack(dst.slot) && src.slot - GameConstants::BACKPACK_START < backpackPos) shift--; - if( (isCurHeroSrc && src.slot >= GameConstants::BACKPACK_START) - || (isCurHeroDst && dst.slot >= GameConstants::BACKPACK_START) ) - scrollBackpack(shift); //update backpack slots + // If backpack is changed, update it + if((isCurHeroSrc && ArtifactUtils::isSlotBackpack(src.slot)) + || (isCurHeroDst && ArtifactUtils::isSlotBackpack(dst.slot))) + scrollBackpack(shift); } void CArtifactsOfHero::artifactRemoved(const ArtifactLocation &al) From 4005b4836072ba3cf8ad51914dac1ba46f74efba Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Thu, 29 Dec 2022 20:39:01 +0200 Subject: [PATCH 051/197] Regressions fixed --- client/widgets/CArtifactHolder.cpp | 32 +++++++++++++++++++++++++++--- client/windows/GUIClasses.cpp | 2 -- lib/CArtHandler.cpp | 21 ++++++++++++++++---- lib/CArtHandler.h | 2 +- server/CGameHandler.cpp | 29 +++++++++++++++------------ 5 files changed, 63 insertions(+), 23 deletions(-) diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp index 2b9200820..46ec17742 100644 --- a/client/widgets/CArtifactHolder.cpp +++ b/client/widgets/CArtifactHolder.cpp @@ -670,6 +670,16 @@ CArtifactsOfHero::CArtifactsOfHero(const Point & position, bool createCommonPart CArtifactsOfHero::~CArtifactsOfHero() { dispose(); + // Artifact located in artifactsTransitionPos should be returned + if(!curHero->artifactsTransitionPos.empty()) + { + auto artPlace = getArtPlace( + ArtifactUtils::getArtifactDstPosition(curHero->artifactsTransitionPos.begin()->artifact, curHero, curHero->bearerType())); + assert(artPlace); + assert(artPlace->ourOwner); + artPlace->setMeAsDest(); + artPlace->ourOwner->realizeCurrentTransaction(); + } } void CArtifactsOfHero::updateParentWindow() @@ -735,8 +745,21 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation & src, const Artifac // however after first movement we pick the art from TRANSITION_POS and the second movement coming when // we have a different artifact may look surprising... but it's valid. + // Used when doing dragAndDrop and artifact swap multiple times + if(src.slot == ArtifactPosition::TRANSITION_POS && + commonInfo->src.slotID == ArtifactPosition::TRANSITION_POS && + commonInfo->dst.slotID == ArtifactPosition::PRE_FIRST) + { + auto art = curHero->getArt(ArtifactPosition::TRANSITION_POS); + assert(art); + CCS->curh->dragAndDropCursor(make_unique("artifact", art->artType->getIconIndex())); + markPossibleSlots(art); + + commonInfo->src.art = art; + commonInfo->src.slotID = src.slot; + } // Artifact was taken from us - if(commonInfo->src == src) + else if(commonInfo->src == src) { // Expected movement from slot ot slot assert(commonInfo->dst == dst @@ -746,13 +769,12 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation & src, const Artifac commonInfo->reset(); unmarkSlots(); } - // The dest artifact was moved -> we are picking it + // The dest artifact was moved after the swap -> we are picking it else if(commonInfo->dst == src) { assert(dst.slot == ArtifactPosition::TRANSITION_POS); commonInfo->reset(); - CArtifactsOfHero::ArtPlacePtr ap; for(CArtifactsOfHero * aoh : commonInfo->participants) { if(dst.isHolder(aoh->curHero)) @@ -795,6 +817,10 @@ void CArtifactsOfHero::artifactRemoved(const ArtifactLocation &al) CArtifactsOfHero::ArtPlacePtr CArtifactsOfHero::getArtPlace(int slot) { + if(slot == ArtifactPosition::TRANSITION_POS) + { + return nullptr; + } if(slot < GameConstants::BACKPACK_START) { if(artWorn.find(ArtifactPosition(slot)) == artWorn.end()) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 8ceaed783..a84b12a1b 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1249,8 +1249,6 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, CExchangeWindow::~CExchangeWindow() { - artifs[0]->commonInfo = nullptr; - artifs[1]->commonInfo = nullptr; } const CGarrisonSlot * CExchangeWindow::getSelectedSlotID() const diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 79bc0d618..49fcce302 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -864,7 +864,7 @@ void CArtifactInstance::removeFrom(ArtifactLocation al) { assert(al.getHolderArtSet()->getArt(al.slot) == this); al.getHolderArtSet()->eraseArtSlot(al.slot); - if(!ArtifactUtils::isSlotBackpack(al.slot)) + if(!ArtifactUtils::isSlotBackpack(al.slot) && (al.slot != ArtifactPosition::TRANSITION_POS)) al.getHolderNode()->detachFrom(*this); } @@ -1335,7 +1335,11 @@ const CCombinedArtifactInstance *CArtifactSet::getAssemblyByConstituent(Artifact const ArtSlotInfo * CArtifactSet::getSlot(ArtifactPosition pos) const { if(pos == ArtifactPosition::TRANSITION_POS) - return &artifactTransitionPos; + { + // Always add to the end. Always take from the beginning. + assert(!artifactsTransitionPos.empty()); + return &(*artifactsTransitionPos.begin()); + } if(vstd::contains(artifactsWorn, pos)) return &artifactsWorn.at(pos); if(pos >= ArtifactPosition::AFTER_LAST ) @@ -1363,7 +1367,11 @@ ArtSlotInfo & CArtifactSet::retrieveNewArtSlot(ArtifactPosition slot) assert(!vstd::contains(artifactsWorn, slot)); if(slot == ArtifactPosition::TRANSITION_POS) - return artifactTransitionPos; + { + // Always add to the end. Always take from the beginning. + artifactsTransitionPos.push_back(ArtSlotInfo()); + return artifactsTransitionPos.back(); + } if(!ArtifactUtils::isSlotBackpack(slot)) return artifactsWorn[slot]; @@ -1384,7 +1392,12 @@ void CArtifactSet::setNewArtSlot(ArtifactPosition slot, CArtifactInstance *art, void CArtifactSet::eraseArtSlot(ArtifactPosition slot) { - if(ArtifactUtils::isSlotBackpack(slot)) + if(slot == ArtifactPosition::TRANSITION_POS) + { + assert(!artifactsTransitionPos.empty()); + artifactsTransitionPos.erase(artifactsTransitionPos.begin()); + } + else if(ArtifactUtils::isSlotBackpack(slot)) { auto backpackSlot = ArtifactPosition(slot - GameConstants::BACKPACK_START); diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index cf9c99a06..cae8abef1 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -317,7 +317,7 @@ class DLL_LINKAGE CArtifactSet public: std::vector artifactsInBackpack; //hero's artifacts from bag std::map artifactsWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 - ArtSlotInfo artifactTransitionPos; // Used as transition position for manual artifact exchange + std::vector artifactsTransitionPos; // Used as transition position for dragAndDrop artifact exchange ArtSlotInfo & retrieveNewArtSlot(ArtifactPosition slot); void setNewArtSlot(ArtifactPosition slot, CArtifactInstance *art, bool locked); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 6d9c553a9..533c6f3db 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3910,21 +3910,24 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat if(ArtifactUtils::isSlotBackpack(dst.slot)) vstd::amin(dst.slot, ArtifactPosition(GameConstants::BACKPACK_START + (si32)dst.getHolderArtSet()->artifactsInBackpack.size())); - if(src.slot == dst.slot && src.artHolder == dst.artHolder) - COMPLAIN_RET("Won't move artifact: Dest same as source!"); - - // Check if dst slot is occupied - if(!ArtifactUtils::isSlotBackpack(dst.slot) && destArtifact) + if(!(src.slot == ArtifactPosition::TRANSITION_POS && dst.slot == ArtifactPosition::TRANSITION_POS)) { - // Previous artifact must be removed first - moveArtifact(dst, ArtifactLocation(dst.artHolder, ArtifactPosition::TRANSITION_POS)); - } - auto hero = boost::get>(dst.artHolder); - if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->id, dst.slot)) - giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); + if(src.slot == dst.slot && src.artHolder == dst.artHolder) + COMPLAIN_RET("Won't move artifact: Dest same as source!"); - MoveArtifact ma(&src, &dst); - sendAndApply(&ma); + // Check if dst slot is occupied + if(!ArtifactUtils::isSlotBackpack(dst.slot) && destArtifact) + { + // Previous artifact must be removed first + moveArtifact(dst, ArtifactLocation(dst.artHolder, ArtifactPosition::TRANSITION_POS)); + } + auto hero = boost::get>(dst.artHolder); + if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->id, dst.slot)) + giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); + + MoveArtifact ma(&src, &dst); + sendAndApply(&ma); + } return true; } From 7e8516f927945708ff0a0c664619c1f0f280d7e6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 6 Jan 2023 23:29:33 +0200 Subject: [PATCH 052/197] Changed Sirens behavior to match H3 logic --- lib/mapObjects/MiscObjects.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 309971b9e..e0fddbb16 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1956,7 +1956,13 @@ void CGSirens::onHeroVisit( const CGHeroInstance * h ) const for (auto i = h->Slots().begin(); i != h->Slots().end(); i++) { - TQuantity drown = static_cast(i->second->count * 0.3); + // 1-sized stacks are not affected by sirens + if (i->second->count == 1) + continue; + + // tested H3 behavior: 30% (rounded up) of stack drowns + TQuantity drown = std::ceil(i->second->count * 0.3); + if(drown) { cb->changeStackCount(StackLocation(h, i->first), -drown); From 8526eba6fa82006409650f032aeb83e58276e86f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 31 Dec 2022 17:55:22 +0200 Subject: [PATCH 053/197] Added checks to music player to detect potential freezes --- client/CMusicHandler.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index d52c81b83..563627beb 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -89,7 +89,7 @@ CSoundHandler::CSoundHandler(): soundBase::battle02, soundBase::battle03, soundBase::battle04, soundBase::battle05, soundBase::battle06, soundBase::battle07 }; - + //predefine terrain set //TODO: support custom sounds for new terrains and load from json horseSounds = @@ -542,6 +542,20 @@ MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string mu } MusicEntry::~MusicEntry() { + if (playing) + { + assert(0); + logGlobal->error("Attempt to delete music while playing!"); + Mix_HaltMusic(); + } + + if (loop == 0 && Mix_FadingMusic() != MIX_NO_FADING) + { + assert(0); + logGlobal->error("Attempt to delete music while fading out!"); + Mix_HaltMusic(); + } + logGlobal->trace("Del-ing music file %s", currentName); if (music) Mix_FreeMusic(music); @@ -619,7 +633,7 @@ bool MusicEntry::play() bool MusicEntry::stop(int fade_ms) { - if (playing) + if (Mix_PlayingMusic()) { playing = false; loop = 0; From 9308319ac7a42105ee0a8ee4c794a9b139376540 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 8 Jan 2023 18:05:00 +0200 Subject: [PATCH 054/197] Added workaround for ~200 ms lag occuring after hero move --- client/CPlayerInterface.cpp | 3 +++ client/windows/CAdvmapInterface.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 9dbf79dfb..b55e6d2bd 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -2550,6 +2550,9 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) // (i == 0) means hero went through all the path adventureInt->updateMoveHero(h, (i != 0)); adventureInt->updateNextHero(h); + + // ugly workaround to force instant update of adventure map + adventureInt->animValHitCount = 8; } setMovementStatus(false); diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 7706af146..99e676a36 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -1044,7 +1044,7 @@ void CAdvMapInt::show(SDL_Surface * to) { ++heroAnim; } - if(animValHitCount == 8) + if(animValHitCount >= 8) { CGI->mh->updateWater(); animValHitCount = 0; From 6379c5f6fae9a039089c4a7193f94372807ed7a1 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 8 Jan 2023 14:11:08 +0200 Subject: [PATCH 055/197] Install additional dll on Windows to get better style for Qt --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 95fe102a4..0a71610aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -506,6 +506,9 @@ if(WIN32) FILES ${integration_loc} DESTINATION ${BIN_DIR}/platforms ) + install( + FILES "$" + DESTINATION ${BIN_DIR}/styles) endif() endif() From 1468f6aded05c37ec92c97b3435050ef10d7ec01 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 20 Dec 2022 16:14:06 +0200 Subject: [PATCH 056/197] Converted terrainTypeHandler into proper handler class --- client/CMusicHandler.cpp | 31 +- client/CPlayerInterface.cpp | 2 +- client/mapHandler.cpp | 16 +- client/widgets/AdventureMapClasses.cpp | 16 +- config/terrains.json | 20 +- lib/CCreatureHandler.cpp | 4 +- lib/CGeneralTextHandler.cpp | 2 - lib/CGeneralTextHandler.h | 1 - lib/CHeroHandler.cpp | 13 - lib/CHeroHandler.h | 1 - lib/CModHandler.cpp | 3 + lib/CPathfinder.cpp | 4 +- lib/CStack.cpp | 2 +- lib/CTownHandler.cpp | 6 +- lib/GameConstants.h | 70 ++-- lib/HeroBonus.cpp | 10 +- lib/NetPacksLib.cpp | 6 +- lib/Terrain.cpp | 445 ++++++------------------- lib/Terrain.h | 149 +++++---- lib/VCMI_Lib.cpp | 2 + lib/VCMI_Lib.h | 8 + lib/mapObjects/CGHeroInstance.cpp | 10 +- lib/mapObjects/ObjectTemplate.cpp | 32 +- lib/mapping/CDrawRoadsOperation.cpp | 4 +- lib/mapping/CMap.cpp | 4 +- lib/mapping/CMapOperation.cpp | 10 +- lib/mapping/MapEditUtils.cpp | 2 +- lib/mapping/MapFormatH3M.cpp | 11 +- lib/mapping/MapFormatJson.cpp | 6 +- lib/rmg/CRmgTemplate.cpp | 12 +- lib/rmg/CZonePlacer.cpp | 8 +- lib/rmg/ConnectionsPlacer.cpp | 6 +- lib/rmg/Functions.cpp | 16 +- lib/rmg/RiverPlacer.cpp | 14 +- lib/rmg/RmgMap.cpp | 2 +- lib/rmg/RmgObject.cpp | 2 +- lib/rmg/RoadPlacer.cpp | 4 +- lib/rmg/RockPlacer.cpp | 4 +- lib/rmg/Zone.cpp | 2 +- mapeditor/mainwindow.cpp | 22 +- mapeditor/maphandler.cpp | 12 +- mapeditor/objectbrowser.cpp | 4 +- server/CGameHandler.cpp | 2 +- 43 files changed, 410 insertions(+), 590 deletions(-) diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index 053876cad..4291dcd12 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -94,30 +94,29 @@ CSoundHandler::CSoundHandler(): //TODO: support custom sounds for new terrains and load from json horseSounds = { - {Terrain::DIRT, soundBase::horseDirt}, - {Terrain::SAND, soundBase::horseSand}, - {Terrain::GRASS, soundBase::horseGrass}, - {Terrain::SNOW, soundBase::horseSnow}, - {Terrain::SWAMP, soundBase::horseSwamp}, - {Terrain::ROUGH, soundBase::horseRough}, - {Terrain::SUBTERRANEAN, soundBase::horseSubterranean}, - {Terrain::LAVA, soundBase::horseLava}, - {Terrain::WATER, soundBase::horseWater}, - {Terrain::ROCK, soundBase::horseRock} + {TerrainId::DIRT, soundBase::horseDirt}, + {TerrainId::SAND, soundBase::horseSand}, + {TerrainId::GRASS, soundBase::horseGrass}, + {TerrainId::SNOW, soundBase::horseSnow}, + {TerrainId::SWAMP, soundBase::horseSwamp}, + {TerrainId::ROUGH, soundBase::horseRough}, + {TerrainId::SUBTERRANEAN, soundBase::horseSubterranean}, + {TerrainId::LAVA, soundBase::horseLava}, + {TerrainId::WATER, soundBase::horseWater}, + {TerrainId::ROCK, soundBase::horseRock} }; } void CSoundHandler::loadHorseSounds() { - const auto & terrains = CGI->terrainTypeHandler->terrains(); - for(const auto & terrain : terrains) + for(const auto & terrain : CGI->terrainTypeHandler->objects) { //since all sounds are hardcoded, let's keep it - if(vstd::contains(horseSounds, terrain.id)) + if(vstd::contains(horseSounds, terrain->id)) continue; //Use already existing horse sound - horseSounds[terrain.id] = horseSounds.at(terrains[terrain.id].horseSoundId); + horseSounds[terrain->id] = horseSounds.at(static_cast(CGI->terrainTypeHandler->getById(terrain->id)->horseSoundId)); } } @@ -368,9 +367,9 @@ CMusicHandler::CMusicHandler(): void CMusicHandler::loadTerrainMusicThemes() { - for (const auto & terrain : CGI->terrainTypeHandler->terrains()) + for (const auto & terrain : CGI->terrainTypeHandler->objects) { - addEntryToSet("terrain_" + terrain.name, "Music/" + terrain.musicFilename); + addEntryToSet("terrain_" + terrain->name, "Music/" + terrain->musicFilename); } } diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 20c0be95f..62ffc5335 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -2372,7 +2372,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) for (auto & elem : path.nodes) elem.coord = h->convertFromVisitablePos(elem.coord); - TerrainId currentTerrain = Terrain::BORDER; // not init yet + TerrainId currentTerrain = TerrainId::BORDER; // not init yet TerrainId newTerrain; int sh = -1; diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 05fb0d797..2395d90cc 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -175,17 +175,17 @@ void CMapHandler::initTerrainGraphics() std::map terrainFiles; std::map riverFiles; std::map roadFiles; - for(const auto & terrain : VLC->terrainTypeHandler->terrains()) + for(const auto & terrain : VLC->terrainTypeHandler->objects) { - terrainFiles[terrain.name] = terrain.tilesFilename; + terrainFiles[terrain->name] = terrain->tilesFilename; } - for(const auto & river : VLC->terrainTypeHandler->rivers()) + for(const auto & river : VLC->riverTypeHandler->objects) { - riverFiles[river.fileName] = river.fileName; + riverFiles[river->fileName] = river->fileName; } - for(const auto & road : VLC->terrainTypeHandler->roads()) + for(const auto & road : VLC->roadTypeHandler->objects) { - roadFiles[road.fileName] = road.fileName; + roadFiles[road->fileName] = road->fileName; } loadFlipped(terrainAnimations, terrainImages, terrainFiles); @@ -1388,8 +1388,8 @@ void CMapHandler::getTerrainDescr(const int3 & pos, std::string & out, bool isRM break; } } - if(!isTile2Terrain || out.empty()) - out = CGI->generaltexth->terrainNames[t.terType->id]; + + VLC->terrainTypeHandler->getById(t.terType->id)->terrainText; if(t.getDiggingStatus(false) == EDiggingStatus::CAN_DIG) { diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index 17023a650..d9856e882 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -499,25 +499,25 @@ std::map > CMinimap::loadColors() { std::map > ret; - for(const auto & terrain : CGI->terrainTypeHandler->terrains()) + for(const auto & terrain : CGI->terrainTypeHandler->objects) { SDL_Color normal = { - ui8(terrain.minimapUnblocked[0]), - ui8(terrain.minimapUnblocked[1]), - ui8(terrain.minimapUnblocked[2]), + ui8(terrain->minimapUnblocked[0]), + ui8(terrain->minimapUnblocked[1]), + ui8(terrain->minimapUnblocked[2]), ui8(255) }; SDL_Color blocked = { - ui8(terrain.minimapBlocked[0]), - ui8(terrain.minimapBlocked[1]), - ui8(terrain.minimapBlocked[2]), + ui8(terrain->minimapBlocked[0]), + ui8(terrain->minimapBlocked[1]), + ui8(terrain->minimapBlocked[2]), ui8(255) }; - ret[terrain.id] = std::make_pair(normal, blocked); + ret[terrain->id] = std::make_pair(normal, blocked); } return ret; } diff --git a/config/terrains.json b/config/terrains.json index 149ca98c3..7968cfcac 100644 --- a/config/terrains.json +++ b/config/terrains.json @@ -1,7 +1,7 @@ { "dirt" : { - "originalTerrainId": 0, + "index": 0, "moveCost" : 100, "minimapUnblocked" : [ 82, 56, 8 ], "minimapBlocked" : [ 57, 40, 8 ], @@ -15,7 +15,7 @@ }, "sand" : { - "originalTerrainId": 1, + "index": 1, "moveCost" : 150, "minimapUnblocked" : [ 222, 207, 140 ], "minimapBlocked" : [ 165, 158, 107 ], @@ -30,7 +30,7 @@ }, "grass" : { - "originalTerrainId": 2, + "index": 2, "moveCost" : 100, "minimapUnblocked" : [ 0, 65, 0 ], "minimapBlocked" : [ 0, 48, 0 ], @@ -43,7 +43,7 @@ }, "snow" : { - "originalTerrainId": 3, + "index": 3, "moveCost" : 150, "minimapUnblocked" : [ 181, 199, 198 ], "minimapBlocked" : [ 140, 158, 156 ], @@ -56,7 +56,7 @@ }, "swamp" : { - "originalTerrainId": 4, + "index": 4, "moveCost" : 175, "minimapUnblocked" : [ 74, 134, 107 ], "minimapBlocked" : [ 33, 89, 66 ], @@ -69,7 +69,7 @@ }, "rough" : { - "originalTerrainId": 5, + "index": 5, "moveCost" : 125, "minimapUnblocked" : [ 132, 113, 49 ], "minimapBlocked" : [ 99, 81, 33 ], @@ -82,7 +82,7 @@ }, "subterra" : { - "originalTerrainId": 6, + "index": 6, "moveCost" : 100, "minimapUnblocked" : [ 132, 48, 0 ], "minimapBlocked" : [ 90, 8, 0 ], @@ -97,7 +97,7 @@ }, "lava" : { - "originalTerrainId": 7, + "index": 7, "moveCost" : 100, "minimapUnblocked" : [ 74, 73, 74 ], "minimapBlocked" : [ 41, 40, 41 ], @@ -112,7 +112,7 @@ }, "water" : { - "originalTerrainId": 8, + "index": 8, "moveCost" : 100, "minimapUnblocked" : [ 8, 81, 148 ], "minimapBlocked" : [ 8, 81, 148 ], @@ -130,7 +130,7 @@ }, "rock" : { - "originalTerrainId": 9, + "index": 9, "moveCost" : -1, "minimapUnblocked" : [ 0, 0, 0 ], "minimapBlocked" : [ 0, 0, 0 ], diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index ac50e96c5..c2dc8878d 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -288,7 +288,7 @@ std::string CCreature::nodeName() const bool CCreature::isItNativeTerrain(TerrainId terrain) const { auto native = getNativeTerrain(); - return native == terrain || native == Terrain::ANY_TERRAIN; + return native == terrain || native == TerrainId::ANY_TERRAIN; } TerrainId CCreature::getNativeTerrain() const @@ -299,7 +299,7 @@ TerrainId CCreature::getNativeTerrain() const //this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses //and in the CGHeroInstance::getNativeTerrain() to setup mevement bonuses or/and penalties. return hasBonus(selectorNoTerrainPenalty, selectorNoTerrainPenalty) - ? TerrainId(Terrain::ANY_TERRAIN) + ? TerrainId(TerrainId::ANY_TERRAIN) : (*VLC->townh)[faction]->nativeTerrain; } diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 08746da4f..923b9a91c 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -343,7 +343,6 @@ CGeneralTextHandler::CGeneralTextHandler(): advobtxt (*this, "core.advevent" ), xtrainfo (*this, "core.xtrainfo" ), restypes (*this, "core.restypes" ), - terrainNames (*this, "core.terrname" ), randsign (*this, "core.randsign" ), overview (*this, "core.overview" ), arraytxt (*this, "core.arraytxt" ), @@ -372,7 +371,6 @@ CGeneralTextHandler::CGeneralTextHandler(): readToVector("core.advevent", "DATA/ADVEVENT.TXT" ); readToVector("core.xtrainfo", "DATA/XTRAINFO.TXT" ); readToVector("core.restypes", "DATA/RESTYPES.TXT" ); - readToVector("core.terrname", "DATA/TERRNAME.TXT" ); readToVector("core.randsign", "DATA/RANDSIGN.TXT" ); readToVector("core.overview", "DATA/OVERVIEW.TXT" ); readToVector("core.arraytxt", "DATA/ARRAYTXT.TXT" ); diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index bad392794..0e25fd38e 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -207,7 +207,6 @@ public: LegacyTextContainer advobtxt; LegacyTextContainer xtrainfo; LegacyTextContainer restypes; //names of resources - LegacyTextContainer terrainNames; LegacyTextContainer randsign; LegacyTextContainer seerEmpty; LegacyTextContainer seerNames; diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 72bc7ce37..155d0b993 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -345,11 +345,6 @@ CHeroHandler::~CHeroHandler() = default; CHeroHandler::CHeroHandler() { - loadTerrains(); - for(const auto & terrain : VLC->terrainTypeHandler->terrains()) - { - VLC->modh->identifiers.registerObject(CModHandler::scopeBuiltin(), "terrain", terrain.name, terrain.id); - } loadBallistics(); loadExperience(); } @@ -972,14 +967,6 @@ ui64 CHeroHandler::reqExp (ui32 level) const } } -void CHeroHandler::loadTerrains() -{ - for(const auto & terrain : VLC->terrainTypeHandler->terrains()) - { - terrCosts[terrain.id] = terrain.moveCost; - } -} - std::vector CHeroHandler::getDefaultAllowed() const { // Look Data/HOTRAITS.txt for reference diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 023015705..6f9e8be68 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -260,7 +260,6 @@ class DLL_LINKAGE CHeroHandler : public CHandlerBasescriptHandler, "script"))); #endif handlers.insert(std::make_pair("battlefields", ContentTypeHandler(VLC->battlefieldsHandler, "battlefield"))); + handlers.insert(std::make_pair("terrains", ContentTypeHandler(VLC->terrainTypeHandler, "terrain"))); + handlers.insert(std::make_pair("rivers", ContentTypeHandler(VLC->riverTypeHandler, "river"))); + handlers.insert(std::make_pair("roads", ContentTypeHandler(VLC->roadTypeHandler, "road"))); handlers.insert(std::make_pair("obstacles", ContentTypeHandler(VLC->obstacleHandler, "obstacle"))); //TODO: any other types of moddables? } diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index ba3813139..4fff7bac5 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -1000,10 +1000,10 @@ bool CPathfinderHelper::passOneTurnLimitCheck(const PathNodeInfo & source) const TurnInfo::BonusCache::BonusCache(TConstBonusListPtr bl) { - for(const auto & terrain : VLC->terrainTypeHandler->terrains()) + for(const auto & terrain : VLC->terrainTypeHandler->objects) { noTerrainPenalty.push_back(static_cast( - bl->getFirst(Selector::type()(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain.id))))); + bl->getFirst(Selector::type()(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain->id))))); } freeShipBoarding = static_cast(bl->getFirst(Selector::type()(Bonus::FREE_SHIP_BOARDING))); diff --git a/lib/CStack.cpp b/lib/CStack.cpp index 63046dfaa..f0c01bbc8 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -333,7 +333,7 @@ bool CStack::canBeHealed() const bool CStack::isOnNativeTerrain() const { //this code is called from CreatureTerrainLimiter::limit on battle start - auto res = nativeTerrain == Terrain::ANY_TERRAIN || nativeTerrain == battle->getTerrainType(); + auto res = nativeTerrain == TerrainId::ANY_TERRAIN || nativeTerrain == battle->getTerrainType(); return res; } diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 6cda5f1e9..3727c0a9a 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -28,9 +28,9 @@ VCMI_LIB_NAMESPACE_BEGIN const int NAMES_PER_TOWN=16; // number of town names per faction in H3 files. Json can define any number -const TerrainId CTownHandler::defaultGoodTerrain(Terrain::GRASS); -const TerrainId CTownHandler::defaultEvilTerrain(Terrain::LAVA); -const TerrainId CTownHandler::defaultNeutralTerrain(Terrain::ROUGH); +const TerrainId CTownHandler::defaultGoodTerrain(TerrainId::GRASS); +const TerrainId CTownHandler::defaultEvilTerrain(TerrainId::LAVA); +const TerrainId CTownHandler::defaultNeutralTerrain(TerrainId::ROUGH); const std::map CBuilding::MODES = { diff --git a/lib/GameConstants.h b/lib/GameConstants.h index b995bc089..e119ee5bf 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -830,29 +830,6 @@ public: ID_LIKE_OPERATORS(Obj, Obj::EObj) -namespace Terrain -{ - enum ETerrain : si8 - { - NATIVE_TERRAIN = -4, - ANY_TERRAIN = -3, - WRONG = -2, - BORDER = -1, - FIRST_REGULAR_TERRAIN = 0, - DIRT = 0, - SAND, - GRASS, - SNOW, - SWAMP, - ROUGH, - SUBTERRANEAN, - LAVA, - WATER, - ROCK, - ORIGINAL_TERRAIN_COUNT - }; -} - namespace Road { enum ERoad : ui8 @@ -1181,7 +1158,50 @@ class BattleField : public BaseForID DLL_LINKAGE static BattleField fromString(std::string identifier); }; - + +class TerrainId +{ +public: + enum ETerrainID { + NATIVE_TERRAIN = -4, + ANY_TERRAIN = -3, + WRONG = -2, + BORDER = -1, + FIRST_REGULAR_TERRAIN = 0, + DIRT, + SAND, + GRASS, + SNOW, + SWAMP, + ROUGH, + SUBTERRANEAN, + LAVA, + WATER, + ROCK, + ORIGINAL_TERRAIN_COUNT + }; + + TerrainId(ETerrainID _num = WRONG) : num(_num) + {} + + ETerrainID num; + + ID_LIKE_CLASS_COMMON(TerrainId, ETerrainID) + + DLL_LINKAGE operator std::string() const; + DLL_LINKAGE const TerrainId * getInfo() const; + + DLL_LINKAGE static ETerrainID fromString(std::string identifier); + + TerrainId & operator++() + { + num = static_cast(static_cast(num) + 1); + return *this; + } +}; + +ID_LIKE_OPERATORS(TerrainId, TerrainId::ETerrainID) + class ObstacleInfo; class Obstacle : public BaseForID { @@ -1239,7 +1259,7 @@ typedef si64 TExpType; typedef std::pair TDmgRange; typedef si32 TBonusSubtype; typedef si32 TQuantity; -typedef si8 TerrainId; +//typedef si8 TerrainId; typedef si8 RoadId; typedef si8 RiverId; diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 575e7c35a..333e59dba 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -2106,7 +2106,7 @@ bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest) } CreatureTerrainLimiter::CreatureTerrainLimiter() - : terrainType(Terrain::NATIVE_TERRAIN) + : terrainType(TerrainId::NATIVE_TERRAIN) { } @@ -2120,7 +2120,7 @@ int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const const CStack *stack = retrieveStackBattle(&context.node); if(stack) { - if (terrainType == Terrain::NATIVE_TERRAIN)//terrainType not specified = native + if (terrainType == TerrainId::NATIVE_TERRAIN)//terrainType not specified = native { return !stack->isOnNativeTerrain(); } @@ -2136,8 +2136,8 @@ int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const std::string CreatureTerrainLimiter::toString() const { boost::format fmt("CreatureTerrainLimiter(terrainType=%s)"); - auto terrainName = VLC->terrainTypeHandler->terrains()[terrainType].name; - fmt % (terrainType == Terrain::NATIVE_TERRAIN ? "native" : terrainName); + auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->name; + fmt % (terrainType == TerrainId::NATIVE_TERRAIN ? "native" : terrainName); return fmt.str(); } @@ -2146,7 +2146,7 @@ JsonNode CreatureTerrainLimiter::toJsonNode() const JsonNode root(JsonNode::JsonType::DATA_STRUCT); root["type"].String() = "CREATURE_TERRAIN_LIMITER"; - auto terrainName = VLC->terrainTypeHandler->terrains()[terrainType].name; + auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->name; root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainName)); return root; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 5e4e869ea..ddd1113a9 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -720,13 +720,13 @@ DLL_LINKAGE void GiveHero::applyGs(CGameState *gs) DLL_LINKAGE void NewObject::applyGs(CGameState *gs) { - TerrainId terrainType = Terrain::BORDER; + TerrainId terrainType = TerrainId::BORDER; if(ID == Obj::BOAT && !gs->isInTheMap(pos)) //special handling for bug #3060 - pos outside map but visitablePos is not { CGObjectInstance testObject = CGObjectInstance(); testObject.pos = pos; - testObject.appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(Terrain::WATER).front(); + testObject.appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(TerrainId::WATER).front(); const int3 previousXAxisTile = int3(pos.x - 1, pos.y, pos.z); assert(gs->isInTheMap(previousXAxisTile) && (testObject.visitablePos() == previousXAxisTile)); @@ -743,7 +743,7 @@ DLL_LINKAGE void NewObject::applyGs(CGameState *gs) { case Obj::BOAT: o = new CGBoat(); - terrainType = Terrain::WATER; //TODO: either boat should only spawn on water, or all water objects should be handled this way + terrainType = TerrainId::WATER; //TODO: either boat should only spawn on water, or all water objects should be handled this way break; case Obj::MONSTER: //probably more options will be needed o = new CGCreature(); diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index 5237a5c35..b1df78159 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -19,6 +19,7 @@ VCMI_LIB_NAMESPACE_BEGIN //("allowedTerrain"\s*:\s*\[.*)9(.*\],\n) //\1"rock"\2 +/* TerrainTypeHandler::TerrainTypeHandler() { auto allConfigs = VLC->modh->getActiveMods(); @@ -29,377 +30,168 @@ TerrainTypeHandler::TerrainTypeHandler() initRoads(allConfigs); recreateRoadMaps(); initTerrains(allConfigs); //maps will be populated inside -} +}*/ -void TerrainTypeHandler::initTerrains(const std::vector & allConfigs) +TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) { - std::vector> resolveLater; + TerrainType * info = new TerrainType; - objects.resize(Terrain::ORIGINAL_TERRAIN_COUNT); //make space for original terrains + info->id = TerrainId(index); - for(auto & mod : allConfigs) + info->moveCost = static_cast(json["moveCost"].Integer()); + info->musicFilename = json["music"].String(); + info->tilesFilename = json["tiles"].String(); + info->horseSoundId = static_cast(json["horseSoundId"].Float()); + info->transitionRequired = json["transitionRequired"].Bool(); + info->terrainViewPatterns = json["terrainViewPatterns"].String(); + info->terrainText = json["text"].String(); + + const JsonVector & unblockedVec = json["minimapUnblocked"].Vector(); + info->minimapUnblocked = { - if(!CResourceHandler::get(mod)->existsResource(ResourceID("config/terrains.json"))) - continue; - - JsonNode terrs(mod, ResourceID("config/terrains.json")); - for(auto & terr : terrs.Struct()) + ui8(unblockedVec[0].Float()), + ui8(unblockedVec[1].Float()), + ui8(unblockedVec[2].Float()) + }; + + const JsonVector &blockedVec = json["minimapBlocked"].Vector(); + info->minimapBlocked = + { + ui8(blockedVec[0].Float()), + ui8(blockedVec[1].Float()), + ui8(blockedVec[2].Float()) + }; + + for(const auto& node : json["type"].Vector()) + { + //Set bits + auto s = node.String(); + if (s == "LAND") info->passabilityType |= TerrainType::PassabilityType::LAND; + if (s == "WATER") info->passabilityType |= TerrainType::PassabilityType::WATER; + if (s == "ROCK") info->passabilityType |= TerrainType::PassabilityType::ROCK; + if (s == "SURFACE") info->passabilityType |= TerrainType::PassabilityType::SURFACE; + if (s == "SUB") info->passabilityType |= TerrainType::PassabilityType::SUBTERRANEAN; + } + +// if(json["river"].isNull()) +// info->river = River::NO_RIVER; +// else +// info->river = getRiverByCode(json["river"].String())->id; + + info->typeCode = json["code"].String(); + assert(info->typeCode.length() == 2); + + for(auto & t : json["battleFields"].Vector()) + info->battleFields.emplace_back(t.String()); + + + //Update terrain with this id in the future, after all terrain types are populated + + for(auto & t : json["prohibitTransitions"].Vector()) + { + VLC->modh->identifiers.requestIdentifier("terrain", t, [info](int32_t identifier) { - TerrainType info(terr.first); //set name - - info.moveCost = static_cast(terr.second["moveCost"].Integer()); - const JsonVector &unblockedVec = terr.second["minimapUnblocked"].Vector(); - info.minimapUnblocked = - { - ui8(unblockedVec[0].Float()), - ui8(unblockedVec[1].Float()), - ui8(unblockedVec[2].Float()) - }; - - const JsonVector &blockedVec = terr.second["minimapBlocked"].Vector(); - info.minimapBlocked = - { - ui8(blockedVec[0].Float()), - ui8(blockedVec[1].Float()), - ui8(blockedVec[2].Float()) - }; - info.musicFilename = terr.second["music"].String(); - info.tilesFilename = terr.second["tiles"].String(); - - if(terr.second["type"].isNull()) - { - info.passabilityType = TerrainType::PassabilityType::LAND | TerrainType::PassabilityType::SURFACE; - } - else if (terr.second["type"].getType() == JsonNode::JsonType::DATA_VECTOR) - { - for(const auto& node : terr.second["type"].Vector()) - { - //Set bits - auto s = node.String(); - if (s == "LAND") info.passabilityType |= TerrainType::PassabilityType::LAND; - if (s == "WATER") info.passabilityType |= TerrainType::PassabilityType::WATER; - if (s == "ROCK") info.passabilityType |= TerrainType::PassabilityType::ROCK; - if (s == "SURFACE") info.passabilityType |= TerrainType::PassabilityType::SURFACE; - if (s == "SUB") info.passabilityType |= TerrainType::PassabilityType::SUBTERRANEAN; - } - } - else //should be string - one option only - { - auto s = terr.second["type"].String(); - if (s == "LAND") info.passabilityType = TerrainType::PassabilityType::LAND; - if (s == "WATER") info.passabilityType = TerrainType::PassabilityType::WATER; - if (s == "ROCK") info.passabilityType = TerrainType::PassabilityType::ROCK; - if (s == "SURFACE") info.passabilityType = TerrainType::PassabilityType::SURFACE; - if (s == "SUB") info.passabilityType = TerrainType::PassabilityType::SUBTERRANEAN; - } - - if(terr.second["river"].isNull()) - { - info.river = River::NO_RIVER; - } - else - { - info.river = getRiverByCode(terr.second["river"].String())->id; - } - - if(terr.second["horseSoundId"].isNull()) - { - info.horseSoundId = Terrain::ROCK; //rock sound as default - } - else - { - info.horseSoundId = static_cast(terr.second["horseSoundId"].Float()); - } - - if(!terr.second["text"].isNull()) - { - info.terrainText = terr.second["text"].String(); - } - - if(terr.second["code"].isNull()) - { - info.typeCode = terr.first.substr(0, 2); - } - else - { - info.typeCode = terr.second["code"].String(); - assert(info.typeCode.length() == 2); - } - - if(!terr.second["battleFields"].isNull()) - { - for(auto & t : terr.second["battleFields"].Vector()) - { - info.battleFields.emplace_back(t.String()); - } - } - - info.transitionRequired = false; - if(!terr.second["transitionRequired"].isNull()) - { - info.transitionRequired = terr.second["transitionRequired"].Bool(); - } - - info.terrainViewPatterns = "normal"; - if(!terr.second["terrainViewPatterns"].isNull()) - { - info.terrainViewPatterns = terr.second["terrainViewPatterns"].String(); - } - - if(!terr.second["originalTerrainId"].isNull()) - { - //place in reserved slot - info.id = (TerrainId)(terr.second["originalTerrainId"].Float()); - objects[info.id] = info; - } - else - { - //append at the end - info.id = static_cast(objects.size()); - objects.push_back(info); - } - TerrainId id = info.id; - - //Update terrain with this id in the future, after all terrain types are populated - - if(!terr.second["prohibitTransitions"].isNull()) - { - for(auto & t : terr.second["prohibitTransitions"].Vector()) - { - std::string prohibitedTerrainName = t.String(); - resolveLater.push_back([this, prohibitedTerrainName, id]() - { - //FIXME: is that reference to the element in vector? - objects[id].prohibitTransitions.emplace_back(getInfoByName(prohibitedTerrainName)->id); - }); - } - } - - if(terr.second["rockTerrain"].isNull()) - { - objects[id].rockTerrain = Terrain::ROCK; - } - else - { - auto rockTerrainName = terr.second["rockTerrain"].String(); - resolveLater.push_back([this, rockTerrainName, id]() - { - //FIXME: is that reference to the element in vector? - objects[id].rockTerrain = getInfoByName(rockTerrainName)->id; - }); - } - } + info->prohibitTransitions.push_back(TerrainId(identifier)); + }); } - for(size_t i = Terrain::FIRST_REGULAR_TERRAIN; i < Terrain::ORIGINAL_TERRAIN_COUNT; i++) + info->rockTerrain = TerrainId::ROCK; + + if(!json["rockTerrain"].isNull()) { - //Make sure that original terrains are loaded - assert(objects[i].id != Terrain::WRONG); - } - - recreateTerrainMaps(); - - for(auto& functor : resolveLater) - { - functor(); - } -} - -void TerrainTypeHandler::initRivers(const std::vector & allConfigs) -{ - riverTypes.resize(River::ORIGINAL_RIVER_COUNT); //make space for original rivers - //First object will be default NO_RIVER - - for(auto & mod : allConfigs) - { - if (!CResourceHandler::get(mod)->existsResource(ResourceID("config/rivers.json"))) - continue; - - JsonNode rivs(mod, ResourceID("config/rivers.json")); - for(auto & river : rivs.Struct()) + auto rockTerrainName = json["rockTerrain"].String(); + VLC->modh->identifiers.requestIdentifier("terrain", rockTerrainName, [info](int32_t identifier) { - RiverType info; - - info.name = river.first; - info.fileName = river.second["animation"].String(); - info.code = river.second["code"].String(); - info.deltaName = river.second["delta"].String(); - - if (!river.second["originalRiverId"].isNull()) - { - info.id = static_cast(river.second["originalRiverId"].Float()); - riverTypes[info.id] = info; - } - else - { - info.id = static_cast(riverTypes.size()); - riverTypes.push_back(info); - } - } + info->rockTerrain = TerrainId(identifier); + }); } - recreateRiverMaps(); + return info; } -void TerrainTypeHandler::initRoads(const std::vector & allConfigs) +const std::vector & TerrainTypeHandler::getTypeNames() const { - roadTypes.resize(Road::ORIGINAL_ROAD_COUNT); //make space for original rivers - //first object will be default NO_ROAD - - for(auto & mod : allConfigs) - { - if (!CResourceHandler::get(mod)->existsResource(ResourceID("config/roads.json"))) - continue; - - JsonNode rds(mod, ResourceID("config/roads.json")); - for(auto & road : rds.Struct()) - { - RoadType info; - - info.name = road.first; - info.fileName = road.second["animation"].String(); - info.code = road.second["code"].String(); - info.movementCost = static_cast(road.second["moveCost"].Float()); - - if (!road.second["originalRoadId"].isNull()) - { - info.id = static_cast(road.second["originalRoadId"].Float()); - roadTypes[info.id] = info; - } - else - { - info.id = static_cast(roadTypes.size()); - roadTypes.push_back(info); - } - } - } - - recreateRoadMaps(); + static const std::vector typeNames = { "terrain" }; + return typeNames; } -void TerrainTypeHandler::recreateTerrainMaps() +std::vector TerrainTypeHandler::loadLegacyData(size_t dataSize) { - //This assumes the vector will never be updated or reallocated in the future - - for(size_t i = 0; i < objects.size(); i++) - { - const auto * terrainInfo = &objects[i]; - - terrainInfoByName[terrainInfo->name] = terrainInfo; - terrainInfoByCode[terrainInfo->typeCode] = terrainInfo; - terrainInfoById[terrainInfo->id] = terrainInfo; - } + return {}; } -void TerrainTypeHandler::recreateRiverMaps() +std::vector TerrainTypeHandler::getDefaultAllowed() const { - for(size_t i = River::FIRST_REGULAR_RIVER ; i < riverTypes.size(); i++) - { - const auto * riverInfo = &riverTypes[i]; - - riverInfoByName[riverInfo->name] = riverInfo; - riverInfoByCode[riverInfo->code] = riverInfo; - riverInfoById[riverInfo->id] = riverInfo; - } + return {}; } -void TerrainTypeHandler::recreateRoadMaps() +RiverType * RiverTypeHandler::loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) { - for(size_t i = Road::FIRST_REGULAR_ROAD ; i < roadTypes.size(); i++) - { - const auto * roadInfo = &roadTypes[i]; + RiverType * info = new RiverType; - roadInfoByName[roadInfo->name] = roadInfo; - roadInfoByCode[roadInfo->code] = roadInfo; - roadInfoById[roadInfo->id] = roadInfo; - } + info->fileName = json["animation"].String(); + info->code = json["code"].String(); + info->deltaName = json["delta"].String(); + + return info; } -const std::vector & TerrainTypeHandler::terrains() const +const std::vector & RiverTypeHandler::getTypeNames() const { - //FIXME: somehow make it non-copyable? Pointers must point to original data and not its copy - return objects; + static const std::vector typeNames = { "river" }; + return typeNames; } -const std::vector& TerrainTypeHandler::rivers() const +std::vector RiverTypeHandler::loadLegacyData(size_t dataSize) { - return riverTypes; + return {}; } -const std::vector& TerrainTypeHandler::roads() const +std::vector RiverTypeHandler::getDefaultAllowed() const { - return roadTypes; + return {}; } -const TerrainType* TerrainTypeHandler::getInfoByName(const std::string& terrainName) const +RoadType * RoadTypeHandler::loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) { - return terrainInfoByName.at(terrainName); + RoadType * info = new RoadType; + + info->fileName = json["animation"].String(); + info->code = json["code"].String(); + info->movementCost = json["moveCost"].Integer(); + + return info; } -const TerrainType* TerrainTypeHandler::getInfoByCode(const std::string& terrainCode) const +const std::vector & RoadTypeHandler::getTypeNames() const { - return terrainInfoByCode.at(terrainCode); + static const std::vector typeNames = { "river" }; + return typeNames; } -const TerrainType* TerrainTypeHandler::getInfoById(TerrainId id) const +std::vector RoadTypeHandler::loadLegacyData(size_t dataSize) { - return terrainInfoById.at(id); + return {}; } -const RiverType* TerrainTypeHandler::getRiverByName(const std::string& riverName) const +std::vector RoadTypeHandler::getDefaultAllowed() const { - return riverInfoByName.at(riverName); + return {}; } -const RiverType* TerrainTypeHandler::getRiverByCode(const std::string& riverCode) const -{ - return riverInfoByCode.at(riverCode); -} - -const RiverType* TerrainTypeHandler::getRiverById(RiverId id) const -{ - return riverInfoById.at(id); -} - -const RoadType* TerrainTypeHandler::getRoadByName(const std::string& roadName) const -{ - return roadInfoByName.at(roadName); -} - -const RoadType* TerrainTypeHandler::getRoadByCode(const std::string& roadCode) const -{ - return roadInfoByCode.at(roadCode); -} - -const RoadType* TerrainTypeHandler::getRoadById(RoadId id) const -{ - return roadInfoById.at(id); -} - -std::ostream & operator<<(std::ostream & os, const TerrainType & terrainType) -{ - return os << static_cast(terrainType); -} - TerrainType::operator std::string() const { return name; } - -TerrainType::TerrainType(const std::string& _name): - minimapBlocked({0,0,0}), //black - minimapUnblocked({ 128,128,128 }), //grey - name(_name), - river(River::NO_RIVER), - id(Terrain::WRONG), - rockTerrain(Terrain::ROCK), - moveCost(GameConstants::BASE_MOVEMENT_COST), - horseSoundId(0), - passabilityType(0), - transitionRequired(false) -{ -} - + bool TerrainType::operator==(const TerrainType& other) { return id == other.id; @@ -455,19 +247,4 @@ bool TerrainType::isTransitionRequired() const return transitionRequired; } -RiverType::RiverType(const std::string & fileName, const std::string & code, RiverId id): - fileName(fileName), - code(code), - id(id) -{ -} - -RoadType::RoadType(const std::string& fileName, const std::string& code, RoadId id): - fileName(fileName), - code(code), - id(id), - movementCost(GameConstants::BASE_MOVEMENT_COST) -{ -} - VCMI_LIB_NAMESPACE_END diff --git a/lib/Terrain.h b/lib/Terrain.h index 818d70e9e..29df6877d 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -10,16 +10,25 @@ #pragma once +#include +#include #include "ConstTransitivePtr.h" #include "GameConstants.h" #include "JsonNode.h" +#include "IHandlerBase.h" VCMI_LIB_NAMESPACE_BEGIN -class DLL_LINKAGE TerrainType +class DLL_LINKAGE TerrainType : public EntityT { public: - + int32_t getIndex() const override; + int32_t getIconIndex() const override; + const std::string & getName() const override; + const std::string & getJsonKey() const override; + void registerIcons(const IconRegistar & cb) const override; + TerrainId getId() const override; + enum PassabilityType : ui8 { LAND = 1, @@ -88,10 +97,16 @@ public: } }; -class DLL_LINKAGE RiverType +class DLL_LINKAGE RiverType : public EntityT { public: - std::string name; + int32_t getIndex() const override; + int32_t getIconIndex() const override; + const std::string & getName() const override; + const std::string & getJsonKey() const override; + void registerIcons(const IconRegistar & cb) const override; + TerrainId getId() const override; + std::string fileName; std::string code; std::string deltaName; @@ -101,10 +116,6 @@ public: template void serialize(Handler& h, const int version) { - if(version >= 806) - { - h & name; - } h & fileName; h & code; h & deltaName; @@ -112,10 +123,16 @@ public: } }; -class DLL_LINKAGE RoadType +class DLL_LINKAGE RoadType : public EntityT { public: - std::string name; + int32_t getIndex() const override; + int32_t getIconIndex() const override; + const std::string & getName() const override; + const std::string & getJsonKey() const override; + void registerIcons(const IconRegistar & cb) const override; + TerrainId getId() const override; + std::string fileName; std::string code; RoadId id; @@ -125,10 +142,6 @@ public: template void serialize(Handler& h, const int version) { - if(version >= 806) - { - h & name; - } h & fileName; h & code; h & id; @@ -136,69 +149,85 @@ public: } }; -DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const TerrainType & terrainType); - -class DLL_LINKAGE TerrainTypeHandler //TODO: public IHandlerBase ? +class DLL_LINKAGE TerrainTypeService : public EntityServiceT { public: +}; - TerrainTypeHandler(); - ~TerrainTypeHandler() {}; +class DLL_LINKAGE RiverTypeService : public EntityServiceT +{ +public: +}; - const std::vector & terrains() const; - const TerrainType * getInfoByName(const std::string & terrainName) const; - const TerrainType * getInfoByCode(const std::string & terrainCode) const; - const TerrainType * getInfoById(TerrainId id) const; +class DLL_LINKAGE RoadTypeService : public EntityServiceT +{ +public: +}; - const std::vector & rivers() const; - const RiverType * getRiverByName(const std::string & riverName) const; - const RiverType * getRiverByCode(const std::string & riverCode) const; - const RiverType * getRiverById(RiverId id) const; +class DLL_LINKAGE TerrainTypeHandler : public CHandlerBase +{ +public: + virtual TerrainType * loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) override; - const std::vector & roads() const; - const RoadType * getRoadByName(const std::string & roadName) const; - const RoadType * getRoadByCode(const std::string & roadCode) const; - const RoadType * getRoadById(RoadId id) const; + virtual const std::vector & getTypeNames() const override; + virtual std::vector loadLegacyData(size_t dataSize) override; + virtual std::vector getDefaultAllowed() const override; - template void serialize(Handler &h, const int version) + TerrainType * getInfoByCode(const std::string & identifier); + TerrainType * getInfoByName(const std::string & identifier); + + template void serialize(Handler & h, const int version) { h & objects; - h & riverTypes; - h & roadTypes; - - if (!h.saving) - { - recreateTerrainMaps(); - recreateRiverMaps(); - recreateRoadMaps(); - } } +}; -private: +class DLL_LINKAGE RiverTypeHandler : public CHandlerBase +{ +public: + virtual RiverType * loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) override; - std::vector objects; - std::vector riverTypes; - std::vector roadTypes; + virtual const std::vector & getTypeNames() const override; + virtual std::vector loadLegacyData(size_t dataSize) override; + virtual std::vector getDefaultAllowed() const override; - std::unordered_map terrainInfoByName; - std::unordered_map terrainInfoByCode; - std::unordered_map terrainInfoById; + RiverType * getInfoByCode(const std::string & identifier); + RiverType * getInfoByName(const std::string & identifier); - std::unordered_map riverInfoByName; - std::unordered_map riverInfoByCode; - std::unordered_map riverInfoById; + template void serialize(Handler & h, const int version) + { + h & objects; + } +}; - std::unordered_map roadInfoByName; - std::unordered_map roadInfoByCode; - std::unordered_map roadInfoById; +class DLL_LINKAGE RoadTypeHandler : public CHandlerBase +{ +public: + virtual RoadType * loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) override; - void initTerrains(const std::vector & allConfigs); - void initRivers(const std::vector & allConfigs); - void initRoads(const std::vector & allConfigs); - void recreateTerrainMaps(); - void recreateRiverMaps(); - void recreateRoadMaps(); + virtual const std::vector & getTypeNames() const override; + virtual std::vector loadLegacyData(size_t dataSize) override; + virtual std::vector getDefaultAllowed() const override; + RoadType * getInfoByCode(const std::string & identifier); + RoadType * getInfoByName(const std::string & identifier); + + template void serialize(Handler & h, const int version) + { + h & objects; + } }; VCMI_LIB_NAMESPACE_END diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 4b74bf86f..19d8785a6 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -197,6 +197,8 @@ void LibClasses::init(bool onlyEssential) createHandler(bth, "Bonus type", pomtime); + createHandler(roadTypeHandler, "Road", pomtime); + createHandler(riverTypeHandler, "River", pomtime); createHandler(terrainTypeHandler, "Terrain", pomtime); createHandler(generaltexth, "General text", pomtime); diff --git a/lib/VCMI_Lib.h b/lib/VCMI_Lib.h index 738743aba..55e58712b 100644 --- a/lib/VCMI_Lib.h +++ b/lib/VCMI_Lib.h @@ -30,6 +30,8 @@ class BattleFieldHandler; class IBonusTypeHandler; class CBonusTypeHandler; class TerrainTypeHandler; +class RoadTypeHandler; +class RiverTypeHandler; class ObstacleHandler; class CTerrainViewPatternConfig; class CRmgTemplateStorage; @@ -86,7 +88,11 @@ public: CTownHandler * townh; CGeneralTextHandler * generaltexth; CModHandler * modh; + TerrainTypeHandler * terrainTypeHandler; + RoadTypeHandler * roadTypeHandler; + RiverTypeHandler * riverTypeHandler; + CTerrainViewPatternConfig * terviewh; CRmgTemplateStorage * tplh; BattleFieldHandler * battlefieldsHandler; @@ -127,6 +133,8 @@ public: h & skillh; h & battlefieldsHandler; h & obstacleHandler; + h & roadTypeHandler; + h & riverTypeHandler; h & terrainTypeHandler; if(!h.saving) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 6b5301732..d30d663df 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -86,7 +86,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f ret = std::max(dest.roadType->movementCost, from.roadType->movementCost); } else if(ti->nativeTerrain != from.terType->id &&//the terrain is not native - ti->nativeTerrain != Terrain::ANY_TERRAIN && //no special creature bonus + ti->nativeTerrain != TerrainId::ANY_TERRAIN && //no special creature bonus !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType->id)) //no special movement bonus { @@ -106,18 +106,18 @@ TerrainId CGHeroInstance::getNativeTerrain() const // will always have best penalty without any influence from player-defined stacks order // TODO: What should we do if all hero stacks are neutral creatures? - TerrainId nativeTerrain = Terrain::BORDER; + TerrainId nativeTerrain = TerrainId::BORDER; for(auto stack : stacks) { TerrainId stackNativeTerrain = stack.second->type->getNativeTerrain(); //consider terrain bonuses e.g. Lodestar. - if(stackNativeTerrain == Terrain::BORDER) //where does this value come from? + if(stackNativeTerrain == TerrainId::BORDER) //where does this value come from? continue; - if(nativeTerrain == Terrain::BORDER) + if(nativeTerrain == TerrainId::BORDER) nativeTerrain = stackNativeTerrain; else if(nativeTerrain != stackNativeTerrain) - return Terrain::BORDER; + return TerrainId::BORDER; } return nativeTerrain; } diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index d91536542..4822058fd 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -157,20 +157,20 @@ void ObjectTemplate::readTxt(CLegacyConfigParser & parser) // so these two fields can be interpreted as "strong affinity" and "weak affinity" towards terrains std::string & terrStr = strings[4]; // allowed terrains, 1 = object can be placed on this terrain - assert(terrStr.size() == Terrain::ROCK); // all terrains but rock - counting from 0 - for(TerrainId i = Terrain::FIRST_REGULAR_TERRAIN; i < Terrain::ROCK; i++) + assert(terrStr.size() == TerrainId::ROCK); // all terrains but rock - counting from 0 + for(TerrainId i = TerrainId(0); i < TerrainId::ROCK; ++i) { if (terrStr[8-i] == '1') allowedTerrains.insert(i); } //assuming that object can be placed on other land terrains - if(allowedTerrains.size() >= 8 && !allowedTerrains.count(Terrain::WATER)) + if(allowedTerrains.size() >= 8 && !allowedTerrains.count(TerrainId::WATER)) { - for(const auto & terrain : VLC->terrainTypeHandler->terrains()) + for(const auto & terrain : VLC->terrainTypeHandler->objects) { - if(terrain.isLand() && terrain.isPassable()) - allowedTerrains.insert(terrain.id); + if(terrain->isLand() && terrain->isPassable()) + allowedTerrains.insert(terrain->id); } } @@ -231,19 +231,19 @@ void ObjectTemplate::readMap(CBinaryReader & reader) reader.readUInt16(); ui16 terrMask = reader.readUInt16(); - for(size_t i = Terrain::FIRST_REGULAR_TERRAIN; i < Terrain::ROCK; i++) + for(TerrainId i = TerrainId::FIRST_REGULAR_TERRAIN; i < TerrainId::ORIGINAL_TERRAIN_COUNT; ++i) { if (((terrMask >> i) & 1 ) != 0) allowedTerrains.insert(i); } //assuming that object can be placed on other land terrains - if(allowedTerrains.size() >= 8 && !allowedTerrains.count(Terrain::WATER)) + if(allowedTerrains.size() >= 8 && !allowedTerrains.count(TerrainId::WATER)) { - for(const auto & terrain : VLC->terrainTypeHandler->terrains()) + for(const auto & terrain : VLC->terrainTypeHandler->objects) { - if(terrain.isLand() && terrain.isPassable()) - allowedTerrains.insert(terrain.id); + if(terrain->isLand() && terrain->isPassable()) + allowedTerrains.insert(terrain->id); } } @@ -299,11 +299,11 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain) } else { - for(const auto & terrain : VLC->terrainTypeHandler->terrains()) + for(const auto & terrain : VLC->terrainTypeHandler->objects) { - if(!terrain.isPassable() || terrain.isWater()) + if(!terrain->isPassable() || terrain->isWater()) continue; - allowedTerrains.insert(terrain.id); + allowedTerrains.insert(terrain->id); } } @@ -378,14 +378,14 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const if(withTerrain) { //assumed that ROCK and WATER terrains are not included - if(allowedTerrains.size() < (VLC->terrainTypeHandler->terrains().size() - 2)) + if(allowedTerrains.size() < (VLC->terrainTypeHandler->objects.size() - 2)) { JsonVector & data = node["allowedTerrains"].Vector(); for(auto type : allowedTerrains) { JsonNode value(JsonNode::JsonType::DATA_STRING); - value.String() = type; + value.String() = VLC->terrainTypeHandler->getById(type)->name; data.push_back(value); } } diff --git a/lib/mapping/CDrawRoadsOperation.cpp b/lib/mapping/CDrawRoadsOperation.cpp index 4c529b17f..727037a3d 100644 --- a/lib/mapping/CDrawRoadsOperation.cpp +++ b/lib/mapping/CDrawRoadsOperation.cpp @@ -338,12 +338,12 @@ std::string CDrawRiversOperation::getLabel() const void CDrawRoadsOperation::executeTile(TerrainTile & tile) { - tile.roadType = const_cast(&VLC->terrainTypeHandler->roads()[roadType]); + tile.roadType = const_cast(VLC->roadTypeHandler->getByIndex(roadType)); } void CDrawRiversOperation::executeTile(TerrainTile & tile) { - tile.riverType = const_cast(&VLC->terrainTypeHandler->rivers()[riverType]); + tile.riverType = const_cast(VLC->riverTypeHandler->getByIndex(riverType)); } bool CDrawRoadsOperation::canApplyPattern(const LinePattern & pattern) const diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 490653924..90ef2802b 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -128,9 +128,9 @@ CCastleEvent::CCastleEvent() : town(nullptr) TerrainTile::TerrainTile(): terType(nullptr), terView(0), - riverType(const_cast(&VLC->terrainTypeHandler->rivers()[River::NO_RIVER])), + riverType(nullptr), riverDir(0), - roadType(const_cast(&VLC->terrainTypeHandler->roads()[Road::NO_ROAD])), + roadType(nullptr), roadDir(0), extTileFlags(0), visitable(false), diff --git a/lib/mapping/CMapOperation.cpp b/lib/mapping/CMapOperation.cpp index 6f7798741..acc0c85c8 100644 --- a/lib/mapping/CMapOperation.cpp +++ b/lib/mapping/CMapOperation.cpp @@ -97,7 +97,7 @@ void CDrawTerrainOperation::execute() for(const auto & pos : terrainSel.getSelectedItems()) { auto & tile = map->getTile(pos); - tile.terType = const_cast(&VLC->terrainTypeHandler->terrains()[terType]); + tile.terType = const_cast(VLC->terrainTypeHandler->getById(terType)); invalidateTerrainViews(pos); } @@ -422,14 +422,14 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi bool nativeTestOk, nativeTestStrongOk; nativeTestOk = nativeTestStrongOk = (rule.isNativeStrong() || rule.isNativeRule()) && !isAlien; - if(centerTerType->id == Terrain::DIRT) + if(centerTerType->id == TerrainId::DIRT) { nativeTestOk = rule.isNativeRule() && !terType->isTransitionRequired(); bool sandTestOk = (rule.isSandRule() || rule.isTransition()) && terType->isTransitionRequired(); applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk || nativeTestStrongOk); } - else if(centerTerType->id == Terrain::SAND) + else if(centerTerType->id == TerrainId::SAND) { applyValidationRslt(true); } @@ -551,12 +551,12 @@ CClearTerrainOperation::CClearTerrainOperation(CMap* map, CRandomGenerator* gen) { CTerrainSelection terrainSel(map); terrainSel.selectRange(MapRect(int3(0, 0, 0), map->width, map->height)); - addOperation(std::make_unique(map, terrainSel, Terrain::WATER, gen)); + addOperation(std::make_unique(map, terrainSel, TerrainId::WATER, gen)); if(map->twoLevel) { terrainSel.clearSelection(); terrainSel.selectRange(MapRect(int3(0, 0, 1), map->width, map->height)); - addOperation(std::make_unique(map, terrainSel, Terrain::ROCK, gen)); + addOperation(std::make_unique(map, terrainSel, TerrainId::ROCK, gen)); } } diff --git a/lib/mapping/MapEditUtils.cpp b/lib/mapping/MapEditUtils.cpp index b0e9dc822..8845a0f94 100644 --- a/lib/mapping/MapEditUtils.cpp +++ b/lib/mapping/MapEditUtils.cpp @@ -272,7 +272,7 @@ CTerrainViewPatternConfig::~CTerrainViewPatternConfig() const std::vector & CTerrainViewPatternConfig::getTerrainViewPatterns(TerrainId terrain) const { - auto iter = terrainViewPatterns.find(VLC->terrainTypeHandler->terrains()[terrain].terrainViewPatterns); + auto iter = terrainViewPatterns.find(VLC->terrainTypeHandler->getById(terrain)->terrainViewPatterns); if (iter == terrainViewPatterns.end()) return terrainViewPatterns.at("normal"); return iter->second; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 719a8eb1e..d3984bc20 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -923,9 +923,6 @@ bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot) void CMapLoaderH3M::readTerrain() { map->initTerrain(); - const auto & terrains = VLC->terrainTypeHandler->terrains(); - const auto & rivers = VLC->terrainTypeHandler->rivers(); - const auto & roads = VLC->terrainTypeHandler->roads(); // Read terrain int3 pos; @@ -937,14 +934,14 @@ void CMapLoaderH3M::readTerrain() for(pos.x = 0; pos.x < map->width; pos.x++) { auto & tile = map->getTile(pos); - tile.terType = const_cast(&terrains[reader.readUInt8()]); + tile.terType = const_cast(VLC->terrainTypeHandler->getByIndex(reader.readUInt8())); tile.terView = reader.readUInt8(); - tile.riverType = const_cast(&rivers[reader.readUInt8()]); + tile.riverType = const_cast(VLC->riverTypeHandler->getByIndex(reader.readUInt8())); tile.riverDir = reader.readUInt8(); - tile.roadType = const_cast(&roads[reader.readUInt8()]); + tile.roadType = const_cast(VLC->roadTypeHandler->getByIndex(reader.readUInt8())); tile.roadDir = reader.readUInt8(); tile.extTileFlags = reader.readUInt8(); - tile.blocked = ((!tile.terType->isPassable() || tile.terType->id == Terrain::BORDER ) ? true : false); //underground tiles are always blocked + tile.blocked = ((!tile.terType->isPassable() || tile.terType->id == TerrainId::BORDER ) ? true : false); //underground tiles are always blocked tile.visitable = 0; } } diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 158e94d10..ddb095a98 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -979,13 +979,13 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile startPos += 2; try { - tile.roadType = const_cast(VLC->terrainTypeHandler->getRoadByCode(typeCode)); + tile.roadType = const_cast(VLC->roadTypeHandler->getInfoByCode(typeCode)); } catch (const std::exception&) //it's not a road, it's a river { try { - tile.riverType = const_cast(VLC->terrainTypeHandler->getRiverByCode(typeCode)); + tile.riverType = const_cast(VLC->riverTypeHandler->getInfoByCode(typeCode)); hasRoad = false; } catch (const std::exception&) @@ -1021,7 +1021,7 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile {//river type const std::string typeCode = src.substr(startPos, 2); startPos += 2; - tile.riverType = const_cast(VLC->terrainTypeHandler->getRiverByCode(typeCode)); + tile.riverType = const_cast(VLC->riverTypeHandler->getInfoByCode(typeCode)); } {//river dir int pos = startPos; diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 4e54e413a..235b7a658 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -74,8 +74,8 @@ public: static std::string encode(const si32 index) { - const auto& terrains = VLC->terrainTypeHandler->terrains(); - return (index >=0 && index < terrains.size()) ? terrains[index].name : ""; + const auto& terrains = VLC->terrainTypeHandler->objects; + return (index >=0 && index < terrains.size()) ? terrains[index]->name : ""; } }; @@ -152,9 +152,9 @@ ZoneOptions::ZoneOptions() terrainTypeLikeZone(NO_ZONE), treasureLikeZone(NO_ZONE) { - for(const auto & terr : VLC->terrainTypeHandler->terrains()) - if(terr.isLand() && terr.isPassable()) - terrainTypes.insert(terr.id); + for(const auto & terr : VLC->terrainTypeHandler->objects) + if(terr->isLand() && terr->isPassable()) + terrainTypes.insert(terr->id); } ZoneOptions & ZoneOptions::operator=(const ZoneOptions & other) @@ -365,7 +365,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) for(auto & ttype : terrainTypes) { JsonNode n; - n.String() = ttype; + n.String() = VLC->terrainTypeHandler->getById(ttype)->name; node.Vector().push_back(n); } } diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 03616a48f..b09f83a9f 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -194,15 +194,15 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const else { auto & tt = (*VLC->townh)[faction]->nativeTerrain; - if(tt == Terrain::DIRT) + if(tt == TerrainId::DIRT) { //any / random zonesToPlace.push_back(zone); } else { - const auto & terrainType = VLC->terrainTypeHandler->terrains()[tt]; - if(terrainType.isUnderground() && !terrainType.isSurface()) + const auto & terrainType = VLC->terrainTypeHandler->getById(tt); + if(terrainType->isUnderground() && !terrainType->isSurface()) { //underground only zonesOnLevel[1]++; @@ -580,7 +580,7 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) //make sure that terrain inside zone is not a rock //FIXME: reorder actions? - paintZoneTerrain(*zone.second, *rand, map, Terrain::SUBTERRANEAN); + paintZoneTerrain(*zone.second, *rand, map, TerrainId::SUBTERRANEAN); } } logGlobal->info("Finished zone colouring"); diff --git a/lib/rmg/ConnectionsPlacer.cpp b/lib/rmg/ConnectionsPlacer.cpp index ddcd5ebfe..4bf47a7ef 100644 --- a/lib/rmg/ConnectionsPlacer.cpp +++ b/lib/rmg/ConnectionsPlacer.cpp @@ -84,9 +84,9 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con //1. Try to make direct connection //Do if it's not prohibited by terrain settings - const auto& terrains = VLC->terrainTypeHandler->terrains(); - bool directProhibited = vstd::contains(terrains[zone.getTerrainType()].prohibitTransitions, otherZone->getTerrainType()) - || vstd::contains(terrains[otherZone->getTerrainType()].prohibitTransitions, zone.getTerrainType()); + const auto& terrains = VLC->terrainTypeHandler->objects; + bool directProhibited = vstd::contains(terrains[zone.getTerrainType()]->prohibitTransitions, otherZone->getTerrainType()) + || vstd::contains(terrains[otherZone->getTerrainType()]->prohibitTransitions, zone.getTerrainType()); auto directConnectionIterator = dNeighbourZones.find(otherZoneId); if(!directProhibited && directConnectionIterator != dNeighbourZones.end()) { diff --git a/lib/rmg/Functions.cpp b/lib/rmg/Functions.cpp index 04846ebe4..3b12e3668 100644 --- a/lib/rmg/Functions.cpp +++ b/lib/rmg/Functions.cpp @@ -119,9 +119,9 @@ void initTerrainType(Zone & zone, CMapGenerator & gen) { //collect all water terrain types std::vector waterTerrains; - for(const auto & terrain : VLC->terrainTypeHandler->terrains()) - if(terrain.isWater()) - waterTerrains.push_back(terrain.id); + for(const auto & terrain : VLC->terrainTypeHandler->objects) + if(terrain->isWater()) + waterTerrains.push_back(terrain->id); zone.setTerrainType(*RandomGeneratorUtil::nextItem(waterTerrains, gen.rand)); } @@ -137,20 +137,20 @@ void initTerrainType(Zone & zone, CMapGenerator & gen) } //Now, replace disallowed terrains on surface and in the underground - const auto & terrainType = VLC->terrainTypeHandler->terrains()[zone.getTerrainType()]; + const auto & terrainType = VLC->terrainTypeHandler->getById(zone.getTerrainType()); if(zone.isUnderground()) { - if(!terrainType.isUnderground()) + if(!terrainType->isUnderground()) { - zone.setTerrainType(Terrain::SUBTERRANEAN); + zone.setTerrainType(TerrainId::SUBTERRANEAN); } } else { - if (!terrainType.isSurface()) + if (!terrainType->isSurface()) { - zone.setTerrainType(Terrain::DIRT); + zone.setTerrainType(TerrainId::DIRT); } } } diff --git a/lib/rmg/RiverPlacer.cpp b/lib/rmg/RiverPlacer.cpp index 64ba549e5..3d5826e5e 100644 --- a/lib/rmg/RiverPlacer.cpp +++ b/lib/rmg/RiverPlacer.cpp @@ -86,7 +86,7 @@ void RiverPlacer::init() void RiverPlacer::drawRivers() { map.getEditManager()->getTerrainSelection().setSelection(rivers.getTilesVector()); - map.getEditManager()->drawRiver(VLC->terrainTypeHandler->terrains()[zone.getTerrainType()].river, &generator.rand); + map.getEditManager()->drawRiver(VLC->terrainTypeHandler->getById(zone.getTerrainType())->river, &generator.rand); } char RiverPlacer::dump(const int3 & t) @@ -195,7 +195,7 @@ void RiverPlacer::preprocess() //calculate delta positions if(connectedToWaterZoneId > -1) { - auto river = VLC->terrainTypeHandler->terrains()[zone.getTerrainType()].river; + auto river = VLC->terrainTypeHandler->getById(zone.getTerrainType())->river; auto & a = neighbourZonesTiles[connectedToWaterZoneId]; auto availableArea = zone.areaPossible() + zone.freePaths(); for(auto & tileToProcess : availableArea.getTilesVector()) @@ -321,9 +321,9 @@ void RiverPlacer::preprocess() void RiverPlacer::connectRiver(const int3 & tile) { - auto riverType = VLC->terrainTypeHandler->terrains()[zone.getTerrainType()].river; - const auto & river = VLC->terrainTypeHandler->rivers()[riverType]; - if(river.id == River::NO_RIVER) + auto riverType = VLC->terrainTypeHandler->getById(zone.getTerrainType())->river; + const auto * river = VLC->riverTypeHandler->getByIndex(riverType); + if(river->id == River::NO_RIVER) return; rmg::Area roads; @@ -381,9 +381,9 @@ void RiverPlacer::connectRiver(const int3 & tile) { if(tmplates.size() % 4 != 0) throw rmgException(boost::to_string(boost::format("River templates for (%d,%d) at terrain %s, river %s are incorrect") % - RIVER_DELTA_ID % RIVER_DELTA_SUBTYPE % zone.getTerrainType() % river.code)); + RIVER_DELTA_ID % RIVER_DELTA_SUBTYPE % zone.getTerrainType() % river->code)); - std::string targetTemplateName = river.deltaName + std::to_string(deltaOrientations[pos]) + ".def"; + std::string targetTemplateName = river->deltaName + std::to_string(deltaOrientations[pos]) + ".def"; for(auto & templ : tmplates) { if(templ->animationFile == targetTemplateName) diff --git a/lib/rmg/RmgMap.cpp b/lib/rmg/RmgMap.cpp index c1aac566e..834a8160a 100644 --- a/lib/rmg/RmgMap.cpp +++ b/lib/rmg/RmgMap.cpp @@ -84,7 +84,7 @@ void RmgMap::initTiles(CMapGenerator & generator) getEditManager()->clearTerrain(&generator.rand); getEditManager()->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions.getWidth(), mapGenOptions.getHeight())); - getEditManager()->drawTerrain(Terrain::GRASS, &generator.rand); + getEditManager()->drawTerrain(TerrainId::GRASS, &generator.rand); auto tmpl = mapGenOptions.getMapTemplate(); zones.clear(); diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index 6c1e27574..e54664fdc 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -121,7 +121,7 @@ void Object::Instance::setTemplate(TerrainId terrain) auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrain); if (templates.empty()) { - auto terrainName = VLC->terrainTypeHandler->terrains()[terrain].name; + auto terrainName = VLC->terrainTypeHandler->getById(terrain)->name; throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.subID % terrainName)); } dObject.appearance = templates.front(); diff --git a/lib/rmg/RoadPlacer.cpp b/lib/rmg/RoadPlacer.cpp index b8a037135..385f51d2c 100644 --- a/lib/rmg/RoadPlacer.cpp +++ b/lib/rmg/RoadPlacer.cpp @@ -78,8 +78,10 @@ void RoadPlacer::drawRoads(bool secondary) zone.areaPossible().subtract(roads); zone.freePaths().unite(roads); map.getEditManager()->getTerrainSelection().setSelection(roads.getTilesVector()); + std::string roadName = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType); - RoadId roadType = VLC->terrainTypeHandler->getRoadByName(roadName)->id; + RoadId roadType = VLC->roadTypeHandler->getInfoByName(roadName)->id; + map.getEditManager()->drawRoad(roadType, &generator.rand); } diff --git a/lib/rmg/RockPlacer.cpp b/lib/rmg/RockPlacer.cpp index 3cf9fbc25..980e35f0b 100644 --- a/lib/rmg/RockPlacer.cpp +++ b/lib/rmg/RockPlacer.cpp @@ -24,8 +24,8 @@ VCMI_LIB_NAMESPACE_BEGIN void RockPlacer::process() { - rockTerrain = VLC->terrainTypeHandler->terrains()[zone.getTerrainType()].rockTerrain; - assert(!VLC->terrainTypeHandler->terrains()[rockTerrain].isPassable()); + rockTerrain = VLC->terrainTypeHandler->getById(zone.getTerrainType())->rockTerrain; + assert(!VLC->terrainTypeHandler->getById(rockTerrain)->isPassable()); accessibleArea = zone.freePaths() + zone.areaUsed(); if(auto * m = zone.getModificator()) diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index fef80250f..8c6f500b7 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -28,7 +28,7 @@ std::function AREA_NO_FILTER = [](const int3 & t) Zone::Zone(RmgMap & map, CMapGenerator & generator) : ZoneOptions(), townType(ETownType::NEUTRAL), - terrainType(Terrain::GRASS), + terrainType(TerrainId::GRASS), map(map), generator(generator) { diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 4b831be65..3396c801c 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -542,32 +542,32 @@ void MainWindow::loadObjectsTree() { ui->terrainFilterCombo->addItem(""); //adding terrains - for(auto & terrain : VLC->terrainTypeHandler->terrains()) + for(auto & terrain : VLC->terrainTypeHandler->objects) { - QPushButton *b = new QPushButton(QString::fromStdString(terrain.name)); + QPushButton *b = new QPushButton(QString::fromStdString(terrain->name)); ui->terrainLayout->addWidget(b); - connect(b, &QPushButton::clicked, this, [this, terrain]{ terrainButtonClicked(terrain.id); }); + connect(b, &QPushButton::clicked, this, [this, terrain]{ terrainButtonClicked(terrain->id); }); //filter - ui->terrainFilterCombo->addItem(QString::fromStdString(terrain)); + ui->terrainFilterCombo->addItem(QString::fromStdString(terrain->name)); } //add spacer to keep terrain button on the top ui->terrainLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); //adding roads - for(auto & road : VLC->terrainTypeHandler->roads()) + for(auto & road : VLC->roadTypeHandler->objects) { - QPushButton *b = new QPushButton(QString::fromStdString(road.fileName)); + QPushButton *b = new QPushButton(QString::fromStdString(road->fileName)); ui->roadLayout->addWidget(b); - connect(b, &QPushButton::clicked, this, [this, road]{ roadOrRiverButtonClicked(road.id, true); }); + connect(b, &QPushButton::clicked, this, [this, road]{ roadOrRiverButtonClicked(road->id, true); }); } //add spacer to keep terrain button on the top ui->roadLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); //adding rivers - for(auto & river : VLC->terrainTypeHandler->rivers()) + for(auto & river : VLC->riverTypeHandler->objects) { - QPushButton *b = new QPushButton(QString::fromStdString(river.fileName)); + QPushButton *b = new QPushButton(QString::fromStdString(river->fileName)); ui->riverLayout->addWidget(b); - connect(b, &QPushButton::clicked, this, [this, river]{ roadOrRiverButtonClicked(river.id, false); }); + connect(b, &QPushButton::clicked, this, [this, river]{ roadOrRiverButtonClicked(river->id, false); }); } //add spacer to keep terrain button on the top ui->riverLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); @@ -914,7 +914,7 @@ void MainWindow::on_terrainFilterCombo_currentTextChanged(const QString &arg1) if(!objectBrowser) return; - objectBrowser->terrain = arg1.isEmpty() ? TerrainId(Terrain::ANY_TERRAIN) : VLC->terrainTypeHandler->getInfoByName(arg1.toStdString())->id; + objectBrowser->terrain = arg1.isEmpty() ? TerrainId(TerrainId::ANY_TERRAIN) : VLC->terrainTypeHandler->getInfoByName(arg1.toStdString())->id; objectBrowser->invalidate(); objectBrowser->sort(0); } diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp index 1a49915ba..589ecc8ab 100644 --- a/mapeditor/maphandler.cpp +++ b/mapeditor/maphandler.cpp @@ -78,17 +78,17 @@ void MapHandler::initTerrainGraphics() std::map terrainFiles; std::map roadFiles; std::map riverFiles; - for(const auto & terrain : VLC->terrainTypeHandler->terrains()) + for(const auto & terrain : VLC->terrainTypeHandler->objects) { - terrainFiles[terrain.name] = terrain.tilesFilename; + terrainFiles[terrain->name] = terrain->tilesFilename; } - for(const auto & river : VLC->terrainTypeHandler->rivers()) + for(const auto & river : VLC->riverTypeHandler->objects) { - riverFiles[river.fileName] = river.fileName; + riverFiles[river->fileName] = river->fileName; } - for(const auto & road : VLC->terrainTypeHandler->roads()) + for(const auto & road : VLC->roadTypeHandler->objects) { - roadFiles[road.fileName] = road.fileName; + roadFiles[road->fileName] = road->fileName; } loadFlipped(terrainAnimations, terrainImages, terrainFiles); diff --git a/mapeditor/objectbrowser.cpp b/mapeditor/objectbrowser.cpp index e7bbdc8c7..e8f8ac826 100644 --- a/mapeditor/objectbrowser.cpp +++ b/mapeditor/objectbrowser.cpp @@ -13,7 +13,7 @@ #include "../lib/mapObjects/CObjectClassesHandler.h" ObjectBrowserProxyModel::ObjectBrowserProxyModel(QObject *parent) - : QSortFilterProxyModel{parent}, terrain(Terrain::ANY_TERRAIN) + : QSortFilterProxyModel{parent}, terrain(TerrainId::ANY_TERRAIN) { } @@ -33,7 +33,7 @@ bool ObjectBrowserProxyModel::filterAcceptsRow(int source_row, const QModelIndex if(!filterAcceptsRowText(source_row, source_parent)) return false; - if(terrain == Terrain::ANY_TERRAIN) + if(terrain == TerrainId::ANY_TERRAIN) return result; auto data = item->data().toJsonObject(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index ddbf5fd92..a8dca3798 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2244,7 +2244,7 @@ void CGameHandler::setupBattle(int3 tile, const CArmedInstance *armies[2], const const auto & t = *getTile(tile); TerrainId terrain = t.terType->id; if (gs->map->isCoastalTile(tile)) //coastal tile is always ground - terrain = Terrain::SAND; + terrain = TerrainId::SAND; BattleField terType = gs->battleGetBattlefieldType(tile, getRandomGenerator()); if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat) From e1799379ddfa2042faacb66a128ada8b676f01ca Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 20 Dec 2022 18:35:40 +0200 Subject: [PATCH 057/197] Terrain/Road/River handler are now in compileable state --- client/CMusicHandler.cpp | 20 +-- client/CPlayerInterface.cpp | 2 +- client/lobby/RandomMapTab.cpp | 12 +- client/mapHandler.cpp | 3 +- config/randomMap.json | 2 +- lib/CCreatureHandler.cpp | 4 +- lib/CGameState.cpp | 2 +- lib/CPathfinder.cpp | 2 +- lib/CStack.cpp | 2 +- lib/CTownHandler.cpp | 20 ++- lib/GameConstants.h | 195 +++++++++++++++++----------- lib/HeroBonus.cpp | 6 +- lib/NetPacksLib.cpp | 6 +- lib/ObstacleHandler.cpp | 8 +- lib/Terrain.cpp | 10 +- lib/Terrain.h | 63 +++++---- lib/mapObjects/CGHeroInstance.cpp | 14 +- lib/mapObjects/ObjectTemplate.cpp | 24 ++-- lib/mapping/CDrawRoadsOperation.cpp | 4 +- lib/mapping/CDrawRoadsOperation.h | 2 +- lib/mapping/CMapOperation.cpp | 8 +- lib/mapping/MapFormatH3M.cpp | 2 +- lib/mapping/MapFormatJson.cpp | 39 +++++- lib/mapping/MapFormatJson.h | 7 + lib/rmg/CRmgTemplate.cpp | 9 +- lib/rmg/CZonePlacer.cpp | 4 +- lib/rmg/ConnectionsPlacer.cpp | 5 +- lib/rmg/Functions.cpp | 4 +- lib/rmg/RiverPlacer.cpp | 2 +- lib/rmg/RmgMap.cpp | 2 +- lib/rmg/RoadPlacer.cpp | 4 +- lib/rmg/Zone.cpp | 2 +- mapeditor/mainwindow.cpp | 13 +- mapeditor/objectbrowser.cpp | 4 +- server/CGameHandler.cpp | 2 +- 35 files changed, 303 insertions(+), 205 deletions(-) diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index 4291dcd12..c604eb6b0 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -94,16 +94,16 @@ CSoundHandler::CSoundHandler(): //TODO: support custom sounds for new terrains and load from json horseSounds = { - {TerrainId::DIRT, soundBase::horseDirt}, - {TerrainId::SAND, soundBase::horseSand}, - {TerrainId::GRASS, soundBase::horseGrass}, - {TerrainId::SNOW, soundBase::horseSnow}, - {TerrainId::SWAMP, soundBase::horseSwamp}, - {TerrainId::ROUGH, soundBase::horseRough}, - {TerrainId::SUBTERRANEAN, soundBase::horseSubterranean}, - {TerrainId::LAVA, soundBase::horseLava}, - {TerrainId::WATER, soundBase::horseWater}, - {TerrainId::ROCK, soundBase::horseRock} + {ETerrainId::DIRT, soundBase::horseDirt}, + {ETerrainId::SAND, soundBase::horseSand}, + {ETerrainId::GRASS, soundBase::horseGrass}, + {ETerrainId::SNOW, soundBase::horseSnow}, + {ETerrainId::SWAMP, soundBase::horseSwamp}, + {ETerrainId::ROUGH, soundBase::horseRough}, + {ETerrainId::SUBTERRANEAN, soundBase::horseSubterranean}, + {ETerrainId::LAVA, soundBase::horseLava}, + {ETerrainId::WATER, soundBase::horseWater}, + {ETerrainId::ROCK, soundBase::horseRock} }; } diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 62ffc5335..96a9ccc7c 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -2372,7 +2372,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) for (auto & elem : path.nodes) elem.coord = h->convertFromVisitablePos(elem.coord); - TerrainId currentTerrain = TerrainId::BORDER; // not init yet + TerrainId currentTerrain = ETerrainId::BORDER; // not init yet TerrainId newTerrain; int sh = -1; diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index b268b764b..ac183ca38 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -108,12 +108,12 @@ RandomMapTab::RandomMapTab(): GH.pushIntT(*this); }); - for(auto road : VLC->terrainTypeHandler->roads()) + for(auto road : VLC->roadTypeHandler->objects) { - std::string cbRoadType = "selectRoad_" + road.name; + std::string cbRoadType = "selectRoad_" + road->getName(); addCallback(cbRoadType, [&, road](bool on) { - mapGenOptions->setRoadEnabled(road.name, on); + mapGenOptions->setRoadEnabled(road->getName(), on); updateMapInfoByHost(); }); } @@ -283,11 +283,11 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr opts) else w->addTextOverlay(readText(variables["defaultTemplate"]), EFonts::FONT_SMALL); } - for(auto r : VLC->terrainTypeHandler->roads()) + for(auto r : VLC->roadTypeHandler->objects) { - if(auto w = widget(r.name)) + if(auto w = widget(r->getName())) { - w->setSelected(opts->isRoadEnabled(r.name)); + w->setSelected(opts->isRoadEnabled(r->getName())); } } } diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 2395d90cc..4015f43be 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -1389,7 +1389,8 @@ void CMapHandler::getTerrainDescr(const int3 & pos, std::string & out, bool isRM } } - VLC->terrainTypeHandler->getById(t.terType->id)->terrainText; + if(!isTile2Terrain || out.empty()) + out = VLC->terrainTypeHandler->getById(t.terType->id)->terrainText; if(t.getDiggingStatus(false) == EDiggingStatus::CAN_DIG) { diff --git a/config/randomMap.json b/config/randomMap.json index fa54f121f..5aa0d981a 100644 --- a/config/randomMap.json +++ b/config/randomMap.json @@ -42,4 +42,4 @@ "value" : [2000, 5333, 8666, 12000], "rewardValue" : [5000, 10000, 15000, 20000] } -} \ No newline at end of file +} diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index c2dc8878d..24c51fadc 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -288,7 +288,7 @@ std::string CCreature::nodeName() const bool CCreature::isItNativeTerrain(TerrainId terrain) const { auto native = getNativeTerrain(); - return native == terrain || native == TerrainId::ANY_TERRAIN; + return native == terrain || native == ETerrainId::ANY_TERRAIN; } TerrainId CCreature::getNativeTerrain() const @@ -299,7 +299,7 @@ TerrainId CCreature::getNativeTerrain() const //this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses //and in the CGHeroInstance::getNativeTerrain() to setup mevement bonuses or/and penalties. return hasBonus(selectorNoTerrainPenalty, selectorNoTerrainPenalty) - ? TerrainId(TerrainId::ANY_TERRAIN) + ? TerrainId(ETerrainId::ANY_TERRAIN) : (*VLC->townh)[faction]->nativeTerrain; } diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 8bf972536..3b6bb16d2 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2135,7 +2135,7 @@ void CGameState::updateRumor() rumorId = *RandomGeneratorUtil::nextItem(sRumorTypes, rand); if(rumorId == RumorState::RUMOR_GRAIL) { - rumorExtra = getTile(map->grailPos)->terType->id; + rumorExtra = getTile(map->grailPos)->terType->id.getNum(); break; } diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 4fff7bac5..dc1c1dd07 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -1003,7 +1003,7 @@ TurnInfo::BonusCache::BonusCache(TConstBonusListPtr bl) for(const auto & terrain : VLC->terrainTypeHandler->objects) { noTerrainPenalty.push_back(static_cast( - bl->getFirst(Selector::type()(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain->id))))); + bl->getFirst(Selector::type()(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain->id.getNum()))))); } freeShipBoarding = static_cast(bl->getFirst(Selector::type()(Bonus::FREE_SHIP_BOARDING))); diff --git a/lib/CStack.cpp b/lib/CStack.cpp index f0c01bbc8..da227fa3f 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -333,7 +333,7 @@ bool CStack::canBeHealed() const bool CStack::isOnNativeTerrain() const { //this code is called from CreatureTerrainLimiter::limit on battle start - auto res = nativeTerrain == TerrainId::ANY_TERRAIN || nativeTerrain == battle->getTerrainType(); + auto res = nativeTerrain == ETerrainId::ANY_TERRAIN || nativeTerrain == battle->getTerrainType(); return res; } diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 3727c0a9a..de01ca400 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -28,9 +28,9 @@ VCMI_LIB_NAMESPACE_BEGIN const int NAMES_PER_TOWN=16; // number of town names per faction in H3 files. Json can define any number -const TerrainId CTownHandler::defaultGoodTerrain(TerrainId::GRASS); -const TerrainId CTownHandler::defaultEvilTerrain(TerrainId::LAVA); -const TerrainId CTownHandler::defaultNeutralTerrain(TerrainId::ROUGH); +const TerrainId CTownHandler::defaultGoodTerrain(ETerrainId::GRASS); +const TerrainId CTownHandler::defaultEvilTerrain(ETerrainId::LAVA); +const TerrainId CTownHandler::defaultNeutralTerrain(ETerrainId::ROUGH); const std::map CBuilding::MODES = { @@ -989,9 +989,17 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode //Contructor is not called here, but operator= auto nativeTerrain = source["nativeTerrain"]; - faction->nativeTerrain = nativeTerrain.isNull() - ? getDefaultTerrainForAlignment(faction->alignment) - : VLC->terrainTypeHandler->getInfoByName(nativeTerrain.String())->id; + + if ( nativeTerrain.isNull()) + { + faction->nativeTerrain = getDefaultTerrainForAlignment(faction->alignment); + } + else + { + VLC->modh->identifiers.requestIdentifier("terrain", nativeTerrain, [=](int32_t index){ + faction->nativeTerrain = TerrainId(index); + }); + } if (!source["town"].isNull()) { diff --git a/lib/GameConstants.h b/lib/GameConstants.h index e119ee5bf..663c21cf1 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -149,12 +149,6 @@ STRONG_INLINE bool operator>=(const A & a, const B & b) \ ID_LIKE_OPERATORS_INTERNAL(ENUM_NAME, CLASS_NAME, a, b.num) -#define OP_DECL_INT(CLASS_NAME, OP) \ -bool operator OP (const CLASS_NAME & b) const \ -{ \ - return num OP b.num; \ -} - #define INSTID_LIKE_CLASS_COMMON(CLASS_NAME, NUMERIC_NAME) \ public: \ CLASS_NAME() : BaseForID(-1) {} \ @@ -205,14 +199,79 @@ public: } typedef BaseForID __SelfType; - OP_DECL_INT(__SelfType, ==) - OP_DECL_INT(__SelfType, !=) - OP_DECL_INT(__SelfType, <) - OP_DECL_INT(__SelfType, >) - OP_DECL_INT(__SelfType, <=) - OP_DECL_INT(__SelfType, >=) + bool operator == (const BaseForID & b) const { return num == b.num; } + bool operator <= (const BaseForID & b) const { return num >= b.num; } + bool operator >= (const BaseForID & b) const { return num <= b.num; } + bool operator != (const BaseForID & b) const { return num != b.num; } + bool operator < (const BaseForID & b) const { return num < b.num; } + bool operator > (const BaseForID & b) const { return num > b.num; } + + BaseForID & operator++() { ++num; return *this; } }; +template < typename T> +class Identifier : public IdTag +{ +public: + using EnumType = T; + using NumericType = typename std::underlying_type::type; + +private: + NumericType num; + +public: + NumericType getNum() const + { + return num; + } + + EnumType toEnum() const + { + return static_cast(num); + } + + template void serialize(Handler &h, const int version) + { + h & num; + } + + explicit Identifier(NumericType _num = -1) + { + num = _num; + } + + /* implicit */ Identifier(EnumType _num) + { + num = static_cast(_num); + } + + void advance(int change) + { + num += change; + } + + bool operator == (const Identifier & b) const { return num == b.num; } + bool operator <= (const Identifier & b) const { return num >= b.num; } + bool operator >= (const Identifier & b) const { return num <= b.num; } + bool operator != (const Identifier & b) const { return num != b.num; } + bool operator < (const Identifier & b) const { return num < b.num; } + bool operator > (const Identifier & b) const { return num > b.num; } + + Identifier & operator++() + { + ++num; + return *this; + } + + Identifier operator++(int) + { + Identifier ret(*this); + ++num; + return ret; + } +}; + + template std::ostream & operator << (std::ostream & os, BaseForID id); @@ -224,6 +283,14 @@ std::ostream & operator << (std::ostream & os, BaseForID id) return os << static_cast(id.getNum()); } +template +std::ostream & operator << (std::ostream & os, Identifier id) +{ + //We use common type with short to force char and unsigned char to be promoted and formatted as numbers. + typedef typename std::common_type::NumericType>::type Number; + return os << static_cast(id.getNum()); +} + class ArtifactInstanceID : public BaseForID { INSTID_LIKE_CLASS_COMMON(ArtifactInstanceID, si32) @@ -830,32 +897,26 @@ public: ID_LIKE_OPERATORS(Obj, Obj::EObj) -namespace Road +enum class Road : int8_t { - enum ERoad : ui8 - { - NO_ROAD = 0, - FIRST_REGULAR_ROAD = 1, - DIRT_ROAD = 1, - GRAVEL_ROAD = 2, - COBBLESTONE_ROAD = 3, - ORIGINAL_ROAD_COUNT //+1 - }; -} + NO_ROAD = 0, + FIRST_REGULAR_ROAD = 1, + DIRT_ROAD = 1, + GRAVEL_ROAD = 2, + COBBLESTONE_ROAD = 3, + ORIGINAL_ROAD_COUNT //+1 +}; -namespace River +enum class River : int8_t { - enum ERiver : ui8 - { - NO_RIVER = 0, - FIRST_REGULAR_RIVER = 1, - WATER_RIVER = 1, - ICY_RIVER = 2, - MUD_RIVER = 3, - LAVA_RIVER = 4, - ORIGINAL_RIVER_COUNT //+1 - }; -} + NO_RIVER = 0, + FIRST_REGULAR_RIVER = 1, + WATER_RIVER = 1, + ICY_RIVER = 2, + MUD_RIVER = 3, + LAVA_RIVER = 4, + ORIGINAL_RIVER_COUNT //+1 +}; namespace SecSkillLevel { @@ -1159,48 +1220,28 @@ class BattleField : public BaseForID DLL_LINKAGE static BattleField fromString(std::string identifier); }; -class TerrainId -{ -public: - enum ETerrainID { - NATIVE_TERRAIN = -4, - ANY_TERRAIN = -3, - WRONG = -2, - BORDER = -1, - FIRST_REGULAR_TERRAIN = 0, - DIRT, - SAND, - GRASS, - SNOW, - SWAMP, - ROUGH, - SUBTERRANEAN, - LAVA, - WATER, - ROCK, - ORIGINAL_TERRAIN_COUNT - }; - - TerrainId(ETerrainID _num = WRONG) : num(_num) - {} - - ETerrainID num; - - ID_LIKE_CLASS_COMMON(TerrainId, ETerrainID) - - DLL_LINKAGE operator std::string() const; - DLL_LINKAGE const TerrainId * getInfo() const; - - DLL_LINKAGE static ETerrainID fromString(std::string identifier); - - TerrainId & operator++() - { - num = static_cast(static_cast(num) + 1); - return *this; - } +enum class ETerrainId { + NATIVE_TERRAIN = -4, + ANY_TERRAIN = -3, + WRONG = -2, + BORDER = -1, + FIRST_REGULAR_TERRAIN = 0, + DIRT, + SAND, + GRASS, + SNOW, + SWAMP, + ROUGH, + SUBTERRANEAN, + LAVA, + WATER, + ROCK, + ORIGINAL_TERRAIN_COUNT }; -ID_LIKE_OPERATORS(TerrainId, TerrainId::ETerrainID) +using TerrainId = Identifier; +using RoadId = Identifier; +using RiverId = Identifier; class ObstacleInfo; class Obstacle : public BaseForID @@ -1259,9 +1300,6 @@ typedef si64 TExpType; typedef std::pair TDmgRange; typedef si32 TBonusSubtype; typedef si32 TQuantity; -//typedef si8 TerrainId; -typedef si8 RoadId; -typedef si8 RiverId; typedef int TRmgTemplateZoneId; @@ -1269,6 +1307,5 @@ typedef int TRmgTemplateZoneId; #undef ID_LIKE_OPERATORS #undef ID_LIKE_OPERATORS_INTERNAL #undef INSTID_LIKE_CLASS_COMMON -#undef OP_DECL_INT VCMI_LIB_NAMESPACE_END diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 333e59dba..5c7c27ce2 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -2106,7 +2106,7 @@ bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest) } CreatureTerrainLimiter::CreatureTerrainLimiter() - : terrainType(TerrainId::NATIVE_TERRAIN) + : terrainType(ETerrainId::NATIVE_TERRAIN) { } @@ -2120,7 +2120,7 @@ int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const const CStack *stack = retrieveStackBattle(&context.node); if(stack) { - if (terrainType == TerrainId::NATIVE_TERRAIN)//terrainType not specified = native + if (terrainType == ETerrainId::NATIVE_TERRAIN)//terrainType not specified = native { return !stack->isOnNativeTerrain(); } @@ -2137,7 +2137,7 @@ std::string CreatureTerrainLimiter::toString() const { boost::format fmt("CreatureTerrainLimiter(terrainType=%s)"); auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->name; - fmt % (terrainType == TerrainId::NATIVE_TERRAIN ? "native" : terrainName); + fmt % (terrainType == ETerrainId::NATIVE_TERRAIN ? "native" : terrainName); return fmt.str(); } diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index ddd1113a9..71de315b6 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -720,13 +720,13 @@ DLL_LINKAGE void GiveHero::applyGs(CGameState *gs) DLL_LINKAGE void NewObject::applyGs(CGameState *gs) { - TerrainId terrainType = TerrainId::BORDER; + TerrainId terrainType = ETerrainId::BORDER; if(ID == Obj::BOAT && !gs->isInTheMap(pos)) //special handling for bug #3060 - pos outside map but visitablePos is not { CGObjectInstance testObject = CGObjectInstance(); testObject.pos = pos; - testObject.appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(TerrainId::WATER).front(); + testObject.appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(ETerrainId::WATER).front(); const int3 previousXAxisTile = int3(pos.x - 1, pos.y, pos.z); assert(gs->isInTheMap(previousXAxisTile) && (testObject.visitablePos() == previousXAxisTile)); @@ -743,7 +743,7 @@ DLL_LINKAGE void NewObject::applyGs(CGameState *gs) { case Obj::BOAT: o = new CGBoat(); - terrainType = TerrainId::WATER; //TODO: either boat should only spawn on water, or all water objects should be handled this way + terrainType = ETerrainId::WATER; //TODO: either boat should only spawn on water, or all water objects should be handled this way break; case Obj::MONSTER: //probably more options will be needed o = new CGCreature(); diff --git a/lib/ObstacleHandler.cpp b/lib/ObstacleHandler.cpp index 36b316881..c427d4640 100644 --- a/lib/ObstacleHandler.cpp +++ b/lib/ObstacleHandler.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "ObstacleHandler.h" #include "BattleFieldHandler.h" +#include "CModHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -86,8 +87,13 @@ ObstacleInfo * ObstacleHandler::loadFromJson(const std::string & scope, const Js info->width = json["width"].Integer(); info->height = json["height"].Integer(); for(auto & t : json["allowedTerrains"].Vector()) - info->allowedTerrains.emplace_back(VLC->terrainTypeHandler->getInfoByName(t.String())->id); + { + VLC->modh->identifiers.requestIdentifier("terrain", t, [info](int32_t identifier){ + info->allowedTerrains.emplace_back(identifier); + }); + } for(auto & t : json["specialBattlefields"].Vector()) + info->allowedSpecialBfields.emplace_back(t.String()); info->blockedTiles = json["blockedTiles"].convertTo>(); info->isAbsoluteObstacle = json["absolute"].Bool(); diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index b1df78159..fa8608a5e 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -95,7 +95,7 @@ TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const }); } - info->rockTerrain = TerrainId::ROCK; + info->rockTerrain = ETerrainId::ROCK; if(!json["rockTerrain"].isNull()) { @@ -247,4 +247,12 @@ bool TerrainType::isTransitionRequired() const return transitionRequired; } +TerrainType::TerrainType() +{} + +RiverType::RiverType() +{} + +RoadType::RoadType() +{} VCMI_LIB_NAMESPACE_END diff --git a/lib/Terrain.h b/lib/Terrain.h index 29df6877d..d102ff7ab 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -22,12 +22,12 @@ VCMI_LIB_NAMESPACE_BEGIN class DLL_LINKAGE TerrainType : public EntityT { public: - int32_t getIndex() const override; - int32_t getIconIndex() const override; - const std::string & getName() const override; - const std::string & getJsonKey() const override; - void registerIcons(const IconRegistar & cb) const override; - TerrainId getId() const override; + int32_t getIndex() const override { return id.getNum(); } + int32_t getIconIndex() const override { return 0; } + const std::string & getName() const override { return name;} + const std::string & getJsonKey() const override { return name;} + void registerIcons(const IconRegistar & cb) const override {} + TerrainId getId() const override { return id;} enum PassabilityType : ui8 { @@ -57,7 +57,7 @@ public: ui8 passabilityType; bool transitionRequired; - TerrainType(const std::string & name = ""); + TerrainType(); bool operator==(const TerrainType & other); bool operator!=(const TerrainType & other); @@ -97,22 +97,22 @@ public: } }; -class DLL_LINKAGE RiverType : public EntityT +class DLL_LINKAGE RiverType : public EntityT { public: - int32_t getIndex() const override; - int32_t getIconIndex() const override; - const std::string & getName() const override; - const std::string & getJsonKey() const override; - void registerIcons(const IconRegistar & cb) const override; - TerrainId getId() const override; + int32_t getIndex() const override { return id.getNum(); } + int32_t getIconIndex() const override { return 0; } + const std::string & getName() const override { return code;} + const std::string & getJsonKey() const override { return code;} + void registerIcons(const IconRegistar & cb) const override {} + RiverId getId() const override { return id;} std::string fileName; std::string code; std::string deltaName; RiverId id; - RiverType(const std::string & fileName = "", const std::string & code = "", RiverId id = River::NO_RIVER); + RiverType(); template void serialize(Handler& h, const int version) { @@ -123,22 +123,22 @@ public: } }; -class DLL_LINKAGE RoadType : public EntityT +class DLL_LINKAGE RoadType : public EntityT { public: - int32_t getIndex() const override; - int32_t getIconIndex() const override; - const std::string & getName() const override; - const std::string & getJsonKey() const override; - void registerIcons(const IconRegistar & cb) const override; - TerrainId getId() const override; + int32_t getIndex() const override { return id.getNum(); } + int32_t getIconIndex() const override { return 0; } + const std::string & getName() const override { return code;} + const std::string & getJsonKey() const override { return code;} + void registerIcons(const IconRegistar & cb) const override {} + RoadId getId() const override { return id;} std::string fileName; std::string code; RoadId id; ui8 movementCost; - RoadType(const std::string & fileName = "", const std::string& code = "", RoadId id = Road::NO_ROAD); + RoadType(); template void serialize(Handler& h, const int version) { @@ -154,12 +154,12 @@ class DLL_LINKAGE TerrainTypeService : public EntityServiceT +class DLL_LINKAGE RiverTypeService : public EntityServiceT { public: }; -class DLL_LINKAGE RoadTypeService : public EntityServiceT +class DLL_LINKAGE RoadTypeService : public EntityServiceT { public: }; @@ -177,8 +177,7 @@ public: virtual std::vector loadLegacyData(size_t dataSize) override; virtual std::vector getDefaultAllowed() const override; - TerrainType * getInfoByCode(const std::string & identifier); - TerrainType * getInfoByName(const std::string & identifier); +// TerrainType * getInfoByCode(const std::string & identifier); template void serialize(Handler & h, const int version) { @@ -186,7 +185,7 @@ public: } }; -class DLL_LINKAGE RiverTypeHandler : public CHandlerBase +class DLL_LINKAGE RiverTypeHandler : public CHandlerBase { public: virtual RiverType * loadFromJson( @@ -199,8 +198,7 @@ public: virtual std::vector loadLegacyData(size_t dataSize) override; virtual std::vector getDefaultAllowed() const override; - RiverType * getInfoByCode(const std::string & identifier); - RiverType * getInfoByName(const std::string & identifier); +// RiverType * getInfoByCode(const std::string & identifier); template void serialize(Handler & h, const int version) { @@ -208,7 +206,7 @@ public: } }; -class DLL_LINKAGE RoadTypeHandler : public CHandlerBase +class DLL_LINKAGE RoadTypeHandler : public CHandlerBase { public: virtual RoadType * loadFromJson( @@ -221,8 +219,7 @@ public: virtual std::vector loadLegacyData(size_t dataSize) override; virtual std::vector getDefaultAllowed() const override; - RoadType * getInfoByCode(const std::string & identifier); - RoadType * getInfoByName(const std::string & identifier); +// RoadType * getInfoByCode(const std::string & identifier); template void serialize(Handler & h, const int version) { diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index d30d663df..1680e896f 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -81,13 +81,13 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f int64_t ret = GameConstants::BASE_MOVEMENT_COST; //if there is road both on dest and src tiles - use road movement cost - if(dest.roadType->id && from.roadType->id) + if(dest.roadType->id != Road::NO_ROAD && from.roadType->id != Road::NO_ROAD) { ret = std::max(dest.roadType->movementCost, from.roadType->movementCost); } else if(ti->nativeTerrain != from.terType->id &&//the terrain is not native - ti->nativeTerrain != TerrainId::ANY_TERRAIN && //no special creature bonus - !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType->id)) //no special movement bonus + ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus + !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType->id.getNum())) //no special movement bonus { ret = VLC->heroh->terrCosts[from.terType->id]; @@ -106,18 +106,18 @@ TerrainId CGHeroInstance::getNativeTerrain() const // will always have best penalty without any influence from player-defined stacks order // TODO: What should we do if all hero stacks are neutral creatures? - TerrainId nativeTerrain = TerrainId::BORDER; + TerrainId nativeTerrain = ETerrainId::BORDER; for(auto stack : stacks) { TerrainId stackNativeTerrain = stack.second->type->getNativeTerrain(); //consider terrain bonuses e.g. Lodestar. - if(stackNativeTerrain == TerrainId::BORDER) //where does this value come from? + if(stackNativeTerrain == ETerrainId::BORDER) //where does this value come from? continue; - if(nativeTerrain == TerrainId::BORDER) + if(nativeTerrain == ETerrainId::BORDER) nativeTerrain = stackNativeTerrain; else if(nativeTerrain != stackNativeTerrain) - return TerrainId::BORDER; + return ETerrainId::BORDER; } return nativeTerrain; } diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 4822058fd..c0b0a72a7 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -157,15 +157,15 @@ void ObjectTemplate::readTxt(CLegacyConfigParser & parser) // so these two fields can be interpreted as "strong affinity" and "weak affinity" towards terrains std::string & terrStr = strings[4]; // allowed terrains, 1 = object can be placed on this terrain - assert(terrStr.size() == TerrainId::ROCK); // all terrains but rock - counting from 0 - for(TerrainId i = TerrainId(0); i < TerrainId::ROCK; ++i) + assert(terrStr.size() == TerrainId(ETerrainId::ROCK).getNum()); // all terrains but rock - counting from 0 + for(TerrainId i = TerrainId(0); i < ETerrainId::ROCK; ++i) { - if (terrStr[8-i] == '1') + if (terrStr[8-i.getNum()] == '1') allowedTerrains.insert(i); } //assuming that object can be placed on other land terrains - if(allowedTerrains.size() >= 8 && !allowedTerrains.count(TerrainId::WATER)) + if(allowedTerrains.size() >= 8 && !allowedTerrains.count(ETerrainId::WATER)) { for(const auto & terrain : VLC->terrainTypeHandler->objects) { @@ -231,14 +231,14 @@ void ObjectTemplate::readMap(CBinaryReader & reader) reader.readUInt16(); ui16 terrMask = reader.readUInt16(); - for(TerrainId i = TerrainId::FIRST_REGULAR_TERRAIN; i < TerrainId::ORIGINAL_TERRAIN_COUNT; ++i) + for(TerrainId i = ETerrainId::FIRST_REGULAR_TERRAIN; i < ETerrainId::ORIGINAL_TERRAIN_COUNT; ++i) { - if (((terrMask >> i) & 1 ) != 0) + if (((terrMask >> i.getNum()) & 1 ) != 0) allowedTerrains.insert(i); } //assuming that object can be placed on other land terrains - if(allowedTerrains.size() >= 8 && !allowedTerrains.count(TerrainId::WATER)) + if(allowedTerrains.size() >= 8 && !allowedTerrains.count(ETerrainId::WATER)) { for(const auto & terrain : VLC->terrainTypeHandler->objects) { @@ -288,13 +288,9 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain) { for(auto& entry : node["allowedTerrains"].Vector()) { - try { - allowedTerrains.insert(VLC->terrainTypeHandler->getInfoByName(entry.String())->id); - } - catch (const std::out_of_range & ) - { - logGlobal->warn("Failed to find terrain '%s' for object '%s'", entry.String(), animationFile); - } + VLC->modh->identifiers.requestIdentifier("terrain", entry, [this](int32_t identifier){ + allowedTerrains.insert(TerrainId(identifier)); + }); } } else diff --git a/lib/mapping/CDrawRoadsOperation.cpp b/lib/mapping/CDrawRoadsOperation.cpp index 727037a3d..4260c7095 100644 --- a/lib/mapping/CDrawRoadsOperation.cpp +++ b/lib/mapping/CDrawRoadsOperation.cpp @@ -338,12 +338,12 @@ std::string CDrawRiversOperation::getLabel() const void CDrawRoadsOperation::executeTile(TerrainTile & tile) { - tile.roadType = const_cast(VLC->roadTypeHandler->getByIndex(roadType)); + tile.roadType = const_cast(VLC->roadTypeHandler->getByIndex(roadType.getNum())); } void CDrawRiversOperation::executeTile(TerrainTile & tile) { - tile.riverType = const_cast(VLC->riverTypeHandler->getByIndex(riverType)); + tile.riverType = const_cast(VLC->riverTypeHandler->getByIndex(riverType.getNum())); } bool CDrawRoadsOperation::canApplyPattern(const LinePattern & pattern) const diff --git a/lib/mapping/CDrawRoadsOperation.h b/lib/mapping/CDrawRoadsOperation.h index bb31868d0..8ba2e7eee 100644 --- a/lib/mapping/CDrawRoadsOperation.h +++ b/lib/mapping/CDrawRoadsOperation.h @@ -80,7 +80,7 @@ private: class CDrawRiversOperation : public CDrawLinesOperation { public: - CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, RoadId roadType, CRandomGenerator * gen); + CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, RiverId roadType, CRandomGenerator * gen); std::string getLabel() const override; protected: diff --git a/lib/mapping/CMapOperation.cpp b/lib/mapping/CMapOperation.cpp index acc0c85c8..8fd262aba 100644 --- a/lib/mapping/CMapOperation.cpp +++ b/lib/mapping/CMapOperation.cpp @@ -422,14 +422,14 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi bool nativeTestOk, nativeTestStrongOk; nativeTestOk = nativeTestStrongOk = (rule.isNativeStrong() || rule.isNativeRule()) && !isAlien; - if(centerTerType->id == TerrainId::DIRT) + if(centerTerType->id == ETerrainId::DIRT) { nativeTestOk = rule.isNativeRule() && !terType->isTransitionRequired(); bool sandTestOk = (rule.isSandRule() || rule.isTransition()) && terType->isTransitionRequired(); applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk || nativeTestStrongOk); } - else if(centerTerType->id == TerrainId::SAND) + else if(centerTerType->id == ETerrainId::SAND) { applyValidationRslt(true); } @@ -551,12 +551,12 @@ CClearTerrainOperation::CClearTerrainOperation(CMap* map, CRandomGenerator* gen) { CTerrainSelection terrainSel(map); terrainSel.selectRange(MapRect(int3(0, 0, 0), map->width, map->height)); - addOperation(std::make_unique(map, terrainSel, TerrainId::WATER, gen)); + addOperation(std::make_unique(map, terrainSel, ETerrainId::WATER, gen)); if(map->twoLevel) { terrainSel.clearSelection(); terrainSel.selectRange(MapRect(int3(0, 0, 1), map->width, map->height)); - addOperation(std::make_unique(map, terrainSel, TerrainId::ROCK, gen)); + addOperation(std::make_unique(map, terrainSel, ETerrainId::ROCK, gen)); } } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index d3984bc20..6d74531db 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -941,7 +941,7 @@ void CMapLoaderH3M::readTerrain() tile.roadType = const_cast(VLC->roadTypeHandler->getByIndex(reader.readUInt8())); tile.roadDir = reader.readUInt8(); tile.extTileFlags = reader.readUInt8(); - tile.blocked = ((!tile.terType->isPassable() || tile.terType->id == TerrainId::BORDER ) ? true : false); //underground tiles are always blocked + tile.blocked = ((!tile.terType->isPassable() || tile.terType->id == ETerrainId::BORDER ) ? true : false); //underground tiles are always blocked tile.visitable = 0; } } diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index ddb095a98..e22043f9b 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -346,6 +346,37 @@ CMapFormatJson::CMapFormatJson(): } +TerrainType * CMapFormatJson::getTerrainByCode( std::string code) +{ + for ( auto const & object : VLC->terrainTypeHandler->objects) + { + if (object->typeCode == code) + return const_cast(object.get()); + } + return nullptr; +} + +RiverType * CMapFormatJson::getRiverByCode( std::string code) +{ + for ( auto const & object : VLC->riverTypeHandler->objects) + { + if (object->code == code) + return const_cast(object.get()); + } + return nullptr; +} + +RoadType * CMapFormatJson::getRoadByCode( std::string code) +{ + for ( auto const & object : VLC->roadTypeHandler->objects) + { + if (object->code == code) + return const_cast(object.get()); + } + return nullptr; +} + + void CMapFormatJson::serializeAllowedFactions(JsonSerializeFormat & handler, std::set & value) { //TODO: unify allowed factions with others - make them std::vector @@ -949,7 +980,7 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile using namespace TerrainDetail; {//terrain type const std::string typeCode = src.substr(0, 2); - tile.terType = const_cast(VLC->terrainTypeHandler->getInfoByCode(typeCode)); + tile.terType = getTerrainByCode(typeCode); } int startPos = 2; //0+typeCode fixed length {//terrain view @@ -979,13 +1010,13 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile startPos += 2; try { - tile.roadType = const_cast(VLC->roadTypeHandler->getInfoByCode(typeCode)); + tile.roadType = getRoadByCode(typeCode); } catch (const std::exception&) //it's not a road, it's a river { try { - tile.riverType = const_cast(VLC->riverTypeHandler->getInfoByCode(typeCode)); + tile.riverType = getRiverByCode(typeCode); hasRoad = false; } catch (const std::exception&) @@ -1021,7 +1052,7 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile {//river type const std::string typeCode = src.substr(startPos, 2); startPos += 2; - tile.riverType = const_cast(VLC->riverTypeHandler->getInfoByCode(typeCode)); + tile.riverType = getRiverByCode(typeCode); } {//river dir int pos = startPos; diff --git a/lib/mapping/MapFormatJson.h b/lib/mapping/MapFormatJson.h index 149984e63..6b0ebe00f 100644 --- a/lib/mapping/MapFormatJson.h +++ b/lib/mapping/MapFormatJson.h @@ -26,6 +26,9 @@ struct TerrainTile; struct PlayerInfo; class CGObjectInstance; class AObjectTypeHandler; +class TerrainType; +class RoadType; +class RiverType; class JsonSerializeFormat; class JsonDeserializer; @@ -57,6 +60,10 @@ protected: CMapFormatJson(); + static TerrainType * getTerrainByCode( std::string code); + static RiverType * getRiverByCode( std::string code); + static RoadType * getRoadByCode( std::string code); + void serializeAllowedFactions(JsonSerializeFormat & handler, std::set & value); ///common part of header saving/loading diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 235b7a658..2a909f57e 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -16,6 +16,7 @@ #include "../mapping/CMap.h" #include "../VCMI_Lib.h" #include "../CTownHandler.h" +#include "../CModHandler.h" #include "../Terrain.h" #include "../serializer/JsonSerializeFormat.h" #include "../StringConstants.h" @@ -69,13 +70,12 @@ class TerrainEncoder public: static si32 decode(const std::string & identifier) { - return VLC->terrainTypeHandler->getInfoByCode(identifier)->id; + return *VLC->modh->identifiers.getIdentifier(VLC->modh->scopeGame(), "terrain", identifier); } static std::string encode(const si32 index) { - const auto& terrains = VLC->terrainTypeHandler->objects; - return (index >=0 && index < terrains.size()) ? terrains[index]->name : ""; + return VLC->terrainTypeHandler->getByIndex(index)->name; } }; @@ -377,7 +377,8 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) terrainTypes.clear(); for(auto ttype : node.Vector()) { - terrainTypes.emplace(VLC->terrainTypeHandler->getInfoByName(ttype.String())->id); + auto identifier = VLC->modh->identifiers.getIdentifier("terrain", ttype); + terrainTypes.emplace(*identifier); } } } diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index b09f83a9f..d79319deb 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -194,7 +194,7 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const else { auto & tt = (*VLC->townh)[faction]->nativeTerrain; - if(tt == TerrainId::DIRT) + if(tt == ETerrainId::DIRT) { //any / random zonesToPlace.push_back(zone); @@ -580,7 +580,7 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) //make sure that terrain inside zone is not a rock //FIXME: reorder actions? - paintZoneTerrain(*zone.second, *rand, map, TerrainId::SUBTERRANEAN); + paintZoneTerrain(*zone.second, *rand, map, ETerrainId::SUBTERRANEAN); } } logGlobal->info("Finished zone colouring"); diff --git a/lib/rmg/ConnectionsPlacer.cpp b/lib/rmg/ConnectionsPlacer.cpp index 4bf47a7ef..862fd0d60 100644 --- a/lib/rmg/ConnectionsPlacer.cpp +++ b/lib/rmg/ConnectionsPlacer.cpp @@ -84,9 +84,8 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con //1. Try to make direct connection //Do if it's not prohibited by terrain settings - const auto& terrains = VLC->terrainTypeHandler->objects; - bool directProhibited = vstd::contains(terrains[zone.getTerrainType()]->prohibitTransitions, otherZone->getTerrainType()) - || vstd::contains(terrains[otherZone->getTerrainType()]->prohibitTransitions, zone.getTerrainType()); + bool directProhibited = vstd::contains(VLC->terrainTypeHandler->getById(zone.getTerrainType())->prohibitTransitions, otherZone->getTerrainType()) + || vstd::contains(VLC->terrainTypeHandler->getById(otherZone->getTerrainType())->prohibitTransitions, zone.getTerrainType()); auto directConnectionIterator = dNeighbourZones.find(otherZoneId); if(!directProhibited && directConnectionIterator != dNeighbourZones.end()) { diff --git a/lib/rmg/Functions.cpp b/lib/rmg/Functions.cpp index 3b12e3668..9bce3ec0d 100644 --- a/lib/rmg/Functions.cpp +++ b/lib/rmg/Functions.cpp @@ -143,14 +143,14 @@ void initTerrainType(Zone & zone, CMapGenerator & gen) { if(!terrainType->isUnderground()) { - zone.setTerrainType(TerrainId::SUBTERRANEAN); + zone.setTerrainType(ETerrainId::SUBTERRANEAN); } } else { if (!terrainType->isSurface()) { - zone.setTerrainType(TerrainId::DIRT); + zone.setTerrainType(ETerrainId::DIRT); } } } diff --git a/lib/rmg/RiverPlacer.cpp b/lib/rmg/RiverPlacer.cpp index 3d5826e5e..7d52941e6 100644 --- a/lib/rmg/RiverPlacer.cpp +++ b/lib/rmg/RiverPlacer.cpp @@ -322,7 +322,7 @@ void RiverPlacer::preprocess() void RiverPlacer::connectRiver(const int3 & tile) { auto riverType = VLC->terrainTypeHandler->getById(zone.getTerrainType())->river; - const auto * river = VLC->riverTypeHandler->getByIndex(riverType); + const auto * river = VLC->riverTypeHandler->getById(riverType); if(river->id == River::NO_RIVER) return; diff --git a/lib/rmg/RmgMap.cpp b/lib/rmg/RmgMap.cpp index 834a8160a..f1a2a8ea8 100644 --- a/lib/rmg/RmgMap.cpp +++ b/lib/rmg/RmgMap.cpp @@ -84,7 +84,7 @@ void RmgMap::initTiles(CMapGenerator & generator) getEditManager()->clearTerrain(&generator.rand); getEditManager()->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions.getWidth(), mapGenOptions.getHeight())); - getEditManager()->drawTerrain(TerrainId::GRASS, &generator.rand); + getEditManager()->drawTerrain(ETerrainId::GRASS, &generator.rand); auto tmpl = mapGenOptions.getMapTemplate(); zones.clear(); diff --git a/lib/rmg/RoadPlacer.cpp b/lib/rmg/RoadPlacer.cpp index 385f51d2c..e39719f25 100644 --- a/lib/rmg/RoadPlacer.cpp +++ b/lib/rmg/RoadPlacer.cpp @@ -16,6 +16,7 @@ #include "RmgMap.h" #include "../mapping/CMap.h" #include "../mapping/CMapEditManager.h" +#include "../CModHandler.h" #include "RmgPath.h" VCMI_LIB_NAMESPACE_BEGIN @@ -80,8 +81,7 @@ void RoadPlacer::drawRoads(bool secondary) map.getEditManager()->getTerrainSelection().setSelection(roads.getTilesVector()); std::string roadName = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType); - RoadId roadType = VLC->roadTypeHandler->getInfoByName(roadName)->id; - + RoadId roadType(*VLC->modh->identifiers.getIdentifier(VLC->modh->scopeGame(), "road", roadName)); map.getEditManager()->drawRoad(roadType, &generator.rand); } diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index 8c6f500b7..244483d5b 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -28,7 +28,7 @@ std::function AREA_NO_FILTER = [](const int3 & t) Zone::Zone(RmgMap & map, CMapGenerator & generator) : ZoneOptions(), townType(ETownType::NEUTRAL), - terrainType(TerrainId::GRASS), + terrainType(ETerrainId::GRASS), map(map), generator(generator) { diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 3396c801c..9a0da79f4 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -21,6 +21,7 @@ #include "../lib/VCMI_Lib.h" #include "../lib/logging/CBasicLogConfigurator.h" #include "../lib/CConfigHandler.h" +#include "../lib/CModHandler.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/GameConstants.h" #include "../lib/mapping/CMapService.h" @@ -558,7 +559,7 @@ void MainWindow::loadObjectsTree() { QPushButton *b = new QPushButton(QString::fromStdString(road->fileName)); ui->roadLayout->addWidget(b); - connect(b, &QPushButton::clicked, this, [this, road]{ roadOrRiverButtonClicked(road->id, true); }); + connect(b, &QPushButton::clicked, this, [this, road]{ roadOrRiverButtonClicked(road->id.getNum(), true); }); } //add spacer to keep terrain button on the top ui->roadLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); @@ -567,7 +568,7 @@ void MainWindow::loadObjectsTree() { QPushButton *b = new QPushButton(QString::fromStdString(river->fileName)); ui->riverLayout->addWidget(b); - connect(b, &QPushButton::clicked, this, [this, river]{ roadOrRiverButtonClicked(river->id, false); }); + connect(b, &QPushButton::clicked, this, [this, river]{ roadOrRiverButtonClicked(river->id.getNum(), false); }); } //add spacer to keep terrain button on the top ui->riverLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); @@ -914,7 +915,13 @@ void MainWindow::on_terrainFilterCombo_currentTextChanged(const QString &arg1) if(!objectBrowser) return; - objectBrowser->terrain = arg1.isEmpty() ? TerrainId(TerrainId::ANY_TERRAIN) : VLC->terrainTypeHandler->getInfoByName(arg1.toStdString())->id; + objectBrowser->terrain = TerrainId(ETerrainId::ANY_TERRAIN); + if (!arg1.isEmpty()) + { + for (auto const & terrain : VLC->terrainTypeHandler->objects) + if (terrain->name == arg1.toStdString()) + objectBrowser->terrain = terrain->id; + } objectBrowser->invalidate(); objectBrowser->sort(0); } diff --git a/mapeditor/objectbrowser.cpp b/mapeditor/objectbrowser.cpp index e8f8ac826..68d4addf1 100644 --- a/mapeditor/objectbrowser.cpp +++ b/mapeditor/objectbrowser.cpp @@ -13,7 +13,7 @@ #include "../lib/mapObjects/CObjectClassesHandler.h" ObjectBrowserProxyModel::ObjectBrowserProxyModel(QObject *parent) - : QSortFilterProxyModel{parent}, terrain(TerrainId::ANY_TERRAIN) + : QSortFilterProxyModel{parent}, terrain(ETerrainId::ANY_TERRAIN) { } @@ -33,7 +33,7 @@ bool ObjectBrowserProxyModel::filterAcceptsRow(int source_row, const QModelIndex if(!filterAcceptsRowText(source_row, source_parent)) return false; - if(terrain == TerrainId::ANY_TERRAIN) + if(terrain == ETerrainId::ANY_TERRAIN) return result; auto data = item->data().toJsonObject(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index a8dca3798..3ce07f687 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2244,7 +2244,7 @@ void CGameHandler::setupBattle(int3 tile, const CArmedInstance *armies[2], const const auto & t = *getTile(tile); TerrainId terrain = t.terType->id; if (gs->map->isCoastalTile(tile)) //coastal tile is always ground - terrain = TerrainId::SAND; + terrain = ETerrainId::SAND; BattleField terType = gs->battleGetBattlefieldType(tile, getRandomGenerator()); if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat) From 2de31781587176fc34123b3cc52241c0ed89637e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 20 Dec 2022 20:26:54 +0200 Subject: [PATCH 058/197] Fixed game startup --- config/defaultMods.json | 3 +++ config/rivers.json | 10 ++++----- config/roads.json | 8 +++---- config/terrains.json | 6 +++--- lib/GameConstants.h | 2 +- lib/Terrain.cpp | 19 ++++++++++++++--- lib/mapObjects/ObjectTemplate.cpp | 35 ++++++++++--------------------- lib/mapObjects/ObjectTemplate.h | 3 +++ lib/rmg/CRmgTemplate.cpp | 6 ++++-- 9 files changed, 50 insertions(+), 42 deletions(-) diff --git a/config/defaultMods.json b/config/defaultMods.json index 7b2e37197..bfced4e84 100644 --- a/config/defaultMods.json +++ b/config/defaultMods.json @@ -10,6 +10,9 @@ "hero" : 156, "spell" : 81, "object" : 256, + "terrain" : 10, + "river" : 5, + "road" : 4, "mapVersion" : 28 // max supported version, SoD }, diff --git a/config/rivers.json b/config/rivers.json index 1ff5e32c2..60008631f 100644 --- a/config/rivers.json +++ b/config/rivers.json @@ -1,30 +1,30 @@ { "waterRiver": { - "originalRiverId": 1, + "index": 1, "code": "rw", //must be 2 characters "animation": "clrrvr", "delta": "clrdelt" }, "iceRiver": { - "originalRiverId": 2, + "index": 2, "code": "ri", "animation": "icyrvr", "delta": "icedelt" }, "mudRiver": { - "originalRiverId": 3, + "index": 3, "code": "rm", "animation": "mudrvr", "delta": "muddelt" }, "lavaRiver": { - "originalRiverId": 4, + "index": 4, "code": "rl", "animation": "lavrvr", "delta": "lavdelt" } -} \ No newline at end of file +} diff --git a/config/roads.json b/config/roads.json index 48832d2e0..7b585ca8a 100644 --- a/config/roads.json +++ b/config/roads.json @@ -1,23 +1,23 @@ { "dirtRoad": { - "originalRoadId": 1, + "index": 1, "code": "pd", //must be 2 characters "animation": "dirtrd", "moveCost": 75 }, "gravelRoad": { - "originalRoadId": 2, + "index": 2, "code": "pg", "animation": "gravrd", "moveCost": 65 }, "cobblestoneRoad": { - "originalRoadId": 3, + "index": 3, "code": "pc", "animation": "cobbrd", "moveCost": 50 } -} \ No newline at end of file +} diff --git a/config/terrains.json b/config/terrains.json index 7968cfcac..14a929e52 100644 --- a/config/terrains.json +++ b/config/terrains.json @@ -88,7 +88,7 @@ "minimapBlocked" : [ 90, 8, 0 ], "music" : "Underground.mp3", "tiles" : "SUBBTL", - "type" : "SUB", + "type" : [ "SUB" ], "code" : "sb", "river" : "rw", "battleFields" : ["subterranean"], @@ -118,7 +118,7 @@ "minimapBlocked" : [ 8, 81, 148 ], "music" : "Water.mp3", "tiles" : "WATRTL", - "type" : "WATER", + "type" : [ "WATER" ], "code" : "wt", "battleFields" : ["ship"], "transitionRequired" : true, @@ -136,7 +136,7 @@ "minimapBlocked" : [ 0, 0, 0 ], "music" : "Underground.mp3", // Impossible in H3 "tiles" : "ROCKTL", - "type" : "ROCK", + "type" : [ "ROCK" ], "code" : "rc", "battleFields" : ["rocklands"], "transitionRequired" : true, diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 663c21cf1..2aab386ce 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -1226,7 +1226,7 @@ enum class ETerrainId { WRONG = -2, BORDER = -1, FIRST_REGULAR_TERRAIN = 0, - DIRT, + DIRT = 0, SAND, GRASS, SNOW, diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index fa8608a5e..3798c31fa 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -12,6 +12,7 @@ #include "Terrain.h" #include "VCMI_Lib.h" #include "CModHandler.h" +#include "CGeneralTextHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -99,8 +100,7 @@ TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const if(!json["rockTerrain"].isNull()) { - auto rockTerrainName = json["rockTerrain"].String(); - VLC->modh->identifiers.requestIdentifier("terrain", rockTerrainName, [info](int32_t identifier) + VLC->modh->identifiers.requestIdentifier("terrain", json["rockTerrain"], [info](int32_t identifier) { info->rockTerrain = TerrainId(identifier); }); @@ -117,7 +117,20 @@ const std::vector & TerrainTypeHandler::getTypeNames() const std::vector TerrainTypeHandler::loadLegacyData(size_t dataSize) { - return {}; + objects.resize(dataSize); + + CLegacyConfigParser terrainParser("DATA/TERRNAME.TXT"); + + std::vector result; + do + { + JsonNode terrain; + terrain["text"].String() = terrainParser.readString(); + result.push_back(terrain); + } + while (terrainParser.endLine()); + + return result; } std::vector TerrainTypeHandler::getDefaultAllowed() const diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index c0b0a72a7..cac2e1f2b 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -165,14 +165,7 @@ void ObjectTemplate::readTxt(CLegacyConfigParser & parser) } //assuming that object can be placed on other land terrains - if(allowedTerrains.size() >= 8 && !allowedTerrains.count(ETerrainId::WATER)) - { - for(const auto & terrain : VLC->terrainTypeHandler->objects) - { - if(terrain->isLand() && terrain->isPassable()) - allowedTerrains.insert(terrain->id); - } - } + anyTerrain = allowedTerrains.size() >= 8 && !allowedTerrains.count(ETerrainId::WATER); id = Obj(boost::lexical_cast(strings[5])); subid = boost::lexical_cast(strings[6]); @@ -238,14 +231,7 @@ void ObjectTemplate::readMap(CBinaryReader & reader) } //assuming that object can be placed on other land terrains - if(allowedTerrains.size() >= 8 && !allowedTerrains.count(ETerrainId::WATER)) - { - for(const auto & terrain : VLC->terrainTypeHandler->objects) - { - if(terrain->isLand() && terrain->isPassable()) - allowedTerrains.insert(terrain->id); - } - } + anyTerrain = allowedTerrains.size() >= 8 && !allowedTerrains.count(ETerrainId::WATER); id = Obj(reader.readUInt32()); subid = reader.readUInt32(); @@ -292,15 +278,11 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain) allowedTerrains.insert(TerrainId(identifier)); }); } + anyTerrain = false; } else { - for(const auto & terrain : VLC->terrainTypeHandler->objects) - { - if(!terrain->isPassable() || terrain->isWater()) - continue; - allowedTerrains.insert(terrain->id); - } + anyTerrain = true; } if(withTerrain && allowedTerrains.empty()) @@ -561,9 +543,14 @@ void ObjectTemplate::calculateVisitableOffset() visitableOffset = int3(0, 0, 0); } -bool ObjectTemplate::canBePlacedAt(TerrainId terrain) const +bool ObjectTemplate::canBePlacedAt(TerrainId terrainID) const { - return vstd::contains(allowedTerrains, terrain); + if (anyTerrain) + { + auto const & terrain = VLC->terrainTypeHandler->getById(terrainID); + return terrain->isLand() && terrain->isPassable(); + } + return vstd::contains(allowedTerrains, terrainID); } void ObjectTemplate::recalculate() diff --git a/lib/mapObjects/ObjectTemplate.h b/lib/mapObjects/ObjectTemplate.h index b492d1770..4f4affa67 100644 --- a/lib/mapObjects/ObjectTemplate.h +++ b/lib/mapObjects/ObjectTemplate.h @@ -35,6 +35,9 @@ class DLL_LINKAGE ObjectTemplate /// list of terrains on which this object can be placed std::set allowedTerrains; + /// or, allow placing object on any terrain + bool anyTerrain; + void afterLoadFixup(); public: diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 2a909f57e..85a4def3c 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -377,8 +377,10 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) terrainTypes.clear(); for(auto ttype : node.Vector()) { - auto identifier = VLC->modh->identifiers.getIdentifier("terrain", ttype); - terrainTypes.emplace(*identifier); + VLC->modh->identifiers.requestIdentifier("terrain", ttype, [this](int32_t identifier) + { + terrainTypes.emplace(identifier); + }); } } } From 99745b5c3cf845e224f00fac6129f791cb2c499c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 20 Dec 2022 20:52:52 +0200 Subject: [PATCH 059/197] Fix rivers & roads loading --- config/gameConfig.json | 8 ++++++++ config/terrains.json | 16 ++++++++-------- lib/CModHandler.cpp | 2 +- lib/Terrain.cpp | 17 +++++++++++++---- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/config/gameConfig.json b/config/gameConfig.json index a8e5bc71b..edd8b7231 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -85,6 +85,14 @@ [ "config/terrains.json" ], + "roads": + [ + "config/roads.json" + ], + "rivers": + [ + "config/rivers.json" + ], "battlefields": [ "config/battlefields.json" diff --git a/config/terrains.json b/config/terrains.json index 14a929e52..f999ec20c 100644 --- a/config/terrains.json +++ b/config/terrains.json @@ -8,7 +8,7 @@ "music" : "Dirt.mp3", "tiles" : "DIRTTL", "code" : "dt", - "river" : "rm", + "river" : "mudRiver", "battleFields" : ["dirt_birches", "dirt_hills", "dirt_pines"], "terrainViewPatterns" : "dirt", "horseSoundId" : 0 @@ -22,7 +22,7 @@ "music" : "Sand.mp3", "tiles" : "SANDTL", "code" : "sa", - "river" : "rm", + "river" : "mudRiver", "battleFields" : ["sand_mesas"], "transitionRequired" : true, "terrainViewPatterns" : "sand", @@ -37,7 +37,7 @@ "music" : "Grass.mp3", "tiles" : "GRASTL", "code" : "gr", - "river" : "rw", + "river" : "waterRiver", "battleFields" : ["grass_hills", "grass_pines"], "horseSoundId" : 2 }, @@ -50,7 +50,7 @@ "music" : "Snow.mp3", "tiles" : "SNOWTL", "code" : "sn", - "river" : "ri", + "river" : "iceRiver", "battleFields" : ["snow_mountains", "snow_trees"], "horseSoundId" : 3 }, @@ -63,7 +63,7 @@ "music" : "Swamp.mp3", "tiles" : "SWMPTL", "code" : "sw", - "river" : "rw", + "river" : "waterRiver", "battleFields" : ["swamp_trees"], "horseSoundId" : 4 }, @@ -76,7 +76,7 @@ "music" : "Rough.mp3", "tiles" : "ROUGTL", "code" : "rg", - "river" : "rm", + "river" : "mudRiver", "battleFields" : ["rough"], "horseSoundId" : 5 }, @@ -90,7 +90,7 @@ "tiles" : "SUBBTL", "type" : [ "SUB" ], "code" : "sb", - "river" : "rw", + "river" : "waterRiver", "battleFields" : ["subterranean"], "rockTerrain" : "rock", "horseSoundId" : 6 @@ -105,7 +105,7 @@ "tiles" : "LAVATL", "type" : ["SUB", "SURFACE"], "code" : "lv", - "river" : "rl", + "river" : "lavaRiver", "battleFields" : ["lava"], "rockTerrain" : "rock", "horseSoundId" : 7 diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 512df3629..64ab15bd5 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -526,7 +526,7 @@ void CContentHandler::preloadData(CModInfo & mod) void CContentHandler::load(CModInfo & mod) { - bool validate = (mod.validation != CModInfo::PASSED); + bool validate = false;//(mod.validation != CModInfo::PASSED); if (!loadMod(mod.identifier, validate)) mod.validation = CModInfo::FAILED; diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index 3798c31fa..86788da4b 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -63,6 +63,8 @@ TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const ui8(blockedVec[2].Float()) }; + info->passabilityType = 0; + for(const auto& node : json["type"].Vector()) { //Set bits @@ -74,10 +76,15 @@ TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const if (s == "SUB") info->passabilityType |= TerrainType::PassabilityType::SUBTERRANEAN; } -// if(json["river"].isNull()) -// info->river = River::NO_RIVER; -// else -// info->river = getRiverByCode(json["river"].String())->id; + info->river = River::NO_RIVER; + if(!json["river"].isNull()) + { + VLC->modh->identifiers.requestIdentifier("river", json["river"], [info](int32_t identifier) + { + info->river = RiverId(identifier); + }); + + } info->typeCode = json["code"].String(); assert(info->typeCode.length() == 2); @@ -161,6 +168,7 @@ const std::vector & RiverTypeHandler::getTypeNames() const std::vector RiverTypeHandler::loadLegacyData(size_t dataSize) { + objects.resize(dataSize); return {}; } @@ -192,6 +200,7 @@ const std::vector & RoadTypeHandler::getTypeNames() const std::vector RoadTypeHandler::loadLegacyData(size_t dataSize) { + objects.resize(dataSize); return {}; } From 4e4dae854f9e680cc3ffc0364c363478e9a727ac Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 21 Dec 2022 00:04:39 +0200 Subject: [PATCH 060/197] Final stabilization changes --- lib/GameConstants.h | 7 +++--- lib/Terrain.cpp | 40 ++++++++++++++----------------- lib/Terrain.h | 18 ++++---------- lib/mapObjects/ObjectTemplate.cpp | 3 --- 4 files changed, 26 insertions(+), 42 deletions(-) diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 2aab386ce..9c2b6d644 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -198,13 +198,12 @@ public: num += change; } - typedef BaseForID __SelfType; bool operator == (const BaseForID & b) const { return num == b.num; } - bool operator <= (const BaseForID & b) const { return num >= b.num; } - bool operator >= (const BaseForID & b) const { return num <= b.num; } + bool operator <= (const BaseForID & b) const { return num <= b.num; } + bool operator >= (const BaseForID & b) const { return num >= b.num; } bool operator != (const BaseForID & b) const { return num != b.num; } bool operator < (const BaseForID & b) const { return num < b.num; } - bool operator > (const BaseForID & b) const { return num > b.num; } + bool operator > (const BaseForID & b) const { return num > b.num; } BaseForID & operator++() { ++num; return *this; } }; diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index 86788da4b..2c2257bd2 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -38,6 +38,7 @@ TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const TerrainType * info = new TerrainType; info->id = TerrainId(index); + info->name = identifier; info->moveCost = static_cast(json["moveCost"].Integer()); info->musicFilename = json["music"].String(); @@ -145,6 +146,11 @@ std::vector TerrainTypeHandler::getDefaultAllowed() const return {}; } +RiverTypeHandler::RiverTypeHandler() +{ + objects.push_back(new RiverType); +} + RiverType * RiverTypeHandler::loadFromJson( const std::string & scope, const JsonNode & json, @@ -153,6 +159,7 @@ RiverType * RiverTypeHandler::loadFromJson( { RiverType * info = new RiverType; + info->id = RiverId(index); info->fileName = json["animation"].String(); info->code = json["code"].String(); info->deltaName = json["delta"].String(); @@ -177,6 +184,11 @@ std::vector RiverTypeHandler::getDefaultAllowed() const return {}; } +RoadTypeHandler::RoadTypeHandler() +{ + objects.push_back(new RoadType); +} + RoadType * RoadTypeHandler::loadFromJson( const std::string & scope, const JsonNode & json, @@ -185,6 +197,7 @@ RoadType * RoadTypeHandler::loadFromJson( { RoadType * info = new RoadType; + info->id = RoadId(index); info->fileName = json["animation"].String(); info->code = json["code"].String(); info->movementCost = json["moveCost"].Integer(); @@ -209,26 +222,6 @@ std::vector RoadTypeHandler::getDefaultAllowed() const return {}; } -TerrainType::operator std::string() const -{ - return name; -} - -bool TerrainType::operator==(const TerrainType& other) -{ - return id == other.id; -} - -bool TerrainType::operator!=(const TerrainType& other) -{ - return id != other.id; -} - -bool TerrainType::operator<(const TerrainType& other) -{ - return id < other.id; -} - bool TerrainType::isLand() const { return !isWater(); @@ -272,9 +265,12 @@ bool TerrainType::isTransitionRequired() const TerrainType::TerrainType() {} -RiverType::RiverType() +RiverType::RiverType(): + id(River::NO_RIVER) {} -RoadType::RoadType() +RoadType::RoadType(): + id(Road::NO_ROAD), + movementCost(GameConstants::BASE_MOVEMENT_COST) {} VCMI_LIB_NAMESPACE_END diff --git a/lib/Terrain.h b/lib/Terrain.h index d102ff7ab..7c10af4e5 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -59,10 +59,6 @@ public: TerrainType(); - bool operator==(const TerrainType & other); - bool operator!=(const TerrainType & other); - bool operator<(const TerrainType & other); - bool isLand() const; bool isWater() const; bool isPassable() const; @@ -71,9 +67,7 @@ public: bool isTransitionRequired() const; bool isSurfaceCartographerCompatible() const; bool isUndergroundCartographerCompatible() const; - - operator std::string() const; - + template void serialize(Handler &h, const int version) { h & battleFields; @@ -177,8 +171,6 @@ public: virtual std::vector loadLegacyData(size_t dataSize) override; virtual std::vector getDefaultAllowed() const override; -// TerrainType * getInfoByCode(const std::string & identifier); - template void serialize(Handler & h, const int version) { h & objects; @@ -194,12 +186,12 @@ public: const std::string & identifier, size_t index) override; + RiverTypeHandler(); + virtual const std::vector & getTypeNames() const override; virtual std::vector loadLegacyData(size_t dataSize) override; virtual std::vector getDefaultAllowed() const override; -// RiverType * getInfoByCode(const std::string & identifier); - template void serialize(Handler & h, const int version) { h & objects; @@ -215,12 +207,12 @@ public: const std::string & identifier, size_t index) override; + RoadTypeHandler(); + virtual const std::vector & getTypeNames() const override; virtual std::vector loadLegacyData(size_t dataSize) override; virtual std::vector getDefaultAllowed() const override; -// RoadType * getInfoByCode(const std::string & identifier); - template void serialize(Handler & h, const int version) { h & objects; diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index cac2e1f2b..3692a237a 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -285,9 +285,6 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain) anyTerrain = true; } - if(withTerrain && allowedTerrains.empty()) - logGlobal->warn("Loaded template %s without allowed terrains!", animationFile); - auto charToTile = [&](const char & ch) -> ui8 { switch (ch) From 64885bdf6b82301b0126630903fa78374608b6e2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 21 Dec 2022 00:45:35 +0200 Subject: [PATCH 061/197] Better names for terrain parameters. Support for new movement sounds. --- client/CMusicHandler.cpp | 31 +----------------- client/CMusicHandler.h | 2 -- client/CPlayerInterface.cpp | 7 ++-- client/mapHandler.cpp | 16 ++++----- client/windows/CAdvmapInterface.cpp | 2 +- config/rivers.json | 16 ++++----- config/roads.json | 12 +++---- config/terrains.json | 40 +++++++++++------------ lib/CGameState.cpp | 3 +- lib/HeroBonus.cpp | 4 +-- lib/Terrain.cpp | 41 ++++++++++++----------- lib/Terrain.h | 50 +++++++++++++++-------------- lib/mapObjects/CObjectHandler.cpp | 2 +- lib/mapObjects/ObjectTemplate.cpp | 2 +- lib/mapping/MapEditUtils.cpp | 2 +- lib/mapping/MapFormatJson.cpp | 8 ++--- lib/rmg/CRmgTemplate.cpp | 4 +-- lib/rmg/RiverPlacer.cpp | 2 +- lib/rmg/RmgObject.cpp | 2 +- mapeditor/mainwindow.cpp | 10 +++--- mapeditor/maphandler.cpp | 15 ++++----- 21 files changed, 121 insertions(+), 150 deletions(-) diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index c604eb6b0..a53ae3952 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -89,35 +89,6 @@ CSoundHandler::CSoundHandler(): soundBase::battle02, soundBase::battle03, soundBase::battle04, soundBase::battle05, soundBase::battle06, soundBase::battle07 }; - - //predefine terrain set - //TODO: support custom sounds for new terrains and load from json - horseSounds = - { - {ETerrainId::DIRT, soundBase::horseDirt}, - {ETerrainId::SAND, soundBase::horseSand}, - {ETerrainId::GRASS, soundBase::horseGrass}, - {ETerrainId::SNOW, soundBase::horseSnow}, - {ETerrainId::SWAMP, soundBase::horseSwamp}, - {ETerrainId::ROUGH, soundBase::horseRough}, - {ETerrainId::SUBTERRANEAN, soundBase::horseSubterranean}, - {ETerrainId::LAVA, soundBase::horseLava}, - {ETerrainId::WATER, soundBase::horseWater}, - {ETerrainId::ROCK, soundBase::horseRock} - }; -} - -void CSoundHandler::loadHorseSounds() -{ - for(const auto & terrain : CGI->terrainTypeHandler->objects) - { - //since all sounds are hardcoded, let's keep it - if(vstd::contains(horseSounds, terrain->id)) - continue; - - //Use already existing horse sound - horseSounds[terrain->id] = horseSounds.at(static_cast(CGI->terrainTypeHandler->getById(terrain->id)->horseSoundId)); - } } void CSoundHandler::init() @@ -369,7 +340,7 @@ void CMusicHandler::loadTerrainMusicThemes() { for (const auto & terrain : CGI->terrainTypeHandler->objects) { - addEntryToSet("terrain_" + terrain->name, "Music/" + terrain->musicFilename); + addEntryToSet("terrain_" + terrain->identifier, "Music/" + terrain->musicFilename); } } diff --git a/client/CMusicHandler.h b/client/CMusicHandler.h index f465a471d..6c9977b4c 100644 --- a/client/CMusicHandler.h +++ b/client/CMusicHandler.h @@ -61,7 +61,6 @@ public: CSoundHandler(); void init() override; - void loadHorseSounds(); void release() override; void setVolume(ui32 percent) override; @@ -84,7 +83,6 @@ public: // Sets std::vector pickupSounds; std::vector battleIntroSounds; - std::map horseSounds; }; // Helper //now it looks somewhat useless diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 96a9ccc7c..49a76f3a2 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -156,7 +156,6 @@ void CPlayerInterface::initGameInterface(std::shared_ptr ENV, std:: cb = CB; env = ENV; - CCS->soundh->loadHorseSounds(); CCS->musich->loadTerrainMusicThemes(); initializeHeroTownList(); @@ -260,7 +259,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose) { updateAmbientSounds(); //We may need to change music - select new track, music handler will change it if needed - CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->name, true, false); + CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->identifier, true, false); if(details.result == TryMoveHero::TELEPORTATION) { @@ -2410,7 +2409,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) } if(i != path.nodes.size() - 1) { - sh = CCS->soundh->playSound(CCS->soundh->horseSounds[currentTerrain], -1); + sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(currentTerrain)->horseSound, -1); } continue; } @@ -2432,7 +2431,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) if(newTerrain != currentTerrain) { CCS->soundh->stopSound(sh); - sh = CCS->soundh->playSound(CCS->soundh->horseSounds[newTerrain], -1); + sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(newTerrain)->horseSound, -1); currentTerrain = newTerrain; } } diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 4015f43be..56017b657 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -177,15 +177,15 @@ void CMapHandler::initTerrainGraphics() std::map roadFiles; for(const auto & terrain : VLC->terrainTypeHandler->objects) { - terrainFiles[terrain->name] = terrain->tilesFilename; + terrainFiles[terrain->identifier] = terrain->tilesFilename; } for(const auto & river : VLC->riverTypeHandler->objects) { - riverFiles[river->fileName] = river->fileName; + riverFiles[river->tilesFilename] = river->tilesFilename; } for(const auto & road : VLC->roadTypeHandler->objects) { - roadFiles[road->fileName] = road->fileName; + roadFiles[road->tilesFilename] = road->tilesFilename; } loadFlipped(terrainAnimations, terrainImages, terrainFiles); @@ -606,7 +606,7 @@ void CMapHandler::CMapBlitter::drawTileTerrain(SDL_Surface * targetSurf, const T ui8 rotation = tinfo.extTileFlags % 4; //TODO: use ui8 instead of string key - auto terrainName = tinfo.terType->name; + auto terrainName = tinfo.terType->identifier; if(parent->terrainImages[terrainName].size()<=tinfo.terView) return; @@ -791,7 +791,7 @@ void CMapHandler::CMapBlitter::drawRoad(SDL_Surface * targetSurf, const TerrainT ui8 rotation = (tinfoUpper->extTileFlags >> 4) % 4; Rect source(0, tileSize / 2, tileSize, tileSize / 2); Rect dest(realPos.x, realPos.y, tileSize, tileSize / 2); - drawElement(EMapCacheType::ROADS, parent->roadImages[tinfoUpper->roadType->fileName][tinfoUpper->roadDir][rotation], + drawElement(EMapCacheType::ROADS, parent->roadImages[tinfoUpper->roadType->tilesFilename][tinfoUpper->roadDir][rotation], &source, targetSurf, &dest); } @@ -800,7 +800,7 @@ void CMapHandler::CMapBlitter::drawRoad(SDL_Surface * targetSurf, const TerrainT ui8 rotation = (tinfo.extTileFlags >> 4) % 4; Rect source(0, 0, tileSize, halfTileSizeCeil); Rect dest(realPos.x, realPos.y + tileSize / 2, tileSize, tileSize / 2); - drawElement(EMapCacheType::ROADS, parent->roadImages[tinfo.roadType->fileName][tinfo.roadDir][rotation], + drawElement(EMapCacheType::ROADS, parent->roadImages[tinfo.roadType->tilesFilename][tinfo.roadDir][rotation], &source, targetSurf, &dest); } } @@ -809,7 +809,7 @@ void CMapHandler::CMapBlitter::drawRiver(SDL_Surface * targetSurf, const Terrain { Rect destRect(realTileRect); ui8 rotation = (tinfo.extTileFlags >> 2) % 4; - drawElement(EMapCacheType::RIVERS, parent->riverImages[tinfo.riverType->fileName][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect); + drawElement(EMapCacheType::RIVERS, parent->riverImages[tinfo.riverType->tilesFilename][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect); } void CMapHandler::CMapBlitter::drawFow(SDL_Surface * targetSurf) const @@ -1390,7 +1390,7 @@ void CMapHandler::getTerrainDescr(const int3 & pos, std::string & out, bool isRM } if(!isTile2Terrain || out.empty()) - out = VLC->terrainTypeHandler->getById(t.terType->id)->terrainText; + out = VLC->terrainTypeHandler->getById(t.terType->id)->nameTranslated; if(t.getDiggingStatus(false) == EDiggingStatus::CAN_DIG) { diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index d41b01b4a..470760cd3 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -1414,7 +1414,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView) auto pos = sel->visitablePos(); auto tile = LOCPLINT->cb->getTile(pos); if(tile) - CCS->musich->playMusicFromSet("terrain", tile->terType->name, true, false); + CCS->musich->playMusicFromSet("terrain", tile->terType->identifier, true, false); } if(centerView) centerOn(sel); diff --git a/config/rivers.json b/config/rivers.json index 60008631f..666cf53cf 100644 --- a/config/rivers.json +++ b/config/rivers.json @@ -2,29 +2,29 @@ "waterRiver": { "index": 1, - "code": "rw", //must be 2 characters - "animation": "clrrvr", + "shortIdentifier": "rw", //must be 2 characters + "tilesFilename": "clrrvr", "delta": "clrdelt" }, "iceRiver": { "index": 2, - "code": "ri", - "animation": "icyrvr", + "shortIdentifier": "ri", + "tilesFilename": "icyrvr", "delta": "icedelt" }, "mudRiver": { "index": 3, - "code": "rm", - "animation": "mudrvr", + "shortIdentifier": "rm", + "tilesFilename": "mudrvr", "delta": "muddelt" }, "lavaRiver": { "index": 4, - "code": "rl", - "animation": "lavrvr", + "shortIdentifier": "rl", + "tilesFilename": "lavrvr", "delta": "lavdelt" } } diff --git a/config/roads.json b/config/roads.json index 7b585ca8a..76f5f4d9d 100644 --- a/config/roads.json +++ b/config/roads.json @@ -2,22 +2,22 @@ "dirtRoad": { "index": 1, - "code": "pd", //must be 2 characters - "animation": "dirtrd", + "shortIdentifier": "pd", //must be 2 characters + "tilesFilename": "dirtrd", "moveCost": 75 }, "gravelRoad": { "index": 2, - "code": "pg", - "animation": "gravrd", + "shortIdentifier": "pg", + "tilesFilename": "gravrd", "moveCost": 65 }, "cobblestoneRoad": { "index": 3, - "code": "pc", - "animation": "cobbrd", + "shortIdentifier": "pc", + "tilesFilename": "cobbrd", "moveCost": 50 } } diff --git a/config/terrains.json b/config/terrains.json index f999ec20c..52448bcd7 100644 --- a/config/terrains.json +++ b/config/terrains.json @@ -7,11 +7,11 @@ "minimapBlocked" : [ 57, 40, 8 ], "music" : "Dirt.mp3", "tiles" : "DIRTTL", - "code" : "dt", + "shortIdentifier" : "dt", "river" : "mudRiver", "battleFields" : ["dirt_birches", "dirt_hills", "dirt_pines"], "terrainViewPatterns" : "dirt", - "horseSoundId" : 0 + "horseSound" : "horseDirt" }, "sand" : { @@ -21,12 +21,12 @@ "minimapBlocked" : [ 165, 158, 107 ], "music" : "Sand.mp3", "tiles" : "SANDTL", - "code" : "sa", + "shortIdentifier" : "sa", "river" : "mudRiver", "battleFields" : ["sand_mesas"], "transitionRequired" : true, "terrainViewPatterns" : "sand", - "horseSoundId" : 1 + "horseSound" : "horseSand" }, "grass" : { @@ -36,10 +36,10 @@ "minimapBlocked" : [ 0, 48, 0 ], "music" : "Grass.mp3", "tiles" : "GRASTL", - "code" : "gr", + "shortIdentifier" : "gr", "river" : "waterRiver", "battleFields" : ["grass_hills", "grass_pines"], - "horseSoundId" : 2 + "horseSound" : "horseGrass" }, "snow" : { @@ -49,10 +49,10 @@ "minimapBlocked" : [ 140, 158, 156 ], "music" : "Snow.mp3", "tiles" : "SNOWTL", - "code" : "sn", + "shortIdentifier" : "sn", "river" : "iceRiver", "battleFields" : ["snow_mountains", "snow_trees"], - "horseSoundId" : 3 + "horseSound" : "horseSnow" }, "swamp" : { @@ -62,10 +62,10 @@ "minimapBlocked" : [ 33, 89, 66 ], "music" : "Swamp.mp3", "tiles" : "SWMPTL", - "code" : "sw", + "shortIdentifier" : "sw", "river" : "waterRiver", "battleFields" : ["swamp_trees"], - "horseSoundId" : 4 + "horseSound" : "horseSwamp" }, "rough" : { @@ -75,10 +75,10 @@ "minimapBlocked" : [ 99, 81, 33 ], "music" : "Rough.mp3", "tiles" : "ROUGTL", - "code" : "rg", + "shortIdentifier" : "rg", "river" : "mudRiver", "battleFields" : ["rough"], - "horseSoundId" : 5 + "horseSound" : "horseRough" }, "subterra" : { @@ -89,11 +89,11 @@ "music" : "Underground.mp3", "tiles" : "SUBBTL", "type" : [ "SUB" ], - "code" : "sb", + "shortIdentifier" : "sb", "river" : "waterRiver", "battleFields" : ["subterranean"], "rockTerrain" : "rock", - "horseSoundId" : 6 + "horseSound" : "horseSubterranean" }, "lava" : { @@ -104,11 +104,11 @@ "music" : "Lava.mp3", "tiles" : "LAVATL", "type" : ["SUB", "SURFACE"], - "code" : "lv", + "shortIdentifier" : "lv", "river" : "lavaRiver", "battleFields" : ["lava"], "rockTerrain" : "rock", - "horseSoundId" : 7 + "horseSound" : "horseLava" }, "water" : { @@ -119,11 +119,11 @@ "music" : "Water.mp3", "tiles" : "WATRTL", "type" : [ "WATER" ], - "code" : "wt", + "shortIdentifier" : "wt", "battleFields" : ["ship"], "transitionRequired" : true, "terrainViewPatterns" : "water", - "horseSoundId" : 8, + "horseSound" : "horseWater", "sounds": { "ambient": ["LOOPOCEA"] } @@ -137,10 +137,10 @@ "music" : "Underground.mp3", // Impossible in H3 "tiles" : "ROCKTL", "type" : [ "ROCK" ], - "code" : "rc", + "shortIdentifier" : "rc", "battleFields" : ["rocklands"], "transitionRequired" : true, "terrainViewPatterns" : "rock", - "horseSoundId" : 9 + "horseSound" : "horseRock" } } diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 3b6bb16d2..9810f0738 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1944,8 +1944,7 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & r if(map->isCoastalTile(tile)) //coastal tile is always ground return BattleField::fromString("sand_shore"); - return BattleField::fromString( - *RandomGeneratorUtil::nextItem(t.terType->battleFields, rand)); + return BattleField(*RandomGeneratorUtil::nextItem(t.terType->battleFields, rand)); } diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 5c7c27ce2..31cd346e5 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -2136,7 +2136,7 @@ int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const std::string CreatureTerrainLimiter::toString() const { boost::format fmt("CreatureTerrainLimiter(terrainType=%s)"); - auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->name; + auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->identifier; fmt % (terrainType == ETerrainId::NATIVE_TERRAIN ? "native" : terrainName); return fmt.str(); } @@ -2146,7 +2146,7 @@ JsonNode CreatureTerrainLimiter::toJsonNode() const JsonNode root(JsonNode::JsonType::DATA_STRUCT); root["type"].String() = "CREATURE_TERRAIN_LIMITER"; - auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->name; + auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->identifier; root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainName)); return root; diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index 2c2257bd2..687518a3a 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -38,15 +38,15 @@ TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const TerrainType * info = new TerrainType; info->id = TerrainId(index); - info->name = identifier; + info->identifier = identifier; info->moveCost = static_cast(json["moveCost"].Integer()); info->musicFilename = json["music"].String(); info->tilesFilename = json["tiles"].String(); - info->horseSoundId = static_cast(json["horseSoundId"].Float()); + info->horseSound = json["horseSound"].String(); info->transitionRequired = json["transitionRequired"].Bool(); info->terrainViewPatterns = json["terrainViewPatterns"].String(); - info->terrainText = json["text"].String(); + info->nameTranslated = json["nameTranslated"].String(); const JsonVector & unblockedVec = json["minimapUnblocked"].Vector(); info->minimapUnblocked = @@ -84,23 +84,24 @@ TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const { info->river = RiverId(identifier); }); - } - info->typeCode = json["code"].String(); - assert(info->typeCode.length() == 2); + info->shortIdentifier = json["shortIdentifier"].String(); + assert(info->shortIdentifier.length() == 2); for(auto & t : json["battleFields"].Vector()) - info->battleFields.emplace_back(t.String()); - - - //Update terrain with this id in the future, after all terrain types are populated + { + VLC->modh->identifiers.requestIdentifier("battlefield", t, [info](int32_t identifier) + { + info->battleFields.emplace_back(identifier); + }); + } for(auto & t : json["prohibitTransitions"].Vector()) { VLC->modh->identifiers.requestIdentifier("terrain", t, [info](int32_t identifier) { - info->prohibitTransitions.push_back(TerrainId(identifier)); + info->prohibitTransitions.emplace_back(identifier); }); } @@ -159,10 +160,11 @@ RiverType * RiverTypeHandler::loadFromJson( { RiverType * info = new RiverType; - info->id = RiverId(index); - info->fileName = json["animation"].String(); - info->code = json["code"].String(); - info->deltaName = json["delta"].String(); + info->id = RiverId(index); + info->identifier = identifier; + info->tilesFilename = json["tilesFilename"].String(); + info->shortIdentifier = json["shortIdentifier"].String(); + info->deltaName = json["delta"].String(); return info; } @@ -197,10 +199,11 @@ RoadType * RoadTypeHandler::loadFromJson( { RoadType * info = new RoadType; - info->id = RoadId(index); - info->fileName = json["animation"].String(); - info->code = json["code"].String(); - info->movementCost = json["moveCost"].Integer(); + info->id = RoadId(index); + info->identifier = identifier; + info->tilesFilename = json["animation"].String(); + info->shortIdentifier = json["code"].String(); + info->movementCost = json["moveCost"].Integer(); return info; } diff --git a/lib/Terrain.h b/lib/Terrain.h index 7c10af4e5..bf6d41a5f 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -24,8 +24,8 @@ class DLL_LINKAGE TerrainType : public EntityT public: int32_t getIndex() const override { return id.getNum(); } int32_t getIconIndex() const override { return 0; } - const std::string & getName() const override { return name;} - const std::string & getJsonKey() const override { return name;} + const std::string & getName() const override { return identifier;} + const std::string & getJsonKey() const override { return identifier;} void registerIcons(const IconRegistar & cb) const override {} TerrainId getId() const override { return id;} @@ -38,22 +38,22 @@ public: ROCK = 16 }; - std::vector battleFields; + std::vector battleFields; std::vector prohibitTransitions; std::array minimapBlocked; std::array minimapUnblocked; - std::string name; + std::string identifier; + std::string shortIdentifier; std::string musicFilename; std::string tilesFilename; - std::string terrainText; - std::string typeCode; + std::string nameTranslated; std::string terrainViewPatterns; - RiverId river; + std::string horseSound; TerrainId id; TerrainId rockTerrain; + RiverId river; int moveCost; - int horseSoundId; ui8 passabilityType; bool transitionRequired; @@ -74,18 +74,18 @@ public: h & prohibitTransitions; h & minimapBlocked; h & minimapUnblocked; - h & name; + h & identifier; h & musicFilename; h & tilesFilename; - h & terrainText; - h & typeCode; + h & nameTranslated; + h & shortIdentifier; h & terrainViewPatterns; h & rockTerrain; h & river; h & id; h & moveCost; - h & horseSoundId; + h & horseSound; h & passabilityType; h & transitionRequired; } @@ -96,13 +96,14 @@ class DLL_LINKAGE RiverType : public EntityT public: int32_t getIndex() const override { return id.getNum(); } int32_t getIconIndex() const override { return 0; } - const std::string & getName() const override { return code;} - const std::string & getJsonKey() const override { return code;} + const std::string & getName() const override { return identifier;} + const std::string & getJsonKey() const override { return identifier;} void registerIcons(const IconRegistar & cb) const override {} RiverId getId() const override { return id;} - std::string fileName; - std::string code; + std::string tilesFilename; + std::string identifier; + std::string shortIdentifier; std::string deltaName; RiverId id; @@ -110,8 +111,8 @@ public: template void serialize(Handler& h, const int version) { - h & fileName; - h & code; + h & tilesFilename; + h & identifier; h & deltaName; h & id; } @@ -122,13 +123,14 @@ class DLL_LINKAGE RoadType : public EntityT public: int32_t getIndex() const override { return id.getNum(); } int32_t getIconIndex() const override { return 0; } - const std::string & getName() const override { return code;} - const std::string & getJsonKey() const override { return code;} + const std::string & getName() const override { return identifier;} + const std::string & getJsonKey() const override { return identifier;} void registerIcons(const IconRegistar & cb) const override {} RoadId getId() const override { return id;} - std::string fileName; - std::string code; + std::string tilesFilename; + std::string identifier; + std::string shortIdentifier; RoadId id; ui8 movementCost; @@ -136,8 +138,8 @@ public: template void serialize(Handler& h, const int version) { - h & fileName; - h & code; + h & tilesFilename; + h & identifier; h & id; h & movementCost; } diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index d6f27bbff..eb0d5dc38 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -213,7 +213,7 @@ void CGObjectInstance::setType(si32 ID, si32 subID) } else { - logGlobal->warn("Object %d:%d at %s has no templates suitable for terrain %s", ID, subID, visitablePos().toString(), tile.terType->name); + logGlobal->warn("Object %d:%d at %s has no templates suitable for terrain %s", ID, subID, visitablePos().toString(), tile.terType->identifier); appearance = handler->getTemplates()[0]; // get at least some appearance since alternative is crash } diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 3692a237a..e85f07380 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -360,7 +360,7 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const for(auto type : allowedTerrains) { JsonNode value(JsonNode::JsonType::DATA_STRING); - value.String() = VLC->terrainTypeHandler->getById(type)->name; + value.String() = VLC->terrainTypeHandler->getById(type)->identifier; data.push_back(value); } } diff --git a/lib/mapping/MapEditUtils.cpp b/lib/mapping/MapEditUtils.cpp index 8845a0f94..e9dece7b5 100644 --- a/lib/mapping/MapEditUtils.cpp +++ b/lib/mapping/MapEditUtils.cpp @@ -357,7 +357,7 @@ void CTerrainViewPatternUtils::printDebuggingInfoAboutTile(const CMap * map, int { auto debugTile = map->getTile(debugPos); - std::string terType = debugTile.terType->name.substr(0, 6); + std::string terType = debugTile.terType->shortIdentifier; line += terType; line.insert(line.end(), PADDED_LENGTH - terType.size(), ' '); } diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index e22043f9b..d20a68ba6 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -350,7 +350,7 @@ TerrainType * CMapFormatJson::getTerrainByCode( std::string code) { for ( auto const & object : VLC->terrainTypeHandler->objects) { - if (object->typeCode == code) + if (object->shortIdentifier == code) return const_cast(object.get()); } return nullptr; @@ -360,7 +360,7 @@ RiverType * CMapFormatJson::getRiverByCode( std::string code) { for ( auto const & object : VLC->riverTypeHandler->objects) { - if (object->code == code) + if (object->shortIdentifier == code) return const_cast(object.get()); } return nullptr; @@ -370,7 +370,7 @@ RoadType * CMapFormatJson::getRoadByCode( std::string code) { for ( auto const & object : VLC->roadTypeHandler->objects) { - if (object->code == code) + if (object->shortIdentifier == code) return const_cast(object.get()); } return nullptr; @@ -1320,7 +1320,7 @@ std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile) out.setf(std::ios::dec, std::ios::basefield); out.unsetf(std::ios::showbase); - out << tile.terType->typeCode << (int)tile.terView << flipCodes[tile.extTileFlags % 4]; + out << tile.terType->shortIdentifier << (int)tile.terView << flipCodes[tile.extTileFlags % 4]; if(tile.roadType->id != Road::NO_ROAD) out << tile.roadType << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4]; diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 85a4def3c..f46cd5fbe 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -75,7 +75,7 @@ public: static std::string encode(const si32 index) { - return VLC->terrainTypeHandler->getByIndex(index)->name; + return VLC->terrainTypeHandler->getByIndex(index)->identifier; } }; @@ -365,7 +365,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) for(auto & ttype : terrainTypes) { JsonNode n; - n.String() = VLC->terrainTypeHandler->getById(ttype)->name; + n.String() = VLC->terrainTypeHandler->getById(ttype)->identifier; node.Vector().push_back(n); } } diff --git a/lib/rmg/RiverPlacer.cpp b/lib/rmg/RiverPlacer.cpp index 7d52941e6..0446e2a18 100644 --- a/lib/rmg/RiverPlacer.cpp +++ b/lib/rmg/RiverPlacer.cpp @@ -381,7 +381,7 @@ void RiverPlacer::connectRiver(const int3 & tile) { if(tmplates.size() % 4 != 0) throw rmgException(boost::to_string(boost::format("River templates for (%d,%d) at terrain %s, river %s are incorrect") % - RIVER_DELTA_ID % RIVER_DELTA_SUBTYPE % zone.getTerrainType() % river->code)); + RIVER_DELTA_ID % RIVER_DELTA_SUBTYPE % zone.getTerrainType() % river->shortIdentifier)); std::string targetTemplateName = river->deltaName + std::to_string(deltaOrientations[pos]) + ".def"; for(auto & templ : tmplates) diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index e54664fdc..fff4a699f 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -121,7 +121,7 @@ void Object::Instance::setTemplate(TerrainId terrain) auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrain); if (templates.empty()) { - auto terrainName = VLC->terrainTypeHandler->getById(terrain)->name; + auto terrainName = VLC->terrainTypeHandler->getById(terrain)->identifier; throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.subID % terrainName)); } dObject.appearance = templates.front(); diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 9a0da79f4..dbe073c6f 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -545,19 +545,19 @@ void MainWindow::loadObjectsTree() //adding terrains for(auto & terrain : VLC->terrainTypeHandler->objects) { - QPushButton *b = new QPushButton(QString::fromStdString(terrain->name)); + QPushButton *b = new QPushButton(QString::fromStdString(terrain->identifier)); ui->terrainLayout->addWidget(b); connect(b, &QPushButton::clicked, this, [this, terrain]{ terrainButtonClicked(terrain->id); }); //filter - ui->terrainFilterCombo->addItem(QString::fromStdString(terrain->name)); + ui->terrainFilterCombo->addItem(QString::fromStdString(terrain->identifier)); } //add spacer to keep terrain button on the top ui->terrainLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); //adding roads for(auto & road : VLC->roadTypeHandler->objects) { - QPushButton *b = new QPushButton(QString::fromStdString(road->fileName)); + QPushButton *b = new QPushButton(QString::fromStdString(road->tilesFilename)); ui->roadLayout->addWidget(b); connect(b, &QPushButton::clicked, this, [this, road]{ roadOrRiverButtonClicked(road->id.getNum(), true); }); } @@ -566,7 +566,7 @@ void MainWindow::loadObjectsTree() //adding rivers for(auto & river : VLC->riverTypeHandler->objects) { - QPushButton *b = new QPushButton(QString::fromStdString(river->fileName)); + QPushButton *b = new QPushButton(QString::fromStdString(river->tilesFilename)); ui->riverLayout->addWidget(b); connect(b, &QPushButton::clicked, this, [this, river]{ roadOrRiverButtonClicked(river->id.getNum(), false); }); } @@ -919,7 +919,7 @@ void MainWindow::on_terrainFilterCombo_currentTextChanged(const QString &arg1) if (!arg1.isEmpty()) { for (auto const & terrain : VLC->terrainTypeHandler->objects) - if (terrain->name == arg1.toStdString()) + if (terrain->identifier == arg1.toStdString()) objectBrowser->terrain = terrain->id; } objectBrowser->invalidate(); diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp index 589ecc8ab..7f9beaa5c 100644 --- a/mapeditor/maphandler.cpp +++ b/mapeditor/maphandler.cpp @@ -80,15 +80,15 @@ void MapHandler::initTerrainGraphics() std::map riverFiles; for(const auto & terrain : VLC->terrainTypeHandler->objects) { - terrainFiles[terrain->name] = terrain->tilesFilename; + terrainFiles[terrain->identifier] = terrain->tilesFilename; } for(const auto & river : VLC->riverTypeHandler->objects) { - riverFiles[river->fileName] = river->fileName; + riverFiles[river->tilesFilename] = river->tilesFilename; } for(const auto & road : VLC->roadTypeHandler->objects) { - roadFiles[road->fileName] = road->fileName; + roadFiles[road->tilesFilename] = road->tilesFilename; } loadFlipped(terrainAnimations, terrainImages, terrainFiles); @@ -101,8 +101,7 @@ void MapHandler::drawTerrainTile(QPainter & painter, int x, int y, int z) auto & tinfo = map->getTile(int3(x, y, z)); ui8 rotation = tinfo.extTileFlags % 4; - //TODO: use ui8 instead of string key - auto terrainName = tinfo.terType->name; + auto terrainName = tinfo.terType->identifier; if(terrainImages.at(terrainName).size() <= tinfo.terView) return; @@ -118,7 +117,7 @@ void MapHandler::drawRoad(QPainter & painter, int x, int y, int z) if(tinfoUpper && tinfoUpper->roadType->id != Road::NO_ROAD) { - auto roadName = tinfoUpper->roadType->fileName; + auto roadName = tinfoUpper->roadType->tilesFilename; QRect source(0, tileSize / 2, tileSize, tileSize / 2); ui8 rotation = (tinfoUpper->extTileFlags >> 4) % 4; bool hflip = (rotation == 1 || rotation == 3), vflip = (rotation == 2 || rotation == 3); @@ -130,7 +129,7 @@ void MapHandler::drawRoad(QPainter & painter, int x, int y, int z) if(tinfo.roadType->id != Road::NO_ROAD) //print road from this tile { - auto roadName = tinfo.roadType->fileName; + auto roadName = tinfo.roadType->tilesFilename; QRect source(0, 0, tileSize, tileSize / 2); ui8 rotation = (tinfo.extTileFlags >> 4) % 4; bool hflip = (rotation == 1 || rotation == 3), vflip = (rotation == 2 || rotation == 3); @@ -149,7 +148,7 @@ void MapHandler::drawRiver(QPainter & painter, int x, int y, int z) return; //TODO: use ui8 instead of string key - auto riverName = tinfo.riverType->fileName; + auto riverName = tinfo.riverType->tilesFilename; if(riverImages.at(riverName).size() <= tinfo.riverDir) return; From f3985d205b5f5be62876d9e7824ed94645808b4c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 24 Dec 2022 16:48:24 +0200 Subject: [PATCH 062/197] Added horseSoundPenalty for terrains (for movement outside of roads) --- client/CPlayerInterface.cpp | 29 +++++++++++++++++++++-------- config/terrains.json | 30 ++++++++++++++++++++---------- lib/Terrain.cpp | 5 +++-- lib/Terrain.h | 2 ++ 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 49a76f3a2..4deccbc30 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -435,7 +435,7 @@ void CPlayerInterface::heroKilled(const CGHeroInstance* hero) adventureInt->select(newSelection, true); else if (adventureInt->selection == hero) adventureInt->selection = nullptr; - + if (vstd::contains(paths, hero)) paths.erase(hero); } @@ -2373,6 +2373,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) TerrainId currentTerrain = ETerrainId::BORDER; // not init yet TerrainId newTerrain; + bool wasOnRoad = true; int sh = -1; auto canStop = [&](CGPathNode * node) -> bool @@ -2388,13 +2389,18 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) for (i=(int)path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE || !canStop(&path.nodes[i])); i--) { - int3 currentCoord = path.nodes[i].coord; + int3 prevCoord = path.nodes[i].coord; int3 nextCoord = path.nodes[i-1].coord; - auto currentObject = getObj(currentCoord, currentCoord == h->pos); + auto prevRoad = cb->getTile(h->convertToVisitablePos(prevCoord))->roadType; + auto nextRoad = cb->getTile(h->convertToVisitablePos(nextCoord))->roadType; + + bool movingOnRoad = prevRoad->getId() != Road::NO_ROAD && nextRoad->getId() != Road::NO_ROAD; + + auto prevObject = getObj(prevCoord, prevCoord == h->pos); auto nextObjectTop = getObj(nextCoord, false); auto nextObject = getObj(nextCoord, true); - auto destTeleportObj = getDestTeleportObj(currentObject, nextObjectTop, nextObject); + auto destTeleportObj = getDestTeleportObj(prevObject, nextObjectTop, nextObject); if (isTeleportAction(path.nodes[i-1].action) && destTeleportObj != nullptr) { CCS->soundh->stopSound(sh); @@ -2409,7 +2415,10 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) } if(i != path.nodes.size() - 1) { - sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(currentTerrain)->horseSound, -1); + if (movingOnRoad) + sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(currentTerrain)->horseSound, -1); + else + sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(currentTerrain)->horseSoundPenalty, -1); } continue; } @@ -2427,12 +2436,16 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) sh = CCS->soundh->playSound(soundBase::horseFlying, -1); #endif { - newTerrain = cb->getTile(h->convertToVisitablePos(currentCoord))->terType->id; - if(newTerrain != currentTerrain) + newTerrain = cb->getTile(h->convertToVisitablePos(prevCoord))->terType->id; + if(newTerrain != currentTerrain || wasOnRoad != movingOnRoad) { CCS->soundh->stopSound(sh); - sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(newTerrain)->horseSound, -1); + if (movingOnRoad) + sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(newTerrain)->horseSound, -1); + else + sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(newTerrain)->horseSoundPenalty, -1); currentTerrain = newTerrain; + wasOnRoad = movingOnRoad; } } diff --git a/config/terrains.json b/config/terrains.json index 52448bcd7..a4ec260cb 100644 --- a/config/terrains.json +++ b/config/terrains.json @@ -11,7 +11,8 @@ "river" : "mudRiver", "battleFields" : ["dirt_birches", "dirt_hills", "dirt_pines"], "terrainViewPatterns" : "dirt", - "horseSound" : "horseDirt" + "horseSound" : "horse00", + "horseSoundPenalty" : "horse20" }, "sand" : { @@ -26,7 +27,8 @@ "battleFields" : ["sand_mesas"], "transitionRequired" : true, "terrainViewPatterns" : "sand", - "horseSound" : "horseSand" + "horseSound" : "horse01", + "horseSoundPenalty" : "horse21" }, "grass" : { @@ -39,7 +41,8 @@ "shortIdentifier" : "gr", "river" : "waterRiver", "battleFields" : ["grass_hills", "grass_pines"], - "horseSound" : "horseGrass" + "horseSound" : "horse02", + "horseSoundPenalty" : "horse22" }, "snow" : { @@ -52,7 +55,8 @@ "shortIdentifier" : "sn", "river" : "iceRiver", "battleFields" : ["snow_mountains", "snow_trees"], - "horseSound" : "horseSnow" + "horseSound" : "horse03", + "horseSoundPenalty" : "horse23" }, "swamp" : { @@ -65,7 +69,8 @@ "shortIdentifier" : "sw", "river" : "waterRiver", "battleFields" : ["swamp_trees"], - "horseSound" : "horseSwamp" + "horseSound" : "horse04", + "horseSoundPenalty" : "horse24" }, "rough" : { @@ -78,7 +83,8 @@ "shortIdentifier" : "rg", "river" : "mudRiver", "battleFields" : ["rough"], - "horseSound" : "horseRough" + "horseSound" : "horse05", + "horseSoundPenalty" : "horse25" }, "subterra" : { @@ -93,7 +99,8 @@ "river" : "waterRiver", "battleFields" : ["subterranean"], "rockTerrain" : "rock", - "horseSound" : "horseSubterranean" + "horseSound" : "horse06", + "horseSoundPenalty" : "horse26" }, "lava" : { @@ -108,7 +115,8 @@ "river" : "lavaRiver", "battleFields" : ["lava"], "rockTerrain" : "rock", - "horseSound" : "horseLava" + "horseSound" : "horse07", + "horseSoundPenalty" : "horse27" }, "water" : { @@ -123,7 +131,8 @@ "battleFields" : ["ship"], "transitionRequired" : true, "terrainViewPatterns" : "water", - "horseSound" : "horseWater", + "horseSound" : "horse08", + "horseSoundPenalty" : "horse28" "sounds": { "ambient": ["LOOPOCEA"] } @@ -141,6 +150,7 @@ "battleFields" : ["rocklands"], "transitionRequired" : true, "terrainViewPatterns" : "rock", - "horseSound" : "horseRock" + "horseSound" : "horse09", + "horseSoundPenalty" : "horse29" } } diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index 687518a3a..a6d50d88f 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -44,6 +44,7 @@ TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const info->musicFilename = json["music"].String(); info->tilesFilename = json["tiles"].String(); info->horseSound = json["horseSound"].String(); + info->horseSoundPenalty = json["horseSoundPenalty"].String(); info->transitionRequired = json["transitionRequired"].Bool(); info->terrainViewPatterns = json["terrainViewPatterns"].String(); info->nameTranslated = json["nameTranslated"].String(); @@ -201,8 +202,8 @@ RoadType * RoadTypeHandler::loadFromJson( info->id = RoadId(index); info->identifier = identifier; - info->tilesFilename = json["animation"].String(); - info->shortIdentifier = json["code"].String(); + info->tilesFilename = json["tilesFilename"].String(); + info->shortIdentifier = json["shortIdentifier"].String(); info->movementCost = json["moveCost"].Integer(); return info; diff --git a/lib/Terrain.h b/lib/Terrain.h index bf6d41a5f..7b64aab3c 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -49,6 +49,7 @@ public: std::string nameTranslated; std::string terrainViewPatterns; std::string horseSound; + std::string horseSoundPenalty; TerrainId id; TerrainId rockTerrain; @@ -86,6 +87,7 @@ public: h & id; h & moveCost; h & horseSound; + h & horseSoundPenalty; h & passabilityType; h & transitionRequired; } From 7c7ae26e67bdc32f95c5b9cfcfd2dfa4e58abe9c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 1 Jan 2023 17:10:47 +0200 Subject: [PATCH 063/197] Map/Road/River identifiers are now private members --- client/CMusicHandler.cpp | 2 +- client/CPlayerInterface.cpp | 4 ++-- client/battle/BattleInterface.cpp | 4 ++-- client/mapHandler.cpp | 12 +++++------ client/widgets/AdventureMapClasses.cpp | 4 ++-- client/windows/CAdvmapInterface.cpp | 2 +- config/terrains.json | 2 +- lib/CGameState.cpp | 2 +- lib/CPathfinder.cpp | 2 +- lib/HeroBonus.cpp | 4 ++-- lib/NetPacksLib.cpp | 2 +- lib/Terrain.cpp | 2 +- lib/Terrain.h | 28 +++++++++++++++++--------- lib/mapObjects/CGHeroInstance.cpp | 10 ++++----- lib/mapObjects/CGTownInstance.cpp | 2 +- lib/mapObjects/CObjectHandler.cpp | 6 +++--- lib/mapObjects/CommonConstructors.cpp | 2 +- lib/mapObjects/ObjectTemplate.cpp | 2 +- lib/mapping/CDrawRoadsOperation.cpp | 8 ++++---- lib/mapping/CMapOperation.cpp | 12 +++++------ lib/mapping/MapFormatH3M.cpp | 2 +- lib/mapping/MapFormatJson.cpp | 4 ++-- lib/rmg/CRmgTemplate.cpp | 6 +++--- lib/rmg/Functions.cpp | 2 +- lib/rmg/RiverPlacer.cpp | 2 +- lib/rmg/RmgObject.cpp | 6 +++--- lib/rmg/WaterProxy.cpp | 4 ++-- mapeditor/mainwindow.cpp | 22 ++++++++++---------- mapeditor/mapcontroller.cpp | 2 +- mapeditor/maphandler.cpp | 10 ++++----- server/CGameHandler.cpp | 2 +- 31 files changed, 92 insertions(+), 82 deletions(-) diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index a53ae3952..a2cee5ca8 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -340,7 +340,7 @@ void CMusicHandler::loadTerrainMusicThemes() { for (const auto & terrain : CGI->terrainTypeHandler->objects) { - addEntryToSet("terrain_" + terrain->identifier, "Music/" + terrain->musicFilename); + addEntryToSet("terrain_" + terrain->getName(), "Music/" + terrain->musicFilename); } } diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 4deccbc30..5b77d2c48 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -259,7 +259,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose) { updateAmbientSounds(); //We may need to change music - select new track, music handler will change it if needed - CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->identifier, true, false); + CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->getName(), true, false); if(details.result == TryMoveHero::TELEPORTATION) { @@ -2436,7 +2436,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) sh = CCS->soundh->playSound(soundBase::horseFlying, -1); #endif { - newTerrain = cb->getTile(h->convertToVisitablePos(prevCoord))->terType->id; + newTerrain = cb->getTile(h->convertToVisitablePos(prevCoord))->terType->getId(); if(newTerrain != currentTerrain || wasOnRoad != movingOnRoad) { CCS->soundh->stopSound(sh); diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index 8a1e17bb9..78977790d 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -136,8 +136,8 @@ BattleInterface::~BattleInterface() if (adventureInt && adventureInt->selection) { //FIXME: this should be moved to adventureInt which should restore correct track based on selection/active player - const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType); - CCS->musich->playMusicFromSet("terrain", terrain.name, true, false); + const auto * terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType; + CCS->musich->playMusicFromSet("terrain", terrain->getName(), true, false); } // may happen if user decided to close game while in battle diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 56017b657..712711ac8 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -177,7 +177,7 @@ void CMapHandler::initTerrainGraphics() std::map roadFiles; for(const auto & terrain : VLC->terrainTypeHandler->objects) { - terrainFiles[terrain->identifier] = terrain->tilesFilename; + terrainFiles[terrain->getName()] = terrain->tilesFilename; } for(const auto & river : VLC->riverTypeHandler->objects) { @@ -606,7 +606,7 @@ void CMapHandler::CMapBlitter::drawTileTerrain(SDL_Surface * targetSurf, const T ui8 rotation = tinfo.extTileFlags % 4; //TODO: use ui8 instead of string key - auto terrainName = tinfo.terType->identifier; + auto terrainName = tinfo.terType->getName(); if(parent->terrainImages[terrainName].size()<=tinfo.terView) return; @@ -786,7 +786,7 @@ void CMapHandler::CMapBlitter::drawObjects(SDL_Surface * targetSurf, const Terra void CMapHandler::CMapBlitter::drawRoad(SDL_Surface * targetSurf, const TerrainTile & tinfo, const TerrainTile * tinfoUpper) const { - if (tinfoUpper && tinfoUpper->roadType->id != Road::NO_ROAD) + if (tinfoUpper && tinfoUpper->roadType->getId() != Road::NO_ROAD) { ui8 rotation = (tinfoUpper->extTileFlags >> 4) % 4; Rect source(0, tileSize / 2, tileSize, tileSize / 2); @@ -795,7 +795,7 @@ void CMapHandler::CMapBlitter::drawRoad(SDL_Surface * targetSurf, const TerrainT &source, targetSurf, &dest); } - if(tinfo.roadType->id != Road::NO_ROAD) //print road from this tile + if(tinfo.roadType->getId() != Road::NO_ROAD) //print road from this tile { ui8 rotation = (tinfo.extTileFlags >> 4) % 4; Rect source(0, 0, tileSize, halfTileSizeCeil); @@ -860,7 +860,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn if(isVisible || info->showAllTerrain) { drawTileTerrain(targetSurf, tinfo, tile); - if(tinfo.riverType->id != River::NO_RIVER) + if(tinfo.riverType->getId() != River::NO_RIVER) drawRiver(targetSurf, tinfo); drawRoad(targetSurf, tinfo, tinfoUpper); } @@ -1390,7 +1390,7 @@ void CMapHandler::getTerrainDescr(const int3 & pos, std::string & out, bool isRM } if(!isTile2Terrain || out.empty()) - out = VLC->terrainTypeHandler->getById(t.terType->id)->nameTranslated; + out = t.terType->getName(); if(t.getDiggingStatus(false) == EDiggingStatus::CAN_DIG) { diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index d9856e882..b5e50dff1 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -390,7 +390,7 @@ const SDL_Color & CMinimapInstance::getTileColor(const int3 & pos) } // else - use terrain color (blocked version or normal) - const auto & colorPair = parent->colors.find(tile->terType->id)->second; + const auto & colorPair = parent->colors.find(tile->terType->getId())->second; if (tile->blocked && (!tile->visitable)) return colorPair.second; else @@ -517,7 +517,7 @@ std::map > CMinimap::loadColors() ui8(255) }; - ret[terrain->id] = std::make_pair(normal, blocked); + ret[terrain->getId()] = std::make_pair(normal, blocked); } return ret; } diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 470760cd3..4ae73a39c 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -1414,7 +1414,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView) auto pos = sel->visitablePos(); auto tile = LOCPLINT->cb->getTile(pos); if(tile) - CCS->musich->playMusicFromSet("terrain", tile->terType->identifier, true, false); + CCS->musich->playMusicFromSet("terrain", tile->terType->getName(), true, false); } if(centerView) centerOn(sel); diff --git a/config/terrains.json b/config/terrains.json index a4ec260cb..ad7c9a3c9 100644 --- a/config/terrains.json +++ b/config/terrains.json @@ -132,7 +132,7 @@ "transitionRequired" : true, "terrainViewPatterns" : "water", "horseSound" : "horse08", - "horseSoundPenalty" : "horse28" + "horseSoundPenalty" : "horse28", "sounds": { "ambient": ["LOOPOCEA"] } diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 9810f0738..1cca708a7 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2134,7 +2134,7 @@ void CGameState::updateRumor() rumorId = *RandomGeneratorUtil::nextItem(sRumorTypes, rand); if(rumorId == RumorState::RUMOR_GRAIL) { - rumorExtra = getTile(map->grailPos)->terType->id.getNum(); + rumorExtra = getTile(map->grailPos)->terType->getIndex(); break; } diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index dc1c1dd07..13d5500ae 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -1003,7 +1003,7 @@ TurnInfo::BonusCache::BonusCache(TConstBonusListPtr bl) for(const auto & terrain : VLC->terrainTypeHandler->objects) { noTerrainPenalty.push_back(static_cast( - bl->getFirst(Selector::type()(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain->id.getNum()))))); + bl->getFirst(Selector::type()(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain->getIndex()))))); } freeShipBoarding = static_cast(bl->getFirst(Selector::type()(Bonus::FREE_SHIP_BOARDING))); diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 31cd346e5..09a1e7499 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -2136,7 +2136,7 @@ int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const std::string CreatureTerrainLimiter::toString() const { boost::format fmt("CreatureTerrainLimiter(terrainType=%s)"); - auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->identifier; + auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getName(); fmt % (terrainType == ETerrainId::NATIVE_TERRAIN ? "native" : terrainName); return fmt.str(); } @@ -2146,7 +2146,7 @@ JsonNode CreatureTerrainLimiter::toJsonNode() const JsonNode root(JsonNode::JsonType::DATA_STRUCT); root["type"].String() = "CREATURE_TERRAIN_LIMITER"; - auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->identifier; + auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getName(); root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainName)); return root; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 71de315b6..3e0b78d00 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -735,7 +735,7 @@ DLL_LINKAGE void NewObject::applyGs(CGameState *gs) else { const TerrainTile & t = gs->map->getTile(pos); - terrainType = t.terType->id; + terrainType = t.terType->getId(); } CGObjectInstance *o = nullptr; diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index a6d50d88f..62ba2cb9a 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -47,7 +47,7 @@ TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const info->horseSoundPenalty = json["horseSoundPenalty"].String(); info->transitionRequired = json["transitionRequired"].Bool(); info->terrainViewPatterns = json["terrainViewPatterns"].String(); - info->nameTranslated = json["nameTranslated"].String(); + //info->nameTranslated = json["nameTranslated"].String(); const JsonVector & unblockedVec = json["minimapUnblocked"].Vector(); info->minimapUnblocked = diff --git a/lib/Terrain.h b/lib/Terrain.h index 7b64aab3c..fa410a32d 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -21,6 +21,11 @@ VCMI_LIB_NAMESPACE_BEGIN class DLL_LINKAGE TerrainType : public EntityT { + friend class TerrainTypeHandler; + std::string identifier; + TerrainId id; + ui8 passabilityType; + public: int32_t getIndex() const override { return id.getNum(); } int32_t getIconIndex() const override { return 0; } @@ -29,6 +34,9 @@ public: void registerIcons(const IconRegistar & cb) const override {} TerrainId getId() const override { return id;} + std::string getNameTextID() const; + std::string getNameTranslated() const; + enum PassabilityType : ui8 { LAND = 1, @@ -42,20 +50,16 @@ public: std::vector prohibitTransitions; std::array minimapBlocked; std::array minimapUnblocked; - std::string identifier; std::string shortIdentifier; std::string musicFilename; std::string tilesFilename; - std::string nameTranslated; std::string terrainViewPatterns; std::string horseSound; std::string horseSoundPenalty; - TerrainId id; TerrainId rockTerrain; RiverId river; int moveCost; - ui8 passabilityType; bool transitionRequired; TerrainType(); @@ -78,7 +82,6 @@ public: h & identifier; h & musicFilename; h & tilesFilename; - h & nameTranslated; h & shortIdentifier; h & terrainViewPatterns; h & rockTerrain; @@ -95,6 +98,10 @@ public: class DLL_LINKAGE RiverType : public EntityT { + friend class RiverTypeHandler; + std::string identifier; + RiverId id; + public: int32_t getIndex() const override { return id.getNum(); } int32_t getIconIndex() const override { return 0; } @@ -103,11 +110,12 @@ public: void registerIcons(const IconRegistar & cb) const override {} RiverId getId() const override { return id;} + std::string getNameTextID() const; + std::string getNameTranslated() const; + std::string tilesFilename; - std::string identifier; std::string shortIdentifier; std::string deltaName; - RiverId id; RiverType(); @@ -122,6 +130,10 @@ public: class DLL_LINKAGE RoadType : public EntityT { + friend class RoadTypeHandler; + std::string identifier; + RoadId id; + public: int32_t getIndex() const override { return id.getNum(); } int32_t getIconIndex() const override { return 0; } @@ -131,9 +143,7 @@ public: RoadId getId() const override { return id;} std::string tilesFilename; - std::string identifier; std::string shortIdentifier; - RoadId id; ui8 movementCost; RoadType(); diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 1680e896f..9355b0c5d 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -81,16 +81,16 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f int64_t ret = GameConstants::BASE_MOVEMENT_COST; //if there is road both on dest and src tiles - use road movement cost - if(dest.roadType->id != Road::NO_ROAD && from.roadType->id != Road::NO_ROAD) + if(dest.roadType->getId() != Road::NO_ROAD && from.roadType->getId() != Road::NO_ROAD) { ret = std::max(dest.roadType->movementCost, from.roadType->movementCost); } - else if(ti->nativeTerrain != from.terType->id &&//the terrain is not native + else if(ti->nativeTerrain != from.terType->getId() &&//the terrain is not native ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus - !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType->id.getNum())) //no special movement bonus + !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType->getId().getNum())) //no special movement bonus { - ret = VLC->heroh->terrCosts[from.terType->id]; + ret = VLC->heroh->terrCosts[from.terType->getId()]; ret -= ti->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::PATHFINDING); if(ret < GameConstants::BASE_MOVEMENT_COST) ret = GameConstants::BASE_MOVEMENT_COST; @@ -519,7 +519,7 @@ void CGHeroInstance::initObj(CRandomGenerator & rand) if (ID != Obj::PRISON) { - auto terrain = cb->gameState()->getTile(visitablePos())->terType->id; + auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId(); auto customApp = VLC->objtypeh->getHandlerFor(ID, type->heroClass->getIndex())->getOverride(terrain, this); if (customApp) appearance = customApp; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 55b5ac447..58530848f 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -1132,7 +1132,7 @@ void CGTownInstance::setType(si32 ID, si32 subID) void CGTownInstance::updateAppearance() { - auto terrain = cb->gameState()->getTile(visitablePos())->terType->id; + auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId(); //FIXME: not the best way to do this auto app = VLC->objtypeh->getHandlerFor(ID, subID)->getOverride(terrain, this); if (app) diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index eb0d5dc38..2562a6d00 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -207,13 +207,13 @@ void CGObjectInstance::setType(si32 ID, si32 subID) logGlobal->error("Unknown object type %d:%d at %s", ID, subID, visitablePos().toString()); return; } - if(!handler->getTemplates(tile.terType->id).empty()) + if(!handler->getTemplates(tile.terType->getId()).empty()) { - appearance = handler->getTemplates(tile.terType->id)[0]; + appearance = handler->getTemplates(tile.terType->getId())[0]; } else { - logGlobal->warn("Object %d:%d at %s has no templates suitable for terrain %s", ID, subID, visitablePos().toString(), tile.terType->identifier); + logGlobal->warn("Object %d:%d at %s has no templates suitable for terrain %s", ID, subID, visitablePos().toString(), tile.terType->getName()); appearance = handler->getTemplates()[0]; // get at least some appearance since alternative is crash } diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp index f66b4a82e..eae4f4fed 100644 --- a/lib/mapObjects/CommonConstructors.cpp +++ b/lib/mapObjects/CommonConstructors.cpp @@ -84,7 +84,7 @@ CGObjectInstance * CTownInstanceConstructor::create(std::shared_ptrcb->getTile(object->pos)->terType->id, object); + auto templ = getOverride(object->cb->getTile(object->pos)->terType->getId(), object); if(templ) object->appearance = templ; } diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index e85f07380..05ea4395e 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -360,7 +360,7 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const for(auto type : allowedTerrains) { JsonNode value(JsonNode::JsonType::DATA_STRING); - value.String() = VLC->terrainTypeHandler->getById(type)->identifier; + value.String() = VLC->terrainTypeHandler->getById(type)->getName(); data.push_back(value); } } diff --git a/lib/mapping/CDrawRoadsOperation.cpp b/lib/mapping/CDrawRoadsOperation.cpp index 4260c7095..cfc2a1a56 100644 --- a/lib/mapping/CDrawRoadsOperation.cpp +++ b/lib/mapping/CDrawRoadsOperation.cpp @@ -358,22 +358,22 @@ bool CDrawRiversOperation::canApplyPattern(const LinePattern & pattern) const bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const { - return tile.roadType->id != Road::NO_ROAD; + return tile.roadType->getId() != Road::NO_ROAD; } bool CDrawRiversOperation::needUpdateTile(const TerrainTile & tile) const { - return tile.riverType->id != River::NO_RIVER; + return tile.riverType->getId() != River::NO_RIVER; } bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const { - return map->getTile(pos).roadType->id != Road::NO_ROAD; + return map->getTile(pos).roadType->getId() != Road::NO_ROAD; } bool CDrawRiversOperation::tileHasSomething(const int3& pos) const { - return map->getTile(pos).riverType->id != River::NO_RIVER; + return map->getTile(pos).riverType->getId() != River::NO_RIVER; } void CDrawRoadsOperation::updateTile(TerrainTile & tile, const LinePattern & pattern, const int flip) diff --git a/lib/mapping/CMapOperation.cpp b/lib/mapping/CMapOperation.cpp index 8fd262aba..2db1f2302 100644 --- a/lib/mapping/CMapOperation.cpp +++ b/lib/mapping/CMapOperation.cpp @@ -154,7 +154,7 @@ void CDrawTerrainOperation::updateTerrainTypes() rect.forEach([&](const int3& posToTest) { auto & terrainTile = map->getTile(posToTest); - if(centerTile.terType->id != terrainTile.terType->id) + if(centerTile.terType->getId() != terrainTile.terType->getId()) { auto formerTerType = terrainTile.terType; terrainTile.terType = centerTile.terType; @@ -257,7 +257,7 @@ void CDrawTerrainOperation::updateTerrainViews() { for(const auto & pos : invalidatedTerViews) { - const auto & patterns = VLC->terviewh->getTerrainViewPatterns(map->getTile(pos).terType->id); + const auto & patterns = VLC->terviewh->getTerrainViewPatterns(map->getTile(pos).terType->getId()); // Detect a pattern which fits best int bestPattern = -1; @@ -393,9 +393,9 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi { if(recDepth == 0 && map->isInTheMap(currentPos)) { - if(terType->id == centerTerType->id) + if(terType->getId() == centerTerType->getId()) { - const auto & patternForRule = VLC->terviewh->getTerrainViewPatternsById(centerTerType->id, rule.name); + const auto & patternForRule = VLC->terviewh->getTerrainViewPatternsById(centerTerType->getId(), rule.name); if(auto p = patternForRule) { auto rslt = validateTerrainView(currentPos, &(*p), 1); @@ -422,14 +422,14 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi bool nativeTestOk, nativeTestStrongOk; nativeTestOk = nativeTestStrongOk = (rule.isNativeStrong() || rule.isNativeRule()) && !isAlien; - if(centerTerType->id == ETerrainId::DIRT) + if(centerTerType->getId() == ETerrainId::DIRT) { nativeTestOk = rule.isNativeRule() && !terType->isTransitionRequired(); bool sandTestOk = (rule.isSandRule() || rule.isTransition()) && terType->isTransitionRequired(); applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk || nativeTestStrongOk); } - else if(centerTerType->id == ETerrainId::SAND) + else if(centerTerType->getId() == ETerrainId::SAND) { applyValidationRslt(true); } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 6d74531db..37ac20bf9 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -941,7 +941,7 @@ void CMapLoaderH3M::readTerrain() tile.roadType = const_cast(VLC->roadTypeHandler->getByIndex(reader.readUInt8())); tile.roadDir = reader.readUInt8(); tile.extTileFlags = reader.readUInt8(); - tile.blocked = ((!tile.terType->isPassable() || tile.terType->id == ETerrainId::BORDER ) ? true : false); //underground tiles are always blocked + tile.blocked = ((!tile.terType->isPassable() || tile.terType->getId() == ETerrainId::BORDER ) ? true : false); //underground tiles are always blocked tile.visitable = 0; } } diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index d20a68ba6..be8727fdb 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -1322,10 +1322,10 @@ std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile) out << tile.terType->shortIdentifier << (int)tile.terView << flipCodes[tile.extTileFlags % 4]; - if(tile.roadType->id != Road::NO_ROAD) + if(tile.roadType->getId() != Road::NO_ROAD) out << tile.roadType << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4]; - if(tile.riverType->id != River::NO_RIVER) + if(tile.riverType->getId() != River::NO_RIVER) out << tile.riverType << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4]; return out.str(); diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index f46cd5fbe..8cacb0762 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -75,7 +75,7 @@ public: static std::string encode(const si32 index) { - return VLC->terrainTypeHandler->getByIndex(index)->identifier; + return VLC->terrainTypeHandler->getByIndex(index)->getName(); } }; @@ -154,7 +154,7 @@ ZoneOptions::ZoneOptions() { for(const auto & terr : VLC->terrainTypeHandler->objects) if(terr->isLand() && terr->isPassable()) - terrainTypes.insert(terr->id); + terrainTypes.insert(terr->getId()); } ZoneOptions & ZoneOptions::operator=(const ZoneOptions & other) @@ -365,7 +365,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) for(auto & ttype : terrainTypes) { JsonNode n; - n.String() = VLC->terrainTypeHandler->getById(ttype)->identifier; + n.String() = VLC->terrainTypeHandler->getById(ttype)->getName(); node.Vector().push_back(n); } } diff --git a/lib/rmg/Functions.cpp b/lib/rmg/Functions.cpp index 9bce3ec0d..583d68861 100644 --- a/lib/rmg/Functions.cpp +++ b/lib/rmg/Functions.cpp @@ -121,7 +121,7 @@ void initTerrainType(Zone & zone, CMapGenerator & gen) std::vector waterTerrains; for(const auto & terrain : VLC->terrainTypeHandler->objects) if(terrain->isWater()) - waterTerrains.push_back(terrain->id); + waterTerrains.push_back(terrain->getId()); zone.setTerrainType(*RandomGeneratorUtil::nextItem(waterTerrains, gen.rand)); } diff --git a/lib/rmg/RiverPlacer.cpp b/lib/rmg/RiverPlacer.cpp index 0446e2a18..51f24a4f8 100644 --- a/lib/rmg/RiverPlacer.cpp +++ b/lib/rmg/RiverPlacer.cpp @@ -323,7 +323,7 @@ void RiverPlacer::connectRiver(const int3 & tile) { auto riverType = VLC->terrainTypeHandler->getById(zone.getTerrainType())->river; const auto * river = VLC->riverTypeHandler->getById(riverType); - if(river->id == River::NO_RIVER) + if(river->getId() == River::NO_RIVER) return; rmg::Area roads; diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index fff4a699f..a4251e684 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -121,7 +121,7 @@ void Object::Instance::setTemplate(TerrainId terrain) auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrain); if (templates.empty()) { - auto terrainName = VLC->terrainTypeHandler->getById(terrain)->identifier; + auto terrainName = VLC->terrainTypeHandler->getById(terrain)->getName(); throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.subID % terrainName)); } dObject.appearance = templates.front(); @@ -293,14 +293,14 @@ void Object::Instance::finalize(RmgMap & map) if (!dObject.appearance) { auto terrainType = map.map().getTile(getPosition(true)).terType; - auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrainType->id); + auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrainType->getId()); if (templates.empty()) { throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") % dObject.ID % dObject.subID % getPosition(true).toString() % terrainType)); } else { - setTemplate(terrainType->id); + setTemplate(terrainType->getId()); } } diff --git a/lib/rmg/WaterProxy.cpp b/lib/rmg/WaterProxy.cpp index d61913a63..1bb018862 100644 --- a/lib/rmg/WaterProxy.cpp +++ b/lib/rmg/WaterProxy.cpp @@ -44,7 +44,7 @@ void WaterProxy::process() { MAYBE_UNUSED(t); assert(map.isOnMap(t)); - assert(map.map().getTile(t).terType->id == zone.getTerrainType()); + assert(map.map().getTile(t).terType->getId() == zone.getTerrainType()); } for(auto z : map.getZones()) @@ -54,7 +54,7 @@ void WaterProxy::process() for(auto & t : z.second->area().getTilesVector()) { - if(map.map().getTile(t).terType->id == zone.getTerrainType()) + if(map.map().getTile(t).terType->getId() == zone.getTerrainType()) { z.second->areaPossible().erase(t); z.second->area().erase(t); diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index dbe073c6f..07ae74b91 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -545,12 +545,12 @@ void MainWindow::loadObjectsTree() //adding terrains for(auto & terrain : VLC->terrainTypeHandler->objects) { - QPushButton *b = new QPushButton(QString::fromStdString(terrain->identifier)); + QPushButton *b = new QPushButton(QString::fromStdString(terrain->getName())); ui->terrainLayout->addWidget(b); - connect(b, &QPushButton::clicked, this, [this, terrain]{ terrainButtonClicked(terrain->id); }); + connect(b, &QPushButton::clicked, this, [this, terrain]{ terrainButtonClicked(terrain->getId()); }); //filter - ui->terrainFilterCombo->addItem(QString::fromStdString(terrain->identifier)); + ui->terrainFilterCombo->addItem(QString::fromStdString(terrain->getName())); } //add spacer to keep terrain button on the top ui->terrainLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); @@ -559,7 +559,7 @@ void MainWindow::loadObjectsTree() { QPushButton *b = new QPushButton(QString::fromStdString(road->tilesFilename)); ui->roadLayout->addWidget(b); - connect(b, &QPushButton::clicked, this, [this, road]{ roadOrRiverButtonClicked(road->id.getNum(), true); }); + connect(b, &QPushButton::clicked, this, [this, road]{ roadOrRiverButtonClicked(road->getIndex(), true); }); } //add spacer to keep terrain button on the top ui->roadLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); @@ -568,7 +568,7 @@ void MainWindow::loadObjectsTree() { QPushButton *b = new QPushButton(QString::fromStdString(river->tilesFilename)); ui->riverLayout->addWidget(b); - connect(b, &QPushButton::clicked, this, [this, river]{ roadOrRiverButtonClicked(river->id.getNum(), false); }); + connect(b, &QPushButton::clicked, this, [this, river]{ roadOrRiverButtonClicked(river->getIndex(), false); }); } //add spacer to keep terrain button on the top ui->riverLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); @@ -919,8 +919,8 @@ void MainWindow::on_terrainFilterCombo_currentTextChanged(const QString &arg1) if (!arg1.isEmpty()) { for (auto const & terrain : VLC->terrainTypeHandler->objects) - if (terrain->identifier == arg1.toStdString()) - objectBrowser->terrain = terrain->id; + if (terrain->getName() == arg1.toStdString()) + objectBrowser->terrain = terrain->getId(); } objectBrowser->invalidate(); objectBrowser->sort(0); @@ -1113,7 +1113,7 @@ void MainWindow::on_actionUpdate_appearance_triggered() if(handler->isStaticObject()) { staticObjects.insert(obj); - if(obj->appearance->canBePlacedAt(terrain->id)) + if(obj->appearance->canBePlacedAt(terrain->getId())) { controller.scene(mapLevel)->selectionObjectsView.deselectObject(obj); continue; @@ -1124,13 +1124,13 @@ void MainWindow::on_actionUpdate_appearance_triggered() } else { - auto app = handler->getOverride(terrain->id, obj); + auto app = handler->getOverride(terrain->getId(), obj); if(!app) { - if(obj->appearance->canBePlacedAt(terrain->id)) + if(obj->appearance->canBePlacedAt(terrain->getId())) continue; - auto templates = handler->getTemplates(terrain->id); + auto templates = handler->getTemplates(terrain->getId()); if(templates.empty()) { ++errors; diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index b94634334..2a7150f84 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -408,7 +408,7 @@ void MapController::commitObstacleFill(int level) if(tl.blocked || tl.visitable) continue; - terrainSelected[tl.terType->id].blockedArea.add(t); + terrainSelected[tl.terType->getId()].blockedArea.add(t); } for(auto & sel : terrainSelected) diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp index 7f9beaa5c..2fd76d587 100644 --- a/mapeditor/maphandler.cpp +++ b/mapeditor/maphandler.cpp @@ -80,7 +80,7 @@ void MapHandler::initTerrainGraphics() std::map riverFiles; for(const auto & terrain : VLC->terrainTypeHandler->objects) { - terrainFiles[terrain->identifier] = terrain->tilesFilename; + terrainFiles[terrain->getName()] = terrain->tilesFilename; } for(const auto & river : VLC->riverTypeHandler->objects) { @@ -101,7 +101,7 @@ void MapHandler::drawTerrainTile(QPainter & painter, int x, int y, int z) auto & tinfo = map->getTile(int3(x, y, z)); ui8 rotation = tinfo.extTileFlags % 4; - auto terrainName = tinfo.terType->identifier; + auto terrainName = tinfo.terType->getName(); if(terrainImages.at(terrainName).size() <= tinfo.terView) return; @@ -115,7 +115,7 @@ void MapHandler::drawRoad(QPainter & painter, int x, int y, int z) auto & tinfo = map->getTile(int3(x, y, z)); auto * tinfoUpper = map->isInTheMap(int3(x, y - 1, z)) ? &map->getTile(int3(x, y - 1, z)) : nullptr; - if(tinfoUpper && tinfoUpper->roadType->id != Road::NO_ROAD) + if(tinfoUpper && tinfoUpper->roadType->getId() != Road::NO_ROAD) { auto roadName = tinfoUpper->roadType->tilesFilename; QRect source(0, tileSize / 2, tileSize, tileSize / 2); @@ -127,7 +127,7 @@ void MapHandler::drawRoad(QPainter & painter, int x, int y, int z) } } - if(tinfo.roadType->id != Road::NO_ROAD) //print road from this tile + if(tinfo.roadType->getId() != Road::NO_ROAD) //print road from this tile { auto roadName = tinfo.roadType->tilesFilename; QRect source(0, 0, tileSize, tileSize / 2); @@ -144,7 +144,7 @@ void MapHandler::drawRiver(QPainter & painter, int x, int y, int z) { auto & tinfo = map->getTile(int3(x, y, z)); - if(tinfo.riverType->id == River::NO_RIVER) + if(tinfo.riverType->getId() == River::NO_RIVER) return; //TODO: use ui8 instead of string key diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 3ce07f687..0d26288e8 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2242,7 +2242,7 @@ void CGameHandler::setupBattle(int3 tile, const CArmedInstance *armies[2], const battleResult.set(nullptr); const auto & t = *getTile(tile); - TerrainId terrain = t.terType->id; + TerrainId terrain = t.terType->getId(); if (gs->map->isCoastalTile(tile)) //coastal tile is always ground terrain = ETerrainId::SAND; From 009705e794318cc9ab5fad3128fe2abaf64734da Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 1 Jan 2023 18:05:09 +0200 Subject: [PATCH 064/197] Names for terrains now pass through translator --- client/mapHandler.cpp | 3 +-- lib/Terrain.cpp | 28 ++++++++++++++-------------- lib/Terrain.h | 2 ++ 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 712711ac8..3d7b08d4b 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -1390,7 +1390,7 @@ void CMapHandler::getTerrainDescr(const int3 & pos, std::string & out, bool isRM } if(!isTile2Terrain || out.empty()) - out = t.terType->getName(); + out = t.terType->getNameTranslated(); if(t.getDiggingStatus(false) == EDiggingStatus::CAN_DIG) { @@ -1486,4 +1486,3 @@ TerrainTileObject::TerrainTileObject(const CGObjectInstance * obj_, SDL_Rect rec TerrainTileObject::~TerrainTileObject() { } - diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index 62ba2cb9a..a01199848 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -20,25 +20,13 @@ VCMI_LIB_NAMESPACE_BEGIN //("allowedTerrain"\s*:\s*\[.*)9(.*\],\n) //\1"rock"\2 -/* -TerrainTypeHandler::TerrainTypeHandler() -{ - auto allConfigs = VLC->modh->getActiveMods(); - allConfigs.insert(allConfigs.begin(), CModHandler::scopeBuiltin()); - - initRivers(allConfigs); - recreateRiverMaps(); - initRoads(allConfigs); - recreateRoadMaps(); - initTerrains(allConfigs); //maps will be populated inside -}*/ - TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) { TerrainType * info = new TerrainType; info->id = TerrainId(index); info->identifier = identifier; + info->modScope = scope; info->moveCost = static_cast(json["moveCost"].Integer()); info->musicFilename = json["music"].String(); @@ -47,7 +35,8 @@ TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const info->horseSoundPenalty = json["horseSoundPenalty"].String(); info->transitionRequired = json["transitionRequired"].Bool(); info->terrainViewPatterns = json["terrainViewPatterns"].String(); - //info->nameTranslated = json["nameTranslated"].String(); + + VLC->generaltexth->registerString(info->getNameTextID(), json["text"].String()); const JsonVector & unblockedVec = json["minimapUnblocked"].Vector(); info->minimapUnblocked = @@ -266,6 +255,17 @@ bool TerrainType::isTransitionRequired() const return transitionRequired; } +std::string TerrainType::getNameTextID() const +{ + TextIdentifier id{ "terrain", modScope, identifier, "name" }; + return id.get(); +} + +std::string TerrainType::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + TerrainType::TerrainType() {} diff --git a/lib/Terrain.h b/lib/Terrain.h index fa410a32d..f10ef2d59 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -22,6 +22,7 @@ VCMI_LIB_NAMESPACE_BEGIN class DLL_LINKAGE TerrainType : public EntityT { friend class TerrainTypeHandler; + std::string modScope; std::string identifier; TerrainId id; ui8 passabilityType; @@ -79,6 +80,7 @@ public: h & prohibitTransitions; h & minimapBlocked; h & minimapUnblocked; + h & modScope; h & identifier; h & musicFilename; h & tilesFilename; From f51b3bca5721ac45ea01c02d16c7e8d31ff3decf Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 1 Jan 2023 22:42:28 +0200 Subject: [PATCH 065/197] use JsonKey or NameTranslated instead of ambiguos Name --- client/CMusicHandler.cpp | 2 +- client/CPlayerInterface.cpp | 2 +- client/battle/BattleInterface.cpp | 2 +- client/lobby/RandomMapTab.cpp | 8 ++++---- client/mapHandler.cpp | 14 +++++++------- client/windows/CAdvmapInterface.cpp | 2 +- lib/HeroBonus.cpp | 4 ++-- lib/Terrain.h | 6 +++--- lib/mapObjects/CObjectHandler.cpp | 2 +- lib/mapObjects/ObjectTemplate.cpp | 2 +- lib/rmg/CRmgTemplate.cpp | 4 ++-- lib/rmg/RmgObject.cpp | 2 +- mapeditor/mainwindow.cpp | 6 +++--- mapeditor/maphandler.cpp | 14 +++++++------- 14 files changed, 35 insertions(+), 35 deletions(-) diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index a2cee5ca8..1e5d64d19 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -340,7 +340,7 @@ void CMusicHandler::loadTerrainMusicThemes() { for (const auto & terrain : CGI->terrainTypeHandler->objects) { - addEntryToSet("terrain_" + terrain->getName(), "Music/" + terrain->musicFilename); + addEntryToSet("terrain_" + terrain->getJsonKey(), "Music/" + terrain->musicFilename); } } diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 5b77d2c48..e1caafcbf 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -259,7 +259,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose) { updateAmbientSounds(); //We may need to change music - select new track, music handler will change it if needed - CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->getName(), true, false); + CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->getJsonKey(), true, false); if(details.result == TryMoveHero::TELEPORTATION) { diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index 78977790d..4fb6492ea 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -137,7 +137,7 @@ BattleInterface::~BattleInterface() { //FIXME: this should be moved to adventureInt which should restore correct track based on selection/active player const auto * terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType; - CCS->musich->playMusicFromSet("terrain", terrain->getName(), true, false); + CCS->musich->playMusicFromSet("terrain", terrain->getJsonKey(), true, false); } // may happen if user decided to close game while in battle diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index ac183ca38..a2b4c03fd 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -110,10 +110,10 @@ RandomMapTab::RandomMapTab(): for(auto road : VLC->roadTypeHandler->objects) { - std::string cbRoadType = "selectRoad_" + road->getName(); + std::string cbRoadType = "selectRoad_" + road->getJsonKey(); addCallback(cbRoadType, [&, road](bool on) { - mapGenOptions->setRoadEnabled(road->getName(), on); + mapGenOptions->setRoadEnabled(road->getJsonKey(), on); updateMapInfoByHost(); }); } @@ -285,9 +285,9 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr opts) } for(auto r : VLC->roadTypeHandler->objects) { - if(auto w = widget(r->getName())) + if(auto w = widget(r->getJsonKey())) { - w->setSelected(opts->isRoadEnabled(r->getName())); + w->setSelected(opts->isRoadEnabled(r->getJsonKey())); } } } diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 3d7b08d4b..23010e47a 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -177,15 +177,15 @@ void CMapHandler::initTerrainGraphics() std::map roadFiles; for(const auto & terrain : VLC->terrainTypeHandler->objects) { - terrainFiles[terrain->getName()] = terrain->tilesFilename; + terrainFiles[terrain->getJsonKey()] = terrain->tilesFilename; } for(const auto & river : VLC->riverTypeHandler->objects) { - riverFiles[river->tilesFilename] = river->tilesFilename; + riverFiles[river->getJsonKey()] = river->tilesFilename; } for(const auto & road : VLC->roadTypeHandler->objects) { - roadFiles[road->tilesFilename] = road->tilesFilename; + roadFiles[road->getJsonKey()] = road->tilesFilename; } loadFlipped(terrainAnimations, terrainImages, terrainFiles); @@ -606,7 +606,7 @@ void CMapHandler::CMapBlitter::drawTileTerrain(SDL_Surface * targetSurf, const T ui8 rotation = tinfo.extTileFlags % 4; //TODO: use ui8 instead of string key - auto terrainName = tinfo.terType->getName(); + auto terrainName = tinfo.terType->getJsonKey(); if(parent->terrainImages[terrainName].size()<=tinfo.terView) return; @@ -791,7 +791,7 @@ void CMapHandler::CMapBlitter::drawRoad(SDL_Surface * targetSurf, const TerrainT ui8 rotation = (tinfoUpper->extTileFlags >> 4) % 4; Rect source(0, tileSize / 2, tileSize, tileSize / 2); Rect dest(realPos.x, realPos.y, tileSize, tileSize / 2); - drawElement(EMapCacheType::ROADS, parent->roadImages[tinfoUpper->roadType->tilesFilename][tinfoUpper->roadDir][rotation], + drawElement(EMapCacheType::ROADS, parent->roadImages[tinfoUpper->roadType->getJsonKey()][tinfoUpper->roadDir][rotation], &source, targetSurf, &dest); } @@ -800,7 +800,7 @@ void CMapHandler::CMapBlitter::drawRoad(SDL_Surface * targetSurf, const TerrainT ui8 rotation = (tinfo.extTileFlags >> 4) % 4; Rect source(0, 0, tileSize, halfTileSizeCeil); Rect dest(realPos.x, realPos.y + tileSize / 2, tileSize, tileSize / 2); - drawElement(EMapCacheType::ROADS, parent->roadImages[tinfo.roadType->tilesFilename][tinfo.roadDir][rotation], + drawElement(EMapCacheType::ROADS, parent->roadImages[tinfo.roadType->getJsonKey()][tinfo.roadDir][rotation], &source, targetSurf, &dest); } } @@ -809,7 +809,7 @@ void CMapHandler::CMapBlitter::drawRiver(SDL_Surface * targetSurf, const Terrain { Rect destRect(realTileRect); ui8 rotation = (tinfo.extTileFlags >> 2) % 4; - drawElement(EMapCacheType::RIVERS, parent->riverImages[tinfo.riverType->tilesFilename][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect); + drawElement(EMapCacheType::RIVERS, parent->riverImages[tinfo.riverType->getJsonKey()][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect); } void CMapHandler::CMapBlitter::drawFow(SDL_Surface * targetSurf) const diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 4ae73a39c..e21b0f478 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -1414,7 +1414,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView) auto pos = sel->visitablePos(); auto tile = LOCPLINT->cb->getTile(pos); if(tile) - CCS->musich->playMusicFromSet("terrain", tile->terType->getName(), true, false); + CCS->musich->playMusicFromSet("terrain", tile->terType->getJsonKey(), true, false); } if(centerView) centerOn(sel); diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 09a1e7499..48b47916f 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -2136,7 +2136,7 @@ int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const std::string CreatureTerrainLimiter::toString() const { boost::format fmt("CreatureTerrainLimiter(terrainType=%s)"); - auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getName(); + auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey(); fmt % (terrainType == ETerrainId::NATIVE_TERRAIN ? "native" : terrainName); return fmt.str(); } @@ -2146,7 +2146,7 @@ JsonNode CreatureTerrainLimiter::toJsonNode() const JsonNode root(JsonNode::JsonType::DATA_STRUCT); root["type"].String() = "CREATURE_TERRAIN_LIMITER"; - auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getName(); + auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey(); root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainName)); return root; diff --git a/lib/Terrain.h b/lib/Terrain.h index f10ef2d59..5f65b42a5 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -27,10 +27,10 @@ class DLL_LINKAGE TerrainType : public EntityT TerrainId id; ui8 passabilityType; + const std::string & getName() const override { return identifier;} public: int32_t getIndex() const override { return id.getNum(); } int32_t getIconIndex() const override { return 0; } - const std::string & getName() const override { return identifier;} const std::string & getJsonKey() const override { return identifier;} void registerIcons(const IconRegistar & cb) const override {} TerrainId getId() const override { return id;} @@ -104,10 +104,10 @@ class DLL_LINKAGE RiverType : public EntityT std::string identifier; RiverId id; + const std::string & getName() const override { return identifier;} public: int32_t getIndex() const override { return id.getNum(); } int32_t getIconIndex() const override { return 0; } - const std::string & getName() const override { return identifier;} const std::string & getJsonKey() const override { return identifier;} void registerIcons(const IconRegistar & cb) const override {} RiverId getId() const override { return id;} @@ -136,10 +136,10 @@ class DLL_LINKAGE RoadType : public EntityT std::string identifier; RoadId id; + const std::string & getName() const override { return identifier;} public: int32_t getIndex() const override { return id.getNum(); } int32_t getIconIndex() const override { return 0; } - const std::string & getName() const override { return identifier;} const std::string & getJsonKey() const override { return identifier;} void registerIcons(const IconRegistar & cb) const override {} RoadId getId() const override { return id;} diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index 2562a6d00..2f6614c23 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -213,7 +213,7 @@ void CGObjectInstance::setType(si32 ID, si32 subID) } else { - logGlobal->warn("Object %d:%d at %s has no templates suitable for terrain %s", ID, subID, visitablePos().toString(), tile.terType->getName()); + logGlobal->warn("Object %d:%d at %s has no templates suitable for terrain %s", ID, subID, visitablePos().toString(), tile.terType->getNameTranslated()); appearance = handler->getTemplates()[0]; // get at least some appearance since alternative is crash } diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 05ea4395e..52f5aa413 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -360,7 +360,7 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const for(auto type : allowedTerrains) { JsonNode value(JsonNode::JsonType::DATA_STRING); - value.String() = VLC->terrainTypeHandler->getById(type)->getName(); + value.String() = VLC->terrainTypeHandler->getById(type)->getJsonKey(); data.push_back(value); } } diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 8cacb0762..d638b522b 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -75,7 +75,7 @@ public: static std::string encode(const si32 index) { - return VLC->terrainTypeHandler->getByIndex(index)->getName(); + return VLC->terrainTypeHandler->getByIndex(index)->getJsonKey(); } }; @@ -365,7 +365,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) for(auto & ttype : terrainTypes) { JsonNode n; - n.String() = VLC->terrainTypeHandler->getById(ttype)->getName(); + n.String() = VLC->terrainTypeHandler->getById(ttype)->getJsonKey(); node.Vector().push_back(n); } } diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index a4251e684..6245dd264 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -121,7 +121,7 @@ void Object::Instance::setTemplate(TerrainId terrain) auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrain); if (templates.empty()) { - auto terrainName = VLC->terrainTypeHandler->getById(terrain)->getName(); + auto terrainName = VLC->terrainTypeHandler->getById(terrain)->getNameTranslated(); throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.subID % terrainName)); } dObject.appearance = templates.front(); diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 07ae74b91..76eca56a0 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -545,12 +545,12 @@ void MainWindow::loadObjectsTree() //adding terrains for(auto & terrain : VLC->terrainTypeHandler->objects) { - QPushButton *b = new QPushButton(QString::fromStdString(terrain->getName())); + QPushButton *b = new QPushButton(QString::fromStdString(terrain->getNameTranslated())); ui->terrainLayout->addWidget(b); connect(b, &QPushButton::clicked, this, [this, terrain]{ terrainButtonClicked(terrain->getId()); }); //filter - ui->terrainFilterCombo->addItem(QString::fromStdString(terrain->getName())); + ui->terrainFilterCombo->addItem(QString::fromStdString(terrain->getNameTranslated())); } //add spacer to keep terrain button on the top ui->terrainLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); @@ -919,7 +919,7 @@ void MainWindow::on_terrainFilterCombo_currentTextChanged(const QString &arg1) if (!arg1.isEmpty()) { for (auto const & terrain : VLC->terrainTypeHandler->objects) - if (terrain->getName() == arg1.toStdString()) + if (terrain->getJsonKey() == arg1.toStdString()) objectBrowser->terrain = terrain->getId(); } objectBrowser->invalidate(); diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp index 2fd76d587..4e71d1fff 100644 --- a/mapeditor/maphandler.cpp +++ b/mapeditor/maphandler.cpp @@ -80,15 +80,15 @@ void MapHandler::initTerrainGraphics() std::map riverFiles; for(const auto & terrain : VLC->terrainTypeHandler->objects) { - terrainFiles[terrain->getName()] = terrain->tilesFilename; + terrainFiles[terrain->getJsonKey()] = terrain->tilesFilename; } for(const auto & river : VLC->riverTypeHandler->objects) { - riverFiles[river->tilesFilename] = river->tilesFilename; + riverFiles[river->getJsonKey()] = river->tilesFilename; } for(const auto & road : VLC->roadTypeHandler->objects) { - roadFiles[road->tilesFilename] = road->tilesFilename; + roadFiles[road->getJsonKey()] = road->tilesFilename; } loadFlipped(terrainAnimations, terrainImages, terrainFiles); @@ -101,7 +101,7 @@ void MapHandler::drawTerrainTile(QPainter & painter, int x, int y, int z) auto & tinfo = map->getTile(int3(x, y, z)); ui8 rotation = tinfo.extTileFlags % 4; - auto terrainName = tinfo.terType->getName(); + auto terrainName = tinfo.terType->getJsonKey(); if(terrainImages.at(terrainName).size() <= tinfo.terView) return; @@ -117,7 +117,7 @@ void MapHandler::drawRoad(QPainter & painter, int x, int y, int z) if(tinfoUpper && tinfoUpper->roadType->getId() != Road::NO_ROAD) { - auto roadName = tinfoUpper->roadType->tilesFilename; + auto roadName = tinfoUpper->roadType->getJsonKey(); QRect source(0, tileSize / 2, tileSize, tileSize / 2); ui8 rotation = (tinfoUpper->extTileFlags >> 4) % 4; bool hflip = (rotation == 1 || rotation == 3), vflip = (rotation == 2 || rotation == 3); @@ -129,7 +129,7 @@ void MapHandler::drawRoad(QPainter & painter, int x, int y, int z) if(tinfo.roadType->getId() != Road::NO_ROAD) //print road from this tile { - auto roadName = tinfo.roadType->tilesFilename; + auto roadName = tinfo.roadType->getJsonKey();; QRect source(0, 0, tileSize, tileSize / 2); ui8 rotation = (tinfo.extTileFlags >> 4) % 4; bool hflip = (rotation == 1 || rotation == 3), vflip = (rotation == 2 || rotation == 3); @@ -148,7 +148,7 @@ void MapHandler::drawRiver(QPainter & painter, int x, int y, int z) return; //TODO: use ui8 instead of string key - auto riverName = tinfo.riverType->tilesFilename; + auto riverName = tinfo.riverType->getJsonKey(); if(riverImages.at(riverName).size() <= tinfo.riverDir) return; From 4f3ea0d1d9995daf723364869f09dd884a43c6ac Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 9 Jan 2023 01:17:37 +0200 Subject: [PATCH 066/197] Renamed Terrain.h/cpp -> TerrainHandler.h/cpp --- client/CMusicHandler.cpp | 2 +- client/CMusicHandler.h | 1 - client/CPlayerInterface.cpp | 1 + client/battle/BattleInterface.cpp | 1 + client/lobby/RandomMapTab.cpp | 1 + client/mapHandler.cpp | 2 +- client/widgets/AdventureMapClasses.cpp | 2 +- client/widgets/AdventureMapClasses.h | 1 - client/windows/CAdvmapInterface.cpp | 1 + cmake_modules/VCMI_lib.cmake | 4 ++-- lib/BattleFieldHandler.h | 1 - lib/CCreatureHandler.cpp | 2 +- lib/CCreatureHandler.h | 1 - lib/CGameInfoCallback.cpp | 1 + lib/CGameState.cpp | 1 + lib/CGeneralTextHandler.cpp | 2 +- lib/CHeroHandler.cpp | 1 - lib/CHeroHandler.h | 1 - lib/CModHandler.cpp | 3 ++- lib/CPathfinder.cpp | 1 + lib/CPathfinder.h | 1 - lib/CStack.h | 1 - lib/CTownHandler.h | 1 - lib/HeroBonus.cpp | 1 + lib/HeroBonus.h | 1 - lib/IGameCallback.cpp | 1 + lib/NetPacksLib.cpp | 1 + lib/ObstacleHandler.h | 1 - lib/PathfinderUtil.h | 2 ++ lib/{Terrain.cpp => TerrainHandler.cpp} | 29 ++++++++++++++++++++++--- lib/{Terrain.h => TerrainHandler.h} | 5 ++++- lib/VCMI_Lib.cpp | 1 + lib/battle/BattleInfo.cpp | 2 +- lib/battle/BattleProxy.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 1 + lib/mapObjects/CGTownInstance.cpp | 1 + lib/mapObjects/CObjectClassesHandler.h | 1 - lib/mapObjects/CObjectHandler.cpp | 1 + lib/mapObjects/CommonConstructors.cpp | 1 + lib/mapObjects/ObjectTemplate.cpp | 2 +- lib/mapping/CDrawRoadsOperation.cpp | 2 ++ lib/mapping/CMap.cpp | 1 + lib/mapping/CMapDefines.h | 4 ++++ lib/mapping/CMapEditManager.h | 1 - lib/mapping/CMapOperation.cpp | 1 + lib/mapping/MapEditUtils.cpp | 1 + lib/mapping/MapEditUtils.h | 2 +- lib/mapping/MapFormatH3M.cpp | 1 + lib/mapping/MapFormatJson.cpp | 1 + lib/registerTypes/TypesLobbyPacks.cpp | 1 + lib/rmg/CRmgTemplate.cpp | 2 +- lib/rmg/CRmgTemplate.h | 2 +- lib/rmg/CZonePlacer.cpp | 1 + lib/rmg/ConnectionsPlacer.cpp | 1 + lib/rmg/Functions.cpp | 1 + lib/rmg/RiverPlacer.cpp | 1 + lib/rmg/RmgObject.cpp | 1 + lib/rmg/RockPlacer.cpp | 1 + lib/rmg/TileInfo.h | 2 +- lib/rmg/WaterProxy.cpp | 1 + mapeditor/mainwindow.cpp | 2 +- mapeditor/mainwindow.h | 2 +- mapeditor/mapcontroller.cpp | 2 +- mapeditor/mapcontroller.h | 2 +- mapeditor/maphandler.cpp | 1 + mapeditor/objectbrowser.cpp | 1 + mapeditor/objectbrowser.h | 2 +- server/CGameHandler.cpp | 1 + 68 files changed, 90 insertions(+), 37 deletions(-) rename lib/{Terrain.cpp => TerrainHandler.cpp} (90%) rename lib/{Terrain.h => TerrainHandler.h} (98%) diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index 1e5d64d19..009ff0219 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -20,7 +20,7 @@ #include "../lib/StringConstants.h" #include "../lib/CRandomGenerator.h" #include "../lib/VCMIDirs.h" -#include "../lib/Terrain.h" +#include "../lib/TerrainHandler.h" #define VCMI_SOUND_NAME(x) #define VCMI_SOUND_FILE(y) #y, diff --git a/client/CMusicHandler.h b/client/CMusicHandler.h index 6c9977b4c..33fcc6b3b 100644 --- a/client/CMusicHandler.h +++ b/client/CMusicHandler.h @@ -11,7 +11,6 @@ #include "../lib/CConfigHandler.h" #include "../lib/CSoundBase.h" -#include "../lib/Terrain.h" struct _Mix_Music; struct SDL_RWops; diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index e1caafcbf..b0bf7bbea 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -61,6 +61,7 @@ #include "windows/InfoWindows.h" #include "../lib/UnlockGuard.h" #include "../lib/CPathfinder.h" +#include "../lib/TerrainHandler.h" #include #include "CServerHandler.h" // FIXME: only needed for CGameState::mutex diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index 4fb6492ea..c95553da2 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -41,6 +41,7 @@ #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/NetPacks.h" #include "../../lib/UnlockGuard.h" +#include "../../lib/TerrainHandler.h" CondSh BattleInterface::givenCommand(nullptr); diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index a2b4c03fd..e1c1d6bda 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -29,6 +29,7 @@ #include "../../lib/rmg/CMapGenOptions.h" #include "../../lib/CModHandler.h" #include "../../lib/rmg/CRmgTemplateStorage.h" +#include "../../lib/TerrainHandler.h" RandomMapTab::RandomMapTab(): InterfaceObjectConfigurable() diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 23010e47a..54211d493 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -30,7 +30,7 @@ #include "CMT.h" #include "CMusicHandler.h" #include "../lib/CRandomGenerator.h" -#include "../lib/Terrain.h" +#include "../lib/TerrainHandler.h" #include "../lib/filesystem/ResourceID.h" #include "../lib/JsonDetail.h" diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index b5e50dff1..53f6ca175 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -41,7 +41,7 @@ #include "../../lib/CHeroHandler.h" #include "../../lib/CModHandler.h" #include "../../lib/CTownHandler.h" -#include "../../lib/Terrain.h" +#include "../../lib/TerrainHandler.h" #include "../../lib/filesystem/Filesystem.h" #include "../../lib/JsonNode.h" #include "../../lib/mapObjects/CGHeroInstance.h" diff --git a/client/widgets/AdventureMapClasses.h b/client/widgets/AdventureMapClasses.h index 1c7ef5b50..b5a1b60b6 100644 --- a/client/widgets/AdventureMapClasses.h +++ b/client/widgets/AdventureMapClasses.h @@ -11,7 +11,6 @@ #include "ObjectLists.h" #include "../../lib/FunctionList.h" -#include "Terrain.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index e21b0f478..ca06de547 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -53,6 +53,7 @@ #include "../../lib/VCMI_Lib.h" #include "../../lib/StartInfo.h" #include "../../lib/mapping/CMapInfo.h" +#include "../../lib/TerrainHandler.h" #define ADVOPT (conf.go()->ac) using namespace CSDL_Ext; diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 0c3266f48..eea6ad26e 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -192,7 +192,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/StartInfo.cpp ${MAIN_LIB_DIR}/ResourceSet.cpp ${MAIN_LIB_DIR}/ScriptHandler.cpp - ${MAIN_LIB_DIR}/Terrain.cpp + ${MAIN_LIB_DIR}/TerrainHandler.cpp ${MAIN_LIB_DIR}/VCMIDirs.cpp ${MAIN_LIB_DIR}/VCMI_Lib.cpp @@ -443,7 +443,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/ScopeGuard.h ${MAIN_LIB_DIR}/StartInfo.h ${MAIN_LIB_DIR}/StringConstants.h - ${MAIN_LIB_DIR}/Terrain.h + ${MAIN_LIB_DIR}/TerrainHandler.h ${MAIN_LIB_DIR}/UnlockGuard.h ${MAIN_LIB_DIR}/VCMIDirs.h ${MAIN_LIB_DIR}/vcmi_endian.h diff --git a/lib/BattleFieldHandler.h b/lib/BattleFieldHandler.h index 51822e0cc..3bfd2440e 100644 --- a/lib/BattleFieldHandler.h +++ b/lib/BattleFieldHandler.h @@ -14,7 +14,6 @@ #include "HeroBonus.h" #include "GameConstants.h" #include "IHandlerBase.h" -#include "Terrain.h" #include "battle/BattleHex.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 24c51fadc..0f1e1f696 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -16,7 +16,7 @@ #include "CGameState.h" #include "CTownHandler.h" #include "CModHandler.h" -#include "Terrain.h" +#include "TerrainHandler.h" #include "StringConstants.h" #include "serializer/JsonDeserializer.h" #include "serializer/JsonUpdater.h" diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index c621d617e..0912dd124 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -19,7 +19,6 @@ #include "JsonNode.h" #include "IHandlerBase.h" #include "CRandomGenerator.h" -#include "Terrain.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 21de886dd..c284852a8 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -17,6 +17,7 @@ #include "battle/BattleInfo.h" // for BattleInfo #include "NetPacks.h" // for InfoWindow #include "CModHandler.h" +#include "TerrainHandler.h" #include "spells/CSpellHandler.h" #include "mapping/CMap.h" #include "CPlayerState.h" diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 1cca708a7..e8a268954 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -20,6 +20,7 @@ #include "CHeroHandler.h" #include "mapObjects/CObjectHandler.h" #include "CModHandler.h" +#include "TerrainHandler.h" #include "CSkillHandler.h" #include "mapping/CMap.h" #include "mapping/CMapService.h" diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 923b9a91c..cf7f348a1 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -18,7 +18,7 @@ #include "GameConstants.h" #include "mapObjects/CQuest.h" #include "VCMI_Lib.h" -#include "Terrain.h" +#include "TerrainHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 155d0b993..469acafa9 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -19,7 +19,6 @@ #include "CCreatureHandler.h" #include "CModHandler.h" #include "CTownHandler.h" -#include "Terrain.h" #include "mapObjects/CObjectHandler.h" //for hero specialty #include "CSkillHandler.h" #include diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 6f9e8be68..93e4f0f87 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -18,7 +18,6 @@ #include "GameConstants.h" #include "HeroBonus.h" #include "IHandlerBase.h" -#include "Terrain.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 64ab15bd5..a694714da 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -25,6 +25,7 @@ #include "spells/CSpellHandler.h" #include "CSkillHandler.h" #include "ScriptHandler.h" +#include "TerrainHandler.h" #include "BattleFieldHandler.h" #include "ObstacleHandler.h" @@ -526,7 +527,7 @@ void CContentHandler::preloadData(CModInfo & mod) void CContentHandler::load(CModInfo & mod) { - bool validate = false;//(mod.validation != CModInfo::PASSED); + bool validate = (mod.validation != CModInfo::PASSED); if (!loadMod(mod.identifier, validate)) mod.validation = CModInfo::FAILED; diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 13d5500ae..1e4cf67c2 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -18,6 +18,7 @@ #include "CStopWatch.h" #include "CConfigHandler.h" #include "CPlayerState.h" +#include "TerrainHandler.h" #include "PathfinderUtil.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 01cc372b4..c2d032988 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -13,7 +13,6 @@ #include "IGameCallback.h" #include "HeroBonus.h" #include "int3.h" -#include "Terrain.h" #include diff --git a/lib/CStack.h b/lib/CStack.h index fa31a3d03..a3805543e 100644 --- a/lib/CStack.h +++ b/lib/CStack.h @@ -14,7 +14,6 @@ #include "CCreatureHandler.h" //todo: remove #include "battle/BattleHex.h" #include "mapObjects/CGHeroInstance.h" // for commander serialization -#include "Terrain.h" #include "battle/CUnitState.h" diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index d85f1465b..0a5443995 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -20,7 +20,6 @@ #include "LogicalExpression.h" #include "battle/BattleHex.h" #include "HeroBonus.h" -#include "Terrain.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 48b47916f..a69628092 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -22,6 +22,7 @@ #include "CStack.h" #include "CArtHandler.h" #include "CModHandler.h" +#include "TerrainHandler.h" #include "StringConstants.h" #include "battle/BattleInfo.h" diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index d120f8df9..b7a7f53d0 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -11,7 +11,6 @@ #include "GameConstants.h" #include "JsonNode.h" -#include "Terrain.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 8e4210d15..d485f69d2 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -32,6 +32,7 @@ #include "CPlayerState.h" #include "CSkillHandler.h" #include "ScriptHandler.h" +#include "TerrainHandler.h" #include "serializer/Connection.h" diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 3e0b78d00..5e71f301d 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -27,6 +27,7 @@ #include "mapping/CMapInfo.h" #include "StartInfo.h" #include "CPlayerState.h" +#include "TerrainHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/ObstacleHandler.h b/lib/ObstacleHandler.h index 77ed0a078..40fef6366 100644 --- a/lib/ObstacleHandler.h +++ b/lib/ObstacleHandler.h @@ -13,7 +13,6 @@ #include #include "GameConstants.h" #include "IHandlerBase.h" -#include "Terrain.h" #include "battle/BattleHex.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/PathfinderUtil.h b/lib/PathfinderUtil.h index 83e9025c2..d62a80c1b 100644 --- a/lib/PathfinderUtil.h +++ b/lib/PathfinderUtil.h @@ -9,6 +9,8 @@ */ #pragma once +#include "TerrainHandler.h" +#include "mapObjects/CObjectHandler.h" #include "mapping/CMapDefines.h" #include "CGameState.h" diff --git a/lib/Terrain.cpp b/lib/TerrainHandler.cpp similarity index 90% rename from lib/Terrain.cpp rename to lib/TerrainHandler.cpp index a01199848..c3f5ed196 100644 --- a/lib/Terrain.cpp +++ b/lib/TerrainHandler.cpp @@ -9,7 +9,7 @@ */ #include "StdInc.h" -#include "Terrain.h" +#include "TerrainHandler.h" #include "VCMI_Lib.h" #include "CModHandler.h" #include "CGeneralTextHandler.h" @@ -156,6 +156,8 @@ RiverType * RiverTypeHandler::loadFromJson( info->shortIdentifier = json["shortIdentifier"].String(); info->deltaName = json["delta"].String(); + VLC->generaltexth->registerString(info->getNameTextID(), json["text"].String()); + return info; } @@ -195,6 +197,8 @@ RoadType * RoadTypeHandler::loadFromJson( info->shortIdentifier = json["shortIdentifier"].String(); info->movementCost = json["moveCost"].Integer(); + VLC->generaltexth->registerString(info->getNameTextID(), json["text"].String()); + return info; } @@ -257,8 +261,7 @@ bool TerrainType::isTransitionRequired() const std::string TerrainType::getNameTextID() const { - TextIdentifier id{ "terrain", modScope, identifier, "name" }; - return id.get(); + return TextIdentifier( "terrain", modScope, identifier, "name" ).get(); } std::string TerrainType::getNameTranslated() const @@ -269,6 +272,26 @@ std::string TerrainType::getNameTranslated() const TerrainType::TerrainType() {} +std::string RoadType::getNameTextID() const +{ + return TextIdentifier( "terrain", identifier, "name" ).get(); +} + +std::string RoadType::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + +std::string RiverType::getNameTextID() const +{ + return TextIdentifier( "terrain", identifier, "name" ).get(); +} + +std::string RiverType::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + RiverType::RiverType(): id(River::NO_RIVER) {} diff --git a/lib/Terrain.h b/lib/TerrainHandler.h similarity index 98% rename from lib/Terrain.h rename to lib/TerrainHandler.h index 5f65b42a5..a4e15f6d2 100644 --- a/lib/Terrain.h +++ b/lib/TerrainHandler.h @@ -1,5 +1,5 @@ /* - * Terrain.h, part of VCMI engine + * TerrainHandler.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -144,6 +144,9 @@ public: void registerIcons(const IconRegistar & cb) const override {} RoadId getId() const override { return id;} + std::string getNameTextID() const; + std::string getNameTranslated() const; + std::string tilesFilename; std::string shortIdentifier; ui8 movementCost; diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 19d8785a6..a29883611 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -18,6 +18,7 @@ #include "CHeroHandler.h" #include "mapObjects/CObjectHandler.h" #include "CTownHandler.h" +#include "TerrainHandler.h" #include "CBuildingHandler.h" #include "spells/CSpellHandler.h" #include "spells/effects/Registry.h" diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index f1687ccef..da9675bce 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -15,7 +15,7 @@ #include "../filesystem/Filesystem.h" #include "../mapObjects/CGTownInstance.h" #include "../CGeneralTextHandler.h" -#include "../Terrain.h" +#include "../TerrainHandler.h" #include "../BattleFieldHandler.h" #include "../ObstacleHandler.h" diff --git a/lib/battle/BattleProxy.cpp b/lib/battle/BattleProxy.cpp index 0ebb475ba..ff2f3d9c9 100644 --- a/lib/battle/BattleProxy.cpp +++ b/lib/battle/BattleProxy.cpp @@ -10,7 +10,7 @@ #include "StdInc.h" #include "BattleProxy.h" #include "Unit.h" -#include "Terrain.h" +#include "TerrainHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 9355b0c5d..9ce71c4a9 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -17,6 +17,7 @@ #include "../NetPacks.h" #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" +#include "../TerrainHandler.h" #include "../CModHandler.h" #include "../CSoundBase.h" #include "../spells/CSpellHandler.h" diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 58530848f..65e8f85c6 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -20,6 +20,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CPlayerState.h" +#include "../TerrainHandler.h" #include "../serializer/JsonSerializeFormat.h" #include "../HeroBonus.h" diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index 9324b9696..a4a2eb910 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -15,7 +15,6 @@ #include "../ConstTransitivePtr.h" #include "../IHandlerBase.h" #include "../JsonNode.h" -#include "Terrain.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index 2f6614c23..1ac9b2675 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -20,6 +20,7 @@ #include "../CGameState.h" #include "../StringConstants.h" #include "../mapping/CMap.h" +#include "../TerrainHandler.h" #include "CObjectClassesHandler.h" #include "CGTownInstance.h" diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp index eae4f4fed..20f0ed114 100644 --- a/lib/mapObjects/CommonConstructors.cpp +++ b/lib/mapObjects/CommonConstructors.cpp @@ -13,6 +13,7 @@ #include "CGTownInstance.h" #include "CGHeroInstance.h" #include "CBank.h" +#include "../TerrainHandler.h" #include "../mapping/CMap.h" #include "../CHeroHandler.h" #include "../CCreatureHandler.h" diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 52f5aa413..8b124be38 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -19,7 +19,7 @@ #include "CObjectHandler.h" #include "../CModHandler.h" #include "../JsonNode.h" -#include "../Terrain.h" +#include "../TerrainHandler.h" #include "CRewardableConstructor.h" diff --git a/lib/mapping/CDrawRoadsOperation.cpp b/lib/mapping/CDrawRoadsOperation.cpp index cfc2a1a56..3a62d3c9d 100644 --- a/lib/mapping/CDrawRoadsOperation.cpp +++ b/lib/mapping/CDrawRoadsOperation.cpp @@ -12,6 +12,8 @@ #include "CDrawRoadsOperation.h" #include "CMap.h" +#include "../TerrainHandler.h" + VCMI_LIB_NAMESPACE_BEGIN const std::vector CDrawLinesOperation::patterns = diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 90ef2802b..6ace46fff 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -15,6 +15,7 @@ #include "../CCreatureHandler.h" #include "../CTownHandler.h" #include "../CHeroHandler.h" +#include "../TerrainHandler.h" #include "../mapObjects/CObjectClassesHandler.h" #include "../mapObjects/CGHeroInstance.h" #include "../CGeneralTextHandler.h" diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index f39e8eb27..ac0500820 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -12,6 +12,10 @@ VCMI_LIB_NAMESPACE_BEGIN +class TerrainType; +class RiverType; +class RoadType; + /// The map event is an event which e.g. gives or takes resources of a specific /// amount to/from players and can appear regularly or once a time. class DLL_LINKAGE CMapEvent diff --git a/lib/mapping/CMapEditManager.h b/lib/mapping/CMapEditManager.h index c8dcbabbc..04a52b39a 100644 --- a/lib/mapping/CMapEditManager.h +++ b/lib/mapping/CMapEditManager.h @@ -12,7 +12,6 @@ #include "../GameConstants.h" #include "CMapOperation.h" -#include "Terrain.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapping/CMapOperation.cpp b/lib/mapping/CMapOperation.cpp index 2db1f2302..46cf575b8 100644 --- a/lib/mapping/CMapOperation.cpp +++ b/lib/mapping/CMapOperation.cpp @@ -12,6 +12,7 @@ #include "CMapOperation.h" #include "../VCMI_Lib.h" +#include "../TerrainHandler.h" #include "CMap.h" #include "MapEditUtils.h" diff --git a/lib/mapping/MapEditUtils.cpp b/lib/mapping/MapEditUtils.cpp index e9dece7b5..a58e30ce7 100644 --- a/lib/mapping/MapEditUtils.cpp +++ b/lib/mapping/MapEditUtils.cpp @@ -13,6 +13,7 @@ #include "../filesystem/Filesystem.h" #include "../JsonNode.h" +#include "../TerrainHandler.h" #include "CMap.h" #include "CMapOperation.h" diff --git a/lib/mapping/MapEditUtils.h b/lib/mapping/MapEditUtils.h index 288cc7b5a..10ebe8e65 100644 --- a/lib/mapping/MapEditUtils.h +++ b/lib/mapping/MapEditUtils.h @@ -12,7 +12,7 @@ #include "../int3.h" #include "../CRandomGenerator.h" -#include "Terrain.h" +#include "../GameConstants.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 37ac20bf9..b785ec6d2 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -24,6 +24,7 @@ #include "../mapObjects/CObjectClassesHandler.h" #include "../mapObjects/MapObjects.h" #include "../VCMI_Lib.h" +#include "../TerrainHandler.h" #include "../NetPacksBase.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index be8727fdb..4b954ceda 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -19,6 +19,7 @@ #include "../CHeroHandler.h" #include "../CTownHandler.h" #include "../VCMI_Lib.h" +#include "../TerrainHandler.h" #include "../mapObjects/ObjectTemplate.h" #include "../mapObjects/CObjectHandler.h" #include "../mapObjects/CObjectClassesHandler.h" diff --git a/lib/registerTypes/TypesLobbyPacks.cpp b/lib/registerTypes/TypesLobbyPacks.cpp index 3a65eb231..2ab65eccf 100644 --- a/lib/registerTypes/TypesLobbyPacks.cpp +++ b/lib/registerTypes/TypesLobbyPacks.cpp @@ -22,6 +22,7 @@ #include "../CHeroHandler.h" #include "../spells/CSpellHandler.h" #include "../CTownHandler.h" +#include "../TerrainHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" #include "../mapObjects/CObjectClassesHandler.h" diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index d638b522b..474887fb9 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -17,7 +17,7 @@ #include "../VCMI_Lib.h" #include "../CTownHandler.h" #include "../CModHandler.h" -#include "../Terrain.h" +#include "../TerrainHandler.h" #include "../serializer/JsonSerializeFormat.h" #include "../StringConstants.h" diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 50ce3227f..7bbc98511 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -13,7 +13,7 @@ #include "../int3.h" #include "../GameConstants.h" #include "../ResourceSet.h" -#include "../Terrain.h" +//#include "../TerrainHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index d79319deb..0e10968af 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -11,6 +11,7 @@ #include "StdInc.h" #include "../CRandomGenerator.h" #include "CZonePlacer.h" +#include "../TerrainHandler.h" #include "../mapping/CMap.h" #include "../mapping/CMapEditManager.h" #include "CMapGenOptions.h" diff --git a/lib/rmg/ConnectionsPlacer.cpp b/lib/rmg/ConnectionsPlacer.cpp index 862fd0d60..93fc2e802 100644 --- a/lib/rmg/ConnectionsPlacer.cpp +++ b/lib/rmg/ConnectionsPlacer.cpp @@ -12,6 +12,7 @@ #include "ConnectionsPlacer.h" #include "CMapGenerator.h" #include "RmgMap.h" +#include "../TerrainHandler.h" #include "../mapping/CMap.h" #include "../mapping/CMapEditManager.h" #include "../mapObjects/CObjectClassesHandler.h" diff --git a/lib/rmg/Functions.cpp b/lib/rmg/Functions.cpp index 583d68861..226c2e10e 100644 --- a/lib/rmg/Functions.cpp +++ b/lib/rmg/Functions.cpp @@ -21,6 +21,7 @@ #include "RmgMap.h" #include "TileInfo.h" #include "RmgPath.h" +#include "../TerrainHandler.h" #include "../CTownHandler.h" #include "../mapping/CMapEditManager.h" #include "../mapping/CMap.h" diff --git a/lib/rmg/RiverPlacer.cpp b/lib/rmg/RiverPlacer.cpp index 51f24a4f8..38f937f65 100644 --- a/lib/rmg/RiverPlacer.cpp +++ b/lib/rmg/RiverPlacer.cpp @@ -13,6 +13,7 @@ #include "Functions.h" #include "CMapGenerator.h" #include "RmgMap.h" +#include "../TerrainHandler.h" #include "../mapping/CMap.h" #include "../mapping/CMapEditManager.h" #include "../mapObjects/CObjectClassesHandler.h" diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index 6245dd264..5edb324ae 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -18,6 +18,7 @@ #include "../mapObjects/CommonConstructors.h" #include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h #include "Functions.h" +#include "../TerrainHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/rmg/RockPlacer.cpp b/lib/rmg/RockPlacer.cpp index 980e35f0b..119b00c75 100644 --- a/lib/rmg/RockPlacer.cpp +++ b/lib/rmg/RockPlacer.cpp @@ -17,6 +17,7 @@ #include "RmgMap.h" #include "CMapGenerator.h" #include "Functions.h" +#include "../TerrainHandler.h" #include "../CRandomGenerator.h" #include "../mapping/CMapEditManager.h" diff --git a/lib/rmg/TileInfo.h b/lib/rmg/TileInfo.h index 951334b6d..e18065a20 100644 --- a/lib/rmg/TileInfo.h +++ b/lib/rmg/TileInfo.h @@ -11,7 +11,7 @@ #pragma once #include "../GameConstants.h" -#include "../Terrain.h" +//#include "../TerrainHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/rmg/WaterProxy.cpp b/lib/rmg/WaterProxy.cpp index 1bb018862..4a203187b 100644 --- a/lib/rmg/WaterProxy.cpp +++ b/lib/rmg/WaterProxy.cpp @@ -12,6 +12,7 @@ #include "WaterProxy.h" #include "CMapGenerator.h" #include "RmgMap.h" +#include "../TerrainHandler.h" #include "../mapping/CMap.h" #include "../mapping/CMapEditManager.h" #include "../mapObjects/CObjectClassesHandler.h" diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 76eca56a0..8e0834445 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -27,7 +27,7 @@ #include "../lib/mapping/CMapService.h" #include "../lib/mapping/CMap.h" #include "../lib/mapping/CMapEditManager.h" -#include "../lib/Terrain.h" +#include "../lib/TerrainHandler.h" #include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/filesystem/CFilesystemLoader.h" diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index f23590ca2..c79e0d377 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -4,7 +4,7 @@ #include #include #include "mapcontroller.h" -#include "../lib/Terrain.h" +//#include "../lib/TerrainHandler.h" #include "resourceExtractor/ResourceConverter.h" class ObjectBrowser; diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 2a7150f84..2a5d3fdd8 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -14,7 +14,7 @@ #include "../lib/mapping/CMapService.h" #include "../lib/mapping/CMap.h" #include "../lib/mapping/CMapEditManager.h" -#include "../lib/Terrain.h" +#include "../lib/TerrainHandler.h" #include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/rmg/ObstaclePlacer.h" #include "../lib/CSkillHandler.h" diff --git a/mapeditor/mapcontroller.h b/mapeditor/mapcontroller.h index 1446225f6..5ecd6c637 100644 --- a/mapeditor/mapcontroller.h +++ b/mapeditor/mapcontroller.h @@ -13,7 +13,7 @@ #include "maphandler.h" #include "mapview.h" #include "../lib/mapping/CMap.h" -#include "../lib/Terrain.h" +//#include "../lib/TerrainHandler.h" class MainWindow; class MapController diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp index 4e71d1fff..c722c86fc 100644 --- a/mapeditor/maphandler.cpp +++ b/mapeditor/maphandler.cpp @@ -12,6 +12,7 @@ #include "StdInc.h" #include "maphandler.h" #include "graphics.h" +#include "../lib/TerrainHandler.h" #include "../lib/mapping/CMap.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/CObjectClassesHandler.h" diff --git a/mapeditor/objectbrowser.cpp b/mapeditor/objectbrowser.cpp index 68d4addf1..fa9d20508 100644 --- a/mapeditor/objectbrowser.cpp +++ b/mapeditor/objectbrowser.cpp @@ -11,6 +11,7 @@ #include "StdInc.h" #include "objectbrowser.h" #include "../lib/mapObjects/CObjectClassesHandler.h" +#include "../lib/TerrainHandler.h" ObjectBrowserProxyModel::ObjectBrowserProxyModel(QObject *parent) : QSortFilterProxyModel{parent}, terrain(ETerrainId::ANY_TERRAIN) diff --git a/mapeditor/objectbrowser.h b/mapeditor/objectbrowser.h index 2c5433ad2..82ab6196b 100644 --- a/mapeditor/objectbrowser.h +++ b/mapeditor/objectbrowser.h @@ -11,7 +11,7 @@ #pragma once #include -#include "../lib/Terrain.h" +#include "../lib/GameConstants.h" class ObjectBrowserProxyModel : public QSortFilterProxyModel { diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 0d26288e8..b8b9d64c2 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -38,6 +38,7 @@ #include "../lib/VCMIDirs.h" #include "../lib/ScopeGuard.h" #include "../lib/CSoundBase.h" +#include "../lib/TerrainHandler.h" #include "CGameHandler.h" #include "CVCMIServer.h" #include "../lib/CCreatureSet.h" From c6d62c62970087d3366af8dc571b3c07c533bffe Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 9 Jan 2023 01:19:56 +0200 Subject: [PATCH 067/197] Added translatable strings for roads & rivers, for use in editor --- config/rivers.json | 4 ++++ config/roads.json | 3 +++ mapeditor/mainwindow.cpp | 4 ++-- mapeditor/mainwindow.h | 1 - mapeditor/mapcontroller.h | 1 - 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/config/rivers.json b/config/rivers.json index 666cf53cf..b9cc138dd 100644 --- a/config/rivers.json +++ b/config/rivers.json @@ -2,6 +2,7 @@ "waterRiver": { "index": 1, + "text" : "Water river", "shortIdentifier": "rw", //must be 2 characters "tilesFilename": "clrrvr", "delta": "clrdelt" @@ -9,6 +10,7 @@ "iceRiver": { "index": 2, + "text" : "Ice river", "shortIdentifier": "ri", "tilesFilename": "icyrvr", "delta": "icedelt" @@ -16,6 +18,7 @@ "mudRiver": { "index": 3, + "text" : "Mud river", "shortIdentifier": "rm", "tilesFilename": "mudrvr", "delta": "muddelt" @@ -23,6 +26,7 @@ "lavaRiver": { "index": 4, + "text" : "Lava river", "shortIdentifier": "rl", "tilesFilename": "lavrvr", "delta": "lavdelt" diff --git a/config/roads.json b/config/roads.json index 76f5f4d9d..b0fc2d79d 100644 --- a/config/roads.json +++ b/config/roads.json @@ -2,6 +2,7 @@ "dirtRoad": { "index": 1, + "text" : "Dirt road", "shortIdentifier": "pd", //must be 2 characters "tilesFilename": "dirtrd", "moveCost": 75 @@ -9,6 +10,7 @@ "gravelRoad": { "index": 2, + "text" : "Gravel road", "shortIdentifier": "pg", "tilesFilename": "gravrd", "moveCost": 65 @@ -16,6 +18,7 @@ "cobblestoneRoad": { "index": 3, + "text" : "Cobblestone road", "shortIdentifier": "pc", "tilesFilename": "cobbrd", "moveCost": 50 diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 8e0834445..10a541abe 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -557,7 +557,7 @@ void MainWindow::loadObjectsTree() //adding roads for(auto & road : VLC->roadTypeHandler->objects) { - QPushButton *b = new QPushButton(QString::fromStdString(road->tilesFilename)); + QPushButton *b = new QPushButton(QString::fromStdString(road->getNameTranslated())); ui->roadLayout->addWidget(b); connect(b, &QPushButton::clicked, this, [this, road]{ roadOrRiverButtonClicked(road->getIndex(), true); }); } @@ -566,7 +566,7 @@ void MainWindow::loadObjectsTree() //adding rivers for(auto & river : VLC->riverTypeHandler->objects) { - QPushButton *b = new QPushButton(QString::fromStdString(river->tilesFilename)); + QPushButton *b = new QPushButton(QString::fromStdString(river->getNameTranslated())); ui->riverLayout->addWidget(b); connect(b, &QPushButton::clicked, this, [this, river]{ roadOrRiverButtonClicked(river->getIndex(), false); }); } diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index c79e0d377..2a48de091 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -4,7 +4,6 @@ #include #include #include "mapcontroller.h" -//#include "../lib/TerrainHandler.h" #include "resourceExtractor/ResourceConverter.h" class ObjectBrowser; diff --git a/mapeditor/mapcontroller.h b/mapeditor/mapcontroller.h index 5ecd6c637..338798201 100644 --- a/mapeditor/mapcontroller.h +++ b/mapeditor/mapcontroller.h @@ -13,7 +13,6 @@ #include "maphandler.h" #include "mapview.h" #include "../lib/mapping/CMap.h" -//#include "../lib/TerrainHandler.h" class MainWindow; class MapController From 668aee71ff8a99b3bb593f33b569cb84448b8743 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 9 Jan 2023 01:53:30 +0200 Subject: [PATCH 068/197] Added schemas for rivers & roads, updated terrain schema --- config/schemas/river.json | 37 +++++++++++++++++++++++ config/schemas/road.json | 37 +++++++++++++++++++++++ config/schemas/terrain.json | 59 ++++++++++++++++++++++++++++--------- config/terrains.json | 6 ++++ 4 files changed, 125 insertions(+), 14 deletions(-) create mode 100644 config/schemas/river.json create mode 100644 config/schemas/road.json diff --git a/config/schemas/river.json b/config/schemas/river.json new file mode 100644 index 000000000..51eb1031f --- /dev/null +++ b/config/schemas/river.json @@ -0,0 +1,37 @@ +{ + "type":"object", + "$schema": "http://json-schema.org/draft-04/schema", + "title" : "VCMI river format", + "description" : "Format used to define new rivers in VCMI", + "required" : [ "text", "shortIdentifier", "tilesFilename", "delta" ], + + "additionalProperties" : false, + "properties":{ + "index" : + { + "type": "number", + "description": "Internal, do not use" + }, + "text": + { + "type": "string", + "description": "Human-readable name of the river" + }, + "shortIdentifier": + { + "type": "string", + "description": "Two-letters unique indentifier for this road. Used in map format" + }, + "tilesFilename": + { + "type": "string", + "description": "Name of file with river graphics", + "format": "defFile" + }, + "delta": + { + "type": "string", + "description": "Name of file with river delta graphics" + } + } +} diff --git a/config/schemas/road.json b/config/schemas/road.json new file mode 100644 index 000000000..8e684c251 --- /dev/null +++ b/config/schemas/road.json @@ -0,0 +1,37 @@ +{ + "type":"object", + "$schema": "http://json-schema.org/draft-04/schema", + "title" : "VCMI road format", + "description" : "Format used to define new roads in VCMI", + "required" : [ "text", "shortIdentifier", "tilesFilename", "moveCost" ], + + "additionalProperties" : false, + "properties":{ + "index" : + { + "type": "number", + "description": "Internal, do not use" + }, + "text": + { + "type": "string", + "description": "Human-readable name of the road" + }, + "shortIdentifier": + { + "type": "string", + "description": "Two-letters unique indentifier for this road. Used in map format" + }, + "tilesFilename": + { + "type": "string", + "description": "Name of file with road graphics", + "format": "defFile" + }, + "moveCost": + { + "type": "number", + "description": "How many movement points needed to move hero" + } + } +} diff --git a/config/schemas/terrain.json b/config/schemas/terrain.json index b4be9e922..37e5a998f 100644 --- a/config/schemas/terrain.json +++ b/config/schemas/terrain.json @@ -3,10 +3,20 @@ "$schema": "http://json-schema.org/draft-04/schema", "title" : "VCMI terrain format", "description" : "Format used to define new terrains in VCMI", - "required" : [ "tiles", "code", "moveCost" ], + "required" : [ "text", "moveCost", "minimapUnblocked", "minimapBlocked", "music", "tiles", "type", "horseSound", "horseSoundPenalty", "shortIdentifier", "battleFields" ], "additionalProperties" : false, "properties":{ + "index" : + { + "type": "number", + "description": "Internal, do not use" + }, + "text": + { + "type": "string", + "description": "Human-readable name of this terrain" + }, "moveCost": { "type": "number", @@ -47,9 +57,13 @@ }, "type": { - "type": "string", + "type": "array", "description": "Type of this terrain. Can be land, water, subterranean or rock", - "enum": ["LAND", "WATER", "SUB", "ROCK"] + "items": + { + "enum": ["LAND", "WATER", "SUB", "ROCK", "SURFACE"], + "type": "string" + } }, "rockTerrain": { @@ -59,23 +73,22 @@ "river": { "type": "string", - "description": "River type which should be used for that terrain", - "enum": ["", "rw", "ri", "rm", "rl"] + "description": "River type which should be used for that terrain" }, - "horseSoundId": - { - "type": "number", - "description": "Id of horse sound to be played when hero is moving across terrain" - }, - "text": + "horseSound": { "type": "string", - "description": "Text to be shown when mouse if over terrain" + "description": "Hero movement sound for this terrain, version for moving on tiles with road" }, - "code": + "horseSoundPenalty": { "type": "string", - "description": "Two-letters unique indentifier for this terrain. Used for terrain serializaion" + "description": "Hero movement sound for this terrain, version for moving on tiles without road" + }, + "shortIdentifier": + { + "type": "string", + "description": "Two-letters unique indentifier for this terrain. Used for map format" }, "battleFields": { @@ -86,6 +99,24 @@ "type": "string" } }, + "sounds": + { + "type": "object", + "description": "list of sounds for this terrain", + "additionalProperties" : false, + "properties": + { + "ambient" : + { + "type": "array", + "description": "list of ambient sounds for this terrain", + "items": + { + "type": "string" + } + } + } + }, "prohibitTransitions": { "type": "array", diff --git a/config/terrains.json b/config/terrains.json index ad7c9a3c9..b381072e1 100644 --- a/config/terrains.json +++ b/config/terrains.json @@ -7,6 +7,7 @@ "minimapBlocked" : [ 57, 40, 8 ], "music" : "Dirt.mp3", "tiles" : "DIRTTL", + "type" : ["SURFACE"], "shortIdentifier" : "dt", "river" : "mudRiver", "battleFields" : ["dirt_birches", "dirt_hills", "dirt_pines"], @@ -22,6 +23,7 @@ "minimapBlocked" : [ 165, 158, 107 ], "music" : "Sand.mp3", "tiles" : "SANDTL", + "type" : ["SURFACE"], "shortIdentifier" : "sa", "river" : "mudRiver", "battleFields" : ["sand_mesas"], @@ -38,6 +40,7 @@ "minimapBlocked" : [ 0, 48, 0 ], "music" : "Grass.mp3", "tiles" : "GRASTL", + "type" : ["SURFACE"], "shortIdentifier" : "gr", "river" : "waterRiver", "battleFields" : ["grass_hills", "grass_pines"], @@ -52,6 +55,7 @@ "minimapBlocked" : [ 140, 158, 156 ], "music" : "Snow.mp3", "tiles" : "SNOWTL", + "type" : ["SURFACE"], "shortIdentifier" : "sn", "river" : "iceRiver", "battleFields" : ["snow_mountains", "snow_trees"], @@ -66,6 +70,7 @@ "minimapBlocked" : [ 33, 89, 66 ], "music" : "Swamp.mp3", "tiles" : "SWMPTL", + "type" : ["SURFACE"], "shortIdentifier" : "sw", "river" : "waterRiver", "battleFields" : ["swamp_trees"], @@ -80,6 +85,7 @@ "minimapBlocked" : [ 99, 81, 33 ], "music" : "Rough.mp3", "tiles" : "ROUGTL", + "type" : ["SURFACE"], "shortIdentifier" : "rg", "river" : "mudRiver", "battleFields" : ["rough"], From c455986a55d61e62b01ca38f2256f879733343a4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 10 Jan 2023 01:05:28 +0200 Subject: [PATCH 069/197] Fix regressions in RMG & map editor --- lib/TerrainHandler.cpp | 10 ++++++---- lib/mapping/CMap.cpp | 4 ++-- lib/mapping/CMapDefines.h | 6 +++--- lib/mapping/CMapOperation.cpp | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/TerrainHandler.cpp b/lib/TerrainHandler.cpp index c3f5ed196..765def37d 100644 --- a/lib/TerrainHandler.cpp +++ b/lib/TerrainHandler.cpp @@ -204,7 +204,7 @@ RoadType * RoadTypeHandler::loadFromJson( const std::vector & RoadTypeHandler::getTypeNames() const { - static const std::vector typeNames = { "river" }; + static const std::vector typeNames = { "road" }; return typeNames; } @@ -274,7 +274,7 @@ TerrainType::TerrainType() std::string RoadType::getNameTextID() const { - return TextIdentifier( "terrain", identifier, "name" ).get(); + return TextIdentifier( "road", identifier, "name" ).get(); } std::string RoadType::getNameTranslated() const @@ -284,7 +284,7 @@ std::string RoadType::getNameTranslated() const std::string RiverType::getNameTextID() const { - return TextIdentifier( "terrain", identifier, "name" ).get(); + return TextIdentifier( "river", identifier, "name" ).get(); } std::string RiverType::getNameTranslated() const @@ -293,11 +293,13 @@ std::string RiverType::getNameTranslated() const } RiverType::RiverType(): - id(River::NO_RIVER) + id(River::NO_RIVER), + identifier("empty") {} RoadType::RoadType(): id(Road::NO_ROAD), + identifier("empty"), movementCost(GameConstants::BASE_MOVEMENT_COST) {} VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 6ace46fff..e77588ac5 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -129,9 +129,9 @@ CCastleEvent::CCastleEvent() : town(nullptr) TerrainTile::TerrainTile(): terType(nullptr), terView(0), - riverType(nullptr), + riverType(VLC->riverTypeHandler->getById(River::NO_RIVER)), riverDir(0), - roadType(nullptr), + roadType(VLC->roadTypeHandler->getById(Road::NO_ROAD)), roadDir(0), extTileFlags(0), visitable(false), diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index ac0500820..52d6885d5 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -86,11 +86,11 @@ struct DLL_LINKAGE TerrainTile EDiggingStatus getDiggingStatus(const bool excludeTop = true) const; bool hasFavorableWinds() const; - TerrainType * terType; + const TerrainType * terType; ui8 terView; - RiverType * riverType; + const RiverType * riverType; ui8 riverDir; - RoadType * roadType; + const RoadType * roadType; ui8 roadDir; /// first two bits - how to rotate terrain graphic (next two - river graphic, next two - road); /// 7th bit - whether tile is coastal (allows disembarking if land or block movement if water); 8th bit - Favorable Winds effect diff --git a/lib/mapping/CMapOperation.cpp b/lib/mapping/CMapOperation.cpp index 46cf575b8..88d25768a 100644 --- a/lib/mapping/CMapOperation.cpp +++ b/lib/mapping/CMapOperation.cpp @@ -346,7 +346,7 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi int cy = pos.y + (i / 3) - 1; int3 currentPos(cx, cy, pos.z); bool isAlien = false; - TerrainType * terType = nullptr; + const TerrainType * terType = nullptr; if(!map->isInTheMap(currentPos)) { // position is not in the map, so take the ter type from the neighbor tile From 8c2b1d74be422b5bf32fe69eeeb546fcccbf4efe Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 10 Jan 2023 20:07:22 +0200 Subject: [PATCH 070/197] Removed unused code --- lib/rmg/CRmgTemplate.h | 1 - lib/rmg/TileInfo.h | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 7bbc98511..b683b8978 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -13,7 +13,6 @@ #include "../int3.h" #include "../GameConstants.h" #include "../ResourceSet.h" -//#include "../TerrainHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/rmg/TileInfo.h b/lib/rmg/TileInfo.h index e18065a20..61e874a74 100644 --- a/lib/rmg/TileInfo.h +++ b/lib/rmg/TileInfo.h @@ -11,7 +11,6 @@ #pragma once #include "../GameConstants.h" -//#include "../TerrainHandler.h" VCMI_LIB_NAMESPACE_BEGIN From 1caec0a8cb3cec9ddb0fae621d2d2d10988c0475 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 10 Jan 2023 20:08:07 +0200 Subject: [PATCH 071/197] Removed default native terrains - breaks neutral faction --- lib/CTownHandler.cpp | 32 +++----------------------------- lib/CTownHandler.h | 5 ----- 2 files changed, 3 insertions(+), 34 deletions(-) diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index de01ca400..49200d298 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -28,10 +28,6 @@ VCMI_LIB_NAMESPACE_BEGIN const int NAMES_PER_TOWN=16; // number of town names per faction in H3 files. Json can define any number -const TerrainId CTownHandler::defaultGoodTerrain(ETerrainId::GRASS); -const TerrainId CTownHandler::defaultEvilTerrain(ETerrainId::LAVA); -const TerrainId CTownHandler::defaultNeutralTerrain(ETerrainId::ROUGH); - const std::map CBuilding::MODES = { { "normal", CBuilding::BUILD_NORMAL }, @@ -951,22 +947,6 @@ void CTownHandler::loadPuzzle(CFaction &faction, const JsonNode &source) assert(faction.puzzleMap.size() == GameConstants::PUZZLE_MAP_PIECES); } -TerrainId CTownHandler::getDefaultTerrainForAlignment(EAlignment::EAlignment alignment) const -{ - TerrainId terrain = defaultGoodTerrain; - - switch(alignment) - { - case EAlignment::EAlignment::EVIL: - terrain = defaultEvilTerrain; - break; - case EAlignment::EAlignment::NEUTRAL: - terrain = defaultNeutralTerrain; - break; - } - return terrain; -} - CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode & source, const std::string & identifier, size_t index) { auto faction = new CFaction(); @@ -987,16 +967,10 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode auto preferUndergound = source["preferUndergroundPlacement"]; faction->preferUndergroundPlacement = preferUndergound.isNull() ? false : preferUndergound.Bool(); - //Contructor is not called here, but operator= - auto nativeTerrain = source["nativeTerrain"]; - - if ( nativeTerrain.isNull()) + faction->nativeTerrain = ETerrainId::NONE; + if ( !source["nativeTerrain"].isNull()) { - faction->nativeTerrain = getDefaultTerrainForAlignment(faction->alignment); - } - else - { - VLC->modh->identifiers.requestIdentifier("terrain", nativeTerrain, [=](int32_t index){ + VLC->modh->identifiers.requestIdentifier("terrain", source["nativeTerrain"], [=](int32_t index){ faction->nativeTerrain = TerrainId(index); }); } diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 0a5443995..b6472a38f 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -363,10 +363,6 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase requirementsToLoad; std::vector overriddenBidsToLoad; //list of buildings, which bonuses should be overridden. - const static TerrainId defaultGoodTerrain; - const static TerrainId defaultEvilTerrain; - const static TerrainId defaultNeutralTerrain; - static TPropagatorPtr & emptyPropagator(); void initializeRequirements(); @@ -396,7 +392,6 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase Date: Tue, 10 Jan 2023 20:08:37 +0200 Subject: [PATCH 072/197] Terrain identifiers will also have mod:name format --- lib/TerrainHandler.cpp | 25 ++++++++++++++++++------- lib/TerrainHandler.h | 2 -- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/TerrainHandler.cpp b/lib/TerrainHandler.cpp index 765def37d..9296f6b7a 100644 --- a/lib/TerrainHandler.cpp +++ b/lib/TerrainHandler.cpp @@ -25,8 +25,11 @@ TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const TerrainType * info = new TerrainType; info->id = TerrainId(index); - info->identifier = identifier; - info->modScope = scope; + + if (identifier.find(':') == std::string::npos) + info->identifier = scope + ":" + identifier; + else + info->identifier = identifier; info->moveCost = static_cast(json["moveCost"].Integer()); info->musicFilename = json["music"].String(); @@ -151,7 +154,11 @@ RiverType * RiverTypeHandler::loadFromJson( RiverType * info = new RiverType; info->id = RiverId(index); - info->identifier = identifier; + if (identifier.find(':') == std::string::npos) + info->identifier = scope + ":" + identifier; + else + info->identifier = identifier; + info->tilesFilename = json["tilesFilename"].String(); info->shortIdentifier = json["shortIdentifier"].String(); info->deltaName = json["delta"].String(); @@ -192,7 +199,11 @@ RoadType * RoadTypeHandler::loadFromJson( RoadType * info = new RoadType; info->id = RoadId(index); - info->identifier = identifier; + if (identifier.find(':') == std::string::npos) + info->identifier = scope + ":" + identifier; + else + info->identifier = identifier; + info->tilesFilename = json["tilesFilename"].String(); info->shortIdentifier = json["shortIdentifier"].String(); info->movementCost = json["moveCost"].Integer(); @@ -261,7 +272,7 @@ bool TerrainType::isTransitionRequired() const std::string TerrainType::getNameTextID() const { - return TextIdentifier( "terrain", modScope, identifier, "name" ).get(); + return TextIdentifier( "terrain", identifier, "name" ).get(); } std::string TerrainType::getNameTranslated() const @@ -294,12 +305,12 @@ std::string RiverType::getNameTranslated() const RiverType::RiverType(): id(River::NO_RIVER), - identifier("empty") + identifier("core:empty") {} RoadType::RoadType(): id(Road::NO_ROAD), - identifier("empty"), + identifier("core:empty"), movementCost(GameConstants::BASE_MOVEMENT_COST) {} VCMI_LIB_NAMESPACE_END diff --git a/lib/TerrainHandler.h b/lib/TerrainHandler.h index a4e15f6d2..bc71eea53 100644 --- a/lib/TerrainHandler.h +++ b/lib/TerrainHandler.h @@ -22,7 +22,6 @@ VCMI_LIB_NAMESPACE_BEGIN class DLL_LINKAGE TerrainType : public EntityT { friend class TerrainTypeHandler; - std::string modScope; std::string identifier; TerrainId id; ui8 passabilityType; @@ -80,7 +79,6 @@ public: h & prohibitTransitions; h & minimapBlocked; h & minimapUnblocked; - h & modScope; h & identifier; h & musicFilename; h & tilesFilename; From 1e37e66e6cb4117304af5c150ec7ffeed34719e5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 10 Jan 2023 20:09:09 +0200 Subject: [PATCH 073/197] Replaced Terrain::BORDER and WRONG with NONE --- client/CPlayerInterface.cpp | 2 +- lib/GameConstants.h | 3 +-- lib/NetPacksLib.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 13 +++++++------ lib/mapping/MapFormatH3M.cpp | 4 +++- lib/rmg/CRmgTemplate.cpp | 2 +- lib/rmg/CZonePlacer.cpp | 2 +- 7 files changed, 15 insertions(+), 13 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index b0bf7bbea..ec75f97bd 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -2372,7 +2372,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) for (auto & elem : path.nodes) elem.coord = h->convertFromVisitablePos(elem.coord); - TerrainId currentTerrain = ETerrainId::BORDER; // not init yet + TerrainId currentTerrain = ETerrainId::NONE; TerrainId newTerrain; bool wasOnRoad = true; int sh = -1; diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 9c2b6d644..3e45df780 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -1222,8 +1222,7 @@ class BattleField : public BaseForID enum class ETerrainId { NATIVE_TERRAIN = -4, ANY_TERRAIN = -3, - WRONG = -2, - BORDER = -1, + NONE = -1, FIRST_REGULAR_TERRAIN = 0, DIRT = 0, SAND, diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 5e71f301d..b1ed9949c 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -721,7 +721,7 @@ DLL_LINKAGE void GiveHero::applyGs(CGameState *gs) DLL_LINKAGE void NewObject::applyGs(CGameState *gs) { - TerrainId terrainType = ETerrainId::BORDER; + TerrainId terrainType = ETerrainId::NONE; if(ID == Obj::BOAT && !gs->isInTheMap(pos)) //special handling for bug #3060 - pos outside map but visitablePos is not { diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 9ce71c4a9..892240bc7 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -88,7 +88,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f } else if(ti->nativeTerrain != from.terType->getId() &&//the terrain is not native ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus - !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType->getId().getNum())) //no special movement bonus + !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType->getIndex())) //no special movement bonus { ret = VLC->heroh->terrCosts[from.terType->getId()]; @@ -105,20 +105,21 @@ TerrainId CGHeroInstance::getNativeTerrain() const // This is clearly bug in H3 however intended behaviour is not clear. // Current VCMI behaviour will ignore neutrals in calculations so army in VCMI // will always have best penalty without any influence from player-defined stacks order + // and army that consist solely from neutral will always be considered to be on native terrain - // TODO: What should we do if all hero stacks are neutral creatures? - TerrainId nativeTerrain = ETerrainId::BORDER; + TerrainId nativeTerrain = ETerrainId::ANY_TERRAIN; for(auto stack : stacks) { TerrainId stackNativeTerrain = stack.second->type->getNativeTerrain(); //consider terrain bonuses e.g. Lodestar. - if(stackNativeTerrain == ETerrainId::BORDER) //where does this value come from? + if(stackNativeTerrain == ETerrainId::NONE) continue; - if(nativeTerrain == ETerrainId::BORDER) + + if(nativeTerrain == ETerrainId::ANY_TERRAIN) nativeTerrain = stackNativeTerrain; else if(nativeTerrain != stackNativeTerrain) - return ETerrainId::BORDER; + return ETerrainId::NONE; } return nativeTerrain; } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index b785ec6d2..87d426cbf 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -942,8 +942,10 @@ void CMapLoaderH3M::readTerrain() tile.roadType = const_cast(VLC->roadTypeHandler->getByIndex(reader.readUInt8())); tile.roadDir = reader.readUInt8(); tile.extTileFlags = reader.readUInt8(); - tile.blocked = ((!tile.terType->isPassable() || tile.terType->getId() == ETerrainId::BORDER ) ? true : false); //underground tiles are always blocked + tile.blocked = !tile.terType->isPassable(); tile.visitable = 0; + + assert(tile.terType->getId() != ETerrainId::NONE); } } } diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 474887fb9..b675cbf2a 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -224,7 +224,7 @@ const std::set & ZoneOptions::getTerrainTypes() const void ZoneOptions::setTerrainTypes(const std::set & value) { - //assert(value.find(ETerrainType::WRONG) == value.end() && value.find(ETerrainType::BORDER) == value.end() && + //assert(value.find(ETerrainType::NONE) == value.end() && // value.find(ETerrainType::WATER) == value.end() && value.find(ETerrainType::ROCK) == value.end()); terrainTypes = value; } diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 0e10968af..543c9c9e2 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -195,7 +195,7 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const else { auto & tt = (*VLC->townh)[faction]->nativeTerrain; - if(tt == ETerrainId::DIRT) + if(tt == ETerrainId::NONE) { //any / random zonesToPlace.push_back(zone); From a0568823a99b892ca8d97a513fb392d1923aa3f5 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Fri, 30 Dec 2022 21:43:32 +0200 Subject: [PATCH 074/197] swap contitutient --- client/widgets/CArtifactHolder.cpp | 33 ++++++++++++------------------ client/widgets/CArtifactHolder.h | 2 +- client/windows/GUIClasses.cpp | 4 ---- client/windows/GUIClasses.h | 1 - lib/CArtHandler.cpp | 19 +++++++++++++---- lib/CArtHandler.h | 1 + server/CGameHandler.cpp | 4 +++- 7 files changed, 33 insertions(+), 31 deletions(-) diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp index 46ec17742..4b12f2543 100644 --- a/client/widgets/CArtifactHolder.cpp +++ b/client/widgets/CArtifactHolder.cpp @@ -278,13 +278,12 @@ void CHeroArtPlace::select () if (locked) return; - selectSlot(true); pickSlot(true); if(ourArt->canBeDisassembled() && slotID < GameConstants::BACKPACK_START) //worn combined artifact -> locks have to disappear { - for(int i = 0; i < GameConstants::BACKPACK_START; i++) + for(auto slot : ArtifactUtils::constituentWornSlots()) { - auto ap = ourOwner->getArtPlace(i); + auto ap = ourOwner->getArtPlace(slot); if(ap)//getArtPlace may return null ap->pickSlot(ourArt->isPart(ap->ourArt)); } @@ -309,9 +308,9 @@ void CHeroArtPlace::deselect () pickSlot(false); if(ourArt && ourArt->canBeDisassembled()) //combined art returned to its slot -> restore locks { - for(int i = 0; i < GameConstants::BACKPACK_START; i++) + for(auto slot : ArtifactUtils::constituentWornSlots()) { - auto place = ourOwner->getArtPlace(i); + auto place = ourOwner->getArtPlace(slot); if(nullptr != place)//getArtPlace may return null place->pickSlot(false); @@ -735,10 +734,10 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation & src, const Artifac if(isCurHeroDst && ArtifactUtils::isSlotBackpack(dst.slot)) updateSlot(dst.slot); // We need to update all slots, artifact might be combined and affect more slots - if(isCurHeroSrc || isCurHeroDst) + if(isCurHeroSrc || isCurHeroDst) updateWornSlots(false); - if(!src.isHolder(curHero) && !isCurHeroDst) + if(!isCurHeroSrc && !isCurHeroDst) return; // When moving one artifact onto another it leads to two art movements: dst->TRANSITION_POS; src->dst @@ -748,11 +747,12 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation & src, const Artifac // Used when doing dragAndDrop and artifact swap multiple times if(src.slot == ArtifactPosition::TRANSITION_POS && commonInfo->src.slotID == ArtifactPosition::TRANSITION_POS && - commonInfo->dst.slotID == ArtifactPosition::PRE_FIRST) + commonInfo->dst.slotID == ArtifactPosition::PRE_FIRST && + isCurHeroDst) { auto art = curHero->getArt(ArtifactPosition::TRANSITION_POS); assert(art); - CCS->curh->dragAndDropCursor(make_unique("artifact", art->artType->getIconIndex())); + CCS->curh->dragAndDropCursor(std::make_unique("artifact", art->artType->getIconIndex())); markPossibleSlots(art); commonInfo->src.art = art; @@ -787,21 +787,14 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation & src, const Artifac commonInfo->src.art = dst.getArt(); commonInfo->src.slotID = dst.slot; assert(commonInfo->src.AOH); - CCS->curh->dragAndDropCursor(make_unique("artifact", dst.getArt()->artType->getIconIndex())); - markPossibleSlots(dst.getArt()); + CCS->curh->dragAndDropCursor(std::make_unique("artifact", dst.getArt()->artType->getIconIndex())); } updateParentWindow(); - int shift = 0; - if(!ArtifactUtils::isSlotBackpack(src.slot) && dst.slot - GameConstants::BACKPACK_START < backpackPos) - shift++; - if(!ArtifactUtils::isSlotBackpack(dst.slot) && src.slot - GameConstants::BACKPACK_START < backpackPos) - shift--; - // If backpack is changed, update it if((isCurHeroSrc && ArtifactUtils::isSlotBackpack(src.slot)) || (isCurHeroDst && ArtifactUtils::isSlotBackpack(dst.slot))) - scrollBackpack(shift); + scrollBackpack(0); } void CArtifactsOfHero::artifactRemoved(const ArtifactLocation &al) @@ -815,7 +808,7 @@ void CArtifactsOfHero::artifactRemoved(const ArtifactLocation &al) } } -CArtifactsOfHero::ArtPlacePtr CArtifactsOfHero::getArtPlace(int slot) +CArtifactsOfHero::ArtPlacePtr CArtifactsOfHero::getArtPlace(ArtifactPosition slot) { if(slot == ArtifactPosition::TRANSITION_POS) { @@ -823,7 +816,7 @@ CArtifactsOfHero::ArtPlacePtr CArtifactsOfHero::getArtPlace(int slot) } if(slot < GameConstants::BACKPACK_START) { - if(artWorn.find(ArtifactPosition(slot)) == artWorn.end()) + if(artWorn.find(slot) == artWorn.end()) { logGlobal->error("CArtifactsOfHero::getArtPlace: invalid slot %d", slot); return nullptr; diff --git a/client/widgets/CArtifactHolder.h b/client/widgets/CArtifactHolder.h index 36e16e547..0adfb0ec9 100644 --- a/client/widgets/CArtifactHolder.h +++ b/client/widgets/CArtifactHolder.h @@ -141,7 +141,7 @@ public: void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst); void artifactRemoved(const ArtifactLocation &al); void artifactUpdateSlots(const ArtifactLocation &al); - ArtPlacePtr getArtPlace(int slot);//may return null + ArtPlacePtr getArtPlace(ArtifactPosition slot);//may return null void setHero(const CGHeroInstance * hero); const CGHeroInstance *getHero() const; diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index a84b12a1b..54d6a1531 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1247,10 +1247,6 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, updateWidgets(); } -CExchangeWindow::~CExchangeWindow() -{ -} - const CGarrisonSlot * CExchangeWindow::getSelectedSlotID() const { return garr->getSelection(); diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index 56097d0b4..0ce7d8314 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -379,7 +379,6 @@ public: const CGarrisonSlot * getSelectedSlotID() const; CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID); - ~CExchangeWindow(); }; /// Here you can buy ships diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 49fcce302..96632f488 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -856,7 +856,7 @@ void CArtifactInstance::putAt(ArtifactLocation al) assert(canBePutAt(al)); al.getHolderArtSet()->setNewArtSlot(al.slot, this, false); - if(!ArtifactUtils::isSlotBackpack(al.slot) && (al.slot != ArtifactPosition::TRANSITION_POS)) + if(ArtifactUtils::isSlotEquipment(al.slot)) al.getHolderNode()->attachTo(*this); } @@ -864,7 +864,7 @@ void CArtifactInstance::removeFrom(ArtifactLocation al) { assert(al.getHolderArtSet()->getArt(al.slot) == this); al.getHolderArtSet()->eraseArtSlot(al.slot); - if(!ArtifactUtils::isSlotBackpack(al.slot) && (al.slot != ArtifactPosition::TRANSITION_POS)) + if(ArtifactUtils::isSlotEquipment(al.slot)) al.getHolderNode()->detachFrom(*this); } @@ -1003,6 +1003,8 @@ bool CArtifactInstance::isPart(const CArtifactInstance *supposedPart) const bool CCombinedArtifactInstance::canBePutAt(const CArtifactSet *artSet, ArtifactPosition slot, bool assumeDestRemoved) const { + if(slot == ArtifactPosition::TRANSITION_POS) + return true; bool canMainArtifactBePlaced = CArtifactInstance::canBePutAt(artSet, slot, assumeDestRemoved); if(!canMainArtifactBePlaced) return false; //no is no... @@ -1075,7 +1077,11 @@ void CCombinedArtifactInstance::addAsConstituent(CArtifactInstance *art, Artifac void CCombinedArtifactInstance::putAt(ArtifactLocation al) { - if(ArtifactUtils::isSlotBackpack(al.slot)) + if(al.slot == ArtifactPosition::TRANSITION_POS) + { + CArtifactInstance::putAt(al); + } + else if(ArtifactUtils::isSlotBackpack(al.slot)) { CArtifactInstance::putAt(al); for(ConstituentInfo &ci : constituentsInfo) @@ -1113,7 +1119,7 @@ void CCombinedArtifactInstance::putAt(ArtifactLocation al) void CCombinedArtifactInstance::removeFrom(ArtifactLocation al) { - if(ArtifactUtils::isSlotBackpack(al.slot)) + if(ArtifactUtils::isSlotBackpack(al.slot) || al.slot == ArtifactPosition::TRANSITION_POS) { CArtifactInstance::removeFrom(al); } @@ -1634,4 +1640,9 @@ DLL_LINKAGE bool ArtifactUtils::isSlotBackpack(ArtifactPosition slot) return slot >= GameConstants::BACKPACK_START; } +DLL_LINKAGE bool ArtifactUtils::isSlotEquipment(ArtifactPosition slot) +{ + return slot < GameConstants::BACKPACK_START && slot >= 0; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index cae8abef1..504c362c7 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -393,6 +393,7 @@ namespace ArtifactUtils DLL_LINKAGE bool isArtRemovable(const std::pair & slot); DLL_LINKAGE bool checkSpellbookIsNeeded(const CGHeroInstance * heroPtr, ArtifactID artID, ArtifactPosition slot); DLL_LINKAGE bool isSlotBackpack(ArtifactPosition slot); + DLL_LINKAGE bool isSlotEquipment(ArtifactPosition slot); } VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 533c6f3db..ce0fb4bee 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3892,7 +3892,7 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat // Check if src/dest slots are appropriate for the artifacts exchanged. // Moving to the backpack is always allowed. - if ((!srcArtifact || dst.slot < GameConstants::BACKPACK_START) + if ((!srcArtifact || !ArtifactUtils::isSlotBackpack(dst.slot)) && srcArtifact && !srcArtifact->canBePutAt(dst, true)) COMPLAIN_RET("Cannot move artifact!"); @@ -3926,6 +3926,8 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); MoveArtifact ma(&src, &dst); + if(dst.slot == ArtifactPosition::TRANSITION_POS) + ma.askAssemble = false; sendAndApply(&ma); } return true; From c6f089c6b1a42ad1bcb25f767cf52a429909a8e8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 11 Jan 2023 13:59:33 +0200 Subject: [PATCH 075/197] Removed outdated comment --- lib/TerrainHandler.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/TerrainHandler.cpp b/lib/TerrainHandler.cpp index 9296f6b7a..819e7cb02 100644 --- a/lib/TerrainHandler.cpp +++ b/lib/TerrainHandler.cpp @@ -16,10 +16,6 @@ VCMI_LIB_NAMESPACE_BEGIN -//regular expression to change id for string at config -//("allowedTerrain"\s*:\s*\[.*)9(.*\],\n) -//\1"rock"\2 - TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) { TerrainType * info = new TerrainType; From 8b6f6f9b931b7c471ae002fbc531e30ad34a7e10 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 11 Jan 2023 13:59:43 +0200 Subject: [PATCH 076/197] Fix msvc compile --- lib/TerrainHandler.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/TerrainHandler.h b/lib/TerrainHandler.h index bc71eea53..ea3adb465 100644 --- a/lib/TerrainHandler.h +++ b/lib/TerrainHandler.h @@ -33,6 +33,7 @@ public: const std::string & getJsonKey() const override { return identifier;} void registerIcons(const IconRegistar & cb) const override {} TerrainId getId() const override { return id;} + void updateFrom(const JsonNode & data) {}; std::string getNameTextID() const; std::string getNameTranslated() const; @@ -109,6 +110,7 @@ public: const std::string & getJsonKey() const override { return identifier;} void registerIcons(const IconRegistar & cb) const override {} RiverId getId() const override { return id;} + void updateFrom(const JsonNode & data) {}; std::string getNameTextID() const; std::string getNameTranslated() const; @@ -141,6 +143,7 @@ public: const std::string & getJsonKey() const override { return identifier;} void registerIcons(const IconRegistar & cb) const override {} RoadId getId() const override { return id;} + void updateFrom(const JsonNode & data) {}; std::string getNameTextID() const; std::string getNameTranslated() const; From e48bd39b9c2bfcb243dcf5871068f918ff777c8a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 11 Jan 2023 15:17:24 +0200 Subject: [PATCH 077/197] Moved road & river handlers into a separate file --- client/CPlayerInterface.cpp | 1 + client/lobby/RandomMapTab.cpp | 2 +- client/mapHandler.cpp | 2 + cmake_modules/VCMI_lib.cmake | 4 + lib/CCreatureHandler.cpp | 1 - lib/CGeneralTextHandler.cpp | 1 - lib/CModHandler.cpp | 2 + lib/CPathfinder.cpp | 1 - lib/IGameCallback.cpp | 2 + lib/RiverHandler.cpp | 78 +++++++++++++++++ lib/RiverHandler.h | 79 +++++++++++++++++ lib/RoadHandler.cpp | 78 +++++++++++++++++ lib/RoadHandler.h | 79 +++++++++++++++++ lib/TerrainHandler.cpp | 121 -------------------------- lib/TerrainHandler.h | 120 ------------------------- lib/VCMI_Lib.cpp | 2 + lib/battle/BattleInfo.cpp | 1 - lib/battle/BattleProxy.cpp | 1 - lib/mapObjects/CGHeroInstance.cpp | 1 + lib/mapping/CDrawRoadsOperation.cpp | 3 +- lib/mapping/CMap.cpp | 2 + lib/mapping/MapFormatH3M.cpp | 2 + lib/mapping/MapFormatJson.cpp | 2 + lib/registerTypes/TypesLobbyPacks.cpp | 2 + lib/rmg/RiverPlacer.cpp | 1 + mapeditor/mainwindow.cpp | 2 + mapeditor/maphandler.cpp | 2 + mapeditor/objectbrowser.cpp | 1 - 28 files changed, 344 insertions(+), 249 deletions(-) create mode 100644 lib/RiverHandler.cpp create mode 100644 lib/RiverHandler.h create mode 100644 lib/RoadHandler.cpp create mode 100644 lib/RoadHandler.h diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index ec75f97bd..2fa5df8b7 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -61,6 +61,7 @@ #include "windows/InfoWindows.h" #include "../lib/UnlockGuard.h" #include "../lib/CPathfinder.h" +#include "../lib/RoadHandler.h" #include "../lib/TerrainHandler.h" #include #include "CServerHandler.h" diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index e1c1d6bda..bcefe0c23 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -29,7 +29,7 @@ #include "../../lib/rmg/CMapGenOptions.h" #include "../../lib/CModHandler.h" #include "../../lib/rmg/CRmgTemplateStorage.h" -#include "../../lib/TerrainHandler.h" +#include "../../lib/RoadHandler.h" RandomMapTab::RandomMapTab(): InterfaceObjectConfigurable() diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 54211d493..215781521 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -30,6 +30,8 @@ #include "CMT.h" #include "CMusicHandler.h" #include "../lib/CRandomGenerator.h" +#include "../lib/RoadHandler.h" +#include "../lib/RiverHandler.h" #include "../lib/TerrainHandler.h" #include "../lib/filesystem/ResourceID.h" #include "../lib/JsonDetail.h" diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index eea6ad26e..4399502d0 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -191,6 +191,8 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/ObstacleHandler.cpp ${MAIN_LIB_DIR}/StartInfo.cpp ${MAIN_LIB_DIR}/ResourceSet.cpp + ${MAIN_LIB_DIR}/RiverHandler.cpp + ${MAIN_LIB_DIR}/RoadHandler.cpp ${MAIN_LIB_DIR}/ScriptHandler.cpp ${MAIN_LIB_DIR}/TerrainHandler.cpp ${MAIN_LIB_DIR}/VCMIDirs.cpp @@ -439,6 +441,8 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/ObstacleHandler.h ${MAIN_LIB_DIR}/PathfinderUtil.h ${MAIN_LIB_DIR}/ResourceSet.h + ${MAIN_LIB_DIR}/RiverHandler.h + ${MAIN_LIB_DIR}/RoadHandler.h ${MAIN_LIB_DIR}/ScriptHandler.h ${MAIN_LIB_DIR}/ScopeGuard.h ${MAIN_LIB_DIR}/StartInfo.h diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 0f1e1f696..f4b5f47bd 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -16,7 +16,6 @@ #include "CGameState.h" #include "CTownHandler.h" #include "CModHandler.h" -#include "TerrainHandler.h" #include "StringConstants.h" #include "serializer/JsonDeserializer.h" #include "serializer/JsonUpdater.h" diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index cf7f348a1..1cae051ad 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -18,7 +18,6 @@ #include "GameConstants.h" #include "mapObjects/CQuest.h" #include "VCMI_Lib.h" -#include "TerrainHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index a694714da..cd8148bce 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -25,6 +25,8 @@ #include "spells/CSpellHandler.h" #include "CSkillHandler.h" #include "ScriptHandler.h" +#include "RoadHandler.h" +#include "RiverHandler.h" #include "TerrainHandler.h" #include "BattleFieldHandler.h" #include "ObstacleHandler.h" diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 1e4cf67c2..13d5500ae 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -18,7 +18,6 @@ #include "CStopWatch.h" #include "CConfigHandler.h" #include "CPlayerState.h" -#include "TerrainHandler.h" #include "PathfinderUtil.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index d485f69d2..03b5edfe6 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -32,6 +32,8 @@ #include "CPlayerState.h" #include "CSkillHandler.h" #include "ScriptHandler.h" +#include "RoadHandler.h" +#include "RiverHandler.h" #include "TerrainHandler.h" #include "serializer/Connection.h" diff --git a/lib/RiverHandler.cpp b/lib/RiverHandler.cpp new file mode 100644 index 000000000..4236d6ce4 --- /dev/null +++ b/lib/RiverHandler.cpp @@ -0,0 +1,78 @@ +/* + * Terrain.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "RiverHandler.h" +#include "CModHandler.h" +#include "CGeneralTextHandler.h" + +VCMI_LIB_NAMESPACE_BEGIN + +RiverTypeHandler::RiverTypeHandler() +{ + objects.push_back(new RiverType); +} + +RiverType * RiverTypeHandler::loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) +{ + RiverType * info = new RiverType; + + info->id = RiverId(index); + if (identifier.find(':') == std::string::npos) + info->identifier = scope + ":" + identifier; + else + info->identifier = identifier; + + info->tilesFilename = json["tilesFilename"].String(); + info->shortIdentifier = json["shortIdentifier"].String(); + info->deltaName = json["delta"].String(); + + VLC->generaltexth->registerString(info->getNameTextID(), json["text"].String()); + + return info; +} + +const std::vector & RiverTypeHandler::getTypeNames() const +{ + static const std::vector typeNames = { "river" }; + return typeNames; +} + +std::vector RiverTypeHandler::loadLegacyData(size_t dataSize) +{ + objects.resize(dataSize); + return {}; +} + +std::vector RiverTypeHandler::getDefaultAllowed() const +{ + return {}; +} + +std::string RiverType::getNameTextID() const +{ + return TextIdentifier( "river", identifier, "name" ).get(); +} + +std::string RiverType::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + +RiverType::RiverType(): + id(River::NO_RIVER), + identifier("core:empty") +{} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/RiverHandler.h b/lib/RiverHandler.h new file mode 100644 index 000000000..0dc57225a --- /dev/null +++ b/lib/RiverHandler.h @@ -0,0 +1,79 @@ +/* + * TerrainHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include +#include +#include "GameConstants.h" +#include "IHandlerBase.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class DLL_LINKAGE RiverType : public EntityT +{ + friend class RiverTypeHandler; + std::string identifier; + RiverId id; + + const std::string & getName() const override { return identifier;} +public: + int32_t getIndex() const override { return id.getNum(); } + int32_t getIconIndex() const override { return 0; } + const std::string & getJsonKey() const override { return identifier;} + void registerIcons(const IconRegistar & cb) const override {} + RiverId getId() const override { return id;} + void updateFrom(const JsonNode & data) {}; + + std::string getNameTextID() const; + std::string getNameTranslated() const; + + std::string tilesFilename; + std::string shortIdentifier; + std::string deltaName; + + RiverType(); + + template void serialize(Handler& h, const int version) + { + h & tilesFilename; + h & identifier; + h & deltaName; + h & id; + } +}; + +class DLL_LINKAGE RiverTypeService : public EntityServiceT +{ +public: +}; + +class DLL_LINKAGE RiverTypeHandler : public CHandlerBase +{ +public: + virtual RiverType * loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) override; + + RiverTypeHandler(); + + virtual const std::vector & getTypeNames() const override; + virtual std::vector loadLegacyData(size_t dataSize) override; + virtual std::vector getDefaultAllowed() const override; + + template void serialize(Handler & h, const int version) + { + h & objects; + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/RoadHandler.cpp b/lib/RoadHandler.cpp new file mode 100644 index 000000000..d7f94eecd --- /dev/null +++ b/lib/RoadHandler.cpp @@ -0,0 +1,78 @@ +/* + * Terrain.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "RoadHandler.h" +#include "CModHandler.h" +#include "CGeneralTextHandler.h" + +VCMI_LIB_NAMESPACE_BEGIN + +RoadTypeHandler::RoadTypeHandler() +{ + objects.push_back(new RoadType); +} + +RoadType * RoadTypeHandler::loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) +{ + RoadType * info = new RoadType; + + info->id = RoadId(index); + if (identifier.find(':') == std::string::npos) + info->identifier = scope + ":" + identifier; + else + info->identifier = identifier; + + info->tilesFilename = json["tilesFilename"].String(); + info->shortIdentifier = json["shortIdentifier"].String(); + info->movementCost = json["moveCost"].Integer(); + + VLC->generaltexth->registerString(info->getNameTextID(), json["text"].String()); + + return info; +} + +const std::vector & RoadTypeHandler::getTypeNames() const +{ + static const std::vector typeNames = { "road" }; + return typeNames; +} + +std::vector RoadTypeHandler::loadLegacyData(size_t dataSize) +{ + objects.resize(dataSize); + return {}; +} + +std::vector RoadTypeHandler::getDefaultAllowed() const +{ + return {}; +} + +std::string RoadType::getNameTextID() const +{ + return TextIdentifier( "road", identifier, "name" ).get(); +} + +std::string RoadType::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + +RoadType::RoadType(): + id(Road::NO_ROAD), + identifier("core:empty"), + movementCost(GameConstants::BASE_MOVEMENT_COST) +{} +VCMI_LIB_NAMESPACE_END diff --git a/lib/RoadHandler.h b/lib/RoadHandler.h new file mode 100644 index 000000000..2b74cdec2 --- /dev/null +++ b/lib/RoadHandler.h @@ -0,0 +1,79 @@ +/* + * TerrainHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include +#include +#include "GameConstants.h" +#include "IHandlerBase.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class DLL_LINKAGE RoadType : public EntityT +{ + friend class RoadTypeHandler; + std::string identifier; + RoadId id; + + const std::string & getName() const override { return identifier;} +public: + int32_t getIndex() const override { return id.getNum(); } + int32_t getIconIndex() const override { return 0; } + const std::string & getJsonKey() const override { return identifier;} + void registerIcons(const IconRegistar & cb) const override {} + RoadId getId() const override { return id;} + void updateFrom(const JsonNode & data) {}; + + std::string getNameTextID() const; + std::string getNameTranslated() const; + + std::string tilesFilename; + std::string shortIdentifier; + ui8 movementCost; + + RoadType(); + + template void serialize(Handler& h, const int version) + { + h & tilesFilename; + h & identifier; + h & id; + h & movementCost; + } +}; + +class DLL_LINKAGE RoadTypeService : public EntityServiceT +{ +public: +}; + +class DLL_LINKAGE RoadTypeHandler : public CHandlerBase +{ +public: + virtual RoadType * loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) override; + + RoadTypeHandler(); + + virtual const std::vector & getTypeNames() const override; + virtual std::vector loadLegacyData(size_t dataSize) override; + virtual std::vector getDefaultAllowed() const override; + + template void serialize(Handler & h, const int version) + { + h & objects; + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/TerrainHandler.cpp b/lib/TerrainHandler.cpp index 819e7cb02..c9e3fac67 100644 --- a/lib/TerrainHandler.cpp +++ b/lib/TerrainHandler.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "TerrainHandler.h" -#include "VCMI_Lib.h" #include "CModHandler.h" #include "CGeneralTextHandler.h" @@ -136,96 +135,6 @@ std::vector TerrainTypeHandler::getDefaultAllowed() const return {}; } -RiverTypeHandler::RiverTypeHandler() -{ - objects.push_back(new RiverType); -} - -RiverType * RiverTypeHandler::loadFromJson( - const std::string & scope, - const JsonNode & json, - const std::string & identifier, - size_t index) -{ - RiverType * info = new RiverType; - - info->id = RiverId(index); - if (identifier.find(':') == std::string::npos) - info->identifier = scope + ":" + identifier; - else - info->identifier = identifier; - - info->tilesFilename = json["tilesFilename"].String(); - info->shortIdentifier = json["shortIdentifier"].String(); - info->deltaName = json["delta"].String(); - - VLC->generaltexth->registerString(info->getNameTextID(), json["text"].String()); - - return info; -} - -const std::vector & RiverTypeHandler::getTypeNames() const -{ - static const std::vector typeNames = { "river" }; - return typeNames; -} - -std::vector RiverTypeHandler::loadLegacyData(size_t dataSize) -{ - objects.resize(dataSize); - return {}; -} - -std::vector RiverTypeHandler::getDefaultAllowed() const -{ - return {}; -} - -RoadTypeHandler::RoadTypeHandler() -{ - objects.push_back(new RoadType); -} - -RoadType * RoadTypeHandler::loadFromJson( - const std::string & scope, - const JsonNode & json, - const std::string & identifier, - size_t index) -{ - RoadType * info = new RoadType; - - info->id = RoadId(index); - if (identifier.find(':') == std::string::npos) - info->identifier = scope + ":" + identifier; - else - info->identifier = identifier; - - info->tilesFilename = json["tilesFilename"].String(); - info->shortIdentifier = json["shortIdentifier"].String(); - info->movementCost = json["moveCost"].Integer(); - - VLC->generaltexth->registerString(info->getNameTextID(), json["text"].String()); - - return info; -} - -const std::vector & RoadTypeHandler::getTypeNames() const -{ - static const std::vector typeNames = { "road" }; - return typeNames; -} - -std::vector RoadTypeHandler::loadLegacyData(size_t dataSize) -{ - objects.resize(dataSize); - return {}; -} - -std::vector RoadTypeHandler::getDefaultAllowed() const -{ - return {}; -} - bool TerrainType::isLand() const { return !isWater(); @@ -279,34 +188,4 @@ std::string TerrainType::getNameTranslated() const TerrainType::TerrainType() {} -std::string RoadType::getNameTextID() const -{ - return TextIdentifier( "road", identifier, "name" ).get(); -} - -std::string RoadType::getNameTranslated() const -{ - return VLC->generaltexth->translate(getNameTextID()); -} - -std::string RiverType::getNameTextID() const -{ - return TextIdentifier( "river", identifier, "name" ).get(); -} - -std::string RiverType::getNameTranslated() const -{ - return VLC->generaltexth->translate(getNameTextID()); -} - -RiverType::RiverType(): - id(River::NO_RIVER), - identifier("core:empty") -{} - -RoadType::RoadType(): - id(Road::NO_ROAD), - identifier("core:empty"), - movementCost(GameConstants::BASE_MOVEMENT_COST) -{} VCMI_LIB_NAMESPACE_END diff --git a/lib/TerrainHandler.h b/lib/TerrainHandler.h index ea3adb465..983e06cc3 100644 --- a/lib/TerrainHandler.h +++ b/lib/TerrainHandler.h @@ -12,9 +12,7 @@ #include #include -#include "ConstTransitivePtr.h" #include "GameConstants.h" -#include "JsonNode.h" #include "IHandlerBase.h" VCMI_LIB_NAMESPACE_BEGIN @@ -97,87 +95,11 @@ public: } }; -class DLL_LINKAGE RiverType : public EntityT -{ - friend class RiverTypeHandler; - std::string identifier; - RiverId id; - - const std::string & getName() const override { return identifier;} -public: - int32_t getIndex() const override { return id.getNum(); } - int32_t getIconIndex() const override { return 0; } - const std::string & getJsonKey() const override { return identifier;} - void registerIcons(const IconRegistar & cb) const override {} - RiverId getId() const override { return id;} - void updateFrom(const JsonNode & data) {}; - - std::string getNameTextID() const; - std::string getNameTranslated() const; - - std::string tilesFilename; - std::string shortIdentifier; - std::string deltaName; - - RiverType(); - - template void serialize(Handler& h, const int version) - { - h & tilesFilename; - h & identifier; - h & deltaName; - h & id; - } -}; - -class DLL_LINKAGE RoadType : public EntityT -{ - friend class RoadTypeHandler; - std::string identifier; - RoadId id; - - const std::string & getName() const override { return identifier;} -public: - int32_t getIndex() const override { return id.getNum(); } - int32_t getIconIndex() const override { return 0; } - const std::string & getJsonKey() const override { return identifier;} - void registerIcons(const IconRegistar & cb) const override {} - RoadId getId() const override { return id;} - void updateFrom(const JsonNode & data) {}; - - std::string getNameTextID() const; - std::string getNameTranslated() const; - - std::string tilesFilename; - std::string shortIdentifier; - ui8 movementCost; - - RoadType(); - - template void serialize(Handler& h, const int version) - { - h & tilesFilename; - h & identifier; - h & id; - h & movementCost; - } -}; - class DLL_LINKAGE TerrainTypeService : public EntityServiceT { public: }; -class DLL_LINKAGE RiverTypeService : public EntityServiceT -{ -public: -}; - -class DLL_LINKAGE RoadTypeService : public EntityServiceT -{ -public: -}; - class DLL_LINKAGE TerrainTypeHandler : public CHandlerBase { public: @@ -197,46 +119,4 @@ public: } }; -class DLL_LINKAGE RiverTypeHandler : public CHandlerBase -{ -public: - virtual RiverType * loadFromJson( - const std::string & scope, - const JsonNode & json, - const std::string & identifier, - size_t index) override; - - RiverTypeHandler(); - - virtual const std::vector & getTypeNames() const override; - virtual std::vector loadLegacyData(size_t dataSize) override; - virtual std::vector getDefaultAllowed() const override; - - template void serialize(Handler & h, const int version) - { - h & objects; - } -}; - -class DLL_LINKAGE RoadTypeHandler : public CHandlerBase -{ -public: - virtual RoadType * loadFromJson( - const std::string & scope, - const JsonNode & json, - const std::string & identifier, - size_t index) override; - - RoadTypeHandler(); - - virtual const std::vector & getTypeNames() const override; - virtual std::vector loadLegacyData(size_t dataSize) override; - virtual std::vector getDefaultAllowed() const override; - - template void serialize(Handler & h, const int version) - { - h & objects; - } -}; - VCMI_LIB_NAMESPACE_END diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index a29883611..e8245d88a 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -18,6 +18,8 @@ #include "CHeroHandler.h" #include "mapObjects/CObjectHandler.h" #include "CTownHandler.h" +#include "RoadHandler.h" +#include "RiverHandler.h" #include "TerrainHandler.h" #include "CBuildingHandler.h" #include "spells/CSpellHandler.h" diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index da9675bce..581d82ddd 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -15,7 +15,6 @@ #include "../filesystem/Filesystem.h" #include "../mapObjects/CGTownInstance.h" #include "../CGeneralTextHandler.h" -#include "../TerrainHandler.h" #include "../BattleFieldHandler.h" #include "../ObstacleHandler.h" diff --git a/lib/battle/BattleProxy.cpp b/lib/battle/BattleProxy.cpp index ff2f3d9c9..ab0402c33 100644 --- a/lib/battle/BattleProxy.cpp +++ b/lib/battle/BattleProxy.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "BattleProxy.h" #include "Unit.h" -#include "TerrainHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 892240bc7..9b8bcdf3d 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -18,6 +18,7 @@ #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" #include "../TerrainHandler.h" +#include "../RoadHandler.h" #include "../CModHandler.h" #include "../CSoundBase.h" #include "../spells/CSpellHandler.h" diff --git a/lib/mapping/CDrawRoadsOperation.cpp b/lib/mapping/CDrawRoadsOperation.cpp index 3a62d3c9d..a2e252bfe 100644 --- a/lib/mapping/CDrawRoadsOperation.cpp +++ b/lib/mapping/CDrawRoadsOperation.cpp @@ -12,7 +12,8 @@ #include "CDrawRoadsOperation.h" #include "CMap.h" -#include "../TerrainHandler.h" +#include "../RoadHandler.h" +#include "../RiverHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index e77588ac5..edf9dec98 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -15,6 +15,8 @@ #include "../CCreatureHandler.h" #include "../CTownHandler.h" #include "../CHeroHandler.h" +#include "../RiverHandler.h" +#include "../RoadHandler.h" #include "../TerrainHandler.h" #include "../mapObjects/CObjectClassesHandler.h" #include "../mapObjects/CGHeroInstance.h" diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 87d426cbf..794e30fcc 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -25,6 +25,8 @@ #include "../mapObjects/MapObjects.h" #include "../VCMI_Lib.h" #include "../TerrainHandler.h" +#include "../RoadHandler.h" +#include "../RiverHandler.h" #include "../NetPacksBase.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 4b954ceda..fec83720b 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -19,6 +19,8 @@ #include "../CHeroHandler.h" #include "../CTownHandler.h" #include "../VCMI_Lib.h" +#include "../RiverHandler.h" +#include "../RoadHandler.h" #include "../TerrainHandler.h" #include "../mapObjects/ObjectTemplate.h" #include "../mapObjects/CObjectHandler.h" diff --git a/lib/registerTypes/TypesLobbyPacks.cpp b/lib/registerTypes/TypesLobbyPacks.cpp index 2ab65eccf..106529f37 100644 --- a/lib/registerTypes/TypesLobbyPacks.cpp +++ b/lib/registerTypes/TypesLobbyPacks.cpp @@ -22,6 +22,8 @@ #include "../CHeroHandler.h" #include "../spells/CSpellHandler.h" #include "../CTownHandler.h" +#include "../RoadHandler.h" +#include "../RiverHandler.h" #include "../TerrainHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" diff --git a/lib/rmg/RiverPlacer.cpp b/lib/rmg/RiverPlacer.cpp index 38f937f65..de4dd0ace 100644 --- a/lib/rmg/RiverPlacer.cpp +++ b/lib/rmg/RiverPlacer.cpp @@ -13,6 +13,7 @@ #include "Functions.h" #include "CMapGenerator.h" #include "RmgMap.h" +#include "../RiverHandler.h" #include "../TerrainHandler.h" #include "../mapping/CMap.h" #include "../mapping/CMapEditManager.h" diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 10a541abe..93f4fe06b 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -27,6 +27,8 @@ #include "../lib/mapping/CMapService.h" #include "../lib/mapping/CMap.h" #include "../lib/mapping/CMapEditManager.h" +#include "../lib/RoadHandler.h" +#include "../lib/RiverHandler.h" #include "../lib/TerrainHandler.h" #include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/filesystem/CFilesystemLoader.h" diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp index c722c86fc..b9799eb0e 100644 --- a/mapeditor/maphandler.cpp +++ b/mapeditor/maphandler.cpp @@ -12,6 +12,8 @@ #include "StdInc.h" #include "maphandler.h" #include "graphics.h" +#include "../lib/RoadHandler.h" +#include "../lib/RiverHandler.h" #include "../lib/TerrainHandler.h" #include "../lib/mapping/CMap.h" #include "../lib/mapObjects/CGHeroInstance.h" diff --git a/mapeditor/objectbrowser.cpp b/mapeditor/objectbrowser.cpp index fa9d20508..68d4addf1 100644 --- a/mapeditor/objectbrowser.cpp +++ b/mapeditor/objectbrowser.cpp @@ -11,7 +11,6 @@ #include "StdInc.h" #include "objectbrowser.h" #include "../lib/mapObjects/CObjectClassesHandler.h" -#include "../lib/TerrainHandler.h" ObjectBrowserProxyModel::ObjectBrowserProxyModel(QObject *parent) : QSortFilterProxyModel{parent}, terrain(ETerrainId::ANY_TERRAIN) From cb3fd24ec0d3308ac7605e62aec7a0ed5db58eb9 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Wed, 11 Jan 2023 17:11:44 +0100 Subject: [PATCH 078/197] Fixes after merge, move removeGUI to Client class --- client/Client.cpp | 16 ++++++++++- client/Client.h | 1 + client/ClientCommandManager.cpp | 51 ++------------------------------- client/ClientCommandManager.h | 1 - 4 files changed, 19 insertions(+), 50 deletions(-) diff --git a/client/Client.cpp b/client/Client.cpp index 1a6b9c4b2..b568ddc95 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -46,9 +46,9 @@ #include "../lib/CThreadHelper.h" #include "../lib/registerTypes/RegisterTypes.h" #include "gui/CGuiHandler.h" -#include "CMT.h" #include "CServerHandler.h" #include "../lib/ScriptHandler.h" +#include "windows/CAdvmapInterface.h" #include #ifdef VCMI_ANDROID @@ -761,6 +761,20 @@ void CClient::reinitScripting() #endif } +void CClient::removeGUI() +{ + // CClient::endGame + GH.curInt = nullptr; + if(GH.topInt()) + GH.topInt()->deactivate(); + adventureInt = nullptr; + GH.listInt.clear(); + GH.objsToBlit.clear(); + GH.statusbar = nullptr; + logGlobal->info("Removed GUI."); + + LOCPLINT = nullptr; +} #ifdef VCMI_ANDROID extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jobject cls) diff --git a/client/Client.h b/client/Client.h index 71fbd2eb2..644514d78 100644 --- a/client/Client.h +++ b/client/Client.h @@ -240,6 +240,7 @@ public: void showInfoDialog(InfoWindow * iw) override {}; void showInfoDialog(const std::string & msg, PlayerColor player) override {}; + void removeGUI(); #if SCRIPTING_ENABLED scripting::Pool * getGlobalContextPool() const override; diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 3f3e55599..b1131c825 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -57,7 +57,7 @@ void ClientCommandManager::handleGoSolo() else { color = LOCPLINT->playerID; - removeGUI(); + CSH->client->removeGUI(); for(auto & elem : CSH->client->gameState()->players) { if(elem.second.human) @@ -92,7 +92,7 @@ void ClientCommandManager::handleControlAi(const std::string &colorName) continue; } - removeGUI(); + CSH->client->removeGUI(); CSH->client->installNewPlayerInterface(std::make_shared(elem.first), elem.first); } GH.totalRedraw(); @@ -169,38 +169,7 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle // } else if(message=="convert txt") { - //TODO: to be replaced with "VLC->generaltexth->dumpAllTexts();" due to https://github.com/vcmi/vcmi/pull/1329 merge: - - printCommandMessage("Command accepted.\t"); - - const boost::filesystem::path outPath = - VCMIDirs::get().userExtractedPath(); - - boost::filesystem::create_directories(outPath); - - auto extractVector = [=](const std::vector & source, const std::string & name) - { - JsonNode data(JsonNode::JsonType::DATA_VECTOR); - int64_t index = 0; - for(auto & line : source) - { - JsonNode lineNode(JsonNode::JsonType::DATA_STRUCT); - lineNode["text"].String() = line; - lineNode["index"].Integer() = index++; - data.Vector().push_back(lineNode); - } - - const boost::filesystem::path filePath = outPath / (name + ".json"); - boost::filesystem::ofstream file(filePath); - file << data.toJson(); - }; - - extractVector(VLC->generaltexth->allTexts, "generalTexts"); - extractVector(VLC->generaltexth->jktexts, "jkTexts"); - extractVector(VLC->generaltexth->arraytxt, "arrayTexts"); - - printCommandMessage("\rExtracting done :)\n"); - printCommandMessage("Extracted files can be found in" + outPath.string() + " directory\n"); + VLC->generaltexth->dumpAllTexts(); } else if(message=="get config") { @@ -459,20 +428,6 @@ void ClientCommandManager::giveTurn(const PlayerColor &colorIdentifier) yt.applyCl(CSH->client); } -void ClientCommandManager::removeGUI() -{ - // CClient::endGame - GH.curInt = nullptr; - if(GH.topInt()) - GH.topInt()->deactivate(); - GH.listInt.clear(); - GH.objsToBlit.clear(); - GH.statusbar = nullptr; - printCommandMessage("Removed GUI.", ELogLevel::INFO); - - LOCPLINT = nullptr; -} - void ClientCommandManager::printInfoAboutInterfaceObject(const CIntObject *obj, int level) { std::stringstream sbuffer; diff --git a/client/ClientCommandManager.h b/client/ClientCommandManager.h index 51295e640..797a07a42 100644 --- a/client/ClientCommandManager.h +++ b/client/ClientCommandManager.h @@ -20,7 +20,6 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a static bool currentCallFromIngameConsole; static void giveTurn(const PlayerColor &color); - static void removeGUI(); static void printInfoAboutInterfaceObject(const CIntObject *obj, int level); static void printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType = ELogLevel::NOT_SET); static void handleGoSolo(); From 91a52ebb8e6e807ec62719b23eb2f79ae943429e Mon Sep 17 00:00:00 2001 From: Dydzio Date: Wed, 11 Jan 2023 17:14:10 +0100 Subject: [PATCH 079/197] Add GUI lock --- client/ClientCommandManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index b1131c825..dc4a61f7d 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -487,6 +487,7 @@ void ClientCommandManager::printCommandMessage(const std::string &commandMessage if(currentCallFromIngameConsole) { + boost::unique_lock un(*CPlayerInterface::pim); if(LOCPLINT && LOCPLINT->cingconsole) { LOCPLINT->cingconsole->print(commandMessage); From e1880604806e649fb189b21ce632d991375e6c05 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 9 Jan 2023 23:27:38 +0200 Subject: [PATCH 080/197] Fix server shutdown on transferring artifact to commander --- server/CGameHandler.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 40e684973..943f0e5bc 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3916,9 +3916,17 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat moveArtifact(dst, ArtifactLocation(dst.artHolder, ArtifactPosition( (si32)dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START))); } - auto hero = boost::get>(dst.artHolder); - if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->id, dst.slot)) - giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); + + try + { + auto hero = boost::get>(dst.artHolder); + if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->id, dst.slot)) + giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); + } + catch (boost::bad_get const &) + { + // object other than hero received an art - ignore + } MoveArtifact ma(&src, &dst); sendAndApply(&ma); From 9658ffba99c825cc46eb92082cbeb8252d14f690 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 9 Jan 2023 23:38:04 +0200 Subject: [PATCH 081/197] Allow disabling & deleting local mods other than vcmi mod --- launcher/modManager/cmodlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modManager/cmodlist.cpp b/launcher/modManager/cmodlist.cpp index e08c99ce4..1ec1313d0 100644 --- a/launcher/modManager/cmodlist.cpp +++ b/launcher/modManager/cmodlist.cpp @@ -124,7 +124,7 @@ bool CModEntry::isCompatible() const bool CModEntry::isEssential() const { - return getValue("storedLocaly").toBool(); + return getName() == "vcmi"; } bool CModEntry::isInstalled() const From dd3adb7e169a886dd1d103344d340bee567741fa Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 12 Jan 2023 23:52:03 +0200 Subject: [PATCH 082/197] Arrow towers damage algorithm should now match H3 --- lib/battle/CBattleInfoCallback.cpp | 41 +++++++++++++----------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 808249246..1e6358d7c 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -24,39 +24,32 @@ VCMI_LIB_NAMESPACE_BEGIN namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO { -/* - *Here are 2 explanations how below algorithm should work in H3, looks like they are not 100% accurate as it results in one damage number, not min/max range: - * - *1. http://heroes.thelazy.net/wiki/Arrow_tower - * - *2. All towns' turrets do the same damage. If Fort, Citadel or Castle is built damage of the Middle turret is 15, and 7,5 for others. - *Buildings increase turrets' damage, but only those buildings that are new in town view, not upgrades to the existing. So, every building save: - *- dwellings' upgrades - *- Mage Guild upgrades - *- Horde buildings - *- income upgrades - *- some special ones - *increases middle Turret damage by 3, and 1,5 for the other two. - *Damage is almost always the maximum one (right click on the Turret), sometimes +1/2 points, and it does not depend on the target. Nothing can influence it, except the mentioned above (but it will be roughly double if the defender has Armorer or Air Shield). - *Maximum damage for Castle, Conflux is 120, Necropolis, Inferno, Fortress 125, Stronghold, Turret, and Dungeon 130 (for all three Turrets). - *Artillery allows the player to control the Turrets. - */ + static void retrieveTurretDamageRange(const CGTownInstance * town, const battle::Unit * turret, double & outMinDmg, double & outMaxDmg)//does not match OH3 yet, but damage is somewhat close { + // http://heroes.thelazy.net/wiki/Arrow_tower assert(turret->creatureIndex() == CreatureID::ARROW_TOWERS); assert(town); assert(turret->getPosition() >= -4 && turret->getPosition() <= -2); - const float multiplier = (turret->getPosition() == -2) ? 1.0f : 0.5f; + // base damage, irregardless of town level + static const int baseDamageKeep = 10; + static const int baseDamageTower = 6; - //Revised - Where do below values come from? - /*int baseMin = 6; - int baseMax = 10;*/ + // extra damage, for each building in town + static const int extraDamage = 2; - const int baseDamage = 15; + const int townLevel = town->getTownLevel(); - outMinDmg = multiplier * (baseDamage + town->getTownLevel() * 3); - outMaxDmg = outMinDmg; + int minDamage; + + if (turret->getPosition() == BattleHex::CASTLE_CENTRAL_TOWER) + minDamage = baseDamageKeep + townLevel * extraDamage; + else + minDamage = baseDamageTower + townLevel / 2 * extraDamage; + + outMinDmg = minDamage; + outMaxDmg = minDamage * 2; } static BattleHex lineToWallHex(int line) //returns hex with wall in given line (y coordinate) From b86704bece692692f52594afd0171b0c20411add Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 12 Jan 2023 23:53:29 +0200 Subject: [PATCH 083/197] Ballistics mechanics should now match H3 --- server/CGameHandler.cpp | 179 ++++++++++++++++++---------------------- 1 file changed, 81 insertions(+), 98 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index ddbf5fd92..fc1b94cd3 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4790,7 +4790,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) case EActionType::CATAPULT: { //TODO: unify with spells::effects:Catapult - auto getCatapultHitChance = [&](EWallPart::EWallPart part, const CHeroHandler::SBallisticsLevelInfo & sbi) -> int + auto getCatapultHitChance = [](EWallPart::EWallPart part, const CHeroHandler::SBallisticsLevelInfo & sbi) -> int { switch(part) { @@ -4811,115 +4811,105 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) } }; - auto wrapper = wrapAction(ba); - - if(target.size() < 1) + auto getBallisticsInfo = [this, &ba] (const CStack * actor) { - complain("Destination required for catapult action."); - ok = false; - break; - } - auto destination = target.at(0).hexValue; + const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side); - const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side); - - CHeroHandler::SBallisticsLevelInfo stackBallisticsParameters; - if(stack->getCreature()->idNumber == CreatureID::CATAPULT) - stackBallisticsParameters = VLC->heroh->ballistics.at(attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::BALLISTICS)); - else - { - if(stack->hasBonusOfType(Bonus::CATAPULT_EXTRA_SHOTS)) //by design use advanced ballistics parameters with this bonus present, upg. cyclops use advanced ballistics, nonupg. use basic in OH3 + if(actor->getCreature()->idNumber == CreatureID::CATAPULT) + return VLC->heroh->ballistics.at(attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::BALLISTICS)); + else { - stackBallisticsParameters = VLC->heroh->ballistics.at(2); - stackBallisticsParameters.shots = 1; //skip default "2 shots" from adv. ballistics + //by design use advanced ballistics parameters with this bonus present, upg. cyclops use advanced ballistics, nonupg. use basic in OH3 + int ballisticsLevel = actor->hasBonusOfType(Bonus::CATAPULT_EXTRA_SHOTS) ? 2 : 1; + + auto parameters = VLC->heroh->ballistics.at(ballisticsLevel); + parameters.shots = 1 + std::max(actor->valOfBonuses(Bonus::CATAPULT_EXTRA_SHOTS), 0); + + return parameters; + } + }; + + auto isWallPartAttackable = [this] (EWallPart::EWallPart part) + { + return (gs->curB->si.wallState[part] == EWallState::INTACT || gs->curB->si.wallState[part] == EWallState::DAMAGED); + }; + + CHeroHandler::SBallisticsLevelInfo stackBallisticsParameters = getBallisticsInfo(stack); + + auto wrapper = wrapAction(ba); + auto destination = target.empty() ? BattleHex(BattleHex::INVALID) : target.at(0).hexValue; + auto desiredTarget = gs->curB->battleHexToWallPart(destination); + + for (int shotNumber=0; shotNumberheroh->ballistics.at(1); - - stackBallisticsParameters.shots += std::max(stack->valOfBonuses(Bonus::CATAPULT_EXTRA_SHOTS), 0); //0 is allowed minimum to let modders force advanced ballistics for "oneshotting creatures" - } - - auto wallPart = gs->curB->battleHexToWallPart(destination); - if (!gs->curB->isWallPartPotentiallyAttackable(wallPart)) - { - complain("catapult tried to attack non-catapultable hex!"); - break; - } - - //in successive iterations damage is dealt but not yet subtracted from wall's HPs - auto ¤tHP = gs->curB->si.wallState; - - if (currentHP.at(wallPart) == EWallState::DESTROYED || currentHP.at(wallPart) == EWallState::NONE) - { - complain("catapult tried to attack already destroyed wall part!"); - break; - } - - for (int g=0; g allowedTargets; - for (size_t i=0; i< currentHP.size(); i++) - { - if(currentHP.at(i) != EWallState::DESTROYED && - currentHP.at(i) != EWallState::NONE) - allowedTargets.push_back(EWallPart::EWallPart(i)); - } - if (allowedTargets.empty()) - break; - attackedPart = *RandomGeneratorUtil::nextItem(allowedTargets, getRandomGenerator()); - } + static const std::array walls = { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL }; + static const std::array towers= { EWallPart::BOTTOM_TOWER, EWallPart::KEEP, EWallPart::UPPER_TOWER }; + static const EWallPart::EWallPart gates = EWallPart::GATE; + + // in H3, catapult under automatic control will attack objects in following order: + // walls, gates, towers + std::vector potentialTargets; + for (auto & part : walls ) + if (isWallPartAttackable(part)) + potentialTargets.push_back(part); + + if (potentialTargets.empty() && isWallPartAttackable(gates)) + potentialTargets.push_back(gates); + + if (potentialTargets.empty()) + for (auto & part : towers ) + if (isWallPartAttackable(part)) + potentialTargets.push_back(part); + + if (potentialTargets.empty()) + break; // everything is gone, can't attack anymore + + actualTarget = *RandomGeneratorUtil::nextItem(potentialTargets, getRandomGenerator()); } - while (!hitSuccessfull); + assert(actualTarget != EWallPart::INVALID); - if (!hitSuccessfull) // break triggered - no target to shoot at - break; + std::array damageChances = { stackBallisticsParameters.noDmg, stackBallisticsParameters.oneDmg, stackBallisticsParameters.twoDmg }; //dmgChance[i] - chance for doing i dmg when hit is successful + int totalChance = std::accumulate(damageChances.begin(), damageChances.end(), 0); + int damageRandom = getRandomGenerator().nextInt(totalChance - 1); + int dealtDamage = 0; - CatapultAttack ca; //package for clients - CatapultAttack::AttackInfo attack; - attack.attackedPart = attackedPart; - attack.destinationTile = destination; - attack.damageDealt = 0; - BattleUnitsChanged removeUnits; - - int dmgChance[] = { stackBallisticsParameters.noDmg, stackBallisticsParameters.oneDmg, stackBallisticsParameters.twoDmg }; //dmgChance[i] - chance for doing i dmg when hit is successful - - int dmgRand = getRandomGenerator().nextInt(99); - //accumulating dmgChance - dmgChance[1] += dmgChance[0]; - dmgChance[2] += dmgChance[1]; //calculating dealt damage - for (int damage = 0; damage < ARRAY_COUNT(dmgChance); ++damage) + for (int damage = 0; damage < damageChances.size(); ++damage) { - if (dmgRand <= dmgChance[damage]) + if (damageRandom <= damageChances[damage]) { - attack.damageDealt = damage; + dealtDamage = damage; break; } + damageRandom -= damageChances[damage]; } - // attacked tile may have changed - update destination - attack.destinationTile = gs->curB->wallPartToBattleHex(EWallPart::EWallPart(attack.attackedPart)); + + CatapultAttack::AttackInfo attack; + attack.attackedPart = actualTarget; + attack.destinationTile = gs->curB->wallPartToBattleHex(actualTarget); + attack.damageDealt = dealtDamage; + + CatapultAttack ca; //package for clients + ca.attacker = ba.stackNumber; + ca.attackedParts.push_back(attack); + sendAndApply(&ca); logGlobal->trace("Catapult attacks %d dealing %d damage", (int)attack.attackedPart, (int)attack.damageDealt); //removing creatures in turrets / keep if one is destroyed - if (currentHP.at(attackedPart) - attack.damageDealt <= 0 && (attackedPart == EWallPart::KEEP || //HP enum subtraction not intuitive, consider using SiegeInfo::applyDamage - attackedPart == EWallPart::BOTTOM_TOWER || attackedPart == EWallPart::UPPER_TOWER)) + if (gs->curB->si.wallState[actualTarget] <= 0 && (actualTarget == EWallPart::KEEP || actualTarget == EWallPart::BOTTOM_TOWER || actualTarget == EWallPart::UPPER_TOWER)) { int posRemove = -1; - switch(attackedPart) + switch(actualTarget) { case EWallPart::KEEP: posRemove = BattleHex::CASTLE_CENTRAL_TOWER; @@ -4936,18 +4926,13 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) { if(elem->initialPosition == posRemove) { + BattleUnitsChanged removeUnits; removeUnits.changedStacks.emplace_back(elem->unitId(), UnitChanges::EOperation::REMOVE); + sendAndApply(&removeUnits); break; } } } - ca.attacker = ba.stackNumber; - ca.attackedParts.push_back(attack); - - sendAndApply(&ca); - - if(!removeUnits.changedStacks.empty()) - sendAndApply(&removeUnits); } //finish by scope guard break; @@ -6769,8 +6754,6 @@ void CGameHandler::runBattle() if (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(Bonus::MANUAL_CONTROL, CreatureID::CATAPULT)) { BattleAction attack; - auto destination = *RandomGeneratorUtil::nextItem(attackableBattleHexes, getRandomGenerator()); - attack.aimToHex(destination); attack.actionType = EActionType::CATAPULT; attack.side = next->side; attack.stackNumber = next->ID; From 500cf7f15d62a8e6acdf467d30c47bed598bc9de Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 13 Jan 2023 00:35:58 +0200 Subject: [PATCH 084/197] EWallPart & EWallState are now enum class --- AI/BattleAI/BattleAI.cpp | 6 ++-- AI/BattleAI/StackWithBonuses.cpp | 2 +- AI/BattleAI/StackWithBonuses.h | 2 +- client/battle/BattleSiegeController.cpp | 14 ++++---- client/battle/BattleSiegeController.h | 2 +- lib/GameConstants.h | 30 +++++++---------- lib/NetPacks.h | 2 +- lib/NetPacksLib.cpp | 2 +- lib/battle/BattleInfo.cpp | 8 ++--- lib/battle/BattleInfo.h | 4 +-- lib/battle/BattleProxy.cpp | 2 +- lib/battle/BattleProxy.h | 2 +- lib/battle/CBattleInfoCallback.cpp | 24 ++++++------- lib/battle/CBattleInfoCallback.h | 6 ++-- lib/battle/CBattleInfoEssentials.cpp | 2 +- lib/battle/CBattleInfoEssentials.h | 2 +- lib/battle/IBattleState.h | 4 +-- lib/battle/SiegeInfo.cpp | 6 ++-- lib/battle/SiegeInfo.h | 4 +-- lib/spells/effects/Catapult.cpp | 45 ++++++++++--------------- lib/spells/effects/Obstacle.cpp | 2 +- server/CGameHandler.cpp | 14 ++++---- 22 files changed, 84 insertions(+), 101 deletions(-) diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 1bbc2f09e..0ae8a183d 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -46,14 +46,14 @@ std::vector CBattleAI::getBrokenWallMoatHexes() const { std::vector result; - for(int wallPart = EWallPart::BOTTOM_WALL; wallPart < EWallPart::UPPER_WALL; wallPart++) + for(EWallPart wallPart : { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL }) { auto state = cb->battleGetWallState(wallPart); if(state != EWallState::DESTROYED) continue; - auto wallHex = cb->wallPartToBattleHex((EWallPart::EWallPart)wallPart); + auto wallHex = cb->wallPartToBattleHex((EWallPart)wallPart); auto moatHex = wallHex.cloneInDirection(BattleHex::LEFT); result.push_back(moatHex); @@ -364,7 +364,7 @@ BattleAction CBattleAI::useCatapult(const CStack * stack) } else { - EWallPart::EWallPart wallParts[] = { + EWallPart wallParts[] = { EWallPart::KEEP, EWallPart::BOTTOM_TOWER, EWallPart::UPPER_TOWER, diff --git a/AI/BattleAI/StackWithBonuses.cpp b/AI/BattleAI/StackWithBonuses.cpp index b843b7b4e..e698c3014 100644 --- a/AI/BattleAI/StackWithBonuses.cpp +++ b/AI/BattleAI/StackWithBonuses.cpp @@ -403,7 +403,7 @@ void HypotheticBattle::removeUnitBonus(uint32_t id, const std::vector & b bonusTreeVersion++; } -void HypotheticBattle::setWallState(int partOfWall, si8 state) +void HypotheticBattle::setWallState(EWallPart partOfWall, EWallState state) { //TODO:HypotheticBattle::setWallState } diff --git a/AI/BattleAI/StackWithBonuses.h b/AI/BattleAI/StackWithBonuses.h index a2ebafd70..48070ca40 100644 --- a/AI/BattleAI/StackWithBonuses.h +++ b/AI/BattleAI/StackWithBonuses.h @@ -130,7 +130,7 @@ public: void updateUnitBonus(uint32_t id, const std::vector & bonus) override; void removeUnitBonus(uint32_t id, const std::vector & bonus) override; - void setWallState(int partOfWall, si8 state) override; + void setWallState(EWallPart partOfWall, EWallState state) override; void addObstacle(const ObstacleChanges & changes) override; void updateObstacle(const ObstacleChanges& changes) override; diff --git a/client/battle/BattleSiegeController.cpp b/client/battle/BattleSiegeController.cpp index 0d6b76895..0f6a76f7f 100644 --- a/client/battle/BattleSiegeController.cpp +++ b/client/battle/BattleSiegeController.cpp @@ -28,7 +28,7 @@ #include "../../lib/CStack.h" #include "../../lib/mapObjects/CGTownInstance.h" -std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState::EWallState state) const +std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const { auto getImageIndex = [&]() -> int { @@ -130,9 +130,9 @@ bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) { case EWallVisual::MOAT: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER; case EWallVisual::MOAT_BANK: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER && town->town->faction->index != ETownType::NECROPOLIS; - case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::KEEP)) != EWallState::DESTROYED; - case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER)) != EWallState::DESTROYED; - case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER)) != EWallState::DESTROYED; + case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && EWallState(owner.curInt->cb->battleGetWallState(EWallPart::KEEP)) != EWallState::DESTROYED; + case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState(owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER)) != EWallState::DESTROYED; + case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState(owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER)) != EWallState::DESTROYED; default: return true; } } @@ -321,7 +321,7 @@ bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const if (!owner.curInt->cb->isWallPartPotentiallyAttackable(wallPart)) return false; - auto state = owner.curInt->cb->battleGetWallState(static_cast(wallPart)); + auto state = owner.curInt->cb->battleGetWallState(wallPart); return state != EWallState::DESTROYED && state != EWallState::NONE; } @@ -354,12 +354,12 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca) for (auto attackInfo : ca.attackedParts) { - int wallId = attackInfo.attackedPart + EWallVisual::DESTRUCTIBLE_FIRST; + int wallId = static_cast(attackInfo.attackedPart) + EWallVisual::DESTRUCTIBLE_FIRST; //gate state changing handled separately if (wallId == EWallVisual::GATE) continue; - auto wallState = EWallState::EWallState(owner.curInt->cb->battleGetWallState(attackInfo.attackedPart)); + auto wallState = EWallState(owner.curInt->cb->battleGetWallState(attackInfo.attackedPart)); wallPieceImages[wallId] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState)); } diff --git a/client/battle/BattleSiegeController.h b/client/battle/BattleSiegeController.h index 90e304c46..dd4795afc 100644 --- a/client/battle/BattleSiegeController.h +++ b/client/battle/BattleSiegeController.h @@ -76,7 +76,7 @@ class BattleSiegeController std::array, EWallVisual::WALL_LAST + 1> wallPieceImages; /// return URI for image for a wall piece - std::string getWallPieceImageName(EWallVisual::EWallVisual what, EWallState::EWallState state) const; + std::string getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const; /// returns BattleHex to which chosen wall piece is bound BattleHex getWallPiecePosition(EWallVisual::EWallVisual what) const; diff --git a/lib/GameConstants.h b/lib/GameConstants.h index b995bc089..5d50073bc 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -615,28 +615,22 @@ namespace ECommander const int MAX_SKILL_LEVEL = 5; } -namespace EWallPart +enum class EWallPart : int8_t { - enum EWallPart - { - INDESTRUCTIBLE_PART_OF_GATE = -3, INDESTRUCTIBLE_PART = -2, INVALID = -1, - KEEP = 0, BOTTOM_TOWER, BOTTOM_WALL, BELOW_GATE, OVER_GATE, UPPER_WALL, UPPER_TOWER, GATE, - PARTS_COUNT /* This constant SHOULD always stay as the last item in the enum. */ - }; -} + INDESTRUCTIBLE_PART_OF_GATE = -3, INDESTRUCTIBLE_PART = -2, INVALID = -1, + KEEP = 0, BOTTOM_TOWER, BOTTOM_WALL, BELOW_GATE, OVER_GATE, UPPER_WALL, UPPER_TOWER, GATE, + PARTS_COUNT /* This constant SHOULD always stay as the last item in the enum. */ +}; -namespace EWallState +enum class EWallState : int8_t { - enum EWallState - { - NONE = -1, //no wall - DESTROYED, - DAMAGED, - INTACT - }; -} + NONE = -1, //no wall + DESTROYED, + DAMAGED, + INTACT +}; -enum class EGateState : ui8 +enum class EGateState : uint8_t { NONE, CLOSED, diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 82df6ab91..76a93c497 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1827,7 +1827,7 @@ struct ELF_VISIBILITY CatapultAttack : public CPackForClient struct AttackInfo { si16 destinationTile; - ui8 attackedPart; + EWallPart attackedPart; ui8 damageDealt; template void serialize(Handler & h, const int version) diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 5e4e869ea..0be775aa8 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -1707,7 +1707,7 @@ DLL_LINKAGE void CatapultAttack::applyBattle(IBattleState * battleState) for(const auto & part : attackedParts) { - auto newWallState = SiegeInfo::applyDamage(EWallState::EWallState(battleState->getWallState(part.attackedPart)), part.damageDealt); + auto newWallState = SiegeInfo::applyDamage(EWallState(battleState->getWallState(part.attackedPart)), part.damageDealt); battleState->setWallState(part.attackedPart, newWallState); } } diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index f1687ccef..74024b8a4 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -225,7 +225,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const for (int b = 0; b < curB->si.wallState.size(); ++b) { - curB->si.wallState[b] = EWallState::INTACT; + curB->si.wallState[EWallPart(b)] = EWallState::INTACT; } if (!town->hasBuilt(BuildingID::CITADEL)) @@ -610,7 +610,7 @@ const CGTownInstance * BattleInfo::getDefendedTown() const return town; } -si8 BattleInfo::getWallState(int partOfWall) const +EWallState BattleInfo::getWallState(EWallPart partOfWall) const { return si.wallState.at(partOfWall); } @@ -913,9 +913,9 @@ void BattleInfo::addOrUpdateUnitBonus(CStack * sta, const Bonus & value, bool fo } } -void BattleInfo::setWallState(int partOfWall, si8 state) +void BattleInfo::setWallState(EWallPart partOfWall, EWallState state) { - si.wallState.at(partOfWall) = state; + si.wallState[partOfWall] = state; } void BattleInfo::addObstacle(const ObstacleChanges & changes) diff --git a/lib/battle/BattleInfo.h b/lib/battle/BattleInfo.h index 76d3a9db2..1bafc74dd 100644 --- a/lib/battle/BattleInfo.h +++ b/lib/battle/BattleInfo.h @@ -87,7 +87,7 @@ public: ui8 getTacticsSide() const override; const CGTownInstance * getDefendedTown() const override; - si8 getWallState(int partOfWall) const override; + EWallState getWallState(EWallPart partOfWall) const override; EGateState getGateState() const override; uint32_t getCastSpells(ui8 side) const override; @@ -115,7 +115,7 @@ public: void updateUnitBonus(uint32_t id, const std::vector & bonus) override; void removeUnitBonus(uint32_t id, const std::vector & bonus) override; - void setWallState(int partOfWall, si8 state) override; + void setWallState(EWallPart partOfWall, EWallState state) override; void addObstacle(const ObstacleChanges & changes) override; void updateObstacle(const ObstacleChanges& changes) override; diff --git a/lib/battle/BattleProxy.cpp b/lib/battle/BattleProxy.cpp index 0ebb475ba..67e4aae16 100644 --- a/lib/battle/BattleProxy.cpp +++ b/lib/battle/BattleProxy.cpp @@ -89,7 +89,7 @@ const CGTownInstance * BattleProxy::getDefendedTown() const return subject->battleGetDefendedTown(); } -si8 BattleProxy::getWallState(int partOfWall) const +EWallState BattleProxy::getWallState(EWallPart partOfWall) const { return subject->battleGetWallState(partOfWall); } diff --git a/lib/battle/BattleProxy.h b/lib/battle/BattleProxy.h index 8a7b0e13d..0e07e707c 100644 --- a/lib/battle/BattleProxy.h +++ b/lib/battle/BattleProxy.h @@ -44,7 +44,7 @@ public: ui8 getTacticsSide() const override; const CGTownInstance * getDefendedTown() const override; - si8 getWallState(int partOfWall) const override; + EWallState getWallState(EWallPart partOfWall) const override; EGateState getGateState() const override; uint32_t getCastSpells(ui8 side) const override; diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 1e6358d7c..05ce607bb 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -71,7 +71,7 @@ static bool sameSideOfWall(BattleHex pos1, BattleHex pos2) } // parts of wall -static const std::pair wallParts[] = +static const std::pair wallParts[] = { std::make_pair(50, EWallPart::KEEP), std::make_pair(183, EWallPart::BOTTOM_TOWER), @@ -89,7 +89,7 @@ static const std::pair wallParts[] = std::make_pair(165, EWallPart::INDESTRUCTIBLE_PART) }; -static EWallPart::EWallPart hexToWallPart(BattleHex hex) +static EWallPart hexToWallPart(BattleHex hex) { for(auto & elem : wallParts) { @@ -100,7 +100,7 @@ static EWallPart::EWallPart hexToWallPart(BattleHex hex) return EWallPart::INVALID; //not found! } -static BattleHex WallPartToHex(EWallPart::EWallPart part) +static BattleHex WallPartToHex(EWallPart part) { for(auto & elem : wallParts) { @@ -1109,13 +1109,13 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const ret[hex] = EAccessibility::UNAVAILABLE; //TODO likely duplicated logic - static const std::pair lockedIfNotDestroyed[] = + static const std::pair lockedIfNotDestroyed[] = { //which part of wall, which hex is blocked if this part of wall is not destroyed - std::make_pair(2, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_4)), - std::make_pair(3, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_3)), - std::make_pair(4, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_2)), - std::make_pair(5, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_1)) + std::make_pair(EWallPart::BOTTOM_WALL, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_4)), + std::make_pair(EWallPart::BELOW_GATE, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_3)), + std::make_pair(EWallPart::OVER_GATE, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_2)), + std::make_pair(EWallPart::UPPER_WALL, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_1)) }; for(auto & elem : lockedIfNotDestroyed) @@ -1604,19 +1604,19 @@ bool CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer * shooter, return true; } -BattleHex CBattleInfoCallback::wallPartToBattleHex(EWallPart::EWallPart part) const +BattleHex CBattleInfoCallback::wallPartToBattleHex(EWallPart part) const { RETURN_IF_NOT_BATTLE(BattleHex::INVALID); return WallPartToHex(part); } -EWallPart::EWallPart CBattleInfoCallback::battleHexToWallPart(BattleHex hex) const +EWallPart CBattleInfoCallback::battleHexToWallPart(BattleHex hex) const { RETURN_IF_NOT_BATTLE(EWallPart::INVALID); return hexToWallPart(hex); } -bool CBattleInfoCallback::isWallPartPotentiallyAttackable(EWallPart::EWallPart wallPart) const +bool CBattleInfoCallback::isWallPartPotentiallyAttackable(EWallPart wallPart) const { RETURN_IF_NOT_BATTLE(false); return wallPart != EWallPart::INDESTRUCTIBLE_PART && wallPart != EWallPart::INDESTRUCTIBLE_PART_OF_GATE && @@ -1632,7 +1632,7 @@ std::vector CBattleInfoCallback::getAttackableBattleHexes() const { if(isWallPartPotentiallyAttackable(wallPartPair.second)) { - auto wallState = static_cast(battleGetWallState(static_cast(wallPartPair.second))); + auto wallState = static_cast(battleGetWallState(wallPartPair.second)); if(wallState == EWallState::INTACT || wallState == EWallState::DAMAGED) { attackableBattleHexes.push_back(BattleHex(wallPartPair.first)); diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index 75fc6bf81..259313fd8 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -110,9 +110,9 @@ public: bool battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; bool battleHasShootingPenalty(const battle::Unit * shooter, BattleHex destHex) const; - BattleHex wallPartToBattleHex(EWallPart::EWallPart part) const; - EWallPart::EWallPart battleHexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found - bool isWallPartPotentiallyAttackable(EWallPart::EWallPart wallPart) const; // returns true if the wall part is potentially attackable (independent of wall state), false if not + BattleHex wallPartToBattleHex(EWallPart part) const; + EWallPart battleHexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found + bool isWallPartPotentiallyAttackable(EWallPart wallPart) const; // returns true if the wall part is potentially attackable (independent of wall state), false if not std::vector getAttackableBattleHexes() const; si8 battleMinSpellLevel(ui8 side) const; //calculates maximum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned diff --git a/lib/battle/CBattleInfoEssentials.cpp b/lib/battle/CBattleInfoEssentials.cpp index 53fcd6dd8..e7e0062aa 100644 --- a/lib/battle/CBattleInfoEssentials.cpp +++ b/lib/battle/CBattleInfoEssentials.cpp @@ -364,7 +364,7 @@ bool CBattleInfoEssentials::battleHasHero(ui8 side) const return getBattle()->getSideHero(side) != nullptr; } -si8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const +EWallState CBattleInfoEssentials::battleGetWallState(EWallPart partOfWall) const { RETURN_IF_NOT_BATTLE(EWallState::NONE); if(battleGetSiegeLevel() == CGTownInstance::NONE) diff --git a/lib/battle/CBattleInfoEssentials.h b/lib/battle/CBattleInfoEssentials.h index 6903e66bc..117e352b3 100644 --- a/lib/battle/CBattleInfoEssentials.h +++ b/lib/battle/CBattleInfoEssentials.h @@ -94,7 +94,7 @@ public: // for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall, // [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle - si8 battleGetWallState(int partOfWall) const; + EWallState battleGetWallState(EWallPart partOfWall) const; EGateState battleGetGateState() const; //helpers diff --git a/lib/battle/IBattleState.h b/lib/battle/IBattleState.h index 62536f4c1..7165ed5b2 100644 --- a/lib/battle/IBattleState.h +++ b/lib/battle/IBattleState.h @@ -49,7 +49,7 @@ public: virtual ObstacleCList getAllObstacles() const = 0; virtual const CGTownInstance * getDefendedTown() const = 0; - virtual si8 getWallState(int partOfWall) const = 0; + virtual EWallState getWallState(EWallPart partOfWall) const = 0; virtual EGateState getGateState() const = 0; virtual PlayerColor getSidePlayer(ui8 side) const = 0; @@ -87,7 +87,7 @@ public: virtual void updateUnitBonus(uint32_t id, const std::vector & bonus) = 0; virtual void removeUnitBonus(uint32_t id, const std::vector & bonus) = 0; - virtual void setWallState(int partOfWall, si8 state) = 0; + virtual void setWallState(EWallPart partOfWall, EWallState state) = 0; virtual void addObstacle(const ObstacleChanges & changes) = 0; virtual void updateObstacle(const ObstacleChanges & changes) = 0; diff --git a/lib/battle/SiegeInfo.cpp b/lib/battle/SiegeInfo.cpp index 13c2dc468..c11a5a6d9 100644 --- a/lib/battle/SiegeInfo.cpp +++ b/lib/battle/SiegeInfo.cpp @@ -15,14 +15,14 @@ VCMI_LIB_NAMESPACE_BEGIN SiegeInfo::SiegeInfo() { - for(int i = 0; i < wallState.size(); ++i) + for(int i = 0; i < int(EWallPart::PARTS_COUNT); ++i) { - wallState[i] = EWallState::NONE; + wallState[EWallPart(i)] = EWallState::NONE; } gateState = EGateState::NONE; } -EWallState::EWallState SiegeInfo::applyDamage(EWallState::EWallState state, unsigned int value) +EWallState SiegeInfo::applyDamage(EWallState state, unsigned int value) { if(value == 0) return state; diff --git a/lib/battle/SiegeInfo.h b/lib/battle/SiegeInfo.h index f508c1f14..680888201 100644 --- a/lib/battle/SiegeInfo.h +++ b/lib/battle/SiegeInfo.h @@ -15,13 +15,13 @@ VCMI_LIB_NAMESPACE_BEGIN //only for use in BattleInfo struct DLL_LINKAGE SiegeInfo { - std::array wallState; + std::map wallState; EGateState gateState; SiegeInfo(); // return EWallState decreased by value of damage points - static EWallState::EWallState applyDamage(EWallState::EWallState state, unsigned int value); + static EWallState applyDamage(EWallState state, unsigned int value); template void serialize(Handler &h, const int version) { diff --git a/lib/spells/effects/Catapult.cpp b/lib/spells/effects/Catapult.cpp index 62c9363f3..8f98f2a53 100644 --- a/lib/spells/effects/Catapult.cpp +++ b/lib/spells/effects/Catapult.cpp @@ -68,7 +68,7 @@ bool Catapult::applicable(Problem & problem, const Mechanics * m) const void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & /* eTarget */) const { //start with all destructible parts - static const std::set potentialTargets = + static const std::set potentialTargets = { EWallPart::KEEP, EWallPart::BOTTOM_TOWER, @@ -80,9 +80,9 @@ void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectT EWallPart::GATE }; - assert(potentialTargets.size() == EWallPart::PARTS_COUNT); + assert(potentialTargets.size() == size_t(EWallPart::PARTS_COUNT)); - std::set allowedTargets; + std::set allowedTargets; for (auto const & target : potentialTargets) { @@ -98,16 +98,13 @@ void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectT CatapultAttack ca; ca.attacker = -1; - BattleUnitsChanged removeUnits; - for(int i = 0; i < targetsToAttack; i++) { // Hit on any existing, not destroyed targets are allowed // Multiple hit on same target are allowed. // Potential overshots (more hits on same targets than remaining HP) are allowed - EWallPart::EWallPart target = *RandomGeneratorUtil::nextItem(allowedTargets, *server->getRNG()); + EWallPart target = *RandomGeneratorUtil::nextItem(allowedTargets, *server->getRNG()); - auto state = m->battle()->battleGetWallState(target); auto attackInfo = ca.attackedParts.begin(); for ( ; attackInfo != ca.attackedParts.end(); ++attackInfo) @@ -127,11 +124,19 @@ void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectT { attackInfo->damageDealt += 1; } + } + server->apply(&ca); + + BattleUnitsChanged removeUnits; + + for (auto const wallPart : { EWallPart::KEEP, EWallPart::BOTTOM_TOWER, EWallPart::UPPER_TOWER }) + { //removing creatures in turrets / keep if one is destroyed BattleHex posRemove; + auto state = m->battle()->battleGetWallState(wallPart); - switch(target) + switch(wallPart) { case EWallPart::KEEP: posRemove = BattleHex::CASTLE_CENTRAL_TOWER; @@ -144,35 +149,19 @@ void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectT break; } - if(posRemove != BattleHex::INVALID && state - attackInfo->damageDealt <= 0) //HP enum subtraction not intuitive, consider using SiegeInfo::applyDamage + if(state == EWallState::DESTROYED) //HP enum subtraction not intuitive, consider using SiegeInfo::applyDamage { auto all = m->battle()->battleGetUnitsIf([=](const battle::Unit * unit) { - return !unit->isGhost(); + return !unit->isGhost() && unit->getPosition() == posRemove; }); + assert(all.size() == 0 || all.size() == 1); for(auto & elem : all) - { - if(elem->getPosition() != posRemove) - continue; - - // if tower was hit multiple times, it may have been destroyed already - bool stackWasRemovedBefore = false; - for(auto & removed : removeUnits.changedStacks) - { - if (removed.id == elem->unitId()) - stackWasRemovedBefore = true; - } - - if (!stackWasRemovedBefore) - removeUnits.changedStacks.emplace_back(elem->unitId(), UnitChanges::EOperation::REMOVE); - break; - } + removeUnits.changedStacks.emplace_back(elem->unitId(), UnitChanges::EOperation::REMOVE); } } - server->apply(&ca); - if(!removeUnits.changedStacks.empty()) server->apply(&removeUnits); } diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index 4c381ce9c..402ccc155 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/lib/spells/effects/Obstacle.cpp @@ -256,7 +256,7 @@ bool Obstacle::isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & if(cb->battleGetSiegeLevel() != 0) { - EWallPart::EWallPart part = cb->battleHexToWallPart(hex); + EWallPart part = cb->battleHexToWallPart(hex); if(part == EWallPart::INVALID || part == EWallPart::INDESTRUCTIBLE_PART_OF_GATE) return true;//no fortification here diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index fc1b94cd3..985f7bbd7 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4790,7 +4790,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) case EActionType::CATAPULT: { //TODO: unify with spells::effects:Catapult - auto getCatapultHitChance = [](EWallPart::EWallPart part, const CHeroHandler::SBallisticsLevelInfo & sbi) -> int + auto getCatapultHitChance = [](EWallPart part, const CHeroHandler::SBallisticsLevelInfo & sbi) -> int { switch(part) { @@ -4829,7 +4829,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) } }; - auto isWallPartAttackable = [this] (EWallPart::EWallPart part) + auto isWallPartAttackable = [this] (EWallPart part) { return (gs->curB->si.wallState[part] == EWallState::INTACT || gs->curB->si.wallState[part] == EWallState::DAMAGED); }; @@ -4851,13 +4851,13 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) } else { - static const std::array walls = { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL }; - static const std::array towers= { EWallPart::BOTTOM_TOWER, EWallPart::KEEP, EWallPart::UPPER_TOWER }; - static const EWallPart::EWallPart gates = EWallPart::GATE; + static const std::array walls = { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL }; + static const std::array towers= { EWallPart::BOTTOM_TOWER, EWallPart::KEEP, EWallPart::UPPER_TOWER }; + static const EWallPart gates = EWallPart::GATE; // in H3, catapult under automatic control will attack objects in following order: // walls, gates, towers - std::vector potentialTargets; + std::vector potentialTargets; for (auto & part : walls ) if (isWallPartAttackable(part)) potentialTargets.push_back(part); @@ -4906,7 +4906,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) logGlobal->trace("Catapult attacks %d dealing %d damage", (int)attack.attackedPart, (int)attack.damageDealt); //removing creatures in turrets / keep if one is destroyed - if (gs->curB->si.wallState[actualTarget] <= 0 && (actualTarget == EWallPart::KEEP || actualTarget == EWallPart::BOTTOM_TOWER || actualTarget == EWallPart::UPPER_TOWER)) + if (gs->curB->si.wallState[actualTarget] == EWallState::DESTROYED && (actualTarget == EWallPart::KEEP || actualTarget == EWallPart::BOTTOM_TOWER || actualTarget == EWallPart::UPPER_TOWER)) { int posRemove = -1; switch(actualTarget) From 1d7f004658bfe28da1282e49cbd86b99aa94bcfa Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 13 Jan 2023 01:09:24 +0200 Subject: [PATCH 085/197] Implemented reinforced walls in towns with Castle --- AI/BattleAI/BattleAI.cpp | 3 +-- client/battle/BattleSiegeController.cpp | 21 ++++++++++++++------- lib/GameConstants.h | 3 ++- lib/NetPacksLib.cpp | 2 +- lib/battle/BattleInfo.cpp | 21 ++++++++++++--------- lib/battle/CBattleInfoCallback.cpp | 4 ++-- lib/battle/SiegeInfo.cpp | 2 ++ server/CGameHandler.cpp | 2 +- 8 files changed, 35 insertions(+), 23 deletions(-) diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 0ae8a183d..a0ccfb6f9 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -378,10 +378,9 @@ BattleAction CBattleAI::useCatapult(const CStack * stack) { auto wallState = cb->battleGetWallState(wallPart); - if(wallState == EWallState::INTACT || wallState == EWallState::DAMAGED) + if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED) { targetHex = cb->wallPartToBattleHex(wallPart); - break; } } diff --git a/client/battle/BattleSiegeController.cpp b/client/battle/BattleSiegeController.cpp index 0f6a76f7f..0b0acf36c 100644 --- a/client/battle/BattleSiegeController.cpp +++ b/client/battle/BattleSiegeController.cpp @@ -32,18 +32,25 @@ std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisua { auto getImageIndex = [&]() -> int { + bool isTower = (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER); + switch (state) { - case EWallState::INTACT : + case EWallState::REINFORCED : return 1; + case EWallState::INTACT : + if (town->hasBuilt(BuildingID::CASTLE)) + return 2; // reinforced walls were damaged + else + return 1; case EWallState::DAMAGED : // towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2 - if(what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER) + if (isTower) return 1; else return 2; case EWallState::DESTROYED : - if (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER) + if (isTower) return 2; else return 3; @@ -130,9 +137,9 @@ bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) { case EWallVisual::MOAT: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER; case EWallVisual::MOAT_BANK: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER && town->town->faction->index != ETownType::NECROPOLIS; - case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && EWallState(owner.curInt->cb->battleGetWallState(EWallPart::KEEP)) != EWallState::DESTROYED; - case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState(owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER)) != EWallState::DESTROYED; - case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState(owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER)) != EWallState::DESTROYED; + case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && owner.curInt->cb->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED; + case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED; + case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED; default: return true; } } @@ -177,7 +184,7 @@ BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTo if ( !getWallPieceExistance(EWallVisual::EWallVisual(g)) ) continue; - wallPieceImages[g] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::INTACT)); + wallPieceImages[g] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED)); } } diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 5d50073bc..1574619de 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -627,7 +627,8 @@ enum class EWallState : int8_t NONE = -1, //no wall DESTROYED, DAMAGED, - INTACT + INTACT, + REINFORCED, // walls in towns with castle }; enum class EGateState : uint8_t diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 0be775aa8..665304f99 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -1707,7 +1707,7 @@ DLL_LINKAGE void CatapultAttack::applyBattle(IBattleState * battleState) for(const auto & part : attackedParts) { - auto newWallState = SiegeInfo::applyDamage(EWallState(battleState->getWallState(part.attackedPart)), part.damageDealt); + auto newWallState = SiegeInfo::applyDamage(battleState->getWallState(part.attackedPart), part.damageDealt); battleState->setWallState(part.attackedPart, newWallState); } } diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 74024b8a4..10ee785da 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -223,20 +223,23 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const { curB->si.gateState = EGateState::CLOSED; - for (int b = 0; b < curB->si.wallState.size(); ++b) + curB->si.wallState[EWallPart::GATE] = EWallState::INTACT; + + for (auto const wall : {EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL} ) { - curB->si.wallState[EWallPart(b)] = EWallState::INTACT; + if (town->hasBuilt(BuildingID::CASTLE)) + curB->si.wallState[wall] = EWallState::REINFORCED; + else + curB->si.wallState[wall] = EWallState::INTACT; } - if (!town->hasBuilt(BuildingID::CITADEL)) - { - curB->si.wallState[EWallPart::KEEP] = EWallState::NONE; - } + if (town->hasBuilt(BuildingID::CITADEL)) + curB->si.wallState[EWallPart::KEEP] = EWallState::INTACT; - if (!town->hasBuilt(BuildingID::CASTLE)) + if (town->hasBuilt(BuildingID::CASTLE)) { - curB->si.wallState[EWallPart::UPPER_TOWER] = EWallState::NONE; - curB->si.wallState[EWallPart::BOTTOM_TOWER] = EWallState::NONE; + curB->si.wallState[EWallPart::UPPER_TOWER] = EWallState::INTACT; + curB->si.wallState[EWallPart::BOTTOM_TOWER] = EWallState::INTACT; } } diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 05ce607bb..e0ffd90d3 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1632,8 +1632,8 @@ std::vector CBattleInfoCallback::getAttackableBattleHexes() const { if(isWallPartPotentiallyAttackable(wallPartPair.second)) { - auto wallState = static_cast(battleGetWallState(wallPartPair.second)); - if(wallState == EWallState::INTACT || wallState == EWallState::DAMAGED) + auto wallState = battleGetWallState(wallPartPair.second); + if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED) { attackableBattleHexes.push_back(BattleHex(wallPartPair.first)); } diff --git a/lib/battle/SiegeInfo.cpp b/lib/battle/SiegeInfo.cpp index c11a5a6d9..4666671e3 100644 --- a/lib/battle/SiegeInfo.cpp +++ b/lib/battle/SiegeInfo.cpp @@ -29,6 +29,8 @@ EWallState SiegeInfo::applyDamage(EWallState state, unsigned int value) switch(applyDamage(state, value - 1)) { + case EWallState::REINFORCED: + return EWallState::INTACT; case EWallState::INTACT: return EWallState::DAMAGED; case EWallState::DAMAGED: diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 985f7bbd7..4934e3315 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4831,7 +4831,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) auto isWallPartAttackable = [this] (EWallPart part) { - return (gs->curB->si.wallState[part] == EWallState::INTACT || gs->curB->si.wallState[part] == EWallState::DAMAGED); + return (gs->curB->si.wallState[part] == EWallState::REINFORCED || gs->curB->si.wallState[part] == EWallState::INTACT || gs->curB->si.wallState[part] == EWallState::DAMAGED); }; CHeroHandler::SBallisticsLevelInfo stackBallisticsParameters = getBallisticsInfo(stack); From 3be3c871fb0da889dbf8c3f6aa87775bb5e133bb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 13 Jan 2023 01:59:09 +0200 Subject: [PATCH 086/197] Destroyed walls will now remove wall penalty --- lib/battle/CBattleInfoCallback.cpp | 99 +++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index e0ffd90d3..ab9ddd892 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -54,7 +54,7 @@ static void retrieveTurretDamageRange(const CGTownInstance * town, const battle: static BattleHex lineToWallHex(int line) //returns hex with wall in given line (y coordinate) { - static const BattleHex lineToHex[] = {12, 29, 45, 62, 78, 95, 112, 130, 147, 165, 182}; + static const BattleHex lineToHex[] = {12, 29, 45, 62, 78, 96, 112, 130, 147, 165, 182}; return lineToHex[line]; } @@ -157,8 +157,89 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(con return ESpellCastProblem::OK; } +struct Point +{ + int x,y; +}; + +/// Algorithm to test whether line segment between points line1-line2 will intersect with +/// AABB (Axis-Aligned Bounding Box) defined by points aabb1 & aabb2 +/// Note that in order to avoid floating point rounding errors algorithm uses integers with no divisions +static bool intersectionTestSegmentAABB(Point line1, Point line2, Point aabb1, Point aabb2) +{ + // check whether segment is located to the left of our AABB + if (line1.x < aabb1.x && line2.x < aabb1.x) + return false; + + // check whether segment is located to the right of our AABB + if (line1.x > aabb2.x && line2.x > aabb2.x) + return false; + + // check whether segment is located on top of our AABB + if (line1.y < aabb1.y && line2.y < aabb1.y) + return false; + + // check whether segment is located below of our AABB + if (line1.y > aabb2.y && line2.y > aabb2.y) + return false; + + Point vector { line2.x - line1.x, line2.y - line1.y}; + + // compute position of AABB corners relative to our line + int tlTest = vector.y*aabb1.x - vector.x*aabb1.y + (line2.x*line1.y-line1.x*line2.y); + int trTest = vector.y*aabb2.x - vector.x*aabb1.y + (line2.x*line1.y-line1.x*line2.y); + int blTest = vector.y*aabb1.x - vector.x*aabb2.y + (line2.x*line1.y-line1.x*line2.y); + int brTest = vector.y*aabb2.x - vector.x*aabb2.y + (line2.x*line1.y-line1.x*line2.y); + + // if all points are on the left of our line then there is no intersection + if ( tlTest > 0 && trTest > 0 && blTest > 0 && brTest > 0 ) + return false; + + // if all points are on the right of our line then there is no intersection + if ( tlTest < 0 && trTest < 0 && blTest < 0 && brTest < 0 ) + return false; + + // if all previous checks failed, this means that there is an intersection between line and AABB + return true; +} + bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const { + auto isTileBlocked = [&](BattleHex tile) + { + EWallPart wallPart = battleHexToWallPart(tile); + if (wallPart == EWallPart::INDESTRUCTIBLE_PART_OF_GATE) + return false; // does not blocks ranged attacks + if (wallPart == EWallPart::INDESTRUCTIBLE_PART) + return true; // always blocks ranged attacks + + assert(isWallPartPotentiallyAttackable(wallPart)); + + EWallState state = battleGetWallState(wallPart); + + return state != EWallState::DESTROYED; + }; + + auto needWallPenalty = [&](BattleHex from, BattleHex dest) + { + Point line1{ from.getX()*10+5, from.getY()*10+5}; + Point line2{ dest.getX()*10+5, dest.getY()*10+5}; + + for (int y = 0; y < GameConstants::BFIELD_HEIGHT; ++y) + { + BattleHex obstacle = lineToWallHex(y); + if (!isTileBlocked(obstacle)) + continue; + + Point aabb1{ obstacle.getX()*10, obstacle.getY()*10 }; + Point aabb2{ aabb1.x + 10, aabb1.y + 10 }; + + if ( intersectionTestSegmentAABB(line1, line2, aabb1, aabb2)) + return true; + } + return false; + }; + RETURN_IF_NOT_BATTLE(false); if(!battleGetSiegeLevel()) return false; @@ -170,21 +251,9 @@ bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, Bat return false; const int wallInStackLine = lineToWallHex(shooterPosition.getY()); - const int wallInDestLine = lineToWallHex(destHex.getY()); + const bool shooterOutsideWalls = shooterPosition < wallInStackLine; - const bool stackLeft = shooterPosition < wallInStackLine; - const bool destRight = destHex > wallInDestLine; - - if (stackLeft && destRight) //shooting from outside to inside - { - int row = (shooterPosition + destHex) / (2 * GameConstants::BFIELD_WIDTH); - if (shooterPosition > destHex && ((destHex % GameConstants::BFIELD_WIDTH - shooterPosition % GameConstants::BFIELD_WIDTH) < 2)) //shooting up high - row -= 2; - const int wallPos = lineToWallHex(row); - if (!isWallPartPotentiallyAttackable(battleHexToWallPart(wallPos))) return true; - } - - return false; + return shooterOutsideWalls && needWallPenalty(shooterPosition, destHex); } si8 CBattleInfoCallback::battleCanTeleportTo(const battle::Unit * stack, BattleHex destHex, int telportLevel) const From 526de47ca4949bb6fa71071eeb654f9123f37e2a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 13 Jan 2023 13:00:07 +0200 Subject: [PATCH 087/197] Removed 0.98 compatibility --- lib/CTownHandler.cpp | 13 +------------ lib/CTownHandler.h | 2 -- lib/spells/effects/Obstacle.cpp | 6 ++++-- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 6cda5f1e9..9b1777c5d 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -177,12 +177,6 @@ CTown::~CTown() str.dellNull(); } -std::vector CTown::defaultMoatHexes() -{ - static const std::vector moatHexes = {11, 28, 44, 61, 77, 111, 129, 146, 164, 181}; - return moatHexes; -} - std::string CTown::getLocalizedFactionName() const { if(faction == nullptr) @@ -855,12 +849,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source) warMachinesToLoad[town] = source["warMachine"]; town->moatDamage = static_cast(source["moatDamage"].Float()); - - // Compatibility for <= 0.98f mods - if(source["moatHexes"].isNull()) - town->moatHexes = CTown::defaultMoatHexes(); - else - town->moatHexes = source["moatHexes"].convertTo >(); + town->moatHexes = source["moatHexes"].convertTo >(); town->mageLevel = static_cast(source["mageGuild"].Float()); town->names = source["names"].convertTo >(); diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index d85f1465b..f27940a09 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -232,8 +232,6 @@ class DLL_LINKAGE CTown public: CTown(); ~CTown(); - // TODO: remove once save and mod compatability not needed - static std::vector defaultMoatHexes(); std::string getLocalizedFactionName() const; std::string getBuildingScope() const; diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index 402ccc155..77bcc6300 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/lib/spells/effects/Obstacle.cpp @@ -258,9 +258,11 @@ bool Obstacle::isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & { EWallPart part = cb->battleHexToWallPart(hex); - if(part == EWallPart::INVALID || part == EWallPart::INDESTRUCTIBLE_PART_OF_GATE) + if(part == EWallPart::INVALID) return true;//no fortification here - else if(static_cast(part) < 0) + else if(part == EWallPart::INDESTRUCTIBLE_PART_OF_GATE) + return false; // location accessible to units, but not targetable by spells + else if(part == EWallPart::INDESTRUCTIBLE_PART) return false;//indestructible part (cant be checked by battleGetWallState) else if(part == EWallPart::BOTTOM_TOWER || part == EWallPart::UPPER_TOWER) return false;//destructible, but should not be available From ccc6ebee0d907aedea6e5363d8f61f033efe1f88 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 13 Jan 2023 15:44:42 +0200 Subject: [PATCH 088/197] Cleared up gate blocking, Force Field can now block gates --- lib/GameConstants.h | 2 +- lib/spells/effects/Obstacle.cpp | 2 +- server/CGameHandler.cpp | 53 ++++++++++++++++++++++----------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 1574619de..a4c5eee11 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -635,7 +635,7 @@ enum class EGateState : uint8_t { NONE, CLOSED, - BLOCKED, //dead or alive stack blocking from outside + BLOCKED, // gate is blocked in closed state, e.g. by creature OPENED, DESTROYED }; diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index 77bcc6300..0b628b994 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/lib/spells/effects/Obstacle.cpp @@ -261,7 +261,7 @@ bool Obstacle::isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & if(part == EWallPart::INVALID) return true;//no fortification here else if(part == EWallPart::INDESTRUCTIBLE_PART_OF_GATE) - return false; // location accessible to units, but not targetable by spells + return true; // location accessible else if(part == EWallPart::INDESTRUCTIBLE_PART) return false;//indestructible part (cant be checked by battleGetWallState) else if(part == EWallPart::BOTTOM_TOWER || part == EWallPart::UPPER_TOWER) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4934e3315..01436631e 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4426,6 +4426,26 @@ static EndAction end_action; void CGameHandler::updateGateState() { + // GATE_BRIDGE - leftmost tile, located over moat + // GATE_OUTER - central tile, mostly covered by gate image + // GATE_INNER - rightmost tile, inside the walls + + // GATE_OUTER or GATE_INNER: + // - if defender moves unit on these tiles, bridge will open + // - if there is a creature (dead or alive) on these tiles, bridge will always remain open + // - blocked to attacker if bridge is closed + + // GATE_BRIDGE + // - if there is a unit or corpse here, bridge can't open (and can't close in fortress) + // - if Force Field is cast here, bridge can't open (but can close, in any town) + // - deals moat damage to attacker if bridge is closed (fortress only) + + bool hasForceFieldOnBridge = !battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), true).empty(); + bool hasStackAtGateInner = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_INNER), false) != nullptr; + bool hasStackAtGateOuter = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr; + bool hasStackAtGateBridge = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr; + bool hasLongBridge = gs->curB->town->subID == ETownType::FORTRESS; + BattleUpdateGateState db; db.state = gs->curB->si.gateState; if (gs->curB->si.wallState[EWallPart::GATE] == EWallState::DESTROYED) @@ -4434,24 +4454,23 @@ void CGameHandler::updateGateState() } else if (db.state == EGateState::OPENED) { - if (!gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) && - !gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_INNER), false)) - { - if (gs->curB->town->subID == ETownType::FORTRESS) - { - if (!gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false)) - db.state = EGateState::CLOSED; - } - else if (gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE))) - db.state = EGateState::BLOCKED; - else - db.state = EGateState::CLOSED; - } + bool hasStackOnLongBridge = hasStackAtGateBridge && hasLongBridge; + bool gateCanClose = !hasStackAtGateInner && !hasStackAtGateOuter && !hasStackOnLongBridge; + + if (gateCanClose) + db.state = EGateState::CLOSED; + else + db.state = EGateState::OPENED; + } + else // CLOSED or BLOCKED + { + bool gateBlocked = hasForceFieldOnBridge || hasStackAtGateBridge; + + if (gateBlocked) + db.state = EGateState::BLOCKED; + else + db.state = EGateState::CLOSED; } - else if (gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false)) - db.state = EGateState::BLOCKED; - else - db.state = EGateState::CLOSED; if (db.state != gs->curB->si.gateState) sendAndApply(&db); From a46ab835ceb6c4269ae44b27e015c8a269fcb126 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 23:01:33 +0200 Subject: [PATCH 089/197] Applied review suggestions --- lib/battle/CBattleInfoCallback.cpp | 39 ++++++++++++++++++------------ 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index ab9ddd892..5dae6f485 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -163,33 +163,36 @@ struct Point }; /// Algorithm to test whether line segment between points line1-line2 will intersect with -/// AABB (Axis-Aligned Bounding Box) defined by points aabb1 & aabb2 +/// rectangle specified by top-left and bottom-right points /// Note that in order to avoid floating point rounding errors algorithm uses integers with no divisions -static bool intersectionTestSegmentAABB(Point line1, Point line2, Point aabb1, Point aabb2) +static bool intersectionSegmentRect(Point line1, Point line2, Point rectTL, Point rectBR) { + assert(rectTL.x < rectBR.x); + assert(rectTL.y < rectBR.y); + // check whether segment is located to the left of our AABB - if (line1.x < aabb1.x && line2.x < aabb1.x) + if (line1.x < rectTL.x && line2.x < rectTL.x) return false; // check whether segment is located to the right of our AABB - if (line1.x > aabb2.x && line2.x > aabb2.x) + if (line1.x > rectBR.x && line2.x > rectBR.x) return false; // check whether segment is located on top of our AABB - if (line1.y < aabb1.y && line2.y < aabb1.y) + if (line1.y < rectTL.y && line2.y < rectTL.y) return false; // check whether segment is located below of our AABB - if (line1.y > aabb2.y && line2.y > aabb2.y) + if (line1.y > rectBR.y && line2.y > rectBR.y) return false; Point vector { line2.x - line1.x, line2.y - line1.y}; // compute position of AABB corners relative to our line - int tlTest = vector.y*aabb1.x - vector.x*aabb1.y + (line2.x*line1.y-line1.x*line2.y); - int trTest = vector.y*aabb2.x - vector.x*aabb1.y + (line2.x*line1.y-line1.x*line2.y); - int blTest = vector.y*aabb1.x - vector.x*aabb2.y + (line2.x*line1.y-line1.x*line2.y); - int brTest = vector.y*aabb2.x - vector.x*aabb2.y + (line2.x*line1.y-line1.x*line2.y); + int tlTest = vector.y*rectTL.x - vector.x*rectTL.y + (line2.x*line1.y-line1.x*line2.y); + int trTest = vector.y*rectBR.x - vector.x*rectTL.y + (line2.x*line1.y-line1.x*line2.y); + int blTest = vector.y*rectTL.x - vector.x*rectBR.y + (line2.x*line1.y-line1.x*line2.y); + int brTest = vector.y*rectBR.x - vector.x*rectBR.y + (line2.x*line1.y-line1.x*line2.y); // if all points are on the left of our line then there is no intersection if ( tlTest > 0 && trTest > 0 && blTest > 0 && brTest > 0 ) @@ -222,8 +225,13 @@ bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, Bat auto needWallPenalty = [&](BattleHex from, BattleHex dest) { - Point line1{ from.getX()*10+5, from.getY()*10+5}; - Point line2{ dest.getX()*10+5, dest.getY()*10+5}; + // arbitrary selected cell size for virtual grid + // any even number can be selected (for division by two) + static const int cellSize = 10; + + // create line that goes from center of shooter cell to center of target cell + Point line1{ from.getX()*cellSize+cellSize/2, from.getY()*cellSize+cellSize/2}; + Point line2{ dest.getX()*cellSize+cellSize/2, dest.getY()*cellSize+cellSize/2}; for (int y = 0; y < GameConstants::BFIELD_HEIGHT; ++y) { @@ -231,10 +239,11 @@ bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, Bat if (!isTileBlocked(obstacle)) continue; - Point aabb1{ obstacle.getX()*10, obstacle.getY()*10 }; - Point aabb2{ aabb1.x + 10, aabb1.y + 10 }; + // create rect around cell with an obstacle + Point rectTL{ obstacle.getX()*cellSize, obstacle.getY()*cellSize }; + Point recrBR{ obstacle.getX()*(cellSize+1), obstacle.getY()*(cellSize+1) }; - if ( intersectionTestSegmentAABB(line1, line2, aabb1, aabb2)) + if ( intersectionSegmentRect(line1, line2, rectTL, recrBR)) return true; } return false; From 06376ca4ef4e354685afa5166bc620668faeb8a4 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 15 Jan 2023 01:09:58 +0100 Subject: [PATCH 090/197] Change ClientCommandManager to become non-static --- client/CMT.cpp | 9 ++++++++- client/ClientCommandManager.cpp | 2 -- client/ClientCommandManager.h | 16 ++++++++-------- client/widgets/AdventureMapClasses.cpp | 8 +++++++- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index 56d851271..00fd1a0f4 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -239,7 +239,14 @@ int main(int argc, char * argv[]) std::cout.flags(std::ios::unitbuf); #ifndef VCMI_IOS console = new CConsoleHandler(); - *console->cb = ClientCommandManager::processCommand; + + auto callbackFunction = [](std::string buffer, bool calledFromIngameConsole) + { + ClientCommandManager commandController; + commandController.processCommand(buffer, calledFromIngameConsole); + }; + + *console->cb = callbackFunction; console->start(); #endif diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index dc4a61f7d..e75688489 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -33,8 +33,6 @@ #include "../lib/ScriptHandler.h" #endif -bool ClientCommandManager::currentCallFromIngameConsole; - void ClientCommandManager::handleGoSolo() { Settings session = settings.write["session"]; diff --git a/client/ClientCommandManager.h b/client/ClientCommandManager.h index 797a07a42..61b2175fb 100644 --- a/client/ClientCommandManager.h +++ b/client/ClientCommandManager.h @@ -17,15 +17,15 @@ class CIntObject; class ClientCommandManager //take mantis #2292 issue about account if thinking about handling cheats from command-line { - static bool currentCallFromIngameConsole; + bool currentCallFromIngameConsole; - static void giveTurn(const PlayerColor &color); - static void printInfoAboutInterfaceObject(const CIntObject *obj, int level); - static void printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType = ELogLevel::NOT_SET); - static void handleGoSolo(); - static void handleControlAi(const std::string &colorName); + void giveTurn(const PlayerColor &color); + void printInfoAboutInterfaceObject(const CIntObject *obj, int level); + void printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType = ELogLevel::NOT_SET); + void handleGoSolo(); + void handleControlAi(const std::string &colorName); public: - ClientCommandManager() = delete; - static void processCommand(const std::string &message, bool calledFromIngameConsole); + ClientCommandManager() = default; + void processCommand(const std::string &message, bool calledFromIngameConsole); }; diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index bd1923d0f..e148f68dd 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -1154,7 +1154,13 @@ void CInGameConsole::endEnteringText(bool processEnteredText) if(txt.at(0) == '/') { //some commands like gosolo don't work when executed from GUI thread - boost::thread clientCommandThread(ClientCommandManager::processCommand, txt.substr(1), true); + auto threadFunction = [=]() + { + ClientCommandManager commandController; + commandController.processCommand(txt.substr(1), true); + }; + + boost::thread clientCommandThread(threadFunction); clientCommandThread.detach(); } else From b5a1b178d9a89915149b5dd182ef550cbd4b9e22 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 15 Jan 2023 01:18:35 +0100 Subject: [PATCH 091/197] Explicitly call reset on shared_ptr for potential readability boost --- client/Client.cpp | 4 ++-- lib/CConsoleHandler.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/Client.cpp b/client/Client.cpp index b568ddc95..91ad53a1f 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -767,10 +767,10 @@ void CClient::removeGUI() GH.curInt = nullptr; if(GH.topInt()) GH.topInt()->deactivate(); - adventureInt = nullptr; + adventureInt.reset(); GH.listInt.clear(); GH.objsToBlit.clear(); - GH.statusbar = nullptr; + GH.statusbar.reset(); logGlobal->info("Removed GUI."); LOCPLINT = nullptr; diff --git a/lib/CConsoleHandler.h b/lib/CConsoleHandler.h index cafbd838c..7c1aa2b81 100644 --- a/lib/CConsoleHandler.h +++ b/lib/CConsoleHandler.h @@ -75,8 +75,8 @@ public: funlockfile(stdout); #endif } - - std::function *cb; //function to be called when message is received + //function to be called when message is received - string: message, bool: whether call was made from in-game console + std::function *cb; private: int run(); From 4736351e7811aa201e5900c0b27a372852e77026 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 15 Jan 2023 01:19:47 +0100 Subject: [PATCH 092/197] Change spaces to tabs in cmakelists --- client/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 328987208..626d66d2a 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -82,7 +82,7 @@ set(client_SRCS NetPacksClient.cpp NetPacksLobbyClient.cpp SDLRWwrapper.cpp - ClientCommandManager.cpp + ClientCommandManager.cpp ) set(client_HEADERS From e855a9db7c4be58a630043e3a1b6294249141a15 Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 31 Dec 2022 16:31:34 +0300 Subject: [PATCH 093/197] [Conan] use profile includes for common parts --- CI/conan/base/apple | 7 +++++++ CI/conan/base/ios | 5 +++++ CI/conan/base/macos | 4 ++++ CI/conan/ios-arm64 | 13 ++----------- CI/conan/ios-armv7 | 13 ++----------- CI/conan/macos-arm | 12 ++---------- CI/conan/macos-intel | 12 ++---------- 7 files changed, 24 insertions(+), 42 deletions(-) create mode 100644 CI/conan/base/apple create mode 100644 CI/conan/base/ios create mode 100644 CI/conan/base/macos diff --git a/CI/conan/base/apple b/CI/conan/base/apple new file mode 100644 index 000000000..477761aa9 --- /dev/null +++ b/CI/conan/base/apple @@ -0,0 +1,7 @@ +[settings] +compiler=apple-clang +compiler.version=13 +compiler.libcxx=libc++ +build_type=Release +[conf] +tools.cmake.cmaketoolchain:generator = Ninja diff --git a/CI/conan/base/ios b/CI/conan/base/ios new file mode 100644 index 000000000..0f48cadf2 --- /dev/null +++ b/CI/conan/base/ios @@ -0,0 +1,5 @@ +include(apple) + +[settings] +os=iOS +os.sdk=iphoneos diff --git a/CI/conan/base/macos b/CI/conan/base/macos new file mode 100644 index 000000000..db13d6a90 --- /dev/null +++ b/CI/conan/base/macos @@ -0,0 +1,4 @@ +include(apple) + +[settings] +os=Macos diff --git a/CI/conan/ios-arm64 b/CI/conan/ios-arm64 index f702a6302..238bbcf8f 100644 --- a/CI/conan/ios-arm64 +++ b/CI/conan/ios-arm64 @@ -1,14 +1,5 @@ +include(base/ios) + [settings] -os=iOS os.version=12.0 -os.sdk=iphoneos arch=armv8 -compiler=apple-clang -compiler.version=13 -compiler.libcxx=libc++ -build_type=Release -[options] -[build_requires] -[env] -[conf] -tools.cmake.cmaketoolchain:generator = Ninja diff --git a/CI/conan/ios-armv7 b/CI/conan/ios-armv7 index 35a87253d..6bec961e7 100644 --- a/CI/conan/ios-armv7 +++ b/CI/conan/ios-armv7 @@ -1,14 +1,5 @@ +include(base/ios) + [settings] -os=iOS os.version=10.0 -os.sdk=iphoneos arch=armv7 -compiler=apple-clang -compiler.version=13 -compiler.libcxx=libc++ -build_type=Release -[options] -[build_requires] -[env] -[conf] -tools.cmake.cmaketoolchain:generator = Ninja diff --git a/CI/conan/macos-arm b/CI/conan/macos-arm index 99858c9b3..d3f06e078 100644 --- a/CI/conan/macos-arm +++ b/CI/conan/macos-arm @@ -1,13 +1,5 @@ +include(base/macos) + [settings] -os=Macos os.version=11.0 arch=armv8 -compiler=apple-clang -compiler.version=13 -compiler.libcxx=libc++ -build_type=Release -[options] -[build_requires] -[env] -[conf] -tools.cmake.cmaketoolchain:generator = Ninja diff --git a/CI/conan/macos-intel b/CI/conan/macos-intel index 510caaee4..b527e056b 100644 --- a/CI/conan/macos-intel +++ b/CI/conan/macos-intel @@ -1,13 +1,5 @@ +include(base/macos) + [settings] -os=Macos os.version=10.13 arch=x86_64 -compiler=apple-clang -compiler.version=13 -compiler.libcxx=libc++ -build_type=Release -[options] -[build_requires] -[env] -[conf] -tools.cmake.cmaketoolchain:generator = Ninja From 43ef6c075e14d5e14b0024c6811608d14c2a320a Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 31 Dec 2022 16:32:11 +0300 Subject: [PATCH 094/197] [Conan] disable bitcode explicitly --- CI/conan/base/apple | 1 + 1 file changed, 1 insertion(+) diff --git a/CI/conan/base/apple b/CI/conan/base/apple index 477761aa9..18655d38b 100644 --- a/CI/conan/base/apple +++ b/CI/conan/base/apple @@ -4,4 +4,5 @@ compiler.version=13 compiler.libcxx=libc++ build_type=Release [conf] +tools.apple:enable_bitcode = False tools.cmake.cmaketoolchain:generator = Ninja From 427cae270462884dbe5c4ed03111dd47fc8f3271 Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 31 Dec 2022 16:33:06 +0300 Subject: [PATCH 095/197] [Conan] fix building Boost.Locale with Apple-clang in versions >= 1.81 --- CI/conan/base/apple | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CI/conan/base/apple b/CI/conan/base/apple index 18655d38b..bdb33e3e6 100644 --- a/CI/conan/base/apple +++ b/CI/conan/base/apple @@ -3,6 +3,10 @@ compiler=apple-clang compiler.version=13 compiler.libcxx=libc++ build_type=Release + +# required for Boost.Locale in versions >= 1.81 +compiler.cppstd=11 + [conf] tools.apple:enable_bitcode = False tools.cmake.cmaketoolchain:generator = Ninja From 453217b4bb76951796f36153941fcd9e17fb44cf Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 14 Jan 2023 13:55:42 +0300 Subject: [PATCH 096/197] [Conan] ignore SDL versions with broken sound --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index fd6108234..a3030c689 100644 --- a/conanfile.py +++ b/conanfile.py @@ -13,7 +13,7 @@ class VCMI(ConanFile): "minizip/[~1.2.12]", "onetbb/[^2021.3]", # Nullkiller AI "qt/[~5.15.2]", # launcher - "sdl/[~2.24.0]", + "sdl/[~2.26.1 || >=2.0.20 <=2.22.0]", # versions in between have broken sound "sdl_image/[~2.0.5]", "sdl_mixer/[~2.0.4]", "sdl_ttf/[~2.0.18]", From 0eef2412db1c9ec5eed247a426bdae534453905b Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 14 Jan 2023 14:25:16 +0300 Subject: [PATCH 097/197] [Conan] switch to apple-clang 14 --- CI/conan/base/apple | 2 +- CI/conan/ios-armv7 | 3 +++ docs/conan.md | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CI/conan/base/apple b/CI/conan/base/apple index bdb33e3e6..ae85bc702 100644 --- a/CI/conan/base/apple +++ b/CI/conan/base/apple @@ -1,6 +1,6 @@ [settings] compiler=apple-clang -compiler.version=13 +compiler.version=14 compiler.libcxx=libc++ build_type=Release diff --git a/CI/conan/ios-armv7 b/CI/conan/ios-armv7 index 6bec961e7..54482ce16 100644 --- a/CI/conan/ios-armv7 +++ b/CI/conan/ios-armv7 @@ -3,3 +3,6 @@ include(base/ios) [settings] os.version=10.0 arch=armv7 + +# Xcode 13.x is the last version that can build for armv7 +compiler.version=13 diff --git a/docs/conan.md b/docs/conan.md index 3bd0308ef..f55ff5ba9 100644 --- a/docs/conan.md +++ b/docs/conan.md @@ -20,8 +20,8 @@ The following platforms are supported and known to work, others might require ch 1. Check if your build environment can use the prebuilt binaries: basically, that your compiler version (or Xcode major version) matches the information below. If you're unsure, simply advance to the next step. - - macOS: libraries are built with Apple clang 13 (Xcode 13.4.1), should be consumable by Xcode and Xcode CLT 13.x - - iOS: libraries are built with Apple clang 13 (Xcode 13.4.1), should be consumable by Xcode 13.x + - macOS: libraries are built with Apple clang 14 (Xcode 14.2), should be consumable by Xcode and Xcode CLT 14.x (older library versions are also available for Xcode 13, see Releases in the respective repo) + - iOS: libraries are built with Apple clang 14 (Xcode 14.2), should be consumable by Xcode 14.x (older library versions are also available for Xcode 13, see Releases in the respective repo) 2. Download the binaries archive and unpack it to `~/.conan` directory: From 9d3ad51de44ed3745bd7d67ccede8bb2e86e85b2 Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 14 Jan 2023 14:37:08 +0300 Subject: [PATCH 098/197] [Conan][docs] fix examples of using our prebuilt binaries for macOS/iOS --- docs/conan.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/conan.md b/docs/conan.md index f55ff5ba9..6e0a2e834 100644 --- a/docs/conan.md +++ b/docs/conan.md @@ -85,7 +85,8 @@ conan install . \ --no-imports \ --build=never \ --profile:build=default \ - --profile:host=CI/conan/macos-intel + --profile:host=CI/conan/macos-intel \ + -o with_apple_system_libs=True cmake -S . -B build -G Xcode \ --toolchain conan-generated/conan_toolchain.cmake @@ -116,7 +117,8 @@ conan install . \ --no-imports \ --build=never \ --profile:build=default \ - --profile:host=CI/conan/ios-arm64 + --profile:host=CI/conan/ios-arm64 \ + -o with_apple_system_libs=True cmake --preset ios-conan ``` From b22c651b0f0c294691aa164d8ed7a7c8b3dbd1ea Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 14 Jan 2023 14:37:59 +0300 Subject: [PATCH 099/197] [CI] switch Apple platforms to Xcode 14 --- CI/ios/before_install.sh | 4 ++-- CI/mac/before_install.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CI/ios/before_install.sh b/CI/ios/before_install.sh index 69e459d86..5f47b58f0 100755 --- a/CI/ios/before_install.sh +++ b/CI/ios/before_install.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -echo DEVELOPER_DIR=/Applications/Xcode_13.4.1.app >> $GITHUB_ENV +echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV mkdir ~/.conan ; cd ~/.conan -curl -L 'https://github.com/vcmi/vcmi-ios-deps/releases/download/1.1/ios-arm64.xz' \ +curl -L 'https://github.com/vcmi/vcmi-ios-deps/releases/download/1.2/ios-arm64.txz' \ | tar -xf - diff --git a/CI/mac/before_install.sh b/CI/mac/before_install.sh index 5bd64351c..b4c46a693 100755 --- a/CI/mac/before_install.sh +++ b/CI/mac/before_install.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash -echo DEVELOPER_DIR=/Applications/Xcode_13.4.1.app >> $GITHUB_ENV +echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV brew install ninja mkdir ~/.conan ; cd ~/.conan -curl -L "https://github.com/vcmi/vcmi-deps-macos/releases/download/1.1/$DEPS_FILENAME.txz" \ +curl -L "https://github.com/vcmi/vcmi-deps-macos/releases/download/1.2/$DEPS_FILENAME.txz" \ | tar -xf - From efbed6000bda890f6b8571b4ea26e769f1183e56 Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 14 Jan 2023 14:38:03 +0300 Subject: [PATCH 100/197] fix typo --- AI/Nullkiller/Analyzers/ObjectClusterizer.cpp | 1 - config/schemas/terrain.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 2acdef5e9..8c0829a36 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -202,7 +202,6 @@ void ObjectClusterizer::clusterize() Obj::WHIRLPOOL, Obj::BUOY, Obj::SIGN, - Obj::SIGN, Obj::GARRISON, Obj::MONSTER, Obj::GARRISON2, diff --git a/config/schemas/terrain.json b/config/schemas/terrain.json index b4be9e922..bcbabeaa8 100644 --- a/config/schemas/terrain.json +++ b/config/schemas/terrain.json @@ -54,7 +54,7 @@ "rockTerrain": { "type": "string", - "description": "The name of tock type terrain which will be used as borders in the underground" + "description": "The name of rock type terrain which will be used as borders in the underground" }, "river": { From 2f149141202eeedd2c3c604926af020dbfca718c Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 14 Jan 2023 16:27:11 +0300 Subject: [PATCH 101/197] fix Boost deprecation warnings warnings introduced in v1.81 # Conflicts: # mapeditor/resourceExtractor/ResourceConverter.cpp --- client/CPlayerInterface.cpp | 2 +- lib/filesystem/CFilesystemLoader.cpp | 19 ++++++++++++++++--- .../resourceExtractor/ResourceConverter.cpp | 16 ++++++---------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index b55e6d2bd..7192b1c88 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1702,7 +1702,7 @@ int CPlayerInterface::getLastIndex( std::string namePrefix) else for (directory_iterator dir(gamesDir); dir != enddir; ++dir) { - if (is_regular(dir->status())) + if (is_regular_file(dir->status())) { std::string name = dir->path().filename().string(); if (starts_with(name, namePrefix) && ends_with(name, ".vcgm1")) diff --git a/lib/filesystem/CFilesystemLoader.cpp b/lib/filesystem/CFilesystemLoader.cpp index 122d22bf8..65d95ff6e 100644 --- a/lib/filesystem/CFilesystemLoader.cpp +++ b/lib/filesystem/CFilesystemLoader.cpp @@ -112,18 +112,31 @@ std::unordered_map CFilesystemLoader::listFiles(const std std::vector path; //vector holding relative path to our file bfs::recursive_directory_iterator enddir; +#if BOOST_VERSION >= 107200 // 1.72 + bfs::recursive_directory_iterator it(baseDirectory, bfs::directory_options::follow_directory_symlink); +#else bfs::recursive_directory_iterator it(baseDirectory, bfs::symlink_option::recurse); +#endif for(; it != enddir; ++it) { EResType::Type type; +#if BOOST_VERSION >= 107200 + const auto currentDepth = it.depth(); +#else + const auto currentDepth = it.level(); +#endif if (bfs::is_directory(it->status())) { - path.resize(it.level() + 1); + path.resize(currentDepth + 1); path.back() = it->path().filename(); // don't iterate into directory if depth limit reached - it.no_push(depth <= it.level()); +#if BOOST_VERSION >= 107200 + it.disable_recursion_pending(depth <= currentDepth); +#else + it.no_push(depth <= currentDepth); +#endif type = EResType::DIRECTORY; } @@ -134,7 +147,7 @@ std::unordered_map CFilesystemLoader::listFiles(const std { //reconstruct relative filename (not possible via boost AFAIK) bfs::path filename; - const size_t iterations = std::min((size_t)it.level(), path.size()); + const size_t iterations = std::min(static_cast(currentDepth), path.size()); if (iterations) { filename = path.front(); diff --git a/mapeditor/resourceExtractor/ResourceConverter.cpp b/mapeditor/resourceExtractor/ResourceConverter.cpp index d2be795b1..f08cd5e49 100644 --- a/mapeditor/resourceExtractor/ResourceConverter.cpp +++ b/mapeditor/resourceExtractor/ResourceConverter.cpp @@ -33,36 +33,32 @@ void ResourceConverter::convertExtractedResourceFiles(ConversionOptions conversi void ResourceConverter::doConvertPcxToPng(bool deleteOriginals) { - std::string filename; - bfs::path imagesPath = VCMIDirs::get().userExtractedPath() / "IMAGES"; bfs::directory_iterator end_iter; for(bfs::directory_iterator dir_itr(imagesPath); dir_itr != end_iter; ++dir_itr) { + const auto filename = dir_itr->path().filename(); try { if (!bfs::is_regular_file(dir_itr->status())) return; - std::string filePath = dir_itr->path().string(); - std::string fileStem = dir_itr->path().stem().string(); - filename = dir_itr->path().filename().string(); - std::string filenameLowerCase = boost::locale::to_lower(filename); + std::string filenameLowerCase = boost::algorithm::to_lower_copy(filename.string()); - if(bfs::extension(filenameLowerCase) == ".pcx") + if(boost::algorithm::to_lower_copy(filename.extension().string()) == ".pcx") { auto img = BitmapHandler::loadBitmap(filenameLowerCase); - bfs::path pngFilePath = imagesPath / (fileStem + ".png"); + bfs::path pngFilePath = imagesPath / (dir_itr->path().stem().string() + ".png"); img.save(pathToQString(pngFilePath), "PNG"); if(deleteOriginals) - bfs::remove(filePath); + bfs::remove(dir_itr->path()); } } catch(const std::exception & ex) { - logGlobal->info(filename + " " + ex.what() + "\n"); + logGlobal->info(filename.string() + " " + ex.what() + "\n"); } } } From 2de48624bcecebda677bca035bc50ad2b8c21ee6 Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 14 Jan 2023 20:29:42 +0300 Subject: [PATCH 102/197] [iOS] fix linking QtCore --- launcher/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 5764c38a1..040bf52e1 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -128,6 +128,11 @@ enable_pch(vcmilauncher) if(APPLE_IOS) set(ICONS_DESTINATION ${DATA_DIR}) + # TODO: remove after fixing Conan's Qt recipe + if(XCODE_VERSION VERSION_GREATER_EQUAL 14.0) + target_link_libraries(vcmilauncher "-framework IOKit") + endif() + # workaround https://github.com/conan-io/conan-center-index/issues/13332 if(USING_CONAN) file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/QIOSIntegrationPlugin.h From 9e7e649d377793fade626ff55bc05c8c6e1762ec Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 8 Jan 2023 22:44:47 +0100 Subject: [PATCH 103/197] Gradual fade-in of built building --- client/gui/CAnimation.cpp | 16 +++++++++++----- client/gui/CAnimation.h | 4 ++-- client/widgets/Images.cpp | 4 ++-- client/widgets/Images.h | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/client/gui/CAnimation.cpp b/client/gui/CAnimation.cpp index ef1b92115..2f502856d 100644 --- a/client/gui/CAnimation.cpp +++ b/client/gui/CAnimation.cpp @@ -94,8 +94,8 @@ public: // Keep the original palette, in order to do color switching operation void savePalette(); - void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr, ui8 alpha=255) const override; - void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src, ui8 alpha=255) const override; + void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr, ui8 alpha=255) override; + void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src, ui8 alpha=255) override; std::shared_ptr scaleFast(float scale) const override; void exportBitmap(const boost::filesystem::path & path) const override; void playerColored(PlayerColor player) override; @@ -642,17 +642,17 @@ SDLImage::SDLImage(std::string filename) } } -void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src, ui8 alpha) const +void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src, ui8 alpha) { if(!surf) return; Rect destRect(posX, posY, surf->w, surf->h); - draw(where, &destRect, src); + draw(where, &destRect, src, alpha); } -void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* src, ui8 alpha) const +void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* src, ui8 alpha) { if (!surf) return; @@ -663,6 +663,12 @@ void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* sr if(src) { + if(alpha != UINT8_MAX) + { + const ColorShifterMultiplyAndAdd alphaShifter ({255, 255, 255, alpha}, {0, 0, 0, 0}); + adjustPalette(&alphaShifter); + } + if(src->x < margins.x) destShift.x += margins.x - src->x; diff --git a/client/gui/CAnimation.h b/client/gui/CAnimation.h index 7263f3ee2..7284f0625 100644 --- a/client/gui/CAnimation.h +++ b/client/gui/CAnimation.h @@ -40,8 +40,8 @@ public: using SpecialPalette = std::array; //draws image on surface "where" at position - virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr, ui8 alpha = 255) const=0; - virtual void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src, ui8 alpha = 255) const = 0; + virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr, ui8 alpha = 255) = 0; + virtual void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src, ui8 alpha = 255) = 0; virtual std::shared_ptr scaleFast(float scale) const = 0; diff --git a/client/widgets/Images.cpp b/client/widgets/Images.cpp index db4c7925e..6191f281c 100644 --- a/client/widgets/Images.cpp +++ b/client/widgets/Images.cpp @@ -339,7 +339,7 @@ void CAnimImage::playerColored(PlayerColor currPlayer) anim->getImage(0, group)->playerColored(player); } -CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 Delay, size_t Group): +CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 Delay, size_t Group, uint8_t alpha): anim(std::make_shared(name)), group(Group), frame(0), @@ -349,7 +349,7 @@ CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 Del flags(Flags), xOffset(0), yOffset(0), - alpha(255) + alpha(alpha) { anim->loadGroup(group); last = anim->size(group); diff --git a/client/widgets/Images.h b/client/widgets/Images.h index cf0b64855..4cf125c13 100644 --- a/client/widgets/Images.h +++ b/client/widgets/Images.h @@ -142,7 +142,7 @@ public: //Set per-surface alpha, 0 = transparent, 255 = opaque void setAlpha(ui32 alphaValue); - CShowableAnim(int x, int y, std::string name, ui8 flags=0, ui32 Delay=4, size_t Group=0); + CShowableAnim(int x, int y, std::string name, ui8 flags=0, ui32 Delay=4, size_t Group=0, uint8_t alpha = 255); ~CShowableAnim(); //set animation to group or part of group From 3596d167a7b4c3af66cd2eb155c082fa4956b917 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Wed, 11 Jan 2023 19:57:42 +0100 Subject: [PATCH 104/197] Adjust code to develop + change place in code where alpha is applied --- client/gui/CAnimation.cpp | 17 +++++------------ client/gui/CAnimation.h | 4 ++-- client/gui/Canvas.cpp | 12 ++++++------ client/gui/Canvas.h | 3 ++- client/widgets/Images.cpp | 11 ++++++++++- client/widgets/Images.h | 2 +- 6 files changed, 26 insertions(+), 23 deletions(-) diff --git a/client/gui/CAnimation.cpp b/client/gui/CAnimation.cpp index 2f502856d..5a76e2bdc 100644 --- a/client/gui/CAnimation.cpp +++ b/client/gui/CAnimation.cpp @@ -94,8 +94,8 @@ public: // Keep the original palette, in order to do color switching operation void savePalette(); - void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr, ui8 alpha=255) override; - void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src, ui8 alpha=255) override; + void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr) const override; + void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src) const override; std::shared_ptr scaleFast(float scale) const override; void exportBitmap(const boost::filesystem::path & path) const override; void playerColored(PlayerColor player) override; @@ -642,17 +642,16 @@ SDLImage::SDLImage(std::string filename) } } -void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src, ui8 alpha) +void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src) const { if(!surf) return; Rect destRect(posX, posY, surf->w, surf->h); - - draw(where, &destRect, src, alpha); + draw(where, &destRect, src); } -void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* src, ui8 alpha) +void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* src) const { if (!surf) return; @@ -663,12 +662,6 @@ void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* sr if(src) { - if(alpha != UINT8_MAX) - { - const ColorShifterMultiplyAndAdd alphaShifter ({255, 255, 255, alpha}, {0, 0, 0, 0}); - adjustPalette(&alphaShifter); - } - if(src->x < margins.x) destShift.x += margins.x - src->x; diff --git a/client/gui/CAnimation.h b/client/gui/CAnimation.h index 7284f0625..dcfb1cb64 100644 --- a/client/gui/CAnimation.h +++ b/client/gui/CAnimation.h @@ -40,8 +40,8 @@ public: using SpecialPalette = std::array; //draws image on surface "where" at position - virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr, ui8 alpha = 255) = 0; - virtual void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src, ui8 alpha = 255) = 0; + virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr) const = 0; + virtual void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src) const = 0; virtual std::shared_ptr scaleFast(float scale) const = 0; diff --git a/client/gui/Canvas.cpp b/client/gui/Canvas.cpp index de300973f..1ca09a147 100644 --- a/client/gui/Canvas.cpp +++ b/client/gui/Canvas.cpp @@ -70,12 +70,12 @@ void Canvas::draw(std::shared_ptr image, const Point & pos, const Rect & image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect); } -void Canvas::draw(std::shared_ptr image, const Point & pos, const Rect & sourceRect, uint8_t alpha) -{ - assert(image); - if (image) - image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect, alpha); -} +//void Canvas::draw(std::shared_ptr image, const Point & pos, const Rect & sourceRect, uint8_t alpha) +//{ +// assert(image); +// if (image) +// image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect, alpha); +//} void Canvas::draw(Canvas & image, const Point & pos) { diff --git a/client/gui/Canvas.h b/client/gui/Canvas.h index 2a156ea3a..de94d4eee 100644 --- a/client/gui/Canvas.h +++ b/client/gui/Canvas.h @@ -52,7 +52,8 @@ public: void draw(std::shared_ptr image, const Point & pos, const Rect & sourceRect); /// renders section of image bounded by sourceRect at specified position at specific transparency value - void draw(std::shared_ptr image, const Point & pos, const Rect & sourceRect, uint8_t alpha); + /// disabled for now as never used in code with alpha support +// void draw(std::shared_ptr image, const Point & pos, const Rect & sourceRect, uint8_t alpha); /// renders another canvas onto this canvas diff --git a/client/widgets/Images.cpp b/client/widgets/Images.cpp index 6191f281c..26edda874 100644 --- a/client/widgets/Images.cpp +++ b/client/widgets/Images.cpp @@ -16,6 +16,7 @@ #include "../gui/SDL_Pixels.h" #include "../gui/CGuiHandler.h" #include "../gui/CCursorHandler.h" +#include "../gui/ColorFilter.h" #include "../battle/BattleInterface.h" #include "../battle/BattleInterfaceClasses.h" @@ -454,7 +455,15 @@ void CShowableAnim::blitImage(size_t frame, size_t group, SDL_Surface *to) Rect src( xOffset, yOffset, pos.w, pos.h); auto img = anim->getImage(frame, group); if(img) - img->draw(to, pos.x, pos.y, &src, alpha); + { + if(alpha != UINT8_MAX) + { + const ColorFilter alphaFilter = ColorFilter::genAlphaShifter(vstd::lerp(0.0f, 1.0f, alpha/255.0f)); + img->adjustPalette(alphaFilter); + } + + img->draw(to, pos.x, pos.y, &src); + } } void CShowableAnim::rotate(bool on, bool vertical) diff --git a/client/widgets/Images.h b/client/widgets/Images.h index 4cf125c13..6150bfc8b 100644 --- a/client/widgets/Images.h +++ b/client/widgets/Images.h @@ -142,7 +142,7 @@ public: //Set per-surface alpha, 0 = transparent, 255 = opaque void setAlpha(ui32 alphaValue); - CShowableAnim(int x, int y, std::string name, ui8 flags=0, ui32 Delay=4, size_t Group=0, uint8_t alpha = 255); + CShowableAnim(int x, int y, std::string name, ui8 flags=0, ui32 Delay=4, size_t Group=0, uint8_t alpha = UINT8_MAX); ~CShowableAnim(); //set animation to group or part of group From 8b2fc5198e85ccf74c7e55fbec1d59b3a875c549 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Wed, 11 Jan 2023 23:32:24 +0100 Subject: [PATCH 105/197] Remove condition that causes unintended behavior --- client/widgets/Images.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/client/widgets/Images.cpp b/client/widgets/Images.cpp index 26edda874..3ede9f5cb 100644 --- a/client/widgets/Images.cpp +++ b/client/widgets/Images.cpp @@ -456,11 +456,8 @@ void CShowableAnim::blitImage(size_t frame, size_t group, SDL_Surface *to) auto img = anim->getImage(frame, group); if(img) { - if(alpha != UINT8_MAX) - { - const ColorFilter alphaFilter = ColorFilter::genAlphaShifter(vstd::lerp(0.0f, 1.0f, alpha/255.0f)); - img->adjustPalette(alphaFilter); - } + const ColorFilter alphaFilter = ColorFilter::genAlphaShifter(vstd::lerp(0.0f, 1.0f, alpha/255.0f)); + img->adjustPalette(alphaFilter); img->draw(to, pos.x, pos.y, &src); } From 4fb554847529c3b7a3b4054863be657a8d1b007e Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 15 Jan 2023 00:09:28 +0100 Subject: [PATCH 106/197] Remove unused stuff from code --- client/gui/Canvas.cpp | 7 ------- client/gui/Canvas.h | 5 ----- 2 files changed, 12 deletions(-) diff --git a/client/gui/Canvas.cpp b/client/gui/Canvas.cpp index 1ca09a147..0d7fc4fcf 100644 --- a/client/gui/Canvas.cpp +++ b/client/gui/Canvas.cpp @@ -70,13 +70,6 @@ void Canvas::draw(std::shared_ptr image, const Point & pos, const Rect & image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect); } -//void Canvas::draw(std::shared_ptr image, const Point & pos, const Rect & sourceRect, uint8_t alpha) -//{ -// assert(image); -// if (image) -// image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect, alpha); -//} - void Canvas::draw(Canvas & image, const Point & pos) { blitAt(image.surface, renderOffset.x + pos.x, renderOffset.y + pos.y, surface); diff --git a/client/gui/Canvas.h b/client/gui/Canvas.h index de94d4eee..80d16040e 100644 --- a/client/gui/Canvas.h +++ b/client/gui/Canvas.h @@ -51,11 +51,6 @@ public: /// renders section of image bounded by sourceRect at specified position void draw(std::shared_ptr image, const Point & pos, const Rect & sourceRect); - /// renders section of image bounded by sourceRect at specified position at specific transparency value - /// disabled for now as never used in code with alpha support -// void draw(std::shared_ptr image, const Point & pos, const Rect & sourceRect, uint8_t alpha); - - /// renders another canvas onto this canvas void draw(Canvas & image, const Point & pos); From bec894c3481907854cb57ed61e7b608e8390faf4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 9 Jan 2023 00:24:11 +0200 Subject: [PATCH 107/197] Implemented automatic detection of H3 files language/encoding --- config/schemas/settings.json | 2 +- launcher/settingsView/csettingsview_moc.cpp | 2 + launcher/settingsView/csettingsview_moc.ui | 5 ++ lib/CGeneralTextHandler.cpp | 82 ++++++++++++++++++++- 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 0c78608b1..dd4a063f3 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -37,7 +37,7 @@ }, "encoding" : { "type" : "string", - "default" : "CP1252" + "default" : "auto" }, "swipe" : { "type" : "boolean", diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 0d34a8e09..dc8d3a6cb 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -33,6 +33,8 @@ QString resolutionToString(const QSize & resolution) /// Note that it is possible to specify enconding manually in settings.json static const std::string knownEncodingsList[] = //TODO: remove hardcode { + // Asks vcmi to automatically detect encoding + "auto", // European Windows-125X encodings "CP1250", // West European, covers mostly Slavic languages that use latin script "CP1251", // Covers languages that use cyrillic scrypt diff --git a/launcher/settingsView/csettingsview_moc.ui b/launcher/settingsView/csettingsview_moc.ui index a9c12aadb..0d0894b72 100644 --- a/launcher/settingsView/csettingsview_moc.ui +++ b/launcher/settingsView/csettingsview_moc.ui @@ -526,6 +526,11 @@ + + + Automatic detection + + Central European (Windows 1250) diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index eed81242e..e365e5cf0 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -106,7 +106,84 @@ bool Unicode::isValidString(const char * data, size_t size) static std::string getSelectedEncoding() { - return settings["general"]["encoding"].String(); + auto explicitSetting = settings["general"]["encoding"].String(); + if (explicitSetting != "auto") + return explicitSetting; + return settings["session"]["encoding"].String(); +} + +/// Detects encoding of H3 text files based on matching against pregenerated footprints of H3 file +/// Can also detect language of H3 install, however right now this is not necessary +static void detectEncoding() +{ + static const size_t knownCount = 6; + + // "footprints" of data collected from known versions of H3 + static const std::array, knownCount> knownFootprints = + { { + { { 0.0559, 0.0000, 0.1983, 0.0051, 0.0222, 0.0183, 0.4596, 0.2405, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000 } }, + { { 0.0493, 0.0000, 0.1926, 0.0047, 0.0230, 0.0121, 0.4133, 0.2780, 0.0002, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0259, 0.0008 } }, + { { 0.0534, 0.0000, 0.1705, 0.0047, 0.0418, 0.0208, 0.4775, 0.2191, 0.0001, 0.0000, 0.0000, 0.0000, 0.0000, 0.0005, 0.0036, 0.0080 } }, + { { 0.0534, 0.0000, 0.1701, 0.0067, 0.0157, 0.0133, 0.4328, 0.2540, 0.0001, 0.0043, 0.0000, 0.0244, 0.0000, 0.0000, 0.0181, 0.0071 } }, + { { 0.0548, 0.0000, 0.1744, 0.0061, 0.0031, 0.0009, 0.0046, 0.0136, 0.0000, 0.0004, 0.0000, 0.0000, 0.0227, 0.0061, 0.4882, 0.2252 } }, + { { 0.0559, 0.0000, 0.1807, 0.0059, 0.0036, 0.0013, 0.0046, 0.0134, 0.0000, 0.0004, 0.0000, 0.0487, 0.0209, 0.0060, 0.4615, 0.1972 } } + } }; + + // languages of known footprints + static const std::array knownLanguages = + { { + "English", "French", "German", "Polish", "Russian", "Ukrainian" + } }; + + // encoding that should be used for known footprints + static const std::array knownEncodings = + { { + "CP1252", "CP1252", "CP1252", "CP1250", "CP1251", "CP1251" + } }; + + // load file that will be used for footprint generation + // this is one of the most text-heavy files in game and consists solely from translated texts + auto resource = CResourceHandler::get()->load(ResourceID("DATA/GENRLTXT.TXT", EResType::TEXT)); + + std::array charCount; + std::array footprint; + std::array deviations; + + boost::range::fill(charCount, 0); + boost::range::fill(footprint, 0.0); + boost::range::fill(deviations, 0.0); + + auto data = resource->readAll(); + + // compute how often each character occurs in input file + for (size_t i = 0; i < data.second; ++i) + charCount[data.first[i]] += 1; + + // and convert computed data into weights + // to reduce amount of data, group footprint data into 16-char blocks. + // While this will reduce precision, it should not affect output + // since we expect only tiny differences compared to reference footprints + for (size_t i = 0; i < 256; ++i) + footprint[i/16] += double(charCount[i]) / data.second; + + logGlobal->debug("Language footprint: %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f", + footprint[0], footprint[1], footprint[2], footprint[3], footprint[4], footprint[5], footprint[6], footprint[7], + footprint[8], footprint[9], footprint[10], footprint[11], footprint[12], footprint[13], footprint[14], footprint[15] + ); + + for (size_t i = 0; i < deviations.size(); ++i) + { + for (size_t j = 0; j < footprint.size(); ++j) + deviations[i] += std::abs((footprint[j] - knownFootprints[i][j])); + } + + size_t bestIndex = boost::range::min_element(deviations) - deviations.begin(); + + for (size_t i = 0; i < deviations.size(); ++i) + logGlobal->debug("Comparing to %s: %f", knownLanguages[i], deviations[i]); + + Settings s = settings.write["session"]["encoding"]; + s->String() = knownEncodings[bestIndex]; } std::string Unicode::toUnicode(const std::string &text) @@ -364,6 +441,9 @@ CGeneralTextHandler::CGeneralTextHandler(): znpc00 (*this, "vcmi.znpc00" ), // technically - wog qeModCommands (*this, "vcmi.quickExchange" ) { + if (getSelectedEncoding().empty()) + detectEncoding(); + readToVector("core.vcdesc", "DATA/VCDESC.TXT" ); readToVector("core.lcdesc", "DATA/LCDESC.TXT" ); readToVector("core.tcommand", "DATA/TCOMMAND.TXT" ); From cbddf2d8fbae9ec7fef7c7016f9a12cb9c213ec4 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 15 Jan 2023 17:22:53 +0100 Subject: [PATCH 108/197] Adjust newly built building appearance and outline blinking speed --- client/windows/CCastleInterface.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 31ba43873..f178c2ea5 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -47,7 +47,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town parent(Par), town(Town), str(Str), - stateCounter(80) + stateCounter(150) { addUsedEvents(LCLICK | RCLICK | HOVER); pos.x += str->pos.x; @@ -154,12 +154,12 @@ SDL_Color multiplyColors(const SDL_Color & b, const SDL_Color & a, double f) void CBuildingRect::show(SDL_Surface * to) { - const ui32 stageDelay = 16; + const ui32 stageDelay = 30; - const ui32 S1_TRANSP = 16; //0.5 sec building appear 0->100 transparency - const ui32 S2_WHITE_B = 32; //0.5 sec border glows from white to yellow - const ui32 S3_YELLOW_B= 48; //0.5 sec border glows from yellow to normal - const ui32 BUILDED = 80; // 1 sec delay, nothing happens + const ui32 S1_TRANSP = 30; //0.5 sec building appear 0->100 transparency + const ui32 S2_WHITE_B = 60; //0.5 sec border glows from white to yellow + const ui32 S3_YELLOW_B= 90; //0.5 sec border glows from yellow to normal + const ui32 BUILDED = 150; // 1 sec delay, nothing happens if(stateCounter < S1_TRANSP) { From 6572f5697ec7118def4fc2d14664362d2399d29e Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 15 Jan 2023 19:10:40 +0100 Subject: [PATCH 109/197] Fix wrong rendering of castle shipyard --- client/windows/CCastleInterface.cpp | 14 ++++++++++++++ config/factions/castle.json | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 31ba43873..8252436db 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -53,6 +53,20 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town pos.x += str->pos.x; pos.y += str->pos.y; + // special animation frame manipulation for castle shipyard with and without ship + // done due to .def used in special way, not to animate building - first image is for shipyard without citadel moat, 2nd image is for including moat + if(Town->town->faction->getId() == FactionID::CASTLE && Str->building && + (Str->building->bid == BuildingID::SHIPYARD || Str->building->bid == BuildingID::SHIP)) + { + if(Town->hasBuilt(BuildingID::CITADEL)) + { + this->first = 1; + this->frame = 1; + } + else + this->last = 0; + } + if(!str->borderName.empty()) border = BitmapHandler::loadBitmap(str->borderName); else diff --git a/config/factions/castle.json b/config/factions/castle.json index 33a0006d8..7d48b39c3 100644 --- a/config/factions/castle.json +++ b/config/factions/castle.json @@ -84,7 +84,7 @@ "mageGuild3": { "animation" : "TBCSMAG3.def", "x" : 704, "y" : 107, "z" : 1, "border" : "TOCSM301.bmp", "area" : "TZCSM301.bmp" }, "mageGuild4": { "animation" : "TBCSMAG4.def", "x" : 704, "y" : 76, "z" : 1, "border" : "TOCSM401.bmp", "area" : "TZCSM401.bmp" }, "tavern": { "animation" : "TBCSTVRN.def", "x" : 0, "y" : 230, "z" : 1, "border" : "TOCSTAV1.bmp", "area" : "TZCSTAV1.bmp" }, - "shipyard": { "animation" : "TBCSDOCK.def", "x" : 478, "y" : 134, "border" : "TOCSDKMS.bmp", "area" : "TZCSDKMS.bmp" }, + "shipyard": { "animation" : "TBCSDOCK.def", "x" : 478, "y" : 134, "z" : 1, "border" : "TOCSDKMS.bmp", "area" : "TZCSDKMS.bmp" }, "fort": { "animation" : "TBCSCSTL.def", "x" : 595, "y" : 66, "border" : "TOCSCAS1.bmp", "area" : "TZCSCAS1.bmp" }, "citadel": { "animation" : "TBCSCAS2.def", "x" : 478, "y" : 66, "border" : "TOCSCAS2.bmp", "area" : "TZCSCAS2.bmp" }, "castle": { "animation" : "TBCSCAS3.def", "x" : 478, "y" : 37, "border" : "TOCSCAS3.bmp", "area" : "TZCSCAS3.bmp" }, @@ -98,7 +98,7 @@ "special1": { "animation" : "TBCSSPEC.def", "x" : 533, "y" : 71, "border" : "TOCSLT01.bmp", "area" : "TZCSLT01.bmp" }, "horde1": { "animation" : "TBCSHRD1.def", "x" : 76, "y" : 53, "z" : -1, "border" : "TOCSGR1H.bmp", "area" : "TZCSGR1H.bmp", "hidden" : true }, "horde1Upgr": { "animation" : "TBCSHRD2.def", "x" : 76, "y" : 35, "z" : -1, "border" : "TOCSGR2H.bmp", "area" : "TZCSGR2H.bmp", "hidden" : true, "builds" : "horde1" }, - "ship": { "animation" : "TBCSBOAT.def", "x" : 478, "y" : 134, "border" : "TOCSDKMN.bmp", "area" : "TZCSDKMN.bmp", "hidden" : true }, + "ship": { "animation" : "TBCSBOAT.def", "x" : 478, "y" : 134, "z" : 1, "border" : "TOCSDKMN.bmp", "area" : "TZCSDKMN.bmp", "hidden" : true }, "special2": { "animation" : "TBCSEXT0.def", "x" : 384, "y" : 193, "z" : -2, "border" : "TOCSCAVM.bmp", "area" : "TZCSCAVM.bmp" }, "special3": { "animation" : "TBCSEXT1.def", "x" : 0, "y" : 198, "z" : 1, "border" : "TOCSTAV2.bmp", "area" : "TZCSTAV2.bmp" }, "grail": { "animation" : "TBCSHOLY.def", "x" : 456, "y" : 109, "z" : -1, "border" : "TOCSHOLY.bmp", "area" : "TZCSHOLY.bmp" }, From 7dd7f4af548b7ae525d2dc3676f73f92441494d5 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 15 Jan 2023 19:56:55 +0100 Subject: [PATCH 110/197] Change to time-based frame counting, easier than I though --- client/windows/CCastleInterface.cpp | 44 ++++++++++++++--------------- client/windows/CCastleInterface.h | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index f178c2ea5..0d5ba23ef 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -44,10 +44,10 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town, const CStructure * Str) : CShowableAnim(0, 0, Str->defName, CShowableAnim::BASE), - parent(Par), - town(Town), - str(Str), - stateCounter(150) + parent(Par), + town(Town), + str(Str), + stateTimeCounter(2500) { addUsedEvents(LCLICK | RCLICK | HOVER); pos.x += str->pos.x; @@ -154,16 +154,16 @@ SDL_Color multiplyColors(const SDL_Color & b, const SDL_Color & a, double f) void CBuildingRect::show(SDL_Surface * to) { - const ui32 stageDelay = 30; + const ui32 stageDelay = 500; - const ui32 S1_TRANSP = 30; //0.5 sec building appear 0->100 transparency - const ui32 S2_WHITE_B = 60; //0.5 sec border glows from white to yellow - const ui32 S3_YELLOW_B= 90; //0.5 sec border glows from yellow to normal - const ui32 BUILDED = 150; // 1 sec delay, nothing happens + const ui32 S1_TRANSP = 500; //500 msec building appear 0->100 transparency + const ui32 S2_WHITE_B = 1000; //500 msec border glows from white to yellow + const ui32 S3_YELLOW_B= 1500; //500 msec border glows from yellow to normal + const ui32 BUILDED = 2500; //1000 msec delay, nothing happens - if(stateCounter < S1_TRANSP) + if(stateTimeCounter < S1_TRANSP) { - setAlpha(255*stateCounter/stageDelay); + setAlpha(255 * stateTimeCounter / stageDelay); CShowableAnim::show(to); } else @@ -172,9 +172,9 @@ void CBuildingRect::show(SDL_Surface * to) CShowableAnim::show(to); } - if(border && stateCounter > S1_TRANSP) + if(border && stateTimeCounter > S1_TRANSP) { - if(stateCounter == BUILDED) + if(stateTimeCounter >= BUILDED) { if(parent->selectedBuilding == this) blitAtLoc(border,0,0,to); @@ -191,11 +191,11 @@ void CBuildingRect::show(SDL_Surface * to) SDL_Color oldColor = border->format->palette->colors[colorID]; SDL_Color newColor; - if (stateCounter < S2_WHITE_B) - newColor = multiplyColors(c1, c2, static_cast(stateCounter % stageDelay) / stageDelay); + if (stateTimeCounter < S2_WHITE_B) + newColor = multiplyColors(c1, c2, static_cast(stateTimeCounter % stageDelay) / stageDelay); else - if (stateCounter < S3_YELLOW_B) - newColor = multiplyColors(c2, c3, static_cast(stateCounter % stageDelay) / stageDelay); + if (stateTimeCounter < S3_YELLOW_B) + newColor = multiplyColors(c2, c3, static_cast(stateTimeCounter % stageDelay) / stageDelay); else newColor = oldColor; @@ -204,13 +204,13 @@ void CBuildingRect::show(SDL_Surface * to) SDL_SetColors(border, &oldColor, colorID, 1); } } - if(stateCounter < BUILDED) - stateCounter++; + if(stateTimeCounter < BUILDED) + stateTimeCounter += GH.mainFPSmng->getElapsedMilliseconds(); } void CBuildingRect::showAll(SDL_Surface * to) { - if (stateCounter == 0) + if (stateTimeCounter == 0) return; CShowableAnim::showAll(to); @@ -632,9 +632,9 @@ void CCastleBuildings::addBuilding(BuildingID building) { //reset animation if(structures.size() == 1) - buildingRect->stateCounter = 0; // transparency -> fully visible stage + buildingRect->stateTimeCounter = 0; // transparency -> fully visible stage else - buildingRect->stateCounter = 16; // already in fully visible stage + buildingRect->stateTimeCounter = 500; // already in fully visible stage break; } } diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index 915e7579d..55bdc31d0 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -51,7 +51,7 @@ public: SDL_Surface* border; SDL_Surface* area; - ui32 stateCounter;//For building construction - current stage in animation + ui32 stateTimeCounter;//For building construction - current stage in animation CBuildingRect(CCastleBuildings * Par, const CGTownInstance *Town, const CStructure *Str); ~CBuildingRect(); From d44e80936961a96e491143401e5b27c0eeeb164c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 5 Jan 2023 19:03:27 +0200 Subject: [PATCH 111/197] Refactoring of CursorHandler, in preparation for HW cursor routines --- client/gui/CCursorHandler.cpp | 284 +++++++++++++++++----------------- client/gui/CCursorHandler.h | 22 ++- 2 files changed, 161 insertions(+), 145 deletions(-) diff --git a/client/gui/CCursorHandler.cpp b/client/gui/CCursorHandler.cpp index bd9726a49..6d42b917c 100644 --- a/client/gui/CCursorHandler.cpp +++ b/client/gui/CCursorHandler.cpp @@ -44,11 +44,11 @@ CCursorHandler::CCursorHandler() , cursorLayer(nullptr) , frameTime(0.f) , showing(false) + , pos(0,0) { cursorLayer = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 40, 40); SDL_SetTextureBlendMode(cursorLayer, SDL_BLENDMODE_BLEND); - xpos = ypos = 0; type = Cursor::Type::DEFAULT; dndObject = nullptr; @@ -72,7 +72,7 @@ CCursorHandler::CCursorHandler() Point CCursorHandler::position() const { - return Point(xpos, ypos); + return pos; } void CCursorHandler::changeGraphic(Cursor::Type type, size_t index) @@ -127,171 +127,175 @@ void CCursorHandler::dragAndDropCursor(std::unique_ptr object) void CCursorHandler::cursorMove(const int & x, const int & y) { - xpos = x; - ypos = y; + pos.x = x; + pos.y = y; } -void CCursorHandler::shiftPos( int &x, int &y ) +Point CCursorHandler::getPivotOffsetDefault(size_t index) { - if(( type == Cursor::Type::COMBAT && frame != static_cast(Cursor::Combat::POINTER)) || type == Cursor::Type::SPELLBOOK) - { - x-=16; - y-=16; + return {0, 0}; +} - // Properly align the melee attack cursors. - if (type == Cursor::Type::COMBAT) - { - switch (static_cast(frame)) - { - case Cursor::Combat::HIT_NORTHEAST: - x -= 6; - y += 16; - break; - case Cursor::Combat::HIT_EAST: - x -= 16; - y += 10; - break; - case Cursor::Combat::HIT_SOUTHEAST: - x -= 6; - y -= 6; - break; - case Cursor::Combat::HIT_SOUTHWEST: - x += 16; - y -= 6; - break; - case Cursor::Combat::HIT_WEST: - x += 16; - y += 11; - break; - case Cursor::Combat::HIT_NORTHWEST: - x += 16; - y += 16; - break; - case Cursor::Combat::HIT_NORTH: - x += 9; - y += 16; - break; - case Cursor::Combat::HIT_SOUTH: - x += 9; - y -= 15; - break; - } - } - } - else if(type == Cursor::Type::ADVENTURE) - { - if (frame == 0) - { - //no-op - } - else if(frame == 2) - { - x -= 12; - y -= 10; - } - else if(frame == 3) - { - x -= 12; - y -= 12; - } - else if(frame < 27) - { - int hlpNum = (frame - 4)%6; - if(hlpNum == 0) - { - x -= 15; - y -= 13; - } - else if(hlpNum == 1) - { - x -= 13; - y -= 13; - } - else if(hlpNum == 2) - { - x -= 20; - y -= 20; - } - else if(hlpNum == 3) - { - x -= 13; - y -= 16; - } - else if(hlpNum == 4) - { - x -= 8; - y -= 9; - } - else if(hlpNum == 5) - { - x -= 14; - y -= 16; - } - } - else if(frame == 41) - { - x -= 14; - y -= 16; - } - else if(frame < 31 || frame == 42) - { - x -= 20; - y -= 20; - } - } +Point CCursorHandler::getPivotOffsetMap(size_t index) +{ + static const std::array offsets = {{ + { 0, 0}, // POINTER = 0, + { 0, 0}, // HOURGLASS = 1, + { 12, 10}, // HERO = 2, + { 12, 12}, // TOWN = 3, + + { 15, 13}, // T1_MOVE = 4, + { 13, 13}, // T1_ATTACK = 5, + { 20, 20}, // T1_SAIL = 6, + { 13, 16}, // T1_DISEMBARK = 7, + { 8, 9}, // T1_EXCHANGE = 8, + { 14, 16}, // T1_VISIT = 9, + + { 15, 13}, // T2_MOVE = 10, + { 13, 13}, // T2_ATTACK = 11, + { 20, 20}, // T2_SAIL = 12, + { 13, 16}, // T2_DISEMBARK = 13, + { 8, 9}, // T2_EXCHANGE = 14, + { 14, 16}, // T2_VISIT = 15, + + { 15, 13}, // T3_MOVE = 16, + { 13, 13}, // T3_ATTACK = 17, + { 20, 20}, // T3_SAIL = 18, + { 13, 16}, // T3_DISEMBARK = 19, + { 8, 9}, // T3_EXCHANGE = 20, + { 14, 16}, // T3_VISIT = 21, + + { 15, 13}, // T4_MOVE = 22, + { 13, 13}, // T4_ATTACK = 23, + { 20, 20}, // T4_SAIL = 24, + { 13, 16}, // T4_DISEMBARK = 25, + { 8, 9}, // T4_EXCHANGE = 26, + { 14, 16}, // T4_VISIT = 27, + + { 20, 20}, // T1_SAIL_VISIT = 28, + { 20, 20}, // T2_SAIL_VISIT = 29, + { 20, 20}, // T3_SAIL_VISIT = 30, + { 20, 20}, // T4_SAIL_VISIT = 31, + + { 6, 1}, // SCROLL_NORTH = 32, + { 16, 2}, // SCROLL_NORTHEAST = 33, + { 21, 6}, // SCROLL_EAST = 34, + { 16, 16}, // SCROLL_SOUTHEAST = 35, + { 6, 21}, // SCROLL_SOUTH = 36, + { 1, 16}, // SCROLL_SOUTHWEST = 37, + { 1, 5}, // SCROLL_WEST = 38, + { 2, 1}, // SCROLL_NORTHWEST = 39, + + { 0, 0}, // POINTER_COPY = 40, + { 14, 16}, // TELEPORT = 41, + { 20, 20}, // SCUTTLE_BOAT = 42 + }}; + + static_assert (offsets.size() == size_t(Cursor::Map::COUNT), "Invalid number of pivot offsets for cursor" ); + assert(index < offsets.size()); + return offsets[index]; +} + +Point CCursorHandler::getPivotOffsetCombat(size_t index) +{ + static const std::array offsets = {{ + { 12, 12 }, // BLOCKED = 0, + { 10, 14 }, // MOVE = 1, + { 14, 14 }, // FLY = 2, + { 12, 12 }, // SHOOT = 3, + { 12, 12 }, // HERO = 4, + { 8, 12 }, // QUERY = 5, + { 0, 0 }, // POINTER = 6, + { 21, 0 }, // HIT_NORTHEAST = 7, + { 31, 5 }, // HIT_EAST = 8, + { 21, 21 }, // HIT_SOUTHEAST = 9, + { 0, 21 }, // HIT_SOUTHWEST = 10, + { 0, 5 }, // HIT_WEST = 11, + { 0, 0 }, // HIT_NORTHWEST = 12, + { 6, 0 }, // HIT_NORTH = 13, + { 6, 31 }, // HIT_SOUTH = 14, + { 14, 0 }, // SHOOT_PENALTY = 15, + { 12, 12 }, // SHOOT_CATAPULT = 16, + { 12, 12 }, // HEAL = 17, + { 12, 12 }, // SACRIFICE = 18, + { 14, 20 }, // TELEPORT = 19 + }}; + + static_assert (offsets.size() == size_t(Cursor::Combat::COUNT), "Invalid number of pivot offsets for cursor" ); + assert(index < offsets.size()); + return offsets[index]; +} + +Point CCursorHandler::getPivotOffsetSpellcast() +{ + return { 18, 28}; +} + +Point CCursorHandler::getPivotOffset() +{ + switch (type) { + case Cursor::Type::ADVENTURE: return getPivotOffsetMap(frame); + case Cursor::Type::COMBAT: return getPivotOffsetCombat(frame); + case Cursor::Type::DEFAULT: return getPivotOffsetDefault(frame); + case Cursor::Type::SPELLBOOK: return getPivotOffsetSpellcast(); + }; + + assert(0); + return {0, 0}; } void CCursorHandler::centerCursor() { - this->xpos = static_cast((screen->w / 2.) - (currentCursor->pos.w / 2.)); - this->ypos = static_cast((screen->h / 2.) - (currentCursor->pos.h / 2.)); + pos.x = static_cast((screen->w / 2.) - (currentCursor->pos.w / 2.)); + pos.y = static_cast((screen->h / 2.) - (currentCursor->pos.h / 2.)); SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); - SDL_WarpMouse(this->xpos, this->ypos); + SDL_WarpMouse(pos.x, pos.y); SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE); } +void CCursorHandler::updateSpellcastCursor() +{ + static const float frameDisplayDuration = 0.1f; + + frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f; + size_t newFrame = frame; + + while (frameTime > frameDisplayDuration) + { + frameTime -= frameDisplayDuration; + newFrame++; + } + + auto & animation = cursors.at(static_cast(type)); + + while (newFrame > animation->size()) + newFrame -= animation->size(); + + changeGraphic(Cursor::Type::SPELLBOOK, newFrame); +} + void CCursorHandler::render() { if(!showing) return; if (type == Cursor::Type::SPELLBOOK) - { - static const float frameDisplayDuration = 0.1f; + updateSpellcastCursor(); - frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f; - size_t newFrame = frame; - - while (frameTime > frameDisplayDuration) - { - frameTime -= frameDisplayDuration; - newFrame++; - } - - auto & animation = cursors.at(static_cast(type)); - - while (newFrame > animation->size()) - newFrame -= animation->size(); - - changeGraphic(Cursor::Type::SPELLBOOK, newFrame); - } //the must update texture in the main (renderer) thread, but changes to cursor type may come from other threads updateTexture(); - int x = xpos; - int y = ypos; - shiftPos(x, y); + Point renderPos = pos; if(dndObject) - { - x -= dndObject->pos.w/2; - y -= dndObject->pos.h/2; - } + renderPos -= dndObject->pos.dimensions() / 2; + else + renderPos -= getPivotOffset(); SDL_Rect destRect; - destRect.x = x; - destRect.y = y; + destRect.x = renderPos.x; + destRect.y = renderPos.y; destRect.w = 40; destRect.h = 40; diff --git a/client/gui/CCursorHandler.h b/client/gui/CCursorHandler.h index 82082a93d..3c7aaafb4 100644 --- a/client/gui/CCursorHandler.h +++ b/client/gui/CCursorHandler.h @@ -8,11 +8,13 @@ * */ #pragma once + class CIntObject; class CAnimImage; struct SDL_Surface; struct SDL_Texture; -struct Point; + +#include "Geometries.h" namespace Cursor { @@ -51,7 +53,9 @@ namespace Cursor SHOOT_CATAPULT = 16, HEAL = 17, SACRIFICE = 18, - TELEPORT = 19 + TELEPORT = 19, + + COUNT }; enum class Map { @@ -97,7 +101,9 @@ namespace Cursor SCROLL_NORTHWEST = 39, //POINTER_COPY = 40, // probably unused TELEPORT = 41, - SCUTTLE_BOAT = 42 + SCUTTLE_BOAT = 42, + + COUNT }; enum class Spellcast { @@ -123,7 +129,6 @@ class CCursorHandler final void clearBuffer(); void updateBuffer(CIntObject * payload); void replaceBuffer(CIntObject * payload); - void shiftPos( int &x, int &y ); void updateTexture(); @@ -135,8 +140,15 @@ class CCursorHandler final void changeGraphic(Cursor::Type type, size_t index); /// position of cursor - int xpos, ypos; + Point pos; + Point getPivotOffsetDefault(size_t index); + Point getPivotOffsetMap(size_t index); + Point getPivotOffsetCombat(size_t index); + Point getPivotOffsetSpellcast(); + Point getPivotOffset(); + + void updateSpellcastCursor(); public: CCursorHandler(); ~CCursorHandler(); From 0e8ee929df51fdafd3ccb31623be24023f393915 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 5 Jan 2023 19:34:37 +0200 Subject: [PATCH 112/197] Renamed CCursorHandler -> CursorHandler --- client/CGameInfo.h | 4 +- client/CMT.cpp | 4 +- client/CMakeLists.txt | 4 +- client/CPlayerInterface.cpp | 2 +- client/battle/BattleActionsController.cpp | 2 +- client/battle/BattleAnimationClasses.cpp | 2 +- client/battle/BattleFieldController.cpp | 2 +- client/battle/BattleInterface.cpp | 2 +- client/battle/BattleInterfaceClasses.cpp | 2 +- client/battle/BattleWindow.cpp | 2 +- client/gui/CGuiHandler.cpp | 2 +- .../{CCursorHandler.cpp => CursorHandler.cpp} | 46 +++++++++---------- .../gui/{CCursorHandler.h => CursorHandler.h} | 6 +-- client/mainmenu/CMainMenu.cpp | 2 +- client/widgets/CArtifactHolder.cpp | 2 +- client/widgets/CComponent.cpp | 2 +- client/widgets/MiscWidgets.cpp | 2 +- client/windows/CAdvmapInterface.cpp | 2 +- client/windows/CTradeWindow.cpp | 2 +- client/windows/CWindowObject.cpp | 2 +- client/windows/GUIClasses.cpp | 2 +- client/windows/InfoWindows.cpp | 2 +- 22 files changed, 49 insertions(+), 49 deletions(-) rename client/gui/{CCursorHandler.cpp => CursorHandler.cpp} (83%) rename client/gui/{CCursorHandler.h => CursorHandler.h} (94%) diff --git a/client/CGameInfo.h b/client/CGameInfo.h index 42ff54acf..042538337 100644 --- a/client/CGameInfo.h +++ b/client/CGameInfo.h @@ -38,7 +38,7 @@ VCMI_LIB_NAMESPACE_END class CMapHandler; class CSoundHandler; class CMusicHandler; -class CCursorHandler; +class CursorHandler; class IMainVideoPlayer; class CServerHandler; @@ -49,7 +49,7 @@ public: CSoundHandler * soundh; CMusicHandler * musich; CConsoleHandler * consoleh; - CCursorHandler * curh; + CursorHandler * curh; IMainVideoPlayer * videoh; }; extern CClientState * CCS; diff --git a/client/CMT.cpp b/client/CMT.cpp index d5727bd97..d4bc18ce9 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -25,7 +25,7 @@ #include "lobby/CSelectionBase.h" #include "windows/CCastleInterface.h" #include "../lib/CConsoleHandler.h" -#include "gui/CCursorHandler.h" +#include "gui/CursorHandler.h" #include "../lib/CGameState.h" #include "../CCallback.h" #include "CPlayerInterface.h" @@ -470,7 +470,7 @@ int main(int argc, char * argv[]) pomtime.getDiff(); graphics = new Graphics(); // should be before curh - CCS->curh = new CCursorHandler(); + CCS->curh = new CursorHandler(); logGlobal->info("Screen handler: %d ms", pomtime.getDiff()); pomtime.getDiff(); diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 6bcd92424..7dd73f81c 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -18,7 +18,7 @@ set(client_SRCS gui/CAnimation.cpp gui/Canvas.cpp - gui/CCursorHandler.cpp + gui/CursorHandler.cpp gui/CGuiHandler.cpp gui/CIntObject.cpp gui/ColorFilter.cpp @@ -104,7 +104,7 @@ set(client_HEADERS gui/CAnimation.h gui/Canvas.h - gui/CCursorHandler.h + gui/CursorHandler.h gui/CGuiHandler.h gui/ColorFilter.h gui/CIntObject.h diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index c7f295371..789e69231 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -19,7 +19,7 @@ #include "battle/BattleWindow.h" #include "../CCallback.h" #include "windows/CCastleInterface.h" -#include "gui/CCursorHandler.h" +#include "gui/CursorHandler.h" #include "windows/CKingdomInterface.h" #include "CGameInfo.h" #include "windows/CHeroWindow.h" diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index e0dc61229..65881f829 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -19,7 +19,7 @@ #include "../CGameInfo.h" #include "../CPlayerInterface.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../gui/CGuiHandler.h" #include "../gui/CIntObject.h" #include "../windows/CCreatureWindow.h" diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index d96f48414..111a4e3a6 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -22,7 +22,7 @@ #include "../CGameInfo.h" #include "../CMusicHandler.h" #include "../CPlayerInterface.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../gui/CGuiHandler.h" #include "../../CCallback.h" diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 3155dc30d..675b39fb0 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -26,7 +26,7 @@ #include "../gui/CAnimation.h" #include "../gui/Canvas.h" #include "../gui/CGuiHandler.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../../CCallback.h" #include "../../lib/BattleFieldHandler.h" diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index 8a1e17bb9..6d3a0c4c5 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -28,7 +28,7 @@ #include "../CMusicHandler.h" #include "../CPlayerInterface.h" #include "../gui/Canvas.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../gui/CGuiHandler.h" #include "../windows/CAdvmapInterface.h" diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 14f67822a..cad232cde 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -26,7 +26,7 @@ #include "../Graphics.h" #include "../gui/CAnimation.h" #include "../gui/Canvas.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../gui/CGuiHandler.h" #include "../widgets/AdventureMapClasses.h" #include "../widgets/Buttons.h" diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index 76b57776b..e12501cc9 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -21,7 +21,7 @@ #include "../CPlayerInterface.h" #include "../CMusicHandler.h" #include "../gui/Canvas.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../gui/CGuiHandler.h" #include "../gui/CAnimation.h" #include "../windows/CSpellWindow.h" diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index 060ce0570..f1f3ee424 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -14,7 +14,7 @@ #include #include "CIntObject.h" -#include "CCursorHandler.h" +#include "CursorHandler.h" #include "../CGameInfo.h" #include "../../lib/CThreadHelper.h" diff --git a/client/gui/CCursorHandler.cpp b/client/gui/CursorHandler.cpp similarity index 83% rename from client/gui/CCursorHandler.cpp rename to client/gui/CursorHandler.cpp index 6d42b917c..e8ab01cc4 100644 --- a/client/gui/CCursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -8,7 +8,7 @@ * */ #include "StdInc.h" -#include "CCursorHandler.h" +#include "CursorHandler.h" #include @@ -18,13 +18,13 @@ #include "../CMT.h" -void CCursorHandler::clearBuffer() +void CursorHandler::clearBuffer() { Uint32 fillColor = SDL_MapRGBA(buffer->format, 0, 0, 0, 0); CSDL_Ext::fillRect(buffer, nullptr, fillColor); } -void CCursorHandler::updateBuffer(CIntObject * payload) +void CursorHandler::updateBuffer(CIntObject * payload) { payload->moveTo(Point(0,0)); payload->showAll(buffer); @@ -32,13 +32,13 @@ void CCursorHandler::updateBuffer(CIntObject * payload) needUpdate = true; } -void CCursorHandler::replaceBuffer(CIntObject * payload) +void CursorHandler::replaceBuffer(CIntObject * payload) { clearBuffer(); updateBuffer(payload); } -CCursorHandler::CCursorHandler() +CursorHandler::CursorHandler() : needUpdate(true) , buffer(nullptr) , cursorLayer(nullptr) @@ -70,12 +70,12 @@ CCursorHandler::CCursorHandler() set(Cursor::Map::POINTER); } -Point CCursorHandler::position() const +Point CursorHandler::position() const { return pos; } -void CCursorHandler::changeGraphic(Cursor::Type type, size_t index) +void CursorHandler::changeGraphic(Cursor::Type type, size_t index) { assert(dndObject == nullptr); @@ -95,28 +95,28 @@ void CCursorHandler::changeGraphic(Cursor::Type type, size_t index) replaceBuffer(currentCursor); } -void CCursorHandler::set(Cursor::Default index) +void CursorHandler::set(Cursor::Default index) { changeGraphic(Cursor::Type::DEFAULT, static_cast(index)); } -void CCursorHandler::set(Cursor::Map index) +void CursorHandler::set(Cursor::Map index) { changeGraphic(Cursor::Type::ADVENTURE, static_cast(index)); } -void CCursorHandler::set(Cursor::Combat index) +void CursorHandler::set(Cursor::Combat index) { changeGraphic(Cursor::Type::COMBAT, static_cast(index)); } -void CCursorHandler::set(Cursor::Spellcast index) +void CursorHandler::set(Cursor::Spellcast index) { //Note: this is animated cursor, ignore specified frame and only change type changeGraphic(Cursor::Type::SPELLBOOK, frame); } -void CCursorHandler::dragAndDropCursor(std::unique_ptr object) +void CursorHandler::dragAndDropCursor(std::unique_ptr object) { dndObject = std::move(object); if(dndObject) @@ -125,18 +125,18 @@ void CCursorHandler::dragAndDropCursor(std::unique_ptr object) replaceBuffer(currentCursor); } -void CCursorHandler::cursorMove(const int & x, const int & y) +void CursorHandler::cursorMove(const int & x, const int & y) { pos.x = x; pos.y = y; } -Point CCursorHandler::getPivotOffsetDefault(size_t index) +Point CursorHandler::getPivotOffsetDefault(size_t index) { return {0, 0}; } -Point CCursorHandler::getPivotOffsetMap(size_t index) +Point CursorHandler::getPivotOffsetMap(size_t index) { static const std::array offsets = {{ { 0, 0}, // POINTER = 0, @@ -196,7 +196,7 @@ Point CCursorHandler::getPivotOffsetMap(size_t index) return offsets[index]; } -Point CCursorHandler::getPivotOffsetCombat(size_t index) +Point CursorHandler::getPivotOffsetCombat(size_t index) { static const std::array offsets = {{ { 12, 12 }, // BLOCKED = 0, @@ -226,12 +226,12 @@ Point CCursorHandler::getPivotOffsetCombat(size_t index) return offsets[index]; } -Point CCursorHandler::getPivotOffsetSpellcast() +Point CursorHandler::getPivotOffsetSpellcast() { return { 18, 28}; } -Point CCursorHandler::getPivotOffset() +Point CursorHandler::getPivotOffset() { switch (type) { case Cursor::Type::ADVENTURE: return getPivotOffsetMap(frame); @@ -244,7 +244,7 @@ Point CCursorHandler::getPivotOffset() return {0, 0}; } -void CCursorHandler::centerCursor() +void CursorHandler::centerCursor() { pos.x = static_cast((screen->w / 2.) - (currentCursor->pos.w / 2.)); pos.y = static_cast((screen->h / 2.) - (currentCursor->pos.h / 2.)); @@ -253,7 +253,7 @@ void CCursorHandler::centerCursor() SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE); } -void CCursorHandler::updateSpellcastCursor() +void CursorHandler::updateSpellcastCursor() { static const float frameDisplayDuration = 0.1f; @@ -274,7 +274,7 @@ void CCursorHandler::updateSpellcastCursor() changeGraphic(Cursor::Type::SPELLBOOK, newFrame); } -void CCursorHandler::render() +void CursorHandler::render() { if(!showing) return; @@ -302,7 +302,7 @@ void CCursorHandler::render() SDL_RenderCopy(mainRenderer, cursorLayer, nullptr, &destRect); } -void CCursorHandler::updateTexture() +void CursorHandler::updateTexture() { if(needUpdate) { @@ -311,7 +311,7 @@ void CCursorHandler::updateTexture() } } -CCursorHandler::~CCursorHandler() +CursorHandler::~CursorHandler() { if(buffer) SDL_FreeSurface(buffer); diff --git a/client/gui/CCursorHandler.h b/client/gui/CursorHandler.h similarity index 94% rename from client/gui/CCursorHandler.h rename to client/gui/CursorHandler.h index 3c7aaafb4..6a613336a 100644 --- a/client/gui/CCursorHandler.h +++ b/client/gui/CursorHandler.h @@ -112,7 +112,7 @@ namespace Cursor } /// handles mouse cursor -class CCursorHandler final +class CursorHandler final { bool needUpdate; SDL_Texture * cursorLayer; @@ -150,8 +150,8 @@ class CCursorHandler final void updateSpellcastCursor(); public: - CCursorHandler(); - ~CCursorHandler(); + CursorHandler(); + ~CursorHandler(); /** * Replaces the cursor with a custom image. diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 3b265d0cd..27f053aa2 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -21,7 +21,7 @@ #include "../../lib/filesystem/CCompressedStream.h" #include "../gui/SDL_Extensions.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../CGameInfo.h" #include "../../lib/CGeneralTextHandler.h" diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp index 29ecc698b..1a82090a5 100644 --- a/client/widgets/CArtifactHolder.cpp +++ b/client/widgets/CArtifactHolder.cpp @@ -11,7 +11,7 @@ #include "CArtifactHolder.h" #include "../gui/CGuiHandler.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "Buttons.h" #include "CComponent.h" diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index 14e166963..dd89c96f2 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -17,7 +17,7 @@ #include #include "../gui/CGuiHandler.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../CMessage.h" #include "../CGameInfo.h" diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp index e11f9ab95..532513038 100644 --- a/client/widgets/MiscWidgets.cpp +++ b/client/widgets/MiscWidgets.cpp @@ -13,7 +13,7 @@ #include "CComponent.h" #include "../gui/CGuiHandler.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../CPlayerInterface.h" #include "../CMessage.h" diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 1cd0f81c0..704d8391d 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -32,7 +32,7 @@ #include "../mapHandler.h" #include "../gui/CAnimation.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../gui/CGuiHandler.h" #include "../gui/SDL_Extensions.h" #include "../widgets/MiscWidgets.h" diff --git a/client/windows/CTradeWindow.cpp b/client/windows/CTradeWindow.cpp index ade100707..24a94adfc 100644 --- a/client/windows/CTradeWindow.cpp +++ b/client/windows/CTradeWindow.cpp @@ -13,7 +13,7 @@ #include "CAdvmapInterface.h" #include "../gui/CGuiHandler.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../widgets/Images.h" #include "../CGameInfo.h" diff --git a/client/windows/CWindowObject.cpp b/client/windows/CWindowObject.cpp index b55271543..6527f555a 100644 --- a/client/windows/CWindowObject.cpp +++ b/client/windows/CWindowObject.cpp @@ -18,7 +18,7 @@ #include "../gui/SDL_Pixels.h" #include "../gui/SDL_Extensions.h" #include "../gui/CGuiHandler.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../battle/BattleInterface.h" #include "../battle/BattleInterfaceClasses.h" diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 129c3235c..d139776d9 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -32,7 +32,7 @@ #include "../gui/CAnimation.h" #include "../gui/CGuiHandler.h" #include "../gui/SDL_Extensions.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../widgets/CComponent.h" #include "../widgets/MiscWidgets.h" diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index 5fa9ffb45..a793d66c2 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -24,7 +24,7 @@ #include "../gui/SDL_Pixels.h" #include "../gui/SDL_Extensions.h" #include "../gui/CGuiHandler.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../battle/BattleInterface.h" #include "../battle/BattleInterfaceClasses.h" From 9971bdca1b7ec5f5edd2b3678670435a76d21bec Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 5 Jan 2023 21:30:54 +0200 Subject: [PATCH 113/197] Existing software cursor logic is now in a separate class --- client/gui/CursorHandler.cpp | 185 +++++++++++++++++------------ client/gui/CursorHandler.h | 69 +++++++---- client/widgets/CArtifactHolder.cpp | 8 +- client/widgets/Images.cpp | 2 +- client/windows/CTradeWindow.cpp | 2 +- 5 files changed, 155 insertions(+), 111 deletions(-) diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index e8ab01cc4..94f07002a 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -12,60 +12,33 @@ #include + #include "SDL_Extensions.h" #include "CGuiHandler.h" -#include "../widgets/Images.h" +#include "CAnimation.h" #include "../CMT.h" -void CursorHandler::clearBuffer() -{ - Uint32 fillColor = SDL_MapRGBA(buffer->format, 0, 0, 0, 0); - CSDL_Ext::fillRect(buffer, nullptr, fillColor); -} - -void CursorHandler::updateBuffer(CIntObject * payload) -{ - payload->moveTo(Point(0,0)); - payload->showAll(buffer); - - needUpdate = true; -} - -void CursorHandler::replaceBuffer(CIntObject * payload) -{ - clearBuffer(); - updateBuffer(payload); -} - CursorHandler::CursorHandler() - : needUpdate(true) - , buffer(nullptr) - , cursorLayer(nullptr) + : cursorSW(new CursorSoftware()) , frameTime(0.f) , showing(false) , pos(0,0) { - cursorLayer = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 40, 40); - SDL_SetTextureBlendMode(cursorLayer, SDL_BLENDMODE_BLEND); type = Cursor::Type::DEFAULT; dndObject = nullptr; cursors = { - std::make_unique("CRADVNTR", 0), - std::make_unique("CRCOMBAT", 0), - std::make_unique("CRDEFLT", 0), - std::make_unique("CRSPELL", 0) + std::make_unique("CRADVNTR"), + std::make_unique("CRCOMBAT"), + std::make_unique("CRDEFLT"), + std::make_unique("CRSPELL") }; - currentCursor = cursors.at(static_cast(Cursor::Type::DEFAULT)).get(); - - buffer = CSDL_Ext::newSurface(40,40); - - SDL_SetSurfaceBlendMode(buffer, SDL_BLENDMODE_NONE); - SDL_ShowCursor(SDL_DISABLE); + for (auto & cursor : cursors) + cursor->preload(); set(Cursor::Map::POINTER); } @@ -79,20 +52,10 @@ void CursorHandler::changeGraphic(Cursor::Type type, size_t index) { assert(dndObject == nullptr); - if(type != this->type) - { - this->type = type; - this->frame = index; - currentCursor = cursors.at(static_cast(type)).get(); - currentCursor->setFrame(index); - } - else if(index != this->frame) - { - this->frame = index; - currentCursor->setFrame(index); - } + this->type = type; + this->frame = index; - replaceBuffer(currentCursor); + cursorSW->setImage(getCurrentImage(), getPivotOffset()); } void CursorHandler::set(Cursor::Default index) @@ -116,19 +79,25 @@ void CursorHandler::set(Cursor::Spellcast index) changeGraphic(Cursor::Type::SPELLBOOK, frame); } -void CursorHandler::dragAndDropCursor(std::unique_ptr object) +void CursorHandler::dragAndDropCursor(std::shared_ptr image) { - dndObject = std::move(object); - if(dndObject) - replaceBuffer(dndObject.get()); - else - replaceBuffer(currentCursor); + dndObject = image; + cursorSW->setImage(getCurrentImage(), getPivotOffset()); +} + +void CursorHandler::dragAndDropCursor (std::string path, size_t index) +{ + CAnimation anim(path); + anim.load(index); + dragAndDropCursor(anim.getImage(index)); } void CursorHandler::cursorMove(const int & x, const int & y) { pos.x = x; pos.y = y; + + cursorSW->setCursorPosition(pos); } Point CursorHandler::getPivotOffsetDefault(size_t index) @@ -233,6 +202,9 @@ Point CursorHandler::getPivotOffsetSpellcast() Point CursorHandler::getPivotOffset() { + if (dndObject) + return dndObject->dimensions(); + switch (type) { case Cursor::Type::ADVENTURE: return getPivotOffsetMap(frame); case Cursor::Type::COMBAT: return getPivotOffsetCombat(frame); @@ -244,13 +216,24 @@ Point CursorHandler::getPivotOffset() return {0, 0}; } +std::shared_ptr CursorHandler::getCurrentImage() +{ + if (dndObject) + return dndObject; + + return cursors[static_cast(type)]->getImage(frame); +} + void CursorHandler::centerCursor() { - pos.x = static_cast((screen->w / 2.) - (currentCursor->pos.w / 2.)); - pos.y = static_cast((screen->h / 2.) - (currentCursor->pos.h / 2.)); + Point screenSize {screen->w, screen->h}; + pos = screenSize / 2 - getPivotOffset(); + SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); SDL_WarpMouse(pos.x, pos.y); SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE); + + cursorSW->setCursorPosition(pos); } void CursorHandler::updateSpellcastCursor() @@ -260,7 +243,7 @@ void CursorHandler::updateSpellcastCursor() frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f; size_t newFrame = frame; - while (frameTime > frameDisplayDuration) + while (frameTime >= frameDisplayDuration) { frameTime -= frameDisplayDuration; newFrame++; @@ -268,7 +251,7 @@ void CursorHandler::updateSpellcastCursor() auto & animation = cursors.at(static_cast(type)); - while (newFrame > animation->size()) + while (newFrame >= animation->size()) newFrame -= animation->size(); changeGraphic(Cursor::Type::SPELLBOOK, newFrame); @@ -282,16 +265,16 @@ void CursorHandler::render() if (type == Cursor::Type::SPELLBOOK) updateSpellcastCursor(); + cursorSW->render(); +} - //the must update texture in the main (renderer) thread, but changes to cursor type may come from other threads - updateTexture(); +void CursorSoftware::render() +{ + //texture must be updated in the main (renderer) thread, but changes to cursor type may come from other threads + if (needUpdate) + updateTexture(); - Point renderPos = pos; - - if(dndObject) - renderPos -= dndObject->pos.dimensions() / 2; - else - renderPos -= getPivotOffset(); + Point renderPos = pos - pivot; SDL_Rect destRect; destRect.x = renderPos.x; @@ -299,23 +282,67 @@ void CursorHandler::render() destRect.w = 40; destRect.h = 40; - SDL_RenderCopy(mainRenderer, cursorLayer, nullptr, &destRect); + SDL_RenderCopy(mainRenderer, cursorTexture, nullptr, &destRect); } -void CursorHandler::updateTexture() +void CursorSoftware::createTexture(const Point & dimensions) { - if(needUpdate) - { - SDL_UpdateTexture(cursorLayer, nullptr, buffer->pixels, buffer->pitch); - needUpdate = false; - } + if(cursorTexture) + SDL_DestroyTexture(cursorTexture); + + if (cursorSurface) + SDL_FreeSurface(cursorSurface); + + cursorSurface = CSDL_Ext::newSurface(dimensions.x, dimensions.y); + cursorTexture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, dimensions.x, dimensions.y); + + SDL_SetSurfaceBlendMode(cursorSurface, SDL_BLENDMODE_NONE); + SDL_SetTextureBlendMode(cursorTexture, SDL_BLENDMODE_BLEND); } -CursorHandler::~CursorHandler() +void CursorSoftware::updateTexture() { - if(buffer) - SDL_FreeSurface(buffer); + Point dimensions(-1, -1); + + if (!cursorSurface || Point(cursorSurface->w, cursorSurface->h) != cursorImage->dimensions()) + createTexture(cursorImage->dimensions()); + + Uint32 fillColor = SDL_MapRGBA(cursorSurface->format, 0, 0, 0, 0); + CSDL_Ext::fillRect(cursorSurface, nullptr, fillColor); + + cursorImage->draw(cursorSurface); + SDL_UpdateTexture(cursorTexture, NULL, cursorSurface->pixels, cursorSurface->pitch); + needUpdate = false; +} + +void CursorSoftware::setImage(std::shared_ptr image, const Point & pivotOffset) +{ + assert(image != nullptr); + cursorImage = image; + pivot = pivotOffset; + needUpdate = true; +} + +void CursorSoftware::setCursorPosition( const Point & newPos ) +{ + pos = newPos; +} + +CursorSoftware::CursorSoftware(): + cursorTexture(nullptr), + cursorSurface(nullptr), + needUpdate(false), + pivot(0,0) +{ + SDL_ShowCursor(SDL_DISABLE); +} + +CursorSoftware::~CursorSoftware() +{ + if(cursorTexture) + SDL_DestroyTexture(cursorTexture); + + if (cursorSurface) + SDL_FreeSurface(cursorSurface); - if(cursorLayer) - SDL_DestroyTexture(cursorLayer); } diff --git a/client/gui/CursorHandler.h b/client/gui/CursorHandler.h index 6a613336a..08e526a5a 100644 --- a/client/gui/CursorHandler.h +++ b/client/gui/CursorHandler.h @@ -9,8 +9,8 @@ */ #pragma once -class CIntObject; -class CAnimImage; +class CAnimation; +class IImage; struct SDL_Surface; struct SDL_Texture; @@ -111,37 +111,52 @@ namespace Cursor }; } +class CursorHardware +{ + //TODO +}; + +class CursorSoftware +{ + std::shared_ptr cursorImage; + + SDL_Texture * cursorTexture; + SDL_Surface * cursorSurface; + + Point pos; + Point pivot; + bool needUpdate; + + void createTexture(const Point & dimensions); + void updateTexture(); +public: + CursorSoftware(); + ~CursorSoftware(); + + void setImage(std::shared_ptr image, const Point & pivotOffset); + void setCursorPosition( const Point & newPos ); + + void render(); + void setVisible(bool on); +}; + /// handles mouse cursor class CursorHandler final { - bool needUpdate; - SDL_Texture * cursorLayer; + std::shared_ptr dndObject; //if set, overrides currentCursor - SDL_Surface * buffer; - CAnimImage * currentCursor; - - std::unique_ptr dndObject; //if set, overrides currentCursor - - std::array, 4> cursors; + std::array, 4> cursors; bool showing; - void clearBuffer(); - void updateBuffer(CIntObject * payload); - void replaceBuffer(CIntObject * payload); - - void updateTexture(); - /// Current cursor Cursor::Type type; size_t frame; float frameTime; + Point pos; void changeGraphic(Cursor::Type type, size_t index); - /// position of cursor - Point pos; - Point getPivotOffsetDefault(size_t index); Point getPivotOffsetMap(size_t index); Point getPivotOffsetCombat(size_t index); @@ -149,17 +164,19 @@ class CursorHandler final Point getPivotOffset(); void updateSpellcastCursor(); + + std::shared_ptr getCurrentImage(); + + std::unique_ptr cursorSW; public: CursorHandler(); ~CursorHandler(); - /** - * Replaces the cursor with a custom image. - * - * @param image Image to replace cursor with or nullptr to use the normal - * cursor. CursorHandler takes ownership of object - */ - void dragAndDropCursor (std::unique_ptr image); + /// Replaces the cursor with a custom image. + /// @param image Image to replace cursor with or nullptr to use the normal cursor. + void dragAndDropCursor(std::shared_ptr image); + + void dragAndDropCursor(std::string path, size_t index); /// Returns current position of the cursor Point position() const; diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp index 1a82090a5..177d22eab 100644 --- a/client/widgets/CArtifactHolder.cpp +++ b/client/widgets/CArtifactHolder.cpp @@ -257,7 +257,7 @@ void CHeroArtPlace::clickRight(tribool down, bool previousState) void CArtifactsOfHero::activate() { if (commonInfo->src.AOH == this && commonInfo->src.art) - CCS->curh->dragAndDropCursor(std::make_unique("artifact", commonInfo->src.art->artType->getIconIndex())); + CCS->curh->dragAndDropCursor("artifact", commonInfo->src.art->artType->getIconIndex()); CIntObject::activate(); } @@ -289,7 +289,7 @@ void CHeroArtPlace::select () } } - CCS->curh->dragAndDropCursor(std::make_unique("artifact", ourArt->artType->getIconIndex())); + CCS->curh->dragAndDropCursor("artifact", ourArt->artType->getIconIndex()); ourOwner->commonInfo->src.setTo(this, false); ourOwner->markPossibleSlots(ourArt); @@ -752,7 +752,7 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation & src, const Artifac { auto art = curHero->getArt(ArtifactPosition::TRANSITION_POS); assert(art); - CCS->curh->dragAndDropCursor(std::make_unique("artifact", art->artType->getIconIndex())); + CCS->curh->dragAndDropCursor("artifact", art->artType->getIconIndex()); markPossibleSlots(art); commonInfo->src.art = art; @@ -787,7 +787,7 @@ void CArtifactsOfHero::artifactMoved(const ArtifactLocation & src, const Artifac commonInfo->src.art = dst.getArt(); commonInfo->src.slotID = dst.slot; assert(commonInfo->src.AOH); - CCS->curh->dragAndDropCursor(std::make_unique("artifact", dst.getArt()->artType->getIconIndex())); + CCS->curh->dragAndDropCursor("artifact", dst.getArt()->artType->getIconIndex()); } updateParentWindow(); diff --git a/client/widgets/Images.cpp b/client/widgets/Images.cpp index 3ede9f5cb..285602f37 100644 --- a/client/widgets/Images.cpp +++ b/client/widgets/Images.cpp @@ -15,7 +15,7 @@ #include "../gui/CAnimation.h" #include "../gui/SDL_Pixels.h" #include "../gui/CGuiHandler.h" -#include "../gui/CCursorHandler.h" +#include "../gui/CursorHandler.h" #include "../gui/ColorFilter.h" #include "../battle/BattleInterface.h" diff --git a/client/windows/CTradeWindow.cpp b/client/windows/CTradeWindow.cpp index 24a94adfc..0edb22e96 100644 --- a/client/windows/CTradeWindow.cpp +++ b/client/windows/CTradeWindow.cpp @@ -188,7 +188,7 @@ void CTradeWindow::CTradeableItem::clickLeft(tribool down, bool previousState) aw->arts->markPossibleSlots(art); //aw->arts->commonInfo->dst.AOH = aw->arts; - CCS->curh->dragAndDropCursor(std::make_unique("artifact", art->artType->iconIndex)); + CCS->curh->dragAndDropCursor("artifact", art->artType->iconIndex); aw->arts->artifactsOnAltar.erase(art); setID(-1); From 26a1c5801bce7195c918398fe2cf13285d3a11d0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 5 Jan 2023 22:13:24 +0200 Subject: [PATCH 114/197] Implemented hardware cursor support --- client/gui/CursorHandler.cpp | 41 +++++++++++++++++++++++++++++++++++- client/gui/CursorHandler.h | 17 ++++++++++++--- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index 94f07002a..6f52ed652 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -20,7 +20,7 @@ #include "../CMT.h" CursorHandler::CursorHandler() - : cursorSW(new CursorSoftware()) + : cursorSW(new CursorHardware()) , frameTime(0.f) , showing(false) , pos(0,0) @@ -52,6 +52,9 @@ void CursorHandler::changeGraphic(Cursor::Type type, size_t index) { assert(dndObject == nullptr); + if (type == this->type && index == this->frame) + return; + this->type = type; this->frame = index; @@ -346,3 +349,39 @@ CursorSoftware::~CursorSoftware() SDL_FreeSurface(cursorSurface); } + +CursorHardware::CursorHardware(): + cursor(nullptr) +{ +} + +CursorHardware::~CursorHardware() +{ + if(cursor) + SDL_FreeCursor(cursor); +} + +void CursorHardware::setImage(std::shared_ptr image, const Point & pivotOffset) +{ + auto cursorSurface = CSDL_Ext::newSurface(image->dimensions().x, image->dimensions().y); + + image->draw(cursorSurface); + + cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y); + + if (!cursor) + logGlobal->error("Failed to set cursor! SDL says %s", SDL_GetError()); + + SDL_FreeSurface(cursorSurface); + SDL_SetCursor(cursor); +} + +void CursorHardware::setCursorPosition( const Point & newPos ) +{ + //no-op +} + +void CursorHardware::render() +{ + //no-op +} diff --git a/client/gui/CursorHandler.h b/client/gui/CursorHandler.h index 08e526a5a..cb47d606a 100644 --- a/client/gui/CursorHandler.h +++ b/client/gui/CursorHandler.h @@ -13,6 +13,7 @@ class CAnimation; class IImage; struct SDL_Surface; struct SDL_Texture; +struct SDL_Cursor; #include "Geometries.h" @@ -113,7 +114,18 @@ namespace Cursor class CursorHardware { - //TODO + std::shared_ptr cursorImage; + + SDL_Cursor * cursor; + +public: + CursorHardware(); + ~CursorHardware(); + + void setImage(std::shared_ptr image, const Point & pivotOffset); + void setCursorPosition( const Point & newPos ); + + void render(); }; class CursorSoftware @@ -137,7 +149,6 @@ public: void setCursorPosition( const Point & newPos ); void render(); - void setVisible(bool on); }; /// handles mouse cursor @@ -167,7 +178,7 @@ class CursorHandler final std::shared_ptr getCurrentImage(); - std::unique_ptr cursorSW; + std::unique_ptr cursorSW; public: CursorHandler(); ~CursorHandler(); From 6985f2e050c63c5ad4d0ebe3e5d7bfe3ff566f67 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 5 Jan 2023 22:38:52 +0200 Subject: [PATCH 115/197] Cursor type can now be set in settings.json --- client/gui/CursorHandler.cpp | 30 +++++++++++++++++++++--------- client/gui/CursorHandler.h | 32 +++++++++++++++++++++----------- config/schemas/settings.json | 6 +++++- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index 6f52ed652..8a1c4516b 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -12,15 +12,23 @@ #include - #include "SDL_Extensions.h" #include "CGuiHandler.h" #include "CAnimation.h" +#include "../../lib/CConfigHandler.h" -#include "../CMT.h" +//#include "../CMT.h" + +std::unique_ptr CursorHandler::createCursor() +{ + if (settings["video"]["softwareCursor"].Bool()) + return std::make_unique(); + else + return std::make_unique(); +} CursorHandler::CursorHandler() - : cursorSW(new CursorHardware()) + : cursor(createCursor()) , frameTime(0.f) , showing(false) , pos(0,0) @@ -58,7 +66,7 @@ void CursorHandler::changeGraphic(Cursor::Type type, size_t index) this->type = type; this->frame = index; - cursorSW->setImage(getCurrentImage(), getPivotOffset()); + cursor->setImage(getCurrentImage(), getPivotOffset()); } void CursorHandler::set(Cursor::Default index) @@ -85,7 +93,7 @@ void CursorHandler::set(Cursor::Spellcast index) void CursorHandler::dragAndDropCursor(std::shared_ptr image) { dndObject = image; - cursorSW->setImage(getCurrentImage(), getPivotOffset()); + cursor->setImage(getCurrentImage(), getPivotOffset()); } void CursorHandler::dragAndDropCursor (std::string path, size_t index) @@ -100,7 +108,7 @@ void CursorHandler::cursorMove(const int & x, const int & y) pos.x = x; pos.y = y; - cursorSW->setCursorPosition(pos); + cursor->setCursorPosition(pos); } Point CursorHandler::getPivotOffsetDefault(size_t index) @@ -206,7 +214,7 @@ Point CursorHandler::getPivotOffsetSpellcast() Point CursorHandler::getPivotOffset() { if (dndObject) - return dndObject->dimensions(); + return dndObject->dimensions() / 2; switch (type) { case Cursor::Type::ADVENTURE: return getPivotOffsetMap(frame); @@ -236,7 +244,7 @@ void CursorHandler::centerCursor() SDL_WarpMouse(pos.x, pos.y); SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE); - cursorSW->setCursorPosition(pos); + cursor->setCursorPosition(pos); } void CursorHandler::updateSpellcastCursor() @@ -268,7 +276,7 @@ void CursorHandler::render() if (type == Cursor::Type::SPELLBOOK) updateSpellcastCursor(); - cursorSW->render(); + cursor->render(); } void CursorSoftware::render() @@ -367,6 +375,7 @@ void CursorHardware::setImage(std::shared_ptr image, const Point & pivot image->draw(cursorSurface); + auto oldCursor = cursor; cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y); if (!cursor) @@ -374,6 +383,9 @@ void CursorHardware::setImage(std::shared_ptr image, const Point & pivot SDL_FreeSurface(cursorSurface); SDL_SetCursor(cursor); + + if (oldCursor) + SDL_FreeCursor(oldCursor); } void CursorHardware::setCursorPosition( const Point & newPos ) diff --git a/client/gui/CursorHandler.h b/client/gui/CursorHandler.h index cb47d606a..ad4d4ee2f 100644 --- a/client/gui/CursorHandler.h +++ b/client/gui/CursorHandler.h @@ -112,7 +112,17 @@ namespace Cursor }; } -class CursorHardware +class ICursor +{ +public: + virtual ~ICursor() = default; + + virtual void setImage(std::shared_ptr image, const Point & pivotOffset) = 0; + virtual void setCursorPosition( const Point & newPos ) = 0; + virtual void render() = 0; +}; + +class CursorHardware : public ICursor { std::shared_ptr cursorImage; @@ -122,13 +132,12 @@ public: CursorHardware(); ~CursorHardware(); - void setImage(std::shared_ptr image, const Point & pivotOffset); - void setCursorPosition( const Point & newPos ); - - void render(); + void setImage(std::shared_ptr image, const Point & pivotOffset) override; + void setCursorPosition( const Point & newPos ) override; + void render() override; }; -class CursorSoftware +class CursorSoftware : public ICursor { std::shared_ptr cursorImage; @@ -145,10 +154,9 @@ public: CursorSoftware(); ~CursorSoftware(); - void setImage(std::shared_ptr image, const Point & pivotOffset); - void setCursorPosition( const Point & newPos ); - - void render(); + void setImage(std::shared_ptr image, const Point & pivotOffset) override; + void setCursorPosition( const Point & newPos ) override; + void render() override; }; /// handles mouse cursor @@ -178,7 +186,9 @@ class CursorHandler final std::shared_ptr getCurrentImage(); - std::unique_ptr cursorSW; + std::unique_ptr cursor; + + static std::unique_ptr createCursor(); public: CursorHandler(); ~CursorHandler(); diff --git a/config/schemas/settings.json b/config/schemas/settings.json index dd4a063f3..8ff443d5d 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -81,7 +81,7 @@ "type" : "object", "additionalProperties" : false, "default": {}, - "required" : [ "screenRes", "bitsPerPixel", "fullscreen", "realFullscreen", "spellbookAnimation","driver", "showIntro", "displayIndex" ], + "required" : [ "screenRes", "bitsPerPixel", "fullscreen", "realFullscreen", "softwareCursor", "spellbookAnimation", "driver", "showIntro", "displayIndex" ], "properties" : { "screenRes" : { "type" : "object", @@ -105,6 +105,10 @@ "type" : "boolean", "default" : false }, + "softwareCursor" : { + "type" : "boolean", + "default" : false + }, "showIntro" : { "type" : "boolean", "default" : true From c6d39da53e5e4d80d8fcab956a68d38befa74a22 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 5 Jan 2023 23:14:52 +0200 Subject: [PATCH 116/197] MXE compile fix --- client/gui/CursorHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index 8a1c4516b..958f6bfd2 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -171,7 +171,7 @@ Point CursorHandler::getPivotOffsetMap(size_t index) { 20, 20}, // SCUTTLE_BOAT = 42 }}; - static_assert (offsets.size() == size_t(Cursor::Map::COUNT), "Invalid number of pivot offsets for cursor" ); + assert(offsets.size() == size_t(Cursor::Map::COUNT)); //Invalid number of pivot offsets for cursor assert(index < offsets.size()); return offsets[index]; } @@ -201,7 +201,7 @@ Point CursorHandler::getPivotOffsetCombat(size_t index) { 14, 20 }, // TELEPORT = 19 }}; - static_assert (offsets.size() == size_t(Cursor::Combat::COUNT), "Invalid number of pivot offsets for cursor" ); + assert(offsets.size() == size_t(Cursor::Combat::COUNT)); //Invalid number of pivot offsets for cursor assert(index < offsets.size()); return offsets[index]; } From 385dda1c5be406e588d4b0e2ef8d33b2e817f4a0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 5 Jan 2023 23:24:49 +0200 Subject: [PATCH 117/197] Clean surface before using it --- client/gui/CursorHandler.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index 958f6bfd2..688d2f49b 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -373,6 +373,9 @@ void CursorHardware::setImage(std::shared_ptr image, const Point & pivot { auto cursorSurface = CSDL_Ext::newSurface(image->dimensions().x, image->dimensions().y); + Uint32 fillColor = SDL_MapRGBA(cursorSurface->format, 0, 0, 0, 0); + CSDL_Ext::fillRect(cursorSurface, nullptr, fillColor); + image->draw(cursorSurface); auto oldCursor = cursor; From fc4121f5ec6d64898ee98f52e1bd687c4052e186 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 11 Jan 2023 13:41:44 +0200 Subject: [PATCH 118/197] Rechecked & updated offsets of adventure map cursors --- client/gui/CursorHandler.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index 688d2f49b..b093637a4 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -126,36 +126,36 @@ Point CursorHandler::getPivotOffsetMap(size_t index) { 15, 13}, // T1_MOVE = 4, { 13, 13}, // T1_ATTACK = 5, - { 20, 20}, // T1_SAIL = 6, - { 13, 16}, // T1_DISEMBARK = 7, + { 16, 32}, // T1_SAIL = 6, + { 13, 20}, // T1_DISEMBARK = 7, { 8, 9}, // T1_EXCHANGE = 8, { 14, 16}, // T1_VISIT = 9, { 15, 13}, // T2_MOVE = 10, { 13, 13}, // T2_ATTACK = 11, - { 20, 20}, // T2_SAIL = 12, - { 13, 16}, // T2_DISEMBARK = 13, + { 16, 32}, // T2_SAIL = 12, + { 13, 20}, // T2_DISEMBARK = 13, { 8, 9}, // T2_EXCHANGE = 14, { 14, 16}, // T2_VISIT = 15, { 15, 13}, // T3_MOVE = 16, { 13, 13}, // T3_ATTACK = 17, - { 20, 20}, // T3_SAIL = 18, - { 13, 16}, // T3_DISEMBARK = 19, + { 16, 32}, // T3_SAIL = 18, + { 13, 20}, // T3_DISEMBARK = 19, { 8, 9}, // T3_EXCHANGE = 20, { 14, 16}, // T3_VISIT = 21, { 15, 13}, // T4_MOVE = 22, { 13, 13}, // T4_ATTACK = 23, - { 20, 20}, // T4_SAIL = 24, - { 13, 16}, // T4_DISEMBARK = 25, + { 16, 32}, // T4_SAIL = 24, + { 13, 20}, // T4_DISEMBARK = 25, { 8, 9}, // T4_EXCHANGE = 26, { 14, 16}, // T4_VISIT = 27, - { 20, 20}, // T1_SAIL_VISIT = 28, - { 20, 20}, // T2_SAIL_VISIT = 29, - { 20, 20}, // T3_SAIL_VISIT = 30, - { 20, 20}, // T4_SAIL_VISIT = 31, + { 16, 32}, // T1_SAIL_VISIT = 28, + { 16, 32}, // T2_SAIL_VISIT = 29, + { 16, 32}, // T3_SAIL_VISIT = 30, + { 16, 32}, // T4_SAIL_VISIT = 31, { 6, 1}, // SCROLL_NORTH = 32, { 16, 2}, // SCROLL_NORTHEAST = 33, From 494274ee4698fec147b349aca33a6cbcd4b48c1c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 15 Jan 2023 23:53:27 +0200 Subject: [PATCH 119/197] Fix single-app build --- launcher/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 3d6a38ec3..6f0eb86ae 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -107,7 +107,7 @@ if(WIN32) endif() if(ENABLE_SINGLE_APP_BUILD) - add_library(vcmilauncher STATIC ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS}) + add_library(vcmilauncher STATIC ${launcher_QM} ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS}) else() add_executable(vcmilauncher WIN32 ${launcher_QM} ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS} ${launcher_ICON}) endif() From 434163461ea8f202dedece157855e8ffd5a02b11 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 15 Jan 2023 23:54:27 +0200 Subject: [PATCH 120/197] Added logging for translation search --- launcher/mainwindow_moc.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 0839561d5..bd001ce6c 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -165,6 +165,7 @@ void MainWindow::updateTranslation() { #ifdef ENABLE_QT_TRANSLATIONS std::string translationFile = settings["general"]["language"].String() + ".qm"; + logGlobal->info("Loading translation '%s'", translationFile); QVector searchPaths; @@ -174,8 +175,10 @@ void MainWindow::updateTranslation() for(auto const & string : boost::adaptors::reverse(searchPaths)) { + logGlobal->info("Searching for translation at '%s'", string.toStdString()); if (translator.load(string)) { + logGlobal->info("Translation found"); if (!qApp->installTranslator(&translator)) logGlobal->error("Failed to install translator"); return; From 0a858d9a7c0f740501eaa88cb45ea1a66e2ae998 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 15 Jan 2023 23:55:24 +0200 Subject: [PATCH 121/197] Upgrade msvc to include enet and qt translator (#1436) - Updated MSVC prebuilt package - Fixed build warnings --- CI/msvc/before_install.sh | 14 +++++++------- CMakeLists.txt | 2 +- client/CMakeLists.txt | 2 +- client/battle/BattleStacksController.cpp | 8 ++++---- lib/CGeneralTextHandler.cpp | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CI/msvc/before_install.sh b/CI/msvc/before_install.sh index c80aaf4d7..851cbd4a9 100644 --- a/CI/msvc/before_install.sh +++ b/CI/msvc/before_install.sh @@ -1,10 +1,10 @@ -curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z" \ - "https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.5/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z" -7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z" +curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z" \ + "https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.6/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z" +7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z" -rm -r -f vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug -mkdir -p vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin -cp vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/bin/* vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin +#rm -r -f vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug +#mkdir -p vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin +#cp vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/bin/* vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin DUMPBIN_DIR=$(vswhere -latest -find **/dumpbin.exe | head -n 1) -dirname "$DUMPBIN_DIR" > $GITHUB_PATH \ No newline at end of file +dirname "$DUMPBIN_DIR" > $GITHUB_PATH diff --git a/CMakeLists.txt b/CMakeLists.txt index fd51722d9..daf8454b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,7 +232,7 @@ if(MINGW OR MSVC) #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") # 4800: implicit conversion from 'xxx' to bool. Possible information loss if(ENABLE_STRICT_COMPILATION) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wx") # Treats all compiler warnings as errors + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX") # Treats all compiler warnings as errors endif() if(ENABLE_MULTI_PROCESS_BUILDS) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 7dd73f81c..4679e75b7 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -232,7 +232,7 @@ if(WIN32) add_custom_command(TARGET vcmiclient POST_BUILD WORKING_DIRECTORY "$" COMMAND ${CMAKE_COMMAND} -E copy AI/fuzzylite.dll fuzzylite.dll - COMMAND ${CMAKE_COMMAND} -E copy AI/tbb.dll tbb.dll + COMMAND ${CMAKE_COMMAND} -E copy AI/tbb12.dll tbb12.dll ) endif() elseif(APPLE_IOS) diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 811726c5f..37c73e7a6 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -83,10 +83,10 @@ BattleStacksController::BattleStacksController(BattleInterface & owner): amountNegative = IImage::createFromFile("CMNUMWIN.BMP"); amountEffNeutral = IImage::createFromFile("CMNUMWIN.BMP"); - static const auto shifterNormal = ColorFilter::genRangeShifter( 0,0,0, 0.6, 0.2, 1.0 ); - static const auto shifterPositive = ColorFilter::genRangeShifter( 0,0,0, 0.2, 1.0, 0.2 ); - static const auto shifterNegative = ColorFilter::genRangeShifter( 0,0,0, 1.0, 0.2, 0.2 ); - static const auto shifterNeutral = ColorFilter::genRangeShifter( 0,0,0, 1.0, 1.0, 0.2 ); + static const auto shifterNormal = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.6f, 0.2f, 1.0f ); + static const auto shifterPositive = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.2f, 1.0f, 0.2f ); + static const auto shifterNegative = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 0.2f, 0.2f ); + static const auto shifterNeutral = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 1.0f, 0.2f ); amountNormal->adjustPalette(shifterNormal); amountPositive->adjustPalette(shifterPositive); diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index e365e5cf0..908ef3281 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -156,7 +156,7 @@ static void detectEncoding() auto data = resource->readAll(); // compute how often each character occurs in input file - for (size_t i = 0; i < data.second; ++i) + for (si64 i = 0; i < data.second; ++i) charCount[data.first[i]] += 1; // and convert computed data into weights From 54e499f5e750df8973b951f13a278946c26fcd36 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Mon, 16 Jan 2023 00:33:53 +0100 Subject: [PATCH 122/197] Minor refactoring from code review --- client/windows/CCastleInterface.cpp | 21 ++++++++------------- client/windows/CCastleInterface.h | 8 ++++++++ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 0d5ba23ef..8c8852be9 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -154,14 +154,9 @@ SDL_Color multiplyColors(const SDL_Color & b, const SDL_Color & a, double f) void CBuildingRect::show(SDL_Surface * to) { - const ui32 stageDelay = 500; + uint32_t stageDelay = BUILDING_APPEAR_TIMEPOINT; - const ui32 S1_TRANSP = 500; //500 msec building appear 0->100 transparency - const ui32 S2_WHITE_B = 1000; //500 msec border glows from white to yellow - const ui32 S3_YELLOW_B= 1500; //500 msec border glows from yellow to normal - const ui32 BUILDED = 2500; //1000 msec delay, nothing happens - - if(stateTimeCounter < S1_TRANSP) + if(stateTimeCounter < BUILDING_APPEAR_TIMEPOINT) { setAlpha(255 * stateTimeCounter / stageDelay); CShowableAnim::show(to); @@ -172,9 +167,9 @@ void CBuildingRect::show(SDL_Surface * to) CShowableAnim::show(to); } - if(border && stateTimeCounter > S1_TRANSP) + if(border && stateTimeCounter > BUILDING_APPEAR_TIMEPOINT) { - if(stateTimeCounter >= BUILDED) + if(stateTimeCounter >= BUILD_ANIMATION_FINISHED_TIMEPOINT) { if(parent->selectedBuilding == this) blitAtLoc(border,0,0,to); @@ -191,10 +186,10 @@ void CBuildingRect::show(SDL_Surface * to) SDL_Color oldColor = border->format->palette->colors[colorID]; SDL_Color newColor; - if (stateTimeCounter < S2_WHITE_B) + if (stateTimeCounter < BUILDING_WHITE_BORDER_TIMEPOINT) newColor = multiplyColors(c1, c2, static_cast(stateTimeCounter % stageDelay) / stageDelay); else - if (stateTimeCounter < S3_YELLOW_B) + if (stateTimeCounter < BUILDING_YELLOW_BORDER_TIMEPOINT) newColor = multiplyColors(c2, c3, static_cast(stateTimeCounter % stageDelay) / stageDelay); else newColor = oldColor; @@ -204,7 +199,7 @@ void CBuildingRect::show(SDL_Surface * to) SDL_SetColors(border, &oldColor, colorID, 1); } } - if(stateTimeCounter < BUILDED) + if(stateTimeCounter < BUILD_ANIMATION_FINISHED_TIMEPOINT) stateTimeCounter += GH.mainFPSmng->getElapsedMilliseconds(); } @@ -634,7 +629,7 @@ void CCastleBuildings::addBuilding(BuildingID building) if(structures.size() == 1) buildingRect->stateTimeCounter = 0; // transparency -> fully visible stage else - buildingRect->stateTimeCounter = 500; // already in fully visible stage + buildingRect->stateTimeCounter = CBuildingRect::BUILDING_APPEAR_TIMEPOINT; // already in fully visible stage break; } } diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index 55bdc31d0..6b9f7f7e8 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -42,6 +42,14 @@ class CBuildingRect : public CShowableAnim { std::string getSubtitle(); public: + enum EBuildingCreationAnimationPhases : uint32_t + { + BUILDING_APPEAR_TIMEPOINT = 500, //500 msec building appears: 0->100% transparency + BUILDING_WHITE_BORDER_TIMEPOINT = 1000, //500 msec border glows from white to yellow + BUILDING_YELLOW_BORDER_TIMEPOINT = 1500, //500 msec border glows from yellow to normal + BUILD_ANIMATION_FINISHED_TIMEPOINT = 2500 //1000 msec delay, nothing happens + }; + /// returns building associated with this structure const CBuilding * getBuilding(); From da39eb789ff47c1bce5bb3ebd71894630b435a96 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Mon, 16 Jan 2023 00:37:18 +0100 Subject: [PATCH 123/197] Fix indent --- client/CMakeLists.txt | 2 +- lib/CConsoleHandler.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 626d66d2a..26c92b55a 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -169,7 +169,7 @@ set(client_HEADERS mapHandler.h resource.h SDLRWwrapper.h - ClientCommandManager.h + ClientCommandManager.h ) if(APPLE_IOS) diff --git a/lib/CConsoleHandler.h b/lib/CConsoleHandler.h index 7c1aa2b81..d929d13ee 100644 --- a/lib/CConsoleHandler.h +++ b/lib/CConsoleHandler.h @@ -76,7 +76,7 @@ public: #endif } //function to be called when message is received - string: message, bool: whether call was made from in-game console - std::function *cb; + std::function *cb; private: int run(); From c252837b6a4d84728ad0efb70344d7a8ea2fd156 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 16 Jan 2023 18:02:11 +0200 Subject: [PATCH 124/197] Fix installation of compiled QM files --- CMakeLists.txt | 11 ++++++----- launcher/CMakeLists.txt | 10 ++++++---- mapeditor/CMakeLists.txt | 9 +++++---- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a724971db..a6fc4adc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,11 @@ if(APPLE_IOS AND COPY_CONFIG_ON_BUILD) set(COPY_CONFIG_ON_BUILD OFF) endif() +# No QT Linguist on MXE +if(${CMAKE_CROSSCOMPILING}) + set(ENABLE_TRANSLATIONS OFF) +endif() + ############################################ # Miscellaneous options # ############################################ @@ -368,12 +373,8 @@ if(ENABLE_LAUNCHER OR ENABLE_EDITOR) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network) - find_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools) - find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools) - if(NOT Qt${QT_VERSION_MAJOR}LinguistTools_DIR) - set(ENABLE_TRANSLATIONS OFF) - endif() if(ENABLE_TRANSLATIONS) + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS LinguistTools) add_definitions(-DENABLE_QT_TRANSLATIONS) endif() endif() diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 20acece54..0be3692d7 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -96,7 +96,7 @@ if(TARGET Qt6::Core) else() qt5_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS}) if(ENABLE_TRANSLATIONS) - set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/translation) + set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/translation) qt5_add_translation( launcher_QM ${launcher_TS} ) endif() endif() @@ -164,8 +164,10 @@ else() # Copy to build directory for easier debugging add_custom_command(TARGET vcmilauncher POST_BUILD - COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/icons - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/launcher/icons ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/icons + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher + COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_SOURCE_DIR}/launcher/icons ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/icons + COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_BINARY_DIR}/translation ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/translation + ) install(TARGETS vcmilauncher DESTINATION ${BIN_DIR}) @@ -179,5 +181,5 @@ endif() install(DIRECTORY icons DESTINATION ${RESOURCES_DESTINATION}) if(ENABLE_TRANSLATIONS) - install(DIRECTORY translation DESTINATION ${RESOURCES_DESTINATION}) + install(FILES ${launcher_QM} DESTINATION ${RESOURCES_DESTINATION}/translation) endif() diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index 8f02f65f2..f7c14d86a 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -98,7 +98,7 @@ if(TARGET Qt6::Core) else() qt5_wrap_ui(editor_UI_HEADERS ${editor_FORMS}) if(ENABLE_TRANSLATIONS) - set_source_files_properties(${editor_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/mapeditor/translation) + set_source_files_properties(${editor_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/translation) qt5_add_translation( editor_QM ${editor_TS} ) endif() endif() @@ -139,14 +139,15 @@ enable_pch(vcmieditor) # Copy to build directory for easier debugging add_custom_command(TARGET vcmieditor POST_BUILD - COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/mapeditor/icons - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/mapeditor/icons ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/mapeditor/icons + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/mapeditor/ + COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_SOURCE_DIR}/mapeditor/icons ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/mapeditor/icons + COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_BINARY_DIR}/translation ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/mapeditor/translation ) install(TARGETS vcmieditor DESTINATION ${BIN_DIR}) # copy whole directory install(DIRECTORY icons DESTINATION ${DATA_DIR}/mapeditor) -install(DIRECTORY translation DESTINATION ${DATA_DIR}/mapeditor) +install(FILES ${editor_QM} DESTINATION ${DATA_DIR}/mapeditor/translation) # Install icons and desktop file on Linux if(NOT WIN32 AND NOT APPLE) install(FILES "vcmieditor.desktop" DESTINATION share/applications) From 8e1d8f835a3c2fa39ff770e8437be1dfe401528d Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 26 Dec 2022 02:37:35 +0400 Subject: [PATCH 125/197] Implemented support of protocol 4 --- client/CMT.cpp | 64 ++++++++++------ launcher/lobby/lobby.h | 31 +++++++- launcher/lobby/lobby_moc.cpp | 65 ++++++++++++++++- launcher/lobby/lobby_moc.h | 7 ++ launcher/lobby/lobby_moc.ui | 138 +++++++++++++++++++++++------------ 5 files changed, 231 insertions(+), 74 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index d4bc18ce9..55224b771 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -208,6 +208,8 @@ int main(int argc, char * argv[]) ("lobby-host", "if this client hosts session") ("lobby-uuid", po::value(), "uuid to the server") ("lobby-connections", po::value(), "connections of server") + ("lobby-username", po::value(), "player name") + ("lobby-gamemode", po::value(), "use 0 for new game and 1 for load game") ("uuid", po::value(), "uuid for the client"); if(argc > 1) @@ -489,29 +491,8 @@ int main(int argc, char * argv[]) session["autoSkip"].Bool() = vm.count("autoSkip"); session["oneGoodAI"].Bool() = vm.count("oneGoodAI"); session["aiSolo"].Bool() = false; + std::shared_ptr mmenu; - session["lobby"].Bool() = false; - if(vm.count("lobby")) - { - session["lobby"].Bool() = true; - session["host"].Bool() = false; - session["address"].String() = vm["lobby-address"].as(); - CSH->uuid = vm["uuid"].as(); - session["port"].Integer() = vm["lobby-port"].as(); - logGlobal->info("Remote lobby mode at %s:%d, uuid is %s", session["address"].String(), session["port"].Integer(), CSH->uuid); - if(vm.count("lobby-host")) - { - session["host"].Bool() = true; - session["hostConnections"].String() = std::to_string(vm["lobby-connections"].as()); - session["hostUuid"].String() = vm["lobby-uuid"].as(); - logGlobal->info("This client will host session, server uuid is %s", session["hostUuid"].String()); - } - - //we should not reconnect to previous game in online mode - Settings saveSession = settings.write["server"]["reconnect"]; - saveSession->Bool() = false; - } - if(vm.count("testmap")) { session["testmap"].String() = vm["testmap"].as(); @@ -526,7 +507,44 @@ int main(int argc, char * argv[]) } else { - GH.curInt = CMainMenu::create().get(); + mmenu = CMainMenu::create(); + GH.curInt = mmenu.get(); + } + + std::vector names; + session["lobby"].Bool() = false; + if(vm.count("lobby")) + { + session["lobby"].Bool() = true; + session["host"].Bool() = false; + session["address"].String() = vm["lobby-address"].as(); + if(vm.count("lobby-username")) + session["username"].String() = vm["lobby-username"].as(); + else + session["username"].String() = settings["launcher"]["lobbyUsername"].String(); + if(vm.count("lobby-gamemode")) + session["gamemode"].Integer() = vm["lobby-gamemode"].as(); + else + session["gamemode"].Integer() = 0; + CSH->uuid = vm["uuid"].as(); + session["port"].Integer() = vm["lobby-port"].as(); + logGlobal->info("Remote lobby mode at %s:%d, uuid is %s", session["address"].String(), session["port"].Integer(), CSH->uuid); + if(vm.count("lobby-host")) + { + session["host"].Bool() = true; + session["hostConnections"].String() = std::to_string(vm["lobby-connections"].as()); + session["hostUuid"].String() = vm["lobby-uuid"].as(); + logGlobal->info("This client will host session, server uuid is %s", session["hostUuid"].String()); + } + + //we should not reconnect to previous game in online mode + Settings saveSession = settings.write["server"]["reconnect"]; + saveSession->Bool() = false; + + //start lobby immediately + names.push_back(session["username"].String()); + ESelectionScreen sscreen = session["gamemode"].Integer() == 0 ? ESelectionScreen::newGame : ESelectionScreen::loadGame; + mmenu->openLobby(sscreen, session["host"].Bool(), &names, ELoadMode::MULTI); } // Restore remote session - start game immediately diff --git a/launcher/lobby/lobby.h b/launcher/lobby/lobby.h index 3e11e3bbc..192f8624a 100644 --- a/launcher/lobby/lobby.h +++ b/launcher/lobby/lobby.h @@ -12,7 +12,7 @@ #include #include -const unsigned int ProtocolVersion = 3; +const unsigned int ProtocolVersion = 4; const std::string ProtocolEncoding = "utf8"; class ProtocolError: public std::runtime_error @@ -24,10 +24,10 @@ public: enum ProtocolConsts { //client consts - GREETING, USERNAME, MESSAGE, VERSION, CREATE, JOIN, LEAVE, KICK, READY, FORCESTART, + GREETING, USERNAME, MESSAGE, VERSION, CREATE, JOIN, LEAVE, KICK, READY, FORCESTART, HERE, ALIVE, HOSTMODE, //server consts - SESSIONS, CREATED, JOINED, KICKED, SRVERROR, CHAT, START, STATUS, HOST, MODS, CLIENTMODS + SESSIONS, CREATED, JOINED, KICKED, SRVERROR, CHAT, START, STATUS, HOST, MODS, CLIENTMODS, USERS, HEALTH, GAMEMODE }; const QMap ProtocolStrings @@ -78,6 +78,16 @@ const QMap ProtocolStrings //[unsupported] start session immediately //%1: room name {FORCESTART, "%1"}, + + //request user list + {HERE, ""}, + + //used as reponse to healcheck + {ALIVE, ""}, + + //host sets game mode (new game or load game) + //%1: game mode - 0 for new game, 1 for load game + {HOSTMODE, "%1"}, //=== server commands === //server commands are started from :>>, arguments are enumerated by : symbol @@ -140,7 +150,20 @@ const QMap ProtocolStrings //received chat message //arg[0]: sender username //arg[1]: message text - {CHAT, "MSG"} + {CHAT, "MSG"}, + + //list of users currently in lobby + //arg[0]: amount of players, following arguments depend on it + //arg[x]: username + //arg[x+1]: room (empty if not in the room) + {USERS, "USERS"}, + + //healthcheck from server + {HEALTH, "HEALTH"}, + + //game mode (new game or load game) set by host + //arg[0]: game mode + {GAMEMODE, "GAMEMODE"} }; class ServerCommand diff --git a/launcher/lobby/lobby_moc.cpp b/launcher/lobby/lobby_moc.cpp index c52f88f29..7b73b2a86 100644 --- a/launcher/lobby/lobby_moc.cpp +++ b/launcher/lobby/lobby_moc.cpp @@ -135,9 +135,12 @@ void Lobby::serverCommand(const ServerCommand & command) try if(args[1] == username) { ui->buttonReady->setText("Ready"); - ui->chat->clear(); //cleanup the chat + ui->optNewGame->setChecked(true); sysMessage(joinStr.arg("you", args[0])); session = args[0]; + bool isHost = command.command == JOINED && hostSession == session; + ui->optNewGame->setEnabled(isHost); + ui->optLoadGame->setEnabled(isHost); ui->stackedWidget->setCurrentWidget(command.command == JOINED ? ui->roomPage : ui->sessionsPage); } else @@ -217,6 +220,8 @@ void Lobby::serverCommand(const ServerCommand & command) try gameArgs << "--lobby"; gameArgs << "--lobby-address" << serverUrl; gameArgs << "--lobby-port" << QString::number(serverPort); + gameArgs << "--lobby-username" << username; + gameArgs << "--lobby-gamemode" << QString::number(isLoadGameMode); gameArgs << "--uuid" << args[0]; startGame(gameArgs); break; @@ -238,6 +243,35 @@ void Lobby::serverCommand(const ServerCommand & command) try chatMessage(args[0], msg); break; } + + case HEALTH: { + protocolAssert(args.size() == 0); + socketLobby.send(ProtocolStrings[ALIVE]); + break; + } + + case USERS: { + protocolAssert(args.size() > 0); + amount = args[0].toInt(); + + protocolAssert(amount == (args.size() - 1)); + ui->listUsers->clear(); + for(int i = 0; i < amount; ++i) + { + ui->listUsers->addItem(new QListWidgetItem(args[i + 1])); + } + break; + } + + case GAMEMODE: { + protocolAssert(args.size() == 1); + isLoadGameMode = args[0].toInt(); + if(isLoadGameMode == 1) + ui->optLoadGame->setChecked(true); + else + ui->optNewGame->setChecked(true); + break; + } default: sysMessage("Unknown server command"); @@ -291,7 +325,7 @@ void Lobby::onDisconnected() ui->userEdit->setEnabled(true); ui->newButton->setEnabled(false); ui->joinButton->setEnabled(false); - ui->sessionsTable->clear(); + ui->sessionsTable->setRowCount(0); } void Lobby::chatMessage(QString title, QString body, bool isSystem) @@ -329,6 +363,7 @@ void Lobby::on_connectButton_toggled(bool checked) { if(checked) { + ui->connectButton->setText("Disconnect"); authentificationStatus = AuthStatus::AUTH_NONE; username = ui->userEdit->text(); const int connectionTimeout = settings["launcher"]["connectionTimeout"].Integer(); @@ -360,8 +395,10 @@ void Lobby::on_connectButton_toggled(bool checked) } else { + ui->connectButton->setText("Connection"); ui->serverEdit->setEnabled(true); ui->userEdit->setEnabled(true); + ui->listUsers->clear(); socketLobby.disconnectServer(); } } @@ -417,3 +454,27 @@ void Lobby::on_kickButton_clicked() socketLobby.send(ProtocolStrings[KICK].arg(ui->playersList->currentItem()->text())); } + +void Lobby::on_buttonResolve_clicked() +{ + //TODO: auto-resolve mods conflicts +} + +void Lobby::on_optNewGame_toggled(bool checked) +{ + if(checked) + { + if(isLoadGameMode) + socketLobby.send(ProtocolStrings[HOSTMODE].arg(0)); + } +} + +void Lobby::on_optLoadGame_toggled(bool checked) +{ + if(checked) + { + if(!isLoadGameMode) + socketLobby.send(ProtocolStrings[HOSTMODE].arg(1)); + } +} + diff --git a/launcher/lobby/lobby_moc.h b/launcher/lobby/lobby_moc.h index 364500223..368d00b3c 100644 --- a/launcher/lobby/lobby_moc.h +++ b/launcher/lobby/lobby_moc.h @@ -49,9 +49,16 @@ private slots: void on_kickButton_clicked(); + void on_buttonResolve_clicked(); + + void on_optNewGame_toggled(bool checked); + + void on_optLoadGame_toggled(bool checked); + private: QString serverUrl; int serverPort; + bool isLoadGameMode = false; Ui::Lobby *ui; SocketLobby socketLobby; diff --git a/launcher/lobby/lobby_moc.ui b/launcher/lobby/lobby_moc.ui index 44d410903..0ceda38a8 100644 --- a/launcher/lobby/lobby_moc.ui +++ b/launcher/lobby/lobby_moc.ui @@ -7,7 +7,7 @@ 0 0 652 - 329 + 331 @@ -30,9 +30,6 @@ - - - @@ -56,13 +53,6 @@ - - - - true - - - @@ -147,34 +137,10 @@ - - + + - Ready - - - - - - - Mods mismatch - - - - - - - Leave - - - - - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::NoSelection + Kick player @@ -191,13 +157,6 @@ - - - - Kick player - - - @@ -205,10 +164,99 @@ + + + + Leave + + + + + + + Mods mismatch + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + + + + + Ready + + + + + + + Resolve conflicts + + + + + + + 0 + + + + + New game + + + + + + + Load game + + + + + + + + + 0 + + + + + + 0 + 120 + + + + QAbstractItemView::NoEditTriggers + + + + + + + true + + + + + + + + From 14fc1c3f2641f5efe4bc5f4d9921f6b202b9d3d2 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 26 Dec 2022 02:52:24 +0400 Subject: [PATCH 126/197] Cosmetic changes --- launcher/lobby/lobby_moc.ui | 399 ++++++++++++++++++++---------------- 1 file changed, 222 insertions(+), 177 deletions(-) diff --git a/launcher/lobby/lobby_moc.ui b/launcher/lobby/lobby_moc.ui index 0ceda38a8..5b016f7c6 100644 --- a/launcher/lobby/lobby_moc.ui +++ b/launcher/lobby/lobby_moc.ui @@ -7,13 +7,26 @@ 0 0 652 - 331 + 383 Form + + + + + 0 + 0 + + + + Username + + + @@ -30,22 +43,6 @@ - - - - - 0 - 0 - - - - Username - - - - - - @@ -60,201 +57,249 @@ - - - + + + + + + 0 - - - - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - false - - - 80 - - - false - - - true - - - 20 - - - 20 - - - - Session - - - - - Players - - - - - - - - - - - - - false - + + + + 0 + + + - New room + People in lobby - - - - false + + + + + 0 + 0 + - - Join room + + + 16777215 + 96 + - - - - - - - - - - Kick player + + 0 - - - - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - - - - - Players in the room - - - - - - - Leave - - - - - - - Mods mismatch - - - - - QAbstractItemView::NoEditTriggers QAbstractItemView::NoSelection + + true + + + QListView::SinglePass + - - + + - Ready + Lobby chat - - - - Resolve conflicts + + + + true - - - - 0 - - - - - New game - - - - - - - Load game - - - - + + - - - - - - - 0 - + - + 0 - 120 + 0 - - QAbstractItemView::NoEditTriggers + + 0 + + + + + + false + + + New room + + + + + + + false + + + Join room + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + 80 + + + false + + + true + + + 20 + + + 20 + + + + Session + + + + + Players + + + + + + + + + + + + + + + + + Kick player + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + + + + Players in the room + + + + + + + Leave + + + + + + + Mods mismatch + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::MultiSelection + + + + + + + Ready + + + + + + + Resolve + + + + + + + 0 + + + + + New game + + + + + + + Load game + + + + + + + - - - - true - - - - - - From a2e358876c6057da4b12c969a0d3626dd0474611 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 26 Dec 2022 03:13:34 +0400 Subject: [PATCH 127/197] Health check fix --- launcher/lobby/lobby_moc.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/lobby/lobby_moc.cpp b/launcher/lobby/lobby_moc.cpp index 7b73b2a86..f177cc2b9 100644 --- a/launcher/lobby/lobby_moc.cpp +++ b/launcher/lobby/lobby_moc.cpp @@ -245,7 +245,6 @@ void Lobby::serverCommand(const ServerCommand & command) try } case HEALTH: { - protocolAssert(args.size() == 0); socketLobby.send(ProtocolStrings[ALIVE]); break; } From 2cce15efbe3f64173f24f30d73af234ff0db85d1 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 26 Dec 2022 04:34:10 +0400 Subject: [PATCH 128/197] Automatic mod conflict resolution --- launcher/lobby/lobby_moc.cpp | 130 +++++++++++++++++++---- launcher/lobby/lobby_moc.h | 9 ++ launcher/mainwindow_moc.cpp | 4 + launcher/modManager/cmodlistview_moc.cpp | 22 +++- launcher/modManager/cmodlistview_moc.h | 6 ++ 5 files changed, 146 insertions(+), 25 deletions(-) diff --git a/launcher/lobby/lobby_moc.cpp b/launcher/lobby/lobby_moc.cpp index f177cc2b9..7aed18e60 100644 --- a/launcher/lobby/lobby_moc.cpp +++ b/launcher/lobby/lobby_moc.cpp @@ -134,6 +134,7 @@ void Lobby::serverCommand(const ServerCommand & command) try if(args[1] == username) { + hostModsMap.clear(); ui->buttonReady->setText("Ready"); ui->optNewGame->setChecked(true); sysMessage(joinStr.arg("you", args[0])); @@ -155,33 +156,15 @@ void Lobby::serverCommand(const ServerCommand & command) try protocolAssert(amount * 2 == (args.size() - 1)); tagPoint = 1; - ui->modsList->clear(); - auto enabledMods = buildModsMap(); for(int i = 0; i < amount; ++i, tagPoint += 2) - { - if(enabledMods.contains(args[tagPoint])) - { - if(enabledMods[args[tagPoint]] == args[tagPoint + 1]) - enabledMods.remove(args[tagPoint]); - else - ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-update.png"), QString("%1 (v%2)").arg(args[tagPoint], args[tagPoint + 1]))); - } - else if(isModAvailable(args[tagPoint], args[tagPoint + 1])) - ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-enabled.png"), QString("%1 (v%2)").arg(args[tagPoint], args[tagPoint + 1]))); - else - ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-delete.png"), QString("%1 (v%2)").arg(args[tagPoint], args[tagPoint + 1]))); - } - for(auto & remainMod : enabledMods.keys()) - { - ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-disabled.png"), QString("%1 (v%2)").arg(remainMod, enabledMods[remainMod]))); - } - if(!ui->modsList->count()) - ui->modsList->addItem("No issues detected"); + hostModsMap[args[tagPoint]] = args[tagPoint + 1]; + + updateMods(); break; } case CLIENTMODS: { - protocolAssert(args.size() > 1); + protocolAssert(args.size() >= 1); amount = args[1].toInt(); protocolAssert(amount * 2 == (args.size() - 2)); @@ -398,10 +381,87 @@ void Lobby::on_connectButton_toggled(bool checked) ui->serverEdit->setEnabled(true); ui->userEdit->setEnabled(true); ui->listUsers->clear(); + hostModsMap.clear(); + updateMods(); socketLobby.disconnectServer(); } } +void Lobby::updateMods() +{ + ui->modsList->clear(); + if(hostModsMap.empty()) + return; + + auto enabledMods = buildModsMap(); + for(auto & mod : hostModsMap.keys()) + { + auto & modValue = hostModsMap[mod]; + auto modName = QString("%1 (v%2)").arg(mod, modValue); + //first - mod name + //second.first - should mod be enabled + //second.second - is possible to resolve + QMap modData; + QList modDataVal; + if(enabledMods.contains(mod)) + { + if(enabledMods[mod] == modValue) + enabledMods.remove(mod); //mod fully matches, remove from list + else + { + modDataVal.append(true); + modDataVal.append(false); + modData[mod] = modDataVal; + auto * lw = new QListWidgetItem(QIcon("icons:mod-update.png"), modName); //mod version mismatch + lw->setData(Qt::UserRole, modData); + ui->modsList->addItem(lw); + } + } + else if(isModAvailable(mod, modValue)) + { + modDataVal.append(true); + modDataVal.append(true); + modData[mod] = modDataVal; + auto * lw = new QListWidgetItem(QIcon("icons:mod-enabled.png"), modName); //mod available and needs to be enabled + lw->setData(Qt::UserRole, modData); + ui->modsList->addItem(lw); + } + else + { + modDataVal.append(true); + modDataVal.append(false); + modData[mod] = modDataVal; + auto * lw = new QListWidgetItem(QIcon("icons:mod-delete.png"), modName); //mod is not available and needs to be installed + lw->setData(Qt::UserRole, modData); + ui->modsList->addItem(lw); + } + } + for(auto & remainMod : enabledMods.keys()) + { + //first - mod name + //second.first - should mod be enabled + //second.second - is possible to resolve + QMap modData; + QList modDataVal; + modDataVal.append(false); + modDataVal.append(true); + modData[remainMod] = modDataVal; + auto modName = QString("%1 (v%2)").arg(remainMod, enabledMods[remainMod]); + auto * lw = new QListWidgetItem(QIcon("icons:mod-disabled.png"), modName); //mod needs to be disabled + lw->setData(Qt::UserRole, modData); + ui->modsList->addItem(lw); + } + if(!ui->modsList->count()) + { + ui->buttonResolve->setEnabled(false); + ui->modsList->addItem("No issues detected"); + } + else + { + ui->buttonResolve->setEnabled(true); + } +} + void Lobby::on_newButton_clicked() { new LobbyRoomRequest(socketLobby, "", buildModsMap(), this); @@ -456,7 +516,31 @@ void Lobby::on_kickButton_clicked() void Lobby::on_buttonResolve_clicked() { - //TODO: auto-resolve mods conflicts + QStringList toEnableList, toDisableList; + for(auto * item : ui->modsList->selectedItems()) + { + auto data = item->data(Qt::UserRole); + if(data.isNull()) + continue; + + auto modData = data.toMap(); + assert(modData.size() == 1); + auto modDataVal = modData.begin()->toList(); + assert(modDataVal.size() == 2); + if(!modDataVal[1].toBool()) + continue; + + if(modDataVal[0].toBool()) + toEnableList << modData.begin().key(); + else + toDisableList << modData.begin().key(); + + } + + for(auto & mod : toDisableList) + emit disableMod(mod); + for(auto & mod : toEnableList) + emit enableMod(mod); } void Lobby::on_optNewGame_toggled(bool checked) diff --git a/launcher/lobby/lobby_moc.h b/launcher/lobby/lobby_moc.h index 368d00b3c..1edb32cdc 100644 --- a/launcher/lobby/lobby_moc.h +++ b/launcher/lobby/lobby_moc.h @@ -22,6 +22,14 @@ class Lobby : public QWidget public: explicit Lobby(QWidget *parent = nullptr); ~Lobby(); + +signals: + + void enableMod(QString mod); + void disableMod(QString mod); + +public slots: + void updateMods(); private slots: void on_messageEdit_returnPressed(); @@ -66,6 +74,7 @@ private: QString session; QString username; QStringList gameArgs; + QMap hostModsMap; enum AuthStatus { diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 5b71b9043..ea9e6bb38 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -53,6 +53,10 @@ MainWindow::MainWindow(QWidget * parent) load(); // load FS before UI ui->setupUi(this); + + connect(ui->lobbyView, &Lobby::enableMod, ui->modlistView, &CModListView::enableModByName); + connect(ui->lobbyView, &Lobby::disableMod, ui->modlistView, &CModListView::disableModByName); + connect(ui->modlistView, &CModListView::modsChanged, ui->lobbyView, &Lobby::updateMods); //load window settings QSettings s(Ui::teamName, Ui::appName); diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index e4c731127..155d89579 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -496,7 +496,14 @@ QStringList CModListView::findDependentMods(QString mod, bool excludeDisabled) void CModListView::on_enableButton_clicked() { QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString(); + + enableModByName(modName); + + checkManagerErrors(); +} +void CModListView::enableModByName(QString modName) +{ assert(findBlockingMods(modName).empty()); assert(findInvalidDependencies(modName).empty()); @@ -505,17 +512,24 @@ void CModListView::on_enableButton_clicked() if(modModel->getMod(name).isDisabled()) manager->enableMod(name); } - checkManagerErrors(); + emit modsChanged(); } void CModListView::on_disableButton_clicked() { QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString(); + disableModByName(modName); + + checkManagerErrors(); +} + +void CModListView::disableModByName(QString modName) +{ if(modModel->hasMod(modName) && modModel->getMod(modName).isEnabled()) manager->disableMod(modName); - checkManagerErrors(); + emit modsChanged(); } void CModListView::on_updateButton_clicked() @@ -544,6 +558,8 @@ void CModListView::on_uninstallButton_clicked() manager->disableMod(modName); manager->uninstallMod(modName); } + + emit modsChanged(); checkManagerErrors(); } @@ -631,6 +647,8 @@ void CModListView::downloadFinished(QStringList savedFiles, QStringList failedFi if(doInstallFiles) installFiles(savedFiles); + + emit modsChanged(); } void CModListView::hideProgressBar() diff --git a/launcher/modManager/cmodlistview_moc.h b/launcher/modManager/cmodlistview_moc.h index 688a79d05..a23f0e713 100644 --- a/launcher/modManager/cmodlistview_moc.h +++ b/launcher/modManager/cmodlistview_moc.h @@ -66,6 +66,8 @@ class CModListView : public QWidget signals: void extraResolutionsEnabledChanged(bool enabled); + + void modsChanged(); public: explicit CModListView(QWidget * parent = 0); @@ -82,6 +84,10 @@ public: bool isExtraResolutionsModEnabled() const; const CModList & getModList() const; + +public slots: + void enableModByName(QString modName); + void disableModByName(QString modName); private slots: void dataChanged(const QModelIndex & topleft, const QModelIndex & bottomRight); From da085b005851c77a0c65a89ade772d7f2450aee6 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Tue, 3 Jan 2023 13:57:51 +0400 Subject: [PATCH 129/197] Code review tweaks --- launcher/lobby/lobby.h | 2 +- launcher/lobby/lobby_moc.cpp | 100 ++++++++++++++++------------------- 2 files changed, 48 insertions(+), 54 deletions(-) diff --git a/launcher/lobby/lobby.h b/launcher/lobby/lobby.h index 192f8624a..2ff03f721 100644 --- a/launcher/lobby/lobby.h +++ b/launcher/lobby/lobby.h @@ -163,7 +163,7 @@ const QMap ProtocolStrings //game mode (new game or load game) set by host //arg[0]: game mode - {GAMEMODE, "GAMEMODE"} + {GAMEMODE, "GAMEMODE"}, }; class ServerCommand diff --git a/launcher/lobby/lobby_moc.cpp b/launcher/lobby/lobby_moc.cpp index 7aed18e60..1137dca1a 100644 --- a/launcher/lobby/lobby_moc.cpp +++ b/launcher/lobby/lobby_moc.cpp @@ -16,6 +16,18 @@ #include "../modManager/cmodlist.h" #include "../../lib/CConfigHandler.h" +enum GameMode +{ + NEW_GAME = 0, LOAD_GAME = 1 +}; + +enum ModResolutionRoles +{ + ModNameRole = Qt::UserRole + 1, + ModEnableRole, + ModResolvableRole +}; + Lobby::Lobby(QWidget *parent) : QWidget(parent), ui(new Ui::Lobby) @@ -248,7 +260,7 @@ void Lobby::serverCommand(const ServerCommand & command) try case GAMEMODE: { protocolAssert(args.size() == 1); isLoadGameMode = args[0].toInt(); - if(isLoadGameMode == 1) + if(isLoadGameMode) ui->optLoadGame->setChecked(true); else ui->optNewGame->setChecked(true); @@ -345,7 +357,7 @@ void Lobby::on_connectButton_toggled(bool checked) { if(checked) { - ui->connectButton->setText("Disconnect"); + ui->connectButton->setText(tr("Disconnect")); authentificationStatus = AuthStatus::AUTH_NONE; username = ui->userEdit->text(); const int connectionTimeout = settings["launcher"]["connectionTimeout"].Integer(); @@ -377,7 +389,7 @@ void Lobby::on_connectButton_toggled(bool checked) } else { - ui->connectButton->setText("Connection"); + ui->connectButton->setText(tr("Connect")); ui->serverEdit->setEnabled(true); ui->userEdit->setEnabled(true); ui->listUsers->clear(); @@ -393,68 +405,51 @@ void Lobby::updateMods() if(hostModsMap.empty()) return; + auto createModListWidget = [](const QIcon & icon, const QString & label, const QString & name, bool enableFlag, bool resolveFlag) + { + auto * lw = new QListWidgetItem(icon, label); //mod version mismatch + lw->setData(ModResolutionRoles::ModNameRole, name); + lw->setData(ModResolutionRoles::ModEnableRole, enableFlag); + lw->setData(ModResolutionRoles::ModResolvableRole, resolveFlag); + return lw; + }; + auto enabledMods = buildModsMap(); - for(auto & mod : hostModsMap.keys()) + for(const auto & mod : hostModsMap.keys()) { auto & modValue = hostModsMap[mod]; auto modName = QString("%1 (v%2)").arg(mod, modValue); - //first - mod name - //second.first - should mod be enabled - //second.second - is possible to resolve - QMap modData; - QList modDataVal; if(enabledMods.contains(mod)) { if(enabledMods[mod] == modValue) enabledMods.remove(mod); //mod fully matches, remove from list else { - modDataVal.append(true); - modDataVal.append(false); - modData[mod] = modDataVal; - auto * lw = new QListWidgetItem(QIcon("icons:mod-update.png"), modName); //mod version mismatch - lw->setData(Qt::UserRole, modData); - ui->modsList->addItem(lw); + //mod version mismatch + ui->modsList->addItem(createModListWidget(QIcon("icons:mod-update.png"), modName, mod, true, false)); } } else if(isModAvailable(mod, modValue)) { - modDataVal.append(true); - modDataVal.append(true); - modData[mod] = modDataVal; - auto * lw = new QListWidgetItem(QIcon("icons:mod-enabled.png"), modName); //mod available and needs to be enabled - lw->setData(Qt::UserRole, modData); - ui->modsList->addItem(lw); + //mod is available and needs to be enabled + ui->modsList->addItem(createModListWidget(QIcon("icons:mod-enabled.png"), modName, mod, true, true)); } else { - modDataVal.append(true); - modDataVal.append(false); - modData[mod] = modDataVal; - auto * lw = new QListWidgetItem(QIcon("icons:mod-delete.png"), modName); //mod is not available and needs to be installed - lw->setData(Qt::UserRole, modData); - ui->modsList->addItem(lw); + //mod is not available and needs to be installed + ui->modsList->addItem(createModListWidget(QIcon("icons:mod-delete.png"), modName, mod, true, false)); } } - for(auto & remainMod : enabledMods.keys()) + for(const auto & remainMod : enabledMods.keys()) { - //first - mod name - //second.first - should mod be enabled - //second.second - is possible to resolve - QMap modData; - QList modDataVal; - modDataVal.append(false); - modDataVal.append(true); - modData[remainMod] = modDataVal; auto modName = QString("%1 (v%2)").arg(remainMod, enabledMods[remainMod]); - auto * lw = new QListWidgetItem(QIcon("icons:mod-disabled.png"), modName); //mod needs to be disabled - lw->setData(Qt::UserRole, modData); - ui->modsList->addItem(lw); + //mod needs to be disabled + ui->modsList->addItem(createModListWidget(QIcon("icons:mod-disabled.png"), modName, remainMod, false, true)); } if(!ui->modsList->count()) { ui->buttonResolve->setEnabled(false); - ui->modsList->addItem("No issues detected"); + ui->modsList->addItem(tr("No issues detected")); } else { @@ -519,24 +514,23 @@ void Lobby::on_buttonResolve_clicked() QStringList toEnableList, toDisableList; for(auto * item : ui->modsList->selectedItems()) { - auto data = item->data(Qt::UserRole); - if(data.isNull()) + auto modName = item->data(ModResolutionRoles::ModNameRole); + if(modName.isNull()) continue; - auto modData = data.toMap(); - assert(modData.size() == 1); - auto modDataVal = modData.begin()->toList(); - assert(modDataVal.size() == 2); - if(!modDataVal[1].toBool()) + bool modToEnable = item->data(ModResolutionRoles::ModEnableRole).toBool(); + bool modToResolve = item->data(ModResolutionRoles::ModResolvableRole).toBool(); + + if(!modToResolve) continue; - if(modDataVal[0].toBool()) - toEnableList << modData.begin().key(); + if(modToEnable) + toEnableList << modName.toString(); else - toDisableList << modData.begin().key(); - + toDisableList << modName.toString(); } + //disabling first, then enabling for(auto & mod : toDisableList) emit disableMod(mod); for(auto & mod : toEnableList) @@ -548,7 +542,7 @@ void Lobby::on_optNewGame_toggled(bool checked) if(checked) { if(isLoadGameMode) - socketLobby.send(ProtocolStrings[HOSTMODE].arg(0)); + socketLobby.send(ProtocolStrings[HOSTMODE].arg(GameMode::NEW_GAME)); } } @@ -557,7 +551,7 @@ void Lobby::on_optLoadGame_toggled(bool checked) if(checked) { if(!isLoadGameMode) - socketLobby.send(ProtocolStrings[HOSTMODE].arg(1)); + socketLobby.send(ProtocolStrings[HOSTMODE].arg(GameMode::LOAD_GAME)); } } From 1ef9610e30a06ef43c387991492690a6b6089473 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Tue, 3 Jan 2023 15:36:26 +0400 Subject: [PATCH 130/197] Remove irrelevant comment --- launcher/lobby/lobby_moc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/lobby/lobby_moc.cpp b/launcher/lobby/lobby_moc.cpp index 1137dca1a..e6689bc9c 100644 --- a/launcher/lobby/lobby_moc.cpp +++ b/launcher/lobby/lobby_moc.cpp @@ -407,7 +407,7 @@ void Lobby::updateMods() auto createModListWidget = [](const QIcon & icon, const QString & label, const QString & name, bool enableFlag, bool resolveFlag) { - auto * lw = new QListWidgetItem(icon, label); //mod version mismatch + auto * lw = new QListWidgetItem(icon, label); lw->setData(ModResolutionRoles::ModNameRole, name); lw->setData(ModResolutionRoles::ModEnableRole, enableFlag); lw->setData(ModResolutionRoles::ModResolvableRole, resolveFlag); From d0b5a023ee2ac226e2144b84e905bff9c4bed799 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Tue, 3 Jan 2023 16:11:12 +0400 Subject: [PATCH 131/197] Fix bug --- client/CMT.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index 55224b771..a0eb08468 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -209,7 +209,7 @@ int main(int argc, char * argv[]) ("lobby-uuid", po::value(), "uuid to the server") ("lobby-connections", po::value(), "connections of server") ("lobby-username", po::value(), "player name") - ("lobby-gamemode", po::value(), "use 0 for new game and 1 for load game") + ("lobby-gamemode", po::value(), "use 0 for new game and 1 for load game") ("uuid", po::value(), "uuid for the client"); if(argc > 1) From a2581fb942740efb3644b635badae04dd8f7437b Mon Sep 17 00:00:00 2001 From: nordsoft Date: Tue, 3 Jan 2023 23:55:47 +0400 Subject: [PATCH 132/197] Fix crash at start --- client/NetPacksLobbyClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index fea5e9a19..b9459b6f6 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -63,7 +63,7 @@ void LobbyClientDisconnected::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHa void LobbyChatMessage::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler) { - if(lobby) + if(lobby && lobby->card) { lobby->card->chat->addNewMessage(playerName + ": " + message); lobby->card->setChat(true); From c0c1be26455cb728a5d3c536ad6385c4f9ac1af3 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 4 Jan 2023 00:05:59 +0400 Subject: [PATCH 133/197] Fix invalid host connection --- server/CVCMIServer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index b399b9ef9..e913afe6e 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -201,6 +201,10 @@ void CVCMIServer::run() void CVCMIServer::establishRemoteConnections() { + //wait for host connection + while(connections.empty()) + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + uuid = cmdLineOptions["lobby-uuid"].as(); int numOfConnections = cmdLineOptions["connections"].as(); auto address = cmdLineOptions["lobby"].as(); From c3b254a815982c64a99530004c8915751f6d6364 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Mon, 16 Jan 2023 18:26:31 +0100 Subject: [PATCH 134/197] Enum value can be used here as well --- client/windows/CCastleInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 8c8852be9..f55c1869b 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -47,7 +47,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town parent(Par), town(Town), str(Str), - stateTimeCounter(2500) + stateTimeCounter(BUILD_ANIMATION_FINISHED_TIMEPOINT) { addUsedEvents(LCLICK | RCLICK | HOVER); pos.x += str->pos.x; From e78d6f9d702e7205e897e13acc1458f9c7b7f7ee Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 16 Jan 2023 21:28:32 +0200 Subject: [PATCH 135/197] Fix MXE detection check --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a6fc4adc5..2f7ee25c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,7 +91,7 @@ if(APPLE_IOS AND COPY_CONFIG_ON_BUILD) endif() # No QT Linguist on MXE -if(${CMAKE_CROSSCOMPILING}) +if((MINGW) AND (${CMAKE_CROSSCOMPILING})) set(ENABLE_TRANSLATIONS OFF) endif() From b9712b08997a6a5425d2e9996d26b19bfd091148 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 16 Jan 2023 22:37:19 +0200 Subject: [PATCH 136/197] Fix translation search paths on ios --- launcher/mainwindow_moc.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index bd001ce6c..6833cdf9c 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -169,9 +169,13 @@ void MainWindow::updateTranslation() QVector searchPaths; +#ifdef Q_OS_IOS + searchPaths.push_back(pathToQString(VCMIDirs::get().binaryPath() / "translation" / translationFile)); +#else for(auto const & string : VCMIDirs::get().dataPaths()) searchPaths.push_back(pathToQString(string / "launcher" / "translation" / translationFile)); searchPaths.push_back(pathToQString(VCMIDirs::get().userDataPath() / "launcher" / "translation" / translationFile)); +#endif for(auto const & string : boost::adaptors::reverse(searchPaths)) { From 7a92aa0a06b6c36d3234b296d20cd47a34475824 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 17 Jan 2023 12:49:33 +0200 Subject: [PATCH 137/197] Fixes for compilation with Qt6 --- launcher/CMakeLists.txt | 2 ++ mapeditor/CMakeLists.txt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 0be3692d7..1ba52a9b3 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -91,6 +91,8 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) if(TARGET Qt6::Core) qt_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS}) if(ENABLE_TRANSLATIONS) + set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/translation) + # TODO: consider using qt_add_translations: https://doc.qt.io/qt-6/qtlinguist-cmake-qt-add-translations.html qt_add_translation( launcher_QM ${launcher_TS} ) endif() else() diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index f7c14d86a..11b3f7659 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -93,6 +93,8 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) if(TARGET Qt6::Core) qt_wrap_ui(editor_UI_HEADERS ${editor_FORMS}) if(ENABLE_TRANSLATIONS) + set_source_files_properties(${editor_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/translation) + # TODO: consider using qt_add_translations: https://doc.qt.io/qt-6/qtlinguist-cmake-qt-add-translations.html qt_add_translation( editor_QM ${editor_TS} ) endif() else() From 707de75ac0fbb9e35b36f7d2ad1acef6a9bae189 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 17 Jan 2023 22:58:22 +0200 Subject: [PATCH 138/197] Cleared up formatting --- lib/rmg/ConnectionsPlacer.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/rmg/ConnectionsPlacer.cpp b/lib/rmg/ConnectionsPlacer.cpp index 93fc2e802..c63377a16 100644 --- a/lib/rmg/ConnectionsPlacer.cpp +++ b/lib/rmg/ConnectionsPlacer.cpp @@ -85,8 +85,11 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con //1. Try to make direct connection //Do if it's not prohibited by terrain settings - bool directProhibited = vstd::contains(VLC->terrainTypeHandler->getById(zone.getTerrainType())->prohibitTransitions, otherZone->getTerrainType()) - || vstd::contains(VLC->terrainTypeHandler->getById(otherZone->getTerrainType())->prohibitTransitions, zone.getTerrainType()); + const auto * ourTerrain = VLC->terrainTypeHandler->getById(zone.getTerrainType()); + const auto * otherTerrain = VLC->terrainTypeHandler->getById(otherZone->getTerrainType()); + + bool directProhibited = vstd::contains(ourTerrain->prohibitTransitions, otherZone->getTerrainType()) + || vstd::contains(otherTerrain->prohibitTransitions, zone.getTerrainType()); auto directConnectionIterator = dNeighbourZones.find(otherZoneId); if(!directProhibited && directConnectionIterator != dNeighbourZones.end()) { From 633b9ef3bee6ffac1500965b5af7e930f0c1eb09 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 17 Jan 2023 22:59:14 +0200 Subject: [PATCH 139/197] Native terrain is now required for faction, opt-out with value "none" --- config/factions/neutral.json | 1 + config/schemas/faction.json | 2 +- lib/CTownHandler.cpp | 6 +++++- lib/GameConstants.h | 2 +- lib/mapObjects/ObjectTemplate.cpp | 4 ++-- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/config/factions/neutral.json b/config/factions/neutral.json index 6a6abac52..580a14c21 100644 --- a/config/factions/neutral.json +++ b/config/factions/neutral.json @@ -3,6 +3,7 @@ { "name" : "Neutral", "index" : 9, + "nativeTerrain" : "none", "alignment" : "neutral", "creatureBackground" : { diff --git a/config/schemas/faction.json b/config/schemas/faction.json index b6e25b4d0..f67ba6386 100644 --- a/config/schemas/faction.json +++ b/config/schemas/faction.json @@ -31,7 +31,7 @@ "$schema": "http://json-schema.org/draft-04/schema", "title" : "VCMI faction format", "description": "Json format for defining new faction (aka towns) in VCMI", - "required" : [ "name", "alignment", "creatureBackground" ], + "required" : [ "name", "alignment", "creatureBackground", "nativeTerrain" ], "dependencies" : { "town" : [ "puzzleMap" ] }, diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 49200d298..65d777dd5 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -967,8 +967,12 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode auto preferUndergound = source["preferUndergroundPlacement"]; faction->preferUndergroundPlacement = preferUndergound.isNull() ? false : preferUndergound.Bool(); + // NOTE: semi-workaround - normally, towns are supposed to have native terrains. + // Towns without one are exceptions. So, vcmi requires nativeTerrain to be defined + // But allows it to be defined with explicit value of "none" if town should not have native terrain + // This is better than allowing such terrain-less towns silently, leading to issues with RMG faction->nativeTerrain = ETerrainId::NONE; - if ( !source["nativeTerrain"].isNull()) + if ( !source["nativeTerrain"].isNull() && source["nativeTerrain"].String() != "none") { VLC->modh->identifiers.requestIdentifier("terrain", source["nativeTerrain"], [=](int32_t index){ faction->nativeTerrain = TerrainId(index); diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 3e45df780..9a31eda12 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -1234,7 +1234,7 @@ enum class ETerrainId { LAVA, WATER, ROCK, - ORIGINAL_TERRAIN_COUNT + ORIGINAL_REGULAR_TERRAIN_COUNT = ROCK }; using TerrainId = Identifier; diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 8b124be38..59727bbec 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -158,7 +158,7 @@ void ObjectTemplate::readTxt(CLegacyConfigParser & parser) std::string & terrStr = strings[4]; // allowed terrains, 1 = object can be placed on this terrain assert(terrStr.size() == TerrainId(ETerrainId::ROCK).getNum()); // all terrains but rock - counting from 0 - for(TerrainId i = TerrainId(0); i < ETerrainId::ROCK; ++i) + for(TerrainId i = TerrainId(0); i < ETerrainId::ORIGINAL_REGULAR_TERRAIN_COUNT; ++i) { if (terrStr[8-i.getNum()] == '1') allowedTerrains.insert(i); @@ -224,7 +224,7 @@ void ObjectTemplate::readMap(CBinaryReader & reader) reader.readUInt16(); ui16 terrMask = reader.readUInt16(); - for(TerrainId i = ETerrainId::FIRST_REGULAR_TERRAIN; i < ETerrainId::ORIGINAL_TERRAIN_COUNT; ++i) + for(TerrainId i = ETerrainId::FIRST_REGULAR_TERRAIN; i < ETerrainId::ORIGINAL_REGULAR_TERRAIN_COUNT; ++i) { if (((terrMask >> i.getNum()) & 1 ) != 0) allowedTerrains.insert(i); From e436b4425ff44c5b8080bc0928e26d4785006a92 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 17 Jan 2023 22:59:32 +0200 Subject: [PATCH 140/197] Serialization version bump --- lib/serializer/CSerializer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index 9ae6f1714..6889ae373 100644 --- a/lib/serializer/CSerializer.h +++ b/lib/serializer/CSerializer.h @@ -14,8 +14,8 @@ VCMI_LIB_NAMESPACE_BEGIN -const ui32 SERIALIZATION_VERSION = 807; -const ui32 MINIMAL_SERIALIZATION_VERSION = 805; +const ui32 SERIALIZATION_VERSION = 810; +const ui32 MINIMAL_SERIALIZATION_VERSION = 810; const std::string SAVEGAME_MAGIC = "VCMISVG"; class CHero; From 6321106b04514580ae84fe45044111595e250ced Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 17 Jan 2023 23:15:06 +0200 Subject: [PATCH 141/197] Reverted MXE to stable version --- CI/mxe/before_install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/mxe/before_install.sh b/CI/mxe/before_install.sh index d1a3b96d8..0a4855f2d 100644 --- a/CI/mxe/before_install.sh +++ b/CI/mxe/before_install.sh @@ -14,8 +14,8 @@ sudo add-apt-repository 'deb http://security.ubuntu.com/ubuntu bionic-security m sudo apt-get install -qq nsis ninja-build libssl1.0.0 # MXE repository was too slow for Travis far too often -wget -nv https://github.com/vcmi/vcmi-deps-mxe/releases/download/2022-12-26/mxe-i686-w64-mingw32.shared-2022-12-26.tar -tar -xvf mxe-i686-w64-mingw32.shared-2022-12-26.tar +wget -nv https://github.com/vcmi/vcmi-deps-mxe/releases/download/2021-02-20/mxe-i686-w64-mingw32.shared-2021-01-22.tar +tar -xvf mxe-i686-w64-mingw32.shared-2021-01-22.tar sudo dpkg -i mxe-*.deb sudo apt-get install -f --yes From c22ab5ec9e450ec2deb741d6b52b3b385968007b Mon Sep 17 00:00:00 2001 From: Dydzio Date: Thu, 12 Jan 2023 15:25:12 +0100 Subject: [PATCH 142/197] Initial version of limiting shooters range feature --- lib/battle/CBattleInfoCallback.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 808249246..c4bae673b 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -699,7 +699,28 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHe return false; if(battleMatchOwner(attacker, defender) && defender->alive()) - return battleCanShoot(attacker); + { + if(battleCanShoot(attacker)) + { + auto shooterBonus = attacker->getBonus(Selector::type()(Bonus::SHOOTER)); + if(shooterBonus->additionalInfo == CAddInfo::NONE) + { + return true; + } + + int shootingRange = shooterBonus->additionalInfo[0]; + int distanceBetweenHexes = BattleHex::getDistance(attacker->getPosition(), dest); + + if(distanceBetweenHexes <= shootingRange) + { + return true; + } + else + { + return false; + } + } + } return false; } From cf1674d0ca6e103343cf63a59847064fee5949da Mon Sep 17 00:00:00 2001 From: Dydzio Date: Thu, 12 Jan 2023 16:27:21 +0100 Subject: [PATCH 143/197] Introduce new bonus for limiting range --- lib/HeroBonus.h | 3 ++- lib/battle/CBattleInfoCallback.cpp | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index d120f8df9..a94e21c95 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -330,7 +330,8 @@ public: BONUS_NAME(GARGOYLE) /* gargoyle is special than NON_LIVING, cannot be rised or healed */ \ BONUS_NAME(SPECIAL_ADD_VALUE_ENCHANT) /*specialty spell like Aenin has, increased effect of spell, additionalInfo = value to add*/\ BONUS_NAME(SPECIAL_FIXED_VALUE_ENCHANT) /*specialty spell like Melody has, constant spell effect (i.e. 3 luck), additionalInfo = value to fix.*/\ - BONUS_NAME(TOWN_MAGIC_WELL) /*one-time pseudo-bonus to implement Magic Well in the town*/ \ + BONUS_NAME(TOWN_MAGIC_WELL) /*one-time pseudo-bonus to implement Magic Well in the town*/\ + BONUS_NAME(LIMITED_SHOOTING_RANGE) /*limits range of shooting creatures, doesn't adjust any other mechanics (half vs full damage etc). val - range in hexes*/\ /* end of list */ diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index c4bae673b..a4caac6a2 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -702,13 +702,13 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHe { if(battleCanShoot(attacker)) { - auto shooterBonus = attacker->getBonus(Selector::type()(Bonus::SHOOTER)); - if(shooterBonus->additionalInfo == CAddInfo::NONE) + auto limitedRangeBonus = attacker->getBonus(Selector::type()(Bonus::LIMITED_SHOOTING_RANGE)); + if(limitedRangeBonus == nullptr) { return true; } - int shootingRange = shooterBonus->additionalInfo[0]; + int shootingRange = limitedRangeBonus->val; int distanceBetweenHexes = BattleHex::getDistance(attacker->getPosition(), dest); if(distanceBetweenHexes <= shootingRange) From 01ce01d8f95f5c617669dc53e2501e056474ea75 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Thu, 12 Jan 2023 17:07:40 +0100 Subject: [PATCH 144/197] Add proper handling for 2-hex units + extract range calculation method --- lib/battle/CBattleInfoCallback.cpp | 27 +++++++++++++-------------- lib/battle/CBattleInfoCallback.h | 1 + 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index a4caac6a2..15bf09923 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -709,16 +709,7 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHe } int shootingRange = limitedRangeBonus->val; - int distanceBetweenHexes = BattleHex::getDistance(attacker->getPosition(), dest); - - if(distanceBetweenHexes <= shootingRange) - { - return true; - } - else - { - return false; - } + return isEnemyUnitWithinSpecifiedRange(attacker->getPosition(), defender, shootingRange); } } @@ -1618,10 +1609,8 @@ bool CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer * shooter, if(auto target = battleGetUnitByPos(destHex, true)) { //If any hex of target creature is within range, there is no penalty - for(auto hex : target->getHexes()) - if(BattleHex::getDistance(shooterPosition, hex) <= GameConstants::BATTLE_PENALTY_DISTANCE) - return false; - //TODO what about two-hex shooters? + if(isEnemyUnitWithinSpecifiedRange(shooterPosition, target, GameConstants::BATTLE_PENALTY_DISTANCE)) + return false; } else { @@ -1632,6 +1621,16 @@ bool CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer * shooter, return true; } +bool CBattleInfoCallback::isEnemyUnitWithinSpecifiedRange(BattleHex attackerPosition, const battle::Unit * defenderUnit, unsigned int range) const +{ + for(auto hex : defenderUnit->getHexes()) + if(BattleHex::getDistance(attackerPosition, hex) <= range) + return true; + + //TODO what about shooting distance calculation for two-hex shooters? Is it correct? + return false; +} + BattleHex CBattleInfoCallback::wallPartToBattleHex(EWallPart::EWallPart part) const { RETURN_IF_NOT_BATTLE(BattleHex::INVALID); diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index 75fc6bf81..e613e906f 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -94,6 +94,7 @@ public: int battleGetSurrenderCost(PlayerColor Player) const; //returns cost of surrendering battle, -1 if surrendering is not possible ReachabilityInfo::TDistances battleGetDistances(const battle::Unit * unit, BattleHex assumedPosition) const; std::set battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const; + bool isEnemyUnitWithinSpecifiedRange(BattleHex attackerPosition, const battle::Unit * defenderUnit, unsigned int range) const; bool battleCanAttack(const CStack * stack, const CStack * target, BattleHex dest) const; //determines if stack with given ID can attack target at the selected destination bool battleCanShoot(const battle::Unit * attacker, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination From f64d6bc1bc15f4f0b2add2d55119034feec21ab1 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 15 Jan 2023 14:52:41 +0100 Subject: [PATCH 145/197] Add support for overwriting default range via additional info --- lib/battle/CBattleInfoCallback.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 15bf09923..d32bcb366 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1609,7 +1609,13 @@ bool CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer * shooter, if(auto target = battleGetUnitByPos(destHex, true)) { //If any hex of target creature is within range, there is no penalty - if(isEnemyUnitWithinSpecifiedRange(shooterPosition, target, GameConstants::BATTLE_PENALTY_DISTANCE)) + int range = GameConstants::BATTLE_PENALTY_DISTANCE; + + auto bonus = shooter->getBonus(Selector::type()(Bonus::LIMITED_SHOOTING_RANGE)); + if(bonus != nullptr && bonus->additionalInfo != CAddInfo::NONE) + range = bonus->additionalInfo[0]; + + if(isEnemyUnitWithinSpecifiedRange(shooterPosition, target, range)) return false; } else From 72eb7943973e44e30eb586c694377163004e101a Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 15 Jan 2023 14:54:41 +0100 Subject: [PATCH 146/197] Fix space to tab, update description --- lib/HeroBonus.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index a94e21c95..11af6f8b1 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -331,7 +331,7 @@ public: BONUS_NAME(SPECIAL_ADD_VALUE_ENCHANT) /*specialty spell like Aenin has, increased effect of spell, additionalInfo = value to add*/\ BONUS_NAME(SPECIAL_FIXED_VALUE_ENCHANT) /*specialty spell like Melody has, constant spell effect (i.e. 3 luck), additionalInfo = value to fix.*/\ BONUS_NAME(TOWN_MAGIC_WELL) /*one-time pseudo-bonus to implement Magic Well in the town*/\ - BONUS_NAME(LIMITED_SHOOTING_RANGE) /*limits range of shooting creatures, doesn't adjust any other mechanics (half vs full damage etc). val - range in hexes*/\ + BONUS_NAME(LIMITED_SHOOTING_RANGE) /*limits range of shooting creatures, doesn't adjust any other mechanics (half vs full damage etc). val - range in hexes, additional info - optional new range for broken arrow mechanic */\ /* end of list */ From 0e72ee9217a59d576c1523d8586b224c803a78cf Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 15 Jan 2023 20:16:14 +0100 Subject: [PATCH 147/197] No longer relevant TODO removed --- lib/battle/CBattleInfoCallback.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index d32bcb366..6094e4694 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1632,8 +1632,7 @@ bool CBattleInfoCallback::isEnemyUnitWithinSpecifiedRange(BattleHex attackerPosi for(auto hex : defenderUnit->getHexes()) if(BattleHex::getDistance(attackerPosition, hex) <= range) return true; - - //TODO what about shooting distance calculation for two-hex shooters? Is it correct? + return false; } From e53e515aa788f5b1ff0d2bb9c1f5fa00ba98c1e9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 18 Jan 2023 01:04:50 +0200 Subject: [PATCH 148/197] Fix msvc compile --- lib/battle/BattleHex.cpp | 2 +- lib/battle/BattleHex.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/battle/BattleHex.cpp b/lib/battle/BattleHex.cpp index a8a026af8..94d8657e2 100644 --- a/lib/battle/BattleHex.cpp +++ b/lib/battle/BattleHex.cpp @@ -159,7 +159,7 @@ BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2) return NONE; } -char BattleHex::getDistance(BattleHex hex1, BattleHex hex2) +uint8_t BattleHex::getDistance(BattleHex hex1, BattleHex hex2) { int y1 = hex1.getY(), y2 = hex2.getY(); diff --git a/lib/battle/BattleHex.h b/lib/battle/BattleHex.h index f3c92b97c..af0f51c64 100644 --- a/lib/battle/BattleHex.h +++ b/lib/battle/BattleHex.h @@ -88,7 +88,7 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f std::vector allNeighbouringTiles() const; static EDir mutualPosition(BattleHex hex1, BattleHex hex2); - static char getDistance(BattleHex hex1, BattleHex hex2); + static uint8_t getDistance(BattleHex hex1, BattleHex hex2); static void checkAndPush(BattleHex tile, std::vector & ret); static BattleHex getClosestTile(ui8 side, BattleHex initialPos, std::set & possibilities); //TODO: vector or set? copying one to another is bad From 8b0625ac43ddd8f6e59994d300d08f439a310c9f Mon Sep 17 00:00:00 2001 From: Dydzio Date: Tue, 17 Jan 2023 22:49:45 +0100 Subject: [PATCH 149/197] Update polish.ts --- launcher/translation/polish.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/translation/polish.ts b/launcher/translation/polish.ts index 53fe63f23..2521652c5 100644 --- a/launcher/translation/polish.ts +++ b/launcher/translation/polish.ts @@ -265,7 +265,7 @@ Off - Włączony + Wyłączony @@ -273,7 +273,7 @@ On - Wyłączony + Włączony @@ -357,8 +357,8 @@ - Polska (Polish) - Polska + Polski (Polish) + Polski From e68430071581cdec195c55fc606721660b5ac4ed Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 18 Jan 2023 14:34:06 +0200 Subject: [PATCH 150/197] Fix mod validation for terrains --- config/schemas/mod.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/config/schemas/mod.json b/config/schemas/mod.json index ca74f80ef..2cdf44246 100644 --- a/config/schemas/mod.json +++ b/config/schemas/mod.json @@ -194,7 +194,21 @@ "type":"array", "description": "List of configuration files for obstacles", "items": { "type":"string", "format" : "textFile" } - + }, + "terrains":{ + "type":"array", + "description": "List of configuration files for terrains", + "items": { "type":"string", "format" : "textFile" } + }, + "roads":{ + "type":"array", + "description": "List of configuration files for roads", + "items": { "type":"string", "format" : "textFile" } + }, + "rivers":{ + "type":"array", + "description": "List of configuration files for rivers", + "items": { "type":"string", "format" : "textFile" } }, "changelog" : { From 3c403bcda365e451a141ba4cbeedc053e20900c2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 17:40:38 +0200 Subject: [PATCH 151/197] Debian packaging update (#1468) * Added qt tools and enet to debian dependencies --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 2a1cfb9ee..c86e49022 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: vcmi Section: games Priority: optional Maintainer: Ivan Savenko -Build-Depends: debhelper (>= 8), cmake, libsdl2-dev, libsdl2-image-dev, libsdl2-ttf-dev, libsdl2-mixer-dev, zlib1g-dev, libavformat-dev, libswscale-dev, libboost-dev (>=1.48), libboost-program-options-dev (>=1.48), libboost-filesystem-dev (>=1.48), libboost-system-dev (>=1.48), libboost-locale-dev (>=1.48), libboost-thread-dev (>=1.48), qtbase5-dev, libtbb2-dev, libfuzzylite-dev, libminizip-dev, libluajit-5.1-dev +Build-Depends: debhelper (>= 8), cmake, libsdl2-dev, libsdl2-image-dev, libsdl2-ttf-dev, libsdl2-mixer-dev, zlib1g-dev, libavformat-dev, libswscale-dev, libboost-dev (>=1.48), libboost-program-options-dev (>=1.48), libboost-filesystem-dev (>=1.48), libboost-system-dev (>=1.48), libboost-locale-dev (>=1.48), libboost-thread-dev (>=1.48), qtbase5-dev, libtbb2-dev, libfuzzylite-dev, libminizip-dev, libluajit-5.1-dev, libenet-dev, qttools5-dev Standards-Version: 3.9.1 Homepage: http://vcmi.eu Vcs-Git: git://github.com/vcmi/vcmi.git From 8f433d345a8247f5a0b962c91b4a9a9a848c7b05 Mon Sep 17 00:00:00 2001 From: krs Date: Tue, 17 Jan 2023 01:07:57 +0200 Subject: [PATCH 152/197] Refactored resource Convertor to be more easy on the eyes Added a little more documentation to functions. Not using anymore dyrectory_iterator but directory_entry. Private functions made a little more generic, extracting some parametrers to higher levels. Export Bitmaps is no longer tied to SPRITES folder. --- mapeditor/Animation.cpp | 2 +- .../resourceExtractor/ResourceConverter.cpp | 45 ++++++++++--------- .../resourceExtractor/ResourceConverter.h | 28 +++++++----- 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/mapeditor/Animation.cpp b/mapeditor/Animation.cpp index 19cd3cccf..b62415cdc 100644 --- a/mapeditor/Animation.cpp +++ b/mapeditor/Animation.cpp @@ -694,7 +694,7 @@ void Animation::exportBitmaps(const QDir & path) const return; } - QString actualPath = path.absolutePath() + "/SPRITES/" + QString::fromStdString(name); + QString actualPath = path.absolutePath() + "/" + QString::fromStdString(name); QDir().mkdir(actualPath); size_t counter = 0; diff --git a/mapeditor/resourceExtractor/ResourceConverter.cpp b/mapeditor/resourceExtractor/ResourceConverter.cpp index 1bc7146ae..b969216a6 100644 --- a/mapeditor/resourceExtractor/ResourceConverter.cpp +++ b/mapeditor/resourceExtractor/ResourceConverter.cpp @@ -24,64 +24,69 @@ void ResourceConverter::convertExtractedResourceFiles(ConversionOptions conversionOptions) { - if (conversionOptions.splitDefs) - splitDefFiles(conversionOptions.deleteOriginals); + bfs::path spritesPath = VCMIDirs::get().userExtractedPath() / "SPRITES"; + bfs::path imagesPath = VCMIDirs::get().userExtractedPath() / "IMAGES"; + std::vector defFiles = { "TwCrPort.def", "CPRSMALL.def", "FlagPort.def", "ITPA.def", "ITPt.def", "Un32.def", "Un44.def" }; - if (conversionOptions.convertPcxToPng) - doConvertPcxToPng(conversionOptions.deleteOriginals); + if(conversionOptions.splitDefs) + splitDefFiles(spritesPath, defFiles, conversionOptions.deleteOriginals); + + if(conversionOptions.convertPcxToPng) + doConvertPcxToPng(imagesPath, conversionOptions.deleteOriginals); } -void ResourceConverter::doConvertPcxToPng(bool deleteOriginals) +void ResourceConverter::doConvertPcxToPng(const bfs::path & sourceFolder, bool deleteOriginals) { - bfs::path imagesPath = VCMIDirs::get().userExtractedPath() / "IMAGES"; - bfs::directory_iterator end_iter; + logGlobal->info("Converting .pcx to .png from folder: %s ...\n", sourceFolder); - for(bfs::directory_iterator dir_itr(imagesPath); dir_itr != end_iter; ++dir_itr) + for(bfs::directory_entry & directoryEntry : bfs::directory_iterator(sourceFolder)) { - const auto filename = dir_itr->path().filename(); + const auto filename = directoryEntry.path().filename(); try { - if (!bfs::is_regular_file(dir_itr->status())) + if(!bfs::is_regular_file(directoryEntry)) return; + std::string filePath = directoryEntry.path().string(); + std::string fileStem = directoryEntry.path().stem().string(); std::string filenameLowerCase = boost::algorithm::to_lower_copy(filename.string()); if(boost::algorithm::to_lower_copy(filename.extension().string()) == ".pcx") { auto img = BitmapHandler::loadBitmap(filenameLowerCase); - bfs::path pngFilePath = imagesPath / (dir_itr->path().stem().string() + ".png"); + bfs::path pngFilePath = sourceFolder / (fileStem + ".png"); img.save(pathToQString(pngFilePath), "PNG"); if(deleteOriginals) - bfs::remove(dir_itr->path()); + bfs::remove(directoryEntry.path()); } } - catch(const std::exception & ex) + catch(const std::exception& ex) { logGlobal->info(filename.string() + " " + ex.what() + "\n"); } } } -void ResourceConverter::splitDefFile(const std::string & fileName, const bfs::path & spritesPath, bool deleteOriginals) +void ResourceConverter::splitDefFile(const std::string & fileName, const bfs::path & sourceFolder, bool deleteOriginals) { if(CResourceHandler::get()->existsResource(ResourceID("SPRITES/" + fileName))) { std::unique_ptr anim = std::make_unique(fileName); anim->preload(); - anim->exportBitmaps(pathToQString(VCMIDirs::get().userExtractedPath())); + anim->exportBitmaps(pathToQString(sourceFolder)); if(deleteOriginals) - bfs::remove(spritesPath / fileName); + bfs::remove(sourceFolder / fileName); } else logGlobal->error("Def File Split error! " + fileName); } -void ResourceConverter::splitDefFiles(bool deleteOriginals) +void ResourceConverter::splitDefFiles(const bfs::path & sourceFolder, std::vector defFileNames, bool deleteOriginals) { - bfs::path spritesPath = VCMIDirs::get().userExtractedPath() / "SPRITES"; + logGlobal->info("Splitting Def Files from folder: %s ...\n", sourceFolder); - for(std::string defFilename : {"TwCrPort.def", "CPRSMALL.def", "FlagPort.def", "ITPA.def", "ITPt.def", "Un32.def", "Un44.def"}) - splitDefFile(defFilename, spritesPath, deleteOriginals); + for(std::string defFilename : defFileNames) + splitDefFile(defFilename, sourceFolder, deleteOriginals); } diff --git a/mapeditor/resourceExtractor/ResourceConverter.h b/mapeditor/resourceExtractor/ResourceConverter.h index 17d3361d6..b4633c287 100644 --- a/mapeditor/resourceExtractor/ResourceConverter.h +++ b/mapeditor/resourceExtractor/ResourceConverter.h @@ -24,26 +24,34 @@ struct ExtractionOptions { bool extractArchives = false; // if set, original H3 archives will be extracted into a separate folder ConversionOptions conversionOptions; + bool moveExtractedArchivesToSoDMod = false; }; +/** +* Class functionality to be used after extracting original H3 resources and before moving those resources to SoD Mod +* Splits def files containing individual images, so that faction resources are independent. (TwCrPort, CPRSMALL, FlagPort, ITPA, ITPt, Un32 and Un44) +* (town creature portraits, hero army creature portraits, adventure map dwellings, small town icons, big town icons, +* hero speciality small icons, hero speciality large icons) +* Converts all PCX images to PNG + */ class ResourceConverter { public: - // Splits def files that are shared between factions and converts pcx to bmp depending on Extraction Options + // Splits def files that are shared between factions and converts pcx to PNG depending on Extraction Options static void convertExtractedResourceFiles(ConversionOptions conversionOptions); private: - // converts all pcx files from /Images into PNG - static void doConvertPcxToPng(bool deleteOriginals); + /** Converts all .pcx from extractedFolder/Images into .png */ + static void doConvertPcxToPng(const bfs::path & sourceFolder, bool deleteOriginals); - // splits a def file into individual parts and converts the output to PNG format - static void splitDefFile(const std::string& fileName, const bfs::path& spritesPath, bool deleteOriginals); + /** splits a .def file into individual images and converts the output to PNG format */ + static void splitDefFile(const std::string & fileName, const bfs::path & sourceFolder, bool deleteOriginals); - // splits def files (TwCrPort, CPRSMALL, FlagPort, ITPA, ITPt, Un32 and Un44) so that faction resources are independent - // (town creature portraits, hero army creature portraits, adventure map dwellings, small town icons, big town icons, - // hero speciality small icons, hero speciality large icons) - static void splitDefFiles(bool deleteOriginals); -}; \ No newline at end of file + /** Splits the given .def files into individual images. + * For each .def file, the resulting images will be output in the same folder, in a subfolder (named just like the .def file) + */ + static void splitDefFiles(const bfs::path & sourceFolder, std::vector defFileNames, bool deleteOriginals); +}; From fd90653e25465ee5316d9541c22ecc52d993bbfc Mon Sep 17 00:00:00 2001 From: krs Date: Tue, 17 Jan 2023 01:08:23 +0200 Subject: [PATCH 153/197] Bug in moving maps. .h3c vs .H3C --- lib/filesystem/CArchiveLoader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/filesystem/CArchiveLoader.cpp b/lib/filesystem/CArchiveLoader.cpp index 23319485c..b58fe2b02 100644 --- a/lib/filesystem/CArchiveLoader.cpp +++ b/lib/filesystem/CArchiveLoader.cpp @@ -91,7 +91,7 @@ void CArchiveLoader::initLODArchive(const std::string &mountPoint, CFileInputStr extractToFolder("IMAGES", mountPoint, entry); else if ((fName.find(".DEF") != std::string::npos ) || (fName.find(".MSK") != std::string::npos) || (fName.find(".FNT") != std::string::npos) || (fName.find(".PAL") != std::string::npos)) extractToFolder("SPRITES", mountPoint, entry); - else if ((fName.find(".h3c") != std::string::npos)) + else if ((fName.find(".H3C") != std::string::npos)) extractToFolder("SPRITES", mountPoint, entry); else extractToFolder("MISC", mountPoint, entry); From 774a8e024cdd7be03d07d46b71c77943e6261631 Mon Sep 17 00:00:00 2001 From: krs Date: Tue, 17 Jan 2023 01:13:32 +0200 Subject: [PATCH 154/197] Fixed early return in PcxToPng convertor Convertor would abort if no regular file found, instead of continuing to next item. --- mapeditor/resourceExtractor/ResourceConverter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapeditor/resourceExtractor/ResourceConverter.cpp b/mapeditor/resourceExtractor/ResourceConverter.cpp index b969216a6..8c27c643b 100644 --- a/mapeditor/resourceExtractor/ResourceConverter.cpp +++ b/mapeditor/resourceExtractor/ResourceConverter.cpp @@ -45,7 +45,7 @@ void ResourceConverter::doConvertPcxToPng(const bfs::path & sourceFolder, bool d try { if(!bfs::is_regular_file(directoryEntry)) - return; + continue; std::string filePath = directoryEntry.path().string(); std::string fileStem = directoryEntry.path().stem().string(); From 05971d5bb538b5038b944c156ca9a7891227bddf Mon Sep 17 00:00:00 2001 From: krs Date: Wed, 18 Jan 2023 19:14:26 +0200 Subject: [PATCH 155/197] Resource converter changes after review. --- mapeditor/resourceExtractor/ResourceConverter.cpp | 6 +++--- mapeditor/resourceExtractor/ResourceConverter.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mapeditor/resourceExtractor/ResourceConverter.cpp b/mapeditor/resourceExtractor/ResourceConverter.cpp index 8c27c643b..3dcfa17a6 100644 --- a/mapeditor/resourceExtractor/ResourceConverter.cpp +++ b/mapeditor/resourceExtractor/ResourceConverter.cpp @@ -29,7 +29,7 @@ void ResourceConverter::convertExtractedResourceFiles(ConversionOptions conversi std::vector defFiles = { "TwCrPort.def", "CPRSMALL.def", "FlagPort.def", "ITPA.def", "ITPt.def", "Un32.def", "Un44.def" }; if(conversionOptions.splitDefs) - splitDefFiles(spritesPath, defFiles, conversionOptions.deleteOriginals); + splitDefFiles(defFiles, spritesPath, conversionOptions.deleteOriginals); if(conversionOptions.convertPcxToPng) doConvertPcxToPng(imagesPath, conversionOptions.deleteOriginals); @@ -83,10 +83,10 @@ void ResourceConverter::splitDefFile(const std::string & fileName, const bfs::pa logGlobal->error("Def File Split error! " + fileName); } -void ResourceConverter::splitDefFiles(const bfs::path & sourceFolder, std::vector defFileNames, bool deleteOriginals) +void ResourceConverter::splitDefFiles(const std::vector & defFileNames, const bfs::path & sourceFolder, bool deleteOriginals) { logGlobal->info("Splitting Def Files from folder: %s ...\n", sourceFolder); - for(std::string defFilename : defFileNames) + for(const auto & defFilename : defFileNames) splitDefFile(defFilename, sourceFolder, deleteOriginals); } diff --git a/mapeditor/resourceExtractor/ResourceConverter.h b/mapeditor/resourceExtractor/ResourceConverter.h index b4633c287..e2d269eeb 100644 --- a/mapeditor/resourceExtractor/ResourceConverter.h +++ b/mapeditor/resourceExtractor/ResourceConverter.h @@ -53,5 +53,5 @@ private: /** Splits the given .def files into individual images. * For each .def file, the resulting images will be output in the same folder, in a subfolder (named just like the .def file) */ - static void splitDefFiles(const bfs::path & sourceFolder, std::vector defFileNames, bool deleteOriginals); + static void splitDefFiles(const std::vector & defFileNames, const bfs::path & sourceFolder, bool deleteOriginals); }; From 8d407be67f776ad7a1ee2a96138a859270f8fce5 Mon Sep 17 00:00:00 2001 From: krs Date: Fri, 20 Jan 2023 02:07:54 +0200 Subject: [PATCH 156/197] Updated according to Ivan's review. --- .../resourceExtractor/ResourceConverter.cpp | 3 +-- .../resourceExtractor/ResourceConverter.h | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/mapeditor/resourceExtractor/ResourceConverter.cpp b/mapeditor/resourceExtractor/ResourceConverter.cpp index 3dcfa17a6..6cdecc345 100644 --- a/mapeditor/resourceExtractor/ResourceConverter.cpp +++ b/mapeditor/resourceExtractor/ResourceConverter.cpp @@ -39,7 +39,7 @@ void ResourceConverter::doConvertPcxToPng(const bfs::path & sourceFolder, bool d { logGlobal->info("Converting .pcx to .png from folder: %s ...\n", sourceFolder); - for(bfs::directory_entry & directoryEntry : bfs::directory_iterator(sourceFolder)) + for(const auto & directoryEntry : bfs::directory_iterator(sourceFolder)) { const auto filename = directoryEntry.path().filename(); try @@ -47,7 +47,6 @@ void ResourceConverter::doConvertPcxToPng(const bfs::path & sourceFolder, bool d if(!bfs::is_regular_file(directoryEntry)) continue; - std::string filePath = directoryEntry.path().string(); std::string fileStem = directoryEntry.path().stem().string(); std::string filenameLowerCase = boost::algorithm::to_lower_copy(filename.string()); diff --git a/mapeditor/resourceExtractor/ResourceConverter.h b/mapeditor/resourceExtractor/ResourceConverter.h index e2d269eeb..cb1035562 100644 --- a/mapeditor/resourceExtractor/ResourceConverter.h +++ b/mapeditor/resourceExtractor/ResourceConverter.h @@ -27,13 +27,13 @@ struct ExtractionOptions bool moveExtractedArchivesToSoDMod = false; }; -/** -* Class functionality to be used after extracting original H3 resources and before moving those resources to SoD Mod -* Splits def files containing individual images, so that faction resources are independent. (TwCrPort, CPRSMALL, FlagPort, ITPA, ITPt, Un32 and Un44) -* (town creature portraits, hero army creature portraits, adventure map dwellings, small town icons, big town icons, -* hero speciality small icons, hero speciality large icons) -* Converts all PCX images to PNG - */ +///

+ + + + Cursor + + + + + + + + Default + + + + + Hardware + + + + + Software + + + +
From 5484efde90d431ac144c38898f1fe1e5c34533c1 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 18:14:51 +0200 Subject: [PATCH 158/197] Mobile platforms will use software cursor as default --- client/gui/CursorHandler.cpp | 49 +++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index b093637a4..1fadfcd4d 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -4,30 +4,39 @@ * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "CursorHandler.h" - + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CursorHandler.h" + #include #include "SDL_Extensions.h" #include "CGuiHandler.h" -#include "CAnimation.h" -#include "../../lib/CConfigHandler.h" - -//#include "../CMT.h" - -std::unique_ptr CursorHandler::createCursor() -{ - if (settings["video"]["softwareCursor"].Bool()) - return std::make_unique(); - else - return std::make_unique(); -} - -CursorHandler::CursorHandler() +#include "CAnimation.h" +#include "../../lib/CConfigHandler.h" + +std::unique_ptr CursorHandler::createCursor() +{ + if (settings["video"]["cursor"].String() == "auto") + { +#if defined(VCMI_ANDROID) || defined(VCMI_IOS) + return std::make_unique(); +#else + return std::make_unique(); +#endif + } + + if (settings["video"]["cursor"].String() == "hardware") + return std::make_unique(); + + assert(settings["video"]["cursor"].String() == "software"); + return std::make_unique(); +} + +CursorHandler::CursorHandler() : cursor(createCursor()) , frameTime(0.f) , showing(false) From fd05036f3bbcec6df743319f4b2583c4f36dd55a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 18:16:11 +0200 Subject: [PATCH 159/197] Hardware cursor can now be hidden --- client/gui/CursorHandler.cpp | 92 ++++++++++++++++++++++++------------ client/gui/CursorHandler.h | 8 +++- 2 files changed, 68 insertions(+), 32 deletions(-) diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index 1fadfcd4d..7d4c5c665 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -4,39 +4,39 @@ * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#include "StdInc.h" -#include "CursorHandler.h" - + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CursorHandler.h" + #include #include "SDL_Extensions.h" #include "CGuiHandler.h" -#include "CAnimation.h" -#include "../../lib/CConfigHandler.h" - -std::unique_ptr CursorHandler::createCursor() -{ - if (settings["video"]["cursor"].String() == "auto") - { -#if defined(VCMI_ANDROID) || defined(VCMI_IOS) - return std::make_unique(); -#else - return std::make_unique(); -#endif - } - - if (settings["video"]["cursor"].String() == "hardware") - return std::make_unique(); - - assert(settings["video"]["cursor"].String() == "software"); - return std::make_unique(); -} - -CursorHandler::CursorHandler() +#include "CAnimation.h" +#include "../../lib/CConfigHandler.h" + +std::unique_ptr CursorHandler::createCursor() +{ + if (settings["video"]["cursor"].String() == "auto") + { +#if defined(VCMI_ANDROID) || defined(VCMI_IOS) + return std::make_unique(); +#else + return std::make_unique(); +#endif + } + + if (settings["video"]["cursor"].String() == "hardware") + return std::make_unique(); + + assert(settings["video"]["cursor"].String() == "software"); + return std::make_unique(); +} + +CursorHandler::CursorHandler() : cursor(createCursor()) , frameTime(0.f) , showing(false) @@ -288,6 +288,24 @@ void CursorHandler::render() cursor->render(); } +void CursorHandler::hide() +{ + if (!showing) + return; + + showing = false; + cursor->setVisible(false); +} + +void CursorHandler::show() +{ + if (showing) + return; + + showing = true; + cursor->setVisible(true); +} + void CursorSoftware::render() { //texture must be updated in the main (renderer) thread, but changes to cursor type may come from other threads @@ -348,10 +366,16 @@ void CursorSoftware::setCursorPosition( const Point & newPos ) pos = newPos; } +void CursorSoftware::setVisible(bool on) +{ + visible = on; +} + CursorSoftware::CursorSoftware(): cursorTexture(nullptr), cursorSurface(nullptr), needUpdate(false), + visible(false), pivot(0,0) { SDL_ShowCursor(SDL_DISABLE); @@ -364,12 +388,12 @@ CursorSoftware::~CursorSoftware() if (cursorSurface) SDL_FreeSurface(cursorSurface); - } CursorHardware::CursorHardware(): cursor(nullptr) { + SDL_ShowCursor(SDL_DISABLE); } CursorHardware::~CursorHardware() @@ -378,6 +402,14 @@ CursorHardware::~CursorHardware() SDL_FreeCursor(cursor); } +void CursorHardware::setVisible(bool on) +{ + if (on) + SDL_ShowCursor(SDL_ENABLE); + else + SDL_ShowCursor(SDL_DISABLE); +} + void CursorHardware::setImage(std::shared_ptr image, const Point & pivotOffset) { auto cursorSurface = CSDL_Ext::newSurface(image->dimensions().x, image->dimensions().y); diff --git a/client/gui/CursorHandler.h b/client/gui/CursorHandler.h index ad4d4ee2f..c769da048 100644 --- a/client/gui/CursorHandler.h +++ b/client/gui/CursorHandler.h @@ -120,6 +120,7 @@ public: virtual void setImage(std::shared_ptr image, const Point & pivotOffset) = 0; virtual void setCursorPosition( const Point & newPos ) = 0; virtual void render() = 0; + virtual void setVisible( bool on) = 0; }; class CursorHardware : public ICursor @@ -135,6 +136,7 @@ public: void setImage(std::shared_ptr image, const Point & pivotOffset) override; void setCursorPosition( const Point & newPos ) override; void render() override; + void setVisible( bool on) override; }; class CursorSoftware : public ICursor @@ -147,6 +149,7 @@ class CursorSoftware : public ICursor Point pos; Point pivot; bool needUpdate; + bool visible; void createTexture(const Point & dimensions); void updateTexture(); @@ -157,6 +160,7 @@ public: void setImage(std::shared_ptr image, const Point & pivotOffset) override; void setCursorPosition( const Point & newPos ) override; void render() override; + void setVisible( bool on) override; }; /// handles mouse cursor @@ -222,8 +226,8 @@ public: void render(); - void hide() { showing=false; }; - void show() { showing=true; }; + void hide(); + void show(); /// change cursor's positions to (x, y) void cursorMove(const int & x, const int & y); From 8aebe404e333bb20f34e0a0a25af245bb2216536 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 19:00:18 +0200 Subject: [PATCH 160/197] Fix status bar activation via click --- client/widgets/TextControls.cpp | 6 +++++- client/widgets/TextControls.h | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index 4fb9d1093..5ea3a96ca 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -392,6 +392,8 @@ CGStatusBar::CGStatusBar(std::shared_ptr background_, EFonts Font, ETe : CLabel(background_->pos.x, background_->pos.y, Font, Align, Color, "") , enteringText(false) { + addUsedEvents(LCLICK); + background = background_; addChild(background.get()); pos = background->pos; @@ -403,6 +405,8 @@ CGStatusBar::CGStatusBar(int x, int y, std::string name, int maxw) : CLabel(x, y, FONT_SMALL, ETextAlignment::CENTER) , enteringText(false) { + addUsedEvents(LCLICK); + OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE); background = std::make_shared(name); pos = background->pos; @@ -428,7 +432,7 @@ void CGStatusBar::init() void CGStatusBar::clickLeft(tribool down, bool previousState) { - if(!down && onClick) + if(!down) { if(LOCPLINT && LOCPLINT->cingconsole->active) LOCPLINT->cingconsole->startEnteringText(); diff --git a/client/widgets/TextControls.h b/client/widgets/TextControls.h index 49189eaa8..70092d0f8 100644 --- a/client/widgets/TextControls.h +++ b/client/widgets/TextControls.h @@ -137,9 +137,6 @@ protected: void clickLeft(tribool down, bool previousState) override; -private: - std::function onClick; - public: template static std::shared_ptr create(Args... args) From b8c5a32b9b2b568049c53d86f43e48680e5205c5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 19:43:16 +0200 Subject: [PATCH 161/197] Fix attack targeting selection for double-wide units --- client/battle/BattleStacksController.cpp | 1 - lib/battle/CBattleInfoCallback.cpp | 10 +++++----- lib/battle/CBattleInfoCallback.h | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 37c73e7a6..afd0a69a5 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -543,7 +543,6 @@ void BattleStacksController::stackMoved(const CStack *stack, std::vectorcb->isToReverse( - attacker->getPosition(), attacker, defender); diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index dc9daae4d..03c1e2ba5 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1470,8 +1470,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes (const battl if (!defender) return at; // can't attack thin air - //FIXME: dragons or cerbers can rotate before attack, making their base hex different (#1124) - bool reverse = isToReverse(destinationTile, attacker, defender); + bool reverse = isToReverse(attacker, defender); if(reverse && attacker->doubleWide()) { hex = attacker->occupiedHex(hex); //the other hex stack stands on @@ -1622,13 +1621,14 @@ static bool isHexInFront(BattleHex hex, BattleHex testHex, BattleSide::Type side } //TODO: this should apply also to mechanics and cursor interface -bool CBattleInfoCallback::isToReverse (BattleHex attackerHex, const battle::Unit * attacker, const battle::Unit * defender) const +bool CBattleInfoCallback::isToReverse (const battle::Unit * attacker, const battle::Unit * defender) const { + BattleHex attackerHex = attacker->getPosition(); + BattleHex defenderHex = defender->getPosition(); + if (attackerHex < 0 ) //turret return false; - BattleHex defenderHex = defender->getPosition(); - if (isHexInFront(attackerHex, defenderHex, BattleSide::Type(attacker->unitSide()))) return false; diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index 378ecdecd..9dccfcc5c 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -137,7 +137,7 @@ public: AttackableTiles getPotentiallyShootableHexes(const battle::Unit* attacker, BattleHex destinationTile, BattleHex attackerPos) const; std::vector getAttackedBattleUnits(const battle::Unit* attacker, BattleHex destinationTile, bool rangedAttack, BattleHex attackerPos = BattleHex::INVALID) const; //calculates range of multi-hex attacks std::set getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, bool rangedAttack, BattleHex attackerPos = BattleHex::INVALID) const; //calculates range of multi-hex attacks - bool isToReverse(BattleHex attackerHex, const battle::Unit *attacker, const battle::Unit *defender) const; //determines if attacker standing at attackerHex should reverse in order to attack defender + bool isToReverse(const battle::Unit *attacker, const battle::Unit *defender) const; //determines if attacker standing at attackerHex should reverse in order to attack defender ReachabilityInfo getReachability(const battle::Unit * unit) const; ReachabilityInfo getReachability(const ReachabilityInfo::Parameters & params) const; From 8cfde8fc5e3694e1c0855e28757cb226b76149b5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 21:51:49 +0200 Subject: [PATCH 162/197] Fixed selection of dragon's breath attack against double-wide units --- lib/battle/CBattleInfoCallback.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 03c1e2ba5..b01624bcd 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1464,7 +1464,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes (const battl AttackableTiles at; RETURN_IF_NOT_BATTLE(at); - BattleHex hex = (attackerPos != BattleHex::INVALID) ? attackerPos : attacker->getPosition(); //real or hypothetical (cursor) position + BattleHex attackOriginHex = (attackerPos != BattleHex::INVALID) ? attackerPos : attacker->getPosition(); //real or hypothetical (cursor) position auto defender = battleGetUnitByPos(destinationTile, true); if (!defender) @@ -1473,7 +1473,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes (const battl bool reverse = isToReverse(attacker, defender); if(reverse && attacker->doubleWide()) { - hex = attacker->occupiedHex(hex); //the other hex stack stands on + attackOriginHex = attacker->occupiedHex(attackOriginHex); //the other hex stack stands on } if(attacker->hasBonusOfType(Bonus::ATTACKS_ALL_ADJACENT)) { @@ -1484,7 +1484,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes (const battl std::vector hexes = attacker->getSurroundingHexes(attackerPos); for(BattleHex tile : hexes) { - if((BattleHex::mutualPosition(tile, destinationTile) > -1 && BattleHex::mutualPosition(tile, hex) > -1)) //adjacent both to attacker's head and attacked tile + if((BattleHex::mutualPosition(tile, destinationTile) > -1 && BattleHex::mutualPosition(tile, attackOriginHex) > -1)) //adjacent both to attacker's head and attacked tile { auto st = battleGetUnitByPos(tile, true); if(st && battleMatchOwner(st, attacker)) //only hostile stacks - does it work well with Berserk? @@ -1497,7 +1497,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes (const battl std::vector hexes = destinationTile.neighbouringTiles(); for(int i = 0; ihasBonusOfType(Bonus::TWO_HEX_ATTACK_BREATH)) { - auto direction = BattleHex::mutualPosition(hex, destinationTile); + auto direction = BattleHex::mutualPosition(attackOriginHex, destinationTile); if(direction != BattleHex::NONE) //only adjacent hexes are subject of dragon breath calculation { BattleHex nextHex = destinationTile.cloneInDirection(direction, false); + if ( defender->doubleWide() ) + { + auto secondHex = destinationTile == defender->getPosition() ? + defender->occupiedHex(): + defender->getPosition(); + + // if targeted double-wide creature is attacked from above or below ( -> second hex is also adjacent to attack origin) + // then dragon breath should target tile on the opposite side of targeted creature + if (BattleHex::mutualPosition(attackOriginHex, secondHex) != BattleHex::NONE) + nextHex = secondHex.cloneInDirection(direction, false); + } + if (nextHex.isValid()) { //friendly stacks can also be damaged by Dragon Breath From d0c292e2fd1cb52192ed6ea55da7cdb6d743047e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 20:19:29 +0200 Subject: [PATCH 163/197] Fix order of map indexes --- lib/spells/AdventureSpellMechanics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spells/AdventureSpellMechanics.cpp b/lib/spells/AdventureSpellMechanics.cpp index f545fcc95..a42920771 100644 --- a/lib/spells/AdventureSpellMechanics.cpp +++ b/lib/spells/AdventureSpellMechanics.cpp @@ -582,7 +582,7 @@ ESpellCastResult ViewMechanics::applyAdventureEffects(SpellCastEnvironment * env { ObjectPosInfo posInfo(obj); - if((*fowMap)[posInfo.pos.x][posInfo.pos.y][posInfo.pos.z] == 0) + if((*fowMap)[posInfo.pos.z][posInfo.pos.x][posInfo.pos.y] == 0) pack.objectPositions.push_back(posInfo); } } From 21c053ba87a988b35c53919ad1c059ea0bea5a23 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 20:19:45 +0200 Subject: [PATCH 164/197] Fix assertion failure on shutdown --- client/CMusicHandler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index 7569218a3..747642223 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -369,6 +369,7 @@ void CMusicHandler::release() boost::mutex::scoped_lock guard(mutex); Mix_HookMusicFinished(nullptr); + current->stop(); current.reset(); next.reset(); From 785a9aadcd0d319f8dee9f2c2303b080c48dfb42 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 20 Jan 2023 15:16:28 +0200 Subject: [PATCH 165/197] Do not consider corpses for movement range display (#1476) --- client/battle/BattleFieldController.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 675b39fb0..2f4d3bb12 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -211,11 +211,11 @@ std::set BattleFieldController::getHighlightedHexesStackRange() for(BattleHex hex : set) result.insert(hex); - // display the movement shadow of the stack at b (i.e. stack under mouse) - const CStack * const shere = owner.curInt->cb->battleGetStackByPos(hoveredHex, false); - if(shere && shere != owner.stacksController->getActiveStack() && shere->alive()) + // display the movement shadow of stack under mouse + const CStack * const hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true); + if(hoveredStack && hoveredStack != owner.stacksController->getActiveStack()) { - std::vector v = owner.curInt->cb->battleGetAvailableHexes(shere, true, nullptr); + std::vector v = owner.curInt->cb->battleGetAvailableHexes(hoveredStack, true, nullptr); for(BattleHex hex : v) result.insert(hex); } From 8149cd6173cbe0c910014991b2017a98d52f1a64 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 23:38:10 +0200 Subject: [PATCH 166/197] Fix missing obstacles on subterranean terrain --- config/battlefields.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/battlefields.json b/config/battlefields.json index 78224cc9b..8296c9236 100644 --- a/config/battlefields.json +++ b/config/battlefields.json @@ -21,7 +21,7 @@ }, "snow_mountains": { "graphics" : "CMBKSNMT.BMP" }, "snow_trees": { "graphics" : "CMBKSNTR.BMP" }, - "subterranean": { "graphics" : "CMBKSUB.BMP", "isSpecial" : true }, + "subterranean": { "graphics" : "CMBKSUB.BMP" }, "swamp_trees": { "graphics" : "CMBKSWMP.BMP" }, "fiery_fields": { "graphics": "CMBKFF.BMP", From 1418ce46789da293a7510058f03692405236ed9f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 19:01:53 +0200 Subject: [PATCH 167/197] Refactoring & fixes of damage calculation --- lib/battle/CBattleInfoCallback.cpp | 305 +---------------- lib/battle/CBattleInfoCallback.h | 8 +- lib/battle/DamageCalculator.cpp | 521 +++++++++++++++++++++++++++++ lib/battle/DamageCalculator.h | 72 ++++ 4 files changed, 612 insertions(+), 294 deletions(-) create mode 100644 lib/battle/DamageCalculator.cpp create mode 100644 lib/battle/DamageCalculator.h diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index b01624bcd..ee92c1356 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -14,6 +14,7 @@ #include "../CStack.h" #include "BattleInfo.h" +#include "DamageCalculator.h" #include "../NetPacks.h" #include "../spells/CSpellHandler.h" #include "../mapObjects/CGTownInstance.h" @@ -25,33 +26,6 @@ VCMI_LIB_NAMESPACE_BEGIN namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO { -static void retrieveTurretDamageRange(const CGTownInstance * town, const battle::Unit * turret, double & outMinDmg, double & outMaxDmg)//does not match OH3 yet, but damage is somewhat close -{ - // http://heroes.thelazy.net/wiki/Arrow_tower - assert(turret->creatureIndex() == CreatureID::ARROW_TOWERS); - assert(town); - assert(turret->getPosition() >= -4 && turret->getPosition() <= -2); - - // base damage, irregardless of town level - static const int baseDamageKeep = 10; - static const int baseDamageTower = 6; - - // extra damage, for each building in town - static const int extraDamage = 2; - - const int townLevel = town->getTownLevel(); - - int minDamage; - - if (turret->getPosition() == BattleHex::CASTLE_CENTRAL_TOWER) - minDamage = baseDamageKeep + townLevel * extraDamage; - else - minDamage = baseDamageTower + townLevel / 2 * extraDamage; - - outMinDmg = minDamage; - outMaxDmg = minDamage * 2; -} - static BattleHex lineToWallHex(int line) //returns hex with wall in given line (y coordinate) { static const BattleHex lineToHex[] = {12, 29, 45, 62, 78, 96, 112, 130, 147, 165, 182}; @@ -789,277 +763,24 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHe TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info) const { - auto battleBonusValue = [&](const IBonusBearer * bearer, CSelector selector) -> int - { - auto noLimit = Selector::effectRange()(Bonus::NO_LIMIT); - auto limitMatches = info.shooting - ? Selector::effectRange()(Bonus::ONLY_DISTANCE_FIGHT) - : Selector::effectRange()(Bonus::ONLY_MELEE_FIGHT); + DamageCalculator calculator(*this, info); - //any regular bonuses or just ones for melee/ranged - return bearer->getBonuses(selector, noLimit.Or(limitMatches))->totalValue(); - }; - - const IBonusBearer * attackerBonuses = info.attacker; - const IBonusBearer * defenderBonuses = info.defender; - - double additiveBonus = 1.0 + info.additiveBonus; - double multBonus = 1.0 * info.multBonus; - double minDmg = 0.0; - double maxDmg = 0.0; - - minDmg = info.attacker->getMinDamage(info.shooting); - maxDmg = info.attacker->getMaxDamage(info.shooting); - - minDmg *= info.attacker->getCount(), - maxDmg *= info.attacker->getCount(); - - if(info.attacker->creatureIndex() == CreatureID::ARROW_TOWERS) - { - SiegeStuffThatShouldBeMovedToHandlers::retrieveTurretDamageRange(battleGetDefendedTown(), info.attacker, minDmg, maxDmg); - TDmgRange unmodifiableTowerDamage = std::make_pair(int64_t(minDmg), int64_t(maxDmg)); - return unmodifiableTowerDamage; - } - - const std::string cachingStrSiedgeWeapon = "type_SIEGE_WEAPON"; - static const auto selectorSiedgeWeapon = Selector::type()(Bonus::SIEGE_WEAPON); - - if(attackerBonuses->hasBonus(selectorSiedgeWeapon, cachingStrSiedgeWeapon) && info.attacker->creatureIndex() != CreatureID::ARROW_TOWERS) //any siege weapon, but only ballista can attack (second condition - not arrow turret) - { //minDmg and maxDmg are multiplied by hero attack + 1 - auto retrieveHeroPrimSkill = [&](int skill) -> int - { - std::shared_ptr b = attackerBonuses->getBonus(Selector::sourceTypeSel(Bonus::HERO_BASE_SKILL).And(Selector::typeSubtype(Bonus::PRIMARY_SKILL, skill))); - return b ? b->val : 0; //if there is no hero or no info on his primary skill, return 0 - }; - - - minDmg *= retrieveHeroPrimSkill(PrimarySkill::ATTACK) + 1; - maxDmg *= retrieveHeroPrimSkill(PrimarySkill::ATTACK) + 1; - } - - double attackDefenceDifference = 0.0; - - double multAttackReduction = 1.0 - battleBonusValue(attackerBonuses, Selector::type()(Bonus::GENERAL_ATTACK_REDUCTION)) / 100.0; - attackDefenceDifference += info.attacker->getAttack(info.shooting) * multAttackReduction; - - double multDefenceReduction = 1.0 - battleBonusValue(attackerBonuses, Selector::type()(Bonus::ENEMY_DEFENCE_REDUCTION)) / 100.0; - attackDefenceDifference -= info.defender->getDefense(info.shooting) * multDefenceReduction; - - const std::string cachingStrSlayer = "type_SLAYER"; - static const auto selectorSlayer = Selector::type()(Bonus::SLAYER); - - //slayer handling //TODO: apply only ONLY_MELEE_FIGHT / DISTANCE_FIGHT? - auto slayerEffects = attackerBonuses->getBonuses(selectorSlayer, cachingStrSlayer); - - if(std::shared_ptr slayerEffect = slayerEffects->getFirst(Selector::all)) - { - std::vector affectedIds; - const auto spLevel = slayerEffect->val; - const CCreature * defenderType = info.defender->unitType(); - bool isAffected = false; - - for(const auto & b : defenderType->getBonusList()) - { - if((b->type == Bonus::KING3 && spLevel >= 3) || //expert - (b->type == Bonus::KING2 && spLevel >= 2) || //adv + - (b->type == Bonus::KING1 && spLevel >= 0)) //none or basic + - { - isAffected = true; - break; - } - } - - if(isAffected) - { - attackDefenceDifference += SpellID(SpellID::SLAYER).toSpell()->getLevelPower(spLevel); - if(info.attacker->hasBonusOfType(Bonus::SPECIAL_PECULIAR_ENCHANT, SpellID::SLAYER)) - { - ui8 attackerTier = info.attacker->unitType()->level; - ui8 specialtyBonus = std::max(5 - attackerTier, 0); - attackDefenceDifference += specialtyBonus; - } - } - } - - //bonus from attack/defense skills - if(attackDefenceDifference < 0) //decreasing dmg - { - const double defenseMultiplier = VLC->modh->settings.DEFENSE_POINT_DMG_MULTIPLIER; - const double defenseMultiplierCap = VLC->modh->settings.DEFENSE_POINTS_DMG_MULTIPLIER_CAP; - - const double dec = std::min(defenseMultiplier * (-attackDefenceDifference), defenseMultiplierCap); - multBonus *= 1.0 - dec; - } - else //increasing dmg - { - const double attackMultiplier = VLC->modh->settings.ATTACK_POINT_DMG_MULTIPLIER; - const double attackMultiplierCap = VLC->modh->settings.ATTACK_POINTS_DMG_MULTIPLIER_CAP; - - const double inc = std::min(attackMultiplier * attackDefenceDifference, attackMultiplierCap); - additiveBonus += inc; - } - - const std::string cachingStrJousting = "type_JOUSTING"; - static const auto selectorJousting = Selector::type()(Bonus::JOUSTING); - - const std::string cachingStrChargeImmunity = "type_CHARGE_IMMUNITY"; - static const auto selectorChargeImmunity = Selector::type()(Bonus::CHARGE_IMMUNITY); - - //applying jousting bonus - if(info.chargedFields > 0 && attackerBonuses->hasBonus(selectorJousting, cachingStrJousting) && !defenderBonuses->hasBonus(selectorChargeImmunity, cachingStrChargeImmunity)) - additiveBonus += info.chargedFields * 0.05; - - //handling secondary abilities and artifacts giving premies to them - const std::string cachingStrArchery = "type_SECONDARY_SKILL_PREMYs_ARCHERY"; - static const auto selectorArchery = Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ARCHERY); - - const std::string cachingStrOffence = "type_SECONDARY_SKILL_PREMYs_OFFENCE"; - static const auto selectorOffence = Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::OFFENCE); - - const std::string cachingStrArmorer = "type_SECONDARY_SKILL_PREMYs_ARMORER"; - static const auto selectorArmorer = Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ARMORER); - - if(info.shooting) - additiveBonus += attackerBonuses->valOfBonuses(selectorArchery, cachingStrArchery) / 100.0; - else - additiveBonus += attackerBonuses->valOfBonuses(selectorOffence, cachingStrOffence) / 100.0; - - multBonus *= (std::max(0, 100 - defenderBonuses->valOfBonuses(selectorArmorer, cachingStrArmorer))) / 100.0; - - //handling hate effect - //assume that unit have only few HATE features and cache them all - const std::string cachingStrHate = "type_HATE"; - static const auto selectorHate = Selector::type()(Bonus::HATE); - - auto allHateEffects = attackerBonuses->getBonuses(selectorHate, cachingStrHate); - - additiveBonus += allHateEffects->valOfBonuses(Selector::subtype()(info.defender->creatureIndex())) / 100.0; - - const std::string cachingStrMeleeReduction = "type_GENERAL_DAMAGE_REDUCTIONs_0"; - static const auto selectorMeleeReduction = Selector::typeSubtype(Bonus::GENERAL_DAMAGE_REDUCTION, 0); - - const std::string cachingStrRangedReduction = "type_GENERAL_DAMAGE_REDUCTIONs_1"; - static const auto selectorRangedReduction = Selector::typeSubtype(Bonus::GENERAL_DAMAGE_REDUCTION, 1); - - //handling spell effects - if(!info.shooting) //eg. shield - { - multBonus *= (100 - defenderBonuses->valOfBonuses(selectorMeleeReduction, cachingStrMeleeReduction)) / 100.0; - } - else //eg. air shield - { - multBonus *= (100 - defenderBonuses->valOfBonuses(selectorRangedReduction, cachingStrRangedReduction)) / 100.0; - } - - if(info.shooting) - { - //todo: set actual percentage in spell bonus configuration instead of just level; requires non trivial backward compatibility handling - - //get list first, total value of 0 also counts - TConstBonusListPtr forgetfulList = attackerBonuses->getBonuses(Selector::type()(Bonus::FORGETFULL),"type_FORGETFULL"); - - if(!forgetfulList->empty()) - { - int forgetful = forgetfulList->valOfBonuses(Selector::all); - - //none of basic level - if(forgetful == 0 || forgetful == 1) - multBonus *= 0.5; - else - logGlobal->warn("Attempt to calculate shooting damage with adv+ FORGETFULL effect"); - } - } - - const std::string cachingStrForcedMinDamage = "type_ALWAYS_MINIMUM_DAMAGE"; - static const auto selectorForcedMinDamage = Selector::type()(Bonus::ALWAYS_MINIMUM_DAMAGE); - - const std::string cachingStrForcedMaxDamage = "type_ALWAYS_MAXIMUM_DAMAGE"; - static const auto selectorForcedMaxDamage = Selector::type()(Bonus::ALWAYS_MAXIMUM_DAMAGE); - - TConstBonusListPtr curseEffects = attackerBonuses->getBonuses(selectorForcedMinDamage, cachingStrForcedMinDamage); - TConstBonusListPtr blessEffects = attackerBonuses->getBonuses(selectorForcedMaxDamage, cachingStrForcedMaxDamage); - - int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue(); - double curseMultiplicativePenalty = curseEffects->size() ? (*std::max_element(curseEffects->begin(), curseEffects->end(), &Bonus::compareByAdditionalInfo>))->additionalInfo[0] : 0; - - if(curseMultiplicativePenalty) //curse handling (partial, the rest is below) - { - multBonus *= 1.0 - curseMultiplicativePenalty/100; - } - - const std::string cachingStrAdvAirShield = "isAdvancedAirShield"; - auto isAdvancedAirShield = [](const Bonus* bonus) - { - return bonus->source == Bonus::SPELL_EFFECT - && bonus->sid == SpellID::AIR_SHIELD - && bonus->val >= SecSkillLevel::ADVANCED; - }; - - if(info.shooting) - { - //wall / distance penalty + advanced air shield - BattleHex attackerPos = info.attackerPos.isValid() ? info.attackerPos : info.attacker->getPosition(); - BattleHex defenderPos = info.defenderPos.isValid() ? info.defenderPos : info.defender->getPosition(); - - const bool distPenalty = battleHasDistancePenalty(attackerBonuses, attackerPos, defenderPos); - const bool obstaclePenalty = battleHasWallPenalty(attackerBonuses, attackerPos, defenderPos); - - if(distPenalty || defenderBonuses->hasBonus(isAdvancedAirShield, cachingStrAdvAirShield)) - multBonus *= 0.5; - - if(obstaclePenalty) - multBonus *= 0.5; //cumulative - } - else - { - const std::string cachingStrNoMeleePenalty = "type_NO_MELEE_PENALTY"; - static const auto selectorNoMeleePenalty = Selector::type()(Bonus::NO_MELEE_PENALTY); - - if(info.attacker->isShooter() && !attackerBonuses->hasBonus(selectorNoMeleePenalty, cachingStrNoMeleePenalty)) - multBonus *= 0.5; - } - - // psychic elementals versus mind immune units 50% - if(info.attacker->creatureIndex() == CreatureID::PSYCHIC_ELEMENTAL) - { - const std::string cachingStrMindImmunity = "type_MIND_IMMUNITY"; - static const auto selectorMindImmunity = Selector::type()(Bonus::MIND_IMMUNITY); - - if(defenderBonuses->hasBonus(selectorMindImmunity, cachingStrMindImmunity)) - multBonus *= 0.5; - } - - // TODO attack on petrified unit 50% - // blinded unit retaliates - - minDmg *= additiveBonus * multBonus; - maxDmg *= additiveBonus * multBonus; - - if(curseEffects->size()) //curse handling (rest) - { - minDmg += curseBlessAdditiveModifier; - maxDmg = minDmg; - } - else if(blessEffects->size()) //bless handling - { - maxDmg += curseBlessAdditiveModifier; - minDmg = maxDmg; - } - - TDmgRange returnedVal = std::make_pair(int64_t(minDmg), int64_t(maxDmg)); - - //damage cannot be less than 1 - vstd::amax(returnedVal.first, 1); - vstd::amax(returnedVal.second, 1); - - return returnedVal; + return calculator.calculateDmgRange(); } -TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg) const +TDmgRange CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, TDmgRange * retaliationDmg) const +{ + RETURN_IF_NOT_BATTLE(std::make_pair(0, 0)); + auto reachability = battleGetDistances(attacker, attacker->getPosition()); + int movementDistance = reachability[attackerPosition]; + return battleEstimateDamage(attacker, defender, movementDistance, retaliationDmg); +} + +TDmgRange CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, TDmgRange * retaliationDmg) const { RETURN_IF_NOT_BATTLE(std::make_pair(0, 0)); const bool shooting = battleCanShoot(attacker, defender->getPosition()); - const BattleAttackInfo bai(attacker, defender, shooting); + const BattleAttackInfo bai(attacker, defender, movementDistance, shooting); return battleEstimateDamage(bai, retaliationDmg); } diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index 9dccfcc5c..2ef124aeb 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -104,8 +104,12 @@ public: TDmgRange calculateDmgRange(const BattleAttackInfo & info) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair - TDmgRange battleEstimateDamage(const BattleAttackInfo & bai, TDmgRange * retaliationDmg = nullptr) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair - TDmgRange battleEstimateDamage(const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg = nullptr) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair + /// estimates damage dealt by attacker to defender; + /// only non-random bonuses are considered in estimation + /// returns pair + TDmgRange battleEstimateDamage(const BattleAttackInfo & bai, TDmgRange * retaliationDmg = nullptr) const; + TDmgRange battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, TDmgRange * retaliationDmg = nullptr) const; + TDmgRange battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, TDmgRange * retaliationDmg = nullptr) const; bool battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; bool battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; diff --git a/lib/battle/DamageCalculator.cpp b/lib/battle/DamageCalculator.cpp new file mode 100644 index 000000000..985c8942b --- /dev/null +++ b/lib/battle/DamageCalculator.cpp @@ -0,0 +1,521 @@ +/* + * DamageCalculator.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "DamageCalculator.h" +#include "CBattleInfoCallback.h" +#include "Unit.h" + +#include "../HeroBonus.h" +#include "../mapObjects/CGTownInstance.h" +#include "../spells/CSpellHandler.h" +#include "../CModHandler.h" + +namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO +{ + +static void retrieveTurretDamageRange(const CGTownInstance * town, const battle::Unit * turret, double & outMinDmg, double & outMaxDmg) +{ + // http://heroes.thelazy.net/wiki/Arrow_tower + assert(turret->creatureIndex() == CreatureID::ARROW_TOWERS); + assert(town); + assert(turret->getPosition() >= -4 && turret->getPosition() <= -2); + + // base damage, irregardless of town level + static const int baseDamageKeep = 10; + static const int baseDamageTower = 6; + + // extra damage, for each building in town + static const int extraDamage = 2; + + const int townLevel = town->getTownLevel(); + + int minDamage; + + if (turret->getPosition() == BattleHex::CASTLE_CENTRAL_TOWER) + minDamage = baseDamageKeep + townLevel * extraDamage; + else + minDamage = baseDamageTower + townLevel / 2 * extraDamage; + + outMinDmg = minDamage; + outMaxDmg = minDamage * 2; +} + +} + +TDmgRange DamageCalculator::getBaseDamageSingle() const +{ + double minDmg = 0.0; + double maxDmg = 0.0; + + minDmg = info.attacker->getMinDamage(info.shooting); + maxDmg = info.attacker->getMaxDamage(info.shooting); + + if(info.attacker->creatureIndex() == CreatureID::ARROW_TOWERS) + SiegeStuffThatShouldBeMovedToHandlers::retrieveTurretDamageRange(callback.battleGetDefendedTown(), info.attacker, minDmg, maxDmg); + + const std::string cachingStrSiedgeWeapon = "type_SIEGE_WEAPON"; + static const auto selectorSiedgeWeapon = Selector::type()(Bonus::SIEGE_WEAPON); + + if(info.attacker->hasBonus(selectorSiedgeWeapon, cachingStrSiedgeWeapon) && info.attacker->creatureIndex() != CreatureID::ARROW_TOWERS) //any siege weapon, but only ballista can attack (second condition - not arrow turret) + { //minDmg and maxDmg are multiplied by hero attack + 1 + auto retrieveHeroPrimSkill = [&](int skill) -> int + { + std::shared_ptr b = info.attacker->getBonus(Selector::sourceTypeSel(Bonus::HERO_BASE_SKILL).And(Selector::typeSubtype(Bonus::PRIMARY_SKILL, skill))); + return b ? b->val : 0; //if there is no hero or no info on his primary skill, return 0 + }; + + minDmg *= retrieveHeroPrimSkill(PrimarySkill::ATTACK) + 1; + maxDmg *= retrieveHeroPrimSkill(PrimarySkill::ATTACK) + 1; + } + return { minDmg, maxDmg }; +} + +TDmgRange DamageCalculator::getBaseDamageBlessCurse() const +{ + const std::string cachingStrForcedMinDamage = "type_ALWAYS_MINIMUM_DAMAGE"; + static const auto selectorForcedMinDamage = Selector::type()(Bonus::ALWAYS_MINIMUM_DAMAGE); + + const std::string cachingStrForcedMaxDamage = "type_ALWAYS_MAXIMUM_DAMAGE"; + static const auto selectorForcedMaxDamage = Selector::type()(Bonus::ALWAYS_MAXIMUM_DAMAGE); + + TConstBonusListPtr curseEffects = info.attacker->getBonuses(selectorForcedMinDamage, cachingStrForcedMinDamage); + TConstBonusListPtr blessEffects = info.attacker->getBonuses(selectorForcedMaxDamage, cachingStrForcedMaxDamage); + + int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue(); + + TDmgRange baseDamage = getBaseDamageSingle(); + TDmgRange modifiedDamage = { + std::max( int64_t(1), baseDamage.first + curseBlessAdditiveModifier), + std::max( int64_t(1), baseDamage.second + curseBlessAdditiveModifier) + }; + + if ( curseEffects->size() && blessEffects->size() ) + { + logGlobal->warn("Stack has both curse and bless! Effects will negate each other!"); + return modifiedDamage; + } + + if(curseEffects->size()) + { + return { + modifiedDamage.first, + modifiedDamage.first + }; + } + + if(blessEffects->size()) + { + return { + modifiedDamage.second, + modifiedDamage.second + }; + } + + return modifiedDamage; +} + +TDmgRange DamageCalculator::getBaseDamageStack() const +{ + auto stackSize = info.attacker->getCount(); + auto baseDamage = getBaseDamageBlessCurse(); + return { + baseDamage.first * stackSize, + baseDamage.second * stackSize + }; +} + +int DamageCalculator::getActorAttackBase() const +{ + return info.attacker->getAttack(info.shooting); +} + +int DamageCalculator::getActorAttackEffective() const +{ + return getActorAttackBase() + getActorAttackSlayer(); +} + +int DamageCalculator::getActorAttackSlayer() const +{ + const std::string cachingStrSlayer = "type_SLAYER"; + static const auto selectorSlayer = Selector::type()(Bonus::SLAYER); + + auto slayerEffects = info.attacker->getBonuses(selectorSlayer, cachingStrSlayer); + + if(std::shared_ptr slayerEffect = slayerEffects->getFirst(Selector::all)) + { + std::vector affectedIds; + const auto spLevel = slayerEffect->val; + const CCreature * defenderType = info.defender->unitType(); + bool isAffected = false; + + for(const auto & b : defenderType->getBonusList()) + { + if((b->type == Bonus::KING3 && spLevel >= 3) || //expert + (b->type == Bonus::KING2 && spLevel >= 2) || //adv + + (b->type == Bonus::KING1 && spLevel >= 0)) //none or basic + + { + isAffected = true; + break; + } + } + + if(isAffected) + { + int attackBonus = SpellID(SpellID::SLAYER).toSpell()->getLevelPower(spLevel); + if(info.attacker->hasBonusOfType(Bonus::SPECIAL_PECULIAR_ENCHANT, SpellID::SLAYER)) + { + ui8 attackerTier = info.attacker->unitType()->level; + ui8 specialtyBonus = std::max(5 - attackerTier, 0); + attackBonus += specialtyBonus; + } + return attackBonus; + } + } + return 0; +} + +int DamageCalculator::getTargetDefenseBase() const +{ + return info.defender->getDefense(info.shooting); +} + +int DamageCalculator::getTargetDefenseEffective() const +{ + return getTargetDefenseBase() + getTargetDefenseIgnored(); +} + +int DamageCalculator::getTargetDefenseIgnored() const +{ + double multDefenceReduction = battleBonusValue(info.attacker, Selector::type()(Bonus::ENEMY_DEFENCE_REDUCTION)) / 100.0; + + if(multDefenceReduction > 0) + { + int reduction = std::floor(multDefenceReduction * getTargetDefenseBase()) + 1; + return -std::min(reduction,getTargetDefenseBase()); + } + + return 0; +} + +double DamageCalculator::getAttackSkillFactor() const +{ + int attackAdvantage = getActorAttackEffective() - getTargetDefenseEffective(); + + if (attackAdvantage > 0) + { + const double attackMultiplier = VLC->modh->settings.ATTACK_POINT_DMG_MULTIPLIER; + const double attackMultiplierCap = VLC->modh->settings.ATTACK_POINTS_DMG_MULTIPLIER_CAP; + const double attackFactor = std::min(attackMultiplier * attackAdvantage, attackMultiplierCap); + + return attackFactor; + } + return 0.f; +} + +double DamageCalculator::getAttackBlessFactor() const +{ + const std::string cachingStrDamage = "type_GENERAL_DAMAGE_PREMY"; + static const auto selectorDamage = Selector::type()(Bonus::GENERAL_DAMAGE_PREMY); + return info.attacker->valOfBonuses(selectorDamage, cachingStrDamage) / 100.0; +} + +double DamageCalculator::getAttackOffenseArcheryFactor() const +{ + if(info.shooting) + { + const std::string cachingStrArchery = "type_SECONDARY_SKILL_PREMYs_ARCHERY"; + static const auto selectorArchery = Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ARCHERY); + return info.attacker->valOfBonuses(selectorArchery, cachingStrArchery) / 100.0; + } + else + { + const std::string cachingStrOffence = "type_SECONDARY_SKILL_PREMYs_OFFENCE"; + static const auto selectorOffence = Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::OFFENCE); + return info.attacker->valOfBonuses(selectorOffence, cachingStrOffence) / 100.0; + } +} + +double DamageCalculator::getAttackLuckFactor() const +{ + if (info.luckyStrike) + return 1.0; + return 0.0; +} + +double DamageCalculator::getAttackDeathBlowFactor() const +{ + if (info.deathBlow) + return 1.0; + return 0.0; +} + +double DamageCalculator::getAttackDoubleDamageFactor() const +{ + if (info.doubleDamage) + return 1.0; + return 0.0; +} + +double DamageCalculator::getAttackJoustingFactor() const +{ + const std::string cachingStrJousting = "type_JOUSTING"; + static const auto selectorJousting = Selector::type()(Bonus::JOUSTING); + + const std::string cachingStrChargeImmunity = "type_CHARGE_IMMUNITY"; + static const auto selectorChargeImmunity = Selector::type()(Bonus::CHARGE_IMMUNITY); + + //applying jousting bonus + if(info.chargeDistance > 0 && info.attacker->hasBonus(selectorJousting, cachingStrJousting) && !info.defender->hasBonus(selectorChargeImmunity, cachingStrChargeImmunity)) + return info.chargeDistance * 0.05; + return 0.0; +} + +double DamageCalculator::getAttackHateFactor() const +{ + //assume that unit have only few HATE features and cache them all + const std::string cachingStrHate = "type_HATE"; + static const auto selectorHate = Selector::type()(Bonus::HATE); + + auto allHateEffects = info.attacker->getBonuses(selectorHate, cachingStrHate); + + return allHateEffects->valOfBonuses(Selector::subtype()(info.defender->creatureIndex())) / 100.0; +} + +double DamageCalculator::getDefenseSkillFactor() const +{ + int defenseAdvantage = getTargetDefenseEffective() - getActorAttackEffective(); + + //bonus from attack/defense skills + if(defenseAdvantage > 0) //decreasing dmg + { + const double defenseMultiplier = VLC->modh->settings.DEFENSE_POINT_DMG_MULTIPLIER; + const double defenseMultiplierCap = VLC->modh->settings.DEFENSE_POINTS_DMG_MULTIPLIER_CAP; + + const double dec = std::min(defenseMultiplier * defenseAdvantage, defenseMultiplierCap); + return dec; + } + return 0.0; +} + +double DamageCalculator::getDefenseArmorerFactor() const +{ + const std::string cachingStrArmorer = "type_SECONDARY_SKILL_PREMYs_ARMORER"; + static const auto selectorArmorer = Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ARMORER); + return info.defender->valOfBonuses(selectorArmorer, cachingStrArmorer) / 100.0; + +} + +double DamageCalculator::getDefenseMagicShieldFactor() const +{ + const std::string cachingStrMeleeReduction = "type_GENERAL_DAMAGE_REDUCTIONs_0"; + static const auto selectorMeleeReduction = Selector::typeSubtype(Bonus::GENERAL_DAMAGE_REDUCTION, 0); + + const std::string cachingStrRangedReduction = "type_GENERAL_DAMAGE_REDUCTIONs_1"; + static const auto selectorRangedReduction = Selector::typeSubtype(Bonus::GENERAL_DAMAGE_REDUCTION, 1); + + //handling spell effects - shield and air shield + if(info.shooting) + return info.defender->valOfBonuses(selectorRangedReduction, cachingStrRangedReduction) / 100.0; + else + return info.defender->valOfBonuses(selectorMeleeReduction, cachingStrMeleeReduction) / 100.0; +} + +double DamageCalculator::getDefenseRangePenaltiesFactor() const +{ + if(info.shooting) + { + BattleHex attackerPos = info.attackerPos.isValid() ? info.attackerPos : info.attacker->getPosition(); + BattleHex defenderPos = info.defenderPos.isValid() ? info.defenderPos : info.defender->getPosition(); + + const std::string cachingStrAdvAirShield = "isAdvancedAirShield"; + auto isAdvancedAirShield = [](const Bonus* bonus) + { + return bonus->source == Bonus::SPELL_EFFECT + && bonus->sid == SpellID::AIR_SHIELD + && bonus->val >= SecSkillLevel::ADVANCED; + }; + + const bool distPenalty = callback.battleHasDistancePenalty(info.attacker, attackerPos, defenderPos); + + if(distPenalty || info.defender->hasBonus(isAdvancedAirShield, cachingStrAdvAirShield)) + return 0.5; + + } + else + { + const std::string cachingStrNoMeleePenalty = "type_NO_MELEE_PENALTY"; + static const auto selectorNoMeleePenalty = Selector::type()(Bonus::NO_MELEE_PENALTY); + + if(info.attacker->isShooter() && !info.attacker->hasBonus(selectorNoMeleePenalty, cachingStrNoMeleePenalty)) + return 0.5; + } + return 0.0; +} + +double DamageCalculator::getDefenseObstacleFactor() const +{ + if(info.shooting) + { + BattleHex attackerPos = info.attackerPos.isValid() ? info.attackerPos : info.attacker->getPosition(); + BattleHex defenderPos = info.defenderPos.isValid() ? info.defenderPos : info.defender->getPosition(); + + const bool obstaclePenalty = callback.battleHasWallPenalty(info.attacker, attackerPos, defenderPos); + if(obstaclePenalty) + return 0.5; + } + return 0.0; +} + +double DamageCalculator::getDefenseUnluckyFactor() const +{ + if (info.unluckyStrike) + return 0.5; + return 0.0; +} + +double DamageCalculator::getDefenseBlindParalysisFactor() const +{ + + double multAttackReduction = battleBonusValue(info.attacker, Selector::type()(Bonus::GENERAL_ATTACK_REDUCTION)) / 100.0; + + return multAttackReduction; +} + +double DamageCalculator::getDefenseForgetfulnessFactor() const +{ + if(info.shooting) + { + //todo: set actual percentage in spell bonus configuration instead of just level; requires non trivial backward compatibility handling + //get list first, total value of 0 also counts + TConstBonusListPtr forgetfulList = info.attacker->getBonuses(Selector::type()(Bonus::FORGETFULL),"type_FORGETFULL"); + + if(!forgetfulList->empty()) + { + int forgetful = forgetfulList->valOfBonuses(Selector::all); + + //none of basic level + if(forgetful == 0 || forgetful == 1) + return 0.5; + else + logGlobal->warn("Attempt to calculate shooting damage with adv+ FORGETFULL effect"); + } + } + return 0.0; +} + +double DamageCalculator::getDefensePetrificationFactor() const +{ + // Creatures that are petrified by a Basilisk's Petrifying attack or a Medusa's Stone gaze take 50% damage (R8 = 0.50) from ranged and melee attacks. Taking damage also deactivates the effect. + const std::string cachingStrAllReduction = "type_GENERAL_DAMAGE_REDUCTIONs_N1"; + static const auto selectorAllReduction = Selector::typeSubtype(Bonus::GENERAL_DAMAGE_REDUCTION, -1); + + return info.defender->valOfBonuses(selectorAllReduction, cachingStrAllReduction) / 100.0; +} + +double DamageCalculator::getDefenseMagicFactor() const +{ + // Magic Elementals deal half damage (R8 = 0.50) against Magic Elementals and Black Dragons. This is not affected by the Orb of Vulnerability, Anti-Magic, or Magic Resistance. + if(info.attacker->creatureIndex() == CreatureID::MAGIC_ELEMENTAL) + { + const std::string cachingStrMagicImmunity = "type_LEVEL_SPELL_IMMUNITY"; + static const auto selectorMagicImmunity = Selector::type()(Bonus::LEVEL_SPELL_IMMUNITY); + + if(info.defender->valOfBonuses(selectorMagicImmunity, cachingStrMagicImmunity) >= 5) + return 0.5; + } + return 0.0; +} + +double DamageCalculator::getDefenseMindFactor() const +{ + // Psychic Elementals deal half damage (R8 = 0.50) against creatures that are immune to Mind spells, such as Giants and Undead. This is not affected by the Orb of Vulnerability. + if(info.attacker->creatureIndex() == CreatureID::PSYCHIC_ELEMENTAL) + { + const std::string cachingStrMindImmunity = "type_MIND_IMMUNITY"; + static const auto selectorMindImmunity = Selector::type()(Bonus::MIND_IMMUNITY); + + if(info.defender->hasBonus(selectorMindImmunity, cachingStrMindImmunity)) + return 0.5; + } + return 0.0; +} + +std::vector DamageCalculator::getAttackFactors() const +{ + return { + getAttackSkillFactor(), + getAttackOffenseArcheryFactor(), + getAttackBlessFactor(), + getAttackLuckFactor(), + getAttackJoustingFactor(), + getAttackDeathBlowFactor(), + getAttackDoubleDamageFactor(), + getAttackHateFactor() + }; +} + +std::vector DamageCalculator::getDefenseFactors() const +{ + return { + getDefenseSkillFactor(), + getDefenseArmorerFactor(), + getDefenseMagicShieldFactor(), + getDefenseRangePenaltiesFactor(), + getDefenseObstacleFactor(), + getDefenseBlindParalysisFactor(), + getDefenseUnluckyFactor(), + getDefenseForgetfulnessFactor(), + getDefensePetrificationFactor(), + getDefenseMagicFactor(), + getDefenseMindFactor() + }; +} + +int DamageCalculator::battleBonusValue(const IBonusBearer * bearer, const CSelector & selector) const +{ + auto noLimit = Selector::effectRange()(Bonus::NO_LIMIT); + auto limitMatches = info.shooting + ? Selector::effectRange()(Bonus::ONLY_DISTANCE_FIGHT) + : Selector::effectRange()(Bonus::ONLY_MELEE_FIGHT); + + //any regular bonuses or just ones for melee/ranged + return bearer->getBonuses(selector, noLimit.Or(limitMatches))->totalValue(); +}; + +TDmgRange DamageCalculator::calculateDmgRange() const +{ + TDmgRange result = getBaseDamageStack(); + + auto attackFactors = getAttackFactors(); + auto defenseFactors = getDefenseFactors(); + + double attackFactorTotal = 1.0; + double defenseFactorTotal = 1.0; + + for (auto & factor : attackFactors) + { + assert(factor >= 0.0); + attackFactorTotal += factor; + } + + for (auto & factor : defenseFactors) + { + assert(factor >= 0.0); + defenseFactorTotal *= ( 1 - std::min(1.0, factor)); + } + + double resultingFactor = std::min(8.0, attackFactorTotal) * std::max( 0.01, defenseFactorTotal); + + return { + std::max( 1.0, std::floor(result.first * resultingFactor)), + std::max( 1.0, std::floor(result.second * resultingFactor)) + }; +} diff --git a/lib/battle/DamageCalculator.h b/lib/battle/DamageCalculator.h new file mode 100644 index 000000000..ad8111dc2 --- /dev/null +++ b/lib/battle/DamageCalculator.h @@ -0,0 +1,72 @@ +/* + * DamageCalculator.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "../GameConstants.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class CBattleInfoCallback; +class IBonusBearer; +class CSelector; +struct BattleAttackInfo; + +class DLL_LINKAGE DamageCalculator +{ + const CBattleInfoCallback & callback; + const BattleAttackInfo & info; + + int battleBonusValue(const IBonusBearer * bearer, const CSelector & selector) const; + + TDmgRange getBaseDamageSingle() const; + TDmgRange getBaseDamageBlessCurse() const; + TDmgRange getBaseDamageStack() const; + + int getActorAttackBase() const; + int getActorAttackEffective() const; + int getActorAttackSlayer() const; + int getTargetDefenseBase() const; + int getTargetDefenseEffective() const; + int getTargetDefenseIgnored() const; + + double getAttackSkillFactor() const; + double getAttackOffenseArcheryFactor() const; + double getAttackBlessFactor() const; + double getAttackLuckFactor() const; + double getAttackJoustingFactor() const; + double getAttackDeathBlowFactor() const; + double getAttackDoubleDamageFactor() const; + double getAttackHateFactor() const; + + double getDefenseSkillFactor() const; + double getDefenseArmorerFactor() const; + double getDefenseMagicShieldFactor() const; + double getDefenseRangePenaltiesFactor() const; + double getDefenseObstacleFactor() const; + double getDefenseBlindParalysisFactor() const; + double getDefenseUnluckyFactor() const; + double getDefenseForgetfulnessFactor() const; + double getDefensePetrificationFactor() const; + double getDefenseMagicFactor() const; + double getDefenseMindFactor() const; + + std::vector getAttackFactors() const; + std::vector getDefenseFactors() const; +public: + DamageCalculator(const CBattleInfoCallback & callback, const BattleAttackInfo & info ): + callback(callback), + info(info) + {} + + TDmgRange calculateDmgRange() const; +}; + +VCMI_LIB_NAMESPACE_END From b86969909ae4ff071ea98c0574d0513e22daec50 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 19:03:18 +0200 Subject: [PATCH 168/197] Fixes compilation due to changes in callback interface --- AI/BattleAI/AttackPossibility.cpp | 8 ++++--- AI/BattleAI/BattleAI.cpp | 2 +- AI/BattleAI/BattleExchangeVariant.cpp | 6 ++++-- AI/BattleAI/PotentialTargets.cpp | 6 ++---- AI/StupidAI/StupidAI.cpp | 3 ++- cmake_modules/VCMI_lib.cmake | 2 ++ lib/GameConstants.h | 1 + lib/battle/BattleAttackInfo.cpp | 30 +++++++++------------------ lib/battle/BattleAttackInfo.h | 13 ++++++------ server/CGameHandler.cpp | 19 +++++------------ 10 files changed, 39 insertions(+), 51 deletions(-) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index 93405b392..d998706ab 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -49,7 +49,8 @@ int64_t AttackPossibility::calculateDamageReduce( vstd::amin(damageDealt, defender->getAvailableHealth()); - auto enemyDamageBeforeAttack = cb.battleEstimateDamage(BattleAttackInfo(defender, attacker, defender->canShoot())); + // FIXME: provide distance info for Jousting bonus + auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attacker, 0); auto enemiesKilled = damageDealt / defender->MaxHealth() + (damageDealt % defender->MaxHealth() >= defender->getFirstHPleft() ? 1 : 0); auto enemyDamage = averageDmg(enemyDamageBeforeAttack); auto damagePerEnemy = enemyDamage / (double)defender->getCount(); @@ -74,10 +75,11 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & a if(!state.battleCanShoot(st)) continue; - BattleAttackInfo rangeAttackInfo(st, attacker, true); + // FIXME: provide distance info for Jousting bonus + BattleAttackInfo rangeAttackInfo(st, attacker, 0, true); rangeAttackInfo.defenderPos = hex; - BattleAttackInfo meleeAttackInfo(st, attacker, false); + BattleAttackInfo meleeAttackInfo(st, attacker, 0, false); meleeAttackInfo.defenderPos = hex; auto rangeDmg = state.battleEstimateDamage(rangeAttackInfo); diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index a0ccfb6f9..346452b25 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -210,7 +210,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) bestAttack.attackerState->unitType()->identifier, bestAttack.affectedUnits[0]->unitType()->identifier, (int)bestAttack.affectedUnits[0]->getCount(), action, (int)bestAttack.from, (int)bestAttack.attack.attacker->getPosition().hex, - bestAttack.attack.chargedFields, bestAttack.attack.attacker->Speed(0, true), + bestAttack.attack.chargeDistance, bestAttack.attack.attacker->Speed(0, true), bestAttack.defenderDamageReduce, bestAttack.attackerDamageReduce, bestAttack.attackValue() ); } diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 7d21569c9..c426e2186 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -69,7 +69,8 @@ int64_t BattleExchangeVariant::trackAttack( const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation); TDmgRange retaliation; - BattleAttackInfo bai(attacker.get(), defender.get(), shooting); + // FIXME: provide distance info for Jousting bonus + BattleAttackInfo bai(attacker.get(), defender.get(), 0, shooting); if(shooting) { @@ -234,7 +235,8 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Uni for(auto hex : hexes) { - auto bai = BattleAttackInfo(activeStack, closestStack, cb->battleCanShoot(activeStack)); + // FIXME: provide distance info for Jousting bonus + auto bai = BattleAttackInfo(activeStack, closestStack, 0, cb->battleCanShoot(activeStack)); auto attack = AttackPossibility::evaluate(bai, hex, hb); attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure diff --git a/AI/BattleAI/PotentialTargets.cpp b/AI/BattleAI/PotentialTargets.cpp index 8ce7b23c6..8fc3911cf 100644 --- a/AI/BattleAI/PotentialTargets.cpp +++ b/AI/BattleAI/PotentialTargets.cpp @@ -46,10 +46,8 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility { - auto bai = BattleAttackInfo(attackerInfo, defender, shooting); - - if(hex.isValid() && !shooting) - bai.chargedFields = reachability.distances[hex]; + int distance = hex.isValid() ? reachability.distances[hex] : 0; + auto bai = BattleAttackInfo(attackerInfo, defender, distance, shooting); return AttackPossibility::evaluate(bai, hex, state); }; diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index 4baaa0742..446fbb29c 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -55,7 +55,8 @@ public: {} void calcDmg(const CStack * ourStack) { - TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, &retal); + // FIXME: provide distance info for Jousting bonus + TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, 0, &retal); adi = static_cast((dmg.first + dmg.second) / 2); adr = static_cast((retal.first + retal.second) / 2); } diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 4399502d0..869080564 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -19,6 +19,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/battle/CObstacleInstance.cpp ${MAIN_LIB_DIR}/battle/CPlayerBattleCallback.cpp ${MAIN_LIB_DIR}/battle/CUnitState.cpp + ${MAIN_LIB_DIR}/battle/DamageCalculator.cpp ${MAIN_LIB_DIR}/battle/Destination.cpp ${MAIN_LIB_DIR}/battle/IBattleState.cpp ${MAIN_LIB_DIR}/battle/ReachabilityInfo.cpp @@ -253,6 +254,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/battle/CObstacleInstance.h ${MAIN_LIB_DIR}/battle/CPlayerBattleCallback.h ${MAIN_LIB_DIR}/battle/CUnitState.h + ${MAIN_LIB_DIR}/battle/DamageCalculator.h ${MAIN_LIB_DIR}/battle/Destination.h ${MAIN_LIB_DIR}/battle/IBattleInfoCallback.h ${MAIN_LIB_DIR}/battle/IBattleState.h diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 24e0a09d4..c6c8aa27e 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -1126,6 +1126,7 @@ public: GOLD_GOLEM = 116, DIAMOND_GOLEM = 117, PSYCHIC_ELEMENTAL = 120, + MAGIC_ELEMENTAL = 121, CATAPULT = 145, BALLISTA = 146, FIRST_AID_TENT = 147, diff --git a/lib/battle/BattleAttackInfo.cpp b/lib/battle/BattleAttackInfo.cpp index 1d38d699b..2746dc7cd 100644 --- a/lib/battle/BattleAttackInfo.cpp +++ b/lib/battle/BattleAttackInfo.cpp @@ -13,31 +13,21 @@ VCMI_LIB_NAMESPACE_BEGIN -BattleAttackInfo::BattleAttackInfo(const battle::Unit * Attacker, const battle::Unit * Defender, bool Shooting) +BattleAttackInfo::BattleAttackInfo(const battle::Unit * Attacker, const battle::Unit * Defender, int chargeDistance, bool Shooting) : attacker(Attacker), - defender(Defender) -{ - shooting = Shooting; - chargedFields = 0; - additiveBonus = 0.0; - multBonus = 1.0; - attackerPos = BattleHex::INVALID; - defenderPos = BattleHex::INVALID; -} + defender(Defender), + shooting(Shooting), + attackerPos(BattleHex::INVALID), + defenderPos(BattleHex::INVALID), + chargeDistance(chargeDistance) +{} BattleAttackInfo BattleAttackInfo::reverse() const { - BattleAttackInfo ret = *this; - - std::swap(ret.attacker, ret.defender); - std::swap(ret.defenderPos, ret.attackerPos); - - ret.shooting = false; - ret.chargedFields = 0; - - ret.additiveBonus = 0.0; - ret.multBonus = 1.0; + BattleAttackInfo ret(defender, attacker, 0, false); + ret.defenderPos = attackerPos; + ret.attackerPos = defenderPos; return ret; } diff --git a/lib/battle/BattleAttackInfo.h b/lib/battle/BattleAttackInfo.h index b24376a30..d221581fb 100644 --- a/lib/battle/BattleAttackInfo.h +++ b/lib/battle/BattleAttackInfo.h @@ -27,13 +27,14 @@ struct DLL_LINKAGE BattleAttackInfo BattleHex attackerPos; BattleHex defenderPos; - bool shooting; - int chargedFields; + int chargeDistance = 0; + bool shooting = false; + bool luckyStrike = false; + bool unluckyStrike = false; + bool deathBlow = false; + bool doubleDamage = false; - double additiveBonus; - double multBonus; - - BattleAttackInfo(const battle::Unit * Attacker, const battle::Unit * Defender, bool Shooting = false); + BattleAttackInfo(const battle::Unit * Attacker, const battle::Unit * Defender, int chargeDistance, bool Shooting); BattleAttackInfo reverse() const; }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index b2b82d002..589154c41 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1224,21 +1224,12 @@ int64_t CGameHandler::applyBattleEffects(BattleAttack & bat, std::shared_ptrunitId(); bsa.stackAttacked = def->unitId(); { - BattleAttackInfo bai(attackerState.get(), def, bat.shot()); - bai.chargedFields = distance; + BattleAttackInfo bai(attackerState.get(), def, distance, bat.shot()); - if(bat.deathBlow()) - bai.additiveBonus += 1.0; - - if(bat.ballistaDoubleDmg()) - bai.additiveBonus += 1.0; - - if(bat.lucky()) - bai.additiveBonus += 1.0; - - //unlucky hit, used only if negative luck is enabled - if(bat.unlucky()) - bai.additiveBonus -= 0.5; // FIXME: how bad (and luck in general) should work with following bonuses? + bai.deathBlow = bat.deathBlow(); + bai.doubleDamage = bat.ballistaDoubleDmg(); + bai.luckyStrike = bat.lucky(); + bai.unluckyStrike = bat.unlucky(); auto range = gs->curB->calculateDmgRange(bai); bsa.damageAmount = gs->curB->getActualDamage(range, attackerState->getCount(), getRandomGenerator()); From fb3c3a4636ec1af1f39eaf520afa13781e22300e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 19:03:38 +0200 Subject: [PATCH 169/197] Provide distance information fo Jousting ability --- client/battle/BattleActionsController.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index 65881f829..3c1285b90 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -460,23 +460,26 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType) } }; - TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere); + BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber); + TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere, attackFromHex); std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage) } break; case PossiblePlayerBattleAction::SHOOT: { - if (owner.curInt->cb->battleHasShootingPenalty(owner.stacksController->getActiveStack(), myNumber)) + auto const * shooter = owner.stacksController->getActiveStack(); + + if (owner.curInt->cb->battleHasShootingPenalty(shooter, myNumber)) cursorFrame = Cursor::Combat::SHOOT_PENALTY; else cursorFrame = Cursor::Combat::SHOOT; realizeAction = [=](){owner.giveCommand(EActionType::SHOOT, myNumber);}; - TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere); + TDmgRange damage = owner.curInt->cb->battleEstimateDamage(shooter, shere, shooter->getPosition()); std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg //printing - Shoot %s (%d shots left, %s damage) - newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % owner.stacksController->getActiveStack()->shots.available() % estDmgText).str(); + newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % shooter->shots.available() % estDmgText).str(); } break; case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: From 4a09dc07571f57261e23692c3def68bc4845f857 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 19:03:56 +0200 Subject: [PATCH 170/197] Fixed Adela's Bless specialty --- lib/HeroBonus.h | 2 +- lib/spells/effects/Timed.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 6422b20e5..954b0a03f 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -236,7 +236,7 @@ public: BONUS_NAME(MANA_CHANNELING) /*value in %, eg. familiar*/ \ BONUS_NAME(SPELL_LIKE_ATTACK) /*subtype - spell, value - spell level; range is taken from spell, but damage from creature; eg. magog*/ \ BONUS_NAME(THREE_HEADED_ATTACK) /*eg. cerberus*/ \ - BONUS_NAME(UNUSED_BONUS_ENTRY) \ + BONUS_NAME(GENERAL_DAMAGE_PREMY) \ BONUS_NAME(FIRE_IMMUNITY) /*subtype 0 - all, 1 - all except positive, 2 - only damage spells*/ \ BONUS_NAME(WATER_IMMUNITY) \ BONUS_NAME(EARTH_IMMUNITY) \ diff --git a/lib/spells/effects/Timed.cpp b/lib/spells/effects/Timed.cpp index c9d6322f3..41cafcd29 100644 --- a/lib/spells/effects/Timed.cpp +++ b/lib/spells/effects/Timed.cpp @@ -141,7 +141,7 @@ void Timed::apply(ServerCallback * server, const Mechanics * m, const EffectTarg if(casterHero && casterHero->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, m->getSpellIndex())) //TODO: better handling of bonus percentages { int damagePercent = casterHero->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, m->getSpellIndex()) / tier; - Bonus specialBonus(Bonus::N_TURNS, Bonus::CREATURE_DAMAGE, Bonus::SPELL_EFFECT, damagePercent, m->getSpellIndex(), 0, Bonus::PERCENT_TO_ALL); + Bonus specialBonus(Bonus::N_TURNS, Bonus::GENERAL_DAMAGE_PREMY, Bonus::SPELL_EFFECT, damagePercent, m->getSpellIndex()); specialBonus.turnsRemain = duration; buffer.push_back(specialBonus); } From 4015d3d8bdb4118d1c9ae282de37bf0d6d6f1541 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 19:05:20 +0200 Subject: [PATCH 171/197] Fixed creature specialties --- lib/HeroBonus.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index a69628092..72a4254b3 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -1971,13 +1971,13 @@ JsonNode ILimiter::toJsonNode() const int CCreatureTypeLimiter::limit(const BonusLimitationContext &context) const { - const CCreature *c = retrieveCreature(&context.node); - if(!c) - return true; - return c != creature && (!includeUpgrades || !creature->isMyUpgrade(c)); - //drop bonus if it's not our creature and (we don`t check upgrades or its not our upgrade) -} - + const CCreature *c = retrieveCreature(&context.node); + if(!c) + return true; + return c->getId() != creature->getId() && (!includeUpgrades || !creature->isMyUpgrade(c)); + //drop bonus if it's not our creature and (we don`t check upgrades or its not our upgrade) +} + CCreatureTypeLimiter::CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades) : creature(&creature_), includeUpgrades(IncludeUpgrades) { From 33b5bfbbd217c7a5518781b132e57fd0173619f5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 19:05:36 +0200 Subject: [PATCH 172/197] Fixed secondary skill specialties --- lib/HeroBonus.cpp | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 72a4254b3..0bcdfe794 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -977,12 +977,21 @@ void CBonusSystemNode::getAllBonusesRec(BonusList &out) const for(const auto & b : beforeUpdate) { - auto updated = b->updater - ? getUpdatedBonus(b, b->updater) + auto updated = b->updater + ? getUpdatedBonus(b, b->updater) : b; - //do not add bonus with same pointer - if(!vstd::contains(out, updated)) + //do not add bonus with updater + bool bonusExists = false; + for (auto const & bonus : out ) + { + if (bonus == updated) + bonusExists = true; + if (bonus->updater && bonus->updater == updated->updater) + bonusExists = true; + } + + if (!bonusExists) out.push_back(updated); } } @@ -1971,13 +1980,13 @@ JsonNode ILimiter::toJsonNode() const int CCreatureTypeLimiter::limit(const BonusLimitationContext &context) const { - const CCreature *c = retrieveCreature(&context.node); - if(!c) - return true; - return c->getId() != creature->getId() && (!includeUpgrades || !creature->isMyUpgrade(c)); - //drop bonus if it's not our creature and (we don`t check upgrades or its not our upgrade) -} - + const CCreature *c = retrieveCreature(&context.node); + if(!c) + return true; + return c->getId() != creature->getId() && (!includeUpgrades || !creature->isMyUpgrade(c)); + //drop bonus if it's not our creature and (we don`t check upgrades or its not our upgrade) +} + CCreatureTypeLimiter::CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades) : creature(&creature_), includeUpgrades(IncludeUpgrades) { From 1f38f27741dbee95c7b5dbf5c654d67cc2880c40 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 19:05:58 +0200 Subject: [PATCH 173/197] Fixed Bless and Curse damage calculation --- config/spells/timed.json | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/config/spells/timed.json b/config/spells/timed.json index 66ea44fdd..a30b58cf5 100644 --- a/config/spells/timed.json +++ b/config/spells/timed.json @@ -373,8 +373,20 @@ } } }, + "advanced":{ + "effects" : { + "alwaysMaximumDamage" : { + "val" : 1 + } + } + }, "expert":{ - "range" : "X" + "range" : "X", + "effects" : { + "alwaysMaximumDamage" : { + "val" : 1 + } + } } }, "counters" : { @@ -415,8 +427,20 @@ } } }, + "advanced":{ + "effects" : { + "alwaysMinimumDamage" : { + "val" : 1 + } + } + }, "expert":{ - "range" : "X" + "range" : "X", + "effects" : { + "alwaysMinimumDamage" : { + "val" : 1 + } + } } }, "counters" : { From 03ca4b54c084c89b1e8f63dd88359ff84e089b54 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 19:06:28 +0200 Subject: [PATCH 174/197] Fixed damage to opposite elementals --- config/creatures/conflux.json | 97 +++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/config/creatures/conflux.json b/config/creatures/conflux.json index 2f8532c3c..d86a959c9 100755 --- a/config/creatures/conflux.json +++ b/config/creatures/conflux.json @@ -37,7 +37,20 @@ "type" : "MORE_DAMAGE_FROM_SPELL", "subtype" : "spell.armageddon", "val" : 100 + }, + "oppositeEarth" : + { + "type" : "HATE", + "subtype" : "creature.earthElemental", + "val" : 100 + }, + "oppositeMagma" : + { + "type" : "HATE", + "subtype" : "creature.magmaElemental", + "val" : 100 } + }, "upgrades": ["stormElemental"], "graphics" : @@ -92,6 +105,18 @@ { "type" : "SPELL_IMMUNITY", "subtype" : "spell.armageddon" + }, + "oppositeAir" : + { + "type" : "HATE", + "subtype" : "creature.airElemental", + "val" : 100 + }, + "oppositeStorm" : + { + "type" : "HATE", + "subtype" : "creature.stormElemental", + "val" : 100 } }, "upgrades": ["magmaElemental"], @@ -143,6 +168,18 @@ "type" : "MORE_DAMAGE_FROM_SPELL", "subtype" : "spell.iceBolt", "val" : 100 + }, + "oppositeWater" : + { + "type" : "HATE", + "subtype" : "creature.waterElemental", + "val" : 100 + }, + "oppositeIce" : + { + "type" : "HATE", + "subtype" : "creature.iceElemental", + "val" : 100 } }, "upgrades": ["energyElemental"], @@ -213,6 +250,18 @@ { "type" : "WATER_IMMUNITY", "subtype" : 2 //immune to damage spells only + }, + "oppositeFire" : + { + "type" : "HATE", + "subtype" : "creature.fireElemental", + "val" : 100 + }, + "oppositeEnergy" : + { + "type" : "HATE", + "subtype" : "creature.energyElemental", + "val" : 100 } }, "doubleWide" : true, @@ -395,6 +444,18 @@ { "type" : "WATER_IMMUNITY", "subtype" : 2 //immune to damage spells only + }, + "oppositeFire" : + { + "type" : "HATE", + "subtype" : "creature.fireElemental", + "val" : 100 + }, + "oppositeEnergy" : + { + "type" : "HATE", + "subtype" : "creature.energyElemental", + "val" : 100 } }, "doubleWide" : true, @@ -467,6 +528,18 @@ { "type" : "SPELL_IMMUNITY", "subtype" : "spell.armageddon" + }, + "oppositeAir" : + { + "type" : "HATE", + "subtype" : "creature.airElemental", + "val" : 100 + }, + "oppositeStorm" : + { + "type" : "HATE", + "subtype" : "creature.stormElemental", + "val" : 100 } }, "graphics" : @@ -535,6 +608,18 @@ "type" : "MORE_DAMAGE_FROM_SPELL", "subtype" : "spell.armageddon", "val" : 100 + }, + "oppositeEarth" : + { + "type" : "HATE", + "subtype" : "creature.earthElemental", + "val" : 100 + }, + "oppositeMagma" : + { + "type" : "HATE", + "subtype" : "creature.magmaElemental", + "val" : 100 } }, "graphics" : @@ -606,6 +691,18 @@ "type" : "SPELLCASTER", "subtype" : "spell.protectFire", "val" : 2 + }, + "oppositeWater" : + { + "type" : "HATE", + "subtype" : "creature.waterElemental", + "val" : 100 + }, + "oppositeIce" : + { + "type" : "HATE", + "subtype" : "creature.iceElemental", + "val" : 100 } }, "graphics" : From f33d7d4f9072b8a8b1a56b18b3049fb831fea278 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 19:06:47 +0200 Subject: [PATCH 175/197] Fixed petrify & paralyze damage calculation --- config/spells/ability.json | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/config/spells/ability.json b/config/spells/ability.json index 45d19490c..5a1af9d90 100644 --- a/config/spells/ability.json +++ b/config/spells/ability.json @@ -32,11 +32,9 @@ "N_TURNS" ] }, - "defenceBoost" : { - "type" : "PRIMARY_SKILL", - "subtype" : "primSkill.defence", + "generalDamageReduction" : { + "type" : "GENERAL_DAMAGE_REDUCTION", "val" : 50, - "valueType" : "PERCENT_TO_ALL", "duration" : [ "UNTIL_BEING_ATTACKED", "N_TURNS" @@ -189,9 +187,9 @@ "N_TURNS" ] }, - "noRetaliation" : { - "val" : 0, - "type" : "NO_RETALIATION", + "generalAttackReduction" : { + "val" : 75, + "type" : "GENERAL_ATTACK_REDUCTION", "duration" : [ "UNTIL_BEING_ATTACKED", "N_TURNS" From 789f4d6bc88821fbb633371a6642a5ad767f7296 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 19:22:32 +0200 Subject: [PATCH 176/197] Formatting --- lib/battle/DamageCalculator.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/battle/DamageCalculator.cpp b/lib/battle/DamageCalculator.cpp index 985c8942b..e593c4377 100644 --- a/lib/battle/DamageCalculator.cpp +++ b/lib/battle/DamageCalculator.cpp @@ -39,7 +39,7 @@ static void retrieveTurretDamageRange(const CGTownInstance * town, const battle: int minDamage; - if (turret->getPosition() == BattleHex::CASTLE_CENTRAL_TOWER) + if(turret->getPosition() == BattleHex::CASTLE_CENTRAL_TOWER) minDamage = baseDamageKeep + townLevel * extraDamage; else minDamage = baseDamageTower + townLevel / 2 * extraDamage; @@ -97,7 +97,7 @@ TDmgRange DamageCalculator::getBaseDamageBlessCurse() const std::max( int64_t(1), baseDamage.second + curseBlessAdditiveModifier) }; - if ( curseEffects->size() && blessEffects->size() ) + if(curseEffects->size() && blessEffects->size() ) { logGlobal->warn("Stack has both curse and bless! Effects will negate each other!"); return modifiedDamage; @@ -201,7 +201,6 @@ int DamageCalculator::getTargetDefenseIgnored() const int reduction = std::floor(multDefenceReduction * getTargetDefenseBase()) + 1; return -std::min(reduction,getTargetDefenseBase()); } - return 0; } @@ -209,7 +208,7 @@ double DamageCalculator::getAttackSkillFactor() const { int attackAdvantage = getActorAttackEffective() - getTargetDefenseEffective(); - if (attackAdvantage > 0) + if(attackAdvantage > 0) { const double attackMultiplier = VLC->modh->settings.ATTACK_POINT_DMG_MULTIPLIER; const double attackMultiplierCap = VLC->modh->settings.ATTACK_POINTS_DMG_MULTIPLIER_CAP; @@ -245,21 +244,21 @@ double DamageCalculator::getAttackOffenseArcheryFactor() const double DamageCalculator::getAttackLuckFactor() const { - if (info.luckyStrike) + if(info.luckyStrike) return 1.0; return 0.0; } double DamageCalculator::getAttackDeathBlowFactor() const { - if (info.deathBlow) + if(info.deathBlow) return 1.0; return 0.0; } double DamageCalculator::getAttackDoubleDamageFactor() const { - if (info.doubleDamage) + if(info.doubleDamage) return 1.0; return 0.0; } @@ -376,16 +375,14 @@ double DamageCalculator::getDefenseObstacleFactor() const double DamageCalculator::getDefenseUnluckyFactor() const { - if (info.unluckyStrike) + if(info.unluckyStrike) return 0.5; return 0.0; } double DamageCalculator::getDefenseBlindParalysisFactor() const { - double multAttackReduction = battleBonusValue(info.attacker, Selector::type()(Bonus::GENERAL_ATTACK_REDUCTION)) / 100.0; - return multAttackReduction; } From b7c27e7d476774f452d42d44b30c3c3f2f988827 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 19:40:34 +0200 Subject: [PATCH 177/197] ios compilation fix --- lib/battle/DamageCalculator.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/battle/DamageCalculator.cpp b/lib/battle/DamageCalculator.cpp index e593c4377..d2b50046b 100644 --- a/lib/battle/DamageCalculator.cpp +++ b/lib/battle/DamageCalculator.cpp @@ -18,6 +18,8 @@ #include "../spells/CSpellHandler.h" #include "../CModHandler.h" +VCMI_LIB_NAMESPACE_BEGIN + namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO { @@ -516,3 +518,5 @@ TDmgRange DamageCalculator::calculateDmgRange() const std::max( 1.0, std::floor(result.second * resultingFactor)) }; } + +VCMI_LIB_NAMESPACE_END From 30d7bdc884c221a24804155070cfb68c30bcf2f3 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 16 Jan 2023 18:28:05 +0200 Subject: [PATCH 178/197] Moved tower damage computation to town instance --- lib/battle/DamageCalculator.cpp | 55 +++++++++++-------------------- lib/mapObjects/CGTownInstance.cpp | 36 ++++++++++++++++++++ lib/mapObjects/CGTownInstance.h | 6 ++++ 3 files changed, 61 insertions(+), 36 deletions(-) diff --git a/lib/battle/DamageCalculator.cpp b/lib/battle/DamageCalculator.cpp index d2b50046b..e176e4bde 100644 --- a/lib/battle/DamageCalculator.cpp +++ b/lib/battle/DamageCalculator.cpp @@ -20,38 +20,6 @@ VCMI_LIB_NAMESPACE_BEGIN -namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO -{ - -static void retrieveTurretDamageRange(const CGTownInstance * town, const battle::Unit * turret, double & outMinDmg, double & outMaxDmg) -{ - // http://heroes.thelazy.net/wiki/Arrow_tower - assert(turret->creatureIndex() == CreatureID::ARROW_TOWERS); - assert(town); - assert(turret->getPosition() >= -4 && turret->getPosition() <= -2); - - // base damage, irregardless of town level - static const int baseDamageKeep = 10; - static const int baseDamageTower = 6; - - // extra damage, for each building in town - static const int extraDamage = 2; - - const int townLevel = town->getTownLevel(); - - int minDamage; - - if(turret->getPosition() == BattleHex::CASTLE_CENTRAL_TOWER) - minDamage = baseDamageKeep + townLevel * extraDamage; - else - minDamage = baseDamageTower + townLevel / 2 * extraDamage; - - outMinDmg = minDamage; - outMaxDmg = minDamage * 2; -} - -} - TDmgRange DamageCalculator::getBaseDamageSingle() const { double minDmg = 0.0; @@ -61,19 +29,34 @@ TDmgRange DamageCalculator::getBaseDamageSingle() const maxDmg = info.attacker->getMaxDamage(info.shooting); if(info.attacker->creatureIndex() == CreatureID::ARROW_TOWERS) - SiegeStuffThatShouldBeMovedToHandlers::retrieveTurretDamageRange(callback.battleGetDefendedTown(), info.attacker, minDmg, maxDmg); + { + auto town = callback.battleGetDefendedTown(); + assert(town); + + switch(info.attacker->getPosition()) + { + case BattleHex::CASTLE_CENTRAL_TOWER: + return town->getKeepDamageRange(); + case BattleHex::CASTLE_BOTTOM_TOWER: + case BattleHex::CASTLE_UPPER_TOWER: + return town->getTowerDamageRange(); + default: + assert(0); + } + } const std::string cachingStrSiedgeWeapon = "type_SIEGE_WEAPON"; static const auto selectorSiedgeWeapon = Selector::type()(Bonus::SIEGE_WEAPON); - if(info.attacker->hasBonus(selectorSiedgeWeapon, cachingStrSiedgeWeapon) && info.attacker->creatureIndex() != CreatureID::ARROW_TOWERS) //any siege weapon, but only ballista can attack (second condition - not arrow turret) - { //minDmg and maxDmg are multiplied by hero attack + 1 + if(info.attacker->hasBonus(selectorSiedgeWeapon, cachingStrSiedgeWeapon) && info.attacker->creatureIndex() != CreatureID::ARROW_TOWERS) + { auto retrieveHeroPrimSkill = [&](int skill) -> int { std::shared_ptr b = info.attacker->getBonus(Selector::sourceTypeSel(Bonus::HERO_BASE_SKILL).And(Selector::typeSubtype(Bonus::PRIMARY_SKILL, skill))); - return b ? b->val : 0; //if there is no hero or no info on his primary skill, return 0 + return b ? b->val : 0; }; + //minDmg and maxDmg are multiplied by hero attack + 1 minDmg *= retrieveHeroPrimSkill(PrimarySkill::ATTACK) + 1; maxDmg *= retrieveHeroPrimSkill(PrimarySkill::ATTACK) + 1; } diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 65e8f85c6..6317e2425 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -799,6 +799,42 @@ void CGTownInstance::addTownBonuses() } } +TDmgRange CGTownInstance::getTowerDamageRange() const +{ + assert(hasBuilt(BuildingID::CASTLE)); + + // http://heroes.thelazy.net/wiki/Arrow_tower + // base damage, irregardless of town level + static const int baseDamage = 6; + // extra damage, for each building in town + static const int extraDamage = 1; + + const int minDamage = baseDamage + extraDamage * getTownLevel(); + + return { + minDamage, + minDamage * 2 + }; +} + +TDmgRange CGTownInstance::getKeepDamageRange() const +{ + assert(hasBuilt(BuildingID::CITADEL)); + + // http://heroes.thelazy.net/wiki/Arrow_tower + // base damage, irregardless of town level + static const int baseDamage = 10; + // extra damage, for each building in town + static const int extraDamage = 2; + + const int minDamage = baseDamage + extraDamage * getTownLevel(); + + return { + minDamage, + minDamage * 2 + }; +} + void CGTownInstance::deleteTownBonus(BuildingID::EBuildingID bid) { size_t i = 0; diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 032dfc1e2..bfd8b9052 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -330,6 +330,12 @@ public: void addHeroToStructureVisitors(const CGHeroInstance *h, si64 structureInstanceID) const; //hero must be visiting or garrisoned in town void deleteTownBonus(BuildingID::EBuildingID bid); + /// Returns damage range for secondary towers of this town + TDmgRange getTowerDamageRange() const; + + /// Returns damage range for central tower(keep) of this town + TDmgRange getKeepDamageRange() const; + const CTown * getTown() const ; CGTownInstance(); From 5da407e82273503a569c5737af9c3ef570f73f6d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 2 Jan 2023 00:06:42 +0200 Subject: [PATCH 179/197] All spell texts are now passed through translator --- AI/BattleAI/BattleAI.cpp | 4 +- AI/Nullkiller/Goals/AdventureSpellCast.cpp | 10 ++-- client/battle/BattleActionsController.cpp | 10 ++-- client/lobby/CBonusSelection.cpp | 4 +- client/widgets/CComponent.cpp | 5 +- client/windows/CCastleInterface.cpp | 6 +-- client/windows/CCreatureWindow.cpp | 2 +- client/windows/CSpellWindow.cpp | 10 ++-- include/vcmi/spells/Spell.h | 9 +++- lib/CArtHandler.cpp | 2 +- lib/CBonusTypeHandler.cpp | 2 +- lib/CGameState.cpp | 2 +- lib/HeroBonus.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 10 ++-- lib/mapObjects/MiscObjects.cpp | 2 +- lib/spells/CSpellHandler.cpp | 60 ++++++++++++++-------- lib/spells/CSpellHandler.h | 21 ++++---- mapeditor/inspector/inspector.cpp | 6 +-- 18 files changed, 97 insertions(+), 70 deletions(-) diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 346452b25..ebb154e8b 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -687,7 +687,7 @@ void CBattleAI::attemptCastingSpell() if(castToPerform.value > 0) { - LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->name % castToPerform.value); + LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->getNameTranslated() % castToPerform.value); BattleAction spellcast; spellcast.actionType = EActionType::HERO_SPELL; spellcast.actionSubtype = castToPerform.spell->id; @@ -698,7 +698,7 @@ void CBattleAI::attemptCastingSpell() } else { - LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->name % castToPerform.value); + LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->getNameTranslated() % castToPerform.value); } } diff --git a/AI/Nullkiller/Goals/AdventureSpellCast.cpp b/AI/Nullkiller/Goals/AdventureSpellCast.cpp index bffab6a13..8661f8cee 100644 --- a/AI/Nullkiller/Goals/AdventureSpellCast.cpp +++ b/AI/Nullkiller/Goals/AdventureSpellCast.cpp @@ -33,16 +33,16 @@ void AdventureSpellCast::accept(AIGateway * ai) auto spell = getSpell(); - logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->name, hero->name); + logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getNameTranslated(), hero->name); if(!spell->isAdventure()) - throw cannotFulfillGoalException(spell->name + " is not an adventure spell."); + throw cannotFulfillGoalException(spell->getNameTranslated() + " is not an adventure spell."); if(!hero->canCastThisSpell(spell)) - throw cannotFulfillGoalException("Hero can not cast " + spell->name); + throw cannotFulfillGoalException("Hero can not cast " + spell->getNameTranslated()); if(hero->mana < hero->getSpellCost(spell)) - throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->name); + throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated()); if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero) throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->name); @@ -70,7 +70,7 @@ void AdventureSpellCast::accept(AIGateway * ai) std::string AdventureSpellCast::toString() const { - return "AdventureSpellCast " + spellID.toSpell()->name; + return "AdventureSpellCast " + spellID.toSpell()->getNameTranslated(); } } diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index 3c1285b90..b37df10d3 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -484,7 +484,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType) break; case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time - newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % currentSpell->name % shere->getName()); //Cast %s on %s + newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % currentSpell->getNameTranslated() % shere->getName()); //Cast %s on %s switch (currentSpell->id) { case SpellID::SACRIFICE: @@ -497,7 +497,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType) break; case PossiblePlayerBattleAction::ANY_LOCATION: currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time - newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s + newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s isCastingPossible = true; break; case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell @@ -522,7 +522,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType) isCastingPossible = true; break; case PossiblePlayerBattleAction::FREE_LOCATION: - newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s + newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s isCastingPossible = true; break; case PossiblePlayerBattleAction::HEAL: @@ -561,7 +561,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType) break; case PossiblePlayerBattleAction::FREE_LOCATION: cursorFrame = Cursor::Combat::BLOCKED; - newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->name); //No room to place %s here + newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->getNameTranslated()); //No room to place %s here break; default: if (myNumber == -1) @@ -582,7 +582,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType) default: spellcastingCursor = true; if (newConsoleMsg.empty() && currentSpell) - newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s + newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s break; } diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index d9210285c..5ab2a1ae6 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -176,7 +176,7 @@ void CBonusSelection::createBonusesIcons() { case CScenarioTravel::STravelBonus::SPELL: desc = CGI->generaltexth->allTexts[715]; - boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getName()); + boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getNameTranslated()); break; case CScenarioTravel::STravelBonus::MONSTER: picNumber = bonDescs[i].info2 + 2; @@ -213,7 +213,7 @@ void CBonusSelection::createBonusesIcons() break; case CScenarioTravel::STravelBonus::SPELL_SCROLL: desc = CGI->generaltexth->allTexts[716]; - boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getName()); + boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getNameTranslated()); break; case CScenarioTravel::STravelBonus::PRIMARY_SKILL: { diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index dd89c96f2..7ed7ce5a5 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -25,6 +25,7 @@ #include "../../lib/CArtHandler.h" #include "../../lib/CTownHandler.h" +#include "../../lib/spells/CSpellHandler.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/CSkillHandler.h" #include "../../lib/CGeneralTextHandler.h" @@ -168,7 +169,7 @@ std::string CComponent::getDescription() return art->getEffectiveDescription(); } case experience: return CGI->generaltexth->allTexts[241]; - case spell: return SpellID(subtype).toSpell(CGI->spells())->getLevelDescription(val); + case spell: return (*CGI->spellh)[subtype]->getDescriptionTranslated(val); case morale: return CGI->generaltexth->heroscrn[ 4 - (val>0) + (val<0)]; case luck: return CGI->generaltexth->heroscrn[ 7 - (val>0) + (val<0)]; case building: return (*CGI->townh)[subtype]->town->buildings[BuildingID(val)]->Description(); @@ -212,7 +213,7 @@ std::string CComponent::getSubtitleInternal() return boost::lexical_cast(val); //amount of experience OR level required for seer hut; } } - case spell: return CGI->spells()->getByIndex(subtype)->getName(); + case spell: return CGI->spells()->getByIndex(subtype)->getNameTranslated(); case morale: return ""; case luck: return ""; case building: diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 5a3d64f91..3fd14ce7b 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1771,19 +1771,19 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell) void CMageGuildScreen::Scroll::clickLeft(tribool down, bool previousState) { if(down) - LOCPLINT->showInfoDialog(spell->getLevelDescription(0), std::make_shared(CComponent::spell, spell->id)); + LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared(CComponent::spell, spell->id)); } void CMageGuildScreen::Scroll::clickRight(tribool down, bool previousState) { if(down) - CRClickPopup::createAndPush(spell->getLevelDescription(0), std::make_shared(CComponent::spell, spell->id)); + CRClickPopup::createAndPush(spell->getDescriptionTranslated(0), std::make_shared(CComponent::spell, spell->id)); } void CMageGuildScreen::Scroll::hover(bool on) { if(on) - GH.statusbar->write(spell->name); + GH.statusbar->write(spell->getNameTranslated()); else GH.statusbar->clear(); diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index 0c769b54d..f51c13958 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -206,7 +206,7 @@ CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int if (hasGraphics) { spellText = CGI->generaltexth->allTexts[610]; //"%s, duration: %d rounds." - boost::replace_first(spellText, "%s", spell->getName()); + boost::replace_first(spellText, "%s", spell->getNameTranslated()); //FIXME: support permanent duration int duration = battleStack->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT,effect))->turnsRemain; boost::replace_first(spellText, "%d", boost::lexical_cast(duration)); diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index b7d6c7f9f..2bb056406 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -90,7 +90,7 @@ public: return false; } - return A->name < B->name; + return A->getNameTranslated() < B->getNameTranslated(); } } spellsorter; @@ -545,7 +545,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) if((combatSpell ^ inCombat) || inCastle) { std::vector> hlp(1, std::make_shared(CComponent::spell, mySpell->id, 0)); - owner->myInt->showInfoDialog(mySpell->getLevelDescription(schoolLevel), hlp); + owner->myInt->showInfoDialog(mySpell->getDescriptionTranslated(schoolLevel), hlp); } else if(combatSpell) { @@ -600,7 +600,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState) boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast(causedDmg)); } - CRClickPopup::createAndPush(mySpell->getLevelDescription(schoolLevel) + dmgInfo, std::make_shared(CComponent::spell, mySpell->id)); + CRClickPopup::createAndPush(mySpell->getDescriptionTranslated(schoolLevel) + dmgInfo, std::make_shared(CComponent::spell, mySpell->id)); } } @@ -609,7 +609,7 @@ void CSpellWindow::SpellArea::hover(bool on) if(mySpell) { if(on) - owner->statusBar->write(boost::to_string(boost::format("%s (%s)") % mySpell->name % CGI->generaltexth->allTexts[171+mySpell->level])); + owner->statusBar->write(boost::to_string(boost::format("%s (%s)") % mySpell->getNameTranslated() % CGI->generaltexth->allTexts[171+mySpell->level])); else owner->statusBar->clear(); } @@ -651,7 +651,7 @@ void CSpellWindow::SpellArea::setSpell(const CSpell * spell) } name->color = firstLineColor; - name->setText(mySpell->name); + name->setText(mySpell->getNameTranslated()); level->color = secondLineColor; if(schoolLevel > 0) diff --git a/include/vcmi/spells/Spell.h b/include/vcmi/spells/Spell.h index 302ae05f6..b260cf9a2 100644 --- a/include/vcmi/spells/Spell.h +++ b/include/vcmi/spells/Spell.h @@ -23,6 +23,8 @@ class Caster; class DLL_LINKAGE Spell: public EntityT { + using EntityT::getName; + public: using SchoolCallback = std::function; @@ -51,7 +53,12 @@ public: * Returns spell level power, base power ignored */ virtual int32_t getLevelPower(const int32_t skillLevel) const = 0; - virtual const std::string & getLevelDescription(const int32_t skillLevel) const = 0; + + virtual std::string getNameTextID() const = 0; + virtual std::string getNameTranslated() const = 0; + + virtual std::string getDescriptionTextID(int32_t level) const = 0; + virtual std::string getDescriptionTranslated(int32_t level) const = 0; }; diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 96632f488..4f321aad3 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -769,7 +769,7 @@ std::string CArtifactInstance::getEffectiveDescription(const CGHeroInstance * he if(spellID.getNum() >= 0) { if(nameStart != std::string::npos && nameEnd != std::string::npos) - text = text.replace(nameStart, nameEnd - nameStart + 1, spellID.toSpell(VLC->spells())->getName()); + text = text.replace(nameStart, nameEnd - nameStart + 1, spellID.toSpell(VLC->spells())->getNameTranslated()); } } else if(hero && artType->constituentOf.size()) //display info about set diff --git a/lib/CBonusTypeHandler.cpp b/lib/CBonusTypeHandler.cpp index 7edd984b4..2f4f6a8e4 100644 --- a/lib/CBonusTypeHandler.cpp +++ b/lib/CBonusTypeHandler.cpp @@ -150,7 +150,7 @@ std::string CBonusTypeHandler::bonusToString(const std::shared_ptr & bonu else if(name == "subtype.spell") { const SpellID sp(bonus->subtype); - return sp.toSpell()->name; + return sp.toSpell()->getNameTranslated(); } else if(name == "MR") { diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index e8a268954..f76de8821 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -123,7 +123,7 @@ void MetaString::getLocalString(const std::pair &txt, std::string &dst { auto spell = SpellID(ser).toSpell(VLC->spells()); if(spell) - dst = spell->getName(); + dst = spell->getNameTranslated(); else dst = "#!#"; } diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 0bcdfe794..760aafd66 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -1597,7 +1597,7 @@ std::string Bonus::Description() const str << ArtifactID(sid).toArtifact(VLC->artifacts())->getName(); break; case SPELL_EFFECT: - str << SpellID(sid).toSpell(VLC->spells())->getName(); + str << SpellID(sid).toSpell(VLC->spells())->getNameTranslated(); break; case CREATURE_ABILITY: str << VLC->creh->objects[sid]->namePl; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 9b8bcdf3d..4bbb931b6 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -737,7 +737,7 @@ bool CGHeroInstance::canCastThisSpell(const spells::Spell * spell) const { if(inSpellBook) {//hero has this spell in spellbook - logGlobal->error("Special spell %s in spellbook.", spell->getName()); + logGlobal->error("Special spell %s in spellbook.", spell->getNameTranslated()); } return specificBonus; } @@ -747,7 +747,7 @@ bool CGHeroInstance::canCastThisSpell(const spells::Spell * spell) const { //hero has this spell in spellbook //it is normal if set in map editor, but trace it to possible debug of magic guild - logGlobal->trace("Banned spell %s in spellbook.", spell->getName()); + logGlobal->trace("Banned spell %s in spellbook.", spell->getNameTranslated()); } return inSpellBook || specificBonus || schoolBonus || levelBonus; } @@ -770,19 +770,19 @@ bool CGHeroInstance::canLearnSpell(const spells::Spell * spell) const if(spell->isSpecial()) { - logGlobal->warn("Hero %s try to learn special spell %s", nodeName(), spell->getName()); + logGlobal->warn("Hero %s try to learn special spell %s", nodeName(), spell->getNameTranslated()); return false;//special spells can not be learned } if(spell->isCreatureAbility()) { - logGlobal->warn("Hero %s try to learn creature spell %s", nodeName(), spell->getName()); + logGlobal->warn("Hero %s try to learn creature spell %s", nodeName(), spell->getNameTranslated()); return false;//creature abilities can not be learned } if(!IObjectInterface::cb->isAllowed(0, spell->getIndex())) { - logGlobal->warn("Hero %s try to learn banned spell %s", nodeName(), spell->getName()); + logGlobal->warn("Hero %s try to learn banned spell %s", nodeName(), spell->getNameTranslated()); return false;//banned spells should not be learned } diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index aaa3f9a73..575eb5002 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1663,7 +1663,7 @@ std::string CGShrine::getHoverText(PlayerColor player) const if(wasVisited(player)) { hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s) - boost::algorithm::replace_first(hoverName,"%s", spell.toSpell()->name); + boost::algorithm::replace_first(hoverName,"%s", spell.toSpell()->getNameTextID()); } return hoverName; } diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 7e20dbb92..b1b1a9c0d 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -85,8 +85,7 @@ static const ESpellSchool SCHOOL_ORDER[4] = } //namespace SpellConfig ///CSpell::LevelInfo -CSpell::LevelInfo::LevelInfo() - : description(""), +CSpell::LevelInfo::LevelInfo(): cost(0), power(0), AIValue(0), @@ -204,7 +203,29 @@ SpellID CSpell::getId() const const std::string & CSpell::getName() const { - return name; + return identifier; +} + +std::string CSpell::getNameTextID() const +{ + TextIdentifier id("spell", modScope, identifier, "name"); + return id.get(); +} + +std::string CSpell::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + +std::string CSpell::getDescriptionTextID(int32_t level) const +{ + TextIdentifier id("spell", modScope, identifier, "description", SpellConfig::LEVEL_NAMES[level]); + return id.get(); +} + +std::string CSpell::getDescriptionTranslated(int32_t level) const +{ + return VLC->generaltexth->translate(getDescriptionTextID(level)); } const std::string & CSpell::getJsonKey() const @@ -340,11 +361,6 @@ int32_t CSpell::getLevelPower(const int32_t skillLevel) const return getLevelInfo(skillLevel).power; } -const std::string & CSpell::getLevelDescription(const int32_t skillLevel) const -{ - return getLevelInfo(skillLevel).description; -} - si32 CSpell::getProbability(const TFaction factionId) const { if(!vstd::contains(probabilities,factionId)) @@ -366,7 +382,7 @@ void CSpell::getEffects(std::vector & lst, const int level, const bool cu if(levelObject.effects.empty() && levelObject.cumulativeEffects.empty()) { - logGlobal->error("This spell (%s) has no effects for level %d", name, level); + logGlobal->error("This spell (%s) has no effects for level %d", getNameTranslated(), level); return; } @@ -702,6 +718,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & CSpell * spell = new CSpell(); spell->id = id; spell->identifier = identifier; + spell->modScope = scope; const auto type = json["type"].String(); @@ -716,9 +733,9 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & spell->combat = type == "combat"; } - spell->name = json["name"].String(); + VLC->generaltexth->registerString(spell->getNameTextID(), json["name"].String()); - logMod->trace("%s: loading spell %s", __FUNCTION__, spell->name); + logMod->trace("%s: loading spell %s", __FUNCTION__, spell->getNameTranslated()); const auto schoolNames = json["school"]; @@ -753,7 +770,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & else if(targetType == "LOCATION") spell->targetType = spells::AimType::LOCATION; else - logMod->warn("Spell %s: target type %s - assumed NO_TARGET.", spell->name, (targetType.empty() ? "empty" : "unknown ("+targetType+")")); + logMod->warn("Spell %s: target type %s - assumed NO_TARGET.", spell->getNameTranslated(), (targetType.empty() ? "empty" : "unknown ("+targetType+")")); for(const auto & counteredSpell: json["counters"].Struct()) { @@ -800,7 +817,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & else if(!implicitPositiveness) { spell->positiveness = CSpell::NEUTRAL; //duplicates constructor but, just in case - logMod->error("Spell %s: no positiveness specified, assumed NEUTRAL.", spell->name); + logMod->error("Spell %s: no positiveness specified, assumed NEUTRAL.", spell->getNameTranslated()); } spell->special = flags["special"].Bool(); @@ -810,7 +827,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & auto it = bonusNameMap.find(name); if(it == bonusNameMap.end()) { - logMod->error("Spell %s: invalid bonus name %s", spell->name, name); + logMod->error("Spell %s: invalid bonus name %s", spell->getNameTranslated(), name); } else { @@ -844,7 +861,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & if(!(immunities.empty() && absoluteImmunities.empty() && limiters.empty() && absoluteLimiters.empty())) { - logMod->warn("Spell %s has old target condition format. Expected configuration: ", spell->name); + logMod->warn("Spell %s has old target condition format. Expected configuration: ", spell->getNameTranslated()); spell->targetCondition = spell->convertTargetCondition(immunities, absoluteImmunities, limiters, absoluteLimiters); logMod->warn("\n\"targetCondition\" : %s", spell->targetCondition.toJson()); } @@ -855,13 +872,13 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & //TODO: could this be safely merged instead of discarding? if(!json["immunity"].isNull()) - logMod->warn("Spell %s 'immunity' field mixed with 'targetCondition' discarded", spell->name); + logMod->warn("Spell %s 'immunity' field mixed with 'targetCondition' discarded", spell->getNameTranslated()); if(!json["absoluteImmunity"].isNull()) - logMod->warn("Spell %s 'absoluteImmunity' field mixed with 'targetCondition' discarded", spell->name); + logMod->warn("Spell %s 'absoluteImmunity' field mixed with 'targetCondition' discarded", spell->getNameTranslated()); if(!json["limit"].isNull()) - logMod->warn("Spell %s 'limit' field mixed with 'targetCondition' discarded", spell->name); + logMod->warn("Spell %s 'limit' field mixed with 'targetCondition' discarded", spell->getNameTranslated()); if(!json["absoluteLimit"].isNull()) - logMod->warn("Spell %s 'absoluteLimit' field mixed with 'targetCondition' discarded", spell->name); + logMod->warn("Spell %s 'absoluteLimit' field mixed with 'targetCondition' discarded", spell->getNameTranslated()); } const JsonNode & graphicsNode = json["graphics"]; @@ -930,7 +947,8 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & const si32 levelPower = levelObject.power = static_cast(levelNode["power"].Integer()); - levelObject.description = levelNode["description"].String(); + VLC->generaltexth->registerString(spell->getDescriptionTranslated(levelIndex), levelNode["description"].String()); + levelObject.cost = static_cast(levelNode["cost"].Integer()); levelObject.AIValue = static_cast(levelNode["aiValue"].Integer()); levelObject.smartTarget = levelNode["targetModifier"]["smart"].Bool(); @@ -973,7 +991,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & levelObject.battleEffects = levelNode["battleEffects"]; if(!levelObject.cumulativeEffects.empty() || !levelObject.effects.empty() || spell->isOffensive()) - logGlobal->error("Mixing %s special effects with old format effects gives unpredictable result", spell->name); + logGlobal->error("Mixing %s special effects with old format effects gives unpredictable result", spell->getNameTranslated()); } } return spell; diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index a9a3c0a68..718779962 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -126,7 +126,6 @@ public: public: struct LevelInfo { - std::string description; //descriptions of spell for skill level si32 cost; si32 power; si32 AIValue; @@ -147,7 +146,6 @@ public: template void serialize(Handler & h, const int version) { - h & description; h & cost; h & power; h & AIValue; @@ -168,6 +166,11 @@ public: * */ const CSpell::LevelInfo & getLevelInfo(const int32_t level) const; + + SpellID id; + std::string identifier; + std::string modScope; + const std::string & getName() const override; public: enum ESpellPositiveness { @@ -189,10 +192,6 @@ public: using BTVector = std::vector; - SpellID id; - std::string identifier; - std::string name; - si32 level; std::map school; @@ -237,13 +236,16 @@ public: int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getName() const override; const std::string & getJsonKey() const override; SpellID getId() const override; - int32_t getLevel() const override; + std::string getNameTextID() const override; + std::string getNameTranslated() const override; - const std::string & getLevelDescription(const int32_t skillLevel) const override; + std::string getDescriptionTextID(int32_t level) const override; + std::string getDescriptionTranslated(int32_t level) const override; + + int32_t getLevel() const override; boost::logic::tribool getPositiveness() const override; @@ -277,7 +279,6 @@ public: { h & identifier; h & id; - h & name; h & level; h & power; h & probabilities; diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 097cbc3df..e7a60417f 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -288,9 +288,9 @@ void Inspector::updateProperties(CGArtifact * o) for(auto spell : VLC->spellh->objects) { //if(map->isAllowedSpell(spell->id)) - delegate->options << QObject::tr(spell->name.c_str()); + delegate->options << QObject::tr(spell->getJsonKey().c_str()); } - addProperty("Spell", VLC->spellh->objects[spellId]->name, delegate, false); + addProperty("Spell", VLC->spellh->objects[spellId]->getJsonKey(), delegate, false); } } } @@ -524,7 +524,7 @@ void Inspector::setProperty(CGArtifact * o, const QString & key, const QVariant { for(auto spell : VLC->spellh->objects) { - if(spell->name == value.toString().toStdString()) + if(spell->getJsonKey() == value.toString().toStdString()) { o->storedArtifact = CArtifactInstance::createScroll(spell->getId()); break; From e22f6283c2043731068b9d02db225963fdf15ecb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 1 Jan 2023 22:20:41 +0200 Subject: [PATCH 180/197] Secondary skills strings are now passed through translator --- client/lobby/CBonusSelection.cpp | 2 +- client/widgets/CComponent.cpp | 4 +-- client/windows/CHeroWindow.cpp | 4 +-- client/windows/CKingdomInterface.cpp | 6 ++-- client/windows/GUIClasses.cpp | 18 ++++++------ include/vcmi/SkillService.h | 4 +-- lib/CGameState.cpp | 2 +- lib/CHeroHandler.cpp | 2 +- lib/CSkillHandler.cpp | 41 +++++++++++++++++---------- lib/CSkillHandler.h | 24 +++++++++------- lib/HeroBonus.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 2 +- lib/mapObjects/MiscObjects.cpp | 2 +- mapeditor/inspector/rewardswidget.cpp | 2 +- mapeditor/mapsettings.cpp | 2 +- 15 files changed, 65 insertions(+), 52 deletions(-) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 5ab2a1ae6..e89efd322 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -252,7 +252,7 @@ void CBonusSelection::createBonusesIcons() desc = CGI->generaltexth->allTexts[718]; boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->levels[bonDescs[i].info3 - 1]); //skill level - boost::algorithm::replace_first(desc, "%s", CGI->skillh->skillName(bonDescs[i].info2)); //skill name + boost::algorithm::replace_first(desc, "%s", CGI->skillh->getByIndex(bonDescs[i].info2)->getNameTranslated()); //skill name picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1; break; diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index 7ed7ce5a5..65b26fbc3 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -151,7 +151,7 @@ std::string CComponent::getDescription() { case primskill: return (subtype < 4)? CGI->generaltexth->arraytxt[2+subtype] //Primary skill : CGI->generaltexth->allTexts[149]; //mana - case secskill: return CGI->skillh->skillInfo(subtype, val); + case secskill: return CGI->skillh->getByIndex(subtype)->getDescriptionTranslated(val); case resource: return CGI->generaltexth->allTexts[242]; case creature: return ""; case artifact: @@ -196,7 +196,7 @@ std::string CComponent::getSubtitleInternal() switch(compType) { case primskill: return boost::str(boost::format("%+d %s") % val % (subtype < 4 ? CGI->generaltexth->primarySkillNames[subtype] : CGI->generaltexth->allTexts[387])); - case secskill: return CGI->generaltexth->levels[val-1] + "\n" + CGI->skillh->skillName(subtype); + case secskill: return CGI->generaltexth->levels[val-1] + "\n" + CGI->skillh->getByIndex(subtype)->getNameTranslated(); case resource: return boost::lexical_cast(val); case creature: return (val? boost::lexical_cast(val) + " " : "") + CGI->creh->objects[subtype]->*(val != 1 ? &CCreature::namePl : &CCreature::nameSing); case artifact: return CGI->artifacts()->getByIndex(subtype)->getName(); diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 62e8d0f11..39401c45e 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -263,12 +263,12 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded) { int skill = curHero->secSkills[g].first; int level = curHero->getSecSkillLevel(SecondarySkill(curHero->secSkills[g].first)); - std::string skillName = CGI->skillh->skillName(skill); + std::string skillName = CGI->skillh->getByIndex(skill)->getNameTranslated(); std::string skillValue = CGI->generaltexth->levels[level-1]; secSkillAreas[g]->type = skill; secSkillAreas[g]->bonusValue = level; - secSkillAreas[g]->text = CGI->skillh->skillInfo(skill, level); + secSkillAreas[g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level); secSkillAreas[g]->hoverText = boost::str(boost::format(heroscrn[21]) % skillValue % skillName); secSkillImages[g]->setFrame(skill*3 + level + 2); secSkillNames[g]->setText(skillName); diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index c7d19c3f0..fc4729c66 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -166,7 +166,7 @@ std::string InfoBoxAbstractHeroData::getNameText() return CGI->heroh->objects[getSubID()]->specName; case HERO_SECONDARY_SKILL: if (getValue()) - return CGI->skillh->skillName(getSubID()); + return CGI->skillh->getByIndex(getSubID())->getNameTranslated(); else return ""; default: @@ -274,7 +274,7 @@ void InfoBoxAbstractHeroData::prepareMessage(std::string & text, std::shared_ptr int subID = getSubID(); if(value) { - text = CGI->skillh->skillInfo(subID, (int)value); + text = CGI->skillh->getByIndex(subID)->getDescriptionTranslated((int)value); comp = std::make_shared(CComponent::secskill, subID, (int)value); } break; @@ -355,7 +355,7 @@ std::string InfoBoxHeroData::getHoverText() if (hero->secSkills.size() > index) { std::string level = CGI->generaltexth->levels[hero->secSkills[index].second-1]; - std::string skill = CGI->skillh->skillName(hero->secSkills[index].first); + std::string skill = CGI->skillh->getByIndex(hero->secSkills[index].first)->getNameTranslated(); return boost::str(boost::format(CGI->generaltexth->heroscrn[21]) % level % skill); } else diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index d139776d9..3fd6a588b 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1166,11 +1166,11 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, secSkillAreas[b][g]->type = skill; secSkillAreas[b][g]->bonusValue = level; - secSkillAreas[b][g]->text = CGI->skillh->skillInfo(skill, level); + secSkillAreas[b][g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level); secSkillAreas[b][g]->hoverText = CGI->generaltexth->heroscrn[21]; boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->generaltexth->levels[level - 1]); - boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->skillh->skillName(skill)); + boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->skillh->getByIndex(skill)->getNameTranslated()); } heroAreas[b] = std::make_shared(257 + 228*b, 13, hero); @@ -1516,7 +1516,7 @@ CUniversityWindow::CItem::CItem(CUniversityWindow * _parent, int _ID, int X, int icon = std::make_shared("SECSKILL", _ID * 3 + 3, 0); - name = std::make_shared(22, -13, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->skillh->skillName(ID)); + name = std::make_shared(22, -13, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->skillh->getByIndex(ID)->getNameTranslated()); level = std::make_shared(22, 57, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->levels[0]); pos.h = icon->pos.h; @@ -1536,14 +1536,14 @@ void CUniversityWindow::CItem::clickRight(tribool down, bool previousState) { if(down) { - CRClickPopup::createAndPush(CGI->skillh->skillInfo(ID, 1), std::make_shared(CComponent::secskill, ID, 1)); + CRClickPopup::createAndPush(CGI->skillh->getByIndex(ID)->getDescriptionTranslated(1), std::make_shared(CComponent::secskill, ID, 1)); } } void CUniversityWindow::CItem::hover(bool on) { if(on) - GH.statusbar->write(CGI->skillh->skillName(ID)); + GH.statusbar->write(CGI->skillh->getByIndex(ID)->getNameTranslated()); else GH.statusbar->clear(); } @@ -1625,12 +1625,12 @@ CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * owner_, int SKILL, bo std::string text = CGI->generaltexth->allTexts[608]; boost::replace_first(text, "%s", CGI->generaltexth->levels[0]); - boost::replace_first(text, "%s", CGI->skillh->skillName(SKILL)); + boost::replace_first(text, "%s", CGI->skillh->getByIndex(SKILL)->getNameTranslated()); boost::replace_first(text, "%d", "2000"); clerkSpeech = std::make_shared(text, Rect(24, 129, 413, 70), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); - name = std::make_shared(230, 37, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->skillh->skillName(SKILL)); + name = std::make_shared(230, 37, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->skillh->getByIndex(SKILL)->getNameTranslated()); icon = std::make_shared("SECSKILL", SKILL*3+3, 0, 211, 51); level = std::make_shared(230, 107, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->levels[1]); @@ -1638,11 +1638,11 @@ CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * owner_, int SKILL, bo cost = std::make_shared(230, 267, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, "2000"); std::string hoverText = CGI->generaltexth->allTexts[609]; - boost::replace_first(hoverText, "%s", CGI->generaltexth->levels[0]+ " " + CGI->skillh->skillName(SKILL)); + boost::replace_first(hoverText, "%s", CGI->generaltexth->levels[0]+ " " + CGI->skillh->getByIndex(SKILL)->getNameTranslated()); text = CGI->generaltexth->zelp[633].second; boost::replace_first(text, "%s", CGI->generaltexth->levels[0]); - boost::replace_first(text, "%s", CGI->skillh->skillName(SKILL)); + boost::replace_first(text, "%s", CGI->skillh->getByIndex(SKILL)->getNameTranslated()); boost::replace_first(text, "%d", "2000"); confirm = std::make_shared(Point(148, 299), "IBY6432.DEF", CButton::tooltip(hoverText, text), [=](){makeDeal(SKILL);}, SDLK_RETURN); diff --git a/include/vcmi/SkillService.h b/include/vcmi/SkillService.h index 6e8618898..ce0fa8b12 100644 --- a/include/vcmi/SkillService.h +++ b/include/vcmi/SkillService.h @@ -15,9 +15,9 @@ VCMI_LIB_NAMESPACE_BEGIN class SecondarySkill; -class Skill; +class CSkill; -class DLL_LINKAGE SkillService : public EntityServiceT +class DLL_LINKAGE SkillService : public EntityServiceT { public: }; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index f76de8821..202d73036 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -133,7 +133,7 @@ void MetaString::getLocalString(const std::pair &txt, std::string &dst } else if(type == SEC_SKILL_NAME) { - dst = VLC->skillh->skillName(ser); + dst = VLC->skillh->getByIndex(ser)->getNameTranslated(); } else { diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 469acafa9..e1721d0f7 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -316,7 +316,7 @@ void CHeroClassHandler::afterLoadFinalization() if(heroClass->secSkillProbability[skillID] < 0) { const CSkill * skill = (*VLC->skillh)[SecondarySkill(skillID)]; - logMod->trace("%s: no probability for %s, using default", heroClass->identifier, skill->identifier); + logMod->trace("%s: no probability for %s, using default", heroClass->identifier, skill->getJsonKey()); heroClass->secSkillProbability[skillID] = skill->gainChance[heroClass->affinity]; } } diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index 8a1a70c72..d8a630c24 100644 --- a/lib/CSkillHandler.cpp +++ b/lib/CSkillHandler.cpp @@ -56,7 +56,18 @@ int32_t CSkill::getIconIndex() const const std::string & CSkill::getName() const { - return name; + return identifier; +} + +std::string CSkill::getNameTextID() const +{ + TextIdentifier id("skill", modScope, identifier, "name"); + return id.get(); +} + +std::string CSkill::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); } const std::string & CSkill::getJsonKey() const @@ -64,6 +75,17 @@ const std::string & CSkill::getJsonKey() const return identifier; } +std::string CSkill::getDescriptionTextID(int level) const +{ + TextIdentifier id("skill", modScope, identifier, "description", NSecondarySkill::levels[level]); + return id.get(); +} + +std::string CSkill::getDescriptionTranslated(int level) const +{ + return VLC->generaltexth->translate(getDescriptionTextID(level)); +} + void CSkill::registerIcons(const IconRegistar & cb) const { for(int level = 1; level <= 3; level++) @@ -86,7 +108,7 @@ void CSkill::addNewBonus(const std::shared_ptr & b, int level) b->source = Bonus::SECONDARY_SKILL; b->sid = id; b->duration = Bonus::PERMANENT; - b->description = name; + b->description = getNameTextID(); levels[level-1].effects.push_back(b); } @@ -104,7 +126,6 @@ CSkill::LevelInfo & CSkill::at(int level) DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill::LevelInfo & info) { - out << "(\"" << info.description << "\", ["; for(int i=0; i < info.effects.size(); i++) out << (i ? "," : "") << info.effects[i]->Description(); return out << "])"; @@ -187,21 +208,11 @@ const std::vector & CSkillHandler::getTypeNames() const return typeNames; } -const std::string & CSkillHandler::skillInfo(int skill, int level) const -{ - return objects[skill]->at(level).description; -} - -const std::string & CSkillHandler::skillName(int skill) const -{ - return objects[skill]->name; -} - CSkill * CSkillHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) { CSkill * skill = new CSkill(SecondarySkill((si32)index), identifier); - skill->name = json["name"].String(); + VLC->generaltexth->registerString(skill->getNameTextID(), json["name"].String()); switch(json["gainChance"].getType()) { case JsonNode::JsonType::DATA_INTEGER: @@ -226,7 +237,7 @@ CSkill * CSkillHandler::loadFromJson(const std::string & scope, const JsonNode & skill->addNewBonus(bonus, level); } CSkill::LevelInfo & skillAtLevel = skill->at(level); - skillAtLevel.description = levelNode["description"].String(); + VLC->generaltexth->registerString(skill->getDescriptionTextID(level), levelNode["description"].String()); skillAtLevel.iconSmall = levelNode["images"]["small"].String(); skillAtLevel.iconMedium = levelNode["images"]["medium"].String(); skillAtLevel.iconLarge = levelNode["images"]["large"].String(); diff --git a/lib/CSkillHandler.h b/lib/CSkillHandler.h index 2d06c3f3c..e771a25ca 100644 --- a/lib/CSkillHandler.h +++ b/lib/CSkillHandler.h @@ -25,7 +25,6 @@ class DLL_LINKAGE CSkill : public Skill public: struct LevelInfo { - std::string description; //descriptions of spell for skill level std::string iconSmall; std::string iconMedium; std::string iconLarge; @@ -36,7 +35,6 @@ public: template void serialize(Handler & h, const int version) { - h & description; h & iconSmall; h & iconMedium; h & iconLarge; @@ -48,25 +46,33 @@ private: std::vector levels; // bonuses provided by basic, advanced and expert level void addNewBonus(const std::shared_ptr & b, int level); + SecondarySkill id; + std::string modScope; + std::string identifier; + + const std::string & getName() const override; + public: CSkill(SecondarySkill id = SecondarySkill::DEFAULT, std::string identifier = "default"); ~CSkill(); int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getName() const override; const std::string & getJsonKey() const override; void registerIcons(const IconRegistar & cb) const override; SecondarySkill getId() const override; + std::string getNameTextID() const; + std::string getNameTranslated() const; + + std::string getDescriptionTextID(int level) const; + std::string getDescriptionTranslated(int level) const; + const LevelInfo & at(int level) const; LevelInfo & at(int level); std::string toString() const; - SecondarySkill id; - std::string identifier; - std::string name; //as displayed in GUI std::array gainChance; // gainChance[0/1] = default gain chance on level-up for might/magic heroes void updateFrom(const JsonNode & data); @@ -76,7 +82,6 @@ public: { h & id; h & identifier; - h & name; h & gainChance; h & levels; } @@ -86,7 +91,7 @@ public: friend DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill::LevelInfo & info); }; -class DLL_LINKAGE CSkillHandler: public CHandlerBase +class DLL_LINKAGE CSkillHandler: public CHandlerBase { public: CSkillHandler(); @@ -99,9 +104,6 @@ public: std::vector getDefaultAllowed() const override; - const std::string & skillInfo(int skill, int level) const; - const std::string & skillName(int skill) const; - ///json serialization helpers static si32 decodeSkill(const std::string & identifier); static std::string encodeSkill(const si32 index); diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 760aafd66..3ac8cf3b7 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -1603,7 +1603,7 @@ std::string Bonus::Description() const str << VLC->creh->objects[sid]->namePl; break; case SECONDARY_SKILL: - str << VLC->skillh->skillName(sid); + str << VLC->skillh->getByIndex(sid)->getNameTranslated(); break; case HERO_SPECIAL: str << VLC->heroh->objects[sid]->name; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 4bbb931b6..d2e153bbb 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1522,7 +1522,7 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler) if(rawId < 0 || rawId >= VLC->skillh->size()) logGlobal->error("Invalid secondary skill %d", rawId); - handler.serializeEnum((*VLC->skillh)[SecondarySkill(rawId)]->identifier, p.second, 0, NSecondarySkill::levels); + handler.serializeEnum((*VLC->skillh)[SecondarySkill(rawId)]->getJsonKey(), p.second, 0, NSecondarySkill::levels); } } } diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 575eb5002..84b3cda6e 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1498,7 +1498,7 @@ std::string CGWitchHut::getHoverText(PlayerColor player) const if(wasVisited(player)) { hoverName += "\n" + VLC->generaltexth->allTexts[356]; // + (learn %s) - boost::algorithm::replace_first(hoverName, "%s", VLC->skillh->skillName(ability)); + boost::algorithm::replace_first(hoverName, "%s", VLC->skillh->getByIndex(ability)->getNameTranslated()); } return hoverName; } diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index a6f098a41..9de126b24 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -74,7 +74,7 @@ QList RewardsWidget::getListForType(RewardType typeId) for(int i = 0; i < map.allowedAbilities.size(); ++i) { if(map.allowedAbilities[i]) - result.append(QString::fromStdString(VLC->skillh->objects.at(i)->getName())); + result.append(QString::fromStdString(VLC->skillh->objects.at(i)->getNameTranslated())); } break; diff --git a/mapeditor/mapsettings.cpp b/mapeditor/mapsettings.cpp index eb60ef0f5..232eeb806 100644 --- a/mapeditor/mapsettings.cpp +++ b/mapeditor/mapsettings.cpp @@ -34,7 +34,7 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) : for(int i = 0; i < controller.map()->allowedAbilities.size(); ++i) { - auto * item = new QListWidgetItem(QString::fromStdString(VLC->skillh->objects[i]->getName())); + auto * item = new QListWidgetItem(QString::fromStdString(VLC->skillh->objects[i]->getNameTranslated())); item->setData(Qt::UserRole, QVariant::fromValue(i)); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(controller.map()->allowedAbilities[i] ? Qt::Checked : Qt::Unchecked); From fa6f7513e87ea108e0a0052c7ae9a838f2d13688 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 2 Jan 2023 13:27:03 +0200 Subject: [PATCH 181/197] All heroes-related strings are passed through translator --- AI/Nullkiller/AIGateway.cpp | 16 ++-- AI/Nullkiller/AIUtility.cpp | 2 +- AI/Nullkiller/Analyzers/HeroManager.cpp | 4 +- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 4 +- AI/Nullkiller/Goals/AbstractGoal.cpp | 2 +- AI/Nullkiller/Goals/AdventureSpellCast.cpp | 4 +- .../Goals/ExchangeSwapTownHeroes.cpp | 6 +- AI/Nullkiller/Goals/ExecuteHeroChain.cpp | 12 +-- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 4 +- .../Pathfinding/Actions/BuyArmyAction.cpp | 2 +- AI/Nullkiller/Pathfinding/Actors.cpp | 2 +- AI/VCAI/AIUtility.cpp | 2 +- AI/VCAI/Goals/AbstractGoal.cpp | 2 +- AI/VCAI/Goals/AdventureSpellCast.cpp | 4 +- AI/VCAI/Goals/CompleteQuest.cpp | 4 +- AI/VCAI/Goals/Explore.cpp | 4 +- AI/VCAI/Goals/GatherArmy.cpp | 2 +- AI/VCAI/Goals/GatherTroops.cpp | 2 +- AI/VCAI/Goals/VisitHero.cpp | 2 +- AI/VCAI/Goals/VisitObj.cpp | 2 +- AI/VCAI/Goals/VisitTile.cpp | 2 +- AI/VCAI/Pathfinding/AIPathfinder.cpp | 2 +- AI/VCAI/VCAI.cpp | 26 +++--- client/CPlayerInterface.cpp | 4 +- client/ClientCommandManager.cpp | 4 +- client/battle/BattleInterfaceClasses.cpp | 2 +- client/battle/BattleWindow.cpp | 6 +- client/lobby/CBonusSelection.cpp | 2 +- client/lobby/OptionsTab.cpp | 6 +- client/mapHandler.cpp | 2 +- client/widgets/AdventureMapClasses.cpp | 2 +- client/windows/CCastleInterface.cpp | 12 +-- client/windows/CHeroWindow.cpp | 16 ++-- client/windows/CKingdomInterface.cpp | 12 +-- client/windows/CTradeWindow.cpp | 6 +- client/windows/GUIClasses.cpp | 18 ++-- include/vcmi/HeroClass.h | 4 + include/vcmi/HeroType.h | 12 +++ lib/CGameState.cpp | 12 +-- lib/CHeroHandler.cpp | 83 ++++++++++++++++--- lib/CHeroHandler.h | 59 ++++++++----- lib/HeroBonus.cpp | 2 +- lib/mapObjects/CBank.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 70 ++++++++++------ lib/mapObjects/CGHeroInstance.h | 19 +++-- lib/mapObjects/CGPandoraBox.cpp | 14 ++-- lib/mapObjects/CGTownInstance.cpp | 8 +- lib/mapObjects/CObjectHandler.cpp | 2 +- lib/mapObjects/CQuest.cpp | 10 +-- lib/mapObjects/CommonConstructors.cpp | 2 +- lib/mapObjects/MiscObjects.cpp | 4 +- lib/mapping/CMap.cpp | 4 +- lib/mapping/MapFormatH3M.cpp | 16 ++-- lib/mapping/MapFormatJson.cpp | 6 +- lib/serializer/CSerializer.cpp | 4 +- lib/spells/AdventureSpellMechanics.cpp | 8 +- lib/spells/effects/Summon.cpp | 2 +- mapeditor/inspector/inspector.cpp | 18 ++-- mapeditor/inspector/questwidget.cpp | 2 +- mapeditor/mapcontroller.cpp | 6 +- mapeditor/mapsettings.cpp | 2 +- mapeditor/validator.cpp | 2 +- server/CGameHandler.cpp | 16 ++-- server/CQuery.cpp | 2 +- 64 files changed, 356 insertions(+), 239 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 3cfd446b6..4689a4581 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -283,7 +283,7 @@ void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID her auto firstHero = cb->getHero(hero1); auto secondHero = cb->getHero(hero2); - status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->name % firstHero->tempOwner % secondHero->name % secondHero->tempOwner)); + status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->getNameTranslated() % firstHero->tempOwner % secondHero->getNameTranslated() % secondHero->tempOwner)); requestActionASAP([=]() { @@ -544,7 +544,7 @@ void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimaryS LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID); NET_EVENT_HANDLER; - status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->name % hero->level)); + status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->getNameTranslated() % hero->level)); HeroPtr hPtr = hero; requestActionASAP([=]() @@ -787,7 +787,7 @@ void AIGateway::makeTurn() for (auto h : cb->getHeroesInfo()) { if (h->movement) - logAi->warn("Hero %s has %d MP left", h->name, h->movement); + logAi->warn("Hero %s has %d MP left", h->getNameTranslated(), h->movement); } #if NKAI_TRACE_LEVEL == 0 } @@ -808,7 +808,7 @@ void AIGateway::makeTurn() void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) { - LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->name % obj->getObjectName() % obj->pos.toString()); + LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString()); switch(obj->ID) { case Obj::CREATURE_GENERATOR1: @@ -1070,7 +1070,7 @@ void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * arm assert(playerID > PlayerColor::PLAYER_LIMIT || status.getBattle() == UPCOMING_BATTLE); status.setBattle(ONGOING_BATTLE); const CGObjectInstance * presumedEnemy = vstd::backOrNull(cb->getVisitableObjs(tile)); //may be nullptr in some very are cases -> eg. visited monolith and fighting with an enemy at the FoW covered exit - battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->name : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString()); + battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->getNameTranslated() : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString()); CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side); } @@ -1172,7 +1172,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) } }; - logAi->debug("Moving hero %s to tile %s", h->name, dst.toString()); + logAi->debug("Moving hero %s to tile %s", h->getNameTranslated(), dst.toString()); int3 startHpos = h->visitablePos(); bool ret = false; if(startHpos == dst) @@ -1192,7 +1192,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) cb->getPathsInfo(h.get())->getPath(path, dst); if(path.nodes.empty()) { - logAi->error("Hero %s cannot reach %s.", h->name, dst.toString()); + logAi->error("Hero %s cannot reach %s.", h->getNameTranslated(), dst.toString()); return true; } int i = (int)path.nodes.size() - 1; @@ -1344,7 +1344,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) throw cannotFulfillGoalException("Invalid path found!"); } - logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->name, startHpos.toString(), h->visitablePos().toString(), ret); + logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->getNameTranslated(), startHpos.toString(), h->visitablePos().toString(), ret); } return ret; } diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index 56c668ad2..86d25e84d 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -69,7 +69,7 @@ HeroPtr::HeroPtr(const CGHeroInstance * H) } h = H; - name = h->name; + name = h->getNameTranslated(); hid = H->id; // infosCount[ai->playerID][hid]++; } diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 76abf65b9..92467c2cb 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -70,7 +70,7 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const { - auto heroSpecial = Selector::source(Bonus::HERO_SPECIAL, hero->type->ID.getNum()); + auto heroSpecial = Selector::source(Bonus::HERO_SPECIAL, hero->type->getIndex()); auto secondarySkillBonus = Selector::type()(Bonus::SECONDARY_SKILL_PREMY); auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus)); float specialityScore = 0.0f; @@ -172,7 +172,7 @@ void HeroManager::update() for(auto hero : myHeroes) { - logAi->trace("Hero %s has role %s", hero->name, heroRoles[hero] == HeroRole::MAIN ? "main" : "scout"); + logAi->trace("Hero %s has role %s", hero->getNameTranslated(), heroRoles[hero] == HeroRole::MAIN ? "main" : "scout"); } } diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 5ca3f1f4e..2ff3d1942 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -78,7 +78,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta logAi->trace( "Hero %s in garrison of town %s is suposed to defend the town", - town->garrisonHero->name, + town->garrisonHero->getNameTranslated(), town->name); return; @@ -101,7 +101,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta town->name, treat.danger, std::to_string(treat.turn), - treat.hero->name); + treat.hero->getNameTranslated()); bool treatIsUnderControl = false; diff --git a/AI/Nullkiller/Goals/AbstractGoal.cpp b/AI/Nullkiller/Goals/AbstractGoal.cpp index 9433dfad2..9798b8650 100644 --- a/AI/Nullkiller/Goals/AbstractGoal.cpp +++ b/AI/Nullkiller/Goals/AbstractGoal.cpp @@ -69,7 +69,7 @@ std::string AbstractGoal::toString() const //TODO: virtualize return boost::lexical_cast(goalType); } if(hero.get(true)) //FIXME: used to crash when we lost hero and failed goal - desc += " (" + hero->name + ")"; + desc += " (" + hero->getNameTranslated() + ")"; return desc; } diff --git a/AI/Nullkiller/Goals/AdventureSpellCast.cpp b/AI/Nullkiller/Goals/AdventureSpellCast.cpp index 8661f8cee..c5d44f577 100644 --- a/AI/Nullkiller/Goals/AdventureSpellCast.cpp +++ b/AI/Nullkiller/Goals/AdventureSpellCast.cpp @@ -33,7 +33,7 @@ void AdventureSpellCast::accept(AIGateway * ai) auto spell = getSpell(); - logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getNameTranslated(), hero->name); + logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getNameTranslated(), hero->getNameTranslated()); if(!spell->isAdventure()) throw cannotFulfillGoalException(spell->getNameTranslated() + " is not an adventure spell."); @@ -45,7 +45,7 @@ void AdventureSpellCast::accept(AIGateway * ai) throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated()); if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero) - throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->name); + throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated()); if(town && spellID == SpellID::TOWN_PORTAL) { diff --git a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp index ff7ac5245..6ac8a6355 100644 --- a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp +++ b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp @@ -54,13 +54,13 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai) if(currentGarrisonHero.get() != town->visitingHero.get()) { - logAi->error("VisitingHero is empty, expected %s", currentGarrisonHero->name); + logAi->error("VisitingHero is empty, expected %s", currentGarrisonHero->getNameTranslated()); return; } ai->buildArmyIn(town); ai->nullkiller->unlockHero(currentGarrisonHero.get()); - logAi->debug("Extracted hero %s from garrison of %s", currentGarrisonHero->name, town->name); + logAi->debug("Extracted hero %s from garrison of %s", currentGarrisonHero->getNameTranslated(), town->name); return; } @@ -91,7 +91,7 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai) ai->makePossibleUpgrades(town->visitingHero); } - logAi->debug("Put hero %s to garrison of %s", garrisonHero->name, town->name); + logAi->debug("Put hero %s to garrison of %s", garrisonHero->getNameTranslated(), town->name); } } diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index c36cb7ee5..9f1839bf8 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -75,7 +75,7 @@ void ExecuteHeroChain::accept(AIGateway * ai) continue; } - logAi->debug("Executing chain node %d. Moving hero %s to %s", i, hero->name, node.coord.toString()); + logAi->debug("Executing chain node %d. Moving hero %s to %s", i, hero->getNameTranslated(), node.coord.toString()); try { @@ -111,7 +111,7 @@ void ExecuteHeroChain::accept(AIGateway * ai) { logAi->error( "Unable to complete chain. Expected hero %s to arrive to %s in 0 turns but he cannot do this", - hero->name, + hero->getNameTranslated(), node.coord.toString()); return; @@ -143,7 +143,7 @@ void ExecuteHeroChain::accept(AIGateway * ai) if(isOk && path.nodes.back().turns > 0) { - logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->name, hero->movement, node.coord.toString()); + logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->getNameTranslated(), hero->movement, node.coord.toString()); ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN); return; @@ -162,7 +162,7 @@ void ExecuteHeroChain::accept(AIGateway * ai) { logAi->error( "Enable to complete chain. Expected hero %s to arive to %s but he is at %s", - hero->name, + hero->getNameTranslated(), node.coord.toString(), hero->visitablePos().toString()); @@ -187,14 +187,14 @@ void ExecuteHeroChain::accept(AIGateway * ai) std::string ExecuteHeroChain::toString() const { - return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->name; + return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->getNameTranslated(); } bool ExecuteHeroChain::moveHeroToTile(const CGHeroInstance * hero, const int3 & tile) { if(tile == hero->visitablePos() && cb->getVisitableObjs(hero->visitablePos()).size() < 2) { - logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", hero->name, tile.toString()); + logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", hero->getNameTranslated(), tile.toString()); return true; } diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 78dff15ec..7709dc0ad 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1437,10 +1437,10 @@ std::string AIPath::toString() const { std::stringstream str; - str << targetHero->name << "[" << std::hex << chainMask << std::dec << "]" << ", turn " << (int)(turn()) << ": "; + str << targetHero->getNameTranslated() << "[" << std::hex << chainMask << std::dec << "]" << ", turn " << (int)(turn()) << ": "; for(auto node : nodes) - str << node.targetHero->name << "[" << std::hex << node.chainMask << std::dec << "]" << "->" << node.coord.toString() << "; "; + str << node.targetHero->getNameTranslated() << "[" << std::hex << node.chainMask << std::dec << "]" << "->" << node.coord.toString() << "; "; return str.str(); } diff --git a/AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp b/AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp index 3a15f7e4c..27d3b1cfa 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp +++ b/AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp @@ -27,7 +27,7 @@ namespace AIPathfinding if(!hero->visitedTown) { throw cannotFulfillGoalException( - hero->name + " being at " + hero->visitablePos().toString() + " has no town to recruit creatures."); + hero->getNameTranslated() + " being at " + hero->visitablePos().toString() + " has no town to recruit creatures."); } ai->recruitCreatures(hero->visitedTown, hero); diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index dcea063e1..9734d5655 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -80,7 +80,7 @@ int ChainActor::maxMovePoints(CGPathNode::ELayer layer) std::string ChainActor::toString() const { - return hero->name; + return hero->getNameTranslated(); } ObjectActor::ObjectActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn) diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index ffee1b621..69cfd3b7e 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -69,7 +69,7 @@ HeroPtr::HeroPtr(const CGHeroInstance * H) } h = H; - name = h->name; + name = h->getNameTranslated(); hid = H->id; // infosCount[ai->playerID][hid]++; } diff --git a/AI/VCAI/Goals/AbstractGoal.cpp b/AI/VCAI/Goals/AbstractGoal.cpp index 2021d5071..b197a894a 100644 --- a/AI/VCAI/Goals/AbstractGoal.cpp +++ b/AI/VCAI/Goals/AbstractGoal.cpp @@ -106,7 +106,7 @@ std::string AbstractGoal::name() const //TODO: virtualize return boost::lexical_cast(goalType); } if(hero.get(true)) //FIXME: used to crash when we lost hero and failed goal - desc += " (" + hero->name + ")"; + desc += " (" + hero->getNameTranslated() + ")"; return desc; } diff --git a/AI/VCAI/Goals/AdventureSpellCast.cpp b/AI/VCAI/Goals/AdventureSpellCast.cpp index 927eee3d1..d64c39c40 100644 --- a/AI/VCAI/Goals/AdventureSpellCast.cpp +++ b/AI/VCAI/Goals/AdventureSpellCast.cpp @@ -33,7 +33,7 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve() auto spell = getSpell(); - logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getName(), hero->name); + logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getName(), hero->getNameTranslated()); if(!spell->isAdventure()) throw cannotFulfillGoalException(spell->getName() + " is not an adventure spell."); @@ -45,7 +45,7 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve() throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getName()); if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero) - throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->name); + throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated()); return iAmElementar(); } diff --git a/AI/VCAI/Goals/CompleteQuest.cpp b/AI/VCAI/Goals/CompleteQuest.cpp index 57c2a62c4..9fc6a326f 100644 --- a/AI/VCAI/Goals/CompleteQuest.cpp +++ b/AI/VCAI/Goals/CompleteQuest.cpp @@ -92,7 +92,7 @@ TSubgoal CompleteQuest::whatToDoToAchieve() result->name(), result->tile.toString(), result->objid, - result->hero.validAndSet() ? result->hero->name : "not specified"); + result->hero.validAndSet() ? result->hero->getNameTranslated() : "not specified"); return result; } @@ -273,4 +273,4 @@ TGoalVec CompleteQuest::missionDestroyObj() const } return solutions; -} \ No newline at end of file +} diff --git a/AI/VCAI/Goals/Explore.cpp b/AI/VCAI/Goals/Explore.cpp index 14cdce7c4..89b97da1c 100644 --- a/AI/VCAI/Goals/Explore.cpp +++ b/AI/VCAI/Goals/Explore.cpp @@ -240,7 +240,7 @@ bool Explore::operator==(const Explore & other) const std::string Explore::completeMessage() const { - return "Hero " + hero.get()->name + " completed exploration"; + return "Hero " + hero.get()->getNameTranslated() + " completed exploration"; } TSubgoal Explore::whatToDoToAchieve() @@ -339,7 +339,7 @@ TGoalVec Explore::getAllPossibleSubgoals() { for(auto h : heroes) { - logAi->trace("Exploration searching for a new point for hero %s", h->name); + logAi->trace("Exploration searching for a new point for hero %s", h->getNameTranslated()); TSubgoal goal = explorationNewPoint(h); diff --git a/AI/VCAI/Goals/GatherArmy.cpp b/AI/VCAI/Goals/GatherArmy.cpp index e81a41413..d692944a3 100644 --- a/AI/VCAI/Goals/GatherArmy.cpp +++ b/AI/VCAI/Goals/GatherArmy.cpp @@ -33,7 +33,7 @@ bool GatherArmy::operator==(const GatherArmy & other) const std::string GatherArmy::completeMessage() const { - return "Hero " + hero.get()->name + " gathered army of value " + boost::lexical_cast(value); + return "Hero " + hero.get()->getNameTranslated() + " gathered army of value " + boost::lexical_cast(value); } TSubgoal GatherArmy::whatToDoToAchieve() diff --git a/AI/VCAI/Goals/GatherTroops.cpp b/AI/VCAI/Goals/GatherTroops.cpp index 642429dd7..9c70cba67 100644 --- a/AI/VCAI/Goals/GatherTroops.cpp +++ b/AI/VCAI/Goals/GatherTroops.cpp @@ -56,7 +56,7 @@ TSubgoal GatherTroops::whatToDoToAchieve() { if(getCreaturesCount(hero) >= this->value) { - logAi->trace("Completing GATHER_TROOPS by hero %s", hero->name); + logAi->trace("Completing GATHER_TROOPS by hero %s", hero->getNameTranslated()); throw goalFulfilledException(sptr(*this)); } diff --git a/AI/VCAI/Goals/VisitHero.cpp b/AI/VCAI/Goals/VisitHero.cpp index f0498db58..09ae2bba8 100644 --- a/AI/VCAI/Goals/VisitHero.cpp +++ b/AI/VCAI/Goals/VisitHero.cpp @@ -33,7 +33,7 @@ bool VisitHero::operator==(const VisitHero & other) const std::string VisitHero::completeMessage() const { - return "hero " + hero.get()->name + " visited hero " + boost::lexical_cast(objid); + return "hero " + hero.get()->getNameTranslated() + " visited hero " + boost::lexical_cast(objid); } TSubgoal VisitHero::whatToDoToAchieve() diff --git a/AI/VCAI/Goals/VisitObj.cpp b/AI/VCAI/Goals/VisitObj.cpp index 4cbfd1465..7ebaba442 100644 --- a/AI/VCAI/Goals/VisitObj.cpp +++ b/AI/VCAI/Goals/VisitObj.cpp @@ -33,7 +33,7 @@ bool VisitObj::operator==(const VisitObj & other) const std::string VisitObj::completeMessage() const { - return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast(objid); + return "hero " + hero.get()->getNameTranslated() + " captured Object ID = " + boost::lexical_cast(objid); } TGoalVec VisitObj::getAllPossibleSubgoals() diff --git a/AI/VCAI/Goals/VisitTile.cpp b/AI/VCAI/Goals/VisitTile.cpp index c53b90665..c804a2800 100644 --- a/AI/VCAI/Goals/VisitTile.cpp +++ b/AI/VCAI/Goals/VisitTile.cpp @@ -33,7 +33,7 @@ bool VisitTile::operator==(const VisitTile & other) const std::string VisitTile::completeMessage() const { - return "Hero " + hero.get()->name + " visited tile " + tile.toString(); + return "Hero " + hero.get()->getNameTranslated() + " visited tile " + tile.toString(); } TSubgoal VisitTile::whatToDoToAchieve() diff --git a/AI/VCAI/Pathfinding/AIPathfinder.cpp b/AI/VCAI/Pathfinding/AIPathfinder.cpp index bec15a66f..0b52a9ece 100644 --- a/AI/VCAI/Pathfinding/AIPathfinder.cpp +++ b/AI/VCAI/Pathfinding/AIPathfinder.cpp @@ -55,7 +55,7 @@ void AIPathfinder::updatePaths(std::vector heroes) auto calculatePaths = [&](const CGHeroInstance * hero, std::shared_ptr config) { - logAi->debug("Recalculate paths for %s", hero->name); + logAi->debug("Recalculate paths for %s", hero->getNameTranslated()); cb->calculatePaths(config); }; diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 3f9f8143f..1842ee35b 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -303,7 +303,7 @@ void VCAI::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, Q auto firstHero = cb->getHero(hero1); auto secondHero = cb->getHero(hero2); - status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->name % firstHero->tempOwner % secondHero->name % secondHero->tempOwner)); + status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->getNameTranslated() % firstHero->tempOwner % secondHero->getNameTranslated() % secondHero->tempOwner)); requestActionASAP([=]() { @@ -617,7 +617,7 @@ void VCAI::heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill { LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID); NET_EVENT_HANDLER; - status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->name % hero->level)); + status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->getNameTranslated() % hero->level)); requestActionASAP([=](){ answerQuery(queryID, 0); }); } @@ -814,7 +814,7 @@ void VCAI::makeTurn() for (auto h : cb->getHeroesInfo()) { if (h->movement) - logAi->warn("Hero %s has %d MP left", h->name, h->movement); + logAi->warn("Hero %s has %d MP left", h->getNameTranslated(), h->movement); } } catch (boost::thread_interrupted & e) @@ -1034,7 +1034,7 @@ void VCAI::mainLoop() void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) { - LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->name % obj->getObjectName() % obj->pos.toString()); + LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString()); switch(obj->ID) { case Obj::CREATURE_GENERATOR1: @@ -1427,7 +1427,7 @@ void VCAI::wander(HeroPtr h) { //TODO pick the truly best const CGTownInstance * t = *boost::max_element(townsNotReachable, compareReinforcements); - logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->name, t->name, t->visitablePos().toString()); + logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->getNameTranslated(), t->name, t->visitablePos().toString()); int3 pos1 = h->pos; striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h))); //TODO: drop "strive", add to mainLoop //if out hero is stuck, we may need to request another hero to clear the way we see @@ -1581,7 +1581,7 @@ void VCAI::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, i assert(playerID > PlayerColor::PLAYER_LIMIT || status.getBattle() == UPCOMING_BATTLE); status.setBattle(ONGOING_BATTLE); const CGObjectInstance * presumedEnemy = vstd::backOrNull(cb->getVisitableObjs(tile)); //may be nullptr in some very are cases -> eg. visited monolith and fighting with an enemy at the FoW covered exit - battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->name : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString()); + battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->getNameTranslated() : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString()); CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side); } @@ -1667,7 +1667,7 @@ void VCAI::validateVisitableObjs() }); for(auto & p : reservedHeroesMap) { - errorMsg = " shouldn't be on list for hero " + p.first->name + "!"; + errorMsg = " shouldn't be on list for hero " + p.first->getNameTranslated() + "!"; vstd::erase_if(p.second, shouldBeErased); } @@ -1808,7 +1808,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) } }; - logAi->debug("Moving hero %s to tile %s", h->name, dst.toString()); + logAi->debug("Moving hero %s to tile %s", h->getNameTranslated(), dst.toString()); int3 startHpos = h->visitablePos(); bool ret = false; if(startHpos == dst) @@ -1828,7 +1828,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) cb->getPathsInfo(h.get())->getPath(path, dst); if(path.nodes.empty()) { - logAi->error("Hero %s cannot reach %s.", h->name, dst.toString()); + logAi->error("Hero %s cannot reach %s.", h->getNameTranslated(), dst.toString()); throw goalFulfilledException(sptr(Goals::VisitTile(dst).sethero(h))); } int i = (int)path.nodes.size() - 1; @@ -1990,7 +1990,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) throw cannotFulfillGoalException("Invalid path found!"); } evaluateGoal(h); //new hero position means new game situation - logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->name, startHpos.toString(), h->visitablePos().toString(), ret); + logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->getNameTranslated(), startHpos.toString(), h->visitablePos().toString(), ret); } return ret; } @@ -2027,7 +2027,7 @@ void VCAI::tryRealize(Goals::VisitTile & g) throw cannotFulfillGoalException("Cannot visit tile: hero is out of MPs!"); if(g.tile == g.hero->visitablePos() && cb->getVisitableObjs(g.hero->visitablePos()).size() < 2) { - logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->name, g.tile.toString()); + logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->getNameTranslated(), g.tile.toString()); throw goalFulfilledException(sptr(g)); } if(ai->moveHeroToTile(g.tile, g.hero.get())) @@ -2043,7 +2043,7 @@ void VCAI::tryRealize(Goals::VisitObj & g) throw cannotFulfillGoalException("Cannot visit object: hero is out of MPs!"); if(position == g.hero->visitablePos() && cb->getVisitableObjs(g.hero->visitablePos()).size() < 2) { - logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->name, g.tile.toString()); + logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->getNameTranslated(), g.tile.toString()); throw goalFulfilledException(sptr(g)); } if(ai->moveHeroToTile(position, g.hero.get())) @@ -2404,7 +2404,7 @@ void VCAI::performTypicalActions() if(!h) //hero might be lost. getUnblockedHeroes() called once on start of turn continue; - logAi->debug("Hero %s started wandering, MP=%d", h->name.c_str(), h->movement); + logAi->debug("Hero %s started wandering, MP=%d", h->getNameTranslated(), h->movement); makePossibleUpgrades(*h); pickBestArtifacts(*h); try diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index f0b364e4f..03616dc55 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -410,7 +410,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose) void CPlayerInterface::heroKilled(const CGHeroInstance* hero) { EVENT_HANDLER_CALLED_BY_CLIENT; - LOG_TRACE_PARAMS(logGlobal, "Hero %s killed handler for player %s", hero->name % playerID); + LOG_TRACE_PARAMS(logGlobal, "Hero %s killed handler for player %s", hero->getNameTranslated() % playerID); const CArmedInstance *newSelection = nullptr; if (makingTurn) @@ -1268,7 +1268,7 @@ template void CPlayerInterface::serializeTempl( Handler &h, c if (p.second.nodes.size()) pathsMap[p.first] = p.second.endPos(); else - logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->name); + logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->getNameTranslated()); } h & pathsMap; } diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index e75688489..3113b4342 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -317,7 +317,7 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle if(what == "hs") { for(const CGHeroInstance *h : LOCPLINT->cb->getHeroesInfo()) - if(h->type->ID.getNum() == id1) + if(h->type->getIndex() == id1) if(const CArtifactInstance *a = h->getArt(ArtifactPosition(id2))) printCommandMessage(a->nodeName()); } @@ -491,4 +491,4 @@ void ClientCommandManager::printCommandMessage(const std::string &commandMessage LOCPLINT->cingconsole->print(commandMessage); } } -} \ No newline at end of file +} diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index cad232cde..f6c4415df 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -613,7 +613,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface if (ourHero) { str += CGI->generaltexth->allTexts[305]; - boost::algorithm::replace_first(str, "%s", ourHero->name); + boost::algorithm::replace_first(str, "%s", ourHero->getNameTranslated()); boost::algorithm::replace_first(str, "%d", boost::lexical_cast(br.exp[weAreAttacker ? 0 : 1])); } diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index e12501cc9..c0f0b0ee0 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -274,10 +274,10 @@ void BattleWindow::bFleef() //calculating fleeing hero's name if (owner.attackingHeroInstance) if (owner.attackingHeroInstance->tempOwner == owner.curInt->cb->getMyColor()) - heroName = owner.attackingHeroInstance->name; + heroName = owner.attackingHeroInstance->getNameTranslated(); if (owner.defendingHeroInstance) if (owner.defendingHeroInstance->tempOwner == owner.curInt->cb->getMyColor()) - heroName = owner.defendingHeroInstance->name; + heroName = owner.defendingHeroInstance->getNameTranslated(); //calculating text auto txt = boost::format(CGI->generaltexth->allTexts[340]) % heroName; //The Shackles of War are present. %s can not retreat! @@ -416,7 +416,7 @@ void BattleWindow::bSpellf() const auto artID = ArtifactID(blockingBonus->sid); //If we have artifact, put name of our hero. Otherwise assume it's the enemy. //TODO check who *really* is source of bonus - std::string heroName = myHero->hasArt(artID) ? myHero->name : owner.enemyHero().name; + std::string heroName = myHero->hasArt(artID) ? myHero->getNameTranslated() : owner.enemyHero().name; //%s wields the %s, an ancient artifact which creates a p dead to all magic. LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683]) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index e89efd322..023fd1183 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -318,7 +318,7 @@ void CBonusSelection::createBonusesIcons() } else { - boost::algorithm::replace_first(desc, "%s", CGI->heroh->objects[bonDescs[i].info2]->name); + boost::algorithm::replace_first(desc, "%s", CGI->heroh->objects[bonDescs[i].info2]->getNameTranslated()); } break; } diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 6637d81d0..27563fade 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -201,7 +201,7 @@ std::string OptionsTab::CPlayerSettingsHelper::getName() if(!settings.heroName.empty()) return settings.heroName; auto index = settings.hero >= CGI->heroh->size() ? 0 : settings.hero; - return (*CGI->heroh)[index]->name; + return (*CGI->heroh)[index]->getNameTranslated(); } } } @@ -257,7 +257,7 @@ std::string OptionsTab::CPlayerSettingsHelper::getSubtitle() case HERO: { if(settings.hero >= 0) - return getName() + " - " + (*CGI->heroh)[heroIndex]->heroClass->name; + return getName() + " - " + (*CGI->heroh)[heroIndex]->heroClass->getNameTranslated(); return getName(); } @@ -398,7 +398,7 @@ void OptionsTab::CPlayerOptionTooltipBox::genHeroWindow() auto heroIndex = settings.hero >= CGI->heroh->size() ? 0 : settings.hero; imageSpeciality = std::make_shared("UN44", (*CGI->heroh)[heroIndex]->imageIndex, 0, pos.w / 2 - 22, 134); - labelSpecialityName = std::make_shared(pos.w / 2, 188, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, (*CGI->heroh)[heroIndex]->specName); + labelSpecialityName = std::make_shared(pos.w / 2, 188, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, (*CGI->heroh)[heroIndex]->getSpecialtyNameTranslated()); } void OptionsTab::CPlayerOptionTooltipBox::genBonusWindow() diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 215781521..b7341d068 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -960,7 +960,7 @@ CMapHandler::AnimBitmapHolder CMapHandler::CMapBlitter::findHeroBitmap(const CGH { if(hero->tempOwner >= PlayerColor::PLAYER_LIMIT) //Neutral hero? { - logGlobal->error("A neutral hero (%s) at %s. Should not happen!", hero->name, hero->pos.toString()); + logGlobal->error("A neutral hero (%s) at %s. Should not happen!", hero->getNameTranslated(), hero->pos.toString()); return CMapHandler::AnimBitmapHolder(); } diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index 9a6bd3443..6fabf2e1a 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -242,7 +242,7 @@ void CHeroList::CHeroItem::showTooltip() std::string CHeroList::CHeroItem::getHoverText() { - return boost::str(boost::format(CGI->generaltexth->allTexts[15]) % hero->name % hero->type->heroClass->name); + return boost::str(boost::format(CGI->generaltexth->allTexts[15]) % hero->getNameTranslated() % hero->type->heroClass->getNameTranslated()); } std::shared_ptr CHeroList::createHeroItem(size_t index) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 3fd14ce7b..dfd288945 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -353,25 +353,25 @@ void CHeroGSlot::hover(bool on) if(isSelected())//view NNN { temp = CGI->generaltexth->tcommands[4]; - boost::algorithm::replace_first(temp,"%s",hero->name); + boost::algorithm::replace_first(temp,"%s",hero->getNameTranslated()); } else if(other->hero && other->isSelected())//exchange { temp = CGI->generaltexth->tcommands[7]; - boost::algorithm::replace_first(temp,"%s",hero->name); - boost::algorithm::replace_first(temp,"%s",other->hero->name); + boost::algorithm::replace_first(temp,"%s",hero->getNameTranslated()); + boost::algorithm::replace_first(temp,"%s",other->hero->getNameTranslated()); } else// select NNN (in ZZZ) { if(upg)//down - visiting { temp = CGI->generaltexth->tcommands[32]; - boost::algorithm::replace_first(temp,"%s",hero->name); + boost::algorithm::replace_first(temp,"%s",hero->getNameTranslated()); } else //up - garrison { temp = CGI->generaltexth->tcommands[12]; - boost::algorithm::replace_first(temp,"%s",hero->name); + boost::algorithm::replace_first(temp,"%s",hero->getNameTranslated()); } } } @@ -380,7 +380,7 @@ void CHeroGSlot::hover(bool on) if(other->isSelected() && other->hero) //move NNNN { temp = CGI->generaltexth->tcommands[6]; - boost::algorithm::replace_first(temp,"%s",other->hero->name); + boost::algorithm::replace_first(temp,"%s",other->hero->getNameTranslated()); } else //empty { diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 39401c45e..afc4ed853 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -211,19 +211,19 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded) assert(hero == curHero); - name->setText(curHero->name); - title->setText((boost::format(CGI->generaltexth->allTexts[342]) % curHero->level % curHero->type->heroClass->name).str()); + name->setText(curHero->getNameTranslated()); + title->setText((boost::format(CGI->generaltexth->allTexts[342]) % curHero->level % curHero->type->heroClass->getNameTranslated()).str()); - specArea->text = curHero->type->specDescr; + specArea->text = curHero->type->getSpecialtyDescriptionTranslated(); specImage->setFrame(curHero->type->imageIndex); - specName->setText(curHero->type->specName); + specName->setText(curHero->type->getSpecialtyNameTranslated()); tacticsButton = std::make_shared(Point(539, 483), "hsbtns8.def", std::make_pair(heroscrn[26], heroscrn[31]), 0, SDLK_b); tacticsButton->addHoverText(CButton::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]); - dismissButton->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->name % curHero->type->heroClass->name)); - portraitArea->hoverText = boost::str(boost::format(CGI->generaltexth->allTexts[15]) % curHero->name % curHero->type->heroClass->name); - portraitArea->text = curHero->getBiography(); + dismissButton->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->getNameTranslated() % curHero->type->heroClass->getNameTranslated())); + portraitArea->hoverText = boost::str(boost::format(CGI->generaltexth->allTexts[15]) % curHero->getNameTranslated() % curHero->type->heroClass->getNameTranslated()); + portraitArea->text = curHero->getBiographyTranslated(); portraitImage->setFrame(curHero->portrait); { @@ -291,7 +291,7 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded) //printing spell points, boost::format can't be used due to locale issues spellPointsArea->text = CGI->generaltexth->allTexts[205]; - boost::replace_first(spellPointsArea->text, "%s", boost::lexical_cast(curHero->name)); + boost::replace_first(spellPointsArea->text, "%s", boost::lexical_cast(curHero->getNameTranslated())); boost::replace_first(spellPointsArea->text, "%d", boost::lexical_cast(curHero->mana)); boost::replace_first(spellPointsArea->text, "%d", boost::lexical_cast(heroWArt.manaLimit())); diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index fc4729c66..7598ff1b1 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -163,7 +163,7 @@ std::string InfoBoxAbstractHeroData::getNameText() case HERO_EXPERIENCE: return CGI->generaltexth->jktexts[6]; case HERO_SPECIAL: - return CGI->heroh->objects[getSubID()]->specName; + return CGI->heroh->objects[getSubID()]->getSpecialtyNameTranslated(); case HERO_SECONDARY_SKILL: if (getValue()) return CGI->skillh->getByIndex(getSubID())->getNameTranslated(); @@ -256,7 +256,7 @@ void InfoBoxAbstractHeroData::prepareMessage(std::string & text, std::shared_ptr switch (type) { case HERO_SPECIAL: - text = CGI->heroh->objects[getSubID()]->specDescr; + text = CGI->heroh->objects[getSubID()]->getSpecialtyDescriptionTranslated(); break; case HERO_PRIMARY_SKILL: text = CGI->generaltexth->arraytxt[2+getSubID()]; @@ -303,7 +303,7 @@ int InfoBoxHeroData::getSubID() else return 0; case HERO_SPECIAL: - return hero->type->ID.getNum(); + return hero->type->getIndex(); case HERO_MANA: case HERO_EXPERIENCE: return 0; @@ -390,7 +390,7 @@ void InfoBoxHeroData::prepareMessage(std::string & text, std::shared_ptrgeneraltexth->allTexts[205]; - boost::replace_first(text, "%s", boost::lexical_cast(hero->name)); + boost::replace_first(text, "%s", boost::lexical_cast(hero->getNameTranslated())); boost::replace_first(text, "%d", boost::lexical_cast(hero->mana)); boost::replace_first(text, "%d", boost::lexical_cast(hero->manaLimit())); break; @@ -863,7 +863,7 @@ CHeroItem::CHeroItem(const CGHeroInstance * Hero) arts2->recActions = SHARE_POS; backpack->recActions = SHARE_POS; - name = std::make_shared(75, 7, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, hero->name); + name = std::make_shared(75, 7, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, hero->getNameTranslated()); //layout is not trivial: MACH4 - catapult - excluded, MISC[x] rearranged assert(arts1->arts.size() == 9); @@ -919,7 +919,7 @@ CHeroItem::CHeroItem(const CGHeroInstance * Hero) portrait = std::make_shared("PortraitsLarge", hero->portrait, 0, 5, 6); heroArea = std::make_shared(5, 6, hero); - name = std::make_shared(73, 7, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, hero->name); + name = std::make_shared(73, 7, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, hero->getNameTranslated()); artsText = std::make_shared(320, 55, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[2]); for(size_t i=0; i(152, 102, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name))); + labels.push_back(std::make_shared(152, 102, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->getNameTranslated()))); break; case EMarketMode::ARTIFACT_RESOURCE: //%s's Artifacts - labels.push_back(std::make_shared(152, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->name))); + labels.push_back(std::make_shared(152, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->getNameTranslated()))); break; } @@ -1099,7 +1099,7 @@ CAltarWindow::CAltarWindow(const IMarket * Market, const CGHeroInstance * Hero, { //%s's Creatures labels.push_back(std::make_shared(155, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, - boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name))); + boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->getNameTranslated()))); //Altar of Sacrifice labels.push_back(std::make_shared(450, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[479])); diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 3fd6a588b..558145a37 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -404,11 +404,11 @@ CLevelWindow::CLevelWindow(const CGHeroInstance * hero, PrimarySkill::PrimarySki ok = std::make_shared(Point(297, 413), "IOKAY", CButton::tooltip(), std::bind(&CLevelWindow::close, this), SDLK_RETURN); //%s has gained a level. - mainTitle = std::make_shared(192, 33, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[444]) % hero->name)); + mainTitle = std::make_shared(192, 33, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[444]) % hero->getNameTranslated())); //%s is now a level %d %s. levelTitle = std::make_shared(192, 162, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, - boost::str(boost::format(CGI->generaltexth->allTexts[445]) % hero->name % hero->level % hero->type->heroClass->name)); + boost::str(boost::format(CGI->generaltexth->allTexts[445]) % hero->getNameTranslated() % hero->level % hero->type->heroClass->getNameTranslated())); skillIcon = std::make_shared("PSKIL42", pskill, 0, 174, 190); @@ -743,7 +743,7 @@ void CTavernWindow::show(SDL_Surface * to) oldSelected = selected; //Recruit %s the %s - recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[3]) % sel->h->name % sel->h->type->heroClass->name)); + recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[3]) % sel->h->getNameTranslated() % sel->h->type->heroClass->getNameTranslated())); } printAtMiddleWBLoc(sel->description, 146, 395, FONT_SMALL, 200, Colors::WHITE, to); @@ -777,7 +777,7 @@ CTavernWindow::HeroPortrait::HeroPortrait(int & sel, int id, int x, int y, const if(H) { hoverName = CGI->generaltexth->tavernInfo[4]; - boost::algorithm::replace_first(hoverName,"%s",H->name); + boost::algorithm::replace_first(hoverName,"%s",H->getNameTranslated()); int artifs = (int)h->artifactsWorn.size() + (int)h->artifactsInBackpack.size(); for(int i=13; i<=17; i++) //war machines and spellbook don't count @@ -785,9 +785,9 @@ CTavernWindow::HeroPortrait::HeroPortrait(int & sel, int id, int x, int y, const artifs--; description = CGI->generaltexth->allTexts[215]; - boost::algorithm::replace_first(description, "%s", h->name); + boost::algorithm::replace_first(description, "%s", h->getNameTranslated()); boost::algorithm::replace_first(description, "%d", boost::lexical_cast(h->level)); - boost::algorithm::replace_first(description, "%s", h->type->heroClass->name); + boost::algorithm::replace_first(description, "%s", h->type->heroClass->getNameTranslated()); boost::algorithm::replace_first(description, "%d", boost::lexical_cast(artifs)); portrait = std::make_shared("portraitsLarge", h->portrait); @@ -1078,7 +1078,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, auto genTitle = [](const CGHeroInstance * h) { boost::format fmt(CGI->generaltexth->allTexts[138]); - fmt % h->name % h->level % h->type->heroClass->name; + fmt % h->getNameTranslated() % h->level % h->type->heroClass->getNameTranslated(); return boost::str(fmt); }; @@ -1178,7 +1178,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, specialtyAreas[b] = std::make_shared(); specialtyAreas[b]->pos = genRect(32, 32, pos.x + 69 + 490*b, pos.y + (qeLayout ? 41 : 45)); specialtyAreas[b]->hoverText = CGI->generaltexth->heroscrn[27]; - specialtyAreas[b]->text = hero->type->specDescr; + specialtyAreas[b]->text = hero->type->getSpecialtyDescriptionTranslated(); experienceAreas[b] = std::make_shared(); experienceAreas[b]->pos = genRect(32, 32, pos.x + 105 + 490*b, pos.y + (qeLayout ? 41 : 45)); @@ -1192,7 +1192,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, spellPointsAreas[b]->pos = genRect(32, 32, pos.x + 141 + 490*b, pos.y + (qeLayout ? 41 : 45)); spellPointsAreas[b]->hoverText = CGI->generaltexth->heroscrn[22]; spellPointsAreas[b]->text = CGI->generaltexth->allTexts[205]; - boost::algorithm::replace_first(spellPointsAreas[b]->text, "%s", hero->name); + boost::algorithm::replace_first(spellPointsAreas[b]->text, "%s", hero->getNameTranslated()); boost::algorithm::replace_first(spellPointsAreas[b]->text, "%d", boost::lexical_cast(hero->mana)); boost::algorithm::replace_first(spellPointsAreas[b]->text, "%d", boost::lexical_cast(hero->manaLimit())); diff --git a/include/vcmi/HeroClass.h b/include/vcmi/HeroClass.h index 5440910e0..3f6c47c53 100644 --- a/include/vcmi/HeroClass.h +++ b/include/vcmi/HeroClass.h @@ -18,7 +18,11 @@ class HeroClassID; class DLL_LINKAGE HeroClass : public EntityT { + using EntityT::getName; public: + virtual std::string getNameTranslated() const = 0; + virtual std::string getNameTextID() const = 0; + }; diff --git a/include/vcmi/HeroType.h b/include/vcmi/HeroType.h index 664a370ec..3df57b24b 100644 --- a/include/vcmi/HeroType.h +++ b/include/vcmi/HeroType.h @@ -18,7 +18,19 @@ class HeroTypeID; class DLL_LINKAGE HeroType : public EntityT { + using EntityT::getName; public: + virtual std::string getNameTranslated() const = 0; + virtual std::string getBiographyTranslated() const = 0; + virtual std::string getSpecialtyNameTranslated() const = 0; + virtual std::string getSpecialtyDescriptionTranslated() const = 0; + virtual std::string getSpecialtyTooltipTranslated() const = 0; + + virtual std::string getNameTextID() const = 0; + virtual std::string getBiographyTextID() const = 0; + virtual std::string getSpecialtyNameTextID() const = 0; + virtual std::string getSpecialtyDescriptionTextID() const = 0; + virtual std::string getSpecialtyTooltipTextID() const = 0; }; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 202d73036..3f72410a7 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1451,7 +1451,7 @@ void CGameState::initHeroes() hero->initHero(getRandomGenerator()); getPlayerState(hero->getOwner())->heroes.push_back(hero); - map->allHeroes[hero->type->ID.getNum()] = hero; + map->allHeroes[hero->type->getIndex()] = hero; } for(auto obj : map->objects) //prisons @@ -1468,7 +1468,7 @@ void CGameState::initHeroes() ph->initHero(getRandomGenerator()); hpool.heroesPool[ph->subID] = ph; hpool.pavailable[ph->subID] = 0xff; - heroesToCreate.erase(ph->type->ID); + heroesToCreate.erase(ph->type->getId()); map->allHeroes[ph->subID] = ph; } @@ -2796,7 +2796,7 @@ std::set CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllow for(auto hero : map->heroesOnMap) //heroes instances initialization { if(hero->type) - ret -= hero->type->ID; + ret -= hero->type->getId(); else ret -= HeroTypeID(hero->subID); } @@ -2918,7 +2918,7 @@ CGHeroInstance * CGameState::getUsedHero(HeroTypeID hid) const { for(auto hero : map->heroesOnMap) //heroes instances initialization { - if(hero->type && hero->type->ID == hid) + if(hero->type && hero->type->getId() == hid) { return hero; } @@ -2930,7 +2930,7 @@ CGHeroInstance * CGameState::getUsedHero(HeroTypeID hid) const { auto hero = dynamic_cast(obj.get()); assert(hero); - if ( hero->type && hero->type->ID == hid ) + if ( hero->type && hero->type->getId() == hid ) return hero; } } @@ -3024,7 +3024,7 @@ void InfoAboutHero::initFromHero(const CGHeroInstance *h, InfoAboutHero::EInfoLe initFromArmy(h, detailed); hclass = h->type->heroClass; - name = h->name; + name = h->getNameTranslated(); portrait = h->portrait; if(detailed) diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index e1721d0f7..9b1eda46d 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -43,7 +43,7 @@ int32_t CHero::getIconIndex() const const std::string & CHero::getName() const { - return name; + return identifier; } const std::string & CHero::getJsonKey() const @@ -56,6 +56,56 @@ HeroTypeID CHero::getId() const return ID; } +std::string CHero::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + +std::string CHero::getBiographyTranslated() const +{ + return VLC->generaltexth->translate(getBiographyTextID()); +} + +std::string CHero::getSpecialtyNameTranslated() const +{ + return VLC->generaltexth->translate(getSpecialtyNameTextID()); +} + +std::string CHero::getSpecialtyDescriptionTranslated() const +{ + return VLC->generaltexth->translate(getSpecialtyDescriptionTextID()); +} + +std::string CHero::getSpecialtyTooltipTranslated() const +{ + return VLC->generaltexth->translate(getSpecialtyTooltipTextID()); +} + +std::string CHero::getNameTextID() const +{ + return TextIdentifier("hero", modScope, identifier, "name").get(); +} + +std::string CHero::getBiographyTextID() const +{ + return TextIdentifier("hero", modScope, identifier, "biography").get(); +} + +std::string CHero::getSpecialtyNameTextID() const +{ + return TextIdentifier("hero", modScope, identifier, "specialty", "name").get(); +} + +std::string CHero::getSpecialtyDescriptionTextID() const +{ + return TextIdentifier("hero", modScope, identifier, "specialty", "description").get(); +} + +std::string CHero::getSpecialtyTooltipTextID() const +{ + return TextIdentifier("hero", modScope, identifier, "specialty", "tooltip").get(); +} + void CHero::registerIcons(const IconRegistar & cb) const { cb(getIconIndex(), 0, "UN32", iconSpecSmall); @@ -120,7 +170,7 @@ int32_t CHeroClass::getIconIndex() const const std::string & CHeroClass::getName() const { - return name; + return identifier; } const std::string & CHeroClass::getJsonKey() const @@ -138,6 +188,16 @@ void CHeroClass::registerIcons(const IconRegistar & cb) const } +std::string CHeroClass::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + +std::string CHeroClass::getNameTextID() const +{ + return TextIdentifier("heroClass", modScope, identifier, "specialty", "name").get(); +} + void CHeroClass::updateFrom(const JsonNode & data) { //TODO: CHeroClass::updateFrom @@ -164,7 +224,7 @@ void CHeroClassHandler::fillPrimarySkillData(const JsonNode & node, CHeroClass * if(currentPrimarySkillValue < primarySkillLegalMinimum) { logMod->error("Hero class '%s' has incorrect initial value '%d' for skill '%s'. Value '%d' will be used instead.", - heroClass->identifier, currentPrimarySkillValue, skillName, primarySkillLegalMinimum); + heroClass->getName(), currentPrimarySkillValue, skillName, primarySkillLegalMinimum); currentPrimarySkillValue = primarySkillLegalMinimum; } heroClass->primarySkillInitial.push_back(currentPrimarySkillValue); @@ -192,7 +252,8 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js heroClass->imageMapFemale = node["animation"]["map"]["female"].String(); heroClass->imageMapMale = node["animation"]["map"]["male"].String(); - heroClass->name = node["name"].String(); + VLC->generaltexth->registerString( heroClass->getNameTextID(), node["name"].String()); + heroClass->affinity = vstd::find_pos(affinityStr, node["affinity"].String()); fillPrimarySkillData(node, heroClass, PrimarySkill::ATTACK); @@ -362,11 +423,11 @@ CHero * CHeroHandler::loadFromJson(const std::string & scope, const JsonNode & n hero->sex = node["female"].Bool(); hero->special = node["special"].Bool(); - hero->name = node["texts"]["name"].String(); - hero->biography = node["texts"]["biography"].String(); - hero->specName = node["texts"]["specialty"]["name"].String(); - hero->specTooltip = node["texts"]["specialty"]["tooltip"].String(); - hero->specDescr = node["texts"]["specialty"]["description"].String(); + VLC->generaltexth->registerString( hero->getNameTextID(), node["texts"]["name"].String()); + VLC->generaltexth->registerString( hero->getBiographyTextID(), node["texts"]["biography"].String()); + VLC->generaltexth->registerString( hero->getSpecialtyNameTextID(), node["texts"]["specialty"]["name"].String()); + VLC->generaltexth->registerString( hero->getSpecialtyTooltipTextID(), node["texts"]["specialty"]["tooltip"].String()); + VLC->generaltexth->registerString( hero->getSpecialtyDescriptionTextID(), node["texts"]["specialty"]["description"].String()); hero->iconSpecSmall = node["images"]["specialtySmall"].String(); hero->iconSpecLarge = node["images"]["specialtyLarge"].String(); @@ -703,7 +764,7 @@ void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node) const JsonNode & specialtiesNode = node["specialties"]; if (!specialtiesNode.isNull()) { - logMod->warn("Hero %s has deprecated specialties format.", hero->identifier); + logMod->warn("Hero %s has deprecated specialties format.", hero->getNameTranslated()); for(const JsonNode &specialty : specialtiesNode.Vector()) { SSpecialtyInfo spec; @@ -892,7 +953,7 @@ void CHeroHandler::afterLoadFinalization() if(hero->specDeprecated.size() > 0 || hero->specialtyDeprecated.size() > 0) { - logMod->debug("Converting specialty format for hero %s(%s)", hero->identifier, FactionID::encode(hero->heroClass->faction)); + logMod->debug("Converting specialty format for hero %s(%s)", hero->getNameTranslated(), FactionID::encode(hero->heroClass->faction)); std::vector> convertedBonuses; for(const SSpecialtyInfo & spec : hero->specDeprecated) { diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 93e4f0f87..546439eac 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -30,7 +30,8 @@ class JsonSerializeFormat; class BattleField; struct SSpecialtyInfo -{ si32 type; +{ + si32 type; si32 val; si32 subtype; si32 additionalinfo; @@ -57,6 +58,16 @@ struct SSpecialtyBonus class DLL_LINKAGE CHero : public HeroType { + friend class CHeroHandler; + + HeroTypeID ID; + std::string identifier; + std::string modScope; + std::string nameTextID; //name of hero + std::string biographyTextID; + + const std::string & getName() const override; + public: struct InitialArmyStack { @@ -71,8 +82,6 @@ public: h & creature; } }; - std::string identifier; - HeroTypeID ID; si32 imageIndex; std::vector initialArmy; @@ -87,13 +96,6 @@ public: bool special; // hero is special and won't be placed in game (unless preset on map), e.g. campaign heroes ui8 sex; // default sex: 0=male, 1=female - /// Localized texts - std::string name; //name of hero - std::string biography; - std::string specName; - std::string specDescr; - std::string specTooltip; - /// Graphics std::string iconSpecSmall; std::string iconSpecLarge; @@ -106,11 +108,22 @@ public: int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getName() const override; const std::string & getJsonKey() const override; HeroTypeID getId() const override; void registerIcons(const IconRegistar & cb) const override; + std::string getNameTranslated() const override; + std::string getBiographyTranslated() const override; + std::string getSpecialtyNameTranslated() const override; + std::string getSpecialtyDescriptionTranslated() const override; + std::string getSpecialtyTooltipTranslated() const override; + + std::string getNameTextID() const override; + std::string getBiographyTextID() const override; + std::string getSpecialtyNameTextID() const override; + std::string getSpecialtyDescriptionTextID() const override; + std::string getSpecialtyTooltipTextID() const override; + void updateFrom(const JsonNode & data); void serializeJson(JsonSerializeFormat & handler); @@ -126,11 +139,8 @@ public: h & haveSpellBook; h & sex; h & special; - h & name; - h & biography; - h & specName; - h & specDescr; - h & specTooltip; + h & nameTextID; + h & biographyTextID; h & iconSpecSmall; h & iconSpecLarge; h & portraitSmall; @@ -146,6 +156,13 @@ std::vector> SpecialtyBonusToBonuses(const SSpecialtyBonu class DLL_LINKAGE CHeroClass : public HeroClass { + friend class CHeroClassHandler; + HeroClassID id; // use getId instead + std::string modScope; + std::string identifier; // use getJsonKey instead + std::string nameTextID; + + const std::string & getName() const override; public: enum EClassAffinity { @@ -153,11 +170,8 @@ public: MAGIC }; - std::string identifier; - std::string name; // translatable //double aggression; // not used in vcmi. TFaction faction; - HeroClassID id; ui8 affinity; // affinity, using EClassAffinity enum // default chance for hero of specific class to appear in tavern, if field "tavern" was not set @@ -183,11 +197,13 @@ public: int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getName() const override; const std::string & getJsonKey() const override; HeroClassID getId() const override; void registerIcons(const IconRegistar & cb) const override; + std::string getNameTranslated() const override; + std::string getNameTextID() const override; + bool isMagicHero() const; SecondarySkill chooseSecSkill(const std::set & possibles, CRandomGenerator & rand) const; //picks secondary skill out from given possibilities @@ -196,8 +212,9 @@ public: template void serialize(Handler & h, const int version) { + h & modScope; h & identifier; - h & name; + h & nameTextID; h & faction; h & id; h & defaultTavernChance; diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 3ac8cf3b7..eb21e5063 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -1606,7 +1606,7 @@ std::string Bonus::Description() const str << VLC->skillh->getByIndex(sid)->getNameTranslated(); break; case HERO_SPECIAL: - str << VLC->heroh->objects[sid]->name; + str << VLC->heroh->objects[sid]->getNameTranslated(); break; default: //todo: handle all possible sources diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp index ffdab4555..89d98b1b0 100644 --- a/lib/mapObjects/CBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -327,7 +327,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const iw.text.addTxt(MetaString::ADVOB_TXT, 186); iw.text.addReplacement(loot.buildList()); - iw.text.addReplacement(hero->name); + iw.text.addReplacement(hero->getNameTranslated()); cb->showInfoDialog(&iw); cb->giveCreatures(this, hero, ourArmy, false); } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index d2e153bbb..fa5939ee7 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -65,7 +65,7 @@ static int lowestSpeed(const CGHeroInstance * chi) return chi->commander->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED); } - logGlobal->error("Hero %d (%s) has no army!", chi->id.getNum(), chi->name); + logGlobal->error("Hero %d (%s) has no army!", chi->id.getNum(), chi->getNameTranslated()); return 20; } @@ -309,8 +309,6 @@ void CGHeroInstance::initHero(CRandomGenerator & rand) } if(secSkills.size() == 1 && secSkills[0] == std::pair(SecondarySkill::DEFAULT, -1)) //set secondary skills to default secSkills = type->secSkillsInit; - if (!name.length()) - name = type->name; if (sex == 0xFF)//sex is default sex = type->sex; @@ -373,7 +371,7 @@ void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor * dst) if(creature == nullptr) { - logGlobal->error("Hero %s has invalid creature with id %d in initial army", name, stack.creature.toEnum()); + logGlobal->error("Hero %s has invalid creature with id %d in initial army", getNameTranslated(), stack.creature.toEnum()); continue; } @@ -394,11 +392,11 @@ void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor * dst) if(!getArt(slot)) putArtifact(slot, CArtifactInstance::createNewArtifactInstance(aid)); else - logGlobal->warn("Hero %s already has artifact at %d, omitting giving artifact %d", name, slot.toEnum(), aid.toEnum()); + logGlobal->warn("Hero %s already has artifact at %d, omitting giving artifact %d", getNameTranslated(), slot.toEnum(), aid.toEnum()); } else { - logGlobal->error("Hero %s has invalid war machine in initial army", name); + logGlobal->error("Hero %s has invalid war machine in initial army", getNameTranslated()); } } else @@ -469,21 +467,14 @@ std::string CGHeroInstance::getObjectName() const if(ID != Obj::PRISON) { std::string hoverName = VLC->generaltexth->allTexts[15]; - boost::algorithm::replace_first(hoverName,"%s",name); - boost::algorithm::replace_first(hoverName,"%s", type->heroClass->name); + boost::algorithm::replace_first(hoverName,"%s",getNameTranslated()); + boost::algorithm::replace_first(hoverName,"%s", type->heroClass->getNameTranslated()); return hoverName; } else return CGObjectInstance::getObjectName(); } -const std::string & CGHeroInstance::getBiography() const -{ - if (biography.length()) - return biography; - return type->biography; -} - ui8 CGHeroInstance::maxlevelsToMagicSchool() const { return type->heroClass->isMagicHero() ? 3 : 4; @@ -536,14 +527,13 @@ void CGHeroInstance::initObj(CRandomGenerator & rand) for(std::shared_ptr b : sb.bonuses) addNewBonus(b); for(SSpecialtyInfo & spec : type->specDeprecated) - for(std::shared_ptr b : SpecialtyInfoToBonuses(spec, type->ID.getNum())) + for(std::shared_ptr b : SpecialtyInfoToBonuses(spec, type->getIndex())) addNewBonus(b); //initialize bonuses recreateSecondarySkillsBonuses(); mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one - type->name = name; } void CGHeroInstance::recreateSecondarySkillsBonuses() @@ -686,7 +676,7 @@ PlayerColor CGHeroInstance::getCasterOwner() const void CGHeroInstance::getCasterName(MetaString & text) const { //FIXME: use local name, MetaString need access to gamestate as hero name is part of map object - text.addReplacement(name); + text.addReplacement(getNameTranslated()); } void CGHeroInstance::getCastDescription(const spells::Spell * spell, const std::vector & attacked, MetaString & text) const @@ -995,7 +985,39 @@ void CGHeroInstance::initExp(CRandomGenerator & rand) std::string CGHeroInstance::nodeName() const { - return "Hero " + name; + return "Hero " + getNameTextID(); +} + +std::string CGHeroInstance::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + +std::string CGHeroInstance::getNameTextID() const +{ + if (!nameCustom.empty()) + return nameCustom; + if (type) + return type->getNameTextID(); + + assert(0); + return ""; +} + +std::string CGHeroInstance::getBiographyTranslated() const +{ + return VLC->generaltexth->translate(getBiographyTextID()); +} + +std::string CGHeroInstance::getBiographyTextID() const +{ + if (!biographyCustom.empty()) + return biographyCustom; + if (type) + return type->getBiographyTextID(); + + assert(0); + return ""; } void CGHeroInstance::putArtifact(ArtifactPosition pos, CArtifactInstance *art) @@ -1230,7 +1252,7 @@ PrimarySkill::PrimarySkill CGHeroInstance::nextPrimarySkill(CRandomGenerator & r if(primarySkill >= GameConstants::PRIMARY_SKILLS) { primarySkill = rand.nextInt(GameConstants::PRIMARY_SKILLS - 1); - logGlobal->error("Wrong values in primarySkill%sLevel for hero class %s", isLowLevelHero ? "Low" : "High", type->heroClass->identifier); + logGlobal->error("Wrong values in primarySkill%sLevel for hero class %s", isLowLevelHero ? "Low" : "High", type->heroClass->getNameTranslated()); randomValue = 100 / GameConstants::PRIMARY_SKILLS; } logGlobal->trace("The hero gets the primary skill %d with a probability of %d %%.", primarySkill, randomValue); @@ -1378,11 +1400,11 @@ std::string CGHeroInstance::getHeroTypeName() const { if(type) { - return type->identifier; + return type->getJsonKey(); } else { - return VLC->heroh->objects[subID]->identifier; + return VLC->heroh->objects[subID]->getJsonKey(); } } return ""; @@ -1421,7 +1443,7 @@ void CGHeroInstance::updateFrom(const JsonNode & data) void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler) { - handler.serializeString("biography", biography); + handler.serializeString("biography", biographyCustom); handler.serializeInt("experience", exp, 0); if(!handler.saving && exp != 0xffffffff) //do not gain levels if experience is not initialized @@ -1432,7 +1454,7 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler) } } - handler.serializeString("name", name); + handler.serializeString("name", nameCustom); handler.serializeBool("female", sex, 1, 0, 0xFF); { diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index b010f73f6..c9df2fb36 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -45,11 +45,13 @@ class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, // We serialize heroes into JSON for crossover friend class CCampaignState; friend class CMapLoaderH3M; + friend class CMapFormatJson; private: std::set spells; //known spells (spell IDs) public: + ////////////////////////////////////////////////////////////////////////// ui8 moveDir; //format: 123 @@ -62,13 +64,15 @@ public: ConstTransitivePtr type; TExpType exp; //experience points ui32 level; //current level of hero - std::string name; //may be custom - std::string biography; //if custom si32 portrait; //may be custom si32 mana; // remaining spell points std::vector > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert); if hero has ability (-1, -1) it meansthat it should have default secondary abilities ui32 movement; //remaining movement points ui8 sex; + + std::string nameCustom; + std::string biographyCustom; + bool inTownGarrison; // if hero is in town garrison ConstTransitivePtr visitedTown; //set if hero is visiting town or in the town garrison ConstTransitivePtr commander; @@ -145,6 +149,12 @@ public: ////////////////////////////////////////////////////////////////////////// + std::string getNameTranslated() const; + std::string getNameTextID() const; + + std::string getBiographyTranslated() const; + std::string getBiographyTextID() const; + bool hasSpellbook() const; int maxSpellLevel() const; void addSpellToSpellbook(SpellID spell); @@ -153,7 +163,6 @@ public: void removeSpellbook(); const std::set & getSpellsInSpellbook() const; EAlignment::EAlignment getAlignment() const; - const std::string &getBiography() const; bool needsLastStack()const override; ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling @@ -299,8 +308,8 @@ public: h & static_cast(*this); h & exp; h & level; - h & name; - h & biography; + h & nameCustom; + h & biographyCustom; h & portrait; h & mana; h & secSkills; diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index e04dfa377..a71f68825 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -102,7 +102,7 @@ void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const TExpType expVal = h->calculateXp(gainedExp); //getText(iw,afterBattle,175,h); //wtf? iw.text.addTxt(MetaString::ADVOB_TXT, 175); //%s learns something - iw.text.addReplacement(h->name); + iw.text.addReplacement(h->getNameTranslated()); if(expVal) iw.components.push_back(Component(Component::EXPERIENCE,0,static_cast(expVal),0)); @@ -183,7 +183,7 @@ void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const { iw.text.addTxt(MetaString::ADVOB_TXT, 184); //%s learns a spell } - iw.text.addReplacement(h->name); + iw.text.addReplacement(h->getNameTranslated()); cb->changeSpells(h, true, spellsToGive); cb->showInfoDialog(&iw); } @@ -249,7 +249,7 @@ void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const iw.components.clear(); // getText(iw,afterBattle,183,h); iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - iw.text.addReplacement(h->name); + iw.text.addReplacement(h->getNameTranslated()); for(auto & elem : artifacts) { iw.components.push_back(Component(Component::ARTIFACT,elem,0,0)); @@ -258,7 +258,7 @@ void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const cb->showInfoDialog(&iw); iw.components.clear(); iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - once more? - iw.text.addReplacement(h->name); + iw.text.addReplacement(h->getNameTranslated()); } } if(iw.components.size()) @@ -290,7 +290,7 @@ void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const iw.text.addTxt(MetaString::ADVOB_TXT, 186); iw.text.addReplacement(loot.buildList()); - iw.text.addReplacement(h->name); + iw.text.addReplacement(h->getNameTranslated()); cb->showInfoDialog(&iw); cb->giveCreatures(this, h, creatures, false); @@ -307,7 +307,7 @@ void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int text, const C if(afterBattle || !message.size()) { iw.text.addTxt(MetaString::ADVOB_TXT,text);//%s has lost treasure. - iw.text.addReplacement(h->name); + iw.text.addReplacement(h->getNameTranslated()); } else { @@ -323,7 +323,7 @@ void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int nega if(afterBattle || !message.size()) { iw.text.addTxt(MetaString::ADVOB_TXT,val < 0 ? negative : positive); //%s's luck takes a turn for the worse / %s's luck increases - iw.text.addReplacement(h->name); + iw.text.addReplacement(h->getNameTranslated()); } else { diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 6317e2425..deae9b107 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -720,7 +720,7 @@ void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const } else { - logGlobal->error("%s visits allied town of %s from different pos?", h->name, name); + logGlobal->error("%s visits allied town of %s from different pos?", h->getNameTranslated(), name); } } @@ -730,10 +730,10 @@ void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const if(visitingHero == h) { cb->stopHeroVisitCastle(this, h); - logGlobal->trace("%s correctly left town %s", h->name, name); + logGlobal->trace("%s correctly left town %s", h->getNameTranslated(), name); } else - logGlobal->warn("Warning, %s tries to leave the town %s but hero is not inside.", h->name, name); + logGlobal->warn("Warning, %s tries to leave the town %s but hero is not inside.", h->getNameTranslated(), name); } std::string CGTownInstance::getObjectName() const @@ -1451,7 +1451,7 @@ void CGTownInstance::addHeroToStructureVisitors(const CGHeroInstance *h, si64 st else { //should never ever happen - logGlobal->error("Cannot add hero %s to visitors of structure # %d", h->name, structureInstanceID); + logGlobal->error("Cannot add hero %s to visitors of structure # %d", h->getNameTranslated(), structureInstanceID); throw std::runtime_error("internal error"); } } diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index 1ac9b2675..c3774071f 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -494,7 +494,7 @@ void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visit if(visitor) { out.addTxt(MetaString::GENERAL_TXT, 134); - out.addReplacement(visitor->name); + out.addReplacement(visitor->getNameTranslated()); } else out.addTxt(MetaString::ADVOB_TXT, 189); diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index d945c5eb6..5974c1149 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -170,7 +170,7 @@ bool CQuest::checkQuest(const CGHeroInstance * h) const } return true; case MISSION_HERO: - if(m13489val == h->type->ID.getNum()) + if(m13489val == h->type->getIndex()) return true; return false; case MISSION_PLAYER: @@ -230,7 +230,7 @@ void CQuest::getVisitText(MetaString &iwText, std::vector &components //FIXME: portrait may not match hero, if custom portrait was set in map editor components.push_back(Component(Component::HERO_PORTRAIT, VLC->heroh->objects[m13489val]->imageIndex, 0, 0)); if(!isCustom) - iwText.addReplacement(VLC->heroh->objects[m13489val]->name); + iwText.addReplacement(VLC->heroh->objects[m13489val]->getNameTextID()); break; case MISSION_KILL_CREATURE: { @@ -369,7 +369,7 @@ void CQuest::getRolloverText(MetaString &ms, bool onHover) const } break; case MISSION_HERO: - ms.addReplacement(VLC->heroh->objects[m13489val]->name); + ms.addReplacement(VLC->heroh->objects[m13489val]->getNameTextID()); break; case MISSION_PLAYER: ms.addReplacement(VLC->generaltexth->colors[m13489val]); @@ -452,7 +452,7 @@ void CQuest::getCompletionText(MetaString &iwText, std::vector &compo break; case MISSION_HERO: if (!isCustomComplete) - iwText.addReplacement(VLC->heroh->objects[m13489val]->name); + iwText.addReplacement(VLC->heroh->objects[m13489val]->getNameTextID()); break; case MISSION_PLAYER: if (!isCustomComplete) @@ -567,7 +567,7 @@ void CGSeerHut::setObjToKill() } else if(quest->missionType == CQuest::MISSION_KILL_HERO) { - quest->heroName = getHeroToKill(false)->name; + quest->heroName = getHeroToKill(false)->getNameTranslated(); quest->heroPortrait = getHeroToKill(false)->portrait; } } diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp index 20f0ed114..bbd50ea07 100644 --- a/lib/mapObjects/CommonConstructors.cpp +++ b/lib/mapObjects/CommonConstructors.cpp @@ -123,7 +123,7 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std auto heroTest = [&](const HeroTypeID & id) { - return hero->type->ID == id; + return hero->type->getId() == id; }; if(filters.count(templ->stringID)) diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 84b3cda6e..3a4765824 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -596,7 +596,7 @@ void CGCreature::giveReward(const CGHeroInstance * h) const if(iw.components.size()) { iw.text.addTxt(MetaString::ADVOB_TXT, 183); // % has found treasure - iw.text.addReplacement(h->name); + iw.text.addReplacement(h->getNameTranslated()); cb->showInfoDialog(&iw); } } @@ -1347,7 +1347,7 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const else //fix for mod artifacts with no event text { iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - iw.text.addReplacement(h->name); + iw.text.addReplacement(h->getNameTranslated()); } } } diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index edf9dec98..af6cb61ba 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -568,7 +568,7 @@ void CMap::checkForObjectives() boost::algorithm::replace_first(event.onFulfill, "%s", town->name); const CGHeroInstance *hero = dynamic_cast(cond.object); if (hero) - boost::algorithm::replace_first(event.onFulfill, "%s", hero->name); + boost::algorithm::replace_first(event.onFulfill, "%s", hero->getNameTranslated()); } break; @@ -580,7 +580,7 @@ void CMap::checkForObjectives() { const CGHeroInstance *hero = dynamic_cast(cond.object); if (hero) - boost::algorithm::replace_first(event.onFulfill, "%s", hero->name); + boost::algorithm::replace_first(event.onFulfill, "%s", hero->getNameTranslated()); } break; case EventCondition::TRANSPORT: diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 794e30fcc..652f97ff0 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -787,7 +787,7 @@ void CMapLoaderH3M::readPredefinedHeroes() bool hasCustomBio = reader.readBool(); if(hasCustomBio) { - hero->biography = reader.readString(); + hero->biographyCustom = reader.readString(); } // 0xFF is default, 00 male, 01 female @@ -825,7 +825,7 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero) { if(hero->artifactsWorn.size() || hero->artifactsInBackpack.size()) { - logGlobal->warn("Hero %s at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->name, hero->pos.toString()); + logGlobal->warn("Hero %s at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getNameTranslated(), hero->pos.toString()); hero->artifactsInBackpack.clear(); while(hero->artifactsWorn.size()) hero->eraseArtSlot(hero->artifactsWorn.begin()->first); @@ -1436,7 +1436,7 @@ void CMapLoaderH3M::readObjects() } else { - logGlobal->info("Hero placeholder: %s at %s", VLC->heroh->objects[htid]->name, objPos.toString()); + logGlobal->info("Hero placeholder: %s at %s", VLC->heroh->objects[htid]->getNameTranslated(), objPos.toString()); hp->power = 0; } @@ -1592,7 +1592,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const i { if(elem.heroId == nhi->subID) { - nhi->name = elem.name; + nhi->nameCustom = elem.name; nhi->portrait = elem.portrait; break; } @@ -1601,7 +1601,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const i bool hasName = reader.readBool(); if(hasName) { - nhi->name = reader.readString(); + nhi->nameCustom = reader.readString(); } if(map->version > EMapFormat::AB) { @@ -1666,7 +1666,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const i bool hasCustomBiography = reader.readBool(); if(hasCustomBiography) { - nhi->biography = reader.readString(); + nhi->biographyCustom = reader.readString(); } nhi->sex = reader.readUInt8(); @@ -1688,7 +1688,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const i if(nhi->spells.size()) { nhi->clear(); - logGlobal->warn("Hero %s subID=%d has spells set twice (in map properties and on adventure map instance). Using the latter set...", nhi->name, nhi->subID); + logGlobal->warn("Hero %s subID=%d has spells set twice (in map properties and on adventure map instance). Using the latter set...", nhi->getNameTranslated(), nhi->subID); } if(hasCustomSpells) @@ -1721,7 +1721,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const i .And(Selector::sourceType()(Bonus::HERO_BASE_SKILL)), nullptr); if(ps->size()) { - logGlobal->warn("Hero %s subID=%d has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", nhi->name, nhi->subID); + logGlobal->warn("Hero %s subID=%d has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", nhi->getNameTranslated(), nhi->subID); for(auto b : *ps) nhi->removeBonus(b); } diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index fec83720b..bc1df5ba2 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -524,18 +524,18 @@ void CMapFormatJson::serializePlayerInfo(JsonSerializeFormat & handler) if(hero) { auto heroData = handler.enterStruct(hero->instanceName); - heroData->serializeString("name", hero->name); + heroData->serializeString("name", hero->nameCustom); if(hero->ID == Obj::HERO) { std::string temp; if(hero->type) { - temp = hero->type->identifier; + temp = hero->type->getJsonKey(); } else { - temp = VLC->heroh->objects[hero->subID]->identifier; + temp = VLC->heroh->objects[hero->subID]->getJsonKey(); } handler.serializeString("type", temp); } diff --git a/lib/serializer/CSerializer.cpp b/lib/serializer/CSerializer.cpp index fe53ad072..153c0c22a 100644 --- a/lib/serializer/CSerializer.cpp +++ b/lib/serializer/CSerializer.cpp @@ -34,9 +34,9 @@ void CSerializer::addStdVecItems(CGameState *gs, LibClasses *lib) registerVectoredType(&gs->map->objects, [](const CGObjectInstance &obj){ return obj.id; }); registerVectoredType(&lib->heroh->objects, - [](const CHero &h){ return h.ID; }); + [](const CHero &h){ return h.getId(); }); registerVectoredType(&gs->map->allHeroes, - [](const CGHeroInstance &h){ return h.type->ID; }); + [](const CGHeroInstance &h){ return h.type->getId(); }); registerVectoredType(&lib->creh->objects, [](const CCreature &cre){ return cre.idNumber; }); registerVectoredType(&lib->arth->objects, diff --git a/lib/spells/AdventureSpellMechanics.cpp b/lib/spells/AdventureSpellMechanics.cpp index a42920771..8f072dc10 100644 --- a/lib/spells/AdventureSpellMechanics.cpp +++ b/lib/spells/AdventureSpellMechanics.cpp @@ -147,7 +147,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment InfoWindow iw; iw.player = parameters.caster->tempOwner; iw.text.addTxt(MetaString::GENERAL_TXT, 333);//%s is already in boat - iw.text.addReplacement(parameters.caster->name); + iw.text.addReplacement(parameters.caster->getNameTranslated()); env->apply(&iw); return ESpellCastResult::CANCEL; } @@ -170,7 +170,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment InfoWindow iw; iw.player = parameters.caster->tempOwner; iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed. - iw.text.addReplacement(parameters.caster->name); + iw.text.addReplacement(parameters.caster->getNameTranslated()); env->apply(&iw); return ESpellCastResult::OK; } @@ -236,7 +236,7 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironmen InfoWindow iw; iw.player = parameters.caster->tempOwner; iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed - iw.text.addReplacement(parameters.caster->name); + iw.text.addReplacement(parameters.caster->getNameTranslated()); env->apply(&iw); return ESpellCastResult::OK; } @@ -307,7 +307,7 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm InfoWindow iw; iw.player = parameters.caster->tempOwner; iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today. - iw.text.addReplacement(parameters.caster->name); + iw.text.addReplacement(parameters.caster->getNameTranslated()); env->apply(&iw); return ESpellCastResult::CANCEL; } diff --git a/lib/spells/effects/Summon.cpp b/lib/spells/effects/Summon.cpp index 83082b012..fc65c8dcd 100644 --- a/lib/spells/effects/Summon.cpp +++ b/lib/spells/effects/Summon.cpp @@ -80,7 +80,7 @@ bool Summon::applicable(Problem & problem, const Mechanics * m) const auto caster = dynamic_cast(m->caster); if(caster) { - text.addReplacement(caster->name); + text.addReplacement(caster->getNameTranslated()); text.addReplacement(MetaString::CRE_PL_NAMES, elemental->creatureIndex()); diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index e7a60417f..e260859f1 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -131,9 +131,7 @@ void Initializer::initialize(CGHeroInstance * o) if(!o->type) o->type = VLC->heroh->objects.at(o->subID); - o->name = o->type->getName(); o->sex = o->type->sex; - o->biography = o->type->biography; o->portrait = o->type->imageIndex; o->randomizeArmy(o->type->heroClass->faction); } @@ -237,15 +235,15 @@ void Inspector::updateProperties(CGHeroInstance * o) addProperty("Owner", o->tempOwner, o->ID == Obj::PRISON); //field is not editable for prison addProperty("Experience", o->exp, false); - addProperty("Hero class", o->type->heroClass->getName(), true); + addProperty("Hero class", o->type->heroClass->getNameTranslated(), true); { //Sex auto * delegate = new InspectorDelegate; delegate->options << "MALE" << "FEMALE"; addProperty("Sex", (o->sex ? "FEMALE" : "MALE"), delegate , false); } - addProperty("Name", o->name, false); - addProperty("Biography", o->biography, new MessageDelegate, false); + addProperty("Name", o->nameCustom, false); + addProperty("Biography", o->biographyCustom, new MessageDelegate, false); addProperty("Portrait", o->portrait, false); { //Hero type @@ -255,10 +253,10 @@ void Inspector::updateProperties(CGHeroInstance * o) if(map->allowedHeroes.at(i)) { if(o->ID == Obj::PRISON || (o->type && VLC->heroh->objects[i]->heroClass->getIndex() == o->type->heroClass->getIndex())) - delegate->options << QObject::tr(VLC->heroh->objects[i]->getName().c_str()); + delegate->options << QObject::tr(VLC->heroh->objects[i]->getNameTranslated().c_str()); } } - addProperty("Hero type", o->type->getName(), delegate, false); + addProperty("Hero type", o->type->getNameTranslated(), delegate, false); } } @@ -554,7 +552,7 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari o->sex = value.toString() == "MALE" ? 0 : 1; if(key == "Name") - o->name = value.toString().toStdString(); + o->nameCustom = value.toString().toStdString(); if(key == "Experience") o->exp = value.toInt(); @@ -563,12 +561,10 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari { for(auto t : VLC->heroh->objects) { - if(t->getName() == value.toString().toStdString()) + if(t->getNameTranslated() == value.toString().toStdString()) o->type = t.get(); } - o->name = o->type->getName(); o->sex = o->type->sex; - o->biography = o->type->biography; o->portrait = o->type->imageIndex; o->randomizeArmy(o->type->heroClass->faction); updateProperties(); //updating other properties after change diff --git a/mapeditor/inspector/questwidget.cpp b/mapeditor/inspector/questwidget.cpp index c7103a101..953408c6a 100644 --- a/mapeditor/inspector/questwidget.cpp +++ b/mapeditor/inspector/questwidget.cpp @@ -95,7 +95,7 @@ void QuestWidget::obtainData() case CQuest::Emission::MISSION_HERO: activeId = true; for(int i = 0; i < map.allowedHeroes.size(); ++i) - ui->targetId->addItem(QString::fromStdString(VLC->heroh->objects.at(i)->getName())); + ui->targetId->addItem(QString::fromStdString(VLC->heroh->objects.at(i)->getNameTranslated())); ui->targetId->setCurrentIndex(seerhut.quest->m13489val); break; case CQuest::Emission::MISSION_PLAYER: diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 2a5d3fdd8..85127ee8b 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -131,7 +131,7 @@ void MapController::repairMap() if(obj->ID == Obj::HERO) { nih->typeName = "hero"; - nih->subTypeName = type->heroClass->identifier; + nih->subTypeName = type->heroClass->getJsonKey(); } if(obj->ID == Obj::PRISON) { @@ -140,10 +140,6 @@ void MapController::repairMap() } nih->type = type; - if(nih->name.empty()) - nih->name = nih->type->name; - if(nih->biography.empty()) - nih->biography = nih->type->biography; if(nih->ID == Obj::HERO) //not prison nih->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front(); diff --git a/mapeditor/mapsettings.cpp b/mapeditor/mapsettings.cpp index 232eeb806..000318f0c 100644 --- a/mapeditor/mapsettings.cpp +++ b/mapeditor/mapsettings.cpp @@ -58,7 +58,7 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) : } for(int i = 0; i < controller.map()->allowedHeroes.size(); ++i) { - auto * item = new QListWidgetItem(QString::fromStdString(VLC->heroh->objects[i]->getName())); + auto * item = new QListWidgetItem(QString::fromStdString(VLC->heroh->objects[i]->getNameTranslated())); item->setData(Qt::UserRole, QVariant::fromValue(i)); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(controller.map()->allowedHeroes[i] ? Qt::Checked : Qt::Unchecked); diff --git a/mapeditor/validator.cpp b/mapeditor/validator.cpp index f2833909a..2fd622d69 100644 --- a/mapeditor/validator.cpp +++ b/mapeditor/validator.cpp @@ -119,7 +119,7 @@ std::list Validator::validate(const CMap * map) if(ins->type) { if(!map->allowedHeroes[ins->type->getId().getNum()]) - issues.emplace_back(QString("Hero %1 is prohibited by map settings").arg(ins->type->getName().c_str()), false); + issues.emplace_back(QString("Hero %1 is prohibited by map settings").arg(ins->type->getNameTranslated().c_str()), false); } else issues.emplace_back(QString("Hero %1 has an empty type and must be removed").arg(ins->instanceName.c_str()), true); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 589154c41..9d39f824f 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -423,7 +423,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero) } // give primary skill - logGlobal->trace("%s got level %d", hero->name, hero->level); + logGlobal->trace("%s got level %d", hero->getNameTranslated(), hero->level); auto primarySkill = hero->nextPrimarySkill(getRandomGenerator()); SetPrimSkill sps; @@ -633,7 +633,7 @@ void CGameHandler::changePrimSkill(const CGHeroInstance * hero, PrimarySkill::Pr InfoWindow iw; iw.player = hero->tempOwner; iw.text.addTxt(MetaString::GENERAL_TXT, 1); //can gain no more XP - iw.text.addReplacement(hero->name); + iw.text.addReplacement(hero->getNameTextID()); sendAndApply(&iw); } } @@ -856,7 +856,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con InfoWindow iw; iw.player = finishingBattle->winnerHero->tempOwner; iw.text.addTxt(MetaString::GENERAL_TXT, 221); //Through eagle-eyed observation, %s is able to learn %s - iw.text.addReplacement(finishingBattle->winnerHero->name); + iw.text.addReplacement(finishingBattle->winnerHero->getNameTextID()); std::ostringstream names; for (int i = 0; i < cs.spells.size(); i++) @@ -2392,7 +2392,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo auto doMove = [&](TryMoveHero::EResult result, EGuardLook lookForGuards, EVisitDest visitDest, ELEaveTile leavingTile) -> bool { - LOG_TRACE_PARAMS(logGlobal, "Hero %s starts movement from %s to %s", h->name % tmh.start.toString() % tmh.end.toString()); + LOG_TRACE_PARAMS(logGlobal, "Hero %s starts movement from %s to %s", h->getNameTranslated() % tmh.start.toString() % tmh.end.toString()); auto moveQuery = std::make_shared(this, tmh, h); queries.addQuery(moveQuery); @@ -2422,7 +2422,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo } queries.popIfTop(moveQuery); - logGlobal->trace("Hero %s ends movement", h->name); + logGlobal->trace("Hero %s ends movement", h->getNameTranslated()); return result != TryMoveHero::FAILED; }; @@ -2822,7 +2822,7 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t iw.components.push_back(Component(Component::SEC_SKILL, 18, ScholarSkillLevel, 0)); iw.text.addTxt(MetaString::GENERAL_TXT, 139);//"%s, who has studied magic extensively, - iw.text.addReplacement(h1->name); + iw.text.addReplacement(h1->getNameTextID()); if (!cs2.spells.empty())//if found new spell - apply { @@ -2840,7 +2840,7 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t } } iw.text.addTxt(MetaString::GENERAL_TXT, 142);//from %s - iw.text.addReplacement(h2->name); + iw.text.addReplacement(h2->getNameTextID()); sendAndApply(&cs2); } @@ -2864,7 +2864,7 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t default: iw.text << ", "; } } iw.text.addTxt(MetaString::GENERAL_TXT, 148);//from %s - iw.text.addReplacement(h2->name); + iw.text.addReplacement(h2->getNameTextID()); sendAndApply(&cs1); } sendAndApply(&iw); diff --git a/server/CQuery.cpp b/server/CQuery.cpp index 6ae3375c0..b42699e27 100644 --- a/server/CQuery.cpp +++ b/server/CQuery.cpp @@ -486,7 +486,7 @@ void CHeroMovementQuery::onExposure(QueryPtr topQuery) if(visitDestAfterVictory && hero->tempOwner == players[0]) //hero still alive, so he won with the guard //TODO what if there were H4-like escape? we should also check pos { - logGlobal->trace("Hero %s after victory over guard finishes visit to %s", hero->name, tmh.end.toString()); + logGlobal->trace("Hero %s after victory over guard finishes visit to %s", hero->getNameTranslated(), tmh.end.toString()); //finish movement visitDestAfterVictory = false; gh->visitObjectOnTile(*gh->getTile(hero->convertToVisitablePos(tmh.end)), hero); From d2b837b1162defb35baeb938582a05b827075688 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 2 Jan 2023 18:00:51 +0200 Subject: [PATCH 182/197] All creature-related texts go through translator --- AI/BattleAI/AttackPossibility.cpp | 4 +- AI/BattleAI/BattleAI.cpp | 4 +- AI/BattleAI/PotentialTargets.cpp | 4 +- AI/BattleAI/StackWithBonuses.cpp | 2 +- AI/Nullkiller/AIGateway.cpp | 2 +- client/battle/BattleInterfaceClasses.cpp | 2 +- client/battle/BattleProjectileController.cpp | 4 +- client/lobby/CBonusSelection.cpp | 2 +- client/widgets/CComponent.cpp | 9 +++- client/widgets/CGarrisonInt.cpp | 12 ++--- client/windows/CCastleInterface.cpp | 18 +++---- client/windows/CCreatureWindow.cpp | 6 +-- client/windows/CTradeWindow.cpp | 6 +-- client/windows/GUIClasses.cpp | 12 ++--- include/vcmi/Creature.h | 13 ++++- lib/CBonusTypeHandler.cpp | 2 +- lib/CCreatureHandler.cpp | 53 +++++++++++++------- lib/CCreatureHandler.h | 29 ++++++----- lib/CCreatureSet.cpp | 8 +-- lib/CGameState.cpp | 4 +- lib/CStack.cpp | 4 +- lib/CStack.h | 2 +- lib/CTownHandler.cpp | 2 +- lib/HeroBonus.cpp | 6 +-- lib/battle/BattleInfo.cpp | 2 +- lib/battle/CBattleInfoCallback.cpp | 2 +- lib/battle/CUnitState.cpp | 2 +- lib/mapObjects/CBank.cpp | 6 +-- lib/mapObjects/MiscObjects.cpp | 2 +- lib/mapping/CMap.cpp | 2 +- mapeditor/inspector/armywidget.cpp | 4 +- mapeditor/inspector/rewardswidget.cpp | 2 +- server/CGameHandler.cpp | 6 +-- 33 files changed, 138 insertions(+), 100 deletions(-) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index d998706ab..7dd30449b 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -213,8 +213,8 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state); logAi->debug("BattleAI best AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld", - attackInfo.attacker->unitType()->identifier, - attackInfo.defender->unitType()->identifier, + attackInfo.attacker->unitType()->getJsonKey(), + attackInfo.defender->unitType()->getJsonKey(), (int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(), bestAp.defenderDamageReduce, bestAp.attackerDamageReduce, bestAp.collateralDamageReduce, bestAp.shootersBlockedDmg); diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index ebb154e8b..888e51842 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -207,8 +207,8 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) } logAi->debug("BattleAI: %s -> %s x %d, %s, from %d curpos %d dist %d speed %d: +%lld -%lld = %lld", - bestAttack.attackerState->unitType()->identifier, - bestAttack.affectedUnits[0]->unitType()->identifier, + bestAttack.attackerState->unitType()->getJsonKey(), + bestAttack.affectedUnits[0]->unitType()->getJsonKey(), (int)bestAttack.affectedUnits[0]->getCount(), action, (int)bestAttack.from, (int)bestAttack.attack.attacker->getPosition().hex, bestAttack.attack.chargeDistance, bestAttack.attack.attacker->Speed(0, true), bestAttack.defenderDamageReduce, bestAttack.attackerDamageReduce, bestAttack.attackValue() diff --git a/AI/BattleAI/PotentialTargets.cpp b/AI/BattleAI/PotentialTargets.cpp index 8fc3911cf..5d4aac5e5 100644 --- a/AI/BattleAI/PotentialTargets.cpp +++ b/AI/BattleAI/PotentialTargets.cpp @@ -90,8 +90,8 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet auto & bestAp = possibleAttacks[0]; logGlobal->info("Battle AI best: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld", - bestAp.attack.attacker->unitType()->identifier, - state.battleGetUnitByPos(bestAp.dest)->unitType()->identifier, + bestAp.attack.attacker->unitType()->getJsonKey(), + state.battleGetUnitByPos(bestAp.dest)->unitType()->getJsonKey(), (int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(), bestAp.defenderDamageReduce, bestAp.attackerDamageReduce, bestAp.collateralDamageReduce, bestAp.shootersBlockedDmg); } diff --git a/AI/BattleAI/StackWithBonuses.cpp b/AI/BattleAI/StackWithBonuses.cpp index e698c3014..27d223359 100644 --- a/AI/BattleAI/StackWithBonuses.cpp +++ b/AI/BattleAI/StackWithBonuses.cpp @@ -205,7 +205,7 @@ std::string StackWithBonuses::getDescription() const oss << unitOwner().getStr(); oss << " battle stack [" << unitId() << "]: " << getCount() << " of "; if(type) - oss << type->namePl; + oss << type->getJsonKey(); else oss << "[UNDEFINED TYPE]"; diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 4689a4581..d395e4392 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -737,7 +737,7 @@ bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj) { myCb->upgradeCreature(obj, SlotID(i), ui.newID[0]); upgraded = true; - logAi->debug("Upgraded %d %s to %s", s->count, ui.oldID.toCreature()->namePl, ui.newID[0].toCreature()->namePl); + logAi->debug("Upgraded %d %s to %s", s->count, ui.oldID.toCreature()->getNamePluralTranslated(), ui.newID[0].toCreature()->getNamePluralTranslated()); } } } diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index f6c4415df..3208e7bde 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -551,7 +551,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface if(best != stacks.end()) //should be always but to be safe... { icons.push_back(std::make_shared("TWCRPORT", (*best)->type->getIconIndex(), 0, xs[i], 38)); - sideNames[i] = (*best)->type->getPluralName(); + sideNames[i] = (*best)->type->getNamePluralTranslated(); } } } diff --git a/client/battle/BattleProjectileController.cpp b/client/battle/BattleProjectileController.cpp index 571aca45a..f9862fa7e 100644 --- a/client/battle/BattleProjectileController.cpp +++ b/client/battle/BattleProjectileController.cpp @@ -158,7 +158,7 @@ const CCreature & BattleProjectileController::getShooter(const CStack * stack) c if(creature->animation.missleFrameAngles.empty()) { - logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->nameSing); + logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->getNameSingularTranslated()); creature = CGI->creh->objects[CreatureID::ARCHER]; } @@ -313,7 +313,7 @@ void BattleProjectileController::createProjectile(const CStack * shooter, Point std::shared_ptr projectile; if (stackUsesRayProjectile(shooter) && stackUsesMissileProjectile(shooter)) { - logAnim->error("Mod error: Creature '%s' has both missile and ray projectiles configured. Mod should be fixed. Using ray projectile configuration...", shooterInfo.nameSing); + logAnim->error("Mod error: Creature '%s' has both missile and ray projectiles configured. Mod should be fixed. Using ray projectile configuration...", shooterInfo.getNameSingularTranslated()); } if (stackUsesRayProjectile(shooter)) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 023fd1183..796b60fb5 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -182,7 +182,7 @@ void CBonusSelection::createBonusesIcons() picNumber = bonDescs[i].info2 + 2; desc = CGI->generaltexth->allTexts[717]; boost::algorithm::replace_first(desc, "%d", boost::lexical_cast(bonDescs[i].info3)); - boost::algorithm::replace_first(desc, "%s", CGI->creatures()->getByIndex(bonDescs[i].info2)->getPluralName()); + boost::algorithm::replace_first(desc, "%s", CGI->creatures()->getByIndex(bonDescs[i].info2)->getNamePluralTranslated()); break; case CScenarioTravel::STravelBonus::BUILDING: { diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index 65b26fbc3..1bf52cca0 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -198,7 +198,14 @@ std::string CComponent::getSubtitleInternal() case primskill: return boost::str(boost::format("%+d %s") % val % (subtype < 4 ? CGI->generaltexth->primarySkillNames[subtype] : CGI->generaltexth->allTexts[387])); case secskill: return CGI->generaltexth->levels[val-1] + "\n" + CGI->skillh->getByIndex(subtype)->getNameTranslated(); case resource: return boost::lexical_cast(val); - case creature: return (val? boost::lexical_cast(val) + " " : "") + CGI->creh->objects[subtype]->*(val != 1 ? &CCreature::namePl : &CCreature::nameSing); + case creature: + { + auto creature = CGI->creh->getByIndex(subtype); + if ( val ) + return boost::lexical_cast(val) + " " + (val > 1 ? creature->getNamePluralTranslated() : creature->getNameSingularTranslated()); + else + return val > 1 ? creature->getNamePluralTranslated() : creature->getNameSingularTranslated(); + } case artifact: return CGI->artifacts()->getByIndex(subtype)->getName(); case experience: { diff --git a/client/widgets/CGarrisonInt.cpp b/client/widgets/CGarrisonInt.cpp index a7e46991d..0b020c926 100644 --- a/client/widgets/CGarrisonInt.cpp +++ b/client/widgets/CGarrisonInt.cpp @@ -53,18 +53,18 @@ void CGarrisonSlot::hover (bool on) if(owner->getSelection() == this) { temp = CGI->generaltexth->tcommands[4]; //View %s - boost::algorithm::replace_first(temp,"%s",creature->nameSing); + boost::algorithm::replace_first(temp,"%s",creature->getNameSingularTranslated()); } else if (owner->getSelection()->creature == creature) { temp = CGI->generaltexth->tcommands[2]; //Combine %s armies - boost::algorithm::replace_first(temp,"%s",creature->nameSing); + boost::algorithm::replace_first(temp,"%s",creature->getNameSingularTranslated()); } else if (owner->getSelection()->creature) { temp = CGI->generaltexth->tcommands[7]; //Exchange %s with %s - boost::algorithm::replace_first(temp,"%s",owner->getSelection()->creature->nameSing); - boost::algorithm::replace_first(temp,"%s",creature->nameSing); + boost::algorithm::replace_first(temp,"%s",owner->getSelection()->creature->getNameSingularTranslated()); + boost::algorithm::replace_first(temp,"%s",creature->getNameSingularTranslated()); } else { @@ -92,7 +92,7 @@ void CGarrisonSlot::hover (bool on) { temp = CGI->generaltexth->tcommands[32]; //Select %s (visiting) } - boost::algorithm::replace_first(temp,"%s",creature->nameSing); + boost::algorithm::replace_first(temp,"%s",creature->getNameSingularTranslated()); } } else @@ -110,7 +110,7 @@ void CGarrisonSlot::hover (bool on) else { temp = CGI->generaltexth->tcommands[6]; //Move %s - boost::algorithm::replace_first(temp,"%s",owner->getSelection()->creature->nameSing); + boost::algorithm::replace_first(temp,"%s",owner->getSelection()->creature->getNameSingularTranslated()); } } else diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index dfd288945..68ec7b256 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -242,7 +242,7 @@ std::string CBuildingRect::getSubtitle()//hover text for building if(availableCreatures.size()) { int creaID = availableCreatures.back();//taking last of available creatures - return CGI->generaltexth->allTexts[16] + " " + CGI->creh->objects.at(creaID)->namePl; + return CGI->generaltexth->allTexts[16] + " " + CGI->creh->objects.at(creaID)->getNamePluralTranslated(); } else { @@ -284,7 +284,7 @@ CDwellingInfoBox::CDwellingInfoBox(int centerX, int centerY, const CGTownInstanc const CCreature * creature = CGI->creh->objects.at(Town->creatures.at(level).second.back()); - title = std::make_shared(80, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, creature->namePl); + title = std::make_shared(80, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, creature->getNamePluralTranslated()); animation = std::make_shared(30, 44, creature, true, true); std::string text = boost::lexical_cast(Town->creatures.at(level).first); @@ -1036,7 +1036,7 @@ void CCreaInfo::update() void CCreaInfo::hover(bool on) { std::string message = CGI->generaltexth->allTexts[588]; - boost::algorithm::replace_first(message, "%s", creature->namePl); + boost::algorithm::replace_first(message, "%s", creature->getNamePluralTranslated()); if(on) { @@ -1064,7 +1064,7 @@ void CCreaInfo::clickLeft(tribool down, bool previousState) std::string CCreaInfo::genGrowthText() { GrowthInfo gi = town->getGrowthInfo(level); - std::string descr = boost::str(boost::format(CGI->generaltexth->allTexts[589]) % creature->nameSing % gi.totalGrowth()); + std::string descr = boost::str(boost::format(CGI->generaltexth->allTexts[589]) % creature->getNameSingularTranslated() % gi.totalGrowth()); for(const GrowthInfo::Entry & entry : gi.entries) descr +="\n" + entry.description; @@ -1641,9 +1641,9 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance * if(getMyCreature() != nullptr) { - hoverText = boost::str(boost::format(CGI->generaltexth->tcommands[21]) % getMyCreature()->namePl); + hoverText = boost::str(boost::format(CGI->generaltexth->tcommands[21]) % getMyCreature()->getNamePluralTranslated()); new CCreaturePic(159, 4, getMyCreature(), false); - new CLabel(78, 11, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyCreature()->namePl); + new CLabel(78, 11, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyCreature()->getNamePluralTranslated()); Rect sizes(287, 4, 96, 18); values.push_back(std::make_shared(sizes, CGI->generaltexth->allTexts[190], CGI->generaltexth->fcommands[0], getMyCreature()->getAttack(false))); @@ -1807,15 +1807,15 @@ CBlacksmithDialog::CBlacksmithDialog(bool possible, CreatureID creMachineID, Art anim->clipRect(113,125,200,150); title = std::make_shared(165, 28, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, - boost::str(boost::format(CGI->generaltexth->allTexts[274]) % creature->nameSing)); + boost::str(boost::format(CGI->generaltexth->allTexts[274]) % creature->getNameSingularTranslated())); costText = std::make_shared(165, 218, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->jktexts[43]); costValue = std::make_shared(165, 290, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, boost::lexical_cast(aid.toArtifact(CGI->artifacts())->getPrice())); - std::string text = boost::str(boost::format(CGI->generaltexth->allTexts[595]) % creature->nameSing); + std::string text = boost::str(boost::format(CGI->generaltexth->allTexts[595]) % creature->getNameSingularTranslated()); buy = std::make_shared(Point(42, 312), "IBUY30.DEF", CButton::tooltip(text), [&](){ close(); }, SDLK_RETURN); - text = boost::str(boost::format(CGI->generaltexth->allTexts[596]) % creature->nameSing); + text = boost::str(boost::format(CGI->generaltexth->allTexts[596]) % creature->getNameSingularTranslated()); cancel = std::make_shared(Point(224, 312), "ICANCEL.DEF", CButton::tooltip(text), [&](){ close(); }, SDLK_ESCAPE); if(possible) diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index f51c13958..bcbf999a0 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -86,9 +86,9 @@ public: std::string getName() const { if(commander) - return commander->type->nameSing; + return commander->type->getNameSingularTranslated(); else - return creature->namePl; + return creature->getNamePluralTranslated(); } private: @@ -866,7 +866,7 @@ std::string CStackWindow::generateStackExpDescription() tier = 0; int number; std::string expText = CGI->generaltexth->translate("vcmi.stackExperience.description"); - boost::replace_first(expText, "%s", creature->namePl); + boost::replace_first(expText, "%s", creature->getNamePluralTranslated()); boost::replace_first(expText, "%s", CGI->generaltexth->translate("vcmi.stackExperience.rank", rank)); boost::replace_first(expText, "%i", boost::lexical_cast(rank)); boost::replace_first(expText, "%i", boost::lexical_cast(stack->experience)); diff --git a/client/windows/CTradeWindow.cpp b/client/windows/CTradeWindow.cpp index 31076c190..545ea8a35 100644 --- a/client/windows/CTradeWindow.cpp +++ b/client/windows/CTradeWindow.cpp @@ -244,7 +244,7 @@ void CTradeWindow::CTradeableItem::hover(bool on) { case CREATURE: case CREATURE_PLACEHOLDER: - GH.statusbar->write(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->namePl)); + GH.statusbar->write(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->getNamePluralTranslated())); break; case ARTIFACT_PLACEHOLDER: if(id < 0) @@ -285,9 +285,9 @@ std::string CTradeWindow::CTradeableItem::getName(int number) const return CGI->generaltexth->restypes[id]; case CREATURE: if(number == 1) - return CGI->creh->objects[id]->nameSing; + return CGI->creh->objects[id]->getNameSingularTranslated(); else - return CGI->creh->objects[id]->namePl; + return CGI->creh->objects[id]->getNamePluralTranslated(); case ARTIFACT_TYPE: case ARTIFACT_INSTANCE: return CGI->artifacts()->getByIndex(id)->getName(); diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 558145a37..874b5b9d4 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -140,7 +140,7 @@ void CRecruitmentWindow::select(std::shared_ptr card) totalCostValue->set(card->creature->cost * maxAmount); //Recruit %s - title->setText(boost::str(boost::format(CGI->generaltexth->tcommands[21]) % card->creature->namePl)); + title->setText(boost::str(boost::format(CGI->generaltexth->tcommands[21]) % card->creature->getNamePluralTranslated())); maxButton->block(maxAmount == 0); slider->block(maxAmount == 0); @@ -158,7 +158,7 @@ void CRecruitmentWindow::buy() if(dst->ID == Obj::HERO) { txt = CGI->generaltexth->allTexts[425]; //The %s would join your hero, but there aren't enough provisions to support them. - boost::algorithm::replace_first(txt, "%s", slider->getValue() > 1 ? CGI->creh->objects[crid]->namePl : CGI->creh->objects[crid]->nameSing); + boost::algorithm::replace_first(txt, "%s", slider->getValue() > 1 ? CGI->creh->objects[crid]->getNamePluralTranslated() : CGI->creh->objects[crid]->getNameSingularTranslated()); } else { @@ -332,7 +332,7 @@ CSplitWindow::CSplitWindow(const CCreature * creature, std::function(Point(21, 194), 257, std::bind(&CSplitWindow::sliderMoved, this, _1), 0, sliderPosition, rightAmount - rightMin, true); std::string titleStr = CGI->generaltexth->allTexts[256]; - boost::algorithm::replace_first(titleStr,"%s", creature->namePl); + boost::algorithm::replace_first(titleStr,"%s", creature->getNamePluralTranslated()); title = std::make_shared(150, 34, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, titleStr); } @@ -1681,7 +1681,7 @@ CGarrisonWindow::CGarrisonWindow(const CArmedInstance * up, const CGHeroInstance if(up->Slots().size() > 0) { titleText = CGI->generaltexth->allTexts[35]; - boost::algorithm::replace_first(titleText, "%s", up->Slots().begin()->second->type->namePl); + boost::algorithm::replace_first(titleText, "%s", up->Slots().begin()->second->type->getNamePluralTranslated()); } else { @@ -1878,9 +1878,9 @@ std::string CHillFortWindow::getTextForSlot(SlotID slot) std::string str = CGI->generaltexth->allTexts[318]; int amount = hero->getStackCount(slot); if(amount == 1) - boost::algorithm::replace_first(str,"%s",hero->getCreature(slot)->nameSing); + boost::algorithm::replace_first(str,"%s",hero->getCreature(slot)->getNameSingularTranslated()); else - boost::algorithm::replace_first(str,"%s",hero->getCreature(slot)->namePl); + boost::algorithm::replace_first(str,"%s",hero->getCreature(slot)->getNamePluralTranslated()); return str; } diff --git a/include/vcmi/Creature.h b/include/vcmi/Creature.h index 1ada50ce2..91996e39d 100644 --- a/include/vcmi/Creature.h +++ b/include/vcmi/Creature.h @@ -18,9 +18,18 @@ class CreatureID; class DLL_LINKAGE Creature : public EntityWithBonuses { +protected: + using EntityWithBonuses::getName; + + virtual std::string getNameTranslated() const = 0; + virtual std::string getNameTextID() const = 0; public: - virtual const std::string & getPluralName() const = 0; - virtual const std::string & getSingularName() const = 0; + virtual std::string getNamePluralTranslated() const = 0; + virtual std::string getNameSingularTranslated() const = 0; + + virtual std::string getNamePluralTextID() const = 0; + virtual std::string getNameSingularTextID() const = 0; + virtual uint32_t getMaxHealth() const = 0; virtual int32_t getAdvMapAmountMin() const = 0; diff --git a/lib/CBonusTypeHandler.cpp b/lib/CBonusTypeHandler.cpp index 2f4f6a8e4..8e0047f34 100644 --- a/lib/CBonusTypeHandler.cpp +++ b/lib/CBonusTypeHandler.cpp @@ -145,7 +145,7 @@ std::string CBonusTypeHandler::bonusToString(const std::shared_ptr & bonu else if(name == "subtype.creature") { const CreatureID cre(bonus->subtype); - return cre.toCreature()->namePl; + return cre.toCreature()->getNamePluralTranslated(); } else if(name == "subtype.spell") { diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index f4b5f47bd..b1712c213 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -35,7 +35,7 @@ int32_t CCreature::getIconIndex() const const std::string & CCreature::getName() const { - return nameSing;//??? + return identifier; } const std::string & CCreature::getJsonKey() const @@ -64,16 +64,6 @@ uint32_t CCreature::getMaxHealth() const return CBonusSystemNode::MaxHealth(); } -const std::string & CCreature::getPluralName() const -{ - return namePl; -} - -const std::string & CCreature::getSingularName() const -{ - return nameSing; -} - int32_t CCreature::getAdvMapAmountMin() const { return ammMin; @@ -170,6 +160,36 @@ int32_t CCreature::getCost(int32_t resIndex) const return 0; } +std::string CCreature::getNameTranslated() const +{ + return getNameSingularTranslated(); +} + +std::string CCreature::getNamePluralTranslated() const +{ + return VLC->generaltexth->translate(getNamePluralTextID()); +} + +std::string CCreature::getNameSingularTranslated() const +{ + return VLC->generaltexth->translate(getNameSingularTextID()); +} + +std::string CCreature::getNameTextID() const +{ + return getNameSingularTextID(); +} + +std::string CCreature::getNamePluralTextID() const +{ + return TextIdentifier("creatures", modScope, identifier, "name", "plural" ).get(); +} + +std::string CCreature::getNameSingularTextID() const +{ + return TextIdentifier("creatures", modScope, identifier, "name", "singular" ).get(); +} + int CCreature::getQuantityID(const int & quantity) { if (quantity<5) @@ -281,7 +301,7 @@ bool CCreature::valid() const std::string CCreature::nodeName() const { - return "\"" + namePl + "\""; + return "\"" + getNamePluralTextID() + "\""; } bool CCreature::isItNativeTerrain(TerrainId terrain) const @@ -344,12 +364,6 @@ void CCreature::updateFrom(const JsonNode & data) void CCreature::serializeJson(JsonSerializeFormat & handler) { - { - auto nameNode = handler.enterStruct("name"); - handler.serializeString("singular", nameSing); - handler.serializeString("plural", namePl); - } - handler.serializeInt("fightValue", fightValue); handler.serializeInt("aiValue", AIValue); handler.serializeInt("growth", growth); @@ -599,6 +613,9 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json cre->cost = Res::ResourceSet(node["cost"]); + VLC->generaltexth->registerString(cre->getNameSingularTextID(), node["name"]["singular"].String()); + VLC->generaltexth->registerString(cre->getNamePluralTextID(), node["name"]["plural"].String()); + cre->addBonus(node["hitPoints"].Integer(), Bonus::STACK_HEALTH); cre->addBonus(node["speed"].Integer(), Bonus::STACKS_SPEED); cre->addBonus(node["attack"].Integer(), Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK); diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 0912dd124..ae96b590e 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -29,16 +29,21 @@ class JsonSerializeFormat; class DLL_LINKAGE CCreature : public Creature, public CBonusSystemNode { -public: + friend class CCreatureHandler; + std::string modScope; std::string identifier; - std::string nameRef; // reference name, stringID - std::string nameSing;// singular name, e.g. Centaur - std::string namePl; // plural name, e.g. Centaurs +// std::string nameRef; // reference name, stringID +// std::string nameSing;// singular name, e.g. Centaur +// std::string namePl; // plural name, e.g. Centaurs - std::string abilityText; //description of abilities + const std::string & getName() const override; + std::string getNameTranslated() const override; + std::string getNameTextID() const override; +public: CreatureID idNumber; + TFaction faction; ui8 level; // 0 - unknown; 1-7 for "usual" creatures @@ -132,6 +137,12 @@ public: ArtifactID warMachine; + std::string getNamePluralTranslated() const override; + std::string getNameSingularTranslated() const override; + + std::string getNamePluralTextID() const override; + std::string getNameSingularTextID() const override; + bool isItNativeTerrain(TerrainId terrain) const; /** Returns creature native terrain considering some terrain bonuses. @@ -142,13 +153,10 @@ public: TerrainId getNativeTerrain() const; int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getName() const override; const std::string & getJsonKey() const override; void registerIcons(const IconRegistar & cb) const override; CreatureID getId() const override; virtual const IBonusBearer * accessBonuses() const override; - const std::string & getPluralName() const override; - const std::string & getSingularName() const override; uint32_t getMaxHealth() const override; int32_t getAdvMapAmountMin() const override; @@ -199,9 +207,6 @@ public: template void serialize(Handler &h, const int version) { h & static_cast(*this); - h & namePl; - h & nameSing; - h & nameRef; h & cost; h & upgrades; h & fightValue; @@ -211,7 +216,6 @@ public: h & ammMin; h & ammMax; h & level; - h & abilityText; h & animDefName; h & advMapDef; h & iconIndex; @@ -226,6 +230,7 @@ public: h & doubleWide; h & special; h & identifier; + h & modScope; h & warMachine; } diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index e1551c9b2..1ff18ee06 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -384,7 +384,7 @@ std::string CCreatureSet::getArmyDescription() const std::vector guards; for(auto & elem : stacks) { - auto str = boost::str(boost::format("%s %s") % getRoughAmount(elem.first, 2) % getCreature(elem.first)->namePl); + auto str = boost::str(boost::format("%s %s") % getRoughAmount(elem.first, 2) % getCreature(elem.first)->getNamePluralTranslated()); guards.push_back(str); } if(guards.size()) @@ -843,7 +843,7 @@ std::string CStackInstance::nodeName() const std::ostringstream oss; oss << "Stack of " << count << " of "; if(type) - oss << type->namePl; + oss << type->getNamePluralTextID(); else if(idRand >= 0) oss << "[no type, idRand=" << idRand << "]"; else @@ -875,7 +875,7 @@ CreatureID CStackInstance::getCreatureID() const std::string CStackInstance::getName() const { - return (count > 1) ? type->namePl : type->nameSing; + return (count > 1) ? type->getNamePluralTranslated() : type->getNameSingularTranslated(); } ui64 CStackInstance::getPower() const @@ -1044,7 +1044,7 @@ void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler) { if(type) { - std::string typeName = type->identifier; + std::string typeName = type->getJsonKey(); handler.serializeString("type", typeName); } } diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 3f72410a7..8ba1e26f8 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -99,7 +99,7 @@ void MetaString::getLocalString(const std::pair &txt, std::string &dst { auto cre = CreatureID(ser).toCreature(VLC->creatures()); if(cre) - dst = cre->getPluralName(); + dst = cre->getNamePluralTranslated(); else dst = "#!#"; } @@ -107,7 +107,7 @@ void MetaString::getLocalString(const std::pair &txt, std::string &dst { auto cre = CreatureID(ser).toCreature(VLC->creatures()); if(cre) - dst = cre->getSingularName(); + dst = cre->getNameSingularTranslated(); else dst = "#!#"; } diff --git a/lib/CStack.cpp b/lib/CStack.cpp index da227fa3f..45ad53d1f 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -181,7 +181,7 @@ std::string CStack::nodeName() const oss << owner.getStr(); oss << " battle stack [" << ID << "]: " << getCount() << " of "; if(type) - oss << type->namePl; + oss << type->getNamePluralTextID(); else oss << "[UNDEFINED TYPE]"; @@ -320,7 +320,7 @@ bool CStack::isMeleeAttackPossible(const battle::Unit * attacker, const battle:: std::string CStack::getName() const { - return (getCount() == 1) ? type->nameSing : type->namePl; //War machines can't use base + return (getCount() == 1) ? type->getNameSingularTranslated() : type->getNamePluralTranslated(); //War machines can't use base } bool CStack::canBeHealed() const diff --git a/lib/CStack.h b/lib/CStack.h index a3805543e..9e788c3a8 100644 --- a/lib/CStack.h +++ b/lib/CStack.h @@ -128,7 +128,7 @@ public: else if(!army || extSlot == SlotID() || !army->hasStackAtSlot(extSlot)) { base = nullptr; - logGlobal->warn("%s doesn't have a base stack!", type->nameSing); + logGlobal->warn("%s doesn't have a base stack!", type->getNameSingularTranslated()); } else { diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 67491465d..ad95127ae 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -758,7 +758,7 @@ void CTownHandler::loadSiegeScreen(CTown &town, const JsonNode & source) if(!(*VLC->creh)[crId]->animation.missleFrameAngles.size()) logMod->error("Mod '%s' error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Siege will not work properly!" , town.faction->name - , (*VLC->creh)[crId]->nameSing); + , (*VLC->creh)[crId]->getNameSingularTranslated()); town.clientInfo.siegeShooter = crId; }); diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index eb21e5063..d85364e46 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -1600,7 +1600,7 @@ std::string Bonus::Description() const str << SpellID(sid).toSpell(VLC->spells())->getNameTranslated(); break; case CREATURE_ABILITY: - str << VLC->creh->objects[sid]->namePl; + str << VLC->creh->objects[sid]->getNamePluralTranslated(); break; case SECONDARY_SKILL: str << VLC->skillh->getByIndex(sid)->getNameTranslated(); @@ -2006,7 +2006,7 @@ void CCreatureTypeLimiter::setCreature (CreatureID id) std::string CCreatureTypeLimiter::toString() const { boost::format fmt("CCreatureTypeLimiter(creature=%s, includeUpgrades=%s)"); - fmt % creature->identifier % (includeUpgrades ? "true" : "false"); + fmt % creature->getJsonKey() % (includeUpgrades ? "true" : "false"); return fmt.str(); } @@ -2015,7 +2015,7 @@ JsonNode CCreatureTypeLimiter::toJsonNode() const JsonNode root(JsonNode::JsonType::DATA_STRUCT); root["type"].String() = "CREATURE_TYPE_LIMITER"; - root["parameters"].Vector().push_back(JsonUtils::stringNode(creature->identifier)); + root["parameters"].Vector().push_back(JsonUtils::stringNode(creature->getJsonKey())); root["parameters"].Vector().push_back(JsonUtils::boolNode(includeUpgrades)); return root; diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 72b07cf3c..541df08fb 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -52,7 +52,7 @@ void BattleInfo::calculateCasualties(std::map * casualties) const const CStack * const st = elem; si32 killed = st->getKilled(); if(killed > 0) - casualties[st->side][st->getCreature()->idNumber] += killed; + casualties[st->side][st->getCreature()->getId()] += killed; } } diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index ee92c1356..285d73bf5 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -701,7 +701,7 @@ bool CBattleInfoCallback::battleCanAttack(const CStack * stack, const CStack * t if(!battleMatchOwner(stack, target)) return false; - auto &id = stack->getCreature()->idNumber; + auto id = stack->getCreature()->getId(); if (id == CreatureID::FIRST_AID_TENT || id == CreatureID::CATAPULT) return false; diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index 0d97fea3d..06745b334 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -443,7 +443,7 @@ int32_t CUnitState::creatureIndex() const CreatureID CUnitState::creatureId() const { - return unitType()->idNumber; + return unitType()->getId(); } int32_t CUnitState::creatureLevel() const diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp index 89d98b1b0..21faaf608 100644 --- a/lib/mapObjects/CBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -59,7 +59,7 @@ void CBank::setConfig(const BankConfig & config) clear(); // remove all stacks, if any for (auto & stack : config.guards) - setCreature (SlotID(stacksCount()), stack.type->idNumber, stack.count); + setCreature (SlotID(stacksCount()), stack.type->getId(), stack.count); } void CBank::setPropertyDer (ui8 what, ui32 val) @@ -255,7 +255,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const return a.type->fightValue < b.type->fightValue; })->type; - iw.text.addReplacement(MetaString::CRE_PL_NAMES, strongest->idNumber); + iw.text.addReplacement(MetaString::CRE_PL_NAMES, strongest->getId()); iw.text.addReplacement(loot.buildList()); } cb->showInfoDialog(&iw); @@ -309,7 +309,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const CCreatureSet ourArmy; for (auto slot : bc->creatures) { - ourArmy.addToSlot(ourArmy.getSlotFor(slot.type->idNumber), slot.type->idNumber, slot.count); + ourArmy.addToSlot(ourArmy.getSlotFor(slot.type->idNumber), slot.type->getId(), slot.count); } for (auto & elem : ourArmy.Slots()) diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 3a4765824..092f64537 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -200,7 +200,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const std::string tmp = VLC->generaltexth->advobtxt[90]; boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast(getStackCount(SlotID(0)))); boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast(action)); - boost::algorithm::replace_first(tmp,"%s",VLC->creh->objects[subID]->namePl); + boost::algorithm::replace_first(tmp,"%s",VLC->creh->objects[subID]->getNamePluralTranslated()); ynd.text << tmp; cb->showBlockingDialog(&ynd); break; diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index af6cb61ba..63c15b15c 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -543,7 +543,7 @@ void CMap::checkForObjectives() break; case EventCondition::HAVE_CREATURES: - boost::algorithm::replace_first(event.onFulfill, "%s", VLC->creh->objects[cond.objectType]->nameSing); + boost::algorithm::replace_first(event.onFulfill, "%s", VLC->creh->objects[cond.objectType]->getNameSingularTranslated()); boost::algorithm::replace_first(event.onFulfill, "%d", boost::lexical_cast(cond.value)); break; diff --git a/mapeditor/inspector/armywidget.cpp b/mapeditor/inspector/armywidget.cpp index e9423a8a2..e392e0508 100644 --- a/mapeditor/inspector/armywidget.cpp +++ b/mapeditor/inspector/armywidget.cpp @@ -38,8 +38,8 @@ ArmyWidget::ArmyWidget(CArmedInstance & a, QWidget *parent) : for(int c = 0; c < VLC->creh->objects.size(); ++c) { auto creature = VLC->creh->objects[c]; - uiSlots[i]->insertItem(c + 1, tr(creature->getPluralName().c_str())); - uiSlots[i]->setItemData(c + 1, creature->getId().getNum()); + uiSlots[i]->insertItem(c + 1, creature->getNamePluralTranslated().c_str()); + uiSlots[i]->setItemData(c + 1, creature->getIndex()); } } diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index 9de126b24..f6607aeb1 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -97,7 +97,7 @@ QList RewardsWidget::getListForType(RewardType typeId) case RewardType::CREATURE: for(auto creature : VLC->creh->objects) { - result.append(QString::fromStdString(creature->getName())); + result.append(QString::fromStdString(creature->getNameSingularTranslated())); } break; } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 9d39f824f..2e67078bf 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1305,11 +1305,11 @@ void CGameHandler::addGenericKilledLog(BattleLogMessage & blm, const CStack * de boost::format txt(formatString); if(killed > 1) { - txt % killed % (multiple ? VLC->generaltexth->allTexts[43] : defender->getCreature()->namePl); // creatures perish + txt % killed % (multiple ? VLC->generaltexth->allTexts[43] : defender->getCreature()->getNamePluralTranslated()); // creatures perish } else //killed == 1 { - txt % (multiple ? VLC->generaltexth->allTexts[42] : defender->getCreature()->nameSing); // creature perishes + txt % (multiple ? VLC->generaltexth->allTexts[42] : defender->getCreature()->getNameSingularTranslated()); // creature perishes } MetaString line; line << txt.str(); @@ -6423,7 +6423,7 @@ bool CGameHandler::addToSlot(const StackLocation &sl, const CCreature *c, TQuant changeStackCount(sl, count); else { - COMPLAIN_RET("Cannot add " + c->namePl + " to slot " + boost::lexical_cast(sl.slot) + "!"); + COMPLAIN_RET("Cannot add " + c->getNamePluralTranslated() + " to slot " + boost::lexical_cast(sl.slot) + "!"); } return true; } From 388ed88b5d8f8dd43f3cbf23de01a128d408b305 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 2 Jan 2023 15:58:56 +0200 Subject: [PATCH 183/197] All artifact strings now pass through translator --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 2 +- AI/Nullkiller/Goals/AbstractGoal.cpp | 2 +- AI/VCAI/Goals/AbstractGoal.cpp | 2 +- client/CPlayerInterface.cpp | 4 +- client/battle/BattleWindow.cpp | 2 +- client/lobby/CBonusSelection.cpp | 2 +- client/widgets/CArtifactHolder.cpp | 26 ++++---- client/widgets/CComponent.cpp | 2 +- client/windows/CCreatureWindow.cpp | 2 +- client/windows/CTradeWindow.cpp | 14 ++-- include/vcmi/Artifact.h | 11 +++- lib/CArtHandler.cpp | 76 ++++++++++++++-------- lib/CArtHandler.h | 27 ++++---- lib/CGameState.cpp | 10 +-- lib/CStack.cpp | 2 +- lib/HeroBonus.cpp | 2 +- lib/NetPacksLib.cpp | 14 ++-- lib/mapObjects/CGMarket.cpp | 2 +- lib/mapObjects/CGTownInstance.cpp | 2 +- lib/mapObjects/CRewardableObject.cpp | 4 +- lib/mapObjects/MiscObjects.cpp | 16 +---- lib/mapping/CMap.cpp | 2 +- lib/mapping/MapFormatH3M.cpp | 2 +- lib/rmg/CMapGenerator.cpp | 4 +- lib/serializer/CSerializer.cpp | 2 +- mapeditor/inspector/questwidget.cpp | 2 +- mapeditor/inspector/rewardswidget.cpp | 2 +- mapeditor/mapsettings.cpp | 2 +- server/CGameHandler.cpp | 20 +++--- 29 files changed, 142 insertions(+), 118 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index b0abfffac..18c976e7a 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -191,7 +191,7 @@ int getDwellingArmyCost(const CGObjectInstance * target) uint64_t evaluateArtifactArmyValue(CArtifactInstance * art) { - if(art->artType->id == ArtifactID::SPELL_SCROLL) + if(art->artType->getId() == ArtifactID::SPELL_SCROLL) return 1500; auto statsValue = diff --git a/AI/Nullkiller/Goals/AbstractGoal.cpp b/AI/Nullkiller/Goals/AbstractGoal.cpp index 9798b8650..e188b78d1 100644 --- a/AI/Nullkiller/Goals/AbstractGoal.cpp +++ b/AI/Nullkiller/Goals/AbstractGoal.cpp @@ -60,7 +60,7 @@ std::string AbstractGoal::toString() const //TODO: virtualize desc = "GATHER TROOPS"; break; case GET_ART_TYPE: - desc = "GET ARTIFACT OF TYPE " + VLC->arth->objects[aid]->getName(); + desc = "GET ARTIFACT OF TYPE " + VLC->arth->objects[aid]->getNameTranslated(); break; case DIG_AT_TILE: desc = "DIG AT TILE " + tile.toString(); diff --git a/AI/VCAI/Goals/AbstractGoal.cpp b/AI/VCAI/Goals/AbstractGoal.cpp index b197a894a..b469c8008 100644 --- a/AI/VCAI/Goals/AbstractGoal.cpp +++ b/AI/VCAI/Goals/AbstractGoal.cpp @@ -91,7 +91,7 @@ std::string AbstractGoal::name() const //TODO: virtualize } break; case GET_ART_TYPE: - desc = "GET ARTIFACT OF TYPE " + VLC->artifacts()->getByIndex(aid)->getName(); + desc = "GET ARTIFACT OF TYPE " + VLC->artifacts()->getByIndex(aid)->getNameTranslated(); break; case VISIT_TILE: desc = "VISIT TILE " + tile.toString(); diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 03616dc55..d4df5435b 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1363,14 +1363,14 @@ void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHer */ void CPlayerInterface::showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList onYes) { - std::string text = artifact->getDescription(); + std::string text = artifact->getDescriptionTranslated(); text += "\n\n"; std::vector> scs; if(assembledArtifact) { // You possess all of the components to... - text += boost::str(boost::format(CGI->generaltexth->allTexts[732]) % assembledArtifact->getName()); + text += boost::str(boost::format(CGI->generaltexth->allTexts[732]) % assembledArtifact->getNameTranslated()); // Picture of assembled artifact at bottom. auto sc = std::make_shared(CComponent::artifact, assembledArtifact->getIndex(), 0); diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index c0f0b0ee0..f47a559ac 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -420,7 +420,7 @@ void BattleWindow::bSpellf() //%s wields the %s, an ancient artifact which creates a p dead to all magic. LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683]) - % heroName % CGI->artifacts()->getByIndex(artID)->getName())); + % heroName % CGI->artifacts()->getByIndex(artID)->getNameTranslated())); } } } diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 796b60fb5..27d943c5c 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -209,7 +209,7 @@ void CBonusSelection::createBonusesIcons() } case CScenarioTravel::STravelBonus::ARTIFACT: desc = CGI->generaltexth->allTexts[715]; - boost::algorithm::replace_first(desc, "%s", CGI->artifacts()->getByIndex(bonDescs[i].info2)->getName()); + boost::algorithm::replace_first(desc, "%s", CGI->artifacts()->getByIndex(bonDescs[i].info2)->getNameTranslated()); break; case CScenarioTravel::STravelBonus::SPELL_SCROLL: desc = CGI->generaltexth->allTexts[716]; diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp index 177d22eab..600059c55 100644 --- a/client/widgets/CArtifactHolder.cpp +++ b/client/widgets/CArtifactHolder.cpp @@ -125,13 +125,13 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState) // If clicked on spellbook, open it only if no artifact is held at the moment. if(ourArt && !down && previousState && !ourOwner->commonInfo->src.AOH) { - if(ourArt->artType->id == ArtifactID::SPELLBOOK) - GH.pushIntT(ourOwner->curHero, LOCPLINT, LOCPLINT->battleInt.get()); + if(ourArt->artType->getId() == ArtifactID::SPELLBOOK) + GH.pushIntT(ourOwner->curHero, LOCPLINT, LOCPLINT->battleInt.get()); } if (!down && previousState) { - if(ourArt && ourArt->artType->id == ArtifactID::SPELLBOOK) + if(ourArt && ourArt->artType->getId() == ArtifactID::SPELLBOOK) return; //this is handled separately if(!ourOwner->commonInfo->src.AOH) //nothing has been clicked @@ -139,7 +139,7 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState) if(ourArt //to prevent selecting empty slots (bugfix to what GrayFace reported) && ourOwner->curHero->tempOwner == LOCPLINT->playerID)//can't take art from another player { - if(ourArt->artType->id == ArtifactID::CATAPULT) //catapult cannot be highlighted + if(ourArt->artType->getId() == ArtifactID::CATAPULT) //catapult cannot be highlighted { std::vector> catapult(1, std::make_shared(CComponent::artifact, 3, 0)); LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[312], catapult); //The Catapult must be equipped. @@ -164,7 +164,7 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState) { const CArtifact * const cur = ourOwner->commonInfo->src.art->artType; - if(cur->id == ArtifactID::CATAPULT) + if(cur->getId() == ArtifactID::CATAPULT) { //should not happen, catapult cannot be selected logGlobal->error("Attempt to move Catapult"); @@ -172,7 +172,7 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState) else if(cur->isBig()) { //war machines cannot go to backpack - LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % cur->getName())); + LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % cur->getNameTranslated())); } else { @@ -217,10 +217,10 @@ bool CHeroArtPlace::askToAssemble(const CArtifactInstance *art, ArtifactPosition LOCPLINT->showArtifactAssemblyDialog( art->artType, combination, - std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), hero, slot, true, combination->id)); + std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), hero, slot, true, combination->getId())); if(assemblyPossibilities.size() > 2) - logGlobal->warn("More than one possibility of assembling on %s... taking only first", art->artType->getName()); + logGlobal->warn("More than one possibility of assembling on %s... taking only first", art->artType->getNameTranslated()); return true; } return false; @@ -388,7 +388,7 @@ void CHeroArtPlace::setArtifact(const CArtifactInstance *art) text = art->getEffectiveDescription(ourOwner->curHero); - if(art->artType->id == ArtifactID::SPELL_SCROLL) + if(art->artType->getId() == ArtifactID::SPELL_SCROLL) { int spellID = art->getGivenSpellID(); if(spellID >= 0) @@ -402,14 +402,14 @@ void CHeroArtPlace::setArtifact(const CArtifactInstance *art) else { baseType = CComponent::artifact; - type = art->artType->id; + type = art->artType->getId(); bonusValue = 0; } if (locked) // Locks should appear as empty. hoverText = CGI->generaltexth->allTexts[507]; else - hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % ourArt->artType->getName()); + hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % ourArt->artType->getNameTranslated()); } void CArtifactsOfHero::SCommonPart::reset() @@ -1066,7 +1066,7 @@ void CCommanderArtPlace::setArtifact(const CArtifactInstance * art) text = art->getEffectiveDescription(); - if (art->artType->id == ArtifactID::SPELL_SCROLL) + if (art->artType->getId() == ArtifactID::SPELL_SCROLL) { int spellID = art->getGivenSpellID(); if (spellID >= 0) @@ -1080,7 +1080,7 @@ void CCommanderArtPlace::setArtifact(const CArtifactInstance * art) else { baseType = CComponent::artifact; - type = art->artType->id; + type = art->artType->getId(); bonusValue = 0; } } diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index 1bf52cca0..8a4599468 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -206,7 +206,7 @@ std::string CComponent::getSubtitleInternal() else return val > 1 ? creature->getNamePluralTranslated() : creature->getNameSingularTranslated(); } - case artifact: return CGI->artifacts()->getByIndex(subtype)->getName(); + case artifact: return CGI->artifacts()->getByIndex(subtype)->getNameTranslated(); case experience: { if(subtype == 1) //+1 level - tree of knowledge diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index bcbf999a0..d3aa2846b 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -597,7 +597,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s { parent->stackArtifactIcon = std::make_shared("ARTIFACT", art->artType->iconIndex, 0, pos.x, pos.y); parent->stackArtifactHelp = std::make_shared(Rect(pos, Point(44, 44)), CComponent::artifact); - parent->stackArtifactHelp->type = art->artType->id; + parent->stackArtifactHelp->type = art->artType->getId(); if(parent->info->owner) { diff --git a/client/windows/CTradeWindow.cpp b/client/windows/CTradeWindow.cpp index 545ea8a35..9420f18b4 100644 --- a/client/windows/CTradeWindow.cpp +++ b/client/windows/CTradeWindow.cpp @@ -250,7 +250,7 @@ void CTradeWindow::CTradeableItem::hover(bool on) if(id < 0) GH.statusbar->write(CGI->generaltexth->zelp[582].first); else - GH.statusbar->write(CGI->artifacts()->getByIndex(id)->getName()); + GH.statusbar->write(CGI->artifacts()->getByIndex(id)->getNameTranslated()); break; } } @@ -269,7 +269,7 @@ void CTradeWindow::CTradeableItem::clickRight(tribool down, bool previousState) case ARTIFACT_PLACEHOLDER: //TODO: it's would be better for market to contain actual CArtifactInstance and not just ids of certain artifact type so we can use getEffectiveDescription. if(id >= 0) - adventureInt->handleRightClick(CGI->artifacts()->getByIndex(id)->getDescription(), down); + adventureInt->handleRightClick(CGI->artifacts()->getByIndex(id)->getDescriptionTranslated(), down); break; } } @@ -290,7 +290,7 @@ std::string CTradeWindow::CTradeableItem::getName(int number) const return CGI->creh->objects[id]->getNamePluralTranslated(); case ARTIFACT_TYPE: case ARTIFACT_INSTANCE: - return CGI->artifacts()->getByIndex(id)->getName(); + return CGI->artifacts()->getByIndex(id)->getNameTranslated(); } logGlobal->error("Invalid trade item type: %d", (int)type); return ""; @@ -313,7 +313,7 @@ void CTradeWindow::CTradeableItem::setArtInstance(const CArtifactInstance *art) assert(type == ARTIFACT_PLACEHOLDER || type == ARTIFACT_INSTANCE); hlp = art; if(art) - setID(art->artType->id); + setID(art->artType->getId()); else setID(-1); } @@ -1397,7 +1397,7 @@ void CAltarWindow::calcTotalExp() for(const CArtifactInstance *art : arts->artifactsOnAltar) { int dmp, valOfArt; - market->getOffer(art->artType->id, 0, dmp, valOfArt, mode); + market->getOffer(art->artType->getId(), 0, dmp, valOfArt, mode); val += valOfArt; //WAS val += valOfArt * arts->artifactsOnAltar.count(*i); } } @@ -1470,7 +1470,7 @@ void CAltarWindow::showAll(SDL_Surface * to) artIcon->showAll(to); int dmp, val; - market->getOffer(arts->commonInfo->src.art->artType->id, 0, dmp, val, EMarketMode::ARTIFACT_EXP); + market->getOffer(arts->commonInfo->src.art->artType->getId(), 0, dmp, val, EMarketMode::ARTIFACT_EXP); val = static_cast(hero->calculateXp(val)); printAtMiddleLoc(boost::lexical_cast(val), 304, 498, FONT_SMALL, Colors::WHITE, to); } @@ -1496,7 +1496,7 @@ bool CAltarWindow::putOnAltar(std::shared_ptr altarSlot, const C } int dmp, val; - market->getOffer(art->artType->id, 0, dmp, val, EMarketMode::ARTIFACT_EXP); + market->getOffer(art->artType->getId(), 0, dmp, val, EMarketMode::ARTIFACT_EXP); val = static_cast(hero->calculateXp(val)); arts->artifactsOnAltar.insert(art); diff --git a/include/vcmi/Artifact.h b/include/vcmi/Artifact.h index f9f7ad89e..fda53de1a 100644 --- a/include/vcmi/Artifact.h +++ b/include/vcmi/Artifact.h @@ -19,13 +19,20 @@ class CreatureID; class DLL_LINKAGE Artifact : public EntityWithBonuses { + using EntityWithBonuses::getName; public: virtual bool isBig() const = 0; virtual bool isTradable() const = 0; - virtual const std::string & getDescription() const = 0; - virtual const std::string & getEventText() const = 0; virtual uint32_t getPrice() const = 0; virtual CreatureID getWarMachine() const = 0; + + virtual std::string getDescriptionTranslated() const = 0; + virtual std::string getEventTranslated() const = 0; + virtual std::string getNameTranslated() const = 0; + + virtual std::string getDescriptionTextID() const = 0; + virtual std::string getEventTextID() const = 0; + virtual std::string getNameTextID() const = 0; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 4f321aad3..2ca86712f 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -62,7 +62,7 @@ int32_t CArtifact::getIconIndex() const const std::string & CArtifact::getName() const { - return name; + return identifier; } const std::string & CArtifact::getJsonKey() const @@ -86,13 +86,34 @@ const IBonusBearer * CArtifact::accessBonuses() const return this; } -const std::string & CArtifact::getDescription() const +std::string CArtifact::getDescriptionTranslated() const { - return description; + return VLC->generaltexth->translate(getDescriptionTextID()); } -const std::string & CArtifact::getEventText() const + +std::string CArtifact::getEventTranslated() const { - return eventText; + return VLC->generaltexth->translate(getEventTextID()); +} + +std::string CArtifact::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + +std::string CArtifact::getDescriptionTextID() const +{ + return TextIdentifier("object", modScope, identifier, "description").get(); +} + +std::string CArtifact::getEventTextID() const +{ + return TextIdentifier("object", modScope, identifier, "event").get(); +} + +std::string CArtifact::getNameTextID() const +{ + return TextIdentifier("object", modScope, identifier, "name").get(); } uint32_t CArtifact::getPrice() const @@ -167,7 +188,7 @@ void CArtifact::addNewBonus(const std::shared_ptr& b) { b->source = Bonus::ARTIFACT; b->duration = Bonus::PERMANENT; - b->description = name; + b->description = getNameTranslated(); CBonusSystemNode::addNewBonus(b); } @@ -323,10 +344,13 @@ CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode } art->id = ArtifactID(index); art->identifier = identifier; + art->modScope = scope; + const JsonNode & text = node["text"]; - art->name = text["name"].String(); - art->description = text["description"].String(); - art->eventText = text["event"].String(); + + VLC->generaltexth->registerString(art->getNameTextID(), text["name"].String()); + VLC->generaltexth->registerString(art->getDescriptionTextID(), text["description"].String()); + VLC->generaltexth->registerString(art->getEventTextID(), text["event"].String()); const JsonNode & graphics = node["graphics"]; art->image = graphics["image"].String(); @@ -734,7 +758,7 @@ void CArtifactInstance::setType( CArtifact *Art ) std::string CArtifactInstance::nodeName() const { - return "Artifact instance of " + (artType ? artType->getName() : std::string("uninitialized")) + " type"; + return "Artifact instance of " + (artType ? artType->getJsonKey() : std::string("uninitialized")) + " type"; } CArtifactInstance *CArtifactInstance::createScroll(SpellID sid) @@ -756,9 +780,9 @@ std::string CArtifactInstance::getEffectiveDescription(const CGHeroInstance * he { std::string text = artType->getDescription(); if (!vstd::contains(text, '{')) - text = '{' + artType->getName() + "}\n\n" + text; //workaround for new artifacts with single name, turns it to H3-style + text = '{' + artType->getNameTranslated() + "}\n\n" + text; //workaround for new artifacts with single name, turns it to H3-style - if(artType->id == ArtifactID::SPELL_SCROLL) + if(artType->getId() == ArtifactID::SPELL_SCROLL) { // we expect scroll description to be like this: This scroll contains the [spell name] spell which is added into your spell book for as long as you carry the scroll. // so we want to replace text in [...] with a spell name @@ -777,12 +801,12 @@ std::string CArtifactInstance::getEffectiveDescription(const CGHeroInstance * he std::string artList; auto combinedArt = artType->constituentOf[0]; text += "\n\n"; - text += "{" + combinedArt->getName() + "}"; + text += "{" + combinedArt->getNameTranslated() + "}"; int wornArtifacts = 0; for(auto a : *combinedArt->constituents) //TODO: can the artifact be a part of more than one set? { - artList += "\n" + a->getName(); - if (hero->hasArt(a->id, true)) + artList += "\n" + a->getNameTranslated(); + if (hero->hasArt(a->getId(), true)) wornArtifacts++; } text += " (" + boost::str(boost::format("%d") % wornArtifacts) + " / " + @@ -841,7 +865,7 @@ bool CArtifactInstance::canBePutAt(const CArtifactSet *artSet, ArtifactPosition auto possibleSlots = artType->possibleSlots.find(artSet->bearerType()); if(possibleSlots == artType->possibleSlots.end()) { - logMod->warn("Warning: artifact %s doesn't have defined allowed slots for bearer of type %s", artType->getName(), artSet->bearerType()); + logMod->warn("Warning: artifact %s doesn't have defined allowed slots for bearer of type %s", artType->getNameTranslated(), artSet->bearerType()); return false; } @@ -889,7 +913,7 @@ std::vector CArtifactInstance::assemblyPossibilities(const CA if(equipped) { // Search for equipped arts - if (!h->hasArt(constituent->id, true, false, false)) + if (!h->hasArt(constituent->getId(), true, false, false)) { possible = false; break; @@ -898,7 +922,7 @@ std::vector CArtifactInstance::assemblyPossibilities(const CA else { // Search in backpack - if(!h->hasArtBackpack(constituent->id)) + if(!h->hasArtBackpack(constituent->getId())) { possible = false; break; @@ -1061,14 +1085,14 @@ void CCombinedArtifactInstance::createConstituents() for(const CArtifact * art : *artType->constituents) { - addAsConstituent(CArtifactInstance::createNewArtifactInstance(art->id), ArtifactPosition::PRE_FIRST); + addAsConstituent(CArtifactInstance::createNewArtifactInstance(art->getId()), ArtifactPosition::PRE_FIRST); } } void CCombinedArtifactInstance::addAsConstituent(CArtifactInstance *art, ArtifactPosition slot) { assert(vstd::contains_if(*artType->constituents, [=](const CArtifact * constituent){ - return constituent->id == art->artType->id; + return constituent->getId() == art->artType->getId(); })); assert(art->getParentNodes().size() == 1 && art->getParentNodes().front() == art->artType); constituentsInfo.push_back(ConstituentInfo(art, slot)); @@ -1227,7 +1251,7 @@ std::vector CArtifactSet::getAllArtPositions(ArtifactID aid, b { std::vector result; for(auto & slotInfo : artifactsWorn) - if(slotInfo.second.artifact->artType->id == aid && (allowLocked || !slotInfo.second.locked)) + if(slotInfo.second.artifact->artType->getId() == aid && (allowLocked || !slotInfo.second.locked)) result.push_back(slotInfo.first); if(onlyWorn) @@ -1248,7 +1272,7 @@ std::vector CArtifactSet::getBackpackArtPositions(ArtifactID a for(auto & artInfo : artifactsInBackpack) { auto * art = artInfo.getArt(); - if(art && art->artType->id == aid) + if(art && art->artType->getId() == aid) result.emplace_back(backpackPosition); backpackPosition++; } @@ -1318,7 +1342,7 @@ CArtifactSet::searchForConstituent(ArtifactID aid) const auto ass = static_cast(art.get()); for(auto& ci : ass->constituentsInfo) { - if(ci.art->artType->id == aid) + if(ci.art->artType->getId() == aid) { return {ass, ci.art}; } @@ -1468,12 +1492,12 @@ void CArtifactSet::serializeJsonHero(JsonSerializeFormat & handler, CMap * map) { backpackTemp.reserve(artifactsInBackpack.size()); for(const ArtSlotInfo & info : artifactsInBackpack) - backpackTemp.push_back(info.artifact->artType->id); + backpackTemp.push_back(info.artifact->artType->getId()); } handler.serializeIdArray(NArtifactPosition::backpack, backpackTemp); if(!handler.saving) { - for(const ArtifactID & artifactID : backpackTemp) + for(const ArtifactID & artifactID : backpackTemp) { auto artifact = CArtifactInstance::createArtifact(map, artifactID.toEnum()); auto slot = ArtifactPosition(GameConstants::BACKPACK_START + (si32)artifactsInBackpack.size()); @@ -1503,7 +1527,7 @@ void CArtifactSet::serializeJsonSlot(JsonSerializeFormat & handler, const Artifa if(info != nullptr && !info->locked) { - artifactID = info->artifact->artType->id; + artifactID = info->artifact->artType->getId(); handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE); } } diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index 504c362c7..0ef065344 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -45,13 +45,16 @@ namespace ArtBearer class DLL_LINKAGE CArtifact : public Artifact, public CBonusSystemNode //container for artifacts { -protected: - std::string name, description; //set if custom - std::string eventText; //short story displayed upon picking + ArtifactID id; + + std::string modScope; + std::string identifier; + + const std::string & getName() const override; + public: enum EartClass {ART_SPECIAL=1, ART_TREASURE=2, ART_MINOR=4, ART_MAJOR=8, ART_RELIC=16}; //artifact classes - std::string identifier; std::string image; std::string large; // big image for custom artifacts, used in drag & drop std::string advMapDef; //used for adventure map object @@ -61,19 +64,23 @@ public: std::unique_ptr > constituents; // Artifacts IDs a combined artifact consists of, or nullptr. std::vector constituentOf; // Reverse map of constituents - combined arts that include this art EartClass aClass; - ArtifactID id; CreatureID warMachine; int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getName() const override; const std::string & getJsonKey() const override; void registerIcons(const IconRegistar & cb) const override; ArtifactID getId() const override; virtual const IBonusBearer * accessBonuses() const override; - const std::string & getEventText() const override; - const std::string & getDescription() const override; + std::string getDescriptionTranslated() const override; + std::string getEventTranslated() const override; + std::string getNameTranslated() const override; + + std::string getDescriptionTextID() const override; + std::string getEventTextID() const override; + std::string getNameTextID() const override; + uint32_t getPrice() const override; CreatureID getWarMachine() const override; bool isBig() const override; @@ -91,9 +98,6 @@ public: template void serialize(Handler &h, const int version) { h & static_cast(*this); - h & name; - h & description; - h & eventText; h & image; h & large; h & advMapDef; @@ -104,6 +108,7 @@ public: h & constituentOf; h & aClass; h & id; + h & modScope; h & identifier; h & warMachine; } diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 8ba1e26f8..8fcd22251 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -75,7 +75,7 @@ void MetaString::getLocalString(const std::pair &txt, std::string &dst { auto art = ArtifactID(ser).toArtifact(VLC->artifacts()); if(art) - dst = art->getName(); + dst = art->getNameTranslated(); else dst = "#!#"; } @@ -83,7 +83,7 @@ void MetaString::getLocalString(const std::pair &txt, std::string &dst { auto art = ArtifactID(ser).toArtifact(VLC->artifacts()); if(art) - dst = art->getDescription(); + dst = art->getDescriptionTranslated(); else dst = "#!#"; } @@ -91,7 +91,7 @@ void MetaString::getLocalString(const std::pair &txt, std::string &dst { auto art = ArtifactID(ser).toArtifact(VLC->artifacts()); if(art) - dst = art->getEventText(); + dst = art->getEventTranslated(); else dst = "#!#"; } @@ -1304,7 +1304,7 @@ void CGameState::prepareCrossoverHeroes(std::vectorartType->id; + int id = art->artType->getId(); assert( 8*18 > id );//number of arts that fits into h3m format bool takeable = travelOptions.artifsKeptByHero[id / 8] & ( 1 << (id%8) ); @@ -2891,7 +2891,7 @@ void CGameState::replaceHeroesPlaceholders(const std::vectorartType = VLC->arth->objects[art->artType->id]; + art->artType = VLC->arth->objects[art->artType->getId()]; gs->map->artInstances.push_back(art); art->id = ArtifactInstanceID((si32)gs->map->artInstances.size() - 1); }; diff --git a/lib/CStack.cpp b/lib/CStack.cpp index 45ad53d1f..0c2283aa0 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -365,7 +365,7 @@ bool CStack::unitHasAmmoCart(const battle::Unit * unit) const auto ownerHero = battle->battleGetOwnerHero(unit); if(ownerHero && ownerHero->artifactsWorn.find(ArtifactPosition::MACH2) != ownerHero->artifactsWorn.end()) { - if(battle->battleGetOwnerHero(unit)->artifactsWorn.at(ArtifactPosition::MACH2).artifact->artType->id == ArtifactID::AMMO_CART) + if(battle->battleGetOwnerHero(unit)->artifactsWorn.at(ArtifactPosition::MACH2).artifact->artType->getId() == ArtifactID::AMMO_CART) { return true; } diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index d85364e46..8c56f9f3c 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -1594,7 +1594,7 @@ std::string Bonus::Description() const switch(source) { case ARTIFACT: - str << ArtifactID(sid).toArtifact(VLC->artifacts())->getName(); + str << ArtifactID(sid).toArtifact(VLC->artifacts())->getNameTranslated(); break; case SPELL_EFFECT: str << SpellID(sid).toSpell(VLC->spells())->getNameTranslated(); diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 88dcb408b..5d5cf5ea9 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -419,7 +419,7 @@ DLL_LINKAGE void RemoveObject::applyGs(CGameState *gs) beatenHero->tempOwner = PlayerColor::NEUTRAL; //no one owns beaten hero vstd::erase_if(beatenHero->artifactsInBackpack, [](const ArtSlotInfo& asi) { - return asi.artifact->artType->id == ArtifactID::GRAIL; + return asi.artifact->artType->getId() == ArtifactID::GRAIL; }); if(beatenHero->visitedTown) @@ -1061,7 +1061,7 @@ DLL_LINKAGE void EraseArtifact::applyGs(CGameState *gs) auto slot = al.getSlot(); if(slot->locked) { - logGlobal->debug("Erasing locked artifact: %s", slot->artifact->artType->getName()); + logGlobal->debug("Erasing locked artifact: %s", slot->artifact->artType->getNameTranslated()); DisassembledArtifact dis; dis.al.artHolder = al.artHolder; auto aset = al.getHolderArtSet(); @@ -1081,12 +1081,12 @@ DLL_LINKAGE void EraseArtifact::applyGs(CGameState *gs) } } assert(found && "Failed to determine the assembly this locked artifact belongs to"); - logGlobal->debug("Found the corresponding assembly: %s", dis.al.getSlot()->artifact->artType->getName()); + logGlobal->debug("Found the corresponding assembly: %s", dis.al.getSlot()->artifact->artType->getNameTranslated()); dis.applyGs(gs); } else { - logGlobal->debug("Erasing artifact %s", slot->artifact->artType->getName()); + logGlobal->debug("Erasing artifact %s", slot->artifact->artType->getNameTranslated()); } al.removeArtifact(); } @@ -1178,7 +1178,7 @@ DLL_LINKAGE void AssembledArtifact::applyGs(CGameState *gs) bool combineEquipped = !ArtifactUtils::isSlotBackpack(al.slot); assert(vstd::contains_if(transformedArt->assemblyPossibilities(artSet, combineEquipped), [=](const CArtifact * art)->bool { - return art->id == builtArt->id; + return art->getId() == builtArt->getId(); })); MAYBE_UNUSED(transformedArt); @@ -1187,8 +1187,8 @@ DLL_LINKAGE void AssembledArtifact::applyGs(CGameState *gs) // Retrieve all constituents for(const CArtifact * constituent : *builtArt->constituents) { - ArtifactPosition pos = combineEquipped ? artSet->getArtPos(constituent->id, true, false) : - artSet->getArtBackpackPos(constituent->id); + ArtifactPosition pos = combineEquipped ? artSet->getArtPos(constituent->getId(), true, false) : + artSet->getArtBackpackPos(constituent->getId()); assert(pos >= 0); CArtifactInstance * constituentInstance = artSet->getArt(pos); diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index 95687a963..9d1ee89f7 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -275,7 +275,7 @@ std::vector CGBlackMarket::availableItemsIds(EMarketMode::EMarketMode mode) std::vector ret; for(const CArtifact *a : artifacts) if(a) - ret.push_back(a->id); + ret.push_back(a->getId()); else ret.push_back(-1); return ret; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index deae9b107..48ac932d4 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -1145,7 +1145,7 @@ std::vector CGTownInstance::availableItemsIds(EMarketMode::EMarketMode mode std::vector ret; for(const CArtifact *a : merchantArtifacts) if(a) - ret.push_back(a->id); + ret.push_back(a->getId()); else ret.push_back(-1); return ret; diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 0b8e73f52..64ab68008 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -861,7 +861,7 @@ void CGOnceVisitable::initObj(CRandomGenerator & rand) info[0].reward.bonuses.push_back(bonus); info[0].limiter.numOfGrants = 1; info[0].message.addTxt(MetaString::ADVOB_TXT, 162); - info[0].message.addReplacement(VLC->arth->objects[info[0].reward.artifacts.back()]->getName()); + info[0].message.addReplacement(VLC->arth->objects[info[0].reward.artifacts.back()]->getNameTranslated()); } break; case Obj::WAGON: @@ -876,7 +876,7 @@ void CGOnceVisitable::initObj(CRandomGenerator & rand) loadRandomArtifact(rand, info[0], 10, 10, 0, 0); info[0].limiter.numOfGrants = 1; info[0].message.addTxt(MetaString::ADVOB_TXT, 155); - info[0].message.addReplacement(VLC->arth->objects[info[0].reward.artifacts.back()]->getName()); + info[0].message.addReplacement(VLC->arth->objects[info[0].reward.artifacts.back()]->getNameTranslated()); } else if(hlp < 90) //2 - 5 of non-gold resource { diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 092f64537..85f568957 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1320,7 +1320,7 @@ void CGArtifact::initObj(CRandomGenerator & rand) std::string CGArtifact::getObjectName() const { - return VLC->artifacts()->getByIndex(subID)->getName(); + return VLC->artifacts()->getByIndex(subID)->getNameTranslated(); } void CGArtifact::onHeroVisit(const CGHeroInstance * h) const @@ -1337,19 +1337,7 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const if(message.length()) iw.text << message; else - { - auto artifact = ArtifactID(subID).toArtifact(VLC->artifacts()); - - if((artifact != nullptr) && (!artifact->getEventText().empty())) - { - iw.text.addTxt(MetaString::ART_EVNTS, subID); - } - else //fix for mod artifacts with no event text - { - iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - iw.text.addReplacement(h->getNameTranslated()); - } - } + iw.text.addTxt(MetaString::ART_EVNTS, subID); } break; case Obj::SPELL_SCROLL: diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 63c15b15c..82aa93516 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -539,7 +539,7 @@ void CMap::checkForObjectives() switch (cond.condition) { case EventCondition::HAVE_ARTIFACT: - boost::algorithm::replace_first(event.onFulfill, "%s", VLC->arth->objects[cond.objectType]->getName()); + boost::algorithm::replace_first(event.onFulfill, "%s", VLC->arth->objects[cond.objectType]->getNameTranslated()); break; case EventCondition::HAVE_CREATURES: diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 652f97ff0..4d8a0d0e6 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -679,7 +679,7 @@ void CMapLoaderH3M::readAllowedArtifacts() // combo if (artifact->constituents) { - map->allowedArtifact[artifact->id] = false; + map->allowedArtifact[artifact->getId()] = false; } } if (map->version == EMapFormat::ROE) diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 9ed865dec..760204810 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -112,8 +112,8 @@ void CMapGenerator::initQuestArtsRemaining() { for (auto art : VLC->arth->objects) { - if (art->aClass == CArtifact::ART_TREASURE && VLC->arth->legalArtifact(art->id) && art->constituentOf.empty()) //don't use parts of combined artifacts - questArtifacts.push_back(art->id); + if (art->aClass == CArtifact::ART_TREASURE && VLC->arth->legalArtifact(art->getId()) && art->constituentOf.empty()) //don't use parts of combined artifacts + questArtifacts.push_back(art->getId()); } } diff --git a/lib/serializer/CSerializer.cpp b/lib/serializer/CSerializer.cpp index 153c0c22a..a7956651b 100644 --- a/lib/serializer/CSerializer.cpp +++ b/lib/serializer/CSerializer.cpp @@ -40,7 +40,7 @@ void CSerializer::addStdVecItems(CGameState *gs, LibClasses *lib) registerVectoredType(&lib->creh->objects, [](const CCreature &cre){ return cre.idNumber; }); registerVectoredType(&lib->arth->objects, - [](const CArtifact &art){ return art.id; }); + [](const CArtifact &art){ return art.getId(); }); registerVectoredType(&gs->map->artInstances, [](const CArtifactInstance &artInst){ return artInst.id; }); registerVectoredType(&gs->map->quests, diff --git a/mapeditor/inspector/questwidget.cpp b/mapeditor/inspector/questwidget.cpp index 953408c6a..747a9c7a7 100644 --- a/mapeditor/inspector/questwidget.cpp +++ b/mapeditor/inspector/questwidget.cpp @@ -68,7 +68,7 @@ void QuestWidget::obtainData() case CQuest::Emission::MISSION_ART: activeId = true; for(int i = 0; i < map.allowedArtifact.size(); ++i) - ui->targetId->addItem(QString::fromStdString(VLC->arth->objects.at(i)->getName())); + ui->targetId->addItem(QString::fromStdString(VLC->arth->objects.at(i)->getNameTranslated())); if(!seerhut.quest->m5arts.empty()) ui->targetId->setCurrentIndex(seerhut.quest->m5arts.front()); //TODO: support multiple artifacts diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index f6607aeb1..1210e7ce9 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -82,7 +82,7 @@ QList RewardsWidget::getListForType(RewardType typeId) for(int i = 0; i < map.allowedArtifact.size(); ++i) { if(map.allowedArtifact[i]) - result.append(QString::fromStdString(VLC->arth->objects.at(i)->getName())); + result.append(QString::fromStdString(VLC->arth->objects.at(i)->getNameTranslated())); } break; diff --git a/mapeditor/mapsettings.cpp b/mapeditor/mapsettings.cpp index 000318f0c..498f4c33a 100644 --- a/mapeditor/mapsettings.cpp +++ b/mapeditor/mapsettings.cpp @@ -50,7 +50,7 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) : } for(int i = 0; i < controller.map()->allowedArtifact.size(); ++i) { - auto * item = new QListWidgetItem(QString::fromStdString(VLC->arth->objects[i]->getName())); + auto * item = new QListWidgetItem(QString::fromStdString(VLC->arth->objects[i]->getNameTranslated())); item->setData(Qt::UserRole, QVariant::fromValue(i)); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(controller.map()->allowedArtifact[i] ? Qt::Checked : Qt::Unchecked); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 2e67078bf..3a292acf2 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -774,7 +774,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con ma.src = ArtifactLocation(finishingBattle->loserHero, artSlot.first); const CArtifactInstance * art = ma.src.getArt(); if (art && !art->artType->isBig() && - art->artType->id != ArtifactID::SPELLBOOK) + art->artType->getId() != ArtifactID::SPELLBOOK) // don't move war machines or locked arts (spellbook) { sendMoveArtifact(art, &ma); @@ -787,7 +787,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con ma.src = ArtifactLocation(finishingBattle->loserHero, ArtifactPosition(GameConstants::BACKPACK_START)); //backpack automatically shifts arts to beginning const CArtifactInstance * art = ma.src.getArt(); - if (art->artType->id != ArtifactID::GRAIL) //grail may not be won + if (art->artType->getId() != ArtifactID::GRAIL) //grail may not be won { sendMoveArtifact(art, &ma); } @@ -834,8 +834,8 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con for (auto art : arts) //TODO; separate function to display loot for various ojects? { iw.components.push_back(Component( - Component::ARTIFACT, art->artType->id, - art->artType->id == ArtifactID::SPELL_SCROLL? art->getGivenSpellID() : 0, 0)); + Component::ARTIFACT, art->artType->getId(), + art->artType->getId() == ArtifactID::SPELL_SCROLL? art->getGivenSpellID() : 0, 0)); if (iw.components.size() >= 14) { sendAndApply(&iw); @@ -3917,7 +3917,7 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat try { auto hero = boost::get>(dst.artHolder); - if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->id, dst.slot)) + if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->getId(), dst.slot)) giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); } catch (boost::bad_get const &) @@ -3964,7 +3964,7 @@ bool CGameHandler::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID auto art = artifact.second.getArt(); assert(art); - if(ArtifactUtils::checkSpellbookIsNeeded(dstHero, art->artType->id, artifact.first)) + if(ArtifactUtils::checkSpellbookIsNeeded(dstHero, art->artType->getId(), artifact.first)) giveHeroNewArtifact(dstHero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); } }; @@ -4001,7 +4001,7 @@ bool CGameHandler::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID artFittingSet.putArtifact(dstSlot, static_cast>(artifact)); slotsSrcDst.push_back(BulkMoveArtifacts::LinkedSlots(srcSlot, dstSlot)); - if(ArtifactUtils::checkSpellbookIsNeeded(pdstHero, artifact->artType->id, dstSlot)) + if(ArtifactUtils::checkSpellbookIsNeeded(pdstHero, artifact->artType->getId(), dstSlot)) giveHeroNewArtifact(pdstHero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); }; @@ -4144,7 +4144,7 @@ bool CGameHandler::buyArtifact(const IMarket *m, const CGHeroInstance *h, Res::E bool found = false; for (const CArtifact *&art : saa.arts) { - if (art && art->id == aid) + if (art && art->getId() == aid) { art = nullptr; found = true; @@ -4169,7 +4169,7 @@ bool CGameHandler::sellArtifact(const IMarket *m, const CGHeroInstance *h, Artif COMPLAIN_RET_FALSE_IF((!art->artType->isTradable()), "Cannot sell a war machine or spellbook!"); int resVal = 0, dump = 1; - m->getOffer(art->artType->id, rid, dump, resVal, EMarketMode::ARTIFACT_RESOURCE); + m->getOffer(art->artType->getId(), rid, dump, resVal, EMarketMode::ARTIFACT_RESOURCE); removeArtifact(ArtifactLocation(h, h->getArtPos(art))); giveResource(h->tempOwner, rid, resVal); @@ -6327,7 +6327,7 @@ bool CGameHandler::sacrificeArtifact(const IMarket * m, const CGHeroInstance * h COMPLAIN_RET("No artifact at position to sacrifice!"); } - si32 typId = art->artType->id; + si32 typId = art->artType->getId(); int dmp, expToGive; m->getOffer(typId, 0, dmp, expToGive, EMarketMode::ARTIFACT_EXP); From 05a1d7c6e3327eef90a4bc47ad87264e048a29d8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 4 Jan 2023 15:17:50 +0200 Subject: [PATCH 184/197] All text for factions/towns/building are passed through translator --- AI/Nullkiller/AIGateway.cpp | 4 +- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 4 +- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 12 +- AI/Nullkiller/Goals/BuildThis.cpp | 4 +- AI/Nullkiller/Goals/BuyArmy.cpp | 2 +- .../Goals/ExchangeSwapTownHeroes.cpp | 6 +- AI/Nullkiller/Goals/RecruitHero.cpp | 6 +- .../Pathfinding/Actions/TownPortalAction.cpp | 2 +- AI/Nullkiller/Pathfinding/Actors.cpp | 2 +- AI/VCAI/Goals/BuyArmy.cpp | 2 +- AI/VCAI/VCAI.cpp | 10 +- client/battle/BattleInterfaceClasses.cpp | 2 +- client/battle/BattleSiegeController.cpp | 6 +- client/lobby/CBonusSelection.cpp | 2 +- client/lobby/OptionsTab.cpp | 2 +- client/widgets/CComponent.cpp | 6 +- client/windows/CCastleInterface.cpp | 58 +++++----- client/windows/CKingdomInterface.cpp | 2 +- client/windows/CTradeWindow.cpp | 6 +- client/windows/GUIClasses.cpp | 2 +- include/vcmi/Faction.h | 4 + lib/CGameState.cpp | 17 +-- lib/CHeroHandler.cpp | 4 +- lib/CTownHandler.cpp | 105 +++++++++++++----- lib/CTownHandler.h | 44 +++++--- lib/mapObjects/CGTownInstance.cpp | 30 +++-- lib/mapObjects/CGTownInstance.h | 5 +- lib/mapObjects/CommonConstructors.cpp | 2 +- lib/mapping/CMap.cpp | 2 +- lib/mapping/MapFormatH3M.cpp | 2 +- lib/mapping/MapFormatJson.cpp | 6 +- lib/rmg/CMapGenerator.cpp | 2 +- mapeditor/inspector/inspector.cpp | 4 +- mapeditor/inspector/townbulidingswidget.cpp | 2 +- mapeditor/playerparams.cpp | 4 +- server/CGameHandler.cpp | 10 +- 36 files changed, 230 insertions(+), 153 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index d395e4392..21dc1061c 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -1351,8 +1351,8 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) void AIGateway::buildStructure(const CGTownInstance * t, BuildingID building) { - auto name = t->town->buildings.at(building)->Name(); - logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->name, t->pos.toString()); + auto name = t->town->buildings.at(building)->getNameTranslated(); + logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString()); cb->buildBuilding(t, building); //just do this; } diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index d719c03f4..4ae59be8f 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -130,7 +130,7 @@ void BuildAnalyzer::update() for(const CGTownInstance* town : towns) { - logAi->trace("Checking town %s", town->name); + logAi->trace("Checking town %s", town->getNameTranslated()); developmentInfos.push_back(TownDevelopmentInfo(town)); TownDevelopmentInfo & developmentInfo = developmentInfos.back(); @@ -351,7 +351,7 @@ BuildingInfo::BuildingInfo( dailyIncome = building->produce; exists = town->hasBuilt(id); prerequisitesCount = 1; - name = building->Name(); + name = building->getNameTranslated(); if(creature) { diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 2ff3d1942..7b99df87c 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -50,14 +50,14 @@ Goals::TGoalVec DefenceBehavior::decompose() const void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const { - logAi->trace("Evaluating defence for %s", town->name); + logAi->trace("Evaluating defence for %s", town->getNameTranslated()); auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town); auto treats = { treatNode.fastestDanger, treatNode.maximumDanger }; if(!treatNode.fastestDanger.hero) { - logAi->trace("No treat found for town %s", town->name); + logAi->trace("No treat found for town %s", town->getNameTranslated()); return; } @@ -79,7 +79,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta logAi->trace( "Hero %s in garrison of town %s is suposed to defend the town", town->garrisonHero->getNameTranslated(), - town->name); + town->getNameTranslated()); return; } @@ -88,7 +88,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta if(reinforcement) { - logAi->trace("Town %s can buy defence army %lld", town->name, reinforcement); + logAi->trace("Town %s can buy defence army %lld", town->getNameTranslated(), reinforcement); tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(0.5f))); } @@ -98,7 +98,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta { logAi->trace( "Town %s has treat %lld in %s turns, hero: %s", - town->name, + town->getNameTranslated(), treat.danger, std::to_string(treat.turn), treat.hero->getNameTranslated()); @@ -187,7 +187,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta if(paths.empty()) { - logAi->trace("No ways to defend town %s", town->name); + logAi->trace("No ways to defend town %s", town->getNameTranslated()); continue; } diff --git a/AI/Nullkiller/Goals/BuildThis.cpp b/AI/Nullkiller/Goals/BuildThis.cpp index 06a06b25b..8f411ab68 100644 --- a/AI/Nullkiller/Goals/BuildThis.cpp +++ b/AI/Nullkiller/Goals/BuildThis.cpp @@ -46,7 +46,7 @@ bool BuildThis::operator==(const BuildThis & other) const std::string BuildThis::toString() const { - return "Build " + buildingInfo.name + " in " + town->name; + return "Build " + buildingInfo.name + " in " + town->getNameTranslated(); } void BuildThis::accept(AIGateway * ai) @@ -58,7 +58,7 @@ void BuildThis::accept(AIGateway * ai) if(cb->canBuildStructure(town, b) == EBuildingState::ALLOWED) { logAi->debug("Player %d will build %s in town of %s at %s", - ai->playerID, town->town->buildings.at(b)->Name(), town->name, town->pos.toString()); + ai->playerID, town->town->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->pos.toString()); cb->buildBuilding(town, b); return; diff --git a/AI/Nullkiller/Goals/BuyArmy.cpp b/AI/Nullkiller/Goals/BuyArmy.cpp index dc35c16ba..8d74a7045 100644 --- a/AI/Nullkiller/Goals/BuyArmy.cpp +++ b/AI/Nullkiller/Goals/BuyArmy.cpp @@ -29,7 +29,7 @@ bool BuyArmy::operator==(const BuyArmy & other) const std::string BuyArmy::toString() const { - return "Buy army at " + town->name; + return "Buy army at " + town->getNameTranslated(); } void BuyArmy::accept(AIGateway * ai) diff --git a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp index 6ac8a6355..cdb789cc3 100644 --- a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp +++ b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp @@ -33,7 +33,7 @@ ExchangeSwapTownHeroes::ExchangeSwapTownHeroes( std::string ExchangeSwapTownHeroes::toString() const { - return "Exchange and swap heroes of " + town->name; + return "Exchange and swap heroes of " + town->getNameTranslated(); } bool ExchangeSwapTownHeroes::operator==(const ExchangeSwapTownHeroes & other) const @@ -60,7 +60,7 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai) ai->buildArmyIn(town); ai->nullkiller->unlockHero(currentGarrisonHero.get()); - logAi->debug("Extracted hero %s from garrison of %s", currentGarrisonHero->getNameTranslated(), town->name); + logAi->debug("Extracted hero %s from garrison of %s", currentGarrisonHero->getNameTranslated(), town->getNameTranslated()); return; } @@ -91,7 +91,7 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai) ai->makePossibleUpgrades(town->visitingHero); } - logAi->debug("Put hero %s to garrison of %s", garrisonHero->getNameTranslated(), town->name); + logAi->debug("Put hero %s to garrison of %s", garrisonHero->getNameTranslated(), town->getNameTranslated()); } } diff --git a/AI/Nullkiller/Goals/RecruitHero.cpp b/AI/Nullkiller/Goals/RecruitHero.cpp index f01776252..d6cfe2908 100644 --- a/AI/Nullkiller/Goals/RecruitHero.cpp +++ b/AI/Nullkiller/Goals/RecruitHero.cpp @@ -26,7 +26,7 @@ using namespace Goals; std::string RecruitHero::toString() const { - return "Recruit hero at " + town->name; + return "Recruit hero at " + town->getNameTranslated(); } void RecruitHero::accept(AIGateway * ai) @@ -40,7 +40,7 @@ void RecruitHero::accept(AIGateway * ai) throw cannotFulfillGoalException("No town to recruit hero!"); } - logAi->debug("Trying to recruit a hero in %s at %s", t->name, t->visitablePos().toString()); + logAi->debug("Trying to recruit a hero in %s at %s", t->getNameTranslated(), t->visitablePos().toString()); auto heroes = cb->getAvailableHeroes(t); @@ -78,4 +78,4 @@ void RecruitHero::accept(AIGateway * ai) ai->moveHeroToTile(t->visitablePos(), t->visitingHero.get()); } -} \ No newline at end of file +} diff --git a/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp b/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp index 30f1ffc6d..25c2acc5c 100644 --- a/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp +++ b/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp @@ -34,7 +34,7 @@ void TownPortalAction::execute(const CGHeroInstance * hero) const std::string TownPortalAction::toString() const { - return "Town Portal to " + target->name; + return "Town Portal to " + target->getNameTranslated(); } /* bool TownPortalAction::canAct(const CGHeroInstance * hero, const AIPathNode * source) const diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index 9734d5655..aedcc1e6b 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -463,5 +463,5 @@ TownGarrisonActor::TownGarrisonActor(const CGTownInstance * town, uint64_t chain std::string TownGarrisonActor::toString() const { - return town->name; + return town->getNameTranslated(); } diff --git a/AI/VCAI/Goals/BuyArmy.cpp b/AI/VCAI/Goals/BuyArmy.cpp index cf5f1bb39..fb9b4afea 100644 --- a/AI/VCAI/Goals/BuyArmy.cpp +++ b/AI/VCAI/Goals/BuyArmy.cpp @@ -41,5 +41,5 @@ TSubgoal BuyArmy::whatToDoToAchieve() std::string BuyArmy::completeMessage() const { - return boost::format("Bought army of value %d in town of %s") % value, town->name; + return boost::format("Bought army of value %d in town of %s") % value, town->getNameTranslated(); } diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 1842ee35b..440340ffa 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1427,7 +1427,7 @@ void VCAI::wander(HeroPtr h) { //TODO pick the truly best const CGTownInstance * t = *boost::max_element(townsNotReachable, compareReinforcements); - logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->getNameTranslated(), t->name, t->visitablePos().toString()); + logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->getNameTranslated(), t->getNameTranslated(), t->visitablePos().toString()); int3 pos1 = h->pos; striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h))); //TODO: drop "strive", add to mainLoop //if out hero is stuck, we may need to request another hero to clear the way we see @@ -1997,8 +1997,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) void VCAI::buildStructure(const CGTownInstance * t, BuildingID building) { - auto name = t->town->buildings.at(building)->Name(); - logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->name, t->pos.toString()); + auto name = t->town->buildings.at(building)->getNameTranslated(); + logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString()); cb->buildBuilding(t, building); //just do this; } @@ -2081,7 +2081,7 @@ void VCAI::tryRealize(Goals::BuildThis & g) if (cb->canBuildStructure(t, b) == EBuildingState::ALLOWED) { logAi->debug("Player %d will build %s in town of %s at %s", - playerID, t->town->buildings.at(b)->Name(), t->name, t->pos.toString()); + playerID, t->town->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->pos.toString()); cb->buildBuilding(t, b); throw goalFulfilledException(sptr(g)); } @@ -2439,7 +2439,7 @@ void VCAI::checkHeroArmy(HeroPtr h) void VCAI::recruitHero(const CGTownInstance * t, bool throwing) { - logAi->debug("Trying to recruit a hero in %s at %s", t->name, t->visitablePos().toString()); + logAi->debug("Trying to recruit a hero in %s at %s", t->getNameTranslated(), t->visitablePos().toString()); auto heroes = cb->getAvailableHeroes(t); if(heroes.size()) diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 3208e7bde..4c4ce524d 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -804,7 +804,7 @@ void StackQueue::update() int32_t StackQueue::getSiegeShooterIconID() { - return owner.siegeController->getSiegedTown()->town->faction->index; + return owner.siegeController->getSiegedTown()->town->faction->getIndex(); } StackQueue::StackBox::StackBox(StackQueue * owner): diff --git a/client/battle/BattleSiegeController.cpp b/client/battle/BattleSiegeController.cpp index 0b0acf36c..6d23c5ffd 100644 --- a/client/battle/BattleSiegeController.cpp +++ b/client/battle/BattleSiegeController.cpp @@ -65,7 +65,7 @@ std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisua { case EWallVisual::BACKGROUND_WALL: { - switch(town->town->faction->index) + switch(town->town->faction->getIndex()) { case ETownType::RAMPART: case ETownType::NECROPOLIS: @@ -135,8 +135,8 @@ bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) switch (what) { - case EWallVisual::MOAT: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER; - case EWallVisual::MOAT_BANK: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER && town->town->faction->index != ETownType::NECROPOLIS; + case EWallVisual::MOAT: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->getIndex() != ETownType::TOWER; + case EWallVisual::MOAT_BANK: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->getIndex() != ETownType::TOWER && town->town->faction->getIndex() != ETownType::NECROPOLIS; case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && owner.curInt->cb->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED; case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED; case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED; diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 27d943c5c..f562d95ae 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -203,7 +203,7 @@ void CBonusSelection::createBonusesIcons() picNumber = -1; if(vstd::contains((*CGI->townh)[faction]->town->buildings, buildID)) - desc = (*CGI->townh)[faction]->town->buildings.find(buildID)->second->Name(); + desc = (*CGI->townh)[faction]->town->buildings.find(buildID)->second->getNameTranslated(); break; } diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 27563fade..199788093 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -184,7 +184,7 @@ std::string OptionsTab::CPlayerSettingsHelper::getName() default: { auto factionIndex = settings.castle >= CGI->townh->size() ? 0 : settings.castle; - return (*CGI->townh)[factionIndex]->name; + return (*CGI->townh)[factionIndex]->getNameTranslated(); } } } diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index 8a4599468..95ccd0f7c 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -172,7 +172,7 @@ std::string CComponent::getDescription() case spell: return (*CGI->spellh)[subtype]->getDescriptionTranslated(val); case morale: return CGI->generaltexth->heroscrn[ 4 - (val>0) + (val<0)]; case luck: return CGI->generaltexth->heroscrn[ 7 - (val>0) + (val<0)]; - case building: return (*CGI->townh)[subtype]->town->buildings[BuildingID(val)]->Description(); + case building: return (*CGI->townh)[subtype]->town->buildings[BuildingID(val)]->getDescriptionTranslated(); case hero: return ""; case flag: return ""; } @@ -228,10 +228,10 @@ std::string CComponent::getSubtitleInternal() auto building = (*CGI->townh)[subtype]->town->buildings[BuildingID(val)]; if(!building) { - logGlobal->error("Town of faction %s has no building #%d", (*CGI->townh)[subtype]->town->faction->name, val); + logGlobal->error("Town of faction %s has no building #%d", (*CGI->townh)[subtype]->town->faction->getNameTranslated(), val); return (boost::format("Missing building #%d") % val).str(); } - return building->Name(); + return building->getNameTranslated(); } case hero: return ""; case flag: return CGI->generaltexth->capColors[subtype]; diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 68ec7b256..6dfb48fa4 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -145,8 +145,8 @@ void CBuildingRect::clickRight(tribool down, bool previousState) const CBuilding *bld = town->town->buildings.at(bid); if (bid < BuildingID::DWELL_FIRST) { - CRClickPopup::createAndPush(CInfoWindow::genText(bld->Name(), bld->Description()), - std::make_shared(CComponent::building, bld->town->faction->index, bld->bid)); + CRClickPopup::createAndPush(CInfoWindow::genText(bld->getNameTranslated(), bld->getDescriptionTranslated()), + std::make_shared(CComponent::building, bld->town->faction->getIndex(), bld->bid)); } else { @@ -235,7 +235,7 @@ std::string CBuildingRect::getSubtitle()//hover text for building int bid = getBuilding()->bid; if (bid<30)//non-dwellings - only buiding name - return town->town->buildings.at(getBuilding()->bid)->Name(); + return town->town->buildings.at(getBuilding()->bid)->getNameTranslated(); else//dwellings - recruit %creature% { auto & availableCreatures = town->creatures[(bid-30)%GameConstants::CREATURES_PER_TOWN].second; @@ -736,7 +736,7 @@ void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuil if(town->visitingHero) GH.pushIntT(town, town->visitingHero, EMarketMode::RESOURCE_ARTIFACT); else - LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % b->Name())); //Only visiting heroes may use the %s. + LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % b->getNameTranslated())); //Only visiting heroes may use the %s. break; case BuildingSubID::FOUNTAIN_OF_FORTUNE: @@ -747,7 +747,7 @@ void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuil if(getHero()) GH.pushIntT(town, getHero(), EMarketMode::CREATURE_RESOURCE); else - LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % b->Name())); //Only visiting heroes may use the %s. + LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % b->getNameTranslated())); //Only visiting heroes may use the %s. break; case BuildingSubID::MAGIC_UNIVERSITY: @@ -801,7 +801,7 @@ void CCastleBuildings::enterBlacksmith(ArtifactID artifactID) const CGHeroInstance *hero = town->visitingHero; if(!hero) { - LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->town->buildings.find(BuildingID::BLACKSMITH)->second->Name())); + LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->town->buildings.find(BuildingID::BLACKSMITH)->second->getNameTranslated())); return; } auto art = artifactID.toArtifact(CGI->artifacts()); @@ -815,7 +815,7 @@ void CCastleBuildings::enterBlacksmith(ArtifactID artifactID) void CCastleBuildings::enterBuilding(BuildingID building) { std::vector> comps(1, std::make_shared(CComponent::building, town->subID, building)); - LOCPLINT->showInfoDialog( town->town->buildings.find(building)->second->Description(), comps); + LOCPLINT->showInfoDialog( town->town->buildings.find(building)->second->getDescriptionTranslated(), comps); } void CCastleBuildings::enterCastleGate() @@ -831,7 +831,7 @@ void CCastleBuildings::enterCastleGate() { const CGTownInstance *t = Town; if (t->id != this->town->id && t->visitingHero == nullptr && //another town, empty and this is - t->town->faction->index == town->town->faction->index && //the town of the same faction + t->town->faction->getId() == town->town->faction->getId() && //the town of the same faction t->hasBuilt(BuildingSubID::CASTLE_GATE)) //and the town has a castle gate { availableTowns.push_back(t->id.getNum());//add to the list @@ -866,18 +866,18 @@ void CCastleBuildings::enterToTheQuickRecruitmentWindow() void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID::EBuildingID upgrades) { std::vector> comps(1, std::make_shared(CComponent::building,town->subID,building)); - std::string descr = town->town->buildings.find(building)->second->Description(); + std::string descr = town->town->buildings.find(building)->second->getDescriptionTranslated(); std::string hasNotProduced; std::string hasProduced; - if(this->town->town->faction->index == (TFaction)ETownType::RAMPART) + if(this->town->town->faction->getIndex() == (TFaction)ETownType::RAMPART) { hasNotProduced = CGI->generaltexth->allTexts[677]; hasProduced = CGI->generaltexth->allTexts[678]; } else { - auto buildingName = town->town->getSpecialBuilding(subID)->Name(); + auto buildingName = town->town->getSpecialBuilding(subID)->getNameTranslated(); hasNotProduced = std::string(CGI->generaltexth->translate("vcmi.townHall.hasNotProduced")); hasProduced = std::string(CGI->generaltexth->translate("vcmi.townHall.hasProduced")); @@ -890,7 +890,7 @@ void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID: && town->town->buildings.find(BuildingID(upgrades))->second->subId == BuildingSubID::MYSTIC_POND); if(upgrades != BuildingID::NONE) - descr += "\n\n"+town->town->buildings.find(BuildingID(upgrades))->second->Description(); + descr += "\n\n"+town->town->buildings.find(BuildingID(upgrades))->second->getDescriptionTranslated(); if(isMysticPondOrItsUpgrade) //for vanila Rampart like towns { @@ -1114,7 +1114,7 @@ void CTownInfo::hover(bool on) if(on) { if(building ) - GH.statusbar->write(building->Name()); + GH.statusbar->write(building->getNameTranslated()); } else { @@ -1126,8 +1126,8 @@ void CTownInfo::clickRight(tribool down, bool previousState) { if(building && down) { - auto c = std::make_shared(CComponent::building, building->town->faction->index, building->bid); - CRClickPopup::createAndPush(CInfoWindow::genText(building->Name(), building->Description()), c); + auto c = std::make_shared(CComponent::building, building->town->faction->getIndex(), building->bid); + CRClickPopup::createAndPush(CInfoWindow::genText(building->getNameTranslated(), building->getDescriptionTranslated()), c); } } @@ -1152,7 +1152,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst garr->type |= REDRAW_PARENT; heroes = std::make_shared(town, Point(241, 387), Point(241, 483), garr, true); - title = std::make_shared(85, 387, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, town->name); + title = std::make_shared(85, 387, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, town->getNameTranslated()); income = std::make_shared(195, 443, FONT_SMALL, ETextAlignment::CENTER); icon = std::make_shared("ITPT", 0, 0, 15, 387); @@ -1316,7 +1316,7 @@ CHallInterface::CBuildingBox::CBuildingBox(int x, int y, const CGTownInstance * header = std::make_shared("TPTHBAR", panelIndex[state], 0, 1, 73); if(iconIndex[state] >=0) mark = std::make_shared("TPTHCHK", iconIndex[state], 0, 136, 56); - name = std::make_shared(75, 81, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, building->Name()); + name = std::make_shared(75, 81, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, building->getNameTranslated()); //todo: add support for all possible states if(state >= EBuildingState::BUILDING_ERROR) @@ -1334,7 +1334,7 @@ void CHallInterface::CBuildingBox::hover(bool on) toPrint = CGI->generaltexth->allTexts[223]; else toPrint = CGI->generaltexth->hcommands[state]; - boost::algorithm::replace_first(toPrint,"%s",building->Name()); + boost::algorithm::replace_first(toPrint,"%s",building->getNameTranslated()); GH.statusbar->write(toPrint); } else @@ -1368,7 +1368,7 @@ CHallInterface::CHallInterface(const CGTownInstance * Town): auto statusbarBackground = std::make_shared(background->getSurface(), barRect, 5, 556, false); statusbar = CGStatusBar::create(statusbarBackground); - title = std::make_shared(399, 12, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->Name()); + title = std::make_shared(399, 12, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->getNameTranslated()); exit = std::make_shared(Point(748, 556), "TPMAGE1.DEF", CButton::tooltip(CGI->generaltexth->hcommands[8]), [&](){close();}, SDLK_RETURN); exit->assignedKeys.insert(SDLK_ESCAPE); @@ -1415,8 +1415,8 @@ CBuildWindow::CBuildWindow(const CGTownInstance *Town, const CBuilding * Buildin auto statusbarBackground = std::make_shared(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26); statusbar = CGStatusBar::create(statusbarBackground); - name = std::make_shared(197, 30, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->hcommands[7]) % building->Name())); - description = std::make_shared(building->Description(), Rect(33, 135, 329, 67), 0, FONT_MEDIUM, ETextAlignment::CENTER); + name = std::make_shared(197, 30, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->hcommands[7]) % building->getNameTranslated())); + description = std::make_shared(building->getDescriptionTranslated(), Rect(33, 135, 329, 67), 0, FONT_MEDIUM, ETextAlignment::CENTER); stateText = std::make_shared(getTextForState(state), Rect(33, 216, 329, 67), 0, FONT_SMALL, ETextAlignment::CENTER); //Create components for all required resources @@ -1432,8 +1432,8 @@ CBuildWindow::CBuildWindow(const CGTownInstance *Town, const CBuilding * Buildin if(!rightClick) { //normal window - std::string tooltipYes = boost::str(boost::format(CGI->generaltexth->allTexts[595]) % building->Name()); - std::string tooltipNo = boost::str(boost::format(CGI->generaltexth->allTexts[596]) % building->Name()); + std::string tooltipYes = boost::str(boost::format(CGI->generaltexth->allTexts[595]) % building->getNameTranslated()); + std::string tooltipNo = boost::str(boost::format(CGI->generaltexth->allTexts[596]) % building->getNameTranslated()); buy = std::make_shared(Point(45, 446), "IBUY30", CButton::tooltip(tooltipYes), [&](){ buyFunc(); }, SDLK_RETURN); buy->setBorderColor(Colors::METALLIC_GOLD); @@ -1460,7 +1460,7 @@ std::string CBuildWindow::getTextForState(int state) case EBuildingState::ALREADY_PRESENT: case EBuildingState::CANT_BUILD_TODAY: case EBuildingState::NO_RESOURCES: - ret.replace(ret.find_first_of("%s"), 2, building->Name()); + ret.replace(ret.find_first_of("%s"), 2, building->getNameTranslated()); break; case EBuildingState::ALLOWED: return CGI->generaltexth->allTexts[219]; //all prereq. are met @@ -1468,7 +1468,7 @@ std::string CBuildWindow::getTextForState(int state) { auto toStr = [&](const BuildingID build) -> std::string { - return town->town->buildings.at(build)->Name(); + return town->town->buildings.at(build)->getNameTranslated(); }; ret = CGI->generaltexth->allTexts[52]; @@ -1478,7 +1478,7 @@ std::string CBuildWindow::getTextForState(int state) case EBuildingState::MISSING_BASE: { std::string msg = CGI->generaltexth->translate("vcmi.townHall.missingBase"); - ret = boost::str(boost::format(msg) % town->town->buildings.at(building->upgrade)->Name()); + ret = boost::str(boost::format(msg) % town->town->buildings.at(building->upgrade)->getNameTranslated()); break; } } @@ -1542,9 +1542,9 @@ CFortScreen::CFortScreen(const CGTownInstance * town): fortSize--; const CBuilding * fortBuilding = town->town->buildings.at(BuildingID(town->fortLevel()+6)); - title = std::make_shared(400, 12, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE, fortBuilding->Name()); + title = std::make_shared(400, 12, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE, fortBuilding->getNameTranslated()); - std::string text = boost::str(boost::format(CGI->generaltexth->fcommands[6]) % fortBuilding->Name()); + std::string text = boost::str(boost::format(CGI->generaltexth->fcommands[6]) % fortBuilding->getNameTranslated()); exit = std::make_shared(Point(748, 556), "TPMAGE1", CButton::tooltip(text), [&](){ close(); }, SDLK_RETURN); exit->assignedKeys.insert(SDLK_ESCAPE); @@ -1629,7 +1629,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance * if(getMyBuilding() != nullptr) { buildingIcon = std::make_shared(town->town->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21); - buildingName = std::make_shared(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->Name()); + buildingName = std::make_shared(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->getNameTranslated()); if(vstd::contains(town->builtBuildings, getMyBuilding()->bid)) { diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 7598ff1b1..c65ae1426 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -767,7 +767,7 @@ CTownItem::CTownItem(const CGTownInstance * Town) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); background = std::make_shared("OVSLOT", 6); - name = std::make_shared(74, 8, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, town->name); + name = std::make_shared(74, 8, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, town->getNameTranslated()); income = std::make_shared( 190, 60, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::lexical_cast(town->dailyIncome()[Res::GOLD])); hall = std::make_shared( 69, 31, town, true); diff --git a/client/windows/CTradeWindow.cpp b/client/windows/CTradeWindow.cpp index 9420f18b4..5f6e0903c 100644 --- a/client/windows/CTradeWindow.cpp +++ b/client/windows/CTradeWindow.cpp @@ -668,14 +668,14 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta switch (mode) { case EMarketMode::CREATURE_RESOURCE: - title = (*CGI->townh)[ETownType::STRONGHOLD]->town->buildings[BuildingID::FREELANCERS_GUILD]->Name(); + title = (*CGI->townh)[ETownType::STRONGHOLD]->town->buildings[BuildingID::FREELANCERS_GUILD]->getNameTranslated(); break; case EMarketMode::RESOURCE_ARTIFACT: - title = (*CGI->townh)[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->Name(); + title = (*CGI->townh)[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated(); sliderNeeded = false; break; case EMarketMode::ARTIFACT_RESOURCE: - title = (*CGI->townh)[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->Name(); + title = (*CGI->townh)[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated(); // create image that copies part of background containing slot MISC_1 into position of slot MISC_5 // this is workaround for bug in H3 files where this slot for ragdoll on this screen is missing diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 874b5b9d4..15a8f68db 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1586,7 +1586,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, const IMarket if(town) { - auto faction = town->town->faction->index; + auto faction = town->town->faction->getId(); auto bid = town->town->getSpecialBuilding(BuildingSubID::MAGIC_UNIVERSITY)->bid; titlePic = std::make_shared((*CGI->townh)[faction]->town->clientInfo.buildingsIcons, bid); } diff --git a/include/vcmi/Faction.h b/include/vcmi/Faction.h index de50f29b9..7833bc1f8 100644 --- a/include/vcmi/Faction.h +++ b/include/vcmi/Faction.h @@ -18,8 +18,12 @@ class FactionID; class DLL_LINKAGE Faction : public EntityT { + using EntityT::getName; public: virtual bool hasTown() const = 0; + + virtual std::string getNameTranslated() const = 0; + virtual std::string getNameTextID() const = 0; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 8fcd22251..4f00b6411 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -328,7 +328,7 @@ CGHeroInstance * CGameState::HeroesPool::pickHeroFor(bool native, PlayerColor pl if(player>=PlayerColor::PLAYER_LIMIT) { - logGlobal->error("Cannot pick hero for faction %d. Wrong owner!", town->faction->index); + logGlobal->error("Cannot pick hero for faction %s. Wrong owner!", town->faction->getJsonKey()); return nullptr; } @@ -339,7 +339,7 @@ CGHeroInstance * CGameState::HeroesPool::pickHeroFor(bool native, PlayerColor pl for(auto & elem : available) { if(pavailable.find(elem.first)->second & 1<type->heroClass->faction == town->faction->index) + && elem.second->type->heroClass->faction == town->faction->getIndex()) { pool.push_back(elem.second); //get all available heroes } @@ -364,7 +364,7 @@ CGHeroInstance * CGameState::HeroesPool::pickHeroFor(bool native, PlayerColor pl ( !bannedClass || elem.second->type->heroClass != bannedClass) ) // and his class is not same as other hero { pool.push_back(elem.second); - sum += elem.second->type->heroClass->selectionProbability[town->faction->index]; //total weight + sum += elem.second->type->heroClass->selectionProbability[town->faction->getIndex()]; //total weight } } if(!pool.size() || sum == 0) @@ -376,7 +376,7 @@ CGHeroInstance * CGameState::HeroesPool::pickHeroFor(bool native, PlayerColor pl r = rand.nextInt(sum - 1); for (auto & elem : pool) { - r -= elem->type->heroClass->selectionProbability[town->faction->index]; + r -= elem->type->heroClass->selectionProbability[town->faction->getIndex()]; if(r < 0) { ret = elem; @@ -623,7 +623,7 @@ std::pair CGameState::pickObject (CGObjectInstance *obj) if (result.first == Obj::NO_OBJ) { - logGlobal->error("Error: failed to find dwelling for %s of level %d", (*VLC->townh)[faction]->name, int(level)); + logGlobal->error("Error: failed to find dwelling for %s of level %d", (*VLC->townh)[faction]->getNameTranslated(), int(level)); result = std::make_pair(Obj::CREATURE_GENERATOR1, *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), getRandomGenerator())); } @@ -1711,9 +1711,10 @@ void CGameState::initTowns() { vti->town = (*VLC->townh)[vti->subID]->town; } - if(vti->name.empty()) + if(vti->getNameTranslated().empty()) { - vti->name = *RandomGeneratorUtil::nextItem(vti->town->names, getRandomGenerator()); + size_t nameID = getRandomGenerator().nextInt(vti->town->getRandomNamesCount()); + vti->setNameTranslated(vti->town->getRandomNameTranslated(nameID)); } //init buildings @@ -3076,7 +3077,7 @@ void InfoAboutTown::initFromTown(const CGTownInstance *t, bool detailed) army = ArmyDescriptor(t->getUpperArmy(), detailed); built = t->builded; fortLevel = t->fortLevel(); - name = t->name; + name = t->getNameTranslated(); tType = t->town; vstd::clear_pointer(details); diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 9b1eda46d..574048b7b 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -364,11 +364,11 @@ void CHeroClassHandler::afterLoadFinalization() { if (!faction->town) continue; - if (heroClass->selectionProbability.count(faction->index)) + if (heroClass->selectionProbability.count(faction->getIndex())) continue; float chance = static_cast(heroClass->defaultTavernChance * faction->town->defaultTavernChance); - heroClass->selectionProbability[faction->index] = static_cast(sqrt(chance) + 0.5); //FIXME: replace with std::round once MVS supports it + heroClass->selectionProbability[faction->getIndex()] = static_cast(sqrt(chance) + 0.5); //FIXME: replace with std::round once MVS supports it } // set default probabilities for gaining secondary skills where not loaded previously heroClass->secSkillProbability.resize(VLC->skillh->size(), -1); diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index ad95127ae..3b7170515 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -44,14 +44,29 @@ const std::map CBuilding::TOWER_TYPES = { "skyship", CBuilding::HEIGHT_SKYSHIP } }; -const std::string & CBuilding::Name() const +std::string CBuilding::getJsonKey() const { - return name; + return identifier; } -const std::string & CBuilding::Description() const +std::string CBuilding::getNameTranslated() const { - return description; + return VLC->generaltexth->translate(getNameTextID()); +} + +std::string CBuilding::getDescriptionTranslated() const +{ + return VLC->generaltexth->translate(getDescriptionTextID()); +} + +std::string CBuilding::getNameTextID() const +{ + return TextIdentifier("building", town->faction->getJsonKey(), modScope, identifier, "name").get(); +} + +std::string CBuilding::getDescriptionTextID() const +{ + return TextIdentifier("building", town->faction->getJsonKey(), modScope, identifier, "description").get(); } BuildingID CBuilding::getBase() const @@ -109,7 +124,7 @@ int32_t CFaction::getIconIndex() const const std::string & CFaction::getName() const { - return name; + return identifier; } const std::string & CFaction::getJsonKey() const @@ -138,6 +153,16 @@ void CFaction::registerIcons(const IconRegistar & cb) const } } +std::string CFaction::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + +std::string CFaction::getNameTextID() const +{ + return TextIdentifier("faction", modScope, identifier, "name").get(); +} + FactionID CFaction::getId() const { return FactionID(index); @@ -173,12 +198,19 @@ CTown::~CTown() str.dellNull(); } -std::string CTown::getLocalizedFactionName() const +std::string CTown::getRandomNameTranslated(size_t index) const { - if(faction == nullptr) - return "Random"; - else - return faction->name; + return VLC->generaltexth->translate(getRandomNameTextID(index)); +} + +std::string CTown::getRandomNameTextID(size_t index) const +{ + return TextIdentifier("faction", faction->getJsonKey(), "randomName", index).get(); +} + +size_t CTown::getRandomNamesCount() const +{ + return namesCount; } std::string CTown::getBuildingScope() const @@ -187,7 +219,7 @@ std::string CTown::getBuildingScope() const //no faction == random faction return "building"; else - return "building." + faction->identifier; + return "building." + faction->getJsonKey(); } std::set CTown::getAllBuildings() const @@ -231,6 +263,11 @@ void CTown::setGreeting(BuildingSubID::EBuildingSubID subID, const std::string m CTownHandler::CTownHandler() { randomTown = new CTown(); + randomFaction = new CFaction(); + randomFaction->town = randomTown; + randomTown->faction = randomFaction; + randomFaction->identifier = "random"; + randomFaction->modScope = "core"; } CTownHandler::~CTownHandler() @@ -460,10 +497,10 @@ void CTownHandler::addBonusesForVanilaBuilding(CBuilding * building) } else if(building->bid == BuildingID::GRAIL && building->town->faction != nullptr - && boost::algorithm::ends_with(building->town->faction->identifier, ":cove")) + && boost::algorithm::ends_with(building->town->faction->getJsonKey(), ":cove")) { static TPropagatorPtr allCreaturesPropagator(new CPropagatorNodeType(CBonusSystemNode::ENodeTypes::ALL_CREATURES)); - static auto factionLimiter = std::make_shared(building->town->faction->index); + static auto factionLimiter = std::make_shared(building->town->faction->getIndex()); b = createBonus(building, Bonus::NO_TERRAIN_PENALTY, 0, allCreaturesPropagator); b->addLimiter(factionLimiter); } @@ -505,7 +542,7 @@ std::shared_ptr CTownHandler::createBonus(CBuilding * build, Bonus::Bonus std::shared_ptr CTownHandler::createBonus(CBuilding * build, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype) { std::ostringstream descr; - descr << build->name; + descr << build->getNameTranslated(); return createBonusImpl(build->bid, type, val, prop, descr.str(), subtype); } @@ -523,7 +560,7 @@ void CTownHandler::loadSpecialBuildingBonuses(const JsonNode & source, BonusList { for(auto b : source.Vector()) { - auto bonus = JsonUtils::parseBuildingBonus(b, building->bid, building->name); + auto bonus = JsonUtils::parseBuildingBonus(b, building->bid, building->getNameTranslated()); if(bonus == nullptr) continue; @@ -533,7 +570,7 @@ void CTownHandler::loadSpecialBuildingBonuses(const JsonNode & source, BonusList auto limPtr = dynamic_cast(bonus->limiter.get()); if(limPtr != nullptr && limPtr->faction == (TFaction)-1) - limPtr->faction = building->town->faction->index; + limPtr->faction = building->town->faction->getIndex(); } //JsonUtils::parseBuildingBonus produces UNKNOWN type propagator instead of empty. if(bonus->propagator != nullptr @@ -567,8 +604,10 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons ret->identifier = stringID; ret->town = town; - ret->name = source["name"].String(); - ret->description = source["description"].String(); + + VLC->generaltexth->registerString(ret->getNameTextID(), source["name"].String()); + VLC->generaltexth->registerString(ret->getDescriptionTextID(), source["description"].String()); + ret->resources = TResources(source["cost"]); ret->produce = TResources(source["produce"]); @@ -589,7 +628,7 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons ret->subId = BuildingSubID::CUSTOM_VISITING_BONUS; for(auto & bonus : ret->onVisitBonuses) - bonus->sid = Bonus::getSid32(ret->town->faction->index, ret->bid); + bonus->sid = Bonus::getSid32(ret->town->faction->getIndex(), ret->bid); } } //MODS COMPATIBILITY FOR 0.96 @@ -630,7 +669,7 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons if(stringID == source["upgrades"].String()) { throw std::runtime_error(boost::str(boost::format("Building with ID '%s' of town '%s' can't be an upgrade of the same building.") % - stringID % ret->town->getLocalizedFactionName())); + stringID % ret->town->faction->getNameTranslated())); } VLC->modh->identifiers.requestIdentifier(ret->town->getBuildingScope(), source["upgrades"], [=](si32 identifier) @@ -664,21 +703,21 @@ void CTownHandler::loadStructure(CTown &town, const std::string & stringID, cons ret->building = nullptr; ret->buildable = nullptr; - VLC->modh->identifiers.tryRequestIdentifier( source.meta, "building." + town.faction->identifier, stringID, [=, &town](si32 identifier) mutable + VLC->modh->identifiers.tryRequestIdentifier( source.meta, "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable { ret->building = town.buildings[BuildingID(identifier)]; }); if (source["builds"].isNull()) { - VLC->modh->identifiers.tryRequestIdentifier( source.meta, "building." + town.faction->identifier, stringID, [=, &town](si32 identifier) mutable + VLC->modh->identifiers.tryRequestIdentifier( source.meta, "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable { ret->building = town.buildings[BuildingID(identifier)]; }); } else { - VLC->modh->identifiers.requestIdentifier("building." + town.faction->identifier, source["builds"], [=, &town](si32 identifier) mutable + VLC->modh->identifiers.requestIdentifier("building." + town.faction->getJsonKey(), source["builds"], [=, &town](si32 identifier) mutable { ret->buildable = town.buildings[BuildingID(identifier)]; }); @@ -729,7 +768,7 @@ void CTownHandler::loadTownHall(CTown &town, const JsonNode & source) auto & dst = dstBox[k]; auto & src = srcBox[k]; - VLC->modh->identifiers.requestIdentifier("building." + town.faction->identifier, src, [&](si32 identifier) + VLC->modh->identifiers.requestIdentifier("building." + town.faction->getJsonKey(), src, [&](si32 identifier) { dst = BuildingID(identifier); }); @@ -757,7 +796,7 @@ void CTownHandler::loadSiegeScreen(CTown &town, const JsonNode & source) auto crId = CreatureID(creature); if(!(*VLC->creh)[crId]->animation.missleFrameAngles.size()) logMod->error("Mod '%s' error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Siege will not work properly!" - , town.faction->name + , town.faction->getNameTranslated() , (*VLC->creh)[crId]->getNameSingularTranslated()); town.clientInfo.siegeShooter = crId; @@ -848,7 +887,13 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source) town->moatHexes = source["moatHexes"].convertTo >(); town->mageLevel = static_cast(source["mageGuild"].Float()); - town->names = source["names"].convertTo >(); + + town->namesCount = 0; + for (auto const & name : source["names"].Vector()) + { + VLC->generaltexth->registerString(town->getRandomNameTextID(town->namesCount), name.String()); + town->namesCount += 1; + } // Horde building creature level for(const JsonNode &node : source["horde"].Vector()) @@ -886,7 +931,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source) VLC->modh->identifiers.requestIdentifier(node.second.meta, "heroClass",node.first, [=](si32 classID) { - VLC->heroh->classes[HeroClassID(classID)]->selectionProbability[town->faction->index] = chance; + VLC->heroh->classes[HeroClassID(classID)]->selectionProbability[town->faction->getIndex()] = chance; }); } @@ -896,7 +941,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source) VLC->modh->identifiers.requestIdentifier(node.second.meta, "spell", node.first, [=](si32 spellID) { - VLC->spellh->objects.at(spellID)->probabilities[town->faction->index] = chance; + VLC->spellh->objects.at(spellID)->probabilities[town->faction->getIndex()] = chance; }); } @@ -941,9 +986,11 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode auto faction = new CFaction(); faction->index = static_cast(index); - faction->name = source["name"].String(); + faction->modScope = scope; faction->identifier = identifier; + VLC->generaltexth->registerString(faction->getNameTextID(), source["name"].String()); + faction->creatureBg120 = source["creatureBackground"]["120px"].String(); faction->creatureBg130 = source["creatureBackground"]["130px"].String(); diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 29b5d70b3..389dc7a85 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -38,9 +38,8 @@ class JsonSerializeFormat; class DLL_LINKAGE CBuilding { - - std::string name; - std::string description; + std::string modScope; + std::string identifier; public: typedef LogicalExpression TRequired; @@ -49,7 +48,6 @@ public: TResources resources; TResources produce; TRequired requirements; - std::string identifier; BuildingID bid; //structure ID BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty @@ -80,8 +78,13 @@ public: CBuilding() : town(nullptr), mode(BUILD_NORMAL) {}; - const std::string &Name() const; - const std::string &Description() const; + std::string getJsonKey() const; + + std::string getNameTranslated() const; + std::string getDescriptionTranslated() const; + + std::string getNameTextID() const; + std::string getDescriptionTextID() const; //return base of upgrade(s) or this BuildingID getBase() const; @@ -116,13 +119,12 @@ public: template void serialize(Handler &h, const int version) { + h & modScope; h & identifier; h & town; h & bid; h & resources; h & produce; - h & name; - h & description; h & requirements; h & upgrade; h & mode; @@ -180,12 +182,15 @@ struct DLL_LINKAGE SPuzzleInfo class DLL_LINKAGE CFaction : public Faction { -public: - std::string name; //town name, by default - from TownName.txt + friend class CTownHandler; + std::string modScope; //town name, by default - from TownName.txt std::string identifier; TFaction index; + const std::string & getName() const override; + +public: TerrainId nativeTerrain; EAlignment::EAlignment alignment; bool preferUndergroundPlacement; @@ -202,11 +207,13 @@ public: int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getName() const override; const std::string & getJsonKey() const override; void registerIcons(const IconRegistar & cb) const override; FactionID getId() const override; + std::string getNameTranslated() const override; + std::string getNameTextID() const override; + bool hasTown() const override; void updateFrom(const JsonNode & data); @@ -214,7 +221,7 @@ public: template void serialize(Handler &h, const int version) { - h & name; + h & modScope; h & identifier; h & index; h & nativeTerrain; @@ -228,11 +235,13 @@ public: class DLL_LINKAGE CTown { + friend class CTownHandler; + size_t namesCount; + public: CTown(); ~CTown(); - std::string getLocalizedFactionName() const; std::string getBuildingScope() const; std::set getAllBuildings() const; const CBuilding * getSpecialBuilding(BuildingSubID::EBuildingSubID subID) const; @@ -240,9 +249,11 @@ public: void setGreeting(BuildingSubID::EBuildingSubID subID, const std::string message) const; //may affect only mutable field BuildingID::EBuildingID getBuildingType(BuildingSubID::EBuildingSubID subID) const; - CFaction * faction; + std::string getRandomNameTranslated(size_t index) const; + std::string getRandomNameTextID(size_t index) const; + size_t getRandomNamesCount() const; - std::vector names; //names of the town instances + CFaction * faction; /// level -> list of creatures on this tier // TODO: replace with pointers to CCreature @@ -327,7 +338,7 @@ public: template void serialize(Handler &h, const int version) { - h & names; + h & namesCount; h & faction; h & creatures; h & dwellings; @@ -400,6 +411,7 @@ public: static R getMappedValue(const JsonNode & node, const R defval, const std::map & map, bool required = true); CTown * randomTown; + CFaction * randomFaction; CTownHandler(); ~CTownHandler(); diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 48ac932d4..2ebdc7f6b 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -738,7 +738,7 @@ void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const std::string CGTownInstance::getObjectName() const { - return name + ", " + town->faction->name; + return name + ", " + town->faction->getNameTranslated(); } bool CGTownInstance::townEnvisagesBuilding(BuildingSubID::EBuildingSubID subId) const @@ -894,7 +894,7 @@ void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structu bool CGTownInstance::hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const { - return (this->town->faction != nullptr && this->town->faction->index == type && hasBuilt(bid)); + return (this->town->faction != nullptr && this->town->faction->getIndex() == type && hasBuilt(bid)); } void CGTownInstance::newTurn(CRandomGenerator & rand) const @@ -1178,7 +1178,7 @@ void CGTownInstance::updateAppearance() std::string CGTownInstance::nodeName() const { - return "Town (" + (town ? town->faction->name : "unknown") + ") of " + name; + return "Town (" + (town ? town->faction->getNameTranslated() : "unknown") + ") of " + name; } void CGTownInstance::deserializationFix() @@ -1332,6 +1332,16 @@ CBonusSystemNode & CGTownInstance::whatShouldBeAttached() return townAndVis; } +std::string CGTownInstance::getNameTranslated() const +{ + return name; +} + +void CGTownInstance::setNameTranslated( const std::string & newName ) +{ + name = newName; +} + const CArmedInstance * CGTownInstance::getUpperArmy() const { if(garrisonHero) @@ -1377,7 +1387,7 @@ bool CGTownInstance::hasBuilt(BuildingID buildingID) const bool CGTownInstance::hasBuilt(BuildingID buildingID, int townID) const { - if (townID == town->faction->index || townID == ETownType::ANY) + if (townID == town->faction->getIndex() || townID == ETownType::ANY) return hasBuilt(buildingID); return false; } @@ -1513,7 +1523,7 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler) auto encodeBuilding = [this](si32 index) -> std::string { - return getTown()->buildings.at(BuildingID(index))->identifier; + return getTown()->buildings.at(BuildingID(index))->getJsonKey(); }; const std::set standard = getTown()->getAllBuildings();//by default all buildings are allowed @@ -1760,7 +1770,7 @@ void CTownBonus::onHeroVisit (const CGHeroInstance * h) const case BuildingSubID::CUSTOM_VISITING_BONUS: const auto building = town->town->buildings.at(bID); - if(!h->hasBonusFrom(Bonus::TOWN_STRUCTURE, Bonus::getSid32(building->town->faction->index, building->bid))) + if(!h->hasBonusFrom(Bonus::TOWN_STRUCTURE, Bonus::getSid32(building->town->faction->getIndex(), building->bid))) { const auto & bonuses = building->onVisitBonuses; applyBonuses(const_cast(h), bonuses); @@ -1819,7 +1829,7 @@ GrowthInfo::Entry::Entry(const std::string &format, int _count) GrowthInfo::Entry::Entry(int subID, BuildingID building, int _count) : count(_count) { - description = boost::str(boost::format("%s %+d") % (*VLC->townh)[subID]->town->buildings.at(building)->Name() % count); + description = boost::str(boost::format("%s %+d") % (*VLC->townh)[subID]->town->buildings.at(building)->getNameTranslated() % count); } GrowthInfo::Entry::Entry(int _count, const std::string &fullDescription) @@ -1870,12 +1880,12 @@ const std::string CGTownBuilding::getVisitingBonusGreeting() const bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingDefence")); break; } - auto buildingName = town->town->getSpecialBuilding(bType)->Name(); + auto buildingName = town->town->getSpecialBuilding(bType)->getNameTranslated(); if(bonusGreeting.empty()) { bonusGreeting = "Error: Bonus greeting for '%s' is not localized."; - logGlobal->error("'%s' building of '%s' faction has not localized bonus greeting.", buildingName, town->town->getLocalizedFactionName()); + logGlobal->error("'%s' building of '%s' faction has not localized bonus greeting.", buildingName, town->town->faction->getNameTranslated()); } boost::algorithm::replace_first(bonusGreeting, "%s", buildingName); town->town->setGreeting(bType, bonusGreeting); @@ -1887,7 +1897,7 @@ const std::string CGTownBuilding::getCustomBonusGreeting(const Bonus & bonus) co if(bonus.type == Bonus::TOWN_MAGIC_WELL) { auto bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingInTownMagicWell")); - auto buildingName = town->town->getSpecialBuilding(bType)->Name(); + auto buildingName = town->town->getSpecialBuilding(bType)->getNameTranslated(); boost::algorithm::replace_first(bonusGreeting, "%s", buildingName); return bonusGreeting; } diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index bfd8b9052..908da9c68 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -205,12 +205,12 @@ struct DLL_LINKAGE GrowthInfo class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket { + std::string name; // name of town public: enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3}; CTownAndVisitingHero townAndVis; const CTown * town; - std::string name; // name of town si32 builded; //how many buildings has been built this turn si32 destroyed; //how many buildings has been destroyed this turn ConstTransitivePtr garrisonHero, visitingHero; @@ -283,6 +283,9 @@ public: void setGarrisonedHero(CGHeroInstance *h); const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself + std::string getNameTranslated() const; + void setNameTranslated( std::string const & newName ); + ////////////////////////////////////////////////////////////////////////// bool passableFor(PlayerColor color) const override; diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp index bbd50ea07..c0bf427b9 100644 --- a/lib/mapObjects/CommonConstructors.cpp +++ b/lib/mapObjects/CommonConstructors.cpp @@ -58,7 +58,7 @@ void CTownInstanceConstructor::afterLoadFinalization() { filters[entry.first] = LogicalExpression(entry.second, [this](const JsonNode & node) { - return BuildingID(VLC->modh->identifiers.getIdentifier("building." + faction->identifier, node.Vector()[0]).get()); + return BuildingID(VLC->modh->identifiers.getIdentifier("building." + faction->getJsonKey(), node.Vector()[0]).get()); }); } } diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 82aa93516..58355ee4e 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -565,7 +565,7 @@ void CMap::checkForObjectives() { const CGTownInstance *town = dynamic_cast(cond.object); if (town) - boost::algorithm::replace_first(event.onFulfill, "%s", town->name); + boost::algorithm::replace_first(event.onFulfill, "%s", town->getNameTranslated()); const CGHeroInstance *hero = dynamic_cast(cond.object); if (hero) boost::algorithm::replace_first(event.onFulfill, "%s", hero->getNameTranslated()); diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 4d8a0d0e6..79ea208dd 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1942,7 +1942,7 @@ CGTownInstance * CMapLoaderH3M::readTown(int castleID) bool hasName = reader.readBool(); if(hasName) { - nt->name = reader.readString(); + nt->setNameTranslated( reader.readString()); } bool hasGarrison = reader.readBool(); diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index bc1df5ba2..528eb1811 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -388,11 +388,11 @@ void CMapFormatJson::serializeAllowedFactions(JsonSerializeFormat & handler, std temp.resize(VLC->townh->size(), false); auto standard = VLC->townh->getDefaultAllowed(); - if(handler.saving) + if(handler.saving) { for(auto faction : VLC->townh->objects) - if(faction->town && vstd::contains(value, faction->index)) - temp[std::size_t(faction->index)] = true; + if(faction->town && vstd::contains(value, faction->getIndex())) + temp[std::size_t(faction->getIndex())] = true; } handler.serializeLIC("allowedFactions", &FactionID::decode, &FactionID::encode, standard, temp); diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 760204810..5f02c0821 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -175,7 +175,7 @@ std::string CMapGenerator::getMapDescription() const if(pSettings.getStartingTown() != CMapGenOptions::CPlayerSettings::RANDOM_TOWN) { ss << ", " << GameConstants::PLAYER_COLOR_NAMES[pSettings.getColor().getNum()] - << " town choice is " << (*VLC->townh)[pSettings.getStartingTown()]->name; + << " town choice is " << (*VLC->townh)[pSettings.getStartingTown()]->getNameTranslated(); } } diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index e260859f1..7f640c637 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -264,7 +264,7 @@ void Inspector::updateProperties(CGTownInstance * o) { if(!o) return; - addProperty("Town name", o->name, false); + addProperty("Town name", o->getNameTranslated(), false); auto * delegate = new TownBuildingsDelegate(*o); addProperty("Buildings", PropertyEditorPlaceholder(), delegate, false); @@ -492,7 +492,7 @@ void Inspector::setProperty(CGTownInstance * o, const QString & key, const QVari if(!o) return; if(key == "Town name") - o->name = value.toString().toStdString(); + o->setNameTranslated(value.toString().toStdString()); } void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVariant & value) diff --git a/mapeditor/inspector/townbulidingswidget.cpp b/mapeditor/inspector/townbulidingswidget.cpp index 6164c92cd..35ef4f8c4 100644 --- a/mapeditor/inspector/townbulidingswidget.cpp +++ b/mapeditor/inspector/townbulidingswidget.cpp @@ -95,7 +95,7 @@ QStandardItem * TownBulidingsWidget::addBuilding(const CTown & ctown, int bId, s return nullptr; } - QString name = tr(building->Name().c_str()); + QString name = tr(building->getNameTranslated().c_str()); if(name.isEmpty()) name = QString::fromStdString(defaultBuildingIdConversion(buildingId)); diff --git a/mapeditor/playerparams.cpp b/mapeditor/playerparams.cpp index 0e0ed34a2..573764122 100644 --- a/mapeditor/playerparams.cpp +++ b/mapeditor/playerparams.cpp @@ -28,7 +28,7 @@ PlayerParams::PlayerParams(MapController & ctrl, int playerId, QWidget *parent) for(auto idx : VLC->townh->getAllowedFactions()) { CFaction * faction = VLC->townh->objects.at(idx); - auto * item = new QListWidgetItem(QString::fromStdString(faction->name)); + auto * item = new QListWidgetItem(QString::fromStdString(faction->getNameTranslated())); item->setData(Qt::UserRole, QVariant::fromValue(idx)); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); ui->allowedFactions->addItem(item); @@ -64,7 +64,7 @@ PlayerParams::PlayerParams(MapController & ctrl, int playerId, QWidget *parent) { if(playerInfo.hasMainTown && playerInfo.posOfMainTown == town->pos) foundMainTown = townIndex; - const auto name = ctown->faction ? town->getObjectName() : town->name + ", (random)"; + const auto name = ctown->faction ? town->getObjectName() : town->getNameTranslated() + ", (random)"; ui->mainTown->addItem(tr(name.c_str()), QVariant::fromValue(i)); ++townIndex; } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 3a292acf2..c2de15c0a 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1704,7 +1704,7 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa const PlayerState * p = getPlayerState(town->tempOwner); if (!p) { - logGlobal->warn("There is no player owner of town %s at %s", town->name, town->pos.toString()); + logGlobal->warn("There is no player owner of town %s at %s", town->getNameTranslated(), town->pos.toString()); return; } @@ -2512,7 +2512,7 @@ bool CGameHandler::teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui if (((h->getOwner() != t->getOwner()) && complain("Cannot teleport hero to another player")) - || (from->town->faction->index != t->town->faction->index + || (from->town->faction->getId() != t->town->faction->getId() && complain("Source town and destination town should belong to the same faction")) || ((!from || !from->hasBuilt(BuildingSubID::CASTLE_GATE)) @@ -3437,9 +3437,9 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, if(!t) COMPLAIN_RETF("No such town (ID=%s)!", tid); if(!t->town->buildings.count(requestedID)) - COMPLAIN_RETF("Town of faction %s does not have info about building ID=%s!", t->town->faction->name % requestedID); + COMPLAIN_RETF("Town of faction %s does not have info about building ID=%s!", t->town->faction->getNameTranslated() % requestedID); if(t->hasBuilt(requestedID)) - COMPLAIN_RETF("Building %s is already built in %s", t->town->buildings.at(requestedID)->Name() % t->name); + COMPLAIN_RETF("Building %s is already built in %s", t->town->buildings.at(requestedID)->getNameTranslated() % t->getNameTranslated()); const CBuilding * requestedBuilding = t->town->buildings.at(requestedID); @@ -7066,7 +7066,7 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons for (auto & build : town->town->buildings) { if (!town->hasBuilt(build.first) - && !build.second->Name().empty() + && !build.second->getNameTranslated().empty() && build.first != BuildingID::SHIP) { buildStructure(town->id, build.first, true); From 47c1803c424dc0331f47de250a4d4965eb03f996 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 18 Jan 2023 23:56:01 +0200 Subject: [PATCH 185/197] Finalization of refactoring: - Entity interface now has getNameTranslated & getNameTextID methods - Entity interface no longer has getName method - removed (most) usages of normalizeIndentifier workaround method - all moddable objects have identifier in form of mod:name - all moddable object register strings in form of mod.type.name --- AI/VCAI/Goals/AdventureSpellCast.cpp | 12 ++--- client/ClientCommandManager.cpp | 2 +- include/vcmi/Artifact.h | 4 +- include/vcmi/Creature.h | 6 +-- include/vcmi/Entity.h | 5 +- include/vcmi/Faction.h | 3 -- include/vcmi/HeroClass.h | 6 --- include/vcmi/HeroType.h | 4 -- include/vcmi/spells/Spell.h | 2 - lib/BattleFieldHandler.cpp | 64 +++++++++--------------- lib/BattleFieldHandler.h | 5 +- lib/CArtHandler.cpp | 28 +++++------ lib/CArtHandler.h | 4 +- lib/CCreatureHandler.cpp | 13 +++-- lib/CCreatureHandler.h | 3 +- lib/CGameState.cpp | 2 +- lib/CGeneralTextHandler.cpp | 2 + lib/CHeroHandler.cpp | 32 ++++++------ lib/CHeroHandler.h | 8 ++- lib/CSkillHandler.cpp | 13 +++-- lib/CSkillHandler.h | 8 ++- lib/CTownHandler.cpp | 27 +++++----- lib/CTownHandler.h | 7 +-- lib/IHandlerBase.cpp | 5 -- lib/IHandlerBase.h | 6 +-- lib/ObstacleHandler.cpp | 12 +++-- lib/ObstacleHandler.h | 5 +- lib/RiverHandler.cpp | 2 + lib/RiverHandler.h | 7 ++- lib/RoadHandler.cpp | 2 + lib/RoadHandler.h | 7 ++- lib/ScriptHandler.cpp | 2 +- lib/TerrainHandler.cpp | 2 + lib/TerrainHandler.h | 7 ++- lib/mapObjects/CObjectClassesHandler.cpp | 4 +- lib/rmg/CRmgTemplateStorage.cpp | 2 +- lib/spells/CSpellHandler.cpp | 15 +++--- lib/spells/CSpellHandler.h | 3 +- lib/spells/ISpellMechanics.cpp | 2 +- mapeditor/inspector/rewardswidget.cpp | 2 +- mapeditor/mapsettings.cpp | 2 +- 41 files changed, 154 insertions(+), 193 deletions(-) diff --git a/AI/VCAI/Goals/AdventureSpellCast.cpp b/AI/VCAI/Goals/AdventureSpellCast.cpp index d64c39c40..b0c49ff81 100644 --- a/AI/VCAI/Goals/AdventureSpellCast.cpp +++ b/AI/VCAI/Goals/AdventureSpellCast.cpp @@ -33,16 +33,16 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve() auto spell = getSpell(); - logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getName(), hero->getNameTranslated()); + logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getNameTranslated(), hero->getNameTranslated()); if(!spell->isAdventure()) - throw cannotFulfillGoalException(spell->getName() + " is not an adventure spell."); + throw cannotFulfillGoalException(spell->getNameTranslated() + " is not an adventure spell."); if(!hero->canCastThisSpell(spell)) - throw cannotFulfillGoalException("Hero can not cast " + spell->getName()); + throw cannotFulfillGoalException("Hero can not cast " + spell->getNameTranslated()); if(hero->mana < hero->getSpellCost(spell)) - throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getName()); + throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated()); if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero) throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated()); @@ -75,10 +75,10 @@ void AdventureSpellCast::accept(VCAI * ai) std::string AdventureSpellCast::name() const { - return "AdventureSpellCast " + getSpell()->getName(); + return "AdventureSpellCast " + getSpell()->getNameTranslated(); } std::string AdventureSpellCast::completeMessage() const { - return "Spell cast successfully " + getSpell()->getName(); + return "Spell cast successfully " + getSpell()->getNameTranslated(); } diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 3113b4342..1e8c9f30f 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -195,7 +195,7 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle { const JsonNode & object = nameAndObject.second; - std::string name = CModHandler::normalizeIdentifier(object.meta, CModHandler::scopeBuiltin(), nameAndObject.first); + std::string name = CModHandler::makeFullIdentifier(object.meta, contentName, nameAndObject.first); boost::algorithm::replace_all(name,":","_"); diff --git a/include/vcmi/Artifact.h b/include/vcmi/Artifact.h index fda53de1a..e675bb487 100644 --- a/include/vcmi/Artifact.h +++ b/include/vcmi/Artifact.h @@ -19,8 +19,8 @@ class CreatureID; class DLL_LINKAGE Artifact : public EntityWithBonuses { - using EntityWithBonuses::getName; public: + virtual bool isBig() const = 0; virtual bool isTradable() const = 0; virtual uint32_t getPrice() const = 0; @@ -28,11 +28,9 @@ public: virtual std::string getDescriptionTranslated() const = 0; virtual std::string getEventTranslated() const = 0; - virtual std::string getNameTranslated() const = 0; virtual std::string getDescriptionTextID() const = 0; virtual std::string getEventTextID() const = 0; - virtual std::string getNameTextID() const = 0; }; VCMI_LIB_NAMESPACE_END diff --git a/include/vcmi/Creature.h b/include/vcmi/Creature.h index 91996e39d..d29fa4480 100644 --- a/include/vcmi/Creature.h +++ b/include/vcmi/Creature.h @@ -19,10 +19,10 @@ class CreatureID; class DLL_LINKAGE Creature : public EntityWithBonuses { protected: - using EntityWithBonuses::getName; + // use getNamePlural/Singular instead + std::string getNameTranslated() const override = 0; + std::string getNameTextID() const override = 0; - virtual std::string getNameTranslated() const = 0; - virtual std::string getNameTextID() const = 0; public: virtual std::string getNamePluralTranslated() const = 0; virtual std::string getNameSingularTranslated() const = 0; diff --git a/include/vcmi/Entity.h b/include/vcmi/Entity.h index 280d852cf..ec64feaad 100644 --- a/include/vcmi/Entity.h +++ b/include/vcmi/Entity.h @@ -23,8 +23,9 @@ public: virtual int32_t getIndex() const = 0; virtual int32_t getIconIndex() const = 0; - virtual const std::string & getJsonKey() const = 0; - virtual const std::string & getName() const = 0; + virtual std::string getJsonKey() const = 0; + virtual std::string getNameTranslated() const = 0; + virtual std::string getNameTextID() const = 0; virtual void registerIcons(const IconRegistar & cb) const = 0; }; diff --git a/include/vcmi/Faction.h b/include/vcmi/Faction.h index 7833bc1f8..0939c154c 100644 --- a/include/vcmi/Faction.h +++ b/include/vcmi/Faction.h @@ -18,12 +18,9 @@ class FactionID; class DLL_LINKAGE Faction : public EntityT { - using EntityT::getName; public: virtual bool hasTown() const = 0; - virtual std::string getNameTranslated() const = 0; - virtual std::string getNameTextID() const = 0; }; VCMI_LIB_NAMESPACE_END diff --git a/include/vcmi/HeroClass.h b/include/vcmi/HeroClass.h index 3f6c47c53..3cbed38eb 100644 --- a/include/vcmi/HeroClass.h +++ b/include/vcmi/HeroClass.h @@ -18,12 +18,6 @@ class HeroClassID; class DLL_LINKAGE HeroClass : public EntityT { - using EntityT::getName; -public: - virtual std::string getNameTranslated() const = 0; - virtual std::string getNameTextID() const = 0; - - }; diff --git a/include/vcmi/HeroType.h b/include/vcmi/HeroType.h index 3df57b24b..94d0b2ccf 100644 --- a/include/vcmi/HeroType.h +++ b/include/vcmi/HeroType.h @@ -18,15 +18,11 @@ class HeroTypeID; class DLL_LINKAGE HeroType : public EntityT { - using EntityT::getName; -public: - virtual std::string getNameTranslated() const = 0; virtual std::string getBiographyTranslated() const = 0; virtual std::string getSpecialtyNameTranslated() const = 0; virtual std::string getSpecialtyDescriptionTranslated() const = 0; virtual std::string getSpecialtyTooltipTranslated() const = 0; - virtual std::string getNameTextID() const = 0; virtual std::string getBiographyTextID() const = 0; virtual std::string getSpecialtyNameTextID() const = 0; virtual std::string getSpecialtyDescriptionTextID() const = 0; diff --git a/include/vcmi/spells/Spell.h b/include/vcmi/spells/Spell.h index b260cf9a2..453187e00 100644 --- a/include/vcmi/spells/Spell.h +++ b/include/vcmi/spells/Spell.h @@ -23,8 +23,6 @@ class Caster; class DLL_LINKAGE Spell: public EntityT { - using EntityT::getName; - public: using SchoolCallback = std::function; diff --git a/lib/BattleFieldHandler.cpp b/lib/BattleFieldHandler.cpp index 18fc7664c..3e1c3f34d 100644 --- a/lib/BattleFieldHandler.cpp +++ b/lib/BattleFieldHandler.cpp @@ -16,48 +16,27 @@ VCMI_LIB_NAMESPACE_BEGIN BattleFieldInfo * BattleFieldHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) { + assert(identifier.find(':') == std::string::npos); + BattleFieldInfo * info = new BattleFieldInfo(BattleField(index), identifier); - if(json["graphics"].getType() == JsonNode::JsonType::DATA_STRING) + info->graphics = json["graphics"].String(); + info->icon = json["icon"].String(); + info->name = json["name"].String(); + for(auto b : json["bonuses"].Vector()) { - info->graphics = json["graphics"].String(); - } - - if(json["icon"].getType() == JsonNode::JsonType::DATA_STRING) - { - info->icon = json["icon"].String(); - } - - if(json["name"].getType() == JsonNode::JsonType::DATA_STRING) - { - info->name = json["name"].String(); - } - - if(json["bonuses"].getType() == JsonNode::JsonType::DATA_VECTOR) - { - for(auto b : json["bonuses"].Vector()) - { - auto bonus = JsonUtils::parseBonus(b); - - bonus->source = Bonus::TERRAIN_OVERLAY; - bonus->sid = info->getIndex(); - bonus->duration = Bonus::ONE_BATTLE; - - info->bonuses.push_back(bonus); - } - } - - if(json["isSpecial"].getType() == JsonNode::JsonType::DATA_BOOL) - { - info->isSpecial = json["isSpecial"].Bool(); - } - - if(json["impassableHexes"].getType() == JsonNode::JsonType::DATA_VECTOR) - { - for(auto node : json["impassableHexes"].Vector()) - info->impassableHexes.push_back(BattleHex(node.Integer())); + auto bonus = JsonUtils::parseBonus(b); + + bonus->source = Bonus::TERRAIN_OVERLAY; + bonus->sid = info->getIndex(); + bonus->duration = Bonus::ONE_BATTLE; + + info->bonuses.push_back(bonus); } + info->isSpecial = json["isSpecial"].Bool(); + for(auto node : json["impassableHexes"].Vector()) + info->impassableHexes.push_back(BattleHex(node.Integer())); return info; } @@ -89,14 +68,19 @@ int32_t BattleFieldInfo::getIconIndex() const return iconIndex; } -const std::string & BattleFieldInfo::getName() const +std::string BattleFieldInfo::getJsonKey() const +{ + return identifier; +} + +std::string BattleFieldInfo::getNameTextID() const { return name; } -const std::string & BattleFieldInfo::getJsonKey() const +std::string BattleFieldInfo::getNameTranslated() const { - return identifier; + return name; // TODO? } void BattleFieldInfo::registerIcons(const IconRegistar & cb) const diff --git a/lib/BattleFieldHandler.h b/lib/BattleFieldHandler.h index 3bfd2440e..34d60d8be 100644 --- a/lib/BattleFieldHandler.h +++ b/lib/BattleFieldHandler.h @@ -43,8 +43,9 @@ public: int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getName() const override; - const std::string & getJsonKey() const override; + std::string getJsonKey() const override; + std::string getNameTextID() const override; + std::string getNameTranslated() const override; void registerIcons(const IconRegistar & cb) const override; BattleField getId() const override; diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 2ca86712f..0536a2c76 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -60,14 +60,9 @@ int32_t CArtifact::getIconIndex() const return iconIndex; } -const std::string & CArtifact::getName() const +std::string CArtifact::getJsonKey() const { - return identifier; -} - -const std::string & CArtifact::getJsonKey() const -{ - return identifier; + return modScope + ':' + identifier; } void CArtifact::registerIcons(const IconRegistar & cb) const @@ -103,17 +98,17 @@ std::string CArtifact::getNameTranslated() const std::string CArtifact::getDescriptionTextID() const { - return TextIdentifier("object", modScope, identifier, "description").get(); + return TextIdentifier("artifact", modScope, identifier, "description").get(); } std::string CArtifact::getEventTextID() const { - return TextIdentifier("object", modScope, identifier, "event").get(); + return TextIdentifier("artifact", modScope, identifier, "event").get(); } std::string CArtifact::getNameTextID() const { - return TextIdentifier("object", modScope, identifier, "name").get(); + return TextIdentifier("artifact", modScope, identifier, "name").get(); } uint32_t CArtifact::getPrice() const @@ -181,7 +176,7 @@ int CArtifact::getArtClassSerial() const std::string CArtifact::nodeName() const { - return "Artifact: " + getName(); + return "Artifact: " + getNameTranslated(); } void CArtifact::addNewBonus(const std::shared_ptr& b) @@ -301,7 +296,7 @@ std::vector CArtHandler::loadLegacyData(size_t dataSize) void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), objects.size()); + auto object = loadFromJson(scope, data, name, objects.size()); object->iconIndex = object->getIndex() + 5; @@ -312,7 +307,7 @@ void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { - auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), index); + auto object = loadFromJson(scope, data, name, index); object->iconIndex = object->getIndex(); @@ -330,6 +325,9 @@ const std::vector & CArtHandler::getTypeNames() const CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index) { + assert(identifier.find(':') == std::string::npos); + assert(!scope.empty()); + CArtifact * art; if(!VLC->modh->modules.COMMANDERS || node["growing"].isNull()) @@ -690,11 +688,11 @@ void CArtHandler::erasePickedArt(ArtifactID id) artifactList->erase(itr); } else - logMod->warn("Problem: cannot erase artifact %s from list, it was not present", art->getName()); + logMod->warn("Problem: cannot erase artifact %s from list, it was not present", art->getNameTranslated()); } else - logMod->warn("Problem: cannot find list for artifact %s, strange class. (special?)", art->getName()); + logMod->warn("Problem: cannot find list for artifact %s, strange class. (special?)", art->getNameTranslated()); } boost::optional&> CArtHandler::listFromClass( CArtifact::EartClass artifactClass ) diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index 0ef065344..115702b38 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -50,8 +50,6 @@ class DLL_LINKAGE CArtifact : public Artifact, public CBonusSystemNode //contain std::string modScope; std::string identifier; - const std::string & getName() const override; - public: enum EartClass {ART_SPECIAL=1, ART_TREASURE=2, ART_MINOR=4, ART_MAJOR=8, ART_RELIC=16}; //artifact classes @@ -68,7 +66,7 @@ public: int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getJsonKey() const override; + std::string getJsonKey() const override; void registerIcons(const IconRegistar & cb) const override; ArtifactID getId() const override; virtual const IBonusBearer * accessBonuses() const override; diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index b1712c213..590999476 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -33,14 +33,9 @@ int32_t CCreature::getIconIndex() const return iconIndex; } -const std::string & CCreature::getName() const +std::string CCreature::getJsonKey() const { - return identifier; -} - -const std::string & CCreature::getJsonKey() const -{ - return identifier; + return modScope + ':' + identifier;; } void CCreature::registerIcons(const IconRegistar & cb) const @@ -598,6 +593,9 @@ std::vector CCreatureHandler::loadLegacyData(size_t dataSize) CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index) { + assert(identifier.find(':') == std::string::npos); + assert(!scope.empty()); + auto cre = new CCreature(); if(node["hasDoubleWeek"].Bool()) @@ -607,6 +605,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json cre->idNumber = CreatureID(index); cre->iconIndex = cre->getIndex() + 2; cre->identifier = identifier; + cre->modScope = scope; JsonDeserializer handler(nullptr, node); cre->serializeJson(handler); diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index ae96b590e..01b0d6693 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -37,7 +37,6 @@ class DLL_LINKAGE CCreature : public Creature, public CBonusSystemNode // std::string nameSing;// singular name, e.g. Centaur // std::string namePl; // plural name, e.g. Centaurs - const std::string & getName() const override; std::string getNameTranslated() const override; std::string getNameTextID() const override; @@ -153,7 +152,7 @@ public: TerrainId getNativeTerrain() const; int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getJsonKey() const override; + std::string getJsonKey() const override; void registerIcons(const IconRegistar & cb) const override; CreatureID getId() const override; virtual const IBonusBearer * accessBonuses() const override; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 4f00b6411..a63a5a5f2 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1713,7 +1713,7 @@ void CGameState::initTowns() } if(vti->getNameTranslated().empty()) { - size_t nameID = getRandomGenerator().nextInt(vti->town->getRandomNamesCount()); + size_t nameID = getRandomGenerator().nextInt(vti->town->getRandomNamesCount() - 1); vti->setNameTranslated(vti->town->getRandomNameTranslated(nameID)); } diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index d7ed33199..59d0d5aa8 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -405,6 +405,8 @@ const std::string & CGeneralTextHandler::deserialize(const TextIdentifier & iden void CGeneralTextHandler::registerString(const TextIdentifier & UID, const std::string & localized) { + assert(UID.get().find("..") == std::string::npos); + stringsIdentifiers[localized] = UID.get(); stringsLocalizations[UID.get()] = localized; } diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 574048b7b..30007a2d1 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -41,14 +41,9 @@ int32_t CHero::getIconIndex() const return imageIndex; } -const std::string & CHero::getName() const +std::string CHero::getJsonKey() const { - return identifier; -} - -const std::string & CHero::getJsonKey() const -{ - return identifier; + return modScope + ':' + identifier;; } HeroTypeID CHero::getId() const @@ -168,14 +163,9 @@ int32_t CHeroClass::getIconIndex() const return getIndex(); } -const std::string & CHeroClass::getName() const +std::string CHeroClass::getJsonKey() const { - return identifier; -} - -const std::string & CHeroClass::getJsonKey() const -{ - return identifier; + return modScope + ':' + identifier;; } HeroClassID CHeroClass::getId() const @@ -224,7 +214,7 @@ void CHeroClassHandler::fillPrimarySkillData(const JsonNode & node, CHeroClass * if(currentPrimarySkillValue < primarySkillLegalMinimum) { logMod->error("Hero class '%s' has incorrect initial value '%d' for skill '%s'. Value '%d' will be used instead.", - heroClass->getName(), currentPrimarySkillValue, skillName, primarySkillLegalMinimum); + heroClass->getNameTranslated(), currentPrimarySkillValue, skillName, primarySkillLegalMinimum); currentPrimarySkillValue = primarySkillLegalMinimum; } heroClass->primarySkillInitial.push_back(currentPrimarySkillValue); @@ -240,12 +230,16 @@ const std::vector & CHeroClassHandler::getTypeNames() const CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index) { + assert(identifier.find(':') == std::string::npos); + assert(!scope.empty()); + std::string affinityStr[2] = { "might", "magic" }; auto heroClass = new CHeroClass(); heroClass->id = HeroClassID(index); heroClass->identifier = identifier; + heroClass->modScope = scope; heroClass->imageBattleFemale = node["animation"]["battle"]["female"].String(); heroClass->imageBattleMale = node["animation"]["battle"]["male"].String(); //MODS COMPATIBILITY FOR 0.96 @@ -417,9 +411,13 @@ const std::vector & CHeroHandler::getTypeNames() const CHero * CHeroHandler::loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index) { + assert(identifier.find(':') == std::string::npos); + assert(!scope.empty()); + auto hero = new CHero(); hero->ID = HeroTypeID(index); hero->identifier = identifier; + hero->modScope = scope; hero->sex = node["female"].Bool(); hero->special = node["special"].Bool(); @@ -923,7 +921,7 @@ std::vector CHeroHandler::loadLegacyData(size_t dataSize) void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { size_t index = objects.size(); - auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), index); + auto object = loadFromJson(scope, data, name, index); object->imageIndex = (si32)index + GameConstants::HERO_PORTRAIT_SHIFT; // 2 special frames + some extra portraits objects.push_back(object); @@ -933,7 +931,7 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { - auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), index); + auto object = loadFromJson(scope, data, name, index); object->imageIndex = static_cast(index); assert(objects[index] == nullptr); // ensure that this id was not loaded before diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 546439eac..5e49545c5 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -66,8 +66,6 @@ class DLL_LINKAGE CHero : public HeroType std::string nameTextID; //name of hero std::string biographyTextID; - const std::string & getName() const override; - public: struct InitialArmyStack { @@ -108,7 +106,7 @@ public: int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getJsonKey() const override; + std::string getJsonKey() const override; HeroTypeID getId() const override; void registerIcons(const IconRegistar & cb) const override; @@ -146,6 +144,7 @@ public: h & portraitSmall; h & portraitLarge; h & identifier; + h & modScope; h & battleImage; } }; @@ -162,7 +161,6 @@ class DLL_LINKAGE CHeroClass : public HeroClass std::string identifier; // use getJsonKey instead std::string nameTextID; - const std::string & getName() const override; public: enum EClassAffinity { @@ -197,7 +195,7 @@ public: int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getJsonKey() const override; + std::string getJsonKey() const override; HeroClassID getId() const override; void registerIcons(const IconRegistar & cb) const override; diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index d8a630c24..6e6b03563 100644 --- a/lib/CSkillHandler.cpp +++ b/lib/CSkillHandler.cpp @@ -54,11 +54,6 @@ int32_t CSkill::getIconIndex() const return getIndex(); //TODO: actual value with skill level } -const std::string & CSkill::getName() const -{ - return identifier; -} - std::string CSkill::getNameTextID() const { TextIdentifier id("skill", modScope, identifier, "name"); @@ -70,9 +65,9 @@ std::string CSkill::getNameTranslated() const return VLC->generaltexth->translate(getNameTextID()); } -const std::string & CSkill::getJsonKey() const +std::string CSkill::getJsonKey() const { - return identifier; + return modScope + ':' + identifier;; } std::string CSkill::getDescriptionTextID(int level) const @@ -210,7 +205,11 @@ const std::vector & CSkillHandler::getTypeNames() const CSkill * CSkillHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) { + assert(identifier.find(':') == std::string::npos); + assert(!scope.empty()); + CSkill * skill = new CSkill(SecondarySkill((si32)index), identifier); + skill->modScope = scope; VLC->generaltexth->registerString(skill->getNameTextID(), json["name"].String()); switch(json["gainChance"].getType()) diff --git a/lib/CSkillHandler.h b/lib/CSkillHandler.h index e771a25ca..bf48b18f4 100644 --- a/lib/CSkillHandler.h +++ b/lib/CSkillHandler.h @@ -50,20 +50,18 @@ private: std::string modScope; std::string identifier; - const std::string & getName() const override; - public: CSkill(SecondarySkill id = SecondarySkill::DEFAULT, std::string identifier = "default"); ~CSkill(); int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getJsonKey() const override; + std::string getJsonKey() const override; void registerIcons(const IconRegistar & cb) const override; SecondarySkill getId() const override; - std::string getNameTextID() const; - std::string getNameTranslated() const; + std::string getNameTextID() const override; + std::string getNameTranslated() const override; std::string getDescriptionTextID(int level) const; std::string getDescriptionTranslated(int level) const; diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 3b7170515..4e312c948 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -46,7 +46,7 @@ const std::map CBuilding::TOWER_TYPES = std::string CBuilding::getJsonKey() const { - return identifier; + return modScope + ':' + identifier;; } std::string CBuilding::getNameTranslated() const @@ -61,12 +61,12 @@ std::string CBuilding::getDescriptionTranslated() const std::string CBuilding::getNameTextID() const { - return TextIdentifier("building", town->faction->getJsonKey(), modScope, identifier, "name").get(); + return TextIdentifier("building", modScope, town->faction->identifier, identifier, "name").get(); } std::string CBuilding::getDescriptionTextID() const { - return TextIdentifier("building", town->faction->getJsonKey(), modScope, identifier, "description").get(); + return TextIdentifier("building", modScope, town->faction->identifier, identifier, "description").get(); } BuildingID CBuilding::getBase() const @@ -122,14 +122,9 @@ int32_t CFaction::getIconIndex() const return index; //??? } -const std::string & CFaction::getName() const +std::string CFaction::getJsonKey() const { - return identifier; -} - -const std::string & CFaction::getJsonKey() const -{ - return identifier; + return modScope + ':' + identifier;; } void CFaction::registerIcons(const IconRegistar & cb) const @@ -205,7 +200,7 @@ std::string CTown::getRandomNameTranslated(size_t index) const std::string CTown::getRandomNameTextID(size_t index) const { - return TextIdentifier("faction", faction->getJsonKey(), "randomName", index).get(); + return TextIdentifier("faction", faction->modScope, faction->identifier, "randomName", index).get(); } size_t CTown::getRandomNamesCount() const @@ -582,6 +577,9 @@ void CTownHandler::loadSpecialBuildingBonuses(const JsonNode & source, BonusList void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source) { + assert(stringID.find(':') == std::string::npos); + assert(!source.meta.empty()); + auto ret = new CBuilding(); ret->bid = getMappedValue(stringID, BuildingID::NONE, MappedKeys::BUILDING_NAMES_TO_TYPES, false); @@ -603,6 +601,7 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons ret->height = getMappedValue(source["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES); ret->identifier = stringID; + ret->modScope = source.meta; ret->town = town; VLC->generaltexth->registerString(ret->getNameTextID(), source["name"].String()); @@ -983,6 +982,8 @@ void CTownHandler::loadPuzzle(CFaction &faction, const JsonNode &source) CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode & source, const std::string & identifier, size_t index) { + assert(identifier.find(':') == std::string::npos); + auto faction = new CFaction(); faction->index = static_cast(index); @@ -1032,7 +1033,7 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), objects.size()); + auto object = loadFromJson(scope, data, name, objects.size()); objects.push_back(object); @@ -1071,7 +1072,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { - auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name), index); + auto object = loadFromJson(scope, data, name, index); if (objects.size() > index) assert(objects[index] == nullptr); // ensure that this id was not loaded before diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 389dc7a85..ec1fd5158 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -183,13 +183,14 @@ struct DLL_LINKAGE SPuzzleInfo class DLL_LINKAGE CFaction : public Faction { friend class CTownHandler; + friend class CBuilding; + friend class CTown; + std::string modScope; //town name, by default - from TownName.txt std::string identifier; TFaction index; - const std::string & getName() const override; - public: TerrainId nativeTerrain; EAlignment::EAlignment alignment; @@ -207,7 +208,7 @@ public: int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getJsonKey() const override; + std::string getJsonKey() const override; void registerIcons(const IconRegistar & cb) const override; FactionID getId() const override; diff --git a/lib/IHandlerBase.cpp b/lib/IHandlerBase.cpp index a91e5430f..93634e8f7 100644 --- a/lib/IHandlerBase.cpp +++ b/lib/IHandlerBase.cpp @@ -24,9 +24,4 @@ void IHandlerBase::registerObject(std::string scope, std::string type_name, std: return VLC->modh->identifiers.registerObject(scope, type_name, name, index); } -std::string IHandlerBase::normalizeIdentifier(const std::string& scope, const std::string& remoteScope, const std::string& identifier) const -{ - return VLC->modh->normalizeIdentifier(scope, remoteScope, identifier); -} - VCMI_LIB_NAMESPACE_END diff --git a/lib/IHandlerBase.h b/lib/IHandlerBase.h index d47bbd820..813e18ae0 100644 --- a/lib/IHandlerBase.h +++ b/lib/IHandlerBase.h @@ -25,8 +25,6 @@ protected: /// Calls modhandler. Mostly needed to avoid large number of includes in headers void registerObject(std::string scope, std::string type_name, std::string name, si32 index); - std::string normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier) const; - public: /// loads all original game data in vector of json nodes /// dataSize - is number of items that must be loaded (normally - constant from GameConstants) @@ -94,7 +92,7 @@ public: void loadObject(std::string scope, std::string name, const JsonNode & data) override { - auto object = loadFromJson(scope, data, normalizeIdentifier(scope, getScopeBuiltin(), name), objects.size()); + auto object = loadFromJson(scope, data, name, objects.size()); objects.push_back(object); @@ -104,7 +102,7 @@ public: void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override { - auto object = loadFromJson(scope, data, normalizeIdentifier(scope, getScopeBuiltin(), name), index); + auto object = loadFromJson(scope, data, name, index); assert(objects[index] == nullptr); // ensure that this id was not loaded before objects[index] = object; diff --git a/lib/ObstacleHandler.cpp b/lib/ObstacleHandler.cpp index c427d4640..4ae68caae 100644 --- a/lib/ObstacleHandler.cpp +++ b/lib/ObstacleHandler.cpp @@ -24,16 +24,21 @@ int32_t ObstacleInfo::getIconIndex() const return iconIndex; } -const std::string & ObstacleInfo::getName() const +std::string ObstacleInfo::getJsonKey() const { return identifier; } -const std::string & ObstacleInfo::getJsonKey() const +std::string ObstacleInfo::getNameTranslated() const { return identifier; } +std::string ObstacleInfo::getNameTextID() const +{ + return identifier; // TODO? +} + void ObstacleInfo::registerIcons(const IconRegistar & cb) const { } @@ -43,7 +48,6 @@ Obstacle ObstacleInfo::getId() const return obstacle; } - std::vector ObstacleInfo::getBlocked(BattleHex hex) const { std::vector ret; @@ -81,6 +85,8 @@ bool ObstacleInfo::isAppropriate(const TerrainId terrainType, const BattleField ObstacleInfo * ObstacleHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) { + assert(identifier.find(':') == std::string::npos); + auto * info = new ObstacleInfo(Obstacle(index), identifier); info->animation = json["animation"].String(); diff --git a/lib/ObstacleHandler.h b/lib/ObstacleHandler.h index 40fef6366..a7f38249f 100644 --- a/lib/ObstacleHandler.h +++ b/lib/ObstacleHandler.h @@ -44,8 +44,9 @@ public: int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getJsonKey() const override; - const std::string & getName() const override; + std::string getJsonKey() const override; + std::string getNameTranslated() const override; + std::string getNameTextID() const override; void registerIcons(const IconRegistar & cb) const override; Obstacle getId() const override; diff --git a/lib/RiverHandler.cpp b/lib/RiverHandler.cpp index 4236d6ce4..850f738fb 100644 --- a/lib/RiverHandler.cpp +++ b/lib/RiverHandler.cpp @@ -26,6 +26,8 @@ RiverType * RiverTypeHandler::loadFromJson( const std::string & identifier, size_t index) { + assert(identifier.find(':') == std::string::npos); + RiverType * info = new RiverType; info->id = RiverId(index); diff --git a/lib/RiverHandler.h b/lib/RiverHandler.h index 0dc57225a..627dde1de 100644 --- a/lib/RiverHandler.h +++ b/lib/RiverHandler.h @@ -23,17 +23,16 @@ class DLL_LINKAGE RiverType : public EntityT std::string identifier; RiverId id; - const std::string & getName() const override { return identifier;} public: int32_t getIndex() const override { return id.getNum(); } int32_t getIconIndex() const override { return 0; } - const std::string & getJsonKey() const override { return identifier;} + std::string getJsonKey() const override { return identifier;} void registerIcons(const IconRegistar & cb) const override {} RiverId getId() const override { return id;} void updateFrom(const JsonNode & data) {}; - std::string getNameTextID() const; - std::string getNameTranslated() const; + std::string getNameTextID() const override; + std::string getNameTranslated() const override; std::string tilesFilename; std::string shortIdentifier; diff --git a/lib/RoadHandler.cpp b/lib/RoadHandler.cpp index d7f94eecd..8c55c55e8 100644 --- a/lib/RoadHandler.cpp +++ b/lib/RoadHandler.cpp @@ -26,6 +26,8 @@ RoadType * RoadTypeHandler::loadFromJson( const std::string & identifier, size_t index) { + assert(identifier.find(':') == std::string::npos); + RoadType * info = new RoadType; info->id = RoadId(index); diff --git a/lib/RoadHandler.h b/lib/RoadHandler.h index 2b74cdec2..0b84535b6 100644 --- a/lib/RoadHandler.h +++ b/lib/RoadHandler.h @@ -23,17 +23,16 @@ class DLL_LINKAGE RoadType : public EntityT std::string identifier; RoadId id; - const std::string & getName() const override { return identifier;} public: int32_t getIndex() const override { return id.getNum(); } int32_t getIconIndex() const override { return 0; } - const std::string & getJsonKey() const override { return identifier;} + std::string getJsonKey() const override { return identifier;} void registerIcons(const IconRegistar & cb) const override {} RoadId getId() const override { return id;} void updateFrom(const JsonNode & data) {}; - std::string getNameTextID() const; - std::string getNameTranslated() const; + std::string getNameTextID() const override; + std::string getNameTranslated() const override; std::string tilesFilename; std::string shortIdentifier; diff --git a/lib/ScriptHandler.cpp b/lib/ScriptHandler.cpp index ed58f4b92..403fb34af 100644 --- a/lib/ScriptHandler.cpp +++ b/lib/ScriptHandler.cpp @@ -243,7 +243,7 @@ ScriptPtr ScriptHandler::loadFromJson(vstd::CLoggerBase * logger, const std::str void ScriptHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto object = loadFromJson(logMod, scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name)); + auto object = loadFromJson(logMod, scope, data, name); objects[object->identifier] = object; } diff --git a/lib/TerrainHandler.cpp b/lib/TerrainHandler.cpp index c9e3fac67..73bf2c271 100644 --- a/lib/TerrainHandler.cpp +++ b/lib/TerrainHandler.cpp @@ -17,6 +17,8 @@ VCMI_LIB_NAMESPACE_BEGIN TerrainType * TerrainTypeHandler::loadFromJson( const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) { + assert(identifier.find(':') == std::string::npos); + TerrainType * info = new TerrainType; info->id = TerrainId(index); diff --git a/lib/TerrainHandler.h b/lib/TerrainHandler.h index 983e06cc3..648775b42 100644 --- a/lib/TerrainHandler.h +++ b/lib/TerrainHandler.h @@ -24,17 +24,16 @@ class DLL_LINKAGE TerrainType : public EntityT TerrainId id; ui8 passabilityType; - const std::string & getName() const override { return identifier;} public: int32_t getIndex() const override { return id.getNum(); } int32_t getIconIndex() const override { return 0; } - const std::string & getJsonKey() const override { return identifier;} + std::string getJsonKey() const override { return identifier;} void registerIcons(const IconRegistar & cb) const override {} TerrainId getId() const override { return id;} void updateFrom(const JsonNode & data) {}; - std::string getNameTextID() const; - std::string getNameTranslated() const; + std::string getNameTextID() const override; + std::string getNameTranslated() const override; enum PassabilityType : ui8 { diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index e8580e6b9..10024f3d8 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -259,14 +259,14 @@ CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(co void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name)); + auto object = loadFromJson(scope, data, VLC->modh->normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name)); objects[object->id] = object; VLC->modh->identifiers.registerObject(scope, "object", name, object->id); } void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { - auto object = loadFromJson(scope, data, normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name)); + auto object = loadFromJson(scope, data, VLC->modh->normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name)); assert(objects[(si32)index] == nullptr); // ensure that this id was not loaded before objects[(si32)index] = object; VLC->modh->identifiers.registerObject(scope, "object", name, object->id); diff --git a/lib/rmg/CRmgTemplateStorage.cpp b/lib/rmg/CRmgTemplateStorage.cpp index 813fb785e..4904ece2d 100644 --- a/lib/rmg/CRmgTemplateStorage.cpp +++ b/lib/rmg/CRmgTemplateStorage.cpp @@ -31,7 +31,7 @@ void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const try { JsonDeserializer handler(nullptr, data); - auto fullKey = normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name); //actually it's not used + auto fullKey = scope + ":" + name; //actually it's not used templates[fullKey].setId(fullKey); templates[fullKey].serializeJson(handler); templates[fullKey].setName(name); diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index b1b1a9c0d..132d504f1 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -201,11 +201,6 @@ SpellID CSpell::getId() const return id; } -const std::string & CSpell::getName() const -{ - return identifier; -} - std::string CSpell::getNameTextID() const { TextIdentifier id("spell", modScope, identifier, "name"); @@ -228,9 +223,9 @@ std::string CSpell::getDescriptionTranslated(int32_t level) const return VLC->generaltexth->translate(getDescriptionTextID(level)); } -const std::string & CSpell::getJsonKey() const +std::string CSpell::getJsonKey() const { - return identifier; + return modScope + ':' + identifier;; } int32_t CSpell::getIndex() const @@ -711,6 +706,9 @@ const std::vector & CSpellHandler::getTypeNames() const CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) { + assert(identifier.find(':') == std::string::npos); + assert(!scope.empty()); + using namespace SpellConfig; SpellID id(static_cast(index)); @@ -947,7 +945,8 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & const si32 levelPower = levelObject.power = static_cast(levelNode["power"].Integer()); - VLC->generaltexth->registerString(spell->getDescriptionTranslated(levelIndex), levelNode["description"].String()); + if (!spell->isCreatureAbility()) + VLC->generaltexth->registerString(spell->getDescriptionTextID(levelIndex), levelNode["description"].String()); levelObject.cost = static_cast(levelNode["cost"].Integer()); levelObject.AIValue = static_cast(levelNode["aiValue"].Integer()); diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index 718779962..c7c59a819 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -170,7 +170,6 @@ public: SpellID id; std::string identifier; std::string modScope; - const std::string & getName() const override; public: enum ESpellPositiveness { @@ -236,7 +235,7 @@ public: int32_t getIndex() const override; int32_t getIconIndex() const override; - const std::string & getJsonKey() const override; + std::string getJsonKey() const override; SpellID getId() const override; std::string getNameTextID() const override; diff --git a/lib/spells/ISpellMechanics.cpp b/lib/spells/ISpellMechanics.cpp index 5e8f39287..59704af3f 100644 --- a/lib/spells/ISpellMechanics.cpp +++ b/lib/spells/ISpellMechanics.cpp @@ -571,7 +571,7 @@ SpellID BaseMechanics::getSpellId() const std::string BaseMechanics::getSpellName() const { - return owner->getName(); + return owner->getNameTranslated(); } int32_t BaseMechanics::getSpellLevel() const diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index 1210e7ce9..eab3481db 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -90,7 +90,7 @@ QList RewardsWidget::getListForType(RewardType typeId) for(int i = 0; i < map.allowedSpell.size(); ++i) { if(map.allowedSpell[i]) - result.append(QString::fromStdString(VLC->spellh->objects.at(i)->getName())); + result.append(QString::fromStdString(VLC->spellh->objects.at(i)->getNameTranslated())); } break; diff --git a/mapeditor/mapsettings.cpp b/mapeditor/mapsettings.cpp index 498f4c33a..c9b021e6d 100644 --- a/mapeditor/mapsettings.cpp +++ b/mapeditor/mapsettings.cpp @@ -42,7 +42,7 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) : } for(int i = 0; i < controller.map()->allowedSpell.size(); ++i) { - auto * item = new QListWidgetItem(QString::fromStdString(VLC->spellh->objects[i]->getName())); + auto * item = new QListWidgetItem(QString::fromStdString(VLC->spellh->objects[i]->getNameTranslated())); item->setData(Qt::UserRole, QVariant::fromValue(i)); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(controller.map()->allowedSpell[i] ? Qt::Checked : Qt::Unchecked); From 5a9ffb8e954d0c1401c6b8f3cc7bea63140e1964 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 18 Jan 2023 23:57:49 +0200 Subject: [PATCH 186/197] Serialization version bump --- lib/CCreatureHandler.cpp | 2 +- lib/serializer/CSerializer.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 590999476..f5e256301 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -35,7 +35,7 @@ int32_t CCreature::getIconIndex() const std::string CCreature::getJsonKey() const { - return modScope + ':' + identifier;; + return modScope + ':' + identifier; } void CCreature::registerIcons(const IconRegistar & cb) const diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index 6889ae373..b4972e977 100644 --- a/lib/serializer/CSerializer.h +++ b/lib/serializer/CSerializer.h @@ -14,8 +14,8 @@ VCMI_LIB_NAMESPACE_BEGIN -const ui32 SERIALIZATION_VERSION = 810; -const ui32 MINIMAL_SERIALIZATION_VERSION = 810; +const ui32 SERIALIZATION_VERSION = 812; +const ui32 MINIMAL_SERIALIZATION_VERSION = 812; const std::string SAVEGAME_MAGIC = "VCMISVG"; class CHero; From 27442e29504efb2b193214d432d9525b8816cc7e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 00:23:02 +0200 Subject: [PATCH 187/197] Remove unused code --- lib/CCreatureHandler.h | 4 ---- lib/CHeroHandler.h | 6 ------ lib/CTownHandler.h | 2 +- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 01b0d6693..0958c6eea 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -33,10 +33,6 @@ class DLL_LINKAGE CCreature : public Creature, public CBonusSystemNode std::string modScope; std::string identifier; -// std::string nameRef; // reference name, stringID -// std::string nameSing;// singular name, e.g. Centaur -// std::string namePl; // plural name, e.g. Centaurs - std::string getNameTranslated() const override; std::string getNameTextID() const override; diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 5e49545c5..7adc27c25 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -63,8 +63,6 @@ class DLL_LINKAGE CHero : public HeroType HeroTypeID ID; std::string identifier; std::string modScope; - std::string nameTextID; //name of hero - std::string biographyTextID; public: struct InitialArmyStack @@ -137,8 +135,6 @@ public: h & haveSpellBook; h & sex; h & special; - h & nameTextID; - h & biographyTextID; h & iconSpecSmall; h & iconSpecLarge; h & portraitSmall; @@ -159,7 +155,6 @@ class DLL_LINKAGE CHeroClass : public HeroClass HeroClassID id; // use getId instead std::string modScope; std::string identifier; // use getJsonKey instead - std::string nameTextID; public: enum EClassAffinity @@ -212,7 +207,6 @@ public: { h & modScope; h & identifier; - h & nameTextID; h & faction; h & id; h & defaultTavernChance; diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index ec1fd5158..aadb1f42a 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -186,7 +186,7 @@ class DLL_LINKAGE CFaction : public Faction friend class CBuilding; friend class CTown; - std::string modScope; //town name, by default - from TownName.txt + std::string modScope; std::string identifier; TFaction index; From e0842d416c64e0056120849ce7c30b0a0bb7e2f7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 01:45:01 +0200 Subject: [PATCH 188/197] Fix identifier format --- lib/CHeroHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 30007a2d1..a4ecb0f63 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -185,7 +185,7 @@ std::string CHeroClass::getNameTranslated() const std::string CHeroClass::getNameTextID() const { - return TextIdentifier("heroClass", modScope, identifier, "specialty", "name").get(); + return TextIdentifier("heroClass", modScope, identifier, "name").get(); } void CHeroClass::updateFrom(const JsonNode & data) From 9a304841834c0c990d6d8ecc538bc9843fc81eef Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 17 Jan 2023 21:43:44 +0200 Subject: [PATCH 189/197] Point and Rect now reside in library --- client/gui/Geometries.cpp | 32 ++-- client/gui/Geometries.h | 245 +++-------------------------- cmake_modules/VCMI_lib.cmake | 3 + lib/Point.h | 96 +++++++++++ lib/Rect.cpp | 133 ++++++++++++++++ lib/Rect.h | 155 ++++++++++++++++++ lib/battle/CBattleInfoCallback.cpp | 58 +------ 7 files changed, 427 insertions(+), 295 deletions(-) create mode 100644 lib/Point.h create mode 100644 lib/Rect.cpp create mode 100644 lib/Rect.h diff --git a/client/gui/Geometries.cpp b/client/gui/Geometries.cpp index 04a31e7c1..a6b9250b3 100644 --- a/client/gui/Geometries.cpp +++ b/client/gui/Geometries.cpp @@ -1,4 +1,5 @@ /* + * * Geometries.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder @@ -7,32 +8,29 @@ * Full text of license available in license.txt file, in main folder * */ + #include "StdInc.h" #include "Geometries.h" -#include "../CMT.h" + +#include #include -#include "../../lib/int3.h" -Point::Point(const int3 &a) - :x(a.x),y(a.y) -{} - -Point::Point(const SDL_MouseMotionEvent &a) - :x(a.x),y(a.y) -{} - -Rect Rect::createCentered( int w, int h ) +Rect Geometry::fromSDL(const SDL_Rect & rect) { - return Rect(screen->w/2 - w/2, screen->h/2 - h/2, w, h); + return Rect(Point(rect.x, rect.y), Point(rect.w, rect.h)); } -Rect Rect::around(const Rect &r, int width) +SDL_Rect Geometry::toSDL(const Rect & rect) { - return Rect(r.x - width, r.y - width, r.w + width * 2, r.h + width * 2); + SDL_Rect result; + result.x = rect.x; + result.y = rect.y; + result.w = rect.w; + result.h = rect.h; + return result; } -Rect Rect::centerIn(const Rect &r) +Point Geometry::fromSDL(const SDL_MouseMotionEvent & motion) { - return Rect(r.x + (r.w - w) / 2, r.y + (r.h - h) / 2, w, h); + return { motion.x, motion.y }; } - diff --git a/client/gui/Geometries.h b/client/gui/Geometries.h index b1e05d672..e2d60050a 100644 --- a/client/gui/Geometries.h +++ b/client/gui/Geometries.h @@ -9,241 +9,34 @@ */ #pragma once -#include - -enum class ETextAlignment {TOPLEFT, CENTER, BOTTOMRIGHT}; - struct SDL_MouseMotionEvent; +struct SDL_Rect; + +#include "../../lib/Point.h" +#include "../../lib/Rect.h" VCMI_LIB_NAMESPACE_BEGIN class int3; VCMI_LIB_NAMESPACE_END -// A point with x/y coordinate, used mostly for graphic rendering -struct Point +#ifdef VCMI_LIB_NAMESPACE +using VCMI_LIB_WRAP_NAMESPACE(Rect); +using VCMI_LIB_WRAP_NAMESPACE(Point); +#endif + +enum class ETextAlignment {TOPLEFT, CENTER, BOTTOMRIGHT}; + +namespace Geometry { - int x, y; - //constructors - Point() - { - x = y = 0; - }; +/// creates Point using provided event +Point fromSDL(const SDL_MouseMotionEvent & motion); - Point(int X, int Y) - :x(X),y(Y) - {}; +/// creates Rect using provided rect +Rect fromSDL(const SDL_Rect & rect); - Point(const int3 &a); +/// creates SDL_Rect using provided rect +SDL_Rect toSDL(const Rect & rect); - explicit Point(const SDL_MouseMotionEvent &a); +} - template - Point operator+(const T &b) const - { - return Point(x+b.x,y+b.y); - } - - template - Point operator/(const T &div) const - { - return Point(x/div, y/div); - } - - template - Point operator*(const T &mul) const - { - return Point(x*mul, y*mul); - } - - template - Point& operator+=(const T &b) - { - x += b.x; - y += b.y; - return *this; - } - - template - Point operator-(const T &b) const - { - return Point(x - b.x, y - b.y); - } - - template - Point& operator-=(const T &b) - { - x -= b.x; - y -= b.y; - return *this; - } - - template Point& operator=(const T &t) - { - x = t.x; - y = t.y; - return *this; - } - template bool operator==(const T &t) const - { - return x == t.x && y == t.y; - } - template bool operator!=(const T &t) const - { - return !(*this == t); - } -}; - -/// Rectangle class, which have a position and a size -struct Rect : public SDL_Rect -{ - Rect() - { - x = y = w = h = -1; - } - Rect(int X, int Y, int W, int H) - { - x = X; - y = Y; - w = W; - h = H; - } - Rect(const Point & position, const Point & size) - { - x = position.x; - y = position.y; - w = size.x; - h = size.y; - } - Rect(const SDL_Rect & r) - { - x = r.x; - y = r.y; - w = r.w; - h = r.h; - } - Rect(const Rect& r) = default; - - Rect centerIn(const Rect &r); - static Rect createCentered(int w, int h); - static Rect around(const Rect &r, int width = 1); - - bool isIn(int qx, int qy) const - { - if (qx > x && qxy && qy y+h && p.bottomLeft().y > y+h) //rect p is below *this - { - intersect = false; - } - else if(p.topLeft().x > x+w && p.topRight().x > x+w) //rect p is on the right hand side of this - { - intersect = false; - } - else if(p.topLeft().x < x && p.topRight().x < x) //rect p is on the left hand side of this - { - intersect = false; - } - - if(intersect) - { - Rect ret; - ret.x = std::max(this->x, p.x); - ret.y = std::max(this->y, p.y); - Point bR; //bottomRight point of returned rect - bR.x = std::min(this->w+this->x, p.w+p.x); - bR.y = std::min(this->h+this->y, p.h+p.y); - ret.w = bR.x - ret.x; - ret.h = bR.y - ret.y; - return ret; - } - else - { - return Rect(); - } - } - Rect operator|(const Rect &p) const //union of two rects - { - Rect ret; - ret.x = std::min(p.x, this->x); - ret.y = std::min(p.y, this->y); - int x2 = std::max(p.x+p.w, this->x+this->w); - int y2 = std::max(p.y+p.h, this->y+this->h); - ret.w = x2 -ret.x; - ret.h = y2 -ret.y; - return ret; - } -}; diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 869080564..0190221ac 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -442,6 +442,9 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/NetPacksLobby.h ${MAIN_LIB_DIR}/ObstacleHandler.h ${MAIN_LIB_DIR}/PathfinderUtil.h + ${MAIN_LIB_DIR}/Point.h + ${MAIN_LIB_DIR}/Rect.h + ${MAIN_LIB_DIR}/Rect.cpp ${MAIN_LIB_DIR}/ResourceSet.h ${MAIN_LIB_DIR}/RiverHandler.h ${MAIN_LIB_DIR}/RoadHandler.h diff --git a/lib/Point.h b/lib/Point.h new file mode 100644 index 000000000..a4794983c --- /dev/null +++ b/lib/Point.h @@ -0,0 +1,96 @@ +/* + * Geometries.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN +class int3; + +// A point with x/y coordinate, used mostly for graphic rendering +class Point +{ +public: + int x, y; + + //constructors + Point() + { + x = y = 0; + }; + + Point(int X, int Y) + :x(X),y(Y) + {}; + + Point(const int3 &a); + + template + Point operator+(const T &b) const + { + return Point(x+b.x,y+b.y); + } + + template + Point operator/(const T &div) const + { + return Point(x/div, y/div); + } + + template + Point operator*(const T &mul) const + { + return Point(x*mul, y*mul); + } + + template + Point& operator+=(const T &b) + { + x += b.x; + y += b.y; + return *this; + } + + template + Point operator-(const T &b) const + { + return Point(x - b.x, y - b.y); + } + + template + Point& operator-=(const T &b) + { + x -= b.x; + y -= b.y; + return *this; + } + + template Point& operator=(const T &t) + { + x = t.x; + y = t.y; + return *this; + } + template bool operator==(const T &t) const + { + return x == t.x && y == t.y; + } + template bool operator!=(const T &t) const + { + return !(*this == t); + } + + template + void serialize(Handler &h, const int version) + { + h & x; + h & y; + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/Rect.cpp b/lib/Rect.cpp new file mode 100644 index 000000000..a19a3a420 --- /dev/null +++ b/lib/Rect.cpp @@ -0,0 +1,133 @@ +/* + * ResourceSet.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "Rect.h" + +VCMI_LIB_NAMESPACE_BEGIN + +/// Returns rect union - rect that covers both this rect and provided rect +Rect Rect::include(const Rect & other) const +{ + Point topLeft{ + std::min(this->topLeft().x, other.topLeft().x), + std::min(this->topLeft().y, other.topLeft().y) + }; + + Point bottomRight{ + std::max(this->bottomRight().x, other.bottomRight().x), + std::max(this->bottomRight().y, other.bottomRight().y) + }; + + return Rect(topLeft, bottomRight - topLeft); + +} + +Rect Rect::createCentered( const Point & around, const Point & dimensions ) +{ + return Rect(around - dimensions/2, dimensions); +} + +Rect Rect::createAround(const Rect &r, int width) +{ + return Rect(r.x - width, r.y - width, r.w + width * 2, r.h + width * 2); +} + +Rect Rect::createCentered( const Rect & rect, const Point & dimensions) +{ + return createCentered(rect.center(), dimensions); +} + +bool Rect::intersectionTest(const Rect & other) const +{ + // this rect is above other rect + if(this->bottomLeft().y < other.topLeft().y) + return false; + + // this rect is below other rect + if(this->topLeft().y > other.bottomLeft().y ) + return false; + + // this rect is to the left of other rect + if(this->topRight().x < other.topLeft().x) + return false; + + // this rect is to the right of other rect + if(this->topLeft().x > other.topRight().x) + return false; + + return true; +} + +/// Algorithm to test whether line segment between points line1-line2 will intersect with +/// rectangle specified by top-left and bottom-right points +/// Note that in order to avoid floating point rounding errors algorithm uses integers with no divisions +bool Rect::intersectionTest(const Point & line1, const Point & line2) const +{ + // check whether segment is located to the left of our rect + if (line1.x < topLeft().x && line2.x < topLeft().x) + return false; + + // check whether segment is located to the right of our rect + if (line1.x > bottomRight().x && line2.x > bottomRight().x) + return false; + + // check whether segment is located on top of our rect + if (line1.y < topLeft().y && line2.y < topLeft().y) + return false; + + // check whether segment is located below of our rect + if (line1.y > bottomRight().y && line2.y > bottomRight().y) + return false; + + Point vector { line2.x - line1.x, line2.y - line1.y}; + + // compute position of corners relative to our line + int tlTest = vector.y*topLeft().x - vector.x*topLeft().y + (line2.x*line1.y-line1.x*line2.y); + int trTest = vector.y*bottomRight().x - vector.x*topLeft().y + (line2.x*line1.y-line1.x*line2.y); + int blTest = vector.y*topLeft().x - vector.x*bottomRight().y + (line2.x*line1.y-line1.x*line2.y); + int brTest = vector.y*bottomRight().x - vector.x*bottomRight().y + (line2.x*line1.y-line1.x*line2.y); + + // if all points are on the left of our line then there is no intersection + if ( tlTest > 0 && trTest > 0 && blTest > 0 && brTest > 0 ) + return false; + + // if all points are on the right of our line then there is no intersection + if ( tlTest < 0 && trTest < 0 && blTest < 0 && brTest < 0 ) + return false; + + // if all previous checks failed, this means that there is an intersection between line and AABB + return true; +} + + +Rect Rect::intersect(const Rect & other) const +{ + if(intersectionTest(other)) + { + Point topLeft{ + std::max(this->topLeft().x, other.topLeft().x), + std::max(this->topLeft().y, other.topLeft().y) + }; + + Point bottomRight{ + std::min(this->bottomRight().x, other.bottomRight().x), + std::min(this->bottomRight().y, other.bottomRight().y) + }; + + return Rect(topLeft, bottomRight - topLeft); + } + else + { + return Rect(); + } +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/Rect.h b/lib/Rect.h new file mode 100644 index 000000000..566d1dde6 --- /dev/null +++ b/lib/Rect.h @@ -0,0 +1,155 @@ +/* + * Geometries.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "Point.h" + +VCMI_LIB_NAMESPACE_BEGIN + +/// Rectangle class, which have a position and a size +class Rect +{ +public: + int x; + int y; + int w; + int h; + + Rect() + { + x = y = w = h = -1; + } + Rect(int X, int Y, int W, int H) + { + x = X; + y = Y; + w = W; + h = H; + } + Rect(const Point & position, const Point & size) + { + x = position.x; + y = position.y; + w = size.x; + h = size.y; + } + Rect(const Rect& r) = default; + + DLL_LINKAGE static Rect createCentered( const Point & around, const Point & size ); + DLL_LINKAGE static Rect createCentered( const Rect & target, const Point & size ); + DLL_LINKAGE static Rect createAround(const Rect &r, int borderWidth); + + bool isIn(int qx, int qy) const + { + if (qx > x && qxy && qy + void serialize(Handler &h, const int version) + { + h & x; + h & y; + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 285d73bf5..3b171cb55 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -20,6 +20,7 @@ #include "../mapObjects/CGTownInstance.h" #include "../BattleFieldHandler.h" #include "../CModHandler.h" +#include "../Rect.h" VCMI_LIB_NAMESPACE_BEGIN @@ -131,55 +132,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(con return ESpellCastProblem::OK; } -struct Point -{ - int x,y; -}; - -/// Algorithm to test whether line segment between points line1-line2 will intersect with -/// rectangle specified by top-left and bottom-right points -/// Note that in order to avoid floating point rounding errors algorithm uses integers with no divisions -static bool intersectionSegmentRect(Point line1, Point line2, Point rectTL, Point rectBR) -{ - assert(rectTL.x < rectBR.x); - assert(rectTL.y < rectBR.y); - - // check whether segment is located to the left of our AABB - if (line1.x < rectTL.x && line2.x < rectTL.x) - return false; - - // check whether segment is located to the right of our AABB - if (line1.x > rectBR.x && line2.x > rectBR.x) - return false; - - // check whether segment is located on top of our AABB - if (line1.y < rectTL.y && line2.y < rectTL.y) - return false; - - // check whether segment is located below of our AABB - if (line1.y > rectBR.y && line2.y > rectBR.y) - return false; - - Point vector { line2.x - line1.x, line2.y - line1.y}; - - // compute position of AABB corners relative to our line - int tlTest = vector.y*rectTL.x - vector.x*rectTL.y + (line2.x*line1.y-line1.x*line2.y); - int trTest = vector.y*rectBR.x - vector.x*rectTL.y + (line2.x*line1.y-line1.x*line2.y); - int blTest = vector.y*rectTL.x - vector.x*rectBR.y + (line2.x*line1.y-line1.x*line2.y); - int brTest = vector.y*rectBR.x - vector.x*rectBR.y + (line2.x*line1.y-line1.x*line2.y); - - // if all points are on the left of our line then there is no intersection - if ( tlTest > 0 && trTest > 0 && blTest > 0 && brTest > 0 ) - return false; - - // if all points are on the right of our line then there is no intersection - if ( tlTest < 0 && trTest < 0 && blTest < 0 && brTest < 0 ) - return false; - - // if all previous checks failed, this means that there is an intersection between line and AABB - return true; -} - bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const { auto isTileBlocked = [&](BattleHex tile) @@ -214,10 +166,12 @@ bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, Bat continue; // create rect around cell with an obstacle - Point rectTL{ obstacle.getX()*cellSize, obstacle.getY()*cellSize }; - Point recrBR{ obstacle.getX()*(cellSize+1), obstacle.getY()*(cellSize+1) }; + Rect rect { + Point(obstacle.getX(), obstacle.getY()) * cellSize, + Point( cellSize, cellSize) + }; - if ( intersectionSegmentRect(line1, line2, rectTL, recrBR)) + if ( rect.intersectionTest(line1, line2)) return true; } return false; From ee7a573cb80a86d432e093b8591421003fed101b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 17 Jan 2023 22:01:35 +0200 Subject: [PATCH 190/197] Refactoring of SDL API usage: - Rect no longer inherits from SDL_Rect - renamed vcmi functions that used SDL naming format - moved all functions in SDL_Extensions file into CSDL_Ext namespace - SDL_Rect is now used only by SDL_Extensions, all other code uses Rect --- client/CMT.h | 6 +- client/CMessage.cpp | 4 +- client/CVideoHandler.cpp | 12 +- client/CVideoHandler.h | 11 +- client/Graphics.cpp | 2 +- client/battle/BattleAnimationClasses.h | 2 +- client/battle/BattleFieldController.h | 4 +- client/battle/BattleInterfaceClasses.cpp | 6 +- client/battle/BattleObstacleController.h | 2 +- client/battle/BattleProjectileController.h | 2 +- client/battle/BattleSiegeController.h | 2 +- client/battle/BattleStacksController.cpp | 3 +- client/battle/CreatureAnimation.cpp | 1 + client/gui/CAnimation.cpp | 25 +- client/gui/CAnimation.h | 5 +- client/gui/CCursorHandler.cpp | 316 +++++++++++++++++++++ client/gui/CGuiHandler.cpp | 15 +- client/gui/CGuiHandler.h | 6 +- client/gui/CIntObject.cpp | 14 +- client/gui/CIntObject.h | 5 +- client/gui/Canvas.cpp | 10 +- client/gui/ColorFilter.cpp | 2 +- client/gui/CursorHandler.cpp | 8 +- client/gui/Fonts.cpp | 13 +- client/gui/Fonts.h | 2 +- client/gui/NotificationHandler.h | 5 +- client/gui/SDL_Extensions.cpp | 229 +++++++++------ client/gui/SDL_Extensions.h | 176 +++++------- client/gui/SDL_Pixels.h | 14 +- client/mainmenu/CMainMenu.cpp | 5 +- client/mainmenu/CPrologEpilogVideo.cpp | 2 +- client/mapHandler.cpp | 84 +++--- client/mapHandler.h | 29 +- client/widgets/AdventureMapClasses.cpp | 18 +- client/widgets/Buttons.cpp | 15 +- client/widgets/Buttons.h | 3 +- client/widgets/CArtifactHolder.cpp | 8 +- client/widgets/CComponent.cpp | 2 +- client/widgets/Images.cpp | 11 +- client/widgets/Images.h | 5 +- client/widgets/ObjectLists.h | 2 +- client/widgets/TextControls.cpp | 6 +- client/windows/CAdvmapInterface.cpp | 22 +- client/windows/CAdvmapInterface.h | 2 +- client/windows/CCastleInterface.cpp | 6 +- client/windows/CKingdomInterface.cpp | 4 +- client/windows/CQuestLog.cpp | 4 +- client/windows/CSpellWindow.cpp | 28 +- client/windows/CSpellWindow.h | 5 +- client/windows/CTradeWindow.cpp | 10 +- client/windows/CWindowObject.cpp | 8 +- client/windows/GUIClasses.cpp | 22 +- client/windows/GUIClasses.h | 1 + client/windows/InfoWindows.cpp | 6 +- client/windows/InfoWindows.h | 2 +- lib/Rect.h | 6 +- 56 files changed, 770 insertions(+), 448 deletions(-) create mode 100644 client/gui/CCursorHandler.cpp diff --git a/client/CMT.h b/client/CMT.h index e3378edfe..4ccf8f2b1 100644 --- a/client/CMT.h +++ b/client/CMT.h @@ -8,7 +8,11 @@ * */ #pragma once -#include + +struct SDL_Texture; +struct SDL_Window; +struct SDL_Renderer; +struct SDL_Surface; extern SDL_Texture * screenTexture; diff --git a/client/CMessage.cpp b/client/CMessage.cpp index 0ce4affb0..fd39f9a66 100644 --- a/client/CMessage.cpp +++ b/client/CMessage.cpp @@ -102,9 +102,7 @@ SDL_Surface * CMessage::drawDialogBox(int w, int h, PlayerColor playerColor) { for (int j=0; jh) { - Rect srcR(0,0,background->w, background->h); - Rect dstR(i,j,w,h); - CSDL_Ext::blitSurface(background, &srcR, ret, &dstR); + CSDL_Ext::blitSurface(background, ret, Point(i,j)); } } drawBorder(playerColor, ret, w, h); diff --git a/client/CVideoHandler.cpp b/client/CVideoHandler.cpp index 31c2cebf6..c9464d1dc 100644 --- a/client/CVideoHandler.cpp +++ b/client/CVideoHandler.cpp @@ -67,8 +67,8 @@ CVideoPlayer::CVideoPlayer() context = nullptr; texture = nullptr; dest = nullptr; - destRect = genRect(0,0,0,0); - pos = genRect(0,0,0,0); + destRect = CSDL_Ext::genRect(0,0,0,0); + pos = CSDL_Ext::genRect(0,0,0,0); refreshWait = 0; refreshCount = 0; doLoop = false; @@ -339,10 +339,10 @@ void CVideoPlayer::show( int x, int y, SDL_Surface *dst, bool update ) pos.x = x; pos.y = y; - CSDL_Ext::blitSurface(dest, &destRect, dst, &pos); + CSDL_Ext::blitSurface(dest, destRect, dst, pos.topLeft()); if (update) - SDL_UpdateRect(dst, pos.x, pos.y, pos.w, pos.h); + CSDL_Ext::updateRect(dst, pos); } void CVideoPlayer::redraw( int x, int y, SDL_Surface *dst, bool update ) @@ -442,7 +442,9 @@ bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey) if(stopOnKey && keyDown()) return false; - SDL_RenderCopy(mainRenderer, texture, nullptr, &pos); + SDL_Rect rect = Geometry::toSDL(pos); + + SDL_RenderCopy(mainRenderer, texture, nullptr, &rect); SDL_RenderPresent(mainRenderer); // Wait 3 frames diff --git a/client/CVideoHandler.h b/client/CVideoHandler.h index 1e729a8a8..863961013 100644 --- a/client/CVideoHandler.h +++ b/client/CVideoHandler.h @@ -9,8 +9,10 @@ */ #pragma once -struct SDL_Surface; +#include "../lib/Rect.h" +struct SDL_Surface; +struct SDL_Texture; class IVideoPlayer { @@ -54,9 +56,6 @@ public: #include "../lib/filesystem/CInputStream.h" -#include -#include - extern "C" { #include #include @@ -106,8 +105,8 @@ class CVideoPlayer : public IMainVideoPlayer SDL_Texture *texture; SDL_Surface *dest; - SDL_Rect destRect; // valid when dest is used - SDL_Rect pos; // destination on screen + Rect destRect; // valid when dest is used + Rect pos; // destination on screen int refreshWait; // Wait several refresh before updating the image int refreshCount; diff --git a/client/Graphics.cpp b/client/Graphics.cpp index 9bf835757..de9ac1916 100644 --- a/client/Graphics.cpp +++ b/client/Graphics.cpp @@ -286,7 +286,7 @@ void Graphics::blueToPlayersAdv(SDL_Surface * sur, PlayerColor player) //FIXME: not all player colored images have player palette at last 32 indexes //NOTE: following code is much more correct but still not perfect (bugged with status bar) - SDL_SetColors(sur, palette, 224, 32); + CSDL_Ext::setColors(sur, palette, 224, 32); #if 0 diff --git a/client/battle/BattleAnimationClasses.h b/client/battle/BattleAnimationClasses.h index a42503259..867234977 100644 --- a/client/battle/BattleAnimationClasses.h +++ b/client/battle/BattleAnimationClasses.h @@ -28,7 +28,7 @@ class CAnimation; class BattleInterface; class CreatureAnimation; struct StackAttackedInfo; -struct Point; +class Point; /// Base class of battle animations class BattleAnimation diff --git a/client/battle/BattleFieldController.h b/client/battle/BattleFieldController.h index c15fc60cb..1105bb202 100644 --- a/client/battle/BattleFieldController.h +++ b/client/battle/BattleFieldController.h @@ -18,8 +18,8 @@ class CStack; VCMI_LIB_NAMESPACE_END -struct Rect; -struct Point; +class Rect; +class Point; class ClickableHex; class BattleHero; diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 4c4ce524d..bfcd29095 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -160,7 +160,7 @@ void BattleConsole::setEnteringMode(bool on) if (on) { assert(enteringText == false); - CSDL_Ext::startTextInput(&pos); + CSDL_Ext::startTextInput(pos); } else { @@ -494,7 +494,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); - pos = genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19); + pos = CSDL_Ext::genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19); background = std::make_shared("CPRESULT"); background->colorize(owner.playerID); @@ -850,7 +850,7 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn) if (unit->unitType()->idNumber == CreatureID::ARROW_TOWERS) icon->setFrame(owner->getSiegeShooterIconID(), 1); - amount->setText(makeNumberShort(unit->getCount())); + amount->setText(CSDL_Ext::makeNumberShort(unit->getCount())); if(stateIcon) { diff --git a/client/battle/BattleObstacleController.h b/client/battle/BattleObstacleController.h index 688beea57..1b3743637 100644 --- a/client/battle/BattleObstacleController.h +++ b/client/battle/BattleObstacleController.h @@ -21,7 +21,7 @@ class Canvas; class CAnimation; class BattleInterface; class BattleRenderer; -struct Point; +class Point; /// Controls all currently active projectiles on the battlefield /// (with exception of moat, which is apparently handled by siege controller) diff --git a/client/battle/BattleProjectileController.h b/client/battle/BattleProjectileController.h index 8b6be2937..10b201b2b 100644 --- a/client/battle/BattleProjectileController.h +++ b/client/battle/BattleProjectileController.h @@ -19,7 +19,7 @@ class CSpell; VCMI_LIB_NAMESPACE_END -struct Point; +class Point; class CAnimation; class Canvas; class BattleInterface; diff --git a/client/battle/BattleSiegeController.h b/client/battle/BattleSiegeController.h index dd4795afc..91859db3d 100644 --- a/client/battle/BattleSiegeController.h +++ b/client/battle/BattleSiegeController.h @@ -21,7 +21,7 @@ class CGTownInstance; VCMI_LIB_NAMESPACE_END -struct Point; +class Point; class Canvas; class BattleInterface; class BattleRenderer; diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index afd0a69a5..8ef6b8107 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -28,6 +28,7 @@ #include "../gui/CAnimation.h" #include "../gui/CGuiHandler.h" #include "../gui/Canvas.h" +#include "../gui/SDL_Extensions.h" #include "../../lib/spells/ISpellMechanics.h" #include "../../CCallback.h" @@ -317,7 +318,7 @@ void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack * //blitting amount Point textPos = stackAnimation[stack->ID]->pos.topLeft() + amountBG->dimensions()/2 + Point(xAdd, yAdd); - canvas.drawText(textPos, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, makeNumberShort(stack->getCount())); + canvas.drawText(textPos, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, CSDL_Ext::makeNumberShort(stack->getCount())); } void BattleStacksController::showStack(Canvas & canvas, const CStack * stack) diff --git a/client/battle/CreatureAnimation.cpp b/client/battle/CreatureAnimation.cpp index b83a4f202..0dda6089a 100644 --- a/client/battle/CreatureAnimation.cpp +++ b/client/battle/CreatureAnimation.cpp @@ -15,6 +15,7 @@ #include "../gui/Canvas.h" #include "../gui/ColorFilter.h" +#include "../gui/SDL_Extensions.h" static const SDL_Color creatureBlueBorder = { 0, 255, 255, 255 }; static const SDL_Color creatureGoldBorder = { 255, 255, 0, 255 }; diff --git a/client/gui/CAnimation.cpp b/client/gui/CAnimation.cpp index 5a76e2bdc..50052fe48 100644 --- a/client/gui/CAnimation.cpp +++ b/client/gui/CAnimation.cpp @@ -95,7 +95,7 @@ public: void savePalette(); void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr) const override; - void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src) const override; + void draw(SDL_Surface * where, const Rect * dest, const Rect * src) const override; std::shared_ptr scaleFast(float scale) const override; void exportBitmap(const boost::filesystem::path & path) const override; void playerColored(PlayerColor player) override; @@ -651,7 +651,7 @@ void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src) con draw(where, &destRect, src); } -void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* src) const +void SDLImage::draw(SDL_Surface* where, const Rect * dest, const Rect* src) const { if (!surf) return; @@ -668,28 +668,23 @@ void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* sr if(src->y < margins.y) destShift.y += margins.y - src->y; - sourceRect = Rect(*src) & Rect(margins.x, margins.y, surf->w, surf->h); + sourceRect = Rect(*src).intersect(Rect(margins.x, margins.y, surf->w, surf->h)); sourceRect -= margins; } else destShift = margins; - Rect destRect(destShift.x, destShift.y, surf->w, surf->h); - if(dest) - { - destRect.x += dest->x; - destRect.y += dest->y; - } + destShift += dest->topLeft(); if(surf->format->BitsPerPixel == 8) { - CSDL_Ext::blit8bppAlphaTo24bpp(surf, &sourceRect, where, &destRect); + CSDL_Ext::blit8bppAlphaTo24bpp(surf, sourceRect, where, destShift); } else { - SDL_UpperBlit(surf, &sourceRect, where, &destRect); + CSDL_Ext::blitSurface(surf, sourceRect, where, destShift); } } @@ -787,7 +782,7 @@ void SDLImage::shiftPalette(int from, int howMany) { palette[(i+1)%howMany] = surf->format->palette->colors[from + i]; } - SDL_SetColors(surf, palette, from, howMany); + CSDL_Ext::setColors(surf, palette, from, howMany); } } @@ -827,7 +822,7 @@ void SDLImage::setSpecialPallete(const IImage::SpecialPalette & SpecialPalette) { if(surf->format->palette) { - SDL_SetColors(surf, const_cast(SpecialPalette.data()), 1, 7); + CSDL_Ext::setColors(surf, const_cast(SpecialPalette.data()), 1, 7); } } @@ -1273,7 +1268,7 @@ void CFadeAnimation::init(EMode mode, SDL_Surface * sourceSurface, bool freeSurf shouldFreeSurface = freeSurfaceAtEnd; } -void CFadeAnimation::draw(SDL_Surface * targetSurface, const SDL_Rect * sourceRect, SDL_Rect * destRect) +void CFadeAnimation::draw(SDL_Surface * targetSurface, const Point &targetPoint) { if (!fading || !fadingSurface || fadingMode == EMode::NONE) { @@ -1282,6 +1277,6 @@ void CFadeAnimation::draw(SDL_Surface * targetSurface, const SDL_Rect * sourceRe } CSDL_Ext::setAlpha(fadingSurface, (int)(fadingCounter * 255)); - SDL_BlitSurface(fadingSurface, const_cast(sourceRect), targetSurface, destRect); //FIXME + CSDL_Ext::blitSurface(fadingSurface, targetSurface, targetPoint); //FIXME CSDL_Ext::setAlpha(fadingSurface, 255); } diff --git a/client/gui/CAnimation.h b/client/gui/CAnimation.h index dcfb1cb64..add6a3f72 100644 --- a/client/gui/CAnimation.h +++ b/client/gui/CAnimation.h @@ -28,6 +28,7 @@ class JsonNode; VCMI_LIB_NAMESPACE_END struct SDL_Surface; +struct SDL_Color; class CDefFile; class ColorFilter; @@ -41,7 +42,7 @@ public: //draws image on surface "where" at position virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr) const = 0; - virtual void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src) const = 0; + virtual void draw(SDL_Surface * where, const Rect * dest, const Rect * src) const = 0; virtual std::shared_ptr scaleFast(float scale) const = 0; @@ -177,6 +178,6 @@ public: ~CFadeAnimation(); void init(EMode mode, SDL_Surface * sourceSurface, bool freeSurfaceAtEnd = false, float animDelta = DEFAULT_DELTA); void update(); - void draw(SDL_Surface * targetSurface, const SDL_Rect * sourceRect, SDL_Rect * destRect); + void draw(SDL_Surface * targetSurface, const Point & targetPoint); bool isFading() const { return fading; } }; diff --git a/client/gui/CCursorHandler.cpp b/client/gui/CCursorHandler.cpp new file mode 100644 index 000000000..6d475d735 --- /dev/null +++ b/client/gui/CCursorHandler.cpp @@ -0,0 +1,316 @@ +/* + * CCursorHandler.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "CCursorHandler.h" + +#include + +#include "SDL_Extensions.h" +#include "CGuiHandler.h" +#include "../widgets/Images.h" + +#include "../CMT.h" + +void CCursorHandler::clearBuffer() +{ + CSDL_Ext::fillSurface(buffer, Colors::TRANSPARENT ); +} + +void CCursorHandler::updateBuffer(CIntObject * payload) +{ + payload->moveTo(Point(0,0)); + payload->showAll(buffer); + + needUpdate = true; +} + +void CCursorHandler::replaceBuffer(CIntObject * payload) +{ + clearBuffer(); + updateBuffer(payload); +} + +CCursorHandler::CCursorHandler() + : needUpdate(true) + , buffer(nullptr) + , cursorLayer(nullptr) + , frameTime(0.f) + , showing(false) +{ + cursorLayer = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 40, 40); + SDL_SetTextureBlendMode(cursorLayer, SDL_BLENDMODE_BLEND); + + xpos = ypos = 0; + type = Cursor::Type::DEFAULT; + dndObject = nullptr; + + cursors = + { + std::make_unique("CRADVNTR", 0), + std::make_unique("CRCOMBAT", 0), + std::make_unique("CRDEFLT", 0), + std::make_unique("CRSPELL", 0) + }; + + currentCursor = cursors.at(static_cast(Cursor::Type::DEFAULT)).get(); + + buffer = CSDL_Ext::newSurface(40,40); + + SDL_SetSurfaceBlendMode(buffer, SDL_BLENDMODE_NONE); + SDL_ShowCursor(SDL_DISABLE); + + set(Cursor::Map::POINTER); +} + +Point CCursorHandler::position() const +{ + return Point(xpos, ypos); +} + +void CCursorHandler::changeGraphic(Cursor::Type type, size_t index) +{ + assert(dndObject == nullptr); + + if(type != this->type) + { + this->type = type; + this->frame = index; + currentCursor = cursors.at(static_cast(type)).get(); + currentCursor->setFrame(index); + } + else if(index != this->frame) + { + this->frame = index; + currentCursor->setFrame(index); + } + + replaceBuffer(currentCursor); +} + +void CCursorHandler::set(Cursor::Default index) +{ + changeGraphic(Cursor::Type::DEFAULT, static_cast(index)); +} + +void CCursorHandler::set(Cursor::Map index) +{ + changeGraphic(Cursor::Type::ADVENTURE, static_cast(index)); +} + +void CCursorHandler::set(Cursor::Combat index) +{ + changeGraphic(Cursor::Type::COMBAT, static_cast(index)); +} + +void CCursorHandler::set(Cursor::Spellcast index) +{ + //Note: this is animated cursor, ignore specified frame and only change type + changeGraphic(Cursor::Type::SPELLBOOK, frame); +} + +void CCursorHandler::dragAndDropCursor(std::unique_ptr object) +{ + dndObject = std::move(object); + if(dndObject) + replaceBuffer(dndObject.get()); + else + replaceBuffer(currentCursor); +} + +void CCursorHandler::cursorMove(const int & x, const int & y) +{ + xpos = x; + ypos = y; +} + +void CCursorHandler::shiftPos( int &x, int &y ) +{ + if(( type == Cursor::Type::COMBAT && frame != static_cast(Cursor::Combat::POINTER)) || type == Cursor::Type::SPELLBOOK) + { + x-=16; + y-=16; + + // Properly align the melee attack cursors. + if (type == Cursor::Type::COMBAT) + { + switch (static_cast(frame)) + { + case Cursor::Combat::HIT_NORTHEAST: + x -= 6; + y += 16; + break; + case Cursor::Combat::HIT_EAST: + x -= 16; + y += 10; + break; + case Cursor::Combat::HIT_SOUTHEAST: + x -= 6; + y -= 6; + break; + case Cursor::Combat::HIT_SOUTHWEST: + x += 16; + y -= 6; + break; + case Cursor::Combat::HIT_WEST: + x += 16; + y += 11; + break; + case Cursor::Combat::HIT_NORTHWEST: + x += 16; + y += 16; + break; + case Cursor::Combat::HIT_NORTH: + x += 9; + y += 16; + break; + case Cursor::Combat::HIT_SOUTH: + x += 9; + y -= 15; + break; + } + } + } + else if(type == Cursor::Type::ADVENTURE) + { + if (frame == 0) + { + //no-op + } + else if(frame == 2) + { + x -= 12; + y -= 10; + } + else if(frame == 3) + { + x -= 12; + y -= 12; + } + else if(frame < 27) + { + int hlpNum = (frame - 4)%6; + if(hlpNum == 0) + { + x -= 15; + y -= 13; + } + else if(hlpNum == 1) + { + x -= 13; + y -= 13; + } + else if(hlpNum == 2) + { + x -= 20; + y -= 20; + } + else if(hlpNum == 3) + { + x -= 13; + y -= 16; + } + else if(hlpNum == 4) + { + x -= 8; + y -= 9; + } + else if(hlpNum == 5) + { + x -= 14; + y -= 16; + } + } + else if(frame == 41) + { + x -= 14; + y -= 16; + } + else if(frame < 31 || frame == 42) + { + x -= 20; + y -= 20; + } + } +} + +void CCursorHandler::centerCursor() +{ + this->xpos = static_cast((screen->w / 2.) - (currentCursor->pos.w / 2.)); + this->ypos = static_cast((screen->h / 2.) - (currentCursor->pos.h / 2.)); + SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); + CSDL_Ext::warpMouse(this->xpos, this->ypos); + SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE); +} + +void CCursorHandler::render() +{ + if(!showing) + return; + + if (type == Cursor::Type::SPELLBOOK) + { + static const float frameDisplayDuration = 0.1f; + + frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f; + size_t newFrame = frame; + + while (frameTime > frameDisplayDuration) + { + frameTime -= frameDisplayDuration; + newFrame++; + } + + auto & animation = cursors.at(static_cast(type)); + + while (newFrame > animation->size()) + newFrame -= animation->size(); + + changeGraphic(Cursor::Type::SPELLBOOK, newFrame); + } + + //the must update texture in the main (renderer) thread, but changes to cursor type may come from other threads + updateTexture(); + + int x = xpos; + int y = ypos; + shiftPos(x, y); + + if(dndObject) + { + x -= dndObject->pos.w/2; + y -= dndObject->pos.h/2; + } + + SDL_Rect destRect; + destRect.x = x; + destRect.y = y; + destRect.w = 40; + destRect.h = 40; + + SDL_RenderCopy(mainRenderer, cursorLayer, nullptr, &destRect); +} + +void CCursorHandler::updateTexture() +{ + if(needUpdate) + { + SDL_UpdateTexture(cursorLayer, nullptr, buffer->pixels, buffer->pitch); + needUpdate = false; + } +} + +CCursorHandler::~CCursorHandler() +{ + if(buffer) + SDL_FreeSurface(buffer); + + if(cursorLayer) + SDL_DestroyTexture(cursorLayer); +} diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index f1f3ee424..90fa550aa 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -15,6 +15,7 @@ #include "CIntObject.h" #include "CursorHandler.h" +#include "SDL_Extensions.h" #include "../CGameInfo.h" #include "../../lib/CThreadHelper.h" @@ -167,7 +168,7 @@ void CGuiHandler::totalRedraw() #endif for(auto & elem : objsToBlit) elem->showAll(screen2); - blitAt(screen2,0,0,screen); + CSDL_Ext::blitAt(screen2,0,0,screen); } void CGuiHandler::updateTime() @@ -289,7 +290,7 @@ void CGuiHandler::handleCurrentEvent() for(auto i = hlp.begin(); i != hlp.end() && continueEventHandling; i++) { if(!vstd::contains(doubleClickInterested, *i)) continue; - if(isItIn(&(*i)->pos, current->motion.x, current->motion.y)) + if((*i)->pos.isInside(current->motion.x, current->motion.y)) { (*i)->onDoubleClick(); } @@ -321,7 +322,7 @@ void CGuiHandler::handleCurrentEvent() // SDL doesn't have the proper values for mouse positions on SDL_MOUSEWHEEL, refetch them int x = 0, y = 0; SDL_GetMouseState(&x, &y); - (*i)->wheelScrolled(current->wheel.y < 0, isItIn(&(*i)->pos, x, y)); + (*i)->wheelScrolled(current->wheel.y < 0, (*i)->pos.isInside(x, y)); } } else if(current->type == SDL_TEXTINPUT) @@ -367,7 +368,7 @@ void CGuiHandler::handleMouseButtonClick(CIntObjectList & interestedObjs, EIntOb auto prev = (*i)->mouseState(btn); if(!isPressed) (*i)->updateMouseState(btn, isPressed); - if(isItIn(&(*i)->pos, current->motion.x, current->motion.y)) + if((*i)->pos.isInside(current->motion.x, current->motion.y)) { if(isPressed) (*i)->updateMouseState(btn, isPressed); @@ -384,7 +385,7 @@ void CGuiHandler::handleMouseMotion() std::vector hlp; for(auto & elem : hoverable) { - if(isItIn(&(elem)->pos, current->motion.x, current->motion.y)) + if(elem->pos.isInside(current->motion.x, current->motion.y)) { if (!(elem)->hovered) hlp.push_back((elem)); @@ -408,7 +409,7 @@ void CGuiHandler::simpleRedraw() { //update only top interface and draw background if(objsToBlit.size() > 1) - blitAt(screen2,0,0,screen); //blit background + CSDL_Ext::blitAt(screen2,0,0,screen); //blit background if(!objsToBlit.empty()) objsToBlit.back()->show(screen); //blit active interface/window } @@ -419,7 +420,7 @@ void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion) std::list miCopy = motioninterested; for(auto & elem : miCopy) { - if(elem->strongInterest || isItInOrLowerBounds(&elem->pos, motion.x, motion.y)) //checking lower bounds fixes bug #2476 + if(elem->strongInterest || Rect::createAround(elem->pos, 1).isInside( motion.x, motion.y)) //checking bounds including border fixes bug #2476 { (elem)->mouseMoved(motion); } diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h index 0c4df679d..e2c0385fb 100644 --- a/client/gui/CGuiHandler.h +++ b/client/gui/CGuiHandler.h @@ -9,9 +9,9 @@ */ #pragma once -//#include "../../lib/CStopWatch.h" #include "Geometries.h" -#include "SDL_Extensions.h" + +#include VCMI_LIB_NAMESPACE_BEGIN @@ -19,6 +19,8 @@ template struct CondSh; VCMI_LIB_NAMESPACE_END +union SDL_Event; + class CFramerateManager; class IStatusBar; class CIntObject; diff --git a/client/gui/CIntObject.cpp b/client/gui/CIntObject.cpp index 033dc46b5..07b97286e 100644 --- a/client/gui/CIntObject.cpp +++ b/client/gui/CIntObject.cpp @@ -14,6 +14,8 @@ #include "SDL_Extensions.h" #include "../CMessage.h" +#include + IShowActivatable::IShowActivatable() { type = 0; @@ -176,7 +178,7 @@ void CIntObject::printAtMiddleLoc(const std::string & text, const Point &p, EFon void CIntObject::blitAtLoc( SDL_Surface * src, int x, int y, SDL_Surface * dst ) { - blitAt(src, pos.x + x, pos.y + y, dst); + CSDL_Ext::blitAt(src, pos.x + x, pos.y + y, dst); } void CIntObject::blitAtLoc(SDL_Surface * src, const Point &p, SDL_Surface * dst) @@ -219,16 +221,6 @@ void CIntObject::enable() recActions = 255; } -bool CIntObject::isItInLoc( const SDL_Rect &rect, int x, int y ) -{ - return isItIn(&rect, x - pos.x, y - pos.y); -} - -bool CIntObject::isItInLoc( const SDL_Rect &rect, const Point &p ) -{ - return isItIn(&rect, p.x - pos.x, p.y - pos.y); -} - void CIntObject::fitToScreen(int borderWidth, bool propagate) { Point newPos = pos.topLeft(); diff --git a/client/gui/CIntObject.h b/client/gui/CIntObject.h index 46dbea9a9..8ec51eb5f 100644 --- a/client/gui/CIntObject.h +++ b/client/gui/CIntObject.h @@ -9,7 +9,6 @@ */ #pragma once -#include #include "Geometries.h" #include "../Graphics.h" @@ -18,6 +17,8 @@ class CGuiHandler; class CPicture; struct SDL_KeyboardEvent; +struct SDL_TextInputEvent; +struct SDL_TextEditingEvent; using boost::logic::tribool; @@ -165,8 +166,6 @@ public: //request complete redraw of this object void redraw() override; - bool isItInLoc(const SDL_Rect &rect, int x, int y); - bool isItInLoc(const SDL_Rect &rect, const Point &p); const Rect & center(const Rect &r, bool propagate = true); //sets pos so that r will be in the center of screen, assigns sizes of r to pos, returns new position const Rect & center(const Point &p, bool propagate = true); //moves object so that point p will be in its center const Rect & center(bool propagate = true); //centers when pos.w and pos.h are set, returns new position diff --git a/client/gui/Canvas.cpp b/client/gui/Canvas.cpp index 0d7fc4fcf..c35a9de48 100644 --- a/client/gui/Canvas.cpp +++ b/client/gui/Canvas.cpp @@ -16,6 +16,8 @@ #include "../Graphics.h" +#include + Canvas::Canvas(SDL_Surface * surface): surface(surface), renderOffset(0,0) @@ -34,10 +36,10 @@ Canvas::Canvas(Canvas & other, const Rect & newClipRect): Canvas(other) { clipRect.emplace(); - SDL_GetClipRect(surface, clipRect.get_ptr()); + CSDL_Ext::getClipRect(surface, clipRect.get()); Rect currClipRect = newClipRect + renderOffset; - SDL_SetClipRect(surface, &currClipRect); + CSDL_Ext::setClipRect(surface, currClipRect); renderOffset += newClipRect.topLeft(); } @@ -51,7 +53,7 @@ Canvas::Canvas(const Point & size): Canvas::~Canvas() { if (clipRect) - SDL_SetClipRect(surface, clipRect.get_ptr()); + CSDL_Ext::setClipRect(surface, clipRect.get()); SDL_FreeSurface(surface); } @@ -72,7 +74,7 @@ void Canvas::draw(std::shared_ptr image, const Point & pos, const Rect & void Canvas::draw(Canvas & image, const Point & pos) { - blitAt(image.surface, renderOffset.x + pos.x, renderOffset.y + pos.y, surface); + CSDL_Ext::blitAt(image.surface, renderOffset.x + pos.x, renderOffset.y + pos.y, surface); } void Canvas::drawLine(const Point & from, const Point & dest, const SDL_Color & colorFrom, const SDL_Color & colorDest) diff --git a/client/gui/ColorFilter.cpp b/client/gui/ColorFilter.cpp index 52713d8af..68a2989d5 100644 --- a/client/gui/ColorFilter.cpp +++ b/client/gui/ColorFilter.cpp @@ -10,7 +10,7 @@ #include "StdInc.h" #include "ColorFilter.h" -#include +#include #include "../../lib/JsonNode.h" diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index 7d4c5c665..74cb474b3 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -250,7 +250,7 @@ void CursorHandler::centerCursor() pos = screenSize / 2 - getPivotOffset(); SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); - SDL_WarpMouse(pos.x, pos.y); + CSDL_Ext::warpMouse(pos.x, pos.y); SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE); cursor->setCursorPosition(pos); @@ -345,8 +345,7 @@ void CursorSoftware::updateTexture() if (!cursorSurface || Point(cursorSurface->w, cursorSurface->h) != cursorImage->dimensions()) createTexture(cursorImage->dimensions()); - Uint32 fillColor = SDL_MapRGBA(cursorSurface->format, 0, 0, 0, 0); - CSDL_Ext::fillRect(cursorSurface, nullptr, fillColor); + CSDL_Ext::fillSurface(cursorSurface, Colors::TRANSPARENT); cursorImage->draw(cursorSurface); SDL_UpdateTexture(cursorTexture, NULL, cursorSurface->pixels, cursorSurface->pitch); @@ -414,8 +413,7 @@ void CursorHardware::setImage(std::shared_ptr image, const Point & pivot { auto cursorSurface = CSDL_Ext::newSurface(image->dimensions().x, image->dimensions().y); - Uint32 fillColor = SDL_MapRGBA(cursorSurface->format, 0, 0, 0, 0); - CSDL_Ext::fillRect(cursorSurface, nullptr, fillColor); + CSDL_Ext::fillSurface(cursorSurface, Colors::TRANSPARENT); image->draw(cursorSurface); diff --git a/client/gui/Fonts.cpp b/client/gui/Fonts.cpp index b3804237d..277a5bd51 100644 --- a/client/gui/Fonts.cpp +++ b/client/gui/Fonts.cpp @@ -130,11 +130,11 @@ size_t CBitmapFont::getGlyphWidth(const char * data) const void CBitmapFont::renderCharacter(SDL_Surface * surface, const BitmapChar & character, const SDL_Color & color, int &posX, int &posY) const { Rect clipRect; - SDL_GetClipRect(surface, &clipRect); + CSDL_Ext::getClipRect(surface, clipRect); posX += character.leftOffset; - TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0); + CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0); Uint8 bpp = surface->format->BytesPerPixel; @@ -270,7 +270,7 @@ void CTrueTypeFont::renderText(SDL_Surface * surface, const std::string & data, if (color.r != 0 && color.g != 0 && color.b != 0) // not black - add shadow { SDL_Color black = { 0, 0, 0, SDL_ALPHA_OPAQUE}; - renderText(surface, data, black, Point(pos.x + 1, pos.y + 1)); + renderText(surface, data, black, pos + Point(1,1)); } if (!data.empty()) @@ -283,8 +283,7 @@ void CTrueTypeFont::renderText(SDL_Surface * surface, const std::string & data, assert(rendered); - Rect rect(pos.x, pos.y, rendered->w, rendered->h); - SDL_BlitSurface(rendered, nullptr, surface, &rect); + CSDL_Ext::blitSurface(rendered, surface, pos); SDL_FreeSurface(rendered); } } @@ -308,9 +307,9 @@ void CBitmapHanFont::renderCharacter(SDL_Surface * surface, int characterIndex, { //TODO: somewhat duplicated with CBitmapFont::renderCharacter(); Rect clipRect; - SDL_GetClipRect(surface, &clipRect); + CSDL_Ext::getClipRect(surface, clipRect); - TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0); + CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0); Uint8 bpp = surface->format->BytesPerPixel; // start of line, may differ from 0 due to end of surface or clipped surface diff --git a/client/gui/Fonts.h b/client/gui/Fonts.h index a5aad2c46..ee14e24cb 100644 --- a/client/gui/Fonts.h +++ b/client/gui/Fonts.h @@ -15,7 +15,7 @@ class JsonNode; VCMI_LIB_NAMESPACE_END -struct Point; +class Point; struct SDL_Surface; struct SDL_Color; diff --git a/client/gui/NotificationHandler.h b/client/gui/NotificationHandler.h index 536e0978d..dab3a6b7a 100644 --- a/client/gui/NotificationHandler.h +++ b/client/gui/NotificationHandler.h @@ -10,7 +10,8 @@ #pragma once -#include +struct SDL_Window; +union SDL_Event; class NotificationHandler { @@ -19,4 +20,4 @@ public: static void init(SDL_Window * window); static bool handleSdlEvent(const SDL_Event & ev); static void destroy(); -}; \ No newline at end of file +}; diff --git a/client/gui/SDL_Extensions.cpp b/client/gui/SDL_Extensions.cpp index 8629181f0..7fe62b4e3 100644 --- a/client/gui/SDL_Extensions.cpp +++ b/client/gui/SDL_Extensions.cpp @@ -16,6 +16,11 @@ #include "../Graphics.h" #include "../CMT.h" +#include +#include +#include +#include + #ifdef VCMI_APPLE #include #endif @@ -24,18 +29,52 @@ #include "ios/utils.h" #endif -const SDL_Color Colors::YELLOW = { 229, 215, 123, 0 }; -const SDL_Color Colors::WHITE = { 255, 243, 222, 0 }; -const SDL_Color Colors::METALLIC_GOLD = { 173, 142, 66, 0 }; -const SDL_Color Colors::GREEN = { 0, 255, 0, 0 }; -const SDL_Color Colors::ORANGE = { 232, 184, 32, 0 }; -const SDL_Color Colors::BRIGHT_YELLOW = { 242, 226, 110, 0 }; -const SDL_Color Colors::DEFAULT_KEY_COLOR = {0, 255, 255, 0}; +const SDL_Color Colors::YELLOW = { 229, 215, 123, SDL_ALPHA_OPAQUE }; +const SDL_Color Colors::WHITE = { 255, 243, 222, SDL_ALPHA_OPAQUE }; +const SDL_Color Colors::METALLIC_GOLD = { 173, 142, 66, SDL_ALPHA_OPAQUE }; +const SDL_Color Colors::GREEN = { 0, 255, 0, SDL_ALPHA_OPAQUE }; +const SDL_Color Colors::ORANGE = { 232, 184, 32, SDL_ALPHA_OPAQUE }; +const SDL_Color Colors::BRIGHT_YELLOW = { 242, 226, 110, SDL_ALPHA_OPAQUE }; +const SDL_Color Colors::DEFAULT_KEY_COLOR = {0, 255, 255, SDL_ALPHA_OPAQUE}; +const SDL_Color Colors::RED = {255, 0, 0, SDL_ALPHA_OPAQUE}; +const SDL_Color Colors::PURPLE = {255, 75, 125, SDL_ALPHA_OPAQUE}; +const SDL_Color Colors::BLACK = {0, 0, 0, SDL_ALPHA_OPAQUE}; +const SDL_Color Colors::TRANSPARENT = {0, 0, 0, SDL_ALPHA_TRANSPARENT}; -void SDL_UpdateRect(SDL_Surface *surface, int x, int y, int w, int h) +void CSDL_Ext::setColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors) { - Rect rect(x,y,w,h); - if(0 !=SDL_UpdateTexture(screenTexture, &rect, surface->pixels, surface->pitch)) + SDL_SetPaletteColors(surface->format->palette,colors,firstcolor,ncolors); +} + +void CSDL_Ext::warpMouse(int x, int y) +{ + SDL_WarpMouseInWindow(mainWindow,x,y); +} + +bool CSDL_Ext::isCtrlKeyDown() +{ + return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL]; +} + +bool CSDL_Ext::isAltKeyDown() +{ + return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LALT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RALT]; +} + +bool CSDL_Ext::isShiftKeyDown() +{ + return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LSHIFT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RSHIFT]; +} + +void CSDL_Ext::setAlpha(SDL_Surface * bg, int value) +{ + SDL_SetSurfaceAlphaMod(bg, value); +} + +void CSDL_Ext::updateRect(SDL_Surface *surface, const Rect & rect ) +{ + SDL_Rect rectSDL = Geometry::toSDL(rect); + if(0 !=SDL_UpdateTexture(screenTexture, &rectSDL, surface->pixels, surface->pitch)) logGlobal->error("%sSDL_UpdateTexture %s", __FUNCTION__, SDL_GetError()); SDL_RenderClear(mainRenderer); @@ -76,24 +115,14 @@ SDL_Surface * CSDL_Ext::createSurfaceWithBpp(int width, int height) return SDL_CreateRGBSurface(0, width, height, bpp * 8, rMask, gMask, bMask, aMask); } -bool isItIn(const SDL_Rect * rect, int x, int y) +void CSDL_Ext::blitAt(SDL_Surface * src, int x, int y, SDL_Surface * dst) { - return (x>rect->x && xx+rect->w) && (y>rect->y && yy+rect->h); + if(!dst) + dst = screen; + CSDL_Ext::blitSurface(src, dst, Point(x, y)); } -bool isItInOrLowerBounds(const SDL_Rect * rect, int x, int y) -{ - return (x >= rect->x && x < rect->x + rect->w) && (y >= rect->y && y < rect->y + rect->h); -} - -void blitAt(SDL_Surface * src, int x, int y, SDL_Surface * dst) -{ - if(!dst) dst = screen; - SDL_Rect pom = genRect(src->h,src->w,x,y); - CSDL_Ext::blitSurface(src,nullptr,dst,&pom); -} - -void blitAt(SDL_Surface * src, const SDL_Rect & pos, SDL_Surface * dst) +void CSDL_Ext::blitAt(SDL_Surface * src, const Rect & pos, SDL_Surface * dst) { if (src) blitAt(src,pos.x,pos.y,dst); @@ -160,7 +189,7 @@ SDL_Surface * CSDL_Ext::horizontalFlip(SDL_Surface * toRot) return ret; } -Uint32 CSDL_Ext::SDL_GetPixel(SDL_Surface *surface, const int & x, const int & y, bool colorByte) +Uint32 CSDL_Ext::getPixel(SDL_Surface *surface, const int & x, const int & y, bool colorByte) { int bpp = surface->format->BytesPerPixel; /* Here p is the address to the pixel we want to retrieve */ @@ -189,8 +218,14 @@ Uint32 CSDL_Ext::SDL_GetPixel(SDL_Surface *surface, const int & x, const int & y } template -int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const SDL_Rect * srcRect, SDL_Surface * dst, SDL_Rect * dstRect) +int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPointInput) { + SDL_Rect srcRectInstance = Geometry::toSDL(srcRectInput); + SDL_Rect dstRectInstance = Geometry::toSDL(Rect(dstPointInput, srcRectInput.dimensions())); + + SDL_Rect * srcRect =&srcRectInstance; + SDL_Rect * dstRect =&dstRectInstance; + /* Make sure the surfaces aren't locked */ if ( ! src || ! dst ) { @@ -310,13 +345,13 @@ int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const SDL_Rect * sr return 0; } -int CSDL_Ext::blit8bppAlphaTo24bpp(const SDL_Surface * src, const SDL_Rect * srcRect, SDL_Surface * dst, SDL_Rect * dstRect) +int CSDL_Ext::blit8bppAlphaTo24bpp(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint) { switch(dst->format->BytesPerPixel) { - case 2: return blit8bppAlphaTo24bppT<2>(src, srcRect, dst, dstRect); - case 3: return blit8bppAlphaTo24bppT<3>(src, srcRect, dst, dstRect); - case 4: return blit8bppAlphaTo24bppT<4>(src, srcRect, dst, dstRect); + case 2: return blit8bppAlphaTo24bppT<2>(src, srcRect, dst, dstPoint); + case 3: return blit8bppAlphaTo24bppT<3>(src, srcRect, dst, dstPoint); + case 4: return blit8bppAlphaTo24bppT<4>(src, srcRect, dst, dstPoint); default: logGlobal->error("%d bpp is not supported!", (int)dst->format->BitsPerPixel); return -1; @@ -406,26 +441,26 @@ void CSDL_Ext::drawLine(SDL_Surface * sur, int x1, int y1, int x2, int y2, const } } -void CSDL_Ext::drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const int3 &color) +void CSDL_Ext::drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const SDL_Color &color) { for(int i = 0; i < w; i++) { - SDL_PutPixelWithoutRefreshIfInSurf(sur,x+i,y,color.x,color.y,color.z); - SDL_PutPixelWithoutRefreshIfInSurf(sur,x+i,y+h-1,color.x,color.y,color.z); + CSDL_Ext::putPixelWithoutRefreshIfInSurf(sur,x+i,y,color.r,color.g,color.b); + CSDL_Ext::putPixelWithoutRefreshIfInSurf(sur,x+i,y+h-1,color.r,color.g,color.b); } for(int i = 0; i < h; i++) { - SDL_PutPixelWithoutRefreshIfInSurf(sur,x,y+i,color.x,color.y,color.z); - SDL_PutPixelWithoutRefreshIfInSurf(sur,x+w-1,y+i,color.x,color.y,color.z); + CSDL_Ext::putPixelWithoutRefreshIfInSurf(sur,x,y+i,color.r,color.g,color.b); + CSDL_Ext::putPixelWithoutRefreshIfInSurf(sur,x+w-1,y+i,color.r,color.g,color.b); } } -void CSDL_Ext::drawBorder( SDL_Surface * sur, const SDL_Rect &r, const int3 &color ) +void CSDL_Ext::drawBorder( SDL_Surface * sur, const Rect &r, const SDL_Color &color ) { drawBorder(sur, r.x, r.y, r.w, r.h, color); } -void CSDL_Ext::drawDashedBorder(SDL_Surface * sur, const Rect &r, const int3 &color) +void CSDL_Ext::drawDashedBorder(SDL_Surface * sur, const Rect &r, const SDL_Color &color) { const int y1 = r.y, y2 = r.y + r.h-1; for (int i=0; ineutralColor : &graphics->playerColors[player.getNum()]); - SDL_SetColors(sur, color, 5, 1); + CSDL_Ext::setColors(sur, color, 5, 1); } else logGlobal->warn("Warning, setPlayerColor called on not 8bpp surface!"); } -TColorPutter CSDL_Ext::getPutterFor(SDL_Surface * const &dest, int incrementing) +CSDL_Ext::TColorPutter CSDL_Ext::getPutterFor(SDL_Surface * const &dest, int incrementing) { #define CASE_BPP(BytesPerPixel) \ case BytesPerPixel: \ @@ -489,7 +524,7 @@ case BytesPerPixel: \ } -TColorPutterAlpha CSDL_Ext::getPutterAlphaFor(SDL_Surface * const &dest, int incrementing) +CSDL_Ext::TColorPutterAlpha CSDL_Ext::getPutterAlphaFor(SDL_Surface * const &dest, int incrementing) { switch(dest->format->BytesPerPixel) { @@ -524,7 +559,7 @@ bool CSDL_Ext::isTransparent( SDL_Surface * srf, int x, int y ) SDL_Color color; - SDL_GetRGBA(SDL_GetPixel(srf, x, y), srf->format, &color.r, &color.g, &color.b, &color.a); + SDL_GetRGBA(CSDL_Ext::getPixel(srf, x, y), srf->format, &color.r, &color.g, &color.b, &color.a); bool pixelTransparent = color.a < 128; bool pixelCyan = (color.r == 0 && color.g == 255 && color.b == 255); @@ -548,7 +583,7 @@ void CSDL_Ext::VflipSurf(SDL_Surface * surf) } } -void CSDL_Ext::SDL_PutPixelWithoutRefresh(SDL_Surface *ekran, const int & x, const int & y, const Uint8 & R, const Uint8 & G, const Uint8 & B, Uint8 A) +void CSDL_Ext::putPixelWithoutRefresh(SDL_Surface *ekran, const int & x, const int & y, const Uint8 & R, const Uint8 & G, const Uint8 & B, Uint8 A) { Uint8 *p = getPxPtr(ekran, x, y); getPutterFor(ekran, false)(p, R, G, B); @@ -561,17 +596,17 @@ void CSDL_Ext::SDL_PutPixelWithoutRefresh(SDL_Surface *ekran, const int & x, con } } -void CSDL_Ext::SDL_PutPixelWithoutRefreshIfInSurf(SDL_Surface *ekran, const int & x, const int & y, const Uint8 & R, const Uint8 & G, const Uint8 & B, Uint8 A) +void CSDL_Ext::putPixelWithoutRefreshIfInSurf(SDL_Surface *ekran, const int & x, const int & y, const Uint8 & R, const Uint8 & G, const Uint8 & B, Uint8 A) { const SDL_Rect & rect = ekran->clip_rect; if(x >= rect.x && x < rect.w + rect.x && y >= rect.y && y < rect.h + rect.y) - SDL_PutPixelWithoutRefresh(ekran, x, y, R, G, B, A); + CSDL_Ext::putPixelWithoutRefresh(ekran, x, y, R, G, B, A); } template -void CSDL_Ext::applyEffectBpp( SDL_Surface * surf, const SDL_Rect * rect, int mode ) +void CSDL_Ext::applyEffectBpp(SDL_Surface * surf, const Rect & rect, int mode ) { switch(mode) { @@ -580,9 +615,9 @@ void CSDL_Ext::applyEffectBpp( SDL_Surface * surf, const SDL_Rect * rect, int mo const int sepiaDepth = 20; const int sepiaIntensity = 30; - for(int xp = rect->x; xp < rect->x + rect->w; ++xp) + for(int xp = rect.x; xp < rect.x + rect.w; ++xp) { - for(int yp = rect->y; yp < rect->y + rect->h; ++yp) + for(int yp = rect.y; yp < rect.y + rect.h; ++yp) { Uint8 * pixel = (ui8*)surf->pixels + yp * surf->pitch + xp * surf->format->BytesPerPixel; int r = Channels::px::r.get(pixel); @@ -613,9 +648,9 @@ void CSDL_Ext::applyEffectBpp( SDL_Surface * surf, const SDL_Rect * rect, int mo break; case 1: //grayscale { - for(int xp = rect->x; xp < rect->x + rect->w; ++xp) + for(int xp = rect.x; xp < rect.x + rect.w; ++xp) { - for(int yp = rect->y; yp < rect->y + rect->h; ++yp) + for(int yp = rect.y; yp < rect.y + rect.h; ++yp) { Uint8 * pixel = (ui8*)surf->pixels + yp * surf->pitch + xp * surf->format->BytesPerPixel; @@ -638,7 +673,7 @@ void CSDL_Ext::applyEffectBpp( SDL_Surface * surf, const SDL_Rect * rect, int mo } } -void CSDL_Ext::applyEffect( SDL_Surface * surf, const SDL_Rect * rect, int mode ) +void CSDL_Ext::applyEffect( SDL_Surface * surf, const Rect & rect, int mode ) { switch(surf->format->BytesPerPixel) { @@ -766,46 +801,34 @@ SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface *surf, int width, int height) return ret; } -void CSDL_Ext::blitSurface( SDL_Surface * src, const SDL_Rect * srcRect, SDL_Surface * dst, SDL_Rect * dstRect ) +void CSDL_Ext::blitSurface(SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPoint) { - if (dst != screen) - { - SDL_UpperBlit(src, srcRect, dst, dstRect); - } - else - { - SDL_Rect betterDst; - if (dstRect) - { - betterDst = *dstRect; - } - else - { - betterDst = Rect(0, 0, dst->w, dst->h); - } + SDL_Rect srcRect = Geometry::toSDL(srcRectInput); + SDL_Rect dstRect = Geometry::toSDL(Rect(dstPoint, srcRectInput.dimensions())); - SDL_UpperBlit(src, srcRect, dst, &betterDst); - } + SDL_UpperBlit(src, &srcRect, dst, &dstRect); } -void CSDL_Ext::fillRect( SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color ) +void CSDL_Ext::blitSurface(SDL_Surface * src, SDL_Surface * dst, const Point & dest) { - SDL_Rect newRect; - if (dstrect) - { - newRect = *dstrect; - } - else - { - newRect = Rect(0, 0, dst->w, dst->h); - } - SDL_FillRect(dst, &newRect, color); + Rect allSurface( Point(0,0), Point(src->w, src->h)); + + blitSurface(src, allSurface, dst, dest); } -void CSDL_Ext::fillRectBlack( SDL_Surface *dst, SDL_Rect *dstrect) +void CSDL_Ext::fillSurface( SDL_Surface *dst, const SDL_Color & color ) { - const Uint32 black = SDL_MapRGB(dst->format,0,0,0); - fillRect(dst,dstrect,black); + Rect allSurface( Point(0,0), Point(dst->w, dst->h)); + + fillRect(dst, allSurface, color); +} + +void CSDL_Ext::fillRect( SDL_Surface *dst, const Rect & dstrect, const SDL_Color & color ) +{ + SDL_Rect newRect = Geometry::toSDL(dstrect); + + uint32_t sdlColor = SDL_MapRGBA(dst->format, color.r, color.g, color.b, color.a); + SDL_FillRect(dst, &newRect, sdlColor); } void CSDL_Ext::fillTexture(SDL_Surface *dst, SDL_Surface * src) @@ -822,7 +845,7 @@ void CSDL_Ext::fillTexture(SDL_Surface *dst, SDL_Surface * src) { int xLeft = std::min(srcRect.w, dstRect.x + dstRect.w - x); int yLeft = std::min(srcRect.h, dstRect.y + dstRect.h - y); - Rect currentDest(x, y, xLeft, yLeft); + SDL_Rect currentDest{x, y, xLeft, yLeft}; SDL_BlitSurface(src, &srcRect, dst, ¤tDest); } } @@ -834,15 +857,17 @@ SDL_Color CSDL_Ext::makeColor(ui8 r, ui8 g, ui8 b, ui8 a) return ret; } -void CSDL_Ext::startTextInput(SDL_Rect * where) +void CSDL_Ext::startTextInput(const Rect & whereInput) { - auto impl = [](SDL_Rect * where) + SDL_Rect where = Geometry::toSDL(whereInput); + + auto impl = [](SDL_Rect & where) { if (SDL_IsTextInputActive() == SDL_FALSE) { SDL_StartTextInput(); } - SDL_SetTextInputRect(where); + SDL_SetTextInputRect(&where); }; #ifdef VCMI_APPLE @@ -858,12 +883,12 @@ void CSDL_Ext::startTextInput(SDL_Rect * where) SDL_RenderGetViewport(renderer, &viewport); const auto nativeScale = iOS_utils::screenScale(); - auto rectInScreenCoordinates = *where; + auto rectInScreenCoordinates = where; rectInScreenCoordinates.x = (viewport.x + rectInScreenCoordinates.x) * scaleX / nativeScale; rectInScreenCoordinates.y = (viewport.y + rectInScreenCoordinates.y) * scaleY / nativeScale; rectInScreenCoordinates.w = rectInScreenCoordinates.w * scaleX / nativeScale; rectInScreenCoordinates.h = rectInScreenCoordinates.h * scaleY / nativeScale; - impl(&rectInScreenCoordinates); + impl(rectInScreenCoordinates); #else impl(where); #endif @@ -918,7 +943,25 @@ void CSDL_Ext::setDefaultColorKeyPresize(SDL_Surface * surface) } } +void CSDL_Ext::setClipRect(SDL_Surface * src, const Rect & other) +{ + SDL_Rect rect = Geometry::toSDL(other); + + SDL_SetClipRect(src, &rect); +} + +void CSDL_Ext::getClipRect(SDL_Surface * src, Rect & other) +{ + SDL_Rect rect; + + SDL_GetClipRect(src, &rect); + + other = Geometry::fromSDL(rect); +} + + template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<2>(int, int); template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<3>(int, int); template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<4>(int, int); + diff --git a/client/gui/SDL_Extensions.h b/client/gui/SDL_Extensions.h index be7926ca6..75a4339cc 100644 --- a/client/gui/SDL_Extensions.h +++ b/client/gui/SDL_Extensions.h @@ -7,69 +7,25 @@ * Full text of license available in license.txt file, in main folder * */ + #pragma once -#include #include -#include #include -#include "../../lib/int3.h" #include "Geometries.h" #include "../../lib/GameConstants.h" +struct SDL_Window; +struct SDL_Renderer; +struct SDL_Texture; +struct SDL_Surface; +struct SDL_Color; extern SDL_Window * mainWindow; extern SDL_Renderer * mainRenderer; extern SDL_Texture * screenTexture; - -inline void SDL_SetColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors) -{ - SDL_SetPaletteColors(surface->format->palette,colors,firstcolor,ncolors); -} - -inline void SDL_WarpMouse(int x, int y) -{ - SDL_WarpMouseInWindow(mainWindow,x,y); -} - -void SDL_UpdateRect(SDL_Surface *surface, int x, int y, int w, int h); - -inline bool isCtrlKeyDown() -{ - return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL]; -} - -inline bool isAltKeyDown() -{ - return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LALT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RALT]; -} - -inline bool isShiftKeyDown() -{ - return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LSHIFT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RSHIFT]; -} -namespace CSDL_Ext -{ - //todo: should this better be assignment operator? - STRONG_INLINE void colorAssign(SDL_Color & dest, const SDL_Color & source) - { - dest.r = source.r; - dest.g = source.g; - dest.b = source.b; - dest.a = source.a; - } - - inline void setAlpha(SDL_Surface * bg, int value) - { - SDL_SetSurfaceAlphaMod(bg, value); - } -} -struct Rect; - extern SDL_Surface * screen, *screen2, *screenBuf; -void blitAt(SDL_Surface * src, int x, int y, SDL_Surface * dst=screen); -void blitAt(SDL_Surface * src, const SDL_Rect & pos, SDL_Surface * dst=screen); -bool isItIn(const SDL_Rect * rect, int x, int y); -bool isItInOrLowerBounds(const SDL_Rect * rect, int x, int y); + +class Rect; /** * The colors class defines color constants of type SDL_Color. @@ -97,14 +53,27 @@ public: /** default key color for all 8 & 24 bit graphics */ static const SDL_Color DEFAULT_KEY_COLOR; + + /// Selected creature card + static const SDL_Color RED; + + /// Minimap border + static const SDL_Color PURPLE; + + static const SDL_Color BLACK; + + static const SDL_Color TRANSPARENT; }; -//MSVC gives an error when calling abs with ui64 -> we add template that will match calls with unsigned arg and return it -template -typename boost::enable_if_c::type, T>::type abs(T arg) +namespace CSDL_Ext { - return arg; -} + +void setColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors); +void warpMouse(int x, int y); +bool isCtrlKeyDown(); +bool isAltKeyDown(); +bool isShiftKeyDown(); +void setAlpha(SDL_Surface * bg, int value); template std::string makeNumberShort(IntType number, IntType maxLength = 3) //the output is a string containing at most 5 characters [4 if positive] (eg. intead 10000 it gives 10k) @@ -126,12 +95,9 @@ std::string makeNumberShort(IntType number, IntType maxLength = 3) //the output return boost::lexical_cast(number) + *iter; } -typedef void (*TColorPutter)(Uint8 *&ptr, const Uint8 & R, const Uint8 & G, const Uint8 & B); -typedef void (*TColorPutterAlpha)(Uint8 *&ptr, const Uint8 & R, const Uint8 & G, const Uint8 & B, const Uint8 & A); - -inline SDL_Rect genRect(const int & hh, const int & ww, const int & xx, const int & yy) +inline Rect genRect(const int & hh, const int & ww, const int & xx, const int & yy) { - SDL_Rect ret; + Rect ret; ret.h=hh; ret.w=ww; ret.x=xx; @@ -139,52 +105,31 @@ inline SDL_Rect genRect(const int & hh, const int & ww, const int & xx, const in return ret; } -template -struct ColorPutter -{ - static STRONG_INLINE void PutColor(Uint8 *&ptr, const Uint8 & R, const Uint8 & G, const Uint8 & B); - static STRONG_INLINE void PutColor(Uint8 *&ptr, const Uint8 & R, const Uint8 & G, const Uint8 & B, const Uint8 & A); - static STRONG_INLINE void PutColorAlphaSwitch(Uint8 *&ptr, const Uint8 & R, const Uint8 & G, const Uint8 & B, const Uint8 & A); - static STRONG_INLINE void PutColor(Uint8 *&ptr, const SDL_Color & Color); - static STRONG_INLINE void PutColorAlpha(Uint8 *&ptr, const SDL_Color & Color); - static STRONG_INLINE void PutColorRow(Uint8 *&ptr, const SDL_Color & Color, size_t count); -}; +typedef void (*TColorPutter)(Uint8 *&ptr, const Uint8 & R, const Uint8 & G, const Uint8 & B); +typedef void (*TColorPutterAlpha)(Uint8 *&ptr, const Uint8 & R, const Uint8 & G, const Uint8 & B, const Uint8 & A); -typedef void (*BlitterWithRotationVal)(SDL_Surface *src,SDL_Rect srcRect, SDL_Surface * dst, SDL_Rect dstRect, ui8 rotation); + void blitAt(SDL_Surface * src, int x, int y, SDL_Surface * dst=screen); + void blitAt(SDL_Surface * src, const Rect & pos, SDL_Surface * dst=screen); -namespace CSDL_Ext -{ - /// helper that will safely set and un-set ClipRect for SDL_Surface - class CClipRectGuard - { - SDL_Surface * surf; - SDL_Rect oldRect; - public: - CClipRectGuard(SDL_Surface * surface, const SDL_Rect & rect): - surf(surface) - { - SDL_GetClipRect(surf, &oldRect); - SDL_SetClipRect(surf, &rect); - } + void setClipRect(SDL_Surface * src, const Rect & other); + void getClipRect(SDL_Surface * src, Rect & other); - ~CClipRectGuard() - { - SDL_SetClipRect(surf, &oldRect); - } - }; + void blitSurface(SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dest); + void blitSurface(SDL_Surface * src, SDL_Surface * dst, const Point & dest); - void blitSurface(SDL_Surface * src, const SDL_Rect * srcRect, SDL_Surface * dst, SDL_Rect * dstRect); - void fillRect(SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color); - void fillRectBlack(SDL_Surface * dst, SDL_Rect * dstrect); + void fillSurface(SDL_Surface *dst, const SDL_Color & color); + void fillRect(SDL_Surface *dst, const Rect & dstrect, const SDL_Color & color); //fill dest image with source texture. void fillTexture(SDL_Surface *dst, SDL_Surface * sourceTexture); - void SDL_PutPixelWithoutRefresh(SDL_Surface *ekran, const int & x, const int & y, const Uint8 & R, const Uint8 & G, const Uint8 & B, Uint8 A = 255); - void SDL_PutPixelWithoutRefreshIfInSurf(SDL_Surface *ekran, const int & x, const int & y, const Uint8 & R, const Uint8 & G, const Uint8 & B, Uint8 A = 255); + void updateRect(SDL_Surface *surface, const Rect & rect); + + void putPixelWithoutRefresh(SDL_Surface *ekran, const int & x, const int & y, const Uint8 & R, const Uint8 & G, const Uint8 & B, Uint8 A = 255); + void putPixelWithoutRefreshIfInSurf(SDL_Surface *ekran, const int & x, const int & y, const Uint8 & R, const Uint8 & G, const Uint8 & B, Uint8 A = 255); SDL_Surface * verticalFlip(SDL_Surface * toRot); //vertical flip SDL_Surface * horizontalFlip(SDL_Surface * toRot); //horizontal flip - Uint32 SDL_GetPixel(SDL_Surface *surface, const int & x, const int & y, bool colorByte = false); + Uint32 getPixel(SDL_Surface *surface, const int & x, const int & y, bool colorByte = false); bool isTransparent(SDL_Surface * srf, int x, int y); //checks if surface is transparent at given position Uint8 *getPxPtr(const SDL_Surface * const &srf, const int x, const int y); @@ -192,16 +137,16 @@ namespace CSDL_Ext TColorPutterAlpha getPutterAlphaFor(SDL_Surface * const &dest, int incrementing); //incrementing: -1, 0, 1 template - int blit8bppAlphaTo24bppT(const SDL_Surface * src, const SDL_Rect * srcRect, SDL_Surface * dst, SDL_Rect * dstRect); //blits 8 bpp surface with alpha channel to 24 bpp surface - int blit8bppAlphaTo24bpp(const SDL_Surface * src, const SDL_Rect * srcRect, SDL_Surface * dst, SDL_Rect * dstRect); //blits 8 bpp surface with alpha channel to 24 bpp surface + int blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint); //blits 8 bpp surface with alpha channel to 24 bpp surface + int blit8bppAlphaTo24bpp(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint); //blits 8 bpp surface with alpha channel to 24 bpp surface Uint32 colorToUint32(const SDL_Color * color); //little endian only SDL_Color makeColor(ui8 r, ui8 g, ui8 b, ui8 a); void update(SDL_Surface * what = screen); //updates whole surface (default - main screen) void drawLine(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2); - void drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const int3 &color); - void drawBorder(SDL_Surface * sur, const SDL_Rect &r, const int3 &color); - void drawDashedBorder(SDL_Surface * sur, const Rect &r, const int3 &color); + void drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const SDL_Color &color); + void drawBorder(SDL_Surface * sur, const Rect &r, const SDL_Color &color); + void drawDashedBorder(SDL_Surface * sur, const Rect &r, const SDL_Color &color); void setPlayerColor(SDL_Surface * sur, PlayerColor player); //sets correct color of flags; -1 for neutral std::string processStr(std::string str, std::vector & tor); //replaces %s in string SDL_Surface * newSurface(int w, int h, SDL_Surface * mod=screen); //creates new surface, with flags/format same as in surface given @@ -217,10 +162,10 @@ namespace CSDL_Ext SDL_Surface * scaleSurface(SDL_Surface *surf, int width, int height); template - void applyEffectBpp( SDL_Surface * surf, const SDL_Rect * rect, int mode ); - void applyEffect(SDL_Surface * surf, const SDL_Rect * rect, int mode); //mode: 0 - sepia, 1 - grayscale + void applyEffectBpp( SDL_Surface * surf, const Rect & rect, int mode ); + void applyEffect(SDL_Surface * surf, const Rect & rect, int mode); //mode: 0 - sepia, 1 - grayscale - void startTextInput(SDL_Rect * where); + void startTextInput(const Rect & where); void stopTextInput(); void setColorKey(SDL_Surface * surface, SDL_Color color); @@ -230,4 +175,23 @@ namespace CSDL_Ext ///set key-color to 0,255,255 only if it exactly mapped void setDefaultColorKeyPresize(SDL_Surface * surface); + + /// helper that will safely set and un-set ClipRect for SDL_Surface + class CClipRectGuard + { + SDL_Surface * surf; + Rect oldRect; + public: + CClipRectGuard(SDL_Surface * surface, const Rect & rect): + surf(surface) + { + CSDL_Ext::getClipRect(surf, oldRect); + CSDL_Ext::setClipRect(surf, rect); + } + + ~CClipRectGuard() + { + CSDL_Ext::setClipRect(surf, oldRect); + } + }; } diff --git a/client/gui/SDL_Pixels.h b/client/gui/SDL_Pixels.h index 9a0d3de7a..f18f599bc 100644 --- a/client/gui/SDL_Pixels.h +++ b/client/gui/SDL_Pixels.h @@ -9,10 +9,10 @@ */ #pragma once -#include - #include "SDL_Extensions.h" +#include + // for accessing channels from pixel in format-independent way //should be as fast as accessing via pointer at least for 3-4 bytes per pixel namespace Channels @@ -119,6 +119,16 @@ namespace Channels }; } +template +struct ColorPutter +{ + static STRONG_INLINE void PutColor(Uint8 *&ptr, const Uint8 & R, const Uint8 & G, const Uint8 & B); + static STRONG_INLINE void PutColor(Uint8 *&ptr, const Uint8 & R, const Uint8 & G, const Uint8 & B, const Uint8 & A); + static STRONG_INLINE void PutColorAlphaSwitch(Uint8 *&ptr, const Uint8 & R, const Uint8 & G, const Uint8 & B, const Uint8 & A); + static STRONG_INLINE void PutColor(Uint8 *&ptr, const SDL_Color & Color); + static STRONG_INLINE void PutColorAlpha(Uint8 *&ptr, const SDL_Color & Color); + static STRONG_INLINE void PutColorRow(Uint8 *&ptr, const SDL_Color & Color, size_t count); +}; template struct ColorPutter<2, incrementPtr> diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 27f053aa2..814015686 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -546,8 +546,9 @@ CLoadingScreen::~CLoadingScreen() void CLoadingScreen::showAll(SDL_Surface * to) { - Rect rect(0, 0, to->w, to->h); - SDL_FillRect(to, &rect, 0); + //FIXME: filling screen with transparency? BLACK intended? + //Rect rect(0, 0, to->w, to->h); + //CSDL_Ext::fillRect(to, rect, Colors::TRANSPARENCY); CWindowObject::showAll(to); } diff --git a/client/mainmenu/CPrologEpilogVideo.cpp b/client/mainmenu/CPrologEpilogVideo.cpp index a6bac79a3..5f04aedb6 100644 --- a/client/mainmenu/CPrologEpilogVideo.cpp +++ b/client/mainmenu/CPrologEpilogVideo.cpp @@ -39,7 +39,7 @@ CPrologEpilogVideo::CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog void CPrologEpilogVideo::show(SDL_Surface * to) { - CSDL_Ext::fillRectBlack(to, &pos); + CSDL_Ext::fillRect(to, pos, Colors::BLACK); //BUG: some videos are 800x600 in size while some are 800x400 //VCMI should center them in the middle of the screen. Possible but needs modification //of video player API which I'd like to avoid until we'll get rid of Windows-specific player diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index b7341d068..f8371c27a 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -288,7 +288,7 @@ void CMapHandler::initObjectRects() for(int fy=0; fy < obj->getHeight(); ++fy) { int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z); - SDL_Rect cr; + Rect cr; cr.w = 32; cr.h = 32; cr.x = image->width() - fx * 32 - 32; @@ -374,7 +374,7 @@ CMapHandler::CMapBlitter *CMapHandler::resolveBlitter(const MapDrawingInfo * inf return normalBlitter; } -void CMapHandler::CMapNormalBlitter::drawElement(EMapCacheType cacheType, std::shared_ptr source, SDL_Rect * sourceRect, SDL_Surface * targetSurf, SDL_Rect * destRect) const +void CMapHandler::CMapNormalBlitter::drawElement(EMapCacheType cacheType, std::shared_ptr source, Rect * sourceRect, SDL_Surface * targetSurf, Rect * destRect) const { source->draw(targetSurf, destRect, sourceRect); } @@ -387,8 +387,8 @@ void CMapHandler::CMapNormalBlitter::init(const MapDrawingInfo * drawingInfo) tileCount.y = parent->tilesH; topTile = info->topTile; - initPos.x = parent->offsetX + info->drawBounds->x; - initPos.y = parent->offsetY + info->drawBounds->y; + initPos.x = parent->offsetX + info->drawBounds.x; + initPos.y = parent->offsetY + info->drawBounds.y; realTileRect = Rect(initPos.x, initPos.y, tileSize, tileSize); @@ -430,11 +430,11 @@ void CMapHandler::CMapNormalBlitter::init(const MapDrawingInfo * drawingInfo) tileCount.y = parent->sizes.y + parent->frameH - topTile.y; } -SDL_Rect CMapHandler::CMapNormalBlitter::clip(SDL_Surface * targetSurf) const +Rect CMapHandler::CMapNormalBlitter::clip(SDL_Surface * targetSurf) const { - SDL_Rect prevClip; - SDL_GetClipRect(targetSurf, &prevClip); - SDL_SetClipRect(targetSurf, info->drawBounds); + Rect prevClip; + CSDL_Ext::getClipRect(targetSurf, prevClip); + CSDL_Ext::setClipRect(targetSurf, info->drawBounds); return prevClip; } @@ -512,7 +512,7 @@ void CMapHandler::CMapWorldViewBlitter::calculateWorldViewCameraPos() topTile.y = parent->sizes.y - tileCount.y; } -void CMapHandler::CMapWorldViewBlitter::drawElement(EMapCacheType cacheType, std::shared_ptr source, SDL_Rect * sourceRect, SDL_Surface * targetSurf, SDL_Rect * destRect) const +void CMapHandler::CMapWorldViewBlitter::drawElement(EMapCacheType cacheType, std::shared_ptr source, Rect * sourceRect, SDL_Surface * targetSurf, Rect * destRect) const { auto scaled = parent->cache.requestWorldViewCacheOrCreate(cacheType, source); @@ -584,7 +584,7 @@ void CMapHandler::CMapWorldViewBlitter::drawOverlayEx(SDL_Surface * targetSurf) } } -void CMapHandler::CMapWorldViewBlitter::drawHeroFlag(SDL_Surface * targetSurf, std::shared_ptr source, SDL_Rect * sourceRect, SDL_Rect * destRect, bool moving) const +void CMapHandler::CMapWorldViewBlitter::drawHeroFlag(SDL_Surface * targetSurf, std::shared_ptr source, Rect * sourceRect, Rect * destRect, bool moving) const { if (moving) return; @@ -592,7 +592,7 @@ void CMapHandler::CMapWorldViewBlitter::drawHeroFlag(SDL_Surface * targetSurf, s CMapBlitter::drawHeroFlag(targetSurf, source, sourceRect, destRect, false); } -void CMapHandler::CMapWorldViewBlitter::drawObject(SDL_Surface * targetSurf, std::shared_ptr source, SDL_Rect * sourceRect, bool moving) const +void CMapHandler::CMapWorldViewBlitter::drawObject(SDL_Surface * targetSurf, std::shared_ptr source, Rect * sourceRect, bool moving) const { if (moving) return; @@ -625,11 +625,11 @@ void CMapHandler::CMapWorldViewBlitter::init(const MapDrawingInfo * drawingInfo) tileSize = (int) floorf(32.0f * info->scale); halfTileSizeCeil = (int)ceilf(tileSize / 2.0f); - tileCount.x = (int) ceilf((float)info->drawBounds->w / tileSize); - tileCount.y = (int) ceilf((float)info->drawBounds->h / tileSize); + tileCount.x = (int) ceilf((float)info->drawBounds.w / tileSize); + tileCount.y = (int) ceilf((float)info->drawBounds.h / tileSize); - initPos.x = info->drawBounds->x; - initPos.y = info->drawBounds->y; + initPos.x = info->drawBounds.x; + initPos.y = info->drawBounds.y; realTileRect = Rect(initPos.x, initPos.y, tileSize, tileSize); defaultTileRect = Rect(0, 0, tileSize, tileSize); @@ -637,19 +637,19 @@ void CMapHandler::CMapWorldViewBlitter::init(const MapDrawingInfo * drawingInfo) calculateWorldViewCameraPos(); } -SDL_Rect CMapHandler::CMapWorldViewBlitter::clip(SDL_Surface * targetSurf) const +Rect CMapHandler::CMapWorldViewBlitter::clip(SDL_Surface * targetSurf) const { - SDL_Rect prevClip; + Rect prevClip; - SDL_FillRect(targetSurf, info->drawBounds, SDL_MapRGB(targetSurf->format, 0, 0, 0)); + CSDL_Ext::fillRect(targetSurf, info->drawBounds, Colors::BLACK); // makes the clip area smaller if the map is smaller than the screen frame // (actually, it could be made 1 tile bigger so that overlay icons on edge tiles could be drawn partly outside) - Rect clipRect(std::max(info->drawBounds->x, info->drawBounds->x - topTile.x * tileSize), - std::max(info->drawBounds->y, info->drawBounds->y - topTile.y * tileSize), - std::min(info->drawBounds->w, parent->sizes.x * tileSize), - std::min(info->drawBounds->h, parent->sizes.y * tileSize)); - SDL_GetClipRect(targetSurf, &prevClip); - SDL_SetClipRect(targetSurf, &clipRect); //preventing blitting outside of that rect + Rect clipRect(std::max(info->drawBounds.x, info->drawBounds.x - topTile.x * tileSize), + std::max(info->drawBounds.y, info->drawBounds.y - topTile.y * tileSize), + std::min(info->drawBounds.w, parent->sizes.x * tileSize), + std::min(info->drawBounds.h, parent->sizes.y * tileSize)); + CSDL_Ext::getClipRect(targetSurf, prevClip); + CSDL_Ext::setClipRect(targetSurf, clipRect); //preventing blitting outside of that rect return prevClip; } @@ -715,12 +715,12 @@ void CMapHandler::CMapBlitter::drawOverlayEx(SDL_Surface * targetSurf) //nothing to do here } -void CMapHandler::CMapBlitter::drawHeroFlag(SDL_Surface * targetSurf, std::shared_ptr source, SDL_Rect * sourceRect, SDL_Rect * destRect, bool moving) const +void CMapHandler::CMapBlitter::drawHeroFlag(SDL_Surface * targetSurf, std::shared_ptr source, Rect * sourceRect, Rect * destRect, bool moving) const { drawElement(EMapCacheType::HERO_FLAGS, source, sourceRect, targetSurf, destRect); } -void CMapHandler::CMapBlitter::drawObject(SDL_Surface * targetSurf, std::shared_ptr source, SDL_Rect * sourceRect, bool moving) const +void CMapHandler::CMapBlitter::drawObject(SDL_Surface * targetSurf, std::shared_ptr source, Rect * sourceRect, bool moving) const { Rect dstRect(realTileRect); drawElement(EMapCacheType::OBJECTS, source, sourceRect, targetSurf, &dstRect); @@ -739,7 +739,7 @@ void CMapHandler::CMapBlitter::drawObjects(SDL_Surface * targetSurf, const Terra // this object is currently fading, so skip normal drawing Rect r2(realTileRect); CFadeAnimation * fade = (*fadeIter).second.second; - fade->draw(targetSurf, nullptr, &r2); + fade->draw(targetSurf, r2.topLeft()); continue; } logGlobal->error("Fading map object with missing fade anim : %d", object.fadeAnimKey); @@ -903,7 +903,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn if (!block) block = BitmapHandler::loadBitmap("blocked"); - CSDL_Ext::blitSurface(block, nullptr, targetSurf, &realTileRect); + CSDL_Ext::blitSurface(block, targetSurf, realTileRect.topLeft()); } } if (settings["session"]["showVisit"].Bool()) @@ -914,7 +914,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn if (!visit) visit = BitmapHandler::loadBitmap("visitable"); - CSDL_Ext::blitSurface(visit, nullptr, targetSurf, &realTileRect); + CSDL_Ext::blitSurface(visit, targetSurf, realTileRect.topLeft()); } } } @@ -932,26 +932,26 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn { const int3 color(0x555555, 0x555555, 0x555555); - if (realPos.y >= info->drawBounds->y && - realPos.y < info->drawBounds->y + info->drawBounds->h) + if (realPos.y >= info->drawBounds.y && + realPos.y < info->drawBounds.y + info->drawBounds.h) for(int i = 0; i < tileSize; i++) - if (realPos.x + i >= info->drawBounds->x && - realPos.x + i < info->drawBounds->x + info->drawBounds->w) - CSDL_Ext::SDL_PutPixelWithoutRefresh(targetSurf, realPos.x + i, realPos.y, color.x, color.y, color.z); + if (realPos.x + i >= info->drawBounds.x && + realPos.x + i < info->drawBounds.x + info->drawBounds.w) + CSDL_Ext::putPixelWithoutRefresh(targetSurf, realPos.x + i, realPos.y, color.x, color.y, color.z); - if (realPos.x >= info->drawBounds->x && - realPos.x < info->drawBounds->x + info->drawBounds->w) + if (realPos.x >= info->drawBounds.x && + realPos.x < info->drawBounds.x + info->drawBounds.w) for(int i = 0; i < tileSize; i++) - if (realPos.y + i >= info->drawBounds->y && - realPos.y + i < info->drawBounds->y + info->drawBounds->h) - CSDL_Ext::SDL_PutPixelWithoutRefresh(targetSurf, realPos.x, realPos.y + i, color.x, color.y, color.z); + if (realPos.y + i >= info->drawBounds.y && + realPos.y + i < info->drawBounds.y + info->drawBounds.h) + CSDL_Ext::putPixelWithoutRefresh(targetSurf, realPos.x, realPos.y + i, color.x, color.y, color.z); } } } postProcessing(targetSurf); - SDL_SetClipRect(targetSurf, &prevClip); + CSDL_Ext::setClipRect(targetSurf, prevClip); } CMapHandler::AnimBitmapHolder CMapHandler::CMapBlitter::findHeroBitmap(const CGHeroInstance * hero, int anim) const @@ -1207,7 +1207,7 @@ bool CMapHandler::printObject(const CGObjectInstance * obj, bool fadein) { for(int fy=0; fy ambientSound; - TerrainTileObject(const CGObjectInstance *obj_, SDL_Rect rect_, bool visitablePos = false); + TerrainTileObject(const CGObjectInstance *obj_, Rect rect_, bool visitablePos = false); ~TerrainTileObject(); }; @@ -98,7 +97,7 @@ struct MapDrawingInfo bool scaled; int3 &topTile; // top-left tile in viewport [in tiles] std::shared_ptr> visibilityMap; - SDL_Rect * drawBounds; // map rect drawing bounds on screen + Rect drawBounds; // map rect drawing bounds on screen std::shared_ptr icons; // holds overlay icons for world view mode float scale; // map scale for world view mode (only if scaled == true) @@ -115,7 +114,7 @@ struct MapDrawingInfo bool showAllTerrain; //for expert viewEarth - MapDrawingInfo(int3 &topTile_, std::shared_ptr> visibilityMap_, SDL_Rect * drawBounds_, std::shared_ptr icons_ = nullptr) + MapDrawingInfo(int3 &topTile_, std::shared_ptr> visibilityMap_, const Rect & drawBounds_, std::shared_ptr icons_ = nullptr) : scaled(false), topTile(topTile_), @@ -216,7 +215,7 @@ class CMapHandler const MapDrawingInfo * info; // data for drawing passed from outside /// general drawing method, called internally by more specialized ones - virtual void drawElement(EMapCacheType cacheType, std::shared_ptr source, SDL_Rect * sourceRect, SDL_Surface * targetSurf, SDL_Rect * destRect) const = 0; + virtual void drawElement(EMapCacheType cacheType, std::shared_ptr source, Rect * sourceRect, SDL_Surface * targetSurf, Rect * destRect) const = 0; // first drawing pass @@ -228,8 +227,8 @@ class CMapHandler virtual void drawRoad(SDL_Surface * targetSurf, const TerrainTile & tinfo, const TerrainTile * tinfoUpper) const; /// draws all objects on current tile (higher-level logic, unlike other draw*** methods) virtual void drawObjects(SDL_Surface * targetSurf, const TerrainTile2 & tile) const; - virtual void drawObject(SDL_Surface * targetSurf, std::shared_ptr source, SDL_Rect * sourceRect, bool moving) const; - virtual void drawHeroFlag(SDL_Surface * targetSurf, std::shared_ptr source, SDL_Rect * sourceRect, SDL_Rect * destRect, bool moving) const; + virtual void drawObject(SDL_Surface * targetSurf, std::shared_ptr source, Rect * sourceRect, bool moving) const; + virtual void drawHeroFlag(SDL_Surface * targetSurf, std::shared_ptr source, Rect * sourceRect, Rect * destRect, bool moving) const; // second drawing pass @@ -252,7 +251,7 @@ class CMapHandler /// initializes frame-drawing (called at the start of every redraw) virtual void init(const MapDrawingInfo * drawingInfo) = 0; /// calculates clip region for map viewport - virtual SDL_Rect clip(SDL_Surface * targetSurf) const = 0; + virtual Rect clip(SDL_Surface * targetSurf) const = 0; virtual ui8 getHeroFrameGroup(ui8 dir, bool isMoving) const; virtual ui8 getPhaseShift(const CGObjectInstance *object) const; @@ -279,10 +278,10 @@ class CMapHandler class CMapNormalBlitter : public CMapBlitter { protected: - void drawElement(EMapCacheType cacheType, std::shared_ptr source, SDL_Rect * sourceRect, SDL_Surface * targetSurf, SDL_Rect * destRect) const override; + void drawElement(EMapCacheType cacheType, std::shared_ptr source, Rect * sourceRect, SDL_Surface * targetSurf, Rect * destRect) const override; void drawTileOverlay(SDL_Surface * targetSurf,const TerrainTile2 & tile) const override {} void init(const MapDrawingInfo * info) override; - SDL_Rect clip(SDL_Surface * targetSurf) const override; + Rect clip(SDL_Surface * targetSurf) const override; public: CMapNormalBlitter(CMapHandler * parent); virtual ~CMapNormalBlitter(){} @@ -293,14 +292,14 @@ class CMapHandler private: std::shared_ptr objectToIcon(Obj id, si32 subId, PlayerColor owner) const; protected: - void drawElement(EMapCacheType cacheType, std::shared_ptr source, SDL_Rect * sourceRect, SDL_Surface * targetSurf, SDL_Rect * destRect) const override; + void drawElement(EMapCacheType cacheType, std::shared_ptr source, Rect * sourceRect, SDL_Surface * targetSurf, Rect * destRect) const override; void drawTileOverlay(SDL_Surface * targetSurf, const TerrainTile2 & tile) const override; - void drawHeroFlag(SDL_Surface * targetSurf, std::shared_ptr source, SDL_Rect * sourceRect, SDL_Rect * destRect, bool moving) const override; - void drawObject(SDL_Surface * targetSurf, std::shared_ptr source, SDL_Rect * sourceRect, bool moving) const override; + void drawHeroFlag(SDL_Surface * targetSurf, std::shared_ptr source, Rect * sourceRect, Rect * destRect, bool moving) const override; + void drawObject(SDL_Surface * targetSurf, std::shared_ptr source, Rect * sourceRect, bool moving) const override; void drawFrame(SDL_Surface * targetSurf) const override {} void drawOverlayEx(SDL_Surface * targetSurf) override; void init(const MapDrawingInfo * info) override; - SDL_Rect clip(SDL_Surface * targetSurf) const override; + Rect clip(SDL_Surface * targetSurf) const override; ui8 getPhaseShift(const CGObjectInstance *object) const override { return 0u; } void calculateWorldViewCameraPos(); public: diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index 6fabf2e1a..732091417 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -237,7 +237,7 @@ void CHeroList::CHeroItem::open() void CHeroList::CHeroItem::showTooltip() { - CRClickPopup::createAndPush(hero, Point(GH.current->motion)); + CRClickPopup::createAndPush(hero, Geometry::fromSDL(GH.current->motion)); } std::string CHeroList::CHeroItem::getHoverText() @@ -329,7 +329,7 @@ void CTownList::CTownItem::open() void CTownList::CTownItem::showTooltip() { - CRClickPopup::createAndPush(town, Point(GH.current->motion)); + CRClickPopup::createAndPush(town, Geometry::fromSDL(GH.current->motion)); } std::string CTownList::CTownItem::getHoverText() @@ -593,8 +593,8 @@ void CMinimap::showAll(SDL_Surface * to) int3 tileCountOnScreen = adventureInt->terrain.tileCountOnScreen(); //draw radar - SDL_Rect oldClip; - SDL_Rect radar = + Rect oldClip; + Rect radar = { si16(adventureInt->position.x * pos.w / mapSizes.x + pos.x), si16(adventureInt->position.y * pos.h / mapSizes.y + pos.y), @@ -612,10 +612,10 @@ void CMinimap::showAll(SDL_Surface * to) return; // whole map is visible at once, no point in redrawing border } - SDL_GetClipRect(to, &oldClip); - SDL_SetClipRect(to, &pos); - CSDL_Ext::drawDashedBorder(to, radar, int3(255,75,125)); - SDL_SetClipRect(to, &oldClip); + CSDL_Ext::getClipRect(to, oldClip); + CSDL_Ext::setClipRect(to, pos); + CSDL_Ext::drawDashedBorder(to, radar, Colors::PURPLE); + CSDL_Ext::setClipRect(to, oldClip); } } @@ -1224,7 +1224,7 @@ void CAdvMapPanel::setPlayerColor(const PlayerColor & clr) void CAdvMapPanel::showAll(SDL_Surface * to) { if(background) - blitAt(background, pos.x, pos.y, to); + CSDL_Ext::blitAt(background, pos.x, pos.y, to); CIntObject::showAll(to); } diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp index aace018d3..998f0ec2a 100644 --- a/client/widgets/Buttons.cpp +++ b/client/widgets/Buttons.cpp @@ -28,10 +28,12 @@ void CButton::update() { if (overlay) { + Point targetPos = Rect::createCentered( pos, overlay->pos.dimensions()).topLeft(); + if (state == PRESSED) - overlay->moveTo(overlay->pos.centerIn(pos).topLeft() + Point(1,1)); + overlay->moveTo(targetPos + Point(1,1)); else - overlay->moveTo(overlay->pos.centerIn(pos).topLeft()); + overlay->moveTo(targetPos); } int newPos = stateToIndex[int(state)]; @@ -88,7 +90,8 @@ void CButton::addOverlay(std::shared_ptr newOverlay) if(overlay) { addChild(newOverlay.get()); - overlay->moveTo(overlay->pos.centerIn(pos).topLeft()); + Point targetPos = Rect::createCentered( pos, overlay->pos.dimensions()).topLeft(); + overlay->moveTo(targetPos); } update(); } @@ -277,8 +280,8 @@ void CButton::showAll(SDL_Surface * to) CIntObject::showAll(to); auto borderColor = stateToBorderColor[getState()]; - if (borderColor && borderColor->a == 0) - CSDL_Ext::drawBorder(to, pos.x-1, pos.y-1, pos.w+2, pos.h+2, int3(borderColor->r, borderColor->g, borderColor->b)); + if (borderColor) + CSDL_Ext::drawBorder(to, pos.x-1, pos.y-1, pos.w+2, pos.h+2, *borderColor); } std::pair CButton::tooltip() @@ -765,7 +768,7 @@ void CSlider::setAmount( int to ) void CSlider::showAll(SDL_Surface * to) { - CSDL_Ext::fillRectBlack(to, &pos); + CSDL_Ext::fillRect(to, pos, Colors::BLACK); CIntObject::showAll(to); } diff --git a/client/widgets/Buttons.h b/client/widgets/Buttons.h index 4c7a261f3..971b8614a 100644 --- a/client/widgets/Buttons.h +++ b/client/widgets/Buttons.h @@ -11,7 +11,6 @@ #include "../gui/CIntObject.h" #include "../gui/SDL_Extensions.h" - #include "../../lib/FunctionList.h" VCMI_LIB_NAMESPACE_BEGIN @@ -24,7 +23,7 @@ struct ButtonInfo; VCMI_LIB_NAMESPACE_END struct SDL_Surface; -struct Rect; +class Rect; class CAnimImage; class CLabel; class CAnimation; diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp index 600059c55..9da38876d 100644 --- a/client/widgets/CArtifactHolder.cpp +++ b/client/widgets/CArtifactHolder.cpp @@ -340,15 +340,15 @@ void CHeroArtPlace::showAll(SDL_Surface * to) // Draw vertical bars. for (int i = 0; i < pos.h; ++i) { - CSDL_Ext::SDL_PutPixelWithoutRefresh(to, pos.x, pos.y + i, 240, 220, 120); - CSDL_Ext::SDL_PutPixelWithoutRefresh(to, pos.x + pos.w - 1, pos.y + i, 240, 220, 120); + CSDL_Ext::putPixelWithoutRefresh(to, pos.x, pos.y + i, 240, 220, 120); + CSDL_Ext::putPixelWithoutRefresh(to, pos.x + pos.w - 1, pos.y + i, 240, 220, 120); } // Draw horizontal bars. for (int i = 0; i < pos.w; ++i) { - CSDL_Ext::SDL_PutPixelWithoutRefresh(to, pos.x + i, pos.y, 240, 220, 120); - CSDL_Ext::SDL_PutPixelWithoutRefresh(to, pos.x + i, pos.y + pos.h - 1, 240, 220, 120); + CSDL_Ext::putPixelWithoutRefresh(to, pos.x + i, pos.y, 240, 220, 120); + CSDL_Ext::putPixelWithoutRefresh(to, pos.x + i, pos.y + pos.h - 1, 240, 220, 120); } } } diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index 95ccd0f7c..5017781ed 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -296,7 +296,7 @@ void CSelectableComponent::showAll(SDL_Surface * to) CComponent::showAll(to); if(selected) { - CSDL_Ext::drawBorder(to, Rect::around(image->pos), int3(239,215,123)); + CSDL_Ext::drawBorder(to, Rect::createAround(image->pos, 1), Colors::BRIGHT_YELLOW); } } diff --git a/client/widgets/Images.cpp b/client/widgets/Images.cpp index 285602f37..88bced4b5 100644 --- a/client/widgets/Images.cpp +++ b/client/widgets/Images.cpp @@ -129,16 +129,9 @@ void CPicture::showAll(SDL_Surface * to) if(bg && visible) { if(srcRect) - { - SDL_Rect srcRectCpy = *srcRect; - SDL_Rect dstRect = srcRectCpy; - dstRect.x = pos.x; - dstRect.y = pos.y; - - CSDL_Ext::blitSurface(bg, &srcRectCpy, to, &dstRect); - } + CSDL_Ext::blitSurface(bg, *srcRect, to, pos.topLeft()); else - blitAt(bg, pos, to); + CSDL_Ext::blitAt(bg, pos, to); } } diff --git a/client/widgets/Images.h b/client/widgets/Images.h index 6150bfc8b..10e5df50a 100644 --- a/client/widgets/Images.h +++ b/client/widgets/Images.h @@ -10,11 +10,12 @@ #pragma once #include "../gui/CIntObject.h" -#include "../gui/SDL_Extensions.h" #include "../battle/BattleConstants.h" +#include + struct SDL_Surface; -struct Rect; +class Rect; class CAnimImage; class CLabel; class CAnimation; diff --git a/client/widgets/ObjectLists.h b/client/widgets/ObjectLists.h index b2c9e1aaa..52dade789 100644 --- a/client/widgets/ObjectLists.h +++ b/client/widgets/ObjectLists.h @@ -12,7 +12,7 @@ #include "../gui/CIntObject.h" struct SDL_Surface; -struct Rect; +class Rect; class CAnimImage; class CSlider; class CLabel; diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index 5ea3a96ca..38c971aa6 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -349,7 +349,7 @@ void CGStatusBar::setEnteringMode(bool on) { assert(enteringText == false); alignment = ETextAlignment::TOPLEFT; - CSDL_Ext::startTextInput(&pos); + CSDL_Ext::startTextInput(pos); setText(consoleText); } else @@ -505,7 +505,7 @@ CTextInput::CTextInput(const Rect & Pos, SDL_Surface * srf) background = std::make_shared(Pos, 0, true); Rect hlp = Pos; if(srf) - CSDL_Ext::blitSurface(srf, &hlp, background->getSurface(), nullptr); + CSDL_Ext::blitSurface(srf, hlp, background->getSurface(), Point(0,0)); else SDL_FillRect(background->getSurface(), nullptr, 0); pos.w = background->pos.w; @@ -527,7 +527,7 @@ CKeyboardFocusListener::CKeyboardFocusListener(CTextInput * textInput) void CKeyboardFocusListener::focusGot() { - CSDL_Ext::startTextInput(&textInput->pos); + CSDL_Ext::startTextInput(textInput->pos); usageIndex++; } diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 2936eeef0..8a0c23a65 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -248,7 +248,7 @@ void CTerrainRect::hover(bool on) } //Hoverable::hover(on); } -void CTerrainRect::showPath(const SDL_Rect * extRect, SDL_Surface * to) +void CTerrainRect::showPath(const Rect & extRect, SDL_Surface * to) { const static int pns[9][9] = { {16, 17, 18, 7, -1, 19, 6, 5, -1}, @@ -319,9 +319,9 @@ void CTerrainRect::showPath(const SDL_Rect * extRect, SDL_Surface * to) int hvx = (x + arrow->width()) - (pos.x + pos.w), hvy = (y + arrow->height()) - (pos.y + pos.h); - SDL_Rect prevClip; - SDL_GetClipRect(to, &prevClip); - SDL_SetClipRect(to, extRect); //preventing blitting outside of that rect + Rect prevClip; + CSDL_Ext::getClipRect(to, prevClip); + CSDL_Ext::setClipRect(to, extRect); //preventing blitting outside of that rect if(ADVOPT.smoothMove) //version for smooth hero move, with pos shifts { @@ -367,7 +367,7 @@ void CTerrainRect::showPath(const SDL_Rect * extRect, SDL_Surface * to) arrow->draw(to, x, y, &srcRect); } } - SDL_SetClipRect(to, &prevClip); + CSDL_Ext::setClipRect(to, prevClip); } } //for (int i=0;inodes.size()-1;i++) @@ -377,7 +377,7 @@ void CTerrainRect::show(SDL_Surface * to) { if (adventureInt->mode == EAdvMapMode::NORMAL) { - MapDrawingInfo info(adventureInt->position, LOCPLINT->cb->getVisibilityMap(), &pos); + MapDrawingInfo info(adventureInt->position, LOCPLINT->cb->getVisibilityMap(), pos); info.otherheroAnim = true; info.anim = adventureInt->anim; info.heroAnim = adventureInt->heroAnim; @@ -389,12 +389,12 @@ void CTerrainRect::show(SDL_Surface * to) { Rect r(pos); fadeAnim->update(); - fadeAnim->draw(to, nullptr, &r); + fadeAnim->draw(to, r.topLeft()); } if (currentPath/* && adventureInt->position.z==currentPath->startPos().z*/) //drawing path { - showPath(&pos, to); + showPath(pos, to); } } } @@ -404,7 +404,7 @@ void CTerrainRect::showAll(SDL_Surface * to) // world view map is static and doesn't need redraw every frame if (adventureInt->mode == EAdvMapMode::WORLD_VIEW) { - MapDrawingInfo info(adventureInt->position, LOCPLINT->cb->getVisibilityMap(), &pos, adventureInt->worldViewIcons); + MapDrawingInfo info(adventureInt->position, LOCPLINT->cb->getVisibilityMap(), pos, adventureInt->worldViewIcons); info.scaled = true; info.scale = adventureInt->worldViewScale; adventureInt->worldViewOptions.adjustDrawingInfo(info); @@ -460,7 +460,7 @@ void CTerrainRect::fadeFromCurrentView() if (!fadeSurface) fadeSurface = CSDL_Ext::newSurface(pos.w, pos.h); - SDL_BlitSurface(screen, &pos, fadeSurface, nullptr); + CSDL_Ext::blitSurface(screen, fadeSurface, Point(0,0)); fadeAnim->init(CFadeAnimation::EMode::OUT, fadeSurface); } @@ -1814,7 +1814,7 @@ void CAdvMapInt::tileRClicked(const int3 &mapPos) return; } - CRClickPopup::createAndPush(obj, Point(GH.current->motion), ETextAlignment::CENTER); + CRClickPopup::createAndPush(obj, Geometry::fromSDL(GH.current->motion), ETextAlignment::CENTER); } void CAdvMapInt::enterCastingMode(const CSpell * sp) diff --git a/client/windows/CAdvmapInterface.h b/client/windows/CAdvmapInterface.h index eb38b3cd6..101aeb18f 100644 --- a/client/windows/CAdvmapInterface.h +++ b/client/windows/CAdvmapInterface.h @@ -91,7 +91,7 @@ public: void show(SDL_Surface * to) override; void showAll(SDL_Surface * to) override; void showAnim(SDL_Surface * to); - void showPath(const SDL_Rect * extRect, SDL_Surface * to); + void showPath(const Rect &extRect, SDL_Surface * to); int3 whichTileIsIt(const int x, const int y); //x,y are cursor position int3 whichTileIsIt(); //uses current cursor pos /// @returns number of visible tiles on screen respecting current map scaling diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 6dfb48fa4..3e51d0af3 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -208,9 +208,9 @@ void CBuildingRect::show(SDL_Surface * to) else newColor = oldColor; - SDL_SetColors(border, &newColor, colorID, 1); + CSDL_Ext::setColors(border, &newColor, colorID, 1); blitAtLoc(border, 0, 0, to); - SDL_SetColors(border, &oldColor, colorID, 1); + CSDL_Ext::setColors(border, &oldColor, colorID, 1); } } if(stateTimeCounter < BUILD_ANIMATION_FINISHED_TIMEPOINT) @@ -254,7 +254,7 @@ std::string CBuildingRect::getSubtitle()//hover text for building void CBuildingRect::mouseMoved (const SDL_MouseMotionEvent & sEvent) { - if(area && isItIn(&pos,sEvent.x, sEvent.y)) + if(area && pos.isInside(sEvent.x, sEvent.y)) { if(CSDL_Ext::isTransparent(area, GH.current->motion.x-pos.x, GH.current->motion.y-pos.y)) //hovered pixel is inside this building { diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index c65ae1426..9511a3cc6 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -72,9 +72,9 @@ InfoBox::InfoBox(Point position, InfoPos Pos, InfoSize Size, std::shared_ptrpos; + pos = pos.include(name->pos); if(value) - pos = pos | value->pos; + pos = pos.include(value->pos); hover = std::make_shared(); hover->hoverText = data->getHoverText(); diff --git a/client/windows/CQuestLog.cpp b/client/windows/CQuestLog.cpp index 2295433ba..e3ef8a562 100644 --- a/client/windows/CQuestLog.cpp +++ b/client/windows/CQuestLog.cpp @@ -208,10 +208,10 @@ void CQuestLog::showAll(SDL_Surface * to) if(questIndex >= 0 && questIndex < labels.size()) { //TODO: use child object to selection rect - Rect rect = Rect::around(labels[questIndex]->pos); + Rect rect = Rect::createAround(labels[questIndex]->pos, 1); rect.x -= 2; // Adjustment needed as we want selection box on top of border in graphics rect.w += 2; - CSDL_Ext::drawBorder(to, rect, int3(Colors::METALLIC_GOLD.r, Colors::METALLIC_GOLD.g, Colors::METALLIC_GOLD.b)); + CSDL_Ext::drawBorder(to, rect, Colors::METALLIC_GOLD); } } diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 2bb056406..f18dadaaa 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -42,7 +42,7 @@ #include "../../lib/mapObjects/CGHeroInstance.h" -CSpellWindow::InteractiveArea::InteractiveArea(const SDL_Rect & myRect, std::function funcL, int helpTextId, CSpellWindow * _owner) +CSpellWindow::InteractiveArea::InteractiveArea(const Rect & myRect, std::function funcL, int helpTextId, CSpellWindow * _owner) { addUsedEvents(LCLICK | RCLICK | HOVER); pos = myRect; @@ -186,29 +186,29 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m mana = std::make_shared(435, 426, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, boost::lexical_cast(myHero->mana)); statusBar = CGStatusBar::create(7, 569, "Spelroll.bmp"); - SDL_Rect temp_rect = genRect(45, 35, 479 + pos.x, 405 + pos.y); + Rect temp_rect = CSDL_Ext::genRect(45, 35, 479 + pos.x, 405 + pos.y); interactiveAreas.push_back(std::make_shared(temp_rect, std::bind(&CSpellWindow::fexitb, this), 460, this)); - temp_rect = genRect(45, 35, 221 + pos.x, 405 + pos.y); + temp_rect = CSDL_Ext::genRect(45, 35, 221 + pos.x, 405 + pos.y); interactiveAreas.push_back(std::make_shared(temp_rect, std::bind(&CSpellWindow::fbattleSpellsb, this), 453, this)); - temp_rect = genRect(45, 35, 355 + pos.x, 405 + pos.y); + temp_rect = CSDL_Ext::genRect(45, 35, 355 + pos.x, 405 + pos.y); interactiveAreas.push_back(std::make_shared(temp_rect, std::bind(&CSpellWindow::fadvSpellsb, this), 452, this)); - temp_rect = genRect(45, 35, 418 + pos.x, 405 + pos.y); + temp_rect = CSDL_Ext::genRect(45, 35, 418 + pos.x, 405 + pos.y); interactiveAreas.push_back(std::make_shared(temp_rect, std::bind(&CSpellWindow::fmanaPtsb, this), 459, this)); - temp_rect = genRect(36, 56, 549 + pos.x, 94 + pos.y); + temp_rect = CSDL_Ext::genRect(36, 56, 549 + pos.x, 94 + pos.y); interactiveAreas.push_back(std::make_shared(temp_rect, std::bind(&CSpellWindow::selectSchool, this, 0), 454, this)); - temp_rect = genRect(36, 56, 549 + pos.x, 151 + pos.y); + temp_rect = CSDL_Ext::genRect(36, 56, 549 + pos.x, 151 + pos.y); interactiveAreas.push_back(std::make_shared(temp_rect, std::bind(&CSpellWindow::selectSchool, this, 3), 457, this)); - temp_rect = genRect(36, 56, 549 + pos.x, 210 + pos.y); + temp_rect = CSDL_Ext::genRect(36, 56, 549 + pos.x, 210 + pos.y); interactiveAreas.push_back(std::make_shared(temp_rect, std::bind(&CSpellWindow::selectSchool, this, 1), 455, this)); - temp_rect = genRect(36, 56, 549 + pos.x, 270 + pos.y); + temp_rect = CSDL_Ext::genRect(36, 56, 549 + pos.x, 270 + pos.y); interactiveAreas.push_back(std::make_shared(temp_rect, std::bind(&CSpellWindow::selectSchool, this, 2), 456, this)); - temp_rect = genRect(36, 56, 549 + pos.x, 330 + pos.y); + temp_rect = CSDL_Ext::genRect(36, 56, 549 + pos.x, 330 + pos.y); interactiveAreas.push_back(std::make_shared(temp_rect, std::bind(&CSpellWindow::selectSchool, this, 4), 458, this)); - temp_rect = genRect(leftCorner->bg->h, leftCorner->bg->w, 97 + pos.x, 77 + pos.y); + temp_rect = CSDL_Ext::genRect(leftCorner->bg->h, leftCorner->bg->w, 97 + pos.x, 77 + pos.y); interactiveAreas.push_back(std::make_shared(temp_rect, std::bind(&CSpellWindow::fLcornerb, this), 450, this)); - temp_rect = genRect(rightCorner->bg->h, rightCorner->bg->w, 487 + pos.x, 72 + pos.y); + temp_rect = CSDL_Ext::genRect(rightCorner->bg->h, rightCorner->bg->w, 487 + pos.x, 72 + pos.y); interactiveAreas.push_back(std::make_shared(temp_rect, std::bind(&CSpellWindow::fRcornerb, this), 451, this)); //areas for spells @@ -216,7 +216,7 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m for(int v=0; v<12; ++v) { - temp_rect = genRect(65, 78, xpos, ypos); + temp_rect = CSDL_Ext::genRect(65, 78, xpos, ypos); spellAreas[v] = std::make_shared(temp_rect, this); if(v == 5) //to right page @@ -494,7 +494,7 @@ int CSpellWindow::pagesWithinCurrentTab() return battleSpellsOnly ? sitesPerTabBattle[selectedTab] : sitesPerTabAdv[selectedTab]; } -CSpellWindow::SpellArea::SpellArea(SDL_Rect pos, CSpellWindow * owner) +CSpellWindow::SpellArea::SpellArea(Rect pos, CSpellWindow * owner) { this->pos = pos; this->owner = owner; diff --git a/client/windows/CSpellWindow.h b/client/windows/CSpellWindow.h index b23891da1..b5b0f2774 100644 --- a/client/windows/CSpellWindow.h +++ b/client/windows/CSpellWindow.h @@ -19,7 +19,6 @@ class CSpell; VCMI_LIB_NAMESPACE_END struct SDL_Surface; -struct SDL_Rect; class IImage; class CAnimImage; class CPicture; @@ -42,7 +41,7 @@ class CSpellWindow : public CWindowObject std::shared_ptr level; std::shared_ptr cost; public: - SpellArea(SDL_Rect pos, CSpellWindow * owner); + SpellArea(Rect pos, CSpellWindow * owner); ~SpellArea(); void setSpell(const CSpell * spell); @@ -63,7 +62,7 @@ class CSpellWindow : public CWindowObject void clickRight(tribool down, bool previousState) override; void hover(bool on) override; - InteractiveArea(const SDL_Rect & myRect, std::function funcL, int helpTextId, CSpellWindow * _owner); + InteractiveArea(const Rect &myRect, std::function funcL, int helpTextId, CSpellWindow * _owner); }; std::shared_ptr spellIcons; diff --git a/client/windows/CTradeWindow.cpp b/client/windows/CTradeWindow.cpp index 5f6e0903c..4f5bc7ffc 100644 --- a/client/windows/CTradeWindow.cpp +++ b/client/windows/CTradeWindow.cpp @@ -501,9 +501,9 @@ void CTradeWindow::getPositionsFor(std::vector &poss, bool Left, EType typ const std::vector tmp = { - genRect(h, w, x, y), genRect(h, w, x + dx, y), genRect(h, w, x + 2*dx, y), - genRect(h, w, x, y + dy), genRect(h, w, x + dx, y + dy), genRect(h, w, x + 2*dx, y + dy), - genRect(h, w, x + dx, y + 2*dy) + CSDL_Ext::genRect(h, w, x, y), CSDL_Ext::genRect(h, w, x + dx, y), CSDL_Ext::genRect(h, w, x + 2*dx, y), + CSDL_Ext::genRect(h, w, x, y + dy), CSDL_Ext::genRect(h, w, x + dx, y + dy), CSDL_Ext::genRect(h, w, x + 2*dx, y + dy), + CSDL_Ext::genRect(h, w, x + dx, y + 2*dy) }; vstd::concatenate(poss, tmp); @@ -564,9 +564,9 @@ void CTradeWindow::showAll(SDL_Surface * to) CWindowObject::showAll(to); if(hRight) - CSDL_Ext::drawBorder(to, hRight->pos.x-1, hRight->pos.y-1, hRight->pos.w+2, hRight->pos.h+2, int3(255,231,148)); + CSDL_Ext::drawBorder(to, hRight->pos.x-1, hRight->pos.y-1, hRight->pos.w+2, hRight->pos.h+2, Colors::BRIGHT_YELLOW); if(hLeft && hLeft->type != ARTIFACT_INSTANCE) - CSDL_Ext::drawBorder(to, hLeft->pos.x-1, hLeft->pos.y-1, hLeft->pos.w+2, hLeft->pos.h+2, int3(255,231,148)); + CSDL_Ext::drawBorder(to, hLeft->pos.x-1, hLeft->pos.y-1, hLeft->pos.w+2, hLeft->pos.h+2, Colors::BRIGHT_YELLOW); if(readyToTrade) { diff --git a/client/windows/CWindowObject.cpp b/client/windows/CWindowObject.cpp index 6527f555a..a4c801fba 100644 --- a/client/windows/CWindowObject.cpp +++ b/client/windows/CWindowObject.cpp @@ -170,12 +170,10 @@ void CWindowObject::setShadow(bool on) shadowBottomTempl = CSDL_Ext::createSurfaceWithBpp<4>(1, size); shadowRightTempl = CSDL_Ext::createSurfaceWithBpp<4>(size, 1); - Uint32 shadowColor = SDL_MapRGBA(shadowCornerTempl->format, 0, 0, 0, 192); - //fill with shadow body color - SDL_FillRect(shadowCornerTempl, nullptr, shadowColor); - SDL_FillRect(shadowBottomTempl, nullptr, shadowColor); - SDL_FillRect(shadowRightTempl, nullptr, shadowColor); + CSDL_Ext::fillSurface(shadowCornerTempl, { 0, 0, 0, 192 } ); + CSDL_Ext::fillSurface(shadowBottomTempl, { 0, 0, 0, 192 } ); + CSDL_Ext::fillSurface(shadowRightTempl, { 0, 0, 0, 192 } ); //fill last row and column with more transparent color blitAlphaCol(shadowRightTempl , size-1); diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 15a8f68db..72fed07f5 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -102,9 +102,9 @@ void CRecruitmentWindow::CCreatureCard::showAll(SDL_Surface * to) { CIntObject::showAll(to); if(selected) - drawBorder(to, pos, int3(248, 0, 0)); + drawBorder(to, pos, Colors::RED); else - drawBorder(to, pos, int3(232, 212, 120)); + drawBorder(to, pos, Colors::YELLOW); } void CRecruitmentWindow::select(std::shared_ptr card) @@ -179,17 +179,17 @@ void CRecruitmentWindow::showAll(SDL_Surface * to) CWindowObject::showAll(to); // recruit\total values - drawBorder(to, pos.x + 172, pos.y + 222, 67, 42, int3(239,215,123)); - drawBorder(to, pos.x + 246, pos.y + 222, 67, 42, int3(239,215,123)); + drawBorder(to, pos.x + 172, pos.y + 222, 67, 42, Colors::YELLOW); + drawBorder(to, pos.x + 246, pos.y + 222, 67, 42, Colors::YELLOW); //cost boxes - drawBorder(to, pos.x + 64, pos.y + 222, 99, 76, int3(239,215,123)); - drawBorder(to, pos.x + 322, pos.y + 222, 99, 76, int3(239,215,123)); + drawBorder(to, pos.x + 64, pos.y + 222, 99, 76, Colors::YELLOW); + drawBorder(to, pos.x + 322, pos.y + 222, 99, 76, Colors::YELLOW); //buttons borders - drawBorder(to, pos.x + 133, pos.y + 312, 66, 34, int3(173,142,66)); - drawBorder(to, pos.x + 211, pos.y + 312, 66, 34, int3(173,142,66)); - drawBorder(to, pos.x + 289, pos.y + 312, 66, 34, int3(173,142,66)); + drawBorder(to, pos.x + 133, pos.y + 312, 66, 34, Colors::METALLIC_GOLD); + drawBorder(to, pos.x + 211, pos.y + 312, 66, 34, Colors::METALLIC_GOLD); + drawBorder(to, pos.x + 289, pos.y + 312, 66, 34, Colors::METALLIC_GOLD); } CRecruitmentWindow::CRecruitmentWindow(const CGDwelling * Dwelling, int Level, const CArmedInstance * Dst, const std::function & Recruit, int y_offset): @@ -747,7 +747,7 @@ void CTavernWindow::show(SDL_Surface * to) } printAtMiddleWBLoc(sel->description, 146, 395, FONT_SMALL, 200, Colors::WHITE, to); - CSDL_Ext::drawBorder(to,sel->pos.x-2,sel->pos.y-2,sel->pos.w+4,sel->pos.h+4,int3(247,223,123)); + CSDL_Ext::drawBorder(to,sel->pos.x-2,sel->pos.y-2,sel->pos.w+4,sel->pos.h+4,Colors::BRIGHT_YELLOW); } } @@ -1383,7 +1383,7 @@ void CPuzzleWindow::showAll(SDL_Surface * to) Rect mapRect = genRect(544, 591, pos.x + 8, pos.y + 7); int3 topTile = grailPos - moveInt; - MapDrawingInfo info(topTile, LOCPLINT->cb->getVisibilityMap(), &mapRect); + MapDrawingInfo info(topTile, LOCPLINT->cb->getVisibilityMap(), mapRect); info.puzzleMode = true; info.grailPos = grailPos; CGI->mh->drawTerrainRectNew(to, &info); diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index 0ce7d8314..a9436a5ee 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -13,6 +13,7 @@ #include "../lib/GameConstants.h" #include "../lib/ResourceSet.h" #include "../lib/CConfigHandler.h" +#include "../lib/int3.h" #include "../widgets/CArtifactHolder.h" #include "../widgets/CGarrisonInt.h" #include "../widgets/Images.h" diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index a793d66c2..18cecd486 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -42,7 +42,7 @@ void CSimpleWindow::show(SDL_Surface * to) { if(bitmap) - blitAt(bitmap,pos.x,pos.y,to); + CSDL_Ext::blitAt(bitmap,pos.x,pos.y,to); } CSimpleWindow::~CSimpleWindow() { @@ -262,7 +262,7 @@ void CInfoPopup::close() void CInfoPopup::show(SDL_Surface * to) { - blitAt(bitmap,pos.x,pos.y,to); + CSDL_Ext::blitAt(bitmap,pos.x,pos.y,to); } CInfoPopup::~CInfoPopup() @@ -306,7 +306,7 @@ void CRClickPopup::createAndPush(const std::string &txt, const CInfoWindow::TCom player = PlayerColor(1); auto temp = std::make_shared(txt, player, comps); - temp->center(Point(GH.current->motion)); //center on mouse + temp->center(Geometry::fromSDL(GH.current->motion)); //center on mouse #ifdef VCMI_IOS // TODO: enable also for android? temp->moveBy({0, -temp->pos.h / 2}); diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h index 6e00a53cf..b73af37e3 100644 --- a/client/windows/InfoWindows.h +++ b/client/windows/InfoWindows.h @@ -19,7 +19,7 @@ class CGGarrison; VCMI_LIB_NAMESPACE_END struct SDL_Surface; -struct Rect; +class Rect; class CAnimImage; class CLabel; class CAnimation; diff --git a/lib/Rect.h b/lib/Rect.h index 566d1dde6..495a68ac8 100644 --- a/lib/Rect.h +++ b/lib/Rect.h @@ -46,15 +46,15 @@ public: DLL_LINKAGE static Rect createCentered( const Rect & target, const Point & size ); DLL_LINKAGE static Rect createAround(const Rect &r, int borderWidth); - bool isIn(int qx, int qy) const + bool isInside(int qx, int qy) const { if (qx > x && qxy && qy Date: Tue, 17 Jan 2023 22:18:34 +0200 Subject: [PATCH 191/197] Bugfixing --- client/battle/BattleInterfaceClasses.cpp | 2 +- lib/CTownHandler.cpp | 4 +-- lib/CTownHandler.h | 13 +--------- lib/Rect.cpp | 33 ++++++++++++------------ lib/Rect.h | 2 ++ 5 files changed, 22 insertions(+), 32 deletions(-) diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index bfcd29095..599d3c5ca 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -308,7 +308,7 @@ void BattleHero::clickRight(tribool down, bool previousState) return; Point windowPosition; - windowPosition.x = (!defender) ? owner.fieldController->pos.topLeft().x + 1 : owner.fieldController->pos.topRight().x - 79; + windowPosition.x = (!defender) ? owner.fieldController->pos.left() + 1 : owner.fieldController->pos.right() - 79; windowPosition.y = owner.fieldController->pos.y + 135; InfoAboutHero targetHero; diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 4e312c948..3b4a4271c 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -776,9 +776,9 @@ void CTownHandler::loadTownHall(CTown &town, const JsonNode & source) } } -CTown::ClientInfo::Point JsonToPoint(const JsonNode & node) +Point JsonToPoint(const JsonNode & node) { - CTown::ClientInfo::Point ret; + Point ret; ret.x = static_cast(node["x"].Float()); ret.y = static_cast(node["y"].Float()); return ret; diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index aadb1f42a..c89d6daae 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -20,6 +20,7 @@ #include "LogicalExpression.h" #include "battle/BattleHex.h" #include "HeroBonus.h" +#include "Point.h" VCMI_LIB_NAMESPACE_BEGIN @@ -279,18 +280,6 @@ public: // Client-only data. Should be moved away from lib struct ClientInfo { - struct Point - { - si32 x; - si32 y; - - template void serialize(Handler &h, const int version) - { - h & x; - h & y; - } - }; - //icons [fort is present?][build limit reached?] -> index of icon in def files int icons[2][2]; std::string iconSmall[2][2]; /// icon names used during loading diff --git a/lib/Rect.cpp b/lib/Rect.cpp index a19a3a420..1fcd3ec90 100644 --- a/lib/Rect.cpp +++ b/lib/Rect.cpp @@ -17,17 +17,16 @@ VCMI_LIB_NAMESPACE_BEGIN Rect Rect::include(const Rect & other) const { Point topLeft{ - std::min(this->topLeft().x, other.topLeft().x), - std::min(this->topLeft().y, other.topLeft().y) + std::min(this->left(), other.left()), + std::min(this->top(), other.top()) }; Point bottomRight{ - std::max(this->bottomRight().x, other.bottomRight().x), - std::max(this->bottomRight().y, other.bottomRight().y) + std::max(this->right(), other.right()), + std::max(this->bottom(), other.bottom()) }; return Rect(topLeft, bottomRight - topLeft); - } Rect Rect::createCentered( const Point & around, const Point & dimensions ) @@ -48,19 +47,19 @@ Rect Rect::createCentered( const Rect & rect, const Point & dimensions) bool Rect::intersectionTest(const Rect & other) const { // this rect is above other rect - if(this->bottomLeft().y < other.topLeft().y) + if(this->bottom() < other.top()) return false; // this rect is below other rect - if(this->topLeft().y > other.bottomLeft().y ) + if(this->top() > other.bottom() ) return false; // this rect is to the left of other rect - if(this->topRight().x < other.topLeft().x) + if(this->right() < other.left()) return false; // this rect is to the right of other rect - if(this->topLeft().x > other.topRight().x) + if(this->left() > other.right()) return false; return true; @@ -72,19 +71,19 @@ bool Rect::intersectionTest(const Rect & other) const bool Rect::intersectionTest(const Point & line1, const Point & line2) const { // check whether segment is located to the left of our rect - if (line1.x < topLeft().x && line2.x < topLeft().x) + if (line1.x < left() && line2.x < left()) return false; // check whether segment is located to the right of our rect - if (line1.x > bottomRight().x && line2.x > bottomRight().x) + if (line1.x > right() && line2.x > right()) return false; // check whether segment is located on top of our rect - if (line1.y < topLeft().y && line2.y < topLeft().y) + if (line1.y < top() && line2.y < top()) return false; // check whether segment is located below of our rect - if (line1.y > bottomRight().y && line2.y > bottomRight().y) + if (line1.y > bottom() && line2.y > bottom()) return false; Point vector { line2.x - line1.x, line2.y - line1.y}; @@ -113,13 +112,13 @@ Rect Rect::intersect(const Rect & other) const if(intersectionTest(other)) { Point topLeft{ - std::max(this->topLeft().x, other.topLeft().x), - std::max(this->topLeft().y, other.topLeft().y) + std::max(this->left(), other.left()), + std::max(this->top(), other.top()) }; Point bottomRight{ - std::min(this->bottomRight().x, other.bottomRight().x), - std::min(this->bottomRight().y, other.bottomRight().y) + std::min(this->right(), other.right()), + std::min(this->bottom(), other.bottom()) }; return Rect(topLeft, bottomRight - topLeft); diff --git a/lib/Rect.h b/lib/Rect.h index 495a68ac8..a27a7ff63 100644 --- a/lib/Rect.h +++ b/lib/Rect.h @@ -149,6 +149,8 @@ public: { h & x; h & y; + h & w; + h & h; } }; From 6e2d09d605f5568e7eb9e43ea45ad0ead8852477 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 18 Jan 2023 02:32:08 +0200 Subject: [PATCH 192/197] Cleanup --- client/gui/CCursorHandler.cpp | 316 ---------------------------------- client/gui/Geometries.cpp | 1 - lib/Point.h | 2 +- lib/Rect.cpp | 2 +- lib/Rect.h | 2 +- 5 files changed, 3 insertions(+), 320 deletions(-) delete mode 100644 client/gui/CCursorHandler.cpp diff --git a/client/gui/CCursorHandler.cpp b/client/gui/CCursorHandler.cpp deleted file mode 100644 index 6d475d735..000000000 --- a/client/gui/CCursorHandler.cpp +++ /dev/null @@ -1,316 +0,0 @@ -/* - * CCursorHandler.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "CCursorHandler.h" - -#include - -#include "SDL_Extensions.h" -#include "CGuiHandler.h" -#include "../widgets/Images.h" - -#include "../CMT.h" - -void CCursorHandler::clearBuffer() -{ - CSDL_Ext::fillSurface(buffer, Colors::TRANSPARENT ); -} - -void CCursorHandler::updateBuffer(CIntObject * payload) -{ - payload->moveTo(Point(0,0)); - payload->showAll(buffer); - - needUpdate = true; -} - -void CCursorHandler::replaceBuffer(CIntObject * payload) -{ - clearBuffer(); - updateBuffer(payload); -} - -CCursorHandler::CCursorHandler() - : needUpdate(true) - , buffer(nullptr) - , cursorLayer(nullptr) - , frameTime(0.f) - , showing(false) -{ - cursorLayer = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 40, 40); - SDL_SetTextureBlendMode(cursorLayer, SDL_BLENDMODE_BLEND); - - xpos = ypos = 0; - type = Cursor::Type::DEFAULT; - dndObject = nullptr; - - cursors = - { - std::make_unique("CRADVNTR", 0), - std::make_unique("CRCOMBAT", 0), - std::make_unique("CRDEFLT", 0), - std::make_unique("CRSPELL", 0) - }; - - currentCursor = cursors.at(static_cast(Cursor::Type::DEFAULT)).get(); - - buffer = CSDL_Ext::newSurface(40,40); - - SDL_SetSurfaceBlendMode(buffer, SDL_BLENDMODE_NONE); - SDL_ShowCursor(SDL_DISABLE); - - set(Cursor::Map::POINTER); -} - -Point CCursorHandler::position() const -{ - return Point(xpos, ypos); -} - -void CCursorHandler::changeGraphic(Cursor::Type type, size_t index) -{ - assert(dndObject == nullptr); - - if(type != this->type) - { - this->type = type; - this->frame = index; - currentCursor = cursors.at(static_cast(type)).get(); - currentCursor->setFrame(index); - } - else if(index != this->frame) - { - this->frame = index; - currentCursor->setFrame(index); - } - - replaceBuffer(currentCursor); -} - -void CCursorHandler::set(Cursor::Default index) -{ - changeGraphic(Cursor::Type::DEFAULT, static_cast(index)); -} - -void CCursorHandler::set(Cursor::Map index) -{ - changeGraphic(Cursor::Type::ADVENTURE, static_cast(index)); -} - -void CCursorHandler::set(Cursor::Combat index) -{ - changeGraphic(Cursor::Type::COMBAT, static_cast(index)); -} - -void CCursorHandler::set(Cursor::Spellcast index) -{ - //Note: this is animated cursor, ignore specified frame and only change type - changeGraphic(Cursor::Type::SPELLBOOK, frame); -} - -void CCursorHandler::dragAndDropCursor(std::unique_ptr object) -{ - dndObject = std::move(object); - if(dndObject) - replaceBuffer(dndObject.get()); - else - replaceBuffer(currentCursor); -} - -void CCursorHandler::cursorMove(const int & x, const int & y) -{ - xpos = x; - ypos = y; -} - -void CCursorHandler::shiftPos( int &x, int &y ) -{ - if(( type == Cursor::Type::COMBAT && frame != static_cast(Cursor::Combat::POINTER)) || type == Cursor::Type::SPELLBOOK) - { - x-=16; - y-=16; - - // Properly align the melee attack cursors. - if (type == Cursor::Type::COMBAT) - { - switch (static_cast(frame)) - { - case Cursor::Combat::HIT_NORTHEAST: - x -= 6; - y += 16; - break; - case Cursor::Combat::HIT_EAST: - x -= 16; - y += 10; - break; - case Cursor::Combat::HIT_SOUTHEAST: - x -= 6; - y -= 6; - break; - case Cursor::Combat::HIT_SOUTHWEST: - x += 16; - y -= 6; - break; - case Cursor::Combat::HIT_WEST: - x += 16; - y += 11; - break; - case Cursor::Combat::HIT_NORTHWEST: - x += 16; - y += 16; - break; - case Cursor::Combat::HIT_NORTH: - x += 9; - y += 16; - break; - case Cursor::Combat::HIT_SOUTH: - x += 9; - y -= 15; - break; - } - } - } - else if(type == Cursor::Type::ADVENTURE) - { - if (frame == 0) - { - //no-op - } - else if(frame == 2) - { - x -= 12; - y -= 10; - } - else if(frame == 3) - { - x -= 12; - y -= 12; - } - else if(frame < 27) - { - int hlpNum = (frame - 4)%6; - if(hlpNum == 0) - { - x -= 15; - y -= 13; - } - else if(hlpNum == 1) - { - x -= 13; - y -= 13; - } - else if(hlpNum == 2) - { - x -= 20; - y -= 20; - } - else if(hlpNum == 3) - { - x -= 13; - y -= 16; - } - else if(hlpNum == 4) - { - x -= 8; - y -= 9; - } - else if(hlpNum == 5) - { - x -= 14; - y -= 16; - } - } - else if(frame == 41) - { - x -= 14; - y -= 16; - } - else if(frame < 31 || frame == 42) - { - x -= 20; - y -= 20; - } - } -} - -void CCursorHandler::centerCursor() -{ - this->xpos = static_cast((screen->w / 2.) - (currentCursor->pos.w / 2.)); - this->ypos = static_cast((screen->h / 2.) - (currentCursor->pos.h / 2.)); - SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); - CSDL_Ext::warpMouse(this->xpos, this->ypos); - SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE); -} - -void CCursorHandler::render() -{ - if(!showing) - return; - - if (type == Cursor::Type::SPELLBOOK) - { - static const float frameDisplayDuration = 0.1f; - - frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f; - size_t newFrame = frame; - - while (frameTime > frameDisplayDuration) - { - frameTime -= frameDisplayDuration; - newFrame++; - } - - auto & animation = cursors.at(static_cast(type)); - - while (newFrame > animation->size()) - newFrame -= animation->size(); - - changeGraphic(Cursor::Type::SPELLBOOK, newFrame); - } - - //the must update texture in the main (renderer) thread, but changes to cursor type may come from other threads - updateTexture(); - - int x = xpos; - int y = ypos; - shiftPos(x, y); - - if(dndObject) - { - x -= dndObject->pos.w/2; - y -= dndObject->pos.h/2; - } - - SDL_Rect destRect; - destRect.x = x; - destRect.y = y; - destRect.w = 40; - destRect.h = 40; - - SDL_RenderCopy(mainRenderer, cursorLayer, nullptr, &destRect); -} - -void CCursorHandler::updateTexture() -{ - if(needUpdate) - { - SDL_UpdateTexture(cursorLayer, nullptr, buffer->pixels, buffer->pitch); - needUpdate = false; - } -} - -CCursorHandler::~CCursorHandler() -{ - if(buffer) - SDL_FreeSurface(buffer); - - if(cursorLayer) - SDL_DestroyTexture(cursorLayer); -} diff --git a/client/gui/Geometries.cpp b/client/gui/Geometries.cpp index a6b9250b3..530cbebe4 100644 --- a/client/gui/Geometries.cpp +++ b/client/gui/Geometries.cpp @@ -1,5 +1,4 @@ /* - * * Geometries.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder diff --git a/lib/Point.h b/lib/Point.h index a4794983c..f58ffac73 100644 --- a/lib/Point.h +++ b/lib/Point.h @@ -1,5 +1,5 @@ /* - * Geometries.h, part of VCMI engine + * Point.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/Rect.cpp b/lib/Rect.cpp index 1fcd3ec90..28d164edc 100644 --- a/lib/Rect.cpp +++ b/lib/Rect.cpp @@ -1,5 +1,5 @@ /* - * ResourceSet.cpp, part of VCMI engine + * Rect.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/Rect.h b/lib/Rect.h index a27a7ff63..1b6d3eb7a 100644 --- a/lib/Rect.h +++ b/lib/Rect.h @@ -1,5 +1,5 @@ /* - * Geometries.h, part of VCMI engine + * Rect.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * From 9be38780cd6357748c8d2976660f4c251623aaec Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 18 Jan 2023 15:50:52 +0200 Subject: [PATCH 193/197] Removed remaining parts of Geomeotries.h/cpp --- client/CMakeLists.txt | 3 +- client/CMessage.h | 1 - client/CVideoHandler.cpp | 2 +- client/Graphics.h | 1 - client/battle/BattleAnimationClasses.h | 1 - client/battle/BattleEffectsController.h | 2 +- client/battle/BattleProjectileController.cpp | 1 - client/battle/BattleProjectileController.h | 3 +- client/battle/BattleStacksController.h | 2 +- client/gui/CAnimation.h | 3 +- client/gui/CGuiHandler.h | 2 +- client/gui/CIntObject.h | 3 +- client/gui/Canvas.cpp | 1 - client/gui/Canvas.h | 3 +- client/gui/CursorHandler.h | 2 +- client/gui/Geometries.cpp | 35 -------------- client/gui/Geometries.h | 42 ---------------- client/gui/InterfaceObjectConfigurable.h | 1 + client/gui/SDL_Extensions.cpp | 51 ++++++++++++++++---- client/gui/SDL_Extensions.h | 22 +++++---- client/gui/TextAlignment.h | 12 +++++ client/mapHandler.h | 2 +- client/widgets/AdventureMapClasses.cpp | 4 +- client/widgets/TextControls.h | 1 + client/windows/CAdvmapInterface.cpp | 2 +- client/windows/InfoWindows.cpp | 2 +- client/windows/InfoWindows.h | 1 + 27 files changed, 86 insertions(+), 119 deletions(-) delete mode 100644 client/gui/Geometries.cpp delete mode 100644 client/gui/Geometries.h create mode 100644 client/gui/TextAlignment.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 668698873..ea7c16cd8 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -23,7 +23,6 @@ set(client_SRCS gui/CIntObject.cpp gui/ColorFilter.cpp gui/Fonts.cpp - gui/Geometries.cpp gui/SDL_Extensions.cpp gui/NotificationHandler.cpp gui/InterfaceObjectConfigurable.cpp @@ -110,7 +109,7 @@ set(client_HEADERS gui/ColorFilter.h gui/CIntObject.h gui/Fonts.h - gui/Geometries.h + gui/TextAlignment.h gui/SDL_Compat.h gui/SDL_Extensions.h gui/SDL_Pixels.h diff --git a/client/CMessage.h b/client/CMessage.h index d407eadb2..8eac68881 100644 --- a/client/CMessage.h +++ b/client/CMessage.h @@ -10,7 +10,6 @@ #pragma once #include "Graphics.h" -#include "gui/Geometries.h" struct SDL_Surface; class CInfoWindow; diff --git a/client/CVideoHandler.cpp b/client/CVideoHandler.cpp index c9464d1dc..46514e598 100644 --- a/client/CVideoHandler.cpp +++ b/client/CVideoHandler.cpp @@ -442,7 +442,7 @@ bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey) if(stopOnKey && keyDown()) return false; - SDL_Rect rect = Geometry::toSDL(pos); + SDL_Rect rect = CSDL_Ext::toSDL(pos); SDL_RenderCopy(mainRenderer, texture, nullptr, &rect); SDL_RenderPresent(mainRenderer); diff --git a/client/Graphics.h b/client/Graphics.h index 947ffddc6..0b539d396 100644 --- a/client/Graphics.h +++ b/client/Graphics.h @@ -11,7 +11,6 @@ #include "gui/Fonts.h" #include "../lib/GameConstants.h" -#include "gui/Geometries.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/client/battle/BattleAnimationClasses.h b/client/battle/BattleAnimationClasses.h index 867234977..580e1b7c1 100644 --- a/client/battle/BattleAnimationClasses.h +++ b/client/battle/BattleAnimationClasses.h @@ -10,7 +10,6 @@ #pragma once #include "../../lib/battle/BattleHex.h" -#include "../gui/Geometries.h" #include "BattleConstants.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/client/battle/BattleEffectsController.h b/client/battle/BattleEffectsController.h index cb7a1bf30..49af24316 100644 --- a/client/battle/BattleEffectsController.h +++ b/client/battle/BattleEffectsController.h @@ -10,7 +10,7 @@ #pragma once #include "../../lib/battle/BattleHex.h" -#include "../gui/Geometries.h" +#include "../../lib/Point.h" #include "BattleConstants.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/client/battle/BattleProjectileController.cpp b/client/battle/BattleProjectileController.cpp index f9862fa7e..429d11fc1 100644 --- a/client/battle/BattleProjectileController.cpp +++ b/client/battle/BattleProjectileController.cpp @@ -15,7 +15,6 @@ #include "BattleStacksController.h" #include "CreatureAnimation.h" -#include "../gui/Geometries.h" #include "../gui/CAnimation.h" #include "../gui/Canvas.h" #include "../gui/CGuiHandler.h" diff --git a/client/battle/BattleProjectileController.h b/client/battle/BattleProjectileController.h index 10b201b2b..102a99f09 100644 --- a/client/battle/BattleProjectileController.h +++ b/client/battle/BattleProjectileController.h @@ -10,7 +10,7 @@ #pragma once #include "../../lib/CCreatureHandler.h" -#include "../gui/Geometries.h" +#include "../../lib/Point.h" VCMI_LIB_NAMESPACE_BEGIN @@ -19,7 +19,6 @@ class CSpell; VCMI_LIB_NAMESPACE_END -class Point; class CAnimation; class Canvas; class BattleInterface; diff --git a/client/battle/BattleStacksController.h b/client/battle/BattleStacksController.h index 0e3113c25..31d859c73 100644 --- a/client/battle/BattleStacksController.h +++ b/client/battle/BattleStacksController.h @@ -9,7 +9,6 @@ */ #pragma once -#include "../gui/Geometries.h" #include "../gui/ColorFilter.h" VCMI_LIB_NAMESPACE_BEGIN @@ -33,6 +32,7 @@ class CreatureAnimation; class BattleAnimation; class BattleRenderer; class IImage; +class Point; struct BattleStackFilterEffect { diff --git a/client/gui/CAnimation.h b/client/gui/CAnimation.h index add6a3f72..d32ab424e 100644 --- a/client/gui/CAnimation.h +++ b/client/gui/CAnimation.h @@ -10,7 +10,6 @@ #pragma once #include "../../lib/vcmi_endian.h" -#include "Geometries.h" #include "../../lib/GameConstants.h" #ifdef IN @@ -31,6 +30,8 @@ struct SDL_Surface; struct SDL_Color; class CDefFile; class ColorFilter; +class Rect; +class Point; /* * Base class for images, can be used for non-animation pictures as well diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h index e2c0385fb..38f540185 100644 --- a/client/gui/CGuiHandler.h +++ b/client/gui/CGuiHandler.h @@ -9,7 +9,7 @@ */ #pragma once -#include "Geometries.h" +#include "../../lib/Point.h" #include diff --git a/client/gui/CIntObject.h b/client/gui/CIntObject.h index 8ec51eb5f..e4836c544 100644 --- a/client/gui/CIntObject.h +++ b/client/gui/CIntObject.h @@ -9,7 +9,7 @@ */ #pragma once -#include "Geometries.h" +#include "../../lib/Rect.h" #include "../Graphics.h" struct SDL_Surface; @@ -19,6 +19,7 @@ class CPicture; struct SDL_KeyboardEvent; struct SDL_TextInputEvent; struct SDL_TextEditingEvent; +struct SDL_MouseMotionEvent; using boost::logic::tribool; diff --git a/client/gui/Canvas.cpp b/client/gui/Canvas.cpp index c35a9de48..adc443924 100644 --- a/client/gui/Canvas.cpp +++ b/client/gui/Canvas.cpp @@ -11,7 +11,6 @@ #include "Canvas.h" #include "SDL_Extensions.h" -#include "Geometries.h" #include "CAnimation.h" #include "../Graphics.h" diff --git a/client/gui/Canvas.h b/client/gui/Canvas.h index 80d16040e..d4fcc1619 100644 --- a/client/gui/Canvas.h +++ b/client/gui/Canvas.h @@ -9,7 +9,8 @@ */ #pragma once -#include "Geometries.h" +#include "TextAlignment.h" +#include "../../lib/Rect.h" struct SDL_Color; struct SDL_Surface; diff --git a/client/gui/CursorHandler.h b/client/gui/CursorHandler.h index c769da048..b66bfb92b 100644 --- a/client/gui/CursorHandler.h +++ b/client/gui/CursorHandler.h @@ -15,7 +15,7 @@ struct SDL_Surface; struct SDL_Texture; struct SDL_Cursor; -#include "Geometries.h" +#include "../../lib/Point.h" namespace Cursor { diff --git a/client/gui/Geometries.cpp b/client/gui/Geometries.cpp deleted file mode 100644 index 530cbebe4..000000000 --- a/client/gui/Geometries.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Geometries.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#include "StdInc.h" -#include "Geometries.h" - -#include -#include - -Rect Geometry::fromSDL(const SDL_Rect & rect) -{ - return Rect(Point(rect.x, rect.y), Point(rect.w, rect.h)); -} - -SDL_Rect Geometry::toSDL(const Rect & rect) -{ - SDL_Rect result; - result.x = rect.x; - result.y = rect.y; - result.w = rect.w; - result.h = rect.h; - return result; -} - -Point Geometry::fromSDL(const SDL_MouseMotionEvent & motion) -{ - return { motion.x, motion.y }; -} diff --git a/client/gui/Geometries.h b/client/gui/Geometries.h deleted file mode 100644 index e2d60050a..000000000 --- a/client/gui/Geometries.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Geometries.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -struct SDL_MouseMotionEvent; -struct SDL_Rect; - -#include "../../lib/Point.h" -#include "../../lib/Rect.h" - -VCMI_LIB_NAMESPACE_BEGIN -class int3; -VCMI_LIB_NAMESPACE_END - -#ifdef VCMI_LIB_NAMESPACE -using VCMI_LIB_WRAP_NAMESPACE(Rect); -using VCMI_LIB_WRAP_NAMESPACE(Point); -#endif - -enum class ETextAlignment {TOPLEFT, CENTER, BOTTOMRIGHT}; - -namespace Geometry -{ - -/// creates Point using provided event -Point fromSDL(const SDL_MouseMotionEvent & motion); - -/// creates Rect using provided rect -Rect fromSDL(const SDL_Rect & rect); - -/// creates SDL_Rect using provided rect -SDL_Rect toSDL(const Rect & rect); - -} - diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h index ccdb11efa..7506592fc 100644 --- a/client/gui/InterfaceObjectConfigurable.h +++ b/client/gui/InterfaceObjectConfigurable.h @@ -11,6 +11,7 @@ #pragma once #include "CIntObject.h" +#include "TextAlignment.h" #include "../../lib/JsonNode.h" diff --git a/client/gui/SDL_Extensions.cpp b/client/gui/SDL_Extensions.cpp index 7fe62b4e3..b2ef89c3c 100644 --- a/client/gui/SDL_Extensions.cpp +++ b/client/gui/SDL_Extensions.cpp @@ -41,6 +41,37 @@ const SDL_Color Colors::PURPLE = {255, 75, 125, SDL_ALPHA_OPAQUE}; const SDL_Color Colors::BLACK = {0, 0, 0, SDL_ALPHA_OPAQUE}; const SDL_Color Colors::TRANSPARENT = {0, 0, 0, SDL_ALPHA_TRANSPARENT}; +Rect CSDL_Ext::genRect(const int & hh, const int & ww, const int & xx, const int & yy) +{ + Rect ret; + ret.h=hh; + ret.w=ww; + ret.x=xx; + ret.y=yy; + return ret; +} + +Rect CSDL_Ext::fromSDL(const SDL_Rect & rect) +{ + return Rect(Point(rect.x, rect.y), Point(rect.w, rect.h)); +} + +SDL_Rect CSDL_Ext::toSDL(const Rect & rect) +{ + SDL_Rect result; + result.x = rect.x; + result.y = rect.y; + result.w = rect.w; + result.h = rect.h; + return result; +} + +Point CSDL_Ext::fromSDL(const SDL_MouseMotionEvent & motion) +{ + return { motion.x, motion.y }; +} + + void CSDL_Ext::setColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors) { SDL_SetPaletteColors(surface->format->palette,colors,firstcolor,ncolors); @@ -73,7 +104,7 @@ void CSDL_Ext::setAlpha(SDL_Surface * bg, int value) void CSDL_Ext::updateRect(SDL_Surface *surface, const Rect & rect ) { - SDL_Rect rectSDL = Geometry::toSDL(rect); + SDL_Rect rectSDL = CSDL_Ext::toSDL(rect); if(0 !=SDL_UpdateTexture(screenTexture, &rectSDL, surface->pixels, surface->pitch)) logGlobal->error("%sSDL_UpdateTexture %s", __FUNCTION__, SDL_GetError()); @@ -220,8 +251,8 @@ Uint32 CSDL_Ext::getPixel(SDL_Surface *surface, const int & x, const int & y, bo template int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPointInput) { - SDL_Rect srcRectInstance = Geometry::toSDL(srcRectInput); - SDL_Rect dstRectInstance = Geometry::toSDL(Rect(dstPointInput, srcRectInput.dimensions())); + SDL_Rect srcRectInstance = CSDL_Ext::toSDL(srcRectInput); + SDL_Rect dstRectInstance = CSDL_Ext::toSDL(Rect(dstPointInput, srcRectInput.dimensions())); SDL_Rect * srcRect =&srcRectInstance; SDL_Rect * dstRect =&dstRectInstance; @@ -803,8 +834,8 @@ SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface *surf, int width, int height) void CSDL_Ext::blitSurface(SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPoint) { - SDL_Rect srcRect = Geometry::toSDL(srcRectInput); - SDL_Rect dstRect = Geometry::toSDL(Rect(dstPoint, srcRectInput.dimensions())); + SDL_Rect srcRect = CSDL_Ext::toSDL(srcRectInput); + SDL_Rect dstRect = CSDL_Ext::toSDL(Rect(dstPoint, srcRectInput.dimensions())); SDL_UpperBlit(src, &srcRect, dst, &dstRect); } @@ -825,7 +856,7 @@ void CSDL_Ext::fillSurface( SDL_Surface *dst, const SDL_Color & color ) void CSDL_Ext::fillRect( SDL_Surface *dst, const Rect & dstrect, const SDL_Color & color ) { - SDL_Rect newRect = Geometry::toSDL(dstrect); + SDL_Rect newRect = CSDL_Ext::toSDL(dstrect); uint32_t sdlColor = SDL_MapRGBA(dst->format, color.r, color.g, color.b, color.a); SDL_FillRect(dst, &newRect, sdlColor); @@ -859,9 +890,9 @@ SDL_Color CSDL_Ext::makeColor(ui8 r, ui8 g, ui8 b, ui8 a) void CSDL_Ext::startTextInput(const Rect & whereInput) { - SDL_Rect where = Geometry::toSDL(whereInput); + const SDL_Rect where = CSDL_Ext::toSDL(whereInput); - auto impl = [](SDL_Rect & where) + auto impl = [](SDL_Rect where) { if (SDL_IsTextInputActive() == SDL_FALSE) { @@ -945,7 +976,7 @@ void CSDL_Ext::setDefaultColorKeyPresize(SDL_Surface * surface) void CSDL_Ext::setClipRect(SDL_Surface * src, const Rect & other) { - SDL_Rect rect = Geometry::toSDL(other); + SDL_Rect rect = CSDL_Ext::toSDL(other); SDL_SetClipRect(src, &rect); } @@ -956,7 +987,7 @@ void CSDL_Ext::getClipRect(SDL_Surface * src, Rect & other) SDL_GetClipRect(src, &rect); - other = Geometry::fromSDL(rect); + other = CSDL_Ext::fromSDL(rect); } diff --git a/client/gui/SDL_Extensions.h b/client/gui/SDL_Extensions.h index 75a4339cc..9d39021ea 100644 --- a/client/gui/SDL_Extensions.h +++ b/client/gui/SDL_Extensions.h @@ -11,8 +11,8 @@ #pragma once #include #include -#include "Geometries.h" #include "../../lib/GameConstants.h" +#include "../../lib/Rect.h" struct SDL_Window; struct SDL_Renderer; @@ -26,6 +26,7 @@ extern SDL_Texture * screenTexture; extern SDL_Surface * screen, *screen2, *screenBuf; class Rect; +class Point; /** * The colors class defines color constants of type SDL_Color. @@ -68,6 +69,15 @@ public: namespace CSDL_Ext { +/// creates Point using provided event +Point fromSDL(const SDL_MouseMotionEvent & motion); + +/// creates Rect using provided rect +Rect fromSDL(const SDL_Rect & rect); + +/// creates SDL_Rect using provided rect +SDL_Rect toSDL(const Rect & rect); + void setColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors); void warpMouse(int x, int y); bool isCtrlKeyDown(); @@ -95,15 +105,7 @@ std::string makeNumberShort(IntType number, IntType maxLength = 3) //the output return boost::lexical_cast(number) + *iter; } -inline Rect genRect(const int & hh, const int & ww, const int & xx, const int & yy) -{ - Rect ret; - ret.h=hh; - ret.w=ww; - ret.x=xx; - ret.y=yy; - return ret; -} +Rect genRect(const int & hh, const int & ww, const int & xx, const int & yy); typedef void (*TColorPutter)(Uint8 *&ptr, const Uint8 & R, const Uint8 & G, const Uint8 & B); typedef void (*TColorPutterAlpha)(Uint8 *&ptr, const Uint8 & R, const Uint8 & G, const Uint8 & B, const Uint8 & A); diff --git a/client/gui/TextAlignment.h b/client/gui/TextAlignment.h new file mode 100644 index 000000000..8cb379e70 --- /dev/null +++ b/client/gui/TextAlignment.h @@ -0,0 +1,12 @@ +/* + * TextAlignment.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +enum class ETextAlignment {TOPLEFT, CENTER, BOTTOMRIGHT}; diff --git a/client/mapHandler.h b/client/mapHandler.h index 3d4127c7a..daca70154 100644 --- a/client/mapHandler.h +++ b/client/mapHandler.h @@ -12,7 +12,7 @@ #include "../lib/int3.h" #include "../lib/spells/ViewSpellInt.h" -#include "gui/Geometries.h" +#include "../lib/Rect.h" #include "SDL.h" #ifdef IN diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index 732091417..2b7968b90 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -237,7 +237,7 @@ void CHeroList::CHeroItem::open() void CHeroList::CHeroItem::showTooltip() { - CRClickPopup::createAndPush(hero, Geometry::fromSDL(GH.current->motion)); + CRClickPopup::createAndPush(hero, CSDL_Ext::fromSDL(GH.current->motion)); } std::string CHeroList::CHeroItem::getHoverText() @@ -329,7 +329,7 @@ void CTownList::CTownItem::open() void CTownList::CTownItem::showTooltip() { - CRClickPopup::createAndPush(town, Geometry::fromSDL(GH.current->motion)); + CRClickPopup::createAndPush(town, CSDL_Ext::fromSDL(GH.current->motion)); } std::string CTownList::CTownItem::getHoverText() diff --git a/client/widgets/TextControls.h b/client/widgets/TextControls.h index 70092d0f8..064e47536 100644 --- a/client/widgets/TextControls.h +++ b/client/widgets/TextControls.h @@ -10,6 +10,7 @@ #pragma once #include "../gui/CIntObject.h" +#include "../gui/TextAlignment.h" #include "../gui/SDL_Extensions.h" #include "../../lib/FunctionList.h" diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 8a0c23a65..7ae0a3796 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -1814,7 +1814,7 @@ void CAdvMapInt::tileRClicked(const int3 &mapPos) return; } - CRClickPopup::createAndPush(obj, Geometry::fromSDL(GH.current->motion), ETextAlignment::CENTER); + CRClickPopup::createAndPush(obj, CSDL_Ext::fromSDL(GH.current->motion), ETextAlignment::CENTER); } void CAdvMapInt::enterCastingMode(const CSpell * sp) diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index 18cecd486..3641941dd 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -306,7 +306,7 @@ void CRClickPopup::createAndPush(const std::string &txt, const CInfoWindow::TCom player = PlayerColor(1); auto temp = std::make_shared(txt, player, comps); - temp->center(Geometry::fromSDL(GH.current->motion)); //center on mouse + temp->center(CSDL_Ext::fromSDL(GH.current->motion)); //center on mouse #ifdef VCMI_IOS // TODO: enable also for android? temp->moveBy({0, -temp->pos.h / 2}); diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h index b73af37e3..870b2b5ea 100644 --- a/client/windows/InfoWindows.h +++ b/client/windows/InfoWindows.h @@ -10,6 +10,7 @@ #pragma once #include "CWindowObject.h" +#include "../gui/TextAlignment.h" #include "../../lib/FunctionList.h" VCMI_LIB_NAMESPACE_BEGIN From aae83af9f3b46258d31fb53721ac176c1b872678 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 18 Jan 2023 16:09:46 +0200 Subject: [PATCH 194/197] Fix Windows compile --- client/gui/NotificationHandler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/gui/NotificationHandler.cpp b/client/gui/NotificationHandler.cpp index 28438ac04..f539c4729 100644 --- a/client/gui/NotificationHandler.cpp +++ b/client/gui/NotificationHandler.cpp @@ -11,6 +11,7 @@ #include "StdInc.h" #include "NotificationHandler.h" #include +#include #include #if defined(VCMI_WINDOWS) @@ -156,4 +157,4 @@ bool NotificationHandler::handleSdlEvent(const SDL_Event & ev) return false; } -#endif \ No newline at end of file +#endif From fc0f5a9fb3909f18595cbee79413c4cea9b0ad39 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 18 Jan 2023 16:54:53 +0200 Subject: [PATCH 195/197] Reduced SDL includes --- client/CBitmapHandler.cpp | 3 +-- client/CMT.cpp | 1 + client/CMusicHandler.cpp | 2 +- client/CPlayerInterface.cpp | 2 +- client/CVideoHandler.cpp | 1 - client/Client.cpp | 2 -- client/gui/CGuiHandler.cpp | 2 +- client/gui/CursorHandler.cpp | 6 ++---- client/gui/SDL_Extensions.cpp | 2 +- client/gui/SDL_Extensions.h | 2 +- client/mapHandler.h | 1 - client/widgets/AdventureMapClasses.cpp | 2 +- 12 files changed, 10 insertions(+), 16 deletions(-) diff --git a/client/CBitmapHandler.cpp b/client/CBitmapHandler.cpp index a8f716e94..c13f2381f 100644 --- a/client/CBitmapHandler.cpp +++ b/client/CBitmapHandler.cpp @@ -10,8 +10,7 @@ #include "StdInc.h" #include "../lib/filesystem/Filesystem.h" -#include "SDL.h" -#include "SDL_image.h" +#include #include "CBitmapHandler.h" #include "gui/SDL_Extensions.h" #include "../lib/vcmi_endian.h" diff --git a/client/CMT.cpp b/client/CMT.cpp index dadbd27c3..050c1e3c7 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -56,6 +56,7 @@ #include "mainmenu/CPrologEpilogVideo.h" #include +#include #ifdef VCMI_WINDOWS #include "SDL_syswm.h" diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index 747642223..4e2f3bdb0 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -9,7 +9,7 @@ */ #include "StdInc.h" #include -#include +#include #include "CMusicHandler.h" #include "CGameInfo.h" diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index d4df5435b..2076695b7 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -63,7 +63,7 @@ #include "../lib/CPathfinder.h" #include "../lib/RoadHandler.h" #include "../lib/TerrainHandler.h" -#include +#include #include "CServerHandler.h" // FIXME: only needed for CGameState::mutex #include "../lib/CGameState.h" diff --git a/client/CVideoHandler.cpp b/client/CVideoHandler.cpp index 46514e598..b3dbe9563 100644 --- a/client/CVideoHandler.cpp +++ b/client/CVideoHandler.cpp @@ -8,7 +8,6 @@ * */ #include "StdInc.h" -#include #include "CVideoHandler.h" #include "gui/CGuiHandler.h" diff --git a/client/Client.cpp b/client/Client.cpp index 0c8412688..8418d12f1 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -10,8 +10,6 @@ #include "StdInc.h" #include "Client.h" -#include - #include "CMusicHandler.h" #include "../lib/mapping/CCampaignHandler.h" #include "../CCallback.h" diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index 90fa550aa..be4abb5b3 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -11,7 +11,7 @@ #include "CGuiHandler.h" #include "../lib/CondSh.h" -#include +#include #include "CIntObject.h" #include "CursorHandler.h" diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index 74cb474b3..0e660b7d6 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -11,8 +11,6 @@ #include "StdInc.h" #include "CursorHandler.h" -#include - #include "SDL_Extensions.h" #include "CGuiHandler.h" #include "CAnimation.h" @@ -345,7 +343,7 @@ void CursorSoftware::updateTexture() if (!cursorSurface || Point(cursorSurface->w, cursorSurface->h) != cursorImage->dimensions()) createTexture(cursorImage->dimensions()); - CSDL_Ext::fillSurface(cursorSurface, Colors::TRANSPARENT); + CSDL_Ext::fillSurface(cursorSurface, Colors::TRANSPARENCY); cursorImage->draw(cursorSurface); SDL_UpdateTexture(cursorTexture, NULL, cursorSurface->pixels, cursorSurface->pitch); @@ -413,7 +411,7 @@ void CursorHardware::setImage(std::shared_ptr image, const Point & pivot { auto cursorSurface = CSDL_Ext::newSurface(image->dimensions().x, image->dimensions().y); - CSDL_Ext::fillSurface(cursorSurface, Colors::TRANSPARENT); + CSDL_Ext::fillSurface(cursorSurface, Colors::TRANSPARENCY); image->draw(cursorSurface); diff --git a/client/gui/SDL_Extensions.cpp b/client/gui/SDL_Extensions.cpp index b2ef89c3c..3f326146f 100644 --- a/client/gui/SDL_Extensions.cpp +++ b/client/gui/SDL_Extensions.cpp @@ -39,7 +39,7 @@ const SDL_Color Colors::DEFAULT_KEY_COLOR = {0, 255, 255, SDL_ALPHA_OPAQUE}; const SDL_Color Colors::RED = {255, 0, 0, SDL_ALPHA_OPAQUE}; const SDL_Color Colors::PURPLE = {255, 75, 125, SDL_ALPHA_OPAQUE}; const SDL_Color Colors::BLACK = {0, 0, 0, SDL_ALPHA_OPAQUE}; -const SDL_Color Colors::TRANSPARENT = {0, 0, 0, SDL_ALPHA_TRANSPARENT}; +const SDL_Color Colors::TRANSPARENCY = {0, 0, 0, SDL_ALPHA_TRANSPARENT}; Rect CSDL_Ext::genRect(const int & hh, const int & ww, const int & xx, const int & yy) { diff --git a/client/gui/SDL_Extensions.h b/client/gui/SDL_Extensions.h index 9d39021ea..42ddda8f5 100644 --- a/client/gui/SDL_Extensions.h +++ b/client/gui/SDL_Extensions.h @@ -63,7 +63,7 @@ public: static const SDL_Color BLACK; - static const SDL_Color TRANSPARENT; + static const SDL_Color TRANSPARENCY; }; namespace CSDL_Ext diff --git a/client/mapHandler.h b/client/mapHandler.h index daca70154..b1c2ff839 100644 --- a/client/mapHandler.h +++ b/client/mapHandler.h @@ -13,7 +13,6 @@ #include "../lib/int3.h" #include "../lib/spells/ViewSpellInt.h" #include "../lib/Rect.h" -#include "SDL.h" #ifdef IN #undef IN diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index 2b7968b90..c2ab4d00a 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -10,7 +10,7 @@ #include "StdInc.h" #include "AdventureMapClasses.h" -#include +#include #include "MiscWidgets.h" #include "CComponent.h" From 8ed115a6289fdd2c6e055222e9ca99f55764d38e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 18 Jan 2023 17:32:57 +0200 Subject: [PATCH 196/197] Fix ios compile --- client/battle/BattleAnimationClasses.h | 2 +- client/battle/BattleFieldController.h | 5 ++--- client/battle/BattleObstacleController.h | 2 +- client/battle/BattleSiegeController.h | 2 +- client/battle/BattleStacksController.h | 2 +- client/gui/CAnimation.h | 4 ++-- client/gui/Fonts.h | 2 +- client/gui/SDL_Extensions.h | 4 ++++ client/widgets/Buttons.h | 2 +- client/widgets/Images.h | 5 ++++- client/widgets/ObjectLists.h | 5 ++++- client/windows/InfoWindows.h | 2 +- 12 files changed, 23 insertions(+), 14 deletions(-) diff --git a/client/battle/BattleAnimationClasses.h b/client/battle/BattleAnimationClasses.h index 580e1b7c1..f89d4435c 100644 --- a/client/battle/BattleAnimationClasses.h +++ b/client/battle/BattleAnimationClasses.h @@ -17,6 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CStack; class CCreature; class CSpell; +class Point; VCMI_LIB_NAMESPACE_END @@ -27,7 +28,6 @@ class CAnimation; class BattleInterface; class CreatureAnimation; struct StackAttackedInfo; -class Point; /// Base class of battle animations class BattleAnimation diff --git a/client/battle/BattleFieldController.h b/client/battle/BattleFieldController.h index 1105bb202..bcb16b361 100644 --- a/client/battle/BattleFieldController.h +++ b/client/battle/BattleFieldController.h @@ -15,12 +15,11 @@ VCMI_LIB_NAMESPACE_BEGIN class CStack; - -VCMI_LIB_NAMESPACE_END - class Rect; class Point; +VCMI_LIB_NAMESPACE_END + class ClickableHex; class BattleHero; class Canvas; diff --git a/client/battle/BattleObstacleController.h b/client/battle/BattleObstacleController.h index 1b3743637..24c0cad02 100644 --- a/client/battle/BattleObstacleController.h +++ b/client/battle/BattleObstacleController.h @@ -13,6 +13,7 @@ VCMI_LIB_NAMESPACE_BEGIN struct BattleHex; struct CObstacleInstance; +class Point; VCMI_LIB_NAMESPACE_END @@ -21,7 +22,6 @@ class Canvas; class CAnimation; class BattleInterface; class BattleRenderer; -class Point; /// Controls all currently active projectiles on the battlefield /// (with exception of moat, which is apparently handled by siege controller) diff --git a/client/battle/BattleSiegeController.h b/client/battle/BattleSiegeController.h index 91859db3d..262b78b76 100644 --- a/client/battle/BattleSiegeController.h +++ b/client/battle/BattleSiegeController.h @@ -18,10 +18,10 @@ struct CatapultAttack; class CCreature; class CStack; class CGTownInstance; +class Point; VCMI_LIB_NAMESPACE_END -class Point; class Canvas; class BattleInterface; class BattleRenderer; diff --git a/client/battle/BattleStacksController.h b/client/battle/BattleStacksController.h index 31d859c73..92ffdaae0 100644 --- a/client/battle/BattleStacksController.h +++ b/client/battle/BattleStacksController.h @@ -18,6 +18,7 @@ class BattleAction; class CStack; class CSpell; class SpellID; +class Point; VCMI_LIB_NAMESPACE_END @@ -32,7 +33,6 @@ class CreatureAnimation; class BattleAnimation; class BattleRenderer; class IImage; -class Point; struct BattleStackFilterEffect { diff --git a/client/gui/CAnimation.h b/client/gui/CAnimation.h index d32ab424e..d0e4ab84c 100644 --- a/client/gui/CAnimation.h +++ b/client/gui/CAnimation.h @@ -23,6 +23,8 @@ VCMI_LIB_NAMESPACE_BEGIN class JsonNode; +class Rect; +class Point; VCMI_LIB_NAMESPACE_END @@ -30,8 +32,6 @@ struct SDL_Surface; struct SDL_Color; class CDefFile; class ColorFilter; -class Rect; -class Point; /* * Base class for images, can be used for non-animation pictures as well diff --git a/client/gui/Fonts.h b/client/gui/Fonts.h index ee14e24cb..2811a79a3 100644 --- a/client/gui/Fonts.h +++ b/client/gui/Fonts.h @@ -12,10 +12,10 @@ VCMI_LIB_NAMESPACE_BEGIN class JsonNode; +class Point; VCMI_LIB_NAMESPACE_END -class Point; struct SDL_Surface; struct SDL_Color; diff --git a/client/gui/SDL_Extensions.h b/client/gui/SDL_Extensions.h index 42ddda8f5..125ac8f8d 100644 --- a/client/gui/SDL_Extensions.h +++ b/client/gui/SDL_Extensions.h @@ -25,9 +25,13 @@ extern SDL_Renderer * mainRenderer; extern SDL_Texture * screenTexture; extern SDL_Surface * screen, *screen2, *screenBuf; +VCMI_LIB_NAMESPACE_BEGIN + class Rect; class Point; +VCMI_LIB_NAMESPACE_END + /** * The colors class defines color constants of type SDL_Color. */ diff --git a/client/widgets/Buttons.h b/client/widgets/Buttons.h index 971b8614a..6835f1cd3 100644 --- a/client/widgets/Buttons.h +++ b/client/widgets/Buttons.h @@ -19,11 +19,11 @@ namespace config { struct ButtonInfo; } +class Rect; VCMI_LIB_NAMESPACE_END struct SDL_Surface; -class Rect; class CAnimImage; class CLabel; class CAnimation; diff --git a/client/widgets/Images.h b/client/widgets/Images.h index 10e5df50a..2b4b1bd24 100644 --- a/client/widgets/Images.h +++ b/client/widgets/Images.h @@ -14,8 +14,11 @@ #include -struct SDL_Surface; +VCMI_LIB_NAMESPACE_BEGIN class Rect; +VCMI_LIB_NAMESPACE_END + +struct SDL_Surface; class CAnimImage; class CLabel; class CAnimation; diff --git a/client/widgets/ObjectLists.h b/client/widgets/ObjectLists.h index 52dade789..9930f5fe7 100644 --- a/client/widgets/ObjectLists.h +++ b/client/widgets/ObjectLists.h @@ -11,8 +11,11 @@ #include "../gui/CIntObject.h" -struct SDL_Surface; +VCMI_LIB_NAMESPACE_BEGIN class Rect; +VCMI_LIB_NAMESPACE_END + +struct SDL_Surface; class CAnimImage; class CSlider; class CLabel; diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h index 870b2b5ea..a179c26df 100644 --- a/client/windows/InfoWindows.h +++ b/client/windows/InfoWindows.h @@ -16,11 +16,11 @@ VCMI_LIB_NAMESPACE_BEGIN class CGGarrison; +class Rect; VCMI_LIB_NAMESPACE_END struct SDL_Surface; -class Rect; class CAnimImage; class CLabel; class CAnimation; From a19c9eccc4a35bf245bc76bed2b317106db1c4f7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 18 Jan 2023 18:11:49 +0200 Subject: [PATCH 197/197] Fix ios compile --- client/lobby/SelectionTab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index d8d89d8d0..bf34f4429 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -274,7 +274,7 @@ void SelectionTab::clickLeft(tribool down, bool previousState) } #ifdef VCMI_IOS // focus input field if clicked inside it - else if(inputName && inputName->active && inputNameRect.isIn(GH.current->button.x, GH.current->button.y)) + else if(inputName && inputName->active && inputNameRect.isInside(GH.current->button.x, GH.current->button.y)) inputName->giveFocus(); #endif }