mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-13 19:54:17 +02:00
@@ -13,9 +13,9 @@
|
||||
// Eventually only IBattleInfoCallback and battle::Unit should be used,
|
||||
// CUnitState should be private and CStack should be removed completely
|
||||
|
||||
uint64_t averageDmg(const TDmgRange & range)
|
||||
uint64_t averageDmg(const DamageRange & range)
|
||||
{
|
||||
return (range.first + range.second) / 2;
|
||||
return (range.min + range.max) / 2;
|
||||
}
|
||||
|
||||
AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack)
|
||||
@@ -52,7 +52,7 @@ int64_t AttackPossibility::calculateDamageReduce(
|
||||
// FIXME: provide distance info for Jousting bonus
|
||||
auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attacker, 0);
|
||||
auto enemiesKilled = damageDealt / defender->MaxHealth() + (damageDealt % defender->MaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
|
||||
auto enemyDamage = averageDmg(enemyDamageBeforeAttack);
|
||||
auto enemyDamage = averageDmg(enemyDamageBeforeAttack.damage);
|
||||
auto damagePerEnemy = enemyDamage / (double)defender->getCount();
|
||||
|
||||
return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->MaxHealth()));
|
||||
@@ -85,7 +85,7 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & a
|
||||
auto rangeDmg = state.battleEstimateDamage(rangeAttackInfo);
|
||||
auto meleeDmg = state.battleEstimateDamage(meleeAttackInfo);
|
||||
|
||||
int64_t gain = averageDmg(rangeDmg) - averageDmg(meleeDmg) + 1;
|
||||
int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1;
|
||||
res += gain;
|
||||
}
|
||||
|
||||
@@ -156,16 +156,16 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
|
||||
{
|
||||
int64_t damageDealt, damageReceived, defenderDamageReduce, attackerDamageReduce;
|
||||
|
||||
TDmgRange retaliation(0, 0);
|
||||
DamageEstimation retaliation;
|
||||
auto attackDmg = state.battleEstimateDamage(ap.attack, &retaliation);
|
||||
|
||||
vstd::amin(attackDmg.first, defenderState->getAvailableHealth());
|
||||
vstd::amin(attackDmg.second, defenderState->getAvailableHealth());
|
||||
vstd::amin(attackDmg.damage.min, defenderState->getAvailableHealth());
|
||||
vstd::amin(attackDmg.damage.max, defenderState->getAvailableHealth());
|
||||
|
||||
vstd::amin(retaliation.first, ap.attackerState->getAvailableHealth());
|
||||
vstd::amin(retaliation.second, ap.attackerState->getAvailableHealth());
|
||||
vstd::amin(retaliation.damage.min, ap.attackerState->getAvailableHealth());
|
||||
vstd::amin(retaliation.damage.max, ap.attackerState->getAvailableHealth());
|
||||
|
||||
damageDealt = averageDmg(attackDmg);
|
||||
damageDealt = averageDmg(attackDmg.damage);
|
||||
defenderDamageReduce = calculateDamageReduce(attacker, defender, damageDealt, state);
|
||||
ap.attackerState->afterAttack(attackInfo.shooting, false);
|
||||
|
||||
@@ -175,7 +175,7 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
|
||||
|
||||
if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked)
|
||||
{
|
||||
damageReceived = averageDmg(retaliation);
|
||||
damageReceived = averageDmg(retaliation.damage);
|
||||
attackerDamageReduce = calculateDamageReduce(defender, attacker, damageReceived, state);
|
||||
defenderState->afterAttack(attackInfo.shooting, true);
|
||||
}
|
||||
|
@@ -68,7 +68,7 @@ int64_t BattleExchangeVariant::trackAttack(
|
||||
static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION);
|
||||
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
|
||||
|
||||
TDmgRange retaliation;
|
||||
DamageEstimation retaliation;
|
||||
// FIXME: provide distance info for Jousting bonus
|
||||
BattleAttackInfo bai(attacker.get(), defender.get(), 0, shooting);
|
||||
|
||||
@@ -78,7 +78,7 @@ int64_t BattleExchangeVariant::trackAttack(
|
||||
}
|
||||
|
||||
auto attack = cb.battleEstimateDamage(bai, &retaliation);
|
||||
int64_t attackDamage = (attack.first + attack.second) / 2;
|
||||
int64_t attackDamage = (attack.damage.min + attack.damage.max) / 2;
|
||||
int64_t defenderDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), defender.get(), attackDamage, cb);
|
||||
int64_t attackerDamageReduce = 0;
|
||||
|
||||
@@ -108,9 +108,9 @@ int64_t BattleExchangeVariant::trackAttack(
|
||||
|
||||
if(defender->alive() && defender->ableToRetaliate() && !counterAttacksBlocked && !shooting)
|
||||
{
|
||||
if(retaliation.second != 0)
|
||||
if(retaliation.damage.max != 0)
|
||||
{
|
||||
auto retaliationDamage = (retaliation.first + retaliation.second) / 2;
|
||||
auto retaliationDamage = (retaliation.damage.min + retaliation.damage.max) / 2;
|
||||
attackerDamageReduce = AttackPossibility::calculateDamageReduce(defender.get(), attacker.get(), retaliationDamage, cb);
|
||||
|
||||
if(!evaluateOnly)
|
||||
|
@@ -428,9 +428,9 @@ uint32_t HypotheticBattle::nextUnitId() const
|
||||
return nextId++;
|
||||
}
|
||||
|
||||
int64_t HypotheticBattle::getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const
|
||||
int64_t HypotheticBattle::getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const
|
||||
{
|
||||
return (damage.first + damage.second) / 2;
|
||||
return (damage.min + damage.max) / 2;
|
||||
}
|
||||
|
||||
int64_t HypotheticBattle::getTreeVersion() const
|
||||
|
@@ -138,7 +138,7 @@ public:
|
||||
|
||||
uint32_t nextUnitId() const override;
|
||||
|
||||
int64_t getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
|
||||
int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
|
||||
|
||||
int64_t getTreeVersion() const;
|
||||
|
||||
|
@@ -56,9 +56,10 @@ public:
|
||||
void calcDmg(const CStack * ourStack)
|
||||
{
|
||||
// FIXME: provide distance info for Jousting bonus
|
||||
TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, 0, &retal);
|
||||
adi = static_cast<int>((dmg.first + dmg.second) / 2);
|
||||
adr = static_cast<int>((retal.first + retal.second) / 2);
|
||||
DamageEstimation retal;
|
||||
DamageEstimation dmg = cbc->battleEstimateDamage(ourStack, s, 0, &retal);
|
||||
adi = static_cast<int>((dmg.damage.min + dmg.damage.max) / 2);
|
||||
adr = static_cast<int>((retal.damage.min + retal.damage.max) / 2);
|
||||
}
|
||||
|
||||
bool operator==(const EnemyInfo& ei) const
|
||||
|
289
Mods/vcmi/config/vcmi/chinese.json
Normal file
289
Mods/vcmi/config/vcmi/chinese.json
Normal file
@@ -0,0 +1,289 @@
|
||||
{
|
||||
"vcmi.adventureMap.monsterThreat.title" : "\n\n 威胁等级: ",
|
||||
"vcmi.adventureMap.monsterThreat.levels.0" : "极低",
|
||||
"vcmi.adventureMap.monsterThreat.levels.1" : "很低",
|
||||
"vcmi.adventureMap.monsterThreat.levels.2" : "低",
|
||||
"vcmi.adventureMap.monsterThreat.levels.3" : "较低",
|
||||
"vcmi.adventureMap.monsterThreat.levels.4" : "中等",
|
||||
"vcmi.adventureMap.monsterThreat.levels.5" : "较高",
|
||||
"vcmi.adventureMap.monsterThreat.levels.6" : "高",
|
||||
"vcmi.adventureMap.monsterThreat.levels.7" : "很高",
|
||||
"vcmi.adventureMap.monsterThreat.levels.8" : "挑战性的",
|
||||
"vcmi.adventureMap.monsterThreat.levels.9" : "压倒性的",
|
||||
"vcmi.adventureMap.monsterThreat.levels.10" : "致命的",
|
||||
"vcmi.adventureMap.monsterThreat.levels.11" : "无法取胜的",
|
||||
|
||||
"vcmi.adventureMap.confirmRestartGame" : "你想要重新开始游戏吗?",
|
||||
"vcmi.adventureMap.noTownWithMarket" : "没有足够的市场。",
|
||||
"vcmi.adventureMap.noTownWithTavern" : "没有酒馆可供查看。",
|
||||
"vcmi.adventureMap.spellUnknownProblem" : "无此魔法的信息。",
|
||||
"vcmi.adventureMap.playerAttacked" : "玩家遭受攻击: %s",
|
||||
"vcmi.adventureMap.moveCostDetails" : "移动点数 - 花费: %TURNS 轮 + %POINTS 点移动力, 剩余移动力: %REMAINING",
|
||||
"vcmi.adventureMap.moveCostDetailsNoTurns" : "移动点数 - 花费: %POINTS 点移动力, 剩余移动力: %REMAINING",
|
||||
|
||||
"vcmi.server.errors.existingProcess" : "另一个VCMI进程在运行,请结束当前进程。",
|
||||
"vcmi.server.errors.modsIncompatibility" : "需要加载mod:",
|
||||
"vcmi.server.confirmReconnect" : "连接到上次吗?",
|
||||
|
||||
"vcmi.settingsMainWindow.generalTab.hover" : "常规",
|
||||
"vcmi.settingsMainWindow.generalTab.help" : "切换到“系统选项”选项卡 - 这些设置与常规游戏客户端行为相关",
|
||||
"vcmi.settingsMainWindow.battleTab.hover" : "战斗",
|
||||
"vcmi.settingsMainWindow.battleTab.help" : "切换到“战斗选项”选项卡 - 这些设置允许配置战斗界面和相关内容",
|
||||
"vcmi.settingsMainWindow.adventureTab.hover" : "冒险地图",
|
||||
"vcmi.settingsMainWindow.adventureTab.help" : "切换到“冒险地图”选项卡 - 冒险地图允许你移动英雄",
|
||||
"vcmi.settingsMainWindow.otherTab.hover" : "其他设置",
|
||||
"vcmi.settingsMainWindow.otherTab.help" : "切换到“其他设置”选项卡 - 由于各种原因,这些选项不适合其他类别",
|
||||
|
||||
"vcmi.systemOptions.fullscreenButton.hover" : "全屏",
|
||||
"vcmi.systemOptions.fullscreenButton.help" : "{全屏n}\n\n 当你选择全屏时,VCMI将会全屏运行,否则只会运行在指定框内",
|
||||
"vcmi.systemOptions.resolutionButton.hover" : "分辨率",
|
||||
"vcmi.systemOptions.resolutionButton.help" : "{选择分辨率}\n\n 改变游戏的分辨率,达到更加清晰的效果。需要重新启动才能完成更改。",
|
||||
"vcmi.systemOptions.resolutionMenu.hover" : "选择分辨率",
|
||||
"vcmi.systemOptions.resolutionMenu.help" : "选择游戏的分辨率。",
|
||||
"vcmi.systemOptions.fullscreenFailed" : "{全屏}\n\n 选择切换到全屏失败!当前分辨率不支持全屏!",
|
||||
"vcmi.systemOptions.framerateButton.hover" : "显示传输帧数",
|
||||
"vcmi.systemOptions.framerateButton.help" : "{显示传输帧数}\n\n 打开/关闭在游戏窗口角落的传输帧数计数器。",
|
||||
|
||||
"vcmi.adventureOptions.numericQuantities.hover" : "生物数量显示",
|
||||
"vcmi.adventureOptions.numericQuantities.help" : "{生物数量显示}\n\n 以数字 A-B 格式显示不准确的敌方生物数量。",
|
||||
"vcmi.adventureOptions.forceMovementInfo.hover" : "在状态栏中显示移动力",
|
||||
"vcmi.adventureOptions.forceMovementInfo.help" : "{在状态栏中显示移动力}\n\n 不需要按ALT就可以显示移动力。",
|
||||
"vcmi.adventureOptions.showGrid.hover" : "显示六角网格",
|
||||
"vcmi.adventureOptions.showGrid.help" : "{显示六角网格}\n\n 在战场上显示六角网格。",
|
||||
"vcmi.adventureOptions.mapScrollSpeed4.hover": "4",
|
||||
"vcmi.adventureOptions.mapScrollSpeed4.help": "设置动画速度为超快",
|
||||
"vcmi.adventureOptions.mapScrollSpeed5.hover": "5",
|
||||
"vcmi.adventureOptions.mapScrollSpeed5.help": "设置动画速度为极速",
|
||||
|
||||
"vcmi.battleOptions.showQueue.hover": "显示移动次序",
|
||||
"vcmi.battleOptions.showQueue.help": "{显示移动次序}\n\n 显示当前生物的移动次序。",
|
||||
"vcmi.battleOptions.queueSizeLabel.hover": "次序条尺寸 (设置后下一场战斗生效)",
|
||||
"vcmi.battleOptions.queueSizeAutoButton.hover": "自动设置尺寸",
|
||||
"vcmi.battleOptions.queueSizeAutoButton.help": "根据游戏分辨率设置尺寸 (像素小于700为小尺寸,根据实际调整)",
|
||||
"vcmi.battleOptions.queueSizeSmallButton.hover": "小尺寸",
|
||||
"vcmi.battleOptions.queueSizeSmallButton.help": "设置次序条为小尺寸",
|
||||
"vcmi.battleOptions.queueSizeBigButton.hover": "大尺寸",
|
||||
"vcmi.battleOptions.queueSizeBigButton.help": "设置次寻条为大尺寸(不能在像素小于700时生效)",
|
||||
"vcmi.battleOptions.animationsSpeed4.hover": "4",
|
||||
"vcmi.battleOptions.animationsSpeed4.help": "设置动画速度为超快",
|
||||
"vcmi.battleOptions.animationsSpeed5.hover": "5",
|
||||
"vcmi.battleOptions.animationsSpeed5.help": "设置动画速度为极速",
|
||||
"vcmi.battleOptions.animationsSpeed6.hover": "6",
|
||||
"vcmi.battleOptions.animationsSpeed6.help": "设置动画速度为最快",
|
||||
"vcmi.battleOptions.skipBattleIntroMusic.hover": "跳过开场音乐",
|
||||
"vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过开场音乐}\n\n 战斗开始时跳过开场音乐,直接按Esc也可以跳过。",
|
||||
|
||||
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "显示所有可以招募的城镇生物",
|
||||
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{显示所有可以招募的城镇生物}\n\n 显示当前所有可供招募的城镇生物 (左下角)。",
|
||||
"vcmi.otherOptions.compactTownCreatureInfo.hover": "缩小城镇生物信息",
|
||||
"vcmi.otherOptions.compactTownCreatureInfo.help": "{缩小城镇生物信息}\n\n 将城镇生物信息最小化.",
|
||||
|
||||
"vcmi.townHall.missingBase" : "你必须先建造%s ",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "没有可供雇佣的生物。",
|
||||
"vcmi.townHall.greetingManaVortex" : "当你接近%s时,你的身体充满了新的能量。这使你的魔法值加倍。",
|
||||
"vcmi.townHall.greetingKnowledge" : "你学习了%s上的图形,并深入了解各种魔法的运作,这使你的知识点数+1。",
|
||||
"vcmi.townHall.greetingSpellPower" : "%s教你新的方法来集中你的魔法力量,这使你的力量点数+1。",
|
||||
"vcmi.townHall.greetingExperience" : "访问%s给你提供了更好的学习方法。这使你的经验值+1000。",
|
||||
"vcmi.townHall.greetingAttack" : "在%s参观后给你提供了更好的战斗技巧,这使你的攻击点数+1。",
|
||||
"vcmi.townHall.greetingDefence" : "在%s中度过一段时间后,经验丰富的勇士会教你额外的防御技能,这使你的防御点数+1。",
|
||||
"vcmi.townHall.hasNotProduced" : "本周%s并没有产生什么资源。",
|
||||
"vcmi.townHall.hasProduced" : "本周%s产生了%d个%s。",
|
||||
"vcmi.townHall.greetingCustomBonus" : "参观%s后,你的技巧有了提升。这使你受益匪浅。并且使你+%d %s%s",
|
||||
"vcmi.townHall.greetingCustomUntil" : "直到下一场战斗。",
|
||||
"vcmi.townHall.greetingInTownMagicWell" : "%s使你的魔法值恢复到最大值。",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "以下任何前提:",
|
||||
"vcmi.logicalExpressions.allOf" : "以下所有前提:",
|
||||
"vcmi.logicalExpressions.noneOf" : "无前提:",
|
||||
|
||||
"vcmi.heroWindow.openCommander.hover" : "开启指挥官界面",
|
||||
"vcmi.heroWindow.openCommander.help" : "开启英雄的指挥官界面",
|
||||
|
||||
"vcmi.commanderWindow.artifactMessage" : "你要把这个宝物还给英雄吗?",
|
||||
|
||||
"vcmi.creatureWindow.showBonuses.hover" : "属性界面",
|
||||
"vcmi.creatureWindow.showBonuses.help" : "显示指挥官的所有增强属性",
|
||||
"vcmi.creatureWindow.showSkills.hover" : "技能页面",
|
||||
"vcmi.creatureWindow.showSkills.help" : "显示指挥官的所有技能",
|
||||
"vcmi.creatureWindow.returnArtifact.hover" : "交换宝物",
|
||||
"vcmi.creatureWindow.returnArtifact.help" : "将宝物还到英雄的背包里",
|
||||
|
||||
"vcmi.questLog.hideComplete.hover" : "隐藏完成任务",
|
||||
"vcmi.questLog.hideComplete.help" : "隐藏所有完成的任务",
|
||||
|
||||
"vcmi.randomMapTab.widgets.defaultTemplate" : "默认",
|
||||
"vcmi.randomMapTab.widgets.templateLabel" : "格式",
|
||||
"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "设置...",
|
||||
"vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "同盟关系",
|
||||
|
||||
// few strings from WoG used by vcmi
|
||||
"vcmi.stackExperience.description" : "» 经 验 获 得 明 细 «\n\n生物类型 ................... : %s\n经验等级 ................. : %s (%i)\n经验点数 ............... : %i\n下一个等级所需经验 .. : %i\n每次战斗最大获得经验 ... : %i%% (%i)\n获得经验的生物数量 .... : %i\n最大招募数量\n不会丢失经验升级 .... : %i\n经验倍数 ........... : %.2f\n升级倍数 .............. : %.2f\n10级后经验值 ........ : %i\n最大招募数量下\n 升级到10级所需经验数量: %i",
|
||||
"vcmi.stackExperience.rank.1" : "新兵 1级",
|
||||
"vcmi.stackExperience.rank.2" : "列兵 2级",
|
||||
"vcmi.stackExperience.rank.3" : "下士 3级",
|
||||
"vcmi.stackExperience.rank.4" : "中士 4级",
|
||||
"vcmi.stackExperience.rank.5" : "上士 5级",
|
||||
"vcmi.stackExperience.rank.6" : "少尉 6级",
|
||||
"vcmi.stackExperience.rank.7" : "中尉 7级",
|
||||
"vcmi.stackExperience.rank.8" : "上尉 8级",
|
||||
"vcmi.stackExperience.rank.9" : "少校 9级",
|
||||
"vcmi.stackExperience.rank.10" : "中校 10级",
|
||||
"vcmi.stackExperience.rank.11" : "上校 11级",
|
||||
|
||||
"core.bonus.ADDITIONAL_ATTACK.name": "双击",
|
||||
"core.bonus.ADDITIONAL_ATTACK.description": "可以攻击两次",
|
||||
"core.bonus.ADDITIONAL_RETALIATION.name": "额外反击",
|
||||
"core.bonus.ADDITIONAL_RETALIATION.description": "可以额外反击 ${val} 次",
|
||||
"core.bonus.AIR_IMMUNITY.name": "气系免疫",
|
||||
"core.bonus.AIR_IMMUNITY.description": "免疫所有气系魔法",
|
||||
"core.bonus.ATTACKS_ALL_ADJACENT.name": "环击",
|
||||
"core.bonus.ATTACKS_ALL_ADJACENT.description": "攻击所有相邻部队",
|
||||
"core.bonus.BLOCKS_RETALIATION.name": "无反击",
|
||||
"core.bonus.BLOCKS_RETALIATION.description": "敌人无法反击",
|
||||
"core.bonus.BLOCKS_RANGED_RETALIATION.name": "远程无反击",
|
||||
"core.bonus.BLOCKS_RANGED_RETALIATION.description": "敌人无法对射击进行反击",
|
||||
"core.bonus.CATAPULT.name": "攻城",
|
||||
"core.bonus.CATAPULT.description": "可以攻击城墙",
|
||||
"core.bonus.CATAPULT_EXTRA_SHOTS.name": "额外攻击城墙",
|
||||
"core.bonus.CATAPULT_EXTRA_SHOTS.description": "可以额外攻击城墙 ${val} 次",
|
||||
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "施法消耗 - (${val})",
|
||||
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "减少英雄的施法消耗",
|
||||
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "对方施法消耗 + (${val})",
|
||||
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "增加对方施法消耗",
|
||||
"core.bonus.CHARGE_IMMUNITY.name": "I免疫冲锋",
|
||||
"core.bonus.CHARGE_IMMUNITY.description": "对冲锋特技的额外伤害免疫",
|
||||
"core.bonus.DARKNESS.name": "黑暗天幕",
|
||||
"core.bonus.DARKNESS.description": "增加 ${val} 半径黑幕",
|
||||
"core.bonus.DEATH_STARE.name": "死亡凝视 (${val}%)",
|
||||
"core.bonus.DEATH_STARE.description": "${val}% 几率直接杀死生物",
|
||||
"core.bonus.DEFENSIVE_STANCE.name": "防御奖励",
|
||||
"core.bonus.DEFENSIVE_STANCE.description": "当选择防御时+${val} 防御力",
|
||||
"core.bonus.DESTRUCTION.name": "毁灭",
|
||||
"core.bonus.DESTRUCTION.description": "有${val}% 杀死额外数量的部队",
|
||||
"core.bonus.DOUBLE_DAMAGE_CHANCE.name": "致命一击",
|
||||
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% 几率造成双倍伤害",
|
||||
"core.bonus.DRAGON_NATURE.name": "龙",
|
||||
"core.bonus.DRAGON_NATURE.description": "生物属于龙类",
|
||||
"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "魔法伤害免疫",
|
||||
"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "对魔法伤害免疫",
|
||||
"core.bonus.EARTH_IMMUNITY.name": "土系免疫",
|
||||
"core.bonus.EARTH_IMMUNITY.description": "免疫所有土系魔法",
|
||||
"core.bonus.ENCHANTER.name": "施法者",
|
||||
"core.bonus.ENCHANTER.description": "每回合群体施放 ${subtype.spell} ",
|
||||
"core.bonus.ENCHANTED.name": "魔法护身",
|
||||
"core.bonus.ENCHANTED.description": "自身被 ${subtype.spell} 魔法影响",
|
||||
"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "忽略防御 (${val}%)",
|
||||
"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "攻击时忽略对方部分防御力",
|
||||
"core.bonus.FIRE_IMMUNITY.name": "火系免疫",
|
||||
"core.bonus.FIRE_IMMUNITY.description": "免疫所有火系魔法",
|
||||
"core.bonus.FIRE_SHIELD.name": "烈火神盾 (${val}%)",
|
||||
"core.bonus.FIRE_SHIELD.description": "拥有烈火神盾护身",
|
||||
"core.bonus.FIRST_STRIKE.name": "抢先攻击",
|
||||
"core.bonus.FIRST_STRIKE.description": "在被反击前做出攻击",
|
||||
"core.bonus.FEAR.name": "恐惧",
|
||||
"core.bonus.FEAR.description": "引起恐惧",
|
||||
"core.bonus.FEARLESS.name": "无惧",
|
||||
"core.bonus.FEARLESS.description": "免疫恐惧",
|
||||
"core.bonus.FLYING.name": "飞行兵种",
|
||||
"core.bonus.FLYING.description": "生物可以飞行",
|
||||
"core.bonus.FREE_SHOOTING.name": "近身射击",
|
||||
"core.bonus.FREE_SHOOTING.description": "靠近敌方也能射击",
|
||||
"core.bonus.FULL_HP_REGENERATION.name": "重生",
|
||||
"core.bonus.FULL_HP_REGENERATION.description": "可以自动恢复所有生命值",
|
||||
"core.bonus.GARGOYLE.name": "石像鬼属性",
|
||||
"core.bonus.GARGOYLE.description": "不能被复活或治疗",
|
||||
"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "减少伤害 (${val}%)",
|
||||
"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "受攻击时减少受到的伤害",
|
||||
"core.bonus.HATE.name": "${subtype.creature}的死敌",
|
||||
"core.bonus.HATE.description": "对该部队造成 ${val}% 的额外伤害",
|
||||
"core.bonus.HEALER.name": "治疗",
|
||||
"core.bonus.HEALER.description": "可以治疗友军单位",
|
||||
"core.bonus.HP_REGENERATION.name": "重生",
|
||||
"core.bonus.HP_REGENERATION.description": "每回合恢复 ${val} 点生命值",
|
||||
"core.bonus.JOUSTING.name": "冲锋",
|
||||
"core.bonus.JOUSTING.description": "每格行动增加+5%伤害",
|
||||
"core.bonus.KING1.name": "一般顶级怪物",
|
||||
"core.bonus.KING1.description": "被初级屠戮成性影响",
|
||||
"core.bonus.KING2.name": "智慧顶级怪物",
|
||||
"core.bonus.KING2.description": "被中级屠戮成性影响",
|
||||
"core.bonus.KING3.name": "精神顶级怪物",
|
||||
"core.bonus.KING3.description":"被高级屠戮成性影响",
|
||||
"core.bonus.LEVEL_SPELL_IMMUNITY.name": "免疫 1-${val} 级魔法",
|
||||
"core.bonus.LEVEL_SPELL_IMMUNITY.description": "免疫等级为 1-${val} 级的所有魔法",
|
||||
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "半程射击",
|
||||
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "超过 ${val} 格不能射击",
|
||||
"core.bonus.LIFE_DRAIN.name": "吸取生命 (${val}%)",
|
||||
"core.bonus.LIFE_DRAIN.description": "吸取 ${val}% 伤害回复自身",
|
||||
"core.bonus.MANA_CHANNELING.name": "偷取魔法 ${val}%",
|
||||
"core.bonus.MANA_CHANNELING.description": "偷取部分敌人施法消耗",
|
||||
"core.bonus.MANA_DRAIN.name": "吸取魔力",
|
||||
"core.bonus.MANA_DRAIN.description": "每回合吸取 ${val} 魔法值",
|
||||
"core.bonus.MAGIC_MIRROR.name": "带有魔法神镜 (${val}%)",
|
||||
"core.bonus.MAGIC_MIRROR.description": "${val}% 几率反射魔法",
|
||||
"core.bonus.MAGIC_RESISTANCE.name": "(${MR}%) 魔法抵抗",
|
||||
"core.bonus.MAGIC_RESISTANCE.description": "${MR}% 几率抵抗敌人的魔法",
|
||||
"core.bonus.MIND_IMMUNITY.name": "免疫心智",
|
||||
"core.bonus.MIND_IMMUNITY.description": "不受心智魔法的影响",
|
||||
"core.bonus.NO_DISTANCE_PENALTY.name": "无障碍射击",
|
||||
"core.bonus.NO_DISTANCE_PENALTY.description": "射击不受距离影响",
|
||||
"core.bonus.NO_MELEE_PENALTY.name": "无近战惩罚",
|
||||
"core.bonus.NO_MELEE_PENALTY.description": "近战伤害不减",
|
||||
"core.bonus.NO_MORALE.name": "无士气",
|
||||
"core.bonus.NO_MORALE.description": "生物不受士气影响",
|
||||
"core.bonus.NO_WALL_PENALTY.name": "无城墙影响",
|
||||
"core.bonus.NO_WALL_PENALTY.description": "射击不受城墙的影响",
|
||||
"core.bonus.NON_LIVING.name": "无生命",
|
||||
"core.bonus.NON_LIVING.description": "不受只对生命实体生物有效的魔法",
|
||||
"core.bonus.RANDOM_SPELLCASTER.name": "随机施法",
|
||||
"core.bonus.RANDOM_SPELLCASTER.description": "随机施放增益魔法",
|
||||
"core.bonus.RANGED_RETALIATION.name": "远程反击",
|
||||
"core.bonus.RANGED_RETALIATION.description": "可以对远程攻击进行反击",
|
||||
"core.bonus.RECEPTIVE.name": "接受有益魔法",
|
||||
"core.bonus.RECEPTIVE.description": "不会免疫有益的魔法",
|
||||
"core.bonus.REBIRTH.name": "复生 (${val}%)",
|
||||
"core.bonus.REBIRTH.description": "{val}% 数量死亡后会复活",
|
||||
"core.bonus.RETURN_AFTER_STRIKE.name": "攻击并返回",
|
||||
"core.bonus.RETURN_AFTER_STRIKE.description": "攻击后回到初始位置",
|
||||
"core.bonus.SELF_LUCK.name": "永久幸运",
|
||||
"core.bonus.SELF_LUCK.description": "永久拥有幸运值",
|
||||
"core.bonus.SELF_MORALE.name": "士气高涨",
|
||||
"core.bonus.SELF_MORALE.description": "永久拥有高昂的士气",
|
||||
"core.bonus.SHOOTER.name": "射手",
|
||||
"core.bonus.SHOOTER.description": "生物可以设计",
|
||||
"core.bonus.SHOOTS_ALL_ADJACENT.name": "范围远程攻击",
|
||||
"core.bonus.SHOOTS_ALL_ADJACENT.description": "远程攻击可伤害范围内的多个目标",
|
||||
"core.bonus.SOUL_STEAL.name": "杀死敌人复生",
|
||||
"core.bonus.SOUL_STEAL.description": "当杀死敌人时获得 ${val} 数量",
|
||||
"core.bonus.SPELLCASTER.name": "施法者",
|
||||
"core.bonus.SPELLCASTER.description": "生物可以施放 ${subtype.spell}",
|
||||
"core.bonus.SPELL_AFTER_ATTACK.name": "攻击后施法",
|
||||
"core.bonus.SPELL_AFTER_ATTACK.description": "${val}% 攻击后施放 ${subtype.spell}",
|
||||
"core.bonus.SPELL_BEFORE_ATTACK.name": "攻击前施法",
|
||||
"core.bonus.SPELL_BEFORE_ATTACK.description": "${val}% 攻击前施放 ${subtype.spell}",
|
||||
"core.bonus.SPELL_DAMAGE_REDUCTION.name": "魔法伤害抵抗",
|
||||
"core.bonus.SPELL_DAMAGE_REDUCTION.description": "受魔法攻击时伤害减少 ${val}%",
|
||||
"core.bonus.SPELL_IMMUNITY.name": "特定魔法免疫",
|
||||
"core.bonus.SPELL_IMMUNITY.description": "免疫 ${subtype.spell}",
|
||||
"core.bonus.SPELL_LIKE_ATTACK.name": "魔法攻击",
|
||||
"core.bonus.SPELL_LIKE_ATTACK.description": "攻击时使用 ${subtype.spell}",
|
||||
"core.bonus.SPELL_RESISTANCE_AURA.name": "抗魔光环",
|
||||
"core.bonus.SPELL_RESISTANCE_AURA.description": "邻近部队获得 ${val}% 魔法抵抗",
|
||||
"core.bonus.SUMMON_GUARDIANS.name": "召唤守卫",
|
||||
"core.bonus.SUMMON_GUARDIANS.description": "战斗前召唤 ${subtype.creature} (${val}%)",
|
||||
"core.bonus.SYNERGY_TARGET.name": "可协助攻击",
|
||||
"core.bonus.SYNERGY_TARGET.description": "生物受到协助攻击的影响",
|
||||
"core.bonus.TWO_HEX_ATTACK_BREATH.name": "龙息",
|
||||
"core.bonus.TWO_HEX_ATTACK_BREATH.description": "吐息攻击2个部队",
|
||||
"core.bonus.THREE_HEADED_ATTACK.name": "半环击",
|
||||
"core.bonus.THREE_HEADED_ATTACK.description": "攻击正前方多个敌人",
|
||||
"core.bonus.TRANSMUTATION.name": "变换",
|
||||
"core.bonus.TRANSMUTATION.description": "${val}% 机会将敌人变成其他生物",
|
||||
"core.bonus.UNDEAD.name": "不死生物",
|
||||
"core.bonus.UNDEAD.description": "生物有丧尸属性",
|
||||
"core.bonus.UNLIMITED_RETALIATIONS.name": "无限反击",
|
||||
"core.bonus.UNLIMITED_RETALIATIONS.description": "每回合可以无限反击敌人",
|
||||
"core.bonus.WATER_IMMUNITY.name": "水系免疫",
|
||||
"core.bonus.WATER_IMMUNITY.description": "免疫水系魔法",
|
||||
"core.bonus.WIDE_BREATH.name": "弧形焰息",
|
||||
"core.bonus.WIDE_BREATH.description": "吐息攻击前方扇形6个部队"
|
||||
}
|
@@ -84,10 +84,21 @@
|
||||
|
||||
"vcmi.battleWindow.pressKeyToSkipIntro" : "Press any key to skip battle intro",
|
||||
|
||||
"vcmi.battleWindow.damageEstimation.melee" : "Attack %CREATURE (%DAMAGE).",
|
||||
"vcmi.battleWindow.damageEstimation.meleeKills" : "Attack %CREATURE (%DAMAGE, %KILLS).",
|
||||
"vcmi.battleWindow.damageEstimation.ranged" : "Shoot %CREATURE (%SHOTS, %DAMAGE).",
|
||||
"vcmi.battleWindow.damageEstimation.rangedKills" : "Shoot %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
|
||||
"vcmi.battleWindow.damageEstimation.shots" : "%d shots left",
|
||||
"vcmi.battleWindow.damageEstimation.shots.1" : "%d shot left",
|
||||
"vcmi.battleWindow.damageEstimation.damage" : "%d damage",
|
||||
"vcmi.battleWindow.damageEstimation.damage.1" : "%d damage",
|
||||
"vcmi.battleWindow.damageEstimation.kills" : "%d will perish",
|
||||
"vcmi.battleWindow.damageEstimation.kills.1" : "%d will perish",
|
||||
|
||||
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Show Available Creatures",
|
||||
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Show Available Creatures}\n\n Shows creatures available to purchase instead of their growth in town summary (bottom-left corner).",
|
||||
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Show Weekly Growth of Creatures",
|
||||
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Show Weekly Growth of Creatures}\n\n Shows creatures' weekly growth instead of avaialable amount in town summary (bottom-left corner).",
|
||||
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Show Weekly Growth of Creatures}\n\n Shows creatures' weekly growth instead of available amount in town summary (bottom-left corner).",
|
||||
"vcmi.otherOptions.compactTownCreatureInfo.hover": "Compact Creature Info",
|
||||
"vcmi.otherOptions.compactTownCreatureInfo.help": "{Compact Creature Info}\n\n Smaller town creatures information in town summary.",
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"vcmi.adventureMap.monsterThreat.title" : "\n\n Zagrożenie: ",
|
||||
"vcmi.adventureMap.monsterThreat.title" : "\n\n Poziom zagrożenia: ",
|
||||
"vcmi.adventureMap.monsterThreat.levels.0" : "Zerowy",
|
||||
"vcmi.adventureMap.monsterThreat.levels.1" : "Bardzo słaby",
|
||||
"vcmi.adventureMap.monsterThreat.levels.2" : "Słaby",
|
||||
@@ -25,12 +25,82 @@
|
||||
"vcmi.server.errors.modsIncompatibility" : "Mody wymagane do wczytania gry:",
|
||||
"vcmi.server.confirmReconnect" : "Połączyć z ostatnią sesją?",
|
||||
|
||||
"vcmi.settingsMainWindow.generalTab.hover" : "Ogólne",
|
||||
"vcmi.settingsMainWindow.generalTab.help" : "Przełącza do zakładki opcji ogólnych, która zawiera ustawienia związane z ogólnym działaniem gry",
|
||||
"vcmi.settingsMainWindow.battleTab.hover" : "Bitwa",
|
||||
"vcmi.settingsMainWindow.battleTab.help" : "Przełącza do zakładki opcji bitewnych, która pozwala konfigurować zachowanie gry w bitwach",
|
||||
"vcmi.settingsMainWindow.adventureTab.hover" : "Mapa przygody",
|
||||
"vcmi.settingsMainWindow.adventureTab.help" : "Przełącza do zakładki opcji mapy przygody - mapa przygody to część gry, w której poruszasz bohaterami",
|
||||
|
||||
"vcmi.systemOptions.videoGroup" : "Ustawienia grafiki",
|
||||
"vcmi.systemOptions.audioGroup" : "Ustawienia audio",
|
||||
"vcmi.systemOptions.otherGroup" : "Inne ustawienia", // unused right now
|
||||
"vcmi.systemOptions.townsGroup" : "Ekran miasta",
|
||||
|
||||
"vcmi.systemOptions.fullscreenButton.hover" : "Pełny ekran",
|
||||
"vcmi.systemOptions.fullscreenButton.help" : "{Fullscreen}\n\n Po wybraniu VCMI uruchomi się w trybie pełnoekranowym, w przeciwnym wypadku uruchomi się w oknie",
|
||||
"vcmi.systemOptions.fullscreenButton.help" : "{Pełny ekran}\n\n Po wybraniu VCMI uruchomi się w trybie pełnoekranowym, w przeciwnym wypadku uruchomi się w oknie",
|
||||
"vcmi.systemOptions.resolutionButton.hover" : "Rozdzielczość: %wx%h",
|
||||
"vcmi.systemOptions.resolutionButton.help" : "{Select resolution}\n\n Zmień rozdzielczość ekranu w grze. Restart gry jest wymagany, by zmiany zostały uwzględnione.",
|
||||
"vcmi.systemOptions.resolutionButton.help" : "{Wybierz rozdzielczość}\n\n Zmień rozdzielczość ekranu w grze. Restart gry jest wymagany, by zmiany zostały uwzględnione.",
|
||||
"vcmi.systemOptions.resolutionMenu.hover" : "Wybierz rozdzielczość",
|
||||
"vcmi.systemOptions.resolutionMenu.help" : "Zmień rozdzielczość ekranu w grze.",
|
||||
"vcmi.systemOptions.fullscreenFailed" : "{Pełny ekran}\n\n Nieudane przełączenie w tryb pełnoekranowy! Obecna rozdzielczość nie jest wspierana przez wyświetlacz!",
|
||||
"vcmi.systemOptions.framerateButton.hover" : "Pokaż FPS",
|
||||
"vcmi.systemOptions.framerateButton.help" : "{Pokaż FPS}\n\n Przełącza widoczność licznika klatek na sekundę (FPS) w rogu okna gry.",
|
||||
|
||||
"vcmi.adventureOptions.infoBarPick.hover" : "Pokaż komunikaty w panelu informacyjnym",
|
||||
"vcmi.adventureOptions.infoBarPick.help" : "{Pokaż komunikaty w panelu informacyjnym}\n\nGdy to możliwe, wiadomości z odwiedzania obiektów będą pokazywane w panelu informacyjnym zamiast w osobnym okienku.",
|
||||
"vcmi.adventureOptions.numericQuantities.hover" : "Liczbowe ilości stworzeń",
|
||||
"vcmi.adventureOptions.numericQuantities.help" : "{Liczbowe ilości stworzeń}\n\n Pokazuje przybliżone ilości wrogów w liczbowym formacie A-B.",
|
||||
"vcmi.adventureOptions.forceMovementInfo.hover" : "Zawsze pokazuj koszt ruchu",
|
||||
"vcmi.adventureOptions.forceMovementInfo.help" : "{Zawsze pokazuj koszt ruchu}\n\n Zastępuje domyślne informacje paska statusu danymi o ruchu bez potrzeby przytrzymywania klawisza ALT.",
|
||||
"vcmi.adventureOptions.showGrid.hover" : "Pokaż siatkę",
|
||||
"vcmi.adventureOptions.showGrid.help" : "{Pokaż siatkę}\n\n Włącza siatkę pokazującą brzegi pól mapy przygody.",
|
||||
"vcmi.adventureOptions.mapSwipe.hover" : "Przeciąganie mapy",
|
||||
"vcmi.adventureOptions.mapSwipe.help" : "{Przeciąganie mapy}\n\n Pozwala przesuwać mapę przygody palcem dla systemów z ekranami dotykowymi. Obecnie pozwala też przesuwać mapę lewym przyciskiem myszy.",
|
||||
"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
|
||||
"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
|
||||
"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
|
||||
"vcmi.adventureOptions.mapScrollSpeed1.help": "Ustaw szybkość przesuwania mapy na bardzo wolną.",
|
||||
"vcmi.adventureOptions.mapScrollSpeed5.help": "Ustaw szybkość przesuwania mapy na bardzo szybką.",
|
||||
"vcmi.adventureOptions.mapScrollSpeed6.help": "Ustaw szybkość przesuwania mapy na błyskawiczną.",
|
||||
|
||||
"vcmi.battleOptions.queueSizeLabel.hover": "Pokaż kolejkę ruchu jednostek",
|
||||
"vcmi.battleOptions.queueSizeNoneButton.hover": "BRAK",
|
||||
"vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO",
|
||||
"vcmi.battleOptions.queueSizeSmallButton.hover": "MAŁA",
|
||||
"vcmi.battleOptions.queueSizeBigButton.hover": "DUŻA",
|
||||
"vcmi.battleOptions.queueSizeNoneButton.help": "Kompletnie wyłącza widoczność kolejki ruchu jednostek",
|
||||
"vcmi.battleOptions.queueSizeAutoButton.help": "Ustawia rozmiar kolejki zależnie od rozdzielczości gry (mała dla rozdzielczości z wysokością poniżej 700 pikseli, duża dla pozostałych)",
|
||||
"vcmi.battleOptions.queueSizeSmallButton.help": "Ustawia rozmiar kolejki na mały",
|
||||
"vcmi.battleOptions.queueSizeBigButton.help": "Ustawia rozmiar kolejki na duży (nie wspierane dla rozdzielczości z wysokością mniejszą niż 700 pikseli)",
|
||||
"vcmi.battleOptions.animationsSpeed1.hover": "",
|
||||
"vcmi.battleOptions.animationsSpeed5.hover": "",
|
||||
"vcmi.battleOptions.animationsSpeed6.hover": "",
|
||||
"vcmi.battleOptions.animationsSpeed1.help": "Ustawia szybkość animacji na bardzo wolną",
|
||||
"vcmi.battleOptions.animationsSpeed5.help": "Ustawia szybkość animacji na bardzo szybką",
|
||||
"vcmi.battleOptions.animationsSpeed6.help": "Ustawia szybkość animacji na błyskawiczną",
|
||||
"vcmi.battleOptions.skipBattleIntroMusic.hover": "Pomiń muzykę startową",
|
||||
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Pomiń muzykę startową}\n\n Pomija krótką muzykę, która jest odtwarzana na początku każdej bitwy przed rozpoczęciem akcji. Może również być pominięta poprzez naciśnięcie ESC.",
|
||||
|
||||
"vcmi.battleWindow.pressKeyToSkipIntro" : "Naciśnij dowolny klawisz by pominąć muzykę startową",
|
||||
|
||||
"vcmi.battleWindow.damageEstimation.melee" : "Atakuj %CREATURE (%DAMAGE).",
|
||||
"vcmi.battleWindow.damageEstimation.meleeKills" : "Atakuj %CREATURE (%DAMAGE, %KILLS).",
|
||||
"vcmi.battleWindow.damageEstimation.ranged" : "Strzelaj do %CREATURE (%SHOTS, %DAMAGE).",
|
||||
"vcmi.battleWindow.damageEstimation.rangedKills" : "Strzelaj do %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
|
||||
"vcmi.battleWindow.damageEstimation.shots" : "pozostałe strzały: %d",
|
||||
"vcmi.battleWindow.damageEstimation.shots.1" : "pozostał %d strzał",
|
||||
"vcmi.battleWindow.damageEstimation.damage" : "obrażenia: %d",
|
||||
"vcmi.battleWindow.damageEstimation.damage.1" : "obrażenia: %d",
|
||||
"vcmi.battleWindow.damageEstimation.kills" : "%d zginie",
|
||||
"vcmi.battleWindow.damageEstimation.kills.1" : "%d zginie",
|
||||
|
||||
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Pokaż dostępne stworzenia",
|
||||
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Pokaż dostępne stworzenia}\n\n Pokazuje dostępne stworzenia zamiast tygodniowego przyrostu w podsumowaniu miasta (lewy dolny róg).",
|
||||
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Pokaż tygodniowy przyrost stworzeń",
|
||||
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Pokaż tygodniowy przyrost stworzeń}\n\n Shows creatures' weekly growth instead of avaialable amount in town summary (lewy dolny r óg).",
|
||||
"vcmi.otherOptions.compactTownCreatureInfo.hover": "Kompaktowa informacja o stworzeniu",
|
||||
"vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompaktowa informacja o stworzeniu}\n\n Zmniejszona informacja o stworzeniu w podsumowaniu miasta.",
|
||||
|
||||
"vcmi.townHall.missingBase" : "Podstawowy budynek %s musi zostać najpierw wybudowany",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "Brak stworzeń do rekrutacji!",
|
||||
@@ -69,6 +139,7 @@
|
||||
"vcmi.randomMapTab.widgets.templateLabel" : "Szablon",
|
||||
"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Ustaw...",
|
||||
"vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Sojusze",
|
||||
"vcmi.randomMapTab.widgets.roadTypesLabel" : "Typy dróg",
|
||||
|
||||
"core.bonus.ADDITIONAL_ATTACK.name": "Podwójne Uderzenie",
|
||||
"core.bonus.ADDITIONAL_ATTACK.description": "Atakuje podwójnie",
|
||||
@@ -140,12 +211,8 @@
|
||||
"core.bonus.HP_REGENERATION.description": "Leczy ${SHval} punktów zdrowia każdej rundy",
|
||||
"core.bonus.JOUSTING.name": "Szarża Czempiona",
|
||||
"core.bonus.JOUSTING.description": "+${val}% obrażeń na przebytego heksa",
|
||||
"core.bonus.KING1.name": "Król 1",
|
||||
"core.bonus.KING1.description": "Wrażliwy na podstawowy czar POGROMCA",
|
||||
"core.bonus.KING2.name": "Król 2",
|
||||
"core.bonus.KING2.description": "Wrażliwy na zaawansowany czar POGROMCA",
|
||||
"core.bonus.KING3.name": "Król 3",
|
||||
"core.bonus.KING3.description":"Wrażliwy na ekspercki czar POGROMCA",
|
||||
"core.bonus.KING.name": "Król",
|
||||
"core.bonus.KING.description": "Wrażliwy na czar POGROMCA stopnia zaawansowania ${val} lub wyższego",
|
||||
"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odporność na czary 1-${val}",
|
||||
"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odporny na czary 1-${val} poziomu",
|
||||
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Ograniczony zasięg strzelania",
|
||||
|
@@ -84,6 +84,17 @@
|
||||
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Пропускати вступну музику}\n\n Пропускати коротку музику, яка грає на початку кожної битви перед початком дії. Також можна пропустити, натиснувши клавішу ESC.",
|
||||
|
||||
"vcmi.battleWindow.pressKeyToSkipIntro" : "Натисніть будь-яку клавішу, щоб розпочати бій",
|
||||
|
||||
"vcmi.battleWindow.damageEstimation.melee" : "Атакувати %CREATURE (%DAMAGE).",
|
||||
"vcmi.battleWindow.damageEstimation.meleeKills" : "Атакувати %CREATURE (%DAMAGE, %KILLS).",
|
||||
"vcmi.battleWindow.damageEstimation.ranged" : "Стріляти в %CREATURE (%SHOTS, %DAMAGE).",
|
||||
"vcmi.battleWindow.damageEstimation.rangedKills" : "Стріляти в %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
|
||||
"vcmi.battleWindow.damageEstimation.shots" : "%d пострілів залишилось",
|
||||
"vcmi.battleWindow.damageEstimation.shots.1" : "%d постріл залишився",
|
||||
"vcmi.battleWindow.damageEstimation.damage" : "%d одиниць пошкоджень",
|
||||
"vcmi.battleWindow.damageEstimation.damage.1" : "%d одиниця пошкодження",
|
||||
"vcmi.battleWindow.damageEstimation.kills" : "%d загинуть",
|
||||
"vcmi.battleWindow.damageEstimation.kills.1" : "%d загине",
|
||||
|
||||
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Показувати доступних істот",
|
||||
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Показувати доступних істот}\n\n Показує істот, яких можна придбати, замість їхнього приросту у зведенні по місту (нижній лівий кут).",
|
||||
|
@@ -1,6 +1,16 @@
|
||||
{
|
||||
"name" : "VCMI essential files",
|
||||
"description" : "Essential files required for VCMI to run correctly",
|
||||
|
||||
"chinese" : {
|
||||
"name" : "VCMI essential files",
|
||||
"description" : "Essential files required for VCMI to run correctly",
|
||||
|
||||
"skipValidation" : true,
|
||||
"translations" : [
|
||||
"config/vcmi/chinese.json"
|
||||
]
|
||||
},
|
||||
|
||||
"german" : {
|
||||
"name" : "VCMI - grundlegende Dateien",
|
||||
|
@@ -583,6 +583,7 @@ void CPlayerInterface::heroInGarrisonChange(const CGTownInstance *town)
|
||||
castleInt->garr->setArmy(town->visitingHero, 1);
|
||||
castleInt->garr->recreateSlots();
|
||||
castleInt->heroes->update();
|
||||
castleInt->redraw();
|
||||
}
|
||||
for (auto isa : GH.listInt)
|
||||
{
|
||||
@@ -591,9 +592,9 @@ void CPlayerInterface::heroInGarrisonChange(const CGTownInstance *town)
|
||||
{
|
||||
ki->townChanged(town);
|
||||
ki->updateGarrisons();
|
||||
ki->redraw();
|
||||
}
|
||||
}
|
||||
GH.totalRedraw();
|
||||
}
|
||||
void CPlayerInterface::heroVisitsTown(const CGHeroInstance* hero, const CGTownInstance * town)
|
||||
{
|
||||
@@ -1510,6 +1511,7 @@ void CPlayerInterface::objectRemoved(const CGObjectInstance * obj)
|
||||
const CGHeroInstance * h = static_cast<const CGHeroInstance *>(obj);
|
||||
heroKilled(h);
|
||||
}
|
||||
GH.fakeMouseMove();
|
||||
}
|
||||
|
||||
void CPlayerInterface::objectRemovedAfter()
|
||||
@@ -1559,7 +1561,6 @@ void CPlayerInterface::update()
|
||||
}
|
||||
|
||||
assert(adventureInt);
|
||||
assert(adventureInt->selection);
|
||||
|
||||
// Handles mouse and key input
|
||||
GH.updateTime();
|
||||
@@ -2032,7 +2033,9 @@ bool CPlayerInterface::capturedAllEvents()
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ignoreEvents)
|
||||
bool needToLockAdventureMap = adventureInt->active && CGI->mh->hasOngoingAnimations();
|
||||
|
||||
if (ignoreEvents || needToLockAdventureMap)
|
||||
{
|
||||
boost::unique_lock<boost::mutex> un(eventsM);
|
||||
while(!SDLEventsQueue.empty())
|
||||
|
@@ -17,7 +17,7 @@ class CIntObject;
|
||||
|
||||
class ClientCommandManager //take mantis #2292 issue about account if thinking about handling cheats from command-line
|
||||
{
|
||||
bool currentCallFromIngameConsole;
|
||||
bool currentCallFromIngameConsole = false;
|
||||
|
||||
void giveTurn(const PlayerColor &color);
|
||||
void printInfoAboutInterfaceObject(const CIntObject *obj, int level);
|
||||
|
@@ -514,6 +514,11 @@ void ApplyClientNetPackVisitor::visitNewStructures(NewStructures & pack)
|
||||
{
|
||||
callInterfaceIfPresent(cl, town->tempOwner, &IGameEventsReceiver::buildChanged, town, id, 1);
|
||||
}
|
||||
|
||||
// invalidate section of map view with our object and force an update
|
||||
CGI->mh->onObjectInstantRemove(town);
|
||||
CGI->mh->onObjectInstantAdd(town);
|
||||
|
||||
}
|
||||
void ApplyClientNetPackVisitor::visitRazeStructures(RazeStructures & pack)
|
||||
{
|
||||
@@ -522,6 +527,10 @@ void ApplyClientNetPackVisitor::visitRazeStructures(RazeStructures & pack)
|
||||
{
|
||||
callInterfaceIfPresent(cl, town->tempOwner, &IGameEventsReceiver::buildChanged, town, id, 2);
|
||||
}
|
||||
|
||||
// invalidate section of map view with our object and force an update
|
||||
CGI->mh->onObjectInstantRemove(town);
|
||||
CGI->mh->onObjectInstantAdd(town);
|
||||
}
|
||||
|
||||
void ApplyClientNetPackVisitor::visitSetAvailableCreatures(SetAvailableCreatures & pack)
|
||||
@@ -607,7 +616,7 @@ void ApplyClientNetPackVisitor::visitSetObjectProperty(SetObjectProperty & pack)
|
||||
|
||||
if (pack.what == ObjProperty::OWNER)
|
||||
{
|
||||
// invalidate section of map view with our objec and force an update with new flag color
|
||||
// invalidate section of map view with our object and force an update with new flag color
|
||||
CGI->mh->onObjectInstantRemove(gs.getObjInstance(pack.id));
|
||||
CGI->mh->onObjectInstantAdd(gs.getObjInstance(pack.id));
|
||||
}
|
||||
@@ -739,11 +748,14 @@ void ApplyFirstClientNetPackVisitor::visitBattleStackMoved(BattleStackMoved & pa
|
||||
void ApplyFirstClientNetPackVisitor::visitBattleAttack(BattleAttack & pack)
|
||||
{
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleAttack, &pack);
|
||||
|
||||
// battleStacksAttacked should be excuted before BattleAttack.applyGs() to play animation before damaging unit
|
||||
// so this has to be here instead of ApplyClientNetPackVisitor::visitBattleAttack()
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, pack.bsa, pack.shot());
|
||||
}
|
||||
|
||||
void ApplyClientNetPackVisitor::visitBattleAttack(BattleAttack & pack)
|
||||
{
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, pack.bsa, pack.shot());
|
||||
}
|
||||
|
||||
void ApplyFirstClientNetPackVisitor::visitStartAction(StartAction & pack)
|
||||
|
@@ -986,7 +986,6 @@ void CAdvMapInt::initializeNewTurn()
|
||||
{
|
||||
heroList->update();
|
||||
townList->update();
|
||||
mapAudio->onPlayerTurnStarted();
|
||||
|
||||
const CGHeroInstance * heroToSelect = nullptr;
|
||||
|
||||
@@ -1017,6 +1016,7 @@ void CAdvMapInt::initializeNewTurn()
|
||||
|
||||
updateNextHero(nullptr);
|
||||
showAll(screen);
|
||||
mapAudio->onPlayerTurnStarted();
|
||||
|
||||
if(settings["session"]["autoSkip"].Bool() && !GH.isKeyboardShiftDown())
|
||||
{
|
||||
@@ -1404,6 +1404,7 @@ void CAdvMapInt::aiTurnStarted()
|
||||
mapAudio->onEnemyTurnStarted();
|
||||
adventureInt->minimap->setAIRadar(true);
|
||||
adventureInt->infoBar->startEnemyTurn(LOCPLINT->cb->getCurrentPlayer());
|
||||
adventureInt->minimap->showAll(screen);//force refresh on inactive object
|
||||
adventureInt->infoBar->showAll(screen);//force refresh on inactive object
|
||||
}
|
||||
|
||||
|
@@ -24,71 +24,85 @@
|
||||
#include "../../lib/TextOperations.h"
|
||||
#include "../../lib/mapObjects/CArmedInstance.h"
|
||||
|
||||
#include <SDL_timer.h>
|
||||
|
||||
CInGameConsole::CInGameConsole()
|
||||
: CIntObject(KEYBOARD | TEXTINPUT),
|
||||
prevEntDisp(-1),
|
||||
defaultTimeout(10000),
|
||||
maxDisplayedTexts(10)
|
||||
: CIntObject(KEYBOARD | TIME | TEXTINPUT)
|
||||
, prevEntDisp(-1)
|
||||
{
|
||||
type |= REDRAW_PARENT;
|
||||
}
|
||||
|
||||
void CInGameConsole::showAll(SDL_Surface * to)
|
||||
{
|
||||
show(to);
|
||||
}
|
||||
|
||||
void CInGameConsole::show(SDL_Surface * to)
|
||||
{
|
||||
int number = 0;
|
||||
|
||||
std::vector<std::list< std::pair< std::string, uint32_t > >::iterator> toDel;
|
||||
|
||||
boost::unique_lock<boost::mutex> lock(texts_mx);
|
||||
for(auto it = texts.begin(); it != texts.end(); ++it, ++number)
|
||||
for(auto & text : texts)
|
||||
{
|
||||
Point leftBottomCorner(0, pos.h);
|
||||
Point textPosition(leftBottomCorner.x + 50, leftBottomCorner.y - texts.size() * 20 - 80 + number * 20);
|
||||
|
||||
graphics->fonts[FONT_MEDIUM]->renderTextLeft(to, it->first, Colors::GREEN,
|
||||
Point(leftBottomCorner.x + 50, leftBottomCorner.y - (int)texts.size() * 20 - 80 + number*20));
|
||||
graphics->fonts[FONT_MEDIUM]->renderTextLeft(to, text.text, Colors::GREEN, textPosition );
|
||||
|
||||
if((int)(SDL_GetTicks() - it->second) > defaultTimeout)
|
||||
{
|
||||
toDel.push_back(it);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto & elem : toDel)
|
||||
{
|
||||
texts.erase(elem);
|
||||
number++;
|
||||
}
|
||||
}
|
||||
|
||||
void CInGameConsole::print(const std::string &txt)
|
||||
void CInGameConsole::tick(uint32_t msPassed)
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(texts_mx);
|
||||
int lineLen = conf.go()->ac.outputLineLength;
|
||||
|
||||
if(txt.size() < lineLen)
|
||||
size_t sizeBefore = texts.size();
|
||||
{
|
||||
texts.push_back(std::make_pair(txt, SDL_GetTicks()));
|
||||
if(texts.size() > maxDisplayedTexts)
|
||||
{
|
||||
texts.pop_front();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(lineLen);
|
||||
for(int g=0; g<txt.size() / lineLen + 1; ++g)
|
||||
{
|
||||
std::string part = txt.substr(g * lineLen, lineLen);
|
||||
if(part.size() == 0)
|
||||
break;
|
||||
boost::unique_lock<boost::mutex> lock(texts_mx);
|
||||
|
||||
texts.push_back(std::make_pair(part, SDL_GetTicks()));
|
||||
if(texts.size() > maxDisplayedTexts)
|
||||
for(auto & text : texts)
|
||||
text.timeOnScreen += msPassed;
|
||||
|
||||
vstd::erase_if(
|
||||
texts,
|
||||
[&](const auto & value)
|
||||
{
|
||||
texts.pop_front();
|
||||
return value.timeOnScreen > defaultTimeout;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if(sizeBefore != texts.size())
|
||||
GH.totalRedraw(); // FIXME: ingame console has no parent widget set
|
||||
}
|
||||
|
||||
void CInGameConsole::print(const std::string & txt)
|
||||
{
|
||||
// boost::unique_lock scope
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(texts_mx);
|
||||
int lineLen = conf.go()->ac.outputLineLength;
|
||||
|
||||
if(txt.size() < lineLen)
|
||||
{
|
||||
texts.push_back({txt, 0});
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(lineLen);
|
||||
for(int g = 0; g < txt.size() / lineLen + 1; ++g)
|
||||
{
|
||||
std::string part = txt.substr(g * lineLen, lineLen);
|
||||
if(part.empty())
|
||||
break;
|
||||
|
||||
texts.push_back({part, 0});
|
||||
}
|
||||
}
|
||||
|
||||
while(texts.size() > maxDisplayedTexts)
|
||||
texts.erase(texts.begin());
|
||||
}
|
||||
|
||||
GH.totalRedraw(); // FIXME: ingame console has no parent widget set
|
||||
}
|
||||
|
||||
void CInGameConsole::keyPressed (const SDL_Keycode & key)
|
||||
@@ -136,7 +150,7 @@ void CInGameConsole::keyPressed (const SDL_Keycode & key)
|
||||
}
|
||||
case SDLK_UP: //up arrow
|
||||
{
|
||||
if(previouslyEntered.size() == 0)
|
||||
if(previouslyEntered.empty())
|
||||
break;
|
||||
|
||||
if(prevEntDisp == -1)
|
||||
@@ -178,7 +192,7 @@ void CInGameConsole::keyPressed (const SDL_Keycode & key)
|
||||
|
||||
void CInGameConsole::textInputed(const std::string & inputtedText)
|
||||
{
|
||||
if(!captureAllKeys || enteredText.size() == 0)
|
||||
if(!captureAllKeys || enteredText.empty())
|
||||
return;
|
||||
enteredText.resize(enteredText.size()-1);
|
||||
|
||||
|
@@ -14,20 +14,39 @@
|
||||
class CInGameConsole : public CIntObject
|
||||
{
|
||||
private:
|
||||
std::list< std::pair< std::string, uint32_t > > texts; //list<text to show, time of add>
|
||||
boost::mutex texts_mx; // protects texts
|
||||
std::vector< std::string > previouslyEntered; //previously entered texts, for up/down arrows to work
|
||||
int prevEntDisp; //displayed entry from previouslyEntered - if none it's -1
|
||||
int defaultTimeout; //timeout for new texts (in ms)
|
||||
int maxDisplayedTexts; //hiw many texts can be displayed simultaneously
|
||||
struct TextState
|
||||
{
|
||||
std::string text;
|
||||
uint32_t timeOnScreen;
|
||||
};
|
||||
|
||||
/// Currently visible texts in the overlay
|
||||
std::vector<TextState> texts;
|
||||
|
||||
/// protects texts
|
||||
boost::mutex texts_mx;
|
||||
|
||||
/// previously entered texts, for up/down arrows to work
|
||||
std::vector<std::string> previouslyEntered;
|
||||
|
||||
/// displayed entry from previouslyEntered - if none it's -1
|
||||
int prevEntDisp;
|
||||
|
||||
/// timeout for new texts (in ms)
|
||||
static constexpr int defaultTimeout = 10000;
|
||||
|
||||
/// how many texts can be displayed simultaneously
|
||||
static constexpr int maxDisplayedTexts = 10;
|
||||
|
||||
std::weak_ptr<IStatusBar> currentStatusBar;
|
||||
std::string enteredText;
|
||||
|
||||
public:
|
||||
void print(const std::string &txt);
|
||||
void print(const std::string & txt);
|
||||
|
||||
void tick(uint32_t msPassed) override;
|
||||
void show(SDL_Surface * to) override;
|
||||
void showAll(SDL_Surface * to) override;
|
||||
void keyPressed(const SDL_Keycode & key) override;
|
||||
void textInputed(const std::string & enteredText) override;
|
||||
void textEdited(const std::string & enteredText) override;
|
||||
|
@@ -254,11 +254,21 @@ void CInfoBar::showSelection()
|
||||
showGameStatus();//FIXME: may be incorrect but shouldn't happen in general
|
||||
}
|
||||
|
||||
void CInfoBar::tick()
|
||||
void CInfoBar::tick(uint32_t msPassed)
|
||||
{
|
||||
removeUsedEvents(TIME);
|
||||
if(GH.topInt() == adventureInt)
|
||||
popComponents(true);
|
||||
assert(timerCounter > 0);
|
||||
|
||||
if (msPassed >= timerCounter)
|
||||
{
|
||||
timerCounter = 0;
|
||||
removeUsedEvents(TIME);
|
||||
if(GH.topInt() == adventureInt)
|
||||
popComponents(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
timerCounter -= msPassed;
|
||||
}
|
||||
}
|
||||
|
||||
void CInfoBar::clickLeft(tribool down, bool previousState)
|
||||
@@ -290,6 +300,7 @@ void CInfoBar::hover(bool on)
|
||||
|
||||
CInfoBar::CInfoBar(const Rect & position)
|
||||
: CIntObject(LCLICK | RCLICK | HOVER, position.topLeft()),
|
||||
timerCounter(0),
|
||||
state(EMPTY)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
@@ -302,6 +313,14 @@ CInfoBar::CInfoBar(const Point & position): CInfoBar(Rect(position.x, position.y
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void CInfoBar::setTimer(uint32_t msToTrigger)
|
||||
{
|
||||
if (!(active & TIME))
|
||||
addUsedEvents(TIME);
|
||||
timerCounter = msToTrigger;
|
||||
}
|
||||
|
||||
void CInfoBar::showDate()
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
|
||||
@@ -312,7 +331,6 @@ void CInfoBar::showDate()
|
||||
redraw();
|
||||
}
|
||||
|
||||
|
||||
void CInfoBar::pushComponents(const std::vector<Component> & components, std::string message, int timer)
|
||||
{
|
||||
auto actualPush = [&](const std::vector<Component> & components, std::string message, int timer, size_t max){
|
||||
|
@@ -138,6 +138,7 @@ private:
|
||||
|
||||
std::shared_ptr<CVisibleInfo> visibleInfo;
|
||||
EState state;
|
||||
uint32_t timerCounter;
|
||||
bool shouldPopAll = false;
|
||||
|
||||
std::queue<std::pair<VisibleComponentInfo::Cache, int>> componentsQueue;
|
||||
@@ -151,13 +152,14 @@ private:
|
||||
//removes all information about current state, deactivates timer (if any)
|
||||
void reset();
|
||||
|
||||
void tick() override;
|
||||
void tick(uint32_t msPassed) override;
|
||||
|
||||
void clickLeft(tribool down, bool previousState) override;
|
||||
void clickRight(tribool down, bool previousState) override;
|
||||
void hover(bool on) override;
|
||||
|
||||
void playNewDaySound();
|
||||
void setTimer(uint32_t msToTrigger);
|
||||
public:
|
||||
CInfoBar(const Rect & pos);
|
||||
CInfoBar(const Point & pos);
|
||||
|
@@ -77,7 +77,7 @@ void CList::CListItem::onSelect(bool on)
|
||||
if(on)
|
||||
selection = genSelection();
|
||||
select(on);
|
||||
GH.totalRedraw();
|
||||
redraw();
|
||||
}
|
||||
|
||||
CList::CList(int Size, Point position, std::string btnUp, std::string btnDown, size_t listAmount, int helpUp, int helpDown, CListBox::CreateFunc create)
|
||||
|
@@ -222,10 +222,7 @@ void CMinimap::setAIRadar(bool on)
|
||||
aiShield->disable();
|
||||
update();
|
||||
}
|
||||
|
||||
// this may happen during AI turn when this interface is inactive
|
||||
// force redraw in order to properly update interface
|
||||
GH.totalRedraw();
|
||||
redraw();
|
||||
}
|
||||
|
||||
void CMinimap::updateTile(const int3 &pos)
|
||||
|
@@ -131,7 +131,11 @@ std::vector<std::string> MapAudioPlayer::getAmbientSounds(const int3 & tile)
|
||||
{
|
||||
const auto & object = CGI->mh->getMap()->objects[objectID.getNum()];
|
||||
|
||||
if(object->getAmbientSound())
|
||||
assert(object);
|
||||
if (!object)
|
||||
logGlobal->warn("Already removed object %d found on tile! (%d %d %d)", objectID.getNum(), tile.x, tile.y, tile.z);
|
||||
|
||||
if(object && object->getAmbientSound())
|
||||
result.push_back(object->getAmbientSound().get());
|
||||
}
|
||||
|
||||
@@ -194,8 +198,10 @@ MapAudioPlayer::MapAudioPlayer()
|
||||
objects.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]);
|
||||
|
||||
for(const auto & obj : CGI->mh->getMap()->objects)
|
||||
{
|
||||
if (obj)
|
||||
addObject(obj);
|
||||
}
|
||||
}
|
||||
|
||||
MapAudioPlayer::~MapAudioPlayer()
|
||||
|
@@ -47,6 +47,10 @@ protected:
|
||||
void onAfterHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
|
||||
void onAfterHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
|
||||
|
||||
void onBeforeHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override {}
|
||||
void onBeforeHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override {}
|
||||
void onBeforeHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override {}
|
||||
|
||||
public:
|
||||
MapAudioPlayer();
|
||||
~MapAudioPlayer() override;
|
||||
|
@@ -25,25 +25,95 @@
|
||||
#include "../windows/CCreatureWindow.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/spells/Problem.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
|
||||
static std::string formatDmgRange(std::pair<ui32, ui32> dmgRange)
|
||||
struct TextReplacement
|
||||
{
|
||||
if (dmgRange.first != dmgRange.second)
|
||||
return (boost::format("%d - %d") % dmgRange.first % dmgRange.second).str();
|
||||
else
|
||||
return (boost::format("%d") % dmgRange.first).str();
|
||||
std::string placeholder;
|
||||
std::string replacement;
|
||||
};
|
||||
|
||||
using TextReplacementList = std::vector<TextReplacement>;
|
||||
|
||||
static std::string replacePlaceholders(std::string input, const TextReplacementList & format )
|
||||
{
|
||||
for(const auto & entry : format)
|
||||
boost::replace_all(input, entry.placeholder, entry.replacement);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
static std::string translatePlural(int amount, const std::string& baseTextID)
|
||||
{
|
||||
if(amount == 1)
|
||||
return CGI->generaltexth->translate(baseTextID + ".1");
|
||||
return CGI->generaltexth->translate(baseTextID);
|
||||
}
|
||||
|
||||
static std::string formatPluralImpl(int amount, const std::string & amountString, const std::string & baseTextID)
|
||||
{
|
||||
std::string baseString = translatePlural(amount, baseTextID);
|
||||
TextReplacementList replacements {
|
||||
{ "%d", amountString }
|
||||
};
|
||||
|
||||
return replacePlaceholders(baseString, replacements);
|
||||
}
|
||||
|
||||
static std::string formatPlural(int amount, const std::string & baseTextID)
|
||||
{
|
||||
return formatPluralImpl(amount, std::to_string(amount), baseTextID);
|
||||
}
|
||||
|
||||
static std::string formatPlural(DamageRange range, const std::string & baseTextID)
|
||||
{
|
||||
if (range.min == range.max)
|
||||
return formatPlural(range.min, baseTextID);
|
||||
|
||||
std::string rangeString = std::to_string(range.min) + " - " + std::to_string(range.max);
|
||||
|
||||
return formatPluralImpl(range.max, rangeString, baseTextID);
|
||||
}
|
||||
|
||||
static std::string formatAttack(const DamageEstimation & estimation, const std::string & creatureName, const std::string & baseTextID, int shotsLeft)
|
||||
{
|
||||
TextReplacementList replacements = {
|
||||
{ "%CREATURE", creatureName },
|
||||
{ "%DAMAGE", formatPlural(estimation.damage, "vcmi.battleWindow.damageEstimation.damage") },
|
||||
{ "%SHOTS", formatPlural(shotsLeft, "vcmi.battleWindow.damageEstimation.shots") },
|
||||
{ "%KILLS", formatPlural(estimation.kills, "vcmi.battleWindow.damageEstimation.kills") },
|
||||
};
|
||||
|
||||
return replacePlaceholders(CGI->generaltexth->translate(baseTextID), replacements);
|
||||
}
|
||||
|
||||
static std::string formatMeleeAttack(const DamageEstimation & estimation, const std::string & creatureName)
|
||||
{
|
||||
std::string baseTextID = estimation.kills.max == 0 ?
|
||||
"vcmi.battleWindow.damageEstimation.melee" :
|
||||
"vcmi.battleWindow.damageEstimation.meleeKills";
|
||||
|
||||
return formatAttack(estimation, creatureName, baseTextID, 0);
|
||||
}
|
||||
|
||||
static std::string formatRangedAttack(const DamageEstimation & estimation, const std::string & creatureName, int shotsLeft)
|
||||
{
|
||||
std::string baseTextID = estimation.kills.max == 0 ?
|
||||
"vcmi.battleWindow.damageEstimation.ranged" :
|
||||
"vcmi.battleWindow.damageEstimation.rangedKills";
|
||||
|
||||
return formatAttack(estimation, creatureName, baseTextID, shotsLeft);
|
||||
}
|
||||
|
||||
BattleActionsController::BattleActionsController(BattleInterface & owner):
|
||||
owner(owner),
|
||||
heroSpellToCast(nullptr),
|
||||
creatureSpellToCast(nullptr)
|
||||
heroSpellToCast(nullptr)
|
||||
{}
|
||||
|
||||
void BattleActionsController::endCastingSpell()
|
||||
@@ -63,8 +133,8 @@ bool BattleActionsController::isActiveStackSpellcaster() const
|
||||
if (!casterStack)
|
||||
return false;
|
||||
|
||||
const auto randomSpellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER));
|
||||
return (randomSpellcaster && casterStack->canCast());
|
||||
bool spellcaster = casterStack->hasBonusOfType(Bonus::SPELLCASTER);
|
||||
return (spellcaster && casterStack->canCast());
|
||||
}
|
||||
|
||||
void BattleActionsController::enterCreatureCastingMode()
|
||||
@@ -83,10 +153,13 @@ void BattleActionsController::enterCreatureCastingMode()
|
||||
if (!isActiveStackSpellcaster())
|
||||
return;
|
||||
|
||||
if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION))
|
||||
for (auto const & action : possibleActions)
|
||||
{
|
||||
if (action.get() != PossiblePlayerBattleAction::NO_LOCATION)
|
||||
continue;
|
||||
|
||||
const spells::Caster * caster = owner.stacksController->getActiveStack();
|
||||
const CSpell * spell = getStackSpellToCast();
|
||||
const CSpell * spell = action.spell().toSpell();
|
||||
|
||||
spells::Target target;
|
||||
target.emplace_back();
|
||||
@@ -105,31 +178,26 @@ void BattleActionsController::enterCreatureCastingMode()
|
||||
|
||||
CCS->curh->set(Cursor::Combat::POINTER);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
|
||||
|
||||
auto actionFilterPredicate = [](const PossiblePlayerBattleAction x)
|
||||
{
|
||||
possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
|
||||
return !x.spellcast();
|
||||
};
|
||||
|
||||
auto actionFilterPredicate = [](const PossiblePlayerBattleAction x)
|
||||
{
|
||||
return (x != PossiblePlayerBattleAction::ANY_LOCATION) && (x != PossiblePlayerBattleAction::NO_LOCATION) &&
|
||||
(x != PossiblePlayerBattleAction::FREE_LOCATION) && (x != PossiblePlayerBattleAction::AIMED_SPELL_CREATURE) &&
|
||||
(x != PossiblePlayerBattleAction::OBSTACLE);
|
||||
};
|
||||
|
||||
vstd::erase_if(possibleActions, actionFilterPredicate);
|
||||
GH.fakeMouseMove();
|
||||
}
|
||||
vstd::erase_if(possibleActions, actionFilterPredicate);
|
||||
GH.fakeMouseMove();
|
||||
}
|
||||
|
||||
std::vector<PossiblePlayerBattleAction> BattleActionsController::getPossibleActionsForStack(const CStack *stack) const
|
||||
{
|
||||
BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass
|
||||
|
||||
if (getStackSpellToCast())
|
||||
data.creatureSpellToCast = getStackSpellToCast()->getId();
|
||||
else
|
||||
data.creatureSpellToCast = SpellID::NONE;
|
||||
for (auto const & spell : creatureSpells)
|
||||
data.creatureSpellsToCast.push_back(spell->id);
|
||||
|
||||
data.tacticsMode = owner.tacticsMode;
|
||||
auto allActions = owner.curInt->cb->getClientActionsForStack(stack, data);
|
||||
@@ -146,7 +214,7 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac
|
||||
|
||||
auto assignPriority = [&](PossiblePlayerBattleAction const & item) -> uint8_t //large lambda assigning priority which would have to be part of possibleActions without it
|
||||
{
|
||||
switch(item)
|
||||
switch(item.get())
|
||||
{
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
case PossiblePlayerBattleAction::ANY_LOCATION:
|
||||
@@ -207,7 +275,7 @@ void BattleActionsController::castThisSpell(SpellID spellID)
|
||||
assert(castingHero); // code below assumes non-null hero
|
||||
PossiblePlayerBattleAction spellSelMode = owner.curInt->cb->getCasterAction(spellID.toSpell(), castingHero, spells::Mode::HERO);
|
||||
|
||||
if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
|
||||
if (spellSelMode.get() == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
|
||||
{
|
||||
heroSpellToCast->aimToHex(BattleHex::INVALID);
|
||||
owner.curInt->cb->battleMakeAction(heroSpellToCast.get());
|
||||
@@ -228,19 +296,30 @@ const CSpell * BattleActionsController::getHeroSpellToCast( ) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const CSpell * BattleActionsController::getStackSpellToCast( ) const
|
||||
const CSpell * BattleActionsController::getStackSpellToCast(BattleHex hoveredHex)
|
||||
{
|
||||
if (isActiveStackSpellcaster())
|
||||
return creatureSpellToCast;
|
||||
if (heroSpellToCast)
|
||||
return nullptr;
|
||||
|
||||
return nullptr;
|
||||
if (!owner.stacksController->getActiveStack())
|
||||
return nullptr;
|
||||
|
||||
if (!hoveredHex.isValid())
|
||||
return nullptr;
|
||||
|
||||
auto action = selectAction(hoveredHex);
|
||||
|
||||
if (action.spell() == SpellID::NONE)
|
||||
return nullptr;
|
||||
|
||||
return action.spell().toSpell();
|
||||
}
|
||||
|
||||
const CSpell * BattleActionsController::getCurrentSpell( ) const
|
||||
const CSpell * BattleActionsController::getCurrentSpell(BattleHex hoveredHex)
|
||||
{
|
||||
if (getHeroSpellToCast())
|
||||
return getHeroSpellToCast();
|
||||
return getStackSpellToCast();
|
||||
return getStackSpellToCast(hoveredHex);
|
||||
}
|
||||
|
||||
const CStack * BattleActionsController::getStackForHex(BattleHex hoveredHex)
|
||||
@@ -253,7 +332,7 @@ const CStack * BattleActionsController::getStackForHex(BattleHex hoveredHex)
|
||||
|
||||
void BattleActionsController::actionSetCursor(PossiblePlayerBattleAction action, BattleHex targetHex)
|
||||
{
|
||||
switch (action)
|
||||
switch (action.get())
|
||||
{
|
||||
case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
|
||||
CCS->curh->set(Cursor::Combat::POINTER);
|
||||
@@ -316,7 +395,7 @@ void BattleActionsController::actionSetCursor(PossiblePlayerBattleAction action,
|
||||
|
||||
void BattleActionsController::actionSetCursorBlocked(PossiblePlayerBattleAction action, BattleHex targetHex)
|
||||
{
|
||||
switch (action)
|
||||
switch (action.get())
|
||||
{
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
|
||||
@@ -339,7 +418,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
||||
{
|
||||
const CStack * targetStack = getStackForHex(targetHex);
|
||||
|
||||
switch (action) //display console message, realize selected action
|
||||
switch (action.get()) //display console message, realize selected action
|
||||
{
|
||||
case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
|
||||
return (boost::format(CGI->generaltexth->allTexts[481]) % targetStack->getName()).str(); //Select %s
|
||||
@@ -356,26 +435,29 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
||||
{
|
||||
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
|
||||
TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex);
|
||||
std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
|
||||
return (boost::format(CGI->generaltexth->allTexts[36]) % targetStack->getName() % estDmgText).str(); //Attack %s (%s damage)
|
||||
DamageEstimation estimation = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex);
|
||||
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
|
||||
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
|
||||
|
||||
return formatMeleeAttack(estimation, targetStack->getName());
|
||||
}
|
||||
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
{
|
||||
auto const * shooter = owner.stacksController->getActiveStack();
|
||||
const auto * shooter = owner.stacksController->getActiveStack();
|
||||
|
||||
TDmgRange damage = owner.curInt->cb->battleEstimateDamage(shooter, targetStack, shooter->getPosition());
|
||||
std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
|
||||
//printing - Shoot %s (%d shots left, %s damage)
|
||||
return (boost::format(CGI->generaltexth->allTexts[296]) % targetStack->getName() % shooter->shots.available() % estDmgText).str();
|
||||
DamageEstimation estimation = owner.curInt->cb->battleEstimateDamage(shooter, targetStack, shooter->getPosition());
|
||||
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
|
||||
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
|
||||
|
||||
return formatRangedAttack(estimation, targetStack->getName(), shooter->shots.available());
|
||||
}
|
||||
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
return boost::str(boost::format(CGI->generaltexth->allTexts[27]) % getCurrentSpell()->getNameTranslated() % targetStack->getName()); //Cast %s on %s
|
||||
return boost::str(boost::format(CGI->generaltexth->allTexts[27]) % action.spell().toSpell()->getNameTranslated() % targetStack->getName()); //Cast %s on %s
|
||||
|
||||
case PossiblePlayerBattleAction::ANY_LOCATION:
|
||||
return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % getCurrentSpell()->getNameTranslated()); //Cast %s
|
||||
return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % action.spell().toSpell()->getNameTranslated()); //Cast %s
|
||||
|
||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
|
||||
return boost::str(boost::format(CGI->generaltexth->allTexts[301]) % targetStack->getName()); //Cast a spell on %
|
||||
@@ -390,7 +472,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
||||
return (boost::format(CGI->generaltexth->allTexts[549]) % targetStack->getName()).str(); //sacrifice the %s
|
||||
|
||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||
return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % getCurrentSpell()->getNameTranslated()); //Cast %s
|
||||
return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % action.spell().toSpell()->getNameTranslated()); //Cast %s
|
||||
|
||||
case PossiblePlayerBattleAction::HEAL:
|
||||
return (boost::format(CGI->generaltexth->allTexts[419]) % targetStack->getName()).str(); //Apply first aid to the %s
|
||||
@@ -410,7 +492,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
||||
|
||||
std::string BattleActionsController::actionGetStatusMessageBlocked(PossiblePlayerBattleAction action, BattleHex targetHex)
|
||||
{
|
||||
switch (action)
|
||||
switch (action.get())
|
||||
{
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
|
||||
@@ -423,7 +505,7 @@ std::string BattleActionsController::actionGetStatusMessageBlocked(PossiblePlaye
|
||||
return CGI->generaltexth->allTexts[543]; //choose army to sacrifice
|
||||
break;
|
||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||
return boost::str(boost::format(CGI->generaltexth->allTexts[181]) % getCurrentSpell()->getNameTranslated()); //No room to place %s here
|
||||
return boost::str(boost::format(CGI->generaltexth->allTexts[181]) % action.spell().toSpell()->getNameTranslated()); //No room to place %s here
|
||||
break;
|
||||
default:
|
||||
return "";
|
||||
@@ -435,7 +517,7 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
|
||||
const CStack * targetStack = getStackForHex(targetHex);
|
||||
bool targetStackOwned = targetStack && targetStack->owner == owner.curInt->playerID;
|
||||
|
||||
switch (action)
|
||||
switch (action.get())
|
||||
{
|
||||
case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
|
||||
case PossiblePlayerBattleAction::CREATURE_INFO:
|
||||
@@ -472,11 +554,14 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
return owner.curInt->cb->battleCanShoot(owner.stacksController->getActiveStack(), targetHex);
|
||||
|
||||
case PossiblePlayerBattleAction::NO_LOCATION:
|
||||
return false;
|
||||
|
||||
case PossiblePlayerBattleAction::ANY_LOCATION:
|
||||
return isCastingPossibleHere(owner.stacksController->getActiveStack(), targetStack, targetHex);
|
||||
return isCastingPossibleHere(action.spell().toSpell(), owner.stacksController->getActiveStack(), targetStack, targetHex);
|
||||
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
return targetStack && isCastingPossibleHere(owner.stacksController->getActiveStack(), targetStack, targetHex);
|
||||
return targetStack && isCastingPossibleHere(action.spell().toSpell(), owner.stacksController->getActiveStack(), targetStack, targetHex);
|
||||
|
||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
|
||||
if(targetStack && targetStackOwned && targetStack != owner.stacksController->getActiveStack() && targetStack->alive()) //only positive spells for other allied creatures
|
||||
@@ -497,8 +582,8 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
|
||||
|
||||
case PossiblePlayerBattleAction::OBSTACLE:
|
||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||
return isCastingPossibleHere(owner.stacksController->getActiveStack(), targetStack, targetHex);
|
||||
return isCastingPossibleHere(owner.stacksController->getActiveStack(), targetStack, targetHex);
|
||||
return isCastingPossibleHere(action.spell().toSpell(), owner.stacksController->getActiveStack(), targetStack, targetHex);
|
||||
return isCastingPossibleHere(action.spell().toSpell(), owner.stacksController->getActiveStack(), targetStack, targetHex);
|
||||
|
||||
case PossiblePlayerBattleAction::CATAPULT:
|
||||
return owner.siegeController && owner.siegeController->isAttackableByCatapult(targetHex);
|
||||
@@ -515,7 +600,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
|
||||
{
|
||||
const CStack * targetStack = getStackForHex(targetHex);
|
||||
|
||||
switch (action) //display console message, realize selected action
|
||||
switch (action.get()) //display console message, realize selected action
|
||||
{
|
||||
case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
|
||||
{
|
||||
@@ -546,7 +631,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
|
||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
||||
{
|
||||
bool returnAfterAttack = action == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
|
||||
bool returnAfterAttack = action.get() == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
|
||||
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
|
||||
if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
|
||||
{
|
||||
@@ -599,16 +684,16 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
|
||||
case PossiblePlayerBattleAction::SACRIFICE:
|
||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||
{
|
||||
if (action == PossiblePlayerBattleAction::AIMED_SPELL_CREATURE )
|
||||
if (action.get() == PossiblePlayerBattleAction::AIMED_SPELL_CREATURE )
|
||||
{
|
||||
if (getCurrentSpell()->id == SpellID::SACRIFICE)
|
||||
if (action.spell() == SpellID::SACRIFICE)
|
||||
{
|
||||
heroSpellToCast->aimToHex(targetHex);
|
||||
possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE);
|
||||
owner.stacksController->setSelectedStack(targetStack);
|
||||
return;
|
||||
}
|
||||
if (getCurrentSpell()->id == SpellID::TELEPORT)
|
||||
if (action.spell() == SpellID::TELEPORT)
|
||||
{
|
||||
heroSpellToCast->aimToUnit(targetStack);
|
||||
possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT);
|
||||
@@ -619,9 +704,9 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
|
||||
|
||||
if (!spellcastingModeActive())
|
||||
{
|
||||
if (getStackSpellToCast())
|
||||
if (action.spell().toSpell())
|
||||
{
|
||||
owner.giveCommand(EActionType::MONSTER_SPELL, targetHex, getStackSpellToCast()->getId());
|
||||
owner.giveCommand(EActionType::MONSTER_SPELL, targetHex, action.spell());
|
||||
}
|
||||
else //unknown random spell
|
||||
{
|
||||
@@ -750,12 +835,25 @@ void BattleActionsController::onHexLeftClicked(BattleHex clickedHex)
|
||||
|
||||
void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterStack)
|
||||
{
|
||||
const auto spellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER));
|
||||
creatureSpells.clear();
|
||||
|
||||
bool spellcaster = casterStack->hasBonusOfType(Bonus::SPELLCASTER);
|
||||
if(casterStack->canCast() && spellcaster)
|
||||
{
|
||||
// faerie dragon can cast only one, randomly selected spell until their next move
|
||||
//TODO: faerie dragon type spell should be selected by server
|
||||
creatureSpellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), casterStack, CBattleInfoCallback::RANDOM_AIMED).toSpell();
|
||||
const auto * spellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), casterStack, CBattleInfoCallback::RANDOM_AIMED).toSpell();
|
||||
|
||||
if (spellToCast)
|
||||
creatureSpells.push_back(spellToCast);
|
||||
}
|
||||
|
||||
TConstBonusListPtr bl = casterStack->getBonuses(Selector::type()(Bonus::SPELLCASTER));
|
||||
|
||||
for (auto const & bonus : *bl)
|
||||
{
|
||||
if (bonus->additionalInfo[0] <= 0)
|
||||
creatureSpells.push_back(SpellID(bonus->subtype).toSpell());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,11 +874,9 @@ spells::Mode BattleActionsController::getCurrentCastMode() const
|
||||
|
||||
}
|
||||
|
||||
bool BattleActionsController::isCastingPossibleHere(const CStack *casterStack, const CStack *targetStack, BattleHex targetHex)
|
||||
bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell, const CStack *casterStack, const CStack *targetStack, BattleHex targetHex)
|
||||
{
|
||||
auto currentSpell = getCurrentSpell();
|
||||
assert(currentSpell);
|
||||
|
||||
if (!currentSpell)
|
||||
return false;
|
||||
|
||||
@@ -823,7 +919,7 @@ void BattleActionsController::activateStack()
|
||||
std::list<PossiblePlayerBattleAction> actionsToSelect;
|
||||
if(!possibleActions.empty())
|
||||
{
|
||||
switch(possibleActions.front())
|
||||
switch(possibleActions.front().get())
|
||||
{
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
actionsToSelect.push_back(possibleActions.front());
|
||||
@@ -873,12 +969,7 @@ bool BattleActionsController::currentActionSpellcasting(BattleHex hoveredHex)
|
||||
|
||||
auto action = selectAction(hoveredHex);
|
||||
|
||||
return
|
||||
action == PossiblePlayerBattleAction::ANY_LOCATION ||
|
||||
action == PossiblePlayerBattleAction::NO_LOCATION ||
|
||||
action == PossiblePlayerBattleAction::FREE_LOCATION ||
|
||||
action == PossiblePlayerBattleAction::AIMED_SPELL_CREATURE ||
|
||||
action == PossiblePlayerBattleAction::OBSTACLE;
|
||||
return action.spellcast();
|
||||
}
|
||||
|
||||
const std::vector<PossiblePlayerBattleAction> & BattleActionsController::getPossibleActions() const
|
||||
|
@@ -45,9 +45,9 @@ class BattleActionsController
|
||||
std::string currentConsoleMsg;
|
||||
|
||||
/// if true, active stack could possibly cast some target spell
|
||||
const CSpell * creatureSpellToCast;
|
||||
std::vector<const CSpell *> creatureSpells;
|
||||
|
||||
bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber);
|
||||
bool isCastingPossibleHere (const CSpell * spell, const CStack *sactive, const CStack *shere, BattleHex myNumber);
|
||||
bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber) const; //TODO: move to BattleState / callback
|
||||
std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn
|
||||
void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
|
||||
@@ -74,7 +74,7 @@ class BattleActionsController
|
||||
const CSpell * getHeroSpellToCast() const;
|
||||
|
||||
/// if current stack is spellcaster, returns spell being cast, or null othervice
|
||||
const CSpell * getStackSpellToCast( ) const;
|
||||
const CSpell * getStackSpellToCast(BattleHex hoveredHex);
|
||||
|
||||
/// returns true if current stack is a spellcaster
|
||||
bool isActiveStackSpellcaster() const;
|
||||
@@ -116,7 +116,7 @@ public:
|
||||
void onHexRightClicked(BattleHex clickedHex);
|
||||
|
||||
const spells::Caster * getCurrentSpellcaster() const;
|
||||
const CSpell * getCurrentSpell() const;
|
||||
const CSpell * getCurrentSpell(BattleHex hoveredHex);
|
||||
spells::Mode getCurrentCastMode() const;
|
||||
|
||||
/// methods to work with array of possible actions, needed to control special creatures abilities
|
||||
|
@@ -263,7 +263,7 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesSpellRange()
|
||||
const CSpell *spell = nullptr;
|
||||
|
||||
spells::Mode mode = owner.actionsController->getCurrentCastMode();
|
||||
spell = owner.actionsController->getCurrentSpell();
|
||||
spell = owner.actionsController->getCurrentSpell(hoveredHex);
|
||||
caster = owner.actionsController->getCurrentSpellcaster();
|
||||
|
||||
if(caster && spell) //when casting spell
|
||||
|
@@ -54,6 +54,7 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
|
||||
, attackerInt(att)
|
||||
, defenderInt(defen)
|
||||
, curInt(att)
|
||||
, battleOpeningDelayActive(true)
|
||||
{
|
||||
if(spectatorInt)
|
||||
{
|
||||
@@ -112,7 +113,7 @@ void BattleInterface::playIntroSoundAndUnlockInterface()
|
||||
}
|
||||
};
|
||||
|
||||
battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
|
||||
int battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
|
||||
if (battleIntroSoundChannel != -1)
|
||||
{
|
||||
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
|
||||
@@ -120,8 +121,15 @@ void BattleInterface::playIntroSoundAndUnlockInterface()
|
||||
if (settings["gameTweaks"]["skipBattleIntroMusic"].Bool())
|
||||
openingEnd();
|
||||
}
|
||||
else
|
||||
else // failed to play sound
|
||||
{
|
||||
onIntroSoundPlayed();
|
||||
}
|
||||
}
|
||||
|
||||
bool BattleInterface::openingPlaying()
|
||||
{
|
||||
return battleOpeningDelayActive;
|
||||
}
|
||||
|
||||
void BattleInterface::onIntroSoundPlayed()
|
||||
@@ -132,6 +140,19 @@ void BattleInterface::onIntroSoundPlayed()
|
||||
CCS->musich->playMusicFromSet("battle", true, true);
|
||||
}
|
||||
|
||||
void BattleInterface::openingEnd()
|
||||
{
|
||||
assert(openingPlaying());
|
||||
if (!openingPlaying())
|
||||
return;
|
||||
|
||||
onAnimationsFinished();
|
||||
if(tacticsMode)
|
||||
tacticNextStack(nullptr);
|
||||
activateStack();
|
||||
battleOpeningDelayActive = false;
|
||||
}
|
||||
|
||||
BattleInterface::~BattleInterface()
|
||||
{
|
||||
CPlayerInterface::battleInt = nullptr;
|
||||
@@ -310,6 +331,12 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
|
||||
{
|
||||
windowObject->blockUI(true);
|
||||
|
||||
// Disable current active stack duing the cast
|
||||
// Store the current activeStack to stackToActivate
|
||||
stacksController->deactivateStack();
|
||||
|
||||
CCS->curh->set(Cursor::Combat::BLOCKED);
|
||||
|
||||
const SpellID spellID = sc->spellID;
|
||||
const CSpell * spell = spellID.toSpell();
|
||||
auto targetedTile = sc->tile;
|
||||
@@ -524,24 +551,6 @@ void BattleInterface::activateStack()
|
||||
GH.fakeMouseMove();
|
||||
}
|
||||
|
||||
bool BattleInterface::openingPlaying()
|
||||
{
|
||||
return battleIntroSoundChannel != -1;
|
||||
}
|
||||
|
||||
void BattleInterface::openingEnd()
|
||||
{
|
||||
assert(openingPlaying());
|
||||
if (!openingPlaying())
|
||||
return;
|
||||
|
||||
onAnimationsFinished();
|
||||
if(tacticsMode)
|
||||
tacticNextStack(nullptr);
|
||||
activateStack();
|
||||
battleIntroSoundChannel = -1;
|
||||
}
|
||||
|
||||
bool BattleInterface::makingTurn() const
|
||||
{
|
||||
return stacksController->getActiveStack() != nullptr;
|
||||
@@ -551,6 +560,9 @@ void BattleInterface::endAction(const BattleAction* action)
|
||||
{
|
||||
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
|
||||
|
||||
// Activate stack from stackToActivate because this might have been temporary disabled, e.g., during spell cast
|
||||
activateStack();
|
||||
|
||||
stacksController->endAction(action);
|
||||
windowObject->updateQueue();
|
||||
|
||||
@@ -693,6 +705,8 @@ void BattleInterface::requestAutofightingAIToTakeAction()
|
||||
auto ba = std::make_unique<BattleAction>(curInt->autofightingAI->activeStack(activeStack));
|
||||
givenCommand.setn(ba.release());
|
||||
}
|
||||
|
||||
stacksController->setActiveStack(nullptr);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -111,8 +111,8 @@ class BattleInterface
|
||||
/// defender interface, not null if attacker is human in our vcmiclient
|
||||
std::shared_ptr<CPlayerInterface> defenderInt;
|
||||
|
||||
/// ID of channel on which battle opening sound is playing, or -1 if none
|
||||
int battleIntroSoundChannel;
|
||||
/// if set to true, battle is still starting and waiting for intro sound to end / key press from player
|
||||
bool battleOpeningDelayActive;
|
||||
|
||||
void playIntroSoundAndUnlockInterface();
|
||||
void onIntroSoundPlayed();
|
||||
|
@@ -88,16 +88,13 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
|
||||
static const auto shifterNegative = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 0.2f, 0.2f );
|
||||
static const auto shifterNeutral = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 1.0f, 0.2f );
|
||||
|
||||
amountNormal->adjustPalette(shifterNormal, 0);
|
||||
amountPositive->adjustPalette(shifterPositive, 0);
|
||||
amountNegative->adjustPalette(shifterNegative, 0);
|
||||
amountEffNeutral->adjustPalette(shifterNeutral, 0);
|
||||
// do not change border color
|
||||
static const int32_t ignoredMask = 1 << 26;
|
||||
|
||||
//Restore border color {255, 231, 132, 255} to its original state
|
||||
amountNormal->resetPalette(26);
|
||||
amountPositive->resetPalette(26);
|
||||
amountNegative->resetPalette(26);
|
||||
amountEffNeutral->resetPalette(26);
|
||||
amountNormal->adjustPalette(shifterNormal, ignoredMask);
|
||||
amountPositive->adjustPalette(shifterPositive, ignoredMask);
|
||||
amountNegative->adjustPalette(shifterNegative, ignoredMask);
|
||||
amountEffNeutral->adjustPalette(shifterNeutral, ignoredMask);
|
||||
|
||||
std::vector<const CStack*> stacks = owner.curInt->cb->battleGetAllStacks(true);
|
||||
for(const CStack * s : stacks)
|
||||
@@ -187,7 +184,7 @@ void BattleStacksController::stackReset(const CStack * stack)
|
||||
void BattleStacksController::stackAdded(const CStack * stack, bool instant)
|
||||
{
|
||||
// Tower shooters have only their upper half visible
|
||||
static const int turretCreatureAnimationHeight = 225;
|
||||
static const int turretCreatureAnimationHeight = 232;
|
||||
|
||||
stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER; // must be set before getting stack position
|
||||
|
||||
@@ -201,6 +198,11 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
|
||||
|
||||
stackAnimation[stack->ID] = AnimationControls::getAnimation(turretCreature);
|
||||
stackAnimation[stack->ID]->pos.h = turretCreatureAnimationHeight;
|
||||
stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
|
||||
|
||||
// FIXME: workaround for visible animation of Medusa tails (animation disabled in H3)
|
||||
if (turretCreature->idNumber == CreatureID::MEDUSA )
|
||||
stackAnimation[stack->ID]->pos.w = 250;
|
||||
|
||||
coords = owner.siegeController->getTurretCreaturePosition(stack->initialPosition);
|
||||
}
|
||||
@@ -209,10 +211,10 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
|
||||
stackAnimation[stack->ID] = AnimationControls::getAnimation(stack->getCreature());
|
||||
stackAnimation[stack->ID]->onAnimationReset += std::bind(&onAnimationFinished, stack, stackAnimation[stack->ID]);
|
||||
stackAnimation[stack->ID]->pos.h = stackAnimation[stack->ID]->getHeight();
|
||||
stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
|
||||
}
|
||||
stackAnimation[stack->ID]->pos.x = coords.x;
|
||||
stackAnimation[stack->ID]->pos.y = coords.y;
|
||||
stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
|
||||
stackAnimation[stack->ID]->setType(ECreatureAnimType::HOLDING);
|
||||
|
||||
if (!instant)
|
||||
@@ -683,6 +685,15 @@ void BattleStacksController::stackActivated(const CStack *stack)
|
||||
owner.activateStack();
|
||||
}
|
||||
|
||||
void BattleStacksController::deactivateStack()
|
||||
{
|
||||
if (!activeStack) {
|
||||
return;
|
||||
}
|
||||
stackToActivate = activeStack;
|
||||
setActiveStack(nullptr);
|
||||
}
|
||||
|
||||
void BattleStacksController::activateStack()
|
||||
{
|
||||
if ( !currentAnimations.empty())
|
||||
@@ -840,7 +851,7 @@ std::vector<const CStack *> BattleStacksController::selectHoveredStacks()
|
||||
const CSpell *spell = nullptr;
|
||||
|
||||
spells::Mode mode = owner.actionsController->getCurrentCastMode();
|
||||
spell = owner.actionsController->getCurrentSpell();
|
||||
spell = owner.actionsController->getCurrentSpell(hoveredHex);
|
||||
caster = owner.actionsController->getCurrentSpellcaster();
|
||||
|
||||
if(caster && spell && owner.actionsController->currentActionSpellcasting(hoveredHex) ) //when casting spell
|
||||
|
@@ -118,7 +118,9 @@ public:
|
||||
void startAction(const BattleAction* action);
|
||||
void endAction(const BattleAction* action);
|
||||
|
||||
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
|
||||
void deactivateStack(); //copy activeStack to stackToActivate, then set activeStack to nullptr to temporary disable current stack
|
||||
|
||||
void activateStack(); //copy stackToActivate to activeStack to enable controls of the stack
|
||||
|
||||
void setActiveStack(const CStack *stack);
|
||||
void setSelectedStack(const CStack *stack);
|
||||
|
@@ -38,7 +38,8 @@
|
||||
#include "../windows/settings/SettingsMainWindow.h"
|
||||
|
||||
BattleWindow::BattleWindow(BattleInterface & owner):
|
||||
owner(owner)
|
||||
owner(owner),
|
||||
defaultAction(PossiblePlayerBattleAction::INVALID)
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
pos.w = 800;
|
||||
@@ -326,7 +327,7 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
|
||||
return;
|
||||
|
||||
std::string iconName = variables["actionIconDefault"].String();
|
||||
switch(action)
|
||||
switch(action.get())
|
||||
{
|
||||
case PossiblePlayerBattleAction::ATTACK:
|
||||
iconName = variables["actionIconAttack"].String();
|
||||
|
@@ -12,6 +12,7 @@
|
||||
#include "../gui/CIntObject.h"
|
||||
#include "../gui/InterfaceObjectConfigurable.h"
|
||||
#include "../../lib/battle/CBattleInfoCallback.h"
|
||||
#include "../../lib/battle/PossiblePlayerBattleAction.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
class CStack;
|
||||
|
@@ -343,13 +343,14 @@ static SDL_Color addColors(const SDL_Color & base, const SDL_Color & over)
|
||||
|
||||
void CreatureAnimation::genSpecialPalette(IImage::SpecialPalette & target)
|
||||
{
|
||||
target[0] = genShadow(shadowAlpha / 2);
|
||||
target.resize(8);
|
||||
target[0] = genShadow(0);
|
||||
target[1] = genShadow(shadowAlpha / 2);
|
||||
target[2] = genShadow(shadowAlpha);
|
||||
target[3] = genShadow(shadowAlpha);
|
||||
target[4] = genBorderColor(getBorderStrength(elapsedTime), border);
|
||||
target[5] = addColors(genShadow(shadowAlpha), genBorderColor(getBorderStrength(elapsedTime), border));
|
||||
target[6] = addColors(genShadow(shadowAlpha / 2), genBorderColor(getBorderStrength(elapsedTime), border));
|
||||
// colors 2 & 3 are not used in creatures
|
||||
target[4] = genShadow(shadowAlpha);
|
||||
target[5] = genBorderColor(getBorderStrength(elapsedTime), border);
|
||||
target[6] = addColors(genShadow(shadowAlpha), genBorderColor(getBorderStrength(elapsedTime), border));
|
||||
target[7] = addColors(genShadow(shadowAlpha / 2), genBorderColor(getBorderStrength(elapsedTime), border));
|
||||
}
|
||||
|
||||
void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter, bool facingRight)
|
||||
@@ -371,8 +372,8 @@ void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter,
|
||||
IImage::SpecialPalette SpecialPalette;
|
||||
genSpecialPalette(SpecialPalette);
|
||||
|
||||
image->setSpecialPallete(SpecialPalette);
|
||||
image->adjustPalette(shifter, 8);
|
||||
image->setSpecialPallete(SpecialPalette, IImage::SPECIAL_PALETTE_MASK_CREATURES);
|
||||
image->adjustPalette(shifter, IImage::SPECIAL_PALETTE_MASK_CREATURES);
|
||||
|
||||
canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
|
||||
|
||||
|
@@ -192,7 +192,7 @@ void CGuiHandler::updateTime()
|
||||
for (auto & elem : hlp)
|
||||
{
|
||||
if(!vstd::contains(timeinterested,elem)) continue;
|
||||
(elem)->onTimer(ms);
|
||||
(elem)->tick(ms);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -29,7 +29,6 @@ CIntObject::CIntObject(int used_, Point pos_):
|
||||
active(active_m)
|
||||
{
|
||||
hovered = captureAllKeys = strongInterest = false;
|
||||
toNextTick = timerDelay = 0;
|
||||
used = used_;
|
||||
|
||||
recActions = defActions = GH.defActionsDef;
|
||||
@@ -60,24 +59,6 @@ CIntObject::~CIntObject()
|
||||
parent_m->removeChild(this);
|
||||
}
|
||||
|
||||
void CIntObject::setTimer(int msToTrigger)
|
||||
{
|
||||
if (!(active & TIME))
|
||||
activate(TIME);
|
||||
toNextTick = timerDelay = msToTrigger;
|
||||
used |= TIME;
|
||||
}
|
||||
|
||||
void CIntObject::onTimer(int timePassed)
|
||||
{
|
||||
toNextTick -= timePassed;
|
||||
if (toNextTick < 0)
|
||||
{
|
||||
toNextTick += timerDelay;
|
||||
tick();
|
||||
}
|
||||
}
|
||||
|
||||
void CIntObject::show(SDL_Surface * to)
|
||||
{
|
||||
if(defActions & UPDATE)
|
||||
|
@@ -65,14 +65,8 @@ class CIntObject : public IShowActivatable //interface object
|
||||
{
|
||||
ui16 used;//change via addUsed() or delUsed
|
||||
|
||||
//time handling
|
||||
int toNextTick;
|
||||
int timerDelay;
|
||||
|
||||
std::map<MouseButton, bool> currentMouseState;
|
||||
|
||||
void onTimer(int timePassed);
|
||||
|
||||
//non-const versions of fields to allow changing them in CIntObject
|
||||
CIntObject *parent_m; //parent object
|
||||
ui16 active_m;
|
||||
@@ -129,8 +123,7 @@ public:
|
||||
virtual void mouseMoved (const Point & cursorPosition){}
|
||||
|
||||
//time handling
|
||||
void setTimer(int msToTrigger);//set timer delay and activate timer if needed.
|
||||
virtual void tick(){}
|
||||
virtual void tick(uint32_t msPassed){}
|
||||
|
||||
//mouse wheel
|
||||
virtual void wheelScrolled(bool down, bool in){}
|
||||
|
@@ -26,27 +26,27 @@ public:
|
||||
virtual bool hasOngoingAnimations() = 0;
|
||||
|
||||
/// Plays fade-in animation and adds object to map
|
||||
virtual void onObjectFadeIn(const CGObjectInstance * obj) {}
|
||||
virtual void onObjectFadeIn(const CGObjectInstance * obj) = 0;
|
||||
|
||||
/// Plays fade-out animation and removed object from map
|
||||
virtual void onObjectFadeOut(const CGObjectInstance * obj) {}
|
||||
virtual void onObjectFadeOut(const CGObjectInstance * obj) = 0;
|
||||
|
||||
/// Adds object to map instantly, with no animation
|
||||
virtual void onObjectInstantAdd(const CGObjectInstance * obj) {}
|
||||
virtual void onObjectInstantAdd(const CGObjectInstance * obj) = 0;
|
||||
|
||||
/// Removes object from map instantly, with no animation
|
||||
virtual void onObjectInstantRemove(const CGObjectInstance * obj) {}
|
||||
virtual void onObjectInstantRemove(const CGObjectInstance * obj) = 0;
|
||||
|
||||
/// Perform hero movement animation, moving hero across terrain
|
||||
virtual void onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {}
|
||||
virtual void onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest) = 0;
|
||||
|
||||
/// Perform initialization of hero teleportation animation with terrain fade animation
|
||||
virtual void onBeforeHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {}
|
||||
virtual void onAfterHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) {}
|
||||
virtual void onBeforeHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) = 0;
|
||||
virtual void onAfterHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest) = 0;
|
||||
|
||||
virtual void onBeforeHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest){};
|
||||
virtual void onAfterHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest){};
|
||||
virtual void onBeforeHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) = 0;
|
||||
virtual void onAfterHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) = 0;
|
||||
|
||||
virtual void onBeforeHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest){};
|
||||
virtual void onAfterHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest){};
|
||||
virtual void onBeforeHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) = 0;
|
||||
virtual void onAfterHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest) = 0;
|
||||
};
|
||||
|
@@ -60,14 +60,14 @@ BasicMapView::BasicMapView(const Point & offset, const Point & dimensions)
|
||||
void BasicMapView::render(Canvas & target, bool fullUpdate)
|
||||
{
|
||||
Canvas targetClipped(target, pos);
|
||||
|
||||
controller->update(GH.mainFPSmng->getElapsedMilliseconds());
|
||||
tilesCache->update(controller->getContext());
|
||||
tilesCache->render(controller->getContext(), targetClipped, fullUpdate);
|
||||
}
|
||||
|
||||
void BasicMapView::show(SDL_Surface * to)
|
||||
{
|
||||
controller->update(GH.mainFPSmng->getElapsedMilliseconds());
|
||||
|
||||
Canvas target(to);
|
||||
CSDL_Ext::CClipRectGuard guard(to, pos);
|
||||
render(target, false);
|
||||
@@ -75,6 +75,8 @@ void BasicMapView::show(SDL_Surface * to)
|
||||
|
||||
void BasicMapView::showAll(SDL_Surface * to)
|
||||
{
|
||||
controller->update(0);
|
||||
|
||||
Canvas target(to);
|
||||
CSDL_Ext::CClipRectGuard guard(to, pos);
|
||||
render(target, true);
|
||||
|
@@ -24,7 +24,6 @@
|
||||
MapViewActions::MapViewActions(MapView & owner, const std::shared_ptr<MapViewModel> & model)
|
||||
: model(model)
|
||||
, owner(owner)
|
||||
, curHoveredTile(-1, -1, -1)
|
||||
, isSwiping(false)
|
||||
{
|
||||
pos.w = model->getPixelsVisibleDimensions().x;
|
||||
@@ -47,17 +46,6 @@ void MapViewActions::setContext(const std::shared_ptr<IMapRendererContext> & con
|
||||
this->context = context;
|
||||
}
|
||||
|
||||
void MapViewActions::activate()
|
||||
{
|
||||
CIntObject::activate();
|
||||
}
|
||||
|
||||
void MapViewActions::deactivate()
|
||||
{
|
||||
CIntObject::deactivate();
|
||||
curHoveredTile = int3(-1, -1, -1); //we lost info about hovered tile when disabling
|
||||
}
|
||||
|
||||
void MapViewActions::clickLeft(tribool down, bool previousState)
|
||||
{
|
||||
if(indeterminate(down))
|
||||
@@ -159,11 +147,7 @@ void MapViewActions::handleHover(const Point & cursorPosition)
|
||||
return;
|
||||
}
|
||||
|
||||
if(tile != curHoveredTile)
|
||||
{
|
||||
curHoveredTile = tile;
|
||||
adventureInt->onTileHovered(tile);
|
||||
}
|
||||
adventureInt->onTileHovered(tile);
|
||||
}
|
||||
|
||||
void MapViewActions::hover(bool on)
|
||||
|
@@ -23,8 +23,6 @@ class MapViewActions : public CIntObject
|
||||
Point swipeInitialViewPos;
|
||||
Point swipeInitialRealPos;
|
||||
|
||||
int3 curHoveredTile;
|
||||
|
||||
MapView & owner;
|
||||
std::shared_ptr<MapViewModel> model;
|
||||
std::shared_ptr<IMapRendererContext> context;
|
||||
@@ -39,8 +37,6 @@ public:
|
||||
|
||||
void setContext(const std::shared_ptr<IMapRendererContext> & context);
|
||||
|
||||
void activate() override;
|
||||
void deactivate() override;
|
||||
void clickLeft(tribool down, bool previousState) override;
|
||||
void clickRight(tribool down, bool previousState) override;
|
||||
void clickMiddle(tribool down, bool previousState) override;
|
||||
|
@@ -251,6 +251,26 @@ void MapViewController::fadeInObject(const CGObjectInstance * obj)
|
||||
|
||||
void MapViewController::removeObject(const CGObjectInstance * obj)
|
||||
{
|
||||
if (obj->ID == Obj::BOAT)
|
||||
{
|
||||
auto * boat = dynamic_cast<const CGBoat*>(obj);
|
||||
if (boat->hero)
|
||||
{
|
||||
view->invalidate(context, boat->hero->id);
|
||||
state->removeObject(boat->hero);
|
||||
}
|
||||
}
|
||||
|
||||
if (obj->ID == Obj::HERO)
|
||||
{
|
||||
auto * hero = dynamic_cast<const CGHeroInstance*>(obj);
|
||||
if (hero->boat)
|
||||
{
|
||||
view->invalidate(context, hero->boat->id);
|
||||
state->removeObject(hero->boat);
|
||||
}
|
||||
}
|
||||
|
||||
view->invalidate(context, obj->id);
|
||||
state->removeObject(obj);
|
||||
}
|
||||
@@ -265,7 +285,7 @@ void MapViewController::onBeforeHeroEmbark(const CGHeroInstance * obj, const int
|
||||
{
|
||||
if(isEventVisible(obj, from, dest))
|
||||
{
|
||||
onObjectFadeOut(obj);
|
||||
fadeOutObject(obj);
|
||||
setViewCenter(obj->getSightCenter());
|
||||
}
|
||||
else
|
||||
@@ -288,7 +308,7 @@ void MapViewController::onAfterHeroDisembark(const CGHeroInstance * obj, const i
|
||||
{
|
||||
if(isEventVisible(obj, from, dest))
|
||||
{
|
||||
onObjectFadeIn(obj);
|
||||
fadeInObject(obj);
|
||||
setViewCenter(obj->getSightCenter());
|
||||
}
|
||||
addObject(obj);
|
||||
|
@@ -229,5 +229,6 @@ IMapObjectObserver::IMapObjectObserver()
|
||||
|
||||
IMapObjectObserver::~IMapObjectObserver()
|
||||
{
|
||||
CGI->mh->removeMapObserver(this);
|
||||
if (CGI && CGI->mh)
|
||||
CGI->mh->removeMapObserver(this);
|
||||
}
|
||||
|
@@ -40,7 +40,8 @@ enum class EImageBlitMode : uint8_t
|
||||
class IImage
|
||||
{
|
||||
public:
|
||||
using SpecialPalette = std::array<SDL_Color, 7>;
|
||||
using SpecialPalette = std::vector<SDL_Color>;
|
||||
static constexpr int32_t SPECIAL_PALETTE_MASK_CREATURES = 0b11110011;
|
||||
|
||||
//draws image on surface "where" at position
|
||||
virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr) const = 0;
|
||||
@@ -65,7 +66,7 @@ public:
|
||||
|
||||
//only indexed bitmaps, 16 colors maximum
|
||||
virtual void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) = 0;
|
||||
virtual void adjustPalette(const ColorFilter & shifter, size_t colorsToSkip) = 0;
|
||||
virtual void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) = 0;
|
||||
virtual void resetPalette(int colorID) = 0;
|
||||
virtual void resetPalette() = 0;
|
||||
|
||||
@@ -73,7 +74,7 @@ public:
|
||||
virtual void setBlitMode(EImageBlitMode mode) = 0;
|
||||
|
||||
//only indexed bitmaps with 7 special colors
|
||||
virtual void setSpecialPallete(const SpecialPalette & SpecialPalette) = 0;
|
||||
virtual void setSpecialPallete(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) = 0;
|
||||
|
||||
virtual void horizontalFlip() = 0;
|
||||
virtual void verticalFlip() = 0;
|
||||
|
@@ -308,7 +308,7 @@ void SDLImage::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32
|
||||
}
|
||||
}
|
||||
|
||||
void SDLImage::adjustPalette(const ColorFilter & shifter, size_t colorsToSkip)
|
||||
void SDLImage::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
|
||||
{
|
||||
if(originalPalette == nullptr)
|
||||
return;
|
||||
@@ -316,8 +316,11 @@ void SDLImage::adjustPalette(const ColorFilter & shifter, size_t colorsToSkip)
|
||||
SDL_Palette* palette = surf->format->palette;
|
||||
|
||||
// Note: here we skip first colors in the palette that are predefined in H3 images
|
||||
for(int i = colorsToSkip; i < palette->ncolors; i++)
|
||||
for(int i = 0; i < palette->ncolors; i++)
|
||||
{
|
||||
if(i < std::numeric_limits<uint32_t>::digits && ((colorsToSkipMask >> i) & 1) == 1)
|
||||
continue;
|
||||
|
||||
palette->colors[i] = shifter.shiftColor(originalPalette->colors[i]);
|
||||
}
|
||||
}
|
||||
@@ -340,11 +343,17 @@ void SDLImage::resetPalette( int colorID )
|
||||
SDL_SetPaletteColors(surf->format->palette, originalPalette->colors + colorID, colorID, 1);
|
||||
}
|
||||
|
||||
void SDLImage::setSpecialPallete(const IImage::SpecialPalette & SpecialPalette)
|
||||
void SDLImage::setSpecialPallete(const IImage::SpecialPalette & specialPalette, uint32_t colorsToSkipMask)
|
||||
{
|
||||
if(surf->format->palette)
|
||||
{
|
||||
CSDL_Ext::setColors(surf, const_cast<SDL_Color *>(SpecialPalette.data()), 1, 7);
|
||||
size_t last = std::min<size_t>(specialPalette.size(), surf->format->palette->ncolors);
|
||||
|
||||
for (size_t i = 0; i < last; ++i)
|
||||
{
|
||||
if(i < std::numeric_limits<uint32_t>::digits && ((colorsToSkipMask >> i) & 1) == 1)
|
||||
surf->format->palette->colors[i] = specialPalette[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -66,14 +66,14 @@ public:
|
||||
void verticalFlip() override;
|
||||
|
||||
void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
|
||||
void adjustPalette(const ColorFilter & shifter, size_t colorsToSkip) override;
|
||||
void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
|
||||
void resetPalette(int colorID) override;
|
||||
void resetPalette() override;
|
||||
|
||||
void setAlpha(uint8_t value) override;
|
||||
void setBlitMode(EImageBlitMode mode) override;
|
||||
|
||||
void setSpecialPallete(const SpecialPalette & SpecialPalette) override;
|
||||
void setSpecialPallete(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) override;
|
||||
|
||||
friend class SDLImageLoader;
|
||||
|
||||
|
@@ -35,7 +35,8 @@ std::shared_ptr<CIntObject> CObjectList::createItem(size_t index)
|
||||
|
||||
item->recActions = defActions;
|
||||
addChild(item.get());
|
||||
item->activate();
|
||||
if (active)
|
||||
item->activate();
|
||||
return item;
|
||||
}
|
||||
|
||||
|
@@ -1210,7 +1210,8 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
|
||||
|
||||
CCastleInterface::~CCastleInterface()
|
||||
{
|
||||
adventureInt->onAudioResumed();
|
||||
if (adventureInt) // may happen on exiting client with open castle interface
|
||||
adventureInt->onAudioResumed();
|
||||
if(LOCPLINT->castleInt == this)
|
||||
LOCPLINT->castleInt = nullptr;
|
||||
}
|
||||
|
@@ -924,6 +924,9 @@ void CMarketplaceWindow::artifactsChanged(bool Left)
|
||||
toRemove.insert(item);
|
||||
|
||||
removeItems(toRemove);
|
||||
|
||||
// clear set to erase final instance of shared_ptr - we want to redraw screen only after it has been deleted
|
||||
toRemove.clear();
|
||||
redraw();
|
||||
}
|
||||
|
||||
|
@@ -85,6 +85,7 @@ std::shared_ptr<CPicture> CWindowObject::createBg(std::string imageName, bool pl
|
||||
return nullptr;
|
||||
|
||||
auto image = std::make_shared<CPicture>(imageName);
|
||||
image->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
|
||||
if(playerColored)
|
||||
image->colorize(LOCPLINT->playerID);
|
||||
return image;
|
||||
|
@@ -258,6 +258,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
|
||||
${MAIN_LIB_DIR}/battle/IBattleInfoCallback.h
|
||||
${MAIN_LIB_DIR}/battle/IBattleState.h
|
||||
${MAIN_LIB_DIR}/battle/IUnitInfo.h
|
||||
${MAIN_LIB_DIR}/battle/PossiblePlayerBattleAction.h
|
||||
${MAIN_LIB_DIR}/battle/ReachabilityInfo.h
|
||||
${MAIN_LIB_DIR}/battle/SideInBattle.h
|
||||
${MAIN_LIB_DIR}/battle/SiegeInfo.h
|
||||
|
@@ -68,7 +68,7 @@
|
||||
"alpha" : 0.0
|
||||
},
|
||||
{
|
||||
"time" : 0.2
|
||||
"time" : 0.5
|
||||
},
|
||||
],
|
||||
"teleportFadeOut" : [
|
||||
@@ -76,7 +76,7 @@
|
||||
"time" : 0.0
|
||||
},
|
||||
{
|
||||
"time" : 0.2,
|
||||
"time" : 0.5,
|
||||
"alpha" : 0.0
|
||||
},
|
||||
],
|
||||
|
@@ -64,12 +64,12 @@
|
||||
},
|
||||
"language" : {
|
||||
"type":"string",
|
||||
"enum" : [ "chinese", "english", "german", "polish", "russian", "spanish", "ukrainian" ],
|
||||
"enum" : [ "english", "chinese", "german", "polish", "russian", "spanish", "ukrainian" ],
|
||||
"default" : "english"
|
||||
},
|
||||
"gameDataLanguage" : {
|
||||
"type":"string",
|
||||
"enum" : [ "auto", "chinese", "english", "german", "korean", "polish", "russian", "spanish", "ukrainian", "other_cp1250", "other_cp1251", "other_cp1252" ],
|
||||
"enum" : [ "auto", "english", "chinese", "german", "korean", "polish", "russian", "spanish", "ukrainian", "other_cp1250", "other_cp1251", "other_cp1252" ],
|
||||
"default" : "auto"
|
||||
},
|
||||
"lastSave" : {
|
||||
|
@@ -145,7 +145,7 @@
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.cpp" line="242"/>
|
||||
<source>Contact</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Kontakt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.cpp" line="246"/>
|
||||
@@ -242,7 +242,7 @@
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="590"/>
|
||||
<source>Adventure Map AI</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>AI mapy przygody</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="334"/>
|
||||
@@ -261,18 +261,18 @@
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="79"/>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="576"/>
|
||||
<source>Artificial Intelligence</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Sztuczna Inteligencja</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="89"/>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="415"/>
|
||||
<source>Mod Repositories</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Repozytoria modów</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="368"/>
|
||||
<source>Update now</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Zaktualizuj teraz</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="261"/>
|
||||
@@ -285,37 +285,37 @@
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="215"/>
|
||||
<source>Cursor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Kursor</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="500"/>
|
||||
<source>Heroes III Data Language</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Język plików Heroes III</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="552"/>
|
||||
<source>Default</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Domyślny</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="557"/>
|
||||
<source>Hardware</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Sprzętowy</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="562"/>
|
||||
<source>Software</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Programowy</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="597"/>
|
||||
<source>Heroes III Translation</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Tłumaczenie Heroes III</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="429"/>
|
||||
<source>Check on startup</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Sprawdzaj przy uruchomieniu</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="158"/>
|
||||
@@ -408,27 +408,27 @@
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.cpp" line="385"/>
|
||||
<source>Active</source>
|
||||
<translation type="unfinished">Aktywny</translation>
|
||||
<translation>Aktywny</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.cpp" line="390"/>
|
||||
<source>Disabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Wyłączone</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.cpp" line="391"/>
|
||||
<source>Enable</source>
|
||||
<translation type="unfinished">Włącz</translation>
|
||||
<translation>Włącz</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.cpp" line="396"/>
|
||||
<source>Not Installed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Nie zainstalowano</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.cpp" line="397"/>
|
||||
<source>Install</source>
|
||||
<translation type="unfinished">Zainstaluj</translation>
|
||||
<translation>Zainstaluj</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -436,134 +436,134 @@
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="28"/>
|
||||
<source>Language</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Język</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="53"/>
|
||||
<source>Heroes III Data</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Pliki Heroes III</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="78"/>
|
||||
<source>Mods Preset</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Zestaw modów</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="384"/>
|
||||
<source>Your Heroes III data files have been successfully found.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Twoje pliki Heroes III zostały pomyślnie znalezione.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="552"/>
|
||||
<source>Optionally, you can install additional mods either now or at any point later:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Opcjonalnie możesz zainstalować dodatkowe modyfikacje teraz lub później:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="616"/>
|
||||
<source>Install support for playing Heroes III in resolutions other than 800x600.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Zapinstaluj wsparcie dla grania w Heroes III w rozdzielczości innej niż 800x600.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="645"/>
|
||||
<source>Install compatible version of addon Horn of the Abyss: fan-made Heroes III expansion, ported by VCMI team</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Zainstaluj kompatybilną wersję fanowskiego dodatku Horn of the Abyss przeportowaną przez zespół VCMI</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="674"/>
|
||||
<source>Install compatible version of addon "In The Wake of Gods": fan-made Heroes III expansion</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Zainstaluj kompatybilną wersję fanowskiego dodatku "In The Wake Of Gods"</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="721"/>
|
||||
<source>Finish</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Zakończ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="111"/>
|
||||
<source>Step %v out of %m</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Krok %v z %m</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="143"/>
|
||||
<source>Choose your language</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Wybierz język</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="150"/>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="493"/>
|
||||
<source>Next</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Dalej</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="215"/>
|
||||
<source>Find Heroes III data files</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Znajdź pliki Heroes III</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="246"/>
|
||||
<source>Open help in browser</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Otwórz pomoc w przeglądarce</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="259"/>
|
||||
<source>Search again</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Szukaj ponownie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="304"/>
|
||||
<source>If you don't have installed Heroes III copy, it is possible to use our automatic installation tool 'vcmibuilder' to extract data from GoG.com installer. Visit our wiki for detailed instructions.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Jeśli nie masz zainstalowanej kopii Heroes III istnieje możliwość użycia naszego automatycznego narzędzia instalacyjnego 'vcmibuilder' by wyodrębnić dane z instalatora GoG.com. Odwiedź nasze wiki po szczegółowe instrukcje.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="320"/>
|
||||
<source>VCMI requires Heroes III data files in one of the locations listed above. Please copy Heroes III data in one of these directories.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>VCMI wymaga plików Heroes III w jednej z wymienionych wyżej lokalizacji. Proszę, skopiuj pliki Heroes III do jednego z tych katalogów.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="342"/>
|
||||
<source>Heroes III data files</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Pliki Heroes III</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="355"/>
|
||||
<source>Alternatively, you can select directory with installed Heroes III data and VCMI will copy exisiting data automatically.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Możesz też wybrać folder z zainstalowanym Heroes III i VCMI automatycznie skopiuje istniejące dane.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="371"/>
|
||||
<source>Copy existing data</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Skopiuj istniejące dane</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="414"/>
|
||||
<source>Your Heroes III language has been successfully detected.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Twój język Heroes III został pomyślnie wykryty.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="424"/>
|
||||
<source>Automatic detection of language failed. Please select language of your Heroes III copy</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Automatyczna detekcja języka nie powiodła się. Proszę wybrać język twojego Heroes III</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="443"/>
|
||||
<source>Heroes III language</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Język Heroes III</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="486"/>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="714"/>
|
||||
<source>Back</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Wstecz</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="524"/>
|
||||
<source>Install VCMI Mod Preset</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Zainstaluj zestaw modyfikacji</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="584"/>
|
||||
<source>Install translation of Heroes III to your language</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Zainstaluj tłumaczenie Heroes III dla twojego języka</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -663,12 +663,12 @@
|
||||
<message>
|
||||
<location filename="../lobby/lobby_moc.ui" line="76"/>
|
||||
<source>People in lobby</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Ludzie w lobby</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../lobby/lobby_moc.ui" line="114"/>
|
||||
<source>Lobby chat</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Czat lobby</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../lobby/lobby_moc.ui" line="194"/>
|
||||
@@ -683,17 +683,17 @@
|
||||
<message>
|
||||
<location filename="../lobby/lobby_moc.ui" line="274"/>
|
||||
<source>Resolve</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Rozwiąż</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../lobby/lobby_moc.ui" line="286"/>
|
||||
<source>New game</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Nowa gra</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../lobby/lobby_moc.ui" line="293"/>
|
||||
<source>Load game</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Wczytaj grę</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../lobby/lobby_moc.ui" line="149"/>
|
||||
@@ -733,12 +733,12 @@
|
||||
<message>
|
||||
<location filename="../lobby/lobby_moc.cpp" line="369"/>
|
||||
<source>Disconnect</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Rozłącz</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../lobby/lobby_moc.cpp" line="461"/>
|
||||
<source>No issues detected</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Nie znaleziono problemów</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -779,7 +779,7 @@
|
||||
<message>
|
||||
<location filename="../mainwindow_moc.ui" line="226"/>
|
||||
<source>Map Editor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Edytor map</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow_moc.ui" line="279"/>
|
||||
|
@@ -684,7 +684,7 @@ void CModInfo::loadLocalData(const JsonNode & data)
|
||||
{
|
||||
bool validated = false;
|
||||
implicitlyEnabled = true;
|
||||
explicitlyEnabled = true;
|
||||
explicitlyEnabled = !config["keepDisabled"].Bool();
|
||||
checksum = 0;
|
||||
if (data.getType() == JsonNode::JsonType::DATA_BOOL)
|
||||
{
|
||||
|
@@ -1120,6 +1120,7 @@ public:
|
||||
LICHES = 64,
|
||||
BONE_DRAGON = 68,
|
||||
TROGLODYTES = 70,
|
||||
MEDUSA = 76,
|
||||
HYDRA = 110,
|
||||
CHAOS_HYDRA = 111,
|
||||
AIR_ELEMENTAL = 112,
|
||||
@@ -1245,7 +1246,7 @@ class ObstacleInfo;
|
||||
class Obstacle : public BaseForID<Obstacle, si32>
|
||||
{
|
||||
INSTID_LIKE_CLASS_COMMON(Obstacle, si32)
|
||||
|
||||
|
||||
DLL_LINKAGE const ObstacleInfo * getInfo() const;
|
||||
DLL_LINKAGE operator std::string() const;
|
||||
DLL_LINKAGE static Obstacle fromString(const std::string & identifier);
|
||||
@@ -1295,7 +1296,6 @@ enum class EHealPower : ui8
|
||||
// Typedef declarations
|
||||
typedef ui8 TFaction;
|
||||
typedef si64 TExpType;
|
||||
typedef std::pair<si64, si64> TDmgRange;
|
||||
typedef si32 TBonusSubtype;
|
||||
typedef si32 TQuantity;
|
||||
|
||||
|
@@ -663,14 +663,14 @@ const IBonusBearer * BattleInfo::asBearer() const
|
||||
return this;
|
||||
}
|
||||
|
||||
int64_t BattleInfo::getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const
|
||||
int64_t BattleInfo::getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const
|
||||
{
|
||||
if(damage.first != damage.second)
|
||||
if(damage.min != damage.max)
|
||||
{
|
||||
int64_t sum = 0;
|
||||
|
||||
auto howManyToAv = std::min<int32_t>(10, attackerCount);
|
||||
auto rangeGen = rng.getInt64Range(damage.first, damage.second);
|
||||
auto rangeGen = rng.getInt64Range(damage.min, damage.max);
|
||||
|
||||
for(int32_t g = 0; g < howManyToAv; ++g)
|
||||
sum += rangeGen();
|
||||
@@ -679,7 +679,7 @@ int64_t BattleInfo::getActualDamage(const TDmgRange & damage, int32_t attackerCo
|
||||
}
|
||||
else
|
||||
{
|
||||
return damage.first;
|
||||
return damage.min;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -97,7 +97,7 @@ public:
|
||||
|
||||
uint32_t nextUnitId() const override;
|
||||
|
||||
int64_t getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
|
||||
int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// IBattleState
|
||||
|
@@ -15,6 +15,7 @@
|
||||
#include "../CStack.h"
|
||||
#include "BattleInfo.h"
|
||||
#include "DamageCalculator.h"
|
||||
#include "PossiblePlayerBattleAction.h"
|
||||
#include "../NetPacks.h"
|
||||
#include "../spells/CSpellHandler.h"
|
||||
#include "../mapObjects/CGTownInstance.h"
|
||||
@@ -218,11 +219,14 @@ std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsFor
|
||||
{
|
||||
if(stack->canCast()) //TODO: check for battlefield effects that prevent casting?
|
||||
{
|
||||
if(stack->hasBonusOfType(Bonus::SPELLCASTER) && data.creatureSpellToCast != -1)
|
||||
if(stack->hasBonusOfType(Bonus::SPELLCASTER))
|
||||
{
|
||||
const CSpell *spell = SpellID(data.creatureSpellToCast).toSpell();
|
||||
PossiblePlayerBattleAction act = getCasterAction(spell, stack, spells::Mode::CREATURE_ACTIVE);
|
||||
allowedActionList.push_back(act);
|
||||
for (auto const & spellID : data.creatureSpellsToCast)
|
||||
{
|
||||
const CSpell *spell = spellID.toSpell();
|
||||
PossiblePlayerBattleAction act = getCasterAction(spell, stack, spells::Mode::CREATURE_ACTIVE);
|
||||
allowedActionList.push_back(act);
|
||||
}
|
||||
}
|
||||
if(stack->hasBonusOfType(Bonus::RANDOM_SPELLCASTER))
|
||||
allowedActionList.push_back(PossiblePlayerBattleAction::RANDOM_GENIE_SPELL);
|
||||
@@ -251,7 +255,7 @@ std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsFor
|
||||
PossiblePlayerBattleAction CBattleInfoCallback::getCasterAction(const CSpell * spell, const spells::Caster * caster, spells::Mode mode) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(PossiblePlayerBattleAction::INVALID);
|
||||
PossiblePlayerBattleAction spellSelMode = PossiblePlayerBattleAction::ANY_LOCATION;
|
||||
auto spellSelMode = PossiblePlayerBattleAction::ANY_LOCATION;
|
||||
|
||||
const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell), mode);
|
||||
|
||||
@@ -264,7 +268,7 @@ PossiblePlayerBattleAction CBattleInfoCallback::getCasterAction(const CSpell * s
|
||||
else if(ti.type == spells::AimType::OBSTACLE)
|
||||
spellSelMode = PossiblePlayerBattleAction::OBSTACLE;
|
||||
|
||||
return spellSelMode;
|
||||
return PossiblePlayerBattleAction(spellSelMode, spell->id);
|
||||
}
|
||||
|
||||
std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) const
|
||||
@@ -715,57 +719,64 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHe
|
||||
return false;
|
||||
}
|
||||
|
||||
TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info) const
|
||||
DamageEstimation CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info) const
|
||||
{
|
||||
DamageCalculator calculator(*this, info);
|
||||
|
||||
return calculator.calculateDmgRange();
|
||||
}
|
||||
|
||||
TDmgRange CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, TDmgRange * retaliationDmg) const
|
||||
DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageEstimation * retaliationDmg) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
|
||||
RETURN_IF_NOT_BATTLE({});
|
||||
auto reachability = battleGetDistances(attacker, attacker->getPosition());
|
||||
int movementDistance = reachability[attackerPosition];
|
||||
return battleEstimateDamage(attacker, defender, movementDistance, retaliationDmg);
|
||||
}
|
||||
|
||||
TDmgRange CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, TDmgRange * retaliationDmg) const
|
||||
DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, DamageEstimation * retaliationDmg) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
|
||||
RETURN_IF_NOT_BATTLE({});
|
||||
const bool shooting = battleCanShoot(attacker, defender->getPosition());
|
||||
const BattleAttackInfo bai(attacker, defender, movementDistance, shooting);
|
||||
return battleEstimateDamage(bai, retaliationDmg);
|
||||
}
|
||||
|
||||
TDmgRange CBattleInfoCallback::battleEstimateDamage(const BattleAttackInfo & bai, TDmgRange * retaliationDmg) const
|
||||
DamageEstimation CBattleInfoCallback::battleEstimateDamage(const BattleAttackInfo & bai, DamageEstimation * retaliationDmg) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
|
||||
RETURN_IF_NOT_BATTLE({});
|
||||
|
||||
TDmgRange ret = calculateDmgRange(bai);
|
||||
DamageEstimation ret = calculateDmgRange(bai);
|
||||
|
||||
if(retaliationDmg)
|
||||
{
|
||||
if(bai.shooting)
|
||||
{
|
||||
//FIXME: handle RANGED_RETALIATION
|
||||
retaliationDmg->first = retaliationDmg->second = 0;
|
||||
*retaliationDmg = DamageEstimation();
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO: rewrite using boost::numeric::interval
|
||||
//TODO: rewire once more using interval-based fuzzy arithmetic
|
||||
|
||||
int64_t TDmgRange::* pairElems[] = {&TDmgRange::first, &TDmgRange::second};
|
||||
for (int i=0; i<2; ++i)
|
||||
auto const & estimateRetaliation = [&]( int64_t damage)
|
||||
{
|
||||
auto retaliationAttack = bai.reverse();
|
||||
int64_t dmg = ret.*pairElems[i];
|
||||
auto state = retaliationAttack.attacker->acquireState();
|
||||
state->damage(dmg);
|
||||
state->damage(damage);
|
||||
retaliationAttack.attacker = state.get();
|
||||
retaliationDmg->*pairElems[!i] = calculateDmgRange(retaliationAttack).*pairElems[!i];
|
||||
}
|
||||
return calculateDmgRange(retaliationAttack);
|
||||
};
|
||||
|
||||
DamageEstimation retaliationMin = estimateRetaliation(ret.damage.min);
|
||||
DamageEstimation retaliationMax = estimateRetaliation(ret.damage.min);
|
||||
|
||||
retaliationDmg->damage.min = std::min(retaliationMin.damage.min, retaliationMax.damage.min);
|
||||
retaliationDmg->damage.max = std::max(retaliationMin.damage.max, retaliationMax.damage.max);
|
||||
|
||||
retaliationDmg->kills.min = std::min(retaliationMin.kills.min, retaliationMax.kills.min);
|
||||
retaliationDmg->kills.max = std::max(retaliationMin.kills.max, retaliationMax.kills.max);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -993,12 +1004,19 @@ std::set<BattleHex> CBattleInfoCallback::getStoppers(BattlePerspective::BattlePe
|
||||
|
||||
for(auto &oi : battleGetAllObstacles(whichSidePerspective))
|
||||
{
|
||||
if(battleIsObstacleVisibleForSide(*oi, whichSidePerspective))
|
||||
if(!battleIsObstacleVisibleForSide(*oi, whichSidePerspective))
|
||||
continue;
|
||||
|
||||
for(const auto & hex : oi->getStoppingTile())
|
||||
{
|
||||
range::copy(oi->getStoppingTile(), vstd::set_inserter(ret));
|
||||
if(hex == ESiegeHex::GATE_BRIDGE && oi->obstacleType == CObstacleInstance::MOAT)
|
||||
{
|
||||
if(battleGetGateState() == EGateState::OPENED || battleGetGateState() == EGateState::DESTROYED)
|
||||
continue; // this tile is disabled by drawbridge on top of it
|
||||
}
|
||||
ret.insert(hex);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1646,12 +1664,16 @@ SpellID CBattleInfoCallback::getRandomCastedSpell(CRandomGenerator & rand,const
|
||||
int totalWeight = 0;
|
||||
for(const auto & b : *bl)
|
||||
{
|
||||
totalWeight += std::max(b->additionalInfo[0], 1); //minimal chance to cast is 1
|
||||
totalWeight += std::max(b->additionalInfo[0], 0); //spells with 0 weight are non-random, exclude them
|
||||
}
|
||||
|
||||
if (totalWeight == 0)
|
||||
return SpellID::NONE;
|
||||
|
||||
int randomPos = rand.nextInt(totalWeight - 1);
|
||||
for(const auto & b : *bl)
|
||||
{
|
||||
randomPos -= std::max(b->additionalInfo[0], 1);
|
||||
randomPos -= std::max(b->additionalInfo[0], 0);
|
||||
if(randomPos < 0)
|
||||
{
|
||||
return SpellID(b->subtype);
|
||||
|
@@ -24,6 +24,7 @@ class CSpell;
|
||||
struct CObstacleInstance;
|
||||
class IBonusBearer;
|
||||
class CRandomGenerator;
|
||||
class PossiblePlayerBattleAction;
|
||||
|
||||
namespace spells
|
||||
{
|
||||
@@ -42,35 +43,9 @@ struct DLL_LINKAGE AttackableTiles
|
||||
}
|
||||
};
|
||||
|
||||
enum class PossiblePlayerBattleAction // actions performed at l-click
|
||||
{
|
||||
INVALID = -1,
|
||||
CREATURE_INFO,
|
||||
HERO_INFO,
|
||||
MOVE_TACTICS,
|
||||
CHOOSE_TACTICS_STACK,
|
||||
|
||||
MOVE_STACK,
|
||||
ATTACK,
|
||||
WALK_AND_ATTACK,
|
||||
ATTACK_AND_RETURN,
|
||||
SHOOT,
|
||||
CATAPULT,
|
||||
HEAL,
|
||||
|
||||
NO_LOCATION, // massive spells that affect every possible target, automatic casts
|
||||
ANY_LOCATION,
|
||||
OBSTACLE,
|
||||
TELEPORT,
|
||||
SACRIFICE,
|
||||
RANDOM_GENIE_SPELL, // random spell on a friendly creature
|
||||
FREE_LOCATION, // used with Force Field and Fire Wall - all tiles affected by spell must be free
|
||||
AIMED_SPELL_CREATURE, // spell targeted at creature
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE BattleClientInterfaceData
|
||||
{
|
||||
si32 creatureSpellToCast;
|
||||
std::vector<SpellID> creatureSpellsToCast;
|
||||
ui8 tacticsMode;
|
||||
};
|
||||
|
||||
@@ -117,14 +92,14 @@ public:
|
||||
bool battleIsUnitBlocked(const battle::Unit * unit) const; //returns true if there is neighboring enemy stack
|
||||
std::set<const battle::Unit *> battleAdjacentUnits(const battle::Unit * unit) const;
|
||||
|
||||
TDmgRange calculateDmgRange(const BattleAttackInfo & info) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
|
||||
DamageEstimation calculateDmgRange(const BattleAttackInfo & info) const;
|
||||
|
||||
/// estimates damage dealt by attacker to defender;
|
||||
/// only non-random bonuses are considered in estimation
|
||||
/// returns pair <min dmg, max dmg>
|
||||
TDmgRange battleEstimateDamage(const BattleAttackInfo & bai, TDmgRange * retaliationDmg = nullptr) const;
|
||||
TDmgRange battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, TDmgRange * retaliationDmg = nullptr) const;
|
||||
TDmgRange battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, TDmgRange * retaliationDmg = nullptr) const;
|
||||
DamageEstimation battleEstimateDamage(const BattleAttackInfo & bai, DamageEstimation * retaliationDmg = nullptr) const;
|
||||
DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageEstimation * retaliationDmg = nullptr) const;
|
||||
DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, DamageEstimation * retaliationDmg = nullptr) const;
|
||||
|
||||
bool battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const;
|
||||
bool battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const;
|
||||
|
@@ -112,7 +112,10 @@ public:
|
||||
int32_t getFirstHPleft() const;
|
||||
int32_t getResurrected() const;
|
||||
|
||||
/// returns total remaining health
|
||||
int64_t available() const;
|
||||
|
||||
/// returns total initial health
|
||||
int64_t total() const;
|
||||
|
||||
void takeResurrected();
|
||||
|
@@ -20,10 +20,10 @@
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
TDmgRange DamageCalculator::getBaseDamageSingle() const
|
||||
DamageRange DamageCalculator::getBaseDamageSingle() const
|
||||
{
|
||||
double minDmg = 0.0;
|
||||
double maxDmg = 0.0;
|
||||
int64_t minDmg = 0.0;
|
||||
int64_t maxDmg = 0.0;
|
||||
|
||||
minDmg = info.attacker->getMinDamage(info.shooting);
|
||||
maxDmg = info.attacker->getMaxDamage(info.shooting);
|
||||
@@ -63,7 +63,7 @@ TDmgRange DamageCalculator::getBaseDamageSingle() const
|
||||
return { minDmg, maxDmg };
|
||||
}
|
||||
|
||||
TDmgRange DamageCalculator::getBaseDamageBlessCurse() const
|
||||
DamageRange DamageCalculator::getBaseDamageBlessCurse() const
|
||||
{
|
||||
const std::string cachingStrForcedMinDamage = "type_ALWAYS_MINIMUM_DAMAGE";
|
||||
static const auto selectorForcedMinDamage = Selector::type()(Bonus::ALWAYS_MINIMUM_DAMAGE);
|
||||
@@ -76,10 +76,10 @@ TDmgRange DamageCalculator::getBaseDamageBlessCurse() const
|
||||
|
||||
int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue();
|
||||
|
||||
TDmgRange baseDamage = getBaseDamageSingle();
|
||||
TDmgRange modifiedDamage = {
|
||||
std::max(static_cast<int64_t>(1), baseDamage.first + curseBlessAdditiveModifier),
|
||||
std::max(static_cast<int64_t>(1), baseDamage.second + curseBlessAdditiveModifier)
|
||||
DamageRange baseDamage = getBaseDamageSingle();
|
||||
DamageRange modifiedDamage = {
|
||||
std::max(static_cast<int64_t>(1), baseDamage.min + curseBlessAdditiveModifier),
|
||||
std::max(static_cast<int64_t>(1), baseDamage.max + curseBlessAdditiveModifier)
|
||||
};
|
||||
|
||||
if(curseEffects->size() && blessEffects->size() )
|
||||
@@ -91,29 +91,29 @@ TDmgRange DamageCalculator::getBaseDamageBlessCurse() const
|
||||
if(curseEffects->size())
|
||||
{
|
||||
return {
|
||||
modifiedDamage.first,
|
||||
modifiedDamage.first
|
||||
modifiedDamage.min,
|
||||
modifiedDamage.min
|
||||
};
|
||||
}
|
||||
|
||||
if(blessEffects->size())
|
||||
{
|
||||
return {
|
||||
modifiedDamage.second,
|
||||
modifiedDamage.second
|
||||
modifiedDamage.max,
|
||||
modifiedDamage.max
|
||||
};
|
||||
}
|
||||
|
||||
return modifiedDamage;
|
||||
}
|
||||
|
||||
TDmgRange DamageCalculator::getBaseDamageStack() const
|
||||
DamageRange DamageCalculator::getBaseDamageStack() const
|
||||
{
|
||||
auto stackSize = info.attacker->getCount();
|
||||
auto baseDamage = getBaseDamageBlessCurse();
|
||||
return {
|
||||
baseDamage.first * stackSize,
|
||||
baseDamage.second * stackSize
|
||||
baseDamage.min * stackSize,
|
||||
baseDamage.max * stackSize
|
||||
};
|
||||
}
|
||||
|
||||
@@ -450,6 +450,25 @@ std::vector<double> DamageCalculator::getDefenseFactors() const
|
||||
};
|
||||
}
|
||||
|
||||
DamageRange DamageCalculator::getCasualties(const DamageRange & damageDealt) const
|
||||
{
|
||||
return {
|
||||
getCasualties(damageDealt.min),
|
||||
getCasualties(damageDealt.max),
|
||||
};
|
||||
}
|
||||
|
||||
int64_t DamageCalculator::getCasualties(int64_t damageDealt) const
|
||||
{
|
||||
if (damageDealt < info.defender->getFirstHPleft())
|
||||
return 0;
|
||||
|
||||
int64_t damageLeft = damageDealt - info.defender->getFirstHPleft();
|
||||
int64_t killsLeft = damageLeft / info.defender->MaxHealth();
|
||||
|
||||
return 1 + killsLeft;
|
||||
}
|
||||
|
||||
int DamageCalculator::battleBonusValue(const IBonusBearer * bearer, const CSelector & selector) const
|
||||
{
|
||||
auto noLimit = Selector::effectRange()(Bonus::NO_LIMIT);
|
||||
@@ -461,9 +480,9 @@ int DamageCalculator::battleBonusValue(const IBonusBearer * bearer, const CSelec
|
||||
return bearer->getBonuses(selector, noLimit.Or(limitMatches))->totalValue();
|
||||
};
|
||||
|
||||
TDmgRange DamageCalculator::calculateDmgRange() const
|
||||
DamageEstimation DamageCalculator::calculateDmgRange() const
|
||||
{
|
||||
TDmgRange result = getBaseDamageStack();
|
||||
DamageRange damageBase = getBaseDamageStack();
|
||||
|
||||
auto attackFactors = getAttackFactors();
|
||||
auto defenseFactors = getDefenseFactors();
|
||||
@@ -485,10 +504,16 @@ TDmgRange DamageCalculator::calculateDmgRange() const
|
||||
|
||||
double resultingFactor = std::min(8.0, attackFactorTotal) * std::max( 0.01, defenseFactorTotal);
|
||||
|
||||
return {
|
||||
std::max( 1.0, std::floor(result.first * resultingFactor)),
|
||||
std::max( 1.0, std::floor(result.second * resultingFactor))
|
||||
info.defender->getTotalHealth();
|
||||
|
||||
DamageRange damageDealt {
|
||||
std::max<int64_t>( 1.0, std::floor(damageBase.min * resultingFactor)),
|
||||
std::max<int64_t>( 1.0, std::floor(damageBase.max * resultingFactor))
|
||||
};
|
||||
|
||||
DamageRange killsDealt = getCasualties(damageDealt);
|
||||
|
||||
return DamageEstimation{damageDealt, killsDealt};
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@@ -18,6 +18,8 @@ class CBattleInfoCallback;
|
||||
class IBonusBearer;
|
||||
class CSelector;
|
||||
struct BattleAttackInfo;
|
||||
struct DamageRange;
|
||||
struct DamageEstimation;
|
||||
|
||||
class DLL_LINKAGE DamageCalculator
|
||||
{
|
||||
@@ -26,9 +28,12 @@ class DLL_LINKAGE DamageCalculator
|
||||
|
||||
int battleBonusValue(const IBonusBearer * bearer, const CSelector & selector) const;
|
||||
|
||||
TDmgRange getBaseDamageSingle() const;
|
||||
TDmgRange getBaseDamageBlessCurse() const;
|
||||
TDmgRange getBaseDamageStack() const;
|
||||
DamageRange getCasualties(const DamageRange & damageDealt) const;
|
||||
int64_t getCasualties(int64_t damageDealt) const;
|
||||
|
||||
DamageRange getBaseDamageSingle() const;
|
||||
DamageRange getBaseDamageBlessCurse() const;
|
||||
DamageRange getBaseDamageStack() const;
|
||||
|
||||
int getActorAttackBase() const;
|
||||
int getActorAttackEffective() const;
|
||||
@@ -66,7 +71,7 @@ public:
|
||||
info(info)
|
||||
{}
|
||||
|
||||
TDmgRange calculateDmgRange() const;
|
||||
DamageEstimation calculateDmgRange() const;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@@ -26,6 +26,18 @@ namespace battle
|
||||
using UnitFilter = std::function<bool(const Unit *)>;
|
||||
}
|
||||
|
||||
struct DamageRange
|
||||
{
|
||||
int64_t min = 0;
|
||||
int64_t max = 0;
|
||||
};
|
||||
|
||||
struct DamageEstimation
|
||||
{
|
||||
DamageRange damage;
|
||||
DamageRange kills;
|
||||
};
|
||||
|
||||
#if SCRIPTING_ENABLED
|
||||
namespace scripting
|
||||
{
|
||||
|
@@ -66,7 +66,7 @@ public:
|
||||
|
||||
virtual uint32_t nextUnitId() const = 0;
|
||||
|
||||
virtual int64_t getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const = 0;
|
||||
virtual int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const = 0;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE IBattleState : public IBattleInfo
|
||||
|
78
lib/battle/PossiblePlayerBattleAction.h
Normal file
78
lib/battle/PossiblePlayerBattleAction.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* CBattleInfoCallback.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#include "../GameConstants.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class PossiblePlayerBattleAction // actions performed at l-click
|
||||
{
|
||||
public:
|
||||
enum Actions {
|
||||
INVALID = -1,
|
||||
CREATURE_INFO,
|
||||
HERO_INFO,
|
||||
MOVE_TACTICS,
|
||||
CHOOSE_TACTICS_STACK,
|
||||
|
||||
MOVE_STACK,
|
||||
ATTACK,
|
||||
WALK_AND_ATTACK,
|
||||
ATTACK_AND_RETURN,
|
||||
SHOOT,
|
||||
CATAPULT,
|
||||
HEAL,
|
||||
|
||||
RANDOM_GENIE_SPELL, // random spell on a friendly creature
|
||||
|
||||
NO_LOCATION, // massive spells that affect every possible target, automatic casts
|
||||
ANY_LOCATION,
|
||||
OBSTACLE,
|
||||
TELEPORT,
|
||||
SACRIFICE,
|
||||
FREE_LOCATION, // used with Force Field and Fire Wall - all tiles affected by spell must be free
|
||||
AIMED_SPELL_CREATURE, // spell targeted at creature
|
||||
};
|
||||
|
||||
private:
|
||||
Actions action;
|
||||
SpellID spellToCast;
|
||||
|
||||
public:
|
||||
bool spellcast() const
|
||||
{
|
||||
return action == ANY_LOCATION || action == NO_LOCATION || action == OBSTACLE || action == TELEPORT ||
|
||||
action == SACRIFICE || action == FREE_LOCATION || action == AIMED_SPELL_CREATURE;
|
||||
}
|
||||
|
||||
Actions get() const
|
||||
{
|
||||
return action;
|
||||
}
|
||||
|
||||
SpellID spell() const
|
||||
{
|
||||
return spellToCast;
|
||||
}
|
||||
|
||||
PossiblePlayerBattleAction(Actions action, SpellID spellToCast = SpellID::NONE):
|
||||
action(static_cast<Actions>(action)),
|
||||
spellToCast(spellToCast)
|
||||
{
|
||||
assert((spellToCast != SpellID::NONE) == spellcast());
|
||||
}
|
||||
|
||||
bool operator == (const PossiblePlayerBattleAction & other) const
|
||||
{
|
||||
return action == other.action && spellToCast == other.spellToCast;
|
||||
}
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@@ -70,10 +70,19 @@ public:
|
||||
virtual bool canShoot() const = 0;
|
||||
virtual bool isShooter() const = 0;
|
||||
|
||||
/// returns initial size of this unit
|
||||
virtual int32_t getCount() const = 0;
|
||||
|
||||
/// returns remaining health of first unit
|
||||
virtual int32_t getFirstHPleft() const = 0;
|
||||
|
||||
/// returns total amount of killed in this unit
|
||||
virtual int32_t getKilled() const = 0;
|
||||
|
||||
/// returns total health that unit still has
|
||||
virtual int64_t getAvailableHealth() const = 0;
|
||||
|
||||
/// returns total health that unit had initially
|
||||
virtual int64_t getTotalHealth() const = 0;
|
||||
|
||||
virtual int getTotalAttacks(bool ranged) const = 0;
|
||||
|
@@ -12,7 +12,7 @@
|
||||
#include "CGTownInstance.h"
|
||||
#include "CObjectClassesHandler.h"
|
||||
#include "../spells/CSpellHandler.h"
|
||||
|
||||
#include "../battle/IBattleInfoCallback.h"
|
||||
#include "../NetPacks.h"
|
||||
#include "../CConfigHandler.h"
|
||||
#include "../CGeneralTextHandler.h"
|
||||
@@ -807,7 +807,7 @@ void CGTownInstance::addTownBonuses()
|
||||
}
|
||||
}
|
||||
|
||||
TDmgRange CGTownInstance::getTowerDamageRange() const
|
||||
DamageRange CGTownInstance::getTowerDamageRange() const
|
||||
{
|
||||
assert(hasBuilt(BuildingID::CASTLE));
|
||||
|
||||
@@ -825,7 +825,7 @@ TDmgRange CGTownInstance::getTowerDamageRange() const
|
||||
};
|
||||
}
|
||||
|
||||
TDmgRange CGTownInstance::getKeepDamageRange() const
|
||||
DamageRange CGTownInstance::getKeepDamageRange() const
|
||||
{
|
||||
assert(hasBuilt(BuildingID::CITADEL));
|
||||
|
||||
|
@@ -20,6 +20,7 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
class CCastleEvent;
|
||||
class CGTownInstance;
|
||||
class CGDwelling;
|
||||
struct DamageRange;
|
||||
|
||||
class DLL_LINKAGE CSpecObjInfo
|
||||
{
|
||||
@@ -332,10 +333,10 @@ public:
|
||||
void deleteTownBonus(BuildingID::EBuildingID bid);
|
||||
|
||||
/// Returns damage range for secondary towers of this town
|
||||
TDmgRange getTowerDamageRange() const;
|
||||
DamageRange getTowerDamageRange() const;
|
||||
|
||||
/// Returns damage range for central tower(keep) of this town
|
||||
TDmgRange getKeepDamageRange() const;
|
||||
DamageRange getKeepDamageRange() const;
|
||||
|
||||
const CTown * getTown() const ;
|
||||
|
||||
|
@@ -399,12 +399,43 @@ void CObjectClassesHandler::afterLoadFinalization()
|
||||
}
|
||||
}
|
||||
|
||||
generateExtraMonolithsForRMG();
|
||||
}
|
||||
|
||||
void CObjectClassesHandler::generateExtraMonolithsForRMG()
|
||||
{
|
||||
//duplicate existing two-way portals to make reserve for RMG
|
||||
auto& portalVec = objects[Obj::MONOLITH_TWO_WAY]->objects;
|
||||
size_t portalCount = portalVec.size();
|
||||
//FIXME: Monoliths in this vector can be already not useful for every terrain
|
||||
const size_t portalCount = portalVec.size();
|
||||
|
||||
for (size_t i = portalCount; i < 100; ++i)
|
||||
portalVec.push_back(portalVec[static_cast<si32>(i % portalCount)]);
|
||||
//Invalid portals will be skipped and portalVec size stays unchanged
|
||||
for (size_t i = portalCount; portalVec.size() < 100; ++i)
|
||||
{
|
||||
auto index = static_cast<si32>(i % portalCount);
|
||||
auto portal = portalVec[index];
|
||||
auto templates = portal->getTemplates();
|
||||
if (templates.empty() || !templates[0]->canBePlacedAtAnyTerrain())
|
||||
{
|
||||
continue; //Do not clone HoTA water-only portals or any others we can't use
|
||||
}
|
||||
|
||||
//deep copy of noncopyable object :?
|
||||
auto newPortal = std::make_shared<CDefaultObjectTypeHandler<CGMonolith>>();
|
||||
newPortal->rmgInfo = portal->getRMGInfo();
|
||||
newPortal->base = portal->base; //not needed?
|
||||
newPortal->templates = portal->getTemplates();
|
||||
newPortal->sounds = portal->getSounds();
|
||||
newPortal->aiValue = portal->getAiValue();
|
||||
newPortal->battlefield = portal->battlefield; //getter is not initialized at this point
|
||||
newPortal->modScope = portal->modScope; //private
|
||||
newPortal->typeName = portal->getTypeName();
|
||||
newPortal->subTypeName = std::string("monolith") + std::to_string(portalVec.size());
|
||||
newPortal->type = portal->getIndex();
|
||||
|
||||
newPortal->subtype = portalVec.size(); //indexes must be unique, they are returned as a set
|
||||
portalVec.push_back(newPortal);
|
||||
}
|
||||
}
|
||||
|
||||
std::string CObjectClassesHandler::getObjectName(si32 type, si32 subtype) const
|
||||
|
@@ -292,6 +292,8 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
|
||||
|
||||
ObjectClass * loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index);
|
||||
|
||||
void generateExtraMonolithsForRMG();
|
||||
|
||||
public:
|
||||
CObjectClassesHandler();
|
||||
~CObjectClassesHandler();
|
||||
|
@@ -102,6 +102,11 @@ public:
|
||||
return visitDir & 2;
|
||||
};
|
||||
|
||||
inline bool canBePlacedAtAnyTerrain() const
|
||||
{
|
||||
return anyTerrain;
|
||||
};
|
||||
|
||||
// Checks if object can be placed on specific terrain
|
||||
bool canBePlacedAt(TerrainId terrain) const;
|
||||
|
||||
|
@@ -367,10 +367,24 @@ void CMapGenerator::addHeaderInfo()
|
||||
|
||||
int CMapGenerator::getNextMonlithIndex()
|
||||
{
|
||||
if (monolithIndex >= VLC->objtypeh->knownSubObjects(Obj::MONOLITH_TWO_WAY).size())
|
||||
throw rmgException(boost::to_string(boost::format("There is no Monolith Two Way with index %d available!") % monolithIndex));
|
||||
else
|
||||
return monolithIndex++;
|
||||
while (true)
|
||||
{
|
||||
if (monolithIndex >= VLC->objtypeh->knownSubObjects(Obj::MONOLITH_TWO_WAY).size())
|
||||
throw rmgException(boost::to_string(boost::format("There is no Monolith Two Way with index %d available!") % monolithIndex));
|
||||
else
|
||||
{
|
||||
//Skip modded Monoliths which can't beplaced on every terrain
|
||||
auto templates = VLC->objtypeh->getHandlerFor(Obj::MONOLITH_TWO_WAY, monolithIndex)->getTemplates();
|
||||
if (templates.empty() || !templates[0]->canBePlacedAtAnyTerrain())
|
||||
{
|
||||
monolithIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
return monolithIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int CMapGenerator::getPrisonsRemaning() const
|
||||
|
@@ -100,15 +100,20 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
|
||||
borderPos = *RandomGeneratorUtil::nextItem(directConnectionIterator->second, generator.rand);
|
||||
guardPos = zone.areaPossible().nearest(borderPos);
|
||||
assert(borderPos != guardPos);
|
||||
|
||||
auto safetyGap = rmg::Area({guardPos});
|
||||
safetyGap.unite(safetyGap.getBorderOutside());
|
||||
safetyGap.intersect(zone.areaPossible());
|
||||
if(!safetyGap.empty())
|
||||
|
||||
float dist = map.getTile(guardPos).getNearestObjectDistance();
|
||||
if (dist >= 3) //Don't place guards at adjacent tiles
|
||||
{
|
||||
safetyGap.intersect(otherZone->areaPossible());
|
||||
if(safetyGap.empty())
|
||||
break; //successfull position
|
||||
|
||||
auto safetyGap = rmg::Area({ guardPos });
|
||||
safetyGap.unite(safetyGap.getBorderOutside());
|
||||
safetyGap.intersect(zone.areaPossible());
|
||||
if (!safetyGap.empty())
|
||||
{
|
||||
safetyGap.intersect(otherZone->areaPossible());
|
||||
if (safetyGap.empty())
|
||||
break; //successfull position
|
||||
}
|
||||
}
|
||||
|
||||
//failed position
|
||||
@@ -150,6 +155,8 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
|
||||
rmg::Object monster(*monsterType);
|
||||
monster.setPosition(guardPos);
|
||||
manager.placeObject(monster, false, true);
|
||||
//Place objects away from the monster in the other zone, too
|
||||
otherZone->getModificator<ObjectManager>()->updateDistances(monster);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -225,8 +232,10 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
|
||||
rmg::Path path1 = manager.placeAndConnectObject(commonArea, rmgGate1, [this, minDist, &path2, &rmgGate1, &zShift, guarded2, &managerOther, &rmgGate2 ](const int3 & tile)
|
||||
{
|
||||
auto ti = map.getTile(tile);
|
||||
auto otherTi = map.getTile(tile - zShift);
|
||||
float dist = ti.getNearestObjectDistance();
|
||||
if(dist < minDist)
|
||||
float otherDist = otherTi.getNearestObjectDistance();
|
||||
if(dist < minDist || otherDist < minDist)
|
||||
return -1.f;
|
||||
|
||||
rmg::Area toPlace(rmgGate1.getArea() + rmgGate1.getAccessibleArea());
|
||||
@@ -234,8 +243,8 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
|
||||
|
||||
path2 = managerOther.placeAndConnectObject(toPlace, rmgGate2, minDist, guarded2, true, ObjectManager::OptimizeType::NONE);
|
||||
|
||||
return path2.valid() ? 1.f : -1.f;
|
||||
}, guarded1, true, ObjectManager::OptimizeType::NONE);
|
||||
return path2.valid() ? (dist + otherDist) : -1.f;
|
||||
}, guarded1, true, ObjectManager::OptimizeType::DISTANCE);
|
||||
|
||||
if(path1.valid() && path2.valid())
|
||||
{
|
||||
|
@@ -209,24 +209,28 @@ bool WaterProxy::placeBoat(Zone & land, const Lake & lake, RouteInfo & info)
|
||||
auto * manager = zone.getModificator<ObjectManager>();
|
||||
if(!manager)
|
||||
return false;
|
||||
|
||||
|
||||
auto subObjects = VLC->objtypeh->knownSubObjects(Obj::BOAT);
|
||||
auto * boat = dynamic_cast<CGBoat *>(VLC->objtypeh->getHandlerFor(Obj::BOAT, *RandomGeneratorUtil::nextItem(subObjects, generator.rand))->create());
|
||||
|
||||
rmg::Object rmgObject(*boat);
|
||||
rmgObject.setTemplate(zone.getTerrainType());
|
||||
|
||||
|
||||
auto waterAvailable = zone.areaPossible() + zone.freePaths();
|
||||
rmg::Area coast = lake.neighbourZones.at(land.getId()); //having land tiles
|
||||
coast.intersect(land.areaPossible() + land.freePaths()); //having only available land tiles
|
||||
auto boardingPositions = coast.getSubarea([&waterAvailable](const int3 & tile) //tiles where boarding is possible
|
||||
{
|
||||
rmg::Area a({tile});
|
||||
a = a.getBorderOutside();
|
||||
a.intersect(waterAvailable);
|
||||
return !a.empty();
|
||||
});
|
||||
|
||||
auto boardingPositions = coast.getSubarea([&waterAvailable, this](const int3 & tile) //tiles where boarding is possible
|
||||
{
|
||||
//We don't want place boat right to any land object, especiallly the zone guard
|
||||
if (map.getTile(tile).getNearestObjectDistance() <= 3)
|
||||
return false;
|
||||
|
||||
rmg::Area a({tile});
|
||||
a = a.getBorderOutside();
|
||||
a.intersect(waterAvailable);
|
||||
return !a.empty();
|
||||
});
|
||||
|
||||
while(!boardingPositions.empty())
|
||||
{
|
||||
auto boardingPosition = *boardingPositions.getTiles().begin();
|
||||
@@ -239,27 +243,28 @@ bool WaterProxy::placeBoat(Zone & land, const Lake & lake, RouteInfo & info)
|
||||
boardingPositions.erase(boardingPosition);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
//try to place boat at water, create paths on water and land
|
||||
auto path = manager->placeAndConnectObject(shipPositions, rmgObject, 2, false, true, ObjectManager::OptimizeType::NONE);
|
||||
auto path = manager->placeAndConnectObject(shipPositions, rmgObject, 4, false, true, ObjectManager::OptimizeType::NONE);
|
||||
auto landPath = land.searchPath(boardingPosition, false);
|
||||
if(!path.valid() || !landPath.valid())
|
||||
{
|
||||
boardingPositions.erase(boardingPosition);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
info.blocked = rmgObject.getArea();
|
||||
info.visitable = rmgObject.getVisitablePosition();
|
||||
info.boarding = boardingPosition;
|
||||
info.water = shipPositions;
|
||||
|
||||
|
||||
zone.connectPath(path);
|
||||
land.connectPath(landPath);
|
||||
manager->placeObject(rmgObject, false, true);
|
||||
land.getModificator<ObjectManager>()->updateDistances(rmgObject); //Keep land objects away from the boat
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return !boardingPositions.empty();
|
||||
}
|
||||
|
||||
|
@@ -1233,7 +1233,7 @@ int64_t CGameHandler::applyBattleEffects(BattleAttack & bat, std::shared_ptr<bat
|
||||
bai.unluckyStrike = bat.unlucky();
|
||||
|
||||
auto range = gs->curB->calculateDmgRange(bai);
|
||||
bsa.damageAmount = gs->curB->getActualDamage(range, attackerState->getCount(), getRandomGenerator());
|
||||
bsa.damageAmount = gs->curB->getActualDamage(range.damage, attackerState->getCount(), getRandomGenerator());
|
||||
CStack::prepareAttacked(bsa, getRandomGenerator(), bai.defender->acquireState()); //calculate casualties
|
||||
}
|
||||
|
||||
@@ -4910,8 +4910,8 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
|
||||
const CStack * stack = gs->curB->battleGetStackByID(ba.stackNumber);
|
||||
SpellID spellID = SpellID(ba.actionSubtype);
|
||||
|
||||
std::shared_ptr<const Bonus> randSpellcaster = stack->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER));
|
||||
std::shared_ptr<const Bonus> spellcaster = stack->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPELLCASTER, spellID));
|
||||
std::shared_ptr<const Bonus> randSpellcaster = stack->getBonus(Selector::type()(Bonus::RANDOM_SPELLCASTER));
|
||||
std::shared_ptr<const Bonus> spellcaster = stack->getBonus(Selector::typeSubtype(Bonus::SPELLCASTER, spellID));
|
||||
|
||||
//TODO special bonus for genies ability
|
||||
if (randSpellcaster && battleGetRandomStackSpell(getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_AIMED) < 0)
|
||||
|
Reference in New Issue
Block a user