1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-21 17:17:06 +02:00

Merge branch 'vcmi/master' into 'vcmi/develop'

This commit is contained in:
Ivan Savenko 2024-05-10 08:09:51 +00:00
commit 2ddb41e654
50 changed files with 534 additions and 328 deletions

View File

@ -2,8 +2,11 @@
### General
* Added Portuguese (Brazilian) translation
* Added basic support for game controllers
* Added option to disable cheats in game
* Game will no longer run vcmiserver as a separate process on desktop systems
* Game will no longer show server error messages in game chat in release builds
* Implemented switchable artifact sets from HD Mod
### Stability
* Fixed possible crash in Altar of Sacrifice
@ -19,12 +22,19 @@
* Fixed crash on moving through whirlpool when hero has no troops other than commander
* Fixed possible freeze when moving hero over events that give enough experience to cause a level-up
* Fixed possible crash on movement of double-wide creatures next to gates during siege
* Fixed possible hanging app on attempt to close game during loading
### Multiplayer
* Game map will no longer be locked during turn of other human players, allowing to change hero paths or inspect towns or heroes
* Game will now correctly block most of player actions outside of their turn
* Implemented new lobby, available in game with persistent accounts and chat
* Removed old lobby previously available in launcher
* Fixed potential crash that could occur if two players act at the very same time
* Game will no longer pause due to network lag after every tile when instant movement speed is selected in multiplayer
* Game will now show "X player's turn" dialog on new turn in online multiplayer
* Fixed loading of turn timers state from saved games
* Simultaneous turns will now break when players are 1 turn away from each other instead of 2 turns
* Implemented rolling and banning of towns before game start
### Interface
* Implemented configurable keyboard shortcuts, editable in file config/shortcutsConfig.json
@ -35,6 +45,7 @@
* It is no longer possible to start single scenario by pressing "Enter", in line with H3 and to prevent interference with game chat
* Empty treasure banks will no longer ask for confirmation when entering
* Game will now save last used difficulty settings
* Opening random map tab or scenario selection tab in pregame will no longer reset starting towns or heroes unless different map was selected
* Town Portal dialog will now show town icons
* Town Portal dialog will now show town info on right click
* Town Portal dialog will center on town on clicking it
@ -55,10 +66,12 @@
* Fixed translation of some bonuses using incorrect language
* Added option to use 'nearest' rounding mode for UI scaling
* Fixed various minor bugs in trade window interface
* Removed animation of spawning of every single new monster on new month
* Game will now correctly reset artifact drag-and-drop cursor if player opens another dialog on top of hero window
* If player has no valid saves, game will pick "NEWGAME" as proposed save name instead of empty field
* Fixed incorrect visitation sounds of Crypt, Shipwreck and Abandoned Ship
* Fixed double sound playback on capturing mines
* Recruitment costs that consist from 3 different resources should now fit recruitment window UI better
### Campaigns
* Game will now correctly track who defeated the hero or wandering monsters for related quests and victory conditions
@ -88,6 +101,7 @@
### Mechanics
* It is no longer possible to learn spells from Pandora or events if hero can not learn them
* Fixed behavior of 'Dimension Door' spell to be in line with H3:SoD
* Fixed behavior of 'Fly' spell to be in line with H3:SoD
* If it is not possible to cast 'Dimension Door', game will show message immediately on picking spell in spellbook
* Added options to configure 'Dimension Door' spell to be in line with HotA
* Casting 'Town Portal' while in boat will now show correct message box instead of server error
@ -117,6 +131,8 @@
* Decreased minimal density of obstacles on undergound level of the map
* Density of objects should now closely resemble H3 RMG
* Generator will now avoid routing road under guarded objects whenever possible
* Generator will now avoid placing guards near roads
* Generator will not place a guard near the road if it's stronger than 1/3 of max guard strength for this zone
* Interactive objects will now appear on top of static objects
* Windmill will now appear on top of all other objects
@ -140,6 +156,8 @@
### AI
* Fixed possible crash on updating NKAI pathfinding data
* Fixed possible crash if hero has only commander left without army
* Fixed possible crash on attempt to build tavern in a town
* Fixed counting mana usage cost of Fly spell
* Added estimation of value of Pyramid and Cyclops Stockpile
* Reduced memory usage and improved performance of AI pathfinding
@ -153,6 +171,7 @@
### Modding
* Added new game setting that allows inviting heroes to taverns
* It is now possible to add creature or faction description accessible via right-click of the icon
* Fixed reversed Overlord and Warlock classes mapping
* Added 'selectAll' mode for configurable objects which grants all potential rewards
* It is now possible to use most of json5 format in vcmi json files

View File

@ -523,6 +523,15 @@ namespace vstd
}
}
// Removes all duplicate elements from the vector
template<typename T>
void unique(std::vector<T> &vec)
{
std::sort(vec.begin(), vec.end());
auto newEnd = std::unique(vec.begin(), vec.end());
vec.erase(newEnd, vec.end());
}
template<typename InputRange, typename OutputIterator, typename Predicate>
OutputIterator copy_if(const InputRange &input, OutputIterator result, Predicate pred)
{

View File

@ -50,7 +50,7 @@ import eu.vcmi.vcmi.util.ServerResponse;
public class ActivityMods extends ActivityWithToolbar
{
private static final boolean ENABLE_REPO_DOWNLOADING = true;
private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.4.json";
private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.5.json";
private VCMIModsRepo mRepo;
private RecyclerView mRecycler;

View File

@ -199,7 +199,7 @@ public:
void startBattlePrimary(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool creatureBank = false, const CGTownInstance * town = nullptr) override {}; //use hero=nullptr for no hero
void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used
void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
void giveHeroBonus(GiveBonus * bonus) override {};
void setMovePoints(SetMovePoints * smp) override {};
void setMovePoints(ObjectInstanceID hid, int val, bool absolute) override {};

View File

@ -32,9 +32,24 @@ InputSourceKeyboard::InputSourceKeyboard()
#endif
}
std::string InputSourceKeyboard::getKeyNameWithModifiers(const std::string & keyName) const
{
std::string result;
if (isKeyboardCtrlDown())
result += "Ctrl+";
if (isKeyboardAltDown())
result += "Alt+";
if (isKeyboardShiftDown())
result += "Shift+";
result += keyName;
return result;
}
void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
{
std::string keyName = SDL_GetKeyName(key.keysym.sym);
std::string keyName = getKeyNameWithModifiers(SDL_GetKeyName(key.keysym.sym));
logGlobal->trace("keyboard: key '%s' pressed", keyName);
assert(key.state == SDL_PRESSED);

View File

@ -15,6 +15,7 @@ struct SDL_KeyboardEvent;
/// Class that handles keyboard input from SDL events
class InputSourceKeyboard
{
std::string getKeyNameWithModifiers(const std::string & keyName) const;
public:
InputSourceKeyboard();

View File

@ -466,6 +466,9 @@ void CInteractableTownTooltip::init(const CGTownInstance * town)
if(town->id == townId && town->builtBuildings.count(BuildingID::TAVERN))
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
}
}, [&]{
if(!town->town->faction->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated());
});
fastMarket = std::make_shared<LRClickableArea>(Rect(143, 31, 30, 34), []()
{

View File

@ -1351,6 +1351,9 @@ void CCastleInterface::recreateIcons()
{
if(town->builtBuildings.count(BuildingID::TAVERN))
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
}, [&]{
if(!town->town->faction->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated());
});
creainfo.clear();

View File

@ -22,6 +22,7 @@
#include "../widgets/Images.h"
#include "../widgets/TextControls.h"
#include "../widgets/ObjectLists.h"
#include "../windows/InfoWindows.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
@ -517,6 +518,10 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
};
animation = std::make_shared<CCreaturePic>(5, 41, parent->info->creature);
animationArea = std::make_shared<LRClickableArea>(Rect(5, 41, 100, 130), nullptr, [&]{
if(!parent->info->creature->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(parent->info->creature->getDescriptionTranslated());
});
if(parent->info->stackNode != nullptr && parent->info->commander == nullptr)
{

View File

@ -29,6 +29,7 @@ class CButton;
class CMultiLineLabel;
class CListBox;
class CCommanderArtPlace;
class LRClickableArea;
class CCommanderSkillIcon : public LRClickableAreaWText //TODO: maybe bring commander skill button initialization logic inside?
{
@ -132,6 +133,7 @@ class CStackWindow : public CWindowObject
};
std::shared_ptr<CCreaturePic> animation;
std::shared_ptr<LRClickableArea> animationArea;
std::shared_ptr<CLabel> name;
std::shared_ptr<CPicture> icons;
std::shared_ptr<MoraleLuckBox> morale;

View File

@ -811,6 +811,9 @@ CTownItem::CTownItem(const CGTownInstance * Town)
{
if(town->builtBuildings.count(BuildingID::TAVERN))
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
}, [&]{
if(!town->town->faction->getDescriptionTranslated().empty())
CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated());
});
fastMarket = std::make_shared<LRClickableArea>(Rect(153, 6, 65, 64), []()
{

View File

@ -41,6 +41,10 @@
}
}
},
"description" : {
"type" : "string",
"description" : "Description of creature"
},
"faction" : {
"type" : "string",
"description" : "Faction this creature belongs to. Examples: castle, rampart"

View File

@ -40,6 +40,10 @@
"type" : "string",
"description" : "Localizable faction name, e.g. Rampart"
},
"description" : {
"type" : "string",
"description" : "Description about the faction"
},
"alignment" : {
"type" : "string",
"enum" : [ "good", "neutral", "evil" ],

View File

@ -4,6 +4,7 @@
// For players (Linux): create file ~/.config/vcmi/shortcutsConfig.json (or ~/.var/app/eu.vcmi.VCMI/config for Flatpak) to modify this set
//
// When creating your own config, you can remove all hotkeys that you have not changed and game will read them from this file
// It is possible to add modifiers to keys: Ctrl, Shift, or Alt. For example, "Ctrl+Tab" hotkey will only activate if Ctrl is pressed
{
"keyboard" : {
"globalAccept": [ "Return", "Keypad Enter"],
@ -112,6 +113,10 @@
"battleTacticsEnd": [ "Return", "Keypad Enter"],
"battleToggleHeroesStats": [],
"battleSelectAction": "S",
"lobbyActivateInterface": "Ctrl+Tab",
"spectateTrackHero": "F5",
"spectateSkipBattle": "F7",
"spectateSkipBattleResult": "F8",
"townOpenTavern": "T",
"townSwapArmies": "Space",
"recruitmentMax": "End",

2
debian/changelog vendored
View File

@ -8,7 +8,7 @@ vcmi (1.5.0) jammy; urgency=medium
* New upstream release
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 1 Mar 2024 12:00:00 +0200
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 10 May 2024 12:00:00 +0200
vcmi (1.4.5) jammy; urgency=medium

View File

@ -1,8 +1,5 @@
[![VCMI](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg?branch=develop&event=push)](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.0)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.1/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.1)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.2/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.2)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.5/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.5)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
# VCMI Project

View File

@ -18,7 +18,7 @@ Should be done immediately after start of stabilization stage for previous relea
- Add all features and bugs that should be fixed as part of this release into this project
### Start of stabilization stage (major releases only)
Should be done 2-4 weeks before planned release date. All major features should be finished at this point.
Should be done 2 weeks before planned release date. All major features should be finished at this point.
- Create `beta` branch from `develop`
- Bump vcmi version in CMake on `develop` branch
@ -37,9 +37,10 @@ Should be done 1 week before release. Release date should be decided at this poi
- Make sure to announce codebase freeze deadline (1 day before release) to all developers
- Create pull request for release preparation tasks targeting `beta`:
- - Update [changelog](https://github.com/vcmi/vcmi/blob/develop/ChangeLog.md)
- - Update release date for Linux packaging. See [example](https://github.com/vcmi/vcmi/pull/1258)
- - Update build ID for Android packaging. See [example](https://github.com/vcmi/vcmi/pull/2090)
- - Update downloads counter in readme.md. See [example](https://github.com/vcmi/vcmi/pull/2091)
- - Update release date in `debian/changelog`
- - Update release date in `launcher/eu.vcmi.VCMI.metainfo.xml`
- - Update build ID `android/vcmi-app/build.gradle`
- - Update downloads counter in `docs/readme.md`
### Release preparation stage
Should be done 1 day before release. At this point beta branch is in full freeze.

View File

@ -10,6 +10,7 @@ If not enough biomes are defined for [terrain type](Terrain_Format.md), map gene
"obstacleSetId" : {
"biome" : {
"terrain" : "grass", // Id or vector of Ids this obstacle set can spawn at
"level" : "underground", // or "surface", by default both
"faction" : ["castle", "rampart"], //Id or vector of faction Ids. Set will only be used if zone belongs to this faction
"alignment" : ["good", "evil", "neutral"], //Alignment of the zone. Set will only be used if zone has this alignment
"objectType": "mountain"

View File

@ -29,6 +29,10 @@ In order to make functional creature you also need:
"singular" : "Creature",
"plural" : "Creatures"
},
// Description of creature
"description" : "",
"level" : 0,
// Marks this object as special and not available by default

View File

@ -57,6 +57,9 @@ Each town requires a set of buildings (Around 30-45 buildings)
// Localizable faction name, e.g. "Rampart"
"name" : "",
// Description of town (e.g. history or story about town)
"description" : "",
// Faction alignment. Can be good, neutral (default) or evil.
"alignment" : "",

View File

@ -5,6 +5,7 @@
<summary>Open-source game engine for Heroes of Might and Magic III</summary>
<summary xml:lang="cs">Herní engine s otevřeným kódem pro Heroes of Might and Magic III</summary>
<summary xml:lang="de">Open-Source-Spielengine für Heroes of Might and Magic III</summary>
<summary xml:lang="uk">Ігровий рушій з відкритим початковим кодом для Heroes of Might and Magic III</summary>
<developer id="eu.vcmi">
<name>VCMI Team</name>
<name xml:lang="cs">Tým VCMI</name>
@ -15,35 +16,45 @@
<p>VCMI is an open-source engine for Heroes of Might and Magic III with new possibilities. Years of intensive work resulted in an impressive amount of features. Among the current features are:</p>
<p xml:lang="cs">VCMI je engine s otevřeným kódem a novými možnostmi pro Heroes of Might and Magic III. Roky usilovné práce vyústily v úchvatném počtu nových funkcí. Mezi současnými funkcemi jsou:</p>
<p xml:lang="de">VCMI ist eine Open-Source-Engine für Heroes of Might and Magic III mit neuen Möglichkeiten. Jahrelange intensive Arbeit führte zu einer beeindruckenden Anzahl von Features. Zu den aktuellen Features gehören:</p>
<p xml:lang="uk">VCMI - це рушій з відкритим початковим кодом для Heroes of Might and Magic III з новими можливостями. Роки інтенсивної роботи вилилися у вражаючу кількість функцій. Серед поточних можливостей можна виділити наступні:</p>
<ul>
<li>Complete gameplay mechanics</li>
<li xml:lang="cs">Kompletní herní mechaniky</li>
<li xml:lang="de">Vollständige Spielmechanik</li>
<li xml:lang="uk">Уся ігрова механіка</li>
<li>Almost all objects, abilities, spells and other content</li>
<li xml:lang="cs">Skoro všechny předměty, schopnosti, kouzla a ostatní obsah</li>
<li xml:lang="de">Fast alle Objekte, Fähigkeiten, Zaubersprüche und andere Inhalte</li>
<li xml:lang="uk">Практично всі об'єкти, вміння, закляття та інший вміст</li>
<li>Basic battle AI and adventure AI</li>
<li xml:lang="cs">Základní AI boje a mapy světa</li>
<li xml:lang="de">Grundlegende Kampf- und Abenteuer-KI</li>
<li xml:lang="uk">Базовий ШІ для бою та для мапи пригод</li>
<li>Many GUI improvements: high resolutions, stack queue, creature window</li>
<li xml:lang="cs">Mnoho vylepšení rozhraní: vyšší rozlišení, fronta oddílů a okno bojovníků</li>
<li xml:lang="de">Viele GUI-Verbesserungen: Hohe Auflösungen, Truppenwarteschlange, Kreaturenfenster</li>
<li xml:lang="uk">Численні покращення графічного інтерфейсу: висока роздільна здатність, черга ходу істот, нове вікно істот</li>
<li>Advanced and easy mod support - add new towns, creatures, heroes, artifacts and spells without limits or conflicts</li>
<li xml:lang="cs">Pokročilá a jednoduchá podpora modifikací - přidání nových měst, bojovníků, hrdinů, artefaktů a kouzel bez limitů a konfliktů</li>
<li xml:lang="de">Erweiterte und einfache Mod-Unterstützung - füge neue Städte, Kreaturen, Helden, Artefakte und Zaubersprüche ohne Einschränkungen oder Konflikte hinzu</li>
<li xml:lang="uk">Просунута і проста підтримка модів - додавайте нові міста, істот, героїв, артефакти і закляття без обмежень і конфліктів</li>
<li>Launcher for easy configuration - download mods from our server and install them immediately!</li>
<li xml:lang="cs">Spouštěč pro jednoduché nastavení - stahujte modifikace z našeho serveru a hned je instalujte!</li>
<li xml:lang="de">Launcher für einfache Konfiguration - Mods von unserem Server herunterladen und sofort installieren!</li>
<li xml:lang="uk">Лаунчер для легкого налаштування гри - завантажуйте моди з нашого сервера та встановлюйте їх одразу!</li>
<li>Random map generator that supports objects added by mods</li>
<li xml:lang="cs">Náhodný generátor map, který podporuje předměty přidané modifikacemi</li>
<li xml:lang="de">Zufallsgenerator für Karten, der von Mods hinzugefügte Objekte unterstützt</li>
<li xml:lang="uk">Генератор випадкових карт, який підтримує об'єкти, додані модами</li>
</ul>
<p>Note: In order to play the game using VCMI you need to own data files for Heroes of Might and Magic III: The Shadow of Death.</p>
<p xml:lang="cs">Poznámka: pokud chcete hrát hru přes VCMI, musíte vlastnit datové soubory Heroes of Might and Magic III: The Shadow of Death.</p>
<p xml:lang="de">Hinweis: Um das Spiel mit VCMI spielen zu können, sind die Originaldateien für Heroes of Might and Magic III: The Shadow of Death erforderlich.</p>
<p xml:lang="uk">Примітка: Для того, щоб грати в гру за допомогою VCMI, вам потрібно мати файли даних для гри Heroes of Might and Magic III: The Shadow of Death.</p>
<p>If you want help, please check our forum, bug tracker or GitHub page.</p>
<p xml:lang="cs">Pokud chcete pomoct, prosíme, podívejte se na naše fórum nebo GitHub.</p>
<p xml:lang="de">Wird Hilfe benötigt, besucht bitte unser Forum, den Bugtracker oder die GitHub-Seite.</p>
<p xml:lang="uk">Якщо вам потрібна допомога, зверніться до нашого форуму, баг-трекера або на сторінку GitHub.</p>
</description>
<screenshots>
<screenshot type="default">
@ -80,7 +91,7 @@
<launchable type="desktop-id">vcmilauncher.desktop</launchable>
<releases>
<release version="1.6.0" date="2024-08-30" type="development"/>
<release version="1.5.0" date="2024-03-01" type="development"/>
<release version="1.5.0" date="2024-05-10" type="stable"/>
<release version="1.4.5" date="2024-01-23" type="stable"/>
<release version="1.4.4" date="2024-01-20" type="stable"/>
<release version="1.4.3" date="2024-01-19" type="stable"/>

View File

@ -254,7 +254,7 @@
<message>
<location filename="../modManager/cmodlistview_moc.ui" line="373"/>
<source>Install from file</source>
<translation type="unfinished"></translation>
<translation>Zainstaluj z pliku</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.ui" line="424"/>
@ -350,18 +350,18 @@
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="310"/>
<source>please upgrade mod</source>
<translation type="unfinished"></translation>
<translation>proszę zaktualizować moda</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="182"/>
<location filename="../modManager/cmodlistview_moc.cpp" line="796"/>
<source>mods repository index</source>
<translation type="unfinished"></translation>
<translation>indeks repozytorium modów</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="312"/>
<source>or newer</source>
<translation type="unfinished"></translation>
<translation>lub nowsze</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="315"/>
@ -416,42 +416,42 @@
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
<source>All supported files</source>
<translation type="unfinished"></translation>
<translation>Wszystkie wspierane pliki</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
<source>Maps</source>
<translation type="unfinished">Mapy</translation>
<translation>Mapy</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
<source>Campaigns</source>
<translation type="unfinished"></translation>
<translation>Kampanie</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
<source>Configs</source>
<translation type="unfinished"></translation>
<translation>Konfiguracje</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
<source>Mods</source>
<translation type="unfinished">Mody</translation>
<translation>Mody</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="628"/>
<source>Select files (configs, mods, maps, campaigns) to install...</source>
<translation type="unfinished"></translation>
<translation>Wybierz pliki (konfiguracyjne, mody, mapy, kampanie) do zainstalowania...</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="654"/>
<source>Replace config file?</source>
<translation type="unfinished"></translation>
<translation>Zastąpić plik konfiguracji?</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="654"/>
<source>Do you want to replace %1?</source>
<translation type="unfinished"></translation>
<translation>Czy chcesz zastąpić %1?</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="693"/>
@ -505,7 +505,7 @@ Zainstalować pomyślnie pobrane?</translation>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="960"/>
<source>screenshots</source>
<translation type="unfinished"></translation>
<translation>zrzuty ekranu</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.cpp" line="966"/>
@ -523,95 +523,96 @@ Zainstalować pomyślnie pobrane?</translation>
<message>
<location filename="../modManager/cmodmanager.cpp" line="160"/>
<source>Can not install submod</source>
<translation type="unfinished"></translation>
<translation>Nie można zainstalować submoda</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="163"/>
<source>Mod is already installed</source>
<translation type="unfinished"></translation>
<translation>Mod jest już zainstalowany</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="172"/>
<source>Can not uninstall submod</source>
<translation type="unfinished"></translation>
<translation>Nie można odinstalować submoda</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="175"/>
<source>Mod is not installed</source>
<translation type="unfinished"></translation>
<translation>Mod nie jest zainstalowany</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="185"/>
<source>Mod is already enabled</source>
<translation type="unfinished"></translation>
<translation>Mod jest już włączony</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="188"/>
<location filename="../modManager/cmodmanager.cpp" line="231"/>
<source>Mod must be installed first</source>
<translation type="unfinished"></translation>
<translation>Mod musi zostać najpierw zainstalowany</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="192"/>
<source>Mod is not compatible, please update VCMI and checkout latest mod revisions</source>
<translation type="unfinished"></translation>
<translation>Mod nie jest kompatybilny, proszę zaktualizować VCMI i odświeżyć listę modów</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="197"/>
<source>Required mod %1 is missing</source>
<translation type="unfinished"></translation>
<translation>Brakuje wymaganego moda %1</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="202"/>
<source>Required mod %1 is not enabled</source>
<translation type="unfinished"></translation>
<translation>Wymagany mod %1 jest wyłączony</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="211"/>
<location filename="../modManager/cmodmanager.cpp" line="218"/>
<source>This mod conflicts with %1</source>
<translation type="unfinished"></translation>
<translation>Ten mod konfliktuje z %1</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="228"/>
<source>Mod is already disabled</source>
<translation type="unfinished"></translation>
<translation>Mod jest już wyłączony</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="238"/>
<source>This mod is needed to run %1</source>
<translation type="unfinished"></translation>
<translation>Ten mod jest potrzebny do uruchomienia %1</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="280"/>
<source>Mod archive is missing</source>
<translation type="unfinished"></translation>
<translation>Brakuje archiwum modyfikacji</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="283"/>
<source>Mod with such name is already installed</source>
<translation type="unfinished"></translation>
<translation>Mod z taką nazwą jest już zainstalowany</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="288"/>
<source>Mod archive is invalid or corrupted</source>
<translation type="unfinished"></translation>
<translation>Archiwum moda jest niepoprawne lub uszkodzone</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="314"/>
<source>Failed to extract mod data</source>
<translation type="unfinished"></translation>
<translation>Nieudane wyodrębnienie danych moda</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="342"/>
<source>Data with this mod was not found</source>
<translation type="unfinished"></translation>
<translation>Dane z tym modem nie zostały znalezione</translation>
</message>
<message>
<location filename="../modManager/cmodmanager.cpp" line="346"/>
<source>Mod is located in protected directory, please remove it manually:
</source>
<translation type="unfinished"></translation>
<translation>Mod jest umiejscowiony w chronionym folderze, proszę go usunąć ręcznie:
</translation>
</message>
</context>
<context>
@ -716,7 +717,7 @@ Zainstalować pomyślnie pobrane?</translation>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="624"/>
<source>Renderer</source>
<translation type="unfinished"></translation>
<translation>Renderer</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="246"/>
@ -858,27 +859,27 @@ Pełny ekran klasyczny - gra przysłoni cały ekran uruchamiając się w wybrane
<message>
<location filename="../modManager/cmodlist.cpp" line="21"/>
<source>%1 B</source>
<translation type="unfinished"></translation>
<translation>%1 B</translation>
</message>
<message>
<location filename="../modManager/cmodlist.cpp" line="22"/>
<source>%1 KiB</source>
<translation type="unfinished"></translation>
<translation>%1 KiB</translation>
</message>
<message>
<location filename="../modManager/cmodlist.cpp" line="23"/>
<source>%1 MiB</source>
<translation type="unfinished"></translation>
<translation>%1 MiB</translation>
</message>
<message>
<location filename="../modManager/cmodlist.cpp" line="24"/>
<source>%1 GiB</source>
<translation type="unfinished"></translation>
<translation>%1 GiB</translation>
</message>
<message>
<location filename="../modManager/cmodlist.cpp" line="25"/>
<source>%1 TiB</source>
<translation type="unfinished"></translation>
<translation>%1 TiB</translation>
</message>
</context>
<context>
@ -934,7 +935,7 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="288"/>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="350"/>
<source>Install gog.com files</source>
<translation type="unfinished"></translation>
<translation>Użyj instalatora z gog.com</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="362"/>
@ -1020,7 +1021,7 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="346"/>
<source>If you don&apos;t have a copy of Heroes III installed, VCMI can import your Heroes III data using the offline installer from gog.com.</source>
<translation type="unfinished"></translation>
<translation>Jeśli nie masz zainstalowanego Heroes III, VCMI może użyć danych z instalatora offline gog.com.</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="384"/>
@ -1071,64 +1072,64 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="148"/>
<source>Heroes III installation found!</source>
<translation type="unfinished"></translation>
<translation>Znaleziono zainstalowane Heroes III!</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="148"/>
<source>Copy data to VCMI folder?</source>
<translation type="unfinished"></translation>
<translation>Skopiować dane do folderu VCMI?</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="289"/>
<source>Select %1 file...</source>
<comment>param is file extension</comment>
<translation type="unfinished"></translation>
<translation>Wybierz plik %1...</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="290"/>
<source>You have to select %1 file!</source>
<comment>param is file extension</comment>
<translation type="unfinished"></translation>
<translation>Musisz wybrać plik %1!</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="292"/>
<source>GOG file (*.*)</source>
<translation type="unfinished"></translation>
<translation>Instalator GOG (*.*)</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="293"/>
<source>File selection</source>
<translation type="unfinished"></translation>
<translation>Wybór pliku</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="300"/>
<source>Invalid file selected</source>
<translation type="unfinished"></translation>
<translation>Wybrano nieprawidłowy plik</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="307"/>
<source>GOG installer</source>
<translation type="unfinished"></translation>
<translation>Instalator GOG</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="310"/>
<source>GOG data</source>
<translation type="unfinished"></translation>
<translation>Dane GOG</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="314"/>
<source>Installing... Please wait!</source>
<translation type="unfinished"></translation>
<translation>Instalowanie... Proszę czekać!</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="349"/>
<source>No Heroes III data!</source>
<translation type="unfinished"></translation>
<translation>Brak danych Heroes III!</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="349"/>
<source>Selected files do not contain Heroes III data!</source>
<translation type="unfinished"></translation>
<translation>Wybrane pliki nie zawierają danych Heroes III!</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="387"/>
@ -1136,26 +1137,29 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="408"/>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="413"/>
<source>Heroes III data not found!</source>
<translation type="unfinished"></translation>
<translation>Dane Heroes III nie znalezione!</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="387"/>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="401"/>
<source>Failed to detect valid Heroes III data in chosen directory.
Please select directory with installed Heroes III data.</source>
<translation type="unfinished"></translation>
<translation>Nieudane znalezienie poprawnych plików Heroes III w podanej lokalizacji.
Proszę wybrać folder z zainstalowanymi danymi Heroes III.</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="408"/>
<source>Heroes III: HD Edition files are not supported by VCMI.
Please select directory with Heroes III: Complete Edition or Heroes III: Shadow of Death.</source>
<translation type="unfinished"></translation>
<translation>Pliki Heroes III HD Edition nie wspierane przez VCMI.
Proszę wybrać folder z Heroes III: Complete Edition lub Heroes III: Shadow of Death.</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="413"/>
<source>Unknown or unsupported Heroes III version found.
Please select directory with Heroes III: Complete Edition or Heroes III: Shadow of Death.</source>
<translation type="unfinished"></translation>
<translation>Znaleziono nieznaną lub niewspieraną wersję Heroes III.
Proszę wybrać folder z Heroes III: Complete Edition lub Heroes III: Shadow of Death.</translation>
</message>
</context>
<context>
@ -1312,17 +1316,17 @@ Please select directory with Heroes III: Complete Edition or Heroes III: Shadow
<message>
<location filename="../modManager/cmodlistmodel_moc.cpp" line="172"/>
<source>Name</source>
<translation type="unfinished">Nazwa</translation>
<translation>Nazwa</translation>
</message>
<message>
<location filename="../modManager/cmodlistmodel_moc.cpp" line="175"/>
<source>Type</source>
<translation type="unfinished">Typ</translation>
<translation>Typ</translation>
</message>
<message>
<location filename="../modManager/cmodlistmodel_moc.cpp" line="176"/>
<source>Version</source>
<translation type="unfinished">Wersja</translation>
<translation>Wersja</translation>
</message>
</context>
<context>
@ -1345,12 +1349,12 @@ Please select directory with Heroes III: Complete Edition or Heroes III: Shadow
<message>
<location filename="../updatedialog_moc.cpp" line="64"/>
<source>Network error</source>
<translation type="unfinished"></translation>
<translation>Błąd sieciowy</translation>
</message>
<message>
<location filename="../updatedialog_moc.cpp" line="101"/>
<source>Cannot read JSON from url or incorrect JSON data</source>
<translation type="unfinished"></translation>
<translation>Nie można odczytać JSON z url lub JSON ma błędną zawartość</translation>
</message>
</context>
</TS>

View File

@ -199,6 +199,11 @@ std::string CCreature::getNameTextID() const
return getNameSingularTextID();
}
std::string CCreature::getDescriptionTranslated() const
{
return VLC->generaltexth->translate(getDescriptionTextID());
}
std::string CCreature::getNamePluralTextID() const
{
return TextIdentifier("creatures", modScope, identifier, "name", "plural" ).get();
@ -209,6 +214,11 @@ std::string CCreature::getNameSingularTextID() const
return TextIdentifier("creatures", modScope, identifier, "name", "singular" ).get();
}
std::string CCreature::getDescriptionTextID() const
{
return TextIdentifier("creatures", modScope, identifier, "description").get();
}
CCreature::CreatureQuantityId CCreature::getQuantityID(const int & quantity)
{
if (quantity<5)
@ -600,6 +610,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
VLC->generaltexth->registerString(scope, cre->getNameSingularTextID(), node["name"]["singular"].String());
VLC->generaltexth->registerString(scope, cre->getNamePluralTextID(), node["name"]["plural"].String());
VLC->generaltexth->registerString(scope, cre->getDescriptionTextID(), node["description"].String());
cre->addBonus(node["hitPoints"].Integer(), BonusType::STACK_HEALTH);
cre->addBonus(node["speed"].Integer(), BonusType::STACKS_SPEED);

View File

@ -51,6 +51,9 @@ class DLL_LINKAGE CCreature : public Creature, public CBonusSystemNode
TResources cost; //cost[res_id] - amount of that resource required to buy creature from dwelling
public:
std::string getDescriptionTranslated() const;
std::string getDescriptionTextID() const;
ui32 ammMin; // initial size of stack of these creatures on adventure map (if not set in editor)
ui32 ammMax;

View File

@ -188,6 +188,16 @@ std::string CFaction::getNameTextID() const
return TextIdentifier("faction", modScope, identifier, "name").get();
}
std::string CFaction::getDescriptionTranslated() const
{
return VLC->generaltexth->translate(getDescriptionTextID());
}
std::string CFaction::getDescriptionTextID() const
{
return TextIdentifier("faction", modScope, identifier, "description").get();
}
FactionID CFaction::getId() const
{
return FactionID(index);
@ -1037,6 +1047,7 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode
faction->identifier = identifier;
VLC->generaltexth->registerString(scope, faction->getNameTextID(), source["name"].String());
VLC->generaltexth->registerString(scope, faction->getDescriptionTranslated(), source["description"].String());
faction->creatureBg120 = ImagePath::fromJson(source["creatureBackground"]["120px"]);
faction->creatureBg130 = ImagePath::fromJson(source["creatureBackground"]["130px"]);

View File

@ -192,6 +192,8 @@ public:
std::string getNameTranslated() const override;
std::string getNameTextID() const override;
std::string getDescriptionTranslated() const;
std::string getDescriptionTextID() const;
bool hasTown() const override;
TerrainId getNativeTerrain() const override;

View File

@ -117,7 +117,7 @@ public:
virtual void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr)=0; //use hero=nullptr for no hero
virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false)=0; //if any of armies is hero, hero will be used
virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false)=0; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL)=0;
virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveMove, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL)=0;
virtual bool swapGarrisonOnSiege(ObjectInstanceID tid)=0;
virtual void giveHeroBonus(GiveBonus * bonus)=0;
virtual void setMovePoints(SetMovePoints * smp)=0;

View File

@ -253,4 +253,13 @@ enum class EArmyFormation : int8_t
TIGHT
};
enum class EMovementMode : int8_t
{
STANDARD,
DIMENSION_DOOR,
MONOLITH,
CASTLE_GATE,
TOWN_PORTAL,
};
VCMI_LIB_NAMESPACE_END

View File

@ -524,7 +524,7 @@ void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer,
else
dPos = hero->convertFromVisitablePos(cb->getObj(randomExit)->visitablePos());
cb->moveHero(hero->id, dPos, true);
cb->moveHero(hero->id, dPos, EMovementMode::MONOLITH);
}
void CGMonolith::initObj(CRandomGenerator & rand)
@ -703,7 +703,7 @@ void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer
dPos = hero->convertFromVisitablePos(*RandomGeneratorUtil::nextItem(tiles, CRandomGenerator::getDefault()));
}
cb->moveHero(hero->id, dPos, true);
cb->moveHero(hero->id, dPos, EMovementMode::MONOLITH);
}
bool CGWhirlpool::isProtected(const CGHeroInstance * h)

View File

@ -19,13 +19,15 @@ VCMI_LIB_NAMESPACE_BEGIN
ObstacleSet::ObstacleSet():
type(INVALID),
allowedTerrains({TerrainId::NONE})
allowedTerrains({TerrainId::NONE}),
level(EMapLevel::ANY)
{
}
ObstacleSet::ObstacleSet(EObstacleType type, TerrainId terrain):
type(type),
allowedTerrains({terrain})
allowedTerrains({terrain}),
level(EMapLevel::ANY)
{
}
@ -47,16 +49,26 @@ void ObstacleSet::removeEmptyTemplates()
});
}
ObstacleSetFilter::ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain = TerrainId::ANY_TERRAIN, FactionID faction = FactionID::ANY, EAlignment alignment = EAlignment::ANY):
ObstacleSetFilter::ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes,
TerrainId terrain = TerrainId::ANY_TERRAIN,
ObstacleSet::EMapLevel level = ObstacleSet::EMapLevel::ANY,
FactionID faction = FactionID::ANY,
EAlignment alignment = EAlignment::ANY):
allowedTypes(allowedTypes),
level(level),
faction(faction),
alignment(alignment),
terrain(terrain)
{
}
ObstacleSetFilter::ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain = TerrainId::ANY_TERRAIN, FactionID faction = FactionID::ANY, EAlignment alignment = EAlignment::ANY):
ObstacleSetFilter::ObstacleSetFilter(ObstacleSet::EObstacleType allowedType,
TerrainId terrain = TerrainId::ANY_TERRAIN,
ObstacleSet::EMapLevel level = ObstacleSet::EMapLevel::ANY,
FactionID faction = FactionID::ANY,
EAlignment alignment = EAlignment::ANY):
allowedTypes({allowedType}),
level(level),
faction(faction),
alignment(alignment),
terrain(terrain)
@ -70,6 +82,14 @@ bool ObstacleSetFilter::filter(const ObstacleSet &set) const
return false;
}
if (level != ObstacleSet::EMapLevel::ANY && set.getLevel() != ObstacleSet::EMapLevel::ANY)
{
if (level != set.getLevel())
{
return false;
}
}
if (faction != FactionID::ANY)
{
auto factions = set.getFactions();
@ -117,6 +137,16 @@ void ObstacleSet::addTerrain(TerrainId terrain)
this->allowedTerrains.insert(terrain);
}
ObstacleSet::EMapLevel ObstacleSet::getLevel() const
{
return level;
}
void ObstacleSet::setLevel(ObstacleSet::EMapLevel newLevel)
{
level = newLevel;
}
std::set<FactionID> ObstacleSet::getFactions() const
{
return allowedFactions;
@ -248,6 +278,22 @@ std::string ObstacleSet::toString() const
return OBSTACLE_TYPE_STRINGS.at(type);
}
ObstacleSet::EMapLevel ObstacleSet::levelFromString(const std::string &str)
{
static const std::map<std::string, EMapLevel> LEVEL_NAMES =
{
{"surface", SURFACE},
{"underground", UNDERGROUND}
};
if (LEVEL_NAMES.find(str) != LEVEL_NAMES.end())
{
return LEVEL_NAMES.at(str);
}
throw std::runtime_error("Invalid map level: " + str);
}
std::vector<ObstacleSet::EObstacleType> ObstacleSetFilter::getAllowedTypes() const
{
return allowedTypes;
@ -325,6 +371,12 @@ std::shared_ptr<ObstacleSet> ObstacleSetHandler::loadFromJson(const std::string
logMod->error("No terrain specified for obstacle set %s", name);
}
if (biome["level"].isString())
{
auto level = biome["level"].String();
os->setLevel(ObstacleSet::levelFromString(level));
}
auto handleFaction = [os, scope](const std::string & str)
{
VLC->identifiers()->requestIdentifier(scope, "faction", str, [os](si32 id)

View File

@ -37,6 +37,14 @@ public:
ANIMALS, // Living, or bones
OTHER // Crystals, shipwrecks, barrels, etc.
};
enum EMapLevel // TODO: Move somewhere to map definitions
{
ANY = -1,
SURFACE = 0,
UNDERGROUND = 1
};
ObstacleSet();
explicit ObstacleSet(EObstacleType type, TerrainId terrain);
@ -51,6 +59,8 @@ public:
void setTerrain(TerrainId terrain);
void setTerrains(const std::set<TerrainId> & terrains);
void addTerrain(TerrainId terrain);
EMapLevel getLevel() const;
void setLevel(EMapLevel level);
std::set<EAlignment> getAlignments() const;
void addAlignment(EAlignment alignment);
std::set<FactionID> getFactions() const;
@ -58,12 +68,14 @@ public:
static EObstacleType typeFromString(const std::string &str);
std::string toString() const;
static EMapLevel levelFromString(const std::string &str);
si32 id;
private:
EObstacleType type;
EMapLevel level;
std::set<TerrainId> allowedTerrains; // Empty means all terrains
std::set<FactionID> allowedFactions; // Empty means all factions
std::set<EAlignment> allowedAlignments; // Empty means all alignments
@ -75,8 +87,8 @@ using TObstacleTypes = std::vector<std::shared_ptr<ObstacleSet>>;
class DLL_LINKAGE ObstacleSetFilter
{
public:
ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain, FactionID faction, EAlignment alignment);
ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain, FactionID faction, EAlignment alignment);
ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain, ObstacleSet::EMapLevel level, FactionID faction, EAlignment alignment);
ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain, ObstacleSet::EMapLevel level, FactionID faction, EAlignment alignment);
bool filter(const ObstacleSet &set) const;
@ -93,6 +105,7 @@ private:
EAlignment alignment;
// TODO: Filter by faction, surface/underground, etc.
const TerrainId terrain;
ObstacleSet::EMapLevel level;
};
// TODO: Instantiate ObstacleSetHandler

View File

@ -12,12 +12,12 @@
VCMI_LIB_NAMESPACE_BEGIN
NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket, const std::shared_ptr<NetworkContext> & context)
NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket)
: socket(socket)
, context(context)
, listener(listener)
{
socket->set_option(boost::asio::ip::tcp::no_delay(true));
socket->set_option(boost::asio::socket_base::keep_alive(true));
// iOS throws exception on attempt to set buffer size
constexpr auto bufferSize = 4 * 1024 * 1024;
@ -43,32 +43,12 @@ NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, cons
void NetworkConnection::start()
{
heartbeat();
boost::asio::async_read(*socket,
readBuffer,
boost::asio::transfer_exactly(messageHeaderSize),
[self = shared_from_this()](const auto & ec, const auto & endpoint) { self->onHeaderReceived(ec); });
}
void NetworkConnection::heartbeat()
{
constexpr auto heartbeatInterval = std::chrono::seconds(10);
auto timer = std::make_shared<NetworkTimer>(*context, heartbeatInterval);
timer->async_wait( [self = shared_from_this(), timer](const auto & ec)
{
if (ec)
return;
if (!self->socket->is_open())
return;
self->sendPacket({});
self->heartbeat();
});
}
void NetworkConnection::onHeaderReceived(const boost::system::error_code & ecHeader)
{
if (ecHeader)
@ -91,7 +71,7 @@ void NetworkConnection::onHeaderReceived(const boost::system::error_code & ecHea
if (messageSize == 0)
{
//heartbeat package with no payload - wait for next packet
// Zero-sized packet. Strange, but safe to ignore. Start reading next packet
start();
return;
}
@ -124,13 +104,16 @@ void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, u
void NetworkConnection::sendPacket(const std::vector<std::byte> & message)
{
std::lock_guard<std::mutex> lock(writeMutex);
boost::system::error_code ec;
// create array with single element - boost::asio::buffer can be constructed from containers, but not from plain integer
std::array<uint32_t, 1> messageSize{static_cast<uint32_t>(message.size())};
boost::asio::write(*socket, boost::asio::buffer(messageSize), ec );
boost::asio::write(*socket, boost::asio::buffer(message), ec );
if (message.size() > 0)
boost::asio::write(*socket, boost::asio::buffer(message), ec );
//Note: ignoring error code, intended
}

View File

@ -19,17 +19,16 @@ class NetworkConnection : public INetworkConnection, public std::enable_shared_f
static const int messageMaxSize = 64 * 1024 * 1024; // arbitrary size to prevent potential massive allocation if we receive garbage input
std::shared_ptr<NetworkSocket> socket;
std::shared_ptr<NetworkContext> context;
std::mutex writeMutex;
NetworkBuffer readBuffer;
INetworkConnectionListener & listener;
void heartbeat();
void onHeaderReceived(const boost::system::error_code & ec);
void onPacketReceived(const boost::system::error_code & ec, uint32_t expectedPacketSize);
public:
NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket, const std::shared_ptr<NetworkContext> & context);
NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket);
void start();
void close() override;

View File

@ -34,14 +34,14 @@ void NetworkHandler::connectToRemote(INetworkClientListener & listener, const st
auto socket = std::make_shared<NetworkSocket>(*io);
boost::asio::ip::tcp::resolver resolver(*io);
auto endpoints = resolver.resolve(host, std::to_string(port));
boost::asio::async_connect(*socket, endpoints, [this, socket, &listener](const boost::system::error_code& error, const boost::asio::ip::tcp::endpoint& endpoint)
boost::asio::async_connect(*socket, endpoints, [socket, &listener](const boost::system::error_code& error, const boost::asio::ip::tcp::endpoint& endpoint)
{
if (error)
{
listener.onConnectionFailed(error.message());
return;
}
auto connection = std::make_shared<NetworkConnection>(listener, socket, io);
auto connection = std::make_shared<NetworkConnection>(listener, socket);
connection->start();
listener.onConnectionEstablished(connection);

View File

@ -39,7 +39,7 @@ void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingCo
}
logNetwork->info("We got a new connection! :)");
auto connection = std::make_shared<NetworkConnection>(*this, upcomingConnection, io);
auto connection = std::make_shared<NetworkConnection>(*this, upcomingConnection);
connections.insert(connection);
connection->start();
listener.onNewConnection(connection);

View File

@ -30,7 +30,7 @@ struct DLL_LINKAGE CPack
template <typename Handler> void serialize(Handler &h)
{
logNetwork->error("CPack serialized... this should not happen!");
assert(false && "CPack serialized");
throw std::runtime_error("CPack serialized... this should not happen!");
}
void applyGs(CGameState * gs)

View File

@ -130,6 +130,7 @@ std::unique_ptr<CMap> CMapGenerator::generate()
catch (rmgException &e)
{
logGlobal->error("Random map generation received exception: %s", e.what());
throw;
}
Load::Progress::finish();
return std::move(map->mapInstance);

View File

@ -21,21 +21,32 @@ Point2D Point2D::operator * (float scale) const
return Point2D(x() * scale, y() * scale);
}
Point2D Point2D::operator / (float scale) const
{
return Point2D(x() / scale, y() / scale);
}
Point2D Point2D::operator + (const Point2D& other) const
{
return Point2D(x() + other.x(), y() + other.y());
}
Point2D Point2D::operator - (const Point2D& other) const
{
return Point2D(x() - other.x(), y() - other.y());
}
bool Point2D::operator < (const Point2D& other) const
{
if (x() < other.x())
{
return true;
}
else
{
return y() < other.y();
}
if (x() != other.x())
return x() < other.x();
return y() < other.y();
}
bool Point2D::operator == (const Point2D& other) const
{
return vstd::isAlmostEqual(x(), other.x()) && vstd::isAlmostEqual(y(), other.y());
}
std::string Point2D::toString() const
@ -163,6 +174,16 @@ std::set<Point2D> PenroseTiling::generatePenroseTiling(size_t numZones, CRandomG
split(t, points, indices, DEPTH);
}
// Remove duplicates
vstd::unique(points);
// Shift center to (0.5, 0.5)
for (auto & point : points)
{
point = point + Point2D(0.5f, 0.5f);
};
// For 8XM8 map, only 650 out of 15971 points are in the range
vstd::copy_if(points, vstd::set_inserter(finalPoints), [](const Point2D point)
{

View File

@ -28,10 +28,13 @@ public:
using point_xy::point_xy;
Point2D operator * (float scale) const;
Point2D operator / (float scale) const;
Point2D operator + (const Point2D& other) const;
Point2D operator - (const Point2D& other) const;
Point2D rotated(float radians) const;
bool operator < (const Point2D& other) const;
bool operator == (const Point2D& other) const;
std::string toString() const;
};

View File

@ -38,7 +38,11 @@ void ObstaclePlacer::process()
auto faction = zone.getTownType().toFaction();
ObstacleSetFilter filter(ObstacleSet::EObstacleType::INVALID, zone.getTerrainType(), faction->getId(), faction->alignment);
ObstacleSetFilter filter(ObstacleSet::EObstacleType::INVALID,
zone.getTerrainType(),
static_cast<ObstacleSet::EMapLevel>(zone.isUnderground()),
faction->getId(),
faction->alignment);
if (!prepareBiome(filter, zone.getRand()))
{

View File

@ -439,7 +439,7 @@ void DimensionDoorMechanics::endCast(SpellCastEnvironment * env, const Adventure
const TerrainTile * curr = env->getCb()->getTile(casterPosition);
if(dest->isClear(curr))
env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(parameters.pos), true);
env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(parameters.pos), EMovementMode::DIMENSION_DOOR);
}
///TownPortalMechanics
@ -567,7 +567,7 @@ void TownPortalMechanics::endCast(SpellCastEnvironment * env, const AdventureSpe
destination = dynamic_cast<const CGTownInstance*>(topObj);
}
if(env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(destination->visitablePos()), true))
if(env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(destination->visitablePos()), EMovementMode::TOWN_PORTAL))
{
SetMovePoints smp;
smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());

View File

@ -59,7 +59,7 @@ public:
virtual const CMap * getMap() const = 0;
virtual const CGameInfoCallback * getCb() const = 0;
virtual bool moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) = 0; //TODO: remove
virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) = 0; //TODO: remove
virtual void genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented
};

File diff suppressed because it is too large Load Diff

View File

@ -543,7 +543,7 @@ void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTrack
{
if (si->seedToBeUsed == 0)
{
si->seedToBeUsed = static_cast<ui32>(std::time(nullptr));
si->seedToBeUsed = CRandomGenerator::getDefault().nextInt();
}
CMapService mapService;
gs = new CGameState();
@ -1073,11 +1073,11 @@ bool CGameHandler::removeObject(const CGObjectInstance * obj, const PlayerColor
return true;
}
bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit, PlayerColor asker)
bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit, PlayerColor asker)
{
const CGHeroInstance *h = getHero(hid);
// not turn of that hero or player can't simply teleport hero (at least not with this function)
if(!h || (asker != PlayerColor::NEUTRAL && teleporting))
if(!h || (asker != PlayerColor::NEUTRAL && movementMode != EMovementMode::STANDARD))
{
if(h && getStartInfo()->turnTimerInfo.isEnabled() && gs->players[h->getOwner()].turnTimer.turnTimer == 0)
return true; //timer expired, no error
@ -1164,13 +1164,13 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
if(h->boat && h->boat->layer == EPathfindingLayer::SAIL && t.terType->isLand() && t.blocked)
return complainRet("Cannot disembark hero, tile is blocked!");
if(distance(h->pos, dst) >= 1.5 && !teleporting)
if(distance(h->pos, dst) >= 1.5 && movementMode == EMovementMode::STANDARD)
return complainRet("Tiles are not neighboring!");
if(h->inTownGarrison)
return complainRet("Can not move garrisoned hero!");
if(h->movementPointsRemaining() < cost && dst != h->pos && !teleporting)
if(h->movementPointsRemaining() < cost && dst != h->pos && movementMode == EMovementMode::STANDARD)
return complainRet("Hero doesn't have any movement points left!");
if (transit && !canFly && !(canWalkOnSea && t.terType->isWater()) && !CGTeleport::isTeleport(objectToVisit))
@ -1259,12 +1259,12 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
return doMove(TryMoveHero::DISEMBARK, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE);
}
if (teleporting)
if (movementMode != EMovementMode::STANDARD)
{
if (blockingVisit()) // e.g. hero on the other side of teleporter
return true;
EGuardLook guardsCheck = VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS)
EGuardLook guardsCheck = (VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS) && movementMode == EMovementMode::DIMENSION_DOOR)
? CHECK_FOR_GUARDS
: IGNORE_GUARDS;
@ -1337,7 +1337,7 @@ bool CGameHandler::teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui
return false;
int3 pos = h->convertFromVisitablePos(t->visitablePos());
moveHero(hid,pos,1);
moveHero(hid,pos,EMovementMode::CASTLE_GATE);
return true;
}

View File

@ -140,7 +140,7 @@ public:
void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr) override; //use hero=nullptr for no hero
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) override; //if any of armies is hero, hero will be used
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) override; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override;
bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override;
void giveHeroBonus(GiveBonus * bonus) override;
void setMovePoints(SetMovePoints * smp) override;
void setMovePoints(ObjectInstanceID hid, int val, bool absolute) override;

View File

@ -65,7 +65,7 @@ void ApplyGhNetPackVisitor::visitMoveHero(MoveHero & pack)
for (auto const & dest : pack.path)
{
if (!gh.moveHero(pack.hid, dest, 0, pack.transit, pack.player))
if (!gh.moveHero(pack.hid, dest, EMovementMode::STANDARD, pack.transit, pack.player))
{
result = false;
return;

View File

@ -89,9 +89,9 @@ const CMap * ServerSpellCastEnvironment::getMap() const
return gh->gameState()->map;
}
bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, bool teleporting)
bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode)
{
return gh->moveHero(hid, dst, teleporting, false);
return gh->moveHero(hid, dst, mode, false);
}
void ServerSpellCastEnvironment::genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback)

View File

@ -36,7 +36,7 @@ public:
const CMap * getMap() const override;
const CGameInfoCallback * getCb() const override;
bool moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) override;
bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) override;
void genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) override;
private:
CGameHandler * gh;

View File

@ -121,7 +121,7 @@ public:
return gameState.get();
}
bool moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) override
bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode) override
{
return false;
}

View File

@ -77,7 +77,7 @@ public:
void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr) override {} //use hero=nullptr for no hero
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) override {} //if any of armies is hero, hero will be used
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) override {} //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;}
bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;}
bool swapGarrisonOnSiege(ObjectInstanceID tid) override {return false;}
void giveHeroBonus(GiveBonus * bonus) override {}
void setMovePoints(SetMovePoints * smp) override {}