2
0
Эх сурвалжийг харах

Merge pull request #1771 from IvanSavenko/beta_fixes

Fixes for issues discovered in beta
Ivan Savenko 2 жил өмнө
parent
commit
6a3f4fd73d

+ 289 - 0
Mods/vcmi/config/vcmi/chinese.json

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

+ 10 - 0
Mods/vcmi/mod.json

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

+ 7 - 1
client/adventureMap/MapAudioPlayer.cpp

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

+ 4 - 0
client/adventureMap/MapAudioPlayer.h

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

+ 5 - 0
client/battle/BattleActionsController.cpp

@@ -436,6 +436,9 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
 			{
 				BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
 				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());
 			}
 
@@ -444,6 +447,8 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
 			const auto * shooter = owner.stacksController->getActiveStack();
 
 			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());
 		}

+ 23 - 20
client/battle/BattleInterface.cpp

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

+ 2 - 2
client/battle/BattleInterface.h

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

+ 13 - 11
client/battle/BattleStacksController.cpp

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

+ 9 - 8
client/battle/CreatureAnimation.cpp

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

+ 11 - 11
client/mapView/IMapRendererObserver.h

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

+ 4 - 3
client/render/IImage.h

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

+ 13 - 4
client/renderSDL/SDLImage.cpp

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

+ 2 - 2
client/renderSDL/SDLImage.h

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

+ 2 - 1
client/windows/CCastleInterface.cpp

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

+ 3 - 0
client/windows/CTradeWindow.cpp

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

+ 1 - 0
client/windows/CWindowObject.cpp

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

+ 2 - 2
config/battleEffects.json

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

+ 2 - 2
config/schemas/settings.json

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

+ 1 - 1
lib/CModHandler.cpp

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

+ 2 - 1
lib/GameConstants.h

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

+ 10 - 3
lib/battle/CBattleInfoCallback.cpp

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