diff --git a/Mods/vcmi/Sprites/lobby/checkbox.json b/Mods/vcmi/Sprites/lobby/checkbox.json new file mode 100644 index 000000000..bfb1e6587 --- /dev/null +++ b/Mods/vcmi/Sprites/lobby/checkbox.json @@ -0,0 +1,8 @@ +{ + "basepath" : "lobby/", + "images" : + [ + { "frame" : 0, "file" : "checkboxBlueOff.png"}, + { "frame" : 1, "file" : "checkboxBlueOn.png"} + ] +} diff --git a/Mods/vcmi/Sprites/lobby/checkboxBlueOff.png b/Mods/vcmi/Sprites/lobby/checkboxBlueOff.png new file mode 100644 index 000000000..cfb75ae7c Binary files /dev/null and b/Mods/vcmi/Sprites/lobby/checkboxBlueOff.png differ diff --git a/Mods/vcmi/Sprites/lobby/checkboxBlueOn.png b/Mods/vcmi/Sprites/lobby/checkboxBlueOn.png new file mode 100644 index 000000000..1f414653f Binary files /dev/null and b/Mods/vcmi/Sprites/lobby/checkboxBlueOn.png differ diff --git a/Mods/vcmi/Sprites/lobby/checkboxOff.png b/Mods/vcmi/Sprites/lobby/checkboxOff.png new file mode 100644 index 000000000..d650018dc Binary files /dev/null and b/Mods/vcmi/Sprites/lobby/checkboxOff.png differ diff --git a/Mods/vcmi/Sprites/lobby/checkboxOn.png b/Mods/vcmi/Sprites/lobby/checkboxOn.png new file mode 100644 index 000000000..cd6e46308 Binary files /dev/null and b/Mods/vcmi/Sprites/lobby/checkboxOn.png differ diff --git a/Mods/vcmi/config/vcmi/chinese.json b/Mods/vcmi/config/vcmi/chinese.json index 70c7b3275..8f1b403eb 100644 --- a/Mods/vcmi/config/vcmi/chinese.json +++ b/Mods/vcmi/config/vcmi/chinese.json @@ -202,14 +202,14 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "同盟关系", "vcmi.randomMapTab.widgets.roadTypesLabel" : "道路类型", - "vcmi.optionsTab.widgets.chessFieldBase.help" : "{额外计时器}\n\n当{转动计时器}达到零时开始倒计时。 它仅在游戏开始时设置一次。 当计时器达到零时,玩家的回合结束。", - "vcmi.optionsTab.widgets.chessFieldTurn.help" : "{转动计时器}\n\n当玩家在冒险地图上开始回合时开始倒计时。 它在每回合开始时重置为其初始值。 任何未使用的回合时间将被添加到{额外计时器}(如果正在使用)中。", - "vcmi.optionsTab.widgets.chessFieldBattle.help" : "{战斗计时器}\n\n战斗期间当 {堆栈计时器} 达到0时进行倒计时。 每次战斗开始时都会重置为初始值。 如果计时器达到零,当前活动的堆栈将进行防御。", - "vcmi.optionsTab.widgets.chessFieldCreature.help" : "{堆栈计时器}\n\n当玩家在战斗中为当前堆栈选择一个动作时开始倒计时。 堆栈操作完成后,它会重置为其初始值。", - "vcmi.optionsTab.widgets.labelTimer" : "计时器", - "vcmi.optionsTab.widgets.timerModeSwitch.classic" : "经典计时器", - "vcmi.optionsTab.widgets.timerModeSwitch.chess" : "国际象棋计时器", - + "vcmi.optionsTab.chessFieldBase.hover" : "额外计时器", + "vcmi.optionsTab.chessFieldTurn.hover" : "转动计时器", + "vcmi.optionsTab.chessFieldBattle.hover" : "战斗计时器", + "vcmi.optionsTab.chessFieldCreature.hover" : "堆栈计时器", + "vcmi.optionsTab.chessFieldBase.help" : "当{转动计时器}达到零时开始倒计时。 它仅在游戏开始时设置一次。 当计时器达到零时,玩家的回合结束。", + "vcmi.optionsTab.chessFieldTurn.help" : "当玩家在冒险地图上开始回合时开始倒计时。 它在每回合开始时重置为其初始值。 任何未使用的回合时间将被添加到{额外计时器}(如果正在使用)中。", + "vcmi.optionsTab.chessFieldBattle.help" : "战斗期间当 {堆栈计时器} 达到0时进行倒计时。 每次战斗开始时都会重置为初始值。 如果计时器达到零,当前活动的堆栈将进行防御。", + "vcmi.optionsTab.chessFieldCreature.help" : "当玩家在战斗中为当前堆栈选择一个动作时开始倒计时。 堆栈操作完成后,它会重置为其初始值。", // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "敌人依然存活至今,你失败了!", diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index f1c561b97..fbe240eb8 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -192,15 +192,10 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Team Alignments", "vcmi.randomMapTab.widgets.roadTypesLabel" : "Druhy cest", - "vcmi.optionsTab.widgets.chessFieldBase.help" : "{Extra timer}\n\nStarts counting down when the {turn timer} reaches zero. It is set only once at the beginning of the game. When this timer reaches zero, the player's turn ends.", - "vcmi.optionsTab.widgets.chessFieldTurn.help" : "{Turn timer}\n\nStarts counting down when the player starts their turn on the adventure map. It is reset to its initial value at the start of each turn. Any unused turn time will be added to the {Extra timer} if it is in use.", - "vcmi.optionsTab.widgets.chessFieldBattle.help" : "{Battle timer}\n\nCounts down during battles when the {stack timer} reaches zero. It is reset to its initial value at the start of each battle. If the timer reaches zero, the currently active stack will defend.", - "vcmi.optionsTab.widgets.chessFieldCreature.help" : "{Stack timer}\n\nStarts counting down when the player is selecting an action for the current stack during battle. It resets to its initial value after the stack's action is completed.", "vcmi.optionsTab.widgets.labelTimer" : "Časovač", "vcmi.optionsTab.widgets.timerModeSwitch.classic" : "Klasický časovač", "vcmi.optionsTab.widgets.timerModeSwitch.chess" : "Šachový časovač", - // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!", "vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulace! Zvládli jste přežít. Vítězství je vaše!", diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 1d0432297..4ede80f5c 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -220,14 +220,37 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Team Alignments", "vcmi.randomMapTab.widgets.roadTypesLabel" : "Road Types", - "vcmi.optionsTab.widgets.chessFieldBase.help" : "{Extra timer}\n\nStarts counting down when the {turn timer} reaches zero. It is set only once at the beginning of the game. When this timer reaches zero, the player's turn ends.", - "vcmi.optionsTab.widgets.chessFieldTurn.help" : "{Turn timer}\n\nStarts counting down when the player starts their turn on the adventure map. It is reset to its initial value at the start of each turn. Any unused turn time will be added to the {Extra timer} if it is in use.", - "vcmi.optionsTab.widgets.chessFieldBattle.help" : "{Battle timer}\n\nCounts down during battles when the {stack timer} reaches zero. It is reset to its initial value at the start of each battle. If the timer reaches zero, the currently active stack will defend.", - "vcmi.optionsTab.widgets.chessFieldCreature.help" : "{Stack timer}\n\nStarts counting down when the player is selecting an action for the current stack during battle. It resets to its initial value after the stack's action is completed.", - "vcmi.optionsTab.widgets.labelTimer" : "Timer", - "vcmi.optionsTab.widgets.timerModeSwitch.classic" : "Classic timer", - "vcmi.optionsTab.widgets.timerModeSwitch.chess" : "Chess timer", + "vcmi.optionsTab.turnOptions.hover" : "Turn Options", + "vcmi.optionsTab.turnOptions.help" : "Select turn timer and simultaneous turns options", + "vcmi.optionsTab.chessFieldBase.hover" : "Base timer", + "vcmi.optionsTab.chessFieldTurn.hover" : "Turn timer", + "vcmi.optionsTab.chessFieldBattle.hover" : "Battle timer", + "vcmi.optionsTab.chessFieldCreature.hover" : "Unit timer", + "vcmi.optionsTab.chessFieldBase.help" : "Used when {Turn Timer} reaches 0. Set once at game start. On reaching zero, ends current turn. Any ongoing combat with end with a loss.", + "vcmi.optionsTab.chessFieldTurn.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Leftover added to {Base Timer} at turn's end.", + "vcmi.optionsTab.chessFieldBattle.help" : "Used in battles with AI or in pvp combat when {Unit Timer} runs out. Reset at start of each combat.", + "vcmi.optionsTab.chessFieldCreature.help" : "Used when selecting unit action in pvp combat. Reset at start of each unit's turn.", + + "vcmi.optionsTab.simturns" : "Simultaneous turns", + "vcmi.optionsTab.simturnsMin.hover" : "At least for", + "vcmi.optionsTab.simturnsMax.hover" : "At most for", + "vcmi.optionsTab.simturnsAI.hover" : "(Experimental) Simultaneous AI Turns", + "vcmi.optionsTab.simturnsMin.help" : "Play simultaneously for specified number of days. Contacts between players during this period are blocked", + "vcmi.optionsTab.simturnsMax.help" : "Play simultaneously for specified number of days or until contact with another player", + "vcmi.optionsTab.simturnsAI.help" : "{Simultaneous AI Turns}\nExperimental option. Allows AI players to act at the same time as human player when simultaneous turns are enabled.", + + // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language + // Using this information, VCMI will automatically select correct plural form for every possible amount + "vcmi.optionsTab.simturns.days.0" : " %d days", + "vcmi.optionsTab.simturns.days.1" : " %d day", + "vcmi.optionsTab.simturns.days.2" : " %d days", + "vcmi.optionsTab.simturns.weeks.0" : " %d weeks", + "vcmi.optionsTab.simturns.weeks.1" : " %d week", + "vcmi.optionsTab.simturns.weeks.2" : " %d weeks", + "vcmi.optionsTab.simturns.months.0" : " %d months", + "vcmi.optionsTab.simturns.months.1" : " %d month", + "vcmi.optionsTab.simturns.months.2" : " %d months", // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "The enemy has managed to survive till this day. Victory is theirs!", diff --git a/Mods/vcmi/config/vcmi/russian.json b/Mods/vcmi/config/vcmi/russian.json index 24b7d7931..3374b9df4 100644 --- a/Mods/vcmi/config/vcmi/russian.json +++ b/Mods/vcmi/config/vcmi/russian.json @@ -201,13 +201,14 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Распределение команд", "vcmi.randomMapTab.widgets.roadTypesLabel" : "Виды дорог", - "vcmi.optionsTab.widgets.chessFieldBase.help" : "{Время игрока}\n\nОбратный отсчет начинается когда {время на ход} истекает. Устанавливается один раз в начале игры. По истечении времени игрок завершает ход.", - "vcmi.optionsTab.widgets.chessFieldTurn.help" : "{Время на ход}\n\nОбратный отсчет начивается когда игрок начинает свой ход. В начале каждого хода устанавливается в иходное значение. Все неиспользованное время добавляется ко {времени игрока}, если оно используется.", - "vcmi.optionsTab.widgets.chessFieldBattle.help" : "{Время на битву}\n\nОбратный отсчет начинается когда {время на отряд истекает}. В начале каждой битвы устанавливается в исходное значение. По истечении времени текущий отряд получает приказ защищаться.", - "vcmi.optionsTab.widgets.chessFieldCreature.help" : "{Время на отряд}\n\nОбратный отсчет начинается когда игрок получает получает контроль над отрядом во время битвы. Устанавливается в исходное значение всякий раз, когда отряд получает возможность действовать.", - "vcmi.optionsTab.widgets.labelTimer" : "Таймер", - "vcmi.optionsTab.widgets.timerModeSwitch.classic" : "Классические часы", - "vcmi.optionsTab.widgets.timerModeSwitch.chess" : "Шахматные часы", + "vcmi.optionsTab.chessFieldBase.hover" : "Время игрока", + "vcmi.optionsTab.chessFieldTurn.hover" : "Время на ход", + "vcmi.optionsTab.chessFieldBattle.hover" : "Время на битву", + "vcmi.optionsTab.chessFieldCreature.hover" : "Время на отряд", + "vcmi.optionsTab.chessFieldBase.help" : "Обратный отсчет начинается когда {время на ход} истекает. Устанавливается один раз в начале игры. По истечении времени игрок завершает ход.", + "vcmi.optionsTab.chessFieldTurn.help" : "Обратный отсчет начивается когда игрок начинает свой ход. В начале каждого хода устанавливается в иходное значение. Все неиспользованное время добавляется ко {времени игрока}, если оно используется.", + "vcmi.optionsTab.chessFieldBattle.help" : "Обратный отсчет начинается когда {время на отряд истекает}. В начале каждой битвы устанавливается в исходное значение. По истечении времени текущий отряд получает приказ защищаться.", + "vcmi.optionsTab.chessFieldCreature.help" : "Обратный отсчет начинается когда игрок получает получает контроль над отрядом во время битвы. Устанавливается в исходное значение всякий раз, когда отряд получает возможность действовать.", "mapObject.core.creatureBank.cyclopsStockpile.name" : "Хранилище циклопов", "mapObject.core.creatureBank.dragonFlyHive.name" : "Улей летучих змиев", diff --git a/Mods/vcmi/config/vcmi/ukrainian.json b/Mods/vcmi/config/vcmi/ukrainian.json index 81a7244db..3a50c17f9 100644 --- a/Mods/vcmi/config/vcmi/ukrainian.json +++ b/Mods/vcmi/config/vcmi/ukrainian.json @@ -30,6 +30,11 @@ "vcmi.capitalColors.6" : "Сизий", "vcmi.capitalColors.7" : "Рожевий", + "vcmi.heroOverview.startingArmy" : "Початкові загони", + "vcmi.heroOverview.warMachine" : "Бойові машини", + "vcmi.heroOverview.secondarySkills" : "Навички", + "vcmi.heroOverview.spells" : "Закляття", + "vcmi.radialWheel.mergeSameUnit" : "Об'єднати однакових істот", "vcmi.radialWheel.fillSingleUnit" : "Заповнити одиничними істотами", "vcmi.radialWheel.splitSingleUnit" : "Відділити одну істоту", @@ -37,8 +42,21 @@ "vcmi.radialWheel.moveUnit" : "Перемістити істоту до іншої армії", "vcmi.radialWheel.splitUnit" : "Розділити істоту в інший слот", + "vcmi.radialWheel.heroGetArmy" : "Отримати армію іншого героя", + "vcmi.radialWheel.heroSwapArmy" : "Обміняти армії героїв", + "vcmi.radialWheel.heroExchange" : "Відкрити вікно обміну", + "vcmi.radialWheel.heroGetArtifacts" : "Отримати артефакти іншого героя", + "vcmi.radialWheel.heroSwapArtifacts" : "Обміняти артефакти героїв", + "vcmi.radialWheel.heroDismiss" : "Звільнити цього героя", + + "vcmi.radialWheel.moveTop" : "Перемістити на початок", + "vcmi.radialWheel.moveUp" : "Перемістити вгору", + "vcmi.radialWheel.moveDown" : "Перемістити вниз", + "vcmi.radialWheel.moveBottom" : "Перемістити у кінець", + "vcmi.mainMenu.serverConnecting" : "Підключення...", "vcmi.mainMenu.serverAddressEnter" : "Вкажіть адресу:", + "vcmi.mainMenu.serverConnectionFailed" : "Помилка з'єднання", "vcmi.mainMenu.serverClosing" : "Завершення...", "vcmi.mainMenu.hostTCP" : "Створити TCP/IP гру", "vcmi.mainMenu.joinTCP" : "Приєднатися до TCP/IP гри", @@ -46,9 +64,14 @@ "vcmi.lobby.filepath" : "Назва файлу", "vcmi.lobby.creationDate" : "Дата створення", + "vcmi.lobby.scenarioName" : "Scenario name", + "vcmi.lobby.mapPreview" : "Огляд мапи", + "vcmi.lobby.noPreview" : "огляд недоступний", + "vcmi.lobby.noUnderground" : "немає підземелля", "vcmi.server.errors.existingProcess" : "Працює інший процес vcmiserver, будь ласка, спочатку завершіть його", "vcmi.server.errors.modsToEnable" : "{Потрібні модифікації для завантаження гри}", + "vcmi.server.errors.modsToDisable" : "{Модифікації що мають бути вимкнені}", "vcmi.server.confirmReconnect" : "Підключитися до минулої сесії?", "vcmi.settingsMainWindow.generalTab.hover" : "Загальні", @@ -84,6 +107,8 @@ "vcmi.systemOptions.framerateButton.help" : "{Лічильник кадрів}\n\n Перемикає видимість лічильника кадрів на секунду у кутку ігрового вікна", "vcmi.systemOptions.hapticFeedbackButton.hover" : "Тактильний відгук", "vcmi.systemOptions.hapticFeedbackButton.help" : "{Тактильний відгук}\n\nВикористовувати вібрацію при використанні сенсорного екрану", + "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Розширення інтерфейсу", + "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Розширення інтерфейсу}\n\nУвімкніть різні розширення інтерфейсу для покращення якості життя. Наприклад, більша книга заклинань, рюкзак тощо. Вимкнути, щоб отримати більш класичний досвід.", "vcmi.adventureOptions.infoBarPick.help" : "{Повідомлення у панелі статусу}\n\nЗа можливості, повідомлення про відвідування об'єктів карти пригод будуть відображені у панелі статусу замість окремого вікна", "vcmi.adventureOptions.infoBarPick.hover" : "Повідомлення у панелі статусу", @@ -167,7 +192,7 @@ "vcmi.townHall.greetingCustomBonus" : "%s дає вам +%d %s%s", "vcmi.townHall.greetingCustomUntil" : " до наступної битви.", "vcmi.townHall.greetingInTownMagicWell" : "%s повністю відновлює ваш запас очків магії.", - + "vcmi.logicalExpressions.anyOf" : "Будь-що з перерахованого:", "vcmi.logicalExpressions.allOf" : "Все з перерахованого:", "vcmi.logicalExpressions.noneOf" : "Нічого з перерахованого:", @@ -195,6 +220,38 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Розподіл команд", "vcmi.randomMapTab.widgets.roadTypesLabel" : "Види доріг", + "vcmi.optionsTab.turnOptions.hover" : "Параметри ходів", + "vcmi.optionsTab.turnOptions.help" : "Виберіть опції таймера ходів та одночасних ходів", + + "vcmi.optionsTab.chessFieldBase.hover" : "Основний таймер", + "vcmi.optionsTab.chessFieldTurn.hover" : "Таймер ходу", + "vcmi.optionsTab.chessFieldBattle.hover" : "Таймер битви", + "vcmi.optionsTab.chessFieldCreature.hover" : "Таймер загону", + "vcmi.optionsTab.chessFieldBase.help" : "Встановлюється один раз на початку гри. Коли вичерпується, поточний хід буде перервано, поточна битва буде програна.", + "vcmi.optionsTab.chessFieldTurn.help" : "Використовується під час ходу. Встановлюється кожен хід. Залишок додається до {основного таймеру} у кінці ходу", + "vcmi.optionsTab.chessFieldBattle.help" : "Використовується у боях з ШІ чи у боях з гравцями якщо {таймер загону} вичерпується. Встановлюється на початку кожного бою.", + "vcmi.optionsTab.chessFieldCreature.help" : "Використовується при обираннія дії загону у боях з гравцями. Встановлюється на початку кожної дії.", + + "vcmi.optionsTab.simturns" : "Одночасні ходи", + "vcmi.optionsTab.simturnsMin.hover" : "Щонайменше", + "vcmi.optionsTab.simturnsMax.hover" : "Щонайбільше", + "vcmi.optionsTab.simturnsAI.hover" : "(Експериментально) Одночасні ходи ШІ", + "vcmi.optionsTab.simturnsMin.help" : "Грати одночасно обрану кількість днів. Контакти між гравцями у цей період заблоковані", + "vcmi.optionsTab.simturnsMax.help" : "Грати одночасно обрану кількість днів чи до першого контакту з іншим гравцем", + "vcmi.optionsTab.simturnsAI.help" : "{Одночасні ходи ШІ}\nЕкспериментальна опція. Дозволяє гравцям-ШІ діяти одночасно с гравцями-людьми якщо одночасні ходи увімкнені.", + + // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language + // Using this information, VCMI will automatically select correct plural form for every possible amount + "vcmi.optionsTab.simturns.days.0" : " %d днів", + "vcmi.optionsTab.simturns.days.1" : " %d день", + "vcmi.optionsTab.simturns.days.2" : " %d дні", + "vcmi.optionsTab.simturns.weeks.0" : " %d тижнів", + "vcmi.optionsTab.simturns.weeks.1" : " %d тиждень", + "vcmi.optionsTab.simturns.weeks.2" : " %d тижні", + "vcmi.optionsTab.simturns.months.0" : " %d місяців", + "vcmi.optionsTab.simturns.months.1" : " %d місяць", + "vcmi.optionsTab.simturns.months.2" : " %d місяці", + // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "Ворогу вдалося вижити до сьогоднішнього дня. Він переміг!", "vcmi.map.victoryCondition.daysPassed.toSelf" : "Вітаємо! Вам вдалося залишитися в живих. Перемога за вами!", diff --git a/Mods/vcmi/config/vcmi/vietnamese.json b/Mods/vcmi/config/vcmi/vietnamese.json index 890e699d1..0750201cf 100644 --- a/Mods/vcmi/config/vcmi/vietnamese.json +++ b/Mods/vcmi/config/vcmi/vietnamese.json @@ -198,13 +198,14 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel": "Sắp đội", "vcmi.randomMapTab.widgets.roadTypesLabel": "Kiểu đường xá", - "vcmi.optionsTab.widgets.chessFieldBase.help": "{Thời gian thêm}\n\nBắt đầu đếm ngược khi {Thời gian lượt} giảm đến 0. Được đặt 1 lần khi bắt đầu trò chơi. Khi thời gian này giảm đến 0, lượt của người chơi kết thúc.", - "vcmi.optionsTab.widgets.chessFieldTurn.help": "{Thời gian lượt}\n\nBắt đầu đếm ngược khi đến lượt người chơi trên bản đồ phiêu lưu. Nó được đặt lại giá trị ban đầu khi bắt đầu mỗi lượt. Thời gian lượt chưa sử dụng sẽ được thêm vào {Thời gian thêm} nếu có.", - "vcmi.optionsTab.widgets.chessFieldBattle.help": "{Thời gian trận đánh}\n\nĐếm ngược trong suốt trận đánh khi {Thời gian lính} giảm đến 0. Nó được đặt lại giá trị ban đầu khi bắt đầu mỗi trận đánh. Nếu thời gian giảm đến 0, đội lính hiện tại sẽ phòng thủ.", - "vcmi.optionsTab.widgets.chessFieldCreature.help": "{Thời gian lính}\n\nBắt đầu đếm ngược khi người chơi đang chọn hành động cho đội linh hiện tại trong suốt trận đánh. Nó được đặt lại giá trị ban đầu sau khi hành động của đội lính hoàn tất.", - "vcmi.optionsTab.widgets.labelTimer": "Đồng hồ", - "vcmi.optionsTab.widgets.timerModeSwitch.classic": "Đồng hồ cơ bản", - "vcmi.optionsTab.widgets.timerModeSwitch.chess": "Đồng hồ đánh cờ", + "vcmi.optionsTab.chessFieldBase.hover" : "Thời gian thêm", + "vcmi.optionsTab.chessFieldTurn.hover" : "Thời gian lượt", + "vcmi.optionsTab.chessFieldBattle.hover" : "Thời gian trận đánh", + "vcmi.optionsTab.chessFieldCreature.hover" : "Thời gian lính", + "vcmi.optionsTab.chessFieldBase.help": "Bắt đầu đếm ngược khi {Thời gian lượt} giảm đến 0. Được đặt 1 lần khi bắt đầu trò chơi. Khi thời gian này giảm đến 0, lượt của người chơi kết thúc.", + "vcmi.optionsTab.chessFieldTurn.help": "Bắt đầu đếm ngược khi đến lượt người chơi trên bản đồ phiêu lưu. Nó được đặt lại giá trị ban đầu khi bắt đầu mỗi lượt. Thời gian lượt chưa sử dụng sẽ được thêm vào {Thời gian thêm} nếu có.", + "vcmi.optionsTab.chessFieldBattle.help": "Đếm ngược trong suốt trận đánh khi {Thời gian lính} giảm đến 0. Nó được đặt lại giá trị ban đầu khi bắt đầu mỗi trận đánh. Nếu thời gian giảm đến 0, đội lính hiện tại sẽ phòng thủ.", + "vcmi.optionsTab.chessFieldCreature.help": "Bắt đầu đếm ngược khi người chơi đang chọn hành động cho đội linh hiện tại trong suốt trận đánh. Nó được đặt lại giá trị ban đầu sau khi hành động của đội lính hoàn tất.", "vcmi.map.victoryCondition.daysPassed.toOthers": "Đối thủ đã xoay xở để sinh tồn đến ngày này. Họ giành chiến thắng!", "vcmi.map.victoryCondition.daysPassed.toSelf": "Chúc mừng! Bạn đã vượt khó để sinh tồn. Chiến thắng thuộc về bạn!", diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 6257722a2..2f11a87c9 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -51,7 +51,9 @@ set(client_SRCS lobby/CSavingScreen.cpp lobby/CScenarioInfoScreen.cpp lobby/CSelectionBase.cpp + lobby/TurnOptionsTab.cpp lobby/OptionsTab.cpp + lobby/OptionsTabBase.cpp lobby/RandomMapTab.cpp lobby/SelectionTab.cpp @@ -211,7 +213,9 @@ set(client_HEADERS lobby/CSavingScreen.h lobby/CScenarioInfoScreen.h lobby/CSelectionBase.h + lobby/TurnOptionsTab.h lobby/OptionsTab.h + lobby/OptionsTabBase.h lobby/RandomMapTab.h lobby/SelectionTab.h diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index 935dc88da..fecc485da 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -501,12 +501,25 @@ std::shared_ptr InterfaceObjectConfigurable::buildSlider(const JsonNode auto position = readPosition(config["position"]); int length = config["size"].Integer(); auto style = config["style"].String() == "brown" ? CSlider::BROWN : CSlider::BLUE; - auto itemsVisible = config["itemsVisible"].Integer(); - auto itemsTotal = config["itemsTotal"].Integer(); auto value = config["selected"].Integer(); bool horizontal = config["orientation"].String() == "horizontal"; - const auto & result = - std::make_shared(position, length, callbacks_int.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal ? Orientation::HORIZONTAL : Orientation::VERTICAL, style); + auto orientation = horizontal ? Orientation::HORIZONTAL : Orientation::VERTICAL; + + std::shared_ptr result; + + if (config["items"].isNull()) + { + auto itemsVisible = config["itemsVisible"].Integer(); + auto itemsTotal = config["itemsTotal"].Integer(); + + result = std::make_shared(position, length, callbacks_int.at(config["callback"].String()), itemsVisible, itemsTotal, value, orientation, style); + } + else + { + auto items = config["items"].convertTo>(); + result = std::make_shared(position, length, callbacks_int.at(config["callback"].String()), items, value, orientation, style); + } + if(!config["scrollBounds"].isNull()) { diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index fc0333f37..135e0d078 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -8,14 +8,15 @@ * */ #include "StdInc.h" - #include "CLobbyScreen.h" -#include "CBonusSelection.h" -#include "SelectionTab.h" -#include "RandomMapTab.h" -#include "OptionsTab.h" -#include "../CServerHandler.h" +#include "CBonusSelection.h" +#include "TurnOptionsTab.h" +#include "OptionsTab.h" +#include "RandomMapTab.h" +#include "SelectionTab.h" + +#include "../CServerHandler.h" #include "../gui/CGuiHandler.h" #include "../gui/Shortcut.h" #include "../widgets/Buttons.h" @@ -24,12 +25,13 @@ #include "../../CCallback.h" -#include "../CGameInfo.h" -#include "../../lib/networkPacks/PacksForLobby.h" +#include "../../lib/CConfigHandler.h" #include "../../lib/CGeneralTextHandler.h" #include "../../lib/campaign/CampaignHandler.h" #include "../../lib/mapping/CMapInfo.h" +#include "../../lib/networkPacks/PacksForLobby.h" #include "../../lib/rmg/CMapGenOptions.h" +#include "../CGameInfo.h" CLobbyScreen::CLobbyScreen(ESelectionScreen screenType) : CSelectionBase(screenType), bonusSel(nullptr) @@ -50,6 +52,8 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType) }); buttonOptions = std::make_shared(Point(411, 510), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabOpt), EShortcut::LOBBY_ADDITIONAL_OPTIONS); + if(settings["general"]["enableUiEnhancements"].Bool()) + buttonTurnOptions = std::make_shared(Point(619, 510), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabTurnOptions), EShortcut::NONE); }; buttonChat = std::make_shared(Point(619, 80), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT); @@ -60,6 +64,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType) case ESelectionScreen::newGame: { tabOpt = std::make_shared(); + tabTurnOptions = std::make_shared(); tabRand = std::make_shared(); tabRand->mapInfoChanged += std::bind(&IServerAPI::setMapInfo, CSH, _1, _2); buttonRMG = std::make_shared(Point(411, 105), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[47], 0, EShortcut::LOBBY_RANDOM_MAP); @@ -78,6 +83,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType) case ESelectionScreen::loadGame: { tabOpt = std::make_shared(); + tabTurnOptions = std::make_shared(); buttonStart = std::make_shared(Point(411, 535), AnimationPath::builtin("SCNRLOD.DEF"), CGI->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, true), EShortcut::LOBBY_LOAD_GAME); initLobby(); break; @@ -145,6 +151,10 @@ void CLobbyScreen::toggleMode(bool host) auto buttonColor = host ? Colors::WHITE : Colors::ORANGE; buttonSelect->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor); buttonOptions->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, buttonColor); + + if (buttonTurnOptions) + buttonTurnOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.turnOptions.hover"), FONT_SMALL, buttonColor); + if(buttonRMG) { buttonRMG->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor); @@ -153,8 +163,14 @@ void CLobbyScreen::toggleMode(bool host) buttonSelect->block(!host); buttonOptions->block(!host); + if (buttonTurnOptions) + buttonTurnOptions->block(!host); + if(CSH->mi) + { tabOpt->recreate(); + tabTurnOptions->recreate(); + } } void CLobbyScreen::toggleChat() @@ -168,8 +184,13 @@ void CLobbyScreen::toggleChat() void CLobbyScreen::updateAfterStateChange() { - if(CSH->mi && tabOpt) - tabOpt->recreate(); + if(CSH->mi) + { + if (tabOpt) + tabOpt->recreate(); + if (tabTurnOptions) + tabTurnOptions->recreate(); + } buttonStart->block(CSH->mi == nullptr || CSH->isGuest()); diff --git a/client/lobby/CSelectionBase.h b/client/lobby/CSelectionBase.h index 92b0c2b4f..e614468cf 100644 --- a/client/lobby/CSelectionBase.h +++ b/client/lobby/CSelectionBase.h @@ -26,6 +26,7 @@ class CAnimImage; class CToggleGroup; class RandomMapTab; class OptionsTab; +class TurnOptionsTab; class SelectionTab; class InfoCard; class CChatBox; @@ -57,12 +58,14 @@ public: std::shared_ptr buttonSelect; std::shared_ptr buttonRMG; std::shared_ptr buttonOptions; + std::shared_ptr buttonTurnOptions; std::shared_ptr buttonStart; std::shared_ptr buttonBack; std::shared_ptr buttonSimturns; std::shared_ptr tabSel; std::shared_ptr tabOpt; + std::shared_ptr tabTurnOptions; std::shared_ptr tabRand; std::shared_ptr curTab; diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index a2f5027b0..0584a1999 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -42,164 +42,10 @@ #include "../../lib/mapping/CMapInfo.h" #include "../../lib/mapping/CMapHeader.h" -OptionsTab::OptionsTab() : humanPlayers(0) +OptionsTab::OptionsTab() + : OptionsTabBase(JsonPath::builtin("config/widgets/playerOptionsTab.json")) + , humanPlayers(0) { - recActions = 0; - - addCallback("setTimerPreset", [&](int index){ - if(!variables["timerPresets"].isNull()) - { - auto tpreset = variables["timerPresets"].Vector().at(index).Vector(); - TurnTimerInfo tinfo; - tinfo.baseTimer = tpreset.at(0).Integer() * 1000; - tinfo.turnTimer = tpreset.at(1).Integer() * 1000; - tinfo.battleTimer = tpreset.at(2).Integer() * 1000; - tinfo.creatureTimer = tpreset.at(3).Integer() * 1000; - CSH->setTurnTimerInfo(tinfo); - } - }); - - addCallback("setSimturnDuration", [&](int index){ - SimturnsInfo info; - info.optionalTurns = index; - CSH->setSimturnsInfo(info); - }); - - //helper function to parse string containing time to integer reflecting time in seconds - //assumed that input string can be modified by user, function shall support user's intention - // normal: 2:00, 12:30 - // adding symbol: 2:005 -> 2:05, 2:305 -> 23:05, - // adding symbol (>60 seconds): 12:095 -> 129:05 - // removing symbol: 129:0 -> 12:09, 2:0 -> 0:20, 0:2 -> 0:02 - auto parseTimerString = [](const std::string & str) -> int - { - auto sc = str.find(":"); - if(sc == std::string::npos) - return str.empty() ? 0 : std::stoi(str); - - auto l = str.substr(0, sc); - auto r = str.substr(sc + 1, std::string::npos); - if(r.length() == 3) //symbol added - { - l.push_back(r.front()); - r.erase(r.begin()); - } - else if(r.length() == 1) //symbol removed - { - r.insert(r.begin(), l.back()); - l.pop_back(); - } - else if(r.empty()) - r = "0"; - - int sec = std::stoi(r); - if(sec >= 60) - { - if(l.empty()) //9:00 -> 0:09 - return sec / 10; - - l.push_back(r.front()); //0:090 -> 9:00 - r.erase(r.begin()); - } - else if(l.empty()) - return sec; - - return std::stoi(l) * 60 + std::stoi(r); - }; - - addCallback("parseAndSetTimer_base", [parseTimerString](const std::string & str){ - int time = parseTimerString(str) * 1000; - if(time >= 0) - { - TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; - tinfo.baseTimer = time; - CSH->setTurnTimerInfo(tinfo); - } - }); - addCallback("parseAndSetTimer_turn", [parseTimerString](const std::string & str){ - int time = parseTimerString(str) * 1000; - if(time >= 0) - { - TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; - tinfo.turnTimer = time; - CSH->setTurnTimerInfo(tinfo); - } - }); - addCallback("parseAndSetTimer_battle", [parseTimerString](const std::string & str){ - int time = parseTimerString(str) * 1000; - if(time >= 0) - { - TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; - tinfo.battleTimer = time; - CSH->setTurnTimerInfo(tinfo); - } - }); - addCallback("parseAndSetTimer_creature", [parseTimerString](const std::string & str){ - int time = parseTimerString(str) * 1000; - if(time >= 0) - { - TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; - tinfo.creatureTimer = time; - CSH->setTurnTimerInfo(tinfo); - } - }); - - const JsonNode config(JsonPath::builtin("config/widgets/optionsTab.json")); - build(config); - - //set timers combo box callbacks - if(auto w = widget("timerModeSwitch")) - { - w->onConstructItems = [&](std::vector & curItems){ - if(variables["timers"].isNull()) - return; - - for(auto & p : variables["timers"].Vector()) - { - curItems.push_back(&p); - } - }; - - w->onSetItem = [&](const void * item){ - if(item) - { - if(auto * tObj = reinterpret_cast(item)) - { - for(auto wname : (*tObj)["hideWidgets"].Vector()) - { - if(auto w = widget(wname.String())) - w->setEnabled(false); - } - for(auto wname : (*tObj)["showWidgets"].Vector()) - { - if(auto w = widget(wname.String())) - w->setEnabled(true); - } - if((*tObj)["default"].isVector()) - { - TurnTimerInfo tinfo; - tinfo.baseTimer = (*tObj)["default"].Vector().at(0).Integer() * 1000; - tinfo.turnTimer = (*tObj)["default"].Vector().at(1).Integer() * 1000; - tinfo.battleTimer = (*tObj)["default"].Vector().at(2).Integer() * 1000; - tinfo.creatureTimer = (*tObj)["default"].Vector().at(3).Integer() * 1000; - CSH->setTurnTimerInfo(tinfo); - } - } - redraw(); - } - }; - - w->getItemText = [this](int idx, const void * item){ - if(item) - { - if(auto * tObj = reinterpret_cast(item)) - return readText((*tObj)["text"]); - } - return std::string(""); - }; - - w->setItem(0); - } } void OptionsTab::recreate() @@ -221,64 +67,7 @@ void OptionsTab::recreate() entries.insert(std::make_pair(pInfo.first, std::make_shared(pInfo.second, * this))); } - //Simultaneous turns - if(auto turnSlider = widget("labelSimturnsDurationValue")) - turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.optionalTurns); - - if(auto w = widget("labelSimturnsDurationValue")) - { - MetaString message; - message.appendRawString("Simturns: up to %d days"); - message.replaceNumber(SEL->getStartInfo()->simturnsInfo.optionalTurns); - w->setText(message.toString()); - } - - const auto & turnTimerRemote = SEL->getStartInfo()->turnTimerInfo; - - //classic timer - if(auto turnSlider = widget("sliderTurnDuration")) - { - if(!variables["timerPresets"].isNull() && !turnTimerRemote.battleTimer && !turnTimerRemote.creatureTimer && !turnTimerRemote.baseTimer) - { - for(int idx = 0; idx < variables["timerPresets"].Vector().size(); ++idx) - { - auto & tpreset = variables["timerPresets"].Vector()[idx]; - if(tpreset.Vector().at(1).Integer() == turnTimerRemote.turnTimer / 1000) - { - turnSlider->scrollTo(idx); - if(auto w = widget("labelTurnDurationValue")) - w->setText(CGI->generaltexth->turnDurations[idx]); - } - } - } - } - - //chess timer - auto timeToString = [](int time) -> std::string - { - std::stringstream ss; - ss << time / 1000 / 60 << ":" << std::setw(2) << std::setfill('0') << time / 1000 % 60; - return ss.str(); - }; - - if(auto ww = widget("chessFieldBase")) - ww->setText(timeToString(turnTimerRemote.baseTimer), false); - if(auto ww = widget("chessFieldTurn")) - ww->setText(timeToString(turnTimerRemote.turnTimer), false); - if(auto ww = widget("chessFieldBattle")) - ww->setText(timeToString(turnTimerRemote.battleTimer), false); - if(auto ww = widget("chessFieldCreature")) - ww->setText(timeToString(turnTimerRemote.creatureTimer), false); - - if(auto w = widget("timerModeSwitch")) - { - if(turnTimerRemote.battleTimer || turnTimerRemote.creatureTimer || turnTimerRemote.baseTimer) - { - if(auto turnSlider = widget("sliderTurnDuration")) - if(turnSlider->isActive()) - w->setItem(1); - } - } + OptionsTabBase::recreate(); } size_t OptionsTab::CPlayerSettingsHelper::getImageIndex(bool big) diff --git a/client/lobby/OptionsTab.h b/client/lobby/OptionsTab.h index 23eff9c96..691a162bb 100644 --- a/client/lobby/OptionsTab.h +++ b/client/lobby/OptionsTab.h @@ -9,9 +9,9 @@ */ #pragma once +#include "OptionsTabBase.h" #include "../windows/CWindowObject.h" #include "../widgets/Scrollable.h" -#include "../gui/InterfaceObjectConfigurable.h" VCMI_LIB_NAMESPACE_BEGIN struct PlayerSettings; @@ -30,7 +30,7 @@ class CButton; class FilledTexturePlayerColored; /// The options tab which is shown at the map selection phase. -class OptionsTab : public InterfaceObjectConfigurable +class OptionsTab : public OptionsTabBase { struct PlayerOptionsEntry; diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp new file mode 100644 index 000000000..18953eb97 --- /dev/null +++ b/client/lobby/OptionsTabBase.cpp @@ -0,0 +1,295 @@ +/* + * OptionsTabBase.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "OptionsTabBase.h" +#include "CSelectionBase.h" + +#include "../widgets/ComboBox.h" +#include "../widgets/Slider.h" +#include "../widgets/TextControls.h" +#include "../CServerHandler.h" +#include "../CGameInfo.h" + +#include "../../lib/StartInfo.h" +#include "../../lib/Languages.h" +#include "../../lib/MetaString.h" +#include "../../lib/CGeneralTextHandler.h" + +OptionsTabBase::OptionsTabBase(const JsonPath & configPath) +{ + recActions = 0; + + addCallback("setTimerPreset", [&](int index){ + if(!variables["timerPresets"].isNull()) + { + auto tpreset = variables["timerPresets"].Vector().at(index).Vector(); + TurnTimerInfo tinfo; + tinfo.baseTimer = tpreset.at(0).Integer() * 1000; + tinfo.turnTimer = tpreset.at(1).Integer() * 1000; + tinfo.battleTimer = tpreset.at(2).Integer() * 1000; + tinfo.creatureTimer = tpreset.at(3).Integer() * 1000; + CSH->setTurnTimerInfo(tinfo); + } + }); + + addCallback("setSimturnDurationMin", [&](int index){ + SimturnsInfo info = SEL->getStartInfo()->simturnsInfo; + info.requiredTurns = index; + info.optionalTurns = std::max(info.optionalTurns, index); + CSH->setSimturnsInfo(info); + }); + + addCallback("setSimturnDurationMax", [&](int index){ + SimturnsInfo info = SEL->getStartInfo()->simturnsInfo; + info.optionalTurns = index; + info.requiredTurns = std::min(info.requiredTurns, index); + CSH->setSimturnsInfo(info); + }); + + addCallback("setSimturnAI", [&](int index){ + SimturnsInfo info = SEL->getStartInfo()->simturnsInfo; + info.allowHumanWithAI = index; + CSH->setSimturnsInfo(info); + }); + + //helper function to parse string containing time to integer reflecting time in seconds + //assumed that input string can be modified by user, function shall support user's intention + // normal: 2:00, 12:30 + // adding symbol: 2:005 -> 2:05, 2:305 -> 23:05, + // adding symbol (>60 seconds): 12:095 -> 129:05 + // removing symbol: 129:0 -> 12:09, 2:0 -> 0:20, 0:2 -> 0:02 + auto parseTimerString = [](const std::string & str) -> int + { + auto sc = str.find(":"); + if(sc == std::string::npos) + return str.empty() ? 0 : std::stoi(str); + + auto l = str.substr(0, sc); + auto r = str.substr(sc + 1, std::string::npos); + if(r.length() == 3) //symbol added + { + l.push_back(r.front()); + r.erase(r.begin()); + } + else if(r.length() == 1) //symbol removed + { + r.insert(r.begin(), l.back()); + l.pop_back(); + } + else if(r.empty()) + r = "0"; + + int sec = std::stoi(r); + if(sec >= 60) + { + if(l.empty()) //9:00 -> 0:09 + return sec / 10; + + l.push_back(r.front()); //0:090 -> 9:00 + r.erase(r.begin()); + } + else if(l.empty()) + return sec; + + return std::stoi(l) * 60 + std::stoi(r); + }; + + addCallback("parseAndSetTimer_base", [parseTimerString](const std::string & str){ + int time = parseTimerString(str) * 1000; + if(time >= 0) + { + TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; + tinfo.baseTimer = time; + CSH->setTurnTimerInfo(tinfo); + } + }); + addCallback("parseAndSetTimer_turn", [parseTimerString](const std::string & str){ + int time = parseTimerString(str) * 1000; + if(time >= 0) + { + TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; + tinfo.turnTimer = time; + CSH->setTurnTimerInfo(tinfo); + } + }); + addCallback("parseAndSetTimer_battle", [parseTimerString](const std::string & str){ + int time = parseTimerString(str) * 1000; + if(time >= 0) + { + TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; + tinfo.battleTimer = time; + CSH->setTurnTimerInfo(tinfo); + } + }); + addCallback("parseAndSetTimer_creature", [parseTimerString](const std::string & str){ + int time = parseTimerString(str) * 1000; + if(time >= 0) + { + TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; + tinfo.creatureTimer = time; + CSH->setTurnTimerInfo(tinfo); + } + }); + + const JsonNode config(configPath); + build(config); + + //set timers combo box callbacks + if(auto w = widget("timerModeSwitch")) + { + w->onConstructItems = [&](std::vector & curItems){ + if(variables["timers"].isNull()) + return; + + for(auto & p : variables["timers"].Vector()) + { + curItems.push_back(&p); + } + }; + + w->onSetItem = [&](const void * item){ + if(item) + { + if(auto * tObj = reinterpret_cast(item)) + { + for(auto wname : (*tObj)["hideWidgets"].Vector()) + { + if(auto w = widget(wname.String())) + w->setEnabled(false); + } + for(auto wname : (*tObj)["showWidgets"].Vector()) + { + if(auto w = widget(wname.String())) + w->setEnabled(true); + } + if((*tObj)["default"].isVector()) + { + TurnTimerInfo tinfo; + tinfo.baseTimer = (*tObj)["default"].Vector().at(0).Integer() * 1000; + tinfo.turnTimer = (*tObj)["default"].Vector().at(1).Integer() * 1000; + tinfo.battleTimer = (*tObj)["default"].Vector().at(2).Integer() * 1000; + tinfo.creatureTimer = (*tObj)["default"].Vector().at(3).Integer() * 1000; + CSH->setTurnTimerInfo(tinfo); + } + } + redraw(); + } + }; + + w->getItemText = [this](int idx, const void * item){ + if(item) + { + if(auto * tObj = reinterpret_cast(item)) + return readText((*tObj)["text"]); + } + return std::string(""); + }; + + w->setItem(0); + } +} + +void OptionsTabBase::recreate() +{ + auto const & generateSimturnsDurationText = [](int days) -> std::string + { + if (days == 0) + return CGI->generaltexth->translate("core.genrltxt.523"); + + if (days >= 1000000) // Not "unlimited" but close enough + return CGI->generaltexth->translate("core.turndur.10"); + + bool canUseMonth = days % 28 == 0 && days >= 28*2; + bool canUseWeek = days % 7 == 0 && days >= 7*2; + + int value = days; + std::string text = "vcmi.optionsTab.simturns.days"; + + if (canUseWeek && !canUseMonth) + { + value = days / 7; + text = "vcmi.optionsTab.simturns.weeks"; + } + + if (canUseMonth) + { + value = days / 28; + text = "vcmi.optionsTab.simturns.months"; + } + + MetaString message; + message.appendTextID(Languages::getPluralFormTextID( CGI->generaltexth->getPreferredLanguage(), value, text)); + message.replaceNumber(value); + return message.toString(); + }; + + //Simultaneous turns + if(auto turnSlider = widget("simturnsDurationMin")) + turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.requiredTurns); + + if(auto turnSlider = widget("simturnsDurationMax")) + turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.optionalTurns); + + if(auto w = widget("labelSimturnsDurationValueMin")) + w->setText(generateSimturnsDurationText(SEL->getStartInfo()->simturnsInfo.requiredTurns)); + + if(auto w = widget("labelSimturnsDurationValueMax")) + w->setText(generateSimturnsDurationText(SEL->getStartInfo()->simturnsInfo.optionalTurns)); + + if(auto buttonSimturnsAI = widget("buttonSimturnsAI")) + buttonSimturnsAI->setSelectedSilent(SEL->getStartInfo()->simturnsInfo.allowHumanWithAI); + + const auto & turnTimerRemote = SEL->getStartInfo()->turnTimerInfo; + + //classic timer + if(auto turnSlider = widget("sliderTurnDuration")) + { + if(!variables["timerPresets"].isNull() && !turnTimerRemote.battleTimer && !turnTimerRemote.creatureTimer && !turnTimerRemote.baseTimer) + { + for(int idx = 0; idx < variables["timerPresets"].Vector().size(); ++idx) + { + auto & tpreset = variables["timerPresets"].Vector()[idx]; + if(tpreset.Vector().at(1).Integer() == turnTimerRemote.turnTimer / 1000) + { + turnSlider->scrollTo(idx); + if(auto w = widget("labelTurnDurationValue")) + w->setText(CGI->generaltexth->turnDurations[idx]); + } + } + } + } + + //chess timer + auto timeToString = [](int time) -> std::string + { + std::stringstream ss; + ss << time / 1000 / 60 << ":" << std::setw(2) << std::setfill('0') << time / 1000 % 60; + return ss.str(); + }; + + if(auto ww = widget("chessFieldBase")) + ww->setText(timeToString(turnTimerRemote.baseTimer), false); + if(auto ww = widget("chessFieldTurn")) + ww->setText(timeToString(turnTimerRemote.turnTimer), false); + if(auto ww = widget("chessFieldBattle")) + ww->setText(timeToString(turnTimerRemote.battleTimer), false); + if(auto ww = widget("chessFieldCreature")) + ww->setText(timeToString(turnTimerRemote.creatureTimer), false); + + if(auto w = widget("timerModeSwitch")) + { + if(turnTimerRemote.battleTimer || turnTimerRemote.creatureTimer || turnTimerRemote.baseTimer) + { + if(auto turnSlider = widget("sliderTurnDuration")) + if(turnSlider->isActive()) + w->setItem(1); + } + } +} diff --git a/client/lobby/OptionsTabBase.h b/client/lobby/OptionsTabBase.h new file mode 100644 index 000000000..4ba7e82c8 --- /dev/null +++ b/client/lobby/OptionsTabBase.h @@ -0,0 +1,22 @@ +/* + * OptionsTabBase.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../gui/InterfaceObjectConfigurable.h" +#include "../../lib/filesystem/ResourcePath.h" + +/// The options tab which is shown at the map selection phase. +class OptionsTabBase : public InterfaceObjectConfigurable +{ +public: + OptionsTabBase(const JsonPath & configPath); + + void recreate(); +}; diff --git a/client/lobby/TurnOptionsTab.cpp b/client/lobby/TurnOptionsTab.cpp new file mode 100644 index 000000000..6bf3523a8 --- /dev/null +++ b/client/lobby/TurnOptionsTab.cpp @@ -0,0 +1,18 @@ +/* + * TurnOptionsTab.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "TurnOptionsTab.h" + +TurnOptionsTab::TurnOptionsTab() + : OptionsTabBase(JsonPath::builtin("config/widgets/turnOptionsTab.json")) +{ + +} diff --git a/client/lobby/TurnOptionsTab.h b/client/lobby/TurnOptionsTab.h new file mode 100644 index 000000000..ad9ad1450 --- /dev/null +++ b/client/lobby/TurnOptionsTab.h @@ -0,0 +1,18 @@ +/* + * TurnOptionsTab.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "OptionsTabBase.h" + +class TurnOptionsTab : public OptionsTabBase +{ +public: + TurnOptionsTab(); +}; diff --git a/client/widgets/Slider.cpp b/client/widgets/Slider.cpp index 955c01649..b8dfc98bf 100644 --- a/client/widgets/Slider.cpp +++ b/client/widgets/Slider.cpp @@ -69,6 +69,11 @@ int CSlider::getValue() const return value; } +void CSlider::setValue(int to) +{ + scrollTo(value); +} + int CSlider::getCapacity() const { return capacity; @@ -119,7 +124,7 @@ void CSlider::scrollTo(int to) updateSliderPos(); - moved(to); + moved(getValue()); } void CSlider::clickPressed(const Point & cursorPosition) @@ -164,7 +169,7 @@ bool CSlider::receiveEvent(const Point &position, int eventType) const return testTarget.isInside(position); } -CSlider::CSlider(Point position, int totalw, std::function Moved, int Capacity, int Amount, int Value, Orientation orientation, CSlider::EStyle style) +CSlider::CSlider(Point position, int totalw, const std::function & Moved, int Capacity, int Amount, int Value, Orientation orientation, CSlider::EStyle style) : Scrollable(LCLICK | DRAG, position, orientation ), capacity(Capacity), amount(Amount), @@ -297,3 +302,31 @@ void CSlider::scrollToMax() { scrollTo(amount); } + +SliderNonlinear::SliderNonlinear(Point position, int length, const std::function & Moved, const std::vector & values, int Value, Orientation orientation, EStyle style) + : CSlider(position, length, Moved, 1, values.size(), Value, orientation, style) + , scaledValues(values) +{ + +} + +int SliderNonlinear::getValue() const +{ + return scaledValues.at(CSlider::getValue()); +} + +void SliderNonlinear::setValue(int to) +{ + size_t nearest = 0; + + for(size_t i = 0; i < scaledValues.size(); ++i) + { + int nearestDistance = std::abs(to - scaledValues[nearest]); + int currentDistance = std::abs(to - scaledValues[i]); + + if(currentDistance < nearestDistance) + nearest = i; + } + + scrollTo(nearest); +} diff --git a/client/widgets/Slider.h b/client/widgets/Slider.h index f7a8cad10..b6e8be677 100644 --- a/client/widgets/Slider.h +++ b/client/widgets/Slider.h @@ -59,10 +59,11 @@ public: /// Amount modifier void setAmount(int to); + virtual void setValue(int to); /// Accessors int getAmount() const; - int getValue() const; + virtual int getValue() const; int getCapacity() const; void addCallback(std::function callback); @@ -80,7 +81,20 @@ public: /// @param Capacity maximal number of visible at once elements /// @param Amount total amount of elements, including not visible /// @param Value starting position - CSlider(Point position, int length, std::function Moved, int Capacity, int Amount, + CSlider(Point position, int length, const std::function & Moved, int Capacity, int Amount, int Value, Orientation orientation, EStyle style = BROWN); ~CSlider(); }; + +class SliderNonlinear : public CSlider +{ + /// If non-empty then slider has non-linear values, e.g. if slider is at position 5 out of 10 then actual "value" is not 5, but 5th value in this vector + std::vector scaledValues; + + using CSlider::setAmount; // make private +public: + void setValue(int to) override; + int getValue() const override; + + SliderNonlinear(Point position, int length, const std::function & Moved, const std::vector & values, int Value, Orientation orientation, EStyle style); +}; diff --git a/config/widgets/optionsTab.json b/config/widgets/playerOptionsTab.json similarity index 75% rename from config/widgets/optionsTab.json rename to config/widgets/playerOptionsTab.json index bd8c76126..ca3c936f2 100644 --- a/config/widgets/optionsTab.json +++ b/config/widgets/playerOptionsTab.json @@ -73,41 +73,16 @@ "adoptHeight": true }, + // timer { - "name": "simturnsDuration", - "type": "slider", - "orientation": "horizontal", - "position": {"x": 55, "y": 537}, - "size": 194, - "callback": "setSimturnDuration", - "itemsVisible": 1, - "itemsTotal": 28, - "selected": 0, - "style": "blue", - "scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, - "panningStep": 20 - }, - - { - "name": "labelSimturnsDurationValue", "type": "label", "font": "small", "alignment": "center", - "color": "white", - "text": "", - "position": {"x": 319, "y": 545} + "color": "yellow", + "text": "core.genrltxt.521", + "position": {"x": 222, "y": 544} }, - // timer - //{ - // "type": "label", - // "font": "small", - // "alignment": "center", - // "color": "yellow", - // "text": "core.genrltxt.521", - // "position": {"x": 222, "y": 544} - //}, - { "name": "labelTurnDurationValue", "type": "label", @@ -129,8 +104,7 @@ "itemsTotal": 11, "selected": 11, "style": "blue", - "scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, - //"scrollBounds": {"x": -3, "y": -25, "w": 337, "h": 43}, + "scrollBounds": {"x": -3, "y": -25, "w": 337, "h": 43}, "panningStep": 20 }, ], diff --git a/config/widgets/turnOptionsTab.json b/config/widgets/turnOptionsTab.json new file mode 100644 index 000000000..b84aba8e8 --- /dev/null +++ b/config/widgets/turnOptionsTab.json @@ -0,0 +1,285 @@ +{ + "customTypes" : { + "verticalLayout66" : { + "type" : "layout", + "vertical" : true, + "dynamic" : false, + "distance" : 66 + }, + "labelTitle" : { + "type": "label", + "font": "small", + "alignment": "left", + "color": "yellow" + }, + "labelDescription" : { + "type": "multiLineLabel", + "font": "tiny", + "alignment": "center", + "color": "white", + "rect": {"x": 0, "y": 0, "w": 300, "h": 35}, + "adoptHeight": true + }, + "timeInput" : { + "type": "textInput", + "alignment": "center", + "text": "00:00", + "rect": {"x": 0, "y": 0, "w": 86, "h": 23}, + "offset": {"x": 0, "y": 0} + }, + "timeInputBackground" : { + "type": "transparentFilledRectangle", + "rect": {"x": 0, "y": 0, "w": 86, "h": 23}, + "color": [0, 0, 0, 128], + "colorLine": [64, 80, 128, 128] + } + }, + + "items": + [ + { + "name": "background", + "type": "picture", + "image": "RANMAPBK", + "position": {"x": 0, "y": 6} + }, + + { + "name": "labelTitle", + "type": "label", + "font": "big", + "alignment": "center", + "color": "yellow", + "text": "vcmi.optionsTab.turnOptions.hover", + "position": {"x": 222, "y": 36} + }, + + { + "name": "labelSubTitle", + "type": "multiLineLabel", + "font": "small", + "alignment": "center", + "color": "white", + "text": "vcmi.optionsTab.turnOptions.help", + "rect": {"x": 60, "y": 48, "w": 320, "h": 0}, + "adoptHeight": true + }, + { + "type": "texture", + "image": "DiBoxBck", + "color" : "blue", + "rect": {"x" : 64, "y" : 394, "w": 316, "h": 124} + }, + { + "type": "transparentFilledRectangle", + "rect": {"x" : 64, "y" : 394, "w": 316, "h": 124}, + "color": [0, 0, 0, 0], + "colorLine": [64, 80, 128, 128] + }, + { + "type": "transparentFilledRectangle", + "rect": {"x" : 65, "y" : 416, "w": 314, "h": 1}, + "color": [0, 0, 0, 0], + "colorLine": [80, 96, 160, 128] + }, + { + "type": "transparentFilledRectangle", + "rect": {"x" : 65, "y" : 417, "w": 314, "h": 1}, + "color": [0, 0, 0, 0], + "colorLine": [32, 40, 128, 128] + }, + { + "type": "transparentFilledRectangle", + "rect": {"x" : 65, "y" : 466, "w": 314, "h": 1}, + "color": [0, 0, 0, 0], + "colorLine": [80, 96, 160, 128] + }, + { + "type": "transparentFilledRectangle", + "rect": {"x" : 65, "y" : 467, "w": 314, "h": 1}, + "color": [0, 0, 0, 0], + "colorLine": [32, 40, 128, 128] + }, + { + "type" : "verticalLayout66", + "customType" : "labelTitle", + "position": {"x": 70, "y": 134}, + "items": + [ + { + "text": "vcmi.optionsTab.chessFieldBase.hover" + }, + { + "text": "vcmi.optionsTab.chessFieldTurn.hover" + }, + { + "text": "vcmi.optionsTab.chessFieldBattle.hover" + }, + { + "text": "vcmi.optionsTab.chessFieldCreature.hover" + }, + { + "text": "vcmi.optionsTab.simturns" + } + ] + }, + + { + "type" : "verticalLayout66", + "customType" : "labelDescription", + "position": {"x": 70, "y": 155}, + "items": + [ + { + "text": "vcmi.optionsTab.chessFieldBase.help" + }, + { + "text": "vcmi.optionsTab.chessFieldTurn.help" + }, + { + "text": "vcmi.optionsTab.chessFieldBattle.help" + }, + { + "text": "vcmi.optionsTab.chessFieldCreature.help" + } + ] + }, + + { + "type" : "verticalLayout66", + "customType" : "timeInputBackground", + "position": {"x": 294, "y": 129}, + "items": + [ + {}, + {}, + {}, + {} + ] + }, + + { + "type" : "verticalLayout66", + "customType" : "timeInput", + "position": {"x": 294, "y": 129}, + "items": + [ + { + "name": "chessFieldBase", + "callback": "parseAndSetTimer_base", + "help": "vcmi.optionsTab.chessFieldBase.help" + }, + { + "name": "chessFieldTurn", + "callback": "parseAndSetTimer_turn", + "help": "vcmi.optionsTab.chessFieldTurn.help" + }, + { + "name": "chessFieldBattle", + "callback": "parseAndSetTimer_battle", + "help": "vcmi.optionsTab.chessFieldBattle.help" + }, + { + "name": "chessFieldCreature", + "callback": "parseAndSetTimer_creature", + "help": "vcmi.optionsTab.chessFieldCreature.help" + } + ] + }, + + { + "type": "label", + "font": "small", + "alignment": "left", + "color": "white", + "text": "vcmi.optionsTab.simturnsMin.hover", + "position": {"x": 70, "y": 420} + }, + { + "type": "label", + "font": "small", + "alignment": "left", + "color": "white", + "text": "vcmi.optionsTab.simturnsMax.hover", + "position": {"x": 70, "y": 470} + }, + + { + "name": "simturnsDurationMin", + "type": "slider", + "orientation": "horizontal", + "position": {"x": 178, "y": 420}, + "size": 200, + "callback": "setSimturnDurationMin", + "items": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 21, 28, 35, 42, 49, 56, 84, 112, 140, 168 ], + "selected": 0, + "style": "blue", + "scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, + "panningStep": 20 + }, + { + "name": "simturnsDurationMax", + "type": "slider", + "orientation": "horizontal", + "position": {"x": 178, "y": 470}, + "size": 200, + "callback": "setSimturnDurationMax", + "items": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 21, 28, 35, 42, 49, 56, 84, 112, 140, 168, 1000000 ], + "selected": 0, + "style": "blue", + "scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, + "panningStep": 20 + }, + { + "name": "labelSimturnsDurationValueMin", + "type": "label", + "font": "small", + "alignment": "center", + "color": "white", + "text": "", + "position": {"x": 278, "y": 428} + }, + { + "name": "labelSimturnsDurationValueMax", + "type": "label", + "font": "small", + "alignment": "center", + "color": "white", + "text": "", + "position": {"x": 278, "y": 478} + }, + { + "type" : "label", + "text": "vcmi.optionsTab.simturnsMin.help", + "type": "multiLineLabel", + "font": "tiny", + "alignment": "center", + "color": "white", + "rect": {"x": 70, "y": 430, "w": 300, "h": 40} + }, + { + "type" : "label", + "text": "vcmi.optionsTab.simturnsMax.help", + "type": "multiLineLabel", + "font": "tiny", + "alignment": "center", + "color": "white", + "rect": {"x": 70, "y": 480, "w": 300, "h": 40} + }, + { + "name": "buttonSimturnsAI", + "position": {"x": 70, "y": 535}, + "type": "toggleButton", + "image": "lobby/checkbox" + }, + { + "name": "labelSimturnsAI", + "type": "label", + "font": "small", + "alignment": "left", + "color": "yellow", + "text": "vcmi.optionsTab.simturnsAI.hover", + "position": {"x": 110, "y": 540} + } + ] +} diff --git a/lib/Languages.h b/lib/Languages.h index cfd47021d..2f7e41564 100644 --- a/lib/Languages.h +++ b/lib/Languages.h @@ -12,6 +12,17 @@ namespace Languages { +enum class EPluralForms +{ + NONE, + VI_1, // Single plural form, (Vietnamese) + EN_2, // Two forms, singular used for one only (English) + FR_2, // Two forms, singular used for zero and one (French) + UK_3, // Three forms, special cases for numbers ending in 1 and 2, 3, 4, except those ending in 1[1-4] (Ukrainian) + CZ_3, // Three forms, special cases for 1 and 2, 3, 4 (Czech) + PL_3, // Three forms, special case for one and some numbers ending in 2, 3, or 4 (Polish) +}; + enum class ELanguages { CZECH, @@ -57,6 +68,9 @@ struct Options /// primary IETF language tag std::string tagIETF; + /// Ruleset for plural forms in this language + EPluralForms pluralForms = EPluralForms::NONE; + /// VCMI supports translations into this language bool hasTranslation = false; }; @@ -65,27 +79,27 @@ inline const auto & getLanguageList() { static const std::array languages { { - { "czech", "Czech", "Čeština", "CP1250", "cs", true }, - { "chinese", "Chinese", "简体中文", "GBK", "zh", true }, // Note: actually Simplified Chinese - { "english", "English", "English", "CP1252", "en", true }, - { "finnish", "Finnish", "Suomi", "CP1252", "fi", true }, - { "french", "French", "Français", "CP1252", "fr", true }, - { "german", "German", "Deutsch", "CP1252", "de", true }, - { "hungarian", "Hungarian", "Magyar", "CP1250", "hu", true }, - { "italian", "Italian", "Italiano", "CP1250", "it", true }, - { "korean", "Korean", "한국어", "CP949", "ko", true }, - { "polish", "Polish", "Polski", "CP1250", "pl", true }, - { "portuguese", "Portuguese", "Português", "CP1252", "pt", true }, // Note: actually Brazilian Portuguese - { "russian", "Russian", "Русский", "CP1251", "ru", true }, - { "spanish", "Spanish", "Español", "CP1252", "es", true }, - { "swedish", "Swedish", "Svenska", "CP1252", "sv", true }, - { "turkish", "Turkish", "Türkçe", "CP1254", "tr", true }, - { "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", true }, - { "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", true }, // Fan translation uses special encoding + { "czech", "Czech", "Čeština", "CP1250", "cs", EPluralForms::CZ_3, true }, + { "chinese", "Chinese", "简体中文", "GBK", "zh", EPluralForms::VI_1, true }, // Note: actually Simplified Chinese + { "english", "English", "English", "CP1252", "en", EPluralForms::EN_2, true }, + { "finnish", "Finnish", "Suomi", "CP1252", "fi", EPluralForms::EN_2, true }, + { "french", "French", "Français", "CP1252", "fr", EPluralForms::FR_2, true }, + { "german", "German", "Deutsch", "CP1252", "de", EPluralForms::EN_2, true }, + { "hungarian", "Hungarian", "Magyar", "CP1250", "hu", EPluralForms::EN_2, true }, + { "italian", "Italian", "Italiano", "CP1250", "it", EPluralForms::EN_2, true }, + { "korean", "Korean", "한국어", "CP949", "ko", EPluralForms::VI_1, true }, + { "polish", "Polish", "Polski", "CP1250", "pl", EPluralForms::PL_3, true }, + { "portuguese", "Portuguese", "Português", "CP1252", "pt", EPluralForms::EN_2, true }, // Note: actually Brazilian Portuguese + { "russian", "Russian", "Русский", "CP1251", "ru", EPluralForms::UK_3, true }, + { "spanish", "Spanish", "Español", "CP1252", "es", EPluralForms::EN_2, true }, + { "swedish", "Swedish", "Svenska", "CP1252", "sv", EPluralForms::EN_2, true }, + { "turkish", "Turkish", "Türkçe", "CP1254", "tr", EPluralForms::EN_2, true }, + { "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", EPluralForms::UK_3, true }, + { "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", EPluralForms::VI_1, true }, // Fan translation uses special encoding - { "other_cp1250", "Other (East European)", "", "CP1250", "", false }, - { "other_cp1251", "Other (Cyrillic Script)", "", "CP1251", "", false }, - { "other_cp1252", "Other (West European)", "", "CP1252", "", false } + { "other_cp1250", "Other (East European)", "", "CP1250", "", EPluralForms::NONE, false }, + { "other_cp1251", "Other (Cyrillic Script)", "", "CP1251", "", EPluralForms::NONE, false }, + { "other_cp1252", "Other (West European)", "", "CP1252", "", EPluralForms::NONE, false } } }; static_assert(languages.size() == static_cast(ELanguages::COUNT), "Languages array is missing a value!"); @@ -109,4 +123,50 @@ inline const Options & getLanguageOptions(const std::string & language) return emptyValue; } +template +inline constexpr int getPluralFormIndex(EPluralForms form, Numeric value) +{ + // Based on https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html + switch(form) + { + case EPluralForms::NONE: + case EPluralForms::VI_1: + return 0; + case EPluralForms::EN_2: + if (value == 1) + return 1; + return 2; + case EPluralForms::FR_2: + if (value == 1 || value == 0) + return 1; + return 2; + case EPluralForms::UK_3: + if (value % 10 == 1 && value % 100 != 11) + return 1; + if (value%10>=2 && value%10<=4 && (value%100<10 || value%100>=20)) + return 2; + return 0; + case EPluralForms::CZ_3: + if (value == 1) + return 1; + if (value>=2 && value<=4) + return 2; + return 0; + case EPluralForms::PL_3: + if (value == 1) + return 1; + if (value%10>=2 && value%10<=4 && (value%100<10 || value%100>=20)) + return 2; + return 0; + } + throw std::runtime_error("Invalid plural form enumeration received!"); +} + +template +inline std::string getPluralFormTextID(std::string languageName, Numeric value, std::string textID) +{ + int formIndex = getPluralFormIndex(getLanguageOptions(languageName).pluralForms, value); + return textID + '.' + std::to_string(formIndex); +} + } diff --git a/lib/StartInfo.h b/lib/StartInfo.h index 925df407c..208ef2706 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -30,7 +30,7 @@ struct DLL_LINKAGE SimturnsInfo /// Maximum number of turns that might be played simultaneously unless contact is detected int optionalTurns = 0; /// If set to true, human and 1 AI can act at the same time - bool allowHumanWithAI = true; + bool allowHumanWithAI = false; template void serialize(Handler &h, const int version)