1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

Merge pull request #1776 from vcmi/beta

Merge beta -> develop
This commit is contained in:
Ivan Savenko
2023-03-26 01:48:23 +02:00
committed by GitHub
74 changed files with 1265 additions and 489 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View 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个部队"
}

View File

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

View File

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

View File

@@ -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 Показує істот, яких можна придбати, замість їхнього приросту у зведенні по місту (нижній лівий кут).",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -192,7 +192,7 @@ void CGuiHandler::updateTime()
for (auto & elem : hlp)
{
if(!vstd::contains(timeinterested,elem)) continue;
(elem)->onTimer(ms);
(elem)->tick(ms);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -229,5 +229,6 @@ IMapObjectObserver::IMapObjectObserver()
IMapObjectObserver::~IMapObjectObserver()
{
CGI->mh->removeMapObserver(this);
if (CGI && CGI->mh)
CGI->mh->removeMapObserver(this);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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" : {

View File

@@ -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 &quot;In The Wake of Gods&quot;: fan-made Heroes III expansion</source>
<translation type="unfinished"></translation>
<translation>Zainstaluj kompatybilną wersję fanowskiego dodatku &quot;In The Wake Of Gods&quot;</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&apos;t have installed Heroes III copy, it is possible to use our automatic installation tool &apos;vcmibuilder&apos; 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 &apos;vcmibuilder&apos; 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"/>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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