mirror of
https://github.com/vcmi/vcmi.git
synced 2025-03-27 21:49:10 +02:00
commit
17b2ce2c81
25
ChangeLog.md
25
ChangeLog.md
@ -1,3 +1,28 @@
|
||||
# 1.4.0 -> 1.4.1
|
||||
|
||||
### General
|
||||
* Fixed position for interaction with starting heroes
|
||||
* Fixed smooth map scrolling when running at high framerate
|
||||
* Fixed calculation of Fire Shield damage when caster has artifacts that increase its damage
|
||||
* Fixed untranslated message when visiting signs with random text
|
||||
* Fixed slider scrolling to maximum value when clicking on "scroll right" button
|
||||
* Fixed events and seer huts not activating in some cases
|
||||
* Fixed bug leading to Artifact Merchant selling Grails in loaded saved games
|
||||
* Fixed placement of objects in random maps near the top border of the map
|
||||
* Creatures under Slayer spell will no longer deal additional damage to creatures not affected by Slayer
|
||||
* Description of a mod in Launcher will no longer be converted to lower-case
|
||||
* Game will no longer fail to generate random map when AI-only players option is set to non-zero value
|
||||
* Added option to mute audio when VCMI window is not active
|
||||
* Added option to disable smooth map scrolling
|
||||
* Reverted ban on U-turns in pathfinder
|
||||
|
||||
### Stability
|
||||
* Fixed crash on using mods made for VCMI 1.3
|
||||
* Fixed crash on generating random map with large number of monoliths
|
||||
* Fixed crash on losing mission-critical hero in battle
|
||||
* Fixed crash on generating growth detalization in some localizations
|
||||
* Fixed crash on loading of some user-made maps
|
||||
|
||||
# 1.3.2 -> 1.4.0
|
||||
|
||||
### General
|
||||
|
@ -114,6 +114,8 @@
|
||||
"vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Interface Enhancements}\n\nToggle various quality of life interface improvements. Such as a backpack button etc. Disable to have a more classic experience.",
|
||||
"vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Large Spell Book",
|
||||
"vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Large Spell Book}\n\nEnables larger spell book that fits more spells per page. Spell book page change animation does not work with this setting enabled.",
|
||||
"vcmi.systemOptions.audioMuteFocus.hover" : "Mute on inactivity",
|
||||
"vcmi.systemOptions.audioMuteFocus.help" : "{Mute on inactivity}\n\nMute audio on inactive window focus. Exceptions are ingame messages and new turn sound.",
|
||||
|
||||
"vcmi.adventureOptions.infoBarPick.hover" : "Show Messages in Info Panel",
|
||||
"vcmi.adventureOptions.infoBarPick.help" : "{Show Messages in Info Panel}\n\nWhenever possible, game messages from visiting map objects will be shown in the info panel, instead of popping up in a separate window.",
|
||||
@ -129,6 +131,8 @@
|
||||
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Info Panel Creature Management}\n\nAllows rearranging creatures in info panel instead of cycling between default components.",
|
||||
"vcmi.adventureOptions.leftButtonDrag.hover" : "Left Click Drag Map",
|
||||
"vcmi.adventureOptions.leftButtonDrag.help" : "{Left Click Drag Map}\n\nWhen enabled, moving mouse with left button pressed will drag adventure map view.",
|
||||
"vcmi.adventureOptions.smoothDragging.hover" : "Smooth Map Dragging",
|
||||
"vcmi.adventureOptions.smoothDragging.help" : "{Smooth Map Dragging}\n\nWhen enabled, map dragging has a modern run out effect.",
|
||||
"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
|
||||
"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
|
||||
"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
|
||||
@ -258,7 +262,7 @@
|
||||
"vcmi.optionsTab.simturnsMin.help" : "Play simultaneously for specified number of days. Contacts between players during this period are blocked",
|
||||
"vcmi.optionsTab.simturnsMax.help" : "Play simultaneously for specified number of days or until contact with another player",
|
||||
"vcmi.optionsTab.simturnsAI.help" : "{Simultaneous AI Turns}\nExperimental option. Allows AI players to act at the same time as human player when simultaneous turns are enabled.",
|
||||
|
||||
|
||||
"vcmi.optionsTab.turnTime.select" : "Select turn timer preset",
|
||||
"vcmi.optionsTab.turnTime.unlimited" : "Unlimited turn time",
|
||||
"vcmi.optionsTab.turnTime.classic.1" : "Classic timer: 1 minute",
|
||||
|
@ -114,6 +114,8 @@
|
||||
"vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Interface Verbesserungen}\n\nSchaltet verschiedene Interface Verbesserungen um. Wie z.B. ein Rucksack-Button, etc. Deaktivieren, um ein klassischeres Erlebnis zu haben.",
|
||||
"vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Großes Zauberbuch",
|
||||
"vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Großes Zauberbuch}\n\nErmöglicht ein größeres Zauberbuch, in das mehr Zaubersprüche pro Seite passen. Die Animation des Seitenwechsels im Zauberbuch funktioniert nicht, wenn diese Einstellung aktiviert ist.",
|
||||
"vcmi.systemOptions.audioMuteFocus.hover" : "Stumm bei Inaktivität",
|
||||
"vcmi.systemOptions.audioMuteFocus.help" : "{Stumm bei Inaktivität}\n\nSchaltet Audio bei inaktiven Fenster-Fokus stumm. Ausnahmen sind Ingame-Nachrichten und der Neuer-Zug-Sound.",
|
||||
|
||||
"vcmi.adventureOptions.infoBarPick.hover" : "Meldungen im Infobereich anzeigen",
|
||||
"vcmi.adventureOptions.infoBarPick.help" : "{Meldungen im Infobereich anzeigen}\n\nWann immer möglich, werden Spielnachrichten von besuchten Kartenobjekten in der Infoleiste angezeigt, anstatt als Popup-Fenster zu erscheinen",
|
||||
@ -129,6 +131,8 @@
|
||||
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Info-Panel Kreaturenmanagement}\n\nErmöglicht die Neuanordnung von Kreaturen im Info-Panel, anstatt zwischen den Standardkomponenten zu wechseln",
|
||||
"vcmi.adventureOptions.leftButtonDrag.hover" : "Ziehen der Karte mit Links",
|
||||
"vcmi.adventureOptions.leftButtonDrag.help" : "{Ziehen der Karte mit Links}\n\nWenn aktiviert, wird die Maus bei gedrückter linker Taste in die Kartenansicht gezogen",
|
||||
"vcmi.adventureOptions.smoothDragging.hover" : "Nahtloses Ziehen der Karte",
|
||||
"vcmi.adventureOptions.smoothDragging.help" : "{Nahtloses Ziehen der Karte}\n\nWenn aktiviert hat das Ziehen der Karte einen sanften Auslaufeffekt.",
|
||||
"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
|
||||
"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
|
||||
"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
|
||||
|
@ -114,6 +114,8 @@
|
||||
"vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Розширення інтерфейсу}\n\nУвімкніть різні розширення інтерфейсу для покращення якості життя. Наприклад, більша книга заклинань, рюкзак тощо. Вимкнути, щоб отримати більш класичний досвід.",
|
||||
"vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Велика книга заклять",
|
||||
"vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Велика книга заклять}\n\nВмикає більшу книгу заклять, яка вміщує більше заклять на сторінці. Якщо цей параметр увімкнено, анімація зміни сторінок книги заклять не буде відображатися.",
|
||||
"vcmi.systemOptions.audioMuteFocus.hover" : "Тиша при втраті фокусу",
|
||||
"vcmi.systemOptions.audioMuteFocus.help" : "{Тиша при втраті фокусу}\n\nВимкнути звук коли вікно не у фокусі. Виняток становлять ігрові сповіщення та звук нового ходу.",
|
||||
|
||||
"vcmi.adventureOptions.infoBarPick.help" : "{Повідомлення у панелі статусу}\n\nЗа можливості, повідомлення про відвідування об'єктів карти пригод будуть відображені у панелі статусу замість окремого вікна",
|
||||
"vcmi.adventureOptions.infoBarPick.hover" : "Повідомлення у панелі статусу",
|
||||
@ -129,6 +131,8 @@
|
||||
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Керування істотами у вікні статусу}\n\nДозволяє впорядковувати істот у вікні статусу замість циклічного перемикання між типовими компонентами",
|
||||
"vcmi.adventureOptions.leftButtonDrag.hover" : "Переміщення мапи лівою кнопкою",
|
||||
"vcmi.adventureOptions.leftButtonDrag.help" : "{Переміщення мапи лівою кнопкою}\n\nЯкщо увімкнено, переміщення миші з натиснутою лівою кнопкою буде перетягувати мапу пригод",
|
||||
"vcmi.adventureOptions.smoothDragging.hover" : "Плавне перетягування мапи",
|
||||
"vcmi.adventureOptions.smoothDragging.help" : "{Плавне перетягування мапи}\n\nЯкщо увімкнено, перетягування мапи має сучасний ефект завершення.",
|
||||
"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
|
||||
"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
|
||||
"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
|
||||
|
@ -98,7 +98,7 @@
|
||||
]
|
||||
},
|
||||
|
||||
"version" : "1.3",
|
||||
"version" : "1.4",
|
||||
"author" : "VCMI Team",
|
||||
"contact" : "http://forum.vcmi.eu/index.php",
|
||||
"modType" : "Graphical",
|
||||
|
@ -10,8 +10,8 @@ android {
|
||||
applicationId "is.xyz.vcmi"
|
||||
minSdk 19
|
||||
targetSdk 33
|
||||
versionCode 1400
|
||||
versionName "1.4.0"
|
||||
versionCode 1410
|
||||
versionName "1.4.1"
|
||||
setProperty("archivesBaseName", "vcmi")
|
||||
}
|
||||
|
||||
|
@ -170,7 +170,7 @@ void InputHandler::preprocessEvent(const SDL_Event & ev)
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
{
|
||||
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
|
||||
if(settings["general"]["enableUiEnhancements"].Bool()) {
|
||||
if(settings["general"]["audioMuteFocus"].Bool()) {
|
||||
CCS->musich->setVolume(settings["general"]["music"].Integer());
|
||||
CCS->soundh->setVolume(settings["general"]["sound"].Integer());
|
||||
}
|
||||
@ -179,7 +179,7 @@ void InputHandler::preprocessEvent(const SDL_Event & ev)
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
{
|
||||
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
|
||||
if(settings["general"]["enableUiEnhancements"].Bool()) {
|
||||
if(settings["general"]["audioMuteFocus"].Bool()) {
|
||||
CCS->musich->setVolume(0);
|
||||
CCS->soundh->setVolume(0);
|
||||
}
|
||||
|
@ -122,7 +122,10 @@ void MapView::onMapLevelSwitched()
|
||||
void MapView::onMapScrolled(const Point & distance)
|
||||
{
|
||||
if(!isGesturing())
|
||||
{
|
||||
postSwipeSpeed = 0.0;
|
||||
controller->setViewCenter(model->getMapViewCenter() + distance, model->getLevel());
|
||||
}
|
||||
}
|
||||
|
||||
void MapView::onMapSwiped(const Point & viewPosition)
|
||||
|
@ -156,6 +156,11 @@ void CSlider::clickPressed(const Point & cursorPosition)
|
||||
|
||||
bool CSlider::receiveEvent(const Point &position, int eventType) const
|
||||
{
|
||||
if (eventType == LCLICK)
|
||||
{
|
||||
return pos.isInside(position) && !left->pos.isInside(position) && !right->pos.isInside(position);
|
||||
}
|
||||
|
||||
if(eventType != WHEEL && eventType != GESTURE)
|
||||
{
|
||||
return CIntObject::receiveEvent(position, eventType);
|
||||
|
@ -126,6 +126,10 @@ AdventureOptionsTab::AdventureOptionsTab()
|
||||
{
|
||||
return setBoolSetting("adventure", "leftButtonDrag", value);
|
||||
});
|
||||
addCallback("smoothDraggingChanged", [](bool value)
|
||||
{
|
||||
return setBoolSetting("adventure", "smoothDragging", value);
|
||||
});
|
||||
build(config);
|
||||
|
||||
std::shared_ptr<CToggleGroup> playerHeroSpeedToggle = widget<CToggleGroup>("heroMovementSpeedPicker");
|
||||
@ -164,4 +168,8 @@ AdventureOptionsTab::AdventureOptionsTab()
|
||||
std::shared_ptr<CToggleButton> leftButtonDragCheckbox = widget<CToggleButton>("leftButtonDragCheckbox");
|
||||
if (leftButtonDragCheckbox)
|
||||
leftButtonDragCheckbox->setSelected(settings["adventure"]["leftButtonDrag"].Bool());
|
||||
|
||||
std::shared_ptr<CToggleButton> smoothDraggingCheckbox = widget<CToggleButton>("smoothDraggingCheckbox");
|
||||
if (smoothDraggingCheckbox)
|
||||
smoothDraggingCheckbox->setSelected(settings["adventure"]["smoothDragging"].Bool());
|
||||
}
|
||||
|
@ -167,6 +167,11 @@ GeneralOptionsTab::GeneralOptionsTab()
|
||||
setBoolSetting("gameTweaks", "enableLargeSpellbook", value);
|
||||
});
|
||||
|
||||
addCallback("audioMuteFocusChanged", [](bool value)
|
||||
{
|
||||
setBoolSetting("general", "audioMuteFocus", value);
|
||||
});
|
||||
|
||||
//moved from "other" tab that is disabled for now to avoid excessible tabs with barely any content
|
||||
addCallback("availableCreaturesAsDwellingChanged", [=](int value)
|
||||
{
|
||||
@ -215,6 +220,10 @@ GeneralOptionsTab::GeneralOptionsTab()
|
||||
if (enableLargeSpellbookCheckbox)
|
||||
enableLargeSpellbookCheckbox->setSelected(settings["gameTweaks"]["enableLargeSpellbook"].Bool());
|
||||
|
||||
std::shared_ptr<CToggleButton> audioMuteFocusCheckbox = widget<CToggleButton>("audioMuteFocusCheckbox");
|
||||
if (audioMuteFocusCheckbox)
|
||||
audioMuteFocusCheckbox->setSelected(settings["general"]["audioMuteFocus"].Bool());
|
||||
|
||||
std::shared_ptr<CSlider> musicSlider = widget<CSlider>("musicSlider");
|
||||
musicSlider->scrollTo(CCS->musich->getVolume());
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
set(VCMI_VERSION_MAJOR 1)
|
||||
set(VCMI_VERSION_MINOR 4)
|
||||
set(VCMI_VERSION_PATCH 0)
|
||||
set(VCMI_VERSION_PATCH 1)
|
||||
add_definitions(
|
||||
-DVCMI_VERSION_MAJOR=${VCMI_VERSION_MAJOR}
|
||||
-DVCMI_VERSION_MINOR=${VCMI_VERSION_MINOR}
|
||||
|
@ -935,5 +935,16 @@
|
||||
"grassHills" : { "index" :208, "handler": "static", "types" : { "object" : { "index" : 0} } },
|
||||
"roughHills" : { "index" :209, "handler": "static", "types" : { "object" : { "index" : 0} } },
|
||||
"subterraneanRocks" : { "index" :210, "handler": "static", "types" : { "object" : { "index" : 0} } },
|
||||
"swampFoliage" : { "index" :211, "handler": "static", "types" : { "object" : { "index" : 0} } }
|
||||
"swampFoliage" : { "index" :211, "handler": "static", "types" : { "object" : { "index" : 0} } },
|
||||
|
||||
/// special object to handle invalid / unknown objects on some user-made maps
|
||||
"nothing" : {
|
||||
"index" : 0,
|
||||
"handler": "generic",
|
||||
"types" : {
|
||||
"nothing" : {
|
||||
"index" : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,8 @@
|
||||
"useSavePrefix",
|
||||
"savePrefix",
|
||||
"startTurnAutosave",
|
||||
"enableUiEnhancements"
|
||||
"enableUiEnhancements",
|
||||
"audioMuteFocus"
|
||||
],
|
||||
"properties" : {
|
||||
"playerName" : {
|
||||
@ -131,6 +132,10 @@
|
||||
"enableUiEnhancements" : {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"audioMuteFocus" : {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -347,6 +347,9 @@
|
||||
{
|
||||
"text": "vcmi.adventureOptions.leftButtonDrag.hover",
|
||||
"created" : "desktop"
|
||||
},
|
||||
{
|
||||
"text": "vcmi.adventureOptions.smoothDragging.hover"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -391,6 +394,11 @@
|
||||
"help": "vcmi.adventureOptions.leftButtonDrag",
|
||||
"callback": "leftButtonDragChanged",
|
||||
"created" : "desktop"
|
||||
},
|
||||
{
|
||||
"name": "smoothDraggingCheckbox",
|
||||
"help": "vcmi.adventureOptions.smoothDragging",
|
||||
"callback": "smoothDraggingChanged"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -180,6 +180,28 @@
|
||||
"type": "labelCentered",
|
||||
"position": {"x": 565, "y": 158}
|
||||
},
|
||||
{
|
||||
"type" : "verticalLayout",
|
||||
"customType" : "labelDescription",
|
||||
"position" : {"x": 415, "y": 202},
|
||||
"items" : [
|
||||
{
|
||||
"text": "vcmi.systemOptions.audioMuteFocus.hover"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type" : "verticalLayout",
|
||||
"customType" : "checkbox",
|
||||
"position" : {"x": 380, "y": 200},
|
||||
"items" : [
|
||||
{
|
||||
"name": "audioMuteFocusCheckbox",
|
||||
"help": "vcmi.systemOptions.audioMuteFocus",
|
||||
"callback": "audioMuteFocusChanged"
|
||||
}
|
||||
]
|
||||
},
|
||||
/////////////////////////////////////// Bottom section - Towns Settings
|
||||
{
|
||||
"type" : "verticalLayout",
|
||||
|
@ -222,7 +222,7 @@
|
||||
{
|
||||
"name": "chessFieldTurn",
|
||||
"callback": "parseAndSetTimer_turn",
|
||||
"help": "vcmi.optionsTab.chessFieldTurn.help"
|
||||
"help": "vcmi.optionsTab.chessFieldTurnAccumulate.help"
|
||||
},
|
||||
{
|
||||
"name": "chessFieldBattle",
|
||||
@ -232,7 +232,7 @@
|
||||
{
|
||||
"name": "chessFieldUnit",
|
||||
"callback": "parseAndSetTimer_unit",
|
||||
"help": "vcmi.optionsTab.chessFieldUnit.help"
|
||||
"help": "vcmi.optionsTab.chessFieldUnitAccumulate.help"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -1,3 +1,9 @@
|
||||
vcmi (1.4.1) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Ivan Savenko <saven.ivan@gmail.com> Tue, 12 Dec 2023 16:00:00 +0200
|
||||
|
||||
vcmi (1.4.0) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
@ -1,10 +1,19 @@
|
||||
[](https://github.com/vcmi/vcmi/actions/workflows/github.yml)
|
||||
[](https://github.com/vcmi/vcmi/releases/tag/1.4.0)
|
||||
[](https://github.com/vcmi/vcmi/releases/tag/1.4.1)
|
||||
[](https://github.com/vcmi/vcmi/releases)
|
||||
|
||||
# VCMI Project
|
||||
|
||||
VCMI is work-in-progress attempt to recreate engine for Heroes III, giving it new and extended possibilities.
|
||||
VCMI is an open-source recreation of Heroes of Might & Magic III engine, giving it new and extended possibilities.
|
||||
|
||||
<p>
|
||||
<img src="https://github.com/vcmi/VCMI.eu/blob/master/static/img/screenshots/1.3.0/Castle%20Siege.jpg?raw=true" alt="Vanilla town siege in extended window" style="height:120px;"/>
|
||||
<img src="https://github.com/vcmi/VCMI.eu/blob/master/static/img/screenshots/1.3.0/Town%20Screen%20with%20Radial%20Menu.jpg?raw=true" alt="Vanilla town view with radial menu for touchscreen devices" style="height:120px;"/>
|
||||
<img src="https://github.com/vcmi/VCMI.eu/blob/master/static/img/screenshots/1.4.0/Big%20spellbook.jpg?raw=true" alt="Large Spellbook with German translation" style="height:120px;"/>
|
||||
<img src="https://github.com/vcmi/VCMI.eu/blob/master/static/img/screenshots/1.4.0/Quick%20Hero%20Select%20Bastion.jpg?raw=true" alt="New widget for Hero selection, featuring Pavillon Town" style="height:120px;"/>
|
||||
</p>
|
||||
|
||||
|
||||
## Links
|
||||
|
||||
@ -27,6 +36,13 @@ Please see corresponding installation guide articles for details for your platfo
|
||||
- [Android](players/Installation_Android.md)
|
||||
- [iOS](players/Installation_iOS.md)
|
||||
|
||||
<p>
|
||||
<img src="https://github.com/vcmi/VCMI.eu/blob/master/static/img/screenshots/1.4.0/Antagarich%20Burning%20Battle.jpg?raw=true" alt="Forge Town in battle" style="height:120px;"/>
|
||||
<img src="https://github.com/vcmi/VCMI.eu/blob/master/static/img/screenshots/1.4.0/Town%20and%20Unit.jpg?raw=true" alt="Asylum town with new creature dialog" style="height:120px;"/>
|
||||
<img src="https://github.com/vcmi/VCMI.eu/blob/master/static/img/screenshots/1.4.0/Siege.jpg?raw=true" alt="Ruins town siege" style="height:120px;"/>
|
||||
<img src="https://github.com/vcmi/VCMI.eu/blob/master/static/img/screenshots/1.4.0/Editor.jpg?raw=true" alt="Map editor" style="height:120px;"/>
|
||||
</p>
|
||||
|
||||
## Documentation and guidelines for players
|
||||
|
||||
- [General information about VCMI Project](players/Manual.md)
|
||||
|
@ -68,6 +68,7 @@
|
||||
<category>StrategyGame</category>
|
||||
</categories>
|
||||
<releases>
|
||||
<release version="1.4.1" date="2023-12-12" />
|
||||
<release version="1.4.0" date="2023-12-08" />
|
||||
<release version="1.3.2" date="2023-09-15" />
|
||||
<release version="1.3.1" date="2023-08-18" />
|
||||
|
@ -313,7 +313,7 @@ QString CModListView::genModInfoText(CModEntry & mod)
|
||||
|
||||
result += replaceIfNotEmpty(getModNames(mod.getDependencies()), lineTemplate.arg(tr("Required mods")));
|
||||
result += replaceIfNotEmpty(getModNames(mod.getConflicts()), lineTemplate.arg(tr("Conflicting mods")));
|
||||
result += replaceIfNotEmpty(getModNames(mod.getValue("description").toStringList()), textTemplate.arg(tr("Description")));
|
||||
result += replaceIfNotEmpty(mod.getValue("description"), textTemplate.arg(tr("Description")));
|
||||
|
||||
result += "<p></p>"; // to get some empty space
|
||||
|
||||
|
@ -625,7 +625,7 @@ void CArtHandler::makeItCommanderArt(CArtifact * a, bool onlyCommander)
|
||||
a->possibleSlots[ArtBearer::COMMANDER].push_back(ArtifactPosition(slot));
|
||||
}
|
||||
|
||||
bool CArtHandler::legalArtifact(const ArtifactID & id)
|
||||
bool CArtHandler::legalArtifact(const ArtifactID & id) const
|
||||
{
|
||||
auto art = id.toArtifact();
|
||||
//assert ( (!art->constituents) || art->constituents->size() ); //artifacts is not combined or has some components
|
||||
@ -648,18 +648,6 @@ bool CArtHandler::legalArtifact(const ArtifactID & id)
|
||||
return false;
|
||||
}
|
||||
|
||||
void CArtHandler::initAllowedArtifactsList(const std::set<ArtifactID> & allowed)
|
||||
{
|
||||
allowedArtifacts.clear();
|
||||
|
||||
for (ArtifactID i : allowed)
|
||||
{
|
||||
if (legalArtifact(ArtifactID(i)))
|
||||
allowedArtifacts.push_back(i.toArtifact());
|
||||
//keep im mind that artifact can be worn by more than one type of bearer
|
||||
}
|
||||
}
|
||||
|
||||
std::set<ArtifactID> CArtHandler::getDefaultAllowed() const
|
||||
{
|
||||
std::set<ArtifactID> allowedArtifacts;
|
||||
|
@ -141,15 +141,11 @@ public:
|
||||
class DLL_LINKAGE CArtHandler : public CHandlerBase<ArtifactID, Artifact, CArtifact, ArtifactService>
|
||||
{
|
||||
public:
|
||||
/// List of artifacts allowed on the map
|
||||
std::vector<const CArtifact *> allowedArtifacts;
|
||||
|
||||
void addBonuses(CArtifact *art, const JsonNode &bonusList);
|
||||
|
||||
static CArtifact::EartClass stringToClass(const std::string & className); //TODO: rework EartClass to make this a constructor
|
||||
|
||||
bool legalArtifact(const ArtifactID & id);
|
||||
void initAllowedArtifactsList(const std::set<ArtifactID> & allowed);
|
||||
bool legalArtifact(const ArtifactID & id) const;
|
||||
static void makeItCreatureArt(CArtifact * a, bool onlyCreature = true);
|
||||
static void makeItCommanderArt(CArtifact * a, bool onlyCommander = true);
|
||||
|
||||
|
@ -384,8 +384,9 @@ namespace JsonRandom
|
||||
ArtifactID loadArtifact(const JsonNode & value, CRandomGenerator & rng, const Variables & variables)
|
||||
{
|
||||
std::set<ArtifactID> allowedArts;
|
||||
for (auto const * artifact : VLC->arth->allowedArtifacts)
|
||||
allowedArts.insert(artifact->getId());
|
||||
for(const auto & artifact : VLC->arth->objects)
|
||||
if (IObjectInterface::cb->isAllowed(artifact->getId()) && VLC->arth->legalArtifact(artifact->getId()))
|
||||
allowedArts.insert(artifact->getId());
|
||||
|
||||
std::set<ArtifactID> potentialPicks = filterKeys(value, allowedArts, variables);
|
||||
|
||||
|
@ -168,7 +168,10 @@ DLL_LINKAGE std::string MetaString::toString() const
|
||||
boost::replace_first(dst, "%d", std::to_string(numbers[nums++]));
|
||||
break;
|
||||
case EMessage::REPLACE_POSITIVE_NUMBER:
|
||||
boost::replace_first(dst, "%+d", '+' + std::to_string(numbers[nums++]));
|
||||
if (dst.find("%+d") != std::string::npos)
|
||||
boost::replace_first(dst, "%+d", '+' + std::to_string(numbers[nums++]));
|
||||
else
|
||||
boost::replace_first(dst, "%d", std::to_string(numbers[nums++]));
|
||||
break;
|
||||
default:
|
||||
logGlobal->error("MetaString processing error! Received message of type %d", static_cast<int>(elem));
|
||||
|
@ -132,6 +132,9 @@ int DamageCalculator::getActorAttackSlayer() const
|
||||
const std::string cachingStrSlayer = "type_SLAYER";
|
||||
static const auto selectorSlayer = Selector::type()(BonusType::SLAYER);
|
||||
|
||||
if (!info.defender->hasBonusOfType(BonusType::KING))
|
||||
return 0;
|
||||
|
||||
auto slayerEffects = info.attacker->getBonuses(selectorSlayer, cachingStrSlayer);
|
||||
auto slayerAffected = info.defender->unitType()->valOfBonuses(Selector::type()(BonusType::KING));
|
||||
|
||||
|
@ -195,7 +195,6 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, Load::Prog
|
||||
logGlobal->error("Wrong mode: %d", static_cast<int>(scenarioOps->mode));
|
||||
return;
|
||||
}
|
||||
VLC->arth->initAllowedArtifactsList(map->allowedArtifact);
|
||||
logGlobal->info("Map loaded!");
|
||||
|
||||
checkMapChecksum();
|
||||
@ -1444,18 +1443,22 @@ bool CGameState::checkForVictory(const PlayerColor & player, const EventConditio
|
||||
{
|
||||
// list of players that need to control object to fulfull condition
|
||||
// NOTE: cgameinfocallback specified explicitly in order to get const version
|
||||
const auto & team = CGameInfoCallback::getPlayerTeam(player)->players;
|
||||
const auto * team = CGameInfoCallback::getPlayerTeam(player);
|
||||
|
||||
if (condition.objectID != ObjectInstanceID::NONE) // mode A - flag one specific object, like town
|
||||
{
|
||||
return team.count(getObjInstance(condition.objectID)->tempOwner) != 0;
|
||||
const auto * object = getObjInstance(condition.objectID);
|
||||
|
||||
if (!object)
|
||||
return false;
|
||||
return team->players.count(object->getOwner()) != 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(const auto & elem : map->objects) // mode B - flag all objects of this type
|
||||
{
|
||||
//check not flagged objs
|
||||
if ( elem && elem->ID == condition.objectType.as<MapObjectID>() && team.count(elem->tempOwner) == 0 )
|
||||
if ( elem && elem->ID == condition.objectType.as<MapObjectID>() && team->players.count(elem->getOwner()) == 0 )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -1943,8 +1946,13 @@ ArtifactID CGameState::pickRandomArtifact(CRandomGenerator & rand, int flags, st
|
||||
std::set<ArtifactID> potentialPicks;
|
||||
|
||||
// Select artifacts that satisfy provided criterias
|
||||
for (auto const * artifact : VLC->arth->allowedArtifacts)
|
||||
for (auto const & artifactID : map->allowedArtifact)
|
||||
{
|
||||
if (!VLC->arth->legalArtifact(artifactID))
|
||||
continue;
|
||||
|
||||
auto const * artifact = artifactID.toArtifact();
|
||||
|
||||
assert(artifact->aClass != CArtifact::ART_SPECIAL); // should be filtered out when allowedArtifacts is initialized
|
||||
|
||||
if ((flags & CArtifact::ART_TREASURE) == 0 && artifact->aClass == CArtifact::ART_TREASURE)
|
||||
|
@ -312,6 +312,9 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(MapObjectID type, MapObj
|
||||
{
|
||||
try
|
||||
{
|
||||
if (objects.at(type.getNum()) == nullptr)
|
||||
return objects.front()->objects.front();
|
||||
|
||||
auto result = objects.at(type.getNum())->objects.at(subtype.getNum());
|
||||
|
||||
if (result != nullptr)
|
||||
@ -451,11 +454,14 @@ void CObjectClassesHandler::generateExtraMonolithsForRMG(ObjectClass * container
|
||||
newPortal->subTypeName = std::string("monolith") + std::to_string(portalVec.size());
|
||||
newPortal->type = portal->getIndex();
|
||||
|
||||
newPortal->subtype = portalVec.size(); //indexes must be unique, they are returned as a set
|
||||
// Inconsintent original indexing: monolith1 has index 0
|
||||
newPortal->subtype = portalVec.size() - 1; //indexes must be unique, they are returned as a set
|
||||
newPortal->blockVisit = portal->blockVisit;
|
||||
newPortal->removable = portal->removable;
|
||||
|
||||
portalVec.push_back(newPortal);
|
||||
|
||||
registerObject(ModScope::scopeGame(), container->getJsonKey(), newPortal->subTypeName, newPortal->subtype);
|
||||
registerObject(newPortal->modScope, container->getJsonKey(), newPortal->subTypeName, newPortal->subtype);
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,8 +470,11 @@ std::string CObjectClassesHandler::getObjectName(MapObjectID type, MapObjectSubI
|
||||
const auto handler = getHandlerFor(type, subtype);
|
||||
if (handler && handler->hasNameTextID())
|
||||
return handler->getNameTranslated();
|
||||
else
|
||||
|
||||
if (objects[type.getNum()])
|
||||
return objects[type.getNum()]->getNameTranslated();
|
||||
|
||||
return objects.front()->getNameTranslated();
|
||||
}
|
||||
|
||||
SObjectSounds CObjectClassesHandler::getObjectSounds(MapObjectID type, MapObjectSubID subtype) const
|
||||
@ -477,19 +486,27 @@ SObjectSounds CObjectClassesHandler::getObjectSounds(MapObjectID type, MapObject
|
||||
if(type == Obj::PRISON || type == Obj::HERO || type == Obj::SPELL_SCROLL)
|
||||
subtype = 0;
|
||||
|
||||
assert(objects[type.getNum()]);
|
||||
|
||||
return getHandlerFor(type, subtype)->getSounds();
|
||||
if(objects[type.getNum()])
|
||||
return getHandlerFor(type, subtype)->getSounds();
|
||||
else
|
||||
return objects.front()->objects.front()->getSounds();
|
||||
}
|
||||
|
||||
std::string CObjectClassesHandler::getObjectHandlerName(MapObjectID type) const
|
||||
{
|
||||
return objects.at(type.getNum())->handlerName;
|
||||
if (objects.at(type.getNum()))
|
||||
return objects.at(type.getNum())->handlerName;
|
||||
else
|
||||
return objects.front()->handlerName;
|
||||
}
|
||||
|
||||
std::string CObjectClassesHandler::getJsonKey(MapObjectID type) const
|
||||
{
|
||||
return objects.at(type.getNum())->getJsonKey();
|
||||
if (objects.at(type.getNum()) != nullptr)
|
||||
return objects.at(type.getNum())->getJsonKey();
|
||||
|
||||
logGlobal->warn("Unknown object of type %d!", type);
|
||||
return objects.front()->getJsonKey();
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -133,7 +133,7 @@ void CHeroInstanceConstructor::afterLoadFinalization()
|
||||
{
|
||||
filters[entry.first] = LogicalExpression<HeroTypeID>(entry.second, [](const JsonNode & node)
|
||||
{
|
||||
return HeroTypeID(VLC->identifiers()->getIdentifier("hero", node.Vector()[0]).value());
|
||||
return HeroTypeID(VLC->identifiers()->getIdentifier("hero", node.Vector()[0]).value_or(-1));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -150,6 +150,11 @@ bool CGHeroInstance::isCoastVisitable() const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGHeroInstance::isBlockedVisitable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
BattleField CGHeroInstance::getBattlefield() const
|
||||
{
|
||||
return BattleField::NONE;
|
||||
@ -280,7 +285,6 @@ CGHeroInstance::CGHeroInstance():
|
||||
setNodeType(HERO);
|
||||
ID = Obj::HERO;
|
||||
secSkills.emplace_back(SecondarySkill::NONE, -1);
|
||||
blockVisit = true;
|
||||
}
|
||||
|
||||
PlayerColor CGHeroInstance::getOwner() const
|
||||
|
@ -301,6 +301,7 @@ public:
|
||||
void updateFrom(const JsonNode & data) override;
|
||||
|
||||
bool isCoastVisitable() const override;
|
||||
bool isBlockedVisitable() const override;
|
||||
BattleField getBattlefield() const override;
|
||||
protected:
|
||||
void setPropertyDer(ObjProperty what, ObjPropertyID identifier) override;//synchr
|
||||
|
@ -1224,12 +1224,21 @@ TerrainId CGTownInstance::getNativeTerrain() const
|
||||
GrowthInfo::Entry::Entry(const std::string &format, int _count)
|
||||
: count(_count)
|
||||
{
|
||||
description = boost::str(boost::format(format) % count);
|
||||
MetaString formatter;
|
||||
formatter.appendRawString(format);
|
||||
formatter.replacePositiveNumber(count);
|
||||
|
||||
description = formatter.toString();
|
||||
}
|
||||
|
||||
GrowthInfo::Entry::Entry(int subID, const BuildingID & building, int _count): count(_count)
|
||||
{
|
||||
description = boost::str(boost::format("%s %+d") % (*VLC->townh)[subID]->town->buildings.at(building)->getNameTranslated() % count);
|
||||
MetaString formatter;
|
||||
formatter.appendRawString("%s %+d");
|
||||
formatter.replaceRawString((*VLC->townh)[subID]->town->buildings.at(building)->getNameTranslated());
|
||||
formatter.replacePositiveNumber(count);
|
||||
|
||||
description = formatter.toString();
|
||||
}
|
||||
|
||||
GrowthInfo::Entry::Entry(int _count, std::string fullDescription):
|
||||
|
@ -325,7 +325,7 @@ void CQuest::defineQuestName()
|
||||
void CQuest::addKillTargetReplacements(MetaString &out) const
|
||||
{
|
||||
if(!heroName.empty())
|
||||
out.replaceTextID(heroName);
|
||||
out.replaceRawString(heroName);
|
||||
if(stackToKill != CreatureID::NONE)
|
||||
{
|
||||
out.replaceNamePlural(stackToKill);
|
||||
|
@ -59,7 +59,7 @@ std::vector<Component> CRewardableObject::loadComponents(const CGHeroInstance *
|
||||
if (rewardIndices.empty())
|
||||
return result;
|
||||
|
||||
if (configuration.selectMode != Rewardable::SELECT_FIRST)
|
||||
if (configuration.selectMode != Rewardable::SELECT_FIRST && rewardIndices.size() > 1)
|
||||
{
|
||||
for (auto index : rewardIndices)
|
||||
result.push_back(configuration.info.at(index).reward.getDisplayedComponent(contextHero));
|
||||
|
@ -937,7 +937,7 @@ void CGSignBottle::initObj(CRandomGenerator & rand)
|
||||
{
|
||||
auto vector = VLC->generaltexth->findStringsWithPrefix("core.randsign");
|
||||
std::string messageIdentifier = *RandomGeneratorUtil::nextItem(vector, rand);
|
||||
message.appendTextID(TextIdentifier("core", "randsign", messageIdentifier).get());
|
||||
message.appendTextID(messageIdentifier);
|
||||
}
|
||||
|
||||
if(ID == Obj::OCEAN_BOTTLE)
|
||||
|
@ -180,7 +180,7 @@ void ObjectTemplate::readTxt(CLegacyConfigParser & parser)
|
||||
|
||||
void ObjectTemplate::readMsk()
|
||||
{
|
||||
ResourcePath resID(animationFile.getName(), EResType::MASK);
|
||||
ResourcePath resID("Sprites/" + animationFile.getName(), EResType::MASK);
|
||||
|
||||
if (CResourceHandler::get()->existsResource(resID))
|
||||
{
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __UCLIBC__
|
||||
#if defined(__UCLIBC__) || defined(__FreeBSD__)
|
||||
#undef major
|
||||
#undef minor
|
||||
#undef patch
|
||||
|
@ -162,9 +162,6 @@ void CPathfinder::calculatePaths()
|
||||
if(neighbour->locked)
|
||||
continue;
|
||||
|
||||
if (source.node->theNodeBefore && source.node->theNodeBefore->coord == neighbour->coord )
|
||||
continue; // block U-turns
|
||||
|
||||
if(!hlp->isLayerAvailable(neighbour->layer))
|
||||
continue;
|
||||
|
||||
|
@ -100,6 +100,12 @@ std::vector<CGPathNode *> NodeStorage::calculateTeleportations(
|
||||
{
|
||||
auto * node = getNode(neighbour, source.node->layer);
|
||||
|
||||
if(!node->coord.valid())
|
||||
{
|
||||
logAi->debug("Teleportation exit is blocked " + neighbour.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
neighbours.push_back(node);
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ Rewardable::Limiter::Limiter()
|
||||
, heroLevel(-1)
|
||||
, manaPercentage(0)
|
||||
, manaPoints(0)
|
||||
, canLearnSkills(false)
|
||||
, primary(GameConstants::PRIMARY_SKILLS, 0)
|
||||
{
|
||||
}
|
||||
@ -45,6 +46,7 @@ bool operator==(const Rewardable::Limiter & l, const Rewardable::Limiter & r)
|
||||
&& l.manaPoints == r.manaPoints
|
||||
&& l.manaPercentage == r.manaPercentage
|
||||
&& l.secondary == r.secondary
|
||||
&& l.canLearnSkills == r.canLearnSkills
|
||||
&& l.creatures == r.creatures
|
||||
&& l.spells == r.spells
|
||||
&& l.artifacts == r.artifacts
|
||||
|
@ -580,7 +580,7 @@ void CMapGenOptions::finalize(CRandomGenerator & rand)
|
||||
}
|
||||
logGlobal->trace("Player %d: %s", player.second.getColor(), playerType);
|
||||
}
|
||||
logGlobal->info("Final player config: %d total, %d cpu-only", players.size(), static_cast<int>(getCompOnlyPlayerCount()));
|
||||
logGlobal->info("Final player config: %d total, %d cpu-only", players.size(), cpuOnlyPlayers);
|
||||
}
|
||||
|
||||
void CMapGenOptions::updatePlayers()
|
||||
@ -730,12 +730,6 @@ std::vector<const CRmgTemplate *> CMapGenOptions::getPossibleTemplates() const
|
||||
return true;
|
||||
}
|
||||
|
||||
if(compOnlyPlayerCount != CMapGenOptions::RANDOM_SIZE)
|
||||
{
|
||||
if (!tmpl->getHumanPlayers().isInRange(compOnlyPlayerCount))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
|
@ -128,6 +128,16 @@ void Object::Instance::setTemplate(TerrainId terrain, CRandomGenerator & rng)
|
||||
auto terrainName = VLC->terrainTypeHandler->getById(terrain)->getNameTranslated();
|
||||
throw rmgException(boost::str(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.getObjTypeIndex() % terrainName));
|
||||
}
|
||||
//Get terrain-specific template if possible
|
||||
int leastTerrains = (*boost::min_element(templates, [](const std::shared_ptr<const ObjectTemplate> & tmp1, const std::shared_ptr<const ObjectTemplate> & tmp2)
|
||||
{
|
||||
return tmp1->getAllowedTerrains().size() < tmp2->getAllowedTerrains().size();
|
||||
}))->getAllowedTerrains().size();
|
||||
|
||||
vstd::erase_if(templates, [leastTerrains](const std::shared_ptr<const ObjectTemplate> & tmp)
|
||||
{
|
||||
return tmp->getAllowedTerrains().size() > leastTerrains;
|
||||
});
|
||||
|
||||
dObject.appearance = *RandomGeneratorUtil::nextItem(templates, rng);
|
||||
dAccessibleAreaCache.clear();
|
||||
|
@ -107,8 +107,8 @@ const CSpell::LevelInfo & CSpell::getLevelInfo(const int32_t level) const
|
||||
{
|
||||
if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)
|
||||
{
|
||||
logGlobal->error("CSpell::getLevelInfo: invalid school level %d", level);
|
||||
return levels.at(0);
|
||||
logGlobal->error("CSpell::getLevelInfo: invalid school mastery level %d", level);
|
||||
return levels.at(MasteryLevel::EXPERT);
|
||||
}
|
||||
|
||||
return levels.at(level);
|
||||
|
@ -42,6 +42,12 @@ void Summon::adjustTargetTypes(std::vector<TargetType> & types) const
|
||||
|
||||
bool Summon::applicable(Problem & problem, const Mechanics * m) const
|
||||
{
|
||||
if (creature == CreatureID::NONE)
|
||||
{
|
||||
logMod->error("Attempt to summon non-existing creature!");
|
||||
return m->adaptGenericProblem(problem);
|
||||
}
|
||||
|
||||
if(exclusive)
|
||||
{
|
||||
//check if there are summoned creatures of other type
|
||||
|
@ -1133,7 +1133,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
||||
if (guardian && getVisitingHero(guardian) != nullptr)
|
||||
return complainRet("Cannot move hero, destination monster is busy!");
|
||||
|
||||
if (objectToVisit && getVisitingHero(objectToVisit) != nullptr)
|
||||
if (objectToVisit && getVisitingHero(objectToVisit) != nullptr && getVisitingHero(objectToVisit) != h)
|
||||
return complainRet("Cannot move hero, destination object is busy!");
|
||||
|
||||
if (objectToVisit &&
|
||||
|
@ -1032,7 +1032,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
|
||||
const CStack * actor = item.first;
|
||||
int64_t rawDamage = item.second;
|
||||
|
||||
const CGHeroInstance * actorOwner = battle.battleGetFightingHero(actor->unitOwner());
|
||||
const CGHeroInstance * actorOwner = battle.battleGetFightingHero(actor->unitSide());
|
||||
|
||||
if(actorOwner)
|
||||
{
|
||||
@ -1088,7 +1088,10 @@ void BattleActionProcessor::attackCasting(const CBattleInfoCallback & battle, bo
|
||||
TConstBonusListPtr spells = attacker->getBonuses(Selector::type()(attackMode));
|
||||
for(const auto & sf : *spells)
|
||||
{
|
||||
spellsToCast.insert(sf->subtype.as<SpellID>());
|
||||
if (sf->subtype.as<SpellID>() != SpellID())
|
||||
spellsToCast.insert(sf->subtype.as<SpellID>());
|
||||
else
|
||||
logMod->error("Invalid spell to cast during attack!");
|
||||
}
|
||||
for(SpellID spellID : spellsToCast)
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user