diff --git a/Mods/vcmi/config/vcmi/chinese.json b/Mods/vcmi/config/vcmi/chinese.json index 3135bd2c8..3d48a4f41 100644 --- a/Mods/vcmi/config/vcmi/chinese.json +++ b/Mods/vcmi/config/vcmi/chinese.json @@ -118,17 +118,17 @@ // 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级", + "vcmi.stackExperience.rank.0" : "新兵 1级", + "vcmi.stackExperience.rank.1" : "列兵 2级", + "vcmi.stackExperience.rank.2" : "下士 3级", + "vcmi.stackExperience.rank.3" : "中士 4级", + "vcmi.stackExperience.rank.4" : "上士 5级", + "vcmi.stackExperience.rank.5" : "少尉 6级", + "vcmi.stackExperience.rank.6" : "中尉 7级", + "vcmi.stackExperience.rank.7" : "上尉 8级", + "vcmi.stackExperience.rank.8" : "少校 9级", + "vcmi.stackExperience.rank.9" : "中校 10级", + "vcmi.stackExperience.rank.10" : "上校 11级", "core.bonus.ADDITIONAL_ATTACK.name": "双击", "core.bonus.ADDITIONAL_ATTACK.description": "可以攻击两次", @@ -244,10 +244,6 @@ "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": "范围远程攻击", diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 68b0c81ee..f60407b3d 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -1,5 +1,5 @@ { - "vcmi.adventureMap.monsterThreat.title" : "\n\n Threat: ", + "vcmi.adventureMap.monsterThreat.title" : "\n\nThreat: ", "vcmi.adventureMap.monsterThreat.levels.0" : "Effortless", "vcmi.adventureMap.monsterThreat.levels.1" : "Very Weak", "vcmi.adventureMap.monsterThreat.levels.2" : "Weak", @@ -13,24 +13,24 @@ "vcmi.adventureMap.monsterThreat.levels.10" : "Deadly", "vcmi.adventureMap.monsterThreat.levels.11" : "Impossible", - "vcmi.adventureMap.confirmRestartGame" : "Are you sure you want to restart game?", - "vcmi.adventureMap.noTownWithMarket" : "No available marketplace!", - "vcmi.adventureMap.noTownWithTavern" : "No available town with tavern!", - "vcmi.adventureMap.spellUnknownProblem" : "Unknown problem with this spell, no more information available.", + "vcmi.adventureMap.confirmRestartGame" : "Are you sure you want to restart the game?", + "vcmi.adventureMap.noTownWithMarket" : "There are no available marketplaces!", + "vcmi.adventureMap.noTownWithTavern" : "There are no available towns with taverns!", + "vcmi.adventureMap.spellUnknownProblem" : "There is an unknown problem with this spell! No more information is available.", "vcmi.adventureMap.playerAttacked" : "Player has been attacked: %s", "vcmi.adventureMap.moveCostDetails" : "Movement points - Cost: %TURNS turns + %POINTS points, Remaining points: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "Movement points - Cost: %POINTS points, Remaining points: %REMAINING", - "vcmi.server.errors.existingProcess" : "Another vcmiserver process is running, please terminate it first", - "vcmi.server.errors.modsIncompatibility" : "Required mods to load game:", - "vcmi.server.confirmReconnect" : "Connect to the last session?", + "vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.", + "vcmi.server.errors.modsIncompatibility" : "The following mods are required to load the game:", + "vcmi.server.confirmReconnect" : "Do you want to reconnect to the last session?", "vcmi.settingsMainWindow.generalTab.hover" : "General", "vcmi.settingsMainWindow.generalTab.help" : "Switches to General Options tab, which contains settings related to general game client behavior", "vcmi.settingsMainWindow.battleTab.hover" : "Battle", "vcmi.settingsMainWindow.battleTab.help" : "Switches to Battle Options tab, which allows configuring game behavior during battles", "vcmi.settingsMainWindow.adventureTab.hover" : "Adventure Map", - "vcmi.settingsMainWindow.adventureTab.help" : "Switches to Adventure Map Options tab - adventure map is part of the game where you can move your heroes", + "vcmi.settingsMainWindow.adventureTab.help" : "Switches to Adventure Map Options tab (adventure map is the section of the game where players can control the movements of their heroes)", "vcmi.systemOptions.videoGroup" : "Video Settings", "vcmi.systemOptions.audioGroup" : "Audio Settings", @@ -38,51 +38,50 @@ "vcmi.systemOptions.townsGroup" : "Town Screen", "vcmi.systemOptions.fullscreenButton.hover" : "Fullscreen", - "vcmi.systemOptions.fullscreenButton.help" : "{Fullscreen}\n\n If selected, VCMI will run in fullscreen mode, otherwise VCMI will run in window", + "vcmi.systemOptions.fullscreenButton.help" : "{Fullscreen}\n\nIf selected, VCMI will run in fullscreen mode, otherwise it will run in windowed mode", "vcmi.systemOptions.resolutionButton.hover" : "Resolution: %wx%h", - "vcmi.systemOptions.resolutionButton.help" : "{Select Resolution}\n\n Change in-game screen resolution. Game restart required to apply new resolution.", + "vcmi.systemOptions.resolutionButton.help" : "{Select Resolution}\n\nChange in-game screen resolution. A game restart is required to apply the new resolution.", "vcmi.systemOptions.resolutionMenu.hover" : "Select Resolution", "vcmi.systemOptions.resolutionMenu.help" : "Change in-game screen resolution.", - "vcmi.systemOptions.fullscreenFailed" : "{Fullscreen}\n\n Failed to switch to fullscreen mode! Current resolution is not supported by display!", + "vcmi.systemOptions.fullscreenFailed" : "{Fullscreen}\n\nFailed to switch to fullscreen mode! The current resolution is not supported by the display!", "vcmi.systemOptions.framerateButton.hover" : "Show FPS", - "vcmi.systemOptions.framerateButton.help" : "{Show FPS}\n\n Toggles visibility of Frames Per Second counter in corner of game window.", + "vcmi.systemOptions.framerateButton.help" : "{Show FPS}\n\nToggle the visibility of the Frames Per Second counter in the corner of the game window", "vcmi.adventureOptions.infoBarPick.hover" : "Show Messages in Info Panel", - "vcmi.adventureOptions.infoBarPick.help" : "{Show Messages in Info Panel}\n\nWhenever possible, game messages from visiting map objects will be shown in info bar instead of showing up as popup windows", + "vcmi.adventureOptions.infoBarPick.help" : "{Show Messages in Info Panel}\n\nWhenever possible, game messages from visiting map objects will be shown in the info pannel, instead of popping up in a separate window.", "vcmi.adventureOptions.numericQuantities.hover" : "Numeric Creature Quantities", - "vcmi.adventureOptions.numericQuantities.help" : "{Numeric Creature Quantities}\n\n Shows approximate enemy creatures quantities in numeric A-B format.", + "vcmi.adventureOptions.numericQuantities.help" : "{Numeric Creature Quantities}\n\nShow the approximate quantities of enemy creatures in the numeric A-B format.", "vcmi.adventureOptions.forceMovementInfo.hover" : "Always Show Movement Cost", - "vcmi.adventureOptions.forceMovementInfo.help" : "{Always Show Movement Cost}\n\n Replaces default status bar info with movement points data without need to hold ALT button.", + "vcmi.adventureOptions.forceMovementInfo.help" : "{Always Show Movement Cost}\n\nAlways show movement points data in status bar information. (Instead of viewing it only while you hold down ALT key)", "vcmi.adventureOptions.showGrid.hover" : "Show Grid", - "vcmi.adventureOptions.showGrid.help" : "{Show Grid}\n\n Shows grid overlay, showing borders between adventure map tiles.", - "vcmi.adventureOptions.mapSwipe.hover" : "Map Swipe", - "vcmi.adventureOptions.mapSwipe.help" : "{Map Swipe}\n\n Allows map movement via finger swipe gesture on systems with touchscreen. As of right now, can also be accessed via left mouse button.", + "vcmi.adventureOptions.showGrid.help" : "{Show Grid}\n\nShow the grid overlay, highlighting the borders between adventure map tiles.", + "vcmi.adventureOptions.mapSwipe.hover" : "Map Swipe/Panning", + "vcmi.adventureOptions.mapSwipe.help" : "{Map Swipe/Panning}\n\nOn touchscreen devices, you can move the map by swiping with your finger. To pan the map using the mouse, hold down the left or middle mouse button and move the mouse.", "vcmi.adventureOptions.mapScrollSpeed1.hover": "", "vcmi.adventureOptions.mapScrollSpeed5.hover": "", "vcmi.adventureOptions.mapScrollSpeed6.hover": "", - "vcmi.adventureOptions.mapScrollSpeed1.help": "Set map scrolling speed to very slow", - "vcmi.adventureOptions.mapScrollSpeed5.help": "Set map scrolling speed to very fast", - "vcmi.adventureOptions.mapScrollSpeed6.help": "Set map scrolling speed to instantaneous.", + "vcmi.adventureOptions.mapScrollSpeed1.help": "Set the map scrolling speed to very slow", + "vcmi.adventureOptions.mapScrollSpeed5.help": "Set the map scrolling speed to very fast", + "vcmi.adventureOptions.mapScrollSpeed6.help": "Set the map scrolling speed to instantaneous.", - "vcmi.battleOptions.queueSizeLabel.hover": "Show Creature Turn Order", + "vcmi.battleOptions.queueSizeLabel.hover": "Show Turn Order Queue", "vcmi.battleOptions.queueSizeNoneButton.hover": "OFF", "vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO", "vcmi.battleOptions.queueSizeSmallButton.hover": "SMALL", "vcmi.battleOptions.queueSizeBigButton.hover": "BIG", - "vcmi.battleOptions.queueSizeNoneButton.help": "Completely disables visibility of creature turn order in battle", - "vcmi.battleOptions.queueSizeAutoButton.help": "Sets turn order size depending on game resolution (small when playing with screen resolution below 700 pixels high, big otherwise)", - "vcmi.battleOptions.queueSizeSmallButton.help": "Sets turn order size to small", - "vcmi.battleOptions.queueSizeBigButton.help": "Sets turn order size to big (not supported if game resolution is less than 700 pixels high)", + "vcmi.battleOptions.queueSizeNoneButton.help": "Do not display Turn Order Queue", + "vcmi.battleOptions.queueSizeAutoButton.help": "Automatically adjust the size of the turn order queue based on the game's resolution(SMALL size is used when playing the game on a resolution with a height lower than 700 pixels, BIG size is used otherwise)", + "vcmi.battleOptions.queueSizeSmallButton.help": "Sets turn order queue size to SMALL", + "vcmi.battleOptions.queueSizeBigButton.help": "Sets turn order queue size to BIG (not supported if game resolution height is less than 700 pixels)", "vcmi.battleOptions.animationsSpeed1.hover": "", "vcmi.battleOptions.animationsSpeed5.hover": "", "vcmi.battleOptions.animationsSpeed6.hover": "", - "vcmi.battleOptions.animationsSpeed1.help": "Sets animation speed to very slow", - "vcmi.battleOptions.animationsSpeed5.help": "Sets animation speed to very fast", - "vcmi.battleOptions.animationsSpeed6.help": "Sets animation speed to instantaneous", + "vcmi.battleOptions.animationsSpeed1.help": "Set animation speed to very slow", + "vcmi.battleOptions.animationsSpeed5.help": "Set animation speed to very fast", + "vcmi.battleOptions.animationsSpeed6.help": "Set animation speed to instantaneous", "vcmi.battleOptions.skipBattleIntroMusic.hover": "Skip Intro Music", - "vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\n Skip short music that plays at beginning of each battle before action starts. Can also be skipped by pressing ESC key.", - - "vcmi.battleWindow.pressKeyToSkipIntro" : "Press any key to skip battle intro", + "vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\nAllow actions during the intro music that plays at the beginning of each battle", + "vcmi.battleWindow.pressKeyToSkipIntro" : "Press any key to start battle immediately", "vcmi.battleWindow.damageEstimation.melee" : "Attack %CREATURE (%DAMAGE).", "vcmi.battleWindow.damageEstimation.meleeKills" : "Attack %CREATURE (%DAMAGE, %KILLS).", @@ -96,11 +95,11 @@ "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.availableCreaturesAsDwellingLabel.help" : "{Show Available Creatures}\n\nShow the number of creatures available to purchase instead of their growth in town summary (bottom-left corner of town screen).", "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 available amount in town summary (bottom-left corner).", + "vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Show Weekly Growth of Creatures}\n\nShow creatures' weekly growth instead of available amount in town summary (bottom-left corner of town screen).", "vcmi.otherOptions.compactTownCreatureInfo.hover": "Compact Creature Info", - "vcmi.otherOptions.compactTownCreatureInfo.help": "{Compact Creature Info}\n\n Smaller town creatures information in town summary.", + "vcmi.otherOptions.compactTownCreatureInfo.help": "{Compact Creature Info}\n\nShow smaller information for town creatures in town summary (bottom-left corner of town screen).", "vcmi.townHall.missingBase" : "Base building %s must be built first", "vcmi.townHall.noCreaturesToRecruit" : "There are no creatures to recruit!", @@ -120,20 +119,20 @@ "vcmi.logicalExpressions.allOf" : "All of the following:", "vcmi.logicalExpressions.noneOf" : "None of the following:", - "vcmi.heroWindow.openCommander.hover" : "Open commander window", - "vcmi.heroWindow.openCommander.help" : "Displays information about commander of this hero", + "vcmi.heroWindow.openCommander.hover" : "Open commander info window", + "vcmi.heroWindow.openCommander.help" : "Shows details about the commander of this hero", - "vcmi.commanderWindow.artifactMessage" : "Do you want to give this artifact back to hero?", + "vcmi.commanderWindow.artifactMessage" : "Do you want to return this artifact to the hero?", "vcmi.creatureWindow.showBonuses.hover" : "Switch to bonuses view", - "vcmi.creatureWindow.showBonuses.help" : "Displays all active bonuses of the commander", + "vcmi.creatureWindow.showBonuses.help" : "Display all active bonuses of the commander", "vcmi.creatureWindow.showSkills.hover" : "Switch to skills view", - "vcmi.creatureWindow.showSkills.help" : "Displays all learned skills of the commander", - "vcmi.creatureWindow.returnArtifact.hover" : "Give back artifact", - "vcmi.creatureWindow.returnArtifact.help" : "Use this button to return stack artifact back into hero backpack", + "vcmi.creatureWindow.showSkills.help" : "Display all learned skills of the commander", + "vcmi.creatureWindow.returnArtifact.hover" : "Return artifact", + "vcmi.creatureWindow.returnArtifact.help" : "Click this button to return the artifact to the hero's backpack", "vcmi.questLog.hideComplete.hover" : "Hide complete quests", - "vcmi.questLog.hideComplete.help" : "Hide all quests that already completed", + "vcmi.questLog.hideComplete.help" : "Hide all completed quests", "vcmi.randomMapTab.widgets.defaultTemplate" : "(default)", "vcmi.randomMapTab.widgets.templateLabel" : "Template", @@ -143,108 +142,108 @@ // few strings from WoG used by vcmi "vcmi.stackExperience.description" : "» S t a c k E x p e r i e n c e D e t a i l s «\n\nCreature Type ................... : %s\nExperience Rank ................. : %s (%i)\nExperience Points ............... : %i\nExperience Points to Next Rank .. : %i\nMaximum Experience per Battle ... : %i%% (%i)\nNumber of Creatures in stack .... : %i\nMaximum New Recruits\n without losing current Rank .... : %i\nExperience Multiplier ........... : %.2f\nUpgrade Multiplier .............. : %.2f\nExperience after Rank 10 ........ : %i\nMaximum New Recruits to remain at\n Rank 10 if at Maximum Experience : %i", - "vcmi.stackExperience.rank.1" : "Basic", - "vcmi.stackExperience.rank.2" : "Novice", - "vcmi.stackExperience.rank.3" : "Trained", - "vcmi.stackExperience.rank.4" : "Skilled", - "vcmi.stackExperience.rank.5" : "Proven", - "vcmi.stackExperience.rank.6" : "Veteran", - "vcmi.stackExperience.rank.7" : "Adept", - "vcmi.stackExperience.rank.8" : "Expert", - "vcmi.stackExperience.rank.9" : "Elite", - "vcmi.stackExperience.rank.10" : "Master", - "vcmi.stackExperience.rank.11" : "Ace", + "vcmi.stackExperience.rank.0" : "Basic", + "vcmi.stackExperience.rank.1" : "Novice", + "vcmi.stackExperience.rank.2" : "Trained", + "vcmi.stackExperience.rank.3" : "Skilled", + "vcmi.stackExperience.rank.4" : "Proven", + "vcmi.stackExperience.rank.5" : "Veteran", + "vcmi.stackExperience.rank.6" : "Adept", + "vcmi.stackExperience.rank.7" : "Expert", + "vcmi.stackExperience.rank.8" : "Elite", + "vcmi.stackExperience.rank.9" : "Master", + "vcmi.stackExperience.rank.10" : "Ace", "core.bonus.ADDITIONAL_ATTACK.name": "Double Strike", "core.bonus.ADDITIONAL_ATTACK.description": "Attacks twice", "core.bonus.ADDITIONAL_RETALIATION.name": "Additional retaliations", - "core.bonus.ADDITIONAL_RETALIATION.description": "May Retaliate ${val} extra times", + "core.bonus.ADDITIONAL_RETALIATION.description": "May retaliate ${val} extra times", "core.bonus.AIR_IMMUNITY.name": "Air immunity", - "core.bonus.AIR_IMMUNITY.description": "Immune to all Air school spells", + "core.bonus.AIR_IMMUNITY.description": "Immune to all spells from the school of Air magic", "core.bonus.ATTACKS_ALL_ADJACENT.name": "Attack all around", "core.bonus.ATTACKS_ALL_ADJACENT.description": "Attacks all adjacent enemies", "core.bonus.BLOCKS_RETALIATION.name": "No retaliation", "core.bonus.BLOCKS_RETALIATION.description": "Enemy cannot retaliate", "core.bonus.BLOCKS_RANGED_RETALIATION.name": "No ranged retaliation", - "core.bonus.BLOCKS_RANGED_RETALIATION.description": "Enemy cannot Retaliate by shooting", + "core.bonus.BLOCKS_RANGED_RETALIATION.description": "Enemy cannot retaliate by using a ranged attack", "core.bonus.CATAPULT.name": "Catapult", "core.bonus.CATAPULT.description": "Attacks siege walls", "core.bonus.CATAPULT_EXTRA_SHOTS.name": "Additional siege attacks", "core.bonus.CATAPULT_EXTRA_SHOTS.description": "Can hit siege walls ${val} extra times per attack", "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reduce Casting Cost (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Reduces spell cost for hero", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Reduces the spellcasting cost for the hero by ${val}", "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Magic Damper (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Increases Cost of enemy spells", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Increases spellcasting cost of enemy spells by ${val}", "core.bonus.CHARGE_IMMUNITY.name": "Immune to Charge", - "core.bonus.CHARGE_IMMUNITY.description": "Immune to Champion charge", + "core.bonus.CHARGE_IMMUNITY.description": "Immune to Cavalier's and Champion's Charge", "core.bonus.DARKNESS.name": "Darkness cover", - "core.bonus.DARKNESS.description": "Adds ${val} darkness radius", + "core.bonus.DARKNESS.description": "Creates a shroud of darkness with a ${val} radius", "core.bonus.DEATH_STARE.name": "Death Stare (${val}%)", - "core.bonus.DEATH_STARE.description": "${val}% chance to kill single creature", + "core.bonus.DEATH_STARE.description": "Has a ${val}% chance to kill a single creature", "core.bonus.DEFENSIVE_STANCE.name": "Defense Bonus", "core.bonus.DEFENSIVE_STANCE.description": "+${val} Defense when defending", "core.bonus.DESTRUCTION.name": "Destruction", "core.bonus.DESTRUCTION.description": "Has ${val}% chance to kill extra units after attack", "core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Death Blow", - "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% chance for double damage", + "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Has a ${val}% chance of dealing double base damage when attacking", "core.bonus.DRAGON_NATURE.name": "Dragon", "core.bonus.DRAGON_NATURE.description": "Creature has a Dragon Nature", "core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "Direct Damage Immunity", "core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "Immune to direct damage spells", "core.bonus.EARTH_IMMUNITY.name": "Earth immunity", - "core.bonus.EARTH_IMMUNITY.description": "Immune to all Earth school spells", + "core.bonus.EARTH_IMMUNITY.description": "Immune to all spells from the school of Earth magic", "core.bonus.ENCHANTER.name": "Enchanter", "core.bonus.ENCHANTER.description": "Can cast mass ${subtype.spell} every turn", "core.bonus.ENCHANTED.name": "Enchanted", "core.bonus.ENCHANTED.description": "Affected by permanent ${subtype.spell}", "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignore Defense (${val}%)", - "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Ignores part of Defence for the attack", + "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "When attacking, ${val}% of the defender's defense is ignored", "core.bonus.FIRE_IMMUNITY.name": "Fire immunity", - "core.bonus.FIRE_IMMUNITY.description": "Immune to all Fire school spells", + "core.bonus.FIRE_IMMUNITY.description": "Immune to all spells from the school of Fire magic", "core.bonus.FIRE_SHIELD.name": "Fire Shield (${val}%)", "core.bonus.FIRE_SHIELD.description": "Reflects part of melee damage", "core.bonus.FIRST_STRIKE.name": "First Strike", - "core.bonus.FIRST_STRIKE.description": "This creature attacks first instead of retaliating", + "core.bonus.FIRST_STRIKE.description": "This creature retaliates before being attacked", "core.bonus.FEAR.name": "Fear", "core.bonus.FEAR.description": "Causes Fear on an enemy stack", "core.bonus.FEARLESS.name": "Fearless", "core.bonus.FEARLESS.description": "Immune to Fear ability", "core.bonus.FLYING.name": "Fly", - "core.bonus.FLYING.description": "Can Fly (ignores obstacles)", + "core.bonus.FLYING.description": "Flies when moving (ignores obstacles)", "core.bonus.FREE_SHOOTING.name": "Shoot Close", - "core.bonus.FREE_SHOOTING.description": "Can shoot in Close Combat", + "core.bonus.FREE_SHOOTING.description": "Can use ranged attacks at melee range", "core.bonus.GARGOYLE.name": "Gargoyle", - "core.bonus.GARGOYLE.description": "Cannot be rised or healed", + "core.bonus.GARGOYLE.description": "Cannot be raised or healed", "core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Reduce Damage (${val}%)", - "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Reduces physical damage from ranged or melee", + "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Reduces physical damage from ranged or melee attacks", "core.bonus.HATE.name": "Hates ${subtype.creature}", - "core.bonus.HATE.description": "Does ${val}% more damage", + "core.bonus.HATE.description": "Does ${val}% more damage to ${subtype.creature}", "core.bonus.HEALER.name": "Healer", "core.bonus.HEALER.description": "Heals allied units", "core.bonus.HP_REGENERATION.name": "Regeneration", "core.bonus.HP_REGENERATION.description": "Heals ${SHval} hit points every round", - "core.bonus.JOUSTING.name": "Champion Charge", - "core.bonus.JOUSTING.description": "+${val}% damage per hex travelled", + "core.bonus.JOUSTING.name": "Champion charge", + "core.bonus.JOUSTING.description": "+${val}% damage for each hex travelled", "core.bonus.KING.name": "King", "core.bonus.KING.description": "Vulnerable to SLAYER level ${val} or higher", "core.bonus.LEVEL_SPELL_IMMUNITY.name": "Spell immunity 1-${val}", "core.bonus.LEVEL_SPELL_IMMUNITY.description": "Immune to spells of levels 1-${val}", "core.bonus.LIMITED_SHOOTING_RANGE.name" : "Limited shooting range", - "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Cannot shoot targets beyond ${val} hexes away", + "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Unable to target units farther than ${val} hexes", "core.bonus.LIFE_DRAIN.name": "Drain life (${val}%)", "core.bonus.LIFE_DRAIN.description": "Drains ${val}% of damage dealt", "core.bonus.MANA_CHANNELING.name": "Magic Channel ${val}%", - "core.bonus.MANA_CHANNELING.description": "Gives your hero mana spent by enemy", + "core.bonus.MANA_CHANNELING.description": "Gives your hero ${val}% of the mana spent by the enemy", "core.bonus.MANA_DRAIN.name": "Mana Drain", "core.bonus.MANA_DRAIN.description": "Drains ${val} mana every turn", "core.bonus.MAGIC_MIRROR.name": "Magic Mirror (${val}%)", - "core.bonus.MAGIC_MIRROR.description": "${val}% chance to redirects an offensive spell to enemy", - "core.bonus.MAGIC_RESISTANCE.name": "Magic Resistance(${val}%)", - "core.bonus.MAGIC_RESISTANCE.description": "${val}% chance to resist enemy spell", + "core.bonus.MAGIC_MIRROR.description": "Has a ${val}% chance to redirect an offensive spell to an enemy unit", + "core.bonus.MAGIC_RESISTANCE.name": "Magic Resistance (${val}%)", + "core.bonus.MAGIC_RESISTANCE.description": "Has a ${val}% chance to resist an enemy spell", "core.bonus.MIND_IMMUNITY.name": "Mind Spell Immunity", "core.bonus.MIND_IMMUNITY.description": "Immune to Mind-type spells", "core.bonus.NO_DISTANCE_PENALTY.name": "No distance penalty", - "core.bonus.NO_DISTANCE_PENALTY.description": "Full damage from any distance", + "core.bonus.NO_DISTANCE_PENALTY.description": "Does full damage at any distance", "core.bonus.NO_MELEE_PENALTY.name": "No melee penalty", "core.bonus.NO_MELEE_PENALTY.description": "Creature has no Melee Penalty", "core.bonus.NO_MORALE.name": "Neutral Morale", @@ -263,10 +262,6 @@ "core.bonus.REBIRTH.description": "${val}% of stack will rise after death", "core.bonus.RETURN_AFTER_STRIKE.name": "Attack and Return", "core.bonus.RETURN_AFTER_STRIKE.description": "Returns after melee attack", - "core.bonus.SELF_LUCK.name": "Positive luck", - "core.bonus.SELF_LUCK.description": "Always has Positive Luck", - "core.bonus.SELF_MORALE.name": "Positive morale", - "core.bonus.SELF_MORALE.description": "Always has Positive Morale", "core.bonus.SHOOTER.name": "Ranged", "core.bonus.SHOOTER.description": "Creature can shoot", "core.bonus.SHOOTS_ALL_ADJACENT.name": "Shoot all around", @@ -276,11 +271,11 @@ "core.bonus.SPELLCASTER.name": "Spellcaster", "core.bonus.SPELLCASTER.description": "Can cast ${subtype.spell}", "core.bonus.SPELL_AFTER_ATTACK.name": "Cast After Attack", - "core.bonus.SPELL_AFTER_ATTACK.description": "${val}% to cast ${subtype.spell} after attack", + "core.bonus.SPELL_AFTER_ATTACK.description": "Has a ${val}% chance to cast ${subtype.spell} after it attacks", "core.bonus.SPELL_BEFORE_ATTACK.name": "Cast Before Attack", - "core.bonus.SPELL_BEFORE_ATTACK.description": "${val}% to cast ${subtype.spell} before attack", + "core.bonus.SPELL_BEFORE_ATTACK.description": "Has a ${val}% chance to cast ${subtype.spell} before it attacks", "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Spell Resistance", - "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Damage from spells reduced ${val}%.", + "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Damage from spells reduced by ${val}%.", "core.bonus.SPELL_IMMUNITY.name": "Spell immunity", "core.bonus.SPELL_IMMUNITY.description": "Immune to ${subtype.spell}", "core.bonus.SPELL_LIKE_ATTACK.name": "Spell-like attack", @@ -288,7 +283,7 @@ "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura of Resistance", "core.bonus.SPELL_RESISTANCE_AURA.description": "Nearby stacks get ${val}% magic resistance", "core.bonus.SUMMON_GUARDIANS.name": "Summon guardians", - "core.bonus.SUMMON_GUARDIANS.description": "At battle start summons ${subtype.creature} (${val}%)", + "core.bonus.SUMMON_GUARDIANS.description": "At the start of battle summons ${subtype.creature} (${val}%)", "core.bonus.SYNERGY_TARGET.name": "Synergizable", "core.bonus.SYNERGY_TARGET.description": "This creature is vulnerable to synergy effect", "core.bonus.TWO_HEX_ATTACK_BREATH.name": "Breath", @@ -296,13 +291,13 @@ "core.bonus.THREE_HEADED_ATTACK.name": "Three-headed attack", "core.bonus.THREE_HEADED_ATTACK.description": "Attacks three adjacent units", "core.bonus.TRANSMUTATION.name": "Transmutation", - "core.bonus.TRANSMUTATION.description": "${val}% chance to transform attacked unit to other type", + "core.bonus.TRANSMUTATION.description": "${val}% chance to transform attacked unit to a different type", "core.bonus.UNDEAD.name": "Undead", "core.bonus.UNDEAD.description": "Creature is Undead", "core.bonus.UNLIMITED_RETALIATIONS.name": "Unlimited retaliations", - "core.bonus.UNLIMITED_RETALIATIONS.description": "Retaliates any number of attacks", + "core.bonus.UNLIMITED_RETALIATIONS.description": "Can retaliate against an unlimited number of attacks", "core.bonus.WATER_IMMUNITY.name": "Water immunity", - "core.bonus.WATER_IMMUNITY.description": "Immune to all Water school spells", + "core.bonus.WATER_IMMUNITY.description": "Immune to all spells from the school of Water magic", "core.bonus.WIDE_BREATH.name": "Wide breath", "core.bonus.WIDE_BREATH.description": "Wide breath attack (multiple hexes)" } diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index 9c53ac960..c11bd019b 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -47,6 +47,8 @@ "vcmi.systemOptions.framerateButton.hover" : "FPS anzeigen", "vcmi.systemOptions.framerateButton.help" : "{FPS anzeigen}\n\n Schaltet die Sichtbarkeit des Zählers für die Bilder pro Sekunde in der Ecke des Spielfensters um.", + "vcmi.adventureOptions.infoBarPick.hover" : "Meldungen im Infobereich anzeigen", + "vcmi.adventureOptions.infoBarPick.help" : "{Meldungen im Infobereich anzeigen}\n\nWann immer möglich, werden Spielnachrichten von besuchten Kartenobjekten in der Infoleiste angezeigt, anstatt als Popup-Fenster zu erscheinen", "vcmi.adventureOptions.numericQuantities.hover" : "Numerische Kreaturenmengen", "vcmi.adventureOptions.numericQuantities.help" : "{Numerische Kreaturenmengen}\n\n Zeigt die ungefähre Menge der feindlichen Kreaturen im numerischen Format A-B an.", "vcmi.adventureOptions.forceMovementInfo.hover" : "Bewegungskosten immer anzeigen", @@ -79,6 +81,19 @@ "vcmi.battleOptions.animationsSpeed6.help": "Setzt die Animationsgeschwindigkeit auf sofort", "vcmi.battleOptions.skipBattleIntroMusic.hover": "Intro-Musik überspringen", "vcmi.battleOptions.skipBattleIntroMusic.help": "{Intro-Musik überspringen}\n\n Überspringe die kurze Musik, die zu Beginn eines jeden Kampfes gespielt wird, bevor die Action beginnt. Kann auch durch Drücken der ESC-Taste übersprungen werden.", + + "vcmi.battleWindow.pressKeyToSkipIntro" : "Beliebige Taste drücken, um das Kampf-Intro zu überspringen", + + "vcmi.battleWindow.damageEstimation.melee" : "Angriff auf %CREATURE (%DAMAGE).", + "vcmi.battleWindow.damageEstimation.meleeKills" : "Angriff auf %CREATURE (%DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.ranged" : "Schuss auf %CREATURE (%SHOTS, %DAMAGE).", + "vcmi.battleWindow.damageEstimation.rangedKills" : "Schuss auf %CREATURE (%SHOTS, %DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.shots" : "%d Schüsse verbleibend", + "vcmi.battleWindow.damageEstimation.shots.1" : "%d Schüsse verbleibend", + "vcmi.battleWindow.damageEstimation.damage" : "%d Schaden", + "vcmi.battleWindow.damageEstimation.damage.1" : "%d Schaden", + "vcmi.battleWindow.damageEstimation.kills" : "%d werden verenden", + "vcmi.battleWindow.damageEstimation.kills.1" : "%d werden verenden", "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Verfügbare Kreaturen anzeigen", "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Verfügbare Kreaturen anzeigen}\n\n Zeigt in der Stadtübersicht (linke untere Ecke) die zum Kauf verfügbaren Kreaturen anstelle ihres Wachstums an.", @@ -128,17 +143,17 @@ // few strings from WoG used by vcmi "vcmi.stackExperience.description" : "» D e t a i l s z u r S t a p e l e r f a h r u n g «\n\nKreatur-Typ ................... : %s\nErfahrungsrang ................. : %s (%i)\nErfahrungspunkte ............... : %i\nErfahrungspunkte für den nächsten Rang .. : %i\nMaximale Erfahrung pro Kampf ... : %i%% (%i)\nAnzahl der Kreaturen im Stapel .... : %i\nMaximale Anzahl neuer Rekruten\n ohne Verlust von aktuellem Rang .... : %i\nErfahrungs-Multiplikator ........... : %.2f\nUpgrade-Multiplikator .............. : %.2f\nErfahrung nach Rang 10 ........ : %i\nMaximale Anzahl der neuen Rekruten, die bei\n Rang 10 bei maximaler Erfahrung übrig sind : %i", - "vcmi.stackExperience.rank.1" : "Grundlagen", - "vcmi.stackExperience.rank.2" : "Neuling", - "vcmi.stackExperience.rank.3" : "Ausgebildet", - "vcmi.stackExperience.rank.4" : "Kompetent", - "vcmi.stackExperience.rank.5" : "Bewährt", - "vcmi.stackExperience.rank.6" : "Veteran", - "vcmi.stackExperience.rank.7" : "Gekonnt", - "vcmi.stackExperience.rank.8" : "Experte", - "vcmi.stackExperience.rank.9" : "Elite", - "vcmi.stackExperience.rank.10" : "Meister", - "vcmi.stackExperience.rank.11" : "Ass", + "vcmi.stackExperience.rank.0" : "Grundlagen", + "vcmi.stackExperience.rank.1" : "Neuling", + "vcmi.stackExperience.rank.2" : "Ausgebildet", + "vcmi.stackExperience.rank.3" : "Kompetent", + "vcmi.stackExperience.rank.4" : "Bewährt", + "vcmi.stackExperience.rank.5" : "Veteran", + "vcmi.stackExperience.rank.6" : "Gekonnt", + "vcmi.stackExperience.rank.7" : "Experte", + "vcmi.stackExperience.rank.8" : "Elite", + "vcmi.stackExperience.rank.9" : "Meister", + "vcmi.stackExperience.rank.10" : "Ass", "core.bonus.ADDITIONAL_ATTACK.name": "Doppelschlag", "core.bonus.ADDITIONAL_ATTACK.description": "Greift zweimal an", @@ -210,12 +225,8 @@ "core.bonus.HP_REGENERATION.description": "Heilt ${SHval} Trefferpunkte jede Runde", "core.bonus.JOUSTING.name": "Champion Charge", "core.bonus.JOUSTING.description": "+${val}% Schaden pro zurückgelegtem Feld", - "core.bonus.KING1.name": "König 1", - "core.bonus.KING1.description": "Anfällig für grundlegende SLAYER", - "core.bonus.KING2.name": "König 2", - "core.bonus.KING2.description": "Anfällig für erweiterte SLAYER", - "core.bonus.KING3.name": "König3", - "core.bonus.KING3.description":"Anfällig für Experten-SLAYER", + "core.bonus.KING.name": "König", + "core.bonus.KING.description": "Anfällig für SLAYER Level ${val} oder höher", "core.bonus.LEVEL_SPELL_IMMUNITY.name": "Zauberimmunität 1-${val}", "core.bonus.LEVEL_SPELL_IMMUNITY.description": "Immun gegen Zaubersprüche der Stufen 1-${val}", "core.bonus.LIMITED_SHOOTING_RANGE.name" : "Begrenzte Schussweite", @@ -252,10 +263,6 @@ "core.bonus.REBIRTH.description": "${val}% des Stacks wird nach dem Tod auferstehen", "core.bonus.RETURN_AFTER_STRIKE.name": "Angriff und Rückkehr", "core.bonus.RETURN_AFTER_STRIKE.description": "Kehrt nach Nahkampfangriff zurück", - "core.bonus.SELF_LUCK.name": "Positives Glück", - "core.bonus.SELF_LUCK.description": "Hat immer positives Glück", - "core.bonus.SELF_MORALE.name": "Positive Moral", - "core.bonus.SELF_MORALE.description": "Hat immer positive Moral", "core.bonus.SHOOTER.name": "Fernkämpfer", "core.bonus.SHOOTER.description": "Kreatur kann schießen", "core.bonus.SHOOTS_ALL_ADJACENT.name": "Schießt rundherum", diff --git a/Mods/vcmi/config/vcmi/polish.json b/Mods/vcmi/config/vcmi/polish.json index 68affb700..eb21ecf03 100644 --- a/Mods/vcmi/config/vcmi/polish.json +++ b/Mods/vcmi/config/vcmi/polish.json @@ -249,10 +249,6 @@ "core.bonus.REBIRTH.description": "${val}% stworzeń powstanie po śmierci", "core.bonus.RETURN_AFTER_STRIKE.name": "Atak i Powrót", "core.bonus.RETURN_AFTER_STRIKE.description": "Wraca po ataku wręcz", - "core.bonus.SELF_LUCK.name": "Pozytywne szczęście", - "core.bonus.SELF_LUCK.description": "Zawsze posiada pozytywne szczęście", - "core.bonus.SELF_MORALE.name": "Pozytywne Morale", - "core.bonus.SELF_MORALE.description": "Zawsze posiada pozytywne morale", "core.bonus.SHOOTER.name": "Dystansowy", "core.bonus.SHOOTER.description": "Stworzenie może strzelać", "core.bonus.SHOOTS_ALL_ADJACENT.name": "Ostrzeliwuje wszystko dookoła", diff --git a/Mods/vcmi/config/vcmi/russian.json b/Mods/vcmi/config/vcmi/russian.json index c6bd0cae7..f98c7880d 100644 --- a/Mods/vcmi/config/vcmi/russian.json +++ b/Mods/vcmi/config/vcmi/russian.json @@ -145,17 +145,17 @@ // 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\nОпыт после 10 ранга ........ : %i\nМаксимум новичков для сохранения\n ранга 10 при максимальном опыте : %i", - "vcmi.stackExperience.rank.1" : "Рекрут", - "vcmi.stackExperience.rank.2" : "Новичок", - "vcmi.stackExperience.rank.3" : "Тренирован", - "vcmi.stackExperience.rank.4" : "Знающий", - "vcmi.stackExperience.rank.5" : "Подтвержденный", - "vcmi.stackExperience.rank.6" : "Ветеран", - "vcmi.stackExperience.rank.7" : "Адепт", - "vcmi.stackExperience.rank.8" : "Эксперт", - "vcmi.stackExperience.rank.9" : "Элита", - "vcmi.stackExperience.rank.10" : "Мастер", - "vcmi.stackExperience.rank.11" : "Ас", + "vcmi.stackExperience.rank.0" : "Рекрут", + "vcmi.stackExperience.rank.1" : "Новичок", + "vcmi.stackExperience.rank.2" : "Тренирован", + "vcmi.stackExperience.rank.3" : "Знающий", + "vcmi.stackExperience.rank.4" : "Подтвержденный", + "vcmi.stackExperience.rank.5" : "Ветеран", + "vcmi.stackExperience.rank.6" : "Адепт", + "vcmi.stackExperience.rank.7" : "Эксперт", + "vcmi.stackExperience.rank.8" : "Элита", + "vcmi.stackExperience.rank.9" : "Мастер", + "vcmi.stackExperience.rank.10" : "Ас", "core.bonus.ADDITIONAL_ATTACK.name": "Двойной удар", "core.bonus.ADDITIONAL_ATTACK.description": "Бьет дважды", @@ -265,10 +265,6 @@ "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": "Стреляет по области", diff --git a/Mods/vcmi/config/vcmi/spanish.json b/Mods/vcmi/config/vcmi/spanish.json index 312a90254..7da50f97e 100644 --- a/Mods/vcmi/config/vcmi/spanish.json +++ b/Mods/vcmi/config/vcmi/spanish.json @@ -131,17 +131,17 @@ // few strings from WoG used by vcmi "vcmi.stackExperience.description" : "» D e t a l l e s d e E x p e r i e n c i a d e l G r u p o «\n\nTipo de Criatura ................ : %s\nRango de Experiencia ............ : %s (%i)\nPuntos de Experiencia ............ : %i\nPuntos de Experiencia para el\nSiguiente Rango ............... : %i\nExperiencia Máxima por Batalla .. : %i%% (%i)\nNúmero de Criaturas en el grupo .. : %i\nMáximo de Nuevos Reclutas sin\nPerder el Rango Actual ......... : %i\nMultiplicador de Experiencia .... : %.2f\nMultiplicador de Actualización .. : %.2f\nExperiencia después del Rango 10 : %i\nMáximo de Nuevos Reclutas para\nMantener el Rango 10 si\nEstá en la Experiencia Máxima : %i", - "vcmi.stackExperience.rank.1" : "Básico", - "vcmi.stackExperience.rank.2" : "Novato", - "vcmi.stackExperience.rank.3" : "Entrenado", - "vcmi.stackExperience.rank.4" : "Hábil", - "vcmi.stackExperience.rank.5" : "Probado", - "vcmi.stackExperience.rank.6" : "Veterano", - "vcmi.stackExperience.rank.7" : "Experto", - "vcmi.stackExperience.rank.8" : "Experto Superior", - "vcmi.stackExperience.rank.9" : "Élite", - "vcmi.stackExperience.rank.10" : "Maestro", - "vcmi.stackExperience.rank.11" : "As", + "vcmi.stackExperience.rank.0" : "Básico", + "vcmi.stackExperience.rank.1" : "Novato", + "vcmi.stackExperience.rank.2" : "Entrenado", + "vcmi.stackExperience.rank.3" : "Hábil", + "vcmi.stackExperience.rank.4" : "Probado", + "vcmi.stackExperience.rank.5" : "Veterano", + "vcmi.stackExperience.rank.6" : "Experto", + "vcmi.stackExperience.rank.7" : "Experto Superior", + "vcmi.stackExperience.rank.8" : "Élite", + "vcmi.stackExperience.rank.9" : "Maestro", + "vcmi.stackExperience.rank.10" : "As", "core.bonus.ADDITIONAL_ATTACK.name": "Doble Ataque", "core.bonus.ADDITIONAL_ATTACK.description": "Ataca dos veces", @@ -257,10 +257,6 @@ "core.bonus.REBIRTH.description": "El ${val}% del grupo resucitará después de la muerte", "core.bonus.RETURN_AFTER_STRIKE.name": "Atacar y volver", "core.bonus.RETURN_AFTER_STRIKE.description": "Regresa después de un ataque cuerpo a cuerpo", - "core.bonus.SELF_LUCK.name": "Suerte positiva", - "core.bonus.SELF_LUCK.description": "Siempre tiene suerte positiva", - "core.bonus.SELF_MORALE.name": "Moral positiva", - "core.bonus.SELF_MORALE.description": "Siempre tiene moral positiva", "core.bonus.SHOOTER.name": "A distancia", "core.bonus.SHOOTER.description": "La criatura puede disparar", "core.bonus.SHOOTS_ALL_ADJACENT.name": "Dispara en todas direcciones", diff --git a/Mods/vcmi/config/vcmi/ukrainian.json b/Mods/vcmi/config/vcmi/ukrainian.json index 758bb53b8..e805423b7 100644 --- a/Mods/vcmi/config/vcmi/ukrainian.json +++ b/Mods/vcmi/config/vcmi/ukrainian.json @@ -249,10 +249,6 @@ "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" : "Стріляйте по площі", @@ -295,15 +291,15 @@ "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Не може стріляти по цілях на відстані більше ${val} гексів", "vcmi.stackExperience.description" : "» S t a c k E x p e r i e n c e D e t a i l s «\n\nCreature Type ................... : %s\nExperience Rank ................. : %s (%i)\nExperience Points ............... : %i\nExperience Points to Next Rank .. : %i\nMaximum Experience per Battle ... : %i%% (%i)\nNumber of Creatures in stack .... : %i\nMaximum New Recruits\n without losing current Rank .... : %i\nExperience Multiplier ........... : %.2f\nUpgrade Multiplier .............. : %.2f\nExperience after Rank 10 ........ : %i\nMaximum New Recruits to remain at\n Rank 10 if at Maximum Experience : %i", - "vcmi.stackExperience.rank.1" : "Початковий", - "vcmi.stackExperience.rank.2" : "Новачок", - "vcmi.stackExperience.rank.3" : "Підготовлений", - "vcmi.stackExperience.rank.4" : "Досвідчений", - "vcmi.stackExperience.rank.5" : "Випробуваний", - "vcmi.stackExperience.rank.6" : "Ветеран", - "vcmi.stackExperience.rank.7" : "Адепт", - "vcmi.stackExperience.rank.8" : "Експерт", - "vcmi.stackExperience.rank.9" : "Еліта", - "vcmi.stackExperience.rank.10" : "Майстер", - "vcmi.stackExperience.rank.11" : "Профі", + "vcmi.stackExperience.rank.0" : "Початковий", + "vcmi.stackExperience.rank.1" : "Новачок", + "vcmi.stackExperience.rank.2" : "Підготовлений", + "vcmi.stackExperience.rank.3" : "Досвідчений", + "vcmi.stackExperience.rank.4" : "Випробуваний", + "vcmi.stackExperience.rank.5" : "Ветеран", + "vcmi.stackExperience.rank.6" : "Адепт", + "vcmi.stackExperience.rank.7" : "Експерт", + "vcmi.stackExperience.rank.8" : "Еліта", + "vcmi.stackExperience.rank.9" : "Майстер", + "vcmi.stackExperience.rank.10" : "Профі", } diff --git a/Mods/vcmi/mod.json b/Mods/vcmi/mod.json index be2734f86..e24f67a5c 100644 --- a/Mods/vcmi/mod.json +++ b/Mods/vcmi/mod.json @@ -16,7 +16,6 @@ "name" : "VCMI - grundlegende Dateien", "description" : "Grundlegende Dateien, die für die korrekte Ausführung von VCMI erforderlich sind", "author" : "VCMI-Team", - "modType" : "Grafik", "skipValidation" : true, "translations" : [ @@ -28,7 +27,6 @@ "name" : "Podstawowe pliki VCMI", "description" : "Dodatkowe pliki wymagane do prawidłowego działania VCMI", "author" : "Zespół VCMI", - "modType" : "Graficzny", "skipValidation" : true, "translations" : [ @@ -40,7 +38,6 @@ "name" : "Ключевые файлы VCMI", "description" : "Файлы, необходимые для полноценной работы VCMI", "author" : "Команда VCMI", - "modType" : "Графический", "skipValidation" : true, "translations" : [ @@ -52,7 +49,6 @@ "name" : "VCMI - ключові файли", "description" : "Ключові файли необхідні для повноцінної роботи VCMI", "author" : "Команда VCMI", - "modType" : "Графіка", "translations" : [ "config/vcmi/ukrainian.json" @@ -63,7 +59,6 @@ "name" : "VCMI - ficheros necesarios", "description" : "Ficheros necesarios para ejecutar VCMI correctamente", "author" : "Abel Rivas", - "modType" : "Gráfico", "skipValidation" : true, "translations" : [ diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index 4acc87d98..f15ad9c3f 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -142,11 +142,13 @@ Mix_Chunk *CSoundHandler::GetSoundChunk(std::string &sound, bool cache) int CSoundHandler::ambientDistToVolume(int distance) const { - if(distance >= ambientConfig["distances"].Vector().size()) + const auto & distancesVector = ambientConfig["distances"].Vector(); + + if(distance >= distancesVector.size()) return 0; - int volume = static_cast(ambientConfig["distances"].Vector()[distance].Integer()); - return volume * (int)ambientConfig["volume"].Integer() * getVolume() / 10000; + int volume = static_cast(distancesVector[distance].Integer()); + return volume * (int)ambientConfig["volume"].Integer() / 100; } void CSoundHandler::ambientStopSound(std::string soundId) @@ -211,7 +213,20 @@ void CSoundHandler::setVolume(ui32 percent) CAudioBase::setVolume(percent); if (initialized) + { setChannelVolume(-1, volume); + + for (auto const & channel : channelVolumes) + updateChannelVolume(channel.first); + } +} + +void CSoundHandler::updateChannelVolume(int channel) +{ + if (channelVolumes.count(channel)) + setChannelVolume(channel, getVolume() * channelVolumes[channel] / 100); + else + setChannelVolume(channel, getVolume()); } // Sets the sound volume, from 0 (mute) to 100 @@ -258,29 +273,40 @@ void CSoundHandler::ambientUpdateChannels(std::map soundsArg) std::vector stoppedSounds; for(auto & pair : ambientChannels) { - if(!vstd::contains(soundsArg, pair.first)) + const std::string & soundId = pair.first; + const int channel = pair.second; + + if(!vstd::contains(soundsArg, soundId)) { - ambientStopSound(pair.first); - stoppedSounds.push_back(pair.first); + ambientStopSound(soundId); + stoppedSounds.push_back(soundId); } else { - int volume = ambientDistToVolume(soundsArg[pair.first]); - CCS->soundh->setChannelVolume(pair.second, volume); + int volume = ambientDistToVolume(soundsArg[soundId]); + channelVolumes[channel] = volume; + updateChannelVolume(channel); } } for(auto soundId : stoppedSounds) + { + channelVolumes.erase(ambientChannels[soundId]); ambientChannels.erase(soundId); + } for(auto & pair : soundsArg) { - if(!vstd::contains(ambientChannels, pair.first)) - { - int channel = CCS->soundh->playSound(pair.first, -1); - int volume = ambientDistToVolume(pair.second); + const std::string & soundId = pair.first; + const int distance = pair.second; - CCS->soundh->setChannelVolume(channel, volume); - CCS->soundh->ambientChannels.insert(std::make_pair(pair.first, channel)); + if(!vstd::contains(ambientChannels, soundId)) + { + int channel = playSound(soundId, -1); + int volume = ambientDistToVolume(distance); + channelVolumes[channel] = volume; + + updateChannelVolume(channel); + ambientChannels[soundId] = channel; } } } @@ -293,6 +319,7 @@ void CSoundHandler::ambientStopAllChannels() { ambientStopSound(ch.first); } + channelVolumes.clear(); ambientChannels.clear(); } diff --git a/client/CMusicHandler.h b/client/CMusicHandler.h index 5d3319052..9c909c347 100644 --- a/client/CMusicHandler.h +++ b/client/CMusicHandler.h @@ -51,9 +51,12 @@ private: int ambientDistToVolume(int distance) const; void ambientStopSound(std::string soundId); + void updateChannelVolume(int channel); const JsonNode ambientConfig; + std::map ambientChannels; + std::map channelVolumes; public: CSoundHandler(); diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 6927611f0..91e6516fe 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -2033,7 +2033,7 @@ bool CPlayerInterface::capturedAllEvents() return true; } - bool needToLockAdventureMap = adventureInt->active && CGI->mh->hasOngoingAnimations(); + bool needToLockAdventureMap = adventureInt && adventureInt->active && CGI->mh->hasOngoingAnimations(); if (ignoreEvents || needToLockAdventureMap) { diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index 6d86edb31..2f48f2492 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -119,7 +119,10 @@ BattleActionsController::BattleActionsController(BattleInterface & owner): void BattleActionsController::endCastingSpell() { if(heroSpellToCast) + { heroSpellToCast.reset(); + owner.windowObject->blockUI(false); + } if(owner.stacksController->getActiveStack()) possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); //restore actions after they were cleared @@ -287,6 +290,8 @@ void BattleActionsController::castThisSpell(SpellID spellID) possibleActions.push_back (spellSelMode); //only this one action can be performed at the moment GH.fakeMouseMove();//update cursor } + + owner.windowObject->blockUI(true); } const CSpell * BattleActionsController::getHeroSpellToCast( ) const @@ -520,6 +525,8 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B switch (action.get()) { case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK: + return (targetStack && targetStackOwned && targetStack->Speed() > 0); + case PossiblePlayerBattleAction::CREATURE_INFO: return (targetStack && targetStackOwned); @@ -689,14 +696,14 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B if (action.spell() == SpellID::SACRIFICE) { heroSpellToCast->aimToHex(targetHex); - possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE); + possibleActions.push_back({PossiblePlayerBattleAction::SACRIFICE, action.spell()}); owner.stacksController->setSelectedStack(targetStack); return; } if (action.spell() == SpellID::TELEPORT) { heroSpellToCast->aimToUnit(targetStack); - possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT); + possibleActions.push_back({PossiblePlayerBattleAction::TELEPORT, action.spell()}); owner.stacksController->setSelectedStack(targetStack); return; } diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index 30f1eb2b7..96ffffbd4 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -756,7 +756,15 @@ void ShootingAnimation::createProjectile(const Point & from, const Point & dest) uint32_t ShootingAnimation::getAttackClimaxFrame() const { const CCreature *shooterInfo = getCreature(); - return shooterInfo->animation.attackClimaxFrame; + + uint32_t maxFrames = stackAnimation(attackingStack)->framesInGroup(getGroup()); + uint32_t climaxFrame = shooterInfo->animation.attackClimaxFrame; + uint32_t selectedFrame = vstd::clamp(shooterInfo->animation.attackClimaxFrame, 1, maxFrames); + + if (climaxFrame != selectedFrame) + logGlobal->warn("Shooter %s has ranged attack climax frame set to %d, but only %d available!", shooterInfo->getNamePluralTranslated(), climaxFrame, maxFrames); + + return selectedFrame - 1; // H3 counts frames from 1 } ECreatureAnimType ShootingAnimation::getUpwardsGroup() const diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 369fc78ac..c11d82a93 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -35,8 +35,6 @@ #include "../../lib/CStack.h" #include "../../lib/spells/ISpellMechanics.h" -#include - BattleFieldController::BattleFieldController(BattleInterface & owner): owner(owner) { @@ -67,10 +65,7 @@ BattleFieldController::BattleFieldController(BattleInterface & owner): backgroundWithHexes = std::make_unique(Point(background->width(), background->height())); - auto accessibility = owner.curInt->cb->getAccesibility(); - for(int i = 0; i < accessibility.size(); i++) - stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE); - + updateAccessibleHexes(); addUsedEvents(LCLICK | RCLICK | MOVE); LOCPLINT->cingconsole->pos = this->pos; } @@ -180,11 +175,6 @@ void BattleFieldController::redrawBackgroundWithHexes() if (activeStack) occupyableHexes = owner.curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes); - auto accessibility = owner.curInt->cb->getAccesibility(); - - for(int i = 0; i < accessibility.size(); i++) - stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE); - //prepare background graphic with hexes and shaded hexes backgroundWithHexes->draw(background, Point(0,0)); owner.obstacleController->showAbsoluteObstacles(*backgroundWithHexes); @@ -579,6 +569,14 @@ bool BattleFieldController::isTileAttackable(const BattleHex & number) const return false; } +void BattleFieldController::updateAccessibleHexes() +{ + auto accessibility = owner.curInt->cb->getAccesibility(); + + for(int i = 0; i < accessibility.size(); i++) + stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE || (accessibility[i] == EAccessibility::SIDE_COLUMN)); +} + bool BattleFieldController::stackCountOutsideHex(const BattleHex & number) const { return stackCountOutsideHexes[number]; @@ -591,6 +589,7 @@ void BattleFieldController::showAll(SDL_Surface * to) void BattleFieldController::show(SDL_Surface * to) { + updateAccessibleHexes(); owner.stacksController->update(); owner.obstacleController->update(); diff --git a/client/battle/BattleFieldController.h b/client/battle/BattleFieldController.h index fdb5cd22e..60e5ad240 100644 --- a/client/battle/BattleFieldController.h +++ b/client/battle/BattleFieldController.h @@ -56,6 +56,7 @@ class BattleFieldController : public CIntObject void showBackgroundImage(Canvas & canvas); void showBackgroundImageWithHexes(Canvas & canvas); void showHighlightedHexes(Canvas & canvas); + void updateAccessibleHexes(); BattleHex::EDir selectAttackDirection(BattleHex myNumber, const Point & point); diff --git a/client/battle/BattleObstacleController.cpp b/client/battle/BattleObstacleController.cpp index ffa5c0429..d6d5d6bb0 100644 --- a/client/battle/BattleObstacleController.cpp +++ b/client/battle/BattleObstacleController.cpp @@ -78,6 +78,11 @@ void BattleObstacleController::obstaclePlaced(const std::vectorcb->playerToSide(owner.curInt->playerID); + + if(!oi->visibleForSide(side.get(),owner.curInt->cb->battleHasNativeStack(side.get()))) + continue; + auto spellObstacle = dynamic_cast(oi.get()); if (!spellObstacle) diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 7ad36f2ac..532881df8 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -267,12 +267,8 @@ bool BattleStacksController::stackNeedsAmountBox(const CStack * stack) const return false; // if stack has any ongoing animation - hide the box - for(auto anim : currentAnimations) - { - auto stackAnimation = dynamic_cast(anim); - if(stackAnimation && (stackAnimation->stack->ID == stack->ID)) - return false; - } + if (stackAmountBoxHidden.count(stack->ID)) + return false; return true; } @@ -300,26 +296,42 @@ std::shared_ptr BattleStacksController::getStackAmountBox(const CStack * void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack * stack) { - //blitting amount background box auto amountBG = getStackAmountBox(stack); - const int sideShift = stack->side == BattleSide::ATTACKER ? 1 : -1; - const int reverseSideShift = stack->side == BattleSide::ATTACKER ? -1 : 1; - const BattleHex nextPos = stack->getPosition() + sideShift; - const bool edge = stack->getPosition() % GameConstants::BFIELD_WIDTH == (stack->side == BattleSide::ATTACKER ? GameConstants::BFIELD_WIDTH - 2 : 1); - const bool moveInside = !edge && !owner.fieldController->stackCountOutsideHex(nextPos); + bool doubleWide = stack->doubleWide(); + bool turnedRight = facingRight(stack); + bool attacker = stack->side == BattleSide::ATTACKER; - int xAdd = (stack->side == BattleSide::ATTACKER ? 220 : 202) + - (stack->doubleWide() ? 44 : 0) * sideShift + - (moveInside ? amountBG->width() + 10 : 0) * reverseSideShift; - int yAdd = 260 + ((stack->side == BattleSide::ATTACKER || moveInside) ? 0 : -15); + BattleHex stackPos = stack->getPosition(); - canvas.draw(amountBG, stackAnimation[stack->ID]->pos.topLeft() + Point(xAdd, yAdd)); + // double-wide unit turned around - use opposite hex for stack label + if (doubleWide && turnedRight != attacker) + stackPos = stack->occupiedHex(); - //blitting amount - Point textPos = stackAnimation[stack->ID]->pos.topLeft() + amountBG->dimensions()/2 + Point(xAdd, yAdd); + BattleHex frontPos = turnedRight ? + stackPos.cloneInDirection(BattleHex::RIGHT) : + stackPos.cloneInDirection(BattleHex::LEFT); - canvas.drawText(textPos, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, TextOperations::formatMetric(stack->getCount(), 4)); + bool moveInside = !owner.fieldController->stackCountOutsideHex(frontPos); + + Point boxPosition; + + if (moveInside) + { + boxPosition = owner.fieldController->hexPositionLocal(stackPos).center() + Point(-15, 1); + } + else + { + if (turnedRight) + boxPosition = owner.fieldController->hexPositionLocal(frontPos).center() + Point (-22, 1); + else + boxPosition = owner.fieldController->hexPositionLocal(frontPos).center() + Point(-8, -14); + } + + Point textPosition = amountBG->dimensions()/2 + boxPosition; + + canvas.draw(amountBG, boxPosition); + canvas.drawText(textPosition, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, TextOperations::formatMetric(stack->getCount(), 4)); } void BattleStacksController::showStack(Canvas & canvas, const CStack * stack) @@ -368,6 +380,7 @@ void BattleStacksController::updateBattleAnimations() if (hadAnimations && currentAnimations.empty()) { + //stackAmountBoxHidden.clear(); owner.executeStagedAnimations(); if (currentAnimations.empty()) owner.onAnimationsFinished(); @@ -378,8 +391,15 @@ void BattleStacksController::updateBattleAnimations() void BattleStacksController::addNewAnim(BattleAnimation *anim) { + if (currentAnimations.empty()) + stackAmountBoxHidden.clear(); + owner.onAnimationsStarted(); currentAnimations.push_back(anim); + + auto stackAnimation = dynamic_cast(anim); + if(stackAnimation) + stackAmountBoxHidden.insert(stackAnimation->stack->ID); } void BattleStacksController::stackRemoved(uint32_t stackID) @@ -668,6 +688,8 @@ void BattleStacksController::endAction(const BattleAction* action) owner.executeStagedAnimations(); owner.waitForAnimations(); + stackAmountBoxHidden.clear(); + owner.windowObject->blockUI(activeStack == nullptr); removeExpiredColorFilters(); } @@ -738,8 +760,8 @@ Point BattleStacksController::getStackPositionAtHex(BattleHex hexNum, const CSta if(stack && stack->initialPosition < 0) //creatures in turrets return owner.siegeController->getTurretCreaturePosition(stack->initialPosition); - static const Point basePos(-190, -139); // position of creature in topleft corner - static const int imageShiftX = 30; // X offset to base pos for facing right stacks, negative for facing left + static const Point basePos(-189, -139); // position of creature in topleft corner + static const int imageShiftX = 29; // X offset to base pos for facing right stacks, negative for facing left ret.x = basePos.x + 22 * ( (hexNum.getY() + 1)%2 ) + 44 * hexNum.getX(); ret.y = basePos.y + 42 * hexNum.getY(); diff --git a/client/battle/BattleStacksController.h b/client/battle/BattleStacksController.h index 8fe4f6ab0..7985688d6 100644 --- a/client/battle/BattleStacksController.h +++ b/client/battle/BattleStacksController.h @@ -67,6 +67,9 @@ class BattleStacksController /// //TODO: move it to battle callback std::map stackFacingRight; + /// Stacks have amount box hidden due to ongoing animations + std::set stackAmountBoxHidden; + /// currently active stack; nullptr - no one const CStack *activeStack; diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index f8270ec15..b9d308764 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -541,11 +541,8 @@ void BattleWindow::blockUI(bool on) w->block(on || owner.tacticsMode); if(auto w = widget("alternativeAction")) w->block(on || owner.tacticsMode); - - // block only if during enemy turn and auto-fight is off - // otherwise - crash on accessing non-exisiting active stack - if(auto w = widget("options")) - w->block(!owner.curInt->isAutoFightOn && !owner.stacksController->getActiveStack()); + if(auto w = widget("autofight")) + w->block(owner.actionsController->spellcastingModeActive()); auto btactEnd = widget("tacticEnd"); auto btactNext = widget("tacticNext"); diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index daaff2062..5cc58e86b 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -297,7 +297,14 @@ std::shared_ptr InterfaceObjectConfigurable::buildToggleButton(co button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer()); } if(!config["callback"].isNull()) - button->addCallback(callbacks.at(config["callback"].String())); + { + std::string callbackName = config["callback"].String(); + + if (callbacks.count(callbackName)) + button->addCallback(callbacks.at(callbackName)); + else + logGlobal->error("Invalid callback '%s' in widget", callbackName ); + } return button; } diff --git a/client/gui/NotificationHandler.cpp b/client/gui/NotificationHandler.cpp index f539c4729..14aa36ecf 100644 --- a/client/gui/NotificationHandler.cpp +++ b/client/gui/NotificationHandler.cpp @@ -12,9 +12,9 @@ #include "NotificationHandler.h" #include #include -#include #if defined(VCMI_WINDOWS) +#include #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers // Windows Header Files: diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index da9f7027d..86d67dcff 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -98,8 +98,13 @@ void MapTileStorage::load(size_t index, const std::string & filename, EImageBlit for(auto & entry : terrainAnimations) { - entry = std::make_unique(filename); - entry->preload(); + if (!filename.empty()) + { + entry = std::make_unique(filename); + entry->preload(); + } + else + entry = std::make_unique(); for(size_t i = 0; i < entry->size(); ++i) entry->getImage(i)->setBlitMode(blitMode); diff --git a/client/mapView/MapView.cpp b/client/mapView/MapView.cpp index 730ec0b6b..a666e23fa 100644 --- a/client/mapView/MapView.cpp +++ b/client/mapView/MapView.cpp @@ -66,16 +66,18 @@ void BasicMapView::render(Canvas & target, bool fullUpdate) void BasicMapView::show(SDL_Surface * to) { - controller->update(GH.mainFPSmng->getElapsedMilliseconds()); + controller->updateBefore(GH.mainFPSmng->getElapsedMilliseconds()); Canvas target(to); CSDL_Ext::CClipRectGuard guard(to, pos); render(target, false); + + controller->updateAfter(GH.mainFPSmng->getElapsedMilliseconds()); } void BasicMapView::showAll(SDL_Surface * to) { - controller->update(0); + controller->updateBefore(0); Canvas target(to); CSDL_Ext::CClipRectGuard guard(to, pos); diff --git a/client/mapView/MapViewController.cpp b/client/mapView/MapViewController.cpp index c8b6599c7..66427114d 100644 --- a/client/mapView/MapViewController.cpp +++ b/client/mapView/MapViewController.cpp @@ -88,7 +88,7 @@ std::shared_ptr MapViewController::getContext() const return context; } -void MapViewController::update(uint32_t timeDelta) +void MapViewController::updateBefore(uint32_t timeDelta) { // confirmed to match H3 for // - hero embarking on boat (500 ms) @@ -116,56 +116,32 @@ void MapViewController::update(uint32_t timeDelta) settings["adventure"]["enemyMoveTime"].Float(); movementContext->progress += timeDelta / heroMoveTime; + movementContext->progress = std::min( 1.0, movementContext->progress); Point positionFrom = Point(hero->convertToVisitablePos(movementContext->tileFrom)) * model->getSingleTileSize() + model->getSingleTileSize() / 2; Point positionDest = Point(hero->convertToVisitablePos(movementContext->tileDest)) * model->getSingleTileSize() + model->getSingleTileSize() / 2; Point positionCurr = vstd::lerp(positionFrom, positionDest, movementContext->progress); - if(movementContext->progress >= 1.0) - { - setViewCenter(hero->getSightCenter()); - - removeObject(context->getObject(movementContext->target)); - addObject(context->getObject(movementContext->target)); - - activateAdventureContext(movementContext->animationTime); - } - else - { - setViewCenter(positionCurr, movementContext->tileDest.z); - } + setViewCenter(positionCurr, movementContext->tileDest.z); } if(teleportContext) { teleportContext->progress += timeDelta / heroTeleportDuration; - if(teleportContext->progress >= 1.0) - { - activateAdventureContext(teleportContext->animationTime); - } + teleportContext->progress = std::min( 1.0, teleportContext->progress); } if(fadingOutContext) { fadingOutContext->progress -= timeDelta / fadeOutDuration; - - if(fadingOutContext->progress <= 0.0) - { - removeObject(context->getObject(fadingOutContext->target)); - - activateAdventureContext(fadingOutContext->animationTime); - } + fadingOutContext->progress = std::max( 0.0, fadingOutContext->progress); } if(fadingInContext) { fadingInContext->progress += timeDelta / fadeInDuration; - - if(fadingInContext->progress >= 1.0) - { - activateAdventureContext(fadingInContext->animationTime); - } + fadingInContext->progress = std::min( 1.0, fadingInContext->progress); } if(adventureContext) @@ -180,6 +156,48 @@ void MapViewController::update(uint32_t timeDelta) } } +void MapViewController::updateAfter(uint32_t timeDelta) +{ + if(movementContext) + { + const auto * object = context->getObject(movementContext->target); + const auto * hero = dynamic_cast(object); + const auto * boat = dynamic_cast(object); + + assert(boat || hero); + + if(!hero) + hero = boat->hero; + + if(movementContext->progress >= 1.0) + { + setViewCenter(hero->getSightCenter()); + + removeObject(context->getObject(movementContext->target)); + addObject(context->getObject(movementContext->target)); + + activateAdventureContext(movementContext->animationTime); + } + } + + if(teleportContext && teleportContext->progress >= 1.0) + { + activateAdventureContext(teleportContext->animationTime); + } + + if(fadingOutContext && fadingOutContext->progress <= 0.0) + { + removeObject(context->getObject(fadingOutContext->target)); + + activateAdventureContext(fadingOutContext->animationTime); + } + + if(fadingInContext && fadingInContext->progress >= 1.0) + { + activateAdventureContext(fadingInContext->animationTime); + } +} + bool MapViewController::isEventVisible(const CGObjectInstance * obj) { if(adventureContext == nullptr) diff --git a/client/mapView/MapViewController.h b/client/mapView/MapViewController.h index e705f5a4f..5cdd2acf3 100644 --- a/client/mapView/MapViewController.h +++ b/client/mapView/MapViewController.h @@ -83,7 +83,8 @@ public: void setViewCenter(const int3 & position); void setViewCenter(const Point & position, int level); void setTileSize(const Point & tileSize); - void update(uint32_t timeDelta); + void updateBefore(uint32_t timeDelta); + void updateAfter(uint32_t timeDelta); void activateAdventureContext(uint32_t animationTime); void activateAdventureContext(); diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 63030010a..332bc5544 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -727,6 +727,7 @@ void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuil switch(subID) { case BuildingSubID::NONE: + enterBuilding(building); break; case BuildingSubID::MYSTIC_POND: diff --git a/client/windows/settings/GeneralOptionsTab.cpp b/client/windows/settings/GeneralOptionsTab.cpp index ea124f0f4..da5886e1f 100644 --- a/client/windows/settings/GeneralOptionsTab.cpp +++ b/client/windows/settings/GeneralOptionsTab.cpp @@ -142,6 +142,13 @@ GeneralOptionsTab::GeneralOptionsTab() std::shared_ptr compactTownCreatureInfo = widget("compactTownCreatureInfoCheckbox"); compactTownCreatureInfo->setSelected(settings["gameTweaks"]["compactTownCreatureInfo"].Bool()); + + std::shared_ptr musicVolumeLabel = widget("musicValueLabel"); + musicVolumeLabel->setText(std::to_string(CCS->musich->getVolume()) + "%"); + + std::shared_ptr soundVolumeLabel = widget("soundValueLabel"); + musicVolumeLabel->setText(std::to_string(CCS->soundh->getVolume()) + "%"); + } diff --git a/config/bonuses.json b/config/bonuses.json index ff077c3b1..a59e97224 100644 --- a/config/bonuses.json +++ b/config/bonuses.json @@ -431,22 +431,6 @@ } }, - "SELF_LUCK": - { - "graphics": - { - "icon": "zvs/Lib1.res/SelfLuck" - } - }, - - "SELF_MORALE": - { - "graphics": - { - "icon": "zvs/Lib1.res/E_MINOT" - } - }, - "SHOOTER": { "graphics": diff --git a/config/heroClasses.json b/config/heroClasses.json index 103931da2..cf4f65d01 100644 --- a/config/heroClasses.json +++ b/config/heroClasses.json @@ -104,7 +104,7 @@ "index": 10, "faction" : "dungeon", "defaultTavern" : 5, - "affinity" : "might", + "affinity" : "magic", "commander" : "medusaQueen", "mapObject" : { "templates" : { "default" : { "animation" : "AH11_.def", "editorAnimation": "AH11_E.def" } } }, "animation": { "battle" : { "male" : "CH010.DEF", "female" : "CH11.DEF" } } @@ -114,7 +114,7 @@ "index": 11, "faction" : "dungeon", "defaultTavern" : 5, - "affinity" : "magic", + "affinity" : "might", "commander" : "medusaQueen", "mapObject" : { "templates" : { "default" : { "animation" : "AH10_.def", "editorAnimation": "AH10_E.def" } } }, "animation": { "battle" : { "male" : "CH010.DEF", "female" : "CH11.DEF" } } diff --git a/config/heroes/special.json b/config/heroes/special.json index a46cf317b..e26c2ff58 100644 --- a/config/heroes/special.json +++ b/config/heroes/special.json @@ -214,7 +214,7 @@ { "skill" : "armorer", "level": "basic" } ], "specialty" : { - "creature" : "griffin" + "creature" : "swordsman" } }, "mutareDrake": diff --git a/config/objects/generic.json b/config/objects/generic.json index 5ec2d8c49..9838ef0ba 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -215,7 +215,7 @@ "handler" : "whirlpool", "base" : { "sounds" : { - "ambient" : ["LOOPWHIRL"], + "ambient" : ["LOOPWHIR"], "visit" : ["DANGER"] } }, diff --git a/config/schemas/mod.json b/config/schemas/mod.json index bcc7ec933..ce1113aaf 100644 --- a/config/schemas/mod.json +++ b/config/schemas/mod.json @@ -18,10 +18,6 @@ "type":"string", "description": "More lengthy description of mod. No hard limit" }, - "modType" : { - "type":"string", - "description": "Type of mod, e.g. Town, Artifacts, Graphical." - }, "author" : { "type":"string", "description": "Author of the mod. Can be nickname, real name or name of team" @@ -60,6 +56,7 @@ "modType" : { "type":"string", + "enum" : [ "Translation", "Town", "Test", "Templates", "Spells", "Music", "Sounds", "Skills", "Other", "Objects", "Mechanics", "Interface", "Heroes", "Graphical", "Expansion", "Creatures", "Artifacts", "AI" ], "description": "Type of mod, e.g. Town, Artifacts, Graphical." }, diff --git a/config/schemas/obstacle.json b/config/schemas/obstacle.json index 08e7a2e8c..66b204cc9 100644 --- a/config/schemas/obstacle.json +++ b/config/schemas/obstacle.json @@ -32,7 +32,11 @@ }, "animation": { "type": "string", - "description": "Image resource" + "description": "Image resource", + "anyOf" : [ + { "format" : "defFile" }, + { "format" : "imageFile" } + ] }, "unknown": { "type": "number", diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 125857289..2ba733702 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -335,7 +335,7 @@ }, "playerAI" : { "type" : "string", - "default" : "VCAI" + "default" : "Nullkiller" }, "friendlyAI" : { "type" : "string", diff --git a/config/spells/other.json b/config/spells/other.json index dfd7c8412..a0d9c7620 100644 --- a/config/spells/other.json +++ b/config/spells/other.json @@ -90,8 +90,7 @@ "damage":{ "type":"core:damage", "optional":false, - "indirect":true, - "customEffectId" : 82 + "indirect":true } } }, diff --git a/launcher/firstLaunch/firstlaunch_moc.cpp b/launcher/firstLaunch/firstlaunch_moc.cpp index b9df75f56..af334374a 100644 --- a/launcher/firstLaunch/firstlaunch_moc.cpp +++ b/launcher/firstLaunch/firstlaunch_moc.cpp @@ -91,7 +91,7 @@ void FirstLaunchView::on_pushButtonDataCopy_clicked() void FirstLaunchView::on_pushButtonDataHelp_clicked() { - static const QUrl vcmibuilderWiki("https://wiki.vcmi.eu/Installation_on_Linux#Installing_Heroes_III_data_files"); + static const QUrl vcmibuilderWiki("https://wiki.vcmi.eu/Using_vcmibuilder"); QDesktopServices::openUrl(vcmibuilderWiki); } @@ -232,9 +232,10 @@ bool FirstLaunchView::heroesDataDetect() CResourceHandler::load("config/filesystem.json"); // use file from lod archive to check presence of H3 data. Very rough estimate, but will work in majority of cases - bool heroesDataFound = CResourceHandler::get()->existsResource(ResourceID("DATA/GENRLTXT.TXT")); + bool heroesDataFoundROE = CResourceHandler::get()->existsResource(ResourceID("DATA/GENRLTXT.TXT")); + bool heroesDataFoundSOD = CResourceHandler::get()->existsResource(ResourceID("DATA/TENTCOLR.TXT")); - return heroesDataFound; + return heroesDataFoundROE && heroesDataFoundSOD; } void FirstLaunchView::heroesLanguageUpdate() @@ -267,16 +268,46 @@ void FirstLaunchView::copyHeroesData() QStringList dirMaps = sourceRoot.entryList({"maps"}, QDir::Filter::Dirs); QStringList dirMp3 = sourceRoot.entryList({"mp3"}, QDir::Filter::Dirs); - if(dirData.empty() || dirMaps.empty() || dirMp3.empty()) + if(dirData.empty()) + { + QMessageBox::critical(this, "Heroes III data not found!", "Failed to detect valid Heroes III data in chosen directory.\nPlease select directory with installed Heroes III data."); return; + } QDir sourceData = sourceRoot.filePath(dirData.front()); - QStringList lodArchives = sourceData.entryList({"*.lod"}, QDir::Filter::Files); + QStringList roeFiles = sourceData.entryList({"*.lod"}, QDir::Filter::Files); + QStringList sodFiles = sourceData.entryList({"H3ab*.lod"}, QDir::Filter::Files); + QStringList hdFiles = sourceData.entryList({"*.pak"}, QDir::Filter::Files); - if(lodArchives.empty()) + if(sodFiles.empty()) + { + if (roeFiles.empty()) + { + // Directory structure is correct (Data/Maps/Mp3) but no .lod archives that should be present in any install + QMessageBox::critical(this, "Heroes III data not found!", "Failed to detect valid Heroes III data in chosen directory.\nPlease select directory with installed Heroes III data."); + return; + } + + if (!hdFiles.empty()) + { + // HD Edition contains only RoE data so we can't use even unmodified files from it + QMessageBox::critical(this, "Heroes III data not found!", "Heroes III: HD Edition files are not supported by VCMI.\nPlease select directory with Heroes III: Complete Edition or Heroes III: Shadow of Death."); + return; + } + + // RoE or some other unsupported edition. Demo version? + QMessageBox::critical(this, "Heroes III data not found!", "Unknown or unsupported Heroes III version found.\nPlease select directory with Heroes III: Complete Edition or Heroes III: Shadow of Death."); return; + } - QStringList copyDirectories = {dirData.front(), dirMaps.front(), dirMp3.front()}; + QStringList copyDirectories; + + copyDirectories += dirData.front(); + if (!dirMaps.empty()) + copyDirectories += dirMaps.front(); + + if (!dirMp3.empty()) + copyDirectories += dirMp3.front(); QDir targetRoot = pathToQString(VCMIDirs::get().userDataPath()); @@ -310,6 +341,10 @@ void FirstLaunchView::modPresetUpdate() ui->checkBoxPresetExtras->setEnabled(checkCanInstallExtras()); ui->checkBoxPresetHota->setEnabled(checkCanInstallHota()); ui->checkBoxPresetWog->setEnabled(checkCanInstallWog()); + + // we can't install anything - either repository checkout is off or all recommended mods are already installed + if (!checkCanInstallTranslation() && !checkCanInstallExtras() && !checkCanInstallHota() && !checkCanInstallWog()) + exitSetup(); } QString FirstLaunchView::findTranslationModName() @@ -364,7 +399,7 @@ CModListView * FirstLaunchView::getModView() bool FirstLaunchView::checkCanInstallMod(const QString & modID) { - return getModView() && !getModView()->isModInstalled(modID); + return getModView() && getModView()->isModAvailable(modID); } void FirstLaunchView::on_pushButtonPresetBack_clicked() @@ -376,16 +411,16 @@ void FirstLaunchView::on_pushButtonPresetNext_clicked() { QStringList modsToInstall; - if (ui->checkBoxPresetLanguage && checkCanInstallTranslation()) + if (ui->checkBoxPresetLanguage->isChecked() && checkCanInstallTranslation()) modsToInstall.push_back(findTranslationModName()); - if (ui->checkBoxPresetExtras && checkCanInstallExtras()) + if (ui->checkBoxPresetExtras->isChecked() && checkCanInstallExtras()) modsToInstall.push_back("vcmi-extras"); - if (ui->checkBoxPresetWog && checkCanInstallWog()) + if (ui->checkBoxPresetWog->isChecked() && checkCanInstallWog()) modsToInstall.push_back("wake-of-gods"); - if (ui->checkBoxPresetHota && checkCanInstallHota()) + if (ui->checkBoxPresetHota->isChecked() && checkCanInstallHota()) modsToInstall.push_back("hota"); exitSetup(); @@ -394,3 +429,18 @@ void FirstLaunchView::on_pushButtonPresetNext_clicked() getModView()->doInstallMod(modName); } +void FirstLaunchView::on_pushButtonDiscord_clicked() +{ + QDesktopServices::openUrl(QUrl("https://discord.gg/chBT42V")); +} + +void FirstLaunchView::on_pushButtonSlack_clicked() +{ + QDesktopServices::openUrl(QUrl("https://slack.vcmi.eu/")); +} + +void FirstLaunchView::on_pushButtonGithub_clicked() +{ + QDesktopServices::openUrl(QUrl("https://github.com/vcmi/vcmi")); +} + diff --git a/launcher/firstLaunch/firstlaunch_moc.h b/launcher/firstLaunch/firstlaunch_moc.h index 340a98311..f2ca56eb0 100644 --- a/launcher/firstLaunch/firstlaunch_moc.h +++ b/launcher/firstLaunch/firstlaunch_moc.h @@ -93,6 +93,12 @@ private slots: void on_pushButtonPresetNext_clicked(); + void on_pushButtonDiscord_clicked(); + + void on_pushButtonSlack_clicked(); + + void on_pushButtonGithub_clicked(); + private: Ui::FirstLaunchView * ui; diff --git a/launcher/firstLaunch/firstlaunch_moc.ui b/launcher/firstLaunch/firstlaunch_moc.ui index a120ada53..9c56697aa 100644 --- a/launcher/firstLaunch/firstlaunch_moc.ui +++ b/launcher/firstLaunch/firstlaunch_moc.ui @@ -6,8 +6,8 @@ 0 0 - 650 - 409 + 745 + 389 @@ -115,7 +115,7 @@ - 1 + 0 @@ -131,6 +131,9 @@ 0 + + 6 + @@ -144,27 +147,14 @@ - - - - Next + + + + 5 - - - Qt::Horizontal - - - - 40 - 20 - - - - - Qt::Vertical @@ -172,19 +162,90 @@ 20 - 40 + 8 - - - - 5 + + + + + + VCMI on Github + + + + + + + VCMI on Slack + + + + + + + VCMI on Discord + + + + + + + Have a question? Found a bug? Want to help? Join us: + + + + + + + + + Thanks for installing VCMI. + +There are a few more steps to be done before you can start playing. + +Keep in mind that in order to use VCMI you need to own original data files, Heroes III: Complete Edition or Shadow of Death. + +Heroes III: HD Edition is currently not supported + + + true + + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Next + + + + + + labelLanguageWelcome + labelLanguageTitle + listWidgetLanguage @@ -539,24 +600,8 @@ - - - - - - 100 - 0 - - - - Optionally, you can install additional mods either now or at any point later: - - - true - - - - + + @@ -572,7 +617,29 @@ - + + + + + 100 + 0 + + + + + 75 + true + + + + Horn of the Abyss + + + true + + + + @@ -580,15 +647,21 @@ 0 + + + 75 + true + + - Install translation of Heroes III to your language + Heroes III Translation true - + @@ -604,8 +677,110 @@ - + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + 100 + 0 + + + + + 75 + true + + + + High Definition Support + + + true + + + + + + + + 100 + 0 + + + + + 75 + true + + + + In The Wake of Gods + + + true + + + + + + + + 100 + 0 + + + + Install translation of Heroes III to your language + + + true + + + + + + + + 100 + 0 + + + + Optionally, you can install additional mods either now or at any point later: + + + true + + + + + 100 @@ -620,21 +795,8 @@ - - - - - 0 - 0 - - - - - - - - - + + 100 @@ -649,21 +811,8 @@ - - - - - 0 - 0 - - - - - - - - - + + 100 diff --git a/launcher/modManager/cmodlist.cpp b/launcher/modManager/cmodlist.cpp index cc52a8bb6..996ed5a74 100644 --- a/launcher/modManager/cmodlist.cpp +++ b/launcher/modManager/cmodlist.cpp @@ -316,7 +316,6 @@ CModEntry CModList::getMod(QString modname) const settings["active"] = false; } - for(auto entry : repositories) { QVariant repoVal = getValue(entry, path); diff --git a/launcher/modManager/cmodlistmodel_moc.cpp b/launcher/modManager/cmodlistmodel_moc.cpp index 1c58dfef4..2a0a8ebc3 100644 --- a/launcher/modManager/cmodlistmodel_moc.cpp +++ b/launcher/modManager/cmodlistmodel_moc.cpp @@ -12,19 +12,6 @@ #include -namespace ModFields -{ -static const QString names[ModFields::COUNT] = -{ - "name", - "", - "", - "modType", - "version", -}; - -} - namespace ModStatus { static const QString iconDelete = "icons:mod-delete.png"; @@ -48,18 +35,59 @@ QString CModListModel::modIndexToName(const QModelIndex & index) const return ""; } + +QString CModListModel::modTypeName(QString modTypeID) const +{ + static QMap modTypes = { + {"Translation", tr("Translation")}, + {"Town", tr("Town") }, + {"Test", tr("Test") }, + {"Templates", tr("Templates") }, + {"Spells", tr("Spells") }, + {"Music", tr("Music") }, + {"Sounds", tr("Sounds") }, + {"Skills", tr("Skills") }, + {"Other", tr("Other") }, + {"Objects", tr("Objects") }, + {"Mechanical", tr("Mechanics") }, + {"Mechanics", tr("Mechanics") }, + {"Themes", tr("Interface") }, + {"Interface", tr("Interface") }, + {"Heroes", tr("Heroes") }, + {"Graphic", tr("Graphical") }, + {"Graphical", tr("Graphical") }, + {"Expansion", tr("Expansion") }, + {"Creatures", tr("Creatures") }, + {"Artifacts", tr("Artifacts") }, + {"AI", tr("AI") }, + }; + + if (modTypes.contains(modTypeID)) + return modTypes[modTypeID]; + return tr("Other"); +} + QVariant CModListModel::getValue(const CModEntry & mod, int field) const { switch(field) { - case ModFields::STATUS_ENABLED: - return mod.getModStatus() & (ModStatus::ENABLED | ModStatus::INSTALLED); + case ModFields::STATUS_ENABLED: + return mod.getModStatus() & (ModStatus::ENABLED | ModStatus::INSTALLED); - case ModFields::STATUS_UPDATE: - return mod.getModStatus() & (ModStatus::UPDATEABLE | ModStatus::INSTALLED); + case ModFields::STATUS_UPDATE: + return mod.getModStatus() & (ModStatus::UPDATEABLE | ModStatus::INSTALLED); - default: - return mod.getValue(ModFields::names[field]); + case ModFields::NAME: + return mod.getValue("name"); + + case ModFields::VERSION: + return mod.getValue("version"); + + case ModFields::TYPE: + return modTypeName(mod.getValue("modType").toString()); + + default: + return QVariant(); } } diff --git a/launcher/modManager/cmodlistmodel_moc.h b/launcher/modManager/cmodlistmodel_moc.h index 5fe3c0f3b..2c43485c9 100644 --- a/launcher/modManager/cmodlistmodel_moc.h +++ b/launcher/modManager/cmodlistmodel_moc.h @@ -48,6 +48,7 @@ class CModListModel : public QAbstractItemModel, public CModList void endResetModel(); QString modIndexToName(const QModelIndex & index) const; + QString modTypeName(QString modTypeID) const; QVariant getTextAlign(int field) const; QVariant getValue(const CModEntry & mod, int field) const; diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index 679a8a5e8..ef654c7b5 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -24,6 +24,7 @@ #include "../jsonutils.h" #include "../../lib/CConfigHandler.h" +#include "../../lib/Languages.h" void CModListView::setupModModel() { @@ -74,8 +75,9 @@ void CModListView::setupModsView() ui->allModsView->setColumnWidth(ModFields::TYPE, 75); ui->allModsView->setColumnWidth(ModFields::VERSION, 60); } - ui->allModsView->setColumnWidth(ModFields::STATUS_ENABLED, 24); - ui->allModsView->setColumnWidth(ModFields::STATUS_UPDATE, 24); + + ui->allModsView->resizeColumnToContents(ModFields::STATUS_ENABLED); + ui->allModsView->resizeColumnToContents(ModFields::STATUS_UPDATE); ui->allModsView->setUniformRowHeights(true); @@ -212,6 +214,25 @@ QString CModListView::genChangelogText(CModEntry & mod) return result; } +QStringList CModListView::getModNames(QStringList input) +{ + QStringList result; + + for(const auto & modID : input) + { + auto mod = modModel->getMod(modID); + + QString modName = mod.getValue("name").toString(); + + if (modName.isEmpty()) + result += modID; + else + result += modName; + } + + return result; +} + QString CModListView::genModInfoText(CModEntry & mod) { QString prefix = "

%1: "; // shared prefix @@ -221,7 +242,6 @@ QString CModListView::genModInfoText(CModEntry & mod) QString textTemplate = prefix + "

%2

"; QString listTemplate = "

%1: %2

"; QString noteTemplate = "

%1

"; - QString compatibleString = prefix + tr("Mod is compatible") + "

"; QString incompatibleString = redPrefix + tr("Mod is incompatible") + "

"; QString supportedVersions = redPrefix + "%2 %3 %4

"; @@ -242,9 +262,7 @@ QString CModListView::genModInfoText(CModEntry & mod) result += urlTemplate.arg(tr("Contact")).arg(mod.getValue("contact").toString()).arg(mod.getValue("contact").toString()); //compatibility info - if(mod.isCompatible()) - result += compatibleString.arg(tr("Compatibility")); - else + if(!mod.isCompatible()) { auto compatibilityInfo = mod.getValue("compatibility").toMap(); auto minStr = compatibilityInfo.value("min").toString(); @@ -267,9 +285,34 @@ QString CModListView::genModInfoText(CModEntry & mod) } } - result += replaceIfNotEmpty(mod.getValue("depends"), lineTemplate.arg(tr("Required mods"))); - result += replaceIfNotEmpty(mod.getValue("conflicts"), lineTemplate.arg(tr("Conflicting mods"))); - result += replaceIfNotEmpty(mod.getValue("description"), textTemplate.arg(tr("Description"))); + QStringList supportedLanguages; + QVariant baseLanguageVariant = mod.getBaseValue("language"); + QString baseLanguageID = baseLanguageVariant.isValid() ? baseLanguageVariant.toString() : "english"; + + bool needToShowSupportedLanguages = false; + + for(const auto & language : Languages::getLanguageList()) + { + if (!language.hasTranslation) + continue; + + QString languageID = QString::fromStdString(language.identifier); + + if (languageID != baseLanguageID && !mod.getValue(languageID).isValid()) + continue; + + if (languageID != baseLanguageID) + needToShowSupportedLanguages = true; + + supportedLanguages += QApplication::translate("Language", language.nameEnglish.c_str()); + } + + if(needToShowSupportedLanguages) + result += replaceIfNotEmpty(supportedLanguages, lineTemplate.arg(tr("Languages"))); + + result += replaceIfNotEmpty(getModNames(mod.getValue("depends").toStringList()), lineTemplate.arg(tr("Required mods"))); + result += replaceIfNotEmpty(getModNames(mod.getValue("conflicts").toStringList()), lineTemplate.arg(tr("Conflicting mods"))); + result += replaceIfNotEmpty(getModNames(mod.getValue("description").toStringList()), textTemplate.arg(tr("Description"))); result += "

"; // to get some empty space @@ -281,12 +324,12 @@ QString CModListView::genModInfoText(CModEntry & mod) QString notes; - notes += replaceIfNotEmpty(findInvalidDependencies(mod.getName()), listTemplate.arg(unknownDeps)); - notes += replaceIfNotEmpty(findBlockingMods(mod.getName()), listTemplate.arg(blockingMods)); + notes += replaceIfNotEmpty(getModNames(findInvalidDependencies(mod.getName())), listTemplate.arg(unknownDeps)); + notes += replaceIfNotEmpty(getModNames(findBlockingMods(mod.getName())), listTemplate.arg(blockingMods)); if(mod.isEnabled()) - notes += replaceIfNotEmpty(findDependentMods(mod.getName(), true), listTemplate.arg(hasActiveDependentMods)); + notes += replaceIfNotEmpty(getModNames(findDependentMods(mod.getName(), true)), listTemplate.arg(hasActiveDependentMods)); if(mod.isInstalled()) - notes += replaceIfNotEmpty(findDependentMods(mod.getName(), false), listTemplate.arg(hasDependentMods)); + notes += replaceIfNotEmpty(getModNames(findDependentMods(mod.getName(), false)), listTemplate.arg(hasDependentMods)); if(mod.getName().contains('.')) notes += noteTemplate.arg(thisIsSubmod); @@ -829,10 +872,10 @@ void CModListView::doInstallMod(const QString & modName) } } -bool CModListView::isModInstalled(const QString & modName) +bool CModListView::isModAvailable(const QString & modName) { auto mod = modModel->getMod(modName); - return mod.isInstalled(); + return mod.isAvailable(); } bool CModListView::isModEnabled(const QString & modName) diff --git a/launcher/modManager/cmodlistview_moc.h b/launcher/modManager/cmodlistview_moc.h index 7b7566339..7b3b0f3dc 100644 --- a/launcher/modManager/cmodlistview_moc.h +++ b/launcher/modManager/cmodlistview_moc.h @@ -47,6 +47,9 @@ class CModListView : public QWidget void checkManagerErrors(); + /// replace mod ID's with proper human-readable mod names + QStringList getModNames(QStringList input); + // find mods unknown to mod list (not present in repo and not installed) QStringList findInvalidDependencies(QString mod); // find mods that block enabling of this mod: conflicting with this mod or one of required mods @@ -86,8 +89,8 @@ public: /// install mod by name void doInstallMod(const QString & modName); - /// returns true if mod is currently installed - bool isModInstalled(const QString & modName); + /// returns true if mod is available in repository and can be installed + bool isModAvailable(const QString & modName); /// finds translation mod for specified languages. Returns empty string on error QString getTranslationModName(const QString & language); diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 3f464f688..b304f3ea8 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -375,7 +375,7 @@ void CSettingsView::loadTranslation() if (!translationExists) return; - bool translationInstalled = mainWindow->getModView()->isModInstalled(modName); + bool translationAvailable = mainWindow->getModView()->isModAvailable(modName); bool translationEnabled = mainWindow->getModView()->isModEnabled(modName); ui->pushButtonTranslation->setVisible(!translationEnabled); @@ -385,13 +385,13 @@ void CSettingsView::loadTranslation() ui->labelTranslationStatus->setText(tr("Active")); } - if (translationInstalled && !translationEnabled) + if (!translationEnabled && !translationAvailable) { ui->labelTranslationStatus->setText(tr("Disabled")); ui->pushButtonTranslation->setText(tr("Enable")); } - if (!translationInstalled) + if (translationAvailable) { ui->labelTranslationStatus->setText(tr("Not Installed")); ui->pushButtonTranslation->setText(tr("Install")); @@ -413,15 +413,15 @@ void CSettingsView::on_pushButtonTranslation_clicked() if (modName.isEmpty()) return; - if (mainWindow->getModView()->isModInstalled(modName)) - { - mainWindow->getModView()->enableModByName(modName); - } - else + if (mainWindow->getModView()->isModAvailable(modName)) { mainWindow->switchToModsTab(); mainWindow->getModView()->doInstallMod(modName); } + else + { + mainWindow->getModView()->enableModByName(modName); + } } void CSettingsView::on_comboBoxLanguageBase_currentIndexChanged(int index) diff --git a/launcher/translation/english.ts b/launcher/translation/english.ts index d3bd72292..9de39fd25 100644 --- a/launcher/translation/english.ts +++ b/launcher/translation/english.ts @@ -4,17 +4,111 @@ CModListModel - + + Translation + + + + + Town + + + + + Test + + + + + Templates + + + + + Spells + + + + + Music + + + + + Sounds + + + + + Skills + + + + + + Other + + + + + Objects + + + + + + Mechanics + + + + + + Interface + + + + + Heroes + + + + + + Graphical + + + + + Expansion + + + + + Creatures + + + + + Artifacts + + + + + AI + + + + Name - + Type - + Version @@ -63,7 +157,7 @@ - + Description @@ -113,114 +207,113 @@ - + Mod name - + Installed version - + Latest version - + Download size - + Authors - + License - + Contact - - + Compatibility - - + + Required VCMI version - + Supported VCMI version - + Supported VCMI versions - + + Languages + + + + Required mods - + Conflicting mods - + This mod can not be installed or enabled because following dependencies are not present - + This mod can not be enabled because following mods are incompatible with this mod - + This mod can not be disabled because it is required to run following mods - + This mod can not be uninstalled or updated because it is required to run following mods - + This is submod and it can not be installed or uninstalled separately from parent mod - + Notes - + Screenshot %1 - - Mod is compatible - - - - + Mod is incompatible @@ -449,32 +542,32 @@ - + Your Heroes III data files have been successfully found. - + Optionally, you can install additional mods either now or at any point later: - + Install support for playing Heroes III in resolutions other than 800x600. - + Install compatible version of addon Horn of the Abyss: fan-made Heroes III expansion, ported by VCMI team - + Install compatible version of addon "In The Wake of Gods": fan-made Heroes III expansion - + Finish @@ -484,84 +577,135 @@ - + Choose your language - - + + VCMI on Github + + + + + VCMI on Slack + + + + + VCMI on Discord + + + + + Have a question? Found a bug? Want to help? Join us: + + + + + Thanks for installing VCMI. + +There are a few more steps to be done before you can start playing. + +Keep in mind that in order to use VCMI you need to own original data files, Heroes III: Complete Edition or Shadow of Death. + +Heroes III: HD Edition is currently not supported + + + + + Next - + Find Heroes III data files - + Open help in browser - + Search again - + If you don't have installed Heroes III copy, it is possible to use our automatic installation tool 'vcmibuilder' to extract data from GoG.com installer. Visit our wiki for detailed instructions. - + VCMI requires Heroes III data files in one of the locations listed above. Please copy Heroes III data in one of these directories. - + Heroes III data files - + Alternatively, you can select directory with installed Heroes III data and VCMI will copy exisiting data automatically. - + Copy existing data - + Your Heroes III language has been successfully detected. - + Automatic detection of language failed. Please select language of your Heroes III copy - + Heroes III language - - + + Back - + Install VCMI Mod Preset - + + Horn of the Abyss + + + + + Heroes III Translation + + + + + High Definition Support + + + + + In The Wake of Gods + + + + Install translation of Heroes III to your language diff --git a/launcher/translation/german.ts b/launcher/translation/german.ts index ad4592135..32f2109a2 100644 --- a/launcher/translation/german.ts +++ b/launcher/translation/german.ts @@ -4,17 +4,111 @@ CModListModel - + + Translation + Übersetzung + + + + Town + Stadt + + + + Test + Test + + + + Templates + Templates + + + + Spells + Zaubersprüche + + + + Music + Musik + + + + Sounds + Sounds + + + + Skills + Fertigkeiten + + + + + Other + Andere + + + + Objects + Objekte + + + + + Mechanics + Mechaniken + + + + + Interface + Schnittstelle + + + + Heroes + Helden + + + + + Graphical + Grafisches + + + + Expansion + Erweiterung + + + + Creatures + Kreaturen + + + + Artifacts + Artefakte + + + + AI + KI + + + Name Name - + Type Typ - + Version Version @@ -63,7 +157,7 @@ - + Description Beschreibung @@ -113,114 +207,113 @@ Abbrechen - + Mod name Mod-Name - + Installed version Installierte Version - + Latest version Letzte Version - + Download size Downloadgröße - + Authors Autoren - + License Lizenz - + Contact - + Kontakt - - + Compatibility Kompatibilität - - + + Required VCMI version Benötigte VCMI Version - + Supported VCMI version Unterstützte VCMI Version - + Supported VCMI versions Unterstützte VCMI Versionen - + + Languages + Sprachen + + + Required mods Benötigte Mods - + Conflicting mods Mods mit Konflikt - + This mod can not be installed or enabled because following dependencies are not present Diese Mod kann nicht installiert oder aktiviert werden, da die folgenden Abhängigkeiten nicht vorhanden sind - + This mod can not be enabled because following mods are incompatible with this mod Diese Mod kann nicht aktiviert werden, da folgende Mods nicht mit dieser Mod kompatibel sind - + This mod can not be disabled because it is required to run following mods Diese Mod kann nicht deaktiviert werden, da sie zum Ausführen der folgenden Mods erforderlich ist - + This mod can not be uninstalled or updated because it is required to run following mods Diese Mod kann nicht deinstalliert oder aktualisiert werden, da sie für die folgenden Mods erforderlich ist - + This is submod and it can not be installed or uninstalled separately from parent mod Dies ist eine Submod und kann nicht separat von der Hauptmod installiert oder deinstalliert werden - + Notes Anmerkungen - + Screenshot %1 Screenshot %1 - - Mod is compatible - Mod ist kompatibel - - - + Mod is incompatible Mod ist inkompatibel @@ -242,7 +335,7 @@ Adventure Map AI - + Abenteuerkarte KI @@ -261,18 +354,18 @@ Artificial Intelligence - + Künstliche Intelligenz Mod Repositories - + Mod-Repositorien Update now - + Jetzt aktualisieren @@ -285,37 +378,37 @@ Cursor - + Zeiger Heroes III Data Language - + Sprache der Heroes III Daten Default - + Standard Hardware - + Hardware Software - + Software Heroes III Translation - + Heroes III Übersetzung Check on startup - + Beim Start prüfen @@ -408,27 +501,27 @@ Active - Aktiv + Aktiv Disabled - + Deaktiviert Enable - Aktivieren + Aktivieren Not Installed - + Nicht installiert Install - Installieren + Installieren @@ -436,134 +529,191 @@ Language - + Sprache Heroes III Data - + Heroes III Daten Mods Preset - + Mods Voreinstellung - + Your Heroes III data files have been successfully found. - + Ihre Heroes III-Datendateien wurden erfolgreich gefunden. - + Optionally, you can install additional mods either now or at any point later: - + Optional können Sie jetzt oder zu einem beliebigen späteren Zeitpunkt zusätzliche Mods installieren: - + Install support for playing Heroes III in resolutions other than 800x600. - + Installieren Sie Unterstützung für das Spielen von Heroes III in anderen Auflösungen als 800x600. - + Install compatible version of addon Horn of the Abyss: fan-made Heroes III expansion, ported by VCMI team - + Installieren Sie die kompatible Version des Addons Horn of the Abyss: eine von Fans entwickelte Heroes III-Erweiterung, portiert vom VCMI-Team - + Install compatible version of addon "In The Wake of Gods": fan-made Heroes III expansion - + Installieren Sie die kompatible Version des Addons "In The Wake of Gods": von Fans entwickelte Heroes III-Erweiterung - + Finish - + Fertigstellen Step %v out of %m - + Schritt %v von %m - + Choose your language - + Wählen Sie Ihre Sprache - - + + VCMI on Github + VCMI auf Github + + + + VCMI on Slack + VCMI auf Slack + + + + VCMI on Discord + VCMI auf Discord + + + + Have a question? Found a bug? Want to help? Join us: + Haben Sie eine Frage? Einen Fehler gefunden? Möchten Sie helfen? Machen Sie mit: + + + + Thanks for installing VCMI. + +There are a few more steps to be done before you can start playing. + +Keep in mind that in order to use VCMI you need to own original data files, Heroes III: Complete Edition or Shadow of Death. + +Heroes III: HD Edition is currently not supported + Vielen Dank für die Installation von VCMI. + +Es sind noch ein paar Schritte notwendig, bevor Sie mit dem Spielen beginnen können. + +Denken Sie daran, dass Sie die Originaldateien, Heroes III: Complete Edition oder Shadow of Death besitzen müssen, um VCMI verwenden zu können. + +Heroes III: HD Edition wird derzeit nicht unterstützt + + + + Next - + Weiter - + Find Heroes III data files - + Heroes III Daten suchen - + Open help in browser - - - - - Search again - - - - - If you don't have installed Heroes III copy, it is possible to use our automatic installation tool 'vcmibuilder' to extract data from GoG.com installer. Visit our wiki for detailed instructions. - + Hilfe im Browser öffnen + Search again + Erneut suchen + + + + If you don't have installed Heroes III copy, it is possible to use our automatic installation tool 'vcmibuilder' to extract data from GoG.com installer. Visit our wiki for detailed instructions. + Wenn Sie keine Kopie von Heroes III installiert haben, können Sie unser automatisches Installationstool 'vcmibuilder' verwenden, um Daten aus dem GoG.com-Installationsprogramm zu extrahieren. Besuchen Sie unser Wiki für detaillierte Anweisungen. + + + VCMI requires Heroes III data files in one of the locations listed above. Please copy Heroes III data in one of these directories. - + VCMI benötigt Heroes III Daten in einem der oben aufgeführten Verzeichnisse. Bitte kopieren Sie die Heroes III-Daten in eines dieser Verzeichnisse. - + Heroes III data files - + Heroes III Dateien - + Alternatively, you can select directory with installed Heroes III data and VCMI will copy exisiting data automatically. - + Alternativ können Sie ein Verzeichnis mit installierten Heroes III-Daten auswählen, und VCMI wird die vorhandenen Daten automatisch kopieren. - + Copy existing data - + Vorhandene Daten kopieren - + Your Heroes III language has been successfully detected. - + Ihre Heroes III-Sprache wurde erfolgreich erkannt. - + Automatic detection of language failed. Please select language of your Heroes III copy - + Automatische Erkennung der Sprache fehlgeschlagen. Bitte wählen Sie die Sprache Ihrer Heroes III Kopie - + Heroes III language - + Heroes III Sprache - - + + Back - + Zurück - + Install VCMI Mod Preset - + VCMI Mod Voreinstellung installieren - + + Horn of the Abyss + Horn of the Abyss + + + + Heroes III Translation + Heroes III Übersetzung + + + + High Definition Support + Unterstützung für hohe Auflösungen + + + + In The Wake of Gods + In The Wake of Gods + + + Install translation of Heroes III to your language - + Übersetzung von Heroes III für Ihre Sprache installieren @@ -579,67 +729,67 @@ Chinese - + Chinesisch English - English (Englisch) + Englisch French - + Französisch German - + Deutsch Korean - + Koreanisch Polish - + Polnisch Russian - + Russisch Spanish - + Spanisch Ukrainian - + Ukrainisch Other (East European) - + Sonstige (osteuropäisch) Other (Cyrillic Script) - + Sonstige (kyrillische Schrift) Other (West European) - + Sonstige (westeuropäisch) Auto (%1) - + Auto (%1) @@ -663,12 +813,12 @@ People in lobby - + Personen in der Lobby Lobby chat - + Lobby-Chat @@ -683,17 +833,17 @@ Resolve - + Auflösen New game - + Neues Spiel Load game - + Spiel laden @@ -733,12 +883,12 @@ Disconnect - + Verbindung trennen No issues detected - + Keine Probleme festgestellt @@ -789,7 +939,7 @@ Map Editor - + Karteneditor diff --git a/launcher/translation/polish.ts b/launcher/translation/polish.ts index e6bcc32fa..1424c8d87 100644 --- a/launcher/translation/polish.ts +++ b/launcher/translation/polish.ts @@ -4,17 +4,111 @@ CModListModel - + + Translation + + + + + Town + + + + + Test + + + + + Templates + + + + + Spells + + + + + Music + + + + + Sounds + + + + + Skills + + + + + + Other + + + + + Objects + + + + + + Mechanics + + + + + + Interface + + + + + Heroes + + + + + + Graphical + + + + + Expansion + + + + + Creatures + + + + + Artifacts + + + + + AI + + + + Name Nazwa - + Type Typ - + Version Wersja @@ -63,7 +157,7 @@ - + Description Opis @@ -113,114 +207,113 @@ Przerwij - + Mod name Nazwa moda - + Installed version Zainstalowana wersja - + Latest version Najnowsza wersja - + Download size Rozmiar pobierania - + Authors Autorzy - + License Licencja - + Contact Kontakt - - + Compatibility Kompatybilność - - + + Required VCMI version Wymagana wersja VCMI - + Supported VCMI version Wspierana wersja VCMI - + Supported VCMI versions Wspierane wersje VCMI - + + Languages + + + + Required mods Wymagane mody - + Conflicting mods Konfliktujące mody - + This mod can not be installed or enabled because following dependencies are not present Ten mod nie może zostać zainstalowany lub włączony ponieważ następujące zależności nie zostały spełnione - + This mod can not be enabled because following mods are incompatible with this mod Ten mod nie może zostać włączony ponieważ następujące mody są z nim niekompatybilne - + This mod can not be disabled because it is required to run following mods Ten mod nie może zostać wyłączony ponieważ jest wymagany by do uruchomienia następujących modów - + This mod can not be uninstalled or updated because it is required to run following mods Ten mod nie może zostać odinstalowany lub zaktualizowany ponieważ jest wymagany do uruchomienia następujących modów - + This is submod and it can not be installed or uninstalled separately from parent mod To jest moduł składowy innego moda i nie może być zainstalowany lub odinstalowany oddzielnie od moda nadrzędnego - + Notes Uwagi - + Screenshot %1 Zrzut ekranu %1 - - Mod is compatible - Mod jest kompatybilny - - - + Mod is incompatible Mod jest niekompatybilny @@ -449,32 +542,32 @@ Zestaw modów - + Your Heroes III data files have been successfully found. Twoje pliki Heroes III zostały pomyślnie znalezione. - + Optionally, you can install additional mods either now or at any point later: Opcjonalnie możesz zainstalować dodatkowe modyfikacje teraz lub później: - + Install support for playing Heroes III in resolutions other than 800x600. Zapinstaluj wsparcie dla grania w Heroes III w rozdzielczości innej niż 800x600. - + Install compatible version of addon Horn of the Abyss: fan-made Heroes III expansion, ported by VCMI team Zainstaluj kompatybilną wersję fanowskiego dodatku Horn of the Abyss przeportowaną przez zespół VCMI - + Install compatible version of addon "In The Wake of Gods": fan-made Heroes III expansion Zainstaluj kompatybilną wersję fanowskiego dodatku "In The Wake Of Gods" - + Finish Zakończ @@ -484,84 +577,135 @@ Krok %v z %m - + Choose your language Wybierz język - - + + VCMI on Github + + + + + VCMI on Slack + + + + + VCMI on Discord + + + + + Have a question? Found a bug? Want to help? Join us: + + + + + Thanks for installing VCMI. + +There are a few more steps to be done before you can start playing. + +Keep in mind that in order to use VCMI you need to own original data files, Heroes III: Complete Edition or Shadow of Death. + +Heroes III: HD Edition is currently not supported + + + + + Next Dalej - + Find Heroes III data files Znajdź pliki Heroes III - + Open help in browser Otwórz pomoc w przeglądarce - + Search again Szukaj ponownie - + If you don't have installed Heroes III copy, it is possible to use our automatic installation tool 'vcmibuilder' to extract data from GoG.com installer. Visit our wiki for detailed instructions. Jeśli nie masz zainstalowanej kopii Heroes III istnieje możliwość użycia naszego automatycznego narzędzia instalacyjnego 'vcmibuilder' by wyodrębnić dane z instalatora GoG.com. Odwiedź nasze wiki po szczegółowe instrukcje. - + VCMI requires Heroes III data files in one of the locations listed above. Please copy Heroes III data in one of these directories. VCMI wymaga plików Heroes III w jednej z wymienionych wyżej lokalizacji. Proszę, skopiuj pliki Heroes III do jednego z tych katalogów. - + Heroes III data files Pliki Heroes III - + Alternatively, you can select directory with installed Heroes III data and VCMI will copy exisiting data automatically. Możesz też wybrać folder z zainstalowanym Heroes III i VCMI automatycznie skopiuje istniejące dane. - + Copy existing data Skopiuj istniejące dane - + Your Heroes III language has been successfully detected. Twój język Heroes III został pomyślnie wykryty. - + Automatic detection of language failed. Please select language of your Heroes III copy Automatyczna detekcja języka nie powiodła się. Proszę wybrać język twojego Heroes III - + Heroes III language Język Heroes III - - + + Back Wstecz - + Install VCMI Mod Preset Zainstaluj zestaw modyfikacji - + + Horn of the Abyss + + + + + Heroes III Translation + Tłumaczenie Heroes III + + + + High Definition Support + + + + + In The Wake of Gods + + + + Install translation of Heroes III to your language Zainstaluj tłumaczenie Heroes III dla twojego języka diff --git a/launcher/translation/russian.ts b/launcher/translation/russian.ts index bf4f89f7b..e0f81f50d 100644 --- a/launcher/translation/russian.ts +++ b/launcher/translation/russian.ts @@ -4,17 +4,111 @@ CModListModel - + + Translation + + + + + Town + + + + + Test + + + + + Templates + + + + + Spells + + + + + Music + + + + + Sounds + + + + + Skills + + + + + + Other + + + + + Objects + + + + + + Mechanics + + + + + + Interface + + + + + Heroes + + + + + + Graphical + + + + + Expansion + + + + + Creatures + + + + + Artifacts + + + + + AI + + + + Name Название - + Type Тип - + Version Версия @@ -63,7 +157,7 @@ - + Description Описание @@ -113,114 +207,113 @@ Отмена - + Mod name Название мода - + Installed version Установленная версия - + Latest version Последняя версия - + Download size Размер загрузки - + Authors Авторы - + License Лицензия - + Contact - - + Compatibility Совместимость - - + + Required VCMI version Требуемая версия VCMI - + Supported VCMI version Поддерживаемая версия VCMI - + Supported VCMI versions Поддерживаемые версии VCMI - + + Languages + + + + Required mods Зависимости - + Conflicting mods Конфликтующие моды - + This mod can not be installed or enabled because following dependencies are not present Этот мод не может быть установлен или активирован, так как отсутствуют следующие зависимости - + This mod can not be enabled because following mods are incompatible with this mod Этот мод не может быть установлен или активирован, так как следующие моды несовместимы с этим - + This mod can not be disabled because it is required to run following mods Этот мод не может быть выключен, так как он является зависимостью для следующих - + This mod can not be uninstalled or updated because it is required to run following mods Этот мод не может быть удален или обновлен, так как является зависимостью для следующих модов - + This is submod and it can not be installed or uninstalled separately from parent mod Это вложенный мод, он не может быть установлен или удален отдельно от родительского - + Notes Замечания - + Screenshot %1 Скриншот %1 - - Mod is compatible - Мод совместим - - - + Mod is incompatible Мод несовместим @@ -449,32 +542,32 @@ - + Your Heroes III data files have been successfully found. - + Optionally, you can install additional mods either now or at any point later: - + Install support for playing Heroes III in resolutions other than 800x600. - + Install compatible version of addon Horn of the Abyss: fan-made Heroes III expansion, ported by VCMI team - + Install compatible version of addon "In The Wake of Gods": fan-made Heroes III expansion - + Finish @@ -484,84 +577,135 @@ - + Choose your language - - + + VCMI on Github + + + + + VCMI on Slack + + + + + VCMI on Discord + + + + + Have a question? Found a bug? Want to help? Join us: + + + + + Thanks for installing VCMI. + +There are a few more steps to be done before you can start playing. + +Keep in mind that in order to use VCMI you need to own original data files, Heroes III: Complete Edition or Shadow of Death. + +Heroes III: HD Edition is currently not supported + + + + + Next - + Find Heroes III data files - + Open help in browser - + Search again - + If you don't have installed Heroes III copy, it is possible to use our automatic installation tool 'vcmibuilder' to extract data from GoG.com installer. Visit our wiki for detailed instructions. - + VCMI requires Heroes III data files in one of the locations listed above. Please copy Heroes III data in one of these directories. - + Heroes III data files - + Alternatively, you can select directory with installed Heroes III data and VCMI will copy exisiting data automatically. - + Copy existing data - + Your Heroes III language has been successfully detected. - + Automatic detection of language failed. Please select language of your Heroes III copy - + Heroes III language - - + + Back - + Install VCMI Mod Preset - + + Horn of the Abyss + + + + + Heroes III Translation + + + + + High Definition Support + + + + + In The Wake of Gods + + + + Install translation of Heroes III to your language diff --git a/launcher/translation/spanish.ts b/launcher/translation/spanish.ts index 859e98a34..d1dc21041 100644 --- a/launcher/translation/spanish.ts +++ b/launcher/translation/spanish.ts @@ -4,101 +4,180 @@ CModListModel - + + Translation + + + + + Town + + + + + Test + + + + + Templates + + + + + Spells + + + + + Music + + + + + Sounds + + + + + Skills + + + + + + Other + + + + + Objects + + + + + + Mechanics + + + + + + Interface + + + + + Heroes + + + + + + Graphical + + + + + Expansion + + + + + Creatures + + + + + Artifacts + + + + + AI + + + + Name Nombre - + Type Tipo - + Version Versión - - - Size - Tamaño - - - - Author - Autor - - CModListView + CModListView - + Filter Filtrar - + All mods Todos los mods - + Downloadable Descargables - + Installed Instalado - + Updatable Actualizables - + Active Activo - + Inactive Inactivo - + Download && refresh repositories Descargar y actualizar repositorios - - + + Description Descripción - + Changelog Registro de cambios - + Screenshots Capturas de pantalla - - Show details - Mostrar detalles - - - + Uninstall Desinstalar - + Enable Activar @@ -108,134 +187,133 @@ Desactivar - + Update Actualizar - + Install Instalar - + %p% (%v KB out of %m KB) %p% (%v KB de %m KB) - + Abort Cancelar - + Mod name Nombre del mod - + Installed version Versión instalada - + Latest version Última versión - + Download size Tamaño de descarga - + Authors Autores - + License Licencia - - Home - Página principal + + Contact + - - + Compatibility Compatibilidad - - + + Required VCMI version Versión de VCMI requerida - + Supported VCMI version Versión de VCMI compatible - + Supported VCMI versions Versiones de VCMI compatibles - + + Languages + + + + Required mods Mods requeridos - + Conflicting mods Mods conflictivos - + This mod can not be installed or enabled because following dependencies are not present Este mod no se puede instalar o habilitar porque no están presentes las siguientes dependencias - + This mod can not be enabled because following mods are incompatible with this mod Este mod no se puede habilitar porque los siguientes mods son incompatibles con él - + This mod can not be disabled because it is required to run following mods No se puede desactivar este mod porque es necesario para ejecutar los siguientes mods - + This mod can not be uninstalled or updated because it is required to run following mods No se puede desinstalar o actualizar este mod porque es necesario para ejecutar los siguientes mods - + This is submod and it can not be installed or uninstalled separately from parent mod Este es un submod y no se puede instalar o desinstalar por separado del mod principal - + Notes Notas - + Screenshot %1 Captura de pantalla %1 - - Mod is compatible - El mod es compatible - - - + Mod is incompatible El mod es incompatible @@ -243,217 +321,393 @@ CSettingsView - + Change Cambiar - - - + + + Open Abrir - + + + Artificial Intelligence + + + + + + Mod Repositories + + + + + Cursor + + + + User data directory Directorio de datos de usuario - - - - + + Adventure Map AI + + + + + Heroes III Translation + + + + + + + Off Apagado - - - - + + + + On Encendido - + Fullscreen Pantalla completa - - Repositories - Repositorios - - - - Check for updates - Buscar actualizaciones - - - + Neutral AI IA Neutral - + Real Realista - + + General General - - Player AI - IA de jugador - - - + VCMI Language Idioma de VCMI - - Central European (Windows 1250) - Europa central (Windows 1250) - - - - Cyrillic script (Windows 1251) - Cyrillic script (Windows 1251) - - - - Western European (Windows 1252) - Europa oriental (Windows 1252) - - - - Simplified Chinese (GBK) - Chino simplificado (GBK) - - - - Simplified Chinese (GB2312) - Chino simplificado (GB2312) - - - - Korean (Windows 949) - Koreano (Windows 949) - - - - English - English (Inglés) - - - - Deutsch (German) - Deutsch (Alemán) - - - - Polska (Polish) - Polska (Polaco) - - - - Русский (Russian) - Русский (Ruso) - - - - Українська (Ukrainian) - Українська (Ucraniano) - - - + Friendly AI IA amistosa - + Resolution Resolución - - AI on the map - IA en el mapa - - - + Autosave Autoguardado - + + Update now + + + + Display index Mostrar índice - - Check repositories on startup - Comprobar repositorios al iniciar - - - + Network port Puerto de red - + + Data Directories Directorios de datos - + + Video Vídeo - - Heroes III character set - Juego de caracteres de Heroes III - - - + Extra data directory Directorio de datos extra - + + Default + + + + + Hardware + + + + + Software + + + + Log files directory Directorio de archivos de registro - + Show intro Mostrar introducción - - Launcher Settings - Configuración del lanzador - - - + Build version Versión - + + Check on startup + + + + + Heroes III Data Language + + + + Enemy AI IA enemiga - - AI in the battlefield - IA en el campo de batalla + + Active + Activo + + + + Disabled + + + + + Enable + Activar + + + + Not Installed + + + + + Install + Instalar + + + + FirstLaunchView + + + Language + + + + + Heroes III Data + + + + + Mods Preset + + + + + Step %v out of %m + + + + + Choose your language + + + + + VCMI on Github + + + + + VCMI on Slack + + + + + VCMI on Discord + + + + + Have a question? Found a bug? Want to help? Join us: + + + + + Thanks for installing VCMI. + +There are a few more steps to be done before you can start playing. + +Keep in mind that in order to use VCMI you need to own original data files, Heroes III: Complete Edition or Shadow of Death. + +Heroes III: HD Edition is currently not supported + + + + + + Next + + + + + Find Heroes III data files + + + + + Open help in browser + + + + + Search again + + + + + If you don't have installed Heroes III copy, it is possible to use our automatic installation tool 'vcmibuilder' to extract data from GoG.com installer. Visit our wiki for detailed instructions. + + + + + VCMI requires Heroes III data files in one of the locations listed above. Please copy Heroes III data in one of these directories. + + + + + Heroes III data files + + + + + Alternatively, you can select directory with installed Heroes III data and VCMI will copy exisiting data automatically. + + + + + Copy existing data + + + + + Your Heroes III data files have been successfully found. + + + + + Your Heroes III language has been successfully detected. + + + + + Automatic detection of language failed. Please select language of your Heroes III copy + + + + + Heroes III language + + + + + + Back + + + + + Install VCMI Mod Preset + + + + + Horn of the Abyss + + + + + Heroes III Translation + + + + + High Definition Support + + + + + In The Wake of Gods + + + + + Install translation of Heroes III to your language + + + + + Optionally, you can install additional mods either now or at any point later: + + + + + Install support for playing Heroes III in resolutions other than 800x600. + + + + + Install compatible version of addon Horn of the Abyss: fan-made Heroes III expansion, ported by VCMI team + + + + + Install compatible version of addon "In The Wake of Gods": fan-made Heroes III expansion + + + + + Finish + @@ -464,68 +718,172 @@ Visor de imágenes + + Language + + + Chinese + + + + + English + English (Inglés) + + + + French + + + + + German + + + + + Korean + + + + + Polish + + + + + Russian + + + + + Spanish + + + + + Ukrainian + + + + + Other (East European) + + + + + Other (Cyrillic Script) + + + + + Other (West European) + + + + + Auto (%1) + + + Lobby - + + Connect Conectar - + Username Nombre de usuario - + Server Servidor - + + People in lobby + + + + + Lobby chat + + + + Session Sesión - + Players Jugadores - + + Resolve + + + + + New game + + + + + Load game + + + + New room Nueva sala - + Join room Unirse a la sala - + Ready Listo - + Mods mismatch No coinciden los mods - + Leave Salir - + Kick player Expulsar jugador - + Players in the room Jugadores en la sala + + + Disconnect + + + + + No issues detected + + LobbyRoomRequest @@ -553,52 +911,52 @@ MainWindow - - VCMI Launcher - Lanzador de VCMI + + VCMI Launcher + Lanzador de VCMI - - Settings - Configuración + + Settings + Configuración - - Map Editor - Editor de Mapas + + Map Editor + Editor de Mapas - - Start game - Iniciar juego + + Start game + Iniciar juego - - Lobby - Sala de Espera + + Lobby + Sala de Espera - - Mods - Mods + + Mods + Mods UpdateDialog - - You have latest version - Tienes la última versión + + You have latest version + Tienes la última versión - - Close - Cerrar + + Close + Cerrar - - Check updates on startup - Comprobar actualizaciones al iniciar + + Check updates on startup + Comprobar actualizaciones al iniciar diff --git a/launcher/translation/ukrainian.ts b/launcher/translation/ukrainian.ts index 1b059748f..e69f5b18b 100644 --- a/launcher/translation/ukrainian.ts +++ b/launcher/translation/ukrainian.ts @@ -4,17 +4,111 @@ CModListModel - + + Translation + Переклад + + + + Town + Місто + + + + Test + Тестування + + + + Templates + Шаблони + + + + Spells + Закляття + + + + Music + Музика + + + + Sounds + Звуки + + + + Skills + Вміння + + + + + Other + Інше + + + + Objects + Об'єкти + + + + + Mechanics + Механіки + + + + + Interface + Інтерфейс + + + + Heroes + Герої + + + + + Graphical + Графічний + + + + Expansion + Розширення + + + + Creatures + Істоти + + + + Artifacts + Артефакти + + + + AI + ШІ + + + Name Назва - + Type Тип - + Version Версія @@ -63,7 +157,7 @@ - + Description Опис @@ -113,114 +207,113 @@ Відмінити - + Mod name Назва модифікації - + Installed version Встановлена версія - + Latest version Найновіша версія - + Download size Розмір для завантаження - + Authors Автори - + License Ліцензія - + Contact Контакти - - + Compatibility Сумісність - - + + Required VCMI version Необхідна версія VCMI - + Supported VCMI version Підтримувана версія VCMI - + Supported VCMI versions Підтримувані версії VCMI - + + Languages + Мови + + + Required mods Необхідні модифікації - + Conflicting mods Конфліктуючі модифікації - + This mod can not be installed or enabled because following dependencies are not present Цю модифікацію не можна встановити чи активувати, оскільки відсутні наступні залежності - + This mod can not be enabled because following mods are incompatible with this mod Цю модифікацію не можна ввімкнути, оскільки наступні модифікації несумісні з цією модифікацією - + This mod can not be disabled because it is required to run following mods Цю модифікацію не можна відключити, оскільки вона необхідна для запуску наступних модифікацій - + This mod can not be uninstalled or updated because it is required to run following mods Цю модифікацію не можна видалити або оновити, оскільки вона необхідна для запуску наступних модифікацій - + This is submod and it can not be installed or uninstalled separately from parent mod Це вкладена модифікація, і її не можна встановити або видалити окремо від батьківської модифікації - + Notes Примітки - + Screenshot %1 Знімок екрану %1 - - Mod is compatible - Модифікація сумісна - - - + Mod is incompatible Модифікація несумісна @@ -449,32 +542,32 @@ Початкові модифікації - + Your Heroes III data files have been successfully found. Файли даних вашої гри Heroes III успішно знайдено. - + Optionally, you can install additional mods either now or at any point later: За бажанням ви можете встановити додаткові модифікації зараз або пізніше: - + Install support for playing Heroes III in resolutions other than 800x600. - + Встановити підтримку для гри в Heroes III у роздільних здатностях, більших за 800x600. - + Install compatible version of addon Horn of the Abyss: fan-made Heroes III expansion, ported by VCMI team - + Встановити сумісну версію доповнення Horn of the Abyss: фанатське доповнення Heroes III, портоване командою VCMI - + Install compatible version of addon "In The Wake of Gods": fan-made Heroes III expansion - + Встановити сумісну версію доповнення " In The Wake of Gods": фанатське доповнення до Heroes III - + Finish Завершити @@ -484,84 +577,141 @@ Крок %v з %m - + Choose your language Оберіть свою мову - - + + VCMI on Github + VCMI на Github + + + + VCMI on Slack + VCMI на Slack + + + + VCMI on Discord + VCMI на Discord + + + + Have a question? Found a bug? Want to help? Join us: + Маєте питання? Виявили помилку? Хочете допомогти? Приєднуйтесь до нас: + + + + Thanks for installing VCMI. + +There are a few more steps to be done before you can start playing. + +Keep in mind that in order to use VCMI you need to own original data files, Heroes III: Complete Edition or Shadow of Death. + +Heroes III: HD Edition is currently not supported + Дякуємо, що встановили VCMI. + +Залишилося зробити ще кілька кроків, перш ніж ви зможете почати грати. + +Майте на увазі, що для використання VCMI вам потрібно мати оригінальні файли гри Heroes III: Complete Edition або Shadow of Death. + +Heroes III: HD Edition наразі не підтримується + + + + Next Далі - + Find Heroes III data files Пошук файлів даних Heroes III - + Open help in browser Відкрити довідку у браузері - + Search again Повторити пошук - + If you don't have installed Heroes III copy, it is possible to use our automatic installation tool 'vcmibuilder' to extract data from GoG.com installer. Visit our wiki for detailed instructions. - + Якщо у вас не встановлена копія Heroes III, ви можете скористатися нашим засобом встановлення "vcmibuilder", щоб видобути дані з інсталятора GoG.com. Докладні інструкції можна знайти у нашій вікі. - + VCMI requires Heroes III data files in one of the locations listed above. Please copy Heroes III data in one of these directories. - + VCMI потребує файлів даних Heroes III в одному з перелічених вище розташувань. Будь ласка, скопіюйте дані Heroes III в одну з цих директорій. - + Heroes III data files Файли даних Heroes III - + Alternatively, you can select directory with installed Heroes III data and VCMI will copy exisiting data automatically. - + Або ж ви можете вибрати директорію зі встановленими даними Heroes III, і VCMI автоматично скопіює ці дані. - + Copy existing data Копіювати наявні дані - + Your Heroes III language has been successfully detected. Мову вашої гри Heroes III успішно визначено. - + Automatic detection of language failed. Please select language of your Heroes III copy Не вдалося визначити мову гри. Будь ласка, виберіть мову вашої копії Heroes III - + Heroes III language Мова Heroes III - - + + Back Назад - + Install VCMI Mod Preset Встановлення початкових модифікацій VCMI - + + Horn of the Abyss + Horn of the Abyss + + + + Heroes III Translation + Переклад Heroes III + + + + High Definition Support + Підтримка високих роздільних здатностей + + + + In The Wake of Gods + In The Wake of Gods + + + Install translation of Heroes III to your language Встановити переклад Heroes III на вашу мову diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 3eae09b95..8e492240b 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -48,6 +48,20 @@ void CGeneralTextHandler::detectInstallParameters() "ukrainian" } }; + if(!CResourceHandler::get("core")->existsResource(ResourceID("DATA/GENRLTXT.TXT", EResType::TEXT))) + { + Settings language = settings.write["session"]["language"]; + language->String() = "english"; + + Settings confidence = settings.write["session"]["languageDeviation"]; + confidence->Float() = 1.0; + + Settings encoding = settings.write["session"]["encoding"]; + encoding->String() = Languages::getLanguageOptions("english").encoding; + + return; + } + // load file that will be used for footprint generation // this is one of the most text-heavy files in game and consists solely from translated texts auto resource = CResourceHandler::get("core")->load(ResourceID("DATA/GENRLTXT.TXT", EResType::TEXT)); @@ -254,7 +268,7 @@ const std::string & CGeneralTextHandler::deserialize(const TextIdentifier & iden return identifier.get(); } - auto const & entry = stringsLocalizations.at(identifier.get()); + const auto & entry = stringsLocalizations.at(identifier.get()); if (!entry.overrideValue.empty()) return entry.overrideValue; @@ -312,7 +326,7 @@ bool CGeneralTextHandler::validateTranslation(const std::string & language, cons { bool allPresent = true; - for (auto const & string : stringsLocalizations) + for(const auto & string : stringsLocalizations) { if (string.second.modContext != modContext) continue; // Not our mod @@ -341,7 +355,7 @@ bool CGeneralTextHandler::validateTranslation(const std::string & language, cons bool allFound = true; - for (auto const & string : config.Struct()) + for(const auto & string : config.Struct()) { if (stringsLocalizations.count(string.first) > 0) continue; diff --git a/lib/JsonDetail.cpp b/lib/JsonDetail.cpp index 083ec7dcb..8d64812cc 100644 --- a/lib/JsonDetail.cpp +++ b/lib/JsonDetail.cpp @@ -1059,8 +1059,7 @@ namespace std::string defFile(const JsonNode & node) { - TEST_FILE(node.meta, "Sprites/", node.String(), EResType::ANIMATION); - return "Def file \"" + node.String() + "\" was not found"; + return testAnimation(node.String(), node.meta); } std::string animationFile(const JsonNode & node) diff --git a/lib/NetPacksBase.h b/lib/NetPacksBase.h index e59308baa..e7b3da5f1 100644 --- a/lib/NetPacksBase.h +++ b/lib/NetPacksBase.h @@ -308,8 +308,6 @@ public: RESET_STATE, UPDATE, REMOVE, - ACTIVATE_AND_UPDATE, - ACTIVATE_AND_REMOVE }; JsonNode data; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index fe93dc7a2..15a02192f 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -2166,6 +2166,7 @@ void BattleTriggerEffect::applyGs(CGameState * gs) const break; } case Bonus::ENCHANTER: + case Bonus::MORALE: break; case Bonus::FEAR: st->fear = true; @@ -2398,7 +2399,6 @@ void BattleObstaclesChanged::applyBattle(IBattleState * battleState) case BattleChanges::EOperation::ADD: battleState->addObstacle(change); break; - case BattleChanges::EOperation::ACTIVATE_AND_UPDATE: case BattleChanges::EOperation::UPDATE: battleState->updateObstacle(change); break; diff --git a/lib/VCMIDirs.cpp b/lib/VCMIDirs.cpp index 1e8dc49ac..5c56dc54f 100644 --- a/lib/VCMIDirs.cpp +++ b/lib/VCMIDirs.cpp @@ -346,7 +346,7 @@ std::vector VCMIDirsWIN32::dataPaths() const } bfs::path VCMIDirsWIN32::clientPath() const { return binaryPath() / "VCMI_client.exe"; } -bfs::path VCMIDirsWIN32::mapEditorPath() const { return binaryPath() / "VCMI_editor.exe"; } +bfs::path VCMIDirsWIN32::mapEditorPath() const { return binaryPath() / "VCMI_mapeditor.exe"; } bfs::path VCMIDirsWIN32::serverPath() const { return binaryPath() / "VCMI_server.exe"; } bfs::path VCMIDirsWIN32::libraryPath() const { return "."; } diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index c0198c530..3d0e6c8c3 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -798,19 +798,21 @@ std::vector> CBattleInfoCallback::battl return obstacles; } -std::vector> CBattleInfoCallback::getAllAffectedObstaclesByStack(const battle::Unit * unit) const +std::vector> CBattleInfoCallback::getAllAffectedObstaclesByStack(const battle::Unit * unit, const std::set & passed) const { - std::vector> affectedObstacles = std::vector>(); + auto affectedObstacles = std::vector>(); RETURN_IF_NOT_BATTLE(affectedObstacles); if(unit->alive()) { - affectedObstacles = battleGetAllObstaclesOnPos(unit->getPosition(), false); + if(!passed.count(unit->getPosition())) + affectedObstacles = battleGetAllObstaclesOnPos(unit->getPosition(), false); if(unit->doubleWide()) { - BattleHex otherHex = unit->occupiedHex(unit->getPosition()); - if(otherHex.isValid()) + BattleHex otherHex = unit->occupiedHex(); + if(otherHex.isValid() && !passed.count(otherHex)) for(auto & i : battleGetAllObstaclesOnPos(otherHex, false)) - affectedObstacles.push_back(i); + if(!vstd::contains(affectedObstacles, i)) + affectedObstacles.push_back(i); } for(auto hex : unit->getHexes()) if(hex == ESiegeHex::GATE_BRIDGE) @@ -932,6 +934,8 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi return ret; const std::set obstacles = getStoppers(params.perspective); + auto checkParams = params; + checkParams.ignoreKnownAccessible = true; //Ignore starting hexes obstacles std::queue hexq; //bfs queue @@ -949,7 +953,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi hexq.pop(); //walking stack can't step past the obstacles - if(curHex != params.startPosition && isInObstacle(curHex, obstacles, params)) + if(isInObstacle(curHex, obstacles, checkParams)) continue; const int costToNeighbour = ret.distances[curHex.hex] + 1; @@ -982,6 +986,9 @@ bool CBattleInfoCallback::isInObstacle( for(auto occupiedHex : occupiedHexes) { + if(params.ignoreKnownAccessible && vstd::contains(params.knownAccessible, occupiedHex)) + continue; + if(vstd::contains(obstacles, occupiedHex)) { if(occupiedHex == ESiegeHex::GATE_BRIDGE) diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index dccdac50c..1adcbf8c8 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -60,7 +60,7 @@ public: boost::optional battleIsFinished() const override; //return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw std::vector> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const override; - std::vector> getAllAffectedObstaclesByStack(const battle::Unit * unit) const override; + std::vector> getAllAffectedObstaclesByStack(const battle::Unit * unit, const std::set & passed) const override; const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true) const; diff --git a/lib/battle/CBattleInfoEssentials.cpp b/lib/battle/CBattleInfoEssentials.cpp index 081030394..ac3929ea3 100644 --- a/lib/battle/CBattleInfoEssentials.cpp +++ b/lib/battle/CBattleInfoEssentials.cpp @@ -47,10 +47,7 @@ std::vector> CBattleInfoEssentials::bat else { if(!!player && *perspective != battleGetMySide()) - { - logGlobal->error("Unauthorized obstacles access attempt!"); - return ret; - } + logGlobal->warn("Unauthorized obstacles access attempt, assuming massive spell"); } for(const auto & obstacle : getBattle()->getAllObstacles()) diff --git a/lib/battle/IBattleInfoCallback.h b/lib/battle/IBattleInfoCallback.h index 8f8ab8c80..223986eef 100644 --- a/lib/battle/IBattleInfoCallback.h +++ b/lib/battle/IBattleInfoCallback.h @@ -72,7 +72,7 @@ public: //blocking obstacles makes tile inaccessible, others cause special effects (like Land Mines, Moat, Quicksands) virtual std::vector> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const = 0; - virtual std::vector> getAllAffectedObstaclesByStack(const battle::Unit * unit) const = 0; + virtual std::vector> getAllAffectedObstaclesByStack(const battle::Unit * unit, const std::set & passed) const = 0; }; diff --git a/lib/battle/ReachabilityInfo.h b/lib/battle/ReachabilityInfo.h index 134390605..6931e1662 100644 --- a/lib/battle/ReachabilityInfo.h +++ b/lib/battle/ReachabilityInfo.h @@ -28,6 +28,7 @@ struct DLL_LINKAGE ReachabilityInfo ui8 side = 0; bool doubleWide = false; bool flying = false; + bool ignoreKnownAccessible = false; //Ignore obstacles if it is in accessible hexes std::vector knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself) BattleHex startPosition; //assumed position of stack diff --git a/lib/mapObjects/CRewardableConstructor.cpp b/lib/mapObjects/CRewardableConstructor.cpp index 7e860c074..cb3d0f741 100644 --- a/lib/mapObjects/CRewardableConstructor.cpp +++ b/lib/mapObjects/CRewardableConstructor.cpp @@ -16,6 +16,7 @@ #include "../CModHandler.h" #include "JsonRandom.h" #include "../IGameCallback.h" +#include "../CGeneralTextHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -307,9 +308,22 @@ bool CRandomRewardObjectInfo::givesBonuses() const return testForKey(parameters, "bonuses"); } +const JsonNode & CRandomRewardObjectInfo::getParameters() const +{ + return parameters; +} + void CRewardableConstructor::initTypeData(const JsonNode & config) { objectInfo.init(config); + + if (!config["name"].isNull()) + VLC->generaltexth->registerString( config.meta, getNameTextID(), config["name"].String()); +} + +bool CRewardableConstructor::hasNameTextID() const +{ + return !objectInfo.getParameters()["name"].isNull(); } CGObjectInstance * CRewardableConstructor::create(std::shared_ptr tmpl) const diff --git a/lib/mapObjects/CRewardableConstructor.h b/lib/mapObjects/CRewardableConstructor.h index 6e41c7ce6..f47571410 100644 --- a/lib/mapObjects/CRewardableConstructor.h +++ b/lib/mapObjects/CRewardableConstructor.h @@ -28,6 +28,8 @@ class DLL_LINKAGE CRandomRewardObjectInfo : public IObjectInfo void configureReward(CRewardableObject * object, CRandomGenerator & rng, CRewardInfo & info, const JsonNode & source) const; void configureResetInfo(CRewardableObject * object, CRandomGenerator & rng, CRewardResetInfo & info, const JsonNode & source) const; public: + const JsonNode & getParameters() const; + bool givesResources() const override; bool givesExperience() const override; @@ -60,6 +62,8 @@ class DLL_LINKAGE CRewardableConstructor : public AObjectTypeHandler void initTypeData(const JsonNode & config) override; public: + bool hasNameTextID() const override; + CGObjectInstance * create(std::shared_ptr tmpl = nullptr) const override; void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override; diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index ca7845ae0..ec00d5c63 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -83,7 +83,7 @@ CConnection::CConnection(const std::string & host, ui16 port, std::string Name, if(error) { logNetwork->error("Problem with resolving: \n%s", error.message()); - goto connerror1; + throw std::runtime_error("Can't establish connection: Problem with resolving"); } pom = endpoint_iterator; if(pom != end) @@ -91,7 +91,7 @@ CConnection::CConnection(const std::string & host, ui16 port, std::string Name, else { logNetwork->error("Critical problem: No endpoints found!"); - goto connerror1; + throw std::runtime_error("Can't establish connection: No endpoints found!"); } while(pom != end) { @@ -110,20 +110,12 @@ CConnection::CConnection(const std::string & host, ui16 port, std::string Name, } else { - logNetwork->error("Problem with connecting: %s", error.message()); + throw std::runtime_error("Can't establish connection: Failed to connect!"); } endpoint_iterator++; } - - //we shouldn't be here - error handling -connerror1: - logNetwork->error("Something went wrong... checking for error info"); - if(error) - logNetwork->error(error.message()); - else - logNetwork->error("No error info. "); - throw std::runtime_error("Can't establish connection :("); } + CConnection::CConnection(std::shared_ptr Socket, std::string Name, std::string UUID): iser(this), oser(this), diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index 443b168bb..36b60d908 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/lib/spells/effects/Obstacle.cpp @@ -121,6 +121,9 @@ void Obstacle::adjustAffectedHexes(std::set & hexes, const Mechanics bool Obstacle::applicable(Problem & problem, const Mechanics * m) const { + if(hidden && m->battle()->battleHasNativeStack(m->battle()->otherSide(m->casterSide))) + return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem); + return LocationEffect::applicable(problem, m); } @@ -270,12 +273,7 @@ void Obstacle::placeObstacles(ServerCallback * server, const Mechanics * m, cons BattleObstaclesChanged pack; - boost::optional perspective; - - if(!m->battle()->getPlayerID()) - perspective = boost::make_optional(BattlePerspective::ALL_KNOWING); - - auto all = m->battle()->battleGetAllObstacles(perspective); + auto all = m->battle()->battleGetAllObstacles(BattlePerspective::ALL_KNOWING); int obstacleIdToGive = 1; for(auto & one : all) diff --git a/lib/spells/effects/RemoveObstacle.cpp b/lib/spells/effects/RemoveObstacle.cpp index 71535484f..c268fd8c8 100644 --- a/lib/spells/effects/RemoveObstacle.cpp +++ b/lib/spells/effects/RemoveObstacle.cpp @@ -72,7 +72,7 @@ bool RemoveObstacle::canRemove(const CObstacleInstance * obstacle) const return true; const auto *spellObstacle = dynamic_cast(obstacle); - if(removeAllSpells && spellObstacle) + if(removeAllSpells && obstacle->obstacleType == CObstacleInstance::SPELL_CREATED) return true; if(spellObstacle && !removeSpells.empty()) @@ -89,7 +89,7 @@ std::set RemoveObstacle::getTargets(const Mechanics * std::set possibleTargets; if(m->isMassive() || alwaysMassive) { - for(const auto & obstacle : m->battle()->battleGetAllObstacles()) + for(const auto & obstacle : m->battle()->battleGetAllObstacles(BattlePerspective::ALL_KNOWING)) if(canRemove(obstacle.get())) possibleTargets.insert(obstacle.get()); } diff --git a/lib/spells/effects/UnitEffect.cpp b/lib/spells/effects/UnitEffect.cpp index e2ef48402..124e35829 100644 --- a/lib/spells/effects/UnitEffect.cpp +++ b/lib/spells/effects/UnitEffect.cpp @@ -40,7 +40,7 @@ bool UnitEffect::applicable(Problem & problem, const Mechanics * m) const { //stack effect is applicable in general if there is at least one smart target - auto mainFilter = std::bind(&UnitEffect::getStackFilter, this, m, true, _1); + auto mainFilter = std::bind(&UnitEffect::getStackFilter, this, m, false, _1); auto predicate = std::bind(&UnitEffect::eraseByImmunityFilter, this, m, _1); auto targets = m->battle()->battleGetUnitsIf(mainFilter); @@ -59,12 +59,12 @@ bool UnitEffect::applicable(Problem & problem, const Mechanics * m) const bool UnitEffect::applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const { - //stack effect is applicable if it affects at least one smart target - //assume target correctly transformed, just reapply smart filter + //stack effect is applicable if it affects at least one target (smartness should not be checked) + //assume target correctly transformed, just reapply filter for(const auto & item : target) if(item.unitValue) - if(getStackFilter(m, true, item.unitValue)) + if(getStackFilter(m, false, item.unitValue)) return true; return false; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 18ba84335..9643b3bef 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -51,6 +51,8 @@ #include "../lib/serializer/Cast.h" #include "../lib/serializer/JsonSerializer.h" #include "../lib/ScriptHandler.h" +#include "vstd/CLoggerBase.h" +#include #include #include #include @@ -1398,6 +1400,11 @@ int CGameHandler::moveStack(int stack, BattleHex dest) //initing necessary tables auto accessibility = getAccesibility(curStack); + std::set passed; + //Ignore obstacles on starting position + passed.insert(curStack->getPosition()); + if(curStack->doubleWide()) + passed.insert(curStack->occupiedHex()); //shifting destination (if we have double wide stack and we can occupy dest but not be exactly there) if(!stackAtEnd && curStack->doubleWide() && !accessibility.accessible(dest, curStack)) @@ -1427,7 +1434,10 @@ int CGameHandler::moveStack(int stack, BattleHex dest) ret = path.second; - int creSpeed = gs->curB->tacticDistance ? GameConstants::BFIELD_SIZE : curStack->Speed(0, true); + int creSpeed = curStack->Speed(0, true); + + if (gs->curB->tacticDistance > 0 && creSpeed > 0) + creSpeed = GameConstants::BFIELD_SIZE; auto isGateDrawbridgeHex = [&](BattleHex hex) -> bool { @@ -1590,10 +1600,12 @@ int CGameHandler::moveStack(int stack, BattleHex dest) if(otherHex.isValid() && !obstacle2.empty()) obstacleHit = true; } + if(!obstacleHit) + passed.insert(hex); } } - if (tiles.size() > 0) + if (!tiles.empty()) { //commit movement BattleStackMoved sm; @@ -1609,7 +1621,12 @@ int CGameHandler::moveStack(int stack, BattleHex dest) if (curStack->getPosition() != dest) { if(stackIsMoving && start != curStack->getPosition()) - stackIsMoving = handleDamageFromObstacle(curStack, stackIsMoving); + { + stackIsMoving = handleDamageFromObstacle(curStack, stackIsMoving, passed); + passed.insert(curStack->getPosition()); + if(curStack->doubleWide()) + passed.insert(curStack->occupiedHex()); + } if (gateStateChanging) { if (curStack->getPosition() == openGateAtHex) @@ -1637,7 +1654,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest) } //handling obstacle on the final field (separate, because it affects both flying and walking stacks) - handleDamageFromObstacle(curStack); + handleDamageFromObstacle(curStack, false, passed); return ret; } @@ -1659,6 +1676,12 @@ CGameHandler::CGameHandler(CVCMIServer * lobby) CGameHandler::~CGameHandler() { + if (battleThread) + { + //Setting battleMadeAction is needed because battleThread waits for the action to continue the main loop + battleMadeAction.setn(true); + battleThread->join(); + } delete spellEnv; delete gs; } @@ -2700,7 +2723,7 @@ void CGameHandler::startBattlePrimary(const CArmedInstance *army1, const CArmedI auto battleQuery = std::make_shared(this, gs->curB); queries.addQuery(battleQuery); - boost::thread(&CGameHandler::runBattle, this); + this->battleThread = std::make_unique(boost::thread(&CGameHandler::runBattle, this)); } void CGameHandler::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank) @@ -5288,13 +5311,13 @@ void CGameHandler::stackTurnTrigger(const CStack *st) } } -bool CGameHandler::handleDamageFromObstacle(const CStack * curStack, bool stackIsMoving) +bool CGameHandler::handleDamageFromObstacle(const CStack * curStack, bool stackIsMoving, const std::set & passed) { if(!curStack->alive()) return false; bool containDamageFromMoat = false; - bool movementStoped = false; - for(auto & obstacle : getAllAffectedObstaclesByStack(curStack)) + bool movementStopped = false; + for(auto & obstacle : getAllAffectedObstaclesByStack(curStack, passed)) { if(obstacle->obstacleType == CObstacleInstance::SPELL_CREATED) { @@ -5305,7 +5328,7 @@ bool CGameHandler::handleDamageFromObstacle(const CStack * curStack, bool stackI if(!spellObstacle) COMPLAIN_RET("Invalid obstacle instance"); - if(spellObstacle->trigger) + if(spellObstacle->triggersEffects()) { const bool oneTimeObstacle = spellObstacle->removeOnTrigger; @@ -5323,9 +5346,9 @@ bool CGameHandler::handleDamageFromObstacle(const CStack * curStack, bool stackI ObstacleChanges changeInfo; changeInfo.id = spellObstacle->uniqueID; if (oneTimeObstacle) - changeInfo.operation = ObstacleChanges::EOperation::ACTIVATE_AND_REMOVE; + changeInfo.operation = ObstacleChanges::EOperation::REMOVE; else - changeInfo.operation = ObstacleChanges::EOperation::ACTIVATE_AND_UPDATE; + changeInfo.operation = ObstacleChanges::EOperation::UPDATE; SpellCreatedObstacle changedObstacle; changedObstacle.uniqueID = spellObstacle->uniqueID; @@ -5369,13 +5392,13 @@ bool CGameHandler::handleDamageFromObstacle(const CStack * curStack, bool stackI return false; if((obstacle->stopsMovement() && stackIsMoving)) - movementStoped = true; + movementStopped = true; } if(stackIsMoving) - return curStack->alive() && !movementStoped; - else - return curStack->alive(); + return curStack->alive() && !movementStopped; + + return curStack->alive(); } void CGameHandler::handleTimeEvents() @@ -6412,7 +6435,7 @@ void CGameHandler::runBattle() //tactic round { - while (gs->curB->tacticDistance && !battleResult.get()) + while ((lobby->state != EServerState::SHUTDOWN) && gs->curB->tacticDistance && !battleResult.get()) boost::this_thread::sleep(boost::posix_time::milliseconds(50)); } @@ -6490,7 +6513,7 @@ void CGameHandler::runBattle() bool firstRound = true;//FIXME: why first round is -1? //main loop - while (!battleResult.get()) //till the end of the battle ;] + while ((lobby->state != EServerState::SHUTDOWN) && !battleResult.get()) //till the end of the battle ;] { BattleNextRound bnr; bnr.round = gs->curB->round + 1; @@ -6555,7 +6578,7 @@ void CGameHandler::runBattle() }; const CStack * next = nullptr; - while((next = getNextStack())) + while((lobby->state != EServerState::SHUTDOWN) && (next = getNextStack())) { BattleUnitsChanged removeGhosts; for(auto stack : curB.stacks) @@ -6734,7 +6757,7 @@ void CGameHandler::runBattle() boost::unique_lock lock(battleMadeAction.mx); battleMadeAction.data = false; - while (!actionWasMade()) + while ((lobby->state != EServerState::SHUTDOWN) && !actionWasMade()) { battleMadeAction.cond.wait(lock); if (battleGetStackByID(nextId, false) != next) @@ -6790,7 +6813,8 @@ void CGameHandler::runBattle() firstRound = false; } - endBattle(gs->curB->tile, gs->curB->battleGetFightingHero(0), gs->curB->battleGetFightingHero(1)); + if (lobby->state != EServerState::SHUTDOWN) + endBattle(gs->curB->tile, gs->curB->battleGetFightingHero(0), gs->curB->battleGetFightingHero(1)); } bool CGameHandler::makeAutomaticAction(const CStack *stack, BattleAction &ba) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 89bde24e9..0ed407424 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -96,6 +96,7 @@ class CGameHandler : public IGameCallback, public CBattleInfoCallback, public En { CVCMIServer * lobby; std::shared_ptr> applier; + std::unique_ptr battleThread; public: using FireShieldInfo = std::vector>; //use enums as parameters, because doMove(sth, true, false, true) is not readable @@ -232,7 +233,7 @@ public: bool makeCustomAction(BattleAction &ba); void stackEnchantedTrigger(const CStack * stack); void stackTurnTrigger(const CStack *stack); - bool handleDamageFromObstacle(const CStack * curStack, bool stackIsMoving = false); //checks if obstacle is land mine and handles possible consequences + bool handleDamageFromObstacle(const CStack * curStack, bool stackIsMoving = false, const std::set & passed = {}); //checks if obstacle is land mine and handles possible consequences void removeObstacle(const CObstacleInstance &obstacle); bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player ); diff --git a/server/CQuery.cpp b/server/CQuery.cpp index a9849ba4c..cdfd90527 100644 --- a/server/CQuery.cpp +++ b/server/CQuery.cpp @@ -380,6 +380,9 @@ bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const if(auto upgrade = dynamic_ptr_cast(pack)) return !vstd::contains(ourIds, upgrade->id); + if(auto formation = dynamic_ptr_cast(pack)) + return !vstd::contains(ourIds, formation->hid); + return CDialogQuery::blocksPack(pack); } diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index ce24bba5e..1c95a2c3e 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -59,10 +59,6 @@ #include "../lib/CGameState.h" -#if defined(__GNUC__) && !defined(__UCLIBC__) && !defined(__MINGW32__) && !defined(VCMI_MOBILE) -#include -#endif - template class CApplyOnServer; class CBaseForServerApply @@ -999,33 +995,6 @@ ui8 CVCMIServer::getIdOfFirstUnallocatedPlayer() const return 0; } -#if defined(__GNUC__) && !defined(__UCLIBC__) && !defined(__MINGW32__) && !defined(VCMI_MOBILE) -void handleLinuxSignal(int sig) -{ - const int STACKTRACE_SIZE = 100; - void * buffer[STACKTRACE_SIZE]; - int ptrCount = backtrace(buffer, STACKTRACE_SIZE); - char * * strings; - - logGlobal->error("Error: signal %d :", sig); - strings = backtrace_symbols(buffer, ptrCount); - if(strings == nullptr) - { - logGlobal->error("There are no symbols."); - } - else - { - for(int i = 0; i < ptrCount; ++i) - { - logGlobal->error(strings[i]); - } - free(strings); - } - - _exit(EXIT_FAILURE); -} -#endif - static void handleCommandOptions(int argc, const char * argv[], boost::program_options::variables_map & options) { namespace po = boost::program_options; @@ -1101,11 +1070,6 @@ int main(int argc, const char * argv[]) // Correct working dir executable folder (not bundle folder) so we can use executable relative paths boost::filesystem::current_path(boost::filesystem::system_complete(argv[0]).parent_path()); #endif - // Installs a sig sev segmentation violation handler - // to log stacktrace -#if defined(__GNUC__) && !defined(__UCLIBC__) && !defined(__MINGW32__) && !defined(VCMI_MOBILE) - signal(SIGSEGV, handleLinuxSignal); -#endif #ifndef VCMI_IOS console = new CConsoleHandler();