diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index cfda077f3..8098e4668 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -212,11 +212,13 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf // check how much damage we gain from blocking enemy shooters on this hex bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state); - logAi->debug("BattleAI best AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld", +#if BATTLE_TRACE_LEVEL>=1 + logAi->trace("BattleAI best AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld", attackInfo.attacker->unitType()->getJsonKey(), attackInfo.defender->unitType()->getJsonKey(), (int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(), bestAp.defenderDamageReduce, bestAp.attackerDamageReduce, bestAp.collateralDamageReduce, bestAp.shootersBlockedDmg); +#endif //TODO other damage related to attack (eg. fire shield and other abilities) return bestAp; diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index c5b0c581d..5584304df 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -90,6 +90,7 @@ void CBattleAI::initBattleInterface(std::shared_ptr ENV, std::share wasUnlockingGs = CB->unlockGsWhenWaiting; CB->waitTillRealize = true; CB->unlockGsWhenWaiting = false; + movesSkippedByDefense = 0; } BattleAction CBattleAI::activeStack( const CStack * stack ) @@ -182,6 +183,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) if(bestSpellcast.is_initialized() && bestSpellcast->value > bestAttack.damageDiff()) { // return because spellcast value is damage dealt and score is dps reduce + movesSkippedByDefense = 0; return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id); } @@ -197,14 +199,15 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) } else if(bestAttack.attack.shooting) { - result = BattleAction::makeShotAttack(stack, bestAttack.attack.defender); action = "shot"; + movesSkippedByDefense = 0; } else { result = BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.from); action = "melee"; + movesSkippedByDefense = 0; } logAi->debug("BattleAI: %s -> %s x %d, %s, from %d curpos %d dist %d speed %d: +%lld -%lld = %lld", @@ -218,6 +221,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) } else if(bestSpellcast.is_initialized()) { + movesSkippedByDefense = 0; return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id); } @@ -236,12 +240,8 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) } } - if(score > EvaluationResult::INEFFECTIVE_SCORE) - { - return result; - } - - if(!stack->hasBonusOfType(Bonus::FLYING) + if(score <= EvaluationResult::INEFFECTIVE_SCORE + && !stack->hasBonusOfType(Bonus::FLYING) && stack->unitSide() == BattleSide::ATTACKER && cb->battleGetSiegeLevel() >= CGTownInstance::CITADEL) { @@ -249,10 +249,12 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) if(brokenWallMoat.size()) { + movesSkippedByDefense = 0; + if(stack->doubleWide() && vstd::contains(brokenWallMoat, stack->getPosition())) - return BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT)); + result = BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT)); else - return goTowardsNearest(stack, brokenWallMoat); + result = goTowardsNearest(stack, brokenWallMoat); } } } @@ -265,6 +267,15 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) logAi->error("Exception occurred in %s %s",__FUNCTION__, e.what()); } + if(result.actionType == EActionType::DEFEND) + { + movesSkippedByDefense++; + } + else if(result.actionType != EActionType::WAIT) + { + movesSkippedByDefense = 0; + } + return result; } @@ -286,7 +297,9 @@ BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vectorcoversPos(hex)) { @@ -409,6 +422,8 @@ BattleAction CBattleAI::useCatapult(const CStack * stack) attack.side = side; attack.stackNumber = stack->ID; + movesSkippedByDefense = 0; + return attack; } @@ -716,6 +731,7 @@ void CBattleAI::attemptCastingSpell() spellcast.side = side; spellcast.stackNumber = (!side) ? -1 : -2; cb->battleMakeAction(&spellcast); + movesSkippedByDefense = 0; } else { @@ -809,12 +825,21 @@ boost::optional CBattleAI::considerFleeingOrSurrendering() } } + bs.turnsSkippedByDefense = movesSkippedByDefense / bs.ourStacks.size(); + if(!bs.canFlee || !bs.canSurrender) { return boost::none; } - return cb->makeSurrenderRetreatDecision(bs); + auto result = cb->makeSurrenderRetreatDecision(bs); + + if(!result && bs.canFlee && bs.turnsSkippedByDefense > 30) + { + return BattleAction::makeRetreat(bs.ourSide); + } + + return result; } diff --git a/AI/BattleAI/BattleAI.h b/AI/BattleAI/BattleAI.h index 679d54f0a..dd9499d51 100644 --- a/AI/BattleAI/BattleAI.h +++ b/AI/BattleAI/BattleAI.h @@ -60,6 +60,7 @@ class CBattleAI : public CBattleGameInterface //Previous setting of cb bool wasWaitingForRealize, wasUnlockingGs; + int movesSkippedByDefense; public: CBattleAI(); diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 470a23c99..33daba238 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -355,6 +355,13 @@ int64_t BattleExchangeEvaluator::calculateExchange( logAi->trace("Battle exchange at %lld", ap.attack.shooting ? ap.dest : ap.from); #endif + if(cb->battleGetMySide() == BattlePerspective::LEFT_SIDE + && cb->battleGetGateState() == EGateState::BLOCKED + && ap.attack.defender->coversPos(ESiegeHex::GATE_BRIDGE)) + { + return EvaluationResult::INEFFECTIVE_SCORE; + } + std::vector ourStacks; std::vector enemyStacks; diff --git a/AI/BattleAI/BattleExchangeVariant.h b/AI/BattleAI/BattleExchangeVariant.h index d6082c2b5..6c8e0b448 100644 --- a/AI/BattleAI/BattleExchangeVariant.h +++ b/AI/BattleAI/BattleExchangeVariant.h @@ -42,7 +42,7 @@ struct EvaluationResult bool defend; EvaluationResult(const AttackPossibility & ap) - :wait(false), score(0), bestAttack(ap), defend(false) + :wait(false), score(INEFFECTIVE_SCORE), bestAttack(ap), defend(false) { } }; diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityLauncher.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityLauncher.java index d5f6a5b00..fde7580af 100644 --- a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityLauncher.java +++ b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityLauncher.java @@ -18,7 +18,7 @@ import java.util.List; import eu.vcmi.vcmi.content.AsyncLauncherInitialization; import eu.vcmi.vcmi.settings.AdventureAiController; -import eu.vcmi.vcmi.settings.CodepageSettingController; +import eu.vcmi.vcmi.settings.LanguageSettingController; import eu.vcmi.vcmi.settings.CopyDataController; import eu.vcmi.vcmi.settings.ExportDataController; import eu.vcmi.vcmi.settings.LauncherSettingController; @@ -45,7 +45,7 @@ public class ActivityLauncher extends ActivityWithToolbar private TextView mErrorMessage; private Config mConfig; private LauncherSettingController mCtrlScreenRes; - private LauncherSettingController mCtrlCodepage; + private LauncherSettingController mCtrlLanguage; private LauncherSettingController mCtrlPointerMode; private LauncherSettingController mCtrlStart; private LauncherSettingController mCtrlPointerMulti; @@ -203,7 +203,7 @@ public class ActivityLauncher extends ActivityWithToolbar (mCtrlExport = new ExportDataController(this)).init(R.id.launcher_btn_export); new ModsBtnController(this, v -> startActivity(new Intent(ActivityLauncher.this, ActivityMods.class))).init(R.id.launcher_btn_mods); mCtrlScreenRes = new ScreenResSettingController(this).init(R.id.launcher_btn_res, mConfig); - mCtrlCodepage = new CodepageSettingController(this).init(R.id.launcher_btn_cp, mConfig); + mCtrlLanguage = new LanguageSettingController(this).init(R.id.launcher_btn_cp, mConfig); mCtrlPointerMode = new PointerModeSettingController(this).init(R.id.launcher_btn_pointer_mode, mConfig); mCtrlPointerMulti = new PointerMultiplierSettingController(this).init(R.id.launcher_btn_pointer_multi, mConfig); mCtrlSoundVol = new SoundSettingController(this).init(R.id.launcher_btn_volume_sound, mConfig); @@ -211,7 +211,7 @@ public class ActivityLauncher extends ActivityWithToolbar mAiController = new AdventureAiController(this).init(R.id.launcher_btn_adventure_ai, mConfig); mActualSettings.clear(); - mActualSettings.add(mCtrlCodepage); + mActualSettings.add(mCtrlLanguage); mActualSettings.add(mCtrlScreenRes); mActualSettings.add(mCtrlPointerMode); mActualSettings.add(mCtrlPointerMulti); @@ -267,7 +267,7 @@ public class ActivityLauncher extends ActivityWithToolbar private void onConfigUpdated() { updateCtrlConfig(mCtrlScreenRes, mConfig); - updateCtrlConfig(mCtrlCodepage, mConfig); + updateCtrlConfig(mCtrlLanguage, mConfig); updateCtrlConfig(mCtrlPointerMode, mConfig); updateCtrlConfig(mCtrlPointerMulti, mConfig); updateCtrlConfig(mCtrlSoundVol, mConfig); diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java index 7b010b903..31fdd60ff 100644 --- a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java +++ b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java @@ -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/github.json"; + private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.2.json"; private VCMIModsRepo mRepo; private RecyclerView mRecycler; diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/Config.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/Config.java index 65bd3093b..e894c52e0 100644 --- a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/Config.java +++ b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/Config.java @@ -14,13 +14,13 @@ import eu.vcmi.vcmi.util.Log; */ public class Config { - public static final String DEFAULT_CODEPAGE = "CP1250"; + public static final String DEFAULT_LANGUAGE = "english"; public static final int DEFAULT_MUSIC_VALUE = 5; public static final int DEFAULT_SOUND_VALUE = 5; public static final int DEFAULT_SCREEN_RES_W = 800; public static final int DEFAULT_SCREEN_RES_H = 600; - public String mCodepage; + public String mLanguage; public int mResolutionWidth; public int mResolutionHeight; public boolean mSwipeEnabled; @@ -85,7 +85,7 @@ public class Config final Config config = new Config(); final JSONObject general = accessNode(obj, "general"); final JSONObject server = accessNode(obj, "server"); - config.mCodepage = loadEntry(general, "encoding", DEFAULT_CODEPAGE); + config.mLanguage = loadEntry(general, "language", DEFAULT_LANGUAGE); config.mVolumeSound = loadEntry(general, "sound", DEFAULT_SOUND_VALUE); config.mVolumeMusic = loadEntry(general, "music", DEFAULT_MUSIC_VALUE); config.mSwipeEnabled = loadEntry(general, "swipe", true); @@ -101,9 +101,9 @@ public class Config return config; } - public void updateCodepage(final String s) + public void updateLanguage(final String s) { - mCodepage = s; + mLanguage = s; mIsModified = true; } @@ -202,9 +202,9 @@ public class Config final JSONObject screenRes = screenResNode == null ? new JSONObject() : screenResNode; final JSONObject server = serverNode == null ? new JSONObject() : serverNode; - if (mCodepage != null) + if (mLanguage != null) { - general.put("encoding", mCodepage); + general.put("language", mLanguage); } general.put("swipe", mSwipeEnabled); @@ -230,4 +230,4 @@ public class Config return root.toString(); } -} \ No newline at end of file +} diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/CodepageSettingDialog.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/CodepageSettingDialog.java deleted file mode 100644 index 3656d3ba9..000000000 --- a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/CodepageSettingDialog.java +++ /dev/null @@ -1,40 +0,0 @@ -package eu.vcmi.vcmi.settings; - -import java.util.ArrayList; -import java.util.List; - -import eu.vcmi.vcmi.R; - -/** - * @author F - */ -public class CodepageSettingDialog extends LauncherSettingDialog -{ - private static final List AVAILABLE_CODEPAGES = new ArrayList<>(); - - static - { - AVAILABLE_CODEPAGES.add("CP1250"); - AVAILABLE_CODEPAGES.add("CP1251"); - AVAILABLE_CODEPAGES.add("CP1252"); - AVAILABLE_CODEPAGES.add("GBK"); - AVAILABLE_CODEPAGES.add("GB2312"); - } - - public CodepageSettingDialog() - { - super(AVAILABLE_CODEPAGES); - } - - @Override - protected int dialogTitleResId() - { - return R.string.launcher_btn_cp_title; - } - - @Override - protected CharSequence itemName(final String item) - { - return item; - } -} diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/CodepageSettingController.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/LanguageSettingController.java similarity index 53% rename from android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/CodepageSettingController.java rename to android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/LanguageSettingController.java index 93d319df7..e4e226419 100644 --- a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/CodepageSettingController.java +++ b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/LanguageSettingController.java @@ -8,9 +8,9 @@ import eu.vcmi.vcmi.R; /** * @author F */ -public class CodepageSettingController extends LauncherSettingWithDialogController +public class LanguageSettingController extends LauncherSettingWithDialogController { - public CodepageSettingController(final AppCompatActivity activity) + public LanguageSettingController(final AppCompatActivity activity) { super(activity); } @@ -18,20 +18,20 @@ public class CodepageSettingController extends LauncherSettingWithDialogControll @Override protected LauncherSettingDialog dialog() { - return new CodepageSettingDialog(); + return new LanguageSettingDialog(); } @Override public void onItemChosen(final String item) { - mConfig.updateCodepage(item); + mConfig.updateLanguage(item); updateContent(); } @Override protected String mainText() { - return mActivity.getString(R.string.launcher_btn_cp_title); + return mActivity.getString(R.string.launcher_btn_language_title); } @Override @@ -41,8 +41,8 @@ public class CodepageSettingController extends LauncherSettingWithDialogControll { return ""; } - return mConfig.mCodepage == null || mConfig.mCodepage.isEmpty() - ? mActivity.getString(R.string.launcher_btn_cp_subtitle_unknown) - : mActivity.getString(R.string.launcher_btn_cp_subtitle, mConfig.mCodepage); + return mConfig.mLanguage == null || mConfig.mLanguage.isEmpty() + ? mActivity.getString(R.string.launcher_btn_language_subtitle_unknown) + : mActivity.getString(R.string.launcher_btn_language_subtitle, mConfig.mLanguage); } } diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/LanguageSettingDialog.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/LanguageSettingDialog.java new file mode 100644 index 000000000..1fb5e8894 --- /dev/null +++ b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/LanguageSettingDialog.java @@ -0,0 +1,47 @@ +package eu.vcmi.vcmi.settings; + +import java.util.ArrayList; +import java.util.List; + +import eu.vcmi.vcmi.R; + +/** + * @author F + */ +public class LanguageSettingDialog extends LauncherSettingDialog +{ + private static final List AVAILABLE_LANGUAGES = new ArrayList<>(); + + static + { + AVAILABLE_LANGUAGES.add("english"); + AVAILABLE_LANGUAGES.add("chinese"); + AVAILABLE_LANGUAGES.add("french"); + AVAILABLE_LANGUAGES.add("german"); + AVAILABLE_LANGUAGES.add("korean"); + AVAILABLE_LANGUAGES.add("polish"); + AVAILABLE_LANGUAGES.add("russian"); + AVAILABLE_LANGUAGES.add("spanish"); + AVAILABLE_LANGUAGES.add("ukrainian"); + AVAILABLE_LANGUAGES.add("other_cp1250"); + AVAILABLE_LANGUAGES.add("other_cp1251"); + AVAILABLE_LANGUAGES.add("other_cp1252"); + } + + public LanguageSettingDialog() + { + super(AVAILABLE_LANGUAGES); + } + + @Override + protected int dialogTitleResId() + { + return R.string.launcher_btn_language_title; + } + + @Override + protected CharSequence itemName(final String item) + { + return item; + } +} diff --git a/android/vcmi-app/src/main/res/values-de/strings.xml b/android/vcmi-app/src/main/res/values-de/strings.xml index df616c931..04bc85961 100644 --- a/android/vcmi-app/src/main/res/values-de/strings.xml +++ b/android/vcmi-app/src/main/res/values-de/strings.xml @@ -10,9 +10,9 @@ Aktuelle VCMI-Version: %1$s Mods Neue Burgen, Kreaturen, Objekte und Erweiterungen hinzufügen - Zeichensatz - Aktuell: unbekannt - Aktuell: %1$s + Sprache + Aktuell: unbekannt + Aktuell: %1$s Zeigermodus ändern Aktuell: %1$s Aktuell: %1$s diff --git a/android/vcmi-app/src/main/res/values-pl/strings.xml b/android/vcmi-app/src/main/res/values-pl/strings.xml index 771fb9495..0061449d0 100644 --- a/android/vcmi-app/src/main/res/values-pl/strings.xml +++ b/android/vcmi-app/src/main/res/values-pl/strings.xml @@ -10,9 +10,9 @@ Obecna wersja VCMI: %1$s Mody Zainstaluj nowe frakcje, obiekty, dodatki - Strona kodowa - Obecnie: nieznane - Obecnie: %1$s + Język + Obecnie: nieznane + Obecnie: %1$s Zmień tryb kursora Obecnie: %1$s Mnożnik prędkości kursora diff --git a/android/vcmi-app/src/main/res/values-ru/strings.xml b/android/vcmi-app/src/main/res/values-ru/strings.xml index b78cd4f05..7caa88ebb 100644 --- a/android/vcmi-app/src/main/res/values-ru/strings.xml +++ b/android/vcmi-app/src/main/res/values-ru/strings.xml @@ -10,9 +10,9 @@ Текущая версия VCMI: %1$s Моды Добавить новые замки, существа, объекты, расширения - Кодовая страница - Текущая: неизвестно - Текущая: %1$s + Язык + Текущая: неизвестно + Текущая: %1$s Изменить режим управления указателем Currently: %1$s Текущая: %1$s @@ -60,4 +60,4 @@ Загрузить данные VCMI во внутреннее хранилище Скопировать данные VCMI во внутреннее хранилище. Вы можете загрузить старую папку vcmi-data от версии 0.99 или файлы Героев Копируем %1$s - \ No newline at end of file + diff --git a/android/vcmi-app/src/main/res/values-uk/strings.xml b/android/vcmi-app/src/main/res/values-uk/strings.xml index 10ed5c286..095441f32 100644 --- a/android/vcmi-app/src/main/res/values-uk/strings.xml +++ b/android/vcmi-app/src/main/res/values-uk/strings.xml @@ -10,9 +10,9 @@ Поточна версія VCMI: %1$s Моди Додати нові замки, істот, об’єкти, розширення - Кодова сторінка - Поточна: невідомо - Поточна: %1$s + Мова + Поточна: невідомо + Поточна: %1$s Змінити режим керування курсором Поточна: %1$s Поточна: %1$s @@ -60,4 +60,4 @@ Завантажити дані VCMI у внутрішнє сховище Копіювати дані VCMI у внутрішнє сховище. Ви можете завантажити стару папку vcmi-data від версії 0.99 чи файли героїв Копіюємо %1$s - \ No newline at end of file + diff --git a/android/vcmi-app/src/main/res/values/strings.xml b/android/vcmi-app/src/main/res/values/strings.xml index 8858a876e..563911f18 100644 --- a/android/vcmi-app/src/main/res/values/strings.xml +++ b/android/vcmi-app/src/main/res/values/strings.xml @@ -15,9 +15,9 @@ Current VCMI version: %1$s Mods Install new factions, objects, extras - Codepage - Currently: unknown - Currently: %1$s + Language + Currently: unknown + Currently: %1$s Change pointer mode Currently: %1$s Relative pointer speed multiplier diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index f15ad9c3f..1649339c4 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -446,15 +446,7 @@ void CMusicHandler::queueNext(std::unique_ptr queued) void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart) { - try - { - queueNext(std::make_unique(owner, setName, musicURI, looped, fromStart)); - } - catch(std::exception &e) - { - logGlobal->error("Failed to queue music. setName=%s\tmusicURI=%s", setName, musicURI); - logGlobal->error("Exception: %s", e.what()); - } + queueNext(std::make_unique(owner, setName, musicURI, looped, fromStart)); } void CMusicHandler::stopMusic(int fade_ms) @@ -563,12 +555,20 @@ void MusicEntry::load(std::string musicURI) } currentName = musicURI; + music = nullptr; logGlobal->trace("Loading music file %s", musicURI); - auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(ResourceID(std::move(musicURI), EResType::MUSIC))); - - music = Mix_LoadMUS_RW(musicFile, SDL_TRUE); + try + { + auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(ResourceID(std::move(musicURI), EResType::MUSIC))); + music = Mix_LoadMUS_RW(musicFile, SDL_TRUE); + } + catch(std::exception &e) + { + logGlobal->error("Failed to load music. setName=%s\tmusicURI=%s", setName, musicURI); + logGlobal->error("Exception: %s", e.what()); + } if(!music) { diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index b80e7526c..dc7aeb571 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -947,7 +947,7 @@ void CPlayerInterface::battleStacksEffectsSet( const SetStackEffect & sse ) void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte) { EVENT_HANDLER_CALLED_BY_CLIENT; - //TODO why is this different (no return on LOPLINT != this) ? + BATTLE_EVENT_POSSIBLE_RETURN; RETURN_IF_QUICK_COMBAT; battleInt->effectsController->battleTriggerEffect(bte); @@ -1038,6 +1038,12 @@ void CPlayerInterface::yourTacticPhase(int distance) boost::this_thread::sleep(boost::posix_time::millisec(1)); } +void CPlayerInterface::forceEndTacticPhase() +{ + if (battleInt) + battleInt->tacticsMode = false; +} + void CPlayerInterface::showInfoDialog(EInfoWindowMode type, const std::string &text, const std::vector & components, int soundID) { EVENT_HANDLER_CALLED_BY_CLIENT; diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index 591ecf260..5d8be29c4 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -226,6 +226,7 @@ public: void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack void battleGateStateChanged(const EGateState state) override; void yourTacticPhase(int distance) override; + void forceEndTacticPhase() override; //-------------// void showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList onYes); diff --git a/client/Client.cpp b/client/Client.cpp index d153722af..686395719 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -7,6 +7,7 @@ * Full text of license available in license.txt file, in main folder * */ +#include "Global.h" #include "StdInc.h" #include "Client.h" @@ -31,6 +32,7 @@ #include "../lib/registerTypes/RegisterTypes.h" #include "../lib/serializer/Connection.h" +#include #include #if SCRIPTING_ENABLED @@ -369,6 +371,9 @@ void CClient::endGame() logNetwork->info("Deleted mapHandler and gameState."); } + //threads cleanup has to be after gs cleanup and before battleints cleanup to stop tacticThread + cleanThreads(); + playerint.clear(); battleints.clear(); battleCallbacks.clear(); @@ -593,7 +598,8 @@ void CClient::battleStarted(const BattleInfo * info) if(info->tacticDistance && vstd::contains(battleints, info->sides[info->tacticsSide].color)) { - boost::thread(&CClient::commenceTacticPhaseForInt, this, battleints[info->sides[info->tacticsSide].color]); + PlayerColor color = info->sides[info->tacticsSide].color; + playerTacticThreads[color] = std::make_unique(&CClient::commenceTacticPhaseForInt, this, battleints[color]); } } @@ -754,6 +760,23 @@ void CClient::removeGUI() LOCPLINT = nullptr; } +void CClient::cleanThreads() +{ + stopAllBattleActions(); + + while (!playerTacticThreads.empty()) + { + PlayerColor color = playerTacticThreads.begin()->first; + + //set tacticcMode of the players to false to stop tacticThread + if (vstd::contains(battleints, color)) + battleints[color]->forceEndTacticPhase(); + + playerTacticThreads[color]->join(); + playerTacticThreads.erase(color); + } +} + #ifdef VCMI_ANDROID #ifndef SINGLE_PROCESS_APP extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jclass cls) diff --git a/client/Client.h b/client/Client.h index 118b5bcb8..95344ba1f 100644 --- a/client/Client.h +++ b/client/Client.h @@ -9,6 +9,7 @@ */ #pragma once +#include #include #include "../lib/IGameCallback.h" @@ -242,6 +243,8 @@ public: void showInfoDialog(const std::string & msg, PlayerColor player) override {}; void removeGUI(); + void cleanThreads(); + #if SCRIPTING_ENABLED scripting::Pool * getGlobalContextPool() const override; scripting::Pool * getContextPool() const override; @@ -263,6 +266,8 @@ private: std::map> playerActionThreads; + std::map> playerTacticThreads; + void waitForMoveAndSend(PlayerColor color); void reinitScripting(); }; diff --git a/client/LobbyClientNetPackVisitors.h b/client/LobbyClientNetPackVisitors.h index 9290c8f0e..7ea105b02 100644 --- a/client/LobbyClientNetPackVisitors.h +++ b/client/LobbyClientNetPackVisitors.h @@ -12,7 +12,9 @@ #include "../lib/NetPackVisitor.h" class CClient; +VCMI_LIB_NAMESPACE_BEGIN class CGameState; +VCMI_LIB_NAMESPACE_END class ApplyOnLobbyHandlerNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) { @@ -53,4 +55,4 @@ public: virtual void visitLobbyStartGame(LobbyStartGame & pack) override; virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override; virtual void visitLobbyShowMessage(LobbyShowMessage & pack) override; -}; \ No newline at end of file +}; diff --git a/client/adventureMap/CInGameConsole.cpp b/client/adventureMap/CInGameConsole.cpp index ed4588ea4..3af427e9f 100644 --- a/client/adventureMap/CInGameConsole.cpp +++ b/client/adventureMap/CInGameConsole.cpp @@ -38,6 +38,9 @@ void CInGameConsole::showAll(SDL_Surface * to) void CInGameConsole::show(SDL_Surface * to) { + if (LOCPLINT->cingconsole != this) + return; + int number = 0; boost::unique_lock lock(texts_mx); @@ -107,7 +110,11 @@ void CInGameConsole::print(const std::string & txt) void CInGameConsole::keyPressed (const SDL_Keycode & key) { - if(!captureAllKeys && key != SDLK_TAB) return; //because user is not entering any text + if (LOCPLINT->cingconsole != this) + return; + + if(!captureAllKeys && key != SDLK_TAB) + return; //because user is not entering any text switch(key) { @@ -192,6 +199,9 @@ void CInGameConsole::keyPressed (const SDL_Keycode & key) void CInGameConsole::textInputed(const std::string & inputtedText) { + if (LOCPLINT->cingconsole != this) + return; + if(!captureAllKeys || enteredText.empty()) return; enteredText.resize(enteredText.size()-1); diff --git a/client/adventureMap/CInfoBar.cpp b/client/adventureMap/CInfoBar.cpp index d1c1b1b6e..d5249d096 100644 --- a/client/adventureMap/CInfoBar.cpp +++ b/client/adventureMap/CInfoBar.cpp @@ -173,7 +173,7 @@ CInfoBar::VisibleComponentInfo::VisibleComponentInfo(const std::vector(vect, imageRect, 4, 4, 1, maxComponents); } - else - font = tiny ? FONT_TINY : font; if(!message.empty()) - text = std::make_shared(message, textRect, 0, font, ETextAlignment::CENTER, Colors::WHITE); + text = std::make_shared(textRect, font, ETextAlignment::CENTER, Colors::WHITE, message); } void CInfoBar::playNewDaySound() diff --git a/client/adventureMap/CInfoBar.h b/client/adventureMap/CInfoBar.h index 3a507e879..63ec62991 100644 --- a/client/adventureMap/CInfoBar.h +++ b/client/adventureMap/CInfoBar.h @@ -27,7 +27,7 @@ class CComponentBox; class CHeroTooltip; class CTownTooltip; class CLabel; -class CTextBox; +class CMultiLineLabel; /// Info box which shows next week/day information, hold the current date class CInfoBar : public CIntObject @@ -112,7 +112,7 @@ private: class VisibleComponentInfo : public CVisibleInfo { std::shared_ptr comps; - std::shared_ptr text; + std::shared_ptr text; public: struct Cache { diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index f607fd0d2..3e1aa2872 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -130,6 +130,7 @@ void BattleActionsController::endCastingSpell() if(owner.stacksController->getActiveStack()) possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); //restore actions after they were cleared + selectedStack = nullptr; GH.fakeMouseMove(); } diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index 490e6b590..91b3dbb04 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -89,6 +89,7 @@ void BattleWindow::createQueue() //create stack queue and adjust our own position bool embedQueue; + bool showQueue = settings["battle"]["showQueue"].Bool(); std::string queueSize = settings["battle"]["queueSize"].String(); if(queueSize == "auto") @@ -97,13 +98,16 @@ void BattleWindow::createQueue() embedQueue = GH.screenDimensions().y < 700 || queueSize == "small"; queue = std::make_shared(embedQueue, owner); - if(!embedQueue && settings["battle"]["showQueue"].Bool()) + if(!embedQueue && showQueue) { //re-center, taking into account stack queue position pos.y -= queue->pos.h; pos.h += queue->pos.h; pos = center(); } + + if (!showQueue) + queue->disable(); } BattleWindow::~BattleWindow() @@ -143,8 +147,8 @@ void BattleWindow::hideQueue() pos.y += queue->pos.h; pos.h -= queue->pos.h; pos = center(); - GH.totalRedraw(); } + GH.totalRedraw(); } void BattleWindow::showQueue() @@ -230,9 +234,12 @@ void BattleWindow::tacticPhaseStarted() auto menuTactics = widget("menuTactics"); auto tacticNext = widget("tacticNext"); auto tacticEnd = widget("tacticEnd"); + auto alternativeAction = widget("alternativeAction"); menuBattle->disable(); console->disable(); + if (alternativeAction) + alternativeAction->disable(); menuTactics->enable(); tacticNext->enable(); @@ -248,9 +255,12 @@ void BattleWindow::tacticPhaseEnded() auto menuTactics = widget("menuTactics"); auto tacticNext = widget("tacticNext"); auto tacticEnd = widget("tacticEnd"); + auto alternativeAction = widget("alternativeAction"); menuBattle->enable(); console->enable(); + if (alternativeAction) + alternativeAction->enable(); menuTactics->disable(); tacticNext->disable(); diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index 4063d5890..10397ecb6 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -712,7 +712,11 @@ void CGuiHandler::moveCursorToPosition(const Point & position) bool CGuiHandler::isKeyboardCtrlDown() const { +#ifdef VCMI_MAC + return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LGUI] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RGUI]; +#else return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL]; +#endif } bool CGuiHandler::isKeyboardAltDown() const diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index 79b7de24c..340c0e21b 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -31,6 +31,8 @@ static std::map KeycodeMap{ {"left", SDLK_LEFT}, {"right", SDLK_RIGHT}, {"space", SDLK_SPACE}, + {"escape", SDLK_ESCAPE}, + {"backspace", SDLK_BACKSPACE}, {"enter", SDLK_RETURN} }; @@ -220,10 +222,16 @@ int InterfaceObjectConfigurable::readKeycode(const JsonNode & config) const auto s = config.String(); if(s.size() == 1) //keyboard symbol return s[0]; - return KeycodeMap[s]; + + if (KeycodeMap.count(s)) + return KeycodeMap[s]; + + logGlobal->error("Invalid keycode '%s' in interface configuration!", config.String()); + return SDLK_UNKNOWN; } - - return 0; + + logGlobal->error("Invalid keycode format in interface configuration! Expected string or integer!", config.String()); + return SDLK_UNKNOWN; } std::shared_ptr InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 5387c5467..339e838df 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -394,6 +394,10 @@ void TemplatesDropBox::ListItem::clickLeft(tribool down, bool previousState) { dropBox.setTemplate(item); } + else + { + dropBox.clickLeft(true, true); + } } @@ -449,8 +453,14 @@ void TemplatesDropBox::clickLeft(tribool down, bool previousState) { if(down && !hovered) { - assert(GH.topInt().get() == this); - GH.popInt(GH.topInt()); + auto w = widget("slider"); + + // pop the interface only if the mouse is not clicking on the slider + if (!w || !w->mouseState(MouseButton::LEFT)) + { + assert(GH.topInt().get() == this); + GH.popInt(GH.topInt()); + } } } diff --git a/client/render/IFont.cpp b/client/render/IFont.cpp index 48a84cab4..2151b542a 100644 --- a/client/render/IFont.cpp +++ b/client/render/IFont.cpp @@ -13,7 +13,6 @@ #include "../../lib/Point.h" #include "../../lib/TextOperations.h" -// size_t IFont::getStringWidth(const std::string & data) const { diff --git a/client/renderSDL/CBitmapFont.cpp b/client/renderSDL/CBitmapFont.cpp index 93753f70c..0810b649b 100644 --- a/client/renderSDL/CBitmapFont.cpp +++ b/client/renderSDL/CBitmapFont.cpp @@ -70,7 +70,7 @@ CBitmapFont::CBitmapFont(const std::string & filename): loadModFont("core", resource); - for (auto const & modName : VLC->modh->getActiveMods()) + for(const auto & modName : VLC->modh->getActiveMods()) { if (CResourceHandler::get(modName)->existsResource(resource)) loadModFont(modName, resource); @@ -94,6 +94,24 @@ size_t CBitmapFont::getGlyphWidth(const char * data) const return iter->second.leftOffset + iter->second.width + iter->second.rightOffset; } +bool CBitmapFont::canRepresentCharacter(const char *data) const +{ + CodePoint localChar = TextOperations::getUnicodeCodepoint(data, 4); + + auto iter = chars.find(localChar); + + return iter != chars.end(); +} + +bool CBitmapFont::canRepresentString(const std::string & data) const +{ + for(size_t i=0; i(fallbackName); } +CTrueTypeFont::~CTrueTypeFont() = default; + size_t CTrueTypeFont::getLineHeight() const { + if (fallbackFont) + fallbackFont->getLineHeight(); + return TTF_FontHeight(font.get()); } size_t CTrueTypeFont::getGlyphWidth(const char *data) const { + if (fallbackFont && fallbackFont->canRepresentCharacter(data)) + return fallbackFont->getGlyphWidth(data); + return getStringWidth(std::string(data, TextOperations::getUnicodeCharacterSize(*data))); - /* int advance; TTF_GlyphMetrics(font.get(), *data, nullptr, nullptr, nullptr, nullptr, &advance); return advance; - */ } size_t CTrueTypeFont::getStringWidth(const std::string & data) const { + if (fallbackFont && fallbackFont->canRepresentString(data)) + return fallbackFont->getStringWidth(data); + int width; TTF_SizeUTF8(font.get(), data.c_str(), &width, nullptr); return width; @@ -83,7 +100,13 @@ size_t CTrueTypeFont::getStringWidth(const std::string & data) const void CTrueTypeFont::renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const { - if (color.r != 0 && color.g != 0 && color.b != 0) // not black - add shadow + if (fallbackFont && fallbackFont->canRepresentString(data)) + { + fallbackFont->renderText(surface, data, color, pos); + return; + } + + if (dropShadow && color.r != 0 && color.g != 0 && color.b != 0) // not black - add shadow renderText(surface, data, Colors::BLACK, pos + Point(1,1)); if (!data.empty()) diff --git a/client/renderSDL/CTrueTypeFont.h b/client/renderSDL/CTrueTypeFont.h index eb834b40c..407d083f5 100644 --- a/client/renderSDL/CTrueTypeFont.h +++ b/client/renderSDL/CTrueTypeFont.h @@ -15,14 +15,18 @@ VCMI_LIB_NAMESPACE_BEGIN class JsonNode; VCMI_LIB_NAMESPACE_END +class CBitmapFont; + typedef struct _TTF_Font TTF_Font; class CTrueTypeFont : public IFont { + std::unique_ptr fallbackFont; const std::pair, ui64> data; const std::unique_ptr font; const bool blended; + const bool dropShadow; std::pair, ui64> loadData(const JsonNode & config); TTF_Font * loadFont(const JsonNode & config); @@ -31,6 +35,7 @@ class CTrueTypeFont : public IFont void renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const override; public: CTrueTypeFont(const JsonNode & fontConfig); + ~CTrueTypeFont(); size_t getLineHeight() const override; size_t getGlyphWidth(const char * data) const override; diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index f78e218dc..41dfc4ea1 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -63,7 +63,8 @@ int64_t CHeroWithMaybePickedArtifact::getTreeVersion() const si32 CHeroWithMaybePickedArtifact::manaLimit() const { - return hero->manaLimit(); + //TODO: reduplicate code with CGHeroInstance + return si32(getPrimSkillLevel(PrimarySkill::KNOWLEDGE) * (valOfBonuses(Bonus::MANA_PER_KNOWLEDGE))); } CHeroWithMaybePickedArtifact::CHeroWithMaybePickedArtifact(CWindowWithArtifacts * Cww, const CGHeroInstance * Hero) diff --git a/client/windows/CHeroWindow.h b/client/windows/CHeroWindow.h index 6a8bf2dbe..263b50009 100644 --- a/client/windows/CHeroWindow.h +++ b/client/windows/CHeroWindow.h @@ -56,7 +56,7 @@ public: int64_t getTreeVersion() const override; - si32 manaLimit() const override; + si32 manaLimit() const; }; class CHeroWindow : public CStatusbarWindow, public CGarrisonHolder, public CWindowWithArtifacts diff --git a/client/windows/settings/BattleOptionsTab.cpp b/client/windows/settings/BattleOptionsTab.cpp index c2992b3b4..41ee4bb43 100644 --- a/client/windows/settings/BattleOptionsTab.cpp +++ b/client/windows/settings/BattleOptionsTab.cpp @@ -99,14 +99,19 @@ int BattleOptionsTab::getAnimSpeed() const int BattleOptionsTab::getQueueSizeId() const { - std::string text = settings["battle"]["queueSize"].String(); - if(text == "none") + std::string sizeText = settings["battle"]["queueSize"].String(); + bool visible = settings["battle"]["showQueue"].Bool(); + + if(!visible) return -1; - if(text == "auto") + + if(sizeText == "none") + return -1; + if(sizeText == "auto") return 0; - if(text == "small") + if(sizeText == "small") return 1; - if(text == "big") + if(sizeText == "big") return 2; return 0; diff --git a/config/artifacts.json b/config/artifacts.json index eb90dc52e..3290fe817 100644 --- a/config/artifacts.json +++ b/config/artifacts.json @@ -1377,6 +1377,18 @@ "subtype" : 1, "val" : 0, "valueType" : "BASE_NUMBER" + }, + { + "type" : "MAGIC_RESISTANCE", + "val" : 0, + "valueType" : "INDEPENDENT_MIN", + "propagator": "BATTLE_WIDE" + }, + { + "type" : "SPELL_RESISTANCE_AURA", + "val" : 0, + "valueType" : "INDEPENDENT_MIN", + "propagator": "BATTLE_WIDE" } ], "index" : 93, diff --git a/config/objects/rewardableBonusing.json b/config/objects/rewardableBonusing.json index a616f7a1f..ff1f4c2cd 100644 --- a/config/objects/rewardableBonusing.json +++ b/config/objects/rewardableBonusing.json @@ -18,7 +18,8 @@ "value" : 100, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "blockedVisitable" : true, "onVisitedMessage" : 22, "visitMode" : "bonus", @@ -49,7 +50,8 @@ "value" : 100, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 30, "visitMode" : "bonus", "selectMode" : "selectFirst", @@ -81,7 +83,8 @@ "value" : 100, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 50, "visitMode" : "bonus", "selectMode" : "selectFirst", @@ -112,7 +115,8 @@ "value" : 100, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 56, "visitMode" : "bonus", "selectMode" : "selectFirst", @@ -164,7 +168,8 @@ "value" : 100, "rarity" : 50 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 58, "visitMode" : "bonus", "selectMode" : "selectFirst", @@ -195,7 +200,8 @@ "value" : 100, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 63, "visitMode" : "bonus", "selectMode" : "selectFirst", @@ -250,7 +256,8 @@ "value" : 100, "rarity" : 20 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 82, "visitMode" : "bonus", "selectMode" : "selectFirst", @@ -280,7 +287,8 @@ "value" : 100, "rarity" : 50 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 95, "visitMode" : "bonus", "selectMode" : "selectFirst", @@ -312,10 +320,10 @@ "value" : 200, "rarity" : 40 }, - + "compatibilityIdentifiers" : [ "object" ], + "visitMode" : "bonus", "selectMode" : "selectFirst", - "onVisited" : [ { "message" : 139, @@ -370,7 +378,8 @@ "value" : 100, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 141, "visitMode" : "bonus", "selectMode" : "selectFirst", @@ -406,7 +415,8 @@ "value" : 100, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 111, "visitMode" : "bonus", "selectMode" : "selectFirst", @@ -440,7 +450,8 @@ "value" : 500, "rarity" : 50 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 167, "visitMode" : "bonus", "selectMode" : "selectFirst", diff --git a/config/objects/rewardableOncePerHero.json b/config/objects/rewardableOncePerHero.json index d052866bb..e6bac178a 100644 --- a/config/objects/rewardableOncePerHero.json +++ b/config/objects/rewardableOncePerHero.json @@ -18,7 +18,8 @@ "value" : 3000, "rarity" : 50 }, - + "compatibilityIdentifiers" : [ "object" ], + "onSelectMessage" : 0, "onVisitedMessage" : 1, "visitMode" : "hero", @@ -51,6 +52,7 @@ "value" : 1500, "rarity" : 100 }, + "compatibilityIdentifiers" : [ "object" ], "onVisitedMessage" : 40, "visitMode" : "hero", @@ -81,7 +83,8 @@ "value" : 1500, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 60, "visitMode" : "hero", "selectMode" : "selectFirst", @@ -110,7 +113,8 @@ "value" : 12000, "rarity" : 20 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 67, "onEmptyMessage" : 68, "visitMode" : "hero", @@ -154,7 +158,8 @@ "value" : 1500, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 81, "visitMode" : "hero", "selectMode" : "selectFirst", @@ -184,7 +189,8 @@ "value" : 1500, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 101, "visitMode" : "hero", "selectMode" : "selectFirst", @@ -214,6 +220,7 @@ "value" : 2500, "rarity" : 50 }, + "compatibilityIdentifiers" : [ "object" ], "onEmpty" : [ { @@ -270,7 +277,8 @@ "value" : 1000, "rarity" : 50 }, - + "compatibilityIdentifiers" : [ "object" ], + "onSelectMessage" : 71, "onVisitedMessage" : 72, "onEmptyMessage" : 73, @@ -309,6 +317,7 @@ "value" : 1000, "rarity" : 50 }, + "compatibilityIdentifiers" : [ "object" ], "onSelectMessage" : 158, "onVisitedMessage" : 159, @@ -348,6 +357,7 @@ "value" : 1500, "rarity" : 200 }, + "compatibilityIdentifiers" : [ "object" ], "onVisitedMessage" : 144, "visitMode" : "hero", diff --git a/config/objects/rewardableOncePerWeek.json b/config/objects/rewardableOncePerWeek.json index 5288a7ef3..464f8ff32 100644 --- a/config/objects/rewardableOncePerWeek.json +++ b/config/objects/rewardableOncePerWeek.json @@ -17,7 +17,8 @@ "value" : 250, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "onEmptyMessage" : 79, "onVisitedMessage" : 78, "visitMode" : "bonus", @@ -54,7 +55,8 @@ // "value" : 500, // "rarity" : 50 //}, - + "compatibilityIdentifiers" : [ "object" ], + "onEmptyMessage" : 76, "onVisitedMessage" : 75, "resetParameters" : { @@ -92,7 +94,8 @@ "value" : 500, "rarity" : 50 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 93, "resetParameters" : { "period" : 7, @@ -133,7 +136,8 @@ "value" : 1500, "rarity" : 80 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 169, "resetParameters" : { "period" : 7, @@ -174,7 +178,8 @@ "value" : 750, "rarity" : 50 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 165, "resetParameters" : { "period" : 7, diff --git a/config/objects/rewardableOnceVisitable.json b/config/objects/rewardableOnceVisitable.json index ea504f2b0..3eab395fc 100644 --- a/config/objects/rewardableOnceVisitable.json +++ b/config/objects/rewardableOnceVisitable.json @@ -17,7 +17,8 @@ "value" : 500, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 65, "visitMode" : "once", "selectMode" : "selectFirst", @@ -32,7 +33,7 @@ } ] } - ] + ] } } }, @@ -52,7 +53,8 @@ "value" : 500, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 38, "blockedVisitable" : true, "visitMode" : "once", @@ -92,7 +94,8 @@ "value" : 500, "rarity" : 50 }, - + "compatibilityIdentifiers" : [ "object" ], + "onVisitedMessage" : 156, "visitMode" : "once", "selectMode" : "selectFirst", @@ -142,7 +145,8 @@ "value" : 6000, "rarity" : 20 }, - + "compatibilityIdentifiers" : [ "object" ], + "onSelectMessage" : 161, "visitMode" : "once", "selectMode" : "selectFirst", diff --git a/config/objects/rewardablePickable.json b/config/objects/rewardablePickable.json index 868c9239c..2f4b64ef9 100644 --- a/config/objects/rewardablePickable.json +++ b/config/objects/rewardablePickable.json @@ -19,7 +19,8 @@ "value" : 2000, "rarity" : 500 }, - + "compatibilityIdentifiers" : [ "object" ], + "blockedVisitable" : true, "visitMode" : "unlimited", "selectMode" : "selectFirst", @@ -60,7 +61,8 @@ "value" : 2000, "rarity" : 100 }, - + "compatibilityIdentifiers" : [ "object" ], + "blockedVisitable" : true, "visitMode" : "unlimited", "selectMode" : "selectFirst", @@ -117,7 +119,8 @@ "value" : 1500, "rarity" : 500 }, - + "compatibilityIdentifiers" : [ "object" ], + "blockedVisitable" : true, "visitMode" : "unlimited", "selectMode" : "selectFirst", @@ -167,6 +170,7 @@ "value" : 1500, "rarity" : 50 }, + "compatibilityIdentifiers" : [ "object" ], "blockedVisitable" : true, "visitMode" : "unlimited", @@ -217,7 +221,8 @@ "value" : 1500, "rarity" : 1000 }, - + "compatibilityIdentifiers" : [ "object" ], + "blockedVisitable" : true, "onSelectMessage" : 146, "visitMode" : "unlimited", diff --git a/config/widgets/settings/settingsMainContainer.json b/config/widgets/settings/settingsMainContainer.json index 8e7d78f8f..014fc1d96 100644 --- a/config/widgets/settings/settingsMainContainer.json +++ b/config/widgets/settings/settingsMainContainer.json @@ -153,7 +153,7 @@ "imageOrder": [1, 0, 2, 3], "help": "core.help.325", "callback": "closeWindow", - "hotkey": ["esc", "backspace"] + "hotkey": ["escape", "backspace"] } ] } diff --git a/docs/conan.md b/docs/conan.md index 063ed2200..352085f5d 100644 --- a/docs/conan.md +++ b/docs/conan.md @@ -35,7 +35,10 @@ The following platforms are supported and known to work, others might require ch if you want x86, otherwise pick **vcmi-deps-windows-conan.tgz** - [Android](https://github.com/vcmi/vcmi-dependencies/releases) -3. Only if you have Apple Silicon Mac and trying to build for macOS or iOS: follow [instructions how to build Qt host tools for Apple Silicon](https://github.com/vcmi/vcmi-ios-deps#note-for-arm-macs), on step 3 copy them to `~/.conan/data/qt/5.15.x/_/_/package/SOME_HASH/bin` (`5.15.x` and `SOME_HASH` are placeholders). +3. Only if you have Apple Silicon Mac and trying to build for macOS or iOS: + + 1. Open file `~/.conan/data/qt/5.15.x/_/_/export/conanfile.py` (`5.15.x` is a placeholder), search for string `Designer` (there should be only one match), comment this line and the one above it by inserting `#` in the beginning, and save the file. + 2. (optional) If you don't want to use Rosetta, follow [instructions how to build Qt host tools for Apple Silicon](https://github.com/vcmi/vcmi-ios-deps#note-for-arm-macs), on step 3 copy them to `~/.conan/data/qt/5.15.x/_/_/package/SOME_HASH/bin` (`5.15.x` and `SOME_HASH` are placeholders). Make sure **not** to copy `qt.conf`! ## Generate CMake integration diff --git a/include/vcmi/spells/Caster.h b/include/vcmi/spells/Caster.h index 46e347386..09e28aa18 100644 --- a/include/vcmi/spells/Caster.h +++ b/include/vcmi/spells/Caster.h @@ -66,6 +66,8 @@ public: virtual void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const = 0; virtual void spendMana(ServerCallback * server, const int32_t spellCost) const = 0; + + virtual int32_t manaLimit() const = 0; ///used to identify actual hero caster virtual const CGHeroInstance * getHeroCaster() const = 0; diff --git a/launcher/modManager/cmodlistview_moc.ui b/launcher/modManager/cmodlistview_moc.ui index 4b8060ed7..72961f923 100644 --- a/launcher/modManager/cmodlistview_moc.ui +++ b/launcher/modManager/cmodlistview_moc.ui @@ -14,21 +14,6 @@ - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - @@ -124,154 +109,166 @@ - - - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - - 24 - 24 - - - - QAbstractItemView::ScrollPerItem - - - QAbstractItemView::ScrollPerPixel - - - true - - - false - - - - - - - 0 - - - - Description - - - - 4 - - - 4 - - - 4 - - - 4 - - - - - - 0 - 0 - - - - true - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + + + 0 + 0 + + + + Qt::Horizontal + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + 24 + 24 + + + + QAbstractItemView::ScrollPerItem + + + QAbstractItemView::ScrollPerPixel + + + true + + + false + + + + + + 0 + 0 + + + + 0 + + + + Description + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 0 + 0 + + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +hr { height: 1px; border-width: 0; } +</style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:11pt;"><br /></p></body></html> - - - true - - - true - - - - - - - - Changelog - - - - 4 - - - 4 - - - 4 - - - 4 - - - - - - - - - Screenshots - - - - 4 - - - 4 - - - 4 - - - 4 - - - - - Qt::ScrollBarAlwaysOff - - - QAbstractItemView::NoSelection - - - QAbstractItemView::SelectRows - - - - 240 - 180 - - - - QListView::IconMode - - - true - - - - - + + + true + + + true + + + + - - + + + Changelog + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + + + + Screenshots + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectRows + + + + 240 + 180 + + + + QListView::IconMode + + + true + + + + + + + @@ -279,7 +276,7 @@ p, li { white-space: pre-wrap; } true - + 0 0 @@ -306,7 +303,7 @@ p, li { white-space: pre-wrap; } - + 0 0 diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index f2aff395c..b3673e0f0 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -960,9 +960,6 @@ void CCreatureHandler::loadStackExperience(CCreature * creature, const JsonNode { for (const JsonNode &exp : input.Vector()) { - auto bonus = JsonUtils::parseBonus (exp["bonus"]); - bonus->source = Bonus::STACK_EXPERIENCE; - bonus->duration = Bonus::PERMANENT; const JsonVector &values = exp["values"].Vector(); int lowerLimit = 1;//, upperLimit = 255; if (values[0].getType() == JsonNode::JsonType::DATA_BOOL) @@ -971,8 +968,14 @@ void CCreatureHandler::loadStackExperience(CCreature * creature, const JsonNode { if(val.Bool()) { + // parse each bonus separately + // we can not create copies since identifiers resolution does not tracks copies + // leading to unset identifier values in copies + auto bonus = JsonUtils::parseBonus (exp["bonus"]); + bonus->source = Bonus::STACK_EXPERIENCE; + bonus->duration = Bonus::PERMANENT; bonus->limiter = std::make_shared(RankRangeLimiter(lowerLimit)); - creature->addNewBonus (std::make_shared(*bonus)); //bonuses must be unique objects + creature->addNewBonus (bonus); break; //TODO: allow bonuses to turn off? } ++lowerLimit; @@ -985,9 +988,14 @@ void CCreatureHandler::loadStackExperience(CCreature * creature, const JsonNode { if (val.Float() != lastVal) { - bonus->val = static_cast(val.Float()) - lastVal; + JsonNode bonusInput = exp["bonus"]; + bonusInput["val"].Float() = static_cast(val.Float()) - lastVal; + + auto bonus = JsonUtils::parseBonus (bonusInput); + bonus->source = Bonus::STACK_EXPERIENCE; + bonus->duration = Bonus::PERMANENT; bonus->limiter.reset (new RankRangeLimiter(lowerLimit)); - creature->addNewBonus (std::make_shared(*bonus)); + creature->addNewBonus (bonus); } lastVal = static_cast(val.Float()); ++lowerLimit; diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index fee41e281..4b93c7b04 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -82,6 +82,7 @@ public: //battle call-ins virtual BattleAction activeStack(const CStack * stack)=0; //called when it's turn of that stack virtual void yourTacticPhase(int distance){}; //called when interface has opportunity to use Tactics skill -> use cb->battleMakeTacticAction from this function + virtual void forceEndTacticPhase(){}; //force the tatic phase to end to clean up the tactic phase thread }; /// Central class for managing human player / AI interface logic diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index a903a775b..6e2039948 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -790,11 +790,6 @@ int IBonusBearer::getMaxDamage(bool ranged) const return valOfBonuses(selector, cachingStr); } -si32 IBonusBearer::manaLimit() const -{ - return 0; -} - int IBonusBearer::getPrimSkillLevel(PrimarySkill::PrimarySkill id) const { static const CSelector selectorAllSkills = Selector::type()(Bonus::PRIMARY_SKILL); diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index a164854cd..213f2b86b 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -764,7 +764,6 @@ public: virtual si32 magicResistance() const; ui32 Speed(int turn = 0, bool useBind = false) const; //get speed of creature with all modificators - virtual si32 manaLimit() const; //maximum mana value for this hero (basically 10*knowledge) int getPrimSkillLevel(PrimarySkill::PrimarySkill id) const; virtual int64_t getTreeVersion() const = 0; diff --git a/lib/VCMIDirs.cpp b/lib/VCMIDirs.cpp index 5c56dc54f..3c0692885 100644 --- a/lib/VCMIDirs.cpp +++ b/lib/VCMIDirs.cpp @@ -367,7 +367,12 @@ class IVCMIDirsUNIX : public IVCMIDirs bool IVCMIDirsUNIX::developmentMode() const { // We want to be able to run VCMI from single directory. E.g to run from build output directory - return bfs::exists("AI") && bfs::exists("config") && bfs::exists("Mods") && bfs::exists("vcmiserver") && bfs::exists("vcmiclient"); + const bool result = bfs::exists("AI") && bfs::exists("config") && bfs::exists("Mods") && bfs::exists("vcmiclient"); +#if SINGLE_PROCESS_APP + return result; +#else + return result && bfs::exists("vcmiserver"); +#endif } bfs::path IVCMIDirsUNIX::clientPath() const { return binaryPath() / "vcmiclient"; } diff --git a/lib/battle/BattleStateInfoForRetreat.h b/lib/battle/BattleStateInfoForRetreat.h index 4dc713e41..341ed7ffe 100644 --- a/lib/battle/BattleStateInfoForRetreat.h +++ b/lib/battle/BattleStateInfoForRetreat.h @@ -29,6 +29,7 @@ public: std::vector enemyStacks; const CGHeroInstance * ourHero; const CGHeroInstance * enemyHero; + int turnsSkippedByDefense; BattleStateInfoForRetreat(); uint64_t getOurStrength() const; diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index dc069e7ad..137fe8a79 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -488,6 +488,11 @@ void CUnitState::getCastDescription(const spells::Spell * spell, const std::vect text.addReplacement(MetaString::SPELL_NAME, spell->getIndex()); } +int32_t CUnitState::manaLimit() const +{ + return 0; //TODO: creature casting with mana mode (for mods) +} + bool CUnitState::ableToRetaliate() const { return alive() diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index 09e246db5..7bc6878bf 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -195,6 +195,7 @@ public: const CGHeroInstance * getHeroCaster() const override; void getCasterName(MetaString & text) const override; void getCastDescription(const spells::Spell * spell, const std::vector & attacked, MetaString & text) const override; + int32_t manaLimit() const override; bool ableToRetaliate() const override; bool alive() const override; diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 8a9d50256..3b142bb75 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -165,6 +165,8 @@ void CObjectClassesHandler::loadSubObject(const std::string & scope, const std:: obj->objects.push_back(object); registerObject(scope, obj->getJsonKey(), object->getSubTypeName(), object->subtype); + for (auto const & compatID : entry["compatibilityIdentifiers"].Vector()) + registerObject(scope, obj->getJsonKey(), compatID.String(), object->subtype); } void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index) @@ -176,6 +178,8 @@ void CObjectClassesHandler::loadSubObject(const std::string & scope, const std:: obj->objects[index] = object; registerObject(scope, obj->getJsonKey(), object->getSubTypeName(), object->subtype); + for (auto const & compatID : entry["compatibilityIdentifiers"].Vector()) + registerObject(scope, obj->getJsonKey(), compatID.String(), object->subtype); } TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index) diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index 5e03700fd..17e94634c 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -133,6 +133,11 @@ int3 CGObjectInstance::getPosition() const return pos; } +int3 CGObjectInstance::getTopVisiblePos() const +{ + return pos - appearance->getTopVisibleOffset(); +} + void CGObjectInstance::setOwner(const PlayerColor & ow) { tempOwner = ow; diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index 43c00a4e1..983050a9e 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -160,6 +160,7 @@ public: bool visitableAt(int x, int y) const; //returns true if object is visitable at location (x, y) (h3m pos) int3 visitablePos() const override; int3 getPosition() const override; + int3 getTopVisiblePos() const; bool blockingAt(int x, int y) const; //returns true if object is blocking location (x, y) (h3m pos) bool coveringAt(int x, int y) const; //returns true if object covers with picture location (x, y) (h3m pos) std::set getBlockedPos() const; //returns set of positions blocked by this object diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 9cfa405fd..0674ebfb3 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -155,8 +155,14 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const BlockingDialog sd(canRefuse, rewards.size() > 1); sd.player = h->tempOwner; sd.text = dialog; - for (auto index : rewards) - sd.components.push_back(info[index].reward.getDisplayedComponent(h)); + + if (rewards.size() > 1) + for (auto index : rewards) + sd.components.push_back(info[index].reward.getDisplayedComponent(h)); + + if (rewards.size() == 1) + info[rewards[0]].reward.loadComponents(sd.components, h); + cb->showBlockingDialog(&sd); }; diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index ad022cdb0..cf36e66a6 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -524,6 +524,22 @@ bool ObjectTemplate::isVisitableFrom(si8 X, si8 Y) const return dirMap[dy][dx] != 0; } +void ObjectTemplate::calculateTopVisibleOffset() +{ + for(int y = static_cast(getHeight()) - 1; y >= 0; y--) //Templates start from bottom-right corner + { + for(int x = 0; x < static_cast(getWidth()); x++) + { + if (isVisibleAt(x, y)) + { + topVisibleOffset = int3(x, y, 0); + return; + } + } + } + topVisibleOffset = int3(0, 0, 0); +} + void ObjectTemplate::calculateVisitableOffset() { for(int y = 0; y < static_cast(getHeight()); y++) @@ -559,6 +575,7 @@ void ObjectTemplate::recalculate() calculateBlockedOffsets(); calculateBlockMapOffset(); calculateVisitableOffset(); + calculateTopVisibleOffset(); if (visitable && visitDir == 0) logMod->warn("Template for %s is visitable but has no visitable directions!", animationFile); diff --git a/lib/mapObjects/ObjectTemplate.h b/lib/mapObjects/ObjectTemplate.h index 64e2e46ea..ef48b0b57 100644 --- a/lib/mapObjects/ObjectTemplate.h +++ b/lib/mapObjects/ObjectTemplate.h @@ -87,7 +87,12 @@ public: inline int3 getBlockMapOffset() const { return blockMapOffset; - }; + } + + inline int3 getTopVisibleOffset() const + { + return topVisibleOffset; + } // Checks if object is visitable from certain direction. X and Y must be between -1..+1 bool isVisitableFrom(si8 X, si8 Y) const; @@ -137,6 +142,7 @@ private: std::set blockedOffsets; int3 blockMapOffset; int3 visitableOffset; + int3 topVisibleOffset; void recalculate(); @@ -146,6 +152,7 @@ private: void calculateBlockedOffsets(); void calculateBlockMapOffset(); void calculateVisitableOffset(); + void calculateTopVisibleOffset(); public: template void serialize(Handler &h, const int version) diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 2c2fd72cf..d71d2bb7c 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -160,7 +160,7 @@ std::string CMapGenerator::getMapDescription() const std::stringstream ss; ss << boost::str(boost::format(std::string("Map created by the Random Map Generator.\nTemplate was %s, Random seed was %d, size %dx%d") + ", levels %d, players %d, computers %d, water %s, monster %s, VCMI map") % mapTemplate->getName() % - randomSeed % map->map().width % map->map().height % map->map().levels() % static_cast(mapGenOptions.getPlayerCount()) % + randomSeed % map->map().width % map->map().height % static_cast(map->map().levels()) % static_cast(mapGenOptions.getPlayerCount()) % static_cast(mapGenOptions.getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions.getWaterContent()] % monsterStrengthStr[monsterStrengthIndex]); diff --git a/lib/rmg/ObjectManager.cpp b/lib/rmg/ObjectManager.cpp index 1a144a4a9..74841d9a9 100644 --- a/lib/rmg/ObjectManager.cpp +++ b/lib/rmg/ObjectManager.cpp @@ -112,6 +112,9 @@ int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object continue; obj.setPosition(tile); + + if (obj.getVisibleTop().y < 0) + continue; if(!searchArea.contains(obj.getArea()) || !searchArea.overlap(obj.getAccessibleArea())) continue; @@ -131,6 +134,9 @@ int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object for(const auto & tile : searchArea.getTiles()) { obj.setPosition(tile); + + if (obj.getVisibleTop().y < 0) + continue; if(!searchArea.contains(obj.getArea()) || !searchArea.overlap(obj.getAccessibleArea())) continue; diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index 3b76a00f1..a073e9614 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -45,6 +45,11 @@ const Area & Object::Instance::getBlockedArea() const return dBlockedAreaCache; } +int3 Object::Instance::getTopTile() const +{ + return object().getTopVisiblePos(); +} + int3 Object::Instance::getPosition(bool isAbsolute) const { if(isAbsolute) @@ -284,6 +289,19 @@ const Area & Object::getArea() const return dFullAreaCache; } +const int3 Object::getVisibleTop() const +{ + int3 topTile(-1, 10000, -1); //Start at the bottom + for (const auto& i : dInstances) + { + if (i.getTopTile().y < topTile.y) + { + topTile = i.getTopTile(); + } + } + return topTile; +} + void Object::Instance::finalize(RmgMap & map) { if(!map.isOnMap(getPosition(true))) diff --git a/lib/rmg/RmgObject.h b/lib/rmg/RmgObject.h index 24a6f51b2..a164b9161 100644 --- a/lib/rmg/RmgObject.h +++ b/lib/rmg/RmgObject.h @@ -38,6 +38,7 @@ public: void setTemplate(TerrainId terrain); //cache invalidation void setAnyTemplate(); //cache invalidation + int3 getTopTile() const; int3 getPosition(bool isAbsolute = false) const; void setPosition(const int3 & position); //cache invalidation void setPositionRaw(const int3 & position); //no cache invalidation @@ -75,6 +76,7 @@ public: void setTemplate(const TerrainId & terrain); const Area & getArea() const; //lazy cache invalidation + const int3 getVisibleTop() const; void finalize(RmgMap & map); void clear(); diff --git a/lib/rmg/TreasurePlacer.cpp b/lib/rmg/TreasurePlacer.cpp index ee01a96d0..4f30883e8 100644 --- a/lib/rmg/TreasurePlacer.cpp +++ b/lib/rmg/TreasurePlacer.cpp @@ -254,19 +254,23 @@ void TreasurePlacer::addAllPossibleObjects() if(!creature->getAIValue() || tierValues.empty()) //bug #2681 return 0; //this box won't be generated + //Follow the rules from https://heroes.thelazy.net/index.php/Pandora%27s_Box + int actualTier = creature->getLevel() > tierValues.size() ? - tierValues.size() - 1 : - creature->getLevel() - 1; - float creaturesAmount = (static_cast(tierValues[actualTier])) / creature->getAIValue(); - if(creaturesAmount <= 5) + tierValues.size() - 1 : + creature->getLevel() - 1; + float creaturesAmount = std::floor((static_cast(tierValues[actualTier])) / creature->getAIValue()); + if (creaturesAmount < 1) { - creaturesAmount = boost::math::round(creaturesAmount); //allow single monsters - if(creaturesAmount < 1) - return 0; + return 0; + } + else if(creaturesAmount <= 5) + { + //No change } else if(creaturesAmount <= 12) { - (creaturesAmount /= 2) *= 2; + creaturesAmount = std::ceil(creaturesAmount / 2) * 2; } else if(creaturesAmount <= 50) { diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index 9d5565c05..607f6077f 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -273,6 +273,8 @@ public: template void serialize(Handler & h, const int version) { h & identifier; + if (version > 820) + h & modScope; h & id; h & level; h & power; diff --git a/lib/spells/ObstacleCasterProxy.cpp b/lib/spells/ObstacleCasterProxy.cpp index b36e28537..5b1d5ea78 100644 --- a/lib/spells/ObstacleCasterProxy.cpp +++ b/lib/spells/ObstacleCasterProxy.cpp @@ -58,6 +58,11 @@ int64_t ObstacleCasterProxy::getEffectValue(const Spell * spell) const return obs.minimalDamage; } +int32_t SilentCaster::manaLimit() const +{ + return 0; +} + SilentCaster::SilentCaster(PlayerColor owner_, const Caster * hero_): ProxyCaster(hero_), owner(std::move(owner_)) @@ -87,6 +92,5 @@ PlayerColor SilentCaster::getCasterOwner() const return owner; } - } -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/ObstacleCasterProxy.h b/lib/spells/ObstacleCasterProxy.h index eacdb0755..761eea2b5 100644 --- a/lib/spells/ObstacleCasterProxy.h +++ b/lib/spells/ObstacleCasterProxy.h @@ -28,6 +28,7 @@ public: void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const override; void spendMana(ServerCallback * server, const int spellCost) const override; PlayerColor getCasterOwner() const override; + int32_t manaLimit() const override; }; class DLL_LINKAGE ObstacleCasterProxy : public SilentCaster @@ -47,4 +48,4 @@ private: }; }// -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/ProxyCaster.cpp b/lib/spells/ProxyCaster.cpp index 1b72b3d34..2ffaa49c7 100644 --- a/lib/spells/ProxyCaster.cpp +++ b/lib/spells/ProxyCaster.cpp @@ -126,6 +126,14 @@ const CGHeroInstance * ProxyCaster::getHeroCaster() const return nullptr; } +int32_t ProxyCaster::manaLimit() const +{ + if(actualCaster) + return actualCaster->manaLimit(); + + return 0; +} + } VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/ProxyCaster.h b/lib/spells/ProxyCaster.h index da1422caa..85fcf240c 100644 --- a/lib/spells/ProxyCaster.h +++ b/lib/spells/ProxyCaster.h @@ -36,6 +36,7 @@ public: void getCastDescription(const Spell * spell, const std::vector & attacked, MetaString & text) const override; void spendMana(ServerCallback * server, const int32_t spellCost) const override; const CGHeroInstance * getHeroCaster() const override; + int32_t manaLimit() const override; protected: const Caster * actualCaster; diff --git a/lib/spells/effects/Catapult.cpp b/lib/spells/effects/Catapult.cpp index 2d0c8d24d..e6a3c0782 100644 --- a/lib/spells/effects/Catapult.cpp +++ b/lib/spells/effects/Catapult.cpp @@ -23,15 +23,11 @@ VCMI_LIB_NAMESPACE_BEGIN -static const std::string EFFECT_NAME = "core:catapult"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(Catapult, EFFECT_NAME); - bool Catapult::applicable(Problem & problem, const Mechanics * m) const { const auto *town = m->battle()->battleGetDefendedTown(); diff --git a/lib/spells/effects/Clone.cpp b/lib/spells/effects/Clone.cpp index 7dbe418e5..dadfb4d04 100644 --- a/lib/spells/effects/Clone.cpp +++ b/lib/spells/effects/Clone.cpp @@ -19,15 +19,11 @@ VCMI_LIB_NAMESPACE_BEGIN -static const std::string EFFECT_NAME = "core:clone"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(Clone, EFFECT_NAME); - void Clone::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const { for(const Destination & dest : target) diff --git a/lib/spells/effects/Damage.cpp b/lib/spells/effects/Damage.cpp index 0879d16bb..ccc727ec7 100644 --- a/lib/spells/effects/Damage.cpp +++ b/lib/spells/effects/Damage.cpp @@ -22,15 +22,11 @@ VCMI_LIB_NAMESPACE_BEGIN -static const std::string EFFECT_NAME = "core:damage"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(Damage, EFFECT_NAME); - void Damage::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const { StacksInjured stacksInjured; diff --git a/lib/spells/effects/DemonSummon.cpp b/lib/spells/effects/DemonSummon.cpp index 274b4be70..40ec769cc 100644 --- a/lib/spells/effects/DemonSummon.cpp +++ b/lib/spells/effects/DemonSummon.cpp @@ -19,15 +19,11 @@ VCMI_LIB_NAMESPACE_BEGIN -static const std::string EFFECT_NAME = "core:demonSummon"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(DemonSummon, EFFECT_NAME); - void DemonSummon::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const { BattleUnitsChanged pack; diff --git a/lib/spells/effects/Dispel.cpp b/lib/spells/effects/Dispel.cpp index 94836900f..88a437d12 100644 --- a/lib/spells/effects/Dispel.cpp +++ b/lib/spells/effects/Dispel.cpp @@ -23,15 +23,11 @@ VCMI_LIB_NAMESPACE_BEGIN -static const std::string EFFECT_NAME = "core:dispel"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(Dispel, EFFECT_NAME); - void Dispel::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const { const bool describe = server->describeChanges(); diff --git a/lib/spells/effects/Effect.h b/lib/spells/effects/Effect.h index 8e35945df..d70b6ec7a 100644 --- a/lib/spells/effects/Effect.h +++ b/lib/spells/effects/Effect.h @@ -35,9 +35,6 @@ class Effects; class Effect; class Registry; -template -class RegisterEffect; - using TargetType = spells::AimType; class DLL_LINKAGE Effect diff --git a/lib/spells/effects/Heal.cpp b/lib/spells/effects/Heal.cpp index 1354e8b0d..894b3f592 100644 --- a/lib/spells/effects/Heal.cpp +++ b/lib/spells/effects/Heal.cpp @@ -22,16 +22,11 @@ VCMI_LIB_NAMESPACE_BEGIN - -static const std::string EFFECT_NAME = "core:heal"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(Heal, EFFECT_NAME); - void Heal::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const { apply(m->getEffectValue(), server, m, target); diff --git a/lib/spells/effects/Moat.cpp b/lib/spells/effects/Moat.cpp index fc6c87b01..fbed314bf 100644 --- a/lib/spells/effects/Moat.cpp +++ b/lib/spells/effects/Moat.cpp @@ -22,15 +22,11 @@ VCMI_LIB_NAMESPACE_BEGIN -static const std::string EFFECT_NAME = "core:moat"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(Moat, EFFECT_NAME); - static void serializeMoatHexes(JsonSerializeFormat & handler, const std::string & fieldName, std::vector> & moatHexes) { { diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index afd163444..b2ffec0ca 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/lib/spells/effects/Obstacle.cpp @@ -21,15 +21,11 @@ VCMI_LIB_NAMESPACE_BEGIN -static const std::string EFFECT_NAME = "core:obstacle"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(Obstacle, EFFECT_NAME); - using RelativeShape = std::vector>; static void serializeRelativeShape(JsonSerializeFormat & handler, const std::string & fieldName, RelativeShape & value) diff --git a/lib/spells/effects/Registry.cpp b/lib/spells/effects/Registry.cpp index 0377c55c7..ced61dc7f 100644 --- a/lib/spells/effects/Registry.cpp +++ b/lib/spells/effects/Registry.cpp @@ -11,6 +11,24 @@ #include "Registry.h" +#include "Catapult.h" +#include "Clone.h" +#include "Damage.h" +#include "DemonSummon.h" +#include "Dispel.h" +#include "Effect.h" +#include "Effects.h" +#include "Heal.h" +#include "Moat.h" +#include "LocationEffect.h" +#include "Obstacle.h" +#include "RemoveObstacle.h" +#include "Sacrifice.h" +#include "Summon.h" +#include "Teleport.h" +#include "Timed.h" +#include "UnitEffect.h" + VCMI_LIB_NAMESPACE_BEGIN namespace spells @@ -23,6 +41,23 @@ namespace detail class RegistryImpl : public Registry { public: + RegistryImpl() + { + add("core:catapult", std::make_shared>()); + add("core:clone", std::make_shared>()); + add("core:damage", std::make_shared>()); + add("core:demonSummon", std::make_shared>()); + add("core:dispel", std::make_shared>()); + add("core:heal", std::make_shared>()); + add("core:moat", std::make_shared>()); + add("core:obstacle", std::make_shared>()); + add("core:removeObstacle", std::make_shared>()); + add("core:sacrifice", std::make_shared>()); + add("core:summon", std::make_shared>()); + add("core:teleport", std::make_shared>()); + add("core:timed", std::make_shared>()); + } + const IEffectFactory * find(const std::string & name) const override { auto iter = data.find(name); diff --git a/lib/spells/effects/Registry.h b/lib/spells/effects/Registry.h index 912cced73..dc56eeb4b 100644 --- a/lib/spells/effects/Registry.h +++ b/lib/spells/effects/Registry.h @@ -12,13 +12,6 @@ #include "Effect.h" -#define VCMI_REGISTER_SPELL_EFFECT(Type, Name) \ -namespace\ -{\ -RegisterEffect register ## Type(Name);\ -}\ -\ - VCMI_LIB_NAMESPACE_BEGIN namespace spells @@ -60,17 +53,6 @@ public: } }; -template -class RegisterEffect -{ -public: - RegisterEffect(const std::string & name) - { - auto f = std::make_shared>(); - GlobalRegistry::get()->add(name, f); - } -}; - } } diff --git a/lib/spells/effects/RemoveObstacle.cpp b/lib/spells/effects/RemoveObstacle.cpp index 096de59c0..112435dcb 100644 --- a/lib/spells/effects/RemoveObstacle.cpp +++ b/lib/spells/effects/RemoveObstacle.cpp @@ -22,15 +22,11 @@ VCMI_LIB_NAMESPACE_BEGIN -static const std::string EFFECT_NAME = "core:removeObstacle"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(RemoveObstacle, EFFECT_NAME); - bool RemoveObstacle::applicable(Problem & problem, const Mechanics * m) const { if (getTargets(m, EffectTarget(), true).empty()) diff --git a/lib/spells/effects/Sacrifice.cpp b/lib/spells/effects/Sacrifice.cpp index dbe864fe1..1e6404434 100644 --- a/lib/spells/effects/Sacrifice.cpp +++ b/lib/spells/effects/Sacrifice.cpp @@ -21,16 +21,11 @@ VCMI_LIB_NAMESPACE_BEGIN - -static const std::string EFFECT_NAME = "core:sacrifice"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(Sacrifice, EFFECT_NAME); - void Sacrifice::adjustTargetTypes(std::vector & types) const { if(!types.empty()) diff --git a/lib/spells/effects/Summon.cpp b/lib/spells/effects/Summon.cpp index 0b18fcc0e..18e700ff0 100644 --- a/lib/spells/effects/Summon.cpp +++ b/lib/spells/effects/Summon.cpp @@ -24,16 +24,11 @@ VCMI_LIB_NAMESPACE_BEGIN - -static const std::string EFFECT_NAME = "core:summon"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(Summon, EFFECT_NAME); - void Summon::adjustAffectedHexes(std::set & hexes, const Mechanics * m, const Target & spellTarget) const { //no hexes affected diff --git a/lib/spells/effects/Teleport.cpp b/lib/spells/effects/Teleport.cpp index eb4a5e9e0..af7607460 100644 --- a/lib/spells/effects/Teleport.cpp +++ b/lib/spells/effects/Teleport.cpp @@ -18,16 +18,10 @@ VCMI_LIB_NAMESPACE_BEGIN -//TODO: Teleport effect - -static const std::string EFFECT_NAME = "core:teleport"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(Teleport, EFFECT_NAME); - void Teleport::adjustTargetTypes(std::vector & types) const { diff --git a/lib/spells/effects/Timed.cpp b/lib/spells/effects/Timed.cpp index 83a795315..b00f4a2dd 100644 --- a/lib/spells/effects/Timed.cpp +++ b/lib/spells/effects/Timed.cpp @@ -20,15 +20,11 @@ VCMI_LIB_NAMESPACE_BEGIN -static const std::string EFFECT_NAME = "core:timed"; - namespace spells { namespace effects { -VCMI_REGISTER_SPELL_EFFECT(Timed, EFFECT_NAME); - static void describeEffect(std::vector & log, const Mechanics * m, const std::vector & bonuses, const battle::Unit * target) { auto addLogLine = [&](const int32_t baseTextID, const boost::logic::tribool & plural) diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 04a779a2f..329cb9176 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -560,7 +560,10 @@ void MainWindow::loadObjectsTree() connect(b, &QPushButton::clicked, this, [this, terrain]{ terrainButtonClicked(terrain->getId()); }); //filter - ui->terrainFilterCombo->addItem(QString::fromStdString(terrain->getNameTranslated())); + QString displayName = QString::fromStdString(terrain->getNameTranslated()); + QString uniqueName = QString::fromStdString(terrain->getJsonKey()); + + ui->terrainFilterCombo->addItem(displayName, QVariant(uniqueName)); } //add spacer to keep terrain button on the top ui->terrainLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); @@ -936,23 +939,24 @@ void MainWindow::treeViewSelected(const QModelIndex & index, const QModelIndex & preparePreview(index); } -void MainWindow::on_terrainFilterCombo_currentTextChanged(const QString &arg1) +void MainWindow::on_terrainFilterCombo_currentIndexChanged(int index) { if(!objectBrowser) return; + QString uniqueName = ui->terrainFilterCombo->itemData(index).toString(); + objectBrowser->terrain = TerrainId(ETerrainId::ANY_TERRAIN); - if (!arg1.isEmpty()) + if (!uniqueName.isEmpty()) { for (auto const & terrain : VLC->terrainTypeHandler->objects) - if (terrain->getJsonKey() == arg1.toStdString()) + if (terrain->getJsonKey() == uniqueName.toStdString()) objectBrowser->terrain = terrain->getId(); } objectBrowser->invalidate(); objectBrowser->sort(0); } - void MainWindow::on_filter_textChanged(const QString &arg1) { if(!objectBrowser) diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index 2408a4dd5..16f5e6a18 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -83,7 +83,7 @@ private slots: void on_toolErase_clicked(); - void on_terrainFilterCombo_currentTextChanged(const QString &arg1); + void on_terrainFilterCombo_currentIndexChanged(int index); void on_filter_textChanged(const QString &arg1); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index e9fa2d663..858082982 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4842,8 +4842,9 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) { const CSpell * spell = SpellID(healerAbility->subtype).toSpell(); spells::BattleCast parameters(gs->curB, healer, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can heal infinitely by first aid tent + auto dest = battle::Destination(destStack, target.at(0).hexValue); parameters.setSpellLevel(0); - parameters.cast(spellEnv, target); + parameters.cast(spellEnv, {dest}); } break; }