1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

Merge beta into develop

This commit is contained in:
Ivan Savenko 2023-04-11 19:37:35 +03:00
commit dfa2e2a349
91 changed files with 730 additions and 442 deletions

View File

@ -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;

View File

@ -90,6 +90,7 @@ void CBattleAI::initBattleInterface(std::shared_ptr<Environment> 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::vector<Battl
for(auto hex : hexes)
{
if(vstd::contains(avHexes, hex))
{
return BattleAction::makeMove(stack, hex);
}
if(stack->coversPos(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<BattleAction> 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;
}

View File

@ -60,6 +60,7 @@ class CBattleAI : public CBattleGameInterface
//Previous setting of cb
bool wasWaitingForRealize, wasUnlockingGs;
int movesSkippedByDefense;
public:
CBattleAI();

View File

@ -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<const battle::Unit *> ourStacks;
std::vector<const battle::Unit *> enemyStacks;

View File

@ -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)
{
}
};

View File

@ -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<ScreenResSettingController.ScreenRes, Config> mCtrlScreenRes;
private LauncherSettingController<String, Config> mCtrlCodepage;
private LauncherSettingController<String, Config> mCtrlLanguage;
private LauncherSettingController<PointerModeSettingController.PointerMode, Config> mCtrlPointerMode;
private LauncherSettingController<Void, Void> mCtrlStart;
private LauncherSettingController<Float, Config> 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);

View File

@ -50,7 +50,7 @@ import eu.vcmi.vcmi.util.ServerResponse;
public class ActivityMods extends ActivityWithToolbar
{
private static final boolean ENABLE_REPO_DOWNLOADING = true;
private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/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;

View File

@ -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();
}
}
}

View File

@ -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<String>
{
private static final List<String> 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;
}
}

View File

@ -8,9 +8,9 @@ import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class CodepageSettingController extends LauncherSettingWithDialogController<String, Config>
public class LanguageSettingController extends LauncherSettingWithDialogController<String, Config>
{
public CodepageSettingController(final AppCompatActivity activity)
public LanguageSettingController(final AppCompatActivity activity)
{
super(activity);
}
@ -18,20 +18,20 @@ public class CodepageSettingController extends LauncherSettingWithDialogControll
@Override
protected LauncherSettingDialog<String> 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);
}
}

View File

@ -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<String>
{
private static final List<String> 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;
}
}

View File

@ -10,9 +10,9 @@
<string name="launcher_btn_start_subtitle">Aktuelle VCMI-Version: %1$s</string>
<string name="launcher_btn_mods_title">Mods</string>
<string name="launcher_btn_mods_subtitle">Neue Burgen, Kreaturen, Objekte und Erweiterungen hinzufügen</string>
<string name="launcher_btn_cp_title">Zeichensatz</string>
<string name="launcher_btn_cp_subtitle_unknown">Aktuell: unbekannt</string>
<string name="launcher_btn_cp_subtitle">Aktuell: %1$s</string>
<string name="launcher_btn_language_title">Sprache</string>
<string name="launcher_btn_language_subtitle_unknown">Aktuell: unbekannt</string>
<string name="launcher_btn_language_subtitle">Aktuell: %1$s</string>
<string name="launcher_btn_pointermode_title">Zeigermodus ändern</string>
<string name="launcher_btn_pointermode_subtitle">Aktuell: %1$s</string>
<string name="launcher_btn_pointermulti_subtitle">Aktuell: %1$s</string>

View File

@ -10,9 +10,9 @@
<string name="launcher_btn_start_subtitle">Obecna wersja VCMI: %1$s</string>
<string name="launcher_btn_mods_title">Mody</string>
<string name="launcher_btn_mods_subtitle">Zainstaluj nowe frakcje, obiekty, dodatki</string>
<string name="launcher_btn_cp_title">Strona kodowa</string>
<string name="launcher_btn_cp_subtitle_unknown">Obecnie: nieznane</string>
<string name="launcher_btn_cp_subtitle">Obecnie: %1$s</string>
<string name="launcher_btn_language_title">Język</string>
<string name="launcher_btn_language_subtitle_unknown">Obecnie: nieznane</string>
<string name="launcher_btn_language_subtitle">Obecnie: %1$s</string>
<string name="launcher_btn_pointermode_title">Zmień tryb kursora</string>
<string name="launcher_btn_pointermode_subtitle">Obecnie: %1$s</string>
<string name="launcher_btn_pointermulti_title">Mnożnik prędkości kursora</string>

View File

@ -10,9 +10,9 @@
<string name="launcher_btn_start_subtitle">Текущая версия VCMI: %1$s</string>
<string name="launcher_btn_mods_title">Моды</string>
<string name="launcher_btn_mods_subtitle">Добавить новые замки, существа, объекты, расширения</string>
<string name="launcher_btn_cp_title">Кодовая страница</string>
<string name="launcher_btn_cp_subtitle_unknown">Текущая: неизвестно</string>
<string name="launcher_btn_cp_subtitle">Текущая: %1$s</string>
<string name="launcher_btn_language_title">Язык</string>
<string name="launcher_btn_language_subtitle_unknown">Текущая: неизвестно</string>
<string name="launcher_btn_language_subtitle">Текущая: %1$s</string>
<string name="launcher_btn_pointermode_title">Изменить режим управления указателем</string>
<string name="launcher_btn_pointermode_subtitle">Currently: %1$s</string>
<string name="launcher_btn_pointermulti_subtitle">Текущая: %1$s</string>
@ -60,4 +60,4 @@
<string name="launcher_btn_import_title">Загрузить данные VCMI во внутреннее хранилище</string>
<string name="launcher_btn_import_description">Скопировать данные VCMI во внутреннее хранилище. Вы можете загрузить старую папку vcmi-data от версии 0.99 или файлы Героев</string>
<string name="launcher_progress_copy">Копируем %1$s</string>
</resources>
</resources>

View File

@ -10,9 +10,9 @@
<string name="launcher_btn_start_subtitle">Поточна версія VCMI: %1$s</string>
<string name="launcher_btn_mods_title">Моди</string>
<string name="launcher_btn_mods_subtitle">Додати нові замки, істот, об’єкти, розширення</string>
<string name="launcher_btn_cp_title">Кодова сторінка</string>
<string name="launcher_btn_cp_subtitle_unknown">Поточна: невідомо</string>
<string name="launcher_btn_cp_subtitle">Поточна: %1$s</string>
<string name="launcher_btn_language_title">Мова</string>
<string name="launcher_btn_language_subtitle_unknown">Поточна: невідомо</string>
<string name="launcher_btn_language_subtitle">Поточна: %1$s</string>
<string name="launcher_btn_pointermode_title">Змінити режим керування курсором</string>
<string name="launcher_btn_pointermode_subtitle">Поточна: %1$s</string>
<string name="launcher_btn_pointermulti_subtitle">Поточна: %1$s</string>
@ -60,4 +60,4 @@
<string name="launcher_btn_import_title">Завантажити дані VCMI у внутрішнє сховище</string>
<string name="launcher_btn_import_description">Копіювати дані VCMI у внутрішнє сховище. Ви можете завантажити стару папку vcmi-data від версії 0.99 чи файли героїв</string>
<string name="launcher_progress_copy">Копіюємо %1$s</string>
</resources>
</resources>

View File

@ -15,9 +15,9 @@
<string name="launcher_btn_start_subtitle">Current VCMI version: %1$s</string>
<string name="launcher_btn_mods_title">Mods</string>
<string name="launcher_btn_mods_subtitle">Install new factions, objects, extras</string>
<string name="launcher_btn_cp_title">Codepage</string>
<string name="launcher_btn_cp_subtitle_unknown">Currently: unknown</string>
<string name="launcher_btn_cp_subtitle">Currently: %1$s</string>
<string name="launcher_btn_language_title">Language</string>
<string name="launcher_btn_language_subtitle_unknown">Currently: unknown</string>
<string name="launcher_btn_language_subtitle">Currently: %1$s</string>
<string name="launcher_btn_pointermode_title">Change pointer mode</string>
<string name="launcher_btn_pointermode_subtitle">Currently: %1$s</string>
<string name="launcher_btn_pointermulti_title">Relative pointer speed multiplier</string>

View File

@ -446,15 +446,7 @@ void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart)
{
try
{
queueNext(std::make_unique<MusicEntry>(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<MusicEntry>(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)
{

View File

@ -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<Component> & components, int soundID)
{
EVENT_HANDLER_CALLED_BY_CLIENT;

View File

@ -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<bool()> onYes);

View File

@ -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 <memory>
#include <vcmi/events/EventBus.h>
#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<boost::thread>(&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)

View File

@ -9,6 +9,7 @@
*/
#pragma once
#include <memory>
#include <vcmi/Environment.h>
#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<PlayerColor, std::shared_ptr<boost::thread>> playerActionThreads;
std::map<PlayerColor, std::unique_ptr<boost::thread>> playerTacticThreads;
void waitForMoveAndSend(PlayerColor color);
void reinitScripting();
};

View File

@ -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;
};
};

View File

@ -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<boost::mutex> 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);

View File

@ -173,7 +173,7 @@ CInfoBar::VisibleComponentInfo::VisibleComponentInfo(const std::vector<Component
auto fullRect = Rect(CInfoBar::offset, CInfoBar::offset, data_width - 2 * CInfoBar::offset, data_height - 2 * CInfoBar::offset);
auto textRect = fullRect;
auto imageRect = fullRect;
auto font = FONT_SMALL;
auto font = tiny ? FONT_TINY : FONT_SMALL;
auto maxComponents = 2;
if(!compsToDisplay.empty())
@ -210,11 +210,9 @@ CInfoBar::VisibleComponentInfo::VisibleComponentInfo(const std::vector<Component
comps = std::make_shared<CComponentBox>(vect, imageRect, 4, 4, 1, maxComponents);
}
else
font = tiny ? FONT_TINY : font;
if(!message.empty())
text = std::make_shared<CTextBox>(message, textRect, 0, font, ETextAlignment::CENTER, Colors::WHITE);
text = std::make_shared<CMultiLineLabel>(textRect, font, ETextAlignment::CENTER, Colors::WHITE, message);
}
void CInfoBar::playNewDaySound()

View File

@ -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<CComponentBox> comps;
std::shared_ptr<CTextBox> text;
std::shared_ptr<CMultiLineLabel> text;
public:
struct Cache
{

View File

@ -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();
}

View File

@ -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<StackQueue>(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<CIntObject>("menuTactics");
auto tacticNext = widget<CIntObject>("tacticNext");
auto tacticEnd = widget<CIntObject>("tacticEnd");
auto alternativeAction = widget<CIntObject>("alternativeAction");
menuBattle->disable();
console->disable();
if (alternativeAction)
alternativeAction->disable();
menuTactics->enable();
tacticNext->enable();
@ -248,9 +255,12 @@ void BattleWindow::tacticPhaseEnded()
auto menuTactics = widget<CIntObject>("menuTactics");
auto tacticNext = widget<CIntObject>("tacticNext");
auto tacticEnd = widget<CIntObject>("tacticEnd");
auto alternativeAction = widget<CIntObject>("alternativeAction");
menuBattle->enable();
console->enable();
if (alternativeAction)
alternativeAction->enable();
menuTactics->disable();
tacticNext->disable();

View File

@ -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

View File

@ -31,6 +31,8 @@ static std::map<std::string, int> 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<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const

View File

@ -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<CSlider>("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());
}
}
}

View File

@ -13,7 +13,6 @@
#include "../../lib/Point.h"
#include "../../lib/TextOperations.h"
//
size_t IFont::getStringWidth(const std::string & data) const
{

View File

@ -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<data.size(); i += TextOperations::getUnicodeCharacterSize(data[i]))
if (!canRepresentCharacter(data.data() + i))
return false;
return true;
}
void CBitmapFont::renderCharacter(SDL_Surface * surface, const BitmapChar & character, const SDL_Color & color, int &posX, int &posY) const
{
Rect clipRect;

View File

@ -41,6 +41,11 @@ public:
size_t getLineHeight() const override;
size_t getGlyphWidth(const char * data) const override;
/// returns true if this font contains provided utf-8 character
bool canRepresentCharacter(const char * data) const;
bool canRepresentString(const std::string & data) const;
friend class CBitmapHanFont;
friend class CTrueTypeFont;
};

View File

@ -10,6 +10,8 @@
#include "StdInc.h"
#include "CTrueTypeFont.h"
#include "CBitmapFont.h"
#include "../render/Colors.h"
#include "../renderSDL/SDL_Extensions.h"
@ -52,30 +54,45 @@ int CTrueTypeFont::getFontStyle(const JsonNode &config)
CTrueTypeFont::CTrueTypeFont(const JsonNode & fontConfig):
data(loadData(fontConfig)),
font(loadFont(fontConfig), TTF_CloseFont),
dropShadow(fontConfig["blend"].Bool()),
blended(fontConfig["blend"].Bool())
{
assert(font);
TTF_SetFontStyle(font.get(), getFontStyle(fontConfig));
std::string fallbackName = fontConfig["fallback"].String();
if (!fallbackName.empty())
fallbackFont = std::make_unique<CBitmapFont>(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())

View File

@ -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<CBitmapFont> fallbackFont;
const std::pair<std::unique_ptr<ui8[]>, ui64> data;
const std::unique_ptr<TTF_Font, void (*)(TTF_Font*)> font;
const bool blended;
const bool dropShadow;
std::pair<std::unique_ptr<ui8[]>, 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;

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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,

View File

@ -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",

View File

@ -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",

View File

@ -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,

View File

@ -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",

View File

@ -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",

View File

@ -153,7 +153,7 @@
"imageOrder": [1, 0, 2, 3],
"help": "core.help.325",
"callback": "closeWindow",
"hotkey": ["esc", "backspace"]
"hotkey": ["escape", "backspace"]
}
]
}

View File

@ -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

View File

@ -66,6 +66,8 @@ public:
virtual void getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & 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;

View File

@ -14,21 +14,6 @@
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
@ -124,154 +109,166 @@
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QTreeView" name="allModsView">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerItem</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabInfo">
<attribute name="title">
<string>Description</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item row="0" column="0">
<widget class="QTextBrowser" name="modInfoBrowser">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="html">
<string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
<widget class="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTreeView" name="allModsView">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerItem</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
</widget>
<widget class="QTabWidget" name="tabWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabInfo">
<attribute name="title">
<string>Description</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item row="0" column="0">
<widget class="QTextBrowser" name="modInfoBrowser">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="html">
<string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
hr { height: 1px; border-width: 0; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="openLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabChangelog">
<attribute name="title">
<string>Changelog</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item row="0" column="0">
<widget class="QTextBrowser" name="changelogBrowser"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabScreenshots">
<attribute name="title">
<string>Screenshots</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_6">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item row="0" column="0">
<widget class="QListWidget" name="screenshotsList">
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>240</width>
<height>180</height>
</size>
</property>
<property name="viewMode">
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="openLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
<widget class="QWidget" name="tabChangelog">
<attribute name="title">
<string>Changelog</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item row="0" column="0">
<widget class="QTextBrowser" name="changelogBrowser"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabScreenshots">
<attribute name="title">
<string>Screenshots</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_6">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item row="0" column="0">
<widget class="QListWidget" name="screenshotsList">
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>240</width>
<height>180</height>
</size>
</property>
<property name="viewMode">
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
</item>
<item>
<widget class="QWidget" name="progressWidget" native="true">
@ -279,7 +276,7 @@ p, li { white-space: pre-wrap; }
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -306,7 +303,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QProgressBar" name="progressBar">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>

View File

@ -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>(RankRangeLimiter(lowerLimit));
creature->addNewBonus (std::make_shared<Bonus>(*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<int>(val.Float()) - lastVal;
JsonNode bonusInput = exp["bonus"];
bonusInput["val"].Float() = static_cast<int>(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>(*bonus));
creature->addNewBonus (bonus);
}
lastVal = static_cast<int>(val.Float());
++lowerLimit;

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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"; }

View File

@ -29,6 +29,7 @@ public:
std::vector<const battle::Unit *> enemyStacks;
const CGHeroInstance * ourHero;
const CGHeroInstance * enemyHero;
int turnsSkippedByDefense;
BattleStateInfoForRetreat();
uint64_t getOurStrength() const;

View File

@ -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()

View File

@ -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<const Unit *> & attacked, MetaString & text) const override;
int32_t manaLimit() const override;
bool ableToRetaliate() const override;
bool alive() const override;

View File

@ -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)

View File

@ -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;

View File

@ -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<int3> getBlockedPos() const; //returns set of positions blocked by this object

View File

@ -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);
};

View File

@ -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<int>(getHeight()) - 1; y >= 0; y--) //Templates start from bottom-right corner
{
for(int x = 0; x < static_cast<int>(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<int>(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);

View File

@ -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<int3> blockedOffsets;
int3 blockMapOffset;
int3 visitableOffset;
int3 topVisibleOffset;
void recalculate();
@ -146,6 +152,7 @@ private:
void calculateBlockedOffsets();
void calculateBlockMapOffset();
void calculateVisitableOffset();
void calculateTopVisibleOffset();
public:
template <typename Handler> void serialize(Handler &h, const int version)

View File

@ -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<int>(mapGenOptions.getPlayerCount()) %
randomSeed % map->map().width % map->map().height % static_cast<int>(map->map().levels()) % static_cast<int>(mapGenOptions.getPlayerCount()) %
static_cast<int>(mapGenOptions.getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions.getWaterContent()] %
monsterStrengthStr[monsterStrengthIndex]);

View File

@ -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;

View File

@ -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)))

View File

@ -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();

View File

@ -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<float>(tierValues[actualTier])) / creature->getAIValue();
if(creaturesAmount <= 5)
tierValues.size() - 1 :
creature->getLevel() - 1;
float creaturesAmount = std::floor((static_cast<float>(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)
{

View File

@ -273,6 +273,8 @@ public:
template <typename Handler> void serialize(Handler & h, const int version)
{
h & identifier;
if (version > 820)
h & modScope;
h & id;
h & level;
h & power;

View File

@ -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
VCMI_LIB_NAMESPACE_END

View File

@ -28,6 +28,7 @@ public:
void getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & 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
VCMI_LIB_NAMESPACE_END

View File

@ -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

View File

@ -36,6 +36,7 @@ public:
void getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & 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;

View File

@ -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();

View File

@ -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)

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -35,9 +35,6 @@ class Effects;
class Effect;
class Registry;
template<typename F>
class RegisterEffect;
using TargetType = spells::AimType;
class DLL_LINKAGE Effect

View File

@ -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);

View File

@ -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<std::vector<BattleHex>> & moatHexes)
{
{

View File

@ -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<std::vector<BattleHex::EDir>>;
static void serializeRelativeShape(JsonSerializeFormat & handler, const std::string & fieldName, RelativeShape & value)

View File

@ -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<EffectFactory<Catapult>>());
add("core:clone", std::make_shared<EffectFactory<Clone>>());
add("core:damage", std::make_shared<EffectFactory<Damage>>());
add("core:demonSummon", std::make_shared<EffectFactory<DemonSummon>>());
add("core:dispel", std::make_shared<EffectFactory<Dispel>>());
add("core:heal", std::make_shared<EffectFactory<Heal>>());
add("core:moat", std::make_shared<EffectFactory<Moat>>());
add("core:obstacle", std::make_shared<EffectFactory<Obstacle>>());
add("core:removeObstacle", std::make_shared<EffectFactory<RemoveObstacle>>());
add("core:sacrifice", std::make_shared<EffectFactory<Sacrifice>>());
add("core:summon", std::make_shared<EffectFactory<Summon>>());
add("core:teleport", std::make_shared<EffectFactory<Teleport>>());
add("core:timed", std::make_shared<EffectFactory<Timed>>());
}
const IEffectFactory * find(const std::string & name) const override
{
auto iter = data.find(name);

View File

@ -12,13 +12,6 @@
#include "Effect.h"
#define VCMI_REGISTER_SPELL_EFFECT(Type, Name) \
namespace\
{\
RegisterEffect<Type> register ## Type(Name);\
}\
\
VCMI_LIB_NAMESPACE_BEGIN
namespace spells
@ -60,17 +53,6 @@ public:
}
};
template<typename E>
class RegisterEffect
{
public:
RegisterEffect(const std::string & name)
{
auto f = std::make_shared<EffectFactory<E>>();
GlobalRegistry::get()->add(name, f);
}
};
}
}

View File

@ -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())

View File

@ -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<TargetType> & types) const
{
if(!types.empty())

View File

@ -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<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const
{
//no hexes affected

View File

@ -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<TargetType> & types) const
{

View File

@ -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<MetaString> & log, const Mechanics * m, const std::vector<Bonus> & bonuses, const battle::Unit * target)
{
auto addLogLine = [&](const int32_t baseTextID, const boost::logic::tribool & plural)

View File

@ -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)

View File

@ -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);

View File

@ -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;
}